Applications: How to create a custom dialog box for Windows Mobile 6 (native)

Ashraf, on the MSDN forum, asks,

Is there a way to make a default choice for the messagebox that happens after a period of time if the user doesn’t choose (Clicked ) Yes or No buttons.

To elaborate, the requirement is to show a message box to the user with certain options to select, and if the user does not respond within a predefined time limit (say 8 seconds) then the message box must dismiss itself and select a default option. Now such a functionality is not available with the MessageBox() api, you will have to write your own custom dialog box.

Surely, creating a dialog box is quite a simple task using the DialogBox() api, and we have been creating full screen dialog boxes all the while. So how will this custom message box be any different? It’s not much different from a regular dialog box except for a few changes in its properties. First, it has a title bar but no buttons on the title bar (no ‘x’ or ‘ok’ button on the title bar), it doesn’t occupy full screen and it contains the controls that you put into it, thus justifying the title ‘custom’.

So in this post we create a custom dialog box with two buttons, ‘Black’ and ‘White’. The user is given 8 seconds to select one of those colours, if the user doesn’t make a selection in 8 seconds, the default option ‘Black’ is selected. Before going into the implementation here is a video of how the dialog box works;

Custom dialog box

To start off, add a new dialog resource into your application, size it appropriately and add whatever controls you need to the dialog. In my case, I added two static text labels and two buttons, as below;

CustomDialog_rsrc

Now we need to write up the window procedure for this dialog, here is the complete function;

BOOL CALLBACK CustomDialogProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    int wmID, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    static int timeCount = 0;

    switch(uMessage)
    {
        case WM_INITDIALOG:
            {
                SHINITDLGINFO shidi;

                memset(&shidi, 0, sizeof(shidi));

                shidi.dwMask = SHIDIM_FLAGS;
                //shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
                shidi.dwFlags = SHIDIF_SIPDOWN | SHIDIF_EMPTYMENU;
                shidi.hDlg = hDlg;
                SHInitDialog(&shidi);

                SHDoneButton(hDlg, SHDB_HIDE);

                timeCount = 0;

                SetWindowText(GetDlgItem(hDlg, IDC_STATIC_TIME_REMAINING), L"Time remaining: 8 second(s)");
                SetTimer(hDlg, MY_TIMER, 1000, NULL);

            }
            return TRUE;

        case WM_COMMAND:
            {
                wmID = LOWORD(wParam);
                wmEvent = HIWORD(wParam);

                switch(wmID)
                {
                    case IDC_BUTTON_BLACK:

                        KillTimer(hDlg, MY_TIMER);
                        EndDialog(hDlg, IDC_BUTTON_BLACK);
                        break;
                    case IDC_BUTTON_WHITE:
                        KillTimer(hDlg, MY_TIMER);
                        EndDialog(hDlg, IDC_BUTTON_WHITE);
                        break;
                }
            }
            break;

        case WM_TIMER:
            {
                if (wParam == MY_TIMER)
                {
                    WCHAR wszText[128];
                    memset(&wszText, 0, sizeof(wszText));

                    timeCount++;

                    //8 seconds are over, dismiss the dialog, select def value
                    if (timeCount >= 8)
                    {
                        KillTimer(hDlg, MY_TIMER);
                        EndDialog(hDlg, IDC_BUTTON_BLACK_DEF);
                    }

                    wsprintf(wszText, L"Time remaining: %d second(s)", 8-timeCount);

                    SetWindowText(GetDlgItem(hDlg, IDC_STATIC_TIME_REMAINING), wszText);
                    UpdateWindow(GetDlgItem(hDlg, IDC_STATIC_TIME_REMAINING));
                }

            }
            break;
        case WM_PAINT:
            {
                hdc = BeginPaint(hDlg, &ps);

                EndPaint(hDlg, &ps);
            }
            break;

    }

    return FALSE;
}

The MSDN documentation mentions that you need to specify the flag WS_NONAVDONEBUTTON, but I got an error saying that the value could not be found, so we can ignore this for now. Next up, while calling SHInitDialog() for your custom dialog, make sure that you don’t specify SHDIF_DONEBUTTON in the dwFlags member of the SHINITDIALOG structure, this flag makes the ‘ok’ button appear on the dialog title bar. Finally, we need to call SHDoneButton() with SHDB_HIDE flag to, well, hide the Done button. The ‘Done’ button is the same as the ‘ok’ button, so this step might seem redundant, and the dialog works fine without calling SHDoneButton() too, but it’s better to stick with the documentation (; So you can see that we have followed all these steps above, under WM_INITDIALOG. We also setup a few things like a variable to keep track of the time, and setting off a one second timer.

Every time the timer fires, we receive a WM_TIMER message. We then update the static label displaying the amount of time left to the user. If 8 seconds go by without the user selecting any option, we kill the timer and end the dialog with IDC_BUTTON_BLACK_DEF. This is just a #define’d integer value, make sure it’s unique. You’ll see why this is important. If the user makes a selection, either Black or White, we kill the timer and end the dialog with the corresponding selection the user made, that is, either IDC_BUTTON_BLACK or IDC_BUTTON_WHITE.

Ok, so now our custom dialog is ready to be used. I invoke the custom dialog from a menu entry in the main window as below,

case IDM_MENU_CUSTOMDLG:
    {
        int ret = DialogBox(g_hInst, MAKEINTRESOURCE(IDD_CUSTOM_DIALOG), hWnd, CustomDialogProc);

        switch(ret)
        {
            case IDC_BUTTON_BLACK_DEF:
                SetWindowText(g_hStaticSelection, L"You Selected: Black (default)");
                break;
            case IDC_BUTTON_BLACK:
                SetWindowText(g_hStaticSelection, L"You Selected: Black");
                break;

            case IDC_BUTTON_WHITE:
                SetWindowText(g_hStaticSelection, L"You Selected: White");
                break;
        }

        UpdateWindow(g_hStaticSelection);
    }
    break;

So you see why ending the dialog with the corresponding value was important, that’s what the DialogBox() api returns with. And in the main window I update a static text label to show which option was selected.

I cranked this out in about an hour, and unfortunately don’t have time for a managed C# version. That will have to be another post, if I manage to get it working that is (;

How to: Show wait cursor in managed and native code

WaitCursor  Someone on the MSDN forum asked about how to show a wait cursor, like when your application is loading or performing some (background) task. It’s pretty simple to show the wait cursor in both managed and native code, and in this post we will see just how.

Managed Code (C#)

Set Cursor.Current to Cursors.WaitCursor, and call Cursor.Show(). And to come back to normal cursor, set Cursor.Current to Cursors.Default and call Show() again. Below is a button handler for a sample app that I made, (watch the video below)

private void button1_Click(object sender, EventArgs e)
{
    lblProgress.Text = "Downloading ether…";
    lblProgress.Update();

    Cursor.Current = Cursors.WaitCursor;
    Cursor.Show();

    //do some processing
    for (int i = 0; i < 50; i++)
    {
        progressBar1.Value = 2 * (i + 1);
        Thread.Sleep(100);
    }

    Cursor.Current = Cursors.Default;
    Cursor.Show();

    lblProgress.Text = "Download complete.";
    lblProgress.Update();
}

Native Code

In native code, call SetCursor(LoadCursor(NULL, IDC_WAIT)); to show the wait cursor; and SetCursor(LoadCursor(NULL, IDC_ARROW)); to come back to normal. The same button handler for native version of the app is below,

case IDC_BUTTON_DOWNLOAD:
    {
        HWND temp;

        temp = GetDlgItem(hDlg, IDC_STATIC_PROGRESS);
        SetWindowText(temp, L"Downloading ether…");
        UpdateWindow(temp);

        SetCursor(LoadCursor(NULL, IDC_WAIT));

        temp = GetDlgItem(hDlg, IDC_PROGRESSBAR);
        for (int i=0; i<50; i++)
        {
            SendMessage(temp, PBM_SETPOS, (i+1)*2, 0);
            Sleep(100);
        }

        SetCursor(LoadCursor(NULL, IDC_ARROW));

        temp = GetDlgItem(hDlg, IDC_STATIC_PROGRESS);
        SetWindowText(temp, L"Download Complete.");
        UpdateWindow(temp);
    }
    break;

Here is a video of the sample app running. First the managed version is deployed and the native version next,

Applications: Colliding Marbles in C Sharp

If you follow this blog, you know how much I love marbles. I was staying up for Microsoft’s It’s Time To Share event and I thought I’ll write up a C# version of Colliding Marbles. It’s a pretty straight forward port from the native version, the only major difference being in the drawing primitives. Video follows. The solution was created using Visual Studio 2008 and the source code is shared below.

Source Code: CollidingMarbles.zip [Shared on SkyDrive]

Video,