以前つかっていた某有名メーラソフトや、現在使っている家庭内無線LANの無線電波状況を示すソフトは、ウィンドウを最小化してもタスクバーに表示されず、タスクトレイにアイコンを表示して、そのアイコンにマウスでアクセスすることにより、ユーザが制御できるようになっている。このような機能はどのようにして実現されているのだろうか。ここでは、参照元を参考にしてタスクトレイ(正式にはステータスエリア Status Area というらしい)にアイコンを表示する機構を試してみよう。
タスクトレイにアイコンを表示して、そこからのメッセージを受け取るためには ShellAPI にある
function Shell_NotifyIcon(dwMessage: DWORD; lpData: PNotifyIconData): BOOL; stdcall;
を実行して「登録」する必要がある。この第一パラメータには、以下の定数を設定できる。
const NIM_ADD = $00000000; // 新たに登録する NIM_MODIFY = $00000001; // 登録内容を変更する NIM_DELETE = $00000002; // 登録を解除する
第二パラメータには、以下のレコード型へのポインタを設定する。
type PNotifyIconDataA = ^TNotifyIconDataA; PNotifyIconData = PNotifyIconDataA; _NOTIFYICONDATAA = record cbSize: DWORD; // このレコード型のサイズ Wnd: HWND; // メッセージを受け取るウィンドウのハンドル uID: UINT; // アプリ定義の数値 uFlags: UINT; // どのフィールドが有効かを示すフラグ(以下を参照) uCallbackMessage: UINT; // アプリケーション定義のメッセージID hIcon: HICON; // タスクトレイに表示するアイコンのハンドル szTip: array [0..63] of AnsiChar; // ツールチップの文字列 end; _NOTIFYICONDATA = _NOTIFYICONDATAA; TNotifyIconDataA = _NOTIFYICONDATAA; TNotifyIconData = TNotifyIconDataA; NOTIFYICONDATAA = _NOTIFYICONDATAA; NOTIFYICONDATA = NOTIFYICONDATAA;
uFlags には、以下の定数の組み合わせ( or で結合できる)を設定できる。
const NIF_MESSAGE = $00000001; // uCallbackMessage フィールドが有効 NIF_ICON = $00000002; // hIcon フィールドが有効 NIF_TIP = $00000004; // szTip フィールドが有効
トレイに表示されたアイコンからのマウスメッセージは、Wnd で識別されるウィンドウに uCallbackMessage のメッセージとして送られてくる。この LParam には、WM_RBUTTONDOWN などのメッセージIDが入っており、WParam には、上記の uID で設定した数値が入っている。それではさっそくやってみよう。uses に ShellAPI を追加して以下のようにする。
List 1
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ShellAPI, StdCtrls; type TForm1 = class(TForm) Label1: TLabel; Button1: TButton; Button2: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private 宣言 } public NID:TNotifyIconData; WM_CALLBACK_MESSAGE:UINT; procedure WndProc(var Message: TMessage); override; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin WM_CALLBACK_MESSAGE := RegisterWindowMessage('HogeTrayIcon'); FillChar(NID,SizeOf(TNotifyIconData),#0); NID.cbSize := SizeOf(TNotifyIconData); NID.Wnd := Handle; NID.hIcon := Application.Icon.Handle; NID.uCallbackMessage := WM_CALLBACK_MESSAGE; NID.uID := 123; StrPLCopy(NID.szTip,Application.Title,63); NID.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; Shell_NotifyIcon(NIM_ADD,@NID); Randomize; end; procedure TForm1.FormDestroy(Sender: TObject); begin Shell_NotifyIcon(NIM_DELETE,@NID); end; procedure TForm1.WndProc(var Message: TMessage); begin inherited; if Message.Msg = WM_CALLBACK_MESSAGE then case Message.LParam of WM_RBUTTONDOWN:begin SetForegroundWindow(Handle); Color := TColor(Random($1000000)); Label1.Caption := IntToStr(Message.WParam); end; end; end; procedure TForm1.Button1Click(Sender: TObject); var IC:TIcon; begin IC := TIcon.Create; try IC.LoadFromFile('C:\Delphi5\Borland Shared\Images\Icons\chemical.ico'); NID.hIcon := IC.Handle; NID.uFlags := NIF_ICON; Shell_NotifyIcon(NIM_MODIFY,@NID); finally IC.Free; end; end; procedure TForm1.Button2Click(Sender: TObject); var s:string; begin s := 'ヒントを変更しました'; StrPLCopy(NID.szTip,s,Length(s)); NID.uFlags := NIF_TIP; Shell_NotifyIcon(NIM_MODIFY,@NID); end; end.
実行すると右図のようになる。タスクトレイの該当するアイコンの上にマウスカーソルを持っていくと、図のようにツールチップが表示される。アイコンを右クリックすると、そのたびに Form1 の背景色がランダムに変更される。Label1 には uID が設定したとおりに表示される。Button1 を押すとアイコンが変更される。Button2 を押すとツールチップが変更される。
コードを調べよう。登録は FormCreate イベントハンドラで行っている。
procedure TForm1.FormCreate(Sender: TObject); begin WM_CALLBACK_MESSAGE := RegisterWindowMessage('HogeTrayIcon'); FillChar(NID,SizeOf(TNotifyIconData),#0); NID.cbSize := SizeOf(TNotifyIconData); NID.Wnd := Handle; NID.hIcon := Application.Icon.Handle; NID.uCallbackMessage := WM_CALLBACK_MESSAGE; NID.uID := 123; StrPLCopy(NID.szTip,Application.Title,63); NID.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; Shell_NotifyIcon(NIM_ADD,@NID); Randomize; end;
ここでは、適当な文字列をつかって OS にカスタムメッセージを割り当ててもらっている。そして、NID の各フィールドを設定している。この例では、受信ウィンドウとして Form1 を設定している。最後に Shell_NotifyIcon(NIM_ADD,@NID); で登録している。登録解除は FormDestroy で行っている。メッセージは、WM_CALLBACK_MESSAGE を変数で宣言しているのでメッセージ処理ハンドラは使えないので、TForm1 の WndProc() をオーバーライドして捕まえている。
procedure TForm1.WndProc(var Message: TMessage); begin inherited; if Message.Msg = WM_CALLBACK_MESSAGE then case Message.LParam of WM_RBUTTONDOWN:begin SetForegroundWindow(Handle); Color := TColor(Random($1000000)); Label1.Caption := IntToStr(Message.WParam); end; end; end;
ここでは、WM_RBUTTONDOWN メッセージだけを捕まえて Form1 の背景色を変更している。アイコンの変更は
procedure TForm1.Button1Click(Sender: TObject); var IC:TIcon; begin IC := TIcon.Create; try IC.LoadFromFile('C:\Delphi5\Borland Shared\Images\Icons\chemical.ico'); NID.hIcon := IC.Handle; NID.uFlags := NIF_ICON; Shell_NotifyIcon(NIM_MODIFY,@NID); finally IC.Free; end; end;
で行っている。NID の変更部分だけ設定して、Shell_NotifyIcon(NIM_MODIFY,@NID); を実行している。OS は Wnd 部分と uID 部分をみて、どの登録を変更するのかを識別するので、これらの部分は一度設定したら変更しないようにする。ツールチップの変更も同様である。
ここまでで、トレイアイコンアプリケーションの骨格部分はできたので、あとはアイコンを右クリックしたときにポップアップメニューを表示し、フォームを最小化したときにタスクバーのボタンに現れないようにすると完成である。前回のプログラムに、PopupMenu1 と ApplicationEvents1 を追加して以下のように変更する。
List 2
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ShellAPI, StdCtrls, AppEvnts, Menus; type TForm1 = class(TForm) Label1: TLabel; Button1: TButton; Button2: TButton; PopupMenu1: TPopupMenu; ApplicationEvents1: TApplicationEvents; Red1: TMenuItem; Green1: TMenuItem; Blue1: TMenuItem; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure ApplicationEvents1Minimize(Sender: TObject); procedure ApplicationEvents1Restore(Sender: TObject); procedure Red1Click(Sender: TObject); procedure Green1Click(Sender: TObject); procedure Blue1Click(Sender: TObject); private { Private 宣言 } public NID:TNotifyIconData; WM_CALLBACK_MESSAGE:UINT; procedure WndProc(var Message: TMessage); override; procedure ShowPopupMenu; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin WM_CALLBACK_MESSAGE := RegisterWindowMessage('HogeTrayIcon'); FillChar(NID,SizeOf(TNotifyIconData),#0); NID.cbSize := SizeOf(TNotifyIconData); NID.Wnd := Handle; NID.hIcon := Application.Icon.Handle; NID.uCallbackMessage := WM_CALLBACK_MESSAGE; NID.uID := 123; StrPLCopy(NID.szTip,Application.Title,63); NID.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; Shell_NotifyIcon(NIM_ADD,@NID); end; procedure TForm1.FormDestroy(Sender: TObject); begin Shell_NotifyIcon(NIM_DELETE,@NID); end; procedure TForm1.WndProc(var Message: TMessage); begin inherited; if Message.Msg = WM_CALLBACK_MESSAGE then case Message.LParam of WM_RBUTTONDOWN:ShowPopUpMenu; WM_LBUTTONDBLCLK:Application.Restore; end; end; procedure TForm1.Button1Click(Sender: TObject); var IC:TIcon; begin IC := TIcon.Create; try IC.LoadFromFile('C:\Delphi5\Borland Shared\Images\Icons\chemical.ico'); NID.hIcon := IC.Handle; NID.uFlags := NIF_ICON; Shell_NotifyIcon(NIM_MODIFY,@NID); finally IC.Free; end; end; procedure TForm1.Button2Click(Sender: TObject); var s:string; begin s := 'ヒントを変更しました'; StrPLCopy(NID.szTip,s,Length(s)); NID.uFlags := NIF_TIP; Shell_NotifyIcon(NIM_MODIFY,@NID); end; procedure TForm1.ApplicationEvents1Minimize(Sender: TObject); begin ShowWindow(Application.Handle,SW_HIDE); end; procedure TForm1.ApplicationEvents1Restore(Sender: TObject); begin SetForegroundWindow(Application.Handle); end; procedure TForm1.ShowPopupMenu; var Pt: TPoint; begin SetForegroundWindow(Handle); GetCursorPos(Pt); PopupMenu1.Popup(Pt.X, Pt.Y); end; procedure TForm1.Red1Click(Sender: TObject); begin Application.Restore; Color := clRed; end; procedure TForm1.Green1Click(Sender: TObject); begin Application.Restore; Color := clGreen; end; procedure TForm1.Blue1Click(Sender: TObject); begin Application.Restore; Color := clBlue; end; end.
実行すると右図のようになる。
最小化したときタスクバーに表示されないようにするには、
procedure TForm1.ApplicationEvents1Minimize(Sender: TObject); begin ShowWindow(Application.Handle,SW_HIDE); end;
のようにして、Application ウィンドウを非表示にする。ここでは、トレイアイコンを左ダブルクリックしたときに復帰するようにした。
procedure TForm1.WndProc(var Message: TMessage); begin inherited; if Message.Msg = WM_CALLBACK_MESSAGE then case Message.LParam of WM_RBUTTONDOWN:ShowPopUpMenu; WM_LBUTTONDBLCLK:Application.Restore; end; end;
また、WM_RBUTTONDOWN を捕まえてポップアップメニューを表示している。
procedure TForm1.ShowPopupMenu; var Pt: TPoint; begin SetForegroundWindow(Handle); GetCursorPos(Pt); PopupMenu1.Popup(Pt.X, Pt.Y); end;
このように、GetCursorPos() によりマウスカーソルのスクリーン座標を取得して、そこにポップアップメニューを表示する。
最後に以上の機能をコンポーネントにしてみよう。
List 3
unit TrayIconUnit; interface uses Windows, Messages, ShellApi, SysUtils, Graphics, Controls, Forms, Classes, AppEvnts; type TTrayIcon = class(TComponent) private NID:TNotifyIconData; WM_CALLBACK_MESSAGE:UINT; FHandle:HWND; FClickFlag:Boolean; FHint:string; FIcon:TIcon; FHideMinimize:Boolean; FAppEvents:TApplicationEvents; FOnClick:TNotifyEvent; FOnDblClick:TNotifyEvent; FOnMouseMove:TMouseMoveEvent; FOnMouseDown:TMouseEvent; FOnMouseUp:TMouseEvent; procedure SetHint(const Value: string); procedure SetIcon(const Value: TIcon); protected procedure OnMessage(var Msg:TMessage); virtual; procedure Modify; function GetShiftState:TShiftState; procedure Click;virtual; procedure DblClick;virtual; procedure MouseMove(Shift:TShiftState; X,Y:integer);virtual; procedure MouseDown(Button:TMouseButton; Shift:TShiftState; X,Y:integer);virtual; procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X,Y:integer);virtual; procedure Loaded;override; public constructor Create(Owner: TComponent); override; destructor Destroy; override; procedure AppMinimize(Sender:TObject); procedure AppRestore(Sender:TObject); published property Hint:string read FHint write SetHint; property Icon:TIcon read FIcon write SetIcon; property HideMinimize:Boolean read FHideMinimize write FHideMinimize; property OnClick: TNotifyEvent read FOnClick write FOnClick; property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick; property OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove; property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown; property OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp; end; procedure Register; implementation { TTrayIcon } procedure Register; begin RegisterComponents('Samples',[TTrayIcon]); end; constructor TTrayIcon.Create(Owner: TComponent); begin inherited; FIcon := TIcon.Create; Icon := Application.Icon; if not (csDesigning in ComponentState) then begin FAppEvents := TApplicationEvents.Create(self); FAppEvents.OnMinimize := AppMinimize; FAppEvents.OnRestore := AppRestore; WM_CALLBACK_MESSAGE := RegisterWindowMessage('HogeTrayIcon'); FHandle := AllocateHwnd(OnMessage); FillChar(NID,SizeOf(TNotifyIconData),#0); NID.cbSize := SizeOf(TNotifyIconData); NID.Wnd := FHandle; NID.hIcon := FIcon.Handle; NID.uCallbackMessage := WM_CALLBACK_MESSAGE; NID.uID := FHandle; StrPLCopy(NID.szTip,Application.Title,SizeOf(NID.szTip)-1); NID.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; Shell_NotifyIcon(NIM_ADD,@NID); end; end; destructor TTrayIcon.Destroy; begin FIcon.Free; if not (csDesigning in ComponentState) then begin Shell_NotifyIcon(NIM_DELETE,@NID); DeallocateHwnd(FHandle); end; inherited; end; procedure TTrayIcon.OnMessage(var Msg: TMessage); var Pt:TPoint; Shift:TShiftState; begin if (Msg.Msg = WM_CALLBACK_MESSAGE) and (Msg.WParam = WPARAM(FHandle)) then case Msg.LParam of WM_MOUSEMOVE :begin Shift := GetShiftState; GetCursorPos(Pt); MouseMove(Shift,Pt.x,Pt.y); end; WM_LBUTTONDOWN :begin Shift := GetShiftState + [ssLeft]; GetCursorPos(Pt); MouseDown(mbLeft,Shift,Pt.x,Pt.y); FClickFlag := true; end; WM_LBUTTONUP :begin Shift := GetShiftState + [ssLeft]; GetCursorPos(Pt); if FClickFlag then begin FClickFlag := false; Click; end; MouseUp(mbLeft,Shift,Pt.x, Pt.y); end; WM_LBUTTONDBLCLK:DblClick; WM_RBUTTONDOWN :begin Shift := GetShiftState + [ssRight]; GetCursorPos(Pt); MouseDown(mbRight,Shift,Pt.x,Pt.y); end; WM_RBUTTONUP :begin Shift := GetShiftState + [ssRight]; GetCursorPos(Pt); MouseUp(mbRight,Shift,Pt.x, Pt.y); end; WM_RBUTTONDBLCLK:DblClick; end // case else Msg.Result := DefWindowProc(FHandle,Msg.Msg,MSg.WParam,Msg.LParam); end; procedure TTrayIcon.Modify; begin if not (csDesigning in ComponentState) then Shell_NotifyIcon(NIM_MODIFY,@NID); end; procedure TTrayIcon.Loaded; begin inherited; NID.hIcon := FIcon.Handle; NID.uFlags := NIF_ICON; Modify; end; function TTrayIcon.GetShiftState:TShiftState; begin Result := []; if GetKeyState(VK_SHIFT) < 0 then Include(Result,ssShift); if GetKeyState(VK_CONTROL) < 0 then Include(Result,ssCtrl); if GetKeyState(VK_MENU) < 0 then Include(Result,ssAlt); end; procedure TTrayIcon.Click; begin if Assigned(FOnClick) then FOnClick(self); end; procedure TTrayIcon.DblClick; begin if Assigned(FOnDblClick) then FOnDblClick(self); end; procedure TTrayIcon.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); begin if Assigned(FOnMouseDown) then FOnMouseDown(self,Button,Shift,X,Y); end; procedure TTrayIcon.MouseMove(Shift: TShiftState; X, Y: integer); begin if Assigned(FOnMouseMove) then FOnMouseMove(self,Shift,X,Y); end; procedure TTrayIcon.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); begin if Assigned(FOnMouseUp) then FOnMouseUp(self,Button,Shift,X,Y); end; procedure TTrayIcon.SetHint(const Value: string); begin if FHint <> Value then begin FHint := Value; StrPLCopy(NID.szTip,Value,SizeOf(NID.szTip)-1); NID.uFlags := NIF_TIP; Modify; end; end; procedure TTrayIcon.SetIcon(const Value: TIcon); begin if FIcon <> Value then begin FIcon.Assign(Value); NID.hIcon := FIcon.Handle; NID.uFlags := NIF_ICON; Modify; end; end; procedure TTrayIcon.AppMinimize(Sender:TObject); begin if FHideMinimize then ShowWindow(Application.Handle,SW_HIDE); end; procedure TTrayIcon.AppRestore(Sender:TObject); begin if FHideMinimize then SetForegroundWindow(Application.Handle); end; end.
このコンポーネントの HideMinimize はデフォルトでは false であるが、オブジェクトインスペクタで true にすると最小化のときにタスクバーに表示されなくなる。あとのプロパティーやイベントハンドラについては説明はいらないだろう。それではこのコンポーネントをテストしてみよう。右図のように TrayIcon1 と Button1 と PopupMenu1 と ImageList1 を置いて、TrayIcon1 のプロパティーを適当に設定する。
また、ImageList1 をダブルクリックして予め4つのアイコンを右図のように読み込んでおく。HideMinimize を true にしたときは必ず復帰する手段を設定しておくことを忘れないように。以下に全リストを示す。
List 4
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, TrayIconUnit, StdCtrls, Menus, ImgList; type TForm1 = class(TForm) TrayIcon1: TTrayIcon; Button1: TButton; PopupMenu1: TPopupMenu; Red1: TMenuItem; Green1: TMenuItem; Blue1: TMenuItem; ImageList1: TImageList; procedure TrayIcon1Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Red1Click(Sender: TObject); procedure Green1Click(Sender: TObject); procedure Blue1Click(Sender: TObject); procedure TrayIcon1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.TrayIcon1Click(Sender: TObject); begin Application.Restore; end; var IconIndex:integer; procedure TForm1.Button1Click(Sender: TObject); var IC:TIcon; begin IC := TIcon.Create; try Inc(IconIndex); if IConIndex > 3 then IconIndex := 0; ImageList1.GetIcon(IconIndex,IC); TrayIcon1.Icon := IC; finally IC.Free; end; end; procedure TForm1.Red1Click(Sender: TObject); begin Application.Restore; Color := clRed; end; procedure TForm1.Green1Click(Sender: TObject); begin Application.Restore; Color := clGreen; end; procedure TForm1.Blue1Click(Sender: TObject); begin Application.Restore; Color := clBlue; end; procedure TForm1.TrayIcon1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbRight then begin SetForegroundWindow(Handle); PopupMenu1.Popup(X,Y); end; end; end.
実行すると右図のようになる。ここでは HideMinimize を true にして、アイコンを左クリックしたときに最小化から復帰している。Button1Click では、ImageList1 に設定したアイコンを循環的にトレイアイコンに設定している。
以上、トレイアイコンアプリケーションの仕組みを簡単に調べ、それ用のコンポーネントを作成した。