Thursday, September 29, 2005

Writing a Today Screen plugin for Pocket PC

Writing a Today Screen plugin for Pocket PC

I'm one of two developers working with MobiDock which is a site developed for Windows Mobile devices. Anyway, our latest MobiDock-add-on was a Today Screen plugin for Pocket PC. It sounded as a fun project when I started working on it. It was some time since I last wrote any C++, but I thought "what the heck" (or something similarly ignorant in Swedish).

First I found out the examples on MSDN, which was OK. And later I found out that there were issues that were not covered by the MSDN articles. So I will try and focus on those.

What you do is that you download the example, lemme see if I can find it... .. yeah here it is Today the Pocket PC, Tomorrow the World!. That article gives you all code you need to begin your fine career as a Today Screen Plugin writer (awesome title there). Fine, now you've got it.

DllMain

DllMain() is the entry point for your DLL. It receives two messages, DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH. DO NOT IGNORE THESE! I know that it is perfectly possible to write a Today plugin not even declaring DllMain(), that however is highly NOT the recommended way to do it. DLL_PROCESS_ATTACH is called whenever a process wants to create an instance of our DLL. DLL_PROCESS_DETACH is called whenever the process is finished with it. Hence we should allocate whatever resources or whatever in the ATTACH message and then release those resources whenever the DETACH message is received.

I got this one tip from a site where one guy recommends that you unregistered your window class before you try to register it, or else your plugin would be invisible in some circumstances. He also states that he doesn’t know why this strange behaviour occurs, only that this is a solution to it. Well the bug occurs because of bad coding of course. He should have registered the window class in DllMain when the Attach message was received and then unregistered it in the Detach message and he wouldn’t have to solve it that way. More about pragmatic programming in the next section :D.

InitializeCustomItem

InitializeCustomItem is the second entry point. This one is special for Today items only. This is where we set in motion whatever resources we allocated in the DllMain. So DllMain is executed sending the Attach message first, then the InitializeCustomItem is called.

Why don’t we skip the DllMain and allocate all our resources in InitializeCustomItem and then release whatever resources in WM_TODAYCUSTOM_CLEARCACHE? Well the answer to that is actually whole other topic for code design (I urge all coders to read The Pragmatic Programmer). Let’s just say that it is good practice to keep allocation and deallocation of resources in the same method or at least level in code, it simplifies the search for eventual memory leaks.

WinProc

What will happen whenever we’ve set our window up correctly is that the first Window message that your message handler (WndProc) will receive a WM_TODAYCUSTOM_QUERYREFRESHCACHE message. This gives you a TODAYLISTITEM-pointer in the WPARAM. This struct has a member called cyp which you set to the intended height of your plugin. Now you return TRUE, telling the caller that you actually want to change the size of your plugin (The caller is the parent window, the today screen). Returning FALSE will tell the caller that nothings changed. Now remember that this message will be sent to your plugin every once in a while, and every time you return a TRUE your window will be invalidated, hence it will also receive a WM_PAINT message. This is an unwanted behaviour because it will force your plugin to re-render when it is not needed, and that might cause flickering behaviour or worst case it will slow down the whole machine or at least the today screen.

However, the WM_TODAYCUSTOM_QUERYREFRESHCACHE message is clearly useful. Let’s say that we are writing a today plugin that displays the current battery status. Every once in a while we need to check whatever battery status we’ve got in our machine. The WM_TODAYCUSTOM_QUERYREFRESHCACHE would be the perfect place to retrieve such data from the system and store it in local variables. Now that we have updated our internal variables we would of course want the display to reflect those changes so we could return a TRUE from the WinProc.

If we return a TRUE from the WinProc then we should absolutely check that our cyp were correct first, that it reflected the intended height of our plugin. A TRUE response from the WinProc will also result in an invalidation of the whole window. We might just want to update a small portion of it, perhaps something that displays the percentage in text or a small icon or, whatever. So instead we will create a RECT that holds the coordinates of what we want to update and just do a InvalidateRect() on that portion of our window. And then we return FALSE from the WinProc.

WM_PAINT

This is the message we all have been waiting for :). Basically you do whatever you usually do when you do your GDI Window painting. But there are some things worth putting down in this blog. First you might want your plugin to have transparent background. The above MSDN code was written for Pocket PC 2000 which utilized only solid background, thus doesn’t cover the transparency feature implemented since the Pocket PC 2002 version. How do we do it?

TODAYDRAWWATERMARKINFO dwi;

dwi.hwnd = hWnd;
dwi.hdc = hdc;
dwi.rc.left = ps->rcPaint.left;
dwi.rc.top = ps->rcPaint.top;
dwi.rc.right = ps->rcPaint.right;
dwi.rc.bottom = ps->rcPaint.bottom;

SendMessage(GetParent(hWnd), TODAYM_DRAWWATERMARK, 0, (LPARAM)&dwi);

That’s about it, we send a message to the today window, handing the RECT to be updated and our window handle and our device context (hdc).

The flicker drives me nuts!

Now that you’ve got your plugin up and running you may notice that it flickers each time you make a move on the today screen, and I do not mean only when your own plugin got focus but also when you step thru the other today items. How do we solve this? The only solution I ever came up with was to actually use an off screen DC.
How do we get an off screen DC you may ask? There is something called a Memory DC, they’re quite easy to use. What you do basically is just CreateCompatibleDC() and you pass on your current DC (the hdc you get when you BeginPaint()). This gives you a compatible device context, but it isnt associated with a bitmap that can handle more then black and white (which I find strange). So you’ve gotta create one your selfe. That is quite simple too, just CreateCompatibleBitmap(), pass on the hdc and the size of your window in a RECT and you have a nice bitmap. Then you just associate the new bitmap with your DC with SelectObject(). Here is some code to help you out.

HDC hdcOffscreen = CreateCompatibleDC(hdc);

HBITMAP hbOffscreen = CreateCompatibleBitmap(hdc,
windowSize.right - windowSize.left,
windowSize.bottom - windowSize.top);

HBITMAP hbOffscreen_Old = (HBITMAP) SelectObject(hdcOffscreen, hbOffscreen);

Now, promis me that you release theise resources when you are finnished with em =)

SelectObject(hdcOffscreen, hbOffscreen_Old);
DeleteObject(hbOffscreen);
DeleteDC(hdcOffscreen);

I suggest that you create the offscreen resources only once, and then you reuse it for all your WM_PAINT messages. This speeds up the painting a lot. Remember, if you do that, then you also must reinitialize them whenever you change the size of your plugin window. Also you must remember to release the resources whenever your plugin is unloaded, you can do that when the WM_TODAYCUSTOM_CLEARCACHE message arrives. I went as far as creating a DC of the background as well, it’s not necessary, it takes up some extra memory but results in a faster WM_PAINT.

I have probably forgotten stuff that I was intending to include. Yeah, there is one more thing that I use extensively. It’s quite a hassle to debug your today screen item. So I created a small app that updates the “Enabled” registry value for my plugin, simply setting it to 0 if it was 1 and the other way around. Then I do a:

SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0xF2, 0)

This message will force the Today screen to update it self, hence loading/unloading your plugin. Now if you keep that “toggle”-application as a part of your solution and set it as the “Startup project”, your plugin will load/unload whenever you press F5.

Next blog will probably be about Home screen plugins :D

No comments: