Win32 GUI编程的入门章节
Win32编程应该从以下两章入门
User Interface\Windows User Interface
Graphics and Multimedia\Windows GDI
尽管很多框架非常方便,有时候我们还是希望尽量减少程序体积和复杂度,甚至连一个*.XXXXXXnfig都不想要,这时候用C/C++调用DialogBox创建Win32对话框是最好的选项。
而为了更好地使用Win32对话框,需要学习Win32标准控件的使用。学习使用Win32标准控件的最佳途径是边练习边参考Windows SDK Documentation。
新版Windows SDK已经不带帮助文档,而Visual Studio自带的MSDN帮助文档的往往故意砍掉了最重要的User Interface\Windows User Interface一节,所以还是需要下载一份Windows SDK Documentation的。
WinSDK文档的下载可以在我的另一篇帖子里找到。
日常加载代码如下:
<code class="language-cpp">#include <windows.h> #include <tchar.h> #include <windowsx.h> #include <commctrl.h> #include <commdlg.h> #pragma comment(lib, "comctl32.lib") INT_PTR CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd) { // comctl32.dll v4.70-5.81 compatible HMODULE hcomctl32 = LoadLibrary(TEXT("comctl32.dll")); INITCOMMONCONTROLSEX icc = { sizeof icc }; typedef BOOL(WINAPI*TInitCommonControlsEx)(LPINITCOMMONCONTROLSEX); TInitCommonControlsEx pInitCommonControlsEx = NULL; if (hcomctl32) pInitCommonControlsEx = (TInitCommonControlsEx)GetProcAddress(hcomctl32, "InitCommonControlsEx"); if (pInitCommonControlsEx) for (int i = 0; i < 16; i++) icc.dwICC = 1 << i, pInitCommonControlsEx(&icc); // riched20.dll LoadLibrary(TEXT("riched20.dll")); DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc, NULL); return 0; } </commdlg.h></commctrl.h></windowsx.h></tchar.h></windows.h></code>
控件之外
User Interface、Graphics and Multimedia两章与用户界面有关,应该通读这些章节。
System Services、Networking等其它章节包含了大多数非用户界面的编程,也应该尽量多地了解。但是尽量还是在读完上面两章之后进行。
MFC与Win32的差别
经典MFC(MFC9.1之前的版本)除了这些控件之外,还有一些私有功能,比如静态窗口分割、动态窗口分割、浮动工具栏、ActiveX控件等。
实际上,这些东西不一定非要和MFC实现得完全一致。
工具软件用得最多的是静态分割。动态分割、浮动工具栏很少使用。
实际上,静态分割用鼠标事件代码很好实现:
至于拖动阴影,虽然有的软件也没有实现,但是它可以在拖动的时候防止闪烁,提高用户体验,还是实现一下比较好。拖动阴影的实现方法:建立一个单色位图画刷,然后用PatBlt函数对矩形区域进行PATINVERT。
<code class="language-cpp">void InvertFillFocusRect(HDC hdc, int x, int y, int cx, int cy) { WORD patbits[] = { 0x80, 0x40 }; HBITMAP hpatbmp = CreateBitmap(2, 2, 1, 1, patbits); HBRUSH hpatbr = CreatePatternBrush(hpatbmp); HGDIOBJ holdbr = SelectObject(hdc, hpatbr); PatBlt(hdc, x, y, cx, cy, PATINVERT); SelectObject(hdc, holdbr); DeleteObject(hpatbr); DeleteObject(hpatbmp); } </code>
ActiveX功能主要用于嵌入WebBrowser控件,但是随着IE的退出竞争,这种方法越来越不好用了。现在一般使用CEF。实际上,如果使用WebBrowser,就要同时面对IE8/9/10/11四个版本,其中IE8经常导致排版混乱,甚至莫名其妙弹出一些对话框,大大降低用户体验。
但是CEF太臃肿了。对于不准备植入广告的工具软件来说,还是尽量用基于XmlHttpRequest或WebSockets的开放应用接口。实在没有可用的开放应用接口的话,再考虑使用正则表达式等工具分析HTTP文本。
[修改于 7年7个月前 - 2017/05/17 17:08:35]
Win32 GUI编程的入门章节
Win32编程应该从以下两章入门
User Interface\Windows User Interface
Graphics and Multimedia\Windows GDI
Win32控件的分类
这里先不讨论ImageList等控件组件、对话框等,只讨论有类型名称的Win32控件。
可以从下边的列表看出来,大部分控件的宏定义名称都是以WC_开头的,另外有8个是以_CLASS结尾的,工具栏、状态栏、ReBar则是以CLASSNAME结尾。
这些控件所需要的加载代码是不同的:
除此之外,预定义控件大多使用WM_COMMAND等比较老的通知消息;而通用控件大多使用WM_NOTIFY等比较新的通知消息,富文本框则两者都有。但是也并不总是这样的,比如加载comctl32.dll v6.0以后,部分预定义控件也会发出WM_NOTIFY消息。
预定义控件(user32.dll):
<code class="language-txt">WC_BUTTON "BUTTON" // Button,CheckBox,RadioButton,GroupBox WC_COMBOBOX "COMBOBOX" WC_EDIT "EDIT" WC_LISTBOX "LISTBOX" WC_SCROLLBAR "SCROLLBAR" // Horizontal,Vertical WC_STATIC "STATIC" // StaticText,PictureControl </code>
富文本框(richedit.dll、riched20.dll、msftedit.dll):
<code class="language-txt">RICHEDIT_CLASS10A "RICHEDIT" // richedit.dll RICHEDIT_CLASS(A/W) "RichEdit20A/W" // riched20.dll MSFTEDIT_CLASS "RICHEDIT50W" // msftedit.dll (WinXPSP1+) </code>
Win95通用控件(comctl32.dll v4.0):
<code class="language-txt">WC_LISTVIEW "SysListView32" // 0x0001 WC_HEADER "SysHeader32" // 0x0001 WC_TREEVIEW "SysTreeView32" // 0x0002 WC_TABCONTROL "SysTabControl32" // 0x0008 TOOLBARCLASSNAME "ToolbarWindow32" // 0x0004 STATUSCLASSNAME "msctls_statusbar32 // 0x0004 TRACKBAR_CLASS "msctls_trackbar32" // 0x0004 UPDOWN_CLASS "msctls_updown32" // 0x0010 PROGRESS_CLASS "msctls_progress32" // 0x0020 HOTKEY_CLASS "msctls_hotkey32" // 0x0040 ANIMATE_CLASS "SysAnimate32" // 0x0080 TOOLTIPS_CLASS "tooltips_class32" // 0x0002,0x0004,0x0008 </code>
IE3.0通用控件(comctl32.dll v4.70):
<code class="language-txt">WC_COMBOBOXEX "ComboBoxEx32" // 0x0200 REBARCLASSNAME "ReBarWindow32" // 0x0400 MONTHCAL_CLASS "SysMonthCal32" // 0x0100 DATETIMEPICK_CLASS "SysDateTimePick32" // 0x0100 </code>
Win98/IE4.0通用控件(comctl32.dll v4.71,v4.72,v5.80,v5.81,v5.82):
<code class="language-txt">WC_IPADDRESS "SysIPAddress32" // 0x0800 WC_PAGESCROLLER "SysPager" // 0x1000 WC_NATIVEFONTCTL "NativeFontCtl" // 0x2000 (这个控件好像没有官方介绍) </code>
WinXP/Vista通用控件(comctl32.dll v6.0):
<code class="language-txt">(重新注册预定义控件) // 0x4000 (comctl32.dll v6.0, WinXP+, 自动) WC_LINK "SysLink" // 0x8000 (comctl32.dll v6.0, WinXP+, 自动) WC_BUTTON "BUTTON" // SplitButton,CommandLink (comctl32.dll v6.0, WinVista+, 自动) </code>
WinVista网络地址控件(shell32.dll):
<code class="language-txt">WC_NETADDRESS "msctls_netaddress" // shell32.dll (WinVista+) </code>
comctl32.dll用5.82还是6.0
现在的系统上通用控件库有两个版本,comctl32 v5.82和comctl32 v6.0。两者的区别主要是以下几点:
5.82已经包含了绝大多数程序逻辑所必需的功能。6.0风格经常跟着系统变,还需要manifest,用起来确实麻烦一些。
如果比较在意程序风格的统一性和兼容性,建议使用5.82;如果比较在意程序的美观性,也可以使用6.0。
完整集成所有标准控件的代码:
<code class="language-cpp">#include <windows.h> #include <tchar.h> #include <windowsx.h> #include <commctrl.h> #include <commdlg.h> #pragma comment(lib, "comctl32.lib") INT_PTR CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd) { // comctl32.dll v6.0 if exists #ifdef USE_COMCTL32_V6 #if defined _M_IX86 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") #elif defined _M_IA64 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") #elif defined _M_X64 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") #else #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #endif #endif // comctl32.dll v4.70-5.81 compatible HMODULE hcomctl32 = LoadLibrary(TEXT("comctl32.dll")); INITCOMMONCONTROLSEX icc = { sizeof icc }; typedef BOOL(WINAPI*TInitCommonControlsEx)(LPINITCOMMONCONTROLSEX); TInitCommonControlsEx pInitCommonControlsEx = NULL; if (hcomctl32) pInitCommonControlsEx = (TInitCommonControlsEx)GetProcAddress(hcomctl32, "InitCommonControlsEx"); if (pInitCommonControlsEx) for (int i = 0; i < 16; i++) icc.dwICC = 1 << i, pInitCommonControlsEx(&icc); // riched32.dll,riched20.dll,msftedit.dll LoadLibrary(TEXT("riched32.dll")); LoadLibrary(TEXT("riched20.dll")); LoadLibrary(TEXT("msftedit.dll")); // shell32.dll HMODULE hshell32 = LoadLibrary(TEXT("shell32.dll")); FARPROC pInitNetworkAddressControl = NULL; if (hshell32) pInitNetworkAddressControl = GetProcAddress(hshell32, "InitNetworkAddressControl"); if (pInitNetworkAddressControl) pInitNetworkAddressControl(); DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc, NULL); return 0; } </commdlg.h></commctrl.h></windowsx.h></tchar.h></windows.h></code>
系统内置对话框
MessageBox(Ex|Indirect):最简单的消息框,功能有限,但有时候非常方便。
TaskDialog(Indirect):用于Windows Vista+,需要声明comctl32 v6.0,可用于创建Vista风格的消息框。
通用对话框(Common Dialogs):ChooseColor、ChooseFont、GetOpenFileName、GetSaveFileName、PageSetupDlg、PrintDlg、PrintDlgEx、FindText、ReplaceText等。
Shell对话框:ShellAbout、SHBrowseForFolder、RestartDialog(Ex)等。
Toolbar的“自定义工具栏”对话框。
自定义模板对话框
DialogBox(Param):用于按照rc资源中的对话框模板创建模态对话框,这种对话框使用对话框回调而不是窗口回调,并且会阻塞程序运行和父窗口获得焦点,因此也称独占式对话框。
CreateDialog(Param):用于按照rc资源中的对话框模板创建非模态对话框,这种对话框拥有与DialogBox一样的对话框回调,但是不会阻塞程序运行。
<code class="language-cpp">MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { if (IsDialogMessage(hDlg, &msg)) continue; TranslateMessage(&msg); DispatchMessage(&msg); } </code>
PropertySheet:用于创建带选项卡的对话框,也可用于创建向导界面。控制面板上很多对话框也是使用PropertySheet函数弹出的,并不是直接使用DialogBox,比如“日期与时间”对话框与“添加硬件”向导。
关于Network Address Control
Windows Vista增加了一个控件,叫做Network Address Control。这个控件不是在comctl32.dll里面,而是在shell32.dll里面。
这个控件在观感上和Edit控件没有明显的区别,但是可以提供各种类型的网络地址验证、自动转换数据类型、弹出错误提示的功能,支持IPv4、IPv6、域名等各种网络地址。
使用这个控件之前,需要使用InitNetworkAddressControl函数:
<code class="language-cpp">HMODULE hshell32 = LoadLibrary(TEXT("shell32.dll")); FARPROC pInitNetworkAddressControl = NULL; if (hshell32) pInitNetworkAddressControl = GetProcAddress(hshell32, "InitNetworkAddressControl"); if (pInitNetworkAddressControl) pInitNetworkAddressControl(); </code>
DLL版本的一些细节
下面这个文章介绍了一些没有被微软写进文档里的细节,但是最好选择性地参考,不要依赖一些微软没有写进文档的做法,可能会降低程序的兼容性。
XXXXXXXXXXXXXXXXXXXXXXXXXXXX/studies/windows/shell/comctl32/
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/murrays/2006/10/13/richedit-versions/
comctl32.dll版本的区别
传统版本:
需要manifest的版本:
关于SetWindowSubclass等函数
comctl32.dll中包含有SetWindowSubclass,GetWindowSubclass,RemoveWindowSubclass,DefSubclassProc四个函数,可以实现可拆卸的窗口过程替换。
不过个人并不推荐按序号使用这四个函数,因为微软并没有把这四个函数的序号写进文档里。推荐的做法:
riched20.dll v2.0和v3.0的区别
Win98/98SE/NT4自带的riched20.dll版本号是v2.0(文件版本v5.0),功能不全,不过我们可以用instmsia.exe和instmsiw.exe将其更新成v3.0(文件版本v5.30)。Win2000/Me/XP/2003的riched20.dll是v3.0,WinVista以上的riched20.dll是v3.1(文件版本v5.31)。
v2.0和v3.0下载:
一般来说,应该总是认为riched20.dll是v3.0版本,并使用"RichEdit20A"和"RichEdit20W"。除此之外,也可以使用WinXPSP1附带的msftedit.dll(v4.1,"RichEdit50W")。虽然Office 2003/2007/2010里边的riched20.dll版本更高,但是没有官方文档,用起来问题比较多。
判断DLL版本的方法
comctl32.dll导出了一个叫做DllGetVersion的函数,可以获取DLL版本。不过它没有被放到XXXXXXXXXXXb中,因为还有shell32.dll和shlwapi.dll也导出了这个函数。因此需要使用GetProcAddress调用它。它的原型在shlwapi.h头文件中定义为DLLGETVERSIONPROC。
不过由于像riched20.dll之类的大多数DLL都没有导出DllGetVersion函数,因此个人建议通过读取RT_VERSION资源的方式判断DLL文件版本号,这种方法才是最通用的方法。
这里要注意的是,riched20.dll版本v2.0的实际文件版本号是v5.0,而riched20.dll版本v3.0的实际文件版本号是v5.30。
<code class="language-cpp">#include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <shlwapi.h> #include <tchar.h> //#include <strsafe.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "version.lib") BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CheckModuleFileVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullFileVersion = ((ULONGLONG)pvsffi->dwFileVersionMS << 32) + pvsffi->dwFileVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullFileVersion < ullReqVersion) return FALSE; return TRUE; } BOOL CheckModuleProductVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullProductVersion < ullReqVersion) return FALSE; return TRUE; } void NoSupportedDllExit() { MessageBox(NULL, _T("此操作系统需要安装Internet Explorer 5.01和Windows Installer 1.0或以上版本的相应组件以运行此程序。"), _T("错误"), MB_ICONERROR); exit(1); } int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd) { // 检查系统支持 #ifdef UNICODE if (!CheckModuleProductVersion(_T("kernel32.dll"), 5, 0, 0, 0)) #else if (!CheckModuleProductVersion(_T("kernel32.dll"), 4, 10, 0, 0)) #endif { MessageBox(NULL, _T("此操作系统不受支持。"), _T("错误"), MB_ICONERROR); exit(1); } // comctl32.dll v4.70-5.81 compatible HMODULE hcomctl32 = LoadLibrary(TEXT("comctl32.dll")); INITCOMMONCONTROLSEX icc = { sizeof icc }; typedef BOOL(WINAPI*TInitCommonControlsEx)(LPINITCOMMONCONTROLSEX); TInitCommonControlsEx pInitCommonControlsEx = NULL; if (hcomctl32) pInitCommonControlsEx = (TInitCommonControlsEx)GetProcAddress(hcomctl32, "InitCommonControlsEx"); if (pInitCommonControlsEx) for (int i = 0; i < 16; i++) icc.dwICC = 1 << i, pInitCommonControlsEx(&icc); // riched20.dll LoadLibrary(TEXT("riched20.dll")); // 要求comctl32.dll版本v5.81和riched20.dll版本v3.0(实际文件版本v5.30) if (!CheckModuleFileVersion(_T("comctl32.dll"), 5, 81, 0, 0) || !CheckModuleFileVersion(_T("riched20.dll"), 5, 30, 0, 0)) NoSupportedDllExit(); DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc, NULL); return 0; } </stdlib.h></stdio.h></strsafe.h></tchar.h></shlwapi.h></commctrl.h></windowsx.h></windows.h></code>
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |
200字以内,仅用于支线交流,主线讨论请采用回复功能。