Introduction
I bet you are asking yourself "What in the world is a stupid tool for an online game doing on Code Project?" This program is my entry in the New C++ Competition. UO Treasure Hunter Tools (UOTH) uses VC7, WTL7 and ATL7 as its core technology.
So what makes UOTH worthy of consideration for the new competition? Well, there isn't one thing in UOTH that stands out as great or innovative programming. However, what UOTH does contain is a diverse collection of smaller bleeding edge UI and programming techniques.
- Extending the default toolbar customization to include icon size and text position combo boxes.
- Explorer style customization of an "Save As" dialog including the repositioning of controls so that they line up with other controls in the dialog.
- Hoisting the contents of a modal dialog as a child in another dialog.
- Printing and page setup support. This includes the printing of graphics.
- Toolbars on dialogs
- Dynamically adjusting the size of a dialog and painting graphics to the background of the dialog.
- Example of display a dialog as either modal or modeless. The modeless dialog is also adjusted to allow the user to minimize it.
- "Stay on top" example code.
- An example of owner drawn button and dropdown menu.
- Masked image painting.
- Alpha blending monochrome images.
- Stretching of bordered UI graphics without distorting the image.
- Dropdown menu from toolbar.
- Uses EXPAT XML parser for the parsing of configuration files.
- Uses ZLIB and the included zip file support to package the static program data files.
Following are short descriptions of some of the programming highlights contained in UOTH.
Fancy Toolbar Customization
Toolbars these days are much more complex than just the simple 16 color, 16x15 bitmap images. As programmers we have to deal with large and small image sets. We have to deal with the display of text under the button or to the right. We also have to deal with allowing the user to configure which style of toolbar he likes best.
Luckily for the programmer, the common controls provide us with complete support for the advance toolbar color and text options common in today's application. However, a few minor points of interest are left out.
If you right click on an IE6 toolbar and select "customize" you will be presented with the standard toolbar customization dialog. However, this dialog includes two extra combo boxes at the bottom that allow the user to specify the text and icon options. At first glance, it would be reasonable to assume that IE6 includes their own private customization dialog, but that turns out not to be the case.
If you look at the method CMainWnd::OnCustomizeToolbar
file MainWnd.cpp, it contains the notification handler for the toolbar. Specifically, the handling of the TBN_INITCUSTOMIZE
notification contains a hack used to get the window of the toolbar customization dialog.
typedef struct hack_tagNMTOOLBARINIT
{
NMHDR hdr;
HWND hWndDialog;
} hack_NMTOOLBARINIT;
hack_NMTOOLBARINIT *pNMHack = (hack_NMTOOLBARINIT *) pnmh;
HWND hDlg = pNMHack ->hWndDialog;
As well noted in the code, the TBN_INITCUSTOMIZE
notification structure actually contains the handle of the customization dialog. As with any undocumented feature, it is always a risky proposition to utilize it. However, given that IE6 uses this, I doubt it is going away any time soon.
Once we have the handle to the customization dialog, we can create a child dialog inside this dialog. The source code shows exactly how to do this.
Explorer Style Customization of the "Save As" Dialog.
Customizing the "Save As" dialog is very common these days. However, many developers just place their controls in their customization dialogs without regard to how they align with the other controls inside the "Save As" dialog. The sad part is, this is trivial. The CUOAMExportDlg
class contains an example of customizing the "Save As" dialog. We are going to look specifically at what is required to properly reposition controls. This example assumes our customization dialog will appear below the rest of the "Save As" dialog.
Every control on the "Save As" dialog has specific control IDs that we can depend on being constant. These IDs are defined in the system include file "dlgs.h". We can use these control IDs to locate controls on the "Save As" dialog and reposition our controls relative to their positions.
In UOTH, the repositioning of the controls is handled by a routine named RepositionControl
(and to think, I am someone who claims self documenting code is a myth). In this example, since our customization dialog is at the bottom, all we are concerned about is the horizontal position and size of our controls. Their vertical position and size is dictated by their position and size in our dialog.
void CUOAMExportDlg::RepositionControl (CWindow &wnd, UINT nID, bool fSize)
{
CWindow wndParent = GetParent ();
CWindow wndAnchor = wndParent .GetDlgItem (nID);
CRect rectAnchor;
wndAnchor .GetWindowRect (&rectAnchor);
wndParent .ScreenToClient (&rectAnchor);
DWORD dwSWFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE;
CRect rectCtrl;
wnd .GetWindowRect (&rectCtrl);
ScreenToClient (&rectCtrl);
rectCtrl .OffsetRect (rectAnchor .left - rectCtrl .left, 0);
if (fSize)
{
rectCtrl .right = rectCtrl .left + rectAnchor .Width ();
dwSWFlags &= ~SWP_NOSIZE;
}
wnd .SetWindowPos (NULL, rectCtrl .left, rectCtrl .top,
rectCtrl .Width (), rectCtrl .Height (), dwSWFlags);
return;
}
Supplied to this routine is the window of the control to be repositioned, the ID of the "Save As" dialog control, and a flag stating if we wish for our control to be resized. As you can see from the code, it is actually a simple process. This routine is invoked during dialog initialization and resize.
One other final note. If you have a control, such as another button that needs to be placed below the "Ok" button, you might run into problems with the resize grip in the lower right corner of the dialog. The third block of code in the OnInitDialog
method takes care of this problem.
Hoisting one Dialog Into Another
In many applications, you might find the need to place a common dialog elements in multiple locations. In UOTH, the filter dialog is not only used as it's own independent dialog, but also appears in the "Print..." dialog and the "UOAM Export..." dialog. A normal programmer's natural reaction would be to duplicate the code for the dialog in multiple places. However, with some simple adjustments a modal framed dialog can act like a child dialog.
The first thing to consider when a dialog is to act as both a modal framed dialog and a child dialog is that in the case of a child dialog, a WM_COMMAND
message for the IDOK
and IDCANCEL
buttons are never received. Since the programmer is in total control of the dialog, this is a trivial problem to resolve. Instead of placing all the code to save the dialog settings in an OnOK
routine, place the code in another routine that will be invoked by OnOK
and the dialog that will using this dialog as a child dialog. For users of DDX, this would be very trivial. In my case, I don't use DDX since I have always found it to be more of a hassle than benefit.
Handling how data is saved when the user presses the "Ok" button is only half the battle. In order to use the dialog as a child window, you have to invoke the Create
method on the dialog instead of DoModal
. However, the dialog resource is still setup for the dialog to be displayed as a popup with a dialog frame. Luckily, with a little bit of code, this can be fixed dynamically. The following is the CFilterDlg::Create
method.
HWND CFilterDlg::Create (HWND hWndParent, LPARAM dwInitParam)
{
HRSRC hRsrc = FindResource (_Module .GetResourceInstance (),
MAKEINTRESOURCE (IDD), RT_DIALOG);
if (hRsrc == NULL)
return NULL;
DWORD dwSize = ::SizeofResource (_Module .GetResourceInstance (), hRsrc);
HGLOBAL hTemplate = ::GlobalAlloc (GPTR, dwSize);
if (hTemplate == NULL)
return NULL;
DLGTEMPLATE *pTemplate = (DLGTEMPLATE *) ::GlobalLock (hTemplate);
DLGTEMPLATEEX *pTemplateEx = (DLGTEMPLATEEX *) pTemplate;
HGLOBAL hSource = ::LoadResource (_Module .GetResourceInstance (), hRsrc);
LPVOID pSource = ::LockResource (hSource);
memcpy (pTemplate, pSource, dwSize);
UnlockResource (hSource);
::FreeResource (hSource);
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | WS_DLGFRAME |
DS_3DLOOK | DS_FIXEDSYS | DS_SETFONT | DS_CONTROL;
DWORD dwExStyle = 0;
if (pTemplateEx ->signature == 0xFFFF)
{
pTemplateEx ->exStyle = dwExStyle;
pTemplateEx ->style = dwStyle;
}
else
{
pTemplate ->dwExtendedStyle = dwExStyle;
pTemplate ->style = dwStyle;
}
ATLASSERT (m_hWnd == NULL);
_AtlWinModule .AddCreateWndData (&m_thunk.cd,
(CDialogImplBaseT <CWindow> *) this);
#ifdef _DEBUG
m_bModal = false;
#endif
HWND hWnd = ::CreateDialogIndirectParam (
_AtlBaseModule.GetResourceInstance(),
pTemplate, hWndParent, StartDialogProc,
dwInitParam);
ATLASSERT (m_hWnd == hWnd);
if (m_hWnd)
{
::DestroyWindow (GetDlgItem (IDOK));
::DestroyWindow (GetDlgItem (IDCANCEL));
}
::GlobalUnlock (hTemplate);
::GlobalFree (hTemplate);
return hWnd;
}
The first thing that must be done is to load the dialog resource into volatile memory. This allows us to modify the create parameters. Next, the style and extended style flags are changed to force the dialog to display as a child window with no border. Finally, after the dialog is created, the OK and CANCEL buttons are removed.
This code will create the dialog at the default window position. The dialog will need to be repositioned to the proper place by the parent window. This is usually done in the WM_SIZE
message handler.
Other Code of Note (The Brown Nose Section)
- This program wouldn't have been doable without the addition of code from other people here at CodeProject.
- The color button was based on work by Maunder, Alexander Bischofberger and James White (link, link).
- The bitmap mask drawing was based on work by Raja Segar and Chris Becke (link, link).
- The alpha drawing was based on work by Christian Graus (link).
About the Author
Tim has been a professional programmer for way too long. He currently works at a company he co-founded that specializes in data acquisition software for industrial automation.