Simple ATL dialog based exe






4.36/5 (11 votes)
Apr 29, 2004
5 min read

109544

1710
How to create a simple program based on a dialog box using nothing but ATL
Introduction
Did you ever have to write a simple light dialog based exe to do simple stuff? Great then, because I can tell you how to do it in a brief, using nothing but ATL.
Background
ATL is great to develop light COM components with a minimum of dependencies, those can be contained on a DLL or an EXE, also ATL wizard has an option to create an ATL Object based on CAxDialogImpl<>
template, so why not to use these options to create an standalone exe? No reason, isn't it? So, let's do it.
The recipe
- Start a new project.
- Select ATL COM AppWizard
- Choose a name for your project, I'll use DlgTest
- Click Next
- On the Server Type, choose the "Executable (EXE)" option
- Finish and OK. On the ClassView tab of the workspace, right click on the tree root and select from the menu "New ATL Object..." From the ATL Object Wizard window, change to the Miscellaneous category
- No many options there, so select Dialog and then click "Next >"
- Choose a name, I'll use MainDlg, so my class will actually be
CMainDlg
, and the click OK. Hung on, almost there.
Let's now create an instance of our dialog class.
Expand the Global folder on the ClassView's tree and double click on _tWinMain
function, it will open the DlgTest.cpp file and show the code for that function scroll down almost to the end until you see these lines
MSG msg; while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
Create an instance of the CMainDlg
(remember this is the name I chose) just before the loop, and show the dialog.
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_SHOWLet's not forget to add the
#include "MainDlg.h" //the dialog's header file
at the beginning of this file.
I know, you want to taste it, ok, go a head compile it and run it. But is not ready yet...
So, it ran, it showed the dialog box with the 2 default buttons, but when you click either of them you got an assertion, told ya wasn't ready.
But is ok, so we can learn something while cooking this. What happened is, the dialog was created as a modeless using the member function Create()
, but the default implementation from the wizard assumes it would be a modal one, so when the OnOK
or OnCancel
function are called, the default is just call EndDialog
, which is not what a modeless is expecting. To fix this, let's replace the EndDialog
for DestroyWindow
which is the appropriated way to destroy a modeless dialog. Another little thing, this is our only window, so if it is destroyed the whole application should shutdown, so let do that also, will do it calling PostQuitMessage
. Going back to our recipe.
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; }and let's replace
EndDialog
by DestroyWindow
and also add the PostQuitMessage
, so, they should look like this 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; }Done. Now it should run and shutdown gracefully.
Nice, but... not very useful, ok, I know, we forgot the seasoning. Let's put some spices and see what we get.
A form window?
Yes, this dialog based application can look and feel like a form based window application, let me show how to do it.First, let's get rid of the button. From the dialog template, delete the two buttons. But now we need another way to end the program. Let's add a menu, on the resources tree, right click and choose "Insert...", select menu and click New. Let add 4 items on the menu, &File as a top menu, then type &New, a separator and then E&xit. Open the items' properties and choose IDNEW
as the id for &New and IDCLOSE
for E&xit.
We'll use this clean up and to shutdown the application respectively. Go back to the dialog box template, open its properties and in the General tab select the menu id from the Menu combo box. Go to the Styles tab and change the Border to Resizing, you can also add the Minimize and Maximize boxes. Let's add also a text box, so it'll do something. Change the properties of the text box to Multiline, Vertical and Horizontal scroll and Auto HScroll and AutoVScroll. We are done with the template, let's go back to our class and add some code.
Open the class' header file, we don't need to process the IDOK
and IDCANCEL
handlers any more, remember we deleted the buttons, so delete these two lines from the message map section.
BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) //DEL COMMAND_ID_HANDLER(IDOK, OnOK) //DEL COMMAND_ID_HANDLER(IDCANCEL, OnCancel) END_MSG_MAP()
and also the functions definitions
//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 }Now we want to process the messages from the menu, so let's add the the handler on the message map section
BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_COMMAND, OnMenu) END_MSG_MAP()
and the implementation
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; }
Let's now clean the text box when the New menu item is selected
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; }Also we want to have the text box to resized when the window change its size, so let process the
WM_SIZE
message to.
BEGIN_MSG_MAP(CMainDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_COMMAND, OnMenu) MESSAGE_HANDLER(WM_SIZE, OnSize) END_MSG_MAP()
and the implementation
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; }
And last but not least, a very tricky line. But before I tell you, just for the fun of it, compile and run it. The resize is working, and also the Exit menu is working, but is not possible to input any text to the text box.
Why? Because the message loop is not translating the virtual key for our dialog to understand the WM_KEYDOWN
and the WM_KEYUP
, so it is not getting the WM_CHAR
.
So remember this?
MSG msg; while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
ok, we have to change to this
MSG msg; while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); //TranslateMessage function translates //virtual-key messages into character messages. DispatchMessage(&msg); }
and voilà. Now we have a little mini tiny notepad.
Points of Interest
I think the whole point here is not what the application is about, but how to put together a functional exe with minimum dependencies in a record time using only ATL. If this is not useful by itself at least I believe is a good exercise to see beyond what could be the standard use of ATL.Note: I have done no error control what so ever, in a real application the code should have plenty of error controls, never forget that.