简单的 ATL 基于对话框的可执行文件






4.36/5 (11投票s)
2004年4月29日
5分钟阅读

110336

1710
如何仅使用 ATL 创建一个基于对话框的简单程序
引言
你是否曾经需要编写一个简单的、基于对话框的 exe 来做一些简单的事情?如果是,那就太好了,因为我可以用简短的篇幅告诉你如何做到,而且只需要使用 ATL。
背景
ATL 非常适合开发轻量级的 COM 组件,其依赖项最少。这些组件可以包含在 DLL 或 EXE 中。此外,ATL 向导提供了一个选项,可以基于 CAxDialogImpl<>
模板创建 ATL 对象,那么为什么不利用这些选项来创建一个独立的 exe 呢?没有理由不这样做,对吧?那么,我们开始吧。
制作方法
- 启动一个新项目。
- 选择 ATL COM 应用程序向导
- 为你的项目选择一个名称,我将使用 DlgTest
- 单击“下一步”
- 在“服务器类型”中,选择“可执行文件 (EXE)”选项
- 完成并确定。在工作区的“类视图”选项卡中,右键单击树根,然后从菜单中选择“新建 ATL 对象...”在“ATL 对象向导”窗口中,切换到“Miscellaneous”(杂项)类别
- 这里选项不多,所以选择“Dialog”(对话框),然后点击“Next >”(下一步 >)
- 选择一个名称,我将使用 MainDlg,所以我的类实际上将是
CMainDlg
,然后点击确定。稍等,马上就好。
现在让我们创建我们对话框类的一个实例。
展开“类视图”树中的“Global”(全局)文件夹,然后双击 _tWinMain
函数,它将打开 DlgTest.cpp 文件并显示该函数的代码。向下滚动到接近末尾的地方,直到看到以下几行
MSG msg; while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
在循环之前,创建一个 CMainDlg
(记住这是我选择的名称)的实例,并显示对话框。
CMainDlg dlg; //Create an istance of this class dlg.Create(NULL); //Create the main window, //the NULL parameter means the desktop //will be the parent window dlg.ShowWindow(SW_SHOWNORMAL); //Show it, //acording with the SDK, the first time a window is showed //the parameter should be SW_SHOWNORMAL instead of SW_SHOW别忘了在文件开头添加
#include "MainDlg.h" //the dialog's header file
在这个文件的开头。我知道,你想尝尝味道,好吧,去编译并运行它。但它还没准备好……
所以,它运行了,显示了带有 2 个默认按钮的对话框,但是当你点击其中任何一个按钮时,你会得到一个断言,我告诉过你,它还没准备好。
但没关系,我们在烹饪的同时也可以学到一些东西。发生的情况是,对话框是使用 Create()
成员函数创建为无模式的,但向导的默认实现假定它是模式的。因此,当调用 OnOK
或 OnCancel
函数时,默认操作只是调用 EndDialog
,这并不是无模式对话框所期望的。为了解决这个问题,让我们用 DestroyWindow
替换 EndDialog
,这是销毁无模式对话框的正确方法。还有一件小事,这是我们唯一的窗口,所以如果它被销毁,整个应用程序应该关闭。所以,我们也来做这件事,通过调用 PostQuitMessage
来实现。回到我们的制作方法。
LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0; } LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0; }让我们用
DestroyWindow
替换 EndDialog
,并添加 PostQuitMessage
,所以它们看起来应该是这样的LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { DestroyWindow(); //Destroy this window PostQuitMessage(0); //Tell's the main loop to stop and exit returning then code 0 return 0; } LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { DestroyWindow(); //Destroy this window PostQuitMessage(0); //Tell's the main loop to //stop and exit returning then code 0 return 0; }完成。现在它应该可以运行并优雅地关闭了。
不错,但是……没什么用,好吧,我知道,我们忘了调味。让我们加点香料,看看会得到什么。
一个窗体窗口?
是的,这个基于对话框的应用程序可以看起来和感觉像一个基于窗体的应用程序,我来告诉你怎么做。首先,我们去掉按钮。从对话框模板中,删除两个按钮。但现在我们需要另一种方法来结束程序。让我们添加一个菜单,在资源树上,右键单击并选择“Insert...”(插入...),选择“menu”(菜单)并点击“New”(新建)。让我们在菜单中添加 4 个项目,&File 作为顶级菜单,然后输入 &New,一个分隔符,然后是 E&xit。打开项目的属性,为 &New 选择 IDIDNEW
作为 id,为 E&xit 选择 IDIDCLOSE
。
我们将分别使用这个清理和关闭应用程序。回到对话框模板,打开其属性,在“General”(常规)选项卡中,从“Menu”(菜单)组合框中选择菜单 id。转到“Styles”(样式)选项卡,将“Border”(边框)更改为“Resizing”(可调整大小),你也可以添加“Minimize”(最小化)和“Maximize”(最大化)框。我们还可以添加一个文本框,让它做点什么。将文本框的属性更改为“Multiline”(多行)、“Vertical”(垂直)和“Horizontal scroll”(水平滚动),以及“Auto HScroll”(自动水平滚动)和“AutoVScroll”(自动垂直滚动)。我们完成了模板的设置,现在回到我们的类并添加一些代码。
打开类的头文件,我们不再需要处理 IDOK
和 IDCANCEL
处理程序了,记住我们删除了按钮,所以从消息映射部分删除这两行。
BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) //DEL COMMAND_ID_HANDLER(IDOK, OnOK) //DEL COMMAND_ID_HANDLER(IDCANCEL, OnCancel) END_MSG_MAP()
以及函数定义
//DEL LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) //DEL { //DEL DestroyWindow(); //Destroy this window //DEL PostQuitMessage(0); //Tell's the main loop to //DEL // stop and exit returning then code 0 //DEL return 0; //DEL } //DEL LRESULT OnCancel(WORD wNotifyCode, //DEL WORD wID, HWND hWndCtl, BOOL& bHandled) //DEL { //DEL DestroyWindow(); //Destroy this window //DEL PostQuitMessage(0); //DEL //Tell's the main loop to stop and exit returning then code 0 //DEL return 0; //DEL }现在我们想处理来自菜单的消息,所以让我们在消息映射部分添加处理程序
BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_COMMAND, OnMenu) END_MSG_MAP()
以及实现
LRESULT OnMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { WORD itemId = (WORD)(wParam & 0xFFFF); //The low-order word specifies the identifier of the menu item, control, or //accelerator. switch(itemId) { case IDNEW: //Process here the New menu item break; case IDCANCEL: //Process here the close window box on the Title bar case IDCLOSE: //Process here the Exit menu item { DestroyWindow(); PostQuitMessage(0); break; } default: bHandled = FALSE; } return 0; }
现在,当选择“New”(新建)菜单项时,让我们清除文本框。
case IDNEW: { //Process here the New menu item //Let's clean up the text box //Get the HWND of the text box HWND hWnd = GetDlgItem(IDC_EDIT1); //Set an empty string into it ::SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)_T("")); break; }我们还希望文本框在窗口大小改变时进行调整,所以我们也处理
WM_SIZE
消息。BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_COMMAND, OnMenu) MESSAGE_HANDLER(WM_SIZE, OnSize) END_MSG_MAP()
以及实现
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { //Get the size of the client rect and resize the text box RECT rect; GetClientRect(&rect); //The RECT structure receives the client coordinates. //The left and top members are zero. //The right and bottom members contain the width and height of the window. //Get the HWND of the text box HWND hWnd = GetDlgItem(IDC_EDIT1); //Resize it ::MoveWindow(hWnd, 0, 0, rect.right, rect.bottom, TRUE); return 0; }
最后但并非最不重要的一点,一个非常棘手的行。但在我告诉你之前,仅为了好玩,编译并运行它。调整大小正在工作,并且“Exit”(退出)菜单也在工作,但是无法向文本框输入任何文本。
为什么?因为消息循环没有将虚拟键转换为我们的对话框可以理解的 WM_KEYDOWN
和 WM_KEYUP
,所以它没有收到 WM_CHAR
。
所以还记得这个吗?
MSG msg; while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
好的,我们必须改成这样
MSG msg; while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); //TranslateMessage function translates //virtual-key messages into character messages. DispatchMessage(&msg); }
瞧。现在我们有了一个迷你记事本。
关注点
我认为这里的重点不在于应用程序本身是什么,而在于如何在创纪录的时间内,只使用 ATL 来构建一个功能齐全的 exe,而且依赖项最少。如果它本身没有用,至少我认为这是一个很好的练习,可以超越 ATL 的标准用法。注意:我没有任何错误处理,在实际应用中,代码应该包含大量的错误处理,永远不要忘记这一点。