开发文本编辑器学习笔记
- 原教程:《Design and Implementation of a Win32 Text Editor 》
- 作者:james
第一部分 Introduction
虽然教程的标题是说设计并实现一个文本编辑器,但实际上我们主要是讲如何开发作为一个文本编辑器前端的编辑控件(edit control),当然我们也要写一个小的完整程序来测试这个自定义的控件,而这个完整程序的功能不会超过Windows自带的NotePad。
自定义编辑控件
SDK已经提供一些编辑控件,像edit 和 rich-edit controls,但这编辑控件功能都有限,开发自定义控件目的都是要实现自己想的功能的控件。 无论开发什么样的软件或软件组件(像现在的自定义控件),第一步必须明确软件要实现什么样的功能,这里强调清晰和明确的功能,最好用文字写下来,因为初始设计对最终软件的实现有着重大的影响。本教程的自定义编辑控件有如下的功能:
- 第一,对文件没有大小限制,并且在对大文件进行行式编辑时对性能影响很小甚至没有影响;
- 第二,快速和流畅的(图形)显示。包括文本匡选和编辑屏的滚动(scrolling)时的快速和流畅;
- 第三,语法着色;
- 第四,剪贴板和拖放操作的支持;
- 第五,单一字体;
- 第六,撤消和重做的支持;
- 第七,对ASCII, Unicode 和 UTF-8 的兼容。
NeatPad的设计与组成
NeatPad由四个组件组成,两个“可见的"和两个"不可见的"。可见的是主窗口(main window)和编辑控件(TextView );不可见是文本文档对象(TextDocument)和文本文件(text file)。
- 主窗口(main window):主窗口是一个顶层窗口,由标题栏(title)、菜单(menu )和状态栏(status bars)组成,它主要用来面对用户,不直接对文本文件操作;
- 编 辑控件(TextView ):编辑控件是一个单独的窗口(separate window),并且作为主窗口的子窗口,没有标题栏(title)、菜单(menu )和状态栏(status bars),但有滚动条(scrollbars)。主要功能是显示和处理对文本的编辑,包括处理键盘和鼠标的输入、拖入操作等。
- 文本文档对象(TextDocument):文档对象只一个数据源,没有可视部分,所以没有窗口的一切属性(没有标题,不接受用户输入),主功能是面对编辑控件,为其提供所需数据(数据怎么组织的?又怎么样提供?)。
- 文本文件(text file)
各组件间的接口
组件设计体现组件间的“接口(interfaces)”设计。TextView 是面向行(line-oriented)的图形化实体,它的功能只要是显示被更新的数据;而这个操作必须和TextDocument组件交互,从 TextDocument组件读取以“行”为单位的数据。 另一个接口就是TextDocument 和磁盘文件之的接口。与使用一行一行的策略不同,TextDocument要么把整个磁盘文件载入内存,要么再分成可管理的数据块(manageable chunks)。读入的文件的大小的决策可以因情况而定。
TextView 的公共接口——消息发送
第二部分 文本载入与显示
第二部分的目标是把文本文件载入到内存,并且显示到TextView 控件;本部分只完成基本的显示功能,TextView 没有滚动、键盘和鼠标支持。本阶段的代码策略也是很直显的,功能实现没经优化,以易用为原则。 文本文件与文本文档对象(TextDocument )
- 文本文件 除了内容有一些约定(convention)外,与其它二进制文件没什么两样。约定包括不能有不可打印字符(如控制字符)和文本以分隔符(如回车换行符)为界划分成多行。
- 文本文档对象 (TextDocument )则定义成一个C++类的实例。
[sourcecode language='cpp'] class TextDocument { public: bool init(char *filename); ULONG getline(ULONG lineno, char *buf, size_t len); ULONG linecount(); private: bool init_linebuffer(); char *buffer; int length; }; [/sourcecode]
TextDocument的文件载入功能
类接口 | 使用了WIN32 API | 辅助函数 | 功能 |
TextDocument::init(char *filename) | CreateFile | init(HANDLE hFile) | 只打开文件,不载入 |
TextDocument::init(HANDLE hFile) | GetFileSize、ReadFile、CloseHandle | init_linebuffer() | 载入整个文件到内存 |
init_linebuffer() | - | - | 根据文件内容创建行数据缓存(行数组) |
TextView 的显示功能对TextDocument 的需求
第一,TextView 控件必须要知道文本的行数,因为TextView 要根据文档的长度来设定滚动条; 第二,TextView必须能够知道何为一行,因为TextView 是以行为单位绘制文本的。
类接口 | 使用了API函数 | 辅助函数 | 功能 |
TextDocument::init_linebuffer() | - | - | 根据文件内容创建行数据缓存(行数组) |
TextDocument::getline(ULONG lineno, char *buf, size_t len) | - | memcpy |