EWin Tutorial - Dialogs

How do I use a menu?

It's easy! Make a menu in the resource editor. (I'll explain how to do this in a sec.) Remember the name your menu has. Include the MVC-generated header file that has your constants in it:

 
  #include "resource.h"

Now, in your object's constructor, call

 
  Menu( IDM_MENU1 );

Where IDM_MENU1 is replaced with the actual name of your menu. That's all there is to it. Remember that child windows (windows with the WS_CHILD style flag) cannot have menus.

So the menu now appears, right? But you also need to know when the user chooses a menu item. To do this, just overload OnCmdExecute(). Say you have a menu command with a value of ID_DOTHISTHING that does something, and an item named ID_CLOSE that is supposed to close your program:

 
  virtual bool TMyWin::OnCmdExecute(UINT CmdID)
  {
    if (CmdID == ID_DOTHISTHING)
    {
      InfoBox("You did this thing.");
      return true;
    }
    else if (CmdID == ID_CLOSE)
    {
      Close(); //this closes your window
      return true;
    }
    else
      return false;
  }

Simple as cake. What? You don't know how the resource editor works? Hmm. Ok... I will go over how to make menu resources in VC6. You should have a "Resource" tab on one side of your display. Click it, and you will see "MyProgram resources", where "MyProgram" is replaced with your program's name. Right-click on this, and choose "Insert..." from the menu that appears. A dialog will appear, showing all the different types of resources you could add. You want to add a menu resource, so double-click on "Menu". Now you will be presented with an empty menu. Click on the little rectangle, if it isn't already selected. Start typing some text. See, you are typing the text that will appear in the leftmost menu item. Now press Enter. Now you are editing the text that will appear in the first sub-item in that menu. Type some text and press Enter again. You can keep doing this to make more items. You can also add more top-level menu items by clicking on the little grey rectangle and typing some more text. You can insert items in between other items by pressing the Insert key. It isn't an amazingly intuitive interface, but you can get the hang of it. Now, assuming you've got your menu the way you like it, you need to assign ID numbers to each menu item. Double-click on a menu item. A Properties box will appear. Give each item a helpful ID name, say, ID_DOTHISTHING or ID_DOTHISOTHERTHING. You could also type in a particular number, but a name is better. This way, the compiler will automatically come up with a number for you, and will #define this name to be equivalent to that number. Then you can use this name in your C++ code, instead of typing in ugly numbers. (These #defines that MVC makes are placed in a file called "resource.h". You need to #include this file if you want to use the #defines.)


I want to bring up a dialog when somebody chooses one of the items on my menu.

Okay. Read the previous section about how menus work. Assuming you have the menu all working, you just need to add the dialog. But dialogs are not amazingly trivial, so I shall have to start from the beginning and work to the middle.

A dialog is a window. But instead of you manually setting up the positions and styles and whatnot of each of this window's children, you use a dialog template resource to initialize the dialog. In other words, instead of manually setting up the attributes of yourself and your children, you use a resource to set them all up at once.

You create a dialog using a resource editor. Each Windows application can have any number of "resources", blobs of data that are bundled into the .EXE file but that are not code. Menus, dialogs, and bitmaps are all varieties of resources that can be smushed into your executable file. You then retrieve these various resources using different Windows API functions, or the appropriate EWin object will automatically load them for you. Each dialog resource has a unique identifier, and this is what a TDlg uses to load up the appropriate resource.

Oh, another thing you need to be aware of. Each of the controls on your dialog can have an ID number. But since nobody likes looking at hard-coded numbers, you can use #define'd constants instead. You just type these into the dialog-editor GUI, and MSVC6 will then generate a #define for you. It puts all of the #defines that it makes into a file called "resource.h". So, if you want to use these #defines, your code must #include "resource.h" to get them. Note that ALL the #defines that the resource editor makes are placed in resource.h; this includes the #define for the dialog itself, as well as #defines for the items on each dialog. If you want to change the numbers in resource.h, you are allowed to. It's safe to do, I mean. Just make sure that you update the numbers at the bottom of the file. (At the bottom, MSVC makes a bunch of #defines for its own use, to tell it what numbers it has already allocated and what numbers it should use for the next dialog you make, etc. They have names like _APS_NEXT_RESOURCE_VALUE. If you change the resource numbers, make sure that these #defines are set in such a way that they won't overwrite your IDs. In other words, if you make IDD_MYDIALOG be the number 5, then you should make _APS_NEXT_RESOURCE_VALUE be, oh, say 6.)

[MORE] Borland C 5.0 uses a similar system, but instead of putting all of the #defines into a single file, BC5 distributes the #defines to various already-existing header files. It tries to "guess" which header file you'd want those #defines in, based on where other similar #defines are. Unfortunately, it often guesses wrong. So I have come to like Microsoft's approach better here, even though it means you have an extra file in your project.

So say I have a dialog template named IDD_ENTER_YOUR_NAME which has an editbox, a static text, and an OK and Cancel button. The editbox has an ID of ID_MYEDITBOX. The idea is that you type your name into the box and then press OK.

Here is some MVC-specific help on making this dialog. You should have a "Resource" tab on one side of your display. Click it, and you will see "MyProgram resources", where "MyProgram" is replaced with your program's name. Right-click on this, and choose "Insert..." from the menu that appears. A dialog will appear, showing all the different types of resources you could add. You want to add a dialog resource, so double-click on "Dialog". Now you will be presented with a dialog template. You should also see a list of controls, called the "Controls Toolbar". On my machine is is a long thin horizontal strip of buttons on the right side of the screen, but you can dock it anywhere, so it may be elsewhere. If you can't find it, it may not be turned off! Right-click on a blank area among the menus at the top of the screen, and you will be presented with a list of available toolbars. Find "Controls" on this list and choose it. Note that, annoyingly, "Controls" will ONLY APPEAR ON THIS LIST WHILE YOU ARE EDITING A DIALOG. When the dialog editor isn't in selected, you can't turn on the controls toolbar! Annoying. Anyway, so now you have the controls toolbar, yes? So now you want to add an edit box and a static. Find the "Edit Box" button, click it, and then click on your dialog resource where you want the edit box to appear. You can then resize it and drag it around. Now find "Static" on the toolbar and drop a static control on your dialog as well. Great! Now you just need to configure them. Right-click on the editbox and choose "Properties". In the "ID" field, type ID_MYEDITBOX, because that's what we want to call it. Click OK. Now, right-click on the static control and choose "Properties". You can give this control an ID name also, if you like.

Also, change the text in this control, so that it says, "Enter your name" or something like that.

Finally, you need to configure the dialog itself. Right-click on the dialog's title bar and choose Properties. You want to do a few things. First, give it a useful ID name. It will default to something like IDD_DIALOG1, which isn't very informative. Change it to IDD_ENTER_YOUR_NAME. Also, you may want to change the text that will appear in the titlebar. Finally, click on the "More Styles" tab, and make sure the "Visible" checkmark is checked. For some reason, the newest incarnation of Visual C++ defaults to having invisible dialogs! This can be very frustrating to debug, so make a habit of making sure your dialogs are visible.

Oh, one more configuration tip while we're right here. By default, if you double-click on a control in the dialog editor, it will bring up an MFC wizard. This isn't useful to EWin programmers! So instead, you can set the double-click to bring up the Properties dialog, which is much more useful. To do this, choose Tools->Options from the menu, then choose the "Compatibility" tab, then find the checkbox that says "Double-click in dialog editor edits code (MFC only)". Well, it isn't MFC-only -- it's broken. So uncheck that box. That will make your dialog editing a little easier.

Okay, now you're set. Close that window and go back to your code. The ID names that you entered, such as IDD_ENTER_YOUR_NAME or ID_MYEDITBOX, have been given numbers, and there is a file called resource.h that has #define's for each of these.

So now you need to whip up a C++ class to use this dialog resource.

 
  #include "resource.h" //this includes all the MVC #defines
                        //like IDD_ENTER_YOUR_NAME
  #include <dlg.h>      //includes prototypes for the dialog base classes
  #include <edit.h>     //include prototypes for line edits

  class TEnterYourNameDlg : public TModalDlg
  {
    public:
    TLineEdit Edit;
    STRING &Name;
    TEnterYourNameDlg(TWin *PParent, STRING &rName)
      : TModalDlg(PParent, IDD_ENTER_YOUR_NAME),
        Edit(this, TWinAttr().ID(ID_MYEDITBOX)),
        Name(rName)
      {
      }

    virtual void OnInit()
      {
        Edit.Text(Name);
      }

    virtual void OnDone()
      {
        Name = Edit.Text();
      }
  };

One thing to note about dialogs: they don't take a TWinAttr in their constructors. This is because they get all the TWinAttr information out of the dialog resource. So they just take the name (or ID) of a dialog resource to get the information out of.

Another thing to note: I created a TLineEdit named 'Edit', and gave it the ID of a control that was already in the dialog. 'Edit' figured out that the dialog had a control with this ID, and so it associated itself with that control. If I had given Edit a different ID, it would have created a second edit box on our dialog, because no control with the given ID would have existed yet. Again: the way that controls know which dialog control they are associated with is via the ID number. So make sure you get your ID numbers right, and make sure that no two ID numbers on the same dialog are identical.

A third thing to note: I didn't make a TStatic for the static control on my dialog template, and I didn't make TBtn objects for the OK and Cancel buttons. This is because I only need to make objects for the controls that I care about. So unless I am going to change the text of that static after the static has already been created, or something else like that, I don't care about the static at all. It can just sit there for all I care. The dialog template makes sure that simple controls like statics and buttons always gets created. (Of course, if I were not in a dialog, I would need to make a TStatic just to get the static to appear, even if I didn't do anything with the static after it was created. But a dialog makes sure that all of its controls are created, even if there is no C++ class associated with the object!)

So why didn't I make TBtn objects for the OK and Cancel buttons? Don't I care when these buttons are pressed? Yes, of course, but because these buttons appear in practically every dialog, the TDlg base class has special code to automatically handle them. So 99% of the time you don't need to make objects for these buttons -- they are taken care of for you.

So how do you make this dialog work for you? Easy. But first... First I have to get arcane on you. See, there are two kinds of dialogs. The first kind, a "modeless" dialog, is the obvious kind. It works just like a regular window: the parent window creates it, then it goes about your business, handling different menu options and so on, until the modeless dialog ends. Then you get the data out of the dialog.

But lots of the time you don't want anything else to be possible until that window ends. That is, you want to be able to wait for that dialog to end before any more of the parent window's code gets handled.

Well, this goes against the whole point of event-driven programming, but because it was so commonly needed and so handy, Microsoft made a huge hack to bring you the concept of a "modal" window. (It is "modal" because it puts your program into a "mode" until it ends.)

A modal dialog gets created and then your program waits until it is done. Since the dialog we made happens to make sense as a modal dialog and not a modeless one, I can show you with the following code:

 
virtual bool TMyWin::OnCmdExecute(UINT ID)
{
  if (ID == ID_DO_NAME_DIALOG)
  {
    STRING name = "Bob";
    TEnterYourNameDlg NameDlg(this, name);
    if (NameDlg.Create() == IDOK)
    {
      InfoBox("You changed your name to %s!", name.c_str());
    }
    return true;
  }
  else
    return false;
}

So this code whips up a temporary object (one that lives only in this function), and then calls Create(). Normally, calling Create() is done behind your back -- you don't typically have to call Create() for edit boxes and list boxes and buttons and so on. But EWin doesn't call dialogs' Create() functions because it don't know when you may want them to appear. Anyway, when you call Create() on most kinds of windows, the window is created and then the function returns. But a modal dialog is a big hack and is very special. When you call a modal dialog's Create() function, it doesn't return *until the modal dialog has come and gone*!

You use the return value of Create() to see how the modal dialog ended. If it ended with the user pressing OK, then Create() returns IDOK. If the user pressed Cancel, then Create() returns IDCANCEL. If Create() returns zero or a negative number, then the dialog failed to get created; something bad happened before the dialog even got shown.

Note that you should not normally have temporary TWin objects. A TWin that is only in one function won't live very long! For instance:

 
virtual bool OnCmdExecute(UINT ID)
  {
    if (ID == ID_MAKE_ME_A_NEW_BUTTON)
    {
      //using this menu command makes a new button appear on the main window
      TBtn Btn;
      Btn.Create(this, TWinAttr().X(50).Y(50).W(50).H(50).Text("New Btn!"));
      return true;
    }
    return false;
  }

This WON'T WORK. It will create a button, but once this function ends, the TBtn will go out of scope! Thus, the button will get created for a split instant and then it will disappear. (If you do this accidentally, the EWin library will note that something has gone wrong and generate some Assertion dialogs, but if you are in Release mode, you will just get a button for a fraction of a second and then it will go away. Moral: develop in Debug Mode!)


So how about other kinds of controls in dialogs? Listboxes, Comboboxes, Listviews, everything else?

Use pretty much the same formula for any dialog item. You use the resource editor to place the appropriate type of control on your dialog. Using the resource editor, you specify a #define constant value. This value is the control's ID number. Now, in your dialog object (the object that you inherited from TModalDlg or TModelessDlg), you create a TListBox or a TComboBox or whatever. Above, I created a TLineEdit for the single-line editbox that I had put on my dialog resource. If, in your case, you want a TComboBox, you would do something like this:

 
  #include "resource.h" //this includes all the MVC #defines
                        //like IDD_ENTER_YOUR_NAME
  #include <dlg.h>      //prototypes for the dialog base classes
  #include <combobox.h> //prototype for TComboBox

  class TChooseAnItemDlg : public TModalDlg
  {
    public:
    TComboBox ItemList;

    TEnterYourNameDlg(TWin *PParent, STRING &rName)
      : TModalDlg(PParent, IDD_ENTER_YOUR_NAME),
        ItemList(this, TWinAttr().ID(ID_MYITEM))
      {
      }
  };

Compare this to the code above, and you should see a pattern. For any kind of stock control, you use the same method.

Anyway, I had best keep going with this example. Well, this will create a combobox on your dialog, but there won't be any items in it. To add items to it, you call AddItem():

 
virtual void OnInit()
{
  ItemList.AddItem("Item 1");
  ItemList.AddItem("Item 2");
}

And if you want to find out what item has been selected, you can use GetSelectedItem()...

 
virtual void OnDone()
{
  int i = ItemList.GetSelectedItem();
  char s[ 512 ];
  ItemList.GetItemText(i, s, sizeof(s));
  InfoBox("You selected '%s' from the combobox.", s);
}


Help! My dialog won't go away! The OK and Cancel buttons don't do anything!

What has happened is that you have overloaded OnCntrlCmd(), and you didn't call the base-class version. Normally, closing down the dialog is done in OnCntrlCmd(), but you have short-circuited this. All you have to do to fix it is to call the base class for any OnCntrlCmd() messages you don't personally handle, as in:

 
bool TMyDlg::OnCntrlCmd(UINT CntrlId, HWND HCntrl, UINT CmdNum)
{
  if (CntrlId == IDC_MYNEWBUTTON)
  {
    //do some stuff here
    return true;
  }
  else
    return TModalDlg::OnCntrlCmd(CntrlId, HCntrl, CmdNum);
}

Make your code call the version of the class you derive from (which may not be a TModalDlg).

Next I want to talk about what it means if you see an Assertion Failure dialog instead of the dialog box you were expecting.


Help! My dialog causes an "Assertion Failure" to appear when I start it up, and it really shouldn't.

Well, let's run through the reasons why it might do this. It asserts because Windows complained. Are you sure the resource is there, and that you gave it the right name? A very common mistake is that you gave it a string name when the resource actually has an ID, or gave it an ID when the resource actually has a string. (This usually happens when you manually edit the RC file... any dialog that doesn't have a #define for its name is assumed to be a string-named dialog.) Try using the opposite of what you're using -- a string if you're using an ID, and an ID if you're using a string.

The other, more common reason is that you are using a control that the dialog doesn't know how to create. If you used a custom control, did you give it the correct string? AND, did you make a corresponding EWin object for that control? You need to do this.

Even if you aren't using custom controls, you often have to use an EWin object for various controls. For instance, dialogs don't automatically know how to make date/time pickers, so if you want to use a date/time picker, you'll need to make an EWin object with the same ID as the control on your dialog.

[MORE] The only controls that are guaranteed to always work in a dialog, even if you don't have a wrapper for them, are: statics, buttons, radio buttons, check boxes, group boxes, list boxes, combo boxes, edit boxes, and scroll bars. The other built-in types are called the "common controls", and you either need to use an EWin wrapper to get them to work, or else use the Windows API function InitCommonControlsEx().


Why can't I set control parameters in my dialog's constructor? The settings are lost!

Between the constructor and OnInit(), the dialog template's parameters overload the constructor's settings. That is, the styles, x, y, width, height, text, etc., that is in the dialog resource are loaded into memory and override any setting commands you've made in the constructor.

(go on to the next page in the tutorial...)