WINDOWS MEETS C++

Scott Robert Ladd

Scott operates The Ladd Group, a consulting firm that specializes in developing scientific software. He can be reached at 3652 County Road 730, Gunnison, CO 81230-9726.


Programmers have recently been examining Windows-compatible C++ compilers in hope of finding a magic key that will unlock the door to easily written Windows applications. The question they are asking is whether or not the object-oriented power of C++ can be used to simplify Windows programming. The answer is "yes," but with a caveat. Although it supports the notion of subclassing, Windows was not necessarily designed with object-oriented programming in mind. So despite Windows' event-driven architecture, Microsoft's software engineers necessarily approached Windows from a "procedural" (Pascal/C) perspective.

The conflict between C++ and Windows is a contrast of simple solutions and difficult problems. Once the cantankerous nature of Windows has been tamed by a set of C++ classes, object-oriented Windows applications can be built easily and efficiently. Toward that end, this article presents a class hierarchy that will ease you into the development of a generic Windows application.

Designing Classes

A Windows Application (or WinApp) has two basic components: the main message dispatch loop and windows. The message loop takes incoming messages and dispatches them to the appropriate window functions. In every WinApp, the constants are the message loop, the instance handle, and the need to track the error status of the application. These constants can easily be encapsulated into a class.

Windows are created in two steps. First, you register a "window class." (This is not a C++ class; rather, it is a description of a category of windows that share similar characteristics.) Secondly, you generate a window using the window class as an argument to the CreateWindow function. A window class can be used in the creation of many windows. Because window instances and window classes are separate (but related) entities, I designed my class hierarchy with C++ classes for both window classes and windows themselves.

When writing a WinApp in C, two techniques associate data with a specific window. The first, extra data (as defined by the window's class), can be allocated in the window's data structure. The GetWindowWord/SetWindowWord and GetWindowLong/SetWindowLong functions can store and retrieve 16- and 32-bit values, respectively, in these so-called "extra bytes." This is convenient for small amounts of data, but doesn't work well when storing structures or large amounts of data.

The second method of associating data with a window is via a property list. Each data item in a property list is given a text name. Data for a window can be stored and retrieved by name; almost any amount of information can be associated with a window via a property list. However, property lists aren't fast because special functions access each piece of data through an inefficient linear look-up table. If you need quick access to your data, avoid property lists.

Whether or not you use extra bytes or property lists, you end up with reliability problems. Extra bytes are accessed via specific offsets into the internal structure of a window. One small mistake in the offset specification can return invalid data, or even worse, destroy other data. Property lists require maintenance of a list of text names for various data elements; again, an error-prone and onerous task.

Using C++ classes solves the problem of window-specific data storage by encapsulating the data elements for a window. Instead of generating extra window bytes or creating a property list, you can encapsulate data elements for a specific kind of window in a class description. There's no overhead; the member functions defined for a window data type can directly access the associated data without messy offsets or special access functions.

Basic Classes

The design methodology just described specifies three basic classes: a class to encapsulate application-global information; a class to define the generic structure and function of window classes; and a simple window class. All of these classes are required for any WinApp, so they can be defined together in the same files. I'll begin by describing the class definitions in winclass.h (see Listing One, page 33).

The WinApp class defines an application's global characteristics. The instance handle and error status for an application are stored in a WinApp object of which there is only one in any application.

I didn't just define the instance and error status as global variables because global variables can be modified accidentally from anywhere within a program. In a Windows application, where thousands of lines of code are not executed in an obviously structured fashion, tracking down unexpected changes in global variables can be a nightmare. Once the WinApp object is created, the value for Instance can't be changed. Read-only access protects the program from itself. The global error indicator is also handled in an object-oriented fashion; it can only be modified via the appropriate WinApp member functions.

WinApp also defines functional attributes. Every application has a message loop; WinApp encapsulates and standardizes the loop in the Dispatch member function. Once Dispatch has been defined, all applications can use it identically; it need not be redeveloped for every program.

C++ classes for windows provide a foundation for building application-specific classes. WindowClass defines the characteristics common to all window classes. The constructor assigns values to a WNDCLASS structure, obtaining the application instance handle from the app parameter. Registration is handled by the Register/Unregister member functions. The GetName method returns a pointer to the character string that identifies the class. WindowClass combines the complete data and functional definitions of a window class into a single package.

The BasicWindow class is not meant to be used as it is defined, but rather as a general-base class from which other window types can be derived. Windows, controls, and dialog boxes are all windows, although each has unique characteristics. So the BasicWindow class defines the characteristics common to all windows. To prevent BasicWindow objects from being created outside of their derived classes, BasicWindow's constructor is a protected member function. The constructor accepts the name of a window class, which is assigned to the data member ClassName. All windows have a handle identifier and style value, which are maintained in the Handle and Style data members of the BasicWindow class. In addition, every window must keep track of the program instance it is associated with; this value is stored in the Instance data member. The constructor sets the data members to "empty" values, expecting the constructor for a derived class to assign specific values to these components.

The first three public member functions, GetHandle, GetStyle, and GetInstance, retrieve the HANDLE, Style, and Instance data members, respectively, for a BasicWindow object.

Show Update calls the ShowWindow and UpdateWindow API functions. Show is called to hide or display a window, depending on the value of cmdShow. (Valid cmdShow arguments are listed under the ShowWindow function in the Windows API Reference manual.) UpdateWindow sends a WM_PAINT message to the Window's callback function, requesting that the client area be redrawn. By making these functions inline, calls to the corresponding API function are directly embedded in the code when Show and Update are called, eliminating the overhead of a double function call.

GetText copies window's text into a caller-supplied buffer. The text of a programmer-defined window is its heading; for a predefined control, the placement of the text depends upon the type of control. SetText is a complimentary function that changes the text associated with a window.

The Disable function disables a window, and Enable enables a window. Like most BasicWindow functions, these functions are inline. The methods GetExtents and Move retrieve and change, respectively, the x-y position and height/width of a window.

Window Class

Window is the base class for application (nondialog box, noncontrol) windows. The public interface to a Window consists of four functions. The constructor must be passed application and window class information. These objects provide the instance handle and class name required when creating the object. The constructor does not generate the window; it is the Actualize member function that calls CreateWindow.

I had a long debate with myself about how to construct and create Window objects. My first design implemented the CreateWindow call in the constructor, in effect combining the constructor with Actualize. There was no need to maintain arguments for CreateWindow in data members, because CreateWindow was called in a constructor that defined those arguments.

That scheme didn't work well. A window object is often defined before it needs to be displayed, and the all-in-one constructor automatically calls CreateWindow whether or not I want it. Furthermore, when deriving new Window classes, there was a problem with the order of construction. Base class constructors are called before derived class constructors; with the CreateWindow call in the base class constructor, the window was automatically generated using the default parameters before the derived class's constructor could make changes.

Creating windows works better with a split generation process. When a derived class object is created, the base class constructor is called to set default values for the CreateWindow arguments. Immediately thereafter, the derived class constructor is called to change some or all of those defaults. Actualize is called once the construction is complete, using the values of the data members as arguments to CreateWindow. Inefficiency stemming from possible reassignment of values to argument data members is a small price to pay for added flexibility.

The five protected function members are called by the private member function MessageHandler -- the callback function common to all Window objects. You may wonder why I defined MessageHandler as part of the Window class, when it is defined as part of a window class in traditional C applications, and why I defined it as a static, instead of normal member. To answer these questions, you must examine how a callback function works, and understand the difficulties that face C++ programs working with the Windows programming model.

Considerations

As mentioned earlier, the Windows API was designed with C programmers in mind; as such, it makes certain assumptions. For example, a window callback function must have four parameters of a specific type in a specific order. C++ member functions, however, have a "secret" parameter. All nonstatic member functions have a hidden pointer parameter referenced by the keyword this. The this pointer addresses the object for which the function was called. Were MessageHandler defined as a normal member function, the hidden this parameter would prevent it from being called correctly by the message dispatch loop. To eliminate the this pointer, MessageHandler must be a static member function.

This leads us to a chicken-and-egg problem. When a window is created with CreateWindow, it is assigned a callback function based on the callback defined for its window class. CreateWindow sends several messages to the new window before returning. This is all automatic, and there is no way to change the callback address to point to MessageHandler until after CreateWindow has been called to return a window HANDLE! The question is which callback function to use for the window class, so that the initial message sent by CreateWindow can be processed.

The Windows API provides a default callback function named DefWindowProc. The messages sent to a window by CreateWindow are not processed by the programmer; they are usually passed on to DefWindowProc. The only exception is WM_CREATE, a message usually processed to perform initialization of window-specific data.

I solved the problem in Listing Two, page 34, by assigning DefWindowProc as the default callback function for a WindowClass object. When CreateWindow is called for a BasicWindow object, the initial message is passed on to DefWindowProc. Once CreateWindow is done, I use the SetWindowLong API function to change the callback for that window from DefWindowProc to MessageHandler. A WM_CREATE message is then sent explicitly to the window, which now calls MessageHandler for all its messages.

This leads to another problem: To make classes truly polymorphic, each class should be able to define class-specific message handling using virtual functions. A static member function such as MessageHandle, however, cannot be declared virtual! How, then, can we define MessageHandler to be both a static function and polymorphic?

The solution involves sleight-of-hand techniques. When a callback function receives a message, its first argument contains the HANDLE of the window for which the message is intended. The window HANDLE can be used as a way of obtaining a pointer to the Window object. Earlier, I described how Windows can allocate extra data space in the internal structure of a window. In this extra space, a pointer to the Window object can be stored. When MessageHandler is called, it can extract this pointer via the window HANDLE and use it to invoke virtual methods. Here's how it works. First, a Window object is constructed. Then, the Actualize member function is called to generate the window. In Actualize, once the call to CreateWindow is successful, the this pointer for the object is stored in the Window's extra byte with the macro SetWindowPtr. When MessageHandler is called, it can extract this pointer using its window HANDLE argument and the GetWindowPtr macro. Memory model-specific versions of the SetWindowPtr and GetWindowPtr methods are defined in the winclass.h header file. The WindowClass must also declare a number of extra window bytes (defined by the SIZEOF_THIS macro) for holding this pointer.

Once MessageHandler has a pointer to the Window object, it can call virtual member functions to process specific messages. The Window class defines five virtual functions for handling messages. Four of these functions handle specific messages commonly operated upon by all windows: Create handles WM_CREATE; close processes WM_CLOSE; Paint handles WM_PAINT, which must be processed by all windows that display text or graphics; and Command handles WM_COMMAND messages generated by actions involving menu items and control windows. Any other messages are passed to the AuxMsgHandler function, defined by the base class as a call to DefWindowProc. WM_CREATE, WM_CLOSE, WM_PAINT, and WM_COMMAND are the only messages directly handled by most windows I use in my applications.

The remaining implementation issue involves implementing the callback as part of Window. The callback function is more closely tied to the window itself than to the window class. How messages are processed is defined by the menus and controls defined within a window. Quite often in C-based applications, a WinApp is forced to change the callback function assigned from the window class in order to process window-specific messages. Implementing a callback function as a member of the Window class directly associates the callback subfunctions with the window they are working with.

Classes for Controls

Controls are specialized windows created using predefined window classes. All controls share certain characteristics in common; this suggests that a common base class should be used. My control base class is named Control; it is derived from the BasicWindow class. Control provides a set of common features to all classes derived from it.

There is no need for a program to create Control objects; such objects are useless in and of themselves. Only derived classes need to construct Control objects. So the constructor for Control constructor is protected; it can only be called from within the Control class scope or by classes derived from Control. This constructor, in fact, does nothing but call the constructor for the BasicWindow class from which Control is derived.

Like Window, Control has an Actualize member function that displays the Control on this screen. The GetID function returns the ID code for a control.

SubClass is a method that changes the callback function for a control. Microsoft calls this "subclassing," but "redirecting" or "intercepting" would be more appropriate. Subclassing is used by sophisticated WinApps that need direct control over how a Control's messages are being handled. The uses of subclassing, however, are outside the scope of this article, so I'll leave it to you to investigate the subject further.

Two simple control classes are derived from Control: StaticLeft and PushButton. The former is a simple text box; the later is the grey push button used for "Okay" and "Cancel" buttons. The implementation of these classes is extremely simple; they inherit a majority of their functionality from Control and BasicWindow.

A Simple Application

Listings Three through Six (page 36) are the source files required for a C++ WinApp I call "woop." Application-specific classes based on the WindowClass and Window classes provide the core of the program. The WOOPWindow class defines and creates two controls windows inside the client area of its window. Pressing PushButton elicits a beep from the computer's speaker; choosing menu items changes the contents of the StaticLeft control.

The source code compiles correctly under Borland C++ 2.0. While the code compiles without warning under Zortech C++, a minor code generation problem prevents it from executing. A Zortech representative told me the problem would be corrected by the time you read this article.

The classes presented here are merely a beginning; real WinApps are far more complex than woop. My goal was to provide you with a basic understanding of the marriage between C++ and Windows. You can, however, use woop as a starting point to create your own extended classes and customize them for your specific applications.


_WINDOWS MEETS C++_
by Scott Robert Ladd


[LISTING ONE]



//  WINDOWS CLASSES
//  winclass.h -- Class definitions for Windows programming
//  Copyright 1991 by Scott Robert Ladd. All Rights Reserved.

#if !defined(__WINCLASS_H)
#define __WINCLASS_H

#include "windows.h"

// macro resolving to size of this pointer
#define SIZEOF_THIS (sizeof(void *))
// macro resolving to number of extra bytes required by this window
#define PTR_BYTES   (SIZEOF_THIS)
// macros used to store and extract class pointer from extra window bytes
#if sizeof(void *) == 4
    #define SetWindowPtr(wdw,ptr) SetWindowLong((wdw),0,(DWORD)(ptr))
    #define GetWindowPtr(wdw)     (Window *)GetWindowLong(wdw,0)
#else
    #define SetWindowPtr(wdw,ptr) SetWindowWord((wdw),0,(WORD)(ptr))
    #define GetWindowPtr(wdw)     (Window *)GetWindowWord(wdw,0)
#endif
// callback function types
typedef long (_far _pascal * WinCallBack)(HWND,WORD,WORD,DWORD);
typedef int  (_far _pascal * DlgCallBack)(HWND,WORD,WORD,DWORD);
//------------- CLASS WinApp -------------
class WinApp
    {
    public:
        static int Dispatcher();

        static void SetInstance(HANDLE inst);
        static HANDLE GetInstance();
    private:
        static HANDLE Instance;
    };
// set instance handle
inline void WinApp::SetInstance(HANDLE inst)
    {
    if (Instance == NULL)
        Instance = inst;
    }
// get instance handle
inline HANDLE WinApp::GetInstance()
    {
    return Instance;
    }
//------------------ CLASS WindowClass ------------------
class WindowClass : public WinApp
    {
    public:
        WindowClass();                // constructor

        BOOL Register();              // register
        BOOL Unregister();            // unregister

        const char * GetName() const; // get class name string
    protected:
        WNDCLASS ClassData;
    };
//------------------ CLASS BasicWindow ------------------
class BasicWindow : private WinApp
    {
    public:
        HWND   GetHandle()   const;
        HANDLE GetInstance() const;
        DWORD  GetStyle()    const;
        void Show(int cmdShow = SW_SHOW);
        void Update();
        void SetText(char * text);
        void GetText(char * buffer, int n) const;
        void Disable();
        void Enable();
        void GetExtents(int * x, int * y, int * width, int * height) const;
        void Move(int x, int y, int width, int height);
    protected:
        BasicWindow(const char * cname);
        HWND   Handle;
        HANDLE Instance;
        DWORD  Style;
        char   ClassName[32];
    };
inline HWND BasicWindow::GetHandle() const
    {
    return Handle;
    }
inline HANDLE BasicWindow::GetInstance() const
    {
    return Instance;
    }
inline DWORD BasicWindow::GetStyle() const
    {
    return Style;
    }
inline void BasicWindow::Show(int cmdShow)
    {
    ShowWindow(Handle,cmdShow);
    }
inline void BasicWindow::Update()
    {
    UpdateWindow(Handle);
    }
inline void BasicWindow::SetText(char * text)
    {
    SetWindowText(Handle,text);
    }
inline void BasicWindow::GetText(char * buffer, int n) const
    {
    GetWindowText(Handle,buffer,n);
    }
inline void BasicWindow::Disable()
    {
    EnableWindow(Handle,FALSE);
    }
inline void BasicWindow::Enable()
    {
    EnableWindow(Handle,TRUE);
    }
inline void BasicWindow::GetExtents(int * x, int * y, int * width,
                                                           int * height) const
    {
    RECT r;
    GetWindowRect(Handle,&r);
    *x = r.left;
    *y = r.top;
    *width  = r.right  - r.left + 1;
    *height = r.bottom - r.top  + 1;
    }
inline void BasicWindow::Move(int x, int y, int width, int height)
    {
    MoveWindow(Handle,x,y,width,height,TRUE);
    }
//------------- CLASS Window -------------
class Window : public BasicWindow
    {
    public:
        Window(const char * cname);

        BOOL Actualize(const char * title,
                       int x      = CW_USEDEFAULT,
                       int y      = CW_USEDEFAULT,
                       int width  = CW_USEDEFAULT,
                       int height = CW_USEDEFAULT);
        HDC  OpenDC();
        void CloseDC(HDC dc);
    protected:
        virtual void Create(HWND wdw, CREATESTRUCT _far * cs);
        virtual void Close(HWND wdw);
        virtual void Paint(HDC dc);
        virtual void Command(HWND wdw, WORD wParam, DWORD lParam);

        virtual long AuxMsgHandler(HWND wdw,    WORD  message,
                                                   WORD wParam, DWORD lParam);
    private:
        static long _far _pascal _export MessageHandler(HWND  wdw,
                                    WORD  message, WORD  wParam, DWORD lParam);
    };
inline HDC Window::OpenDC()
    {
    return GetDC(Handle);
    }
inline void Window::CloseDC(HDC dc)
    {
    ReleaseDC(Handle,dc);
    }
//-------------- CLASS Control --------------
class Control : public BasicWindow
    {
    public:
        BOOL Actualize(const Window & parent, const char * text, WORD id,
                                          int x, int y, int width, int height);
        WORD GetID() const;
        WinCallBack SubClass(WinCallBack newHandler);
    protected:
        Control(const char * cname);
    };
inline WORD Control::GetID() const
    {
    return GetWindowWord(Handle,GWW_ID);
    }
inline WinCallBack Control::SubClass(WinCallBack newHandler)
    {
    return (WinCallBack)SetWindowLong(Handle,GWL_WNDPROC,(DWORD)newHandler);
    }
inline Control::Control(const char * cname) : BasicWindow(cname)
    {
    // does nothing else
    }
//----------------- CLASS StaticLeft -----------------
class StaticLeft : public Control
    {
    public:
        StaticLeft();
    };
//----------------- CLASS PushButton -----------------
class PushButton : public Control
    {
    public:
        PushButton();
    };
#endif // __WINCLASS_H





[LISTING TWO]


//  WINDOWS CLASSES
//  winclass.cpp -- Windows class implementations.
//  Copyright 1991 by Scott Robert Ladd. All Rights Reserved.

#include "winclass.h"
#include "string.h"

//------------- CLASS WinApp -------------

// define values for static member of WinApp class
HANDLE WinApp::Instance = NULL;
// message dispatcher
int WinApp::Dispatcher()
    {
    if (Instance == NULL)
        return !0;
    MSG msg;
    while (GetMessage(&msg, NULL, NULL, NULL))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }

    return msg.wParam;
    }
//------------------ CLASS WindowClass ------------------
WindowClass::WindowClass()
    {
    #if defined(__BCPLUSPLUS__)
        ClassData.lpfnWndProc   = DefWindowProc;
    #else
        ClassData.lpfnWndProc   = (long (_far _pascal *)())DefWindowProc;
    #endif
    ClassData.hInstance     = WinApp::GetInstance();
    ClassData.style         = 0;
    ClassData.cbClsExtra    = 0;
    ClassData.cbWndExtra    = PTR_BYTES;
    ClassData.hIcon         = NULL;
    ClassData.hCursor       = LoadCursor(NULL,IDC_ARROW);
    ClassData.hbrBackground = GetStockObject(WHITE_BRUSH);
    ClassData.lpszMenuName  = NULL;
    ClassData.lpszClassName = "DefaultWindowClass";
    }
BOOL WindowClass::Register()
    {
    if (!RegisterClass((WNDCLASS _far *)&ClassData))
        return FALSE;
    else
        return TRUE;
    }
BOOL WindowClass::Unregister()
    {
    if (!UnregisterClass(ClassData.lpszClassName,ClassData.hInstance))
        return FALSE;
    else
        return TRUE;
    }
const char * WindowClass::GetName() const
    {
    return (const char *)ClassData.lpszClassName;
    }
//------------------ CLASS BasicWindow ------------------
BasicWindow::BasicWindow(const char * cname)
    {
    Handle   = NULL;
    Instance = WinApp::GetInstance();
    Style    = 0UL;

    if (cname != NULL)
        strncpy(ClassName,cname,32);
    else
        ClassName[0] = 0; // blank class name
    }
//------------- CLASS Window -------------
Window::Window(const char * cname) : BasicWindow(cname)
    {
    Style = WS_OVERLAPPEDWINDOW;
    }
BOOL Window::Actualize(const char * title, int x, int y, int width, int height)
    {
    Handle = CreateWindow(ClassName, (char *)title, Style, x, y,
                                    width, height, NULL, NULL, Instance, 0L);
    if (Handle == NULL)
        return FALSE;
    FARPROC fp = MakeProcInstance((FARPROC)((DWORD)(Window::MessageHandler)),
                                                                     Instance);
    SetWindowLong(Handle,GWL_WNDPROC,(DWORD)fp);
    SetWindowPtr(Handle,this);
    SendMessage(Handle,WM_CREATE,0,0L);
    return TRUE;
    }
void Window::Create(HWND wdw, CREATESTRUCT _far * cs)
    {
    // by default, does NOTHING!
    }
void Window::Close(HWND wdw)
    {
    // by default, does NOTHING!
    }
void Window::Paint(HDC dc)
    {
    // by default, does NOTHING!
    }
void Window::Command(HWND wdw, WORD wParam, DWORD lParam)
    {
    // by default, does NOTHING!
    }
long Window::AuxMsgHandler(HWND wdw, WORD message, WORD wParam, DWORD lParam)
    {
    return DefWindowProc(wdw,message,wParam,lParam);
    }
long _far _pascal _export Window::MessageHandler(HWND wdw, WORD  message,
                                                 WORD wParam, DWORD lParam)
    {
    HDC           dc;
    PAINTSTRUCT   ps;
    Window *      wptr;
    wptr = GetWindowPtr(wdw);
    switch (message)
        {
        case WM_CREATE:
            wptr->Create(wdw, (CREATESTRUCT _far *)lParam);
            break;
        case WM_CLOSE:
            wptr->Close(wdw);
            break;
        case WM_PAINT:
            dc = BeginPaint(wdw,&ps);
            wptr->Paint(dc);
            EndPaint(wdw,&ps);
            break;
        case WM_COMMAND:
            wptr->Command(wdw,wParam,lParam);
            break;
        default:
            return wptr->AuxMsgHandler(wdw,message,wParam,lParam);
        }
    return 0L;
    }
//-------------- CLASS Control --------------
BOOL Control::Actualize(const Window & parent, const char * text, WORD id,
                                           int x, int y, int width, int height)
    {
    Handle = CreateWindow(ClassName, (char *)text, Style, x,y,width,height,
                             parent.GetHandle(), id, parent.GetInstance(), 0L);
    if (Handle == NULL)
        return FALSE;
    return TRUE;
    }
//-----------------  CLASS StaticLeft -----------------
StaticLeft::StaticLeft() : Control("STATIC")
    {
    Style = WS_CHILD | SS_LEFT | SS_NOPREFIX | WS_BORDER;
    }
//----------------- CLASS PushButton -----------------
PushButton::PushButton() : Control("BUTTON")
    {
    Style = WS_CHILD | BS_PUSHBUTTON;
    }







[LISTING THREE]


//  WINDOWS CLASSES
//  woop.cpp -- A program to test basic windows classes.
//  Copyright 1991 by Scott Robert Ladd. All Rights Reserved.

#include "winclass.h"
#include "woop.h"

#define IDC_BUTTON 1000

//---------------------- CLASS MainWindowClass ----------------------
class WOOPWindowClass : public WindowClass
    {
    public:
        WOOPWindowClass();
    };
WOOPWindowClass::WOOPWindowClass() : WindowClass()
    {
    ClassData.hIcon         = LoadIcon(WinApp::GetInstance(),"WOOPIcon");
    ClassData.hbrBackground = GetStockObject(LTGRAY_BRUSH);
    ClassData.lpszMenuName  = "WOOPMenu";
    ClassData.lpszClassName = "MainWindowClass";
    }
//----------------- CLASS WOOPWindow -----------------
class WOOPWindow : public Window
    {
    public:
        WOOPWindow(const char * cname);

        virtual BOOL Actualize();
    protected:
        virtual void Close(HWND wdw);
        virtual void Command(HWND wdw, WORD wParam, DWORD lParam);
        StaticLeft SCtl;
        PushButton BCtl;
    };
WOOPWindow::WOOPWindow(const char * cname) : Window(cname)
    {
    }
BOOL WOOPWindow::Actualize()
    {
    BOOL res;
    res = Window::Actualize("WOOP Window"); // call base class member
    if (res == FALSE)
        return FALSE;
    res = SCtl.Actualize(*this,"Static control",0,100,10,120,16);
    if (res == FALSE)
        return FALSE;
    SCtl.Show();
    SCtl.Update();
    res = BCtl.Actualize(*this,"Button",IDC_BUTTON,100,50,60,40);
    if (res == FALSE)
        return FALSE;
    BCtl.Show();
    BCtl.Update();
    return TRUE;
    }
void WOOPWindow::Close(HWND wdw)
    {
    PostQuitMessage(0);
    }
void WOOPWindow::Command(HWND wdw, WORD wParam, DWORD lParam)
    {
    switch (wParam)
        {
        case IDM_EXIT:
            PostQuitMessage(0);
            break;
        case IDM_ABOUT:
            MessageBox(GetFocus(),"WOOP version 1.00","About...",MB_OK);
            break;
        case IDM_SET:
            SCtl.SetText("Set!");
            break;
        case IDM_RESET:
            SCtl.SetText("Reset!");
            break;
        case IDC_BUTTON:
            MessageBeep(0);
            MessageBeep(0);
        }
    }
//----------------- FUNCTION WinMain -----------------
int _pascal WinMain(HANDLE instance,HANDLE prevInst,LPSTR cmdLine,int cmdShow)
    {
    WinApp::SetInstance(instance);
    WOOPWindowClass wc;
    if (prevInst == NULL)
        wc.Register();
    WOOPWindow wdw(wc.GetName());

    wdw.Actualize();
    wdw.Show(cmdShow);
    wdw.Update();

    return WinApp::Dispatcher();
    };







[LISTING FOUR]


#include "windows.h"
#include "woop.h"

WOOPIcon ICON WOOP.ICO

WOOPMenu MENU
    BEGIN
        POPUP "&Menu"
            BEGIN
            MENUITEM "Set",       IDM_SET
            MENUITEM "Reset",     IDM_RESET
            MENUITEM SEPARATOR
            MENUITEM "&About...", IDM_ABOUT
            MENUITEM SEPARATOR
            MENUITEM "E&xit",     IDM_EXIT
            END
    END







[LISTING FIVE]


#define IDM_SET     100
#define IDM_RESET   101
#define IDM_ABOUT   102
#define IDM_EXIT    103







[LISTING SIX]


NAME        OOPAPP

DESCRIPTION 'OPP Windows App'

EXETYPE     WINDOWS
STUB        'WINSTUB.EXE'
CODE        PRELOAD MOVEABLE DISCARDABLE
DATA        PRELOAD MOVEABLE MULTIPLE

HEAPSIZE    4096
STACKSIZE   4096



[MAKE FILE]

windows = y

woop.exe     : woop.obj winclass.obj woop.res woop.def

woop.obj     : woop.cpp winclass.h woop.h

winclass.obj : winclass.cpp winclass.h

woop.res     : woop.rc woop.h woop.ico

Copyright © 1991, Dr. Dobb's Journal