タスクバーの通知領域に常駐するアプリを作る [C++Builder]

起動したら通知領域にアイコンを表示する常駐アプリの作り方です。

次のような特徴を持つアプリです。

  • 起動時にフォームが表示されない。
  • タスクバーの起動アプリ一覧に載らない。
  • 通知領域にアイコンが表示される。
  • 通知領域のアイコンを左クリックすると、アプリが表示される。
  • 通知領域のアイコンを右クリックすると、メニューが表示される。
  • メニューの「終了」を選択すると終了する。

作成手順

作成する手順を順に記載します。手順を飛ばしてコードだけ参照したい場合には コード全量 を参照ください。

起動時に非表示にする

通知領域に常駐するアプリであるため、起動直後はフォームを表示しないようにします。
ハイライトしている行が追加する部分です。

TForm1::TForm1()
{
	// 起動時にフォームが表示されないようにする。
    Application->ShowMainForm = false;
}

通知領域に表示されるアイコンを設定する

通知領域に表示されるアイコンを設定します。今回はフォームの左上に表示されるアイコンと同じにしたいため、ここではフォームのアイコンを設定しています。
この後に記載している Unit1.cpp で通知領域のアイコンにフォームのアイコンハンドルを指定して同じものが使用できるようにしています。

プロパティ > Icon の … をクリックします。

[読み込み]をクリックします。

*.ico のファイルを選択して [開く] をクリックします。 (例では app.ico を選択しています)

[OK] をクリックします。

Icon のプロパティに (TIcon) が表示されたことを確認します。

アプリ起動時に通知領域にアイコンが追加され、終了時に削除されるようにする

ここからコードを追加していきます。例に使用するファイル名は、プロジェクト作成でデフォルトで作成される Unit1.h と Unit1.cpp を使用しています。

Unit1.h

Unit1.h に 通知領域に表示するための情報フィールドと、アイコンの情報設定/追加/削除するメソッドを追加します。情報を保存するフィールドは、追加と削除で同じ情報を使うためクラスフィールドにしています。

class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
   void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private:    // ユーザー宣言
    // 通知領域に表示するための情報
    NOTIFYICONDATA _notifyIcon;

    // 通知領域へのアイコン追加/削除メソッド
    void _SetupIconNotificationArea();
    void _AddIconNotificationArea();
    void _DeleteIconNotificationArea();
public:        // ユーザー宣言
   __fastcall TForm1(TComponent* Owner);
};

Unit1.cpp

コンストラクタで通知領域にアイコンを追加します。

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // 起動時にフォームが表示されないようにする。
    Application->ShowMainForm = false;

    // 通知領域にアイコンを表示するための情報を生成して追加する。
    _SetupIconNotificationArea();
    _AddIconNotificationArea();
}

// 通知領域にアイコンを表示するための情報を生成する。
void TForm1::_SetupIconNotificationArea()
{
    _notifyIcon.cbSize = sizeof(NOTIFYICONDATA);
    _notifyIcon.hWnd = Form1->Handle;
    _notifyIcon.uID = 100;
    _notifyIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    _notifyIcon.uCallbackMessage = WM_USER + 1;
    _notifyIcon.hIcon = Form1->Icon->Handle;
    lstrcpy(_notifyIcon.szTip, Form1->Caption.c_str());
}

// 通知領域にアイコンを追加する。
void TForm1::_AddIconNotificationArea()
{
    const int MAX_RETRY_COUNT = 3;  // n回リトライする
    const DWORD RETRY_INTERVAL_SECOND = 4 * 1000; // リトライ時のインターバル時間 (秒)

    for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) {
        BOOL added = Shell_NotifyIcon(NIM_ADD, &_notifyIcon);
        if (added) break;
        if (GetLastError() != ERROR_TIMEOUT) break; // タイムアウト以外の失敗

        // タイムアウトなら MODIFY を試す
        BOOL modified = Shell_NotifyIcon(NIM_MODIFY, &_notifyIcon);
        if (modified) break;

        // 次のリトライまで待つ
        Sleep(RETRY_INTERVAL_SECOND);
    }
}

フォームクローズで通知領域からアイコンを削除します。

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    // 通知領域からアイコンを削除する
    _DeleteIconNotificationArea();
}

// 通知領域からアイコンを削除する。
void TForm1::_DeleteIconNotificationArea()
{
    Shell_NotifyIcon(NIM_DELETE, &_notifyIcon);
}

通知領域のアイコンをクリックしたときのコンテキストメニューを作成する

通知領域に追加したアイコンをクリックしたときに表示されるコンテキストメニューを作っていきます。今回はコンテキストメニューには「終了(&X)」の項目だけ表示します。

パレットから TPopupMenu を選択して、Form に配置します。

Form に配置した PopuMenu1 を右クリックして、メニューデザイナ を選びます。

表示されたメニューデザイナの空欄をクリックして、オブジェクトインスペクタの Caption に “終了(&X)” と入力します。

PoupuMenu に PopupMenu1 を入力します。

オブジェクトインスペクタ の表示をイベントタブに変えて、OnClick をダブルクリックします。

「終了(&X)」がクリックされたら、フォームをクローズするようにします。

void __fastcall TForm1::X1Click(TObject *Sender)
{
    // フォームを閉じる (アプリケーションを終了する)
    Close();
}

通知領域のアイコンをクリックしたときの処理を書く

作成したコンテキストメニューが通知領域のアイコンを右クリックしたときに表示されるようにします。併せて、左クリックしたときにフォームが表示されるようにします。

Unit1.h

class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private:    // ユーザー宣言
    // 通知領域に表示するための情報
    NOTIFYICONDATA _notifyIcon;

    // 通知領域へのアイコン追加/削除メソッド
    void _SetupIconNotificationArea();
    void _AddIconNotificationArea();
    void _DeleteIconNotificationArea();

    // 通知領域のアイコンを操作したときに呼ばれるメソッド
    void __fastcall WMTrayEvent(TMessage& Msg);

    // WMTrayEvent()が呼ばれるように登録する
    // WM_USER+1 は NOTIFYICONDATA の uCallbackMessage に指定した値
    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(WM_USER+1, TMessage, WMTrayEvent);
    END_MESSAGE_MAP(TForm);

public:        // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};

Unit1.cpp

// 通知領域のアイコン操作のイベントを受け取る。
void __fastcall TForm1::WMTrayEvent(TMessage &Msg)
{
    switch (Msg.LParam) {
        case WM_LBUTTONUP:
            // 通知領域のアイコンが左クリックされた
            // -> フォームを表示する。
            //    ShowWindow(Handle, SW_SHOW) では 最小化ボタンが
            //    効かなくなるので Visible を操作する
            Visible = true;
            // 最小化で非表示した後に再表示する場合、最小化状態に
            // なっているので元に戻す。
            WindowState = wsNormal;
            break;
        case WM_RBUTTONUP:
            // 通知領域のアイコンが右クリックされた
            // -> ポップアップメニューを表示する。
            POINT P;
            GetCursorPos(&P);
            SetForegroundWindow(Form1->Handle);
            PopupMenu1->Popup(P.x, P.y);
            break;
    }
}

タスクバーが消えて再表示されたときに通知領域のアイコンを再登録する

上記のコードだけでは、エクスプローラーが落ち、タスクバーが消え、タスクバーが再表示されると、通知領域のアイコンが消えてしまいます。再表示されたときに通知領域のアイコンを再登録するようにします。

Unit1.h

class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private:    // ユーザー宣言
    // 通知領域に表示するための情報
    NOTIFYICONDATA _notifyIcon;

    // 通知領域へのアイコン追加/削除メソッド
    void _SetupIconNotificationArea();
    void _AddIconNotificationArea();
    void _DeleteIconNotificationArea();

    // 通知領域のアイコンを操作したときに呼ばれるメソッド
    void __fastcall WMTrayEvent(TMessage& Msg);

    // WMTrayEvent()が呼ばれるように登録する
    // WM_USER+1 は NOTIFYICONDATA の uCallbackMessage に指定した値
    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(WM_USER+1, TMessage, WMTrayEvent);
    END_MESSAGE_MAP(TForm);

    // タスクバー終了->復活で通知されるWM値
    UINT _WM_TASKBARCREATED;

    // _WM_TASKBARCREATED を受け取るために TForm::WndProc をオーバーライドする
    void __fastcall WndProc(Messages::TMessage &msg);

public:        // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};

Unit1.cpp

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    // 起動時にフォームが表示されないようにする。
    Application->ShowMainForm = false;

    // 通知領域にアイコンを表示するための情報を生成して追加する。
    _SetupIconNotificationArea();
    _AddIconNotificationArea();

    // タスクバーが消えて再表示されたときに通知領域にアイコンを再登録
    // するため、タスクバーの再表示メッセージを受け取るようにする。
    _WM_TASKBARCREATED = RegisterWindowMessage(_T("TaskbarCreated"));
}

// メッセージ通知を受取る。
void __fastcall TForm1::WndProc(Messages::TMessage &msg)
{
    if (_WM_TASKBARCREATED != 0 && msg.Msg == _WM_TASKBARCREATED) {
        // RegisterWindowMessage(_T("TaskbarCreated")); が成功している &&
        // タスクバーの再生成が通知された。
        // -> 通知領域にアイコンを追加する
        _AddIconNotificationArea();
    }

    TForm::WndProc(msg);
}

最小化されたら非表示にする

通知領域のアイコンを右クリックしてフォームを表示したあと、最小化ボタン等で最小化したときにタスクバーの起動アプリ一覧に残ってしまわないように非表示にします。

OnResize() イベントを作成して、最小化時に非表示になるようにします。

void __fastcall TForm1::FormResize(TObject *Sender)
{
    // フォームが最小化されたらタスクバーに載らないように非表示にする。
    if (WindowState == wsMinimized) {
        Visible = false;
    }
}

ビルドして実行する

これで完了です。ビルドして実行してみてください。

コード全量

最後に Unit.h と Unit.cpp の全量を掲載しておきます。
ハイライトしている行が手動で追加する部分です。

Unit1.h

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.Menus.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    TPopupMenu *PopupMenu1;
    TMenuItem *X1;
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
    void __fastcall X1Click(TObject *Sender);
private:    // ユーザー宣言
    // 通知領域に表示するための情報
    NOTIFYICONDATA _notifyIcon;

    // 通知領域へのアイコン追加/削除メソッド
    void _SetupIconNotificationArea();
    void _AddIconNotificationArea();
    void _DeleteIconNotificationArea();

    // 通知領域のアイコンを操作したときに呼ばれるメソッド
    void __fastcall WMTrayEvent(TMessage& Msg);

    // WMTrayEvent()が呼ばれるように登録する
    // WM_USER+1 は NOTIFYICONDATA の uCallbackMessage に指定した値
    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(WM_USER+1, TMessage, WMTrayEvent);
    END_MESSAGE_MAP(TForm);

    // タスクバー終了->復活で通知されるWM値
    UINT _WM_TASKBARCREATED;

    // _WM_TASKBARCREATED を受け取るために TForm::WndProc をオーバーライドする
    void __fastcall WndProc(Messages::TMessage &msg);

public:        // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	// 起動時にフォームが表示されないようにする。
	Application->ShowMainForm = false;

	// 通知領域にアイコンを表示するための情報を生成して追加する。
	_SetupIconNotificationArea();
	_AddIconNotificationArea();

	// タスクバーが消えて再表示されたときに通知領域にアイコンを再登録
	// するため、タスクバーの再表示メッセージを受け取るようにする。
	_WM_TASKBARCREATED = RegisterWindowMessage(_T("TaskbarCreated"));
}

//---------------------------------------------------------------------------
// 通知領域にアイコンを表示するための情報を生成する。
void TForm1::_SetupIconNotificationArea()
{
	_notifyIcon.cbSize = sizeof(NOTIFYICONDATA);
	_notifyIcon.hWnd = Form1->Handle;
	_notifyIcon.uID = 100;
	_notifyIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
	_notifyIcon.uCallbackMessage = WM_USER + 1;
	_notifyIcon.hIcon = Form1->Icon->Handle;
	lstrcpy(_notifyIcon.szTip, Form1->Caption.c_str());
}

//---------------------------------------------------------------------------
// 通知領域にアイコンを追加する。
void TForm1::_AddIconNotificationArea()
{
	const int MAX_RETRY_COUNT = 3;  // n回リトライする
	const DWORD RETRY_INTERVAL_SECOND = 4 * 1000; // リトライ時のインターバル時間 (秒)

	for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) {
        BOOL added = Shell_NotifyIcon(NIM_ADD, &_notifyIcon);
        if (added)
            break;
        if (GetLastError() != ERROR_TIMEOUT)
            // タイムアウト以外の失敗
			break;

        // タイムアウトなら MODIFY を試す
        BOOL modified = Shell_NotifyIcon(NIM_MODIFY, &_notifyIcon);
        if (modified)
            break;

        // 次のリトライまで待つ
        Sleep(RETRY_INTERVAL_SECOND);
    }
}

//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
	// 通知領域からアイコンを削除する
	_DeleteIconNotificationArea();
}

//---------------------------------------------------------------------------
// 通知領域からアイコンを削除する。
void TForm1::_DeleteIconNotificationArea()
{
	Shell_NotifyIcon(NIM_DELETE, &_notifyIcon);
}

//---------------------------------------------------------------------------
// 「終了(&X)」がクリックされた。
void __fastcall TForm1::X1Click(TObject *Sender)
{
    // フォームを閉じる (アプリケーションを終了する)
    Close();
}

//---------------------------------------------------------------------------
// 通知領域のアイコン操作のイベントを受け取る。
void __fastcall TForm1::WMTrayEvent(TMessage &Msg)
{
    switch (Msg.LParam) {
        case WM_LBUTTONUP:
            // 通知領域のアイコンが左クリックされた
            // -> フォームを表示する。
            //    ShowWindow(Handle, SW_SHOW) では 最小化ボタンが
            //    効かなくなるので Visible を操作する
            Visible = true;
            // 最小化で非表示した後に再表示する場合、最小化状態に
            // なっているので元に戻す。
            WindowState = wsNormal;
            break;
		case WM_RBUTTONUP:
            // 通知領域のアイコンが右クリックされた
            // -> ポップアップメニューを表示する。
            POINT P;
            GetCursorPos(&P);
            SetForegroundWindow(Form1->Handle);
            PopupMenu1->Popup(P.x, P.y);
			break;
	}
}

//---------------------------------------------------------------------------
// メッセージ通知を受取る。
void __fastcall TForm1::WndProc(Messages::TMessage &msg)
{
	if (_WM_TASKBARCREATED != 0 && msg.Msg == _WM_TASKBARCREATED) {
		// RegisterWindowMessage(_T("TaskbarCreated")); が成功している &&
		// タスクバーの再生成が通知された。
		// -> 通知領域にアイコンを追加する
		_AddIconNotificationArea();
	}

	TForm::WndProc(msg);
}

//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
    // フォームが最小化されたらタスクバーに載らないように非表示にする。
    if (WindowState == wsMinimized) {
        Visible = false;
    }
}

コメント