一种文本编辑器和控制台实现方案

一种文本编辑器和控制台实现方案
by Que's C++ Studio 阙荣文 20210602

源码请移步 github (https://github.com/TedQue/MyConsole)

0. 需求
在所有 Windows 标准控件中, Edit 大概是最复杂者之一.试想一下,实现 Edit 至少需要考虑以下问题:
    选择字体绘制字符
    响应键盘输入
    响应鼠标动作,准确选中指定字符
    访问系统剪贴板,支持热键 Ctrl-C, Ctrl-V 等
    计算长度宽度以正确设置滚动范围
    ...
这些还仅仅是一个标准 Edit 控件的最基本功能,其中的大量细节已经有点让人望而生畏了,然而 Windows 标准控件库中还有一个名为 RichEdit 的控件,
用于插入"富格式"文本,比如带颜色的文字,图片乃至数据库对象等等.Edit控件用于 Windows GUI app 简单输入是完全够用的,RichEdit 则有些不上不下,
一般需求用不上(使用 Edit 控件足矣),复杂需求(比如,即时通讯软件的输入框,需要插入表情,图片等)又不够用(其基于 OLE 对象的接口过时且难以使用).
此外,开发 Windows GUI app 常需要在某个窗口中滚动输出大量文本,这是一个很常见的需求,比如日志输出.某些时候我甚至想要是能把系统控制台窗口直接嵌入,也的确做过类似尝试,
利用 Windows 控制台 AllocConsole(), GetConsoleWindow() 等接口并非不能做到,然终有拼凑之嫌,不够优雅.

以上就是我的3个需求:
    简单文本编辑
    富格式文本编辑
    控制台文本输入输出
我把新控件老套的命名为 MyEdit 和 MyConsole.

1. 设计思路
<<设计模式>>第二章中以一个名为"Lexi"文本编辑器作为示例演示多种模式之用,其设计思路与 MyEdit 有很多相似之处.事实上我是在 MyEdit 完成设计后的编码过程中读的<<设计模式>>
算是不谋而合,后续也受其影响,只是抽象程度不如 Lexi 简洁.

想象我们有一张很大很大的图像,先把整张图像划分为若干行,再把每行划分为若干个单元格,就像小学生使用的作文纸那样.每个单元格内画什么,如何画由单元格自身决定,可以是一个字符也可以
是一个图像.这张图像可能很大,拥有很多很多行列,远远超过屏幕尺寸,所以我们用一个尺寸比屏幕小的窗口盖在图像上,每次只显示被窗口框定的部分,通过移动窗口逐步展示整个图像,
这很好理解,因为它就是 Windows 的本意.我们把这个想象中的图像称为 MyVirtualImage, 单元格称为 MyCell.

我们在内存中维护一个 MyVirtualImage 对象,用一个二维数组记录组成行列的所有 MyCell 对象.绘制图形时, MyVirtualImage 先调用 MyCell::width()/height() 得到单元格对象 MyCell 的大小,
再根据其在行列中的位置计算出属于该单元格的绘制区域并调用其自绘接口:
void MyCell::draw(HDC hDc, const RECT* rc, bool selected).MyCell 对象可以在该区域内绘制任意图形从而使 MyEdit 具备插入富格式文本,图片,以及其他各式各样的自定义单元格的能力.
当然,并不需要把全部 MyCell 单元格都画出来,只要绘制窗口内那部分"可见" MyCell 即可,就像用记事本打开一个很大的文本文件时,每次只需显示窗口能容纳的那几页字符.

把 MyCell 对象插入 MyVirtualImage 对象相对简单,但调用者无法预期何时回收该 MyCell 对象,因为这取决于用户的鼠标键盘动作,也许一直保持,也许在用户按下 DEL, BACKSPACE, Ctrl-X
等键后删除,甚至是在达到 MyVirtualImage 设定的最大 MyCell 容量时被自动删除.为了解决回收难题, MyVirtualImage 在需要删除 MyCell 对象时调用 MyCell::remove() 接口,并保证
之后不再访问该 MyCell 对象指针. MyCell 派生类可以在 remove() 中实现自己的删除逻辑.

MyVirtualImage 插入 MyCell 接口原型为 int MyVirtualImage::insert(int pos, MyCell** cells, int len),大多数时候 MyEdit/MyConsole 都在处理 MyCell 的字符
派生类 MyCellCharacter, 如果每插入一个字符都 new 一个新的 MyCellCharacter 对象效率较低, MyCellCharacterFactory 实现了 MyCellCharacter 对象池,以"块"方式管理多个 MyCellCharacter 对象,
并跟踪该块的引用计数,重复使用从而提高效率.

上文中为了便于理解,说是用一个二维数组管理所有 MyCell 对象,实际上 MyVirtualImage 使用一维数组保存所有 MyCell 对象指针和一个额外的行索引(也是一个一维数组)记录每一行的起始 MyCell 对象在前述一维数组
中的序号.相较而言,二维数组简单易懂,行内添加删除也很高效,但是在处理大量不同长度的行时比较头疼,而行索引方案则可以很高效的处理这种情况.最终我采用了行索引方案,花费较多精力实现高效的行索引更新函数:
int MyVirtualImage::updateRowIndex(int pos, int len),细节可参考源码中该函数的注释部分.
行索引还需要跟踪每一行的宽度和高度, MyVirtualImage 统计所有行高度之和以及最大行宽度得到虚拟图像的尺寸, MyEdit 需要这个信息设置滚动条参数.

2. MyEdit
现在,我们已经有了一张想象中的虚拟图像,我们知道它的尺寸,知道每一个单元格的行列序号,每个单元格都可以正确绘制它想绘制的任意图形,还有添加修改删除单元格对象的接口.
还需要什么?还需要把虚拟图像中的某个部分(窗口)显示在某个 Windows HWND 窗口上,响应用户鼠标键盘动作操作单元格对象(大多数时候是字符单元格对象),设置正确的滚动条参数使窗口可以在虚拟图像上移动...等等
这些琐碎的用户接口相关事务正是 MyEdit 需要完成的工作.

Windows 窗口行为的关键在于其关联的消息处理函数,调用 int MyEdit::attach(HWND hwnd, int m) 把目标窗口过程函数替换为 MyEdit::wndProc() 即可把该窗口子类化为 MyEdit,之后就是在该窗口过程
函数中响应消息,其中并没有什么难点却需要十足的耐心处理各种细节.值得一提的是关于输入法预输入字符的显示与编辑,主要是响应 WM_IME_XXX 相关消息,较为少见,详情请直接阅读源码.因为这个缘故,需要在
工程中加入 Imm32.lib 库支持.

标准的 Windows 控件除了提供函数操作接口外都支持通过 Windows 消息操控,比如调用 GetWindowText() 和发送 WM_GETTEXT 消息都可以获取 Edit 控件的内容. MyEdit 也遵循这个惯例,在 MyConsole.h
中定义了一组支持的 Windows 自定义消息.

3. MyConsole
MyConsole 与 MyEdit 本质上是一样的,只是 MyConsole 需要在输入或输出两种模式间切换,并且检测到用户输入结束(按下回车键)时通知接口调用者.
所以,先用私有继承把 MyEdit 的实现包含进来,之后再额外实现模式切换和事件通知机制(简单发送 Windows 消息至父窗口)即可.

4. 添加到您的项目
MyEdit/MyConsole 直接用 Windows SDK 实现,并不需要 MFC 库支持,使用时包含以下 4 个文件: MyCell.h MyCell.cpp MyConsole.h MyConsole.cpp
如果需求更复杂的单元格功能,比如插入图片,请参考 MyCellCharacter 实现 MyCell 接口.

5. 可能的应用场景和还需要完善的地方
即时通讯软件聊天输入框和显示框;实现自己的文本编辑器;带颜色的日志输出窗口.
没有实现 undo/redo 树;没有实现拖拽功能;没有经过压力测试,性能可能比标准的 Windows 记事本低很多;只实现了最简单的字符单元格接口,要支持完整的富文本格式还缺大量工作.
总而言之,相当简陋,远远不能称之为"产品",并且本项目构建于 2015 年,彼时浮躁,诸多细节不甚考究,如今再读颇感不堪,想要重写一时又无从着手,权当引玉之砖,不足之处望各位同学海涵.

[后记]
因写此文,我用 Visual Studio 2019 社区版重新编译了项目,为了避免不必要的麻烦,也请您使用这个版本.
我个人由于工作需要,目前已转至 Linux 平台.一向穷冗,现整理两个 Windows 平台项目与各位同学交流学习.感叹今日 Windows 桌面应用程序开发之日渐式微,做这两个项目也已经是好几年前事了.
后续还有一篇关于 IOCP 与 OpenSSL 整合的文章,之后将告别 Windows 平台.

-------------------------------------------------------------------------------------------------------------------------
附1: 本项目运行效果
screenshot1.png

-------------------------------------------------------------------------------------------------------------------------
附2: MyConsole 用作日志输出窗口时的运行效果(不同日志级别输出为不同颜色)
screenshot2.png

-------------------------------------------------------------------------------------------------------------------------
附3: 开发日志,记录过程中的所思所想

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用C语言开发文本编辑器是一种常见的做法。C语言是一种功能强大的编程语言,适合编写高性能和高效率的程序。以下是一些我会采取的方法和步骤: 首先,我会创建一个能够接收用户输入和显示编辑内容的界面。这可以通过使用C语言中的标准输入输出函数来实现。用户可以使用键盘输入文本,并在屏幕上查看编辑的结果。 其次,我会设计数据结构来存储文本编辑器的内容。一种常见的数据结构是使用链表表示每一行的文本。这样可以方便地插入、删除和修改文本。我还会为每个字符分配内存,并使用指针表示字符之间的连接。 然后,我会实现一些基本的编辑功能,比如插入、删除、复制和粘贴。这可以通过在链表上进行相应的操作来实现。插入和删除字符可能需要涉及指针的重新连接,而复制和粘贴操作可以使用临时缓冲区来实现。 接下来,我会添加一些高级编辑功能,比如查找和替换。这可以通过使用C语言中的字符串处理函数来实现。我还可以添加一些快捷键或菜单来方便用户进行这些操作。 最后,我会为文本编辑器添加文件操作功能,包括打开、保存和另存为选项。这可以通过使用C语言中的文件操作函数来实现。用户可以选择打开一个已有的文本文件,将编辑的内容保存到文件中,或者将编辑的内容复制到另一个文件中。 总之,使用C语言开发文本编辑器需要设计合适的数据结构和算法,并利用C语言中的各种函数和特性来实现所需的功能。这样可以创建一个高效和可靠的文本编辑器,满足用户的编辑需求。 ### 回答2: 使用C语言开发文本编辑器是一项相对较为复杂的任务,但也是具有挑战性和有趣的工作。在使用C语言进行文本编辑器开发时,我们可以利用C的优势来处理字符串和文件操作。 首先,我们需要定义一个数据结构,该数据结构可以存储文本内容。可以使用字符数组或链表等数据结构来存储文本。这些数据结构可以有效地处理文本的插入、删除和修改等操作。 其次,我们需要实现文本编辑器的核心功能,如打开文件、保存文件、复制、粘贴和查找等操作。我们可以使用C的文件操作来打开和保存文件,并利用字符串处理函数来实现复制、粘贴和查找等功能。例如,可以使用strcpy函数实现复制功能,使用strcat函数实现粘贴功能,使用strstr函数实现查找功能。 此外,在开发文本编辑器时,还可以实现一些其他功能,如修改字体、颜色和大小等。可以使用C的图形库或者调用操作系统的API函数来实现这些功能。 最后,为了方便用户使用文本编辑器,我们可以设计一个用户界面。可以使用C的图形库或者使用控制台实现用户界面。用户界面应该包括菜单、工具栏和编辑区域等元素,用户可以通过这些元素进行文件操作和文本修改。 综上所述,使用C语言开发文本编辑器需要使用C的字符串处理和文件操作等功能。通过合理设计数据结构和用户界面,可以实现一个功能完善的文本编辑器。这个过程可能比较复杂,但是通过仔细的规划和编码,我们可以成功地完成这个任务。 ### 回答3: 文本编辑器是一种用于创建、编辑和管理文本文件的软件工具。使用C语言开发文本编辑器可以获得更高的程序效率和更灵活的控制。以下是用C语言开发文本编辑器的一般步骤和原理。 首先,我们可以使用C语言中的文件操作函数来读取和写入文本文件。可以使用fopen函数打开一个文件,在该文件中进行读写操作。然后,可以使用fgets函数逐行读取文件内容,或使用fscanf函数按照指定的格式从文件中读取内容。 其次,我们可以使用C语言中的字符串处理函数来实现文本编辑器的基本功能。我们可以使用strcpy函数来复制字符串,使用strcat函数来连接字符串,使用strcmp函数来比较字符串,以及使用strlen函数来获取字符串的长度。这些函数可以帮助我们实现插入、删除、查找、替换等操作。 此外,我们可以使用C语言中的动态内存分配函数来实现文本编辑器的灵活性。可以使用malloc函数动态分配内存,使用free函数释放内存。这样,我们可以根据实际需求动态分配和释放内存,以便处理任意大小的文本文件。 最后,为了提高用户体验,可以使用C语言中的图形界面库,如GTK+或SDL,来实现文本编辑器的可视化界面。这样,用户可以通过鼠标和键盘进行操作,更方便地编辑和管理文本文件。 总结来说,使用C语言开发文本编辑器可以通过文件操作函数、字符串处理函数、动态内存分配函数和图形界面库来实现各种功能和提高程序效率。这是一种高效、灵活且可扩展的方法,适用于开发各种规模的文本编辑器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值