Indeed, as noted in the comments, there are volumes that could be
written about how to write a graphical user interface (GUI) as well as
an enormous number of different tools and techniques you can use to do
it. However, while there are many different tools, the fundamental
concepts behind how they work are more or less the same. I've
programmed GUI applications in everything from C++, Java and TCL to
Rexx, for platforms ranging from Windows, Mac, Sun and even OS/2. Even
though the way certain tasks are achieved are clearly different on
each platform, in the end most GUI apps do more or less the same
thing.
I can't tell you everything there is to know, but I can introduce you
to the major concepts that will help you on your way to learning more
on your own, which appears to be what you are after. As I introduce
the concepts, I'll give you some tips about how they apply to various
tools available. I'll bias the conversation toward C++ and Windows,
since you seem to have indicated that this is something that you are
familiar with; however, again, these ideas should hold no matter which
platform you choose to develop for.
-------------------------------------------------------
I. Event Driven Programming, Part #1
-------------------------------------------------------
The most challenging hurdle that most new programmers face when moving
from the command line to a graphical user interface is getting their
head around event-driven programming. While there are some rare
exceptions--I worked on a couple of PlayStation2 games that had menu
systems that were only semi-event driven--practically ALL graphical
applications use an event-driven architecture.
In a typical command line application, you get some input from the
user at startup, immediately do some work based on the input and then
exit. If you need more input from the user you display a prompt and
wait there until the user enters something. [KEY POINT: The *program*
controls the flow of execution.]
In an event driven application, the program starts up and then waits
there for "something" to happen. "Something" might be a keystroke,
mouse click, tick from a timer or any number of other things. Most
often, it's related to some sort of input from the user. When
"something" happens, the program then responds and does some work.
This "something" is called an EVENT. Events are the core of
event-driven programming. Events tell the program when the user does
something that the program needs to respond to. [KEY POINT: The *user*
controls the flow of execution.]
Here is some pseudo-code for a typical command line program:
void main()
{
getKeystroke();
doSomeWork();
exit();
}
Here is the same pseudo-code converted into a home-brew event-driven
program:
void main()
{
// do forever (this loop is also called the "pump")
while(true) {
// every time "something" happens, call
// the function that goes along with it
if ( getKeystroke() ) {
onKeyPress();
}
if ( exitKeyPressed) {
exit();
}
}
}
void onKeyPress() {
doSomeWork();
}
Cheesy as that sounds, that is exactly how most event-driven
applications work... including large Windows applications. I am
over-simplifying a bit, but not by much. In addition to events,
Windows adds an extra layer called MESSAGES to make its job easier.
Messages are just bits of data (a UINT to be exact) with some
parameters attached that get passed around in order to tell a program
what event just happened. Each MESSAGE passes a number, which
represents a specific EVENT. Then the program's EVENT HANDLER is
called with that message number. An event handler is a function in
your program that handles an event; or in other words, a function that
does "something" when "something" happens.
In our pseudo-code listing, this concept could be written like so:
void main()
{
// do forever ("MESSAGE PUMP")
while(true) {
// "translate" the events... every time "something" happens,
// set the variable "message" to the number that corresponds
int message;
if ( getKeystroke() ) {
message = 1;
}
if ( exitKeyPressed) {
message = 2;
}
// "dispatch" the events... call a main event handler to
// figure out which sub-event handler to call
doEvent(message);
}
}
// MAIN EVENT HANDLER
void doEvent(int message)
{
switch(message) {
case 1:
onKeyPress();
break;
case 2:
exit();
break;
}
}
// KEYPRESS EVENT HANDLER
void onKeyPress() {
doSomeWork();
}
This may seem like a round-about way to do things, but messages make
it easier for Windows to pass important events between different
programs. If the program doesn't know what to do with an event, it can
also pass it to the Windows default event handler. The default event
handler is just another function that figures out what to do with the
event when you aren't sure or simply don't want to deal with it.
Following is code for a real Windows program to show you what I mean.
This code will compile and run with Microsoft Visual C\C++6.0; it
should also run with Borland or any other C\C++ compiler that supports
the Windows API. Just in case you aren't sure, the Windows API is a
set of functions, libraries, etc., provided by the operating system to
do the basic things that are required by a graphical program. This
saves you from re-inventing the wheel every time you write a program;
the code for things that all windows programs need, like the basic
functionality of a button, is already done for you. Other platforms,
such as the Mac or X Windows\Motif, have similar APIs. From my
personal experience, I can say that the Windows API is more
full-featured and does a lot more things for you than many of the
other platforms, at the expensive of being enormous, sometimes
over-featured and often overwhelming.
The following code will create a basic window. It also handles the
PAINT event, and provides a sample function that draws a circle on the
window. (I apologize in advance if some of the code wraps funny. You
may need to copy and paste this into a mono-spaced editor to read it
more clearly.)
**********BEGIN LISTING****************
#define STRICT // strict type checking
#define WIN32_LEAN_AND_MEAN // exclude seldom-used code
#include <windows.h> // needed to write windows code
/*---------------------------------------------------------------------------
Paints the Window Client Area
-- This is your event handler for the PAINT event. For each thing
you
want to do, such as handle a keystroke or a button press, you'll
have
an event handler function just like this.
Parameters: HWND hWnd - handle to current window. Windows
identifies each
form and control in an application by assigning it a
handle.
Most API functions need to know the handle of a window
in order
to do anything interesting to it.
Returns: <nothing>
---------------------------------------------------------------------------*/
void onPaint(HWND hWnd)
{
PAINTSTRUCT ps; // structure for paint info
HDC hDC; // handle to graphics device context,
i.e. where to paint
// create device context, prepare for painting
hDC = BeginPaint(hWnd, &ps);
// draw a circle
Ellipse(hDC, 0, 0, 50, 50);
// update the window client area with whatever else we want
// delete device context
DeleteDC(hDC);
// end painting
EndPaint(hWnd, &ps);
}
/*-------------------------------------------------------------------------
Windows Message Pump Callback Routine
-- This is your MAIN EVENT HANDLER. This function takes the events
that
Windows passes in, figures out what they are, and then calls your
additional
sub-event handler functions to handle the event. Any events that
aren't handled
get passed on to the default event handler supplied by Windows
(DefWindowProc).
This saves you from having to write code for events you don't care
about,
like, say, a notice that the window was resized.
Parameters: HWND hWnd - our window's handle
UINT wMsg - message number
WPARAM wParam - word parameter, contents differ for
each message
LPARAM lParam - long parameter, contents differ for
each message
Returns: 0 if we handled the message, result code otherwise
---------------------------------------------------------------------------*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM
lParam)
{
// select and handle possible window messages
switch(wMsg){
// window created, initialize application
case WM_CREATE:
// do whatever you need to do on startup here
break;
// window close suggested
case WM_CLOSE:
// do whatever you need to do on shutdown here
DestroyWindow(hWnd);
break;
// paint window requested
case WM_PAINT:
onPaint(hWnd);
break;
// application terminated
case WM_DESTROY:
PostQuitMessage(0);
break;
// pass on other unhandled messages to default handler
default:
return(DefWindowProc(hWnd, wMsg, wParam, lParam));
}
// return 0 if we handled the message
return 0L;
}
/*-------------------------------------------------------------------------
Main Entry Point
-- this is the equivalent of "void main()" for a windows program
Parameters: HINSTANCE hInstance - current program instance
HINSTANCE hPrevInst - handle to the previous instance
LPSTR lpszCmdLine - command-line arguments
int nCmdShow - window size/mode
Returns: the value of PostQuitMessage
---------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR
lpszCmdLine, int nCmdShow)
{
HWND hWnd; // window handle from
create window
MSG wMsg; // message from
GetMessage
WNDCLASS wndclass; // window class
structure
char *strProgramName = "My Program Name"; // the name of your
program
// if this is the first instance of the application, register the
window structure...
// back in the day, system resources were very tight, and programs
that used several
// similar looking windows could save loads of memory by storing
only one "design"
// for a window in memory. new copies of the application in memory,
or "instances",
// could point to the window design that was already in RAM instead
of loading it
// again. even though it's less important today now that computers
have gobs of
// memory, windows still works this way; it has a number of misc
advantages.
if( ! hPrevInst ) {
wndclass.style = CS_HREDRAW | CS_VREDRAW; //
window style
wndclass.lpfnWndProc = WndProc; //
address to event handling procedure
wndclass.cbClsExtra = 0; // no
extra class data
wndclass.cbWndExtra = 0; // no
extra window data
wndclass.hInstance = hInstance; //
handle to the current instance
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //
stock arrow cursor
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //
stock icon
wndclass.lpszMenuName = NULL; // menu name
wndclass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); //
background color brush
wndclass.lpszClassName = strProgramName; //
window class name
RegisterClass(&wndclass);
}
// create the main window. you can change the parameters to this
function
// to make different kinds of windows
hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, // extended
window style
strProgramName, // program name
strProgramName, // window
titlebar caption
WS_OVERLAPPEDWINDOW | WS_SYSMENU, // base
window style
CW_USEDEFAULT, // default
x position
CW_USEDEFAULT, // default
y position
CW_USEDEFAULT, // default
width
CW_USEDEFAULT, // default
height
NULL, //
parent's handle
NULL, // menu
handle
hInstance, // handle
to the instance
NULL); //
initialization data
// display the main window; unless you make the window visible as
part of the window
// style you specify, the window is invisible by default
ShowWindow(hWnd, nCmdShow);
// windows "message pump"... remember that "while(true)" loop that
sat around
// waiting for events in the pseudocode? well, here it is! through
this loop, messages
// are parsed and eventually sent by windows to your main event
handler, WndProc
while(GetMessage(&wMsg,0,0,0)){
TranslateMessage(&wMsg);
DispatchMessage(&wMsg);
}
// return exit code from program, obtained from PostQuitMessage
return wMsg.wParam;
}
**********END LISTING****************
-------------------------------------------------------
II. Event Driven Programming, Part #2
-------------------------------------------------------
If you actually read the previous listing, you might be reeling a
little by now and wondering if there isn't an easier way. Thankfully,
the answer is "yes".
Most programmers don't want to deal with the whole window setup
routine (WinMain) or frankly the main event handler (WndProc) either.
Most programmers just want to cut to the chase and write the
application-specific code for the events they are interested in (i.e.
onPaint). Fortunately, the people who write development tools are not
insensitive to this need. Many tools exist that will help you do
exactly that.
MFC, for example, is a window framework that does the dirty work for
you. MFC handles the window setup and teardown that is basically the
same for all apps, and allows you to write only the onWhatever event
handler functions that are meaningful to you. Deep, deep down it still
has a message pump but you'll never see it... and for most people,
that's a good thing.
Many other window frameworks other than MFC are available and range
from basic application skeletons to libraries that--like MFC--contain
a lot of extra code for other things that GUI programs like to do:
such as send data over a network or write to a database. ViewKit, for
example, is a basic window framework for CDE and OSF/Motif developers
on Unix platforms.
-------------------------------------------------------
III. Widgets, Windows and Gadgets, Part #1
-------------------------------------------------------
The word WIDGET is a short for "window gadget". A widget is any
control you place on your main window (or FORM) such as a button,
textbox or scrollbar.
Once you understand the basics of how to get an event-driven window up
and running, you'll want to start adding widgets to make your app,
well, do something interesting. If you wanted to add a button using
the Windows-API, you might add the following code to your WinMain
function right before the call to ShowWindow:
CreateWindow(
"BUTTON", // button class control name
"Click Me", // default text
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, // window style
30, // horizontal position
30, // vertical position
50, // width
50, // height
hWnd, // handle to parent or owner window
(HMENU)IDC_MYBUTTONID, // button id, anything you like
hInstance, // handle to application instance
NULL // pointer to window-creation data
);
Be sure to make assign a unique ID to you button. Do this by pasting
the following line at the top of the example:
#define IDC_MYBUTTONID 100
Notice that the CreateWindow function is used to make a button.
Buttons, and other widgets, are really just windows themselves much
like your main application window, but with special functionality
written into them to make them operate as buttons.
To make the button actually do anything, you'll need to attach an
event to it. When a button is clicked, Windows fires the WM_COMMAND
event. It also sets the low-order word of the wParam message parameter
to the ID of the button, so that you know what button was clicked. To
process this event, you'll need to add the following code to your
WndProc function:
// button clicked
case WM_COMMAND:
if (LOWORD(wParam) == IDC_MYBUTTONID) {
onClickMyButton(hWnd);
}
else {
// some other button got clicked
}
break;
Then you would add a function to serve as the event handler for your
button:
void onClickMyButton(HWND hWnd)
{
MessageBox(hWnd, "Yeah, you clicked me!!", "I was Clicked", MB_OK);
}
Congratulations! You've just made a real, working fully graphical
pure-Windows API program that actually does something. It's not much,
but it fully demonstrates the core of what is involved in producing a
simple application with a GUI.
-------------------------------------------------------
IV. Widgets, Windows and Gadgets, Part #2
-------------------------------------------------------
Just like manually writing the code for the event pump, most
programmers have no tolerance for manually coding every button on
their form. Not only is it a pain to do, it's difficult to maintain
when you want to make design changes. While there's no reason you
can't do it that way, the truth is that most major applications do not
create all of their widgets by calling CreateWindow.
Most windows compilers include what is know as a RESOURCE COMPILER,
which generally also includes a graphical resource editor. A RESOURCE
EDITOR lets you design your form by dragging and dropping buttons,
scrollbars and other widgets onto it. When you are done designing,
your form is saved as a RESOURCE SCRIPT and the positions of your
buttons and other widgets are stored inside. When you compile your
application, this script is compiled into your application by the
resource compiler. Various API calls allow you to simply load even
complete window dialogs from a resource file, which saves you the
trouble of writing code to position all of the controls.
Tools such as MFC can help you simplify connecting your code to the
resources you create. However, you can use a resource editor to help
you design your user interfaces whether you use MFC (or any other
framework) or not.
-------------------------------------------------------
V. Toolkits
-------------------------------------------------------
Sometimes even a resource editor and window framework are not enough.
Many people like to shortcut designing their applications even further
by using one of many different toolkits. For example, Tcl and the Tk
Toolkit is one popular means for designing simple GUIs. Many people
develop just the graphical portion of their application in Tcl\Tk, and
then write the remainder of the application that does the actual work
in C++. This is often a very quick way for people to add a GUI to a
command-line application without investing a large amount of time
programming the interface in C++. Mktclapp is a tool that can help you
do that a bit more easily, and can be found here:
http://www.hwaci.com/sw/mktclapp/
Some other window toolkits and frameworks were already mentioned in
the comments.
-------------------------------------------------------
VI. What Tools to Use
-------------------------------------------------------
Which development tool(s) to use is a very personal decision based on
a large number of different factors. Different tools vary greatly in
both performance and ease of use. Some tools are also better suited to
different types of applications. For example, since we've been
discussing MFC, MFC would be a great choice for writing a spreadsheet
application for Windows because it is easy to use and creates many
standard kinds of window appearances with less effort. However, MFC
would be a terrible choice for developing a trendy screen hack because
it makes it far too difficult to get under the hood. MFC would also be
a poor choice for developing a spreadsheet application if you intended
to port the application to several different platforms, say Windows
and Linux. If you wanted to do something like that, you might be
better off using a different compiler entirely, perhaps such as GCC,
along with some sort of cross-platform window toolkit.
My best advice is just to shop around and see what feels right for
you. Think about what kind of application you want to write, and see
if you can find something similar that someone else has done and find
out what they used to do it. The other thing to consider is how much
you are willing to spend. Some tools--like many of the Linux
tools--are free, but some of the other tools--like the Microsoft
compilers--can be kind of pricey. On the flip side, if you take a
college course, you might be able to get the student edition of any of
them for fairly cheap.
-------------------------------------------------------
VII. Where to Learn More
-------------------------------------------------------
Obviously, there is a lot more to learn about developing applications
with graphical user interfaces. You might consider a course at your
local community college, or at the very least, a trip to your local
library. Many of the "For Dummies" books are actually very good as are
most of the books published by O'Reilly.
Don't forget to read the compiler documentation, either. Although I
had a programming background to begin with--both my parents are
programmers, so they taught me ever since I was little--I first taught
myself C years ago strictly from the Borland help files. The MSDN has
a wealth of information and lots of useful examples. Many of the
programming tools available come with some basic docs or sample files
to get you started.
The following sites have good links to information (and lots of code
you can swipe :-) ) as well as answers to common questions related to
programming graphical applications for various platforms:
Experts Exchange
http://www.experts-exchange.com/
Code Guru
http://www.codeguru.com/
Code Project
http://www.codeproject.com/
MSDN
http://msdn.microsoft.com/
Linoleum: Linux Programming Resources
http://leapster.org/linoleum/
Mac OS X Programming: Getting Started
http://developer.apple.com/macosx/gettingstarted/
I hope this answers your question, and helps you get started in the
right direction developing your own graphical applications.
Good luck with you endeavor,
-Spot |