In Part 1 of the series, we have seen how we can hide the application and display an icon in system tray. In Part 2 we will build on Part 1 example to see how we can implement a redisplay of application window upon double click on system tray icon. To do that, we need to create a message that tray icon will report to main application and check it for events.

 

Step 1 – Setting a callback message to tray icon

There is one important property of NOTIFYICONDATA struct that we didn’t do in part 1. Property uCallbackMessage takes an ID of a message we want to send to main application when any event happens to system tray icon. This is what I did. I created a message.h header file that will contain all custom messages used in this sample application and added the following:

#pragma once

#define WM_TRAYMESSAGE (WM_USER + 1)

Beware that custom messages should always start at WM_USER + n, where n is a number greater than 0. Everything below that is reserved for system messages and we really don’t want to mess with that.
Next, I opened up tray.cpp and modified TrayDrawIcon to pass WM_TRAYMESSAGE as a callback message:

void TrayDrawIcon(HWND hWnd) {
	NOTIFYICONDATA nid;
	nid.cbSize = sizeof(NOTIFYICONDATA);
	nid.hWnd = hWnd;
	nid.uID = TRAY_ICONUID;
	nid.uVersion = NOTIFYICON_VERSION;
	nid.uCallbackMessage = WM_TRAYMESSAGE;
	nid.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_SMALL));
	LoadString(hInst, IDS_APP_TITLE, nid.szTip, 128);
	nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
	Shell_NotifyIcon(NIM_ADD, &nid);
}

 

Step 2 – Handling callback message

Then, I had to add WM_TRAYMESSAGE message handling to main application. As all of message parsing goes on in WndProc function in myprojectname.cpp file, I added the following lines of code to main switch statement:

case WM_TRAYMESSAGE:
		switch(lParam) {
		case WM_LBUTTONDBLCLK:
			ShowWindow(hWnd, SW_SHOW);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;

Basically, what this does is tell main application that if message WM_TRAYMESSAGE arrives, check lParam for message that caused message WM_TRAYMESSAGE to arrive in the first place. If message is WM_LBUTTONDBLCLK (or plainly double click), then show window. If there was no double click, call default window message handling procedure.

 

Step 3 – Delete icon from tray

Now that your window will reappear on the screen, there is really no need to host a tray icon anymore. Hence, you will need to delete it. This can be done by calling Shell_NotifyIcon function with NIM_DELETE message. So, in tray.cpp file I created a method called TrayDeleteIcon that does just that.

#pragma once

#include "resource.h"
#include <shellapi.h>

#define TRAY_ICONUID 100

extern HINSTANCE hInst;

void TrayDrawIcon(HWND hWnd);
void TrayDeleteIcon(HWND hWnd);
//
//  FUNCTION: TrayDeleteIcon(HWND)
//
//  PURPOSE:  Deletes application icon from system tray
//
//
void TrayDeleteIcon(HWND hWnd) {
	NOTIFYICONDATA nid;
	nid.cbSize = sizeof(NOTIFYICONDATA);
	nid.hWnd = hWnd;
	nid.uID = TRAY_ICONUID;
	Shell_NotifyIcon(NIM_DELETE, &nid);
}

As you can see, you also need to pass basic data about the icon to Shell_NotifyIcon function, e.g. window handler and icon id.

With function that deletes the icon done, there is only one thing left and that is to change double click message handler to delete the icon after displaying an application window.

case WM_TRAYMESSAGE:
		switch(lParam) {
		case WM_LBUTTONDBLCLK:
			ShowWindow(hWnd, SW_SHOW);
			TrayDeleteIcon(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;

 

Step 4 – Rebuild and run

Rebuild and run the application. When tray icon is double clicked, the app itself should now redisplay on the screen. Also, there should not be any sign of a tray icon anymore.

 

What’s next?

Part 3 will compensate for shortness of part 2. We will take a look at how to display a popup on left and right button clicks on system tray icon.