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) ; break ; } return 0 ;
1 2 3 4 5 6 void DialogBoxA ( [in, optional] hInstance, [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) { switch (message) { case WM_INITDIALOG : return TRUE ; case WM_COMMAND : switch (LOWORD (wParam)) { case IDOK : case IDCANCEL : 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 BEGIN DEFPUSHBUTTON "OK" ,IDOK,66 ,81 ,50 ,14 ICON "ABOUT1" ,IDC_STATIC,7 ,7 ,20 ,20 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 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 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]) ; 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)) ; 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) ; CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ; PaintTheBlock (hCtrlBlock, iColor, iFigure) ; 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) ; UpdateWindow (hCtrl) ; PaintWindow (hCtrl, iColor, iFigure) ; }
回顾父子通信方式
父窗口就只负责主窗口客户区的颜色和图形绘制,对话框负责调这个颜色和图形
父窗口和对话框如何进行通信的?或者说对话框是如何通知父窗口用新样式绘图的呢?
关键在于两个对父过程和对话框过程都可见的全局变量,对话框使用这两个变量间接通知父窗口
并且在对话框关闭之后,父窗口会立刻重绘,这就使得颜色样式更新显得没有延迟
父窗口中,关闭对话框之后的立刻更新:
1 2 3 case IDM_APP_ABOUT: if (DialogBox (hInstance, TEXT ("AboutBox" ), hwnd, AboutDlgProc)) 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, [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 #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)) 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; 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) ; CheckRadioButton (hDlg, IDC_BLACK, IDC_WHITE, LOWORD (wParam)) ; PaintTheBlock (hCtrlBlock, ad.iColor,ad.iFigure ) ; 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 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)) ; 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) break ; case WM_LBUTTONUP : SendMessage (GetParent (hwnd), WM_COMMAND, 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); 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++) { hCtrl = GetDlgItem(hDlg, iCtrlID); SetScrollRange(hCtrl, SB_CTL, 0 , 255 , FALSE); SetScrollPos(hCtrl, SB_CTL, 0 , FALSE); } return TRUE; case WM_VSCROLL: hCtrl = (HWND)lParam; iCtrlID = GetWindowLong(hCtrl, GWL_ID); iIndex = iCtrlID - 10 ; hwndParent = GetParent(hDlg); switch (LOWORD(wParam)) { case SB_PAGEDOWN: iColor[iIndex] += 15 ; case SB_LINEDOWN: iColor[iIndex] = min(255 , iColor[iIndex] + 1 ); break ; case SB_PAGEUP: iColor[iIndex] -= 15 ; 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); 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 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; HWND hInstance; COLORREF rgbResult; COLORREF *lpCustColors; DWORD Flags; LPARAM lCustData; LPCCHOOKPROC lpfnHook; LPCWSTR lpTemplateName; 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 ; 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) ; } 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: if (wParam != VK_LEFT) break ; wParam = VK_BACK ; case WM_CHAR: if ((wParam = (WPARAM) CharUpper ((TCHAR *) wParam)) == VK_RETURN) wParam = '=' ; if (hButton = GetDlgItem (hwnd, wParam)) { SendMessage (hButton, BM_SETSTATE, 1 , 0 ) ; Sleep (100 ) ; SendMessage (hButton, BM_SETSTATE, 0 , 0 ) ; } else { MessageBeep (0 ) ; break ; } case WM_COMMAND: SetFocus (hwnd) ; if (LOWORD (wParam) == VK_BACK) ShowNumber (hwnd, iNumber /= 16 ) ; else if (LOWORD (wParam) == VK_ESCAPE) ShowNumber (hwnd, iNumber = 0 ) ; else if (isxdigit (LOWORD (wParam))) { if (bNewNumber) { iFirstNum = iNumber ; iNumber = 0 ; } bNewNumber = FALSE ; if (iNumber <= MAXDWORD >> 4 ) ShowNumber (hwnd, iNumber = 16 * iNumber + wParam - (isdigit (wParam) ? '0' : 'A' - 10 )) ; else MessageBeep (0 ) ; } else { if (!bNewNumber) 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 DIALOG -1 , -1 , 102 , 122 STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX CLASS "HexCalc" CAPTION "Hex Calculator" { PUSHBUTTON "D" , 68 , 8 , 24 , 14 , 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)) { SendMessage (hButton, BM_SETSTATE, 1 , 0 ) ; Sleep (100 ) ; SendMessage (hButton, BM_SETSTATE, 0 , 0 ) ; } else { MessageBeep (0 ) ; break ; }
对话框脚本中故意设置按钮id和ASCII字符编码相同,因此非常方便
对话框模板
在COLORS3中,我们已经看到摆烂的力量,像调色板这种对话框模板再来一万个也不多
比如查找替换对话框
这些预定义好的对话框模板,都是以一个函数传递一个结构体指针创建的,这些结构体在commdlg.h中
用啥查啥的文档吧