|
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:
Now, in your object's constructor, call
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...) |