試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Gtk#でシステムトレイを使用してメニューを表示する

Gtk#のコード作成上の覚え書き」のメモに加えて少しの追加メモを書いてから、「PyGTKでシステムトレイを使用してメニューを表示する」をGtk#に移植したものを貼り付ける。

メニュー項目の作成について

内蔵のストックアイコンを用いつつメニュー項目には好きな文字列を指定したいという場合、下のような流れとなる。

this.item1 = new Gtk.ImageMenuItem ("item1");
this.item1.Image = new Gtk.Image (Gtk.Stock.Preferences, Gtk.IconSize.Menu);

ストックアイコンに応じた文字列(「終了」など)を自動的に付けたい場合は

this.item_quit = new Gtk.ImageMenuItem (Gtk.Stock.Quit, null);

のようになる。もしそのストックIDに関連付けられている「Ctrl+Q」のような文字列を付けたいのであれば

Gtk.AccelGroup group = new AccelGroup ();
this.item_quit = new Gtk.ImageMenuItem (Gtk.Stock.Quit, group);

のようにGtk.AccelGroupオブジェクトを別途用意の上でストックIDの後ろの引数で指定するバージョン(オーバーロード)のコンストラクタを使用する。

システムトレイアイコン上のメニューのポップアップに関して

システムトレイアイコンのポップアップメニューを開く操作は

  • Gtk.StatusIconオブジェクトのメンバ変数PopupMenuにPopupMenuHandlerオブジェクト(デリゲート)*1を追加
  • ハンドラで同オブジェクトのメンバ関数PresentMenu()を呼び、その際には引数の0番と1番をuint型に型変換したものを渡す(付けないとコンパイル時に警告が出る)

という流れで、PyGTKとは随分異なる。

    icon.PopupMenu += new PopupMenuHandler (this.OnPopup);  // メニューの関連付け
(中略)
  }
(中略)
  void OnPopup (object sender, Gtk.PopupMenuArgs args)
  {
    /* 先頭の引数は型変換するとイベントの発生したGUI部品として取り出せる */
    Gtk.StatusIcon icon = (Gtk.StatusIcon) sender;
    /*
     * システムトレイアイコンの位置に合わせてメニューをポップアップする
     * 2番目と3番目の引数はuint型への型変換が必要(しないと警告が出る)
     */
    icon.PresentMenu (this.menu, (uint) args.Args[0], (uint) args.Args[1]);
  }

コード例

PyGTK版と挙動は同じ。
MonoDevelopで作成する場合、「ソリューション」の「ユーザインターフェース」から「MainWindow」を外しておく。
[任意]ファイル名: GtkSharpSystrayTest.cs

using System;
using Gtk;

namespace GtkSharpSystrayTest
{
  /// <summary>システムトレイのメニュー(ストックアイコン付き)</summary>
  class TrayMenu : Gtk.Menu  // Gtk.Menuを継承
  {
    /// <summary>項目1</summary>
    Gtk.ImageMenuItem item1;
    /// <summary>項目2</summary>
    Gtk.ImageMenuItem item2;
    /// <summary>項目3</summary>
    Gtk.ImageMenuItem item3;
    /// <summary>終了の項目</summary>
    Gtk.ImageMenuItem item_quit;
    /// <summary>システムトレイアイコン</summary>
    Gtk.StatusIcon trayicon;
    /*
     * クラスTrayMenuはGtk.Menuを継承する
     * 今回は親クラスのコンストラクタは引数なしで自動的に呼ばれるので
     * 特に記述はないが、親クラスのコンストラクタに引数を付けたい場合は
     * base([引数...])の指定をして呼び出す
     */
    /// <summary>コンストラクタ</summary>
    /// <param name="trayicon">システムトレイアイコン <see cref="Gtk.StatusIcon"/></param>
    public TrayMenu (Gtk.StatusIcon trayicon)
    {
      this.trayicon = trayicon;  // 今回は引数で受け取っているが、必要ない場合もある
      /*
       * ストックアイコン付きメニュー項目を作成する流れ
       * メンバ変数Imageには
       * ストックアイコンとサイズを指定したGtk.Imageオブジェクトを指定
       */
      this.item1 = new Gtk.ImageMenuItem ("item1");
      this.item1.Image = new Gtk.Image (Gtk.Stock.Preferences, Gtk.IconSize.Menu);
      this.item2 = new Gtk.ImageMenuItem ("item2");
      this.item2.Image = new Gtk.Image (Gtk.Stock.Open, Gtk.IconSize.Menu);
      this.item2.Sensitive = false;  // 選択できない状態にする
      this.item3 = new Gtk.ImageMenuItem ("item3");
      this.item3.Image = new Gtk.Image (Gtk.Stock.Close, Gtk.IconSize.Menu);
      /*
       * 下の形式ではGtk.AccelGroupオブジェクトを作成後
       * 2番目の引数に指定するとストックアイコンと「Ctrl+Q」のような文字列が付く
       */
      this.item_quit = new Gtk.ImageMenuItem (Gtk.Stock.Quit, null);
      //Gtk.AccelGroup group = new AccelGroup ();
      //this.item_quit = new Gtk.ImageMenuItem (Gtk.Stock.Quit, group);
      /* 項目を追加していく */
      this.Append (this.item1);  // 本クラスのメンバ関数(継承したAppend())を呼んでいる
      this.Append (this.item2);
      this.Append (this.item3);
      this.Append (new Gtk.SeparatorMenuItem ());  // 分割線
      this.Append (this.item_quit);
      /* 表示可能にする */
      this.ShowAll ();
      /*
       * シグナルの設定はGTK+っぽくないC#独自(?)な方法で行う
       * これはWindows.Formsでも同様で、左辺のイベントに
       * 右辺の「デリゲート」(関数を指し示すもの)を関連付けるという形になる
       * 各ハンドラの引数もWindows.Formsのハンドラと同様の書き方となっている
       * (「object sender, System.EventArgs e」の形)
       */
      /* トレイアイコンの通常クリック時にOnItem1Activated()を呼ぶ */
      this.trayicon.Activate += new EventHandler (this.OnItem1Activated);
      /* 各項目が選択されたときのハンドラを設定 */
      this.item1.Activated += new EventHandler (this.OnItem1Activated);
      this.item2.Activated += new EventHandler (this.OnItem2Activated);
      this.item3.Activated += new EventHandler (this.OnItem3Activated);
      this.item_quit.Activated += new EventHandler (this.OnItemQuitActivated);
    }
    /*
     * PyGTKでは(selfがある場合はその次に)ウィジェットのオブジェクトが
     * 最初の引数になっているが、Gtk#やWindows.Formsではsenderがそれに当たる
     * 下のほうでは実際に取り出して使用している
     */
    /// <summary>項目1が選択されたときの処理</summary>
    /// <param name="sender">項目1 <see cref="System.Object"/></param>
    /// <param name="e">イベント引数 <see cref="System.EventArgs"/></param>
    void OnItem1Activated (object sender, System.EventArgs e)
    {
      Console.WriteLine ("item1 activated");
    }
    /// <summary>項目2が選択されたときの処理</summary>
    /// <param name="sender">項目2 <see cref="System.Object"/></param>
    /// <param name="e">イベント引数 <see cref="System.EventArgs"/></param>
    void OnItem2Activated (object sender, System.EventArgs e)
    {
      this.item2.Sensitive = false;   // item2を選択できなくする
      this.item3.Sensitive = true;    // item3を選択できるようにする
      this.trayicon.Blinking = false;  // 点滅状態を解除
      Console.WriteLine ("item2 activated");
    }
    /// <summary>項目3が選択されたときの処理</summary>
    /// <param name="sender">項目3 <see cref="System.Object"/></param>
    /// <param name="e">イベント引数 <see cref="System.EventArgs"/></param>
    void OnItem3Activated (object sender, System.EventArgs e)
    {
      this.item2.Sensitive = true;
      this.item3.Sensitive = false;
      this.trayicon.Blinking = true;  // 点滅開始
      Console.WriteLine ("item3 activated");
    }
    /// <summary>終了の項目が選択されたときの処理</summary>
    /// <param name="sender">終了の項目 <see cref="System.Object"/></param>
    /// <param name="e">イベント引数 <see cref="System.EventArgs"/></param>
    void OnItemQuitActivated (object sender, System.EventArgs e)
    {
      Gtk.Application.Quit ();  // Gtk#アプリケーションのメインループ終了
    }
  }
  
  /// <summary>システムトレイのテスト</summary>
  class GtkSharpSystrayTest
  {
    /// <summary>ポップアップメニュー</summary>
    TrayMenu menu;
    /// <summary>アプリケーションのメイン処理</summary>
    public void Main ()
    {
      Gtk.Application.Init ();  // Gtk#アプリケーションの初期化
      /*
       * ストックアイコンをシステムトレイに使用する場合は
       * この静的メンバ関数からオブジェクトを得る
       * Gdk.Pixbufなどの場合はGtk.StatusIconクラスからnewで生成
       * 他にはアイコン名を使用したNewFromIconName()もある
       * なお、Windows.Formsもメインループは「Application.Run()」で
       * 開始するようだ(引数が付く点が異なる)
       */
      Gtk.StatusIcon icon = Gtk.StatusIcon.NewFromStock (Gtk.Stock.DialogInfo);
      icon.Tooltip = "test";  // マウスポインタを少し乗せたときに出る文字列
      this.menu = new TrayMenu (icon);
      icon.PopupMenu += new PopupMenuHandler (this.OnPopup);  // ポップアップイベント
      Gtk.Application.Run ();  // Gtk#アプリケーションのメインループ開始
    }
    /// <summary>メニューをポップアップ</summary>
    /// <param name="sender">システムトレイアイコン <see cref="System.Object"/></param>
    /// <param name="args">ポップアップメニュー引数 <see cref="Gtk.PopupMenuArgs"/></param>
    void OnPopup (object sender, Gtk.PopupMenuArgs args)
    {
      /* 先頭の引数は型変換するとイベントの発生したGUI部品として取り出せる */
      Gtk.StatusIcon icon = (Gtk.StatusIcon) sender;
      /*
       * システムトレイアイコンの位置に合わせてメニューをポップアップする
       * 2番目と3番目の引数はuint型への型変換が必要(しないと警告が出る)
       */
      icon.PresentMenu (this.menu, (uint) args.Args[0], (uint) args.Args[1]);
    }
  }
  
  /// <summary>メインクラス</summary>
  class MainClass
  {
    /// <summary>処理の開始</summary>
    /// <param name="args">コマンド行引数 <see cref="System.String"/></param>
    public static void Main (string[] args)
    {
      GtkSharpSystrayTest app = new GtkSharpSystrayTest ();
      app.Main ();
    }
  }
}

(2009/5/22)「this」が使用できるところで全て「this」を記述
メモリ使用量についてPyGTK版と比較してみたので「ps axl」の結果から抜粋する。

F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
0  1000 30158 19789  20   0 242824 17840 sys_po S+   pts/5      0:00 /usr/bin/python [systraytest.pyの場所]
0  1000 30169 30167  20   0 220360 14592 sys_po Sl+  pts/3      0:00 mono [GtkSharpSystrayTest.exeの場所]

RSS」の部分がKiB単位の使用量となっている。PyGTK版よりもメモリ使用量は少ないように見える。

関連記事:

使用したバージョン:

*1:関数への参照を内部に持つオブジェクト