Click here to Skip to main content
Click here to Skip to main content

DWinLib - Creating Modal Dialogs

, 14 Feb 2013
Rate this:
Please Sign up or sign in to vote.
Need to make a simple, or not-so-simple dialog? The following approach may be just the ticket, even without DWinLib!

The following is an overview of the internal workings of DWinLib, a semi-simple wrapper for the Windows API. The other articles in this series include:

  1. DWinLib - An Overview
  2. Setting up Visual Studio and building a minimal Windows’ wrapper
  3. Compiling a Minimal DWinLib Program
  4. DWinLib - The Guts
  5. Modal Dialogs - A DWinLib Approach - You are here

Introduction

This article discusses one of DWinLib’s benefits I have not seen in other window wrappers: elimination of dialogs. This solution makes Visual Studio Express much more usable for a straight C++ Windows programming environment, as it circumvents the lack of a resource editor in VSE in a fairly simple manner. Along the path we’ll see what else kicks up. The ride should be interesting, so join me!

Getting started

You might think eliminating dialogs is not a big issue. After all, you simply design them in your drag-n-drop editor, and use the thingy’s. Right?

OK, OK. Not quite so simple, is it? You have to create a dialog window procedure, and you need to deal with control IDs. And you may not possess a dialog editor. In addition, I believe more rules must be kept in mind while designing that window procedure.

In my opinion dialog boxes are an Achilles’ heel in Windows programming, especially for those not using the expensive versions of Visual Studio. Cross-compiler compatibility and ease-of-use were prime considerations in DWinLib, and even though I did not have the time or the inclination to create an editor, I was able to allow the design of dialog-like windows without bumping into the problems I mentioned. (But if you really want to make them the hard way, feel free to define a dialog resource yourself and call CreateDialog, just as you could with any other programming environment.)

As I write this, I have succeeded in making both modal and modeless dialog equivalents in DWinLib, but one thing hasn’t been done: return a value from my equivalent window. I will ponder this problem more as we go through this article, and give a method that may work for you.

So, let’s get started.

Hello World, with polish!

Even though building a basic DWinLib program has been illustrated, modifying it for your own use has not. Let me rectify the situation with the most boring of intro programs, “Hello World,” DWinLib style.

If you follow along without using the above zip file, first create a subdirectory to work in and extract the .h, .cpp, and .rc files from the earlier compilation article into this directory. You might also want to change ‘DWinLibBareApp.cpp’ and ‘DWinLibBareApp.rc’ to ‘ModalExample.cpp’ and ‘ModalExample.rc’. (It doesn’t matter if you do as far as VSE is concerned. The modification is to keep the structure clear when you look at the directory listing.) Then make a project in the subdirectory, as outlined in the given build steps. Follow the commands until the program compiles and runs.

Actually, an easier way to get your disk structure set up properly, for those who have worked through the previous articles, is to copy the ‘DWinLibBareApp’ directory, rename it to whatever you desire (i.e., ‘ModalExample), delete the .sln, .user, .ncb, .vcproj, and .suo files, and create the solution there. Again, you might want to change ‘DWinLibBareApp’ to ‘ModalExample.’

The above zip file originated with “C:\Users\David\Documents\Programs\MyProgs\DWinLibExamples\ModalExample” holding the project, with the main file appropriately named “ModalExample.” “Hello World” will be a drive-by shot in this solution.

After you succeed in compiling and executing the program in Debug mode, it is time to get busy.

For the basics of “Hello World,” double-click the ‘AppWindow.cpp’ unit in the Solution Explorer. This will bring it up in the editor. Find the ‘wPaint’ function. Here is the current implementation:

LRESULT AppWindow::wPaint(DwlDC & dc) {
   return 
   int ch = clientHeight();
   int sbw = gGlobals.uiMan().scrollbarWidth();

   //Fill the main window:
   HBRUSH brush1 = CreateSolidBrush(gGlobals.uiMan().backgroundColor());
   HBRUSH brush2 = CreateSolidBrush(gGlobals.uiMan().backgroundColor());
   RECT r;
   r.left = 0;
   r.top = 0;
   r.bottom = clientHeight();
   r.right = clientWidth();
   FillRect(dc(), &r, brush2);

   //And paint the lower right-hand corner between the scrollbars:
   r.top = ch-sbw;
   r.left = clientWidth()-sbw;
   r.bottom = r.top + sbw;
   r.right = r.left + sbw;
   FillRect(dc(), &r, brush1);

   DeleteObject(brush1);
   DeleteObject(brush2);
   return 0;
   }

Place the following non-dotted lines between the dotted ones in the previous code, towards the bottom:

...FillRect(dc(), &r, brush1);

wString str(_T("Hello World!"));
//Now, check how big the text will be:
SIZE textSize;
GetTextExtentPoint32(dc(), str.c_str(), str.length(), &textSize);
SetBkMode(dc(), TRANSPARENT);
TextOut(dc(), (clientWidth()-textSize.cx)/2, (clientHeight()-textSize.cy)/2, str.c_str(), 
           str.length());

...DeleteObject(brush1);

Build and execute the project. When it runs a window will appear which looks something like this (depending upon how you’ve skinned your Windows installation):

As you can see, it already contains a lot more than a standard “Hello World” program. A menu, a status bar, and a dockable toolbar grace the accoutrements. MDI (Multiple Document Interface) works - pressing ‘Ctrl+N’ creates a new subwindow, even though there is no pizzazz built in besides our spiffy message. These ‘extras’ are the reasons for the existing AppWindow and Application code. Eliminate those routines and this functionality will be impaired.

I could have made AppWindows derive from a class with these workings included, so you never had to look at the inner details, but the goals of DWinLib did not contain shielding you from what is going on.

Another thing to note is the application remembers its settings between executions. This is done with an .ini file. I prefer them for this purpose, and don’t care for bloated registries. (Once I reinstalled Windows on a machine with the same programs as before and the boot time decreased significantly. I believe some of the improvement was caused by the registry being much smaller.) If you’re curious about the workings of this aspect, examine the ‘DwlIniFile’ unit, and WinMainO’s usage.

This also means that when creating a new project, the initialization file needs to be changed. Two places are involved. Globals::appWorkDir() specifies the path, and WinMainO::iniFileName() sets the name. Modifying the appropriate lines is a trivial task.

Moving On!

All righty, then! Lets get on to those modal thingy’s, and look at a couple more tricks in DWinLib!

First, undo the “Hello World” changes, so we have a nice white screen to play with again. Now let us put a button on the window which opens our ‘dialog.’

In the AppWindow header, add a line above the class declaration somewhere consisting of “class DwlButton;.” In the private: section of the class, add a line with “dwl::grin_ptr<DwlButton> buttonC;.” (See Note 1.) Add another line in the public: section consisting of “virtual LRESULT wPosChanged(WINDOWPOS * wp);.” Add an additional line in the public: section consisting of “void buttonCallback(DwlWinObject * obj);.”

Now, in the AppWindow.cpp file, add “#include "DwlButton.h"” to the list of include files. Then, in the constructor, below the “verScrollC->showWindow(true);” line, add the following (and see note 2 for an explanation):

buttonC.reset(new DwlButton(this, 10, 10, 130, 25, _T("Open Modal Dialog"),
               CreateDelegate(AppWindow, buttonCallback, this, this)));

Then add the following two function definitions:

void AppWindow::buttonCallback(DwlWinObject * ) {
   DwlModalBaseForm * w = new DwlModalBaseForm(200, 200, 200, 200);
   
   //You can perform 'ShowWindow' from the ModalBaseForm ctor, or from here.
   //I usually do it here, so I can use values this window may know about to center
   //the window where I want it to be centered.  Doing it from here also
   //allows you to derive several layers deep from ModalBaseForm, and
   //not worry about flashing the window in a partial state on the screen before 
   //the final constructors finish.
   
   ShowWindow(w->hwnd(), SW_SHOW);
   }


LRESULT AppWindow::wPosChanged(WINDOWPOS * wp) {
   buttonC->moveWindow((clientWidth()-buttonC->width())/2,
               (clientHeight()-buttonC->height())/2, buttonC->width(), buttonC->height());
   return 0;
   }

Build and run the executable. Something like the following will appear:

There doesn’t seem to be any way to close the window, but pressing ‘Escape’ does the trick. (For some interesting accelerator table information behind this feat, see note 3.) To overcome this, we need to override the DwlModalBaseForm unit.

Before we do, though, let me make a non-note comment about the our DwlModalBaseForm. For all effects and purposes, this window acts like a modal dialog. If you click outside it, on another portion of your application, the nice ‘flashy’ animation occurs, indicating you are not allowed to work there until the active flasher is taken care of. This is obtained via the ‘EnableWindow’ calls within the DwlModalBaseForm constructor and destructor. Pretty slick. The only thing which remains is true halting of your program and the return of a numeric result.

In order to really obtain that aspect of the functioning of modal dialog boxes, it may be necessary to either:

  1. improve DWinLib’s threading mechanism for making windows in separate threads, or
  2. create the dialog the hard way, and make a window procedure for it, as well as a resource.

The reason for ‘may’ in the previous sentence is I remember once creating windows in individual threads with success, but I don’t know if the changes since then have clobbered that functioning. The modifications for Microsoft’s MDI framework were significant.

I believe the threading is necessary because:

  1. I think that is what CreateDialog is doing, and
  2. I’m unaware of a way to obtain a value from a destructor. After the window is created in a separate thread, you would either have to perform a SendMessage to it, and pass the number as a response, or create the window and then execute WaitForSingleObject on the thread handle, and somehow get the return value as the result of the threaded window’s message handling procedure. The problem is a complicated one, and I have not needed a solution for my own code (yet). But let us solve the issue another way.

Faking a return value

To begin, add a header and .cpp file to the ‘Project’ filter in the Solution Explorer. I usually just take a .cpp / .h file combination that is close to the class I need, copy them to a new name, drag that into the Solution Explorer, and modify the files in the IDE. But to my knowledge, I don’t have a file which suits the purposes at hand, and I should probably show this method at least once. (I sure wish VCE had Borland’s ‘Create New Unit’ command; it added a .h / .cpp combination, and even set up the include guards in the .h file!)

So, right-click on the ‘Project’ filter, select ‘Add -> New Item’ twice, selecting ‘Visual C++ -> Header File (.h)’ and ‘C++ File (.cpp),’ using a filename of ‘ModalReturnTest’ for them. Then modify them to:

//ModalReturnTest.h:
#ifndef ModalReturnTestH
#define ModalReturnTestH

#include "ModalBaseForm.h"


class ModalReturnTest : public ModalBaseForm {
   public:
      ModalReturnTest();
   };

#endif


//ModalReturnTest.cpp:
#include "PrecompiledHeaders.h"
#pragma hdrstop

#include "ModalReturnTest.h"


ModalReturnTest::ModalReturnTest() : ModalBaseForm(200, 200, 200, 200) { }

Then change the #include "DwlModalBaseForm.h"; line in AppWindow.cpp to #include "ModalReturnTest.h";, and modify the AppWindow::buttonCallback, DwlModalBaseForm line to

ModalReturnTest * w = new ModalReturnTest();

You can compile now, and have the same thing we had earlier. Next, I need to flush the functionality out in the derived unit, so let me start coding. I’ll post the completed work below, but here is the order I’m taking to get there as I design...

  1. Fix the title bar.
  2. Add four buttons, one of them being ‘Cancel.’
  3. Make a public enum for the return value.
  4. Set up the callback function.

And, after completing the above, the following is the result:

//ModalReturnTest.h, excluding include guards:
#include "DwlModalBaseForm.h"
#include "DwlButton.h"
class AppWindow;

class ModalReturnTest : public DwlModalBaseForm {
   public:
      //Step 3: Set up a public enum for the return value:
      enum MRT { OptionA, OptionB, OptionC };
   private:
      dwl::grin_ptr<DwlButton> buttonAC;
      dwl::grin_ptr<DwlButton> buttonBC;
      dwl::grin_ptr<DwlButton> buttonCC;
      dwl::grin_ptr<DwlButton> cancelButtonC;
      
      MRT returnValueC;
      AppWindow * appWindowC;
   public:
      ModalReturnTest(AppWindow * win, bool modal);
      
      void buttonCallback(DwlWinObject * obj);
   };
#include "PrecompiledHeaders.h"
#pragma hdrstop

#include "ModalReturnTest.h"
#include "AppWindow.h"


ModalReturnTest::ModalReturnTest(AppWindow * win, bool modal) :
            DwlModalBaseForm(200, 200, 155, 165, modal),
            appWindowC(win) {

   //Step 1: Modify the title bar:
   DWORD style = ((WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW) &
               ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
   SetWindowLong(hwndC, GWL_STYLE, style);
   SetWindowPos(hwndC, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE |
               SWP_NOACTIVATE | SWP_FRAMECHANGED);
   SetWindowText(hwndC, _T("Modal Return Test"));

   //Step 2: Set up the buttons:
   buttonAC.reset(new DwlButton(this, 10, 10, 130, 25, _T("Option A"),
               CreateDelegate(ModalReturnTest, buttonCallback, this, this)));
   buttonAC->DwlControlWin::user = OptionA;

   buttonBC.reset(new DwlButton(this, 10, 40, 130, 25, _T("Option B"),
               CreateDelegate(ModalReturnTest, buttonCallback, this, this)));
   buttonBC->DwlControlWin::user = OptionB;

   buttonCC.reset(new DwlButton(this, 10, 70, 130, 25, _T("Option C"),
               CreateDelegate(ModalReturnTest, buttonCallback, this, this)));
   buttonCC->DwlControlWin::user = OptionC;
   
   cancelButtonC.reset(new DwlButton(this, 10, 100, 130, 25, _T("Cancel"),
               CreateDelegate(DwlModalBaseForm, cancel, this, this)));
   }


//Step 4: Set up the callback:
void ModalReturnTest::buttonCallback(DwlWinObject * obj) {
   DwlButton * button = d_cast<DwlButton*>(obj);
   if (!button) return;
   MRT option = button->user.cast<MRT>();
   appWindowC->optionCallback(this, option);
   wClose();
   }

Pretty simple, all told.

Using this technique, the callback is executed before the ‘modal’ window is destroyed. I don’t see any reason that would ever be a problem, but you should be aware of the design strategy.

You will also note the DwlModalBaseForm has ‘accept’ and ‘setSettings’ functions included. The manner in which that unit is coded allows you place an ‘OK’ (or ‘Accept,’ or other text) button on the derived form, and call the DwlModalBaseForm’s ‘accept’ function for your delegate. That ‘accept’ code will then execute an overridden ‘setSettings,’ and if it returns false, the dialog stays open and lets the user fix the item(s) flagged as being wrong.

Of course, disallowing the selection of incompatible options is a better approach, but the above method gives you flexibility. You can also disregard accept and setSettings entirely, and do whatever you want using your own methods in the dialog window code. These techniques saved me some effort in the past, so I left them in if you would like to use them for yourself.

And that’s about everything I can think of worth knowing for modal dialog boxes.

Modeless dialogs

I just had a thought as I was writing this. Previously I created modeless dialogs by making a window with no parent, and doing the work there. One drawback to the method is the creation of another icon in the Windows Start Bar.

This can be circumvented by modifying the definition and implementation of the DwlModalBaseForm. Now I’ll have to go back and fix up the previous, so you won’t notice the changes, but let me get busy.

As I’m doing this, one decision I made is only modal dialogs will create a new accelerator table state. This keeps things simple, as otherwise logic must be added to enable and disable the accelerator table state whenever the modeless window becomes enabled and disabled.

I also made the design decision that DwlModalBaseForms default to modal. To make it modeless, add a fifth ‘false’ argument to the line calling the DwlModalBaseForm constructor.

Lucky me! The change did not alter a single item in the above exposition.

...

Darn! I want the example project to show one of each form, therefore I’m going to modify ModalReturnTest so it can be either modal or non-modal. Then I’ll place another button on the AppWindow which opens the modeless version. That will alter the logic, but I decided not to change this writeup. To view the new code, look in the zip file.

This solution to modal dialogs in DWinLib raises two more items which should be mentioned before leaving. The first is my executable lets you open a modeless and a modal dialog, and switch between them. A true modal dialog would not allow that to happen. A solution to the problem is to hold a pointer to the modeless dialog while it is alive, and perform an if (modelessPtr) EnableWindow(modelessWindowHwnd, FALSE); call to disable it in the AppWindow unit at the time it creates the modal dialog. Then undo that when the modeless dialog is destroyed.

The second item is if you respond to the user’s input, as I do in the demo, using a MessageBox or other floating window, you must make the modal dialog the parent of the responding window. If you don’t, the user can repeatedly push the buttons in the modal window, and when the user presses the second MessageBox’s ‘OK’ button, the application will crash while trying to access the destroyed modal window’s memory.

To set the modal window to be the parent of the MessageBox (or other type of window), it is necessary to give the calling unit an awareness of the modal window’s HWND in some manner. I simply passed the ‘this’ pointer back in the ‘optionCallback’ function, and called hwnd() on that. (Thanks go to Marijn Stevens for pointing out the bug in my original code regarding this.)

== A Wrap ==

That feels like a good place to end this article. I hope the information in here is is useful to you, and was as enjoyable to read as it was to write. If you use DWinLib, may these articles help you in your endeavors, and even if you don’t use my work, hopefully you found something useful in these writings.


Notes

  • Note 1 - The dwl::grin_ptr is used to keep the unit interdependencies down, as well as to provide automatic cleanup of the held pointer. You may read more about this in another article I wrote: Two-thirds of a pimpl and a grin.
  • Note 2 - Windows can be declared either on the stack or in the heap in DWinLib. DWinLib does not impose any limitations, but logic almost always dictates that the window must be heap-based. Looking at my code while getting ready to publish, I see a stack-based button may be possible here because by the time the program gets to the constructor the base class has already been created, and the HWND is valid. Usually such is not the case.

This came to my attention too close to my deadline, so I will leave everything as-is for now, and look at altering the example and write-up appropriately in the coming weeks.

(I don’t often make stack-based objects simply out of the habit of keeping the number of #includes in my header files to an absolute minimum, and the opportunity is so rare I almost never recognize the opportunity, like here.)

Note 3 - You will be unable to remove the WinAccelerator object from the bare DWinLib application without performing major surgery on a few units. This is the portion which takes care of creating the accelerator table for handling the ‘Ctrl+N’ keystrokes, among others. Part of the magic of the DwlModalBaseForm is it swaps the existing accelerator table for a table that includes a handler for ‘Escape.’ You can add your own accelerators by mirroring the following declaration with appropriate identifiers in the constructor of your DwlModalBaseForm derived units:

gWinApp->accelC.addAccelerator(FVIRTKEY, VK_ESCAPE, (short)cancelCallbackC->id());

When the DwlModalBaseForm is destroyed, the old accelerator table is restored. “Pretty spiffy,” as the old saying goes.

As noted at the end of the article, the accelerator table is only changed for modal dialog windows. Modeless windows will not possess their own accelerator.

Update history

  • 2/13/13 - Updated article to reflect DWinLib 3.01, and integrate everything into the DWinLib overview series.
  • 2/27/06 - Updated code in zip file to fix a bug. Also slightly modified the ‘Modeless dialogs’ section by outlining the bug and the fix.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David O'Neil
Software Developer www.randommonkeyworks.com
United States United States
I am the author of Laughing at the Devil: One Man’s Religious Discoveries. For those who are “ready to look at the world - religion, science, spirituality - differently,” LATD is the book to turn to.
 
In about 1994 I began studying and documenting the astronomy of our ancestors. A hint lead to many years of partial understanding, before a profound breakthrough occurred and some old myths finally made sense.
 
The greatest of my discoveries is the celestial observations behind the biblical tale of Samson, which was created 3,000 years ago. That find casts a profound new light on the roots of Western religion, as well as the foundation of modern science. To learn more, visit my website.
 
Trained as a mechanical engineer, I learned C++ programming on my own in order to create a MIDI program. I am delighted to say I also succeeded in that goal. Happy coding, everybody!

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 14 Feb 2013
Article Copyright 2013 by David O'Neil
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid