Step by Step: Develop Orientation-Aware and DPI-Aware Applications for Pocket PC

 

Microsoft

February 2005

Applies to:
   Microsoft® eMbedded Visual C++® version 4.0
   Windows Mobile™ 2003 Second Edition software for Pocket PCs

Summary: The objective of this exercise is for you to learn how to convert an existing application to be aware of landscape orientation and screen resolution. This exercise will take approximately one hour and 30 minutes to complete. (15 printed pages)

Contents

Introduction
Part 1: Fixing Orientation-Awareness Problems
Part 2: Fixing DPI-Awareness Problems
Summary
Appendix A: Installation Instructions

To complete this lab, you will need:

  • Microsoft eMbedded Visual C++ 4.0 Service Pack 3
  • Pocket PC Software Development Kit (SDK)
  • Developer Resources for Windows Mobile 2003 Second Edition
  • Windows Mobile 2003 Second Edition emulator images for Pocket PCs

Note These applications must be installed in order for this exercise to work correctly. If you have these applications installed on your computer, you will need to uninstall them, and reinstall them in this order. See Appendix A in this article for more information.

Introduction

This self-paced exercise demonstrates how to convert an existing application to be aware of landscape orientation and screen resolution. The Crossword sample — a crossword game that contains many characteristics representative of real-world applications, including owner-drawn controls and off-screen memory buffers — is included in Program Files\Developer Resources for Windows Mobile 2003 Second Edition\Samples\Pocket PC\Win32\Crossword folder. To keep from 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.

Part 1: Fixing Orientation-Awareness Problems

The objective of this part of the exercise is to convert the Crossword sample to become aware of changes in orientation between landscape and portrait mode. By default, the sample files are located in the Program Files\Developer Resources for Windows Mobile 2003 Second Edition\Samples\Pocket PC\Win32\Crossword folder. For more information about coding for landscape mode, see Developing Screen Orientation-Aware Applications.

In this part of the exercise, you will perform the following tasks:

  • Open the Crossword sample by using the Windows Mobile™ 2003 Second Edition emulator image for Pocket PCs
  • Adjust the background, text box area, hint area, and Tools command dialogs

Some illustrations in this part of the exercise are thumbnails. You can click the thumbnails for larger images.

Opening the Crossword Sample By Using the Windows Mobile 2003 Second Edition Emulator Image for Pocket PCs

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.

To configure Platform Manager to use the Windows Mobile 2003 Second Edition emulator image for Pocket PCs

  1. Start Microsoft® eMbedded Visual C++® version 4.0.
  2. On the Tools menu, click Configure Platform Manager.
  3. Expand Pocket PC 2003, and then click Add Device.
  4. Type Pocket PC 2003 SE Emulator for the name for the new device.
  5. Click Properties.
  6. Change the Transport to TCP/IP Transport for Windows CE.
  7. Make sure the Startup Server is Emulator Startup Server.
  8. Next to Emulator Startup Server, click Configure.
  9. In the Image list, select WWE PPC 2003 SE.
  10. Close all three dialog boxes by clicking OK in each dialog box.
  11. Open the workspace CrosswordSample.vcw from the BaseSample folder.
  12. On the Windows CE Configuration toolbar, select Win32 (WCE emulator) Debug as the active configuration and Pocket PC 2003 SE Emulator as the default device.

After you have configured the Platform Manager to use the Windows Mobile 2003 Second Edition emulator image for Pocket PC, you need to run the Crossword sample.

To run the Crossword sample

  1. Press F5 to run the sample. If any Find Local Modules dialog boxes appear, click Cancel.

  2. Peruse through the source code to get a feel for how the sample is implemented.

  3. In the emulator, press F2 to observe how it handles portrait/landscape orientation changes.

  4. Notice the following problems, as shown in the following illustration.

    • 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.

Adjusting the Background, Text Box Area, Hint Area, and Tools Command Dialogs

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

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

To extend the background

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

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

  3. In the OnPaint handler, 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.

To widen the text box area

  • 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.

To move the hint area to the side of the screen

  1. Add the following function.

    BOOL InWideMode()
    {
        int height = GetSystemMetrics(SM_CYSCREEN);
        return (height < 320) ? TRUE : FALSE;
    }
    
  2. 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;
    
  3. Replace the existing RECT.r declaration 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.

To move the downward/across arrow

  • In OnPaint, add the following code.

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

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

To relayout the dialogs

  • Add UIHelper.CPP, UIHelper.H, and shguim.h to your project in the LandscapeAware folder. (UIHelper.CPP and UIHelper.H provide the RelayoutDialog helper function you will use.)

You need to create dialog templates for the device to use for wide mode.

To create dialog templates for wide mode

  1. Create a new dialog. (On the Insert menu, click Resource, select Dialog in the Resource type list, and then click New).

  2. Remove the default OK and Cancel buttons.

  3. Right-click the dialog, and then click Properties.

  4. In the ID box, type IDD_TOOLS_OPTIONS_1_WIDE.

  5. Resize 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.)

  6. In the Resource pane on the right, double-click IDD_TOOLS_OPTIONS_1.

  7. Press CTRL+A to select all items, and then press CTRL+C to copy items to the Clipboard.

  8. Open IDD_TOOLS_OPTIONS_1_WIDE, and then press CTRL+V to paste the items.

  9. Resize and rearrange the items so they fit the new dialog, as shown in the following illustration.

    You may find it easier to rearrange items by temporarily enlarging the size of the dialog, and then resizing it back to 187 × 91 DLUs after you have finished shuffling the items around.

  10. For IDD_TOOLS_OPTIONS_1_WIDE, rename the three dialog controls that have IDs equal to 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.

  11. Repeat step 10 for IDD_TOOLS_OPTIONS_1, being sure to give each control the same ID as its corresponding landscape control.

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

To add WM_SIZE handler to dialogs

  1. In CrosswordSample.cpp, ToolsOptions1.cpp, and ToolsOptions2.cpp, add the following statement.

    #include uihelper.h
    
    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;
    
  2. 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;
    
  3. 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;
    
  4. In CrosswordSample.h, add the following line in the Function Prototypes section.

    BOOL                 InWideMode();
    
  5. Verify that the entire application handles landscape/portrait orientation changes.

Part 2: Fixing DPI-Awareness Problems

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

In this part of the exercise, you will perform the following tasks:

  • Open the Crossword sample by using the WWE PPC 2003 SE VGA emulator
  • Adjust the hard-coded constants, background, Enter button, hint area, and dialogs

Some illustrations in this part of the exercise are thumbnails. You can click the thumbnails for larger images.

Opening the Crossword Sample By Using the WWE PPC 2003 SE VGA Emulator

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 disable HIDPI emulation

  1. On the Insert menu, click Resource.
  2. Click Custom.
  3. Type CEUX for the resource type.
  4. Set the resource data to 01 00.
  5. In the Resources pane, right-click the new resource, and then click Properties.
  6. Rename the item 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.)
  7. Clear the External file check box.

Next, you need to switch to the 192–DPI emulator image, the WWE Pocket PC 2003 SE VGA emulator.

To run the Crossword sample on the WWE PPC 2003 SE VGA emulator

  1. On the Tools menu, click Configure Platform Manager.

  2. Click Add Device.

  3. Type a name for the new device (for example, Pocket PC 2003 SE VGA Emulator).

  4. Click Properties.

  5. Change the Transport to TCP/IP Transport for Windows CE.

  6. Make sure the Startup Server is Emulator Startup Server.

  7. Click Configure.

  8. In the Image list, select WWE PPC 2003 SE VGA.

  9. Close all three dialog boxes by clicking OK in each dialog box.

  10. Select the new device from the Windows CE Configuration toolbar.

  11. Press F5 to run the application. You should see the following illustration.

    Click here for larger image

    Click the thumbnail for a larger image.

  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.
    • There is an unnecessary horizontal scroll bar in the font picker list.

Adjusting the Hard-Coded Constants, Background, Enter Button, Hint Area, and Dialogs

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 to adjust 96-DPI coordinates to any arbitrary screen resolution.

To scale hard-coded constants

  1. Add a call to HIDPI_InitScaling at the top of InitInstance.

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

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

    • CreateCrossword: Modify CreateCompatibleBitmap and the RECT r declration. 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;
      
    • 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.

    • OnLButtonDown: Modify the received coordinates and InvalidateRect.

    • 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.

    • OnMessage: 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).

    • InWideMode: There are more than 320 vertical pixels in HIDPI landscape mode.

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

    Click here for larger image

    Click the thumbnail for a larger image.

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

To extend the background image

  1. On the Insert menu, click Resource, click Import, navigate to the HidpiAware folder, and then select Background1_hidpi.bmp.

  2. On the Properties tab, rename the file to IDB_BACKGROUND_1_HIDPI. You will use this bitmap when the screen resolution is 192 DPI or higher.

  3. In OnPaint, 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.

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);
    

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 needs to be easier to see on an HIDPI device's screen.

To enhance the divider line and the hint area's border

  1. 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.
    
  2. Add the following lines after the two DeleteObject calls.

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

Fifth, 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 control panel) by using SHGetUIMetrics defined in shguim.h.

To change the text size for the hint area

  1. 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);
    }
    
  2. In InitInstance, remove the CreateFontIndirect call, and then replace it with a call to the CreateHintFont function you just created.

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

  3. Near the top of Crossword.CPP, declare WM_SH_UIMETRIC_CHANGE as a global.

  4. At the top of InitInstance, initialize WM_SH_UIMETRIC_CHANGE by calling RegisterWindowMessage.

    UINT WM_SH_UIMETRIC_CHANGE; // define this as a global.
    :
    :
    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();
    }
    
  5. Run the Crossword sample to see that more of the problems have been fixed, as shown in the following illustration.

    Click here for larger image

    Click the thumbnail for a larger image.

Sixth, 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.

To fix the Background Picker dialog

  • 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);
    

Finally, in ToolsOptions2.cpp, there is a hard-coded constant in the WM_MEASUREITEM handler that represents the height of each element of the list box, which needs to be scaled vertically.

To fix the Font Picker dialog

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

    lpmis->itemHeight = SCALEY(40);
    

    There is also a hard-coded 15 in the WM_INITDIALOG handler, which refers to the width of a scroll bar.

  2. Use GetSystemMetrics to get the true width of the vertical scroll bar.

  3. 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);
    

Summary

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

In this exercise, you performed the following:

  • Fixed orientation-awareness problems
  • Fixed DPI–awareness problems

Appendix A: Installation Instructions

If you would like to perform this Windows Mobile exercise, you will need to download the Mobile Application Development Toolkit.

After this toolkit has been downloaded, you need to verify that your computer has the correct applications installed in the CORRECT ORDER. If the applications are not installed in the order specified, this exercise may not function properly.

To install the applications in the correct order

  1. Click Start, point to Programs, point to Mobile Application Development Toolkit, and then click Index.html.
  2. In the left navigation panel, click Developer Tools and SDKs.
  3. Follow the instructions on the Developer Tools and SDKs page.