dustland

dustball in dustland

win32程序设计-chapter11 对话框

windows SDK chapter 11 对话框

模态对话框

模态对话框指对话框存在时不能操作父窗口,必须关闭模态对话框才能和父窗口进行交互

如果是普通的模态对话框,此时还可以到其他应用程序.

如果是系统的模态对话框,则整个操作系统都会被这一个模态对话框锁住,必须关了系统模态对话框才可以干别的

因为父窗口过程直接将控制转移给了对话框函数,啥时候对话框函数返回即对话框关闭,控制才能还给父窗口

About1

主窗口过程WndProc在处理菜单消息时有一个创建对话框的情况,即点选ID_APP_ABOUT对应的菜单

1
2
3
4
5
6
7
8
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDM_APP_ABOUT :
DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;//创建对话框,使用AboutBox资源
break ;
}
return 0 ;
1
2
3
4
5
6
void DialogBoxA(
[in, optional] hInstance,//exe句柄
[in] lpTemplate,//对话框模板,可以加载资源
[in, optional] hWndParent,//父窗口句柄
[in, optional] lpDialogFunc//对话框过程函数
);

该函数会一直等到lpDialogFunc回调函数中调用EndDialog才会结束执行,将控制还给WndProc,

这意味在对话框起来之后,主窗口啥也干不了,除非把对话框扬了.

创建对话框的过程:

DialogBox宏用CreateWindowEx函数创建对话框。DialogBox函数然后把一个WM_INITDIALOG消息(和一个WM_SETFONT消息,如果模板指定DS_SETFONT类型)传递到对话框过程。不管模板是否指定WS_VISIBLE类型,函数显示对话框,并且使拥有该对话框的窗口(也称属主窗口)失效,且为对话框启动它本身的消息循环来检索和传递消息。

当对话框应用程序调用EndDialog函数时,DialogBox函数清除对话框户止消息循环,使属主窗口生效(如果以前有效),且返回函数EndDialog调用中的nReSUlt参数。

对话框过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message, 
WPARAM wParam, LPARAM lParam)//hDlg持有自己的对话框句柄
{
switch (message)
{
case WM_INITDIALOG :
return TRUE ;

case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDOK :
case IDCANCEL ://然而该对话框没有取消按钮,只有一个OK按钮,可以使用Esc键触发IDCANCLE
EndDialog (hDlg, 0) ;//结束对话框
return TRUE ;
}
break ;
}
return FALSE ;
}

1
2
3
4
BOOL EndDialog(
[in] HWND hDlg,//需要结束的对话框句柄
[in] INT_PTR nResult//返回值,主窗口可以根据该值与对话框进行交互
);

使用DialogBox创建的对话框必须使用EndDialog制死

EndDialog函数是在对话框窗口过程中调用的,并且只能用于结束对话框过程

EndDialog中指定的返回值就是DialogBox函数的返回值

设计对话框

visual studio添加对话框资源,可以交互式设计对话框

rc文件中

1
2
3
4
5
6
7
8
9
10
ABOUTBOX DIALOGEX 32, 32, 180, 102		//名字(用于索引该对话框) 对话框类型 左上角横坐标,左上角纵坐标,宽度,高度
STYLE DS_SETFONT | WS_POPUP | WS_THICKFRAME //风格 对话框有字,弹出,薄框架
FONT 8, "MS Sans Serif", 0, 0, 0x0 //字号8 字体 ,balabala
BEGIN //对话框主题开始
DEFPUSHBUTTON "OK",IDOK,66,81,50,14 //一个默认按钮,ID是IDOK,上有字样"OK" 左上角(66,81) 宽50,高14
ICON "ABOUT1",IDC_STATIC,7,7,20,20 //图标,名称"ABOUT1" ID是IDC_STATIC,左上角(7,7),宽高20(对于ICON来说该宽高无意义)
CTEXT "dialog",IDC_STATIC,40,12,100,8 //文字
CTEXT "dustball",IDC_STATIC,7,40,166,8
CTEXT "(c) dustball, 2022",IDC_STATIC,7,52,166,8
END //对话框主题结束

这里所有数值的单位都不是像素,而是对话框所用字体的宽的四分之一高的八分之一,具体怎么算的不重要,交互式设计不需要闷头算他娘的

BEGIN和END之间是对话框子窗口控件

1
控件类型 "文本",id,左坐标,上坐标,宽度,高度,控件风格

控件风格比如WS_CHILD,SS_CENTER,WS_VISIBLE

对话框过程

1
BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,WPARAM wParam, LPARAM lParam);

一个返回真假的回调函数,有四个参数,对话框句柄,消息,w参和l参

对比窗口过程和对话框过程

对话框过程 窗口过程
返回值 BOOL LRESULT
DefWindowProc 不会调用,只会返回TRUE 处理不了的消息调用DefWindowProc
处理消息 不需要处理WM_PAINT,WM_DESTORY
不会收到WM_CREATE,
有专门消息WM_INITDIALOG
只需要处理WM_COMMAND

当对话框中按下按钮时,这个按钮回向它的父窗口,也就是这个对话框,发送WM_COMMAND消息,wParam是控件ID

About2

资源脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ABOUTBOX DIALOG DISCARDABLE  32, 32, 200, 234	//名叫ABOUTBOX
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
FONT 8, "MS Sans Serif"
BEGIN
ICON "ABOUT2",IDC_STATIC,7,7,20,20
CTEXT "About2",IDC_STATIC,57,12,86,8
CTEXT "About Box Demo Program",IDC_STATIC,7,40,186,8
LTEXT "",IDC_PAINT,114,67,72,72 //预览块
GROUPBOX "&Color",IDC_STATIC,7,60,84,143
RADIOBUTTON "&Black",IDC_BLACK,16,76,64,8,WS_GROUP | WS_TABSTOP//单选组开始,tab键停留位置,使用tab键可以更换当前对话框内的焦点.同一组内按钮可以使用上下左右键调整选择
RADIOBUTTON "B&lue",IDC_BLUE,16,92,64,8
RADIOBUTTON "&Green",IDC_GREEN,16,108,64,8
RADIOBUTTON "Cya&n",IDC_CYAN,16,124,64,8
RADIOBUTTON "&Red",IDC_RED,16,140,64,8
RADIOBUTTON "&Magenta",IDC_MAGENTA,16,156,64,8
RADIOBUTTON "&Yellow",IDC_YELLOW,16,172,64,8
RADIOBUTTON "&White",IDC_WHITE,16,188,64,8
GROUPBOX "&Figure",IDC_STATIC,109,156,84,46,WS_GROUP
RADIOBUTTON "Rec&tangle",IDC_RECT,116,172,65,8,WS_GROUP | WS_TABSTOP
RADIOBUTTON "&Ellipse",IDC_ELLIPSE,116,188,64,8
DEFPUSHBUTTON "OK",IDOK,35,212,50,14,WS_GROUP
PUSHBUTTON "Cancel",IDCANCEL,113,212,50,14,WS_GROUP
END

全局变量

1
2
int iCurrentColor = IDC_BLACK;  //当前颜色
int iCurrentFigure = IDC_RECT ;//当前形状

这两个变量决定着预览框和主窗口如何绘制.

通过对话框修改

父窗口过程

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
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance ;
PAINTSTRUCT ps ;

switch (message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
return 0 ;

case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDM_APP_ABOUT:
if (DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc))
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
}
break ;

case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;

PaintWindow (hwnd, iCurrentColor, iCurrentFigure) ;
return 0 ;

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

PaintWindow

使用iColor颜色和iFigure形状绘制hwnd指向的窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void PaintWindow (HWND hwnd, int iColor, int iFigure)//根据指定颜色和形状绘制窗口
{
static COLORREF crColor[8] = { RGB ( 0, 0, 0), RGB ( 0, 0, 255),
RGB ( 0, 255, 0), RGB ( 0, 255, 255),
RGB (255, 0, 0), RGB (255, 0, 255),
RGB (255, 255, 0), RGB (255, 255, 255) } ;//颜色列表

HBRUSH hBrush ;
HDC hdc ;
RECT rect ;

hdc = GetDC (hwnd) ;
GetClientRect (hwnd, &rect) ;
hBrush = CreateSolidBrush (crColor[iColor - IDC_BLACK]) ;//iColor是当前正在使用的颜色
hBrush = (HBRUSH) SelectObject (hdc, hBrush) ;

if (iFigure == IDC_RECT)//绘制矩形
Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
else//绘制扁瓜蛋
Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ;

DeleteObject (SelectObject (hdc, hBrush)) ;
ReleaseDC (hwnd, hdc) ;
}

对话框过程

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

BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
static HWND hCtrlBlock ;//预览块句柄
static int iColor, iFigure ;

switch (message)
{
case WM_INITDIALOG:
iColor = iCurrentColor ;//获取当前颜色
iFigure = iCurrentFigure ;

CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, iColor) ;//第一个颜色和最后一个颜色,其间的宏定义都算,这一些选项中有且只有一个处于被选中状态
CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, iFigure) ;

hCtrlBlock = GetDlgItem (hDlg, IDC_PAINT) ;//获取预览块句柄

SetFocus (GetDlgItem (hDlg, iColor)) ;//iColor就是选中颜色按钮的句柄,将焦点让给该按钮
return FALSE ;

case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDOK://点选了确认键,需要修改设置了
iCurrentColor = iColor ;
iCurrentFigure = iFigure ;//修改当前设置
EndDialog (hDlg, TRUE) ;
return TRUE ;

case IDCANCEL:
EndDialog (hDlg, FALSE) ;
return TRUE ;

case IDC_BLACK:
case IDC_RED:
case IDC_GREEN:
case IDC_YELLOW:
case IDC_BLUE:
case IDC_MAGENTA:
case IDC_CYAN:
case IDC_WHITE:
iColor = LOWORD (wParam) ;//LOWORD(wParam)携带控件ID
CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ;//根据鼠标点选改变单选按钮状态
PaintTheBlock (hCtrlBlock, iColor, iFigure) ;//Block是预览块,实时根据选择改变,提供预览功能
return TRUE ;

case IDC_RECT:
case IDC_ELLIPSE:
iFigure = LOWORD (wParam) ;//更新图形状态
CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, LOWORD (wParam)) ;
PaintTheBlock (hCtrlBlock, iColor, iFigure) ;//立刻重绘预览块
return TRUE ;
}
break ;

case WM_PAINT:
PaintTheBlock (hCtrlBlock, iColor, iFigure) ;//重绘时只需要关注预览块
break ;
}
return FALSE ;
}

PaintTheBlock

1
2
3
4
5
6
void PaintTheBlock (HWND hCtrl, int iColor, int iFigure)
{
InvalidateRect (hCtrl, NULL, TRUE) ;//hCtrl句柄控制的客户去全都失效
UpdateWindow (hCtrl) ;//重绘hCtrl
PaintWindow (hCtrl, iColor, iFigure) ;//根据hCtrl和iColor指定的风格重绘
}

回顾父子通信方式

父窗口就只负责主窗口客户区的颜色和图形绘制,对话框负责调这个颜色和图形

父窗口和对话框如何进行通信的?或者说对话框是如何通知父窗口用新样式绘图的呢?

关键在于两个对父过程和对话框过程都可见的全局变量,对话框使用这两个变量间接通知父窗口

并且在对话框关闭之后,父窗口会立刻重绘,这就使得颜色样式更新显得没有延迟

父窗口中,关闭对话框之后的立刻更新:

1
2
3
case IDM_APP_ABOUT:
if (DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc))//如果DialogBox返回值1说明确实有修改,需要重绘,否则不需要重绘
InvalidateRect (hwnd, NULL, TRUE) ;

避免使用全局变量

正当我感觉使用全局变量间接实现父子通信,这个设计合情合理,甚至有点巧妙时,人家又说,这是low B方法.

书上给的方法是,定义一个结构体

1
2
3
typedef struct {
int iColor, iFigure;
}ABOUTBOX_DATA;

这个结构体就记录了对话框能够使用的变量

在WndProc中用这样一个结构体指针传递给DialogBoxParam,即带参数的对话框函数

1
2
3
4
5
6
7
INT_PTR DialogBoxParamA(
[in, optional] HINSTANCE hInstance,//应用程序实例句柄
[in] LPCSTR lpTemplateName,//对话框id
[in, optional] HWND hWndParent,//父窗口
[in, optional] DLGPROC lpDialogFunc,//对话框过程
[in] LPARAM dwInitParam//参数
);

dwInitParam这个参数将会作为对话框WM_INITDIALOG消息的lParam参数传递给对话框过程,也就是把WndProc函数栈变量的地址交给了对话框过程AboutDlgProc,使得对话框过程函数可以操作WndProc的函数栈

AboutDlgProc中有两个关于ABOUTBOX_DATA结构体的变量,一个是指针类型,保管WndProc用WM_INITDIALOG消息的lParam参数指定的指针,用于向WndProc打报告

另一个是AboutDlgProc函数栈下的局部变量,这个是对话框过程自娱自乐用的,打报告的时候只需要拷贝该局部变量的情况

修改后的About2

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
183
184
185
186
187
188
189
/*------------------------------------------
ABOUT2.C -- About Box Demo Program No. 2
(c) Charles Petzold, 1998
------------------------------------------*/

#include <windows.h>
#include "resource.h"

typedef struct {//结构体定义
int iColor, iFigure;
}ABOUTBOX_DATA;


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("About2") ;
MSG msg ;
HWND hwnd ;
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, TEXT ("About Box Demo Program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

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

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}

void PaintWindow (HWND hwnd, int iColor, int iFigure)
{
static COLORREF crColor[8] = { RGB ( 0, 0, 0), RGB ( 0, 0, 255),
RGB ( 0, 255, 0), RGB ( 0, 255, 255),
RGB (255, 0, 0), RGB (255, 0, 255),
RGB (255, 255, 0), RGB (255, 255, 255) } ;

HBRUSH hBrush ;
HDC hdc ;
RECT rect ;

hdc = GetDC (hwnd) ;
GetClientRect (hwnd, &rect) ;
hBrush = CreateSolidBrush (crColor[iColor - IDC_BLACK]) ;
hBrush = (HBRUSH) SelectObject (hdc, hBrush) ;

if (iFigure == IDC_RECT)
Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
else
Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ;

DeleteObject (SelectObject (hdc, hBrush)) ;
ReleaseDC (hwnd, hdc) ;
}

void PaintTheBlock (HWND hCtrl, int iColor, int iFigure)
{
InvalidateRect (hCtrl, NULL, TRUE) ;
UpdateWindow (hCtrl) ;
PaintWindow (hCtrl, iColor, iFigure) ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance ;//应用程序实例句柄
static PAINTSTRUCT ps ;
static ABOUTBOX_DATA ad;//父窗口绘图依据

switch (message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
return 0 ;

case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDM_APP_ABOUT:
if (DialogBoxParam (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc,&ad))//ad地址交给子窗口,使其可以跨函数栈帧修改ad值
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
}
break ;

case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;

PaintWindow (hwnd, ad.iColor, ad.iFigure) ;//根据当前颜色当前形状绘图
return 0 ;

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

BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
static HWND hCtrlBlock ;
static ABOUTBOX_DATA ad,*pad;
switch (message)
{
case WM_INITDIALOG:
pad = (ABOUTBOX_DATA*)lParam;//pad用于承接父窗口传递的指针
ad = *pad;//ad是本函数绘图依据,不到万不得已不使用*pad指针


CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, ad.iColor) ;//第一个颜色和最后一个颜色,其间的宏定义都算,这一些选项中有且只有一个处于被选中状态
CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, ad.iFigure) ;

hCtrlBlock = GetDlgItem (hDlg, IDC_PAINT) ;//预览块矿建

SetFocus (GetDlgItem (hDlg, ad.iColor)) ;
return FALSE ;

case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDOK://点选了确认键,需要修改设置了
*pad = ad;
EndDialog (hDlg, TRUE) ;
return TRUE ;

case IDCANCEL:
EndDialog (hDlg, FALSE) ;
return TRUE ;

case IDC_BLACK:
case IDC_RED:
case IDC_GREEN:
case IDC_YELLOW:
case IDC_BLUE:
case IDC_MAGENTA:
case IDC_CYAN:
case IDC_WHITE:
ad.iColor = LOWORD (wParam) ;//LOWORD(wParam)携带控件ID
CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ;
PaintTheBlock (hCtrlBlock, ad.iColor,ad.iFigure ) ;//Block是预览块,实时根据选择改变,提供预览功能
return TRUE ;

case IDC_RECT:
case IDC_ELLIPSE:
ad.iFigure = LOWORD (wParam) ;
CheckRadioButton (hDlg, IDC_RECT, IDC_ELLIPSE, LOWORD (wParam)) ;
PaintTheBlock (hCtrlBlock, ad.iColor, ad.iFigure) ;
return TRUE ;
}
break ;

case WM_PAINT:
PaintTheBlock (hCtrlBlock, ad.iColor, ad.iFigure) ;
break ;
}
return FALSE ;
}

回顾单选互斥的实现

之前实现单选互斥,需要维护一组按钮的状态,当新按钮按下之前,先得把原来按下的按钮扣起来,在按下新按钮.

而现在只需要

1
CheckRadioButton(hDlg,idFirst,idLast,idCheck);

意思是,在hDlg对话框中,编号属于[idFirst,idLast]这个范围的单选按钮都是互斥的,idCheck决定按下哪一个.

这就要求从idFirst到idLast是连号的,idCheck属于这个范围

About3

自定义对话框控件

资源脚本

1
2
3
4
5
6
7
8
9
10
ABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 100
STYLE DS_MODALFRAME | WS_POPUP
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "OK",IDOK,"EllipPush",WS_GROUP | WS_TABSTOP,73,79,32,14//自定义控件,ID=IDOK,类名="EllipPush"
ICON "ABOUT3",IDC_STATIC,7,7,20,20
CTEXT "About3",IDC_STATIC,40,12,100,8
CTEXT "About Box Demo Program",IDC_STATIC,7,40,166,8
CTEXT "(c) Charles Petzold, 1998",IDC_STATIC,7,52,166,8
END

这里的类名就是注册窗口类时的类名,决定从该类创建的实例的属性

自定义控件过程函数

创建自定义控件类的时候需要注册其过程函数,其中WM_PAINT决定了该自定义控件类的外观

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
LRESULT CALLBACK EllipPushWndProc (HWND hwnd, UINT message, //自定义按钮类行为
WPARAM wParam, LPARAM lParam)
{
TCHAR szText[40] ;
HBRUSH hBrush ;
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;

switch (message)
{
case WM_PAINT ://绘图行为
GetClientRect (hwnd, &rect) ;
GetWindowText (hwnd, szText, lstrlen(szText)) ;//获取本子窗口的描述文字,写到szText数组里,即"OK"字样

hdc = BeginPaint (hwnd, &ps) ;

hBrush = CreateSolidBrush (GetSysColor (COLOR_WINDOW)) ;
hBrush = (HBRUSH) SelectObject (hdc, hBrush) ;
SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;
SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;

Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom) ;//绘制椭圆
DrawText (hdc, szText, -1, &rect,//椭圆中心写字
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

DeleteObject (SelectObject (hdc, hBrush)) ;

EndPaint (hwnd, &ps) ;
return 0 ;

case WM_KEYUP :
if (wParam != VK_SPACE)//按下空格键相当于按下鼠标左键,其他键忽略,交给DefWindowProc处理
break ;
// fall through
case WM_LBUTTONUP :
SendMessage (GetParent (hwnd), WM_COMMAND,//向父窗口即对话框发送WM_COMMAND消息,消息内容是本按钮的id
GetWindowLong (hwnd, GWL_ID), (LPARAM) hwnd) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}

非模态对话框

非模态对话框就是可以不理会对话框内容继续和父窗口交互

一般Ctrl+F召唤的查找框就是非模态的

使用CreateDialogA函数创建非模态对话框

1
2
3
4
5
6
void CreateDialogA(
[in, optional] hInstance,
[in] lpName,//对话框类名,或者类句柄
[in, optional] hWndParent,//父窗口句柄
[in, optional] lpDialogFunc//对话框回调函数
);

COLORS

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

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK ColorScrDlg(HWND, UINT, WPARAM, LPARAM);

HWND hDlgModeless; //非模态对话框的全局句柄,对任意函数可见,

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Colors2");
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(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = CreateSolidBrush(0L);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;

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

hwnd = CreateWindow(
szAppName, TEXT("Color Scroll"),
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,//不擦除子窗口的情况下,重绘父窗口
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

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

hDlgModeless = CreateDialog(hInstance, TEXT("ColorScrDlg"), hwnd, ColorScrDlg);//在winMain中创建非模态对话框,使用字符串索引资源,注册非模态对话框过程ColorScrDlg


while (GetMessage(&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage(hDlgModeless, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
DeleteObject((HGDIOBJ)SetClassLong(hwnd, GCL_HBRBACKGROUND,(LONG)GetStockObject(WHITE_BRUSH)));//删除注册窗口类时创建的逻辑画刷,避免内存泄漏
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

BOOL CALLBACK ColorScrDlg(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)//对话框过程回调
{
static int iColor[3];
HWND hwndParent, hCtrl;
int iCtrlID, iIndex;

switch (message)
{
case WM_INITDIALOG://初始化对话框
for (iCtrlID = 10; iCtrlID < 13; iCtrlID++)//三个滚动条的ID分别是10,11,12
{
hCtrl = GetDlgItem(hDlg, iCtrlID);//获取hDlg句柄对应的对话框上,ID是iCtrlID的控件的句柄,即滚动条控件的句柄
SetScrollRange(hCtrl, SB_CTL, 0, 255, FALSE);//设置滚动条范围0到255,恰好是RGB值的范围
SetScrollPos(hCtrl, SB_CTL, 0, FALSE);//设置滑块初始位置在0上
}
return TRUE;

case WM_VSCROLL:
hCtrl = (HWND)lParam;//是三个滚动条中的哪一个产生的消息
iCtrlID = GetWindowLong(hCtrl, GWL_ID);//获取id
iIndex = iCtrlID - 10;//转换成0,1,2下标//该下标用于索引iColor数组,iColor[0]表示红色色度条//同时iColor[0]值也表示了该条上的滑块位置
hwndParent = GetParent(hDlg);//获取父窗口也就是主窗口句柄

switch (LOWORD(wParam))
{
case SB_PAGEDOWN:
iColor[iIndex] += 15; // fall through//一页是滚动15+1个单位,但是最大不能超过255个单位
case SB_LINEDOWN:
iColor[iIndex] = min(255, iColor[iIndex] + 1);
break;
case SB_PAGEUP:
iColor[iIndex] -= 15; // fall through
case SB_LINEUP:
iColor[iIndex] = max(0, iColor[iIndex] - 1);
break;
case SB_TOP:
iColor[iIndex] = 0;
break;
case SB_BOTTOM:
iColor[iIndex] = 255;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK://拖动滑块
iColor[iIndex] = HIWORD(wParam);
break;
default:
return FALSE;
}
SetScrollPos(hCtrl, SB_CTL, iColor[iIndex], TRUE);//设置滑块位置
SetDlgItemInt(hDlg, iCtrlID + 3, iColor[iIndex], FALSE);//设置对话框控件文本,也就是表明当前色度的数值
//+3是将滚动条号转化为其上的解释性文字的id

DeleteObject((HGDIOBJ)SetClassLong(hwndParent, GCL_HBRBACKGROUND,//删除旧背景颜色
(LONG)CreateSolidBrush(RGB(iColor[0], iColor[1], iColor[2]))));//更新新背景颜色

InvalidateRect(hwndParent, NULL, TRUE);//父窗口全部失效,立刻通知重绘
return TRUE;
}
return FALSE;
}

对话框资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

COLORSCRDLG DIALOG DISCARDABLE 16, 16, 120, 141 //对话框名叫"COLORSCRDLG"
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION //非模态,弹出,可见,有标题
CAPTION "Color Scroll Scrollbars" //对话框标题
FONT 8, "MS Sans Serif" //字号字体
BEGIN
CTEXT "&Red",IDC_STATIC,8,8,24,8,NOT WS_GROUP //滚动条两头的解释性文字,没有逻辑作用
SCROLLBAR 10,8,20,24,100,SBS_VERT | WS_TABSTOP //滚动条, 数值方向滚动,制表停留
CTEXT "0",13,8,124,24,8,NOT WS_GROUP
CTEXT "&Green",IDC_STATIC,48,8,24,8,NOT WS_GROUP
SCROLLBAR 11,48,20,24,100,SBS_VERT | WS_TABSTOP
CTEXT "0",14,48,124,24,8,NOT WS_GROUP
CTEXT "&Blue",IDC_STATIC,89,8,24,8,NOT WS_GROUP
SCROLLBAR 12,89,20,24,100,SBS_VERT | WS_TABSTOP
CTEXT "0",15,89,124,24,8,NOT WS_GROUP
END

CreateDialog和DialogBox

CreateDialog创建非模态对话框的参数和DialogBox创建模态对话框的参数一模一样

两个函数的区别是是否持有控制

DialogBox调用之后控制会从父窗口过程函数转移给对话框过程函数,返回值是一个数

但是CreateDialog会立刻返回对话框的句柄,不会持有控制.

之所以CreateDialog需要返回句柄,是因为非模态对话框的父窗口是可以动的,它可能需要使用对话框句柄

新消息循环

非模态对话框的消息要进入WinMain中的消息循环,相当于一个子窗口,需要WinMain消息循环分拣派发消息

但是模态对话框的消息不需要,因为当模态对话框或者的时候,本程序的消息指定都是发往模态对话框的

考虑非模态对话框的WinMain消息循环就应该:

1
2
3
4
5
6
7
8
while (GetMessage (&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage (hDlgModeless, &msg))//如果没有非模态对话框句柄或者该消息不是非模态对话框的才往下走
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}

IsDialogMessage不止会判断该消息是不是非模态对话框的,并且会把是非模态对话框的消息发送

hDlgModeless是一个对WinMain和对话框过程都可见的全局变量,为啥要对WndProc也可见呢?

得想办法该关闭非模态对话框的时候就得关上吧,这个关闭怎么才能让WinMain知道?

修改hDlgModeless值为NULL,

谁来修改?WndProc吗?WndProc只负责父窗口的逻辑,关于非模态对话框它不管

因此需要非模态对话框自己决定关闭hDlgModeless

COLORS3,但是摆大烂

windows甚至把专门调色的对话框都准备好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <windows.h>
#include <commdlg.h>

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static CHOOSECOLOR cc ;
static COLORREF crCustColors[16] ;
cc.lStructSize = sizeof (CHOOSECOLOR) ;
cc.hwndOwner = NULL ;
cc.hInstance = NULL ;
cc.rgbResult = RGB (0x80, 0x80, 0x80) ;
cc.lpCustColors = crCustColors ;
cc.Flags = CC_RGBINIT | CC_FULLOPEN ;
cc.lCustData = 0 ;
cc.lpfnHook = NULL ;
cc.lpTemplateName = NULL ;

return ChooseColor(&cc) ;
}

就这么几行实现了一个调色的功能

image-20220824160618978

在老版本的windows绘图上,就是用的这个调色板,比如windows7上的

这不win7调色板吗,几天没见,这么拉了

实际上是调用ChooseColor(&cc)函数完成的,只需要给这个函数传递一个CHOOSECOLOR指针,就可以使用面板交互方式调制颜色了

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagCHOOSECOLORW {
DWORD lStructSize;//本结构体大小,冗余量
HWND hwndOwner;//调用ChooseColor函数产生的对话框的父窗口
HWND hInstance;//应用程序实例
COLORREF rgbResult;//RGB值,初始化一开始显式的颜色值
COLORREF *lpCustColors;//16色值,灰度,用于LowB电脑
DWORD Flags;//样式
LPARAM lCustData;//钩子过程参数
LPCCHOOKPROC lpfnHook;//钩子回调函数,需要Flags上置起CC_ENABLEHOOK标志
LPCWSTR lpTemplateName;//类名称或者ID
LPEDITMENU lpEditInfo;
} CHOOSECOLORW, *LPCHOOSECOLORW;

HEXCALC

不到150行源代码实现一个16进制计算器

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
#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HexCalc") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = DLGWINDOWEXTRA ; // Note!//30bytes
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;

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

hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;//创建非模态对话框

ShowWindow (hwnd, iCmdShow) ;//把对话框作为窗口

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}

void ShowNumber (HWND hwnd, UINT iNumber)
{
TCHAR szBuffer[20] ;

wsprintf (szBuffer, TEXT ("%X"), iNumber) ;
SetDlgItemText (hwnd, VK_ESCAPE, szBuffer) ;//设置VK_ESCAPE控件的描述字
}

DWORD CalcIt (UINT iFirstNum, int iOperation, UINT iNum)
{
switch (iOperation)
{
case '=': return iNum ;
case '+': return iFirstNum + iNum ;
case '-': return iFirstNum - iNum ;
case '*': return iFirstNum * iNum ;
case '&': return iFirstNum & iNum ;
case '|': return iFirstNum | iNum ;
case '^': return iFirstNum ^ iNum ;
case '<': return iFirstNum << iNum ;
case '>': return iFirstNum >> iNum ;
case '/': return iNum ? iFirstNum / iNum: MAXDWORD ;
case '%': return iNum ? iFirstNum % iNum: MAXDWORD ;
default : return 0 ;
}
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static BOOL bNewNumber = TRUE ;
static int iOperation = '=' ;
static UINT iNumber, iFirstNum ;
HWND hButton ;

switch (message)
{
case WM_KEYDOWN: // left arrow --> backspace
if (wParam != VK_LEFT)
break ;
wParam = VK_BACK ;
// fall through
case WM_CHAR:
if ((wParam = (WPARAM) CharUpper ((TCHAR *) wParam)) == VK_RETURN)
wParam = '=' ;

if (hButton = GetDlgItem (hwnd, wParam))//字符消息的wParam就是字符的ASCII码,而对话框脚本中我们也把按钮的ID设置成了对应字符的ASCII码
{
SendMessage (hButton, BM_SETSTATE, 1, 0) ;//按下状态
Sleep (100) ;//按下状态持续0.1秒,让人能看见
SendMessage (hButton, BM_SETSTATE, 0, 0) ;//起来
}
else
{
MessageBeep (0) ;//没点到按钮就叫唤
break ;
}
// fall through
case WM_COMMAND:
SetFocus (hwnd) ;

if (LOWORD (wParam) == VK_BACK) // backspace
ShowNumber (hwnd, iNumber /= 16) ;//16进制数右移一位

else if (LOWORD (wParam) == VK_ESCAPE) // escape
ShowNumber (hwnd, iNumber = 0) ;

else if (isxdigit (LOWORD (wParam))) // hex digit//判断是否是16进制数,0-9,A-F
{
if (bNewNumber)//如果是新的计算局面
{
iFirstNum = iNumber ;//第一个操作数置为iNumber
iNumber = 0 ;//第二个操作数尚未到来,虚位以待
}
bNewNumber = FALSE ;//置局面混乱

if (iNumber <= MAXDWORD >> 4)
ShowNumber (hwnd, iNumber = 16 * iNumber + wParam -
(isdigit (wParam) ? '0': 'A' - 10)) ;
else
MessageBeep (0) ;
}
else // operation
{
if (!bNewNumber)//如果不是16进制数,即不是操作数,那就是操作符了
ShowNumber (hwnd, iNumber =CalcIt (iFirstNum, iOperation, iNumber)) ;
bNewNumber = TRUE ;
iOperation = LOWORD (wParam) ;
}
return 0 ;

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

窗口额外

cbClsExtra :windows程序为每一个窗口设计类管理一个WNDCLASS结构。在应用程序注册一个窗口类的时候,可以让windows分配一定字节空间的内存,这部分内存成为类的附件内存,有属于这个窗口类的所有窗口共享,类附件内存信息用于存储窗口类的附加信息。windows系统将这部分内存初始化为0,因此我们经常设置此参数为0.

cbWndExtra :windows程序为每一个窗口管理一个内部数据结构,在注册窗口类的时候,系统可以为每一个窗口分配一定的字节数的附加内存空间,称为窗口附件内存。应用程序可使用这部分内存存储窗口特有的数据,windows系统把这部分内存初始化为0.

HEXCALC.DLG

这个文件不在rc文件中,而是独立成文件,然后被包含进入RC文件

为啥不能直接放到RC里呢?因为visual studio没有添加对话框资源的选项

image-20220824165419486

这个对话框脚本是手打的,然后在资源脚本RC中导入:

1
2
3
4
5
3 TEXTINCLUDE DISCARDABLE 
BEGIN
"#include ""hexcalc.dlg""\r\n"
"\0"
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
/*---------------------------
HEXCALC.DLG dialog script
---------------------------*/

HexCalc DIALOG -1, -1, 102, 122
STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX//层叠,标题,系统菜单,最小方框
CLASS "HexCalc"//类名HexCalc
CAPTION "Hex Calculator"
{
PUSHBUTTON "D", 68, 8, 24, 14, 14//按钮 "D",字符68位置(8,24)宽高14
PUSHBUTTON "A", 65, 8, 40, 14, 14
PUSHBUTTON "7", 55, 8, 56, 14, 14
PUSHBUTTON "4", 52, 8, 72, 14, 14
PUSHBUTTON "1", 49, 8, 88, 14, 14
PUSHBUTTON "0", 48, 8, 104, 14, 14
PUSHBUTTON "0", 27, 26, 4, 50, 14
PUSHBUTTON "E", 69, 26, 24, 14, 14
PUSHBUTTON "B", 66, 26, 40, 14, 14
PUSHBUTTON "8", 56, 26, 56, 14, 14
PUSHBUTTON "5", 53, 26, 72, 14, 14
PUSHBUTTON "2", 50, 26, 88, 14, 14
PUSHBUTTON "Back", 8, 26, 104, 32, 14
PUSHBUTTON "C", 67, 44, 40, 14, 14
PUSHBUTTON "F", 70, 44, 24, 14, 14
PUSHBUTTON "9", 57, 44, 56, 14, 14
PUSHBUTTON "6", 54, 44, 72, 14, 14
PUSHBUTTON "3", 51, 44, 88, 14, 14
PUSHBUTTON "+", 43, 62, 24, 14, 14
PUSHBUTTON "-", 45, 62, 40, 14, 14
PUSHBUTTON "*", 42, 62, 56, 14, 14
PUSHBUTTON "/", 47, 62, 72, 14, 14
PUSHBUTTON "%", 37, 62, 88, 14, 14
PUSHBUTTON "Equals", 61, 62, 104, 32, 14
PUSHBUTTON "&&", 38, 80, 24, 14, 14
PUSHBUTTON "|", 124, 80, 40, 14, 14
PUSHBUTTON "^", 94, 80, 56, 14, 14
PUSHBUTTON "<", 60, 80, 72, 14, 14
PUSHBUTTON ">", 62, 80, 88, 14, 14
}

全是按钮的对话框,但是没有定义动作,我们需要给每个按钮按下之后的效果编程

这个怎么实现的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case WM_CHAR:
if ((wParam = (WPARAM) CharUpper ((TCHAR *) wParam)) == VK_RETURN)
wParam = '=' ;

if (hButton = GetDlgItem (hwnd, wParam))//字符消息的wParam就是字符的ASCII码,而对话框脚本中我们也把按钮的ID设置成了对应字符的ASCII码
{
SendMessage (hButton, BM_SETSTATE, 1, 0) ;//按下状态
Sleep (100) ;//按下状态持续0.1秒,让人能看见,知道自己按下去了
SendMessage (hButton, BM_SETSTATE, 0, 0) ;//起来
}
else
{
MessageBeep (0) ;//没点到按钮就叫唤
break ;
}

对话框脚本中故意设置按钮id和ASCII字符编码相同,因此非常方便

对话框模板

在COLORS3中,我们已经看到摆烂的力量,像调色板这种对话框模板再来一万个也不多

比如查找替换对话框

这些预定义好的对话框模板,都是以一个函数传递一个结构体指针创建的,这些结构体在commdlg.h中

用啥查啥的文档吧