PyGTK と Glade を使ったアプリケーションの作成
Building an Application with PyGTK and Glade by Mark Mruss
#この文書はクリエイティブ・コモンズの帰属 - 非営利 - 同一条件許諾 2.5でライセンスされている。
はじめのチュートリアルで PyGTK と Glade をやってから [訳注:邦訳はこちら]、ほかのもっと複雑なチュートリアルを書こうと思っていたんだけど、トピックが思い付かなかったから(*チュートリアルに関する提案があったら教えてほしい)、シンプルなアプリケーションを作ることにした。役に立つトピックがそこから出てくるかもしれないからね。
僕が思いついたアイデアは -- シンプルだといいけど -- 僕が飲むいろんな種類のワインと、僕がそれらをどれだけ気に入っているかを整理できるプログラムを作ることだ。このプログラムは僕が前から作りたいと思っていたものだし、PyGTK を一緒に勉強できて一石二鳥だと思ったからだ。
このプロジェクトを PyWine と呼ぶことにする。このチュートリアルのソースコードと Glade ファイルは下のリンクに置いておく:
http://www.learningpython.com/source/PyWine.tar.gz
はじめにするのは、PyWine という名前のプロジェクト・ディレクトリに Glade プロジェクトをスタートさせることだ。新しいウィンドウを作って、それに "mainWindow" という名前をつけて、タイトルを "PyWine" にする。そして、はじめのチュートリアルでやったみたいに、破壊シグナル(destroy signal)にハンドラを追加する。
次に4行の「垂直ボックス」をウィンドウに追加する。これを上から順番に、メニューバー、ツールバー [訳注:アイテムの数を1にする]、リスト/ツリー・ビュー、ステータスバーにしてみよう。あと、ツリービューを "wineView" という名前にする。
ウィンドウに追加したリスト/ツリー・ビューは、ワインの情報をユーザに表示するためのものだ。だから、はじめにするのは、ダイアログへのワインの追加をユーザに許可することだ。これをするには、メニュー・コマンドとツールバー・ボタンを使う。
はじめに追加するのはツールバー・ボタンだ。これは単にパレットから「ツールバー・ボタン」を選んで、それからウィンドウに追加したツールバーをクリックすればいい。これはツールバーに変な見た目のボタンを追加するはずだ。それで今度は「ウィジット」タブのツールバー・ボタンのプロパティを編集する。まずは名前を "tbAddWine" にして、ラベルを "Add Wine" [訳注:日本語で「ワインの追加」などでもオッケー]、アイコンをストックの「追加(gtk_add)」アイコンにしてみよう。
ここで tbAddWine ボタンのクリックのイベントのためのハンドラを追加する。ただ、今回はハンドラの名前を、デフォルトの "on_tbAddWine_clicked" じゃなくて、"on_AddWine" と呼ぶことにしよう。
メニューバーに追加
さてメニューに移ろうか。ユーザが、タスクバーあるいはメニューバーから、アイテムを追加できるようにしたいからね。まず、ウィンドウのメニューバーをクリックして、プロパティ・ウィンドウの「ウィジット」タブに行ってほしい。次に、メニューバーを編集するために、「メニューの編集」ボタンをクリックする。
それから、新しいトップレベルのメニューアイテムを追加するために、「追加」ボタンをクリックして、そのラベルを "_Add" [訳注:「追加」でもいい] にセットしよう。そしてメニューリストの "_Add"(「追加」)を選んだ状態で、「子ウィジットの追加」ボタンをクリックする。これで Add メニューの中サブメニューができる。新しいアイテムのラベルを "_Wine"(「ワイン」) にセットして、そのハンドラを "on_AddWine" にセットしよう。
ここで、Add ツールバーのボタン・クリックのイベントと、 Add | Wine メニューによる起動が同じハンドラを使用していることに気づいたかもしれない。これは、その2つのウィジットが、「ワインのリストにワインを追加する」というまったく同じ仕事をするためだ。
ユーザが Add Wine ボタンをクリックするか、あるいは Add | Wine をメニューから選んだときの動作は、ダイアログが登場してワインに関する詳細を入力させるというものだ。ユーザがダイアログ終了のために "Ok" ボタンを押したとき、入力されたワインの情報は ListView に追加される。
ダイアログの作成
それで、次にするのは、ユーザが新しいワインを追加するためのダイアログを作成することだ。ここではシンプルに、ワインの名前と、ワイン醸造所(ワイナリー)、製造年、あとブドウの種類を入力できるようにしよう。
新しいダイアログを作成するには、単に Glade パレットの「ダイアログ」 ボタンをクリックすればいい。これで「新しいダイアログ」ウィンドウが開く。[キャンセル] と [OK] ボタンがついている標準ボタンレイアウトのオプションを選ぼう。次にダイアログの名前を "wineDlg" にして、ウィンドウのタイトルを "Add Wine" にする。
次に、wineDlg にいろんなものを配置するためのテーブルを使うことにする。ウィジットを追加するいつもの方法で、「テーブル」をダイアログに追加してみよう。行の数を4に、列の数をを2にセットする。それから下の画像にあるような状態になるまで、「ラベル」と「テキスト・エントリ」ウィジットを使ってテーブルのスペースを埋めていこう:
僕はテーブルの行の間に3ピクセルのスペースを空けた。この設定は、テーブルのプロパティの「ウィジット」タブでできる。もしテーブル選択がやりにくかったら、メインの Glade ウィンドウのメニューから 「表示」 | 「ウィジット・ツリーの表示」を選んで、ウィジット・ツリーからテーブルを選べばいい。あるいは、SHIFT キーを押したまま、テーブルのウィジットを左クリックしたら、ダイアログを順番に選んでいくことができる。
ここでダイアログの4つすべてのテキスト編集のフィールドに名前をつけなくちゃならない。上から順番にこうしよう:enWine、enWinery、enGrape、enYear。
Python のコード
今から上で作ったコードを実際に動かしてみよう。まず、/projects/PyWine ディレクトリ(glade ファイルを作ったのと同じディレクトリ)に pywine.py という名前の python ファイルを作る。下に示すのが、これから使う基本的なコードだ(ほとんどは前のチュートリアルから取ってきたものだ):
#!/usr/bin/env python
import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gtk
import gtk.glade
except:
sys.exit(1)
class pyWine:
"""This is the PyWine application"""
def __init__(self):
#Set the Glade file
self.gladefile = "pywine.glade"
self.wTree = gtk.glade.XML(self.gladefile, "mainWindow")
#Create our dictionay and connect it
dic = {"on_mainWindow_destroy" : gtk.main_quit
, "on_AddWine" : self.OnAddWine}
self.wTree.signal_autoconnect(dic)
def OnAddWine(self, widget):
"""Called when the use wants to add a wine"""
print "OnAddWine"
if __name__ == "__main__":
wine = pyWine()
gtk.main()
このコードには2つほど手を加えることになる。ひとつは、on_AddWine シグナルのためのハンドラだ。このコードを走らせたとき、「Add Wine」ボタンを押すか、メニューから「Add」 | 「Wine」を選んだときに、"OnAddWine" というのがプリントされることに気づくだろう。もうひとつのコードへの変更は、メイン・ウィンドウの名前を gtk.glade.XML に渡す(pass)ことだ。これでメイン・ウィンドウとその子ウィンドウだけをロードできるようになる。
次に作るのは、ワインの情報を保管するのに使うための Wine クラスだ:
class Wine:
"""This class represents all the wine information"""
def __init__(self, wine="", winery="", grape="", year=""):
self.wine = wine
self.winery = winery
self.grape = grape
self.year = year
とてもシンプルだね。次は、wineDlg ダイアログのために使うクラスの作成だ。これを wineDialog と呼ぶことにしよう:
class wineDialog:
"""This class is used to show wineDlg"""
def __init__(self, wine="", winery="", grape="", year=""):
#setup the glade file
self.gladefile = "pywine.glade"
#setup the wine that we will return
self.wine = Wine(wine,winery,grape,year)
次に追加しなければいけないのは、glade ファイルから wineDialog ウィジットを読み込んで表示させる関数を wineDialog クラスに追加することだ。この関数には、ダイアログの結果を返す(return)こともさせる。この結果は gtk.RESPONSE だ。もっと詳しいことは PyGTK のサイトを見てほしい。
これが run 関数だ:
def run(self):
"""This function will show the wineDlg"""
#load the dialog from the glade file
self.wTree = gtk.glade.XML(self.gladefile, "wineDlg")
#Get the actual dialog widget
self.dlg = self.wTree.get_widget("wineDlg")
#Get all of the Entry Widgets and set their text
self.enWine = self.wTree.get_widget("enWine")
self.enWine.set_text(self.wine.wine)
self.enWinery = self.wTree.get_widget("enWinery")
self.enWinery.set_text(self.wine.winery)
self.enGrape = self.wTree.get_widget("enGrape")
self.enGrape.set_text(self.wine.grape)
self.enYear = self.wTree.get_widget("enYear")
self.enYear.set_text(self.wine.year)
#run the dialog and store the response
self.result = self.dlg.run()
#get the value of the entry fields
self.wine.wine = self.enWine.get_text()
self.wine.winery = self.enWinery.get_text()
self.wine.grape = self.enGrape.get_text()
self.wine.year = self.enYear.get_text()
#we are done with the dialog, destroy it
self.dlg.destroy()
#return the result and the wine
return self.result,self.wine
メイン・ウィンドウを読み込むのと同じやりかたで、ダイアログを glade ファイルから読み込んでいることに気づくだろう。gtk.glade.XML() を呼んで(call)、読み込みたいウィジットの名前を渡す(pass)。これで(メイン・ウィンドウと同じように)自動的にダイアログが表示されるけれど、これではまだ充分じゃない。というのは、呼び手(caller)にダイアログを返す(return)前に、run 関数にはユーザがダイアログを終了させるのを待ってほしいからだ。これをするには、self.dlg = self.wTree.get_widget("wineDlg") でやっているように、ウィジット・ツリーからダイアログ・ウィジットを取得して、GTkDialogs run 関数を呼ぶ。この関数が何をするかについては、PyGTK のドキュメンテーションを見てみよう:
run() メソッドは、ダイアログが「レスポンス(response)」シグナルを出すか、あるいは破壊されるまで再帰的なメイン・ループを閉じます。ダイアログが破壊された場合、run() メソッドは gtk.RESPONSE_NONE を返します。それ以外の場合は、「レスポンス」シグナル出力からのレスポンス ID を返します。run() メソッドは、再帰的なメイン・ループに入るまでに、ダイアログの gtk.Widget.show() を呼んでくれます。 子ダイアログの表示は明示的に行わなければならないので注意してください。
run() メソッドの間、デフォルトの動作である "delete_event"(イベントの削除)は無効になっています。つまり、ダイアログが "delete_event" を受け取ったとき、ウィンドウが破壊されるようには破壊されずに、run() メソッドはただ gtk.RESPONSE_DELETE_EVENT を返します。さらに、run() メソッドの間は、ダイアログはモーダルの状態です [訳注:モーダルダイアログとは、設定や作業の確認を行うためのウィンドウであるダイアログボックスの中でも、1度開いたらそれを閉じるまで他の操作を行わせなくするダイアログボックスのこと、らしい]。response() を呼んで "response" シグナルを出力させることによって、いつでも run() メソッドに値を返させることができます。run() メソッドの間にダイアログを破壊するというのは大いに間違ったアイデアです。なぜなら、その後に走るコードはダイアログが破壊されたかどうかを知らないからです。
run() メソッドが値を返したあとは、あなたが必要に応じてダイアログを隠したり破壊しなければなりません。
「OK」ボタンは gtk.RESPONSE_OK を返して、「Cancel」ボタンは gtk.RESPONSE_CANCEL を返す。だいたいにおいて僕らは、ユーザが「OK」ボタンをクリックした場合の、このダイアログによって返されたワインの情報についてだけ気にしておけばいい。
あと、テキストを取得してセットするのに、ダイアログから GTKEntry ウィジットを取ってくることも分かるだろう。まあ、全体としてこの関数はシンプルだってことだ。
ツリービューとリスト保管
ユーザがリストに追加したいワインが分かったことだし、ここでは実際に gtk.TreeView に追加してみよう。
GTKTreeViews の主な特徴は、モデルが示したどんな方法でも表示を行う、というものだ。gtk.ListStore でも、gtk.TreeStore でも、gtk.TreeModelSort でも、gtk.GenericTreeModel でもいい。ここでは gtk.ListStore を使うことにしよう。
ツリービューとモデルの関係は少し複雑だけど、使いはじめれば、どうしてこういう仕組みになってるかが分かってくるだろう。基本的には、モデルはデータを表していて、ツリービューはデータを表示するひとつの方法だ。だから、同じデータ(モデル)をまったく違うやり方で表示する複数のビューを持つことだってできる。以下に GTk+ のリファレンス・マニュアルの説明を示す:
GTK+ で、ツリーあるいはリストを作成するには、 GtkTreeModel インターフェースを GtkTreeView ウィジットと一緒に使用してください。このウィジットはモデル/ビュー/コントローラ・デザイン(MVC デザイン)に基づいてて、4つの主要な部分から成ります:
ツリービュー・ウィジット (GtkTreeView)
ビュー・コラム (GtkTreeViewColumn)
セル・レンダラー (GtkCellRenderer など)
モデル・インターフェース (GtkTreeModel)
ビューは、はじめの3つのオブジェクトによって構成されており、最後の1つはモデルです。MVC デザインの第一の利点は、ひとつのモデルから複数のビューが作れることです。例えば、ファイルシステムをマップしたモデルをファイルマネージャ用に作るとします。このとき、メモリにはモデルを1コピー置いておくだけで、ファイルシステムの様々な部分をたくさんのビューを作成して表示させることができます。
はじめにする必要があるのは、pyWine クラスの __init__ 関数に少しコードを追加することだ。辞書をウィジット・ツリーに自動結合したすぐあとにね:
#Here are some variables that can be reused later
self.cWine = 0
self.cWinery = 1
self.cGrape = 2
self.cYear = 3
self.sWine = "Wine"
self.sWinery = "Winery"
self.sGrape = "Grape"
self.sYear = "Year"
#Get the treeView from the widget Tree
self.wineView = self.wTree.get_widget("wineView")
#Add all of the List Columns to the wineView
self.AddWineListColumn(self.sWine, self.cWine)
self.AddWineListColumn(self.sWinery, self.cWinery)
self.AddWineListColumn(self.sGrape, self.cGrape)
self.AddWineListColumn(self.sYear, self.cYear)
このコードは、まあ見たままだ。はじめにいくつかの変数を上に定義されたように作って(後でいろんな変更が効くようにするためだ)、ウィジット・ツリーから gtk.TreeView を取得する。その後、リストに必要な列を追加する新しい関数を呼ぶ。AddWineListColumn は、毎回列を作るコードを複製しなくてもいいようにしてくれる関数だ:
def AddWineListColumn(self, title, columnId):
"""This function adds a column to the list view.
First it create the gtk.TreeViewColumn and then set
some needed properties"""
column = gtk.TreeViewColumn(title, gtk.CellRendererText()
, text=columnId)
column.set_resizable(True)
column.set_sort_column_id(columnId)
self.wineView.append_column(column)
このコードはもうちょっと複雑だ。はじめに、 gtk.CellRenderer として gtk.CellRendererText を使っている、新しい gtk.TreeViewColumn を作る。GTK+ リファレンス・マニュアルからもうちょっと一般的な情報を以下に載せることにする:
GtkTreeView ウィジットがモデルを持つとき、そのモデルをどのように表示するかを知っている必要があります。これは列とセル・レンダラーを使って行います。
セル・レンダラーは、何らかの方法でツリーのデータを描画するのに用いられます。GTK+ 2.x に付属のセル・レンダラーは、GtkCellRendererText、GtkCellRenderePixbuf、GtkCellRendererToggle を含めてたくさんあります。カスタムのレンダラーを書くのは比較的簡単です。
GtkTreeViewColumn は、GtkTreeView がツリービューの縦の列をまとめるのに使うオブジェクトです。GtkTreeViewColumn は、列の名前、使用するセル・レンダラーの種類、与えられた行においてモデルから取り出すデータの部分といった情報を知っている必要があります。
だから、基本的にやることというのは、ちゃんとタイトルをもっていて、(シンプルにテキストを表示するために)gtk.CellRendererText を使うと明記して、モデルのどの項目についてのことなのかを伝える、ということだ。それから列をサイズ変更可能にして、ユーザがコラムのヘッダをクリックすることによってリストを並べ替えられるようにする。その後は、ビューにそのコラムを追加するだけだ。
これが終わったら、モデルを作らなくちゃいけない。前に出てきた pyWine クラスの __init__ 関数のところに戻ろう:
#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(str, str, str, str)
#Attatch the model to the treeView
self.wineView.set_model(self.wineList)
基本的には、ただ gtk.ListStore を作って、それに対して、4つ項目があってそれらが文字列であることを伝えているだけだ。そしてモデルをビューにくっつける。これが gtk.TreeView の初期化に必要なことすべてだ。
まとめ
最後にするのは、pyWine クラスに(メニューあるいはツールバー・ボタンから呼ばれる)OnAddWine 関数を書くことだ。これはとてもシンプルな関数だ:
def OnAddWine(self, widget):
"""Called when the use wants to add a wine"""
#Create the dialog, show it, and store the results
wineDlg = wineDialog();
result,newWine = wineDlg.run()
if (result == gtk.RESPONSE_OK):
"""The user clicked Ok, so let's add this
wine to the wine list"""
self.wineList.append(newWine.getList())
これで wineDialog のインスタンスを作って、それを走らせてその結果とユーザが入力したワインの情報を保管する。つぎに、結果が gtk.RESPONSE_OK かどうか(つまりユーザが OK ボタンを押したかどうか)をチェックして、もしそうならワインの情報を gkt.ListStore に追加する。この情報は、gtk.ListStore と gtk.TreeView がつながっているので、自動的に gtk.TreeView に表示される。
このコードをもうちょっと読みやすくするために、wine クラスでシンプルな getList 関数を使うとこうなる:
def getList(self):
"""This function returns a list made up of the
wine information. It is used to add a wine to the
wineList easily"""
return [self.wine, self.winery, self.grape, self.year]

これで、このサンプル・アプリケーションについては終わり。情報を保存したりといったことはまだできないけど、フルの PyGTK アプリケーションの作成のための初期の概略が示せたんじゃないかと思う。
すべてのソースコードと Glade ファイルはここに置いてある。ダウンロードして確認してほしい。
0 comments:
Post a Comment