2024年3月17日发(作者:)
这里所指的文本编辑器不是简单的像Windows自带的单行或多行文本编辑框,而是类似于
Word的文本编辑器。粗看起来,一个编辑器有什么好难的,其实很难的,因为我们认为容
易的事对计算机来说确实天大的问题。比如大家经常上网,可以发现最近几年很多网站登录
时除了输入用户名和密码后还要输入所谓的验证码,而验证码则在输入框旁边歪歪扭扭的画
了出来,就像小学一年纪的学生在一张脏纸上写的一样,这样做只是为了防止程序来模拟登
录,因为歪歪扭扭的文字人类可以很容易的辨认,而计算机则很不容易辨认。
一个文本编辑器主要处理的问题有
文件保存格式的定义,文档保存为文本格式还是二进制格式的,文档中各个信息单元保
存什么信息。文档格式很重要。
和文档存储系统的交流,也就是保存和加载文档的功能,这里的文档存储系统可以是操作系
统文件子系统,数据库,网络,其实文件格式定下了,各种文档存储系统差别不大。
文档加载后的文档对象维护,面对比较复杂的文档处理,需要使用面向对象的编程思想,认
真分析文档结构,将加载的文档数据一点点肢解掉,每一个最小的不可分割的文档数据转换
为一个对象,然后使用一个对象树来保存文档内容的层次关系,这样构造一个文档对象树。
文档编辑工作就是维护这个文档对象树了。
文档对象的排版,文档加载后需要处理整个文档对象树,计算每个对象的显示大小,然后在
视图区中排列要显示的对象,包括段落和文档行的计算,然后计算对象在视图区域中的直角
坐标参数。
文档的绘制,这里的绘制包括在计算机屏幕上绘制文档内容和在打印机上绘制。程序根据计
算好的对象在视图区中的坐标,进行一些坐标转换,在图形输出对象上绘制对象,比如绘制
一个文字或图片。由于.NET框架中,操作屏幕和打印机都是基于GDI+的,两者没有本质
差别,因此一些处理的绘制代码可以绘制屏幕,也可以绘制打印机。在屏幕上绘制文档还特
别需要优化,尽量减少闪烁。
环境消息的处理,环境消息指一些Windows消息,这些消息应该改变文档内容,比如鼠标
键盘消息,系统粘贴板的相关消息。程序处理这些消息,修改文档对象树,向对象树插入删
除或修改文档元素对象。文档对象树发生改变后需要重新对文档进行排版,处理进行段落计
算和文档行计算,重新计算对象在视图区中的位置,然后根据需要刷新屏幕显示。此外还有
用户选择文档内容时也要处理。
文档的保存,程序根据文档对象树生成一些数据,然后保存到文档存储系统,这一步可以看
作对象序列化。
应用程序的开放性,提供二次开发的能力,提供类似VBA的功能
一个完整的功能不弱的文本编辑器结构是很复杂的,涉及到的问题非常广泛,没有数万
行的代码是搞不定的,这些问题在本文是不可能一一列出来并进行讨论,在此只好挑一些重
点来说说。
文档对象模型
在实际开发时不必挨个解决问题,我是首先确定文档对象树的结构,这里使用了文
档对象模型的概念,其实我们已经碰到很多种文档对象模型,最多的莫过于HTML文档对
象模型,我们用JavaScript来控制HTML页面内容时就是使用HTML文档对象模型,此外
还有XML文档对象模型,VBA操作的是Word或Excel文档对象模型。使用文档对象模型,
可将文档中所有的内容和内存中的某个对象联系起来,当应用程序修改了内存的对象的数
据,则相应的文档内容就修改了。删除了内存中的对象也就删除了相应的文档内容。一些文
档对象模型的思想可以参考。
文档对象模型中有很常见的是对象的继承和重载。大家可以看看.NET类库的
名称空间下定义的XML文档对象模型,你可以发现无论是XML文档对象
(XMLDocument),XML节点(XMLElement)还是属性(XMLAttribute),甚至注释(XMLComment)
纯文本数据(XMLText)都是从抽象类XMLNode继承过来的。这样设计的好处是可以很方便
的遍历XML文档对象树,各种对象都是从XMLNode派生的,都根据各自需要重载一些成
员方法,其他程序都可把这些对象都看作XMLNode来使用,利用对象方法的重载和多态性
来实现各自不同的处理。
基础对象
在这种指导思想下,我也定义了一个抽象类TextElement,所有的文档对象都是从该对
象派生的。该类定义了以下虚成员
Left,Top,Width,Height属性,用于表示对象在的位置和显示大小
RealLeft , RealTop 只读属性,表示对象在视图区域中的显示位置
RefreshSize 方法,用于重新计算对象的显示大小
RefreshView 方法,重新绘制对象
HandleMouseDown 方法,处理鼠标按键按下事件
HandleMouseMove 方法,处理鼠标移动事件
HandleMouseUp 方法,处理鼠标按键松开事件
FromXML 方法,从一个XML节点加载对象数据
ToXML 方法,向一个XML节点保存对象的所有的数据
由于文档内容是分层次的,因此还定义一个容器类型TextContainer,该类型从TextElement
派生的,其中进行扩展来可以保存若干个子对象,它定义了以下虚成员
MaxWidth 属性,对象内容的最大宽度,一个文档显示宽度就是纸张宽度减去左右页边距
的距离,文档所有的内容被限制在这个显示宽度中间,该属性和显示宽度有关
ChildElements 只读属性,返回所有子对象的集合,返回类型为ist
AppendChild 方法,该方法参数为一个TextElement对象,本方法将该对象添加到子对象集
合中
RemoveChild 方法,该方法参数为一个TextElement对象,本方法从子对象集合中删除指定
的文档元素对象
RemoveChildRange 方法,该方法和RemoveChild类似,只是用于删除一批子对象
InsertBefore 方法,该方法参数为两个TextElement对象,第一个参数为要新增的文档元素
对象,第二个为插入点所在的文档元素对象
InsertRangeBefore 方法,该方法和InsertBefore类型,只是用于插入一批文档元素对象
在某些容器对象中存在一个特殊的子元素,该子元素为最后一个元素,并且不能删除,比
如对于段落对象,在此是一种容器对象,该对象最后一个元素为一个段落结尾标记对象,该
对象不能删除,而在其他类型的容器对象中也可能存在类似的结尾对象,因此在
TextContainer对象中就考虑这种情况,因此定义了一套虚成员来处理
AddLastElement 虚方法,想容器对象添加段落结尾标记对象来作为最后一个对象,其
他派生的容器对象可以重载该方法来实现自己的最后对象
IsLastElement 函数,该函数参数为一个TextElement对象,本函数返回指定的TextElement
对象是否是最后对象,程序在删除子元素前都有调用该函数,若要删除的元素为最后元素则
不应当删除
TextContainer对象还重载RefreshSize方法来重新计算所有子元素的显示大小,此外还定义
了新的虚方法RefreshLine来进行分行处理,为了方便分行处理,还定义了文档行对象
TextLine,文档行对象用于保存文档内容分行信息,当文档分行完毕而内容没有发生改变时重
新绘制文档内容时就无需重新计算要显示的内容的坐标,文档行对象的成员有


发布评论