Al is the author of several books, including OLE 2.0 and DDE Distilled and Commando Windows Programming (both from Addison-Wesley). You can reach Al on CompuServe at 72010,3574.
Because most Windows applications are so similar, programmers often start with a skeletal Windows program and add to it. But since Windows is object oriented, you may ask, why not encapsulate these common pieces of code? This is the idea behind C++ class libraries like the Microsoft Foundation Classes (MFC) and Borland's ObjectWindows Library (OWL). However, these class libraries have limitations. For one thing, you must embrace the programming philosophy behind the library, as well as use C++. Also, it's difficult to adapt existing code to work with these frameworks.
To deal with such issues, I've created a toolkit called "CoolWorx" that uses the object-oriented nature of Windows to simplify application programming. You can use the library from C or C++. CoolWorx allows you to automatically create and customize window classes on the fly. The library includes an integrated event loop suitable for nearly all applications, encapsulation of common message-handling code, tool and status bars that automatically manage themselves, and a text-editor software component. Figure 1 shows the main screen of an editor written using CoolWorx. Even though the editor is less than 200 lines of code, it still contains full clipboard support, file I/O, a tool bar, and a status bar.
Figure 2 shows the basic flow of nearly every ordinary Windows program. The application registers its window classes, creates some windows, and enters an event loop. Each window class supplies a callback for its windows. These callbacks determine the window's personality. The callback probably calls the standard DefWindowProc() function to handle many Windows messages. DefWindowProc(), for example, responds to nonclient mouse messages.
Several things prevent easy reuse of windows and code. First, each window of a particular class shares the same window procedure. If you want to modify the behavior of a window, you must create a new class, subclass an existing window, or superclass a base class. Another problem with window procedures is the amount of code each function duplicates. For example, nearly all windows process the WM_CLOSE message in a similar way. Using child windows compounds this problem. Instead of child windows managing themselves automatically, most require the parent window to handle special cases. For example, when the parent window resizes, it must resize all child windows.
The event loop is another common piece of code that most applications modify slightly. Although its function is similar in all programs, you must make special modifications to it if you use modeless dialogs, accelerators, or want to do idle-time processing.
The simplest way to encapsulate some common functionality between windows is to write a custom DefWindowProc() (using a different name, of course). The custom procedure can handle any messages that you often process the same way in many different windows. Your custom code can call DefWindowProc() to handle most messages.
Remember, you can define more than one custom default procedure. Any window that needs a certain type of behavior can call the appropriate default procedure. Your window code can still call DefWindowProc() in cases where it does not want the specialized behavior.
WINEXTSD.C (available electronically, see "Availability," page 3), a typical custom procedure, handles WM_CLOSE and some WM_COMMAND messages. The key to processing WM_COMMAND messages this way is to agree on a set of menu IDs that always mean the same thing. CWMENU.H (also available electronically) defines menu IDs for Open, Exit, and other common commands. Later, you will see that there are other techniques you can use if you have common menu IDs.
Nearly all Windows programs have an event loop. Simple programs use a GetMessage() and DispatchMessage() call. More complex programs contain processing for modeless dialogs, accelerators, and idle-time processing. With a little sleight of hand, you can create a nearly universal event loop; see Listing One, page 19. Figure 3(a) is the prototype for the universal event loop. The w parameter is the application's main window. This window receives WM_COMMAND messages from accelerators and idle-processing messages. The mdi parameter contains the MDI client window (for multiple-document-interface applications). Ordinary applications can just set this window handle to NULL. You can translate multiple accelerator tables by supplying an accelerator array in the accary parameter and the length of the table in the nracc parameter. If you have a single accelerator, just pass its address and set nracc to 1.
Figure 3(b) is a typical call to the event loop. If the idle flag is TRUE, cw_Run() will send the main application window CW_IDLE messages when there are no messages waiting in the queue, but Windows allows the application to run. You can use the CW_IDLE message to update status bars or do other background operations.
To handle modeless dialogs, cw_Run() calls the cw_DialogProcess() function. This function uses window properties to mark and process modeless dialogs. By using the cw_CreateDialog() or cw_ModelessRegister() function, each modeless dialog receives the aCW_MODELESS property. The cw_DialogProcess() function checks for this property on any window message. It also checks for the property on the window's parent window. If the window or its parent has the aCW_MODELESS property set, then the window is either a modeless dialog or a control inside a modeless dialog. Either way, cw_DialogProcess() calls IsDialogMessage() to process the message. If the message was WM_NCDESTROY, which is the last message a window receives, cw_DialogProcess() removes the property from the modeless dialog.
Properties are ideal for storing this type of information. You can add any property to any window at any time. Contrast this with another common way to associate data with a window--extra words. To use extra window words, you must allocate them when you register the window class. By using properties, even modeless dialogs you don't control (the common search dialog in COMMDLG.DLL, for example) can work with the universal event loop.
Many programmers believe that properties are inefficient. This is somewhat true when you use strings as property keys. Windows must then search the string table to locate the property. However, if you use atom keys, as CoolWorx does, the calls are very fast since the 16-bit atom is a direct index to the string table.
If you roll your own event loop, you can still call cw_DialogProcess(). If the function returns TRUE, you simply go on to the next event. If the function returns FALSE, you must continue processing the nondialog message. You can use cw_Run() as a starting point for your own event loops.
Many Windows applications need windows that use similar class definitions. However, since each application wants private windows with a different window procedure and custom icons, it registers its classes. Occasionally, a useful window class will reside in a DLL with the CS_GLOBALCLASS style set. This makes the window available to all apps but gives it a fixed icon, window procedure, and so on.
When a DLL makes a window class available, it typically calls RegisterClass() as part of its initialization code (in LibMain() for Windows 3.1 or DLLEntryPoint(), by convention, for Windows NT). However, you can defer class registration until a later time. Consider the cw_BeginIcons() function in Listing One. Each application that wants to use CoolWorx calls this function, passing some icon handles and the program's instance handle. The CoolWorx DLL then registers windows on the program's behalf. This allows CoolWorx to hide the bulk of the class registration, while still allowing each application to have custom icons and private window classes. I call this type of window class a "class template."
Even with class templates, you must supply a callback routine for the class. The application program could supply the callback, but that would force it to use the class for only one type of window. A better answer is self-subclassing. With self-subclassing, you supply a dummy callback for the window (see self_subclass() in Listing One). This callback uses the 32-bit create parameter you supply when creating a window to set the callback address during the WM_CREATE message. It then delegates all future messages to this callback. Instead of using property values, the self-subclass routine stores the callback address in an extra window word. That means that classes that use self-subclassing must set the cbWndExtra field in the WndClass structure to at least four bytes and reserve the first four bytes for the use of the self-subclass routine. This is reasonable since you must choose to use the self-subclass technique when registering the class. This technique allows one class to handle multiple types of windows. By directly manipulating the window words, you could even dynamically change the callback a window uses.
Although Windows provides an edit control, it falls short of being a true software component. To use a multiline edit control as a stand-alone text editor, you need to write a bit of support code. The code in WINEXTED.C provides a method of encapsulating an edit control inside a component window. The edit component, CWEditClass, can load and store files, handle common menu commands, and stand as a main window. Again, the standard menu IDs from CWMENU.H are useful. The component window responds to messages like CM_EDITUNDO, CM_FILESAVE, and others.
The edit component delegates many messages to the underlying edit control. (The edit control's handle is in the first window word of the component.) It also provides calls to save and load files, place the status in a static control, and a few other miscellaneous functions.
Many programs today use tool bars and status bars (ribbons). These ribbons are small, strip-like windows that cling to the edge of another window and contain controls.
Word for Windows, for example, uses small bitmapped buttons, combo boxes, and static controls in its ribbons. Since these ribbons contain controls, it is natural to think of using modeless dialogs to implement them. This allows you to simply construct tool and status bars using any dialog editor. Using unadorned dialogs is unwieldy, however. Each time the parent window resizes, it must resize the ribbon dialogs. Also, the window must deduct the size of all visible windows from its client area.
To solve these problems, CoolWorx uses a private dialog class for ribbons. Ordinary dialogs use a special form of callback that you supply when you create the dialog box. Private dialog classes use this callback, but they also allow you to specify a normal window procedure that gets control before the ordinary dialog callback. This window procedure is ordinary in all respects except that it calls DefDlgProc() instead of DefWindowProc() to handle unprocessed messages. Using a private dialog class also allows you to add extra words to the dialog, change the dialog's icon, and make other modifications. To specify a private dialog class, simply use the CLASS statement in the RC file. Of course, you must register the class before creating any dialogs that use it. CoolWorx automatically registers the RibbonClass class for you.
RibbonClass uses the ribbon_proc() function as a callback. The window procedure forces the focus away from the ribbon. This prevents the ribbon from getting the keyboard focus. In addition, the new class defines several extra words (above the DLGWINDOWEXTRA bytes that all dialogs require). The ribbondp() function is an ordinary dialog callback that ribbons use. It has four main functions. First, it intercepts WM_COMMAND messages from its children and passes them to its parent (the main window). Also, unless you create the ribbon with the CWRIBBON_FOCUSOK style, the WM_COMMAND message forces the focus to the main window. This prevents a button or other control from retaining the focus after the user presses it. When the ribbon is enabled or disabled (using EnableWindow()) it, in turn, enables or disables all of its controls. This forces a disabled ribbon's controls to appear gray.
When you create a ribbon, ribbondp()'s WM_DLGINIT handler takes two actions. The simplest of these is that it calls Ctl3dSubclassDlgEx(). This is a call to CTL3DV2.DLL, the standard Microsoft 3-D control DLL. It can automatically subclass standard dialogs, but not those that use private dialog classes. By adding this call to the dialog's initialization, CTL3DV2 still appears to work automatically.
The more complex action that the WM_DLGINIT handler takes is to set up a linked list of ribbons that belong to the main window. The first ribbon reads the main window's window procedure and places it in the ribbon's extra storage. It then sets its window handle in the aCW_RIBBON property of the main window, and installs a new window handler, ribbonfilt(). Subsequent ribbons simply add themselves to the end of the chain that starts with the window in the aCW_RIBBON property.
Technically, this is window subclassing, but I call it "auto-subclassing." Usually, subclassing augments or restricts a window's functionality. For example, you often subclass edit controls to restrict their input to numeric digits. In this case, ribbonfilt() does not add or take away functions from the parent window; the parent window never knows the subclass is in place. All ribbonfilt() needs to process is the WM_SIZE and WM_DESTROY messages. When a WM_SIZE occurs, all ribbons adjust their sizes automatically. When the WM_DESTROY message comes through, ribbonfilt() removes itself without a trace.
This is a very powerful concept. A ribbon can automatically manage itself with no code in the parent window. The only drawback is if you use multiple types of auto-subclassing windows, it may be difficult to remove them. This is similar to hooking interrupt vectors. If the parent's current window procedure is not your filter procedure, how do you remove yourself from the chain? In the case of ribbons, this is not a problem. Once you create a ribbon, it remains until you destroy its parent window. You can make a ribbon invisible, but you can't destroy it. Since ribbons are aware of other ribbons that the parent window uses, they can account for them when resizing. For example, suppose you create a ribbon at the top of a window and then add a ribbon to the left side of the same window. The left ribbon will shrink to make room for the top ribbon.
To create a ribbon, use the cw_Ribbon() function. You supply a resource ID, a parent window, and one of the CWRIBBON_* constants in CW.H (available electronically). The ID must refer to a dialog template in the same module as the parent window. When you want to know the client area of the parent window, call cw_GetClientRect() instead of GetClientRect(). This special function computes the client area after taking the visible ribbons into account. Unlike GetClientRect(), cw_GetClientRect() may return nonzero values for the upper-left coordinates of the rectangle. Many programs assume that these values are always zero. Consider, for example, the code in Figure 4(a). Although this works, it is technically incorrect. It only works because r.left and r.bottom are always 0. When using cw_GetClientRect(), you should use the code shown in Figure 4(b).
If you are making many GDI calls, it is simple to adjust the viewport origin and clipping region to account for the ribbons. However, if your main window specifies the WS_CLIPCHILDREN style, the clipping region will already exclude the ribbons. Another solution is to create a child window in the client area and use that for your drawing. The standard MDI client window uses this technique.
I've compiled CoolWorx with both Borland and Microsoft C. However, it should compile and run with any Windows C or C++ compiler. CoolWorx resides in a DLL for easy access by application programs. To run a CoolWorx program, you need COOLWORX.DLL and CTL3DV2.DLL (available from Microsoft). The complete CoolWorx API is shown in Table 1. There is also a Windows help file with the online listings.
Each application that uses CoolWorx must call the cw_Begin() or cw_BeginIcons() function before making other CoolWorx calls. Before the program terminates, it should call cw_End(). When your program calls cw_Begin(), CoolWorx creates window classes (using the class templates) and sets up CTL3DV2 to automatically give your dialogs a 3-D look. The cw_End() function unhooks the CTL3DV2 library, but it doesn't call UnregisterClass(), as you might expect from a DLL that registers classes. Since the DLL creates the window classes on behalf of your application, multiple instances of the application use the same classes. When one program terminates, other instances of the program may still be in use. Therefore, you must not unregister the window classes. Since the classes are private, Windows will free them when all instances terminate.
Since CoolWorx is a DLL, it must not use global variables unless they are truly global across all applications. When CoolWorx starts, it creates several string atoms. All programs use the same values for these atoms. All other data must be on a per-instance basis.
Also available electronically is XEDITOR.C, a simple editor (see Figure 1) built with CoolWorx. The WinMain() function is similar to an ordinary program's main function. The only difference is that XEDITOR calls cw_Begin(), cw_Run(), and cw_End() to do most of the work. The init() function only needs to create a window using cw_SDICreate().
The main window procedure handles seven ordinary Windows messages and two that are CoolWorx specific. Table 2 shows the messages and their corresponding actions. Note that CoolWorx windows don't process WM_CREATE. Instead, they process CW_SDICREATE. Also, notice that many menu commands don't have handlers. This is because the editor component processes many of them directly. The main window routine passes the WM_ COMMAND message data to the editor with a CW_STDMENU message. If this call returns TRUE, the editor processed the command, and the window procedure is free to return. If the return value is FALSE, the editor did not know how to process the command. The editor does not handle CM_FILENEW and CM_FILEOPEN, since some MDI programs will create new editors to handle those messages. XEDITOR, on the other hand, simply delegates to the editor component for these commands.
When the component sees a WM_QUERYENDSESSION message, it prompts the user to save the file (if necessary). The cw_DefWindowProc() function converts WM_CLOSE messages to WM_QUERYENDSESSION. This allows the same behavior when Windows shuts down, or when the user closes the application.
The other WM_COMMAND messages XEDITOR processes are specific to this program. For example, VIEW_FONT, VIEW_TB, and VIEW_SB are XEDITOR specific. The NUM_BUTTON and CAP_BUTTON commands originate from the buttons on the status bar.
The version of CoolWorx in this article only handles SDI applications. A demo of a larger version of CoolWorx with the code for this article is available electronically; see "Availability," page 3. This version of CoolWorx handles MDI, graphical buttons, progress bars, and much more.
However, you don't need to adopt CoolWorx to use the techniques presented here. You can easily make better use of the code you already write by using these methods. When you write window procedures, ask yourself how much code you can either factor out with a custom default procedure or combine with a similar window. Look for ways to use window properties and extra storage words to make universal routines like cw_Run(). Think about self-subclassing and auto-subclassing when designing new child windows. The more code you can reuse, the quicker you can write future applications.
Figure 1 Main screen of an editor program written in approximately 200 lines of code using CoolWorx.
Figure 2 Basic flow of a typical Windows program.
Figure 3: (a) Prototype for the universal event loop; (b) initiating the event loop.
Figure 4: (a) This code works only because the application assumes that r.left and r.bottom are always 0; (b) proper coding technique when using cw_GetClientRect().
Table 1: CoolWorx API (* indicates seldom-used functions).
Table 2: XEDITOR message processing.
Copyright © 1995, Dr. Dobb's Journal
(a) WORD WINAPI cw_Run(HWND w, HWND mdi, int nracc,
LPHANDLE accary, BOOL idle);
(b) cw_Run(w,NULL,1,&acc,TRUE);(a)
int wid,hi;
RECT r;
GetClientRect(w,&r);
wid=r.right;
hi=r.bottom;
(b)
int wid,hi;
RECT r;
GetClientRect(w,&r);
wid=r.right-r.left;
hi=r.bottom-r.top;
Function/Message Description
cw_Begin Start CoolWorx with default icons.
cw_BeginIcons Start CoolWorx with custom icons.
cw_End End CoolWorx.
cw_ModelessRegister* Mark a modeless dialog for processing
by the universal event loop (cw_Run()).
cw_DialogProcess* Called by event loops to process modeless dialogs.
cw_Run Universal event loop.
cw_Ribbon Create ribbon.
cw_SetRibbon Set text in ribbon control.
cw_RibbonAdj* Adjust client rectangle to account for visible ribbons.
cw_RibbonInvAdj* Adjust client rectangle to account for all ribbons.
cw_GetClientRect Get adjusted client rectangle.
cw_GetRibbon Return ribbon handle.
cw_EnableCommand Enable or disable a menu and associated ribbon controls.
cw_SDICreate Create a single document-interface window.
cw_DefWindowProc CoolWorx default window procedure (replaces DefWindowProc()).
cw_EditSaveFile Saves editor file.
cw_EditStatus Display editor status in ribbon.
cw_EditNew Clear editor file.
cw_EditOpen Open an editor file by name.
cw_EditOpenFile Open an editor file with dialog.
cw_EditGetSel Get editor's selected text.
cw_EditSetFont Set editor's display font.
cw_StatusKeys Get key status (for example, Num Lock).
cw_ToggleKeyState Toggle key status.
cw_StatusTime Display current time.
cw_StatusHelp Display menu help.
cw_GetFilename Get filename using common dialog.
cw_CreateDialog Create modeless dialog
(also cw_CreateDialogIndirect, cw_CreateDialogParam, and so on).
CW_SDICREATE Process instead of WM_CREATE.
CW_IDLE Message delivered when idle time is available.
CW_INITTOOL Cause edit component to set up toolbar.
CW_STDMENU Send menu command to edit component.
Message Action
WM_SETFOCUS Pass focus to edit component.
WM_SIZE Resize edit component to fill window.
CW_SDICREATE Make edit component.
WM_INITMENU Delegate to edit component which
sets state of menu items
(Undo, Cut, Paste, and so on).
WM_MENUSELECT Place help on status bar.
CW_IDLE Update status bar and allow
edit component to update toolbar.
WM_COMMAND Delegate to edit component;
if edit component doesn't process
this message, then XEDITOR checks
for local commands.
WM_QUERYENDSESSION Delegate to edit component.
WM_DESTROY End application.Listing One
/* COOLWORX.C -- Al Williams */
#include <windows.h>
#include <windowsx.h>
#include "coolworx.h"
#include "cwh.h"
#include <ctl3d.h>
/* Get HWND of WM_COMMAND message */
#ifndef GET_WM_COMMAND_HWND
#ifndef WIN32
#define GET_WM_COMMAND_HWND(wp, lp) (HWND)LOWORD(lp)
#else
#define GET_WM_COMMAND_HWND(wp, lp) (HWND)(lp)
#endif
#endif
ATOM aCW_RIBBONv; // atom for Ribbon prop
ATOM aCW_MODELESSv; // atom for modeless prop
/* Class names */
char cw_RibbonClass[]="RibbonClass";
char cw_EditClass[]="CWEditClass";
char cw_SdiClass[]="CWSDIClass";
HANDLE cw_hInst;
HANDLE dllInst;
/* Avoid warnings */
#define aCW_RIBBON MAKEINTATOM(aCW_RIBBONv)
#define aCW_MODELESS MAKEINTATOM(aCW_MODELESSv)
static void size_ribbon(HWND parent,HWND hDlg,
LONG style,BOOL f);
/* Process possible modeless dialog. cw_Run() calls this so you only need it
if you are rolling your own message loop */
BOOL WINAPI _export cw_DialogProcess(LPMSG m)
{
HWND w=NULL,p=NULL;
if (!m->hwnd) return FALSE;
/* If window is modeless... */
if (GetProp(m->hwnd,aCW_MODELESS)) w=m->hwnd;
else p=GetParent(m->hwnd);
/* ... or parent is modeless... */
if (p&&GetProp(p,aCW_MODELESS))
w=GetParent(m->hwnd);
/* ... then process */
if (w)
{
/* clean up at end */
if (m->message==WM_NCDESTROY&&w==m->hwnd)
RemoveProp(m->hwnd,aCW_MODELESS);
/* Do it */
return IsDialogMessage(w,m);
}
return FALSE;
}
/* Register modeless dialog--don't need to this if you use cw_CreateDialog */
BOOL WINAPI _export cw_ModelessRegister(HWND w,BOOL f)
{
if (f)
SetProp(w,aCW_MODELESS,1);
else
RemoveProp(w,aCW_MODELESS);
return TRUE;
}
/* Ribbon filter -- installed on ribbon's parent window */
long WINAPI _export ribbonfilt(HWND hWnd,UINT message,UINT wParam,LONG lParam)
{
FARPROC chain;
HWND ribbon1=(HWND)GetProp(hWnd,aCW_RIBBON);
chain=(FARPROC)GetWindowLong(ribbon1,CHAIN_LONG);
switch (message)
{
case WM_SIZE:
/* Relocate all ribbons */
{
while (ribbon1)
{
size_ribbon(hWnd,ribbon1,
GetWindowLong(ribbon1,STYLE_LONG),TRUE);
ribbon1=(HWND)GetWindowLong(ribbon1,NEXT_LONG);
}
}
break;
/* Clean up */
case WM_DESTROY:
SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)chain);
RemoveProp(hWnd,aCW_RIBBON);
while (ribbon1)
{
HWND nxt=(HWND)GetWindowLong(ribbon1,NEXT_LONG);
DestroyWindow(ribbon1);
ribbon1=nxt;
}
break;
}
return CallWindowProc((FARPROC)chain,hWnd,message,
wParam,lParam);
}
/* Adjust ribbons to get out of each others way */
static void adj_ribbon(LONG style,int *x,int *y,int *len,
HWND head,HWND rib)
{
HWND w=GetParent(rib);
RECT base,r,bar;
GetClientRect(w,&base);
GetClientRect(w,&r);
GetWindowRect(rib,&bar);
cw_RibbonInvAdj(w,&r,TRUE,rib);
switch (style)
{
case CWRIBBON_TOP:
*x=r.left;
*y=0;
*len=r.right-r.left;
break;
case CWRIBBON_BOTTOM:
*x=r.left;
*y=base.bottom-(bar.bottom-bar.top);
*len=r.right-r.left;
break;
case CWRIBBON_RIGHT:
*x=base.right-(bar.right-bar.left);
*y=r.top;
*len=r.bottom-r.top;
break;
case CWRIBBON_LEFT:
*x=0;
*y=r.top;
*len=r.bottom-r.top;
break;
}
}
/* Compute correct size for ribbon -- account for existing
ribbons (even if invisible) */
static void size_ribbon(HWND parent,HWND hDlg,LONG style,
BOOL f)
{
RECT r,pr;
int x,y,len=0;
HWND head=GetProp(parent,aCW_RIBBON);
style&=CWRIBBON_LEFT; /* includes all position bits */
GetWindowRect(hDlg,&r);
GetClientRect(parent,&pr);
if (style<CWRIBBON_RIGHT)
{
x=0;
len=pr.right;
if (style==CWRIBBON_TOP)
y=0;
else
y=pr.bottom-(r.bottom-r.top);
adj_ribbon(style,&x,&y,&len,head,hDlg);
MoveWindow(hDlg,x,y,len?len:GetSystemMetrics(SM_CXSCREEN),
r.bottom-r.top,f);
}
else
{
y=0;
len=pr.bottom;
if (style==CWRIBBON_LEFT)
x=0;
else
x=pr.right-(r.right-r.left);
adj_ribbon(style,&x,&y,&len,head,hDlg);
MoveWindow(hDlg,x,y,r.right-r.left,
len?len:GetSystemMetrics(SM_CXSCREEN),f);
}
}
/* Ordinary dialog procedure for ribbon */
BOOL WINAPI _export ribbondp(HWND hDlg,UINT message,
UINT wParam,LONG lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
HWND parent,pdlg;
parent=GetParent(hDlg);
SetWindowLong(hDlg,STYLE_LONG,lParam);
if (!(pdlg=GetProp(parent,aCW_RIBBON)))
{
LONG val;
/* we are #1 ribbon -- start chain */
val=GetWindowLong(parent,GWL_WNDPROC);
SetWindowLong(hDlg,CHAIN_LONG,val);
SetProp(parent,aCW_RIBBON,hDlg);
SetWindowLong(parent,GWL_WNDPROC,
(DWORD)ribbonfilt);
}
else
{
HWND ndlg;
/* Add yourself to existing chain */
while (ndlg=(HWND)GetWindowLong(pdlg,NEXT_LONG))
pdlg=ndlg;
SetWindowLong(pdlg,NEXT_LONG,hDlg);
}
SetWindowLong(hDlg,NEXT_LONG,0L);
size_ribbon(parent,hDlg,lParam,FALSE);
/* Make Dialog 3D */
Ctl3dSubclassDlgEx(hDlg,CTL3D_ALL);
SetFocus(parent);
}
return FALSE;
case WM_COMMAND: // pass commands to parent
{
HWND parent;
LONG style;
SendMessage(parent=GetParent(hDlg),message,wParam,
lParam);
style=GetWindowLong(hDlg,STYLE_LONG);
if (!(style&CWRIBBON_FOCUSOK))
{
HWND fw=GetFocus();
if (fw==hDlg||
fw==GET_WM_COMMAND_HWND(wParam,lParam))
SetFocus(parent);
}
}
return 0;
/* Enable/disable all controls */
case WM_ENABLE:
{
HWND ctl=GetWindow(hDlg,GW_CHILD);
while (ctl)
{
EnableWindow(ctl,wParam);
ctl=GetWindow(ctl,GW_HWNDNEXT);
}
}
break;
}
return 0;
}
/* Ribbon private dialog class */
long WINAPI _export ribbonproc(HWND hWnd,
UINT message,UINT wParam, LONG lParam)
{
if (message==WM_NCDESTROY)
{
RemoveProp(hWnd,aCW_MODELESS); // clean up
}
else if (message==WM_SETFOCUS) // pass focus to parent
{
SetFocus((wParam&&!IsChild(hWnd,wParam))?
wParam:GetParent(hWnd));
return 0;
}
return DefDlgProc(hWnd,message,wParam,lParam);
}
/* Start CoolWorx */
BOOL WINAPI _export cw_Begin(HANDLE hInst)
{
return cw_BeginIcons(hInst,
LoadIcon(NULL,IDI_APPLICATION),
LoadIcon(NULL,IDI_APPLICATION));
}
/* Register our classes */
static BOOL reg_classes(HANDLE hInst,HICON sdiicon,
HICON eicon)
{
WNDCLASS wc;
/* Ribbon */
wc.style=0;
wc.lpfnWndProc=(void FAR *)ribbonproc;
wc.cbClsExtra=0;
wc.cbWndExtra=DLGWINDOWEXTRA+12;
wc.hInstance=hInst;
wc.hIcon=NULL;
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=COLOR_BTNFACE+1;
wc.lpszMenuName=NULL;
wc.lpszClassName=cw_RibbonClass;
if (!RegisterClass(&wc)) return FALSE;
/* Editor class */
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc=(void FAR *)cwed_proc;
wc.cbClsExtra=0;
wc.cbWndExtra=10;
wc.hInstance=hInst;
wc.hIcon=eicon;
wc.hCursor=LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground=COLOR_WINDOW+1;
wc.lpszMenuName=NULL;
wc.lpszClassName=cw_EditClass;
if (!RegisterClass(&wc)) return FALSE;
/* SDI window */
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc=(void FAR *)self_subclass;
wc.cbClsExtra=0;
wc.cbWndExtra=4;
wc.hInstance=hInst;
wc.hIcon=sdiicon;
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=COLOR_WINDOW+1;
wc.lpszMenuName=NULL;
wc.lpszClassName=cw_SdiClass;
if (!RegisterClass(&wc)) return FALSE;
return TRUE;
}
/* Start CoolWorx & set icons */
BOOL WINAPI _export cw_BeginIcons(HANDLE hInst,
HICON sdiicon,HICON eicon)
{
WNDCLASS wc;
/* Fire up CTL3DV2 */
Ctl3dRegister(hInst);
Ctl3dAutoSubclass(hInst);
/* If ribbons exist -- we are already going */
if (!GetClassInfo(hInst,cw_RibbonClass,&wc))
if (!reg_classes(hInst,sdiicon,eicon)) return FALSE;
cw_hInst=dllInst;
return TRUE;
}
/* End CoolWorx */
void WINAPI _export cw_End(HANDLE hInst)
{
/* Can't unregister classes -- other instances might
be using them. Let Windows clean it up */
Ctl3dUnregister(hInst);
}
/* Create a ribbon */
HWND WINAPI _export cw_Ribbon(LPCSTR id,HWND par,
LONG param)
{
#ifdef WIN32
return cw_CreateDialogParam(
GetWindowLong(par,GWL_HINSTANCE),
id,par,ribbondp,param);
#else
return cw_CreateDialogParam(
GetWindowWord(par,GWW_HINSTANCE),
id,par,ribbondp,param);
#endif
}
/* Silly helper function */
void WINAPI _export cw_SetRibbon(HWND w,UINT id,LPCSTR s)
{
SendDlgItemMessage(w,id,WM_SETTEXT,0,(DWORD)s);
}
/* General purpose message loop */
WORD WINAPI _export cw_Run(HWND w,HWND mdi,int nracc,
LPHANDLE ary,BOOL idle)
{
MSG msg;
int i;
while (1)
{
/* Check for message */
if (PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
{
/* Got one */
BOOL accproc=FALSE; // no accel processed (yet)
if (!GetMessage(&msg,NULL,0,0)) break;
/* Translate MDI accels for MDI apps */
if (mdi)
if (TranslateMDISysAccel(mdi,&msg)) continue;
/* Scan accel list */
for (i=0;i<nracc;i++)
{
HANDLE acc=ary[i];
if (acc&&TranslateAccelerator(w,acc,&msg))
{
accproc=TRUE;
break;
}
}
/* If no accel processed, keep going */
if (!accproc)
{
/* try modeless dialog */
if (cw_DialogProcess(&msg))
continue;
/* Translate & dispatch */
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} /* No message -- do idle processing */
else if (w&&idle&&IsWindow(w))
SendMessage(w,CW_IDLE,0,0);
}
return msg.wParam;
}
/* Create modeless dialog and set property for dialog manager */
HWND WINAPI _export cw_CreateDialogIndirectParam
(HANDLE hInst,const void FAR *tname,
HWND parent,DLGPROC cb,
LPARAM lParam)
{
HWND rc;
rc=CreateDialogIndirectParam(hInst,tname,
parent,cb,lParam);
if (rc) SetProp(rc,aCW_MODELESS,1);
return rc;
}
/* Create modeless dialog and set property for dialog manager */
HWND WINAPI _export cw_CreateDialogParam(HANDLE hInst,
LPCSTR tname,HWND parent,DLGPROC cb,LPARAM lParam)
{
HWND rc;
rc=CreateDialogParam(hInst,tname,parent,cb,lParam);
if (rc) SetProp(rc,aCW_MODELESS,(HANDLE)1);
return rc;
}
/* Adjust rectangle to account for ribbon */
BOOL WINAPI _export cw_RibbonInvAdj(HWND w,LPRECT r,
BOOL vis,HWND stop)
{
BOOL rv=FALSE;
int adj[4],i;
LONG style;
HWND head=(HWND)GetProp(w,aCW_RIBBON);
RECT bar;
adj[0]=adj[1]=adj[2]=adj[3]=0;
while (head&&head!=stop)
{
if (vis&&!IsWindowVisible(head)) // skip invisible
{
head=(HWND)GetWindowLong(head,NEXT_LONG);
continue;
}
GetWindowRect(head,&bar);
style=GetWindowLong(head,STYLE_LONG)&3;
switch (style)
{
case CWRIBBON_TOP:
adj[CWRIBBON_TOP]=max(adj[CWRIBBON_TOP],
bar.bottom-bar.top);
break;
case CWRIBBON_BOTTOM:
adj[CWRIBBON_BOTTOM]=max(adj[CWRIBBON_BOTTOM],
bar.bottom-bar.top);
break;
case CWRIBBON_RIGHT:
adj[CWRIBBON_RIGHT]=max(adj[CWRIBBON_RIGHT],
bar.right-bar.left);
break;
case CWRIBBON_LEFT:
adj[CWRIBBON_LEFT]=max(adj[CWRIBBON_LEFT],
bar.right-bar.left);
break;
}
head=(HWND)GetWindowLong(head,NEXT_LONG);
}
for (i=0;i<4;i++)
if (adj[i])
{
rv=TRUE;
switch (i)
{
case CWRIBBON_TOP:
r->top+=adj[i];
break;
case CWRIBBON_BOTTOM:
r->bottom-=adj[i];
break;
case CWRIBBON_RIGHT:
r->right-=adj[i];
break;
case CWRIBBON_LEFT:
r->left+=adj[i];
break;
}
}
return rv;
}
BOOL WINAPI _export cw_RibbonAdj(HWND w,LPRECT r)
{
return cw_RibbonInvAdj(w,r,TRUE,NULL);
}
/* Get client rectangle taking ribbons into account
NOTE: r.top and r.left may not be zero when this call returns. */
void WINAPI _export cw_GetClientRect(HWND w,LPRECT r)
{
GetClientRect(w,r);
cw_RibbonAdj(w,r);
}
/* Window proc for self-subclassing windows */
LONG WINAPI _export self_subclass(HWND w,UINT message,
WPARAM wParam, LPARAM lParam)
{
FARPROC p;
if (message==WM_CREATE)
{
/* Get createstruct to fetch callback address */
LPCREATESTRUCT cs=(LPCREATESTRUCT)lParam;
SetWindowLong(w,0,(LONG)cs->lpCreateParams);
}
p=(FARPROC)GetWindowLong(w,0);
return p?
CallWindowProc(p,w,message,wParam,lParam):
DefWindowProc(w,message,wParam,lParam);
}
/* Get ribbon of specified type */
HWND WINAPI _export cw_GetRibbon(HWND w,LONG type)
{
for (w=(HWND)GetProp(w,aCW_RIBBON);w;
w=(HWND)GetWindowLong(w,NEXT_LONG))
{
if (IsWindowVisible(w)&&
((GetWindowLong(w,STYLE_LONG)&3)==type))
return w;
}
return NULL;
}
/* End of the (DLL) world */
#ifdef __BORLANDC
int FAR PASCAL WEP ( int bSystemExit )
#else
int FAR PASCAL _WEP ( int bSystemExit )
#endif
{
DeleteAtom(aCW_RIBBONv);
DeleteAtom(aCW_MODELESSv);
return 1;
}
/* DLL startup */
int FAR PASCAL LibMain( HINSTANCE hModule, WORD wDataSeg,
WORD cbHeapSize, LPSTR lpszCmdLine )
{
// Save module handle to use as instance later
dllInst = hModule;
if (cbHeapSize>0) UnlockData(0);
aCW_RIBBONv=AddAtom("CW_RIBBON");
aCW_MODELESSv=AddAtom("CW_MODELESS");
return TRUE;
}
/* Enable command (menu & toolbar) */
void WINAPI _export cw_EnableCommand(HMENU m,HWND w,
int cmd,BOOL f,BOOL vis)
{
if (m)
{
EnableMenuItem(m,cmd,
(f?MF_ENABLED:(MF_DISABLED|MF_GRAYED))
|MF_BYCOMMAND);
}
if (w)
{
HWND ctl,bar;
bar=(HWND)GetProp(w,aCW_RIBBON);
while (bar)
{
if (!vis||IsWindowVisible(bar))
{
ctl=GetDlgItem(bar,cmd);
if (ctl) EnableWindow(ctl,f);
}
bar=(HWND)GetWindowLong(bar,NEXT_LONG);
}
}
}