dustland

dustball in dustland

win32程序设计-chapter10 菜单

windows SDK chapter 10 菜单与资源

资源

图标光标等资源不是重点,重点在菜单

图标,光标,菜单,对话框都是资源类型.保存在.exe或者.dll等文件中.程序使用函数显式或者隐式地将资源加载进入内存使用,比如LoadIcon和LoadCursor

注意到资源是保存在exe或者dll文件中的,这意味着,不需要另外保存bmp或者ico等文件格式了,要啥直接放到到exe文件中,万事不求人了

资源脚本

资源脚本以.RC为后缀

image-20220821171827617

在visual studio解决方案视图下,它是这样的

image-20220821171915530

如果用其他文本编辑器比如vscode打开ICONDEMO.rc,他实际上时类似于头文件的代码

它不存放任何资源文件,但是指出这些文件的路径还有ID编号

其中菜单的结构最明显

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
//Microsoft Visual C++ 生成的资源脚本。
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// 从 TEXTINCLUDE 资源生成。
//
#ifndef APSTUDIO_INVOKED
#include "targetver.h"
#endif
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE 4, 2

/////////////////////////////////////////////////////////////////////////////
//
// 图标
//

// ID 值最低的图标放在最前面,以确保应用程序图标
// 在所有系统中保持一致。

IDI_ICONDEMO ICON "ICONDEMO.ico"
IDI_SMALL ICON "small.ico"

/////////////////////////////////////////////////////////////////////////////
//
// 菜单
//

IDC_ICONDEMO MENU
BEGIN
POPUP "文件(&F)"
BEGIN
MENUITEM "退出(&X)", IDM_EXIT
END
POPUP "帮助(&H)"
BEGIN
MENUITEM "关于(&A) ...", IDM_ABOUT
END
END

...

资源脚本.rc文件引用了Resource.h头文件,which是给资源编号的,每个资源有唯一的编号方便过程函数按图索骥

Resource.h允许c源程序和.rc资源描述文件引用相同的符号

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
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 使用者 ICONDEMO.rc

#define IDS_APP_TITLE 103

#define IDR_MAINFRAME 128
#define IDD_ICONDEMO_DIALOG 102
#define IDD_ABOUTBOX 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_ICONDEMO 107
#define IDI_SMALL 108
#define IDC_ICONDEMO 109
#define IDC_MYICON 2
#ifndef IDC_STATIC
#define IDC_STATIC -1
#endif
// 新对象的下一组默认值
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NO_MFC 130
#define _APS_NEXT_RESOURCE_VALUE 129
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif

书上给出的忠告是,不要直接修改资源描述文件和资源头文件,让visual studio维护这些东西

visual studio使用RC.EXE工具编译资源在编译阶段把ICONDEMO.rc编译,然后在连接阶段随obj和lib进入exe文件

图标

怎么绘制图标无所谓,只要是.ico格式的都能当成图标,关键看程序怎么用这个图标

在程序中使用自定义图标,不再使用Load(NULL,IDI_APPLICATION)指定的系统默认图标

应该这样写:

1
wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;

意思是往本应用程序实例中加载一个编号为IDI_ICON的图标

在visualstudio项目中可以添加资源

image-20220821173846421

添加Icon图标资源

image-20220821173911820

选择32*32,32位色,然后就可以在32*32的格格矩阵上画画了

image-20220821173956694

visual studio顶部会有工具栏

image-20220821174601043

画完了还得把以前的图标删了,让自己的图标顶置

image-20220822085630711

然后去资源视图改ID,在画画这里找一年也找不到

image-20220821175726126

给他改成ID=IDI_ICON1,visual studio会自动在rc文件和资源头文件中修改变化的,此时在资源头文件Resource.h中可以看到IDI_ICON1的宏定义了

1
#define IDI_ICON1                       129

此时在创建窗口类的时候再写

1
wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON1)) ;

就可以加载图标了

这个图标是放在项目根目录下的,如果删了他,对运行已经编译链接好的程序没有影响,但是再编译时会报错找不到文件

LoadIcon

1
2
3
4
HICON LoadIconA(
[in, optional] HINSTANCE hInstance,
[in] LPCSTR lpIconName
);

hInstance指定图标的文件来源,

NULL则为系统图标

本应用程序实例句柄则为本exe文件中包含的图标

lpIconName指定要加载的图标名.

可以使用字符串,也可以使用MAKEINTERESOURCE宏指定图标的宏定义

1
2
#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))

i先强制转化为WORD类型,这意味着i的有效范围是16位,更高位直接扬了

资源就到此为止吧,再学就不礼貌了,该点菜了

菜单

一开始我还想直接看菜单不看前面的图标光标这种资源,看到资源脚本直接蒙蔽了

概念

主菜单,顶级菜单:标题栏下面,客户区顶的常驻菜单

子菜单,下拉菜单,弹出菜单:主菜单点击后下拉的菜单.这种菜单可以嵌套多层

状态:菜单有启用,禁用状态,启用时有选中和非选中状态.

菜单结构

每个菜单项有三个特征定义,分别是菜单显式什么,菜单ID或者句柄,属性

定义菜单

visual studio中可以向项目添加资源,其中就包括菜单

image-20220821163145869

编辑菜单的方式是交互式的,右侧的菜单编辑器可以调制菜单属性(幸好一个菜单的属性稀松)

image-20220821163233880

菜单属性

能够编辑的菜单属性有12个(实际11个,最下边那个提示是MFC编程用的)

image-20220821163420651

弹出菜单为TRUE则本菜单可以弹出子菜单

灰显指菜单栏变灰且不活动,即禁用了,点击不会产生WM_COMMAND消息

描述文字就是菜单键上的文字

如果描述文字前面加上一个&符号,则该菜单项第一个字符会带上下划线,方便Alt更改菜单时索引他就像这样 image-20220821165229684

已勾选为TRUE则该菜单左侧有一个对号,这是复选标记

image-20220821163918622

已启用和灰显作用类似,但是只是点了没反应,字体不会变灰

分隔就是把该菜单作为分割线,比如1123和3菜单中间这个杠

image-20220821164249794

关键是ID,这是窗口过程给该菜单设置逻辑的唯一凭证

这里的ID是宏定义,实际上的数值visualstudio给我们放到Resource.h中了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Microsoft Visual C++ 生成的包含文件。
// 供 chapter10.rc 使用
//
#define IDC_MYICON 2
#define IDD_CHAPTER10_DIALOG 102
#define IDS_APP_TITLE 103
#define IDD_ABOUTBOX 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_CHAPTER10 107
#define IDI_SMALL 108
#define IDC_CHAPTER10 109
#define IDR_MAINFRAME 128
#define IDR_MENU1 129
#define ID_32771 32771
#define ID_32772 32772
#define ID_32773 32773
#define ID_32774 32774
#define ID_32775 32775
#define ID_32776 32776
#define ID_32777 32777
#define ID_32778 32778
#define IDC_STATIC -1

如果需要更多的资源则自己添加宏定义

程序中使用菜单

有多个可以指定菜单的地方,

最初可以指定菜单是在注册窗口类时

1
wndclass.lpszMenuName   = MAKEINTRESOURCEW(IDC_ICONDEMO);

然后在从创建窗口实例的时候可以指定菜单覆盖窗口类的菜单

1
2
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, hMenu, hInstance, nullptr);

这里hMenu句柄可以是LoadMenu打开的菜单资源

如果CreateWindow这里不指定菜单, 则默认使用窗口类的菜单.否则覆盖之

窗口实例创建之后还有改变菜单的方法

1
SetMenu(hwnd,hMenu)

菜单和消息

WM_MENUSELECT

当鼠标在菜单上移动时产生

LOWORD(wParam) 弹出菜单索引或者菜单ID
HIWORD(wParam) 选择标记
Value Meaning
MF_BITMAP0x00000004L Item displays a bitmap.
MF_CHECKED0x00000008L Item is checked.
MF_DISABLED0x00000002L Item is disabled.
MF_GRAYED0x00000001L Item is grayed.
MF_HILITE0x00000080L Item is highlighted.
MF_MOUSESELECT0x00008000L Item is selected with the mouse.
MF_OWNERDRAW0x00000100L Item is an owner-drawn item.
MF_POPUP0x00000010L Item opens a drop-down menu or submenu.
MF_SYSMENU0x00002000L Item is contained in the window menu. The lParam parameter contains a handle to the menu associated with the message.
lParam 包含所选项的菜单句柄

该消息用于暂时高亮菜单,显式完整文本描述

WM_COMMAND

最重要

表示点击了某个启用的菜单

LOWORD(wParam)菜单ID

下面的三个消息几乎用不到

WM_INITMENUPOPUP

要显示弹出菜单时的消息

wParam弹出菜单的句柄

LOWORD(lParam)弹出菜单的索引

HIWORD(lParam)1系统菜单,0其他菜单

WM_SYSCOMMAND

点击了系统菜单某个启用的菜单项

LWORD(wParam)菜单ID

LOWORD(lParam)鼠标x坐标,屏幕坐标

HIWORD(lParam)鼠标y坐标

WM_MENUCHAR

用户按下了Alt和不对应任何菜单项字符键.

或者弹出菜单时按下不对应任何菜单项的字符键

用于程序捕获并提醒快捷键不存在

LOWORD(wParam) 字符码

HIWORD(wParam) 选择吗

lParam 菜单句柄

该消息直接发送给DefWindowProc会导致操作系统MessageBeep

例程

菜单结构

visual studio中编辑菜单

使用文本编辑器打开MENUDEMO.rc文件,其菜单结构是按照BEGIN,END分块的

BEGIN,END块可以嵌套,约外层的菜单嵌套层数越低

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

IDC_MENUDEMO MENU
BEGIN
POPUP "文件(&F)"
BEGIN
MENUITEM "&New", IDM_FILE_NEW
MENUITEM "&Open", IDM_FILE_OPEN
MENUITEM "&Save", IDM_FILE_SAVE
MENUITEM "Save &As", IDM_FILE_SAVE_AS
MENUITEM "E&xit", IDM_APP_EXIT
MENUITEM SEPARATOR
END
POPUP "编辑(&E)"
BEGIN
MENUITEM "&Undo", IDM_EDIT_UNDO
MENUITEM SEPARATOR
MENUITEM "C&ut", IDM_EDIT_CUT
MENUITEM "&Copy", IDM_EDIT_COPY
MENUITEM "&Paste", IDM_EDIT_PASTE
MENUITEM "De&lete", IDM_EDIT_CLEAR
END
POPUP "背景(&B)"
BEGIN
MENUITEM "&White", IDM_BKGND_WHITE, CHECKED
MENUITEM "&Light Gray", IDM_BKGND_LTGRAY
MENUITEM "&Gray", IDM_BKGND_GRAY
MENUITEM "&Dark Gray", IDM_BKGND_DKGRAY
MENUITEM "&Black", IDM_BKGND_BLACK
END
POPUP "计时(&T)"
BEGIN
MENUITEM "&Start", IDM_TIMER_START
MENUITEM "S&top", IDM_TIMER_STOP, GRAYED
END
POPUP "帮助(&H)"
BEGIN
MENUITEM "&Help...", IDM_APP_HELP
MENUITEM "&About MenuDmo...", IDM_APP_ABOUT
END
END

菜单项前面加&,是为了加下划线,方便使用快捷键

整个菜单的ID表示在一开始列出了,是IDC_MENUDEMO,该值供注册窗口类或者实例化窗口时引用

1
2
IDC_MENUDEMO MENU
//标识符 资源类型

这个ID是多少不重要,只要保证每个资源有唯一的ID就可以了,我们只管给资源ID规定宏定义,visualstudio会自动帮我们分配一个正整数的

此后顶级菜单,即弹出菜单,都会有POPUP声明,然后跟着BEGIN,END作用块

比如POPUP "文件(&F)",这就声明了一个文件弹出菜单.其后面紧跟着的BEGIN,END包裹的块就是它的弹出菜单项

1
2
3
4
  POPUP "文件(&F)"//弹出菜单
BEGIN
...弹出菜单项
END

所有弹出菜单都不能有ID标识,只能是普通菜单项MENUITEM可以有ID标识,比如

1
MENUITEM "&New",                        IDM_FILE_NEW

因为弹出菜单只负责展示其菜单项,不能安排其他行为

程序中使用菜单

visual studio默认建立的资源都是使用ID索引的,没有使用字符串

刚才建立的菜单其ID为IDC_MENUDEMO,怎样才能让窗口使用这个菜单呢?

可以在创建窗口类的时候使用MAKEINTRESOURCE宏

1
wndclass.lpszMenuName = MAKEINTRESOURCE(IDC_MENUDEMO);

也可以在创建窗口实例的时候才指定菜单

1
2
3
4
5
6
7
8
wndclass.lpszMenuName = NULL;//创建窗口类的时候不指定菜单
...
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_MENUDEMO));//加载菜单句柄
hwnd = CreateWindow(szAppName, TEXT("Menu Demonstration"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenu, hInstance, NULL);

菜单消息

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

switch (message)
{
case WM_COMMAND:
hMenu = GetMenu(hwnd);

switch (LOWORD(wParam))
{
case IDM_FILE_NEW:
case IDM_FILE_OPEN:
case IDM_FILE_SAVE:
case IDM_FILE_SAVE_AS:

MessageBeep(0);//蜂鸣
return 0;
case IDM_APP_EXIT:
SendMessage(hwnd, WM_CLOSE, 0, 0);//发送程序退出关闭消息
return 0;

case IDM_EDIT_UNDO:
case IDM_EDIT_CUT:
case IDM_EDIT_COPY:
case IDM_EDIT_PASTE:
case IDM_EDIT_CLEAR:
MessageBeep(0);
return 0;

case IDM_BKGND_WHITE: // Note: Logic below //各种颜色背景是互斥关系,只能使用一个
case IDM_BKGND_LTGRAY: // assumes that IDM_WHITE
case IDM_BKGND_GRAY: // through IDM_BLACK are
case IDM_BKGND_DKGRAY: // consecutive numbers in
case IDM_BKGND_BLACK: // the order shown here.

CheckMenuItem(hMenu, iSelection, MF_UNCHECKED);//原来的选择弃选
iSelection = LOWORD(wParam);//对于WM_COMMAND消息来说,LOWORD(wParam)=点选菜单ID,这里获取新的点选背景ID
CheckMenuItem(hMenu, iSelection, MF_CHECKED);//修改新的背景色

SetClassLong(hwnd, GCL_HBRBACKGROUND, (LONG)GetStockObject(idColor[LOWORD(wParam) - IDM_BKGND_WHITE]));//修改系统属性,画刷颜色修改为新选中的颜色

InvalidateRect(hwnd, NULL, TRUE);
return 0;

case IDM_TIMER_START:
if (SetTimer(hwnd, ID_TIMER, 1000, NULL))//开始计时,时钟编号ID_TIMER(1)
{
EnableMenuItem(hMenu, IDM_TIMER_START, MF_GRAYED);//禁用
EnableMenuItem(hMenu, IDM_TIMER_STOP, MF_ENABLED);//启用计时结束实践
}
return 0;

case IDM_TIMER_STOP:
KillTimer(hwnd, ID_TIMER);//销毁时钟
EnableMenuItem(hMenu, IDM_TIMER_START, MF_ENABLED);
EnableMenuItem(hMenu, IDM_TIMER_STOP, MF_GRAYED);
return 0;
case IDM_APP_HELP:
MessageBox(hwnd, TEXT("Help not yet implemented!"),//摆烂
szAppName, MB_ICONEXCLAMATION | MB_OK);
return 0;

case IDM_APP_ABOUT:
MessageBox(hwnd, TEXT("Menu Demonstration Program\n")
TEXT("(c) Charles Petzold, 1998"),
szAppName, MB_ICONINFORMATION | MB_OK);
return 0;
}
break;

所有点击活动菜单的消息都有一个WM_COMMAND命令

此时其LOWORD(lParam)保存的是击中菜单的ID,如果击中的是弹出菜单则系统负载该逻辑,自动展开菜单,不需要我们指定弹出菜单的行为(事实上弹出菜单不允许拥有ID,这就意味着在程序中无法索引到它,无法安排行为)

复用关闭消息
1
2
case IDM_APP_EXIT:
SendMessage(hwnd, WM_CLOSE, 0, 0);//发送程序退出关闭消息

当点击菜单项项时,直接发送一体哦啊WM_CLOSE消息即可借助已有代码完成功能

改变勾选状态
1
2
3
4
5
DWORD CheckMenuItem(
HMENU hMenu,//菜单句柄,使用GetMenu(hwnd)获得
UINT uIDCheckItem,//需要修改状态的菜单项ID
UINT uCheck//修改后的状态
);
1
2
3
4
CheckMenuItem(hMenu, iSelection, MF_UNCHECKED);//原来的选择弃选
iSelection = LOWORD(wParam);//对于WM_COMMAND消息来说,LOWORD(wParam)=点选菜单ID,这里获取新的点选背景ID
CheckMenuItem(hMenu, iSelection, MF_CHECKED);//修改新的背景色
SetClassLong(hwnd, GCL_HBRBACKGROUND, (LONG)GetStockObject(idColor[LOWORD(wParam) - IDM_BKGND_WHITE]));//修改系统属性,画刷颜色修改为新选中的颜色

点选新的背景颜色之后,首先弃选先前的背景颜色,然后修改为新的背景颜色

之后修使用SetClassLong改程序的背景颜色

1
2
3
4
5
DWORD SetClassLongA(
[in] HWND hWnd,//需要修改属性的窗口句柄
[in] int nIndex,//需要修改的属性
[in] LONG dwNewLong//该属性的新值
);

菜单快捷键

右键菜单

在处理右键消息的时候可以安排上右键快捷菜单

1
2
3
4
5
6
7
8
9
BOOL TrackPopupMenu(
[in] HMENU hMenu,//需要显式的菜单句柄
[in] UINT uFlags,//点选菜单的方式
[in] int x,
[in] int y,
[in] int nReserved,
[in] HWND hWnd,
[in, optional] const RECT *prcRect
);

在指定的位置展示一个快捷菜单,并且追踪在该菜单上的选择.

该快捷菜单可以出现在任何地方

1
Displays a shortcut menu at the specified location and tracks the selection of items on the menu. The shortcut menu can appear anywhere on the screen.
1
2
3
4
5
6
7
8
9
10
case WM_CREATE:
hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDC_MENUDEMO));//hMenu获取整个菜单句柄
hMenu = GetSubMenu(hMenu, 0);//此时hMenu句柄是第一个顶级菜单的句柄,不是整个菜单的句柄,即文件弹出菜单,如果这里写1则为编辑弹出菜单
return 0;
case WM_RBUTTONUP:
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
ClientToScreen(hwnd, &point);
TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL);//允许使用左键和右键,在本菜单上作用相同
return 0;

键盘加速键

键盘加速键不是处理WM_KEYDOWN或者WM_CHAR复制键盘功能,当然这样写也能达到相同的目标

但是键盘加速键可以省去其逻辑

此处略去一大堆优点,因为有些优点现在我也体会不到

键盘加速键应当是Shift,Ctrl,Alt带领的跨界见,避免使用Tab,Enter,Esc,Space作键盘加速键

比如Ctrl+Z 撤销,Ctrl+X剪切,Del删除

加速键表

加速键表也是资源的一种,可以在visual studio中添加Accelerator资源

一方通行

加速键表长这样

ID就是该加速键的编号,键对应键盘动作,类型要么是虚拟键VIRTKEY,要么是字符CHAR

image-20220823114110566

如果键盘加速键想要和菜单关联起来,那么一个键盘加速键的ID需要设置成一个菜单项的ID

整个加速键表类似菜单一样,也有自己的一个ID标识,这个可以在资源视图看到

image-20220823145812581

通常方便使用,加速键表名和程序名,菜单名都相同

有了这个加速键表ID我们就可以在表的层面上和程序打交道,而不是需要负责每个加速键的逻辑.在程序中使用LoadAccelerators函数加载加速键表并获得句柄

1
HANDLE hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_MENUDEMO));

现在程序中就有加速键表句柄了,下面就是如何使用该句柄了

在消息循环翻译分派消息之前先尝试翻译成加速键消息

1
2
3
4
5
6
while (GetMessage(&msg, NULL, 0, 0)) {
if (!TranslateAccelerator(hwnd, hAccel, &msg)) {//此处使用了加速键表句柄hAccel
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}

对于每个消息都会先经过一个加速键翻译,TranslateAccelerator函数确定保存在msg中的消息是否是键盘消息(包括虚拟键消息和字符消息),如果是,并且hAccel是一个有效的加速键表句柄,下面就调用hwnd指向窗口的窗口过程,向其发送同键盘加速键ID对应的菜单项按下的WM_COMMAND或者WM_SYSCOMMAND消息

显然这个翻译后的消息还会进入消息队列,但是翻译后的消息是WM_COMMAND消息,不是WM_KEYDOWN,WM_CHAR消息,不会再被TranslateAccelerator翻译,因此通过条件判断,正式进入消息循环处理

注意到TranslateAccelerator(hwnd, hAccel, &msg)这里指定了一个主窗口句柄,但是一个程序可以有多个窗口,这就会导致所有加速键消息发往主窗口.

如果想让一个消息发往现在的焦点窗口,则可以使用msg.hwnd,这个hwnd是目前消息的窗口句柄

处理WM_COMMAND消息

在处理WM_COMMAND消息时,加速键,菜单,控件消息的参数含义:

image-20220823151633079

如此看来,如果加速键ID和功能表ID故意设置成相同值,就不用大费周折了

例程

菜单表
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
IDC_MENUDEMO2 MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New", IDM_FILE_NEW
MENUITEM "&Open", IDM_FILE_OPEN
MENUITEM "&Save", IDM_FILE_SAVE
MENUITEM "Save &As", IDM_FILE_SAVE_AS
MENUITEM SEPARATOR
MENUITEM "&Print", IDM_FILE_PRINT
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Edit"
BEGIN
MENUITEM "&Undo\tCtrl+Z", IDM_EDIT_UNDO
MENUITEM SEPARATOR
MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT
MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY
MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE
MENUITEM "De&lete\tDel", IDM_EDIT_DELETE
MENUITEM SEPARATOR
MENUITEM "&Select All", IDM_EDIT_SELECT_ALL
END
POPUP "&Help"
BEGIN
MENUITEM "&Help...\tF1", IDM_HELP_HELP
MENUITEM "&About...", IDM_APP_ABOUT
END
END
加速键表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
IDC_MENUDEMO2 ACCELERATORS
BEGIN
"8", IDM_UNDO, VIRTKEY, ALT, NOINVERT
VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT
VK_DELETE, IDM_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT
VK_F1, IDM_HELP_HELP, VIRTKEY, NOINVERT
VK_INSERT, IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT
VK_INSERT, IDM_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT
"^C", IDM_EDIT_COPY, ASCII, NOINVERT
"^V", IDM_EDIT_PASTE, ASCII, NOINVERT
"^X", IDM_EDIT_CUT, ASCII, NOINVERT
"^Z", IDM_EDIT_UNDO, ASCII, NOINVERT
"^A", IDM_EDIT_SELECT_ALL, ASCII, NOINVERT
END

菜单名和加速键表ID都是IDC_MENUDEMO2,可以使用MAKEINTRESOURCE宏加载资源

使用父窗口控制编辑控件子窗口,大大减轻自己写编辑逻辑的工作量

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include <windows.h> 
#include "MENUDEMO2.h"
#define ID_EDIT 1
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
TCHAR szAppName[] = TEXT("PopPad2");
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HACCEL hAccel;
HWND hwnd;
MSG msg;
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 = MAKEINTRESOURCE(IDC_MENUDEMO2);
wndclass.lpszClassName = szAppName;

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

hwnd = CreateWindow(//创建父窗口
szAppName, szAppName,
WS_OVERLAPPEDWINDOW,
GetSystemMetrics(SM_CXSCREEN) / 4,
GetSystemMetrics(SM_CYSCREEN) / 4,
GetSystemMetrics(SM_CXSCREEN) / 2,
GetSystemMetrics(SM_CYSCREEN) / 2,
NULL, NULL, hInstance, NULL
);

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

hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MENUDEMO2));//加载加速键表资源
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, hAccel, &msg))//翻译加速键
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
int AskConfirmation(HWND hwnd)//关闭父窗口时的询问
{
return MessageBox(hwnd, TEXT("Really want to close PopPad2?"),
szAppName, MB_YESNO | MB_ICONQUESTION);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hwndEdit;
int iSelect, iEnable;

switch (message)
{
case WM_CREATE:
hwndEdit = CreateWindow(TEXT("edit"), NULL,//创建子窗口,父窗口过程管理子窗口过程,子窗口是预定义好的编辑控件
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |//子窗口|可见|横竖滚动|边框|左对齐|多行|自动滚动
WS_BORDER | ES_LEFT | ES_MULTILINE |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0, hwnd, (HMENU)ID_EDIT,//子窗口ID
((LPCREATESTRUCT)lParam)->hInstance, NULL);
return 0;

case WM_SETFOCUS:
SetFocus(hwndEdit);//父窗口将焦点让给子窗口
return 0;

case WM_SIZE:
MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam),TRUE);//父窗口移动子窗口,始终填充整个父窗口的客户区
return 0;

case WM_INITMENUPOPUP://初始化弹出菜单,每次点击顶级菜单时,在接收到WM_COMMAND之前先接收到WM_INITMENUPOPUP
if (lParam == 1)
{
EnableMenuItem(
(HMENU)wParam,IDM_EDIT_UNDO,
SendMessage(hwndEdit, EM_CANUNDO, 0, 0) ?
MF_ENABLED : MF_GRAYED
);

EnableMenuItem((HMENU)wParam,
IDM_EDIT_PASTE,
IsClipboardFormatAvailable(CF_TEXT) ?//询问剪切板状态,决定使能还是灰化粘贴键
MF_ENABLED : MF_GRAYED);

iSelect = SendMessage(hwndEdit, EM_GETSEL,0, 0);//获取高亮选中位置

if (HIWORD(iSelect) == LOWORD(iSelect))//LOWORD(iSelect)指向开始高亮的地方,HIWORD(iSelect)指向结束高亮的地方
iEnable = MF_GRAYED;//如果开始==结束说明没有选中东西
else
iEnable = MF_ENABLED;//如果开始!=结束说明选中东西了

EnableMenuItem((HMENU)wParam, IDM_EDIT_CUT, iEnable);//根据有没有选中东西决定复制,剪切,粘贴是否使能
EnableMenuItem((HMENU)wParam, IDM_EDIT_COPY, iEnable);
EnableMenuItem((HMENU)wParam, IDM_EDIT_CLEAR, iEnable);
return 0;
}
break;
case WM_COMMAND:
if (lParam)
{
if (LOWORD(lParam) == ID_EDIT &&
(HIWORD(wParam) == EN_ERRSPACE ||
HIWORD(wParam) == EN_MAXTEXT))
MessageBox(hwnd, TEXT("Edit control out of space."),
szAppName, MB_OK | MB_ICONSTOP);
return 0;
}
else switch (LOWORD(wParam))
{
case IDM_FILE_NEW:
case IDM_FILE_OPEN:
case IDM_FILE_SAVE:
case IDM_FILE_SAVE_AS:
case IDM_FILE_PRINT:
MessageBeep(0);//尚未实现
return 0;

case IDM_APP_EXIT:
SendMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
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://Ctrl+A
SendMessage(hwndEdit, EM_SETSEL, 0, -1);
return 0;

case IDM_HELP_HELP:
MessageBox(hwnd, TEXT("Help not yet implemented!"),
szAppName, MB_OK | MB_ICONEXCLAMATION);
return 0;

case IDM_APP_ABOUT:
MessageBox(hwnd, TEXT("dustball's 卑鄙 notepad"),
szAppName, MB_OK | MB_ICONINFORMATION);
return 0;
}
break;

case WM_CLOSE:
if (IDYES == AskConfirmation(hwnd))
DestroyWindow(hwnd);
return 0;

case WM_QUERYENDSESSION:
if (IDYES == AskConfirmation(hwnd))
return 1;
else
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

未实现的保存,打开,新建等功能需要等到学了对话框再说了