Porting Graphical Apps to Windows CE, Part 2

Monday Feb 10th 2003 by Nancy Nicolaisen

Explore the simplest graphic operation — drawing lines. Do this by building a simple WinCE Etch-A-Sketch program.

Drawing Lines

In the Porting Graphical Apps to Windows CE, Part 1, you learned a quick method for finding unsupported APIs. Recall that in the build output of the example program Unresolved, MoveToEx() and LineTo() turned out to be two such calls. Now let's look more deeply into other limitations that have to do with graphical output. The simplest graphic operation is drawing lines, so we'll explore that first.

Below is the code for an example program called EtchASketch. (The resource file is omitted for the sake of brevity. ) EtchASketch is a line drawing program that functions in more or less the same way as the toy. Each time you choose one of the file menu items, a unit line segment is drawn in that direction.

Figure 3 - The EtchASketch Program

Click here for larger image

Dissecting the EtchASketch Example

As you saw in the last installment, two of the casualties of Win CE API shrinkage are MoveToEx() and LineTo(). There is a single line drawing function now, Polyline(). Here is the declaration of Polyline:

BOOL Polyline(HDC hdc, const POINT * lppt, int cPoints); 

The parameters, in the order shown, are the handle to the display context that receives the lines, a long pointer to an array of POINT structures, and a count of the number of points. The line segments are drawn sequentially, starting at the first point in this array. CE doesn't use the concept of a "current point", so you may need to modify drawing data as well as code when you move from Win 32 to Win CE.

Porting Tip: Windows CE doesn't keep track of the "current point" when drawing lines, so the array of points passed to Polyline() must explicitly include the first point of the first line segment.

Here is the code that initializes the point structures for line drawing in our InitInstance() function. We also set the length of the line segment here.

//initialize the starting point of the 
//ptLineSeg array to the center pt of the screen
ptLineSeg[0].x = ptLineSeg[1].x = 
      GetSystemMetrics(SM_CXSCREEN ) / 2;
ptLineSeg[0].y = ptLineSeg[1].y = 
      GetSystemMetrics(SM_CYSCREEN ) / 2;
//init length of the line segment we draw in response
//to menu commands
iSegLen = GetSystemMetrics(SM_CXSCREEN ) / 25;

Now lets look at what happens when the user makes a "Left" menu choice:

   bDrawingLines = TRUE;
   //set the endpoint of the line segment 
   ptLineSeg[1].x = ptLineSeg[0].x - iSegLen;
   ptLineSeg[1].y = ptLineSeg[0].y;
   //Invalidate the line seg area & 
   // send a paint message
   CopyRect( &rcInvalid, (LPRECT)&ptLineSeg );
   InflateRect( &rcInvalid, 1, 1 );
   InvalidateRect( hWnd, &rcInvalid, FALSE);
   UpdateWindow( hWnd );
   //update the starting point of the next 
   //line segment
   ptLineSeg[0].x = ptLineSeg[1].x;
   ptLineSeg[0].y = ptLineSeg[1].y;

First, we set the Boolean bDrawingLines to TRUE, and update the endpoint of the line segment so that the line is drawn from the first point in the array of structures, one segment to the left. Notice that we are doing simple arithmetic, using screen metrics information to calculate the location of the line segment's end point. Windows CE supports only the MM_TEXT mapping mode, that is, x coordinates increase to the right and y coordinates increase going down.

Porting Tip: Windows CE supports only the MM_TEXT mapping mode. To draw visible lines, use only coordinates that fall with screen or client area bounds.

The next bit of code has to do with CE's quirky erasure behavior, which I'll say more about when we look at the WM_PAINT message processing code:

//Invalidate the line seg area & 
// send a paint message
CopyRect( &rcInvalid, (LPRECT)&ptLineSeg );
InflateRect( &rcInvalid, 1, 1 );
InvalidateRect( hWnd, &rcInvalid, FALSE);
UpdateWindow( hWnd );

First we make a copy of the line segment's points, treating it as a rectangle of 0 height. We inflate the rectangle by one pixel in each direction, and we use this as a parameter to InvalidateRect(). Notice the final parameter of InvalidateRect() is FALSE, which means "don't erase anything except that rectangle we just invalidated". Next, we call UpdateWindow(), which generates a WM_PAINT message.

Finally, here is the WM_PAINT message processing code:

case WM_PAINT:
   hdc = BeginPaint(hWnd, &ps);
   if( bDrawingLines )
      //make a copy of the points
      CopyRect( &rcClientPoints, (LPRECT)&ptLineSeg );
      //convert to client area coordinates
                      (LPPOINT)&rcClientPoints, 2 );
      Polyline(hdc, (LPPOINT)&rcClientPoints, 2 );
      bDrawingLines = FALSE;
   ValidateRect( hWnd, NULL );
   EndPaint(hWnd, &ps);

We open with BeginPaint() which returns a handle to the display context and does an automatic accumulation of the invalidated regions of the client area, erasing the sum of them. Next we test to see if this paint message was generated because we are drawing a line segment. If so, first we adjust the coordinates of the points. We calculated the line segment length and location using screen coordinates, so before we draw we convert them to client area coordinates, using MapWindowPoints().

The parameters, in the order shown, are the constant HWND_DESKTOP, the handle to our main application window, the address of the array of points to convert, and a count of points in the array. The constant HWND_DESKTOP used as the first parameter signifies that we are passing screen coordinates to this function and we want them translated to the coordinate space that belongs to the window identified by the second parameter. If you reverse the order of the first two parameters, then MapWindowPoints() converts application window coordinates to screen coordinates.

Finally, we call Polyline and draw the line, update bDrawingLines, validate the rectangle in which we drew the line segment, and clean up with a call to EndPaint().

All this brings us to the point at which we can examine why there was so much fuss about valid and invalid rectangles. If you use the BeginPaint() /EndPaint() couplet to open and close WM_PAINT message processing, it's very difficult to control the erasure phase of painting.

On the desktop, the following bit of code usually brings you to the redrawing phase of paint processing with the client area of your window intact:

//Works fine on the desktop

InvalidateRect( hWnd, NULL, FALSE ); 

You can't count on this same behavior on CE, apparently because of the way invalid screen areas are accumulated. For this reason, with BeginPaint() / EndPaint()you must specifically invalidate the rectangle in which you want to draw, or the entire client area is erased.

Porting Tip: Use BeginPaint() and EndPaint() to open and close WM_PAINT processing only if you wish to redraw the entire client area in response to a paint message. To leav most of the client area unchanged and redraw only selected portions use GetDC() and ReleaseDC().

Source Code

Download: Etch.zip (3 kb)

Looking Ahead

In the next installment, we'll develop an improved version of the Etch A Sketch program which is capable of drawing solid lines.

About the Author

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, entry time data validation.

In addition to writing for Developer.com, she has written several books including Making Win 32 Applications Mobile. She has also written numerous articles on programming technology for national publications including Dr. Dobbs, BYTE Magazine, Microsoft Systems Journal, PC Magazine; Computer Shopper, Windows Sources and Databased Advisor.

# # #

Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved