Lines and Shapes
One of the areas in which Windows CE provides substantially less functionality than other versions of Windows is in the primitive line-drawing and shape-drawing functions. Gone are the Chord, Arc, and Pie functions that created complex circular shapes. Gone too are most of the functions using the concept of current point. Other than MoveToEx, LineTo, and GetCurrentPositionEx, none of the GDI functions dealing with current point are supported in Windows CE. So drawing a series of connected lines and curves using calls to ArcTo, PolyBezierTo, and so forth is no longer possible. But even with the loss of a number of graphic functions, Windows CE still provides the essential functions necessary to draw lines and shapes.
Lines
Drawing one or more lines is as simple as a call to
BOOL Polyline (HDC hdc, const POINT *lppt, int cPoints);
The second parameter is a pointer to an array of POINT structures that are defined as the following:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT;
Each x and y combination describes a pixel from the upper left corner of the screen. The third parameter is the number of point structures in the array. So to draw a line from (0, 0) to (50, 100), the code would look like this:
POINTS pts[2];
pts[0].x = 0;
pts[0].y = 0;
pts[1].x = 50;
pts[1].y = 100;
PolyLine (hdc, &pts, 2);
Another way to draw the same line would be to use the MoveToEx and LineTo functions. They are prototyped as follows:
BOOL WINAPI MoveToEx (HDC hdc, int X, int Y, LPPOINT lpPoint);
BOOL WINAPI LineTo (HDC hdc, int X, int Y);
To use the functions to draw a line, first call MoveToEx to move the current point to the starting coordinates of the line, and then call LineTo, passing the ending coordinates. The calls to draw the same line as before using these functions would be as follows:
MoveToEx (hdc, 0, 0, NULL);
LineTo (hdc, 50, 100);
To query the current point, call the following function:
WINGDIAPI BOOL WINAPI GetCurrentPositionEx (HDC hdc, LPPOINT pPoint);
Just as in the early text examples, these code fragments make a number of assumptions about the default state of the device context. For example, just what does the line drawn between (0, 0) and (50, 100) look like? What is its width and its color, and is it a solid line? All versions of Windows, including Windows CE, allow these parameters to be specified.
Pens
The tool for specifying the appearance of lines and the outline of shapes is called, appropriately enough, a pen. A pen is another GDI object and, like the others described in this chapter, is created, selected into a device context, used, deselected, and then destroyed. Among other stock GDI objects, stock pens can be retrieved using the following code:
HGDIOBJ GetStockObject (int fnObject);
All versions of Windows provide three stock pens, each 1 pixel wide. The stock pens come in 3 colors: white, black, and null. When you use GetStockObject, the call to retrieve one of those pens employs the parameters WHITE_PEN, BLACK_PEN, and NULL_PEN respectively. Unlike standard graphic objects created by applications, stock objects should never be deleted by the application. Instead, the application should simply deselect the pen from the device context when it's no longer needed.To create a custom pen under Windows, two functions are available. The first is this:
HPEN CreatePen (int fnPenStyle, int nWidth, COLORREF crColor);
The fnPenStyle parameter specifies the appearance of the line to be drawn. For example, the PS_DASH flag can be used to create a dashed line. Windows CE supports only PS_SOLID, PS_DASH, and PS_NULL style flags. The nWidth parameter specifies the width of the pen. Finally, the crColor parameter specifies the color of the pen. The crColor parameter is typed as COLORREF, which can be constructed using the RGB macro. The RGB macro is as follows:
COLORREF RGB (BYTE bRed, BYTE bGreen, BYTE bBlue);
So to create a solid red pen, the code would look like this:
hPen = CreatePen (PS_SOLID, 1, RGB (0xff, 0, 0));
The other pen creation function is the following:
HPEN CreatePenIndirect (const LOGPEN *lplgpn);
where the logical pen structure LOGPEN is defined as
typedef struct tagLOGPEN {
UINT lopnStyle;
POINT lopnWidth;
COLORREF lopnColor;
} LOGPEN;
CreatePenIndirect provides the same parameters to Windows, in a different form. To create the same 1-pixel-wide red pen with CreatePenIndirect, the code would look like this:
LOGPEN lp;
HPEN hPen;
lp.lopnStyle = PS_SOLID;
lp.lopnWidth.x = 1;
lp.lopnWidth.y = 1;
lp.lopnColor = RGB (0xff, 0, 0);
hPen = CreatePenIndirect (&lp);
Windows CE devices don't support complex pens such as wide (more than one pixel wide) dashed lines. To determine what's supported, our old friend GetDeviceCaps comes into play, taking LINECAPS as the second parameter. Refer to the Windows CE documentation for the different flags returned by this call.
Shapes
Lines are useful but Windows also provides functions to draw shapes, both filled and unfilled. Here Windows CE does a good job supporting most of the functions familiar to Windows programmers. The Rectangle, RoundRect, Ellipse, and Polygon functions are all supported.
Brushes
Before I can talk about shapes such as rectangles and ellipses, I need to describe another GDI object that I've mentioned only briefly before now, called a brush. A brush is a bitmap, typically 8 by 8 pixels, used to fill shapes. It's also used by Windows to fill the background of a client window. Windows CE provides a number of stock brushes and also the ability to create a brush from an application-defined pattern. A number of stock brushes, each a solid color, can be retrieved using GetStockObject. Among the brushes available is one for each of the grays of a four-color grayscale display: white, light gray, dark gray, and black.To create solid color brushes, the function to call is the following:
HBRUSH CreateSolidBrush (COLORREF crColor);
The crColor parameter specifies the color of the brush. The color is specified using the RGB macro.To create custom pattern brushes, Windows CE supports the Win32 function:
HBRUSH CreateDIBPatternBrushPt (const void *lpPackedDIB,
UINT iUsage);
The first parameter to this function is a pointer to a DIB in packed format. This means that the pointer points to a buffer that contains a BITMAPINFO structure immediately followed by the bits in the bitmap. Remember that a BITMAPINFO structure is actually a BITMAPINFOHEADER structure followed by a palette in RGBQUAD format, so the buffer contains everything necessary to create a DIB—that is, bitmap information, a palette, and the bits to the bitmap. If the second parameter is set to DIB_RGB_COLORS, the palette specified con tains RGBQUAD values in each entry. For 8-bits-per-pixel bitmaps, the complementary flag DIB_PAL_COLORS can be specified, but Windows CE ignores the bitmap's color table.
The CreateDIBPatternBrushPt function is more important under Windows CE because the hatched brushes, supplied under other versions of Windows by the CreateHatchBrush function, aren't supported under Windows CE. Hatched brushes are brushes composed of any combination of horizontal, vertical, or diagonal lines. Ironically, they're particularly useful with grayscale displays because you can use them to accentuate different areas of a chart with different hatch patterns. You can reproduce these brushes, however, by using CreateDIBPatternBrushPt and the proper bitmap patterns. The Shapes code example, later in the chapter, demonstrates a method for creating hatched brushes under Windows CE.By default, the brush origin will be in the upper left corner of the window. This isn't always what you want. Take, for example, a bar graph where the bar filled with a hatched brush fills a rectangle from (100, 100) to (125, 220). Since this rectangle isn't divisible by 8 (brushes typically being 8 by 8 pixels square), the upper left corner of the bar will be filled with a partial brush that might not look pleasing to the eye.To avoid this situation, you can move the origin of the brush so that each shape can be drawn with the brush aligned correctly in the corner of the shape to be filled. The function available for this remedy is the following:
BOOL SetBrushOrgEx (HDC hdc, int nXOrg, int nYOrg, LPPOINT lppt);
The nXOrg and nYOrg parameters allow the origin to be set between 0 and 7 so that you can position the origin anywhere in the 8-by-8 space of the brush. The lppt parameter is filled with the previous origin of the brush so that you can restore the previous origin if necessary.
Rectangles
The rectangle function draws either a filled or a hollow rectangle; the function is defined as the following:
BOOL Rectangle (HDC hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect);
The function uses the currently selected pen to draw the outline of the rectangle and the current brush to fill the interior. To draw a hollow rectangle, select the null brush into the device context before calling Rectangle.The actual pixels drawn for the border are important to understand. Say we're drawing a 5-by-7 rectangle at 0, 0. The function call would look like this:
Rectangle (0, 0, 5, 7);
Assuming that the selected pen was 1 pixel wide, the resulting rectangle would look like the one shown in Figure 2-6.

Figure 2-6: Magnified view of a rectangle drawn with the Rectangle function
Notice how the right edge of the rectangle is actually drawn in column 4 and that the bottom edge is drawn in row 6. This is standard Windows practice. The rectangle is drawn inside the right and bottom boundary specified for the Rectangle function. If the selected pen is wider than one pixel, the right and bottom edges are drawn with the pen centered on the bounding rectangle. (Other versions of Windows support the PS_INSIDEFRAME pen style that forces the rectangle to be drawn inside the frame regardless of the pen width.)
Circles and Ellipses
Circles and ellipses can be drawn with this function:
BOOL Ellipse (HDC hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect);
The ellipse is drawn using the rectangle passed as a bounding rectangle, as shown in Figure 2-7. As with the Rectangle function, while the interior of the ellipse is filled with the current brush, the outline is drawn with the current pen.

Figure 2-7: The ellipse is drawn within the bounding rectangle passed to the Ellipse function.
Round Rectangles
The RoundRect function
BOOL RoundRect (HDC hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect,
int nWidth, int nHeight);
draws a rectangle with rounded corners. The roundedness of the corners is defined by the last two parameters that specify the width and height of the ellipse used to round the corners, as shown in Figure 2-8. Specifying the ellipse height and width enables your program to draw identically symmetrical rounded corners. Shortening the ellipse height flattens out the sides of the rectangle, while shortening the width of the ellipse flattens the top and bottom of the rectangle.

Figure 2-8: The height and width of the ellipse define the round corners of the rectangle drawn by RoundRect.
Polygons
Finally, the Polygon function
BOOL Polygon (HDC hdc, const POINT *lpPoints, int nCount);
draws a many-sided shape. The second parameter is a pointer to an array of point structures defining the points that delineate the polygon. The resulting shape has one more side than the number of points because the function automatically completes the last line of the polygon by connecting the last point with the first.
Fill Functions
The preceding functions use a combination of a brush and a pen to draw shapes in the device context. Functions are available to fill areas without dealing with the pen that would normally outline the shape. The first of these functions is as follows:
int FillRect (HDC hDC, CONST RECT* lprc, HBRUSH hbr);
The parameters of FillRect are the handle to the device context, the rectangle to fill, and the brush to fill the rectangle. FillRect is a quick and convenient way to paint a solid color or pattern in a rectangular area.While FillRect is convenient, GradientFill is cool. GradientFill fills a rectangular area that starts on one side with one color and then has a smooth transition to another color on the other side. Figure 2-9 shows a window in which the client area is painted with GradientFill. The black-and-white illustration doesn't do the image justice, but even in this figure it's easy to see the smooth nature of the transition.

Figure 2-9: A window painted with the GradientFill function.
The prototype of GradientFill looks like this:
BOOL GradientFill (HDC hdc, PTRIVERTEX pVertex, ULONG dwNumVertex,
PVOID pMesh, ULONG dwNumMesh, ULONG dwMode);
The first parameter is the obligatory handle to the device context. The pVertex parameter points to an array of TRIVERTEX structures, while the dwNumVertex parameter contains the number of entries in the TRIVERTEX array. The TRIVERTEX structure is defined as follows:
struct _TRIVERTEX {
LONG x;
Long y;
COLOR16 Red;
COLOR16 Green;
COLOR16 Blue;
COLOR16 Alpha;s
} TRIVERTEX;
The fields of the TRIVERTEX structure describe a point in the device context and an RGB color. The points should describe the upper left and lower right corners of the rectangle being filled. The pMesh parameter of GradientFill points to a GRADIENT_RECT structure defined as follows:
struct _GRADIENT_RECT
{
ULONG UpperLeft;
ULONG LowerRight;
} GRADIENT_RECT;
The GRADIENT_RECT structure simply specifies which of the entries in the TRIVERTEX structure delineates the upper left and lower right corners. Finally, the dwNumMesh parameter of GradientFill contains the number of GRADIENT_RECT structures, while the dwMode structure contains a flag indicating whether the fill should be left to right (GRADIENT_FILL_RECT_H) or top to bottom (GRADIENT_FILL_RECT_V). The GradientFill function is more complex than is apparent because on the desktop, it can also perform a triangular fill that isn't supported by Windows CE. Here's the code fragment that created the window in Figure 2-9:
TRIVERTEX vert[2];
GRADIENT_RECT gRect;
vert [0] .x = prect->left;
vert [0] .y = prect->top;
vert [0] .Red = 0x0000;
vert [0] .Green = 0x0000;
vert [0] .Blue = 0xff00;
vert [0] .Alpha = 0x0000;
vert [1] .x = prect->right;
vert [1] .y = prect->bottom;
vert [1] .Red = 0x0000;
vert [1] .Green = 0xff00;
vert [1] .Blue = 0x0000;
vert [1] .Alpha = 0x0000;
gRect.UpperLeft = 0;
gRect.LowerRight = 1;
GradientFill(hdc,vert,2,&gRect,1,GRADIENT_FILL_RECT_H);
The Shapes Example Program
The Shapes program, shown in Listing 2-3, demonstrates a number of these functions. In Shapes, four figures are drawn, each filled with a different brush.
Listing 2-3: The Shapes program
Shapes.h
//================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Defines used by MyCreateHatchBrush
//
typedef struct {
BITMAPINFOHEADER bmi;
COLORREF dwPal[2];
BYTE bBits[64];
} BRUSHBMP;
#define HS_HORIZONTAL 0 /* ----- */
#define HS_VERTICAL 1 /* ||||| */
#define HS_FDIAGONAL 2 /* \\\\\ */
#define HS_BDIAGONAL 3 /* ///// */
#define HS_CROSS 4 /* +++++ */
#define HS_DIAGCROSS 5 /* xxxxx */
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
Shapes.cpp
//======================================================================
// Shapes- Brush and shapes demo for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include "shapes.h" // Program-specific stuff
//----------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("Shapes");
HINSTANCE hInst; // Program instance handle
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_PAINT, DoPaintMain,
WM_DESTROY, DoDestroyMain,
};
//======================================================================
//
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
MSG msg;
HWND hwndMain;
// Initialize this instance.
hwndMain = InitInstance(hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
WNDCLASS wc;
HWND hWnd;
// Save program instance handle in global variable.
hInst = hInstance;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application.
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
#endif
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = 0; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Create main window.
hWnd = CreateWindowEx (WS_EX_NODRAG, // Ex Style
szAppName, // Window class
TEXT("Shapes"), // Window title
WS_VISIBLE, // Style flags
CW_USEDEFAULT, // x position
CW_USEDEFAULT, // y position
CW_USEDEFAULT, // Initial width
CW_USEDEFAULT, // Initial height
NULL, // Parent
NULL, // Menu, must be null
hInstance, // Application instance
NULL); // Pointer to create
// parameters
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------
// MyCreateHatchBrush - Creates hatched brushes
//
HBRUSH MyCreateHatchBrush (INT fnStyle, COLORREF clrref) {
BRUSHBMP brbmp;
BYTE *pBytes;
int i;
DWORD dwBits[6][2] = {
{0x000000ff,0x00000000}, {0x10101010,0x10101010},
{0x01020408,0x10204080}, {0x80402010,0x08040201},
{0x101010ff,0x10101010}, {0x81422418,0x18244281},
};
if ((fnStyle < 0) || (fnStyle > dim(dwBits)))
return 0;
memset (&brbmp, 0, sizeof (brbmp));
brbmp.bmi.biSize = sizeof (BITMAPINFOHEADER);
brbmp.bmi.biWidth = 8;
brbmp.bmi.biHeight = 8;
brbmp.bmi.biPlanes = 1;
brbmp.bmi.biBitCount = 1;
brbmp.bmi.biClrUsed = 2;
brbmp.bmi.biClrImportant = 2;
// Initialize the palette of the bitmap.
brbmp.dwPal[0] = PALETTERGB(0xff,0xff,0xff);
brbmp.dwPal[1] = PALETTERGB((BYTE)((clrref >> 16) & 0xff),
(BYTE)((clrref >> 8) & 0xff),
(BYTE)(clrref & 0xff));
// Write the hatch data to the bitmap.
pBytes = (BYTE *)&dwBits[fnStyle];
for (i = 0; i < 8; i++)
brbmp.bBits[i*4] = *pBytes++;
// Return the handle of the brush created.
return CreateDIBPatternBrushPt (&brbmp, DIB_RGB_COLORS);
}
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PAINTSTRUCT ps;
RECT rect;
HDC hdc;
POINT ptArray[6];
HBRUSH hBr, hOldBr;
TCHAR szText[128];
GetClientRect (hWnd, &rect);
hdc = BeginPaint (hWnd, &ps);
// Draw ellipse.
hBr = (HBRUSH) GetStockObject (DKGRAY_BRUSH);
hOldBr = (HBRUSH) SelectObject (hdc, hBr);
Ellipse (hdc, 10, 50, 90, 130);
SelectObject (hdc, hOldBr);
// Draw round rectangle.
hBr = (HBRUSH) GetStockObject (LTGRAY_BRUSH);
hOldBr = (HBRUSH) SelectObject (hdc, hBr);
RoundRect (hdc, 95, 50, 150, 130, 30, 30);
SelectObject (hdc, hOldBr);
// Draw hexagon using Polygon.
hBr = (HBRUSH) GetStockObject (WHITE_BRUSH);
hOldBr = (HBRUSH) SelectObject (hdc, hBr);
ptArray[0].x = 192;
ptArray[0].y = 50;
ptArray[1].x = 155;
ptArray[1].y = 75;
ptArray[2].x = 155;
ptArray[2].y = 105;
ptArray[3].x = 192;
ptArray[3].y = 130;
ptArray[4].x = 230;
ptArray[4].y = 105;
ptArray[5].x = 230;
ptArray[5].y = 75;
Polygon (hdc, ptArray, 6);
SelectObject (hdc, hOldBr);
hBr = (HBRUSH) MyCreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0));
hOldBr = (HBRUSH) SelectObject (hdc, hBr);
Rectangle (hdc, 10, 145, 225, 210);
SelectObject (hdc, hOldBr);
DeleteObject (hBr);
SetBkMode (hdc, OPAQUE);
lstrcpy (szText, TEXT ("Opaque background"));
ExtTextOut (hdc, 20, 160, 0, NULL,
szText, lstrlen (szText), NULL);
SetBkMode (hdc, TRANSPARENT);
lstrcpy (szText, TEXT ("Transparent background"));
ExtTextOut (hdc, 20, 185, 0, NULL,
szText, lstrlen (szText), NULL);
EndPaint (hWnd, &ps);
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
In Shapes, OnPaintMain draws the four figures using the different functions discussed earlier. For each of the shapes, a different brush is created, selected into the device context, and, after the shape has been drawn, deselected from the DC. The first three shapes are filled with solid grayscale shades. These solid brushes are loaded with the GetStockObject function. The final shape is filled with a brush created with the CreateDIBPatternBrushPt. The creation of this brush is segregated into a function called MyCreateHatchBrush that mimics the CreateHatchBrush function not available under Windows CE. To create the hatched brushes, a black-and-white bitmap is built by filling in a bitmap structure and setting the bits to form the hatch patterns. The bitmap itself is the 8-by-8 bitmap specified by CreateDIBPatternBrushPt. Since the bitmap is monochrome, its total size, including the palette and header, is only around 100 bytes. Notice, however, that since each scan line of a bitmap must be double-word aligned, the last three bytes of each one-byte scan line are left unused.Finally the program completes the painting by writing two lines of text into the lower rectangle. The text further demonstrates the difference between the opaque and transparent drawing modes of the system. In this case, the opaque mode of drawing the text might be a better match for the situation because the hatched lines tend to obscure letters drawn in transparent mode. A view of the Shapes window is shown in Figure 2-10.

Figure 2-10: The Shapes example demonstrates drawing different filled shapes.
To keep things simple, the Shapes example assumes that it's running on at least a 240-pixel-wide display. This allows Shapes to work equally well on a Handheld PC and a Pocket PC. I have barely scratched the surface of the abilities of the Windows CE GDI portion of GWE. The goal of this chapter wasn't to provide total presentation of all aspects of GDI programming. Instead, I wanted to demonstrate the methods available for basic drawing and text support under Windows CE. In other chapters in the book, I extend some of the techniques touched on in this chapter. I talk about these new techniques and newly introduced functions at the point, generally, where I demonstrate how to use them in code. To further your knowledge, I recommend Programming Windows, 5th edition, by Charles Petzold (Microsoft Press, 1998), as the best source for learning about the Windows GDI.Now that we've looked at output, it's time to turn our attention to the input side of the system—the keyboard and the touch panel.