Applications: Creating a simple UI application for Windows Mobile 6 using Visual Studio 2005: Part 3

So now our program displays the system width and height in a Message box. Let us see how this message box evolves into a Dialog box.

 

Instead of displaying the system metrics in a Message box, we will display the same information in a full screen dialog box. The dialog box will have a menu bar, with the left soft key as "Back" and the right soft key as "Refresh". We’ll see what "Refresh" will do later.



First lets add the dialog box resource to our project. You can add a new dialog using the resource editor. Go to Resource view, right click on Dialog and "Add Resource..", from the list select POCKETPC_PORTRAIT dialog. This will add a new dialog resource to the project. Rename the dialog id to IDD_DIALOG_SYSMETRIC and change the title to "System Metric". Now add four static texts to the dialog. 2 containing the text "System Width:" and "System Height:" and the other two will contain the actual numbers.  Lets name the controls which will contain the width and height as IDC_STATIC_SYSWIDTH and IDS_STATIC_SYSHEIGHT. The dialog will now look like:

Lets create the menu bar for our new dialog. Add the following piece of code to HelloWorldppc.rc2:


IDR_MENU_SYSMETRIC SHMENUBAR DISCARDABLE

BEGIN

    IDR_MENU_SYSMETRIC,

    2,



    I_IMAGENONE, IDM_BACK, TBSTATE_ENABLED, TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE,

    IDS_BACK, 0, NOMENU,

   

    I_IMAGENONE, IDM_REFRESH, TBSTATE_ENABLED, TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE,

    IDS_REFRESH, 0, NOMENU,

END

This defines the new menu bar, IDR_MENU_SYSMETRIC, for an explanation of the above see my previous post "Applications: Creating a simple UI application for Windows Mobile 6 using Visual Studio 2005: Part 2"



This menu bar does not have any popup menu. You can see that both the entries are TBSTYLE_BUTTON and NOMENU.



Now you need to #define the entries for IDR_MENU_SYSMETRIC, IDM_BACK, IDS_BACK, IDM_REFRESH, IDS_REFRESH in resourceppc.h. Make sure that the numbers you assign to these macros are unique.



The dialog and the menu bar are ready, now we just need to call DialogBox in our main code. So, in HelloWorld.cpp, replace the call to MessageBox under IDM_SYSTEM_METRIC with:



DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG_SYSMETRIC), hWnd, SysMetricDlgProc);



The first parameter contains the instance handle of the module which contains the resource template of the dialog. The MAKEINTRESOURCE macro converts the number IDD_DIALOG_SYSMETRIC to a resource type, required by DialogBox. Third parameter specifies the parent window of the Dialog box. And the last parameter is the callback function which will handle the messages of our dialog.



Also, forward declare the callback function as follows:



BOOL CALLBACK SysMetricDlgProc(HWND, UINT, WPARAM, LPARAM);





Here is the code for the Dilaog proc, SysMetricDlgProc

BOOL CALLBACK SysMetricDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)

{

    int wmId, wmEvent;



    switch (uMessage)

    {

        case WM_INITDIALOG:

            {

                // Create a Done button and size it. 

                SHINITDLGINFO shidi;

                SHMENUBARINFO mbi;



                shidi.dwMask = SHIDIM_FLAGS;

                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;

                shidi.hDlg = hDlg;

                SHInitDialog(&shidi);





                memset(&mbi, 0, sizeof(SHMENUBARINFO));

                mbi.cbSize     = sizeof(SHMENUBARINFO);

                mbi.hwndParent = hDlg;

                mbi.nToolBarId = IDR_MENU_SYSMETRIC;

                mbi.hInstRes   = g_hInst;



                if (!SHCreateMenuBar(&mbi))

                {

                    printf("SHCreateMenuBar failed err code:%d\n", GetLastError());

                }

                else

                {

                }



                UpdateSystemMetric(hDlg);

            }

            return TRUE;



        case WM_COMMAND:

            wmId = LOWORD(wParam);

            wmEvent = HIWORD(wParam);



            switch(wmId)

            {

                case IDOK:

                    EndDialog(hDlg, uMessage);

                    return TRUE;



                case IDM_REFRESH:

                    //MessageBox(NULL, L"Pressed refresh",L"Info", MB_OK);

                    UpdateSystemMetric(hDlg);

                    return TRUE;



                case IDM_BACK:

                    EndDialog(hDlg, uMessage);

                    return TRUE;

            }

            break;



        case WM_CLOSE:

            EndDialog(hDlg, uMessage);

            return TRUE;



    }



    return FALSE;

}





In WM_INITDIALOG, we initialise the dialog as full screen and also add the menu bar. The UpdateSystemMetric() function updates the dialog with the system width and system height.


BOOL UpdateSystemMetric(HWND hDlg)

{

    int width = -1, height = -1;



    width =  GetSystemMetrics(SM_CXSCREEN);

    height = GetSystemMetrics(SM_CYSCREEN);



    SetDlgItemInt(hDlg, IDC_STATIC_SYSWIDTH, width, FALSE);

    SetDlgItemInt(hDlg, IDC_STATIC_SYSHEIGHT, height, FALSE);



    return TRUE;

}

UpdateSystemMetric() function uses the same api to get the screen width and height. And it uses SetDlgItemInt() api to set the contents of IDC_STATIC_SYSWIDTH static control to width, and the same for height.





Now we handle IDM_BACK and IDM_REFRESH under WM_COMMAND. IDM_BACK simply ends the dialog and returns to the main screen, IDM_REFRESH, re-calculates the width and height and updates the values in the UI.





Now when we select the "System Metric" menu item, the dialog box will show up as below:





and when we switch the emulator to landscape mode and hit "Refresh", the values are updated in the UI.

Part 1
Part 2

Applications: Adding Image to your application

Well, back from work and I was sitting idle. I thought the first screen of my application looks a bit stale really, so, I decided to add a small picture to the main screen.

 

Adding an image resource to your project:
This is really easy.

 

1. Copy the bitmap file into you project directory.
2. Go to Resource View, right click and select "Add Resource.."
3. Select "Bitmap" and click on "Import.."
4. Point it to the file you copied in step 1
5. Note the ID assigned to your bitmap. In my case it was IDB_BITMAP1

 

And thats it! The resource is now present within your project, you just need to write code to use it.

 

In my WndProc function, just before breaking under WM_CREATE, I make a call to this function AlignComponents(), which looks like this:

void AlignComponents(HWND hWnd)

{

    RECT rect;

    int imageWidth = 32, imageHeight = 32;

    int imageX = 0, imageY = 0;

    int captionX = 0, captionY = 0;

    const int offset = 25;

    TCHAR captionText[] = TEXT("TechTwaddle\n Copyright (c) 2009");



    GetClientRect(hWnd, &rect);



    imageX = rect.right/2 – imageWidth/2;

    imageY = rect.bottom/2 – imageHeight*2;

   

    HWND hStatic = CreateWindowEx (0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_BITMAP,

                                    imageX, imageY, imageWidth, imageHeight, hWnd, 0, g_hInst, NULL);



    if (hStatic)

    {

        HBITMAP hImage = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));

        SendMessage(hStatic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hImage);

    }



    captionX = rect.left + offset;

    captionY = imageY + imageHeight + 3;



    HWND hStaticCaption = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_CENTER,

                                    captionX, captionY, rect.right – offset*2, 30, hWnd, 0, g_hInst, NULL);



    if(hStaticCaption)

    {

        SendMessage(hStaticCaption, WM_SETTEXT, 0, (LPARAM)captionText);

    }

}

The above code gets the client rect of the main window and calculates the X and Y position of the image based on the rect. I then use CreateWindowEx to create a static control which will show my bitmap. Use LoadBitmap to load the bitmap and get a handle to it and then send STM_SETIMAGE message, passing the bitmap handle so that the image can be shown in the control. Note that the static control was created using SS_BITMAP as one of its styles. Without this style the static control will not show the image.


Then I create another static control positionally relative to the image. And send WM_SETTEXT message to it to set the text. Note that the SS_BITMAP style is not mentioned in this static control. I had just copy pasted the code from above with SS_BITMAP set and sat wondering for a few minutes why the text was not showing up.
 

Anyways, my main screen now looks like:

A lot better than before. I thought I’ll change the font of the text but was too lazy. Maybe tomorrow.

Applications: That thing called Gradience

I added a little gradient fill to the main screen. And it is quite easy really. By the way, here’s what my main screen looks like now, cool eh (:

Lets look at GradientFill first. The function is prototyped as:

 

BOOL GradientFill(HDC hdc, PTRIVERTEX pVertex, ULONG numVertex, PVOID pMesh, ULONG numMesh, ULONG mode);

 

The first parameter is the handle to the device context. The second parameter, pVertex, is a pointer to an array of TRIVERTEX structures. The third parameter contains the number of elements in the array pointed to by pVertex.

TRIVERTEX structure:

struct _TRIVERTEX

{

    LONG x;

    LONG y;

    COLOR16 Red;

    COLOR16 Green;

    COLOR16 Blue;

    COLOR16 Alpha;

} TRIVERTEX;

The structure defines a point x,y in the device context and its color. Alpha define the transparency of the point x,y. The points in the array should define the upper left and the lower right corners of the rectangle being filled. The fourth parameter pMesh points to a GRADIENT_RECT structure.

struct _GRADIENT_RECT

{

    ULONG topLeft;

    ULONG bottomRight;

} GRADIENT_RECT;

The GRADIENT_RECT structure specifies which entry in the array containing TRIVERTEX array is the upper left point and which is the bottom right point. The last parameter, mode, specifies whether the gradient should be filled horizontally, GRADIENT_FILL_RECT_H, or vertically GRADIENT_FILL_RECT_V.

 

Now to the static text control. I tried to make the control transparent but found out that it is not very straight forward. Tried to use SetBkMode(hdc, TRANSPARENT) and reurning NULL_BRUSH in response to WM_CTLCOLORSTATIC, didn’t work for me. So I removed the static control and used ExtTextOut to draw the text directly on the screen.

 

Here is the final piece of code. All the code goes in under WM_PAINT function. And g_hImageStatic is a global which contains window handle to the static control containing the image. I store it while creating the static control in AlignComponents(), check my previous post.

TRIVERTEX vert[2];

GRADIENT_RECT gradRect;

RECT rect;

TCHAR szStrLine1[] = TEXT("TechTwaddle");

TCHAR szStrLine2[] = TEXT("Copyright (c) 2009");



hdc = BeginPaint(hWnd, &ps);



GetClientRect(hWnd, &rect);



vert[0].x = rect.left;

vert[0].y = rect.top;

vert[0].Red = 0x0000;

vert[0].Green = 0x2000;

vert[0].Blue = 0xA000;

vert[0].Alpha = 0x0000;



vert[1].x = rect.right;

vert[1].y = rect.bottom;

vert[1].Red = 0x0000;

vert[1].Green = 0x8000;

vert[1].Blue = 0xFF00;

vert[1].Alpha = 0x0000;



gradRect.UpperLeft = 0;

gradRect.LowerRight = 1;



GradientFill(hdc, vert, 2, &gradRect, 1, GRADIENT_FILL_RECT_H);



if(g_hImageStatic)

{

    GetWindowRect(g_hImageStatic, &rect);



    SetTextColor(hdc, RGB(120, 160, 130));



    SetBkMode(hdc, TRANSPARENT);

    ExtTextOut(hdc, rect.left – 23, rect.bottom – 24, 0, NULL,

        szStrLine1, _tcslen(szStrLine1), NULL);

   

    ExtTextOut(hdc, rect.left – 37, rect.bottom – 11, 0, NULL,

        szStrLine2, _tcslen(szStrLine2), NULL);

}



EndPaint(hWnd, &ps);

Aloha!

Hi there, here goes my first post on geekswithblogs, so let me introduce myself. My name is Prabhu and I work on Windows Mobile technologies.  I love programming and solving puzzles. My work involves working with BSP’s, porting maintaining tweaking device drivers and sometimes writing small utility applications at work. I work for a large corporation which builds and sells Windows Mobile phones for a living and does a lot of other stuff too!

So thats that about me and what I do. This is the first time I am blogging and I thought writing a blog is a great way to learn and share new things. After all, we are all here to make things better, well, at least some of us are (-:I hope to update this blog quite regularly and write about things I already know and things I learn.And btw, what a nice day I choose to start blogging! (;

Disclaimer:
The views expressed in this blog are mine and in no way represent my employer. The author cannot be held responsible for any damages caused due to the information provided here, use it at your own risk.