ホーム >プログラム >Delphi 6 ローテクTips

タスクバーのメニューにプログラム独自のメニューを追加

タスクバーのプログラムアイコンを右クリックした時に表示されるポップアップメニューにプログラム独自のメニューアイテムを追加しようというページ。


WinampではタスクバーのメニューにWinamp独自のメニューがずらっと表示されます。
それと同じようなものを自分のプログラムに組み込もうというのがこのページの趣旨です。

タスクバーのメニューにプログラム独自のメニューを表示させています。

参考サイト

表示

タスクバーのメニューにプログラム独自のメニューを表示させるのは意外と簡単にできます。
まず肝心のメニューはポップアップメニューをメニューデザイナで作成します。
注意点は2点。

要するにオーナードローモードにしないということです。
オーナードローモードにしてしまうとメニューが空白になってしまいます。

空のメニュー表示になってしまった状態。

ということでメニューにUnicodeを表示でやった技は残念ながら使えません。
それ以外は特に気をつける点はありません。
AutoPopupプロパティの値はTrueでもFalseでもどちらでも構いません。
同様にTrackButtonの値もどちらでも構いません。

procedure TForm1.FormCreate(Sender: TObject);
var
  lh_Menu : HMENU;
begin

  ...

  //タスクバーのメニュー。
  lh_Menu := GetSystemMenu(Application.Handle, False);
  InsertMenu(lh_Menu, 0, MF_BYPOSITION or MF_SEPARATOR, 0, nil);
  InsertMenu(lh_Menu, 0, MF_BYPOSITION or MF_POPUP, PopupMenu1.Handle, 'CheaPlayer(&C)');
  DrawMenuBar(lh_Menu);

  ...

end;

まずGetSystemMenuにApplicationのハンドルを渡してタスクバーのメニューのメニューハンドルを取得します。
取得したメニューハンドルを使ってセパレータと先にメニューデザイナで作ったポップアップメニューを表示する項目を挿入します。
この例では「CheaPlayer」としています。
セパレータの挿入は特に問題はないと思います。
その次のInsertMenu APIで第3引数にMF_POPUPを指定して第4引数にメニューデザイナで作成したポップアップメニュー(PopupMenu1)のハンドルを指定しています。
ここがポイントです。
MF_POPUPを指定することでPopupMenu1をタスクバーのメニューのサブメニューとして表示するようにしています。
表示に関してはこれだけです。

メニューコマンドの実行

表示するのは簡単なのですが、そのままではメニューアイテムを選択しても何も起きません。
それはメニューアイテムを選択したというメッセージがフォームではなくApplicationに行くためです。
メニューデザイナで作成したメニューは本来フォームの管轄になるのでフォームにメッセージが行かなければ何も起きません。
タスクバーに表示したメニューアイテムを選択したというメッセージは残念ながらフォームではなくApplicationに行きます。
ということでApplicationに行ったメニューのメッセージは自前で処理しなければなりません。

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
  function _GetMenuItem(AMenuItem: TMenuItem; iItemID: UINT): TMenuItem;
  var
    i : Integer;
  begin
    Result := nil;
    if (AMenuItem.Name <> '') then
    begin
      if (AMenuItem.Command = iItemID) then
      begin
        Result := AMenuItem;
        Exit;
      end;
    end;

    if (AMenuItem.Count > 0) then
    begin
      for i := 0 to AMenuItem.Count -1 do
      begin
        Result := _GetMenuItem(AMenuItem.Items[i], iItemID);
        if (Result <> nil) then
        begin
          Exit;
        end;
      end;
    end;
  end;
var
  li_ItemID  : UINT;
  l_MenuItem : TMenuItem;
  l_Action   : TAction;
begin
  if  (Msg.hwnd    = Application.Handle)
  and (Msg.message = WM_SYSCOMMAND)
  and (Msg.wParam  < $F000)
  then
  begin
    li_ItemID  := Msg.wParam;
    l_MenuItem := _GetMenuItem(PopupMenu_Main.Items, li_ItemID);
    if (l_MenuItem <> nil) then
    begin
      l_Action := TAction(l_MenuItem.Action);
      if (l_Action <> nil) then
      begin
        if (l_Action.AutoCheck) then
        begin
          l_Action.Checked := not(l_Action.Checked);
        end;
      end else
      begin
        if (l_MenuItem.AutoCheck) then
        begin
          l_MenuItem.Checked := not(l_MenuItem.Checked);
        end;
      end;

      if (@l_MenuItem.OnClick <> nil) then
      begin
        if (l_Action <> nil) then
        begin
          l_MenuItem.OnClick(l_MenuItem.Action);
        end else
        begin
          l_MenuItem.OnClick(l_MenuItem);
        end;
      end;
    end;
  end;
end;

これでOKです。

飛んできたメッセージのうちハンドルがApplicationのハンドルでメッセージがWM_SYSCOMMANDでなおかつメッセージのwParamの値が0xF000未満ならメニューアイテムを選択したであろうメッセージと判断できます。

WM_SYSCOMMANDメッセージは他にもアプリケーションを最小化したり元に戻したりした場合にも飛んできますがそれらの場合wParamの値は$F000より大きい値になっています。

この時のwParamの値はメニューアイテムを識別するためのユニークな値(MenuItemID)になっているのでこの値を基にどのメニューアイテムが選択されたのかを調べることができます。
選択されたメニューアイテムを取得するにはwParamの値をメニュー内のメニューアイテムのCommandプロパティと総当りで比較して行きます。
そして一致するメニューアイテムがあればそれが選択されたメニューアイテムであると判断できます。
上のコードでは_GetMenuItem関数がその処理を行っています。

選択されたメニューアイテムが取得できたら今度はそのメニューコマンドを実行させます。
メニューコマンドの実行はメニューアイテムのOnClickイベントを呼ぶことで行います。
この時の引数はメニューアイテムのActionプロパティにアクションがセットされていればそのActionを、セットされていなければメニューアイテム自身を指定します。
OnClickイベントを呼ぶ前にメニュー、もしくはアクションのAutoCheckプロパティを調べてTrueであったらそれぞれのCheckedプロパティの値を反転させます。
そしてメニューアイテムのOnClickイベントを呼びます。