让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示
让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示
如果你已经阅读了
简介
,相信你已经对我们接下来要做的事情有所了解。本文,将会把
简介
中基础程序修改为一个窗体应用程序。并对编辑器接下来的编辑计划进行说明。
1. 程序改造
阅读过曾经我认为C语言就是个弟弟
这篇文章的读者应该知道,编辑器(包括所有Win32
应用程序控件),本质上都是一个窗口(WNDCLASSA
(已被WNDCLASSEX
取代)结构体描述)。
在本节,我们将对上一篇文章所建立的项目进行改造,使其弹出一个主窗体,并附加一个编辑器窗体。
- 设置项目子系统
在之前,我们为了简便,没有修改 vicapp
项目的子系统,其默认值为控制台应用程序,所以我们可以用如下代码调用 vitality-controls
给出的函数 vic_prints
。
#include "../../shared-include/vitality-controls.h"
int main(int argc, char** argv) {
vic_prints("hello vic.");
return 0;
}
但是,对于一个编辑器来说,应该是一个窗体应用程序。所以,我们要对 vicapp
进行子系统设置,打开 vicapp
项目属性(参考上一篇文章),最终设置如下:
- 修改主程序代码
修改之系统为窗口
后,编译程序,会发现如下错误:
这是因为,链接程序会根据项目设置,去查找不同的主函数名称,而对于窗体
应用程序,其主函数名应为WinMain
,所以这里会报找不到符号 WinMain
,因为我们没有定义它。
对于不同项目类型的启动函数定义,参考文件VS安装目录\VC\Tools\MSVC\14.31.31103\crt\src\vcruntime\exe_common.inl
, 现在将相关代码列出如下:
#if defined _SCRT_STARTUP_MAIN
using main_policy = __scrt_main_policy;
using file_policy = __scrt_file_policy;
using argv_policy = __scrt_narrow_argv_policy;
using environment_policy = __scrt_narrow_environment_policy;
static int __cdecl invoke_main()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
#elif defined _SCRT_STARTUP_WMAIN
using main_policy = __scrt_main_policy;
using file_policy = __scrt_file_policy;
using argv_policy = __scrt_wide_argv_policy;
using environment_policy = __scrt_wide_environment_policy;
static int __cdecl invoke_main()
{
return wmain(__argc, __wargv, _get_initial_wide_environment());
}
#elif defined _SCRT_STARTUP_WINMAIN
using main_policy = __scrt_winmain_policy;
using file_policy = __scrt_file_policy;
using argv_policy = __scrt_narrow_argv_policy;
using environment_policy = __scrt_narrow_environment_policy;
static int __cdecl invoke_main()
{
return WinMain(
reinterpret_cast<HINSTANCE>(&__ImageBase),
nullptr,
_get_narrow_winmain_command_line(),
__scrt_get_show_window_mode());
}
#elif defined _SCRT_STARTUP_WWINMAIN
using main_policy = __scrt_winmain_policy;
using file_policy = __scrt_file_policy;
using argv_policy = __scrt_wide_argv_policy;
using environment_policy = __scrt_wide_environment_policy;
static int __cdecl invoke_main()
{
return wWinMain(
reinterpret_cast<HINSTANCE>(&__ImageBase),
nullptr,
_get_wide_winmain_command_line(),
__scrt_get_show_window_mode());
}
#elif defined _SCRT_STARTUP_ENCLAVE || defined _SCRT_STARTUP_WENCLAVE
using main_policy = __scrt_enclavemain_policy;
using file_policy = __scrt_nofile_policy;
using argv_policy = __scrt_no_argv_policy;
using environment_policy = __scrt_no_environment_policy;
#if defined _SCRT_STARTUP_ENCLAVE
static int __cdecl invoke_main()
{
return main(0, nullptr, nullptr);
}
#else
static int __cdecl invoke_main()
{
return wmain(0, nullptr, nullptr);
}
#endif
#endif
可以看到,根据不同的宏定义,函数 invoke_main()
函数的定义也不相同,由于我们的编辑器应该支持Unicode
字符,并且我们是一个窗体应用程序。所以,我们主函数应该参考 _SCRT_STARTUP_WWINMAIN
宏定义内的主函数定义。
除了在 exe_common.inl
中定义了主函数的调用函数,另外,窗体应用程序的主函数还在 WinBase.h
(该文件可以通过 Windows.h
查找到 #include "WinBase.h"
一行,然后打开,或者可以直接引用) 文件中做了定义,如下:
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
int
#if !defined(_MAC)
#if defined(_M_CEE_PURE)
__clrcall
#else
WINAPI
#endif
#else
CALLBACK
#endif
WinMain (
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
);
int
#if defined(_M_CEE_PURE)
__clrcall
#else
WINAPI
#endif
wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
);
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
根据之前的描述,我们把之前的 vitality-controls.h
修改为如下代码:
#pragma once
#ifdef VITALITY_CONTROLS_EXPORTS
#define VIC_API __declspec(dllexport)
#else
#define VIC_API __declspec(dllimport)
#endif // VITALITY_CONTROLS_EXPORTS
#include <Windows.h>
/**
* 函数描述:
* 初始化编辑器环境,需要在调用任何本程序集的函数之前,
* 调用本函数。
*
* 返回值:
* 如果初始化成功,返回 TRUE,否则返回 FALSE,并设置错误码,
* 错误码可以通过 GetLastError() 获取。
*/
VIC_API BOOL Vic_Init();
/**
* 函数描述:
* 创建并初始化一个编辑器。
*
* 参数:
* parent: 新创建的编辑器的父窗体。
*
* 返回值:
* 如果创建控件成功,返回该控件的句柄,否则返回 -1 并设置错误码。
* 错误码可以通过 GetLastError() 获取。
*/
VIC_API HWND Vic_CreateEditor(
HWND parent
);
首先,我们将 stdio.h
的引用,换成了 Windows.h
,这允许我们使用 Windows
关于桌面应用程序的 API
。
其次,我们去除了 vic_print
函数的定义。因为该函数主要是上一篇文章测试跨 DLL
调用函数的测试函数。现在,我们不再需要它。
同时,我们添加了两个函数:
- Vic_Init
用于初始化环境,主要是注册我们编辑器的窗体类。至于要特别添加一个初始化函数,主要是由于微软官方文档
中明确指出,在DllMain
中调用复杂的函数,可能会造成死锁。 - Vic_CreateEditor
用于创建一个编辑器,这里暂时不需要指定编辑器的信息,只是指定一个父窗体的句柄,以便将编辑器添加到窗体。参考曾经我认为C语言就是个弟弟
中创建编辑器控件的代码。
接下来,我们还要实现这两个函数。
在项目 vitality-controls
的 src\include\
目录,建立一个 common.h
文件,输入如下内容:
#pragma once
#include "../../../shared-include/vitality-controls.h"
#define EDITOR_CLASS_NAME L"VicEditor"
LRESULT CALLBACK TextEditorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
其中,该文件引入了外部 API
文件定义,同时,声明了一个宏 EDITOR_CLASS_NAME
,该宏定义了我们要创建的目标编辑器的类名。
在项目 vitality-controls
的 src\controls\
文件夹下,建立一个 init.c
文件,并编辑如下代码:
#include "../include/common.h"
/**
* 函数描述:
* 初始化编辑器环境,需要在调用任何本程序集的函数之前,
* 调用本函数。
*/
VIC_API BOOL Vic_Init() {
WNDCLASSEX wnd = { 0 };
wnd.cbSize = sizeof(wnd);
wnd.hInstance = GetModuleHandle(NULL);
wnd.lpszClassName = EDITOR_CLASS_NAME;
wnd.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
wnd.hCursor = LoadCursor(NULL, IDC_IBEAM);
wnd.style = CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS;
wnd.lpfnWndProc = TextEditorWindowProc;
return RegisterClassEx(&wnd) != 0;
}
在项目 vitality-controls
的 src\controls\
文件夹下,建立一个 common.c
文件,并输入如下代码:
#include "../include/common.h"
/**
* 函数描述:
* 创建并初始化一个编辑器。
*
* 参数:
* parent: 新创建的编辑器的父窗体。
*
* 返回值:
* 如果创建控件成功,返回该控件的句柄,否则返回 NULL 并设置错误码。
* 错误码可以通过 GetLastError() 获取。
*/
VIC_API HWND Vic_CreateEditor(
HWND parent
) {
RECT rect = { 0 };
if (!GetClientRect(parent, &rect)) {
return NULL;
}
return CreateWindowEx(
0,
EDITOR_CLASS_NAME,
L"",
WS_CHILD | WS_VISIBLE | ES_MULTILINE |
WS_VSCROLL | WS_HSCROLL |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0,
rect.right,
rect.bottom,
parent,
NULL,
GetModuleHandle(NULL),
NULL
);
}
LRESULT CALLBACK TextEditorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 0, 0, L"HELLO", 5);
EndPaint(hwnd, &ps);
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
其中,新增了一个 TextEditorWindowProc
函数,该函数是我们编辑器的回调函数,参考 init.c
文件中,对 wnd.lpfnWndProc
字段的赋值。关于回调函数,参考文档
。
最后,让我们修改我们应用程序的主函数,修改项目 vicapp
的主程序文件 vicapp-main.c
如下所示:
#include <Windows.h>
#include "../../shared-include/vitality-controls.h"
LRESULT CALLBACK MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
PCWSTR MAIN_CLASS_NAME = L"VIC-APP-MAIN";
HWND editorHwnd = NULL;
LRESULT CALLBACK MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE: {
editorHwnd = Vic_CreateEditor(hwnd);
if (editorHwnd == 0) {
int lastError = GetLastError();
ShowWindow(hwnd, 0);
}
return 0;
}
case WM_SIZE: {
RECT rect = { 0 };
if (!GetWindowRect(hwnd, &rect)) {
break;
}
MoveWindow(
editorHwnd,
0,
0,
rect.right,
rect.bottom,
TRUE
);
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
BOOL InitApplication(HINSTANCE hinstance)
{
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = MainWindowProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hinstance;
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = GetStockObject(WHITE_BRUSH);
wcx.lpszClassName = MAIN_CLASS_NAME;
wcx.hIconSm = LoadImage(
hinstance,
MAKEINTRESOURCE(5),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR
);
return RegisterClassEx(&wcx);
}
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{
HWND hwnd = CreateWindowEx(
0,
MAIN_CLASS_NAME,
L"VicApp",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
hinstance,
(LPVOID)NULL
);
if (!hwnd) {
return FALSE;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
) {
MSG msg = { 0 };
if (!Vic_Init()) {
int err = GetLastError();
return FALSE;
}
if (!InitApplication(hInstance))
return FALSE;
if (!InitInstance(hInstance, nShowCmd))
return FALSE;
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
其中,在出程序的第一句,我们调用了控件初始化函数 Vic_Init
,并在创建主窗体的事件处理过程中,调用了 Vic_CreateEditor
函数,创建了一个子窗体,该子窗体就是我们的编辑器。
为了突出显示我们的编辑器,我们在 Vic_Init
函数中,设置背景颜色为红色,代码如下:
wnd.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
编译,并运行我们的程序,可以看到如下窗体:
由于我们在处理函数 TextEditorWindowProc
中,在窗体上绘制了字符串 "HELLO"
。所以,可以看到界面上出现了 "HELLO"
的字样,并且背景色为红色。
2. 之后的计划
由于代码编辑的过程中,想法可能发生改变,所以未来的计划并不是固定死的,有可能发生变更。
通常情况下,变更的可能有:
- 发现了某个功能的更好的实现方式。
- 某个功能过于复杂,导致一篇文章写不完。
虽然计划可能会变更,但是大致的思路如下:
背景设置
在这里,你将看到,如何设置背景色,或者将我们的编辑器背景设置为一张图片。
这个过程可能要耗费一节。文本绘制
主要目的是将当前使用
GDI
的文本绘制转换为DirectWrite
绘制。
这个过程可能要耗费一节。光标
在此小节,我们将会看到如何将光标显示在编辑器的指定位置。
这个过程可能要耗费一节。鼠标选择和高亮
在此主题下,我们将会为我们的编辑器添加鼠标选择,以及选择区域高亮显示的支持。
这个过程可能要耗费 2~3 个小结。文本内存结构
这将是一个比较大的主题,因为文件内容在内存中的保存,根据不同的考虑,将会采用不同的内存结构。
这个过程可能要耗费 2~3 个小结。滚动条实现
由于我们计划让我们的编辑器可编辑的文件尽可能的大,并且
Windows
自带的滚动条的取值范围有限,所以我们打算实现一个滚动条,其最大取值为UINT64
的最大取值,这样我们可以处理总行数就大大增加。
这个过程可能要耗费一节。Unicode
支持这个主题下,我们会对
Unicode
编码格式做一个简单的介绍,并实现对Unicode
字符的显示。
这个过程可能要耗费 2~3 个小结。文本透明度设置
由于我们的编辑器允许我们设置背景颜色,甚至背景图片,考虑到文本颜色可能和背景色相近,导致不容易区分,那么文本的透明渲染就很有必要了。如果我们的文本是透明的,那就可以和背景色相结合,生成更丰富的颜色搭配,起到更好的阅读体验的目的。
这个过程可能要耗费 1~2 个小结。添加注解
到此为止,我们的编辑器已经可以显示内容,选择内容,上下左右滚动,是时候添加注解功能了。
这个过程可能要耗费 1~2 个小结。添加样式支持
这里所谓的样式,是根据配置,识别出文件的不同组成部分,然后将给定识别部分显示为固定颜色。如下方代码:
int main(int argc, char** argv) {
return 0;
}
根据配置,将会分别以不同的颜色/字体显示不同的元素,如类型
int
将会被显示为蓝色等等。
这意味着,过了本节,你将至少可以实现一种编程语言的高亮功能。
当前,我们考虑实现C语言
的高亮显示。
好了,到此为止,我们已经能够将我们的控件显示出来了,计划也已经说明。如果你有什么建议,或者发现程序中有 BUG
,欢迎到本文档所在项目lets-write-a-edit-control
下留言,或者到源代码项目 vitality-controls
下提交 issue
。
如果像针对本文留言,关注微信公众号编程之路漫漫
,码途求知己,天涯觅一心。
让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示的更多相关文章
- 写一个带文本菜单的程序,菜单项如下 (1) 取五个数的和 (2) 取五个数的平均值 (X) 退出。
问题: 写一个带文本菜单的程序,菜单项如下(1) 取五个数的和 (2) 取五个数的平均值(X) 退出. 由用户做一个选择, 然后执行相应的功能.当用户选择退出时程序结束. 实现: ...
- [前端随笔][JavaScript] 制作一个富文本编辑器
写在前面 现在网上有很多现成的富文本编辑器,比如百度家的UEditor,kindeditor,niceditor等等,功能特别的多,API也很多,要去熟悉他的规则也很麻烦,所以想自己了解一下原理,做一 ...
- iView + vue-quill-editor 实现一个富文本编辑器(包含图片,视频上传)
1. 引入插件(注意IE10以下不支持) npm install vue-quill-editor --savenpm install quill --save (Vue-Quill-Editor需要 ...
- AUTOGUI生成的一个简易文本编辑器
; Generated by AutoGUI #SingleInstance Force #NoEnv SetWorkingDir %A_ScriptDir% SetBatchLines - #Inc ...
- 把DEDE的在线文本编辑器换成Kindeditor不显示问题
在织梦论坛下载了[Kindeditor编辑器For DedeCMS],按照操作说明安装后,后台文章编辑的区域却显示空白,有人说不兼容V57版本,有人说不兼容gbk版本,我也纠结了很久,在网上找了很多版 ...
- [译] 通过 contentEditable 属性创建一个所见即所得的编辑器(富文本编辑器)
译者注 这只是一篇入门教程,介绍了一些基础知识,仅供参考,切不可因此觉得富文本编辑器很简单. 创建富文本编辑器是一个非常复杂的工程,需要考虑到方方面面,也有很多坑(请参考原文第一条评论). 为免误导大 ...
- easyUI整合富文本编辑器KindEditor详细教程(附源码)
原因 在今年4月份的时候写过一篇关于easyui整合UEditor的文章Spring+SpringMVC+MyBatis+easyUI整合优化篇(六)easyUI与富文本编辑器UEditor整合,从那 ...
- vue集成百度UEditor富文本编辑器
在前端开发的项目中.难免会遇到需要在页面上集成一个富文本编辑器.那么.如果你有这个需求.希望可以帮助到你 vue是前端开发者所追捧的框架,简单易上手,但是基于vue的富文本编辑器大多数太过于精简.于是 ...
- 【JavaScript】富文本编辑器
这是js写的富文本编辑器,还存在一些bug,但基本功能已经实现,通过这个练习,巩固了js富文本编辑方面的知识,里面包含颜色选择器.全屏.表情.上传图片等功能,每个功能实际对应的就是一个小插件啦 部分程 ...
随机推荐
- Django基础五之Ajax
Django基础五之Ajax 目录 Django基础五之Ajax 1. Ajax介绍 2. Ajax前后端传值 2.1 方法一HttpResponse直接返回 2.2 方法二使用Json格式返回 2. ...
- 使用WebService的优点
1.支持跨平台,跨语言,远程调用 WSDL:web service definition language 直译 webservice定义语言 对应一种类型的文件.wsdl2.定义了web servi ...
- For循环案例练习一基础版
输出1-10之间的数据 1 public class LX1 { 2 public static void main(String[] args) { 3 for (int x=1;x<=10; ...
- 一步一步迁移ASP.NET Core 6.0-Part1
.NET 6 发布后,我们现有的应用会逐步升级到这个版本,首当其冲的是原因的ASP.NET Core的工程,如果一步一步升级到ASP.NET Core 6.0 本文简单整理一下升级ASP.NET Co ...
- Java程序员必备的工具和框架
最近几年,Java 的技术栈发展的非常快,成百上千的技术工具正不断地涌出来,这也造成了一个问题: 我们作为开发者,到底应该选哪些工具搭建出最合适的技术栈呢? 今天我就推荐一波我常用的.我了解的工具和框 ...
- QFramework Pro 开发日志(七)v0.4 版本审核通过 与 对话编辑器功能预告
经过一周的工作,v0.4 版本总算完成了. 就在刚刚笔者在 AssetStore 提交了 v0.4 版本. v0.4 版本主要内容有两个 一键生成简单继承类图功能 底层兼容 QFramework v0 ...
- Knife4j 注解详谈
Controller层添加注解 @Api:用于类:表示标识这个类是swagger的资源 属性名称 数据类型 默认值 说明 value String "" 字段 ...
- 时序数据库之InfluxDB的基本操作
1.进入Influxdb的客户端 [root@activity_sentinel ~]# influx 2.数据库的操作 显示所有的数据库名 > show databases name: dat ...
- ShardingSphere-Proxy(一)
1.现实中的问题 我们知道数据库的数据,基本80%的业务是查询,20%的业务涵盖了增删改,经过长期的业务变更和积累数据库的数据到达了一定的数量之后,直接影响的是用户与系统的交互,查询时的速度,插入数据 ...
- Python的类和继承
一.类的封装: 封装将类的信息隐藏在类内部,不允许外部直接修改该类的变量,只能通过该类提供的方法来实现对隐藏信息的操作和访问 class Boss(): # 类的公共属性 level=1 # 类的初始 ...