Al is the author of several books, including DOS 6: A Developer's Guide (M&TBooks, 1993) and Commando Windows Programming (forthcoming from Addison-Wesley). You can reach him at 310 Ivy Glen Court, League City, TX 77573 or on CompuServe at 72010,3574.
Part of the difficulty in writing for Windows lies in a typical program's architecture. Under Windows' event-driven architecture, programs don't manipulate their display in response to user input (or other events). Instead, they update an internal application model to reflect the program's current state. Upon request from Windows, the program (via its WM_PAINT handler) renders a representation, or view, of the model in a window.
Consider, for example, a program that dumps system information to a true window (not a dialog or edit control). You can't simply draw the text to the window as you would under DOS. Instead, you must store the data you want to display internally. When Windows sends you a WM_PAINT message, you have to calculate what part of the text is visible and draw it. You may be asked to draw it repeatedly because of events beyond your control. If all the text won't fit, you'll also need to manage scroll bars and change how you display your text based on them. Most GUI systems encourage you to use this style of programming for one simple reason: resources. A model of your data should require much less space than a bitmapped image of the screen. After all, an 800x600x256 display takes about one-half of a megabyte to store. With the traditional GUI model, all of that memory is in the display adapter. In the system-information example, the text strings to display take up much less space than the dots that draw the characters on the screen.
To simplify this process, I've created VWinL, the Virtual Window Library. VWinL automatically manages your Windows 3.1, Windows NT, or Win32S windows. (See the accompanying textbox, "Porting to Win32.") When you want to display something, you draw it with the usual Windows calls once. VWinL makes sure it stays there, and can automatically manage scroll bars, scaling, and other common tedious tasks.
With the advent of Windows 3.1 and Windows NT, PC GUI programming has hit the big leagues. Windows 3.1 no longer supports real mode--you always have access to extended memory. Windows in 386 Enhanced mode and Windows NT provide virtual memory--you can convert part of your hard disk into usable memory. Since today's typical Windows programs have access to two or more megabytes of memory, why be stingy? Why not use some of that memory to make programming easier?
That's the basic philosophy behind VWinL. VWinL creates virtual drawing surfaces (VMAPs) that you can draw on with standard Windows GDI calls. You can optionally associate a VMAP with one or more physical windows. You can ask VWinL to scale the VMAP to fit in the window, or show as much of the VMAP as will fit. If the VMAP is too large to fit in the window, VWinL will automatically manage scrollbars for you, if you desire.
Each physical window has an independent view of its VMAP. You can have two (or more) windows on the screen viewing the same VMAP in different ways. For example, one window might show the VMAP scaled to fit, while two others are scrolled to show different areas of the VMAP without scaling.
Once you draw something to a VMAP with a window attached, you won't need to draw it again. If you iconify the window, or cover it up and expose it, the image stays in place with no action on your part. As an extra bonus, screen updates are unusually fast--often faster than with traditional Windows programs.
Listing One (page 32) shows a simple program that uses VWinL. (You'll find a summary of VWinL calls in Table 1.) Notice that it includes the VWINL.H file (Listing Two, page 32) and compiles with MAKEFILE.SIM, SIMPLE.RC, and SIMPLE.DEF. (All three are available electronically, see "Availability," page 3.) VWinL programs have a main() function (which is more like a WinMain() function in form) and window callbacks like normal Windows programs. They don't have WM_PAINT routines or event loops like ordinary Windows programs. You'll find VWIN.C in Listing Three (page 33).
Although a VWinL callback looks like a conventional callback, there are several important differences:
to draw to the new window. VWinL
callbacks don't receive WM_CREATE
messages at all--only WM_VCREATE messages.
Most VWinL main() functions are just calls to Vcreate_window() (see Figure 1). This call mimics CreateWindow() for the most part. One difference between Vcreate_window() and CreateWindow() is the menu parameter. CreateWindow() expects a handle to a menu. Vcreate_window() takes an ASCII string or resource ID (the same as LoadMenu()). If you create a child window, cast the integer child id to an LPCSTR and pass it as though it were a menu name.
The other major difference between Vcreate_window() and CreateWindow() is the addition of a parameter for VWinL flags. These flags control VWinL's operation; see Table 2. For example, the V_SCALE flag causes VWinL to scale a VMAP to fit the window. The flags are set separately for each window. By default, Vcreate_window() creates a window and a VMAP simultaneously. However, you can specify the V_NOMAP flag to create a bare window. You'll then need to use Vselect_map() to associate a VMAP with the window.
During window creation, VWinL looks for a resource named VAPPICON to specify your application's icon. If you want to add accelerators, name the table VACCEL so that VWinL can find it.
When you want to draw to a VMAP, you obtain a device context using Vget_
mdc() or Vget_vdc(). Use Vget_mdc() if you have a pointer to the VMAP, and Vget_vdc() if you have a window handle and want the underlying VMAP. You can use the device context freely with any GDI call. Don't call ReleaseDC() or DestroyDC(), however. If you want to release the resources associated with a VMAP, call Vdestroy_
map(). If the VMAP is in use by more than one window, the call will not do anything, so be careful to destroy VMAPs at the proper time. (VWinL attempts to destroy a window's VMAP when the window closes--more about that later.)
When you draw to a VMAP associated with a window, the changes may not be immediately visible. You can force the drawing to appear by calling Vcommit_
draw(). Vselect_map() also forces the window to update.
Don't draw to a VMAP when you want to draw something transient (like dragging a selection box or stretching an object in sync with the mouse). Instead, get the window's real DC (using GetDC(), or some other Windows call) and draw with it. Then to restore the window to its original state, you can call Vcommit_draw().
Be careful to use a solid brush for your window backgrounds if you use the V_SCALE mode. A patterned brush will look strange when VWinL scales it to fit in the window.
When Windows sends your program a WM_DESTROY message, VWinL intercepts it. It then sends your callback routine a WM_DESTROY message. If you want to allow the program to end, you don't need to do anything. If you want the program to continue, call the Vdont_
quit() function.
When VWinL detects a WM_DESTROY message, it will try to delete the window's VMAP (if it has one). Still, you should try to clean up any VMAPs you have open in your main WM_DESTROY routine. If you destroy a VMAP, detach it from its window (using Vselect_
map(w,NULL)) so VWinL will not try to destroy it again.
Since VMAPs can be large, make sure your clean-up routine (or VWinL's) executes. For example, don't call PostQuitMessage() in response to an Exit menu command. The application will terminate immediately and you will lose memory. Instead, pass your main-application window to DestroyWindow(). This will close the window, causing VWinL to terminate your program cleanly.
Although VWinL repaints the entire window on each WM_PAINT message, it is still fast. You may notice that many VWinL programs are faster than comparable ordinary programs when you resize them or restore them from an icon. To display text, for example, an ordinary program must redraw the text in the selected font each time it processes a WM_PAINT message. Windows must calculate the position of each pixel every time. VWinL programs only calculate these coordinates once when you first draw the text. On subsequent paints, the BitBlt() function rapidly transfers the pixels directly to the screen. This makes VWinL programs faster than their conventional counterparts in many cases.
Be careful if you use the V_SCALE flag to force VMAPs to fit in a window. The StretchBlt() call VWinL uses to do scaling is much slower than the ordinary BitBlt(). This is especially true when the window is much larger than the VMAP. You might consider making the VMAP larger than the maximum window size, or restricting the window's size by intercepting the WM_MINMAXINFO message.
Of course, there is no free lunch. VWinL's increased speed and ease of use come at the expense of memory--lots of memory. If your application doesn't need color, you should consider calling Vset_monomode() in your main routine before calling Vcreate_
window() or Vcreate_map(). This will considerably reduce the number of bytes VWinL uses to store VMAPs (unless you are on a monochrome display anyway--then it won't make any
difference).
To illustrate the use of VWinL, the Freeshow program continuously displays the percentage of free system resources available textually and graphically; see Figure 2. (The files FREESHOW.C, FREESHOW.H, FREESHOW.RC, and FREESHOW.DEF are available electronically, see "Availability," page 3.) You can compile this program with the Borland or Microsoft compilers (makefiles for each are on-line). Since Freeshow uses TOOLHELP.DLL, you can't create it as a WIN32 program.
FREESHOW.C has more menu options than necessary for such a simple program, but it illustrates several key VWinL features. For example, you can use the Scale menu choice to flip between VWinL scaling and clipping. If the window is too small to contain the clipped display, VWinL automatically provides scroll bars.
The heart of Freeshow is the get_
free_info() routine. This function does all the drawing in response to a WM_TIMER message (which the WM_VCREATE handler sets up to occur once per second). The SystemHeapInfo() function (from TOOLHELP.DLL) returns the percentage of free space in the USER heap and the GDI heap. Freeshow displays the smaller of these two numbers.
Freeshow's callback only handles a creation message (WM_VCREATE), menu messages (WM_COMMAND), timer messages (WM_TIMER), and the WM_CLOSE message. If Freeshow didn't require timer activation, it wouldn't have to process anything but the WM_COMMAND messages.
VMAPs take advantage of two special Windows features: bitmaps and memory device contexts. All GDI (drawing) functions operate on a device context (DC). Typically, output to a DC appears on a window. VWinL uses the CreateCompatibleDC() function to create a memory device context. A memory DC must have a bitmap associated with it (via SelectObject()). Drawing operations you perform against the memory DC don't appear anywhere on the screen. Instead, the drawing operations act on the associated bitmap.
Windows only allows bitmaps to be 65,535x65,535--VMAPs can't exceed this size. If you use the autoscroll feature, you must restrict your VMAPS to 32,767x32,767. Windows doesn't allow scrollbar ranges to exceed 32K.
The key to VWinL is its default WM_
PAINT handler, do_paint(). This routine copies the bitmap from the window's VMAP to the client area. If the V_SCALE flag is set, VWinL uses StretchBlt() to scale the image as it copies it. Otherwise, BitBlt() simply copies the bitmap.
If the bitmap is smaller than the window's client area, do_paint() erases the region outside the bitmap using the PatBlt() function. This ensures a consistent background when you resize the window.
Windows may send your program a WM_PAINT message for many different reasons. If you iconify your window and restore it, you'll get a WM_PAINT message. You'll also get a WM_PAINT when another window obscures yours and then moves to expose it again. The Vcommit_draw() function is a macro that calls InvalidateRect(). The InvalidateRect() call also generates WM_PAINT messages.
Almost any Windows program can use the VWinL library. Programs that need a quick-and-dirty display of text and graphics work especially well. Although you can implement sophisticated programs like word processors and spreadsheets using VWinL, you may not want to do so. For these programs, you need to create a data model anyway. Therefore you might as well use the more conventional Windows program architecture.
Be careful to consider VWinL's memory usage. A word processor that might have many windows open at once would consume a disproportionate amount of memory. Only you can decide how much memory is too much. Of course, you can mix VWinL windows and normal windows in the same program. You might use VWinL windows only where appropriate.
VWinL can simplify many types of Windows programs. One day, Windows (or another GUI) may support VMAP-style programming. With built-in support, the VMAPs could be stored as a sparse array, and perhaps compressed. Until then, you can use VWinL to experiment with this technique. You will notice that VWinL programs resemble ordinary DOS graphics code more than Windows applications.
You might like to enhance VWinL. A function to print a VMAP would be useful, as would a more sophisticated scrolling algorithm. You might also consider allowing VWinL windows to be MDI children or place VWinL in a DLL.
VWinL will work with Windows NT and Win32S. Since these 32-bit environments offer improved memory management, VWinL makes even more sense for them. Next time you write a Windows program, try VWinL and see how simple a Windows program can be.
Function/Description void Vcommit_draw(HWND w)Forces contents of window's VMAP to appear in the window. Until you call Vcommit_draw(), any output to the VMAP may or may not be visible. Actually a macro. HDC Vget_mdc(VMAP *map)
Returns DC associated with the specified VMAP. Actually a macro. int Vget_stretchmode(VMAP *map)
Returns stretch mode for specified VMAP. For more about stretch modes, see SetStretchBltMode() in the Windows API reference. Actually a macro. void Vget_info(HWND w,MEMWINFO *info)
Returns read-only structure of information pertaining to the window. VMAP *Vget_map(HWND w)
Returns a pointer to the VMAP associated with the window. VMAP *Vcreate_map(int width, int height)
Creates a VMAP of specified width and height matching your current display, unless you have set the monochrome mode (see Vset_monomode()). void Vdestroy_map(VMAP *map)
Releases a VMAP's resources. When a window closes, VWINL attempts to free its VMAP unless the V_NOFREEMAP flag is set. VMAP *Vselect_map(HWND w, VMAP *new)
Changes VMAP associated with a window. If VMAP pointer is NULL, the window will have no VMAP. Function returns a pointer to the previously selected VMAP. unsigned long Vset_flags(HWND w,unsigned long flags,int cmd)
You can use Vset_flags() to change a VWINL window's flags. You may need to call Vcommit_draw() after changing some flags. The cmd argument specifies how VWINL interprets the flag's argument. If cmd is VF_STO, VWINL copies the flags to the window. VF_SET sets the specified flags leaving the other bits unchanged; VF_CLR clears them. The VF_TOG command causes the specified flags to change state. Return value is the previous
flag value. void Vset_offset(HWND w,int x,int y)
Sets offset of specified window. When VWINL draws the VMAP to the window, it will use the offset as the VMAP's starting point (unless V_SCALE is set). x and y parameters are in pixels. void Vget_offset(HWND w,int *x,int *y)
Returns the window's offset (see Vset_offset()). int Vcreate_window(char *title,DWORD style,int x,int y,int width, int height, HWND parent,LPCSTR menu, long (*callback)(), unsigned vflags, HDC *dc,HWND *win,int show)
Vcreate_window mimics CreateWindow(). The menu parameter is actually a resource name or id. vflags field is a VWINL flag. Window handle returns via the win pointer and the VMAP DC (if any) goes to the dc pointer (unless the dc pointer is NULL). Function returns zero upon success; any other value indicates failure. HDC Vget_vdc(HWND w)
Returns the VMAP DC associated with the given window. int Vresize_winmap(HWND w,int width,int height)
Resizes the VMAP associated with the specified window. Automatically adjusts the window's scroll bars and handles other details. int Vresize_map(VMAP *m,int wid,int hi)
Uses Vresize_map() to change the size of a VMAP. If the VMAP is attached to a window, you will usually want to use Vresize_winmap() instead. void Vdont_quit(void)
During a WM_CLOSE message, you may call Vdont_quit() to prevent VWINL from terminating the application. void Vset_scroll(VMAP *m,int xstep,int ystep,int xpage,int ypage)
Sets the scroll increments for a VMAP. By default xstep and ystep equal 1, and the page variables equal 10. This causes smooth scrolling when you click the scroll-bar arrows. When you scroll a page, ten pixels go by. void Vclear_map(VMAP *m)
Erases entire drawing surface of a VMAP using the background color. void Vclear_win(HWND *w)
A macro that clears the VMAP associated with a window. int Vset_stretchmode(VMAP *m,int mode)
Sets the VMAP's stretch mode (used when V_SCALE is set). For more about stretch modes, see SetStretchBltMode() in the Windows API documentation. Returns the previous stretch mode. int Vset_monomode(int mode)
Sets or clears VWINL's monochrome mode. When monochrome mode is set, all Vcreate_window() and Vcreate_map() calls create monochrome bitmaps. These bitmaps may take up less space, but support only two colors.
Porting to Win32
As you may have guessed, VWinL started life as a conventional Windows program and only recently moved to Win32. Porting to Win32 was fairly straightforward, but there are some major issues to consider. First, the wParam variable in the window callback is 32 bits wide. Note that all callbacks use UINT instead of WORD for this parameter. The UINT type is a WORD under Windows 3 and a DWORD (32 bits) under Win32. Also, Win32 packs the WM_COMMAND, WM_HSCROLL, and WM_HSCROLL messages differently. Win32 changes which parameters are in wParam and which are in lParam for several messages. The same data is there, it's just been rearranged. VWinL has special code to get at the right values.
Another issue is that calls that return dimensions have changed. Calls like GetBitmapDimension() and SetWindowOrg() return a 32-bit word with x and y dimensions packed in it. Since each dimension requires a 32-bit word under Win32, these functions now take a pointer to a SIZE or POINT structure. Since this is radically different, the functions now have an Ex suffix (for example, GetBitmapDimensionEx()).
Although integers are now 32 bits wide, this is usually not a problem. VWinL, however, used an integer cast in the Vget_info() and save_info() routines. Until I changed this to a short cast (16 bits), the CreateWindow() routine would fail mysteriously. (Apparently, I was corrupting some important memory locations.) I compiled and tested VWinL and its companion programs using Phar Lap's free QuickStart package and the October Win32 SDK beta tools. QuickStart allows you to run the Win32 tools under DOS (or in a Windows DOS box). The resulting executables will run under Windows NT or Win32S (Microsoft's extension to Windows 3.1 that allows you to run many Windows NT programs).
Since Freeshow uses toolhelp .DLL (no longer supported under Win32), it won't work with Win32. Also MAIN5 (available electronically, see "Availability," page 3) may not work on your Win32S system due to some floating-point emulation problems. It does work under regular Windows 3.1. Despite these problems, however, Win32 is the wave of the future. With the additional memory and improved efficiency Win32 offers, VWinL makes even more sense for NT programs.
--A.W.
Causes VWINL to scale the window's VMAP to fit the window's client area. If this flag is not set, VWINL clips the VMAP to the window. When clipping, VWINL can offset the VMAP (see Vset_offset()) or automatically manage scroll bars.
V_RESIZE Causes the window's VMAP to automatically resize when the window resizes. This causes the VMAP's size to always match the window's size.
V_AUTOHSCROLL When set, VWINL will automatically manage horizontal scroll bars for this window. When passed to Vcreate_window(), this flag forces the window to use the WS_HSCROLL style.
V_AUTOVSCROLL When set, VWINL will automatically manage vertical scroll bars for this window. When passed to Vcreate_window(), this flag forces the window to use the WS_VSCROLL style.
V_NOMAP Pass this flag to Vcreate_window() to prevent VWINL from automatically creating a VMAP with the window. Presumably, you will use Vselect_map() to use a VMAP from another window or from Vcreate_map(). V_NOMAP is only meaningful during Vcreate_window().
V_NOQUIT Ordinarily, closing a VWINL window will cause the entire application to terminate. If V_NOQUIT is set for a window, you may close it without disturbing your applications.
V_NOFREEMAP This flag prevents VWINL from automatically freeing the window's VMAP when you close the window. You are responsible for calling Vdestroy_map() yourself. This is useful when more than one window shares a VMAP.
V_KSCROLL Allows VWINL to intercept scrolling keys and translates them into scroll bar events. This is especially useful in conjunction with V_AUTOHSCROLL and V_AUTOVSCROLL.
V_INIT An internal flag used by VWINL. Don't set this flag at home.
Copyright © 1993, Dr. Dobb's JournalFigure 1: The Vcreate_window() function.
int Vcreate_window(char *title,DWORD style,int x,int y, int wid, int hi, HWND parent,
LPCSTR menu, long (*cb)(HWND,UINT,UINT,LONG), unsigned long vflags,
HDC *dc,HWND *win, int show);
Parameters
title Title that appears in the caption bar.
style The same style bits used by CreateWindow.
x X coordinate for the window; often CW_USEDEFAULT.
y Y coordinate for the window.
wid Width of the window; often CW_USEDEFAULT.
hi Window's height.
parent Handle to the window's parent window. If NULL, create a top-level window.
menu If parent is NULL, a string that identifies the window's menu (or NULL if there is no menu). If parent is not NULL, this is the child-window id (see CreateWindow).
cb Pointer to your callback function. Unlike a normal callback, you don't need to export this function or call MakeProcInstance() to get the pointer.
vflags VWINL flags (see Table 2).
dc Pointer to the new window's VMAP DC (use NULL if you don't need this value).
win Pointer to an HWND that receives the new window handle. You must supply this pointer.
show Same as nShow in CreateWindow. Returns zero if successful, nonzero on failure.
Table 2: VWINL flags. (Note: V_SCALE is incompatible with V_RESIZE, V_AUTOHSCROLL, or V_AUTOVSCROLL. The V_RESIZE flag is not compatible with V_AUTOHSCROLL or V_AUTOVSCROLL.)
FLAG/Description
V_SCALE
Figure 2: The Freeshow program illustrates how VWinL can be used.
[LISTING ONE]
/* Very simple VWINL program */
#include "vwinl.h"
/* User's callback */
long usr_cb(HWND hWnd, UINT Message,
UINT wParam, LONG lParam)
{
if (Message==WM_VCREATE)
{
Vresize_winmap(hWnd,150,100);
/* Why limit ourselves? */
TextOut(Vget_vdc(hWnd),5,50,"Hello Universe",14);
Vcommit_draw(hWnd);
}
return DefWindowProc(hWnd,Message,wParam,lParam);
}
/* Start here */
int main(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
HWND hWnd;
/* Create window or die */
if (Vcreate_window("Simple Test Program",
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,
CW_USEDEFAULT,0,NULL,NULL, usr_cb,V_SCALE,
NULL,&hWnd,nCmdShow))
{
MessageBox(NULL,"Can't create window",NULL,MB_OK);
return 0;
}
return 1;
}
[LISTING TWO]
/* Header for VWINL -- Williams */
#ifndef _VWINL_H
#define _VWINL_H
#include <windows.h>
#ifndef WIN32
#define APIENTRY FAR PASCAL
/* Check for message cracker definition if 1 is there assume they all are */
#ifndef GET_WM_VSCROLL_CODE
#define GET_WM_VSCROLL_CODE(w,l) (w)
#define GET_WM_HSCROLL_CODE(w,l) (w)
#define GET_WM_VSCROLL_HWND(w,l) ((HWND)HIWORD(l))
#define GET_WM_HSCROLL_HWND(w,l) ((HWND)HIWORD(l))
#define GET_WM_VSCROLL_POS(w,l) (LOWORD(l))
#define GET_WM_HSCROLL_POS(w,l) (LOWORD(l))
#endif
#endif /* End of non-WIN32 definitions */
/* Flags */
/* V_SCALE doesn't make sense with V_RESIZE, V_AUTOHSCROLL, or V_AUTOVSCROLL.
V_RESIZE, doesn't make sense with any of AUTOXSCROLL flags. V_NOMAP is only
valid during window creation. V_INIT is reserved for internal use. */
#define V_SCALE 1L
#define V_RESIZE 2L
#define V_AUTOHSCROLL 4L
#define V_AUTOVSCROLL 8L
#define V_NOMAP 0x10L
#define V_NOQUIT 0x20L
#define V_NOFREEMAP 0x40L
#define V_KSCROLL 0x80L
#define V_INIT 0x80000000L
/* Flags for Vset_flags() */
#define VF_STO 0
#define VF_SET 1
#define VF_CLR 2
#define VF_TOG 3
#define WM_VCREATE WM_USER
#define Vcommit_draw(w) InvalidateRect(w,NULL,FALSE)
/* Get VMAP dc */
#define Vget_mdc(m) ((m)->dc)
#define Vget_stretchmode(m) ((m)->stretch_mode)
#define Vclear_win(w) Vclear_map(Vget_map(w))
long APIENTRY VWndProc (HWND, UINT, UINT, LONG) ;
int main(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow );
typedef struct
{
HBITMAP bitmap;
HDC dc;
HBITMAP defbitmap;
int xstep,ystep,xpage,ypage;
unsigned refct;
int stretch_mode;
} VMAP;
typedef struct
{
VMAP *map;
/* dimensions of bitmap (not window) */
unsigned int width;
unsigned int height;
/* flags */
unsigned long flags;
/* display offset */
unsigned int xoff;
unsigned int yoff;
long (*cb)(HWND,UINT,UINT,LONG);
} MEMWINFO;
void Vget_info(HWND w,MEMWINFO *info);
VMAP *Vget_map(HWND w);
VMAP *Vcreate_map(int wid,int hi);
void Vdestroy_map(VMAP *map);
VMAP *Vselect_map(HWND w,VMAP *new);
unsigned long Vset_flags(HWND w,unsigned long flags,int cmd);
void Vset_offset(HWND w,int x,int y);
void Vget_offset(HWND w,int *x,int *y);
int Vcreate_window(char *title,DWORD style,int x,int y, int wid,int hi,
HWND parent,LPCSTR menu, long (*cb)(HWND,UINT,UINT,LONG),
unsigned long vflags,HDC *dc,HWND *win,int show);
HDC Vget_vdc(HWND w);
int Vresize_winmap(HWND w,int wid,int hi);
int Vresize_map(VMAP *m,int wid,int hi);
void Vdont_quit(void);
void Vset_scroll(VMAP *m,int xstp,int ystp,int xpg,int ypg);
void Vclear_map(VMAP *m);
int Vset_stretchmode(VMAP *m,int mode);
int Vset_monomode(int mode);
#ifndef __BORLANDC__
#define main vwin_main
int vwin_main(HANDLE,HANDLE,LPSTR,int);
#else
int main(HANDLE,HANDLE,LPSTR,int);
#endif
#endif
[LISTING THREE]
/* VWIN virtual window package -- Al Williams */
#include "vwinl.h"
#include <windowsx.h>
#include <string.h>
/* Local prototypes */
static void do_paint(HWND);
long WINAPI VWndProc (HWND w, UINT Message, UINT wParam, LONG lParam);
static void save_info(HWND w,MEMWINFO *info);
static void set_sb(HWND w,MEMWINFO *minfo,UINT wid, UINT hi,int save);
static void scrollit(HWND w,MEMWINFO *minfo,int type, WORD code,HWND sb,
WORD pos);
static void key_scroll(HWND w,UINT key);
/* Global variables */
static HANDLE Hinstance; /* Our instance */
static int monomode; /* Make mono bitmaps? */
/* Flag to tell us if user allows us to quit */
static int V_quit=0;
/* VWINL WinMain -- this calls our main() function.
If main() returns 0, then we abort. */
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
WNDCLASS wndClass;
MSG msg;
HACCEL haccel;
/* Register window class style if first instance of this program. */
Hinstance=hInstance;
if ( !hPrevInstance )
{
/* NOTE: VWINL assumes the window will have a common DC! Don't use CS_OWNDC or
CS_PARENTDC unless you know what you are getting into! */
wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
wndClass.lpfnWndProc=(WNDPROC)VWndProc;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=
sizeof(MEMWINFO)+(sizeof(MEMWINFO)%2);
wndClass.hInstance=hInstance;
wndClass.hIcon=LoadIcon(hInstance,"vappicon");
wndClass.hCursor=LoadCursor(NULL, IDC_ARROW );
wndClass.hbrBackground=GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName=NULL;
wndClass.lpszClassName="VWINL";
if (!RegisterClass(&wndClass))
return FALSE;
}
if (!main(hInstance,hPrevInstance, lpszCmdLine, nCmdShow )) return FALSE;
/* Try to load an accelerator -- no big deal if it isn't there */
haccel=LoadAccelerators(hInstance,"VACCEL");
/* Sorta ordinary message loop -- will translate accelerators if appropriate */
while (GetMessage(&msg, NULL, 0, 0))
{
if (!haccel||
!TranslateAccelerator(msg.hwnd, haccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
/* Main Window procedure */
long APIENTRY VWndProc (HWND w, UINT Message, UINT wParam, LONG lParam)
{
MEMWINFO minfo;
Vget_info(w,&minfo);
switch(Message)
{
/* Don't let user see WM_CREATE -- we aren't ready for him yet */
case WM_CREATE:
return DefWindowProc(w,Message,wParam,lParam);
/* Always do painting and don't tell user */
case WM_PAINT:
do_paint(w);
return 0;
case WM_SIZE:
/* Catch 1st size */
if (minfo.flags&V_INIT)
{
if ((LOWORD(lParam)!=minfo.width||
HIWORD(lParam)!=minfo.height)&&minfo.map)
{
Vresize_winmap(w,LOWORD(lParam),HIWORD(lParam));
}
minfo.flags&=~V_INIT;
save_info(w,&minfo);
break;
}
/* Set scrollbars */
if (minfo.map)
{
/* Everytime you toggle a scroll bar from on to off or off to on you get a
WM_SIZE message! This little state machine turn off bars without getting into
an endless loop. */
static sizelock=0;
RECT r;
if (sizelock==1) return 0;
if (sizelock!=2)
{
/* Turn off both scroll bars so set_sb() can use the whole
client area if that's what it needs */
sizelock=1;
if (minfo.flags&V_AUTOVSCROLL)
SetScrollRange(w,SB_VERT,0,0,FALSE);
if (minfo.flags&V_AUTOHSCROLL)
SetScrollRange(w,SB_HORZ,0,0,FALSE);
/* Size might have changed, so reset it */
GetClientRect(w,&r);
lParam=MAKELONG(r.right-r.left,r.bottom-r.top);
}
/* Turn scroll bars on or off */
sizelock=2;
set_sb(w,&minfo,LOWORD(lParam),HIWORD(lParam),TRUE);
/* Size might have changed again, so reset it */
GetClientRect(w,&r);
lParam=MAKELONG(r.right-r.left,r.bottom-r.top);
sizelock=0;
}
/* Handle V_RESIZE if active */
if (minfo.flags&V_RESIZE&&minfo.map)
{
Vresize_winmap(w,LOWORD(lParam),HIWORD(lParam));
}
break;
/* Scroll cases */
case WM_VSCROLL:
if (minfo.flags&V_AUTOVSCROLL)
scrollit(w,&minfo,SB_VERT,
GET_WM_VSCROLL_CODE(wParam,lParam),
GET_WM_VSCROLL_HWND(wParam,lParam),
GET_WM_VSCROLL_POS(wParam,lParam));
break;
case WM_HSCROLL:
if (minfo.flags&V_AUTOHSCROLL)
scrollit(w,&minfo,SB_HORZ,
GET_WM_HSCROLL_CODE(wParam,lParam),
GET_WM_HSCROLL_HWND(wParam,lParam),
GET_WM_HSCROLL_POS(wParam,lParam));
break;
case WM_KEYDOWN:
if (minfo.flags&V_KSCROLL) key_scroll(w,wParam);
break;
case WM_DESTROY:
/* pass to user if V_NOQUIT set */
if (minfo.flags&V_NOQUIT) break;
V_quit=1;
/* Pass to user, if V_quit is set, go ahead and kill ourselves */
if (!minfo.cb(w,Message,wParam,lParam)&&V_quit)
{
/* Clean up window's resources here... */
if (minfo.map&&minfo.map->refct&&!(minfo.flags&V_NOFREEMAP))
{
minfo.map->refct--;
Vdestroy_map(minfo.map);
}
PostQuitMessage(0);
return 0;
}
return 0;
}
/* pass to user's callback */
if (minfo.cb)
return minfo.cb(w,Message,wParam,lParam);
else
return DefWindowProc(w,Message,wParam,lParam);
}
/* Create a VWIN -- see text for description */
int Vcreate_window(char *title,DWORD style,int x,int y,
int wid,int hi,HWND parent,LPCSTR menu,
long (*cb)(HWND,UINT,UINT,LONG),
unsigned long vflags,HDC *dc,HWND *win,int show)
{
HWND w;
HMENU hMenu=NULL;
RECT r;
MEMWINFO minfo;
if (menu&&!parent)
hMenu=LoadMenu(Hinstance,menu);
else
hMenu=(HMENU)menu;
/* Auto set scroll style */
if (vflags&V_AUTOHSCROLL) style|=WS_HSCROLL;
if (vflags&V_AUTOVSCROLL) style|=WS_VSCROLL;
memset(&minfo,0,sizeof(MEMWINFO));
minfo.flags=vflags|V_INIT;
minfo.cb=cb;
w=*win=CreateWindow("VWINL",title,style,x,y,
wid,hi,parent,hMenu,Hinstance,NULL);
if (!*win) return 1;
save_info(*win,&minfo);
SetScrollRange(*win,SB_HORZ,0,0,TRUE);
SetScrollRange(*win,SB_VERT,0,0,TRUE);
GetClientRect(w,&r);
/* create DC the same size */
minfo.xoff=minfo.yoff=0;
minfo.width=r.right-r.left;
minfo.height=r.bottom-r.top;
if ((minfo.flags&V_NOMAP)==0)
{
minfo.map=Vcreate_map(minfo.width,minfo.height);
if (!minfo.map) return 2;
if (dc) *dc=minfo.map->dc;
}
else
minfo.map=NULL;
/* Store cb and other data in extra words */
save_info(w,&minfo);
/* finish up */
ShowWindow(*win,show);
/* Call user's callback with WM_VCREATE */
minfo.cb(*win,WM_VCREATE,0,0);
UpdateWindow(*win);
return 0;
}
/* Make a VMAP -- respects monomode flag */
VMAP *Vcreate_map(int wid,int hi)
{
HDC dc;
VMAP *bm;
HWND w;
w=GetDesktopWindow(); /* any window will do */
dc=GetDC(w);
if (!dc) return NULL;
/* temp use of malloc */
bm=(VMAP *)LocalAlloc(LPTR,sizeof(VMAP));
if (!bm) return NULL;
bm->dc=CreateCompatibleDC(dc);
/* release Desktop DC */
ReleaseDC(w,dc);
if (!bm->dc)
{
LocalFree((HLOCAL)bm);
return NULL;
}
/* Make the bitmap */
/* NOTE: Windows won't let you make an arbitrary colored
bitmap. You must have a device that corresponds to
the color-size of the bitmap */
if (!monomode)
bm->bitmap=CreateBitmap(wid,hi,GetDeviceCaps(dc,PLANES),
GetDeviceCaps(dc,BITSPIXEL),NULL);
else
bm->bitmap=CreateBitmap(wid,hi,1,1,NULL); /* mono */
if (!bm->bitmap)
{
DeleteDC(bm->dc);
LocalFree((HLOCAL)bm);
return NULL;
}
/* Note: This is supposed to be in .1 mm units, but since no
one else uses it, who cares! */
#ifndef WIN32
SetBitmapDimension(bm->bitmap,wid,hi);
#else
SetBitmapDimensionEx(bm->bitmap,wid,hi,NULL);
#endif
bm->defbitmap=SelectObject(bm->dc,bm->bitmap);
if (!bm->defbitmap)
{
DeleteDC(bm->dc);
LocalFree((HLOCAL)bm);
return NULL;
}
/* Set default stuff */
bm->xstep=bm->ystep=1;
bm->xpage=bm->ypage=10;
bm->refct=1;
bm->stretch_mode=BLACKONWHITE;
Vclear_map(bm);
return bm;
}
/* Free up a valid VMAP if its refct is 1 or 0 */
void Vdestroy_map(VMAP *map)
{
if (map->refct>1)
return;
SelectObject(map->dc,map->defbitmap);
DeleteObject(map->bitmap);
DeleteDC(map->dc);
LocalFree((HLOCAL)map);
}
/* Associate a new map (or NULL for no map) with a window--returns old map */
VMAP *Vselect_map(HWND w,VMAP *new)
{
MEMWINFO minfo;
VMAP *rc;
#ifndef WIN32
DWORD dims;
#else
SIZE dims;
#endif
RECT r;
GetClientRect(w,&r);
Vget_info(w,&minfo);
rc=minfo.map;
minfo.map=new;
if (rc) rc->refct--;
/* NULL is OK here in which case we don't do much */
if (new)
{
new->refct++;
#ifndef WIN32
dims=GetBitmapDimension(minfo.map->bitmap);
minfo.width=LOWORD(dims);
minfo.height=HIWORD(dims);
#else
GetBitmapDimensionEx(minfo.map->bitmap,&dims);
minfo.width=dims.cx;
minfo.height=dims.cy;
#endif
}
else
{
minfo.width=minfo.height=0;
}
save_info(w,&minfo);
set_sb(w,&minfo,r.right-r.left,r.bottom-r.top,TRUE);
Vcommit_draw(w);
return rc;
}
/* Set stretch mode for map -- returns old mode */
int Vset_stretchmode(VMAP *m,int mode)
{
int rv;
rv=m->stretch_mode;
m->stretch_mode=mode;
return rv;
}
/* Sets global monomode flag which causes VWINL to create
monochrome bitmaps to save space.
Returns old value (of course) */
int Vset_monomode(int mode)
{
int rv;
rv=monomode;
monomode=mode;
return rv;
}
/* Get window's map */
VMAP *Vget_map(HWND w)
{
MEMWINFO minfo;
Vget_info(w,&minfo);
return minfo.map;
}
/* Erase a map's surface */
void Vclear_map(VMAP *m)
{
HBRUSH brush;
unsigned int wid,hi;
#ifndef WIN32
DWORD dims;
#else
SIZE dims;
#endif
/* We store bitmap dimension this way.
Units are pixels contrary to the .1mm convention */
#ifndef WIN32
dims=GetBitmapDimension(m->bitmap);
wid=LOWORD(dims);
hi=HIWORD(dims);
#else
GetBitmapDimensionEx(m->bitmap,&dims);
wid=dims.cx;
hi=dims.cy;
#endif
/* make background brush */
brush=CreateSolidBrush(GetBkColor(m->dc));
brush=SelectObject(m->dc,brush);
/* Brush area */
PatBlt(m->dc,0,0,wid,hi,PATCOPY);
DeleteObject(SelectObject(m->dc,brush));
}
/* Change a window's VWINL flags -- this only makes sense for some flags. For
example, V_NOMAP is meaningless here. If you ever plan to set V_AUTOHSCROLL
or V_AUTOVSCROLL, make sure to set the scroll bar style flags during
Vcreate_window (this happens automatically when you set V_AUTOxSCROLL during
the create. Returns old flag value */
unsigned long Vset_flags(HWND w,unsigned long flags,int cmd)
{
unsigned long rv;
MEMWINFO minfo;
RECT r;
Vget_info(w,&minfo);
GetClientRect(w,&r);
rv=minfo.flags;
switch (cmd)
{
case VF_SET:
minfo.flags|=flags;
break;
case VF_CLR:
minfo.flags&=~flags;
break;
case VF_TOG:
minfo.flags^=flags;
break;
default:
minfo.flags=flags;
break;
}
save_info(w,&minfo);
if (((rv&V_AUTOHSCROLL)^(minfo.flags&V_AUTOHSCROLL))
||((rv&V_AUTOVSCROLL)^(minfo.flags&V_AUTOVSCROLL)))
{
/* scroll changed */
if (!(minfo.flags&V_AUTOHSCROLL))
{
minfo.xoff=0;
SetScrollRange(w,SB_HORZ,0,0,TRUE);
}
if (!(minfo.flags&V_AUTOVSCROLL))
{
minfo.yoff=0;
SetScrollRange(w,SB_VERT,0,0,TRUE);
}
save_info(w,&minfo);
if (minfo.flags&(V_AUTOHSCROLL|V_AUTOVSCROLL))
set_sb(w,&minfo,r.right-r.left,r.bottom-r.top,TRUE);
Vcommit_draw(w);
}
return rv;
}
/* Set the VMAP offset in pixels */
void Vset_offset(HWND w,int x,int y)
{
MEMWINFO minfo;
Vget_info(w,&minfo);
minfo.xoff=x;
minfo.yoff=y;
save_info(w,&minfo);
}
/* Read the VMAP pixel offsets */
void Vget_offset(HWND w,int *x,int *y)
{
MEMWINFO minfo;
Vget_info(w,&minfo);
if (x) *x=minfo.xoff;
if (y) *y=minfo.yoff;
}
/* Get window's VMAP dc */
HDC Vget_vdc(HWND w)
{
MEMWINFO minfo;
Vget_info(w,&minfo);
return minfo.map->dc;
}
/* Resize a map
Returns 0 if OK */
int Vresize_map(VMAP *m,int wid,int hi)
{
VMAP *newmap;
int oldstate;
HBITMAP oldbm=m->bitmap;
newmap=Vcreate_map(wid,hi);
if (!newmap) return 1;
oldstate=SetMapMode(m->dc,MM_TEXT);
BitBlt(newmap->dc,0,0,wid,hi,m->dc,0,0,SRCCOPY);
/* copy the right parts from newmap */
m->bitmap=newmap->bitmap;
SetMapMode(m->dc,oldstate);
/* Except for bitmap! */
SelectObject(newmap->dc,newmap->defbitmap);
SelectObject(m->dc,m->bitmap);
/* Delete old dc and bitmap */
DeleteDC(newmap->dc);
DeleteObject(oldbm);
LocalFree((HLOCAL)newmap);
return 0;
}
/* Resize VMAP attached to window -- returns 0 if OK */
int Vresize_winmap(HWND w,int wid,int hi)
{
MEMWINFO minfo;
RECT r;
GetClientRect(w,&r);
Vget_info(w,&minfo);
if (Vresize_map(minfo.map,wid,hi)) return 1;
minfo.width=wid;
minfo.height=hi;
save_info(w,&minfo);
set_sb(w,&minfo,r.right-r.left,r.bottom-r.top,TRUE);
return 0;
}
/* Clear quit flag for user */
void Vdont_quit()
{
V_quit=0;
}
/* Get VWIN info -- public */
void Vget_info(HWND w,MEMWINFO *info)
{
int i;
for (i=0;i<sizeof(MEMWINFO);i+=2)
*(unsigned short *)(((unsigned char *)info)+i)=GetWindowWord(w,i);
}
/* Save VWIN info (local use only) */
static void save_info(HWND w,MEMWINFO *info)
{
int i;
for (i=0;i<sizeof(MEMWINFO);i+=2)
SetWindowWord(w,i,*(unsigned short *) (((unsigned char *)info)+i));
}
/* Scroll handler (local) */
static void scrollit(HWND w,MEMWINFO *minfo,int type,
WORD code,HWND sb,WORD pos)
{
unsigned int *offset;
/* Store as long to avoid unsigned underflow */
long newoffset;
int step,page;
RECT r;
GetClientRect(w,&r);
/* Set up offset and steps */
if (type==SB_VERT)
{
offset=&minfo->yoff;
step=minfo->map->ystep;
page=minfo->map->ypage;
}
else
{
offset=&minfo->xoff;
step=minfo->map->xstep;
page=minfo->map->xpage;
}
newoffset=*offset;
/* Process scroll command */
switch (code)
{
case SB_TOP:
newoffset=0;
break;
case SB_BOTTOM:
if (type==SB_VERT)
{
newoffset=minfo->height-(r.bottom-r.top);
}
else
{
newoffset=minfo->width-(r.right-r.left);
}
break;
case SB_LINEUP:
step=-step;
/* fall thru */
case SB_LINEDOWN:
newoffset+=step;
break;
case SB_PAGEUP:
page=-page;
/* fall thru */
case SB_PAGEDOWN:
newoffset+=page;
break;
case SB_THUMBPOSITION:
newoffset=pos;
break;
/* No SB_THUMBTRACK processing; a big hi-res VMAP takes too long to paint */
}
if (newoffset<0) newoffset=0;
/* Update the offset */
if (type==SB_VERT)
{
if (newoffset+(r.bottom-r.top)>minfo->height)
newoffset=minfo->height-(r.bottom-r.top);
}
else
{
if (newoffset+(r.right-r.left)>minfo->width)
newoffset=minfo->width-(r.right-r.left);
}
/* Update position */
SetScrollPos(w,type,(unsigned)newoffset,TRUE);
*offset=newoffset;
save_info(w,minfo);
Vcommit_draw(w);
}
/* Set scroll parameters. Currently you can't read them back unless you call
Vget_info(). If you must have a Vget_scroll call you can write it! */
void Vset_scroll(VMAP *m,int xstep,int ystep,int xpage,int ypage)
{
m->xstep=xstep;
m->ystep=ystep;
m->xpage=xpage;
m->ypage=ypage;
return;
}
/* Process "scroll" keys */
static void key_scroll(HWND w,UINT key)
{
switch (key)
{
case VK_HOME:
SendMessage(w,WM_VSCROLL,SB_TOP,0L);
break;
case VK_END:
SendMessage(w,WM_VSCROLL,SB_BOTTOM,0L);
break;
case VK_PRIOR:
SendMessage(w,WM_VSCROLL,SB_PAGEUP,0L);
break;
case VK_NEXT:
SendMessage(w,WM_VSCROLL,SB_PAGEDOWN,0L);
break;
case VK_UP:
SendMessage(w,WM_VSCROLL,SB_LINEUP,0L);
break;
case VK_DOWN:
SendMessage(w,WM_VSCROLL,SB_LINEDOWN,0L);
break;
case VK_LEFT:
SendMessage(w,WM_HSCROLL,SB_LINEUP,0L);
break;
case VK_RIGHT:
SendMessage(w,WM_HSCROLL,SB_LINEDOWN,0L);
break;
}
}
/* Local function to set scroll bars up */
static void set_sb(HWND w,MEMWINFO *minfo,UINT wid, UINT hi,int save)
{
RECT r;
if (minfo->flags&V_INIT) return;
if (minfo->flags&V_AUTOHSCROLL)
{
if (minfo->xoff&&minfo->width<=wid)
{
/* If bitmap will fit in client area, make it do so */
minfo->xoff=0;
if (save) save_info(w,minfo);
}
/* Set up H bar */
SetScrollPos(w,SB_HORZ,minfo->xoff,FALSE);
SetScrollRange(w,SB_HORZ,0, (unsigned)
max(0L,(long)minfo->width-(long)wid) ,TRUE);
/* Recompute size -- may have changed if scroll bar enabled by above step */
GetClientRect(w,&r);
wid=r.right-r.left;
hi=r.bottom-r.top;
}
if (minfo->flags&V_AUTOVSCROLL)
{
if (minfo->yoff&&minfo->height<=hi)
{
/* If bitmap will fit in client area, make it do so */
minfo->yoff=0;
if (save) save_info(w,minfo);
}
/* Set up V bar */
SetScrollPos(w,SB_VERT,minfo->yoff,FALSE);
SetScrollRange(w,SB_VERT,0, (unsigned)max(0L,(long)minfo->
height-(long)hi), TRUE);
}
}
/* Magic paint routine */
static void do_paint(HWND w)
{
HDC hdc;
PAINTSTRUCT ps;
MEMWINFO minfo;
RECT r;
int oldmode;
#ifndef WIN32
DWORD oldworg,oldvorg;
#else
POINT oldworg,oldvorg;
#endif
hdc=BeginPaint(w,&ps);
GetClientRect(w,&r);
Vget_info(w,&minfo);
if (minfo.map==NULL||minfo.map->dc==0)
{
ReleaseDC(w,hdc);
EndPaint(w,&ps);
return;
}
/* Set up DC the way we like it */
oldmode=SetMapMode(minfo.map->dc,MM_TEXT);
#ifndef WIN32
oldworg=SetWindowOrg(minfo.map->dc,0,0);
oldvorg=SetViewportOrg(minfo.map->dc,0,0);
#else
SetWindowOrgEx(minfo.map->dc,0,0,&oldworg);
SetViewportOrgEx(minfo.map->dc,0,0,&oldvorg);
#endif
/* Do something different for scale window */
if (minfo.flags&V_SCALE)
{
int oldmode;
oldmode=SetStretchBltMode(hdc,minfo.map->stretch_mode);
StretchBlt(hdc,0,0,r.right-r.left,r.bottom-r.top,
minfo.map->dc,minfo.xoff,minfo.yoff,
minfo.width,minfo.height,SRCCOPY);
SetStretchBltMode(hdc,oldmode);
}
else
{
/* if VMAP doesn't entirely cover window, clear first */
if (r.right-r.left>minfo.width-minfo.xoff||
r.bottom-r.top>minfo.height-minfo.yoff)
{
HBRUSH brush;
brush=CreateSolidBrush(GetBkColor(minfo.map->dc));
brush=SelectObject(hdc,brush);
/* erase "under" bitmap */
PatBlt(hdc,0,minfo.height,
r.right-r.left,r.bottom-r.top,PATCOPY);
/* erase to "right" of bitmap */
PatBlt(hdc,minfo.width,0,
r.right-r.left,r.bottom-r.top,PATCOPY);
DeleteObject(SelectObject(hdc,brush));
}
/* Draw it */
BitBlt(hdc,0,0,minfo.width-minfo.xoff, minfo.height-minfo.yoff,
minfo.map->dc, minfo.xoff,minfo.yoff,SRCCOPY);
}
SetMapMode(minfo.map->dc,oldmode);
#ifndef WIN32
SetWindowOrg(minfo.map->dc,LOWORD(oldworg), HIWORD(oldworg));
SetViewportOrg(minfo.map->dc,LOWORD(oldvorg), HIWORD(oldvorg));
#else
SetWindowOrgEx(minfo.map->dc,oldworg.x,oldworg.y,NULL);
SetViewportOrgEx(minfo.map->dc,oldvorg.x,oldvorg.y,NULL);
#endif
ReleaseDC(w,hdc);
EndPaint(w,&ps);
}
End Listings