Currently viewing: GipsySoft » Front Page» Articles

Tray Message

In this article I'll describe some of the problems of, and solutions to, showing an arbitrary window from the system tray.

At the end you'll have a simple function to show any window from the tray area as well as a more general purpose function/component to show HTML messages from the system tray.

I needed this because I wanted to write an Add-In for a product I work on. The product is a rather fabulous PC based TV guide called DigiGuide. Right now DigiGuide only fully supports UK and Ireland but later this year it will provide a TV guide for the USA too!

The Add-In I wrote pops up a small MSN like window when new TV programmes start. Double-clicking on the Add-In's icon in the tray will display all current TV programmes. It's a very handy Add-In and can be found here - it's not quite finished yet because I want to add a few little extra features that people have requested. For details about writing Add-In's for DigiGuide see the DigiGuide Add-In page

I was impressed with John O'Byrne's CodeProject article Taskbar Notification dialog. It was that article that gave me the idea for the Add-In. John's article was a good example but had some things that didn't meet my needs; Doesn't work properly on multi-monitor systems and was not flexible enough to handle the data the way I wanted.

I wanted something very similar but I wanted some variation on the animation (for free if possible). I also wanted the popup windows to grow with their content so using a bitmap was not possible.

[TOP]

Screenshots

Below are some screenshots to keep you interested. Note that the content of the windows is HTML, not simple bitmaps BitBlt'd onto the window. The hyperlinks work, tooltips appear on them and you can control the size of window, content, colour and style from plain old HTML.

Note that in the HTML and mail demos I have forced a bitmap to be transparent - this is not standard HTML and is instead a QHTM extension to make life easier for use poor developers.

Business news

HTML

Got some mail

[TOP]


Where's the system tray?

The first problem is locating the system tray. For my purposes I can simply use the SHAppBarMessage function with the parameter of ABM_GETTASKBARPOS.

Because I wanted my little windows to popup up or slide into view I needed to know whereabouts the tray was. Having the co-ordinates isn't enough. I needed to know whether the tray was to the left or the top (people can and do move the Task Bar!).

To figure all of this out I created a reusable function, source code below:


static BOOL GetTaskBarPositionAndMetrics( TaskBarPosition &position
    , RECT &rcWorkArea )
//
//  Get the position of the task bar
//
//  Also, return some useful metrics for use later
//  (as we have them we may as well use them)
//
{
  BOOL bRetVal = FALSE;

  //
  //  Default to none just in case we fail.
  position = knTBPNone;

  //
  //  Find out where the taskbar is
  APPBARDATA abd ={ sizeof( APPBARDATA ) };
  if( ::SHAppBarMessage( ABM_GETTASKBARPOS, &abd ) )
  {
    //
    //  Just a reference to make the code read better
    const RECT &rcTaskBar = abd.rc;

    //
    //  Figure out the screen dimensions for the monitor that has the task bar
    if( ::GetDisplayWorkArea( rcTaskBar, rcWorkArea ) )
    {


      //
      //  If the task bar is at the top or bottom then the icon tray is on
      //  the right. If it's on the left or right then the icon tray is at
      //  the bottom.
      const UINT uTaskBarHeight = rcTaskBar.bottom - rcTaskBar.top;
      const UINT uTaskBarWidth = rcTaskBar.right - rcTaskBar.left;

      //
      //  From here we can't fail...
      bRetVal = TRUE;

      if( uTaskBarHeight > uTaskBarWidth )
      {
        //
        //  Taskbar is vertical
        if( rcTaskBar.right > rcWorkArea.right )
        {
          position = knTBPRight;
        }
        else
        {
          position = knTBPLeft;
        }
      }
      else
      {
        //
        //  Taskbar is horizontal
        if( rcTaskBar.bottom > rcWorkArea.bottom )
        {
          position = knTBPBottom;
        }
        else
        {
          position = knTBPTop;
        }
      }
    }
  }
  return bRetVal;
}

This function is fairly simple. It return TRUE if it is successful and fills out the reference parameters accordingly. Here's the position information it returns:
enum TaskBarPosition { knTBPNone = 0, knTBPBottom, knTBPTop, knTBPLeft, knTBPRight, knTBPMax };

Notice that this function uses another called GetDisplayWorkArea. This is a multi-monitor safe method of getting the work area of the display. It even works when you have AppBar's all over the screen.

I did consider making sure GetTaskBarPositionAndMetrics would work on right-to-left reading systems but I don't have such a system installed and don't have the means to install one.

These functions are in ShowWindowFromTray.h and ShowWindowFromTray.cpp and can be used all on their own.

[TOP]

Popping up a window

Once we have the position of the system tray we next need a window. I wrote a general purpose function for showing any window from the tray. You could use it to show dialogs, popups or whatever you like. Because it uses AnimateWindow there are some restrictions. The biggest restriction is that it only works on Win98/2K and above. I also found that some windows controls caused the function to fail, at least on Win2K - it seems there are some bugs in it!

I won't go into the gory details of the source for this function simply because it's mostly just figuring out what parameters to use for AnimateWindow. Here's the declaration of the function and it's parameters:


BOOL ShowWindowFromTray( HWND hwnd
  , UINT uMethod
  , UINT uAnimationTimeMS
  , BOOL bShow
  , BOOL bRestrictSize
  );

Parameters

hwnd
The window handle to show from the tray
uMethod
This is the method used to show the window. It can be any one of these #define's
SWFT_METHOD_JUST_SHOW
SWFT_METHOD_SLIDE
SWFT_METHOD_BLEND
SWFT_METHOD_EXPLODE
SWFT_METHOD_DIAGONAL
SWFT_METHOD_ROLL
See ShowWindowFromTray.h for a full description of each method
uAnimationTimeMS
The duration of the animation. 200ms is good but your mileage may vary.
bShow
TRUE if you want to show the window, FALSE if you want to hide it. Hiding does the reverse animation to showing.
bRestrictSize
TRUE if you want the window to be restricted to the desktop - this is useful and I would advise always using it.

This function is in ShowWindowFromTray.h and ShowWindowFromTray.cpp and can be used all on it's own to animate any window to and from the system tray.

[TOP]

Showing a HTML based tray message

Now that we have a utility function to display any window from the system tray we can finally get on with our HTML based messages.

For these I decided to use the free version of my QHTM HTML Win32 control. I thought it would be extremely flexible and would also serve as a fairly good advert for something I sell and support ;-)

Again, I won't go into the details of the function as I would hope it's pretty much self explanatory, but an overview is useful:

  • We first register a window type. This window is the container of the QHTM control. It handles activation, auto-destruction of the window and cleaning up of any resources.
  • When the window is created we create a QHTM window. We give QHTM the HTML that has been passed and then ask QHTM how big the HTML was.
  • Once we know how big the HTML is we can resize our window using AdjustWindowRectEx to get the window size from our proposed client area size.
  • We then show the window using ShowWindowFromTray and optionally set a timer to kill the window.
  • When the timer fires we check to see if the mouse is over our window (in a crude way). If the mouse is not in our window then we destroy it. If the mouse is in our window then we start another timer and check the mouse each time.

Here's the declaration of the functions we can now use to show HTML messages from the tray:


BOOL ShowTrayMessage( HINSTANCE hInstance
  , LPCTSTR pcszHTML
  , UINT uStyle
  , UINT uStyleEx
  , UINT uShowStyle
  , UINT uKillTimer
  );

BOOL ShowTrayMessageIndirect( HINSTANCE hInstance
  , UINT uStyle
  , UINT uStyleEx
  , LPTRAY_MESSAGE lpTrayMessage
  );
The header file ShowTrayMessage.h hopefully has enough comments in it for you figure out the usage. Here's the code from the Demo that is called when you click the button:

ShowTrayMessage( AfxGetInstanceHandle()
      , m_strHTML
      , WS_THICKFRAME | WS_POPUP
      , WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_PALETTEWINDOW
      , uShowStyle
      , m_uAutoKill );

[TOP]

The Demo



The demo shows how to use the functions provided as well as showing off some of the features. It's a simple no-frills dialog based application, just click the "Show tray message" button to see it in action.

[TOP]