Step by Step: Developing Orientation-Aware and Resolution-Aware Windows Mobile-based Applications in Native Code

 

Microsoft Corporation

December 2005

Applies to:
   Microsoft .NET Compact Framework version 1.0
   Microsoft Visual Studio 2005
   Windows Mobile version 5.0 software for Pocket PCs
   Windows Mobile version 5.0 software for Smartphones

Summary: Convert an existing Windows Mobile–based application to be aware of multiple screen orientations, sizes, and resolutions in this self-paced hands-on lab. You will alter the application so that it is aware of a change in screen orientation and adjusts for the presence of portrait, landscape, and square screens. You will then convert the same application so that it effectively adjusts for devices with higher screen resolution. Upon completion of this lab, you will know how to ensure that your existing Windows Mobile–based applications behave properly across the increasing number of devices with multiple screen orientations, sizes, and resolutions. (36 printed pages)

Download the MED305_Dev_Orientation_Res_Aware_Apps_Native.msi from the Microsoft Download Center.

Download the printable version of this hands on lab MED305_MSDN_Dev_Orientation_Res_Aware_Apps_Native.doc from the Microsoft Download Center.

Contents

Introduction
Exercise 1: Fixing Orientation-Awareness Problems
Exercise 2: Fixing DPI-Awareness Problems
Exercise 3: Adding Support for Square Screen
Appendix A: Terminating an Application Running on a Device or Emulator
Conclusion

To complete this exercise, you will need:

  • Windows XP Professional.
    This lab requires Windows XP Professional.

  • Visual Studio 2005.
    This lab requires Visual Studio 2005 Standard, Professional, or Team System Editions. It will not work with any of the Express Editions. If you do not have the correct edition of Visual Studio 2005, find out how you can acquire it from the Visual Studio 2005 Developer Center.

  • ActiveSync 4.0.
    ActiveSync 4.0 allows for connectivity between a Windows Mobile–based device and your computer.

  • Windows Mobile 5.0 SDKs.
    The Windows Mobile 5.0 SDKs for Pocket PC and Smartphone enable development for Windows Mobile–based devices in Visual Studio 2005:

  • Download and install Windows Mobile 5.0 SDK for Pocket PC.

  • Download and install Windows Mobile 5.0 SDK for Smartphone.

    Important   Install Lab Files. This lab requires pre-existing Visual Studio 2005 project, code, and data files. To install these files to the correct path, run the following installation program:

    Note If you used an emulator in a previous hands-on lab, you may wish to do a hard reset of the emulator before starting this lab. On the emulator, select File | Reset | Hard.

    Note If you receive an error during deployment that indicates that the process or file is in use, this means that the program is still running on the emulator and must be exited before a new copy can be deployed and run. This error may appear anytime in the lab that you deploy the emulator. See the Appendix in this lab for instructions for exiting a running application.

Introduction

This self-paced lab demonstrates how to convert an existing application to be aware of landscape orientation and screen resolution. The Crossword sample used in this lab is a crossword game that contains many characteristics representative of real-world applications, including owner-drawn controls and off-screen memory buffers. To prevent cluttering the code with irrelevant details, many features of the Crossword sample have been left unimplemented. The crossword itself is not real and has no solution.

The objective of this lab is to provide you with experience modifying an application to support landscape, high-resolution, and square displays so that you can apply those skills to your own applications.

  • Exercise 1: Fixing Orientation-Awareness Problems
  • Exercise 2: Fixing DPI-Awareness Problems
  • Exercise 3: Adding Support for Square Screen

Exercise 1: Fixing Orientation-Awareness Problems

The objective of this exercise is to convert the Crossword sample to become aware of changes in orientation between landscape and portrait mode.

In this exercise, you will perform the following tasks:

  • Open the Crossword sample by using the Windows Mobile version 5.0 software for Pocket PC
  • Adjust the background, text box area, hint area, and Tools command dialogs

In this task, you will open and run the Crossword sample to become familiar with the project and the types of problems you may encounter when porting your own application.

First, you need to open the solution.

To open the crossword sample by using the Windows Mobile 5.0 Pocket PC Emulator

  1. If Microsoft Visual Studio 2005 is not already open, start it by browsing to Start | All Programs | Microsoft Visual Studio 2005 | Microsoft Visual Studio 2005.

  2. From Visual Studio 2005, select File | Open | Project/Solution.

  3. In the Open Project dialog box, browse to C:\Program Files\Window Mobile Developer Samples\HOLs\MED305 Dev Orientation Res Aware Apps Native\Exercises\Crossword\BaseSample.

  4. Select CrosswordSample.sln.

  5. Click the Open button. The CrosswordSample solution should now open.

    Now, you can run the Crossword sample.

  6. Verify that the Windows Mobile 5.0 Pocket PC Emulator is selected on the drop-down menu on the Visual Studio 2005 Device toolbar.

    Figure 1. The Visual Studio 2005 Device toolbar shows the Windows Mobile 5.0 Pocket PC Emulator is selected. Click the thumbnail for a larger image.

  7. Start the application by selecting Debug | Start Debugging from the Visual Studio 2005 menu.

  8. If a dialog box appears prompting that This project is out of date, click Yes to build the project.

  9. If prompted by the Deploy CrosswordSample dialog box, verify that Windows Mobile 5.0 Pocket PC Emulator is selected in the Device window, and then click Deploy.

    After a short while, the emulator will appear as shown in the following figure.

    Figure 2. The CrosswordSample on the emulator

    Note   If the emulator does not appear, look for an icon on the Windows taskbar and select it to bring the emulator to the foreground. Be aware that the first time the emulator starts, it may take several minutes.

  10. In the emulator, click the button with a picture of a calendar on it to observe how the program handles portrait/landscape orientation changes. This button is located to the right of the grey directional pad, as shown in the following figure.

    Figure 3. Switching the emulator display orientation

    There are problems when the emulator is in landscape mode, as shown in the following figure.

    Figure 4. Application user interface obscured when in landscape mode

    These problems are the following:

    • The background is cut off on the right side of the screen.
    • The text box area should be widened to fill the length of the screen.
    • The hint area is not displayed.
    • The Tools command dialogs are cut off and require scrollbars.
  11. Exit the CrosswordSample application running on the emulator by choosing Debug | Stop Debugging from Visual Studio 2005.

    Note   You are not closing the emulator itself, just the CrosswordSample application that is running on the emulator. By leaving the emulator running, you will save start-up time the next time you run or debug an application on the emulator.

  12. Review the source code to gain an understanding of how the sample is implemented.

In this task, you will fix the problems observed in the previous task.

First, the background image (Background1.bmp) is only 240 x 320 pixels in size, so you need to replace it with the 320 x 320 version of that image located in the LandscapeAware folder.

To adjust the background, text box area, hint area, and tools command dialogs

  1. In the LandscapeAware folder, copy Background1.bmp.

  2. Paste Background1.bmp into the Crossword\BaseSample folder.

    Note   You will be replacing an existing Background1.bmp in the BaseSample folder. On the Confirm File Replace dialog box, click Yes.

  3. In the Solution Explorer, under CrosswordSample, expand the Source Files folder. Right-click CrosswordSample.cpp, and then click View Code.

  4. In the OnPaint handler located in CrosswordSample.cpp, replace the existing BitBlt statement with the following code.

    BitBlt(hDC, 0, 0, 320, 320, hMemDC, 0, 0, SRCCOPY);
    

    Second, you need to widen the text box area to fill the length of the screen. You should receive a WM_SIZE message when the screen orientation changes from portrait to landscape.

  5. In CrosswordSample.cpp, add the following WM_SIZE handler to WndProc.

    case WM_SIZE:
    {
        HWND hEditBox = GetDlgItem(hWnd, IDC_MAIN_EDIT_BOX);
        HWND hEnterButton = GetDlgItem(hWnd, IDC_MAIN_ENTER_BUTTON);
        INT nWidth = LOWORD(lParam);
    
        MoveWindow(hEditBox, 8, 4, nWidth - 70, 20, TRUE);
        MoveWindow(hEnterButton, nWidth - 57, 4, 50, 20, TRUE);
    }
    break;
    

    Third, the hint area needs to be moved to the side of the screen. This movement requires you to add a function that detects whether the Crossword sample should be displayed in wide mode or not. You will make the application use wide mode whenever there are fewer than 320 vertical pixels.

  6. Add the following function to CrosswordSample.cpp. Be sure to add the corresponding declaration to CrosswordSample.h

    BOOL InWideMode()
    {
        int height = GetSystemMetrics(SM_CYSCREEN);
        return (height < 320) ? TRUE : FALSE;
    }
    
  7. In OnPaint (where the hint area is drawn), remove the line that declares RECT r, and then replace it with the following lines of code.

    RECT rTallMode = { 25, 200, 230, 245 };
    RECT rWideMode = { 240, 43, 311, 185 };
    RECT& r = InWideMode() ? rWideMode : rTallMode;
    
  8. Replace the existing RECT r declaration and InvalidateRect call in OnLButtonDown with the following code because the hint area is invalidated when the left mouse button is clicked.

    RECT rTallMode = { 0, 189, 240, 320 };
    RECT rWideMode = { 240, 26, 320, 240 };
    InvalidateRect(hWnd, InWideMode() ? &rWideMode : &rTallMode, FALSE);
    

    Fourth, you need to move the arrow that indicates whether a clue is a downward or across clue.

  9. In OnPaint, change the lines that set the x and y values of the rgArrow array to the following.

    rgArrow[i].x += (InWideMode() ? 280 : 5);
    rgArrow[i].y += (InWideMode() ? 26 : 200);
    

    The following procedures exemplify how to completely display the Tools command dialogs.

  10. Relayout the dialogs:

    • Using Windows Explorer, copy the UIHelper.cpp and UIHelper.h files from the LandscapeAware folder and paste them into the BaseSample folder
    • Using Project | Add Existing Item from the Visual Studio 2005 menu, add the UIHelper.cpp and UIHelper.h files in the BaseSample folder to your project. (UIHelper.CPP and UIHelper.H provide the RelayoutDialog helper function you will use)
  11. Create dialog templates for the device to use for wide mode:

    • Open the Resource View by selecting View | Other Windows | Resource View.

    • Expand the tree in the Resource View down to and including the Dialog folder, and double-click the IDD_TOOLS_OPTIONS_1 dialog to open the dialog in design view. Right-click the dialog in the designer and then click Properties as shown in the following figure.

      Figure 5. Editing the dialog's properties. Click the thumbnail for a larger image.

    • In the Properties pane, rename the three dialog controls that have IDs of IDC_STATIC to the following: IDC_STATIC_1, IDC_STATIC_2, and IDC_STATIC_3. These IDs must all be unique so that RelayoutDialog knows which controls in portrait mode correspond to which controls in landscape mode.

    • Create a new dialog for landscape mode by right-clicking the IDD_TOOLS_OPTIONS_1 dialog in the Resource View, and then clicking Copy, as shown in the following figure.

      Figure 6. Copying a dialog in Resource View

    • Right-click the IDD_TOOLS_OPTIONS_1 dialog again, and this time, click Paste. A copy of IDD_TOOLS_OPTIONS_1, named IDD_TOOLS_OPTIONS_3 is created.

    • Right-click the newly created IDD_TOOLS_OPTIONS_3 dialog, and then click Properties.

    • In the ID box, type IDD_TOOLS_OPTIONS_1_WIDE to rename the dialog.

    • Double-click IDD_TOOLS_OPTIONS_1_WIDE to open it in the dialog editor. Notice that it is a copy of the original dialog.

    • Resize and rearrange the items so they will fit a resized landscape-mode dialog of 187 x 91 DLUs. You may find it easier to rearrange items by temporarily enlarging the size of the dialog. When all controls are rearranged, size the dialog to 187 × 91 DLUs by dragging the lower-right corner to the appropriate size. (These dimensions are roughly the size of a dialog in landscape mode.)

  12. Repeat step 11 for IDD_TOOLS_OPTIONS_2 and IDD_TOOLS_OPTIONS_2_WIDE, being sure to rename the IDC_STATIC control to IDC_STATIC_1 for IDD_TOOLS_OPTIONS_2.

    Figure 7. Rearranging and resizing the Font Picker dialog and its controls

  13. Add the WM_SIZE handler to dialogs: In CrosswordSample.cpp, ToolsOptions1.cpp, and ToolsOptions2.cpp, add the following statement.

    #include "uihelper.h"
    
  14. In ToolsOptions1.cpp, add the following WM_SIZE handler.

    case WM_SIZE:
    {
         RelayoutDialog(g_hInst, hDlg, InWideMode() ?
             MAKEINTRESOURCE(IDD_TOOLS_OPTIONS_1_WIDE) :
             MAKEINTRESOURCE(IDD_TOOLS_OPTIONS_1));
    }
    return TRUE;
    
  15. In ToolsOptions2.cpp, add the following WM_SIZE handler.

    case WM_SIZE:
    {
         RelayoutDialog(g_hInst, hDlg, InWideMode() ?
             MAKEINTRESOURCE(IDD_TOOLS_OPTIONS_2_WIDE) :
             MAKEINTRESOURCE(IDD_TOOLS_OPTIONS_2));
    }
    return TRUE;
    
  16. In ToolsAbout.cpp, add the following WM_SIZE handler (in this case, you're manually handling the About dialog template without using RelayoutDialog).

    case WM_SIZE:
    {
        INT nWidth = LOWORD(lParam);
        INT nHeight = HIWORD(lParam);
        HWND hWnd = GetDlgItem(hDlg, IDC_STATIC_1);
        RECT rWnd;
        RECT rDlg;
        GetWindowRect(hWnd, &rWnd);
        GetWindowRect(hDlg, &rDlg);
        OffsetRect(&rWnd, -rDlg.left, -rDlg.top);
        MoveWindow(hWnd, rWnd.left, rWnd.top, 
        nWidth - rWnd.left - 8, 
        nHeight - rWnd.top - 8, TRUE);
    }
    return TRUE;
    
  17. In CrosswordSample.h, add the following line in the Function Prototypes section.

    BOOL                 InWideMode();
    
  18. Verify that the entire application handles landscape/portrait orientation changes by repeating Exercise 1, Task 1 beginning with step 6.

Exercise 2: Fixing DPI-Awareness Problems

The objective of this exercise is to convert the Crossword sample to become aware of changes in screen resolution. This exercise builds on the work you completed in Exercise 1. For more information about coding for high-resolution devices, see Developing DPI-Aware Applications.

In this exercise, you will perform the following tasks:

  • Run the Crossword sample on a VGA emulator
  • Adjust the hard-coded constants, background, Enter button, hint area, and dialogs

In this task, you will disable HIDPI emulation and build and run the Crossword sample to become familiar with the project and the types of problems you may encounter when porting your own application.

Your application receives HIDPI emulation by default. To turn it off, add the HIDPI_RES_AWARE resource to your program.

To run the crossword sample on a HIDPI Emulator

  1. Display the Resource View by selecting View | Other Windows | Resource View on the Visual Studio 2005 menu.

  2. Right-click CrosswordSample.rc in the Resource View, and then select Add Resource.

  3. In the Add Resource dialog box, click Custom.

  4. In the New Custom Resource dialog box, type CEUX for the resource type.

  5. Set the resource data to 01 00.

  6. If the Properties Windows is not already visible, select View | Properties Windows on the Visual Studio 2005 menu to view the new resource's properties.

  7. Set the ID of the resource to "HI_RES_AWARE", including the quotation marks. (If the quotation marks are omitted, HI_RES_AWARE will be incorrectly defined as a numeric value in resource.h, and you will need to delete the line from resource.h.)

  8. Set External file to false.

    Next, you need to switch to the Windows Mobile 5.0 Pocket PC VGA emulator.

  9. Run the Crossword sample on the Windows Mobile 5.0 Pocket PC VGA emulator by selecting Windows Mobile 5.0 Pocket PC VGA Emulator in the drop-down list on the Visual Studio 2005 Device toolbar.

  10. Start the application by selecting Debug | Start Debugging from the Visual Studio 2005 menu, confirming to build the solution if prompted.

  11. If prompted by the Deploy CrosswordSample dialog box, verify that Windows Mobile 5.0 Pocket PC VGA Emulator is selected in the Device window, and then click Deploy. The application should appear as shown in the following figure.

    Figure 8. The application running incorrectly on a high-resolution device

  12. Observe the following problems:

    • The text entry box is too small.
    • The background image doesn't fill the whole screen.
    • The arrow, hint area, and Enter button are too small.
    • The crossword cell boxes are too small.
    • The font picker list is too crowded.
    • The background image picker previews are too small.
  13. Exit the CrosswordSample application running on the emulator by choosing Debug | Stop Debugging from Visual Studio 2005.

In this task, you will fix the problems observed in the previous task.

First, there are many unscaled constants in this application. You will use the SCALEX and SCALEY macros from UIHelper.h and UIHelper.cpp to adjust 96-dpi coordinates to any arbitrary screen resolution.

To convert the hard-coded constants, background, enter button, hint area, and dialogs

  1. In CroswordSample.cpp, add a call to HIDPI_InitScaling at the top of InitInstance immediately after the two LoadString calls.

    HIDPI_InitScaling();
    
  2. Add SCALEX and SCALEY macros around unscaled constants to all the following functions in CroswordSample.cpp:

    • InitInstance: Modify the two CreateWindow calls that create the child windows. (Do not scale the parameters to CreateWindow, which creates the main application window.)

      SCALEX(8), SCALEY(4), SCALEX(170), SCALEY(20),
      SCALEX(183), SCALEY(4), SCALEX(50), SCALEY(20),
      
      g_hImageList = ImageList_LoadImage(
              g_hInst,
              MAKEINTRESOURCE(IDB_ENTERBTN),
              SCALEX(46),
              0,
              CLR_NONE,
              IMAGE_BITMAP,
              0);
      
    • CreateCrossword: Modify CreateCompatibleBitmap and the lines below the RECT r declaration. Note that when r.right is set equal to r.left + 17, this 17 is equal to the cell size (16 pixels) plus a border (1 pixel). In this case, you will scale the cell area but not the border, so this code should read like the following.

      r.left   = SCALEX(16) * x;
      r.top    = SCALEY(16) * y;
      r.right  = r.left + SCALEX(16) + 1;
      r.bottom = r.top + SCALEY(16) + 1;
      
      g_hMemBitmap = CreateCompatibleBitmap(hScreenDC, SCALEX(240), SCALEY(320));
      
    • DrawHint: For the DrawText call, keep in mind that the constant 15 is equal to the cell size minus the border (similar to the previous example). Don't forget that there is an InvalidateRect call that must be scaled also.

      r.left = x * SCALEX(16) + 1;
      r.top  = y * SCALEY(16) + 1;
      r.right  = r.left + SCALEX(16) - 1;
      r.bottom = r.top + SCALEY(16) - 1;
      
      r.left += SCALEX(8); 
      r.right += SCALEX(8);
      r.top += SCALEY(26);
      r.bottom += SCALEY(26);
      
    • OnLButtonDown: Modify the received coordinates and InvalidateRect.

      x = (x - SCALEX(8)) / SCALEX(16);
      y = (y - SCALEY(26)) / SCALEY(16);
      
      RECT rTallMode = { SCALEX(0), SCALEY(189), SCALEX(240), SCALEY(320) };
      RECT rWideMode = { SCALEX(240), SCALEY(26), SCALEX(320), SCALEY(240) };
      
    • OnPaint: Modify TransparentImage, PolyLine, Rectangle r, the margin for DrawText, and Polygon. (Remember that 16 represents cell width, and 1 represents the border.) The Polygon call, which draws the down and across arrows, is a bit tricky—make sure you scale all of the constants.

      TransparentImage(hDC, 
              SCALEX(8), 
              SCALEY(26), 
              14 * SCALEX(16) + 1, 
              10 * SCALEY(16) + 1, 
              g_hMemDC, 
              0, 
              0, 
              14 * SCALEX(16) + 1, 
              10 * SCALEX(16) + 1, 
              RGB(255, 255, 255));
      
      HPEN hNewPen = CreatePen(PS_SOLID, SCALEX(1), RGB(0, 0, 0));
      HPEN hOldPen = (HPEN)SelectObject(hDC, hNewPen);
      POINT line[] = { {SCALEX(0), SCALEY(188)}, {SCALEX(240), SCALEY(188)} };
      Polyline(hDC, line, 2);
      
      RECT rTallMode = { SCALEX(25), SCALEY(200), SCALEX(230), SCALEY(245) };
      RECT rWideMode = { SCALEX(240), SCALEY(43), SCALEX(311), SCALEY(185) };
      
       r.left  += SCALEX(4);
       r.right -= SCALEX(4);
      
    • WndPRoc: Modify the WM_SIZE handler. Remember that you only want to scale constants. The nWidth value is already HIDPI–aware because it is received from the operating system. Therefore, SCALEX(nWidth - 57) is incorrect; the correct expression is nWidth - SCALEX(57).

      MoveWindow(hEditBox, SCALEX(8), SCALEY(4), nWidth - SCALEX(70), SCALEY(20), TRUE);
      MoveWindow(hEnterButton, nWidth - SCALEX(57), SCALEY(4), SCALEX(50), SCALEY(20), TRUE);
      
    • InWideMode: There are more than 320 vertical pixels in HIDPI landscape mode.

      return (height < SCALEY(320)) ? TRUE : FALSE;
      

    Run the Crossword sample to see that some of the problems have been fixed, as shown in the following figure.

    Figure 9. The application running on a high-resolution device after adjusting scaling constants

  3. Exit the CrosswordSample application running on the emulator by choosing Debug | Stop Debugging from Visual Studio 2005.

    Second, you need to extend the background image the length of the screen by importing the 192–dpi bitmap background.

  4. If not already visible, open the Resource Window. In the Resource Window, right-click CrosswordSample.rc, and then click Add Resource.

  5. In the Add Resource dialog box, click Import.

  6. Browse to the HidpiAware folder, and then select Background1_hidpi.bmp.

  7. In the Properties dialog box, set the ID to IDB_BACKGROUND_1_HIDPI. You will use this bitmap when the screen resolution is 192 dpi or higher.

  8. In the CrosswordSample.cpp OnPaint method, replace the code that draws the background with the following code. Notice the use of StretchBlt.

    int nImageSize = g_HIDPI_LogPixelsX >= 192 ? 640 : 320;
    HBITMAP hBMP = LoadBitmap(g_hInst, g_HIDPI_LogPixelsX >= 192 ?
                   MAKEINTRESOURCE(IDB_BACKGROUND_1_HIDPI) :
                   MAKEINTRESOURCE(IDB_BACKGROUND_1));
    HDC hMemDC = CreateCompatibleDC(hDC);
    HBITMAP hOldBMP = (HBITMAP)SelectObject(hMemDC, hBMP);
    StretchBlt(hDC, 0, 0, SCALEX(320), SCALEY(320), 
               hMemDC, 0, 0, nImageSize, nImageSize, SRCCOPY);
    SelectObject(hMemDC, hOldBMP);
    DeleteObject(hBMP);
    DeleteDC(hMemDC);
    

    Third, the Enter button needs to be enlarged and relocated to fit the screen better.

  9. To enlarge the Enter button, in InitInstance, call HIDPI_ImageList_LoadImage (defined in UIHelper.h) instead of ImageList_LoadImage to scale the images.

    g_hImageList = HIDPI_ImageList_LoadImage(
        g_hInst,
        MAKEINTRESOURCE(IDB_ENTERBTN),
        SCALEX(46),
        0,
        CLR_NONE,
        IMAGE_BITMAP,
        0);
    
  10. To relocate the Enter button, in the WM_DRAWITEM handler, use BF_ADJUST to find the correct location to draw the ImageList.

    case WM_DRAWITEM:
    {
        LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam;
        DrawEdge(lpDis->hDC, &lpDis->rcItem, 
            (lpDis->itemState & ODS_SELECTED) ? EDGE_SUNKEN : EDGE_RAISED, 
            BF_RECT | BF_ADJUST);
        ImageList_Draw(g_hImageList,
            (lpDis->itemState & ODS_SELECTED) ? 0 : 1,
            lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.top, ILD_NORMAL);
    }
    break;
    

    Fourth, the divider line and the hint area's border need to be easier to see on an HIDPI device's screen.

  11. Make the divider line (and the hint area's border) two pixels wide by adding the following code.

    HPEN hNewPen = CreatePen(PS_SOLID, SCALEX(1), RGB(0, 0, 0));
    HPEN hOldPen = (HPEN)SelectObject(hDC, hNewPen);
    POINT line[] = { {SCALEX(0), SCALEY(188)}, {SCALEX(240), SCALEY(188)} };
    Polyline(hDC, line, 2);
    
    // Erase the hint area using a pattern brush.
    
  12. Add the following lines after the two DeleteObject calls.

    SelectObject(hDC, hOldPen);
    DeleteObject(hNewPen);
    

    The text size within the hint area needs to be changed according to the device. You can query the device's font size (which the user can adjust by using the Screen item in Control Panel) by using SHGetUIMetrics defined in shguim.h.

  13. Create a function that creates the font.

    void CreateHintFont()
    {
        if (g_hFont)
        {
            DeleteObject(g_hFont);
        }
    
        DWORD dwRequired;
        LONG  dwFontSize = 12;
        SHGetUIMetrics(SHUIM_FONTSIZE_PIXEL, &dwFontSize,
            sizeof(dwFontSize), &dwRequired);
    
        LOGFONT lf;
        memset(&lf, 0, sizeof(lf));
        _tcscpy(lf.lfFaceName, _T("Courier New"));
        lf.lfHeight = -dwFontSize;
        lf.lfWeight = FW_NORMAL;
        g_hFont = CreateFontIndirect(&lf);
    }
    

    Be sure to also place the following function prototype in CrosswordSample.h.

    void                CreateHintFont();
    
  14. In InitInstance, remove the CreateFontIndirect call, and then replace it with a call to the CreateHintFont function you just created.

    CreateHintFont();
    

    The Crossword sample also needs to be notified when this font size changes.

  15. Near the top of Crossword.cpp, declare WM_SH_UIMETRIC_CHANGE as a global.

    UINT WM_SH_UIMETRIC_CHANGE;
    
  16. At the top of InitInstance, initialize WM_SH_UIMETRIC_CHANGE by calling RegisterWindowMessage.

    WM_SH_UIMETRIC_CHANGE = RegisterWindowMessage(SH_UIMETRIC_CHANGE);
    //When you receive this window message in WndProc, recreate the hint //font:
    if (message == WM_SH_UIMETRIC_CHANGE)
    {
        CreateHintFont();
    }
    else switch (message) 
    
    
  17. Run the Crossword sample to see that more of the problems have been fixed, as shown in the following figure.

    Figure 10. The application correctly scaling on a high-resolution device

    Next, the Background Picker dialog must be fixed. In ToolsOptions1.cpp, there are two places where you send an STM_SETIMAGE message to the Background Picker preview control.

  18. Before each STM_SETIMAGE call, use HIDPI_StretchBitmap (defined in uihelper.h) to scale the bitmap by adding the following code.

    HIDPI_StretchBitmap(&hBMP, SCALEX(320), SCALEY(320), 1, 1);
    

    Next, you need to fix the Font Picker dialog (ToolOptions2.cpp).

  19. Modify the line lpmis->itemHeight = 40 with the following code.

    lpmis->itemHeight = SCALEY(40);
    
  20. There is also a hard-coded 15 in the WM_INITDIALOG handler, which refers to the width of a scroll bar. Use GetSystemMetrics to get the true width of the vertical scroll bar. Replace the line lvColumn.cx = r.right – r.left – 15 with the following code.

    lvColumn.cx = r.right - r.left - GetSystemMetrics(SM_CXVSCROLL) - 2 * GetSystemMetrics(SM_CYBORDER);
    
  21. Run the Crossword sample to see that the remaining issues have been fixed.

  22. Exit the CrosswordSample application running on the emulator by choosing Debug | Stop Debugging from Visual Studio 2005.

Exercise 3: Adding Support for Square Screen

The objective of this exercise is to convert the Crossword sample to add support for square screen devices. This exercise builds on the work you completed in Exercise 1 and Exercise 2.

In this exercise, you will perform the following tasks:

  • Run the Crossword sample on a square screen emulator
  • Make further adjustments to the program layout to support square screen displays

In this task, you will run the Crossword sample to become familiar with the types of problems you may encounter when porting your own application to a square screen device.

First, switch to using the Windows Mobile 5.0 Pocket PC Square VGA Emulator image.

To run the crossword sample on a square screen emulator

  1. Select the Windows Mobile 5.0 Pocket PC Square VGA Emulator in the drop-down list on the Visual Studio 2005 Device toolbar.

  2. Start the application by selecting Debug | Start Debugging from the Visual Studio 2005 menu, confirming to build the solution if prompted.

  3. If prompted by the Deploy CrosswordSample dialog box, verify that Windows Mobile 5.0 Pocket PC Square VGA Emulator is selected in the Device window, and then click Deploy. The application should appear as shown in the following figure.

    Figure 11. The application incorrectly displaying on a square device

    Observe that the hint window and arrow are completely cut off of the screen. Because the display is square, rotating the display doesn't help. You need to specifically adjust the display to handle the square display.

In this task, you will fix the problems observed in the previous task.

The most notable thing to recognize is that even with adding scaling, having hard-coded constants spread throughout your program makes handling the wide variety of available displays difficult. Centralizing display metrics helps significantly in dealing with these display issues. In this task, you will update the program so that the display metrics are managed in a single function. In that function, you will then make the necessary adjustments for portrait, landscape, and square displays.

The first thing to do is add a new function that identifies the three different display states: portrait, landscape, and square. For readability, the function will return an enumeration indicating the current display state.

To adjust the hard-coded constants, background, enter button, hint area, and dialogs

  1. Add the following enumeration and function signature to CrosswordSample.h.

    enum DisplayMode
    {
      Landscape = -1,
      Square = 0,
      Portrait = 1
    };
    
    DisplayMode GetDisplayMode();
    
  2. Now, add the implementation of GetDisplayMode to CrosswordSample.cpp. Rather than just differentiating between portrait and landscape as IsInWideMode does, GetDisplayMode identifies which of the three possible display modes are active.

    DisplayMode GetDisplayMode()
    
    {
      int nWidth = GetSystemMetrics(SM_CXSCREEN);
      int nHeight = GetSystemMetrics(SM_CYSCREEN);
    
      if(nHeight > nWidth)
        return Portrait;
    
      if(nHeight < nWidth)
        return Landscape;
    
      return Square;
    }
    

    The next thing to address is the centralization of the application display metrics. To do this, declare global variables representing each of the display specific screen metrics. Then, provide a function that sets all of the metrics for a particular display layout.

  3. Declare the following global variables to hold each of the display-specific values.

    const int const_nRows = 10; 
    int g_RowHeight ;           
    int g_CrossWordTop;         
    int g_CrossWordBottom;      
    int g_ControlWindowsTop;    
    int g_ControlWindowsBottom; 
    int g_HintLeft;            
    int g_HintTop;              
    int g_HintRight;            
    int g_HintBottom;           
    int g_ArrowLeft;            
    int g_ArrowTop;            
    int g_HintInvalidateLeft;
    int g_HintInvalidateTop;    
    int g_HintInvalidateRight;
    int g_HintInvalidateBottom; 
    
  4. Now, add the DetermineDisplayMetrics function. This function sets each of the global variables to the correct value for the current display mode. Each of the values in this function is taken from the literals that were used in Exercise 1 and Exercise 2. Notice that although most of the values are simply hard-coded values, in some cases values can be calculated from others, such as in the case of g_CrossWordBottom.

    void DetermineDisplayMetrics()
    {
      switch(GetDisplayMode())
      {
        case Portrait:
          g_RowHeight = 16;
          g_CrossWordTop = 26;
          g_ControlWindowsTop = 4;
          g_ControlWindowsBottom = 20;
          g_HintLeft = 25;
          g_HintTop = 200;
          g_HintRight = 230;
          g_HintBottom = 245;
          g_ArrowLeft = 5;
          g_ArrowTop = g_HintTop;
          g_HintInvalidateLeft = 0;
          g_HintInvalidateTop = 189;
          g_HintInvalidateRight = 240;
          g_HintInvalidateBottom = 320;
          break;
        case Landscape:
          g_RowHeight = 16;
          g_CrossWordTop = 26;
          g_ControlWindowsTop = 4;
          g_ControlWindowsBottom = 20;
          g_HintLeft = 240;
          g_HintTop = 43;
          g_HintRight = 311;
          g_HintBottom = 185;
          g_ArrowLeft = 280;
          g_ArrowTop = 26;
          g_HintInvalidateLeft = 240;
          g_HintInvalidateTop = 26;
          g_HintInvalidateRight = 320;
          g_HintInvalidateBottom = 240;
        break;
      }
      g_CrossWordBottom = g_CrossWordTop + (g_RowHeight * const_nRows);
    }
    

    The next thing to do is replace the literals with the global variables.

  5. Locate the InitInstance function. Modify the two CreateWindow function calls to use g_ControlWindowsTop and g_ControlWindowsBottom as shown here.

    CreateWindow(_T("Edit"), NULL, 
      WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 
      SCALEX(8), SCALEY(g_ControlWindowsTop), SCALEX(170),
      SCALEY(g_ControlWindowsBottom),
      hWnd, (HMENU)IDC_MAIN_EDIT_BOX, g_hInst, 0);
    
    CreateWindow(_T("Button"), _T(""), 
      WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
      SCALEX(183), SCALEY(g_ControlWindowsTop), SCALEX(50),
      SCALEY(g_ControlWindowsBottom),
      hWnd, (HMENU)IDC_MAIN_ENTER_BUTTON, g_hInst, 0);
    
  6. Locate the CreateCrossword function. Modify the rectangle declaration at the end of the function so that the top and bottom values of the rectangle use g_RowHeight rather than the literal.

    RECT r;
    r.left   = SCALEX(16) * x;
    r.top    = SCALEY(g_RowHeight) * y;
    r.right  = r.left + SCALEX(16) + 1;
    r.bottom = r.top + SCALEY(g_RowHeight) + 1;
    Rectangle(g_hMemDC, r.left, r.top, r.right, r.bottom);
    
  7. Locate the DrawHint function. Modify the rectangle used by the FillRect and DrawText functions to set the top and bottom members by using g_RowHeight.

    r.left = x * SCALEX(16) + 1;
    r.top  = y * SCALEY(g_RowHeight) + 1;
    r.right  = r.left + SCALEX(16) - 1;
    r.bottom = r.top + SCALEY(g_RowHeight) - 1;
    SetBkMode(g_hMemDC, TRANSPARENT);
    FillRect(g_hMemDC, &r, hBrush);
    DrawText(g_hMemDC, &g_crossword[y][x], 1, &r, DT_CENTER | DT_VCENTER);
    
  8. Still within DrawHint, modify the lines that increment the values stored in the top and bottom members of r to use g_CrossWordTop.

    r.left += SCALEX(8); 
    r.right += SCALEX(8);
    r.top += SCALEY(g_CrossWordTop);
    r.bottom += SCALEY(g_CrossWordTop);
    InvalidateRect(hWnd, &r, false);
    
  9. Locate the OnLButtonDown function and modify the line that sets the value of y to use g_CrossWordTop instead of the literal 26 and g_RowHeight instead of the literal 16.

    y = (y - SCALEY(g_CrossWordTop)) / SCALEY(g_RowHeight);
    
  10. Still in OnLButtonDown, modify the rectangle used by InvalidateRect to use g_HintInvalidateLeft, g_HintInvalidateTop, g_HintInvalidateRight , and g_HintInvalidateBottom.

    RECT r = { SCALEX(g_HintInvalidateLeft), SCALEY(g_HintInvalidateTop), 
      SCALEX(g_HintInvalidateRight), SCALEY(g_HintInvalidateBottom) };
    InvalidateRect(hWnd, &r, FALSE);
    
  11. Locate the OnPaint function. As you might expect, there are several changes to make here. First, modify the call to TransparentImage to use g_CrossWordTop and g_RowHeight as shown here.

    TransparentImage(hDC, 
      SCALEX(8), 
      SCALEY(g_CrossWordTop), 
      14 * SCALEX(16) + 1, 
      const_nRows * SCALEY(g_RowHeight) + 1, 
      g_hMemDC, 
      0, 
      0, 
      14 * SCALEX(16) + 1, 
      const_nRows * SCALEY(g_RowHeight) + 1, 
      RGB(255, 255, 255));
    
  12. Again in OnPaint, delete the rTallMode and rWideMode rectangles. Declare a new rectangle rect that initializes the global variables as shown here. Assign the address to r.

    RECT rect = { SCALEX(g_HintLeft), SCALEY(g_HintTop), 
                  SCALEX(g_HintRight), SCALEY(g_HintBottom) };
    RECT& r = rect;
    HBITMAP hPattern = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_PATTERN));
    HBRUSH hNewBrush = CreatePatternBrush(hPattern);
    HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hNewBrush);
    Rectangle(hDC, r.left, r.top, r.right, r.bottom);
    
  13. Still in OnPaint, modify the lines that increment the x and y values of the rgArrow array to use g_ArrowLeft and g_ArrowTop as shown here.

    rgArrow[i].x += g_ArrowLeft;
    rgArrow[i].y += g_ArrowTop;
    SCALEPT(rgArrow[i]);
    
  14. In the WndProc function, locate the case statement to handle the WM_SIZE message. Modify the two MoveWindow function calls to use g_ControlWindowsTop and g_ControlWindowsBottom as shown here.

    MoveWindow(hEditBox, SCALEX(8), 
               SCALEY(g_ControlWindowsTop), 
               nWidth - SCALEX(70), 
               SCALEY(g_ControlWindowsBottom), 
               TRUE);
    MoveWindow(hEnterButton, nWidth - SCALEX(57),
               SCALEY(g_ControlWindowsTop), 
               SCALEX(50), 
               SCALEY(g_ControlWindowsBottom), 
               TRUE);
    

    With all of the literals replaced with variables, you just need to add the calls to DetermineDisplayMetrics to assign the correct values to the global variables. It needs to be called when the application first starts and any time the display changes.

  15. To get the initial values for the display metrics, call DetermineDisplayMetrics in InitInstance immediately after the call to HIDPI_InitScaling.

    HIDPI_InitScaling();
    DetermineDisplayMetrics();
    
  16. To handle changes in the display, add a call to DetermineDisplayMetrics to the WM_SIZE case statement within WndProc.

    case WM_SIZE:
      {
        DetermineDisplayMetrics();
    
        HWND hEditBox = GetDlgItem(hWnd, IDC_MAIN_EDIT_BOX);
        HWND hEnterButton = GetDlgItem(hWnd, IDC_MAIN_ENTER_BUTTON);
        INT nWidth = LOWORD(lParam);
    
        MoveWindow(hEditBox, SCALEX(8), SCALEY(g_ControlWindowsTop), 
                   nWidth - SCALEX(70), SCALEY(g_ControlWindowsBottom),
                   TRUE);
        MoveWindow(hEnterButton, nWidth - SCALEX(57),
                   SCALEY(g_ControlWindowsTop), SCALEX(50), 
                   SCALEY(g_ControlWindowsBottom), TRUE);
      }
      break;
    

By centralizing all of the screen metrics into a single function as you did in the previous task, adding support for square screen devices is as simple as adding an additional case condition to the DetermineDisplayMetrics function and setting each of the global variables to the correct value. The application drawing will take care of itself because it uses the global variables.

To add the square screen display metrics

  • Add the case condition for Square to DetermineDisplayMetrics (the details of the Square case will be filled in on the following steps).

    void DetermineDisplayMetrics()
    {
    switch(DisplayMode())
    {
        case Portrait:
          // ....
          break;
        case Landscape:
          // ....
          break;
        case Square:
    
          break;
      }
      // ...
    }
    

Now, each of the screen metric values can be set within the new Square branch of switch statement. As you go through setting each of the values, take note of how the same values are set in the Portrait and Landscape cases. Sometimes the values may be the same as the corresponding value in one of the other cases. Some of the values will be somewhat different because the screen space needs to be more closely managed.

  1. Set the g_RowHeight value. Notice that the rows are shorter than in the other cases to conserve screen space.

    g_RowHeight = 12;
    
  2. Set the positioning for the child controls (edit box and button) and the top of the crossword puzzle.

    g_ControlWindowsTop = 4;
    g_ControlWindowsBottom = 20;
    g_CrossWordTop = 26;
    
  3. Set the dimensions for the hint area.

    g_HintLeft = 25;
    g_HintTop = 150;
    g_HintRight = 230;
    g_HintBottom = 187;
    
  4. Set the dimensions for the arrow that appears next to the hint area.

    g_ArrowLeft = 5;
    g_ArrowTop = g_HintTop;
    
  5. Set the hint invalidation area. This is the area that is redrawn each time a hint is displayed. It basically covers the area that the arrow and hint area occupy.

    g_HintInvalidateLeft = 0;
    g_HintInvalidateTop = 145;
    g_HintInvalidateRight = 240;
    g_HintInvalidateBottom = 240;
    

    There is just one other change to make before testing the application. When you are displaying an application on a square screen device, it is important to avoid giving the application a crowded appearance. In the case of the crossword application, the divider line that separates the hint window and puzzle tends to crowd the screen when it appears on a square device. The application actually looks better without it, so add the code to draw the divider line only when in portrait or landscape orientation.

  6. In the OnPaint function, locate the declaration of the line array and the following call to the PolyLine function.

  7. Put those two lines in an if statement that calls GetDisplayMode and checks the return value to not be equal to Square.

    if (GetDisplayMode() != Square)
    {
      POINT line[] = { {SCALEX(0), SCALEY(188)}, {SCALEX(240), SCALEY(188)} };
      Polyline(hDC, line, 2);
    }
    

The application drawing is now ready for square screen devices. There is still a bit of work to do for the background bitmap and the option dialog windows, but the primary portion of the application is ready. Build and run the application on the Windows Mobile 5.0 Square VGA Emulator. It should look similar to the following figure.

Figure 12. The application incorrectly displayed on a square device

Correcting the problem with the bitmap is basically just a continuation of the centralization work you did in Task 2. There is, however, the slight complication that the bitmaps are not just dependent on the shape of the display but also the resolution. Your solution will have to take both of these issues into account.

The first thing to do is add the bitmap for the square display.

To add the square screen bitmaps

  1. If not already visible, open the Resource Window. In the Resource Window, right-click CrosswordSample.rc, and then click Add Resource.

  2. In the Add Resource dialog box, click Import.

  3. Browse to the SquareAware folder, and then select Background1_square.bmp.

  4. In the Properties dialog box, set the ID to IDB_BACKGROUND_1_SQUARE.

    Repeat these same steps for the Background1_squarehidpi.bmp. Set the ID to IDB_BACKGROUND_1_SQUAREHIDPI. You will use this background for square VGA displays.

    Now, modify the program to centralize the bitmap selection and add support for selecting between the four bitmaps rather than just the two.

  5. Add two new global variables to the existing list.

    int g_ImageSize;      // Size to display background bitmap
    int g_BackgroundBitmapID;  // ID of background bitmap to use
    
  6. Locate the DetermineDisplayMetrics function. Add the following statement to the Portrait section of the switch statement. This statement selects between the regular and HIDPI bitmaps.

    g_BackgroundBitmapID = g_HIDPI_LogPixelsX >= 192 ? 
        IDB_BACKGROUND_1_SQUAREHIDPI : IDB_BACKGROUND_1_SQUARE;
    
  7. Because the game uses the same bitmap for both portrait and landscape displays, add this same line to the Landscape section of the switch statement.

  8. For the Square section of the switch statement, use the following statement to select between the regular and HIDPI square backgrounds.

    g_BackgroundBitmapID = g_HIDPI_LogPixelsX >= 192 ? 
        IDB_BACKGROUND_1_SQUAREHIDPI : IDB_BACKGROUND_1;
    
  9. At the end of the DetermineDisplayMetrics function, after the switch statement, add the following statement to store the bitmap display size.

    g_ImageSize = g_HIDPI_LogPixelsX >= 192 ? 640 : 320;
    

    The final step in fixing the bitmap display is to modify the code in the OnPaint function to use the global variables you have set up.

  10. Locate the OnPaint function and delete the line containing the declaration of the nImageSize local variable.

  11. Modify the LoadBitmap function call to use the g_BackgroundBitmapID global variable.

    HBITMAP hBMP = 
      LoadBitmap(g_hInst, MAKEINTRESOURCE(g_BackgroundBitmapID));
    
  12. Modify the call to StretchBlt to use the global variable g_ImageSize rather than the local variable nImageSize.

    StretchBlt(hDC, 0, 0, SCALEX(320), SCALEY(320), hMemDC, 0, 0,
               g_ImageSize, g_ImageSize, SRCCOPY);
    

    The application will now display the appropriate bitmap for each display type. Build and execute the program by using the Windows Mobile 5.0 Square VGA Emulator to verify that the program works as expected. Try other Windows Mobile 5.0 emulators to verify that they also still work.

Adding support for displaying the options dialog is quite simple. It is basically an extension of the same work you already did when adjusting the options dialog to support landscape display.

Follow the directions in Exercise 1, Task 2 for creating a new dialog resource. In this case, use a dialog size of 140 x 91 and set the resource ID to IDD_TOOLS_OPTIONS_1_SQUARE.

Now, modify the WM_SIZE section of the switch statement in the ToolsOptionsDialog1 callback function to switch between the three dialog resources.

To update the dialogs

  1. Locate the WM_SIZE section of the switch statement in the ToolsOptionsDialog1 function in ToolsOptions1.cpp.

  2. Add the following declaration as the first line inside of the WM_SIZE code.

    int resourceID;
    
  3. Follow the resourceID variable declaration with this switch statement to assign the correct resource to the resourceID variable.

    switch (GetDisplayMode())
    {
      case Portrait:
        resourceID = IDD_TOOLS_OPTIONS_1;
        break;
      case Landscape:
        resourceID = IDD_TOOLS_OPTIONS_1_WIDE;
        break;
      case Square:
        resourceID = IDD_TOOLS_OPTIONS_1_SQUARE;
        break;
    }
    
  4. Finally, modify the call to the RelayoutDialog function to use the resourceID variable.

    RelayoutDialog(g_hInst, hDlg, MAKEINTRESOURCE(resourceID));
    

Repeat these same steps for ToolsOptions2.cpp.

Appendix A: Terminating an Application Running on a Device or Emulator

This task describes how to terminate an application that is running on a device or emulator. This is useful for cases where an application has been started without having the debugger attached and needs to be terminated so that a new copy of the application can be deployed. You will terminate the application by using the Remote Process Viewer remote tool in Visual Studio.

Before you can terminate a process, you need to know the name of the executable file. In most cases, this is simply the same name as the Visual Studio project. If you are uncertain of the name of the executable file, you can find it in the project properties.

  1. From Visual Studio, select Project, and then xxx Properties, where xxx represents the name of the current project.

  2. Note the value in the Configuration Properties | Linker | General | Output File field. This is the name that the executable file will be running as on the device or emulator.

  3. Close the Properties dialog box.

    Now, you can terminate the process.

  4. From the Start menu, click Start | Microsoft Visual Studio 2005 | Visual Studio Remote Tools | Remote Process Viewer.

  5. When prompted by the Select a Windows CE Device dialog box, select the emulator or device where the application is running, as shown in the following figure, and then click OK.

    Figure 13. Select a Windows CE Device dialog box

  6. After the connection to the emulator or device completes, locate the application you want to terminate in the top pane of the Remote Process Viewer, as shown in the following figure.

    Figure 14. Selecting the application to terminate. Click the thumbnail for a larger image.

    You may need to widen the Process column (leftmost column) to fully view the process names.

  7. Click the process name to select the process.

  8. To terminate the process, select File | Terminate Process from the Remote Process Viewer menu.

    Note   Be certain that the correct process is selected before clicking Terminate Process. Terminating the incorrect process may render the device or emulator unusable, requiring it to be reset before you can use it again.

  9. Verify that the process is terminated by selecting Target | Refresh on the Remote Process Viewer menu, and scrolling through the top pane again. If the application name still appears, the process was not terminated, and you need to repeat these steps.

    Note   Most processes will terminate in just one try; however, depending on the state of the application, it does occasionally take two attempts.

Conclusion

In this lab, you performed the following exercises.

  • Exercise 1: Fixing Orientation-Awareness Problems
  • Exercise 2: Fixing DPI-Awareness problems
  • Exercise 3: Adding Support for Square Screen

Congratulations! You have successfully converted an application to be landscape, high-resolution, and square aware.