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

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

Gtk#のコード作成上の覚え書き

PyGTKでシステムトレイを使用してメニューを表示する」と同じ処理をGtk#で書いてみたのだが、そのコードを貼り付ける前に作成上の幾つかのメモを先に書いておくことにする。C#言語に関するものも含む。

C#におけるクラスの継承

クラス定義のとき、クラス名の右に「 : [親クラス名]」を書くことによりそのクラスを継承できる。

class TrayMenu : Gtk.Menu  // Gtk.Menuを継承

Pythonとは異なり、複数のクラスを親クラスとした(多重)継承は行えない仕様となっている。多重継承ができると使い方によっては問題が起こる場合がある*1ようで、代わりにこの問題を回避するための「インターフェース」というもの*2がある。
例えばPyGTKにおいてgtk.ListStoreオブジェクトやgtk.TreeStoreオブジェクトが共通して継承しているgtk.TreeModelクラスはGtk#ではクラスではなくインターフェース「public interface TreeModel」となっている。

MonoDevelopにおけるクラス名やメンバ名の補完について

MonoDevelopでは

Gtk.

と入力すると、名前空間Gtk」のクラスや定数、インターフェースなどが候補一覧として表示されるので、これを利用して入力の手間を省けるだけでなく、どのような名前の項目があるのかを知ることもできる。
メンバについても

item2 = new Gtk.ImageMenuItem ("item2");

のようにしてGtk.ImageMenuItemオブジェクトを生成したときに

item2.

と入力するとGtk.ImageMenuItemクラスで利用可能なメンバ一覧が表示される。
補完は「.」の入力に反応する*3形のため、先頭にusing文を記述してその部分の省略を行う書き方ではかえって書きにくい。例えば

Gtk.ImageMenuItem item1;
(あるいは)
ImageMenuItem item1;

と書きたいときに

Ima

と始めても候補は出ないが

Gtk.

と書けば候補が出てきて入力しやすい。また、Pythonのimport文(from付きの場合)にも言えるが、個人的には、(端末への出力に使用するConsoleなどは例外として)using文をたくさん書いて表記を短くするのよりも「名前空間のフルパス」表記のほうが(長いが)多くの場合において見やすい気がする。

シグナルとハンドラについて

Gtk#ではGTK+(C言語)やPyGTKのようなGLibの「シグナル」機能を用いる形とは異なり、各GUI部品のオブジェクトの中にある、シグナルの種類に相当するメンバ変数に、ハンドラを引数とするオブジェクトを渡す形となる。

  item1.Activated += new EventHandler (OnItem1Activated);
(中略)
}
(中略)
void OnItem1Activated (object sender, System.EventArgs e)
{
  (item1が選択されたときの処理)
}

このときの(System.)EventHandlerオブジェクトは関数を指し示す(参照を内部に持つ)オブジェクト「デリゲート」と呼ばれるものらしいが、特にそのような用語を意識したりしなくても上のような形さえ分かっていればイベントは扱える。Windows上で主に使用されているGUIツールキットWindows.Formsにおいてもイベントの処理にはこのような形が使用されているようだ。
引数の「sender」はイベントの発生したオブジェクトで、GTK+(C言語)やPyGTKにおけるハンドラの最初の引数と同様。
(2009/4/6)これはobject型として渡されるが、型変換して取り出すことができる。
使用されるデリゲートはSystem.EventHandlerオブジェクトだけではなく、行う操作によって色々な種類がある。本記事の下のほうに貼り付けてあるコードではGtk.DeleteEventHandlerオブジェクトを使用している。
なお、デリゲートを含む名前空間のusing文*4を消す(書かない)とMonoDevelopでの補完時に

item1.Activated += new System.EventHandler[カーソル位置]

のようにフルパスで補完されるのだが、あまりメリットはない気がする。コード最初のusing文については「このコードではどの名前空間を使用しているか」を(読んでいる人間に)示す意味もあるため、そのままにしておいたほうが良さそう。
(2009/4/6)これと同様なのが宣言時にnewでインスタンスを作成するパターンで、using文があると

Gtk.Foo = new Foo[カーソル位置]

のようにフルパス名前空間にならない。

親クラスのコンストラクタの呼び出しについて

PyGTKで書いたコードではクラスを継承した子クラス内で親クラスのコンストラクタ(__init__())を手動で呼んでいるが、C#では(継承の流れの順番通りに)祖先のクラスのコンストラクタが自動的に呼ばれる。
親クラスのコンストラクタへの引数が渡されない場合は特に何かを書く必要はないが、もし(親クラスのコンストラクタに)引数を渡したい場合は

public [クラス名]() : base([引数...])
{
  (コンストラクタの処理)

のように記述する。*5
(2009/4/6)一部記述を微調整
下はGtk.Windowオブジェクトを継承した例。

using Gtk;

namespace GtkSharpHello2
{
  class MainWindow : Gtk.Window
  {
    /*
     * Gtk.Windowオブジェクトにはウィンドウタイプとタイトルを
     * 同時に引数で受け付けるバージョン(オーバーロード)は存在しないため
     * タイプ(読み込み専用なので変更不可)のみを渡すバージョンを使用して
     * タイトルを後で引数から設定している
     */
    public MainWindow (Gtk.WindowType type, string title) : base(type)
    {
      this.Title = title;
      this.DeleteEvent += new DeleteEventHandler (this.OnDeleteEvent);
      this.ShowAll ();
    }
    void OnDeleteEvent (object sender, Gtk.DeleteEventArgs e)
    {
      Gtk.Application.Quit ();
    }
  }
  class MainClass
  {
    public static void Main (string[] args)
    {
      Gtk.Application.Init ();
      MainWindow win = new MainWindow (Gtk.WindowType.Toplevel, "Hello");
      win.Show ();
      Gtk.Application.Run ();
    }
  }
}

コード中のコメントに「バージョン(オーバーロード)」と書いたが、クラス内には同名のメンバ関数で引数の型や数が異なる複数のバージョン(オーバーロード)を混在でき、呼んだときに渡された引数に合ったバージョンが自動的に使用される。
リファレンスを見ても、コンストラクタなどにおいて、引数の取り方のバリエーションが幾つか設けられているものがあることが分かる。

関連記事:

使用したバージョン:

*1:Pythonのチュートリアルにも注意書きがある

*2:クラスの一部分」のようなもので、複数のインターフェースをクラスで用いることができる

*3:同様に「new」にも反応する

*4:この例では「using System;」

*5:base()を書かないと親クラスの引数なしコンストラクタが自動的に呼ばれるということになる