Dialog and Control Design

Home
Back To Tips Page

This essay was prompted by a number of recent queries in the newsgroup about interfacing controls or dialogs to programs. Most of the questions demonstrated a fundamental lack of understanding about how to design these components. Worse still, the answers given by friendly newsgroup participants tended to be of the form "The way you solve that is..." and tell them how to fix their immediate problem, which was usually syntactic. My responses tended to be of the form "You have a deep and fundamental design problem of which your syntax error problem is so trivial as to be irrelevant. Fix the design and there will be no syntax problem". This led to this essay, which is how to design clean interfaces in Windows.

An unfortunate temptation of programmers is to do something not because it makes sense, but because it is possible. Most of the errors I see in program structure are the result of a failure to understand basic design principles, but instead having a programmer forge blindly ahead, using syntactic techniques to kludge third-rate designs so they compile, and perhaps even work.

As in any aspect of programming, clean design is critical. In Windows it is even more important.

There are a number of horrendous styles of programming, which I discuss in a companion essay on bad programming techniques. This essay attempts to explain the best ways I have found of doing communication with dialogs and controls.

At the heart of all of this is the concept of modularity, with its concomitant feature, reuse. A component should know nothing about its environment that it cannot deduce from formal interfaces which are independent of any one particular incarnation of that environment. That is, we want a picture that resembles this:

This would be a nominally "clean" specification. The component interacts with its environment via a well-defined a fairly narrow interface. The problem seems to come about in defining that interface specification. 

I talk about many failures in my essay "The n habits of highly defective Windows applications". This essay addresses a set of those failures, the failure to understand program structure.

Let's look at some of the issues here:

When a component is, as is typically done, derived from a standard Windows component, it inherits the methods of that component. It may add methods to these. However, it must never assume anything about the environment it is part of. 

Initialization

A dialog typically has two initialization events: the creation of the CDialog-derived object, and the instantiation of the dialog. A great deal of confusion comes about because these are quite different events temporally and philosophically. A CDialog-derived object is always created well before the dialog is instantiated. A common failure mode is to do something such as

CMyDialog dlg;
dlg.Initialize();
dlg.DoModal();

This would work fine if the Initialize restricted itself to setting up variable values. But that makes it redundant with the constructor! (And I should point out that the parameter-free Initialize call was an example from a real request). The argument made was that "the dialog needs to start up with different values each time, so the Initialize sets those values". How a parameterless method of a dialog could set up different values each time escapes me.

Now, if the call were something like

dlg.Initialize(p1, p2, ..., pn);

for some collection of parameter values, it might marginally make sense. However, this is again redundant with the constructor. You can modify the default constructor

CMyDialog::CMyDialog(CWnd * parent = NULL);

to read

CMyDialog:CMyDialog(SomeType1 p1, SomeType2 p2, 
                    ... SomeTypen pn, CWnd * parent = NULL);

and eliminate any need for a separate Initialize method entirely! (Nobody has said that the code generated by ClassWizard is so sacred that it cannot be changed).

In some cases, you may get errors about no default constructor being available. This can be solved by adding a second constructor with all the parameters. In this case, it is often convenient to provide a common Initialize method which takes either the parameters given in the constructor, or a set of default values from the default constructor, but the key here is that such an Initialize method is always protected, and is never, ever visible outside the class or used by other than the various constructors you provide.

CMyDialog::CMyDialog(CWnd * parent = NULL) :
     CDialog(CMyDialog::IDD, parent)
   {
     Initialize(0, NULL, NULL, CString(_T("")), -1);
   }
CMyDialog::CMyDialog(int start, Something * source, Something * dest, 
                     const CString & caption, int offset)
   {
    Initialize(start, source, dest, caption, offset);
   }

Note that all variability here is captured in the constructor.

Another method, commonly used, is to have member variables of the dialog that are initialized by the caller. This is somewhat less elegant than the constructor method, but is in common use, and does not really violate the spirit too badly. It is also made easy to use by some of the ClassWizard support.

First, it is critical to recognize that the notion of initializing controls from ClassWizard-created value variables only works for simple scalar variables, such as int, UINT, or CString into a static or edit control, or BOOL into a checkbox. I consider the mechanism totally worthless for initializing radio buttons, combo boxes, or list boxes, for reasons I discuss in my essay "Who owns the GUI?". The nice thing about the interface is that it actually doesn't make a commitment to representation; next week, I could decouple the BOOL variable from any control and use it in OnInitDialog to initialize a checkbox in a ListBox, or something completely different.

In this case, the code consists of

CMyDialog dlg;
dlg.m_SomeValue = ...;
dlg.m_OtherValue = ...;
dlg.DoModal();

Exactly what happens to those member variables after the dialog starts up is of no concern whatsoever to the programmer who is using the interface; the specification is that certain values must be initialized to represent certain pieces of information. That's all. How those pieces of information are mapped to controls is irrelevant. 

Note that the public m_... variables are the only place I choose to use the m_ prefix. I never use it internally, because compilers already understand how to resolve names, and I don't need to add gratuitous complexity to the dialog to satisfy some compulsion to identify member variables. Typically I use these variables only in the OnInitDialog handler and never, ever use them for any other purpose inside the dialog. If there are output variables, they are set in the OnOK handler upon completion, but that will be discussed below when dealing with output conditions.

I never, ever call UpdateData. I consider programs that use it to be examples of unmaintainable code. Programs that use UpdateData are harder to write, harder to debug, and harder to maintain than programs that do not use it. See my essay on "Avoiding UpdateData". So it will not enter the discussion, except for the implicit UpdateData that happens when a dialog starts (before OnInitDialog gets serious) and the implicit UpdateData which happens on OnOK, which is almost but not quite totally useless, being only good for saving simple scalar values, and completely useless for storing anything about radio buttons, list boxes, or combo boxes.

If you think you need UpdateData you are wrong. You should probably also read my essay on "Dialog box control management", which shows how to create dialog box control management code that is easily written, easily debugged, and easily maintained.

It also makes sense to call methods of the component, providing those methods do not modify the controls. This is because the controls do not yet exist. This is just a generalization of the notion of setting initial variables. In some cases, those initial settings cannot be determined by the caller, but can easily be computed by the component. In this case, you create methods that set variables, where the translation from the "environment space" to the "component space" is computed by the component.

Why is it valid to have the environment call methods of the component but not have the component call methods of the environment? Because the environment is not independent of the component. The particular component was placed there to meet a specific need, and thus its existence is well-known to the environment. But the component must not know about the environment because the slightest changes in the environment require recompilation of the component (making it impossible to put the component in a DLL, for example). But once the component's interface is specified, it tends to be very stable. The difference is between having a dependency graph that is a dag, a directed acyclic graph, in which there are no circular dependencies, and having a cyclic graph, in which there are mutual dependencies. A document, for example, has no dependence on any view (although poor programmers will make the view and its methods visible to the document, this is just another example of violating the environment/component separation).

The interface specification is now not a general-purpose bi-directional arrow without any constraints. We are starting to limit our interface specification. Thus far, we have only an input mechanism specified:

Queries of the Environment

This is the first place where far too many programmers break down. They need to know information about the environment in which the dialog executes. What do they do? They all too often include the header files for unrelated components in the dialog, then use those components directly from the dialog. This is silly.

The construction of good software is not at all aided by the incredibly poor examples provided by Microsoft. For example, all control variables are declared public as if that could ever make sense. It cannot. The ClassWizard is so primitive that this is all it can manage, and it is wrong. Then when you derive a class in your project, there is a #include added for your project.h file. This makes no sense at all! There is nothing your control or dialog needs from your project header file! All it really needs (and sometimes not even that) is your resource.h file. So the first thing I do after creating a control or dialog is to replace the project.h file with resource.h in the #include list of the dialog or control. This gives me a reusable control. 

I find the inclusion of the project.h file to be offensive in the extreme. It encourages sloppy programming. It sets a bad example. And it serves no useful purpose whatsoever. Your dialog has no need to know what is in your CWinApp-derived object, and it certainly has no business knowing what any view or document look like. 

The only header files you should include in a dialog are header files for your custom controls, header files for other dialogs you may invoke as child dialogs, and header files for common functions that are independent of the dialog, application, view, or document involved.

To query the environment, if the information has not been provided as part of the input variables (either via constructor or assignment to public variables), and in particular if it is dynamic, the dialog should send messages to its parent. The result is returned via the LRESULT value. Read my essay on Message Management for more information on writing user-defined messages and handlers. But if I need to know some value about the parent environment, I am most likely to write

LRESULT result = GetParent()->SendMessage(UWM_TELL_ME_ABOUT_THIS);

where I will either treat the LRESULT as a simple LRESULT or I might cast it to some other value, including a pointer (note that LRESULT in 64-bit Windows is a 64-bit value and therefore can be a pointer!)

Therefore, part of the interface specification of the dialog, or control, is that it will query its parent for information by sending a message. This is now part of the interface specification. If the parent fails to handle the message, it typically passes it to DefWindowProc, which returns 0, which can be detected and treated as either a violation of the interface specification (for example, by adding

ASSERT(result != 0);

and making appropriate tests to act on the failure to respond) or be an indication that a default value should be used. Clear and accurate documentation of each message is critical. I consider something like this the minimum acceptable documentation for a simple query:

/***************************************************************
*                       UWM_QUERY_COLOR
* Inputs:
*       WPARAM: integer, desired color type from QueryColor enum
*       LPARAM: ignored, 0
* Result: LRESULT
*	RGB color
* Notes:
*      Failure to handle this message will typically result in
*      the value 0 being returned, which is interpreted as 
*      black.
***************************************************************/
... define the message here
typedef enum {ForegroundColor, BackgroundColor, TextColor} QueryColor;

That is, a message is a method call and must be as thoroughly documented as a method call. If you design a user-defined message, it must have something at least this good accompanying its definition. Otherwise, you have not programmed competently.

Note that at no time does the dialog or control have to know what its parent is. It only has to tell its parent what requirements must be met to properly use the dialog or control.

When handling responses, it is critical that the values passed back are valid for the duration of the communication. For example, simple scalars, such as the RGB value, are easy. Pointers are harder. 

Referenced data is of two forms: references to data which is known to exist, and references to data which is created for purposes of the response.

When the component needs a particular data structure filled in, it should send a pointer to that data structure. The data structure must not contain any data type that is outside the scope of the dialog. For example, suppose I wanted the dialog to query the document. It would be completely inappropriate to put a CMyDocument * pointer in a query data structure, and it would also be completely inappropriate to even put a CDocument * pointer in such a structure, because the component does not need to know where the value is stored! When it makes a query, it expects to get a result back. How that result was formulated is none of its concern. If the parent had to read controls, query the view, query the document, or talk to a database doesn't matter in the slightest.

What matters, and the only thing that matters, is the lifetime of the value.

For example, suppose I created a class to hold graphical information:

class GraphicInfo {
       CPen * pen;  // pen to use for current drawing
       CBrush * brush; // brush to use for current drawing
       COLORREF bk; // background color
};

The simplest form would be to do the following

/***************************************************************
*                       UWM_QUERY_GRAPHICS
* Inputs:
*       WPARAM: (WPARAM)(GraphicInfo *): Pointer to structure to fill
*       LPARAM: ignored, 0
* Result: (LRESULT)(BOOL)
*	TRUE if the message was handled and the GraphicInfo * is valid
*      FALSE if not supported
* Notes:
*      Failure to handle this message will typically result in
*      the value 0 being returned.
***************************************************************/

I could then do something of the form

GraphicInfo info;
if(GetParent()->SendMessage(UWM_QUERY_GRAPHICS, (WPARAM)&info))
    { /* handle graphics */
     ...whatever I want here...
    } /* handle graphics */

Note that my return type is a simple Boolean value, and the lifetime of the GraphicInfo object is guaranteed. No complex protocols are required.

But I could have also specified this as

/***************************************************************
*                       UWM_QUERY_GRAPHICS
* Inputs:
*       WPARAM: ignored, 0
*       LPARAM: ignored, 0
* Result: (LRESULT)(GraphicInfo *)
*	A reference to the current GraphicInfo * object
*          if the message was handled
*      NULL if not supported
* Notes:
*      Failure to handle this message will typically result in
*      the value NULL being returned.
***************************************************************/
GraphicInfo * info = (GraphicInfo *)GetParent()->SendMessage(UWM_QUERY_GRAPHICS);
if(info != NULL)
   { /* handle graphics */
    ...whatever I want here...
   } /* handle graphics */

This imposes the requirement that the parent have a GraphicInfo object available, and that the lifetime of that object will be at least equal to the lifetime of the dialog's use of it. 

This suggests that the GraphicInfo type might have an existence independent of both the parent and the component. In which case it would not be defined in either the parent window definition or the component definition; it would be a separate header and implementation file defining the GraphicInfo class, and it itself would have no idea bout views, dialogs, or anything else.

Or, I could have specified it as 

/***************************************************************
*                       UWM_QUERY_GRAPHICS
* Inputs:
*       WPARAM: ignored, 0
*       LPARAM: ignored, 0
* Result: (LRESULT)(GraphicInfo *)
*	A reference to the current GraphicInfo * object
*          if the message was handled
*      NULL if not supported
* Notes:
*      Failure to handle this message will typically result in
*      the value NULL being returned.
*      The parent creates the GraphicInfo object on the heap.
*      the component will delete this object when it is done
*      with it. The parent must return a valid pointer to a 
*      deletable GraphicInfo object.
***************************************************************/

In this case, the call would appear as

GraphicInfo * info = (GraphicInfo *)GetParent()->SendMessage(UWM_QUERY_GRAPHICS);
if(info != NULL)
   { /* handle info */
    ...whatever I want here...
    delete info;
   } /* handle info */

Note the significant changes here! The environment must not return, for example, a pointer to an object in its class. 

class CMyView : public CView {
    ...
    protected:
      GraphicInfo info;
};
LRESULT CMyView::OnQueryGraphics(WPARAM, LPARAM)
   {
     return (LRESULT)&info; // fatal mistake, given the spec!
   }

would be fatal, because the component would attempt to do a delete on something that is not deletable. If the class was 

class CMyView : public CView {
    ...
    protected:
      GraphicInfo * info;
};
LRESULT CMyView::OnQueryGraphics(WPARAM, LPARAM)
   {
     return (LRESULT)info;
   }

A different failure mode would ensue. When the environment tried to use its GrahicInfo value, thinking it was still valid, it would be using a pointer to already-freed storage, which might have been reallocated for a different purpose. This would be a disaster. The only valid response to this message would be

LRESULT CmyView::OnQueryGraphics(WPARAM, LPARAM)
   {
    GraphicInfo * info = new GraphicInfo(...parameters here...);
    return (LRESULT)info;
   }

It is also important in such a situation to make sure that none of the components are accidentally deleted, and in the case shown here, that the CPen and CBrush would both remain valid for the duration of the GraphicInfo object and not be deleted if they were expected to still exist later!

Now a topic I alluded to earlier: what if the component is the CDocument-derived class and the environment is the CView-derived class? Is it any different? No, not really. The best form of initialization is to have the OnNewDocument handler of the CDocument initialize the document components. However, OnInitialUpdate may set additional values in the document, and of course sets all the appropriate view-specific values. But certain types of views violate rather seriously the document/view separation. CFormView, CRichEditView, CEditView, CTreeView, and CListView all keep the real data in the view itself. This leads to all sorts of problems, and in fact points out how pathetic the UpdateData mechanism really is. In a sane world, it would be invokable by a document to store values in the CDocument. But the ClassWizard is so absymal that it can only parse lines that have scalar values in the view class, and could not handle values in the document or, better still, invoke Set methods of the document. Sad. So in the one case it might have had a purpose, it fails completely (don't bother to write to me that I can hand-edit the lines and move them to outside the magic AFX_... comments. I know that. I tried it, and it means that I'm always fighting the tooling This merely points out that in this dimension the tooling sucks).

My preference is to have the document assume that any or all of the views have data which needs to be reflected back to the document. This is a special case of a more general problem of view consistency, which I will discuss when I talk about notifications. However, my approach to the simpler case is that when the document needs to be made consistent with the views, it does an UpdateAllViews using a specific non-zero lHint parameter which causes the view to store its information in the document. Upon completion of the UpdateAllViews, the document now holds consistent data. This is a form of query where the environment updates the component.

So now that simplistic picture has been enhanced to allow queries and responses.

Notifications to the environment

A dialog sometimes needs to notify its parent that something interesting has happened. A child control very often needs to notify its parent that something interesting has happened.

The worst possible way to do this is to have the dialog or control call a method in the parent. The dialog or control is not reusable, it is sensitive to changes in the parent, and the whole idea violates all known principles of modular programming. The component should be completely indifferent to its parent.

Why does a child need to notify a parent? It could be that some event has occurred which the parent must know about (a mouse click, for example), or a value has changed which the parent must know about. Notifications may be either synchronous or asynchronous. A synchronous notification guarantees that the parent responds to the notification before the child is allowed to resume execution. This is often convenient, because it means the component can put a value on its stack and send a pointer to it to the parent, since the stack will remain intact and the value will have meaning until the notification completes and returns to the component. This is done with SendMessage

This is how child controls notify their parents of events. For example, I don't need to have source code to the button implementation to know that the OnLButtonUp handler contains the line (here expressed in MFC-style notation)

GetParent()->SendMessage(WM_COMMAND, 
                         (WPARAM)MAKELONG(GetDlgCtrlID(), BN_CLICKED), 
                         (LPARAM)m_hWnd);

because that is the exact specification of what a button does. The button doesn't care what its parent does, whether it is a dialog, a CFormView, a dialog bar, a free-floating button on a CView, or even, as I once did, a button whose parent is a CListBox. Your notifications need to be the same.

Now consider a specification of the form

/***************************************************************
*                       UWM_SET_NAME
* Inputs:
*       WPARAM: ignored, 0
*       LPARAM: (LPARAM)(CString *): String to set as the name
* Result: LRESULT
*	Logically void, 0, always
* Effect:
*	Notifies the parent that the name has been changed
* Notes:
*	The sender manages the lifetime of the CString, and
*      consequently this message may ONLY be sent by SendMessage.
*      The receiver must not delete the string referenced by
*      LPARAM
***************************************************************/

(We will contrast this with an asynchronous notification discussed shortly). This message allows the user to write code of the form

void CMyDialog::OnChangeName()
   {
    CString s;
    c_Name.GetWindowText(s);
    GetParent()->SendMessage(UWM_SET_NAME, 0, (LPARAM)&s);
   }

I will make no representation of why it makes sense to notify the parent each time a character is typed; that has been determined, by some specification external to this problem, to be the desired behavior. Given that specification, this accomplishes it. The parent will have a MESSAGE_MAP entry of the form

ON_MESSAGE(UWM_SET_NAME, OnSetName)

or

ON_REGISTERED_MESSAGE(UWM_SET_NAME, OnSetName)

The differences are explained in my essay on Message Management. They are not critical, because the handler has the form

LRESULT CMyWindow::OnSetName(WPARAM, LPARAM lParam)
   {
    CString * s = (CString *)lParam;
    c_CurrentName.SetWindowText(*s);
    return 0;
   }

Note that since WPARAM is not used, it does not need to have a parameter name assigned. Because the address which is passed in LPARAM is still on an active stack, it will remain a valid pointer until the SendMessage returns.

This message must never be sent via PostMessage! It will cause all sorts of errors, from ASSERT faults in the MFC debug runtime through access faults. This is because by the time  the message is handled, the stack entry is most likely gone completely. The place referenced might be some completely different variable.

An asynchronous notification is one where the component does not care when the event notification is processed. It "tosses the message over the fence" and knows it will be processed eventually. 

While this method of notification is most commonly used by separate threads to invoke operations on the main GUI thread, it does have some limited use for dialog-parent and control-parent notifications.

/***************************************************************
*                       UWM_LOG_MESSAGE
* Inputs:
*       WPARAM: ignored, 0
*       LPARAM: (LPARAM)(CString *): String to log
* Result: LRESULT
*	Logically void, 0, always
* Effect:
*	Logs the message in the debug output window
* Notes:
*	The CString object must explicitly deallocated by
*	the handler for this message.
*	This message is usually sent via PostMessage. If sent
*	via SendMessage, the sender must not delete the 
*	CString, nor should it assume upon return that it has
*	not been deleted. The sender must send a pointer to a 
*      deletable string. To do otherwise is a serious error.
***************************************************************/

Here we have a message which can be sent via either SendMessage or PostMessage. Key here is that the sender makes no assumptions about when it is processed, while the recipient must assume that the pointer passed is valid at the time it is used.

CString * s = new CString(_T("An error occurred"));
GetParent()->PostMessage(UWM_LOG_MESSAGE, 0, (LPARAM)s);

Note that you can equally well form the CString value by something like

CString * s = new CString;
s->Format(_T("The result is %d"), result);

The handler is simple:

LRESULT CMyWhatever::OnLogMessage(WPARAM, LPARAM lParam)
   {
    CString * s = (CString *)lParam;
    c_Log.AddString(*s);
    delete s;
    return 0;
   }

Note that the recipient does not need to delete the string immediately; only when it is done with it. For example, something of the form

CArray<CString *, CString *>messages;

would be handled by

LRESULT CMyWhatever::OnLogMessage(WPARAM, LPARAM lParam)
   {
     CString * s = (CString *)lParam;
    messages.Add(s);
    return 0;
   }

The string will not be deleted until the element is deleted from the array. All that is important is that it be deleted when it is no longer needed, or you have a storage leak.

The same mechanism applies equally well to notifications from a document to a view. UpdateAllViews with a non-zero lHint can be initiated by either the document or by a view. For example, suppose there are two views which are CFormView views, and which hold data which belong logically in the document. A change in one should effect a change in the other.

This is actually trickier than you think it should be. The problem is due to the fact that a CEdit control will generate a change notification event whether the change is effected by the user typing or by the program doing a SetWindowText

So consider now two CFormViews. The first one has an OnChange handler that notifies the other of the change. It does this as follows:

void CMyFirstView::OnChangeInput()
   {
     updateControls();
     CString s;
     c_Input.GetWindowText(s);
     GetDocument()->SetInput(s);
     GetDocument()->UpdateAllViews(this, MYHINT_UPDATE_CONTROLS, NULL);
    }
void CMySecondView::OnUpdate(CView * pSender, LPARAM lHint, CObject * pHint)
   {
    switch(lHint)
      { /* lHint */
       case 0:
          ... normal update here
          break;
       case MYHINT_UPDATE_CONTROLS:
          { /* update */
           CString s;
           GetDocument()->GetInput(s);
           c_MyInput.SetWindowText(s);
           ... other controls as needed
          } /* update */
          break;
      } /* lHint */
    }

But there's a problem here. Suppose that CMySecondView has the same sort of code. When we issue that SetWindowText, it causes the OnChange handler of CMySecondView to be invoked, which would call UpdateAllViews, which would cause symmetric code in CMyFirstView to do a SetWindowText, which would invoke its handler again (recursively) and then the whole thing would continue until you ran out of stack space (several seconds on a 1GHz machine). I call this an "EN_CHANGE storm" (modeled after the phrase "interrupt storm" to describe what happens when you take an interrupt and fail to clear the interrupting condition). 

The solution is to have a special edit control that allows you to update it without getting an EN_CHANGE notification, a control I describe in a companion essay.

 So the mechanism is clean, even if it requires a bit of effort to make it work right in Windows.

So we have a more complex scheme for the interface now:

Queries from the Environment

Often these are easy. For example, for a dialog to inquire about the state of a control, it typically just calls a method of the control. This is because the environment (the dialog) knows exactly the methods of the component (child control). This poses no particular problem.

The environment should never actually ask for the contents of member variables; it should always call a method. The reason for using a method is that the implementation may require computing the value, rather than just delivering back a value. In many cases the value may, for example, actually be in a control. But the environment has no business knowing this. 

One of the more profoundly offensive pieces of code that I've seen was looking for the value in a selection based on radio buttons. It actually did something as ghastly as

int result = modeless->GetCheckedRadioButton(IDC_SOMETHING, IDC_OTHER); 

It is hard to imagine anyone could conceive of a piece of code like this making sense. For example, it means that adding another radio button obsoletes pieces of code the programmer of the dialog never heard of. It means the environment knows the code was implemented as a radio button set.

My job was to expand the set of selections from the original 5 to the actually-needed 27 values. There was not enough physical space on the dialog for 27 radio buttons, nor would it have been a usable interface, being so cluttered. So I did the obvious: I converted it to a drop-list dropdown box (one of my self-sizing dropdowns). Needless to say, the rather horrible piece of code, which I did not see, failed horribly. It was hard to imagine anyone doing anything this bad, but someone had.

I replaced it with what should have been done:

SomethingType result = modeless->GetSomethingSelection();

which is all that should ever have been written. The SomethingType was an enumerated type that had nothing to do with the representation of the values in the dialog; the dialog performed a mapping between SomethingType values and however it was represented in the GUI. See my essay "Who owns the GUI?" for additional discussion of this topic.

The problem arises when the component can be queried by several different places. It is also particularly a problem when there is a generic concept of a query. I have, for example, a component which has three different realizations, all of which have the same interface. There is nothing in common between any of these controls except their interface. That is, the fundamental representation of the modeless dialogs is completely different. The controls on each dialog are different. The only thing they have in common is that, by selecting some collection of settings in the dialog, a value is derivable. In this case, it is clumsy to provide a method call. So I chose to create the interface by sending a message to the dialog. It follows the rules of notification of the environment, but the case was somewhat simplified by the fact that the only values I return are scalars. So there is a symmetry which removes circular dependencies. The several dialogs were all managed from a single class, which did have a method call, which sent a message to the actual dialog which was represented by a reference it contained. This was a cleaner and simpler means of implementing a virtual method than most I had figured out. The class I used was not a window class; it was a class that launched a dialog and provided the interface. The dialog only understood queries that came in as messages.

So our interface specification has become a bit more complex:

Notifications from the Environment

These allow greater scope and possibility. These are ways in which the environment notifies the component that something has changed. We have already discussed one way that the views can notify the document of a change, and the document (component) can cause notifications to the view (environment). But it can also be useful to have the environment notify the component that something has changed. For example, if the environment provides a background color, and the background color changes, and the component is supposed to use the same background color as the environment, the component has to be notified that the color has changed. This can be done either by calling methods (for example, SetBackground(COLORREF color) ) or by sending it messages. Typically when I send messages to a control, I wrap it in a nicer interface than SendMessage, in the same way that MFC wraps its controls in methods. The programmer can't really tell if SetBackground is strictly a subroutine call that sets some value, or sends a message to the window. The abstract specification is all that matters. So whether I choose the implementation to be a straight method call, or a message, is none of the business of the environment. The same transparency that is maintained from component to environment is maintained from environment to component.

And, while most of these could be, and typically are, completely synchronous, nothing precludes the user of asynchronous notifications to the component. This may even be valuable if there could a significant delay in the processing of the request. This might mean that there is a thread in the component which performs the computations required to handle the notification. There may even be a notification that is sent or posted to indicate the completion of the computation. 

So notice we now have a nearly-completely-symmetric picture of the interfaces:

Output Specifications

Dialogs, particularly modal dialogs, often have the need to pass out to the environment the effects of the computation. The most common form of doing this is to have member variables. This is because the tooling makes this easy, by creating value variables that receive the contents of simple controls, such as static controls, edit controls, and check boxes. As with input, these are completely useless for radio button groups, list boxes, and combo boxes. You have to do something meaningful in your OnOK handler to save values from these controls. The tooling provided is somewhere between totally useless and outright offensive in its inability to handle these controls. Again, my essay on "Who owns the GUI?" outlines some of the issues here.

A number of Microsoft-supplied dialogs, such as the common dialogs, provide both techniques. Some return values are set by the dialog setting member variables; some return values are obtained by having the environment call methods in the interface.

A key issue here is that the SendMessage option is no longer available. There are no windows available to send messages to! So this is the symmetric problem with the initialization problem. 

Generally, the use of methods is the preferred mechanism for obtaining values. It allows the values to be computed, rather than forcing them to be stored. 

So we now have a completely symmetric set of interface issues to address:

The key here is how clean those interfaces are. A component should contain its entire specification for these interfaces, with no reference to the environment in which it might appear. That is, the component defines the interfaces; the environment uses them. No header files from the environment must ever appear in the component. No methods of the environment should ever be known to the component. The coder of the environment is entirely reactive to the interface specification of the component. The coder of the component could care less about the environment. The coder of the component, when properties of the environment are important, issues queries to that environment which are independent of the implementation of the environment. When the component experiences changes that need to be interpreted by the environment, it issues notifications to the environment that are independent of the implementation of the environment. The only way of the environment having any impact on the component is by calling methods of the component, some of which may actually send messages to the component. 

Dialogs in DLLs

Sometimes you want to create a dialog in a DLL. The interface issues here are even more restrictive.

For example, someone complained that in order to create a dialog that was in a DLL, he had to include the resource.h file in his main application.

This should have been an immediate clue that there was something wrong with the program structure. There is no reason to ever include the resource header file for a DLL in the main application compilation.

The problem was one of not understanding interfaces.

When I put a dialog in a DLL, not even the dialog class is exported. No method of the dialog class is visible. The dialog class itself is completely private to the DLL. There is no way to find out how it works at all. This is how a dialog in a DLL should be written.

To create the dialog, put the invocation in a separate function which is defined in the dialog DLL. That is, if your invocation of the dialog from your app was

BOOL AskUserForParameters()
   {
    CMyDialog dlg;
    dlg.name = name;
    dlg.count = count;
    if(dlg.DoModal() != IDOK)
        return FALSE;
    name = dlg.name;
    count = dlg.count;
    return TRUE;
   }

or something like that, this code can no longer exist in your app. Instead, in the DLL, place the call

__declspec(dllexport) BOOL CallMyDialog(CString & name, UINT & count)
   {
    CMyDialog dlg;
    dlg.name = name;
    dlg.count = count;
    if(dlg.DoModal() != IDOK)
       return FALSE;
    name = dlg.name;
    count = dlg.count;
    return TRUE;
   }

Now the only communication with the dialog is via the CallMyDialog function, which transmits and receives all results. At no point does the app have to have any idea how this is implemented.

A modeless dialog works much the same way. For example, I could do

__declspec(dllexport) CWnd * CreateMyDialog(CString & name, 
                                            UINT & count, 
                                            CWnd * parent /* = NULL */)
   {
    CMyDialog * dlg = new CMyDialog;
    if(dlg == NULL)
      return NULL;
    dlg->name = name;
    dlg->count = count;
    if(!dlg->Create(CMyDialog::IDD, parent))
       { /* failed */
        delete dlg;
        return NULL;
       } /* failed */
    return dlg;
   }

Now the only communication I have is from sending messages to the dialog, or receiving messages from the dialog, as already described.

The header files for the clients of the DLL-based dialog include, as with other styles of dialogs, the declarations of the messages to be used, those which can be sent to the dialog, and those which can be received from the dialog. And the function which invokes it (modally or modelessly). But they do not contain any hint of the dialog class. Note that the parent does not even need to know the class of the dialog in order to send it messages. CWnd * works perfectly well!

Summary

It is not enough to simply create a program that compiles and runs. Syntax is not what programming is about. Programming is an art, and there are beautiful programs and ugly programs. Programs that do things like know their environment, tweak controls in other dialogs, and so on are ugly. They are hard to create, hard to debug, hard to analyze, hard to maintain, and sometimes they never work correctly at all. There are a large number of ways to write correct programs. There are an immensely large number of ways to write programs badly that still give the illusion that you have accomplished something. And, of course, there are a near-infinite number of ways to write programs that don't work. But one thing I've learned about programming over the years: beautiful programs are easy to write, easy to debug, and easy to maintain and extend. They come up quickly, and they are resilient against a number of evolutionary changes. This is why I favor them. It is not some academic snobbery here; it is the fact that when I see a beautiful program, it is easy to read and understand. It is easy to maintain. It is easy to extend. There are not hidden side effects caused by bizarre casting, strange access patterns, and the like. You don't need "cross-reference lists" to figure out who is modifying variables; you can easily see who is modifying the variables. They are modified at selected narrow communication points, and in stylized ways. These are easy to understand. I have to maintain programs I wrote five years ago, and which I haven't seen for two years. If I would have to re-create massively complex logic, I would have no chance of dealing with these programs. I avoid the techniques I condemn in my essay on bad programming practices because using them makes my life miserable when they exist. I don't like being miserable. This essay shows how I avoid them.

[Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright 2003 The Joseph M. Newcomer Co. All Rights Reserved.
Last modified: May 14, 2011