TaskTray / StatusBar
まずはコレをご覧下さい。
という感じで、TaskTray / StatusBar にアイコンを登録する方法を紹介します。
ライブラリのソースコードは最後の「まとめ」にリンクがあります。
当然アプリケーション側は1ソースです。
FormCreate で下記のようにコードを書くと上記のアイコンとメニューが表示されます。
procedure TForm1.FormCreate(Sender: TObject);
var
Bmp: TBitmap;
begin
// TaskTray Icon ライブラリを生成
FTrayIcon := TTrayIcon.Create;
// アイコンそのものをクリックされたときのイベント(Windows Only)
FTrayIcon.RegisterOnClick(TrayIconClickHandler);
// メニューの設定
FTrayIcon.AddMenu('メニューアイテム1', MenuClickedHandler);
FTrayIcon.AddMenu('メニューアイテム2', MenuClickedHandler);
FTrayIcon.AddMenu('-', nil);
FTrayIcon.AddMenu('メニューアイテム3', MenuClickedHandler);
// TaskTray / StatusBar に登録する画像の指定
Bmp := ImageList1.Bitmap(TSizeF.Create(24, 24), 0); // 画像は ImageList が管理しているので削除するとエラーになる
// 画像の登録(複数登録可能。状態変更時 ChangeIcon を使って使うアイコンを切り替えられる)
FTrayIcon.RegisterIcon('Normal', Bmp);
// 使う画像を指定(指定しないと表示されないので注意!)
FTrayIcon.ChangeIcon('Normal', 'ヒント');
// TaskTray / StatusBar に表示
FTrayIcon.Apply;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FTrayIcon.DisposeOf;
end;
procedure TForm1.MenuClickedHandler(Sender: TObject);
begin
ShowMessage('メニューが押されたよ!');
end;
procedure TForm1.TrayIconClickHandler(Sender: TObject);
begin
ShowMessage('TaskTray アイコンがクリックされたよ!');
end;
TTrayIcon ライブラリ
ということで、Windows / macOS それぞれにアイコンとメニューを実装できたわけですが、それぞれの仕組みを解説します。
作成した TrayIcon ライブラリには以下の4つのファイルがあります。
ソースファイル名 | 役割 |
---|---|
PK.TrayIcon.pas | プログラムから扱う本体 |
PK.TrayIcon.Default.pas | インターフェースの定義 |
PK.TrayIcon.Win.pas | Windows TaskTray 用ソース |
PK.TrayIcon.Mac.pas | macOS StatusBar 用ソース |
ソースから実際に使うのは PK.TrayIcon.pas です。
コレを interface 部で uses して、TForm1 のメンバーとして定義します。
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
System.ImageList,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.ImgList,
PK.TrayIcon; // ←コレ!
type
TForm1 = class(TForm)
ImageList1: TImageList;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FTrayIcon: TTrayIcon; // インスタンスを宣言
procedure MenuClickedHandler(Sender: TObject); // メニューがクリックされたときのイベント
procedure TrayIconClickHandler(Sender: TObject); // TrayIcon がクリックされたときのイベント
public
end;
後の使い方は最初のソースの通りです。
TaskTray 編
Windows 用の PK.TaskTray.Win.pas の仕組みですが…ぶっちゃけ何もやっていません!
というのは VCL には TTrayIcon という純正の TrayIcon コンポーネントがあるじゃないですか!
ということで、コレを内部で使っているだけです。
ただ1つだけ問題がありまして…
プロジェクトの依存関係を確認中...
Project1.dproj をコンパイル中 (Debug, Win32)
dcc32 の "Project1.dpr" コマンド ライン
[dcc32 ヒント] H2161 Warning: Duplicate resource: Type 12 (CURSOR GROUP), ID 32761; File d:\programs\embarcadero\studio\20.0\lib\Win32\release\Controls.res resource kept; file d:\programs\embarcadero\studio\20.0\lib\Win32\release\FMX.Controls.Win.res resource discarded.
[dcc32 ヒント] H2161 Warning: Duplicate resource: Type 12 (CURSOR GROUP), ID 32762; File d:\programs\embarcadero\studio\20.0\lib\Win32\release\Controls.res resource kept; file d:\programs\embarcadero\studio\20.0\lib\Win32\release\FMX.Controls.Win.res resource discarded.
[dcc32 ヒント] H2161 Warning: Duplicate resource: Type 12 (CURSOR GROUP), ID 32763; File d:\programs\embarcadero\studio\20.0\lib\Win32\release\Controls.res resource kept; file d:\programs\embarcadero\studio\20.0\lib\Win32\release\FMX.Controls.Win.res resource discarded.
[dcc32 ヒント] H2161 Warning: Duplicate resource: Type 12 (CURSOR GROUP), ID 32766; File d:\programs\embarcadero\studio\20.0\lib\Win32\release\Controls.res resource kept; file d:\programs\embarcadero\studio\20.0\lib\Win32\release\FMX.Controls.Win.res resource discarded.
[dcc32 ヒント] H2161 Warning: Duplicate resource: Type 12 (CURSOR GROUP), ID 32767; File d:\programs\embarcadero\studio\20.0\lib\Win32\release\Controls.res resource kept; file d:\programs\embarcadero\studio\20.0\lib\Win32\release\FMX.Controls.Win.res resource discarded.
成功
経過時間: 00:00:01.1
という感じで H2161 が出ちゃうんですよね。
H2161 というのはリソースが重複している、というヒントで今回の場合は無視して構いません。
原因は VCL と FMX で同じカーソルリソースを読み込んでいるためです。
TTrayIcon.Win.pas の中で HINT の抑止をしようと思ったのですが、ちょっと難しかったのと、万が一の可能性を考えて残してあります。
StatusBar 編
macOS の上のメニューとか出るところを StatusBar といいます。
ここには実は色々なコントロールを乗せられます。
今回はここにアイコンを乗せます。
StatusItem の生成
まず、コンストラクタでシステムの StatusBar インスタンスを取得して、そこに StatusItem を生成しています。この StatusItem がアイコンを表示するエリアです。
そして、StatusItem と自作のオブジェクト(TTrayMenuItem クラス)を結びつけています。
constructor TTrayIconMac.Create;
begin
inherited Create;
FTrayMenuItem := TTrayMenuItem.Create(Self);
FIcons := TDictionary<String, NSImage>.Create;
FEvents := TDictionary<Pointer, TNotifyEvent>.Create;
// システムの StatusBar を取得
FStatusBar := TNSStatusBar.Wrap(TNSStatusBar.OCClass.systemStatusBar);
// StatusItem を生成
FStatusItem := FStatusBar.statusItemWithLength(NSVariableStatusItemLength);
// StatusItem と TTrayMenuItem と結びつける
FStatusItem.setTarget(FTrayMenuItem.GetObjectID);
FStatusItem.setHighlightMode(true);
FMenu := TNSMenu.Create;
FMenu := TNSMenu.Wrap(FMenu.initWithTitle(StrToNSStr(Application.Title)));
end;
TTrayMenuItem
TTrayMenuItem は TOCLocal を継承したクラスで、Delphi のクラスですが、Objective-C からも呼び出せるクラスです。
今回はメニューのクリックで Objective-C から呼び出せされます。
でも、実装はたった↓これだけ。
constructor TTrayMenuItem.Create(const iOwner: TTrayIconMac);
begin
inherited Create;
FOwner := iOwner;
end;
procedure TTrayMenuItem.DispatchMenuClick(Sender: Pointer);
begin
// クリックされたら TTrayIcon の DispatchMenuClick を呼ぶ
FOwner.DispatchMenuClick(Sender);
end;
function TTrayMenuItem.GetObjectiveCClass: PTypeInfo;
begin
Result := TypeInfo(ITrayMenuItem); // 自分の型情報(NSObject を継承している)を返す
end;
Icon と Menu の設定
肝心のメニューを追加すると所と、アイコンを設定するところはこんな感じです。
// メニューの追加
procedure TTrayIconMac.AddMenu(const iName: String; const iEvent: TNotifyEvent);
var
Item: NSMenuItem;
P: Pointer;
begin
if iName = '-' then // '-' を指定されたらセパレータを追加
Item := TNSMenuItem.Wrap(TNSMenuItem.OCClass.separatorItem)
else
begin
// MenuItem を作って、FTrayMenuItem の DispatchMenuClick と結びつける
Item := TNSMenuItem.Create;
Item :=
TNSMenuItem.Wrap(
Item.initWithTitle(
StrToNSStr(iName),
sel_getUid(MarshaledAString('DispatchMenuClick:')),
StrToNSStr('')
)
);
// GetObjectID は Objective-C 側から見た時のポインタ
Item.setTarget(FTrayMenuItem.GetObjectID);
P := (Item as ILocalObject).GetObjectID;
FEvents.Add(P, iEvent);
end;
// NSMenu に NSMenuItem を追加
FMenu.addItem(Item);
end;
procedure TTrayIconMac.Apply;
begin
// NSMenu を NSStatusItem に設定
FStatusItem.setMenu(FMenu);
end;
procedure TTrayIconMac.ChangeIcon(const iName, iHint: String);
var
Image: NSImage;
begin
// アイコンを NSStatusItem に設定
if FIcons.TryGetValue(iName, Image) then
FStatusItem.setImage(Image);
// ToolTip 文字列を設定
FStatusItem.setToolTip(StrToNSStr(iHint));
end;
// アイコンを登録
procedure TTrayIconMac.RegisterIcon(const iName: String; const iIcon: TBitmap);
begin
// BitmapToMenuBitmap は FMX.Helpers.Mac に定義されている関数
FIcons.Add(iName, BitmapToMenuBitmap(iIcon, 16));
end;
と、こちらも定石通りにプログラムするだけです。
まとめ
前に書いた「FireMonkey で Window Drag」と組み合わせたり、もちろん普通に常駐するプログラムを書いたりと、TrayIcon でアプリケーションの幅が広がりそうです。