Download source files - 31 Kb
The problem
Once I had written a control
panel applet to control my NT Service I though that it might be handy to be able to
control the service from an icon in the task tray. Since the result of clicking an icon in
the task tray is often similar to running up a control panel applet I hoped to be able to
reuse much of my code. The end result was a framework for task bar applets and a derived
class that managed control panel applets. I then just let the task bar applet load my
control panel applet and provided access to it from the task tray with no code changes
required. The resulting classes can load any control panel applet into the task tray, just
supply the file name and applet index...
What's so special about a task tray applet anyway?
To put an icon in the task tray all an application has to
do is call Shell_NotifyIcon()
to supply the icon, tool tip, callback message and a window
to send it to. Making an application that's just a task tray applet is just a case of
creating a hidden window, calling Shell_NotifyIcon()
and processing the messages. The
messages are your standard mouse click type messages. It's not that difficult but it takes
a bit of time.
What does CJBTaskTrayApplet give you?
Creating a hidden window with a WndProc that responds to
Task Tray applet messages correctly and does all the right things can be cumbersome if all
you want to do is pop up a dialog box app from the task tray. The framework lets you
derive from an base class that handles all of that stuff for you. All you need to do is
override a few virtual functions and you have a dialog popping up! CJBTaskTrayApplet
also
gives you multiple applets in one exe (with multiple separate icons in the task tray) and
automatic right button menu handling.
Your derived class
To implement your task tray applet you need to do the
following:
- include "TaskBarApplet.hpp" and
"TaskBarAppletMain.hpp" the later gives you a
WinMain()
implementation.
- publicly derive a class from
CJBTaskTrayApplet
.
- implement the pure virtual functions
GetTrayTip()
and
GetIcon() to supply the basic require by the framework.
- handle
OnLeftDoubleClick()
to do something when a user
double clicks on you. This is where you would generally show a dialog or something.
- create an instance of your applet at global scope.
- link with TaskBarApplet.cpp.
If you wish you can also:
- handle
GetMenu()
, HandleMenuCommand()
and ReleaseMenu()
to
customise the menu you get - By default you get a menu with Close on it in response to
OnRightButtonUp()
.
- handle
InitInstance()
to allocate resources at start up.
- handle any or all of
OnLeftButtonDown()
, OnLeftButtonUp()
,
OnRightButtonDown()
, OnRightButtonUp()
, OnLeftDoubleClick()
, OnRightDoubleClick()
and
OnMouseMove()
to customise your applet's user interface.
When I implemented CJBControlPanelApplet I placed multiple applets
in a single DLL. After writing much of the same "base class has a static list"
code again for the task tray applet I decided to try and create a common base class that
incorporated and hid all of the "I'm a class whos instances are in a static
list" functionality. It seemed simple enough until I realised that to be type-safe
and remove heaps of casts in the derived class the base class needed to know about the
derived class. Without a second thought I templatised the base class and had the derived
class inherit from it thus:
class CTaskBarApplet : public CLinkedClass<CTaskBarApplet>
Then I thought about it and it seemed like a really
odd thing to do. It worked great and gave no warnings but was it nice? Since then I've
spoken to several people and read Stroustrup 3e
(and he thinks it's a "Good Thing") and now I like the idea. It did seem very
odd at first though...
Once I had a standard way to link instances of a class
together I needed a way to navigate this list at run-time. After several aborted attempts
I ended up with an STL-style iterator. This lets you do things like:
for (Iterator addIt = Begin(); addIt != End(); addIt++)
{
addIt->AddIcon();
}
Of course, having moved the common functionality out into
a base class I never got around to using the base class in
CJBControlPanelApplet
,
but I've used it in plenty of other places.
The CPlTrayApplet and
CPlAppletClient classes
By this stage I had a task tray framework but I still
needed to somehow reuse the code in my control panel applet. Since a .cpl is just a DLL,
and since I'd worked out how CPlApplets worked when I wrote
CJBControlPanelApplet
I decided to write a wrapper class that could load a control panel applet, send it all the
right messages to initialise it and then run the applet. Once I had CJBCPlAppletClient I
derived a CJBCPlTrayApplet from CJBTaskTrayApplet and wired the two together. The end
result was a collaboration something like this:
CJBCPlAppletClient
isn't linked in any way to the task
tray classes. It's just used by CJBCPlTrayApplet
. It can be used anywhere you want to
access a control panel applet programmatically.
Using MFC with the framework
When I started looking at task tray applets I tried to
write one with MFC. I've seen it done but you have to do a fair bit of work to get it to
work right. Message map entries for the callback message, preventing the CWinApp's main
window from popping up briefly, etc, etc. I didn't need MFC so I wrote the straight Win32
solution. Then I wanted a single complex dialog in a task tray applet and I wanted to use
MFC for the dialog and my framework for the applet... It all went well until I ran it and
the dialog died with assertion failures, I didn't have a CWinApp derived class and it was
a serious requirement... Since I didn't have time to convert my applet framework to a
CWinApp derived framework I cheated. Since all that is required is that a CWinApp object
exists so that the dialog has a message pump to use I stuck a vanilla CWinApp
object
inside WinMain and called AfxWinInit()
directly and everything worked fine...
An #ifdef around this version of WinMain()
means that at the flick of a #define
you can have either
MFC enabled or lean and mean versions of your task tray applets.
I really keep meaning to sit down and make
CJBTaskTrayApplet a real CWinApp derivative, but, this works...
See article on Len's homepage for the latest updates.