How to change the text of a softkey menu?

To change the text of a softkey menu item, all you have to do is pretend that they are buttons. Well, they are specialized buttons in any case. So in order to change its property (in this case the ‘text’) you will have to use the TBBUTTONINFO structure along with TB_GETBUTTONINFO and TB_SETBUTTONINFO messages. Lets dive into the code:



Note: Error checking is omitted for obvious reasons.



    TCHAR szText[128] = TEXT("");

    TBBUTTONINFO tbi;

    ZeroMemory(&tbi, sizeof(tbi));



    tbi.cbSize = sizeof(tbi);

    tbi.dwMask = TBIF_TEXT | TBIF_COMMAND;

    tbi.pszText = szText;

    tbi.cchText = sizeof(szText);

    tbi.idCommand = IDM_MARK_UNMARK;



    SendMessage(g_hWndMenuBar, TB_GETBUTTONINFO, (WPARAM)IDM_MARK_UNMARK, (LPARAM)&tbi);



    if (!wcscmp(szText, L"Mark"))

    {

        wcscpy(szText, L"Unmark");

    }

    else

    {

        wcscpy(szText, L"Mark");

    }



    SendMessage(g_hWndMenuBar, TB_SETBUTTONINFO, (WPARAM)IDM_MARK_UNMARK, (LPARAM)&tbi);

So we fill up the TBBUTTONINFO structure and then send TB_GETBUTTONINFO message to the menu bar. After this call returns, szText will contain the text of the menu item. We switch the contents of szText and then send a TB_SETBUTTONINFO message. And the text on the menu item changes.

 

Couple of seasons back I was working on an application which let the user "Mark" and "Unmark" dates on a calendar control. It was for a smartphone, so I thought this technique is quite handy and just qualifies for a post.



Here is a video of it in action,

 

Displaying a context menu in your application

Context menus really add to the user experience. You may decide to display a context popup menu when the user taps-and-holds or double-taps on the touch screen. In this post we will see how a few lines of code enable you to do just that.

 

While reading about popup menus I came across the WM_CONTEXTMENU message. According to the windows mobile documentation that I referred this message is sent to a window when the user right clicks on the window’s client area. Since I haven’t yet seen a windows mobile device which supports right click functionality, I decided to leave it at that. And for this demonstration I display my context menu whenever the user double clicks (or double taps) on the screen. Remember that for your window to recieve a WM_LBUTTONDBLCLK message when the user double clicks, you should set the CS_DBLCLKS style of your WNDCLASS structure when you do a RegisterClass(). Without this your window will not recieve the double click message.

 

If you are comfortable with the gesture api’s, which Windows Mobile 6.5 supports, then you could use the GID_HOLD message. This message is sent when the user taps and holds on the screen for a specified amount of time.

 

When my main window recieves a WM_LBUTTONDBLCLK message, I call the following function:

//error checking omitted on purpose

POPUP_ITEMS pi[] = {

    {IDM_POPUP_ABOUT, TEXT("About")},

    {IDM_POPUP_EXIT, TEXT("Exit")}

};



int ShowContextMenu(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    POINT pt;

    pt.x = LOWORD(lParam);

    pt.y = HIWORD(lParam);



    ClientToScreen(hWnd, &pt);



    HMENU hMenu = CreatePopupMenu();



    for (int i=0; i< sizeof(pi)/sizeof(pi[0]); i++)

    {

        InsertMenu(hMenu, 0xFFFFFFFF, MF_BYPOSITION | MF_STRING | MF_ENABLED, pi[i].id, pi[i].szText);

    }



    return TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD, pt.x, pt.y, 0, hWnd, NULL);



}



The POPUP_ITEMS structure is defined to hold an integer and a string,



typedef struct _popup_items_

{

    int id;

    TCHAR szText[128];

} POPUP_ITEMS;

In the function, I first get the client co-ordinates of the point where the user double clicked and convert them to Screen co-ordinates. TrackPopupMenu() api, which displays the popup menu, expects the co-ordinates to be passed w.r.t the screen.

 

CreatePopupMenu() creates an empty menu. Use InsertMenu() api to add items into the menu and TrackPopupMenu() will display the popup menu.

 

When you set TPM_RETURNCMD flag in the call to TrackPopupMenu(), the function will return with the identifier of the menu item which the user selected. If the user clicks outside, without selecting anything, then the function simply returns zero. These functions are simple to use and the MSDN documentation will suffice. I have provided links to msdn above.

 

Finally, here is the video,

 

Applications: Painting problems with windowed directdraw app

Well, the painting problem that I mentioned in my last post is solved. Thanks to Joel for helping me out (:

 

When I look in retrospect, the answer was in the question all the time. Painting problem, WM_PAINT message. It was stupid of me to have missed that (; But looking at the bright side I have a much better understanding of how WM_PAINT works now. The only thing that I needed to do was to display the frame every time my window got a WM_PAINT message. So that our client area is re-drawn and is displayed correctly. Just call DisplayFrame() inside WM_PAINT. That is all.

 

Here’s a video of how things are working now, pretty smooth (:

 

How to embed an exe inside another exe as a resource and then launch it

While working on a utility project today, I stumbled upon wanting to embed an executable inside another executable. Sounds fun doesn’t it? And what is even more fun is to be able to launch the embedded exe!

 

Basically, here’s how it works. You embed Foo.exe inside Bar.exe. And by embed I mean, add Foo.exe as a resource in Bar.exe. And then from Bar.exe’s code, you can launch Foo.exe using CreateProcess().

 

So before answering the "Why?" lets answer the "How?"

 

Rename Foo.exe to Foo.txt. We do this just to be safe and to prevent the resource compiler (manager) from throwing unwanted errors. Now add Foo.txt as a normal resource in Bar.exe. Create an entry in Bar.exe’s resource script as below:



IDR_FOO     RCDATA       "Foo.txt"

And of course, you need to #define IDR_FOO in the resource header file. Just make sure its a unique value.

 

The steps are:

 

1) From within Bar.exe’s code, get a pointer to the first byte of Foo.txt
2) You should know the size of Foo.txt in bytes.
3) Using the pointer copy that many bytes into a separate file. ("\\Voila.exe")
4) Call CreateProcess() on "\\Voila.exe"
5) And voila!

 

Let’s dive into the code: (from the entry point of Bar.exe)



    HRSRC hrsrc = NULL;

    HGLOBAL hGlbl = NULL;

    BYTE *pExeResource = NULL;

    HANDLE hFile = INVALID_HANDLE_VALUE;

    DWORD size = 7168;//hardcoding the size of the exe resource (in bytes)



    hrsrc = FindResource(hInstance, (LPCWSTR)IDR_FOO, RT_RCDATA);

    if (hrsrc == NULL)

        return FALSE;



    hGlbl = LoadResource(hInstance, hrsrc);

    if (hGlbl == NULL)

        return FALSE;



    pExeResource = (BYTE*)LockResource(hGlbl);

    if (pExeResource == NULL)

        return FALSE;

   

    hFile = CreateFile(L"\\Voila.exe", GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);



    if (hFile != INVALID_HANDLE_VALUE)

    {

        DWORD bytesWritten = 0;

        WriteFile(hFile, pExeResource, size, &bytesWritten, NULL);

         CloseHandle(hFile);

    }





    int ret = CreateProcess(L"\\Voila.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi);

First, we find the resource using its resource identifier and then load it. Next we use LockResource() to get the pointer to the first byte of the resource data, which in this case would be the executable code. One downside, if you may say so, is that you need to know the exact size of the executable beforehand. Of course its easy to find out and I think its not a problem to hardcode because the size of the embedded executable won’t change. But if you still insist, then you can read it from the registry or a file or something.

 

Once you get the pointer, just copy all the bytes into another file, using WriteFile() API.

 

And finally do a CreateProcess() on the file you just created.

 

If you happen to know any alternate ways of doing this, please leave a message.

 

And coming to the important question of  "Why would any sane person want to embed an exe within an exe?" Well, you will have to wait till the next post to find out (;

 

Aloha!
 
Update:
I have changed the code above to copy all the 7168 bytes into "Voila.exe" in one go, instead of copying byte after byte. And just to be clear this is not a production code, it is just to demonstrate what can be done. Of course, creating the file "Voila.exe" in the root folder is not ideal and it may fail on many devices, and you also need to clean up by deleting the exe file, which can be done using the DeleteFile() API. Ideally, "voila.exe" should be created not in the root folder but instead using SHGetSpecialFolderPath() API passing for e.g. CSIDL_APPDATA. I left out all these details because I thought they were trivial for this post.
 

Applications: Appending text to a multiline Edit control

Today I was developing a small utility application where I needed to append some text to a multiline edit control. When I ran through the list of Edit control messages, I could not find any that could be used to append text. WM_SETTEXT was overwriting the previous contents. And doing a WM_GETTEXT first, appending to it and then WM_SETTEXT seemed like an overkill.

 

A little bit of searching led me to EM_REPLACESEL message. This message is used to replace the currently selected text in the edit control with the specified text but there’s a bit more to it. If there is no selected text in the edit control then the specified text is inserted at the current caret position. Since what I was using was a read-only edit control this works for me but I am not sure if it will work in all cases. I wonder if we need a SETCARETPOS message. So anyways, here’s how to append text in an edit control:



HWND hWndEdit = GetDlgItem(g_hDlg, IDC_EDIT_BOX);


SendMessage(hWndEdit, EM_REPLACESEL, (WPARAM)FALSE, (LPARAM)str);

Applications: Getting device information (embedded exe, rapi and more)

I was working on this application which runs on the PC and gets information about the windows mobile device which is connected to the PC over active sync. Now there are a couple of ways in which you can do this:

 

1) Create a PC app which gets all the information using RAPI api’s and displays it.

 

2) Create two binaries, one for the PC and one for the WinMob device. The PC app launches the WinMob app remotely using RAPI API’s, the WinMob app runs and writes all the information it can gather into a file on the device. The PC app then remotely reads this file and displays it to the user. The let down is that you need to create/maintain two binaries but the advantage is that you are not limited to the RAPI api’s, all device API’s are at your disposal.

 

3) Create a PC app which uses the embedded exe approach. This is a bit loony but works (; And yes, this is what I used in my app.

 

Let me digress here for people who do not know about RapiConfig.exe. RapiConfig is tool that ships with the Windows Mobile SDK’s and can be used to test out CSP’s (Configuration Service Provider) by using provisioning XML’s. RapiConfig works over Active Sync. So basically you run this tool from command line like:



C:\>RapiConfig /P config.xml



This command has the same effect as calling DMProcessConfigXML() API on the device with config.xml’s contents.

And then I came across the DeviceInformation CSP. The DeviceInformation service provider, as the name gives it away, provides information about the device and it can also be used to set information but we won’t go into that. So you give this service provider an XML like this:



<wap-provisioningdoc>

    <characteristic type="DeviceInformation">

       <parm-query name="OperatingSystem" />

       <parm-query name="OperatingSystemVersion" />

       <parm-query name="Product" />

       <parm-query name="ProductVersion" />

    </characteristic>

</wap-provisioningdoc>



and it spits out the response like this:



<wap-provisioningdoc>

    <characteristic type="DeviceInformation">

        <parm name="OperatingSystem" value="Microsoft Windows CE"/>

        <parm name="OperatingSystemVersion" value="Version 5.2 (Build 1235)"/>

        <parm name="Product" value="Windows Mobile® 6 Standard"/>

        <parm name="ProductVersion" value="CE OS 5.2.1235 (Build 17740.0.2.0)"/>

    </characteristic>

</wap-provisioningdoc>

So while developing this application I thought I could use some of RapiConfig. I shouldn’t have to write everything from scratch. I’ll just find out the SDK installation directory on the PC somehow, run this tool with my xml, parse the output xml and display it. But wait, what if the PC does not have the SDK installed. This simple application can’t depend on that. And that is when I had this crazy idea of embedding RapiConfig.exe within my application (; I wrote up a small test app to find out if this approach would even work. It did. You can get the details here in my previous post.

 

So thats the way it works. The application first extracts the embedded RapiConfig exe and the xml file into the current directory where the app is running from. Then it runs RapiConfig passing it the extracted xml. After RapiConfig completes executing and terminates, it creates a RapiConfigOut.xml in the same directory. The application opens this output xml, parses it and displays the information to the user.

 

If no device is connected then RapiConfig keeps waiting for the device to connect. To prevent this I used a few RAPI api’s in my application to find out if a device is connected using Active Sync, and only if it is, I launch RapiConfig exe. Otherwise, I display a message saying "Please make sure the device is connected.. blah bla"



A few points worth jotting down



(*) After extracting the RapiConfig exe, I was calling CreateProcess() on it to launch it:



wsprintf(lpCmdLine, L"/P %s", XML_FILENAME);

ret = CreateProcess(EXE_FILENAME, lpCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &sui, &pi);

But this didn’t work for me. The output XML was the exact same as the input xml, without any data. Launching the same extracted exe from command line worked! How was this possible? I tried a couple of things with SECURITY_ATTRIBUTES, inheriting handles etc but none worked. The documentation for CreateProcess() mentioned that the first parameter to it maybe be NULL, in that case lpCmdLine parameter should include the exe name. And when I gave this a try, it worked! So now you call CreateProcess() like below:



wsprintf(lpCmdLine, L"%s /P %s", EXE_FILENAME, XML_FILENAME);

ret = CreateProcess(NULL, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &sui, &pi);

(*) Some while back I had tried to fiddle with RAPI. I had tried to launch an exe on device remotely from the PC. But it didn’t work. Turns out I was calling CeRapiInit() incorrectly. I was calling it directly, not on any object but actually you need to call it on the IRapiSession interface. I should really be reading the documentation carefully before jumping to the code (;





Here are a few snapshots of the application, this is with craddled emulator.

Oh and the DeviceInformation doesn’t give you the resolution of the device. I used CeGetSystemMetrics() to get that part of information. And if there is any other details that you would want on this app, please leave a comment.

Good day!

CloseHandle(CreateThread(…));

I was browsing through some code when I came across a line of code which said:



CloseHandle( CreateThread(NULL, 0, PeculiarThreadProc, NULL, 0, NULL) );



And I thought, wait a minute, is he closing the handle to the thread immediately after its created. I pinged my TL and he said the thread will continue to run until it terminates. I thought I had read in documents that a thread will terminate when the last handle to it was closed and here it was the last and only handle to the thread. A closer look at the documentation made it clear,



"The thread object remains in the system until the thread has terminated and all handles to it are closed through a call to Closehandle."



I had missed the ‘and’. Anyways, to kill my curiosity for good I went ahead and tested the following code,



int WINAPI WinMain(HINSTANCE hInstance,

                   HINSTANCE hPrevInstance,

                   LPTSTR    lpCmdLine,

                   int       nCmdShow)

{

    CloseHandle( CreateThread(NULL, 0, PeculiarThreadProc, NULL, 0, NULL));


     while(1)

     {

          Sleep(1000);

     }



    return 0;

}



DWORD WINAPI PeculiarThreadProc(void *lPtr)

{

    DWORD tick;

    while(1)

    {

        tick = GetTickCount();

        printf("Tick count:%u\r\n", tick);

        Sleep(1000);       

    }

}



And as expected the thread kept running in all its glory.

Suppose we modify our program a little as below,

int WINAPI WinMain(HINSTANCE hInstance,


                   HINSTANCE hPrevInstance,

                   LPTSTR    lpCmdLine,

                   int       nCmdShow)

{

    CreateThread(NULL, 0, PeculiarThreadProc, NULL, 0, NULL);

    
     while(1)

     {

          //do nothing

          Sleep(1000);

     }



    return 0;

}



DWORD WINAPI PeculiarThreadProc(void *lPtr)

{

        printf("PeculiarThreadProc");

        Sleep(100);       

}


So in this case, the thread terminates but the handle to the thread is not closed. So does that mean that the thread object remains in the system? The answer is yes, and the thread object will exist in signaled state. And of course, if the main thread itself exits then all hope is lost (;

Applications: Creating a windowed DirectDraw application

In this post we’ll learn how to create a windowed directdraw application for windows mobile. A windowed application is not much different from a regular full-screen application but you have to be a little careful because your application has to co-exist with GDI, peacefully. Note that I have used bits of code from Joel’s code project article. Here is the code I call from WinMain():



    //Create the DirectDraw object the primary and auxillary surfaces

    if (InitDirectDraw(g_hWnd))

    {

        if (!CreateDirectDrawSurfaces(g_hWnd))

        {

            printf("CreateDirectDrawSurfaces failed!\r\n");

            FreeDirectDrawResources();

            return FALSE;

        }

    }

    else

    {

        printf("InitDirectDraw failed!\r\n");

        FreeDirectDrawResources();

        return FALSE;

    }



    printf("Checkpoint 1: InitDirectDraw and CreateDirectDrawSurfaces succeeded\r\n");



    //Initialize the surfaces with the image

    if (!InitSurfaces())

    {

        printf("InitSurfaces failed!\r\n");

        return FALSE;

    }

We will see what each one of the functions does, one by one.



BOOL InitDirectDraw(HWND hWnd)

{

    HRESULT result;



    result = DirectDrawCreate(NULL, &g_ddraw, NULL);

    if(SUCCEEDED(result))

    {

        g_ddraw->SetCooperativeLevel(hWnd, DDSCL_NORMAL);

        return TRUE;

    }



    printf("DirectDrawCreate failed!\r\n");

    return FALSE;



}

InitDirectDraw() is simple. The only difference here is the co-operative level, we use DDSCL_NORMAL unlike DDSCL_FULLSCREEN that we used before. The CreateDirectDrawSurfaces() function is below:



BOOL CreateDirectDrawSurfaces(HWND hWnd)

{

    DDSURFACEDESC ddsd;

    HRESULT hr;

    RECT rect = {0,};



    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));



    ddsd.dwSize = sizeof(DDSURFACEDESC);

    ddsd.dwFlags = DDSD_CAPS;

    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;



    //create the primary surface and set the clipper

    if ( (g_ddraw->CreateSurface(&ddsd, &g_primarySurface, NULL)) == DD_OK)

    {

        if ( (g_ddraw->CreateClipper(0, &g_primaryClipper, NULL)) == DD_OK)

        {

            if ( (g_primaryClipper->SetHWnd(0, hWnd)) != DD_OK)

            {

                printf("SetHWnd on primary clip failed hr=0x%x\r\n", hr);

            }

            if ( (g_primarySurface->SetClipper(g_primaryClipper)) != DD_OK)

            {

                printf("SetClipper failed hr=0x%x\r\n", hr);

            }

        }

        else

        {

            printf("CreateClipper failed hr=0x%x\r\n", hr);

            return FALSE;

        }

    }

    else

    {

        printf("CreateSurface failed hr=0x%x\r\n", hr);

        return FALSE;

    }



    //get the size of the client area (this will be used for backbuffer)

    GetClientRect(hWnd, &rect);



    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));



    //create the back buffer

    ddsd.dwSize = sizeof(DDSURFACEDESC);

    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;

    ddsd.dwWidth = rect.right – rect.left;

    ddsd.dwHeight = rect.bottom – rect.top;



    printf("screen width:%d,  height:%d\r\n", ddsd.dwWidth, ddsd.dwHeight);



    if ( (g_ddraw->CreateSurface(&ddsd, &g_backBuffer, NULL)) != DD_OK)

    {

        printf("CreateSurface on back buffer failed hr=0x%x\r\n", hr);

        return FALSE;

    }



    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));



    //create a surface to hold the marbel’s image

    ddsd.dwSize = sizeof(DDSURFACEDESC);

    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT;

    ddsd.dwWidth = 48; //BMP’s width

    ddsd.dwHeight = 48; //BMP’s height



    if ( (hr = g_ddraw->CreateSurface(&ddsd, &g_marbleSurface, NULL)) != DD_OK)

    {

        printf("CreateSurface marble surface failed hr=0x%x\r\n", hr);

        return FALSE;

    }



    return TRUE;

}

We create three surfaces here, a primary surface, a back buffer and an additional surface to hold our marble’s image. Another thing you’ll note is the presence of a DirectDraw Clipper object. The clipper object is used to manage clip lists. Basically, it helps to contain the drawings of our application within the client area of the window. We create the clipper using CreateClipper() method on the directdraw object. Next, we call SetHWnd() on the clipper object and pass in the handle to our main window. The clipper object will use this handle to find out the client area of the window and determine what part to clip. We set the clipper on the primary surface by using SetClipper() method of the directdraw surface object.

 

We create the back buffer next. You’ll see that we use the client area co-ordinates of the main window to decide the width and height of the back buffer. I did not do this initially and used 240 for width and 320 for height. This led to a problem, the image on the screen appeared skewed. The problem was that while Blt’ing, the destination rect on the primary surface was the size of the client area of the main window, which is a little smaller than 240×320 to make way for the task bar and the menu bar. When the back buffer, whose size is 240×320, was Blt onto a smaller surface the image was shrunk to fit. So the image appeared, well, shrunk.



BOOL InitSurfaces()

{

    HBITMAP hbm;



    //load the bitmap resource

    hbm = DDGetBitmapHandle(g_hInst, szMarbleBitmap);



    if (hbm == NULL)

    {

        printf("DDGetBitmapHandle failed\r\n");

        return FALSE;

    }



    DDCopyBitmap(g_marbleSurface, hbm, 0, 0, 48, 48);



    DeleteObject(hbm);



    return TRUE;

}

InitSurfaces() initializes the marble surface with the marble’s image. The DD*() function you see above are from ddutil.cpp which come with the SDK samples. Make sure that the null check "if (hbm == NULL)" isn’t written as "if(hbm = NULL)", that’ll save you a good hour of debugging (;

 

Next up are UpdateFrame() and DisplayFrame() functions. UpdateFrame() updates the back buffer by Blt()’ing the marble surface on it and DisplayFrame() displays the drawing by Blt()’ing the back buffer onto the primary surface.



void DisplayFrame()

{

    HRESULT hr;

    POINT p;

    RECT destRect;



    p.x = p.y = 0;



    ClientToScreen(g_hWnd, &p);

    GetClientRect(g_hWnd, &destRect);



    OffsetRect(&destRect, p.x, p.y);



    //blt the back buffer on the primary surface

    while (TRUE)

    {

        hr = g_primarySurface->Blt(&destRect, g_backBuffer, NULL, 0, NULL);



        if (hr == DD_OK)

            break;



        if (hr != DDERR_WASSTILLDRAWING)

            break;

    }

}

Again, we use ClientToScreen(), GetClientRect() and OffsetRect() functions to determine the position of the destination rectangle on the primary surface.

 

Another important thing is to prevent your application from drawing when it’s not supposed to, for e.g. when it doesn’t have the focus. So we need to maintain a state variable to determine whether we have focus or not and draw only when we our application has the focus. The following code is under the WndProc() function:



        case WM_ACTIVATE:

            SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);

            switch(LOWORD(wParam))

            {

                case WA_ACTIVE:

                case WA_CLICKACTIVE:

                    g_bHasFocus = TRUE;   

                    break;



                case WA_INACTIVE:

                    g_bHasFocus = FALSE;

                    break;



                default:

                    break;

            }

            break;

Pretty straight forward. Also,



        case WM_CANCELMODE:

            g_bHasFocus = FALSE;

            DisplayTextOnScreen(TEXT("Tap here to continue.."));

            //DisplayFrame();

            break;



        case WM_ENTERMENULOOP:

            g_bHasFocus = !(BOOL)wParam;

            break;



        case WM_EXITMENULOOP:

            g_bHasFocus = TRUE;

            break;

 
WM_ENTERMENULOOP and WM_EXITMENULOOP are called whenever user clicks on a menu or leaves a menu. In case, where the Start menu was pressed, my window did not receive these messages, instead it was sent a WM_CANCELMODE message. WM_CANCELMODE is sent whenever a dialog box or a message box is displayed. But when I click on the Start menu again to close the popup, no message was sent to the main window. So I decided to display a small text on the screen which says "Tap here to continue.." and it works pretty well. But there is one problem however, which I’ll come to in a moment. I use DisplayTextOnScreen() to draw text directly on the primary surface,



void DisplayTextOnScreen(TCHAR *tszStr)

{

    HDC hdc;

    RECT rc = {0,}, destRect = {0,}, srcRect = {0,};

    int nMsg;

    SIZE size = {0,};

    HRESULT hr;



    if (g_primarySurface->GetDC(&hdc) == DD_OK)

    {

        SetBkColor(hdc, RGB(115, 115, 115));

        SetTextColor(hdc, RGB(255, 255, 255));

        GetClientRect(g_hWnd, &rc);



        nMsg = lstrlen(tszStr);

        GetTextExtentPoint(hdc, tszStr, nMsg, &size);



        ExtTextOut(hdc,

            (rc.right – size.cx)/2 – 25,

            rc.bottom – size.cy*2,

            //(rc.bottom – size.cy)/2,// – (rc.bottom – size.cy)/4,

            ETO_OPAQUE,

            NULL,

            tszStr,

            nMsg,

            NULL);





        g_primarySurface->ReleaseDC(hdc);

    }

}

And of course, you also have to handle WM_LBUTTONDOWN or WM_LBUTTONUP events to find out if the user clicked on the client area and then change the state variable so the marble can continue to move again.

 

There you go.

 

All the while writing this post I was taking regular backups and the thing never crashed. One of the reasons that makes me love those Murphy’s laws (:

 

And as always, here are a few screenshots,





         

          

You can tell which screen is the problem, right? Do you need a clue? Well, the problem is that when I click on Start and then on Menu Screen 4 happens. And vice-versa too. I am yet to figure out how to solve this, I do however have a slight idea of why it happens. Feel free to help me out.

Update: The problem mentioned above (Screen 4) is solved. Look here.

Applications: Displaying a notification using SHNOTIFICATIONDATA

Sometimes you may want to display a notification to the user to inform her of an event or a pending task. You might say a message box will serve your purpose mostly, but there will be times when a message box is not the appropriate choice. For example when I was developing a call block application with a colleague of mine, we had to display the blocked call notification to the user, and the bubble notification was the most appropriate one. The notification shows an icon on the system tray and displays a message to the user. Lets see how we can do that.

To display a notification we use the SHNotificationAdd() function. This API takes a pointer to SHNOTIFICATIONDATA structure and this contains all the details about the notification that you want to show. The structure is defined as below,

typedef struct _SHNOTIFICATIONDATA{

  DWORD cbStruct;

  DWORD dwID;

  SHNP npPriority;

  DWORD csDuration;

  HICON hicon;

  DWORD grfFlags;

  CLSID clsid;

  HWND hwndSink;

  LPCTSTR pszHTML;

  LPCTSTR pszTitle;

  LPARAM lParam;

  union

  {

    SOFTKEYMENU skm;

    SOFTKEYNOTIFY rgskn[NOTIF_NUM_SOFTKEYS];

  }

  LPCTSTR pszTodaySK;

  LPCTSTR pszTodayExec;

} SHNOTIFICATIONDATA;


cbStruct: size of the structure in bytes

dwID: identifier of this notification, you can give any unique number here

npPriority: priority of the notification. This can take two values, SHNP_INFORM or SHNP_ICONIC

csDuration: duration in seconds, contains the number of seconds that the notification should be displayed for

hicon: handle to the icon which will be displayed on the tray

grfFlags: this contains some flags for the notification, this can take values like, SHNF_CRITICAL (displays the notification with a red border), SHNF_DISPLAYON (the device display is forced to turn on), SHNF_HASMENU (the notification is created with a softkey bar), SHNF_SILENT (does not vibrate or play a sound on the device) etc

clsid: defines a CLSID (a GUID) for the notification, you can create a GUID by using the GuidGen.exe tool which ships with Visual Studio

hwndSink: handle to the window which will receive messages from the notification (for e.g. if the user selects a menu item)

pszHTML: HTML content of the notification

pszTitle: contains the title of the notification

lParam: user defined param

skm: SOFTKEYMENU structure that defines menu for the softkey bar, the SHNF_HASMENU flag in grfFlags member must be set

rgskn: used if the notification is to have two softkeys

pszTodaySK: this contains the string that is displayed on the left softkey when csDuration seconds have elapsed, by default the text used is "Notification"

pszTodayExec: defines the name of the executable file which will run when the user presses the left softkey

The below program displays a notification for 5 seconds,



// {1F1C029E-95B2-4b5d-A2C5-AEF74BFCA979}

static const GUID CLSID_SHOW_NOTI =

{ 0x1f1c029e, 0x95b2, 0x4b5d, { 0xa2, 0xc5, 0xae, 0xf7, 0x4b, 0xfc, 0xa9, 0x79 } };



static HINSTANCE g_hInst = NULL;



int WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd)

{

   SHNOTIFICATIONDATA shNotiData = {0};



   g_hInst = hInst;



   shNotiData.cbStruct = sizeof(shNotiData);

   shNotiData.dwID = 1;

   shNotiData.npPriority = SHNP_INFORM;

   shNotiData.csDuration = 5; //time in seconds

   shNotiData.hicon = LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_ICON1));

   shNotiData.clsid = CLSID_SHOW_NOTI;

   //shNotiData.clsid = CLSID_SHNAPI_OemNotif1;

   shNotiData.grfFlags = SHNF_TITLETIME | SHNF_CRITICAL;//0;

   shNotiData.pszTitle = TEXT("My Notification");

   shNotiData.pszHTML = TEXT("<html><body>This program shows how to display a notification.</body></html>");

   shNotiData.rgskn[0].pszTitle = TEXT("Dismiss");

   shNotiData.rgskn[0].skc.wpCmd = 1001;

   shNotiData.pszTodaySK = TEXT("Alert!");



   Sleep(500);



   SHNotificationAdd(&shNotiData);



   return 0;



}

To remove a notification use SHNotificationRemove() api. And SHNotificationUpdate() and SHNotificationGetData(), update and get the information about a notification, respectively.

Below is a video of the above program, running.