![]() |
|
OverviewIt can be quite a daunting task to sit down and learn to write Presentation Manager programs. There is so much ground to cover, but fear not - getting over the initial learning curve is the hardest part. Once you have a grasp of the basics, it is fairly easy to pick up on more advanced techniques. Here I intend to provide a starting point for C programmers to get into PM, so that you do have a solid grounding. Now unfortunately there are not many books on PM which explain not just the what, but the why. That is, not just presenting a syntax diagram and saying that (for example) WinBeginPaint is used when you want to paint a window, but why you need it, when to use it (and not WinGetPS ), and how to use it. I will endeavour to explain the why, so you understand exactly what you are doing, instead of blindly copying code from examples. You will see the code, and at each step we will discuss the important sections, so you can see just what each API does. You certainly don't have to remember every single API though (there are hundreds). As you have a need to do something, you will learn the new APIs required to achieve that. Please remember that, as this is an introductory article, it is not presented as being 100% technically correct and accurate. It is more to give the reader a practical feel for what is going on. Technical references for technical questions appear in the Bibliography. And remember, you can always read the headers!
ScopeI am assuming that you are a competent C programmer, and have a working knowledge of OS/2 from a user's perspective. The sample code here was produced with IBM's C-Set/2, and is ANSI C. I do not assume anything but a familiarity with GUIs in general.
Trademarks etc.Please note that any trademarks referred to in this article remain the property of their respective companies.
#include
Presentation Manager is the GUI which sits atop OS/2. It consists mainly of a
series of DLLs which contain all the controls, APIs and other stuff which makes
PM tick. The WPS is in effect a PM application itself, albeit with certain
other special properties. One good thing about PM is that because the bulk of
the code to handle the user interface is in DLLs, applications tend to be
smaller, as they contain largely just the code to handle the processing of the
information. This lets the programmer focus on the "guts" of the program,
rather than being concerned with the user interface. Also, PM provides a
consistent and intuitive interface for the user along standard design
guidelines. Of course, as you will no doubt see, there is still a great deal to
learn about PM!
GUIs are modelled on an Event Driven paradigm. This is better understood when
contrasted with the sequential nature of conventional programs. Imagine the
following fictitious code which might appear in the menu of a program:
You can see that in the while loop, the program is constantly polling the
keyboard to check if a key has been pressed. This is a very inefficient
practice, as the machine spends most of its time in that loop, doing nothing.
In a multitasking environment, this waste of CPU time reduces system throughput,
as the CPU could be doing something more useful.
In OS/2 however, programs do not poll for input (whether from the mouse or
keyboard). All input is managed by PM itself. This means that when the user
clicks the mouse on a button for example, PM handles this event. It puts the
event into a queue for that application, and the function which handles this
event (the window procedure) only gets called when it has a message waiting for
it. As a result, the program only acts when it needs to, rather than constantly
checking if it actually has something to do.
So rather than following sequentially through the program, PM programs send,
receive and respond to events. This technique requires a shift in thinking, but
with practice it will become clear. It is arguably a more intuitive model for
programming, and it also promotes good technique.
A Window is simply a rectangular area on the screen. A window can have certain
properties or flags, and PM is full of them. They may or may not have a frame
or border, a titlebar, or a menu.
You may only think of a window as being the Frame window of an application.
However buttons are windows too. So are dialog boxes, list boxes, scrollbars -
in fact each control in PM is a window. We will explore this further in a
future article, but for now just take it that a window is rectangle.
Don't worry too much if this sounds a bit vague - it will become clear once you
start making your own windows.
The messages discussed in our talk of Events is simply a C struct, which looks
like this:
This struct is a key element in PM. The fields are explained thus:
All messages have a unique number, and the system messages are all #defined in
the OS/2 headers. Some typical messages include:
You will not normally deal with the QMSG structure directly - your window
procedure will handle this, and deals only with the handle, the message and the
two parameters (mp1 and mp2).
A handle is a unique number or reference for any given system object. The most
common handles refer to windows. An application can have any number of windows,
and handles are a convenient way to keep track of them. Handles are also used
internally by PM to manage its own information on the system.
A Window Procedure is one of the most important parts of any PM application.
Its job is to handle all of the messages that get sent to its window. It
usually consists of a big switch statement, with a case for each message. You
can handle as many or as few messages as you wish, because (fortunately!) one
of the APIs in PM is a default window procedure. So any message you don't need
or want to handle, you pass on to WinDefWindowProc, which handles it in the
default manner.
Well, enough of this banter - let's do something useful and make a PM program.
Let's start simply by getting a plain window on screen. I will show you the
code, followed by explanations, with the full source at the end of this section.
If you look at the file OS2.H, you will see a whole bunch of #ifdefs, and
#includes. Because the header files are so large and numerous, and you don't
always want to include everything since you may not use all of it, all you need
do is #define which parts you need to include. There are certain things which
are included by default, so here we only need to define INCL_WINFRAMEMGR, for
the definition of WM_ERASEBACKGROUND, as all the rest is already there. The
most important header is PMWIN.H, so take some time to browse through it as it
has some very important things in it.
Having defined which sections we need from the headers, all we need at the
moment is to include OS2.H.
We prototype our functions, like all good ANSI programmers!
The HAB stands for a Handle to the Anchor Block. As you know, a handle is just
a reference, but the anchor block is an internal data structure which PM
manages. The origin of the name is a little obscure. Anyway, each instance of
an application has an HAB. There are few times when you actually use it,
usually only during the initialization of your program. For now, let's say it
is like a handle to your application.
Now we have two other handles, this time handles to windows. Notice that we
have two handles. One for the client, one for the frame. This requires a
little more detail.
The Frame Window is the window from the edges of the resizeable frame border.
It contains the border, titlebar, minimize and maximize buttons, system menu,
and optionally a menu.
The Client Window is the area of whitespace bordered by the frame and the menu.
This is the space where information is displayed, and the user interacts with.
Take a look at the System Editor for example. The frame window encompasses the
entire application window. The client area is the area of white where the text
appears (the edit window). Having a separate client window makes it easier to
paint, scroll, and other such operations.
Here we launch straight into the main function. First we declare some local
variables. The HMQ is a handle to a message queue. When an application is
created, it needs a queue to keep the pending messages in, as they await
processing by the window procedure.
The QMSG is the data structure we looked at earlier (see Messages). This is
used in the main message loop below. The flCreate is used to keep the flags we
need to create our window.
The very first thing we do is call WinInitialize. This initializes certain
things in PM and gives us an HAB. If we could not get a HAB, something really
bad has gone wrong, so we exit immediately.
Here we create our message queue with our HAB, and get a handle to it so we can
refer to it later.
Because each application behaves differently and has their own window procedure,
PM needs to know certain things about your application. This is called
registering. You give it your HAB, a unique string (called the class name)
which identifies your program, a pointer to your window procedure (which is why
you cast it) and any particular flags you may require. These flags are called
Class Styles, and CS_SIZEREDRAW means we want our application to be redrawn if
it is resized. The last parameter allows us to reserve some space within each
window for instance-specific data, but we don't need this yet.
First we set up a variable which has ORed together a bunch of flags (called
Frame Control Flags) to tell PM what sort of window we want. They mean (in
order) we want a titlebar, a system menu, a resizeable border, both minimize and
maximize buttons, we want it to appear in the task list, and we don't care what
it's initial size or position is (let the shell decide).
Now comes the actual window creation. We specify the parent window (usually the
desktop, as is here). WS_VISIBLE means we want it to be visible, pass the frame
control flags, our class name (so it knows which window procedure to use), the
title of the window, then any other special flags. NULLHANDLE tells it to look
for Resources (such as icons, menus, dialogs, etc.) in the executable itself. We
pass the ID of the window (to distinguish it from other windows, and to
associate resources with), and finally get a handle to our client window. I
hope you're keeping up so far, because we're almost there!
This is the most crucial part of the program, and is really what gives it the
capability to respond to events. It is a constant loop which gets a message
from the queue and dispatches it to the window procedure. WinGetMsg will keep
going until it receives the WM_QUIT message. WinDispatchMsg actually gets PM to
call your window procedure, which is why all window procedures have the same
parameters (hwnd, msg, mp1, mp2). The other parameters in WinGetMsg allow you
to do fancy things which we won't get into now.
This is just for cleanup - once the user exits the application, we clean up and
release the resources we used.
As I mentioned above, because PM calls your window procedure, you must have it
declared exactly like this. EXPENTRY tells the compiler that this function will
be in the list of exports (a list of " public" functions).
Here we begin our amazing switch statement, which branches on the message. For
each case, we have a Window Message. This first one is PM asking if we want it
to erase the background for us. If we return true, it will do so. If we want
to paint the background (or client area) ourselves, we would return False.
This case is somewhat special. WM_CLOSE is sent when the user presses Alt-F4,
selects Close from the System menu, double clicks on the System menu, selects
Close from the Window List, or selects File|Exit from the menu (if there is
one). This gives us a chance to ask the user if they are sure they want to
quit, and make sure there are no changes to any files they might wish to save.
Here we use the WinMessageBox API, passing it the parent and owner, the message
we wish to display, the title of the message box, an ID (used for providing
Help), and some flags. The flags specify that we want Yes and No buttons, and a
Query icon. The call will return MBID_YES if they pressed the Yes button, in
which case we send our application the WM_QUIT message. This causes WinGetMsg
(in main) to return FALSE, the while loop to terminate, and finally cleanup
before exiting.
This part makes life easy for us. There are a myriad of other messages that our
application receives, and WinDefWindowProc will handle them all for us. This is
part of the beauty of the event-driven model - we grab only the messages which
interest us, and let PM do the rest. This gives us a standard look and feel,
with no extra work.
Now we have written a bare-bones PM application, follow the instructions in the
next section to run it.
You can extract the source code to a file. To do this, for each file, go to the
code, press Ctrl-F. This will create a file called TEXT.TMP in the root
directory of the current drive. Go to an OS/2 Window and copy that file to a
working directory, renaming it to the respective name (which appears in the
title). You should be able to compile and run the program straight away. Just
issue the command:
And then run:
You should see a plain window with a titlebar. Closing the window will confirm
with a dialog. Once you have seen the program running, go back and reread the
section explaining the code, so you can see just what is happening.
Now that you have a basic PM application running, in the next issue we will add
a menu, some dialogs, and respond to some messages that do something useful. It
may seem like a lot of code to just get a plain window to appear, but we will
see how easy it is to add features next month.
I welcome any feedback on this article (netmail preferred) - any comments or
suggestions you may have, questions on this article, or things you would like to
see in a future article. I hope you have learned something!
The following references were used in the preparation of this article:
|