|
Conventions of
this document
|
Abbreviation |
Meaning |
|
GUI |
Graphical User Interface |
|
Win95 |
Windows 95 |
|
Win98 |
Windows 98 |
|
WinNT |
Windows NT |
|
Win2K |
Windows 2000 |
|
WinME |
Windows Millenium Edition |
|
WinCE |
Windows CE |
|
Win9X |
Windows 95, Windows 98, or Windows ME |
|
WinNT+ |
Windows NT 3.51, NT 4.0, Windows 2000, Windows XP |
|
API |
"Application Programmer's Interface" |
|
API Function |
A "low-level" Windows function such as GetTextExtent()
or MoveWindow() |
|
[MORE] |
This paragraph is for people who weren't confused by the preceding
paragraph and want more details. |
|
Windows |
Microsoft Windows |
|
windows |
blocks of screen real-estate. Note the capitalization. |
|
Msg |
a Windows Message |
|
Dlg |
a Windows Dialog |
|
Win |
a window |
How
is this document supposed to be used?
Well, it is supposed to be a tutorial that will teach you
the basics of EWin. Hopefully it has been broken up in such a
way that it can be used as a reference later on, also. But this
is not a stand-alone tutorial. You're expected to also have EWIN.CHM,
the compressed-HTML-help file that documents the various EWin
objects and member functions. When you see something that doesn't
make sense, or see a function and you can't guess what it does,
please look it up in the online help file and see if it becomes
clearer.
What is EWin?
EWin is a set of C++ wrappers for writing GUI applications
for 32-bit Windows platforms (Win95, Win98, WinNT, Win2K, WinME).
It wrappers such things as windows, dialogs, bitmaps and other
resources, the mouse, the keyboard, etc.
What
operating systems does EWin support?
EWin supports all 32-bit Windows platforms, including Win32s,
Win9X, WinNT+, and WinCE. To run in WinCE, you must rebuild the
EWin libraries with Unicode support. This is very easy (you open
the library project and add UNICODE to the list of defined values).
If you aren't building for WinCE, I recommend using the stock,
non-Unicode libraries, as Win9X doesn't support Unicode.
Would
you break EWin into basic parts?
I think we're getting a bit ahead of things. But sure, why
not? There is the TWin, which is the most basic kind of Window.
All other windows, including dialogs, are derived from TWin.
And every EWin application has a TModule object, which is poorly
named, but is basically the "brains" for the program.
The TModule houses the program's Message Loop. And there is TGdiObject,
which is the base class for all GDI objects such as pens, brushes,
bitmaps, and fonts. And then there is TFauxGdiObject, which is
the base class for things that are kinda like GDI objects: icons,
image lists, etc. But as I said, this is jumping the gun a bit.
How about we start with What is Windows Programming Like, and
then get around to this. Skip the parts you already know, of
course.
What is Windows programming like?
What kind of a question is that? Read "Programming Windows"
by Charles Petzold. This is THE book. Buy it, get it, steal it,
have it. You NEED this book in order to understand how Windows
works at the API level.
You don't need to read all of it, if you're going to use EWin.
But read chapters 1 - 3, at least. This will teach you how painful
Windows programming is without an object-oriented class library
like EWin.
Damn
you, tell me useful things about Windows programming!
Okay, okay. I will try to summarize. First off, I assume you
know how Windows works, from a user's point of view. That is,
you understand about windows and the mouse and stuff. If not,
go use Windows for a couple days and come back.
Okay. Pretty much everything on the screen is a "window".
This is a structure that is used internally to keep track of
what is on the screen.
Even fairly advanced things have at their core the same "window"ness.
A dialog is a fancy kind of window. A tooltip is a fancy window.
The start button? A window. Checkboxes? A window. Edit boxes,
where you type in text? A window. List box? A window. Mouse cursor?
Not a window. Just seeing if you were paying attention. Basically
(and there are a few exceptions), everything on the screen is
a window, except the mouse cursor.
Now what does that get us? Well, it means that everything
on the screen has some fundamental properties. That brings us
to the next topic...
What
are the fundamental properties that every window has?
Every window has an x,y position and width and height. In
Windows, coordinates are top-down. That is, the upper-left of
the screen is (0,0) and the bottom-right of the screen is (640,480)
or whatever your screen resolution is. (Yes, yes, this is backwards
from how mathematicians do things, and how OpenGL coordinates
work, and so on. But it's Windows, and you'll get used to it.
Most people find it pretty intuitive right off the bat.)
So all windows have an x, a y, a width, and a height. They
also have a "text" attribute. For most windows, this
is the text on their title bar. But if the window doesn't have
a title bar, say it is an editbox, then the "text"
field is the text that is currently in the editbox. Some controls,
like listboxes, don't display this "text" anywhere
at all. You can still get and set the text field, but it will
never be shown to the user.
All windows also have a bunch of "style" and "extended
style" flags. Some windows have even more, "extended
extended style" flags and so on. But all windows at least
have these two sets of flags, and there are a bunch of pre-designed
flags. These flags all have the WS_ prefix, for style flags,
and WS_EX_ prefix for extended flags. Sample styles are WS_VISIBLE.
If a window has this style, it is visible; otherwise it is invisible.
Another is WS_DISABLED, which, if a window has this,
causes the window to not accept user input. Many kinds of windows
draw themselves "grayed out" if they have this style.
For more styles, see WS_XXXX and WS_EX_XXXX
in the EWin help.
Oh, oh, another important thing that the styles tell you is
what kind of window it is. There are three basic "kinds"
of windows. These are Cascading Windows, Popup Windows, and Child
Windows. "Cascading Window" is the default, so there
isn't a style for that. A Popup Window has the WS_POPUP
style flag. A pop-up window is pretty much the same as a cascading
window, except that it isn't contained within it's parent, but
rather, floats free like a bird, able to be moved anywhere on
the screen. The last kind of window is a Child Window, which
has the WS_CHILD style flag. A child window is one that
is contained inside another window. It doesn't have a menu. It
is generally a "widget", that is, a GUI control that
the user interacts with, like a button or an editbox or a listbox.
Oh, and never make a window that has both the WS_CHILD
and the WS_POPUP style at the same time. You'll go insane
and it will behave unpredictably.
[MORE] There is actually a WS_OVERLAPPED style, but
it is just made for completeness' sake. It's value is zero, so
it doesn't matter if you try to bitwise-OR it into a window's
styles or not -- it won't do anything.[/MORE]
Another thing to note is that there are a few flags in the
"styles" area that don't have a pre-determined meaning.
They are used by different window types for different things.
For instance, the BS_DEFPUSHBUTTON style for button
windows has a value of "1", and so does the LBS_NOTIFY
style for listboxes. They both have the same value, but those
styles only have meaning for that particular kind of control,
so this is not ambiguous. You simply never give a list box the
BS_DEFPUSHBUTTON style. (If you did, you would be inadvertently
giving it the LBS_NOTIFY style instead.)
(Many controls need more flags than are available in the "styles"
flags, and so they create new "extended extended" styles
and so on. But don't worry about that right now.)
All windows have a menu. Well, that isn't exactly true. All
windows have an internal field where they *could* have a menu
ID. But only certain kinds of windows, Cascading Windows and
Popup Windows, can have menus. The third kind of window, Child
Windows, can't have menus. So, to save internal space, that menu
field is used for a different purpose: an ID number. Thus, child
windows have an ID number which you can use to easily reference
them. And non-child windows have a place where you can associate
a menu with them. You can never have both a menu and an ID number,
since they use the same 16-bit field internally. Got it? [Actually,
it appears to be a 32-bit field under Windows NT, but you had
probably better assume it is still a 16-bit field.]
Every window has a Parent Window field. It may be NULL, meaning
that this window has no parent. (Child Windows must have a parent,
of course.) What does Parent Window mean? Well, for child windows,
it indicates who owns the 'widget'. For other kinds of windows,
its meaning is more subtle and we'll get to that later.
If a window has a parent, and that window is NOT a Popup window,
then its x,y coordinate is relative to its parent's client space
rather than the screen. That is, if a button is at (0,0), it
will appear at the upper-left corner of its parent. But if a
popup window is at (0,0), it will appear at the upper-left corner
of the *screen*. Confused yet?
There are several other attributes that EWin "fakes".
In other words, these things aren't necessarily actual attributes
of all windows, but EWin pretends that they are. If you are using
EWin, then every window has a font, an icon, a cursor shape (the
shape the mouse cursor takes when it is over the window), and
a background brush (to determine the color your window is painted).
So, there you have it. Those are the things that every window
has. Now, every TWin represents a window, and so a TWin provides
functions for getting and setting all this data. It uses the
[controversial] system of providing two functions with the same
name. For backwards compatibility, it also provides Get and Set
versions of most, BUT NOT ALL, of these functions. Thus, if you
have a TWin and want to set its text, you would call
MyWin.Text("Texty Texty");
and to get the text out, you would use
STRING s = MyWin.Text();
Similarly, all TWin's provide functions named Style(),
ExStyle(), Font(), Icon(), BkBrush()
(the background brush), Cursor(), X(), Y(),
Width(), Height(), etc.
You could also use GetText() and SetText()
to set the text, or GetFont() and SetFont()
to set the font. But not all of these attributes have get/set
versions, so for the sake of orthogonality I recommend you use
the other versions. Or just hop into win.h and add the Get/Set
versions for the attributes that are missing them. <shrug>
Actually, I prefer the Get/Set versions myself. It is a hard
decision to make, and different people like different things,
so both versions are provided. If you want to use them, go ahead.
If I missed any Get/Set versions, let me know and I will add
them.
Every window also has a window class, but you don't need to
worry about that too much...
What's
a window class? And why do I care?
You probably don't care. But I will explain a bit anyway.
Internally, deep down inside Windows, every window has a Window
Class. Don't confuse this with a C++ class -- a Window Class
is represented by a WNDCLASS structure. This structure
describes some of the more "fundamental" attributes
of a window. There may be 100 of a particular kind of window,
and yet all of them will share the same class data. A window's
class is an "old-school" part of Windows, and has been
nearly 100% hidden by EWin. You very rarely need to care about
the window's class. Just know that it is there. There are two
exceptions. A window's "class name" is often very useful
for finding a window. And a window's Class Styles are sometimes
used to configure some of the very low-level aspects of a window.
But you don't need to fiddle with a class to set these things
now. If you have a particular set of CS_ class styles
you want to use, just overload TWin::GetClassStyle()
and have it return the styles you want. Also overload TWin::GetClassName()
and have it return a unique name. Whenever you overload
GetClassStyle() you should also overload GetClassName()
and provide a new, unique name for you class. (Otherwise your
GetClassStyle() function may not be called, because Windows will
go, "Oh, hey, this window has the same name as another kind
of window I already know about; no need to ask it for its info
-- I already know it!").
[MORE] Sometimes you may want to just overload TWin::GetClassName()
without overloading TWin::GetClassStyle(). This is useful
if you want to later find your window by calling the FindWindow()
API function.
[MORE] If you really want to set the class information yourself,
you can overload TWin::GetWinClass(). You shouldn't
need to do this, except in some cases when you want fancy WinNT
3.51 support, and a few very arcane situations. Usually, the
library's code fills this structure in with sufficient defaults,
and takes care of all of the messy details. But you can certainly
overload GetWinClass() to do whatever you want. I highly
recommend that you call the base class version of GetWinClass()
first (to get good initial values for the different fields) and
then just change the fields that you need to change.
[Advanced Topic] I know something about Windows programming
already. Where is the message dispatch loop, the WNDPROC window
procedure, and the DLGPROC?
A: These things are done for you by EWin. The message-dispatch
loop is done for you by the EWinMain() macro [via the
call to Module.Run()]. EWin has a hidden WNDPROC
and DLGPROC function. There is only one of each. They're
in module.cpp. They are "generic": they don't do anything,
really, except call the appropriate TWin's OnMsg()
function. So instead of writing a WNDPROC for each different
kind of window, you create a TWin for each different
kind of window. All messages received by that window are sent
to its OnMsg() function. So if you were 'porting Win32
C GUI code to EWin, you could just overload an OnMsg()
and move all of your WNDPROC code there with only minor
changes.
But don't do this for new programs. Instead, overload the
functions you care about. See, the stock TWin version of OnMsg()
figures out what kind of message it is, and then calls another
OnXXX function appropriately. So instead of directly overloading
OnMsg() to handle, say, a mouse event, overload OnMouseMsg()
instead. This provides better encapsulation and makes it easier
to alter your window's behavior in derived classes. It also makes
the code cleaner.
So do you ever overload OnMsg()? Yes, for messages
that don't already have OnXXX messages. Say you need to handle
WM_IME messages, which haven't been wrapped by EWin;
you would overload OnMsg(), and if it's an IME function,
have it call some new OnImeMsg() virtual function that
you create. (If it's not an IME message, have your OnMsg()
function call the base class OnMsg() function so that
the other kinds of messages still get dispatched.)
Note also that you don't need to do this for WM_USER+###
messages or WM_APP+### messages -- all of these messages
are sent to OnUserMsg() or OnAppMsg(), respectively.
So,
anyway, how do I get started? I want a minimal EWin application!
Okay, okay. A minimal EWin app is actually nice and small.
HOWEVER, if you are using Visual C++, the code won't be your
biggest enemy. Your enemy will be the environment, and setting
up the 5,000,000 settings and toggles you need to make things
work. For this reason, I recommend you use the EWin Application
Wizard to whip up a basic application. You can then throw away
the code that it generates, if you want, but it will have set
up all the myriad switches, which is the hard part.
[MORE] If you are using Borland C++ 5.0, it's easy: just add
Ewinlib.lib to your build, pretty much. (Or EWinlibd.lib for
the debug version.) When you use the debug lib, #define DEBUG
for your project.
But for purposes of explaining things, here's the smallest
possible EWin app:
|
|
#include <common.h>
#include <win.h>
EWinMain( TWin(NULL, TWinAttr()) );
|
There you go. This will make a window that just sits there.
When you close the window the program will end. You can even
make this fancier:
|
|
#include <common.h>
#include <win.h>
EWinMain( TMyWin(NULL, TWinAttr().Text("Hello World!")) );
|
This will make a TWin with the titlebar text "Hello World!".
Now you are probably wondering what in the eight hot hells
that TWinAttr() thing does. Don't sweat it just yet.
It's a neat trick that lets you specify any of the TWin's
"built-in" attributes like text, x, y, width, height,
style, etc., without having to specify them ALL. In other words,
it is a C++ technique that gives you variable-argument functions.
I will go over it in detail soon. But for now just trust me.
Okay, so you got your cheesy one-line program. How to make
it do more? You'll need to use a non-stock TWin. So:
|
|
#include <common.h>
#include <win.h>
class TMyWin : public TWin
{
public:
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes) { }
};
EWinMain( TMyWin(NULL, TWinAttr().Text("Hello World!")) );
|
This actually doesn't do anything different -- it still just
says "Hello World" -- but now you are ready to change
it's behavior by overloading things. Like so:
|
|
#include <common.h>
#include <win.h>
class TMyWin : public TWin
{
public:
TMyWin(TWin *PParent, const TWinAttr &Changes)
: TWin(PParent, Changes) { }
virtual bool OnMouseMsg(MouseMsg Msg, WORD KeyFlags,
short WheelDelta, TPoint XY)
{
if (Msg == WM_LBUTTONDOWN)
MessageBeep(1);
return true;
}
};
EWinMain( TMyWin(NULL, TWinAttr().Text("Hello World!")) );
|
This new version catches mouse events. When the user presses
down on the left mouse button inside your window, your window
will now beep. Exciting, no?
This simple example seems to present several more questions...
What
is EWinMain, exactly?
A: EWinMain is a macro. It is defined in emacros.h.
It is one of the very few macros that EWin uses. Macros are generally
considered icky and wrong. But EWinMain hides the stupid
stuff you have to do for every Windows GUI program. What it does
is this: it creates a TModule object, which every EWin
program needs. Then it creates an instance of the TWin-derived
class you passed it, and makes this the main window of your program.
Then it calls TModule->Run(), which causes your program
to do its message loop, dispatching events to the windows that
need them. Maybe it would be easier if I show you what this tranforms
into.
|
|
EWinMain( TWin(NULL, TWinAttr()) );
|
Yields code very similar to (but not exactly like) this:
|
|
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TModule Module(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
TWin *pMainWindow = new TWin(NULL, TWinAttr());
Module.SetMainWin(pMainWindow);
if (!pMainWindow->Create())
Module.Quit();
int n = Module.Run();
delete pMainWindow;
return n;
}
|
So you see it creates the program's "main" function,
or, as it is called for Win32 GUI apps, the "WinMain"
function.
Sometimes, though, you need to do something before or after
the main window has been created. There is a different version
of EWinMain for this: EWinSuperMain. It takes
three parameters. The first is the same as for EWinMain().
The second is the name of a function to run before it does anything
else, and the third is the name of a function to run AFTER everything
else is done (right before the "return n;" line). For
instance:
|
|
void PreFunc()
{
//do pre-setup-processing here
}
void PostFunc()
{
//do post-run processing here, if any; otherwise just leave it blank
}
EWinSuperMain( TWin(NULL, TWinAttr()), PreFunc(), PostFunc() );
|
What could be easier?
Don't answer that.
There are two more versions of EWinMain, used when
you want a different message loop so that you can detect when
nothing is happening. These are EWinMainWithIdleChecking()
and EWinSuperMainWithIdleChecking(). Don't worry too
much about all this right now, though. Just use EWinMain().
What
the HELL is TWinAttr()??? And isn't that a syntax error, there?
To explain what TWinAttr is and how it is used, it is probably
easiest to explain why we came up with it. See, we used to have
a whole bunch of different TWin constructors. If we wanted to
set the width and height, we'd use
|
|
p = new TWin(PParent, width, height);
|
and if we wanted to set the text and the width and the height
we'd go
|
|
p = new TWin(PParent, text, width, height);
|
and so on and so forth. Well, there are a LOT of different
parameters that we like to set on our TWins. This meant
we had LOTS of constructors for TWin, and LOTS of constructors
for each thing that derived from a TWin. It hurt, and
it was unusably sucky. So we got rid of them all. Then, if we
wanted to set the width, we would have to go
|
|
p = new TWin(PParent);
p->Width(width);
p->Height(height);
p->Text(text);
|
And just set the things manually. This was a good solution
and we used it for a while. But programmers are lazy and we soon
longed for those constructors back. It saved us a few lines.
What we needed was a way to specify some of the different fields,
but not all of them all the time. So I read some books by Bjarne
Stroustrup (the designer of C++) and found out how to do this.
It's a neat trick.
What we do is we have a structure, called a TWinAttr,
that has all the different things that are common to all windows
(Text, X, Y, Width, Height, Font, etc., etc.). And all of our
TWin-derived objects take one of these TWinAttr()
objects. They all just pass it to the base TWin constructor.
The base TWin constructor gets all the data out of the
TWinAttr and puts it into itself. So it's pretty straightforward.
I could write:
|
|
TWinAttr Attributes;
Attributes.Text("This is the window's default text");
p = new TWin(PParent, Attributes);
|
and the window's default text would be set to the string above.
But yes, yes, you saw the TWinAttr being used for
more evil magic than that.Well, in order to be able to stick
all this stuff on one line, we create a temporary TWinAttr.
That's what the () are for. So
|
|
p = new TWin(PParent, TWinAttr());
|
Creates a temporary TWinAttr and then throws it away when
the function is done.
But if we want, we can call member functions of this TWinAttr.
We can even chain them together, because all of TWinAttr's
functions return a const reference to themself! Thus, we can
say
|
|
p = new TWin(PParent, TWinAttr().Text("blah").X(6).Y(5));
|
Hmm, I think this is advanced material. Well, never mind how
it works. It just works. If you are scared of it or it confuses
you, just don't use it. It is definitely not required. You never
*need* to use it to do anything. It is just a kind of shorthand.
What
is the first parameter that you pass to the TWin's constructor,
and why has it been NULL in the examples so far?
That's the pointer to the window's parent window (or NULL
to indicate that it doesn't have a parent window).
The first parameter of the TWin that you use in EWinMain()
is always NULL, because that is supposed to be your
main window, and it shouldn't have a parent. But all the TWins
that your main window owns will get your main window as their
parent. Thus, if you needed to make a dialog in your main window
somewhere, you might go:
|
|
TMyDlg Dlg(this);
Dlg.Create();
|
You passed it a pointer to its parent, which is you, the main
window. ("this" is a C++ keyword that gives
a pointer to the current object.)
Why
isn't that first parameter, the Parent pointer, smushed into
the TWinAttr like all the other attributes?
Well, it... I... hm. I didn't think of that. But now that
I have thought about it for a moment, it doesn't sound like a
good idea, because you set the parent pointer a LOT, and the
other attributes get set much less often. I like it this way.
Pbth.
(go on to the next page
in the tutorial...) |