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;
dlg.Create(NULL);
dlg.ShowWindow(SW_SHOWNORMAL);
Let'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.
Open the MainDlg.h file, look for
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();
PostQuitMessage(0);
return 0;
}
LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
DestroyWindow();
PostQuitMessage(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)
END_MSG_MAP()
and also the functions definitions
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);
switch(itemId)
{
case IDNEW:
break;
case IDCANCEL:
case IDCLOSE:
{
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:
{
HWND hWnd = GetDlgItem(IDC_EDIT1);
::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)
{
RECT rect;
GetClientRect(&rect);
HWND hWnd = GetDlgItem(IDC_EDIT1);
::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);
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.