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

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

PyGTKの簡単な例をPythonのコードのみで書く

以前はgladeファイルuiファイルを使用してユーザインターフェース部分をコードから分離して書いたのだが、分離させずにPythonのコードのみで記述することももちろんできる。ただし、書くのは色々と面倒。

コード

PyGTK + Gladeの簡単な例」をもとにしている。
[任意]ファイル名: pygtktest-noglade.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import sys
import os
try:
  import pygtk
  pygtk.require("2.0")
except:
  pass
try:
  import gtk
except:
  sys.exit(1)

class PyGTKTestNoGlade:
  """
  テスト
  """
  def __init__(self):
    """
    初期化処理
    """
    # ウィンドウ
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)  # 種類をトップレベルに
    self.window.set_title("Test")                  # タイトル
    self.window.set_size_request(320, 200)         # 最小サイズ
    #self.window.set_default_size(320, 200)        # 既定のサイズを指定する場合

    # ショートカットキー(アクセラレータ)
    self.accelgroup = gtk.AccelGroup()
    self.window.add_accel_group(self.accelgroup)   # ウィンドウに関連付け

    # メニューバー項目
      # 「ファイル」の項目
    self.menuitem = gtk.MenuItem("_File", True)  # 真偽値は下線の処理の有無
      # 「終了」のストックアイコン/ショートカット付きメニュー項目
      # ストックIDの部分は"gtk-quit"のような文字列でもよい
      # gtk.ImageMenuItemはgtk.MenuItemの子クラス
    self.imagemenuitem = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup)
    #self.imagemenuitem = gtk.ImageMenuItem("gtk-quit", self.accelgroup)
      # 以下、メニューを構成していく
    self.gtkmenu = gtk.Menu()                    # ファイルのサブメニュー
    self.gtkmenu.add(self.imagemenuitem)         # サブメニューに終了を追加
    self.menuitem.set_submenu(self.gtkmenu)      # ファイルの下にサブメニュー
    self.menubar = gtk.MenuBar()                 # メニューバーウィジェット
    self.menubar.append(self.menuitem)           # バーにファイルメニューを追加

    # ボタン
    self.button = gtk.Button()
    self.button.set_label("Test")

    # ステータスバー
    self.statusbar = gtk.Statusbar()

    # 垂直ボックス(コンテナ=入れ物 の一種)
    # 2つの真偽値(2番目と3番目の引数)はそれぞれ
    # expand(場所を最大限広く確保するか)とfill(部品を最大限広く描画するか)
    # (厳密には親子ウィジェット間のスペースの使い方として定義されている)
    # pack_start()は手前から詰めていく形でpack_end()は後ろから詰めていく形
    # http://www.pygtk.org/docs/pygtk/class-gtkbox.html
    self.vbox = gtk.VBox(False, 0)  # 真偽値は子ウィジェットを均等に配置するか
      # 上からメニューバー/ボタン/ステータスバーの順で入れる
    self.vbox.pack_start(self.menubar, False, True, 0)
    self.vbox.pack_start(self.button, True, True, 0)
    self.vbox.pack_start(self.statusbar, False, True, 0)
    self.window.add(self.vbox)  # 空のウィンドウの中に垂直ボックスを入れる

    # シグナルを手動で接続
    self.button.connect("clicked", self.on_button_clicked)
    self.imagemenuitem.connect("select", self.on_imagemenuitem_select)
    self.imagemenuitem.connect("deselect", self.on_imagemenuitem_deselect)
    self.imagemenuitem.connect("activate", gtk.main_quit)
    self.window.connect("delete_event", gtk.main_quit)

    # 表示
    self.window.show_all()
  def on_button_clicked(self, widget):
    """
    ボタンが押されたときの処理
    """
    print "on_button1_clicked"
  def on_imagemenuitem_select(self, widget):
    """
    メニュー項目が選択されたときの処理
    """
    print "on_imagemenuitem1_select"
  def on_imagemenuitem_deselect(self, widget):
    """
    メニュー項目が選択解除されたときの処理
    """
    print "on_imagemenuitem1_deselect"
  def main(self):
    """
    GTK+のメインループを呼ぶ
    """
    gtk.main()

if __name__ == "__main__":
  app = PyGTKTestNoGlade()
  app.main()

(2009/4/17)その後の色々なコード例ではメインウィンドウなどの部品のオブジェクトについてクラスの継承を用いている。これによりコードがより書きやすく分かりやすくなる。
GUIのプログラムにおいて部品などのクラスを継承することは色々なところで役に立つということが分かってきた。
上のコードは

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import sys
import os
try:
  import pygtk
  pygtk.require("2.0")
except:
  pass
try:
  import gtk
except:
  sys.exit(1)


class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  def __init__(self, *args, **kwargs):
    """
    初期化処理
    """
    gtk.Window.__init__(self, *args, **kwargs)   # 親クラスの初期化が必須
    self.set_title("Test")                       # タイトル
    self.set_size_request(320, 200)              # 最小サイズ
    #self.set_default_size(320, 200)             # 既定のサイズを指定する場合

    # ショートカットキー(アクセラレータ)
    self.accelgroup = gtk.AccelGroup()
    self.add_accel_group(self.accelgroup)        # ウィンドウに関連付け

    # メニューバー項目
      # 「ファイル」の項目
    self.menuitem = gtk.MenuItem("_File", True)  # 真偽値は下線の処理の有無
      # 「終了」のストックアイコン/ショートカット付きメニュー項目
      # ストックIDの部分は"gtk-quit"のような文字列でもよい
      # gtk.ImageMenuItemはgtk.MenuItemの子クラス
    self.imagemenuitem = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accelgroup)
    #self.imagemenuitem = gtk.ImageMenuItem("gtk-quit", self.accelgroup)
      # 以下、メニューを構成していく
    self.gtkmenu = gtk.Menu()                    # ファイルのサブメニュー
    self.gtkmenu.add(self.imagemenuitem)         # サブメニューに終了を追加
    self.menuitem.set_submenu(self.gtkmenu)      # ファイルの下にサブメニュー
    self.menubar = gtk.MenuBar()                 # メニューバーウィジェット
    self.menubar.append(self.menuitem)           # バーにファイルメニューを追加

    # ボタン
    self.button = gtk.Button()
    self.button.set_label("Test")

    # ステータスバー
    self.statusbar = gtk.Statusbar()

    # 垂直ボックス(コンテナ=入れ物 の一種)
    # 2つの真偽値(2番目と3番目の引数)はそれぞれ
    # expand(場所を最大限広く確保するか)とfill(部品を最大限広く描画するか)
    # (厳密には親子ウィジェット間のスペースの使い方として定義されている)
    # pack_start()は手前から詰めていく形でpack_end()は後ろから詰めていく形
    # http://www.pygtk.org/docs/pygtk/class-gtkbox.html
    self.vbox = gtk.VBox(False, 0)  # 真偽値は子ウィジェットを均等に配置するか
      # 上からメニューバー/ボタン/ステータスバーの順で入れる
    self.vbox.pack_start(self.menubar, False, True, 0)
    self.vbox.pack_start(self.button, True, True, 0)
    self.vbox.pack_start(self.statusbar, False, True, 0)
    self.add(self.vbox)  # 空のウィンドウの中に垂直ボックスを入れる

    # シグナルを手動で接続
    self.button.connect("clicked", self.on_button_clicked)
    self.imagemenuitem.connect("select", self.on_imagemenuitem_select)
    self.imagemenuitem.connect("deselect", self.on_imagemenuitem_deselect)
    self.imagemenuitem.connect("activate", gtk.main_quit)
    self.connect("delete_event", gtk.main_quit)
  def on_button_clicked(self, widget):
    """
    ボタンが押されたときの処理
    """
    print "on_button1_clicked"
  def on_imagemenuitem_select(self, widget):
    """
    メニュー項目が選択されたときの処理
    """
    print "on_imagemenuitem1_select"
  def on_imagemenuitem_deselect(self, widget):
    """
    メニュー項目が選択解除されたときの処理
    """
    print "on_imagemenuitem1_deselect"

class PyGTKTestNoGladeWithInheritance:
  """
  クラスの継承を用いた場合のGladeを用いないテスト
  """
  def main(self):
    """
    ウィンドウを作成してGTK+のメインループを呼ぶ
    """
    win = MainWindow(gtk.WINDOW_TOPLEVEL)  # gtk.WINDOW_TOPLEVELは省略可
    win.show_all()
    gtk.main()


if __name__ == "__main__":
  app = PyGTKTestNoGladeWithInheritance()
  app.main()

のようになる。MainWindowクラス内で「self.window.[メンバ]」と書いていたものが「self.[メンバ]」のように変わっている。

メニュー

メニューはgtk.UIManagerクラスを使用することで、XMLの記述により簡単に構成することができる(PyGTK 2.4以上が必要)のだが、今回はそれを使用せず、原始的な方法で書くことにした。
「終了」という1つの項目で構成される「ファイル」メニューを含むメニューバーを作るのには

  • 「ファイル」のメニュー項目(gtk.MenuItem)
  • 「終了」のメニュー項目(gtk.ImageMenuItem)*1
  • 「ファイル」を展開したときに出るメニュー(gtk.Menu)
  • ウィンドウ上に配置するメニューバー(gtk.MenuBar)

これだけのものが必要になる。
「終了」のメニュー項目に関しては、アイコンテーマに含まれる「終了」のアイコン*2Ctrl+Qというショートカットキーの設定を適用することができる。そのためには、コードのように「ストックID」と「ウィンドウに関連付け済みのアクセラレータのグループ(gtk.AccelGroupオブジェクト)」を指定する。

シグナル接続(ユーザのアクションを種類ごとに処理に関連付ける)

以前にも書いているように、手動でのシグナル接続としてgobject.GObjectオブジェクトのメンバ関数connect()(リファレンス)を使用する。

垂直ボックス

GTK+アプリケーションでは、場所を分割するために垂直ボックス(gtk.VBox)や水平ボックス(gtk.HBox)などのコンテナ(入れ物)ウィジェットを使用することが多い。
コードを書いてユーザインターフェースのレイアウトを構成していく場合でも、Gladeでグラフィカルに作業していくのと感覚としては同じで、一度Gladeで完成形を作っておき、その構造通りにコードを書いていけば、基本的には同じものが作れる。
今回はgladeファイルのときの例にならって垂直ボックスを3分割してメニュー/ボタン/ステータスバーを入れていった。
gtk.HBoxやgtk.VBoxのコンストラク*3では2つの引数を渡し、1つ目は真偽値で子ウィジェットを均等に配置するかどうか、2つ目は余白スペースとなる。
pack_start()は手前から順に子ウィジェットを中に入れていく。注意するのは2番目と3番目の引数で、これらはGladeでは「パッキング」タブにある「展張」と「フィル」にそれぞれ相当する。スペースの取り方が変わるのだが、Glade上でいじってみて確認するのが分かりやすい。
pack_end()は後ろから順に入れる点を除いてpack_start()と同じ。

*1:gtk.MenuItemの子クラス

*2:GTK+のアイコンテーマには「ストックアイコン」と呼ばれる基本的な(ユーザインターフェース上で使用される)アイコンが含まれている

*3:今回のコード中では「gtk.VBox(False, 3)」の部分