TOC PREV NEXT INDEX

Put your logo here!


Chapter 6: Text in a GUI World

6.1: Text Display Under Windows

An ancient Chinese proverb tells us that "a picture is worth a thousand words." While this may certainly be true in many instances, pictures alone cannot convey information as efficiently as text. Imagine, for example, trying to write this book using only images, no text. So while a picture may be worth a thousand words, sometimes two or three words does the job a whole lot better than those thousand words. Anyone who has used a Windows application suffering from "icon overload" can appreciate the fact that text is still important in a GUI world.

Of course, text under Windows is a far cry from "console" or "green-screen" applications of yesterday. Writing console applications (even under Windows) is a fairly trivial exercise when you view the console display as a "glass teletype" to which you send a stream of characters. Fancier console applications treat the display as a two-dimensional object, allowing the software to position the cursor and text on the screen. However, such applications generally deal with fixed size (fixed pitch) character cells, so keeping track of the information on the display is still fairly trivial. Windows, of course, supports text of different fonts and sizes and you can finely position text on the display. This vastly complicates the display of text in the system. Another big difference between typical console applications and a Windows application is that the console device itself remembers the text written to the display (at least, for as long as such display is necessary). A Windows application, however, must remember all the text it's written to the display and be ready to redraw that text whenever Windows sends the application a redraw message. As such, a simple "text application" under Windows can be quite a bit more complex than an equivalent text-based console application.

As you saw in the previous chapter, writing text to a Windows' GUI display is not quite as trivial as calling HLA's stdout.put procedure. Console output is fundamentally different than text output in a GUI application. In console applications, the output characters are all the same size, they all use the same type style (typeface), and they typically employ a monospaced font (that is, each character is exactly the same width) that makes it easy to create columns of text by controlling the number of characters you write after the beginning of the line. The Windows' console API automatically handles several control characters such as backspaces, tabs, carriage returns, and linefeeds that make it easy to manipulate strings of text on the display. All the assumptions one can make about how the system displays text in a console application are lost when displaying text in a GUI application. There are three fundamental differences between console applications and GUI applications:

Because of the simplifying nature of text output in a console app, writing GUI apps is going to involve more complexity than writing console apps. The purpose of this chapter is to explain that additional complexity.

6.2: Painting

Although this chapter specifically deals with text output in a GUI application, you must remember that on the display screen there really is no such thing as text; everything is a graphic image. The text that appears on the screen is really nothing more than a graphic image. Therefore, drawing text is really nothing more than a special case of drawing some graphic image on a Windows display. Therefore, before we can begin discussing text output under Windows, we need to first discuss some generic information about drawing objects under Windows.

Windows that an application displays are usually divided into two different areas or regions: a client region and a non-client region. The client region consists of the area in the window that the application is responsible for maintaining. The non-client region is that portion of the window that Microsoft Windows maintains, including the borders, scroll bars, and title bar (see Figure 6-1). Although it is possible for an application to affect the non-client region under some special circumstances, for the most part, all drawing by the application in response to a w.WM_PAINT message occurs in the client region.

Figure 6-1: Client Versus Non-Client Areas in a Window


 

As the previous chapter explains, an application does not arbitrarily draw information in the application's window (client area or otherwise). Instead, Windows sends a message (w.WM_PAINT) to the application's window procedure and the window procedure takes the responsibility of (re)drawing the information in the window. One immediate question that should come up is "when does Windows notify the application that it needs to redraw the client area?" As you saw in the last chapter, one way to force Windows to send this message is by calling the w.UpdateWindow API function. When you called w.UpdateWindow and pass it the handle of a given window, Microsoft Windows will invalidate that window and post a message telling the Window to redraw itself.

Calling the w.UpdateWindow function isn't the only way that the contents of some window could become invalid. An application's window could become invalid because the user drags (or opens) some other window over the top of the window and then closes that other window. Because the area covered by the second window in the original application's window now contains the image drawn for that second window, the client area of the original window is invalid - it does not contain a valid image for the original window. When this happens, Microsoft Windows will invalidate the original window and post a w.WM_PAINT message to that window so it will redraw itself and repair the damage caused by overlaying that second window over the first.

Generally, an application will only call the w.UpdateWindow API function to force a redraw of the window from the application's main program. Normally to achieve this, you'd call the w.InvalidateRect API function:


 
type
 
	InvalidateRect: procedure
 
	(
 
				hWnd :dword;
 
		var		lpRect :RECT;
 
				bErase :boolean
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__InvalidateRect@12" );
 

 

The first parameter is the handle of the window you want redrawn (e.g., the application's main window if you want everything redrawn); if you specify NULL here, Windows will redraw all windows. The second parameter is a pointer to a w.RECT data structure that specifies the rectangular region of the window you want redrawn. If you specify NULL as this parameter, Windows will tell the application to repaint the entire window; we'll discuss the use of this parameter later (as a means to improve window redrawing performance), for now you can safely specify NULL as the value for this parameter. The third parameter is a boolean value (true or false) that specifies whether Windows will first erase the background before you actually redraw the window. Again, this is an optimization that can save time redrawing the window by skipping the erase operation. Until you understand how to use this parameter, it's probably safest to pass true as this parameter's value.

When a window becomes invalid, Microsoft Windows sends the w.WM_PAINT message to the window procedure which, presumably, invokes the code or function that redraws the window. As you saw in the previous chapter, the drawing code is sandwiched between calls to the w.BeginPaint and w.EndPaint API calls. We're actually going to create a set of macros to handle the calls to w.BeginPaint and w.EndPaint a little later in this chapter; however, to understand how to write those macros you'll need to understand how these functions operate. But first, we've got to discuss another Windows' object: the device context.

6.2.1: Device Contexts

A device context is a Windows abstraction of an output device that is capable of rendering information sent to a window. Device contexts, or simply DCs, provide a machine-independent view of various display and printer devices allowing an application to write data to a single "device" yet have that information appear properly on a multitude of different real-world devices. For the purposes of this chapter (and several that follow), the
DC will generally represent a video display device; however, keep in mind that much of what this chapter discusses applies to printers and certain other output devices as well.

The actual device context data structure is internal to Windows; an application cannot directly manipulate a DC. Instead, Windows returns a handle to a device context and the application references the DC via this handle. Any modifications an application wants to make to the DC is done via API functions. We'll take a look at some of these functions that manipulate device context attributes in the very next section.

So the first question to ask is "how does an application obtain a device context handle for a given window?" Well, you've already seen one way: by calling the w.BeginPaint API function. For example, the following code is taken from the Paint procedure of the HelloWorld program from the previous chapter:


 
	w.BeginPaint( hWnd, ps ); // Returns device context handle in EAX
 
	mov( eax, hDC );
 

 

Calls to Win32 API functions that actually draw data on the display will require the device context handle that w.BeginPaint returns.

Whenever a window procedure receives a w.WM_PAINT message, the window procedure (or some subservient procedure it calls, like the Paint procedure in the HelloWorld program) typically calls w.BeginPaint to get the device context and do other DC initialization prior to actually drawing information to the device context. That device context is valid until the corresponding call to w.EndPaint.

Between the w.BeginPaint and w.EndPaint calls, the window procedure (and any subservient procedures like Paint) calls various functions that update the window's client region, like the w.DrawText function from the HelloWorld program of the previous chapter. An important thing to realize is that these individual calls don't immediately update the display (or whatever physical device is associated with the device context). Instead, Windows stores up the drawing requests (as part of the w.PAINTSTRUCT parameter, ps in the current example, that you pass to the w.BeginPaint API function). When you call the w.EndPaint function, Windows will begin updating the physical display device.

One issue with the calls to w.BeginPaint and w.EndPaint is that Windows assigns a very low priority w.WM_PAINT messages. This was done to help prevent a massive number of window redraws in response to every little change the application wants to make to the display. By making the w.WM_PAINT message low priority, Windows tends to save up (and coalesce) a sequence of window updates so the window procedure receives only a single w.WM_PAINT message in response to several window updates that need to be done. This reduces the number of w.WM_PAINT messages sent to the application and, therefore, reduces the number of times that the application redraws its window (thus speeding up the whole process). The only problem with this approach is that sometimes the system is processing a large number of higher-priority messages and the window doesn't get updated for some time. You've probably seen a case where you've closed a window and the system doesn't redraw the windows underneath for several seconds. When this happens, Windows (and various Windows applications) are busy processing other higher-priority messages and the w.WM_PAINT message has to wait.

On occasion, you'll need to immediately update some window immediately, without waiting for Windows to pass a w.WM_PAINT message to your window procedure and without waiting for the application to handle all the other higher-priority messages. You can achieve this by calling the w.GetDC and w.ReleaseDC API functions. These two functions have the following prototypes:


 
type
 
	GetDC: procedure
 
	(
 
		hWnd :dword
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__GetDC@4" );
 

 
	ReleaseDC: procedure
 
	(
 
		hWnd :dword;
 
		hDC :dword
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__ReleaseDC@8" );
 

 

You use these two functions just like w.BeginPaint and w.EndPaint (respectively) with just a couple differences. First of all, you don't pass these two parameters a w.PAINTSTRUCT parameter. This is because all calls to the drawing routines between these two functions immediately update the window (rather than storing those drawing operations up in a w.PAINTSTRUCT object). The second major difference is that you can call w.GetDC and w.ReleaseDC anywhere you have a valid window handle, not just in the section of code that handles the w.WM_PAINT message.

There is another variant of w.GetDC, w.GetWindowDC that returns a device context for the entire window, including the non-client areas. An application can use this API function to draw into the non-client areas. Generally, Microsoft recommends against drawing in the non-client area, but if you have a special requirement (like drawing a special image within the title bar), then this function is available. You use w.GetWindowDC just like w.GetDC. Like the call to w.GetDC, you must call the w.ReleaseDC function when you are through drawing to the window. Also like w.GetDC, you can call w.GetWindowDC anywhere, not just when handling a w.WM_PAINT message. Here's the function prototype for the w.GetWindowDC call:


 
type
 
	GetWindowDC: procedure
 
	(
 
		hWnd :dword
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__GetWindowDC@4" );
 

 

6.2.2: Device Context Attributes

Although a DC attempts to abstract away the differences between various physical devices, there is no escaping the fact that behind the abstraction is a real, physical, device. And different real-world devices have some real-world differences that an application cannot simply ignore. For example, some devices (like display adapters) are capable of displaying color information whereas some devices (e.g., most laser printers) are only capable of black & white output. Likewise, some display devices are capable of displaying millions of different colors, while some displays are only capable of placing thousands or hundreds of different colors on the screen simultaneously. Although Windows will attempt to convert data intended for a generic device to each of these specific devices, the actual display (or printout) that the user sees will probably be much better if the application limits itself to the capabilities of the actual physical device. For this reason, Windows provides a set of API calls that let an application determine the capabilities of the underlying physical device.

Some of the attributes associated with a device context exist for convenience, not because of the physical capabilities of the underlying device. For example, the device context maintains default information such as what color to use when drawing text, what background color to use, the current font, the current line drawing style, how to actually draw an image onto the display, and lots of other information. Carrying this information around in the device context saves the programmer from having to pass this information as parameter data to each and every drawing function the application calls. For example, when drawing text one rarely changes the font with each string written to the display. It would be rather painful to have to specify the font information on each call to a function like w.DrawText. Instead, there are various calls you can make to Windows that will set the current device context attributes for things like fonts, colors, and so on, that will take effect on the next API call that draws information to the device context. We'll take a look at some of these functions a little later in this chapter.

6.2.3: Painting Text in the Client Area

Once you obtain a device context (DC) with a call to w.BeginPaint, w.GetDC, or w.GetWindowDC, you may begin drawing in the window. The purpose of this chapter and the next chapter is to introduce you to the various functions you can call to display data in a window. This chapter covers textual output using functions like w.DrawText and w.TextOut, the next chapter discusses those functions you'll use to draw graphic images in a window.

A little later in this section we'll take a look at the actual functions you can call to place textual data in some window of an application. For now, the important thing to note is that Windows only provides functions that display strings of text. There are no formatting routines like C++ stream I/O (cut), C's formatted print (printf), or HLA's stdout.put that automatically convert various data types to string form upon output. Instead, you must manually convert whatever data you want to display into string form and then write that string to the display. Fortunately, HLA provides a string formatting function whose syntax is nearly identical to stdout.put (str.put) that lets you easily convert binary data to string form. So to print data in some internal (e.g., integer or floating point) format, you can first call the str.put procedure to convert the data to a string and then call a Win32 API function like w.DrawText or w.TextOut to display the text in your application's window.

The str.put procedure (macro, actually) takes the following form:

str.put( stringVariable, <<list of items to convert to string form>> );
 

If you're familiar with the HLA stdout.put procedure, then you'll be right at home with the str.put procedure. Except for the presence of a string variable at the beginning of the str.put parameter list, you use the str.put procedure the same way you use stdout.put; the major difference between these two functions is that str.put "writes" its output to the string variable you specify rather than writing it to the standard output device. Here's an example of a typical str.put call:

str.put( OutputString, "Value of i:", i:4, " Value of r:", r:10:2 );
 

The str.put procedure converts the items following the first parameter into a string form and then stores the resulting string into the variable you specify as the first parameter in the call to str.put. You must ensure that you've preallocated enough storage for the string to hold whatever data you write to the output string variable. If you're outputting lines of text to the window (one line at a time), then 256 characters is probably a reasonable amount of storage (more than generous, in fact) to allocate for the output string. If you don't have to worry about issues of reentrancy in multi-threaded applications, you can statically allocate a string to hold the output of str.put thusly:


 
static
 
	OutputString: str.strvar( 256 );
 

 

The str.init macro declares OutputString as a string variable and initializes it with a pointer to a string buffer capable of holding up to 256 characters. The problem, of course, with using static allocation is that if two threads wind up executing some code that manipulates OutputString simultaneously, then your program will produce incorrect output. Although most of the applications you'll write won't be multi-threaded, it's still a good idea to get in the habit of allocating the storage dynamically to avoid problems if you decide to change some existing code to make it multi-threaded in the future.

Of course, the standard way to dynamically allocate storage for a string in HLA is via the stralloc function. The only problem with stralloc is that it ultimately winds up calling Windows' memory management API. While there is nothing fundamentally wrong with doing this, keep in mind two facts: calling Win32 API functions are somewhat expensive (and the memory management calls can be expensive) and we're briefly allocating storage for this string. Once we've converted our output to string form and displayed it in the window, we don't really need the string data anymore. True, we can use the same string variable for several output operations, but the bottom line is that when our Paint procedure (or whomever is handling the w.WM_PAINT message) returns, that string data is no longer needed and we'll have to deallocate the storage (i.e., another expensive call to the Win32 API via the strfree invocation). A more efficient solution is to allocate the string object as local storage within the activation record of the procedure that contains the call to str.put. HLA provides two functions to help you do this efficiently: str.init and tstralloc.

The str.init function takes an existing (preallocated) buffer and initializes it for use as a string variable. This function requires two parameters: a buffer and the size (in bytes) of that buffer. This function returns a pointer to the string object it creates within that buffer area and initializes that string to the empty string. You would normally store the return result of this function (in EAX) into an HLA string variable. Here's an example of the use of this function:


 
procedure demoStrInit;
 
var
 
	sVar:			string;
 
	buffer:			char[272];
 
begin demoStrInit;
 

 
	str.init( buffer, @size( buffer ));
 
	mov( eax, sVar );
 
		.
 
		.
 
		.
 
end demoStrInit;
 

 

An important thing to note about str.init is that the pointer it returns does not contain the address of the first character buffer area whose address you pass as the first parameter. Instead, str.init may skip some bytes at the beginning of the buffer space in order to double-word align the string data, then it sets aside eight bytes for the HLA string's maximum length and current length values (str.init also initializes these fields). The str.init function returns the address of the first byte beyond these two double-word fields (as per the definition of an HLA string). Once you initialize a buffer for use as a string object via str.init, you should only manipulate the data in that buffer using HLA string functions via the string pointer that str.init returns (e.g, the sVar variable in this example). One issue of which you should be aware when using the str.init function is that the buffer you pass it must contain the maximum number of characters you want to allow in the string plus sixteen. That is why buffer in the current example contains 272 characters rather than 256. The str.init function uses these extra characters to hold the maximum and current length values, the zero terminating byte, and any padding characters needed to align the string data on a double-word boundary.

One thing nice about the str.init function is that you don't have to worry about deallocating the storage for the string object if both the string variable (where you store the pointer) and the buffer containing the string data are automatic (VAR) variables. The procedure call and return will automatically allocate and deallocate storage for these variables. This scheme is very convenient to use.

Another way to allocate and initialize a string variable is via the tstralloc function. This function allocates storage on the stack for a string by dropping the stack point down the necessary number of bytes and initializing the maximum and current length fields of the data it allocates. This function returns a pointer to the string data it creates that you can store into an HLA string variable. Here's an example of the call to the tstralloc function:


 
procedure demoTStrAlloc;
 
var
 
	s: string;
 
begin demoTStrAlloc;
 

 
	tstralloc( 256 );
 
	mov( eax, s );
 
		.
 
		.
 
		.
 
end demoTStrAlloc;
 

 

Note that the call to tstralloc drops the stack down by as many as 12 bytes beyond the number you specify. In general, you won't know exactly how many bytes by which tstralloc will drop the stack. Therefore, it's not a good idea to call this function if you've got stuff sitting on the stack that you'll need to manipulate prior to returning from the function. Generally, most people call tstralloc immediately upon entry into a procedure (after the procedure has built the activation record), before pushing any other data onto the stack. By doing so, the procedure's exit code will automatically deallocate the string storage when it destroys the procedure's activation record. If you've got some data sitting on the stack prior to calling tstralloc, you'll probably want to save the value in the ESP register prior to calling tstralloc so you can manually deallocate the string storage later (by loading ESP with this value you've saved).

As you saw in the last chapter, the w.DrawText function is capable of rendering textual data in a window. Here's the HLA prototype for this API function:


 
static
 

 
	DrawText: procedure
 
	(
 
			hDC :dword;
 
			lpString :string;
 
			nCount :dword;
 
		var lpRect :RECT;
 
			uFormat :dword
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__DrawTextA@20" );
 

 

The hDC parameter is a device context handle (that you obtain from the w.BeginPaint, or equivalent, function). The lpString parameter is a pointer to a zero terminated sequence of characters (e.g., an HLA string) that w.DrawText is to display. The nCount parameter specifies the length of the string (in characters). If nCount contains -1, then w.DrawText will determine render all characters up to a zero terminating byte. The lpRect parameter is a pointer to a w.RECT structure. A w.RECT record contains four int32 fields: top, left, bottom, and right. These fields specify the x- and y- coordinates of the top/left hand corner of a rectangular object and the bottom/right hand corner of that object. The w.DrawText function will render the text within this rectangle. The coordinates are relative to the client area of the window associated with the device context (see Figure 6-1 concerning the client area). Although Windows supports several different coordinate systems, the default system is what you'll probably expect with coordinate (0,0) appearing in the upper left hand corner of the window with x-coordinate values increasing as you move left and y-coordinate values increasing as you move down (see Figure 6-2).

Figure 6-2: Windows' Default Coordinate System

The w.DrawText function will format the string within the rectangle the lpRect parameter specifies, including wrapping the text if necessary. The uFormat parameter is a uns32 value that specifies the type of formatting that w.DrawText is to apply to the text when rendering it. There are a few too many options to list here (see the User32 API Reference on the accompanying CD for more details), but a few of the more common formatting options should give you the flavor of what is possible with w.DrawText:

These w.DrawText uFormat values are bit values that you may combine with the HLA "|" bitwise-OR operator (as long as the combination makes sense). For example, "w.DT_SINGLELINE | w.DT_EXPANDTABS" is a perfectly legitimate value to supply for the uFormat parameter.

The w.DrawText function is very powerful, providing some very powerful formatting options. This formatting capability, however, comes at a cost. Not only is the function a tad bit slow (when it has to do all that formatting), but it is also somewhat inconvenient to use; particularly due to the fact that you must supply a bounding rectangle (which is a pain to set up prior to the call). Without question, the most popular text output routine is the w.TextOut function. Here's its prototype:


 
static
 

 
	TextOut:procedure
 
	(
 
			hDC			:dword;
 
			nXStart			:int32;
 
			nYStart			:int32;
 
			lpString			:string;
 
			cbString			:dword
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__TextOutA@20" );
 

 

As usual, the hDC is the device context handle that a function like w.BeginPaint returns to specify where w.TextOut draws the text. The nXStart and nYStart values specify where w.TextOut will begin placing the text in the window (these coordinates are relative to the client area specified by the hDC parameter). The exact nature of these coordinate value depends upon the text-alignment mode maintained by the device context. Generally, these coordinates specify the upper-left-most point of the string when drawn to the window. However, you may change this behavior by calling the w.SetTextAlign function and changing this attribute within the device context. The lpString parameter is a pointer to a sequence of characters (e.g., an HLA string). Note that you do not have to zero-terminate this string, as you must supply the length of the string (in characters) in the cbString parameter. Here's a simple call to the w.TextOut API function that display's "Hello World" near the upper left-hand corner of some window:

w.TextOut( hDC, 10, 10, "Hello World", 11 );
 

The w.TextOut function does not process any control characters. Instead, it draws them on the screen using various graphic images for each of these character codes. In particular, w.TextOut does not recognize tab characters. Windows does provide a variant of w.TextOut, w.TabbedTextOut, that will expand tab characters. Here's the prototype for this function:


 
static
 
	TabbedTextOut: 
 
		procedure
 
		(
 
					hDC :dword;
 
					X :dword;
 
					Y :dword;
 
					lpString :string;
 
					nCount :dword;
 
					nTabPositions :dword;
 
			var		lpnTabStopPositions :dword;
 
					nTabOrigin :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__TabbedTextOutA@32" );
 

 

The hDC, X, Y, lpString, and nCount parameters have the same meaning as the w.TextOut parameters (nCount is the number of characters in lpString). The nTabPositions parameter specifies the number of tab positions appearing in the lpnTabStopPositions (array) parameter. The lpnTabStopPositions parameter is a pointer to the first element of an array of 32-bit unsigned integer values. This array should contain at least nTabPositions elements. The array contains the number of pixels to skip to (relative to the value of the last parameter) for each tab stop that w.TabbedTextOut encounters in the string. For example, if nTabPositions contains 4 and the lpnTabStopPositions array contains 10, 30, 40, and 60, then the tab positions will be at pixels nTabOrigin+10, nTabOrigin+30, nTabOrigin+40, and nTabOrigin+60. As this discussion suggests, the last parameter, nTabOrigin, specifies the pixel position from the start of the string where the tab positions begin.

If the nTabPosition parameter is zero or lpnTabStopPositions pointer is NULL, then w.TabbedTextOut will create a default set of tab stops appearing at an average of eight character positions apart for the current default font. If nTabPosition is one, then w.TabbedTextOut will generate a sequence of tab stops repeating every n pixels, where n is the value held by the first (or only) element of lpnTabStopPositions. See the following listing for an example of the use of the w.TabbedTextOut function. Figure 6-3 shows the output from this application.


 
// TabbedText.hla:
 
//
 
// Demonstrates the use of the w.TabbedTextOut function.
 

 
program TabbedText;
 
#include( "stdlib.hhf" )
 
#include( "w.hhf" )     // Standard windows stuff.
 
#include( "wpa.hhf" )   // "Windows Programming in Assembly" specific stuff.
 
?@nodisplay := true;    // Disable extra code generation in each procedure.
 
?@nostackalign := true; // Stacks are always aligned, no need for extra code.
 

 
const
 
    tab :text := "#$9"; // Tab character
 

 
static
 
    hInstance:  dword;          // "Instance Handle" supplied by Windows.
 

 
    wc:     w.WNDCLASSEX;       // Our "window class" data.
 
    msg:    w.MSG;              // Windows messages go here.
 
    hwnd:   dword;              // Handle to our window.
 
    
 
    
 
readonly
 

 
    ClassName:  string := "TabbedTextWinClass";     // Window Class Name
 
    AppCaption: string := "TabbedTextOut Demo";     // Caption for Window
 
    
 
// The following data type and DATA declaration
 
// defines the message handlers for this program.
 

 
type
 
    MsgProc_t:  procedure( hwnd:dword; wParam:dword; lParam:dword );
 
    
 
    MsgProcPtr_t:
 
        record
 
            
 
            MessageValue:   dword;
 
            MessageHndlr:   MsgProc_t;
 
            
 
        endrecord;
 
    
 
    
 
    
 
// The dispatch table:
 
//
 
//  This table is where you add new messages and message handlers
 
//  to the program.  Each entry in the table must be a tMsgProcPtr
 
//  record containing two entries: the message value (a constant,
 
//  typically one of the wm.***** constants found in windows.hhf)
 
//  and a pointer to a "tMsgProc" procedure that will handle the
 
//  message.
 
 
 
readonly
 
    
 
    Dispatch:   MsgProcPtr_t; @nostorage;
 

 
        MsgProcPtr_t    
 
            MsgProcPtr_t:[ w.WM_DESTROY, &QuitApplication   ],
 
            MsgProcPtr_t:[ w.WM_PAINT,   &Paint             ],
 
            
 
            // Insert new message handler records here.
 
            
 
            MsgProcPtr_t:[ 0, NULL ];   // This marks the end of the list.
 
            
 
    
 
    
 
/**************************************************************************/
 
/*          A P P L I C A T I O N   S P E C I F I C   C O D E             */
 
/**************************************************************************/
 

 
// QuitApplication:
 
//
 
//  This procedure handles the "wm.Destroy" message.
 
//  It tells the application to terminate.  This code sends
 
//  the appropriate message to the main program's message loop
 
//  that will cause the application to terminate.
 
    
 
    
 
procedure QuitApplication( hwnd: dword; wParam:dword; lParam:dword );
 
@nodisplay;
 
begin QuitApplication;
 

 
    w.PostQuitMessage( 0 );
 

 
end QuitApplication;
 

 

 
// Paint:
 
//
 
//  This procedure handles the "wm.Paint" message.
 
//  This procedure displays several lines of text with
 
//  tab characters embedded in them.
 

 
procedure Paint( hwnd: dword; wParam:dword; lParam:dword ); @nodisplay;
 
const
 
    LinesOfText := 8;
 
    NumTabs     := 4;
 

 
var
 
    hdc:    dword;              // Handle to video display device context
 
    ps:     w.PAINTSTRUCT;      // Used while painting text.
 
    rect:   w.RECT;             // Used to invalidate client rectangle.
 

 
readonly
 
    TabStops        :dword[ NumTabs ] := [50, 100, 200, 250];
 
    TextToDisplay   :string[ LinesOfText ] := 
 
        [
 
            "Line 1:" tab "Col 1" tab "Col 2" tab "Col 3",
 
            "Line 2:" tab "1234"  tab "abcd"  tab "++",
 
            "Line 3:" tab "0"     tab "efgh"  tab "=",
 
            "Line 4:" tab "55"    tab "ijkl"  tab ".",
 
            "Line 5:" tab "1.34"  tab "mnop"  tab ",",
 
            "Line 6:" tab "-23"   tab "qrs"   tab "[]",
 
            "Line 7:" tab "+32"   tab "tuv"   tab "()",
 
            "Line 8:" tab "54321" tab "wxyz"  tab "{}"
 

 
        ];
 

 
    
 
begin Paint;
 

 
    push( ebx );
 

 
    // When Windows requests that we draw the window,
 
    // fill in the string in the center of the screen.
 
    // Note that all GDI calls (e.g., w.DrawText) must
 
    // appear within a BeginPaint..EndPaint pair.
 
    
 
    w.BeginPaint( hwnd, ps );
 
    mov( eax, hdc );
 

 
    for( mov( 0, ebx ); ebx < LinesOfText; inc( ebx )) do
 

 
        intmul( 20, ebx, ecx );
 
        add( 10, ecx );
 
        w.TabbedTextOut
 
        ( 
 
            hdc, 
 
            10, 
 
            ecx, 
 
            TextToDisplay[ ebx*4 ], 
 
            str.length( TextToDisplay[ ebx*4 ] ),
 
            NumTabs,
 
            TabStops,
 
            0
 
        );
 

 
    endfor;
 

 
        
 
    w.EndPaint( hwnd, ps );
 
    pop( ebx );
 

 
end Paint;
 

 
/**************************************************************************/
 
/*                   End of Application Specific Code                     */
 
/**************************************************************************/
 

 

 

 

 
// The window procedure.  Since this gets called directly from
 
// windows we need to explicitly reverse the parameters (compared
 
// to the standard STDCALL declaration) in order to make HLA's
 
// Pascal calling convention compatible with Windows.
 
//
 
// This is actually a function that returns a return result in
 
// EAX.  If this function returns zero in EAX, then the event
 
// loop terminates program execution.
 

 
procedure WndProc( hwnd:dword; uMsg:uns32; wParam:dword; lParam:dword  );
 
    @stdcall;
 
    @nodisplay;
 
    @noalignstack;
 
    
 
begin WndProc;
 

 
    // uMsg contains the current message Windows is passing along to
 
    // us.  Scan through the "Dispatch" table searching for a handler
 
    // for this message.  If we find one, then call the associated
 
    // handler procedure.  If we don't have a specific handler for this
 
    // message, then call the default window procedure handler function.
 
        
 
    mov( uMsg, eax );
 
    mov( &Dispatch, edx );
 
    forever
 
    
 
        mov( (type MsgProcPtr_t [edx]).MessageHndlr, ecx );
 
        if( ecx = 0 ) then
 
        
 
            // If an unhandled message comes along,
 
            // let the default window handler process the
 
            // message.  Whatever (non-zero) value this function
 
            // returns is the return result passed on to the
 
            // event loop.
 
            
 
            w.DefWindowProc( hwnd, uMsg, wParam, lParam );
 
            exit WndProc;
 
            
 
        
 
        elseif( eax = (type MsgProcPtr_t [edx]).MessageValue ) then
 
        
 
            // If the current message matches one of the values
 
            // in the message dispatch table, then call the
 
            // appropriate routine.  Note that the routine address
 
            // is still in ECX from the test above.
 
            
 
            push( hwnd );   // (type tMsgProc ecx)(hwnd, wParam, lParam)
 
            push( wParam ); //  This calls the associated routine after
 
            push( lParam ); //  pushing the necessary parameters.
 
            call( ecx );
 
            
 
            sub( eax, eax ); // Return value for function is zero.
 
            break;
 
        
 
        endif;
 
        add( @size( MsgProcPtr_t ), edx );
 
        
 
    endfor;
 
    
 
end WndProc;
 

 

 

 
// Here's the main program for the application.
 
    
 
begin TabbedText;
 

 

 
    // Set up the window class (wc) object:
 
    
 
    mov( @size( w.WNDCLASSEX ), wc.cbSize );
 
    mov( w.CS_HREDRAW | w.CS_VREDRAW, wc.style );
 
    mov( &WndProc, wc.lpfnWndProc );
 
    mov( NULL, wc.cbClsExtra );
 
    mov( NULL, wc.cbWndExtra );
 
    mov( w.COLOR_WINDOW+1, wc.hbrBackground );
 
    mov( NULL, wc.lpszMenuName );
 
    mov( ClassName, wc.lpszClassName );
 
    
 
    // Get this process' handle:
 
    
 
    w.GetModuleHandle( NULL );
 
    mov( eax, hInstance );
 
    mov( eax, wc.hInstance );
 

 
    // Get the icons and cursor for this application:
 
    
 
    w.LoadIcon( NULL, val w.IDI_APPLICATION );
 
    mov( eax, wc.hIcon );
 
    mov( eax, wc.hIconSm );
 
    
 
    w.LoadCursor( NULL, val w.IDC_ARROW );
 
    mov( eax, wc.hCursor );
 
    
 
    // Okay, register this window with Windows so it
 
    // will start passing messages our way.  Once this
 
    // is accomplished, create the window and display it.
 
    
 
    w.RegisterClassEx( wc );
 

 
    w.CreateWindowEx
 
    ( 
 
        NULL, 
 
        ClassName, 
 
        AppCaption, 
 
        w.WS_OVERLAPPEDWINDOW,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        NULL,
 
        NULL,
 
        hInstance,
 
        NULL
 
    );
 
    mov( eax, hwnd );
 
    
 
    w.ShowWindow( hwnd, w.SW_SHOWNORMAL );
 
    w.UpdateWindow( hwnd );
 
    
 
    // Here's the event loop that processes messages
 
    // sent to our window.  On return from GetMessage,
 
    // break if EAX contains false and then quit the
 
    // program.
 
    
 
    forever
 
    
 
        w.GetMessage( msg, NULL, 0, 0 );
 
        breakif( !eax );
 
        w.TranslateMessage( msg );
 
        w.DispatchMessage( msg );
 
        
 
    endfor;
 

 
    // The message handling inside Windows has stored
 
    // the program's return code in the wParam field
 
    // of the message.  Extract this and return it
 
    // as the program's return code.
 
    
 
    mov( msg.wParam, eax );
 
    w.ExitProcess( eax );   
 

 
end TabbedText;
 

 

Figure 6-3: TabbedText.HLA Output

The Microsoft Windows API also provides an extended version of the w.TextOut function, appropriately named w.ExtTextOut. This function uses a couple of additional parameters to specify some additional formatting options. Here's the prototype for the w.ExtTextOut function:


 
static
 
	ExtTextOut: procedure
 
	(
 
				hdc				:dword;
 
				x				:dword;
 
				y				:dword;
 
				fuOptions				:dword;
 
		var		lprc				:RECT;
 
				lpString				:string;
 
				cbCount				:dword;
 
		var		lpDx				:var
 
	);
 
	@stdcall;
 
	@returns( "eax" );
 
	@external( "__imp__ExtTextOutA@32" );
 

 

The hdc, x, y, lpString, and cbCount parameters are compatible to the parameters you supply to the w.TextOut function. The fuOptions parameter specifies how to use the application-defined lprc rectangle. The possible options include clipping the text to the rectangle, specifying an opaque background using the rectangle, and certain non-western alphabet options. Please consult the GDI32 documentation on the accompanying CD-ROM for more details concerning the fuOptions and lprc parameters. The lpDx array is option. If you pass NULL as this parameter's value, then the w.ExtTextOut function will use the default character spacing for each of the characters that it draws to the window. If this pointer is non-NULL, then it must point to an array of uns32 values containing the same number of elements as there are characters in the string. These array elements specify the spacing between the characters in the string. This feature allows you to condense or expand the characters' spacing when the w.ExtTextOut function draws those characters to the window. This feature is quite useful for word processors and desktop publishing systems that need to take detailed control over the output of characters on the output medium. This book will tend to favor the w.TextOut function over the w.ExtTextOut function, because the former is easier to use. For more details concerning the w.ExtTextOut function, please see the CD-ROM accompanying this book.

As noted in the previous section, the device context maintains certain default values so that you do not have to specify these values on each and every call to various GDI functions. One important attribute for text output is the text align attribute. This attribute specifies how functions like w.TextOut and w.ExtTextOut interpret the coordinate values you pass as parameters. The text align attributes (kept as a bitmap) are the following:

These values are all bits in a bit mask and you may combine various options that make sense (e.g., w.TA_BOTTOM and w.TA_TOP are mutually exclusive). The default values are "w.TA_TOP | w.TA_LEFT | w.TA_NOUPDATECP". This means that unless you change the text align attribute value, the x- and y- coordinates you specify in the w.TextOut and w.ExtTextOut calls supply the top/left coordinate of the bounding rectangle where output is to commence (see Figure 6-4). For right-aligned text, you'd normally use the combination w.TA_RIGHT | w.TA_TOP | w.TA_NOUPDATECP. Other options are certainly possible, applications that allow the user to align text with other objects would normally make use of these other text alignment options.

Figure 6-4: Default Reference Point for Textual Output

The w.TA_NOUPDATECP and w.TA_UPDATECP options are fairly interesting. If the w.TA_NOUPDATECP option is set then the application must explicitly state the (x,y) position where Windows begins drawing the text on the display. However, if the w.TA_UPDATECP flag is set, then Windows ignores the X- and Y- coordinate values you supply (except on the first call) and Windows automatically updates the cursor position based on the width of the text you write to the display (and any special control characters such as carriage returns and line feeds).

You can retrieve and set the text alignment attribute via the w.GetTextAlign and w.SetTextAlign API functions. These functions have the following prototypes:


 
static
 
	GetTextAlign: 
 
		procedure
 
		(
 
			hdc :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__GetTextAlign@4" );
 

 
	SetTextAlign: 
 
		procedure
 
		(
 
			hdc :dword;
 
			fMode :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__SetTextAlign@8" );
 

 

You pass the w.GetTextAlign function a device context handle (that you obtain from w.BeginPaint) and it returns the attribute bit map value in the EAX register. You pass the w.SetTextAlign function the device context handle and the new bitmap value (in the fMode parameter). This fMode parameter can be any combination of the w.TA_XXXX constants listed earlier; you may combine these constants with the HLA bitwise-OR operator ("|"), e.g.,


 
	// The following statement restores the default text align values:
 

 
	w.SetTextAlign( hDC, w.TA_TOP | w.TA_LEFT | w.TA_NOUPDATECP );
 

 

Another set of important text attributes are the text color and background mode attributes. Windows lets you specify a foreground color (that is used to draw the actual characters), a background color (the background is a rectangular area surrounding the text) and the opacity of the background. You can manipulate these attributes using the following Win32 API functions:


 
static
 

 
	GetBkColor: 
 
		procedure
 
		(
 
			hdc :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__GetBkColor@4" );
 

 
	GetTextColor: 
 
		procedure
 
		(
 
			hdc :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__GetTextColor@4" );
 

 
	SetBkColor: 
 
		procedure
 
		(
 
			hdc :dword;
 
			crColor :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__SetBkColor@8" );
 

 
	SetTextColor: 
 
		procedure
 
		(
 
			hdc :dword;
 
			crColor :COLORREF
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__SetTextColor@8" );
 

 
	SetBkMode: 
 
		procedure
 
		(
 
			hdc :dword;
 
			iBkMode :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__SetBkMode@8" );
 

 
	GetBkMode: 
 
		procedure
 
		(
 
			hdc :dword
 
		);
 
		@stdcall;
 
		@returns( "eax" );
 
		@external( "__imp__GetBkMode@4" );
 

 

As is typical for most GDI (Windows' Graphic Device Interface) functions, these functions all take a parameter that is the handle for the current device context (hdc); as such, you must only call these functions between a w.BeginPaint and w.EndPaint sequence (or w.GetDC/w.GetWindowDC and w.ReleaseDC sequence). The w.GetXXXX functions return a color or mode value in EAX. The w.SetXXXX functions all take a second dword parameter that specifies the new color or mode attribute.

The `color' functions (w.SetTextColor, w.SetBkColor, w.GetTextColor, and w.GetBkColor) work with 24-bit "RGB" (red-green-blue) values. An RGB value consists of three eight-bit values specifying shades for red, green, and blue that Windows will use to approximate your desired color on the output device. The blue value appears in the low-order eight bits, the green value appears in bits 8-15 of the 24-bit value, and the red value appears in bits 16-23. The w.GetXXXX functions return an RGB value in the EAX register. The w.SetXXXX functions pass the 24-bit RGB value as a double word parameter (the high-order eight bits of the double word should contain zero). The wpa.hhf header file contains a macro (RGB) that accepts three eight-bit constants and merges them together to produce a 24-bit RGB value. You can use this macro in the w.SetTextColor and w.SetBkColor function calls thusly:


 
	w.SetTextColor( hdc, RGB( redValue, greenValue, blueValue ));
 
	w.SetBkColor( hdc, RGB( $FF, 0, 0 ));
 

 

It is important to remember that RGB is a macro that only accepts constants; you cannot supply variables, registers, or other non-constant values to this macro. It's easy enough to write a function to which you could pass three arbitrary eight-bit values, but merging red-green-blue values into a 24-bit is sufficiently trivial that it's best to simply do this operation in-line, e.g.,


 
	mov( blue, ah );
 
	mov( 0, al );
 
	bswap( eax );
 
	mov( red, al );
 
	mov( green, ah ); // RGB value is now in EAX.
 

 

The w.SetTextColor API function sets the color that Windows uses when drawing text to the device context. Note that not all devices support a 24-bit color space, Windows will approximate the color (using dithering or other techniques) if the device does not support the actual color you specify.

The w.SetBkColor function sets the background that Windows draws when rendering text to the device. Windows draws this background color to a rectangle immediately surrounding the output text. Obviously, there should be a fair amount of contrast between the background color you select and the foreground (text) color or the text will be difficult to read.

Windows always draws a solid color (or a dithering approximation), never a pattern as the background color for text. By default, Windows always draws the background color (rectangle) prior to rendering the text on the device. However, you can tell Windows to draw only the text without the background color by setting the background mode to "transparent". This is achieved via the call to w.SetBkMode and passing a device context handle and either the constant w.TRANSPARENT (to stop drawing the background rectangle) or w.OPAQUE (to start drawing the background rectangle behind the text).

The following listing demonstrates the use of the w.SetTextColor, w.SetBkColor, and w.SetBkMode functions to draw text using various shades of gray. This also demonstrates the w.OPAQUE and w.TRANSPARENT drawing modes by overlaying some text on the display. The output of this application appears in Figure 6-5.


 
// TextAttr.hla:
 
//
 
// Displays text with various colors and attributes.
 

 
program TextATtr;
 
#include( "w.hhf" )     // Standard windows stuff.
 
#include( "wpa.hhf" )   // "Windows Programming in Assembly" specific stuff.
 
?@nodisplay := true;    // Disable extra code generation in each procedure.
 
?@nostackalign := true; // Stacks are always aligned, no need for extra code.
 

 
static
 
    hInstance:  dword;          // "Instance Handle" supplied by Windows.
 

 
    wc:     w.WNDCLASSEX;       // Our "window class" data.
 
    msg:    w.MSG;              // Windows messages go here.
 
    hwnd:   dword;              // Handle to our window.
 
    
 
    
 
readonly
 

 
    ClassName:  string := "TextAttrWinClass";       // Window Class Name
 
    AppCaption: string := "Text Attributes";        // Caption for Window
 
    
 
// The following data type and DATA declaration
 
// defines the message handlers for this program.
 

 
type
 
    MsgProc_t:  procedure( hwnd:dword; wParam:dword; lParam:dword );
 
    
 
    MsgProcPtr_t:
 
        record
 
            
 
            MessageValue:   dword;
 
            MessageHndlr:   MsgProc_t;
 
            
 
        endrecord;
 
    
 
    
 
    
 
// The dispatch table:
 
//
 
//  This table is where you add new messages and message handlers
 
//  to the program.  Each entry in the table must be a tMsgProcPtr
 
//  record containing two entries: the message value (a constant,
 
//  typically one of the wm.***** constants found in windows.hhf)
 
//  and a pointer to a "tMsgProc" procedure that will handle the
 
//  message.
 
 
 
readonly
 
    
 
    Dispatch:   MsgProcPtr_t; @nostorage;
 

 
        MsgProcPtr_t    
 
            MsgProcPtr_t:[ w.WM_DESTROY, &QuitApplication   ],
 
            MsgProcPtr_t:[ w.WM_PAINT,   &Paint             ],
 
            
 
            // Insert new message handler records here.
 
            
 
            MsgProcPtr_t:[ 0, NULL ];   // This marks the end of the list.
 
            
 
    
 
    
 
/**************************************************************************/
 
/*          A P P L I C A T I O N   S P E C I F I C   C O D E             */
 
/**************************************************************************/
 

 
// QuitApplication:
 
//
 
//  This procedure handles the "wm.Destroy" message.
 
//  It tells the application to terminate.  This code sends
 
//  the appropriate message to the main program's message loop
 
//  that will cause the application to terminate.
 
    
 
    
 
procedure QuitApplication( hwnd: dword; wParam:dword; lParam:dword );
 
@nodisplay;
 
begin QuitApplication;
 

 
    w.PostQuitMessage( 0 );
 

 
end QuitApplication;
 

 

 
// Paint:
 
//
 
//  This procedure handles the "wm.Paint" message.
 
//  This procedure displays several lines of text with
 
//  different colors.
 

 
procedure Paint( hwnd: dword; wParam:dword; lParam:dword ); @nodisplay;
 
const
 
    LinesOfText := 8;
 
    
 
    TextToDisplay :string := "Text in shades of gray";
 
    OverlaidText  :string := "Overlaid Text";
 

 
    fgShades :w.RGBTRIPLE[ LinesOfText ] :=
 
        [
 
            w.RGBTRIPLE:[ 0, 0, 0],
 
            w.RGBTRIPLE:[ $10, $10, $10 ],
 
            w.RGBTRIPLE:[ $20, $20, $20 ],
 
            w.RGBTRIPLE:[ $30, $30, $30 ],
 
            w.RGBTRIPLE:[ $40, $40, $40 ],
 
            w.RGBTRIPLE:[ $50, $50, $50 ],
 
            w.RGBTRIPLE:[ $60, $60, $60 ],
 
            w.RGBTRIPLE:[ $70, $70, $70 ]
 
        ];
 

 
    bgShades : w.RGBTRIPLE[ LinesOfText ] :=
 
        [
 
            w.RGBTRIPLE:[ $F0, $F0, $F0 ],
 
            w.RGBTRIPLE:[ $E0, $E0, $E0 ],
 
            w.RGBTRIPLE:[ $D0, $D0, $D0 ],
 
            w.RGBTRIPLE:[ $C0, $C0, $C0 ],
 
            w.RGBTRIPLE:[ $B0, $B0, $B0 ],
 
            w.RGBTRIPLE:[ $A0, $A0, $A0 ],
 
            w.RGBTRIPLE:[ $90, $90, $90 ],
 
            w.RGBTRIPLE:[ $80, $80, $80 ]
 
        ];
 

 

 
var
 
    hdc:    dword;              // Handle to video display device context
 
    ps:     w.PAINTSTRUCT;      // Used while painting text.
 
    rect:   w.RECT;             // Used to invalidate client rectangle.
 
    
 
begin Paint;
 

 
    // When Windows requests that we draw the window,
 
    // fill in the string in the center of the screen.
 
    // Note that all GDI calls (e.g., w.DrawText) must
 
    // appear within a BeginPaint..EndPaint pair.
 
    
 
    w.BeginPaint( hwnd, ps );
 
    mov( eax, hdc );
 

 
    w.SetBkMode( hdc, w.OPAQUE );
 
    #for( i := 0 to LinesOfText-1 )
 

 
        w.SetTextColor
 
        ( 
 
            hdc, 
 
            RGB
 
            ( 
 
                fgShades[i].rgbtRed, 
 
                fgShades[i].rgbtGreen, 
 
                fgShades[i].rgbtBlue
 
            )
 
        );
 

 
        w.SetBkColor
 
        ( 
 
            hdc, 
 
            RGB
 
            ( 
 
                bgShades[i].rgbtRed, 
 
                bgShades[i].rgbtGreen, 
 
                bgShades[i].rgbtBlue
 
            )
 
        );
 
        w.TextOut( hdc, 10, i*20+10, TextToDisplay, @length( TextToDisplay ));
 

 
    #endfor
 

 
    w.SetBkMode( hdc, w.TRANSPARENT );
 
    #for( i := 0 to LinesOfText-1 )
 

 
        w.SetTextColor
 
        ( 
 
            hdc, 
 
            RGB
 
            ( 
 
                fgShades[i].rgbtRed, 
 
                fgShades[i].rgbtGreen, 
 
                fgShades[i].rgbtBlue
 
            )
 
        );
 
        w.TextOut( hdc, 100, i*20+20, TextToDisplay, @length( TextToDisplay ));
 

 
    #endfor
 

 

 
        
 
    w.EndPaint( hwnd, ps );
 

 
end Paint;
 

 
/**************************************************************************/
 
/*                   End of Application Specific Code                     */
 
/**************************************************************************/
 

 

 

 

 
// The window procedure.  Since this gets called directly from
 
// windows we need to explicitly reverse the parameters (compared
 
// to the standard STDCALL declaration) in order to make HLA's
 
// Pascal calling convention compatible with Windows.
 
//
 
// This is actually a function that returns a return result in
 
// EAX.  If this function returns zero in EAX, then the event
 
// loop terminates program execution.
 

 
procedure WndProc( hwnd:dword; uMsg:uns32; wParam:dword; lParam:dword  );
 
    @stdcall;
 
    @nodisplay;
 
    @noalignstack;
 
    
 
begin WndProc;
 

 
    // uMsg contains the current message Windows is passing along to
 
    // us.  Scan through the "Dispatch" table searching for a handler
 
    // for this message.  If we find one, then call the associated
 
    // handler procedure.  If we don't have a specific handler for this
 
    // message, then call the default window procedure handler function.
 
        
 
    mov( uMsg, eax );
 
    mov( &Dispatch, edx );
 
    forever
 
    
 
        mov( (type MsgProcPtr_t [edx]).MessageHndlr, ecx );
 
        if( ecx = 0 ) then
 
        
 
            // If an unhandled message comes along,
 
            // let the default window handler process the
 
            // message.  Whatever (non-zero) value this function
 
            // returns is the return result passed on to the
 
            // event loop.
 
            
 
            w.DefWindowProc( hwnd, uMsg, wParam, lParam );
 
            exit WndProc;
 
            
 
        
 
        elseif( eax = (type MsgProcPtr_t [edx]).MessageValue ) then
 
        
 
            // If the current message matches one of the values
 
            // in the message dispatch table, then call the
 
            // appropriate routine.  Note that the routine address
 
            // is still in ECX from the test above.
 
            
 
            push( hwnd );   // (type tMsgProc ecx)(hwnd, wParam, lParam)
 
            push( wParam ); //  This calls the associated routine after
 
            push( lParam ); //  pushing the necessary parameters.
 
            call( ecx );
 
            
 
            sub( eax, eax ); // Return value for function is zero.
 
            break;
 
        
 
        endif;
 
        add( @size( MsgProcPtr_t ), edx );
 
        
 
    endfor;
 
    
 
end WndProc;
 

 

 

 
// Here's the main program for the application.
 
    
 
begin TextATtr;
 

 

 
    // Set up the window class (wc) object:
 
    
 
    mov( @size( w.WNDCLASSEX ), wc.cbSize );
 
    mov( w.CS_HREDRAW | w.CS_VREDRAW, wc.style );
 
    mov( &WndProc, wc.lpfnWndProc );
 
    mov( NULL, wc.cbClsExtra );
 
    mov( NULL, wc.cbWndExtra );
 
    mov( w.COLOR_WINDOW+1, wc.hbrBackground );
 
    mov( NULL, wc.lpszMenuName );
 
    mov( ClassName, wc.lpszClassName );
 
    
 
    // Get this process' handle:
 
    
 
    w.GetModuleHandle( NULL );
 
    mov( eax, hInstance );
 
    mov( eax, wc.hInstance );
 

 
    // Get the icons and cursor for this application:
 
    
 
    w.LoadIcon( NULL, val w.IDI_APPLICATION );
 
    mov( eax, wc.hIcon );
 
    mov( eax, wc.hIconSm );
 
    
 
    w.LoadCursor( NULL, val w.IDC_ARROW );
 
    mov( eax, wc.hCursor );
 
    
 
    // Okay, register this window with Windows so it
 
    // will start passing messages our way.  Once this
 
    // is accomplished, create the window and display it.
 
    
 
    w.RegisterClassEx( wc );
 

 
    w.CreateWindowEx
 
    ( 
 
        NULL, 
 
        ClassName, 
 
        AppCaption, 
 
        w.WS_OVERLAPPEDWINDOW,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        w.CW_USEDEFAULT,
 
        NULL,
 
        NULL,
 
        hInstance,
 
        NULL
 
    );
 
    mov( eax, hwnd );
 
    
 
    w.ShowWindow( hwnd, w.SW_SHOWNORMAL );
 
    w.UpdateWindow( hwnd );
 
    
 
    // Here's the event loop that processes messages
 
    // sent to our window.  On return from GetMessage,
 
    // break if EAX contains false and then quit the
 
    // program.
 
    
 
    forever
 
    
 
        w.GetMessage( msg, NULL, 0, 0 );
 
        breakif( !eax );
 
        w.TranslateMessage( msg );
 
        w.DispatchMessage( msg );
 
        
 
    endfor;
 

 
    // The message handling inside Windows has stored
 
    // the program's return code in the wParam field
 
    // of the message.  Extract this and return it
 
    // as the program's return code.
 
    
 
    mov( msg.wParam, eax );
 
    w.ExitProcess( eax );   
 

 
end TextATtr;
 

 

Figure 6-5: Output From TextAttr Application

6.2.4: BeginPaint, EndPaint, GetDC, GetWindowDC, and ReleaseDC Macros

One minor issue concerns the use of GDI and Windows' User Interface API functions like w.TextOut - they are only legal between calls to functions like w.BeginPaint and w.EndPaint that obtain and release a device context. Furthermore, if an application calls a function like w.BeginPaint, it mu