科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件如何用C#编写文本编辑器(3)

如何用C#编写文本编辑器(3)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

其实关于分行操作应当还有更优化的方法,但本人能力有限,只能提出这种方法。试验证明,在处理小的文档时程序运行速度还行,但当文档内容很多,有数万个字符时,分行速度就很慢,还望高手提供解决之道。

来源:soft6 2008年5月16日

关键字: 编辑器 文本 C# Windows

  • 评论
  • 分享微博
  • 分享邮件

其实关于分行操作应当还有更优化的方法,但本人能力有限,只能提出这种方法。试验证明,在处理小的文档时程序运行速度还行,但当文档内容很多,有数万个字符时,分行速度就很慢,还望高手提供解决之道。

为了表示整个文档对象,还定义了文档对象TextDocument ,该对象在文档对象模型中是个最大的对象,我没有模仿其他文档对象的模式将其从TextElement派生过来的,而是直接定义的。该对象用于从整体上操作文档,并列出了一些操作文档的基本操作,比如删除,复制粘贴等。此外还提供一套方法来实现VBA的功能。

此外还定义了文档内容管理对象Content ,该对象隶属于TextDocument对象,用于管理所有的文档元素,它定义了属性Elements,该属性为一个保存了文档所有元素对象的列表。该对象还定义了属性SelectStart来表示插入点的位置,SelectLength 来表示选择区域的长度,为0表示没有选中任何元素,为正数则表示从插入点向后选中了若干个元素,为负数则表示从插入点向前选中了若干个元素。本对象还定义了一套处理插入点的函数,比如向左向右移动若干个元素,向上向下移动一行。大家都知道,在文本框中可以直接用光标键来移动插入点,也可以使用光标键时同时按下Shift键来移动插入点并选择文档内容,用户也可以用鼠标点击操作来移动插入点,鼠标点击的同时按下Shift键也能移动插入点选择文档内容;为此在Content对象定义了属性AutoClearSelection,当设置了该属性则移动插入点时设置SelectLength为0,若没有设置该属性则移动插入点时设置SelectLength值,使得新插入点和旧插入点之间的元素被选中,这样文本编辑器根据用户是否按下Shift键来设置AutoClearSelection属性就行了。用户修改了插入点和选择区域,则文本编辑器需要重新绘制用户界面,此时需要优化,只重新绘制选择状态发生改变的元素。可以证明,当选择的元素为连续的,则无论如何的修改选择区域和插入点,最多只有两片区域中的元素的选择状态发生改变。因此只要获得这两片区域的起始位置和长度,然后重新绘制这两个区域中的元素即可。

用户可以对文档进行很多种操作,比如移动插入点,选择元素,设置字符的字体颜色和大小,插入文字和图片,修改元素的设置,删除剪切复制粘贴等等,有好几十种操作,而且这些操作在某个时刻是不可用的,需要进行判断,若这些操作都在TextDocument中定义相应的接口函数,则TextDocument类代码太多,过于臃肿,而且每新增一种操作都需要修改TextDocument,因此在此提出动作这个概念。动作就是一个实现某种文档操作的类型,该类型有统一的接口,并使用TextDocument或其他对象提供的基本的操作来实现比较复杂的操作。为此定义动作基础类EditorAction,该类为抽象类,它的主要接口有:

◆HotKey 字段,动作对应的热键代码,动作对象初始化的时候设置该动作对应的热键

◆KeyCode 字段,触发动作时的键盘按键编码

◆ShiftKey 字段,触发动作时的Shift键状态

◆ControlKey 字段,触发动作时的Control键状态

◆AltKey 字段,触发动作时的Alt键状态

◆MouseX,MouseY 字段,触发动作时的鼠标光标在视图区域中的坐标

◆MouseButton 字段,触发动作时的鼠标按键状态

◆Param1,Param2,Param3 字段,动作的参数,其意义由具体的动作决定

◆TestHotKey 函数测试键盘热键,本函数由文本编辑器调用来判断是否触发某动作

◆ActionName 只读属性,动作名称

◆isEnable 动作是否可用

◆Execute 执行动作

◆OwnerDocument 动作对象所操作的文档对象

各种实际的动作对象都是从EditorAction派生的,若对象有热键则在初始化时设置HotKey字段,首先重载ActionName给定一个名称,然后重载Execute来实现各自的动作处理过程,还可根据需要重载isEnable或TestHotKey。

在TextDocument中有个属性Actions,该只读属性为包含各种动作对象的列表,当TextDocument初始化时就初始化该动作对象列表,当文本编辑器获得输入焦点时按下键盘按键则程序会遍历Actions中所有的动作,进行热键判断,若命中热键则执行该动作,其他应用程序也可根据各个动作的isEnable属性来设置文本编辑功能按钮和相应菜单的可用性。

比如定义复制动作对象EditorCopyAction,该类型从EditorAction派生的,重载ActionName使其返回"copy";重载isEnable,当文档有被选中的部分则返回True否则返回False,重载Execute来调用TextDocument中实现复制功能的函数,该对象初始化的时候设置HotKey为 System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C,这样定义了该动作的热键为Ctl+C。

这种动作处理的模式还便于程序进行扩展,其他应用程序也可往动作列表中添加自定义的动作对象,这样文本编辑器就能自动应用该动作。应用程序还可修改各种动作的热键设置来实现用户操作的个性化。

其实这种动作处理的模式我是看了SharpDevelop的文本编辑器部分的源代码而领悟的,拿过来用用,实践证明还是很不错的。

我既然做的是文本编辑器当然支持复制粘贴功能了,首先将将复制操作。程序可以同时向Windows剪贴板发送多种格式的数据,这些数据可以是纯文本的,也可以是图象或者自定义格式,其他程序在进行粘贴操作是可以选择其中所需格式的数据。例如大家在VS.NET的代码窗体中复制某段代码,粘贴到Word和记事本中的结果是不一致的,虽然文本内容是一样的,但粘贴到Word中连代码文本的颜色也显示出来的,而记事本则是纯文本数据。大家可以用剪贴板查看器clipbrd.exe来实时查看Windows剪贴板中的内容。在.NET中向剪贴板发送数据还是比较方便的,首先实例化一个System.Windows.Forms.DataObject对象,调用它的SetData方法,该方法第一个参数为格式的名称,第二个参数为数据,可以多次调用该方法来保存不同格式的数据,然后调用静态库函数System.Windows.Forms.Clipboard.SetDataObject 方法即可。在这个文本编辑器中复制数据时同时向系统剪切板保存两种数据,首先保存文档中被选中部分的纯文本数据,然后将被选中的部分转换为一个XML字符串,然后使用自定义的格式名称保存进去。这样其他程序就能使用其中的纯文本数据了。程序在进行粘贴操作时首先调用静态库函数System.Windows.Forms.Clipboard.GetDataObject方法,获得一个 实现了System.Windows.Forms.IDataObject接口的对象,然后调用它的GetDataPresent方法,若发现其中有我自定义的数据则读取该数据,然后将其中的数据当作字符串取出来,这是一个XML字符串,解析该XML字符串,并生成一系列的文档元素对象插入到文档当前位置,这种粘贴操作能将所有的文档元素及其格式给粘贴过来。若没有自定义数据但是有纯文本数据,则读取纯文本数据,并根据文本生成一系列文本元素对象,然后插入到文档当前位置。

VBA

文档对象还支持VBA,.NET框架支持VB.NET脚本语言,.NET类库中的类 Microsoft.VisualBasic.Vsa.VsaEngine及接口 Microsoft.Vsa.IVsaSite 就支持脚本语言。我参照HTML文档对象模型,在VB.NET的基础上设计一种处理文档的脚本语言,该语言中直接使用脚本全局对象document就访问了文档对象TextDocument,而使用document.all就能访问文档中的某些做了标记的文档元素对象,使用 dbconnection 就能使用文本编辑器后台使用的数据库连接对象,使用eventobj访问文档编辑器触发的事件的信息,使用vbsystem来调用某些例程。首先定义一些类型,用于实现脚本全局对象dbconnection,eventobj,vbsystem的功能,而全局对象document的类型就是TextDocument,已经实现,但document.all还未实现,为此在TextDocument中新增只读属性all,该属性返回一个System.Object类型的对象,由于document.all的类型中定义的字段根据文档的内容而动态改变,因此需要使用.NET的反射机制动态的创建对象类型并实例化对象,其创建过程为:

◆新增一个System.Reflection.AssemblyName对象,设置其Name属性为“RunTimeTextDocumentLib”

◆使用AppDomain.CurrentDomain.DefineDynamicAssembly来创建一个程序集生成器System.Reflection.Emit.AssemblyBuilder

◆使用程序集生成器的DefineDynamicModule来创建一个模块生成器

◆使用模块生成器的DefineType来创建一个类型生成器,类型名称为AllElements

◆遍历文档内容,根据名称和特定文档对象的对应关系生成一个按名称访问的哈希列表

◆遍历哈希列表中的名称,使用类型生成器的DefineField方法创建一个公开字段,字段类型为object类型

◆使用类型生成器生成一个新的类型System.Type,然后动态创建一个该类型的实例,这样动态生成了AllElements对象

◆遍历文档元素对象哈希列表,使用System.Type.InvokeMember向该AllElements对象设置字段值

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章