Home » Source Code » CTrayIconPosition - where is my tray icon?

CTrayIconPosition - where is my tray icon?

maninwest
2015-01-22 02:50:06
The author
View(s):
Download(s): 1
Point (s): 1 
Category Category:
VC6VC6 Shell ScriptShell

Description

Translated by maninwest@Codeforge Author:Irek Zielinski@CodeProject

Ever wanted to know position of your tray icon? Windows supplies no API for that. This class is a compact solution that works.

This compact class makes one impossible thing possible - it's able to detect position of tray icon of your application.
Two ways to do it (2+)
Direct method:
Seems to be perfect if end user do not use a different tray manager than the one that is installed with MS Windows.
This method was proposed by Neal Andrews and I ported it from VB (source code) to C++. The main idea behind this method is that system tray uses an ordinary toolbar control to display icons (if you don't believe me - check it out with Spy++ application). It's also an easy thing to find a handle of this control and ask it directly for the rectangle of our icon. There are two things that need to be implemented. First, we need to find a handle to toolbar control. It can be done by enumerating all windows in a system and finding the one withShell_TrayWndclass name (this is a main window for a system tray). Then we enumerate all the child windows of the tray to find a toolbar (ToolbarWindow32class name).
Once we have a handle to a toolbar, we can query it for the number of icons it currently posseses:
//now we check how many buttons is there - should be more than 0
int iButtonsCount = SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);

If number of icons seems to be fine (is greater than 0), ee can start thinking how to ask this control for our icon. If toolbar would be a part of our application, we could just send it TB_GETBUTTON and TB_GETITEMRECT messages. It could look like:
for(int iButton=0; iButton<iButtonsCount; iButton++)
{
    TBBUTTON buttonData;
    //this structure will be filled with data about button

    SendMessage(hWndTray, TB_GETBUTTON, iButton, (LPARAM)&buttonData);
}

But in our code, such message would fail or even raise a General Protection Fault error! The main reason is that we can't pass pointer to locally allocatedTBBUTTONstructure to another process (process of Windows tray application). To solve this problem, we need to allocateTBBUTTONstructure inside tray application process. Then we can send message to a toolbar with a pointer to that allocated memory, and at the end - we can read this block of memory back to our application.

Code sample (error checking was skipped for easier reading):


BOOL FindOutPositionOfIconDirectly(const HWND a_hWndOwner, 
             const int a_iButtonID, CRect& a_rcIcon)
{
    HWND hWndTray = GetTrayToolbarControl();

    //now we have to get an ID of the parent process for system tray
    DWORD dwTrayProcessID = -1;
    GetWindowThreadProcessId(hWndTray, &dwTrayProcessID);

    //here we get a handle to tray application process
    HANDLE hTrayProc = 
      OpenProcess(PROCESS_ALL_ACCESS, 0, dwTrayProcessID);
 
    //now we check how many buttons is there - should be more than 0
    int iButtonsCount = SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);

    //We want to get data from another process - it's not possible 
    //to just send messages like TB_GETBUTTON with a locally
    //allocated buffer for return data. Pointer to locally allocated 
    //data has no usefull meaning in a context of another
    //process (since Win95) - so we need 
    //to allocate some memory inside Tray process.
    //We allocate sizeof(TBBUTTON) bytes of memory - 
    //because TBBUTTON is the biggest structure we will fetch. 
    //But this buffer will be also used to get smaller 
    //pieces of data like RECT structures.
    LPVOID lpData = VirtualAllocEx(hTrayProc, NULL, 
                    sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);

    BOOL bIconFound = FALSE;

    for(int iButton=0; iButton<iButtonsCount; iButton++)
    {
        //first let's read TBUTTON information 
        //about each button in a task bar of tray

        DWORD dwBytesRead = -1;
        TBBUTTON buttonData;
        SendMessage(hWndTray, TB_GETBUTTON, iButton, (LPARAM)lpData);

        //we filled lpData with details of iButton icon of toolbar 
        //- now let's copy this data from tray application
        //back to our process
        ReadProcessMemory(hTrayProc, lpData, &buttonData, 
                         sizeof(TBBUTTON), &dwBytesRead);

        //let's read extra data of each button: 
        //there will be a HWND of the window that 
        //created an icon and icon ID
        DWORD dwExtraData[2] = { 0,0 };
        ReadProcessMemory(hTrayProc, (LPVOID)buttonData.dwData, 
               dwExtraData, sizeof(dwExtraData), &dwBytesRead);

        HWND hWndOfIconOwner = (HWND) dwExtraData[0];
        int  iIconId         = (int)  dwExtraData[1];

        if(hWndOfIconOwner != a_hWndOwner || iIconId != a_iButtonID)
        {
            continue;
        }

        //we found our icon - in WinXP it could be hidden - let's check it:
        if( buttonData.fsState & TBSTATE_HIDDEN )
        {
            break;
        }

        //now just ask a tool bar of rectangle of our icon
        RECT rcPosition = {0,0};
        SendMessage(hWndTray, TB_GETITEMRECT, iButton, (LPARAM)lpData);
        ReadProcessMemory(hTrayProc, lpData, 
                     &rcPosition, sizeof(RECT), &dwBytesRead);

        MapWindowPoints(hWndTray, NULL, (LPPOINT)&rcPosition, 2);
        a_rcIcon = rcPosition;

        bIconFound = TRUE;
        break;
    }

    VirtualFreeEx(hTrayProc, lpData, NULL, MEM_RELEASE);
    CloseHandle(hTrayProc);

    return bIconFound;
}


Visual scan method: There is also a different approach possible. We can find the rectangle of system tray (we did it in previous method) and then scan this area for our icon manually. The idea is easy but implementation was not. As you may guess,Shell_NotifyIconwhile adding your icon to the system tray often does a lot of things with your icon. What is being done depends, for example, on the version of Windows and sometimes on your graphic mode (number of colors). In other words, if you askShell_NotifyIconto add your beautiful 32x32 pixels big and colorful icon to the tray - it could land there with reduced size and number of colors, and it's near not possible to predict how it would look like in the system tray. So it would not be wise to try to seek for your colorful icon.

But there is an easy and reliable solution (it really works on nearly all machines!). What about changing your default icon to plain-black one, seek for black rectangle in system tray, and after that restore the icon of your application? Not convinced? Well, I was skeptic also - but it just works fine Smile | :)
Using both methods: Direct scan method seems to be perfect - but what if user changed his/her default tray application to some third party software available in the market? It's rather unlikely but if you write an application that has to work on every PC, you should consider it. Second approach (visual scan) has a chance to succeed when first one will fail. Ultimate solution is simple - use both methods - if one fails, just try the second one. The code posted here gives you an easy ability to take this approach.
Usage in your projects
I wrote a compact classCTrayIconPosition. If you want to use it in your project - follow these few simple steps:
Add TrayIconPosition.h and TrayIconPosition.cpp to your project.
Add#include "TrayIconPosition.h"in files where you plan to use this class.
Declare a variable of this class (in my opinion, the best is to make it a member variable of your main dialog window or something like that).
CopyIDI_BLANK_BLACKicon from sample project, to your application.
Use API described below.
CTrayIconPosition API


void InitializePositionTracking(HWND hwndOfIconOwner, int iIconID);
Before calling this function, you should already have your icon in system tray. This function initializes tracking mechanism.
int iIconID- it's the ID of the icon you set while adding the icon to tray withShell_NotifyIcon.
BOOL GetTrayIconPosition(TrackType a_eTrackType = UseBothTechniquesDirectPrefered, Precision a_ePrec = Default);
This function calculates position of tray icon, and returnsTRUEif icon was found andFALSEif it was not found. But even if return value isFALSE- you can use point value - since it most likely will contain useful data. For example, under Windows XP, your tray icon can be hidden - then the return value of this function will beFALSE. But point will contain left, middle part of system tray (in WinXP, it's hide/unhide icons button). Remember that call of this function can change your tray icon to black - callRestoreTrayIconif you want to undo this effect.
Please notea_eTrackTypeparameter: it controls how class should do the tracking. Allowed values are:
UseBothTechniquesDirectPrefered- class will try to detect your icon using direct method first; in case of failure, it will do the visual scan of system tray.
UseBothTechniquesVisualScanPrefered- similar toUseBothTechniquesDirectPreferedbut the order of detection is visual first and direct if visual failed.
UseDirectOnly- self explaining.
UseVisualScanOnly- self explaining.
void RestoreTrayIcon(HICON icon);
Restores black icon set byGetTrayIconPosition. Since icon changes quite often in my Tray Helper application, I implemented restoring icon in a separate function call. If your application has static, always the same icon, it could be convenient to change this class to auto call this restore function.
void SetDefaultPrecision(Precision newPrecision);
Let me explain meaning of this function on example:
You callGetTrayIconPostionmember function many times a second. Since this function sets black icon in tray - such numerous calls in short period of time could look quite bad (flickering). But usually, position of tray icon doesn't change that often. That's whyCTrayIconPositionkeeps a cache of last calculated position and if you callGetTrayIconPosition- it is able to return cached value instead of checking it over and over again. Cached value is valid only for some time - and using this function, you can set it if you want more accurate results or less accurate with less flickering.
Acceptable values:
CTrayIconPosition::Default
CTrayIconPosition::High- cached position will expire in 10 seconds
CTrayIconPosition::Medium- cached position will expire in 30 seconds
CTrayIconPosition::Low- cached position will expire in 60 seconds
On default,Highprecision is assumed.
void Invalidate();
This function forces next call ofGetTrayIconPositionnot to use cached values.
Example of usage
BOOL FindOutPositionOfIconDirectly(const HWND a_hWndOwner, 
             const int a_iButtonID, CRect& a_rcIcon)
{
    HWND hWndTray = GetTrayToolbarControl();

    //now we have to get an ID of the parent process for system tray
    DWORD dwTrayProcessID = -1;
    GetWindowThreadProcessId(hWndTray, &dwTrayProcessID);

    //here we get a handle to tray application process
    HANDLE hTrayProc = 
      OpenProcess(PROCESS_ALL_ACCESS, 0, dwTrayProcessID);
 
    //now we check how many buttons is there - should be more than 0
    int iButtonsCount = SendMessage(hWndTray, TB_BUTTONCOUNT, 0, 0);

    //We want to get data from another process - it's not possible 
    //to just send messages like TB_GETBUTTON with a locally
    //allocated buffer for return data. Pointer to locally allocated 
    //data has no usefull meaning in a context of another
    //process (since Win95) - so we need 
    //to allocate some memory inside Tray process.
    //We allocate sizeof(TBBUTTON) bytes of memory - 
    //because TBBUTTON is the biggest structure we will fetch. 
    //But this buffer will be also used to get smaller 
    //pieces of data like RECT structures.
    LPVOID lpData = VirtualAllocEx(hTrayProc, NULL, 
                    sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);

    BOOL bIconFound = FALSE;

    for(int iButton=0; iButton<iButtonsCount; iButton++)
    {
        //first let's read TBUTTON information 
        //about each button in a task bar of tray

        DWORD dwBytesRead = -1;
        TBBUTTON buttonData;
        SendMessage(hWndTray, TB_GETBUTTON, iButton, (LPARAM)lpData);

        //we filled lpData with details of iButton icon of toolbar 
        //- now let's copy this data from tray application
        //back to our process
        ReadProcessMemory(hTrayProc, lpData, &buttonData, 
                         sizeof(TBBUTTON), &dwBytesRead);

        //let's read extra data of each button: 
        //there will be a HWND of the window that 
        //created an icon and icon ID
        DWORD dwExtraData[2] = { 0,0 };
        ReadProcessMemory(hTrayProc, (LPVOID)buttonData.dwData, 
               dwExtraData, sizeof(dwExtraData), &dwBytesRead);

        HWND hWndOfIconOwner = (HWND) dwExtraData[0];
        int  iIconId         = (int)  dwExtraData[1];

        if(hWndOfIconOwner != a_hWndOwner || iIconId != a_iButtonID)
        {
            continue;
        }

        //we found our icon - in WinXP it could be hidden - let's check it:
        if( buttonData.fsState & TBSTATE_HIDDEN )
        {
            break;
        }

        //now just ask a tool bar of rectangle of our icon
        RECT rcPosition = {0,0};
        SendMessage(hWndTray, TB_GETITEMRECT, iButton, (LPARAM)lpData);
        ReadProcessMemory(hTrayProc, lpData, 
                     &rcPosition, sizeof(RECT), &dwBytesRead);

        MapWindowPoints(hWndTray, NULL, (LPPOINT)&rcPosition, 2);
        a_rcIcon = rcPosition;

        bIconFound = TRUE;
        break;
    }

    VirtualFreeEx(hTrayProc, lpData, NULL, MEM_RELEASE);
    CloseHandle(hTrayProc);

    return bIconFound;
}

Sponsored links

File list

Tips: You can preview the content of files by clicking file names^_^
Name Size Date
01.96 kB
TrayFinder.rc2402.00 B2003-02-20 21:42
TrayFinder.ico1.05 kB2003-02-20 21:42
blank1.ico766.00 B2003-02-20 21:45
ico00018.ico2.93 kB2003-02-20 21:45
BalloonHelp.cpp45.29 kB2003-01-09 19:25
StdAfx.cpp212.00 B2003-02-20 21:42
TrayFinder.cpp2.04 kB2003-02-20 21:42
TrayFinderDlg.cpp6.97 kB2004-12-20 15:24
TrayIconPosition.cpp12.94 kB2004-12-22 11:31
TrayFinder.dsp4.53 kB2003-02-20 22:18
TrayFinder.dsw545.00 B2003-02-20 21:42
BalloonHelp.h14.23 kB2003-01-09 19:25
resource.h1.03 kB2004-12-20 15:21
StdAfx.h999.00 B2003-02-20 21:42
TrayFinder.h1.34 kB2003-02-20 21:42
TrayFinderDlg.h1.52 kB2004-12-20 15:18
TrayIconPosition.h2.42 kB2004-12-20 15:06
TrayFinder.rc6.57 kB2004-12-20 15:24
...
Sponsored links

Comments

(Add your comment, get 0.1 Point)
Minimum:15 words, Maximum:160 words
  • 1
  • Page 1
  • Total 1

CTrayIconPosition - where is my tray icon? (16.79 kB)(34.63 kB)

Need 1 Point(s)
Your Point (s)

Your Point isn't enough.

Get 22 Point immediately by PayPal

Point will be added to your account automatically after the transaction.

More(Debit card / Credit card / PayPal Credit / Online Banking)

Submit your source codes. Get more Points

LOGIN

Don't have an account? Register now
Need any help?
Mail to: support@codeforge.com

切换到中文版?

CodeForge Chinese Version
CodeForge English Version

Where are you going?

^_^"Oops ...

Sorry!This guy is mysterious, its blog hasn't been opened, try another, please!
OK

Warm tip!

CodeForge to FavoriteFavorite by Ctrl+D