dustland

dustball in dustland

windows 记事本

windows SDK PopPad3

一个比较完整的记事本,借以学习

windows API程序设计

模块化程序设计

windows消息机制

模块之间通信

面向对象思想

项目结构

其项目结构为:

image-20220824171205363

其中源文件的名称是自解释的,

PopPad.c实现主窗口过程,它调用其他模块中的函数实现功能

PopFile.c模块,实现的是与文件系统交互的逻辑

PopFind.c模块,实现的是查找功能

PopFont.c模块,实现的是设置字体的功能

PopPrnt0.c模块,在未来实现打印机功能

PopPad.c

程序入口,主模块

初始化

本模块开始,定义全局变量,常量,函数引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define EDITID   1//编辑器控件的id
#define UNTITLED TEXT ("(untitled)") //未保存文件的名称
//声明本模块中的函数
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;

//声明其他模块中的函数
// Functions in POPFILE.C

void PopFileInitialize (HWND) ;
BOOL PopFileOpenDlg (HWND, PTSTR, PTSTR) ;
BOOL PopFileSaveDlg (HWND, PTSTR, PTSTR) ;
BOOL PopFileRead (HWND, PTSTR) ;
BOOL PopFileWrite (HWND, PTSTR) ;

// Functions in POPFIND.C

HWND PopFindFindDlg (HWND) ;
HWND PopFindReplaceDlg (HWND) ;
BOOL PopFindFindText (HWND, int *, LPFINDREPLACE) ;
BOOL PopFindReplaceText (HWND, int *, LPFINDREPLACE) ;
BOOL PopFindNextText (HWND, int *) ;
BOOL PopFindValidFind (void) ;

// Functions in POPFONT.C

void PopFontInitialize (HWND) ;
BOOL PopFontChooseFont (HWND) ;
void PopFontSetFont (HWND) ;
void PopFontDeinitialize (void) ;

// Functions in POPPRNT.C

BOOL PopPrntPrintFile (HINSTANCE, HWND, HWND, PTSTR) ;

// Global variables

static HWND hDlgModeless ;//全局非模态对话框句柄,用于查找框
static TCHAR szAppName[] = TEXT ("PopPad") ;//本引用程序名,用于注册类名,资源名

WinMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
MSG msg;
HWND hwnd;
HACCEL hAccel;
WNDCLASS wndclass;//主窗口样式

wndclass.style = CS_HREDRAW | CS_VREDRAW;//窗口宽高发生变化时都会重绘
wndclass.lpfnWndProc = WndProc; //注册主窗口过程回调函数
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, szAppName);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = szAppName;//和菜单挂钩
wndclass.lpszClassName = szAppName;//定义本窗口类类名

if (!RegisterClass(&wndclass)) //注册窗口类
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}

hwnd = CreateWindow(
szAppName, NULL,//使用szAppName窗口类进行实例化,无窗口名
WS_OVERLAPPEDWINDOW,//层叠窗口
CW_USEDEFAULT, CW_USEDEFAULT,//x,y,宽,高 都是默认值
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, szCmdLine//无父窗口,不覆盖菜单,应用程序实例,命令行
);

ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);

hAccel = LoadAccelerators(hInstance, szAppName);//键盘加速键,也是用szAppName作为资源名

while (GetMessage(&msg, NULL, 0, 0))//从消息循环获得一条消息
{
if (hDlgModeless == NULL || !IsDialogMessage(hDlgModeless, &msg))//需要先分清本条消息是给主窗口的还是给对话框窗口的
{//hDlgModeless是非模态对话框,即查找框的句柄.该句柄为NULL时表明当前没有活动的查找框,那么本消息只能是主窗口的,
//如果hDlgModeless不为NULL,还需判断到底是谁的消息,只需要调用IsDialogMessage(hDlgModeless, &msg)
//如果是查找框的消息,则IsDialogMessage返回TRUE,并且已经把该消息发往查找框自己的过程函数了
//如果不是查找框的消息,则函数返回FALSE,继续向下执行
if (!TranslateAccelerator(hwnd, hAccel, &msg))//翻译加速键,如果是加速键消息则翻译了然后重新进入消息循环,
{//如果不是键盘加速键或者说已经被翻译成非键盘加速键的消息,则通过if判断
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
return msg.wParam;
}

从主函数上看,本程序使用了键盘加速键,并且可以有查找框这种非模态窗口

WndProc主窗口过程

变量定义

1
2
3
4
5
6
7
8
static BOOL      bNeedSave = FALSE ;//记录当前文件自从最近的修之后有没有改动,如果有则需要修改,相当于脏位
static HINSTANCE hInst ; //本应用程序实例句柄
static HWND hwndEdit ; //编辑控件的句柄
static int iOffset ; //高亮选中字体数
static TCHAR szFileName[MAX_PATH], szTitleName[MAX_PATH] ;//文件名,标题栏名称
static UINT messageFindReplace ;//记录查找替换结果
int iSelBeg, iSelEnd, iEnable ;//高亮选择区域开始,结束,iEnable是剪切复制等功能的使能开关,只有有高亮区域时iEnable有效
LPFINDREPLACE pfr ;//查找替换框指针,用这个指针操作查替框行为

下面就进入switch-case分拣消息了

switch(message)

WM_CREATE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
case WM_CREATE:
hInst = ((LPCREATESTRUCT) lParam) -> hInstance ;//hInst初始化为当前应用程序实例句柄,在创建编辑控件的时候需要用到该值

// Create the edit control child window

hwndEdit = CreateWindow (TEXT ("edit"), NULL,//创建子窗口,使用"edit"类(预定义控件类),子窗口无名
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |//子窗口属性:子窗口|可见|水平滚动|竖直滚动|
WS_BORDER | ES_LEFT | ES_MULTILINE | //有边框 | 文本左对齐 | 多行文本
ES_NOHIDESEL | ES_AUTOHSCROLL | ES_AUTOVSCROLL,//失去焦点时变灰|自动水平滚动|自动竖直滚动(显式区域跟随输入提示符)
0, 0, 0, 0,//一开始窗口没有大小
hwnd, (HMENU) EDITID, hInst, NULL) ;//主窗口作为父窗口,EDITID宏作为编辑控件索引,应用程序实例句柄

SendMessage (hwndEdit, EM_LIMITTEXT, 32000, 0L) ;//立刻向编辑控件发送一条消息,限制总字数不超过32000字

// Initialize common dialog box stuff

PopFileInitialize (hwnd) ;//PopFile.c模块中的函数,用于初始化用户在Open和Save As对话框中的选项
PopFontInitialize (hwndEdit) ;//PopFont.c中的函数,初始化用户在Font对话框中的选项

messageFindReplace = RegisterWindowMessage (FINDMSGSTRING) ;//注册自己的窗口消息,messageFindReplace使其消息编号
//RegisterWindowMessage专门用于注册在两个相互写作的窗口之间通信的消息,另一个注册过FINDMSGSTRING的窗口是查找框,
//也就是说,只要唤醒查找框主窗口就会收到messageFindReplace消息
DoCaption (hwnd, szTitleName) ;//将主窗口标题改为szTitleName
return 0 ;
WM_SETFOCUS
1
2
3
case WM_SETFOCUS:
SetFocus (hwndEdit) ;//主窗口获得焦点立刻把焦点让给编辑控件
return 0 ;

WM_SIZE

1
2
3
case WM_SIZE: //编辑控件应该占据主窗口的整个客户区
MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ;
return 0 ;

WM_INITMENUPOPUP

switch(lParam)
case 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
case 1:             // Edit menu//1号弹出菜单,即Edit菜单

// Enable Undo if edit control can do it

EnableMenuItem ((HMENU) wParam, IDM_EDIT_UNDO,
SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ?//向编辑控件询问是否可以执行撤销操作
MF_ENABLED : MF_GRAYED) ;//如果SendMessage返回TRUE则MF_ENABLED,那么EnableMenuItem就将IDM_EDIT_UNDO菜单项使能
//否则将IDM_EDIT_UNDO菜单项变灰禁用

// Enable Paste if text is in the clipboard

EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE,
IsClipboardFormatAvailable (CF_TEXT) ?//询问剪贴板是否可用,
MF_ENABLED : MF_GRAYED) ;//使能或者吃灰

// Enable Cut, Copy, and Del if text is selected

SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iSelBeg,//向编辑控件询问当前高亮情况,写到iSelBeg和iSelEnd中
(LPARAM) &iSelEnd) ;

iEnable = iSelBeg != iSelEnd ? MF_ENABLED : MF_GRAYED ;//iEnable是剪切拷贝删除的开关,
//如果iSelBeg==iSelEnd说明没有高亮,则iEnable=MF_GRAYED吃灰

EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT, iEnable) ;//根据iEnable情况决定三个菜单是吃灰还是使能
EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY, iEnable) ;
EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, iEnable) ;
break ;
case 2
1
2
3
4
5
6
7
8
9
10
11
12
case 2:             // Search menu//第二个弹出菜单,查找菜单

// Enable Find, Next, and Replace if modeless
// dialogs are not already active

iEnable = hDlgModeless == NULL ?//如果当前已经有非模态对话框实例,即已经有打开的查找或者查换窗口,则不允许再打开第二个,吃灰
MF_ENABLED : MF_GRAYED ;

EnableMenuItem ((HMENU) wParam, IDM_SEARCH_FIND, iEnable) ;//根据iEnable决定这三个菜单是吃灰还是使能
EnableMenuItem ((HMENU) wParam, IDM_SEARCH_NEXT, iEnable) ;
EnableMenuItem ((HMENU) wParam, IDM_SEARCH_REPLACE, iEnable) ;
break ;

WM_COMMAND

控件消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (lParam && LOWORD (wParam) == EDITID)//如果是来自编辑控件的消息,LOWORD(wParam)是找茬的控件的索引,即菜单值
{
switch (HIWORD (wParam))//HIWORD(wParam)存放的是消息类型
{
case EN_UPDATE ://有更新,比如增删字符
bNeedSave = TRUE ;//bNeedSave标志着文件脏了,需要保存
return 0 ;

case EN_ERRSPACE ://内存不够了
case EN_MAXTEXT ://超过最大字数32000字了
MessageBox (hwnd, TEXT ("Edit control out of space."),//弹出消息框
szAppName, MB_OK | MB_ICONSTOP) ;
return 0 ;
}
break ;
}
菜单消息
switch(LOWORD(wParam))
IDM_FILE_NEW
1
2
3
4
5
6
7
8
9
10
11
case IDM_FILE_NEW://新建文件 
if (bNeedSave && IDCANCEL == AskAboutSave (hwnd, szTitleName))
//如果当前文件脏了,并且弹出的询问保存对话框中选择了是,直接返回,不新建窗口
return 0 ;
//如果刚才的文件不需要保存,或者弹窗询问是否保存时选择了否,则放弃刚才的文件,新建一个
SetWindowText (hwndEdit, TEXT ("\0")) ;//设置编辑控件初始内容为空,这里如果写上其他字符串则作为编辑控件的第一行
szFileName[0] = '\0' ;//无文件名
szTitleName[0] = '\0' ;//无标题名
DoCaption (hwnd, szTitleName) ;//更新主窗口标题栏
bNeedSave = FALSE ;//设置不需要保存
return 0 ;
IDM_FILE_OPEN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case IDM_FILE_OPEN://打开文件 
if (bNeedSave && IDCANCEL == AskAboutSave (hwnd, szTitleName))//和IDM_FILE_NEW时一个逻辑
return 0 ;

if (PopFileOpenDlg (hwnd, szFileName, szTitleName))//PopFileOpenDlg封装了GetOpenFileName函数
//szFileName保存了用户希望打开的文件名,设置标题栏应该表现的值
{
if (!PopFileRead (hwndEdit, szFileName))//PopFileRead尝试往hwndEdit控件读取szFileName文件
{//如果读取失败则弹窗报错
OkMessage (hwnd, TEXT ("Could not read file %s!"),//封装MessageBox
szTitleName) ;
szFileName[0] = '\0' ;
szTitleName[0] = '\0' ;
}
}

DoCaption (hwnd, szTitleName) ;//修改标题栏
bNeedSave = FALSE ;//刚打开文件不需要保存
return 0 ;
IDM_FILE_SAVE&&IDM_FILE_SAVE_AS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
case IDM_FILE_SAVE://保存文件
if (szFileName[0])//如果已经有文件名,说明要么是保存过了,要么是打开的其他文件
{
if (PopFileWrite (hwndEdit, szFileName))//通知hwndEdit控件输出到szFileName文件
{
bNeedSave = FALSE ;//刚保存过,不需要再保存
return 1 ;//返回1表明保存成功
}
else//保存失败
{
OkMessage (hwnd, TEXT ("Could not write file %s"),
szTitleName) ;
return 0 ;
}
}
//保存文件时还有一种情况,就是新建的,之前从来没有保存的文件,这种情况下
// 保存和另存为有相同的逻辑
// fall through
case IDM_FILE_SAVE_AS://另存为
if (PopFileSaveDlg (hwnd, szFileName, szTitleName))//弹出保存对话框
{
//进入if则表明保存成功

DoCaption (hwnd, szTitleName) ;//修改文件名为新的szTitleName

if (PopFileWrite (hwndEdit, szFileName))//通知hwndEdit写入szFileName文件
{
bNeedSave = FALSE ;//刚保存过,不需要再保存
return 1 ;
}
else//保存失败
{
OkMessage (hwnd, TEXT ("Could not write file %s"),
szTitleName) ;
return 0 ;
}
}
return 0 ;
IDM_FILE_PRINT
1
2
3
4
5
6
case IDM_FILE_PRINT://打印
if (!PopPrntPrintFile (hInst, hwnd, hwndEdit, szTitleName))//桩函数,尚未实现
OkMessage (hwnd, TEXT ("Could not print file %s"),
szTitleName) ;
return 0 ;

IDM_APP_EXIT
1
2
3
case IDM_APP_EXIT://关闭窗口
SendMessage (hwnd, WM_CLOSE, 0, 0) ;
return 0 ;
撤剪拷粘清全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case IDM_EDIT_UNDO://撤销操作,应该发往编辑控件
SendMessage (hwndEdit, WM_UNDO, 0, 0) ;
return 0 ;

case IDM_EDIT_CUT://剪切
SendMessage (hwndEdit, WM_CUT, 0, 0) ;
return 0 ;

case IDM_EDIT_COPY:
SendMessage (hwndEdit, WM_COPY, 0, 0) ;
return 0 ;

case IDM_EDIT_PASTE:
SendMessage (hwndEdit, WM_PASTE, 0, 0) ;
return 0 ;

case IDM_EDIT_CLEAR:
SendMessage (hwndEdit, WM_CLEAR, 0, 0) ;
return 0 ;

case IDM_EDIT_SELECT_ALL:
SendMessage (hwndEdit, EM_SETSEL, 0, -1) ;
return 0 ;
查找&&查换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case IDM_SEARCH_FIND://查找
SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ;//
hDlgModeless = PopFindFindDlg (hwnd) ;//创建查找框,PopFindFindDlg封装了FindText API
return 0 ;

case IDM_SEARCH_NEXT://查找下一个
SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ;

if (PopFindValidFind ())
PopFindNextText (hwndEdit, &iOffset) ;
else
hDlgModeless = PopFindFindDlg (hwnd) ;

return 0 ;

case IDM_SEARCH_REPLACE://查找替换
SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ;
hDlgModeless = PopFindReplaceDlg (hwnd) ;
return 0 ;
IDM_FORMAT_FONT
1
2
3
4
5
case IDM_FORMAT_FONT://字体格式
if (PopFontChooseFont (hwnd))
PopFontSetFont (hwndEdit) ;//PopFontChooseFont和PopFontSetFont属于同一模块,模块内部会有通信

return 0 ;
帮助&&关于
1
2
3
4
5
6
7
8
case IDM_HELP://帮助
OkMessage (hwnd, TEXT ("Help not yet implemented!"),
TEXT ("\0")) ;
return 0 ;

case IDM_APP_ABOUT: //关于
DialogBox (hInst, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;//使用AboutDlgProc作为模态对话框的窗口过程
return 0 ;

关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case WM_CLOSE://关闭窗口
if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName))//不需要保存或者询问保存点选的否
DestroyWindow (hwnd) ;//销毁父窗口

return 0 ;

case WM_QUERYENDSESSION ://结束对话或者系统关闭时询问
if (!bNeedSave || IDCANCEL != AskAboutSave (hwnd, szTitleName))
return 1 ;

return 0 ;

case WM_DESTROY:
PopFontDeinitialize () ;//清理逻辑字体
PostQuitMessage (0) ;//发送退出消息,结束消息循环
return 0 ;

messageFindReplace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
default:
// Process "Find-Replace" messages

if (message == messageFindReplace)//如果是召唤查找框的消息,此时lParam就是查找框结构体的地址
{
pfr = (LPFINDREPLACE) lParam ;//pfr指针获取FINDREPLACE结构体地址

if (pfr->Flags & FR_DIALOGTERM)
hDlgModeless = NULL ;

if (pfr->Flags & FR_FINDNEXT)
if (!PopFindFindText (hwndEdit, &iOffset, pfr))
OkMessage (hwnd, TEXT ("Text not found!"),
TEXT ("\0")) ;

if (pfr->Flags & FR_REPLACE || pfr->Flags & FR_REPLACEALL)
if (!PopFindReplaceText (hwndEdit, &iOffset, pfr))
OkMessage (hwnd, TEXT ("Text not found!"),
TEXT ("\0")) ;

if (pfr->Flags & FR_REPLACEALL)
while (PopFindReplaceText (hwndEdit, &iOffset, pfr)) ;

return 0 ;
}
break ;
}

return DefWindowProc (hwnd, message, wParam, lParam) ;

PopFile.c

整个模块包装了一个对象ofn,模块内的函数都是作用与该ofn对象的,本质上是面向对象风格的

模块变量ofn

1
static OPENFILENAME ofn ;

OPENFILENAME结构体用于保存用户关于保存文件的选择,比如文件名,文件位置等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <commdlg.h>
typedef struct tagOFNA {
DWORD lStructSize;//本结构体的大小
HWND hwndOwner;//调用对话框的窗口句柄
HINSTANCE hInstance;//对话框模板内存对象
LPCSTR lpstrFilter;//过滤器格式化字符串
LPSTR lpstrCustomFilter;//保留用户选择的过滤器字符串
DWORD nMaxCustFilter;//lpstrCustomFilter的长度
DWORD nFilterIndex;//当前选中的过滤器的下标
LPSTR lpstrFile;//文件路径
DWORD nMaxFile;//文件路径长度
LPSTR lpstrFileTitle;//文件名和拓展名
DWORD nMaxFileTitle;//文件名和拓展名的长度
LPCSTR lpstrInitialDir;//初始目录
LPCSTR lpstrTitle;//对话框标题
DWORD Flags;//标志
WORD nFileOffset;//文件名在lpstrFile中的下标
WORD nFileExtension;//拓展名在lpstrFile中的下标
LPCSTR lpstrDefExt;//默认拓展名
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;//指向钩子程序
LPCSTR lpTemplateName;
LPEDITMENU lpEditInfo;
LPCSTR lpstrPrompt;
void *pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
} OPENFILENAMEA, *LPOPENFILENAMEA;

lStructSize

本结构体大小,冗余量

hwndOwner

拥有本对话框的窗口句柄

hInstance

如果Flags=OFN_ENABLETEMPLATEHANDLE,那么hInstance时内存中的一个对话框模板

如果Flags=OFN_ENABLETEMPLATE ,那么hInstance是一个模块句柄,该模块中有lpTemplateName指明的对话框模板,

如果Flags=OFN_EXPLORER ,那么系统使用文件资源管理器Explorer风格的对话框

如果Flags=NULL,那么系统使用老式文件资源管理器Explorer风格对话框

lpstrFilter

文件名过滤器,打开文件的时候经常可以看见这个功能

image-20220831173604402

比如上传图片的时候限制后缀为.jpg或者.png格式

实际上是一个字符串

其格式是这样的:

1
2
3
4
static TCHAR szFilter[] = TEXT("废话 \0*.<拓展名>\0") \
TEXT("废话 \0*.<拓展名>\0") \
...\
TEXT("废话 \0*.<拓展名>\0\0") ;//最后两个\0挨着意思是szFilter到头了

废话是写给人看的

本函数在实现的时候就根据\0去分割,

两个\0之间的*.<拓展名>不允许有空格

*是通配符,表示所有字符串

比如

1
2
3
static TCHAR szFilter[] = TEXT ("Text Files (*.TXT)\0*.txt\0")  \
TEXT ("ASCII Files (*.ASC)\0*.asc\0") \
TEXT ("All Files (*.*)\0*.*\0\0") ;

这就允许*.txt,*.asc以及任意格式的文件了

既然允许任意格式,那还费力写txt和asc干啥

如果lpstrFilter=NULL,则不显示任何过滤器

模块函数

PopFileOpenDlg

实质上是封装了GetOpenFileName函数

1
2
3
4
5
6
7
8
9
10
BOOL PopFileOpenDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName)
{
ofn.hwndOwner = hwnd ;//设置窗口主人句柄为主窗口
ofn.lpstrFile = pstrFileName ;//外部指针
ofn.lpstrFileTitle = pstrTitleName ;
ofn.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT ;
//隐藏只读选项(不是很明白啥意思),如果不存在文件则给用户新建文件的权限
return GetOpenFileName (&ofn) ;
}

这里Flags中有一个OFN_CREATEPROMPT,设置这个标记,当试图打开一个不存的文件的时候,会弹窗询问是否新建该文件

image-20220910214637043

否则,即如果不将这个Flag置起来,如此打开不存在的文件会被拒绝

image-20220910214723862

GetOpenFileName干了啥?

创建"打开"对话框,允许用户指定要打开的文件或文件夹.

image-20220910215708583

参数&ofn指定了获取文件名,目录放到哪里,以及Flags决定的标志

本函数在主模块PopPad.c的主窗口过程IDM_FILE_OPEN消息中唯一一次被调用,即发生点击Open菜单的时候,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case IDM_FILE_OPEN:
if (bNeedSave && IDCANCEL == AskAboutSave (hwnd, szTitleName))
return 0 ;//判断是否需要保存旧的文件

if (PopFileOpenDlg (hwnd, szFileName, szTitleName))//PopFileOpenDlg会返回是否成功打开(可能因为不存在或者权限 失败,只有成功打开才能继续)
//
{
if (!PopFileRead (hwndEdit, szFileName))
{
OkMessage (hwnd, TEXT ("Could not read file %s!"),
szTitleName) ;
szFileName[0] = '\0' ;
szTitleName[0] = '\0' ;
}
}

DoCaption (hwnd, szTitleName) ;
bNeedSave = FALSE ;
return 0 ;

大概PopFileOpenDlg返回时,ofn结构体已经保存了用户的选择,而PopPad.c中调用该函数时传递的指针已经被PopFileOpenDlg设置为和ofn中的指针同指向了,因此PopPad.c中的szFileName和szTitleName旧获取到了用户希望打开的文件信息

PopFileSaveDlg

1
2
3
4
5
6
7
8
9
BOOL PopFileSaveDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName)
{
ofn.hwndOwner = hwnd ;
ofn.lpstrFile = pstrFileName ;//直接使用外部指针
ofn.lpstrFileTitle = pstrTitleName ;
ofn.Flags = OFN_OVERWRITEPROMPT ;

return GetSaveFileName (&ofn) ;
}

OFN_OVERWRITEPROMPT标志的作用是,保存文件时如果有重名文件则弹窗让用户确认是否覆盖,否则会直接覆盖(可能会因为只读权限导致无法覆盖).

GetSaveFileName函数会创建一个保存对话框,让用户指定保存的位置

image-20220910220340540

显然该函数只会被主模块的Save和Save As菜单消息处理使用

PopFileRead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
BOOL PopFileRead (HWND hwndEdit, PTSTR pstrFileName)
//将pstrFileName中的所有文字读入poppad,成功则TRUE,否则FALSE
{
BYTE bySwap ;//调换字节时的临时变量
DWORD dwBytesRead ;
HANDLE hFile ;//打开文件句柄,相当于linux上的文件描述符file descriptor
int i, iFileLength, iUniTest ;
PBYTE pBuffer, pText, pConv ;

// Open the file.

if (INVALID_HANDLE_VALUE ==
(hFile = CreateFile (pstrFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL)))
return FALSE ;//判断是否能成功打开文件,如果能则hFile获取文件句柄,否则返回FALSE

// Get file size in bytes and allocate memory for read.
// Add an extra two bytes for zero termination.

iFileLength = GetFileSize (hFile, NULL) ; //iFileLength获取文件总长度
pBuffer = malloc (iFileLength + 2) ;//pBuffer动态在内存上申请iFileLength+2的空间,意思是最后要放\0

// Read file and put terminating zeros at end.

ReadFile (hFile, pBuffer, iFileLength, &dwBytesRead, NULL) ;
//从hFile读取至多iFileLength个字节放到pBuffer中,实际读取了多少个放到dwBytesRead
CloseHandle (hFile) ;//关闭文件句柄
pBuffer[iFileLength] = '\0' ;//pBuffer缓冲区以两个\0结尾
pBuffer[iFileLength + 1] = '\0' ;

// Test to see if the text is unicode

iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE ;
//刚才是以Ascii码的方式读入的字节,每个字节都是独立的,
//现在需要判断读入的是否是Unicode码,如果是则需要两个字节并一个字
if (IsTextUnicode (pBuffer, iFileLength, &iUniTest))
{//如果真的是unicode编码的,
pText = pBuffer + 2 ;
iFileLength -= 2 ;

if (iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE)
//虽然是unicode,但是每个unicode的两个字节都反过来了,应该调换一下
{
for (i = 0 ; i < iFileLength / 2 ; i++)//每两个字节一组进行调换
{
bySwap = ((BYTE *) pText) [2 * i] ;//暂时存放低字节
((BYTE *) pText) [2 * i] = ((BYTE *) pText) [2 * i + 1] ;//原来的高字节放到新的低字节
((BYTE *) pText) [2 * i + 1] = bySwap ;//原来的低字节放到新的高字节
}
}

// Allocate memory for possibly converted string

pConv = malloc(iFileLength + 2);

// If the edit control is not Unicode, convert Unicode text to
// non-Unicode (ie, in general, wide character).

#ifndef UNICODE
WideCharToMultiByte (CP_ACP, 0, (PWSTR) pText, -1, pConv,
iFileLength + 2, NULL, NULL) ;

// If the edit control is Unicode, just copy the string
#else
lstrcpy ((PTSTR) pConv, (PTSTR) pText) ;//pText拷贝到pConv
#endif

}
else // the file is not Unicode
{
pText = pBuffer ;

// Allocate memory for possibly converted string.

pConv = malloc (2 * iFileLength + 2) ;

// If the edit control is Unicode, convert ASCII text.

#ifdef UNICODE //如果定义了UNICODE码
MultiByteToWideChar (CP_ACP, 0, pText, -1, (PTSTR) pConv,
iFileLength + 1) ;//pConv转到Unicode码,放到pText

// If not, just copy buffer
#else
lstrcpy ((PTSTR) pConv, (PTSTR) pText) ;
#endif
}

SetWindowText (hwndEdit, (PTSTR) pConv) ;//将pConv指向的文字区交给hwndEdit窗口,打印到屏幕
free (pBuffer) ;//事了拂衣去,深藏功与名
free (pConv) ;

return TRUE ;
}

这里调用了很多API

CreateFile
1
2
3
4
5
6
7
8
9
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);

打开或者创建一个文件,返回其文件句柄,lpFileName是希望打开或者创建的文件,dwDesiredAccess是希望的访问权限,包括读写执行.后面使用该文件的时候不能超过CreateFile时限定的权限

dwShareMode一旦置起来,则该文件只允许被当前程序打开,直到本程序释放文件句柄后,才允许文件被下一个程序访问

lpSecurityAttributes安全属性,包括文件句柄的继承性以及访问权限,在<<windows核心编程>>中有详细解释

dwCreationDisposition,指定打开文件的模式,包括文件存在或者不存在时的打开规则

dwFlagsAndAttributes文件属性标志,即创建只读文件或者隐藏文件等

hTemplateFile模板文件句柄,用另一个文件的权限等信息覆盖本文件的设置

在popfile.c中该函数是这样用的:

1
2
3
4
5
6
7
8
9
hFile = CreateFile (
pstrFileName, //从poppad.c主模块中传递过来的文件名
GENERIC_READ, //读权限
FILE_SHARE_READ,//允许共享读
NULL, //使用默认安全属性
OPEN_EXISTING, //打开已经存在的,如果不存在则报错
0, //忽略
NULL//忽略,不使用模板文件
);
GetFileSize
1
2
3
4
DWORD GetFileSize(
[in] HANDLE hFile,
[out, optional] LPDWORD lpFileSizeHigh
);

lpFileSizeHigh返回文件长度的高二字节(文件总大小要占用四个字节,lpFileSizeHigh只返回高2字节,虽然不知道这样返回有啥意义)

调用成功则返回准确的文件大小,一个双字

1
iFileLength = GetFileSize (hFile, NULL) ;

iFileLength获得了文件大小

ReadFile
1
2
3
4
5
6
7
BOOL ReadFile(
[in] HANDLE hFile,
[out] LPVOID lpBuffer,
[in] DWORD nNumberOfBytesToRead,
[out, optional] LPDWORD lpNumberOfBytesRead,
[in, out, optional] LPOVERLAPPED lpOverlapped
);

读取文件内容,从hFile句柄指定的文件读取至多nNumberOfBytesToRead个字节,放到lpBuffer指定的缓冲区,实际读取的字节数还要取决于hFile文件的 大小,lpNumberOfBytesRead存放实际读取到的字节数

lpOverlapped和异步IO有关,现在不管它,默认NULL

1
2
ReadFile (hFile, pBuffer, iFileLength, &dwBytesRead, NULL);

从hFile读取至多iFileLength个字节放到pBuffer中,实际读取了多少个放到dwBytesRead

PopFileWrite

逻辑和PopFileRead基本相同,读改成写就完了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
BOOL PopFileWrite (HWND hwndEdit, PTSTR pstrFileName)
{
DWORD dwBytesWritten ;
HANDLE hFile ;
int iLength ;
PTSTR pstrBuffer ;
WORD wByteOrderMark = 0xFEFF ;

// Open the file, creating it if necessary

if (INVALID_HANDLE_VALUE ==
(hFile = CreateFile (pstrFileName, GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, 0, NULL)))
return FALSE ;

// Get the number of characters in the edit control and allocate
// memory for them.

iLength = GetWindowTextLength (hwndEdit) ;
pstrBuffer = (PTSTR) malloc ((iLength + 1) * sizeof (TCHAR)) ;

if (!pstrBuffer)
{
CloseHandle (hFile) ;
return FALSE ;
}

// If the edit control will return Unicode text, write the
// byte order mark to the file.

#ifdef UNICODE
WriteFile (hFile, &wByteOrderMark, 2, &dwBytesWritten, NULL) ;
#endif

// Get the edit buffer and write that out to the file.

GetWindowText (hwndEdit, pstrBuffer, iLength + 1) ;
WriteFile (hFile, pstrBuffer, iLength * sizeof (TCHAR),
&dwBytesWritten, NULL) ;

if ((iLength * sizeof (TCHAR)) != (int) dwBytesWritten)
{
CloseHandle (hFile) ;
free (pstrBuffer) ;
return FALSE ;
}

CloseHandle (hFile) ;
free (pstrBuffer) ;

return TRUE ;
}

PopFind.c

本模块实现了对poppad的查找替换的支持

模块全局/静态变量

1
2
3
4
5
6
7
#define MAX_STRING_LEN   256

static TCHAR szFindText [MAX_STRING_LEN] ;//需要查找的文本缓冲区
static TCHAR szReplText [MAX_STRING_LEN] ;//需要替换的文本缓冲区

...
static FINDREPLACE fr ;

关于FINDREPLACE

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct tagFINDREPLACEW {
DWORD lStructSize;//本结构体大小
HWND hwndOwner;//窗口句柄
HINSTANCE hInstance;//对话框内存对象
DWORD Flags;//标志
LPWSTR lpstrFindWhat;//需要查找的文字
LPWSTR lpstrReplaceWith;//需要替换成的文字
WORD wFindWhatLen;//需要查找的文字长度
WORD wReplaceWithLen;//需要替换成的文字长度
LPARAM lCustData;//钩子过程参数
LPFRHOOKPROC lpfnHook;//钩子过程
LPCWSTR lpTemplateName;//查找替换框模板
} FINDREPLACEW, *LPFINDREPLACEW;

模块函数

PopFindFindDlg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HWND PopFindFindDlg (HWND hwnd)
{
static FINDREPLACE fr ; // must be static for modeless dialog!!!

fr.lStructSize = sizeof (FINDREPLACE) ;
fr.hwndOwner = hwnd ;
fr.hInstance = NULL ;
fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD ;//隐藏查找方向,隐藏大小写匹配,隐藏全词匹配
fr.lpstrFindWhat = szFindText ;//从静态变量获取要查找的字符串
fr.lpstrReplaceWith = NULL ;//之查找不替换,不需要本缓冲区
fr.wFindWhatLen = MAX_STRING_LEN ;//最大查找长度
fr.wReplaceWithLen = 0 ;
fr.lCustData = 0 ;
fr.lpfnHook = NULL ;
fr.lpTemplateName = NULL ;

return FindText (&fr) ;//调用API
}

这里fr.Flags= FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD ;

意思是:

隐藏查找方向,隐藏大小写匹配,隐藏全词匹配

如果这些都不隐藏,则查找对话框是这样的

image-20220910231639276

都隐藏之后的查找框

image-20220910231722908

FindText只需要fr结构体作为参数,如果查找成功则返回查找框的句柄

PopFindReplaceDlg

逻辑和PopFindFindDlg相似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HWND PopFindReplaceDlg (HWND hwnd)
{
static FINDREPLACE fr ; // must be static for modeless dialog!!!

fr.lStructSize = sizeof (FINDREPLACE) ;
fr.hwndOwner = hwnd ;
fr.hInstance = NULL ;
fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | FR_HIDEWHOLEWORD ;
fr.lpstrFindWhat = szFindText ;
fr.lpstrReplaceWith = szReplText ;
fr.wFindWhatLen = MAX_STRING_LEN ;
fr.wReplaceWithLen = MAX_STRING_LEN ;
fr.lCustData = 0 ;
fr.lpfnHook = NULL ;
fr.lpTemplateName = NULL ;

return ReplaceText (&fr) ;
}

PopFindFindText

查找逻辑函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
BOOL PopFindFindText (HWND hwndEdit, int * piSearchOffset, LPFINDREPLACE pfr)
{
int iLength, iPos ;
PTSTR pstrDoc, pstrPos ;

// Read in the edit document

iLength = GetWindowTextLength (hwndEdit) ;
//hwndEdit标题栏字数,如果是编辑控件则是该控件中已经输入的字数

if (NULL == (pstrDoc = (PTSTR) malloc ((iLength + 1) * sizeof (TCHAR))))//申请相应大小的堆空间
return FALSE ;

GetWindowText (hwndEdit, pstrDoc, iLength + 1) ;//将编辑空间中的所有字节拷贝到pstrDoc缓冲区

// Search the document for the find string

pstrPos = _tcsstr (pstrDoc + * piSearchOffset, pfr->lpstrFindWhat) ;
//调用字符串查找函数,如果找到则返回* piSearchOffset之后首次匹配的指针位置
free (pstrDoc) ;

// Return an error code if the string cannot be found

if (pstrPos == NULL)//如果是NULL则表明没有查找到
return FALSE ;

// Find the position in the document and the new start offset

iPos = pstrPos - pstrDoc ;//否则查找到了,返回下标(指针做差得到距离,又pstrDoc是缓冲区起始指针,因此该距离就是下标)
* piSearchOffset = iPos + lstrlen (pfr->lpstrFindWhat) ;//移动查找起始指针,避免下一次重复查找

// Select the found text

SendMessage (hwndEdit, EM_SETSEL, iPos, * piSearchOffset) ;//通知编辑空间,对偏移量*piSearchOffset,长度为iPos的文字进行高亮选中
SendMessage (hwndEdit, EM_SCROLLCARET, 0, 0) ;//将刚才高亮的区域滚动进入客户区

return TRUE ;
}

到此需要再看一下主模块中对于调用查找框和使用查找逻辑函数,是怎么配合的

在主窗口过程的最后,default块中,处理查找替换消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
             case IDM_SEARCH_FIND:
SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) &iOffset) ;//获取高亮区域,将其位置放到iOffset
hDlgModeless = PopFindFindDlg (hwnd) ;//调用PopFindFindDlg,获取到的输入都放在popfind模块,主模块不关心
return 0 ;

....

//messageFindReplace在主窗口过程一开始被注册messageFindReplace = RegisterWindowMessage (FINDMSGSTRING) ;
//在commdlg.h中宏定义为#define FINDMSGSTRINGW L"commdlg_FindReplace"
//也就是说只要是调用了查找框,就会发送一条该注册消息
if (message == messageFindReplace)//调用查找框之后会收到该消息,顺理成章
{
pfr = (LPFINDREPLACE) lParam ;

if (pfr->Flags & FR_DIALOGTERM)
hDlgModeless = NULL ;

if (pfr->Flags & FR_FINDNEXT)
if (!PopFindFindText (hwndEdit, &iOffset, pfr))//IDM_SEARCH_FIND会修改iOffset为当前高亮位置
OkMessage (hwnd, TEXT ("Text not found!"),//如果没有查到则弹窗报告
TEXT ("\0")) ;

if (pfr->Flags & FR_REPLACE || pfr->Flags & FR_REPLACEALL)
if (!PopFindReplaceText (hwndEdit, &iOffset, pfr))
OkMessage (hwnd, TEXT ("Text not found!"),
TEXT ("\0")) ;

if (pfr->Flags & FR_REPLACEALL)
while (PopFindReplaceText (hwndEdit, &iOffset, pfr)) ;

return 0 ;
}

也就是说,用户通过菜单或者ctrl+f快捷键,让主窗口过程处理IDM_SEARCH_FIND,创建了模态查找框,由于已经注册过messageFindReplace和查找框关联,主窗口会接着收到该消息,查找框用来获取用户希望查找的字符串,数据存放到popfind模块中,不归主模块poppad.c管理

然后messageFindReplace中调用PopFindFindText,再次将控制交给popfind模块,该模块的函数可以访问该模块的静态变量

PopFindFindText就在该模块中发挥作用了

PopFindNextText

查找下一个

1
2
3
4
5
6
7
8
BOOL PopFindNextText (HWND hwndEdit, int * piSearchOffset)
{
FINDREPLACE fr ;

fr.lpstrFindWhat = szFindText ;

return PopFindFindText (hwndEdit, piSearchOffset, &fr) ;
}

只需要更新一下起始查找位置然后套用PopFindFindText就可以了

PopFindReplaceText

查找并替换

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL PopFindReplaceText (HWND hwndEdit, int * piSearchOffset, LPFINDREPLACE pfr)
{
// Find the text

if (!PopFindFindText (hwndEdit, piSearchOffset, pfr))
return FALSE ;

// Replace it

SendMessage (hwndEdit, EM_REPLACESEL, 0, (LPARAM) pfr->lpstrReplaceWith) ;

return TRUE ;
}

首先调用PopFindFindText找到目标字符串,然后给编辑控件发EM_REPLACESEL消息,提醒编辑控件用pfr->lpstrReplaceWith替换原来的字符串

而pfr->lpstrReplaceWith这个值是之前调用模态查替对话框获得的输入

替换编辑控件中哪个位置的字符串呢?

这个不需要操心,因为查替首先要查找,查找到了就会高亮,需要替换的就是高亮的部分

PopFindValidFind

是否是有效查找,待查缓冲区非空才有效

1
2
3
4
BOOL PopFindValidFind (void)
{
return * szFindText != '\0' ;
}