2011年5月24日火曜日

PythonでGUIアプリ第二弾

さてと。懲りずにwxPythonでGUIアプリを作ろう第二弾です。


今回も謎のエラーに随分悩まされまして、@kaorin_linux氏に助けて頂きました。ありがとうございます。いやはや、LinuxでGUIアプリを作るのはかなり敷居が高いですよ。


さて、お題。今回の元ネタはこちら(日本語訳はこちら)になります。今回は元ネタがWeb上に公開されてるからいいですね(笑)。


紹介ページ見てもらえば分かるんですけど、元々これはwxPythonではなくってpyGTKを使ったお題なんです。が。実はちょっと記事が古くて、現状のGlade(GTK+用のGUI Builder)と整合性が無い。このページの示唆のまま進んだんですが、結局完全なGUIアプリを手に入れる事が出来ませんでした。そこでwxPythonでリベンジを目論んだわけです。


今回はTreeView(wxPythonで言うListCtrl)と言う、これまたwxPython系の記事であまりお目にかからない機能を用います。まあ、ワインのリストを入力する、ってちょっとあんま役に立たなさそうなアプリなんですけど(笑)、いいでしょう。この辺クリアしておけば、今後楽しくGUIアプリを作るキッカケになるやもしれません。そうか(笑)?


まあ、どっちにせよ、基本的には極私的備忘録ですからね(笑)。


では早速やりましょうか。


wxGladeを使う


今回は素直にwxGladeを使います。wxGladeとはwxWidgets用のGUI Buiderです。他にもwxWidgets用にはBoa Constructorなんかもあるんですが、wxGladeが一番古く、また開発が比較的活発でポピュラーなんで今回はこれを素直に用います。以下がwxGladeの画面写真ですね。



冗談です(笑)。それはともかく(笑)、3つのウィンドウが出てきますね。まずはそれぞれの名称を。



まずはパレットです。これはウィジェット(GUI画面構成の部品)を選択するウィンドウです。wxPythonの全機能は網羅していませんが、「よく使う」部品が並べられています。ここでマウスでクリックした部品をデザイナ(後述)に配置していくわけです。



次がウィジェットツリーです。ここでは親ウィンドウ(ウィジェットツリー上ではApplicationと呼ばれる)を頂点としてのウィジェットの木構造が表示されます。とてもオブジェクト指向的ですね(笑)。



最後がプロパティウィンドウです。ここでデザイナ上に配置されたウィジェットの様々な属性(つまりプロパティ)を調整します。まあ、これも全て調整出来るわけじゃあないんですが、主だったトコは調整出来るでしょう。基本的にウィジェットツリー上でクリックしたウィジェットのプロパティに従って、ここの画面が切り替わります。


基本的にはこのまんま、ですね。では進みましょうか。


画面デザイン


画面デザインの最終型は以下のようなモノとします。



公式ページの通り、ですね。ではサクサク進めましょうか。


フレームとデザイナ


まずはパレットから次のアイコンをクリックします。



Add a Frameですね。これがGUIデザインに於ける基本中の基本部品となります。こいつをクリックすると、



Select frame classと言うポップアップが表れます。デフォルトであるwxFrameになってるのを確認して[OK]ボタンを押します。そうすると、ウィジェットツリーは以下のようになり、



そしてデザイナが出てきます。



このデザイナ上にGUI部品(ウィジェット)を配置していくんですね。なお、sizerってのはウィジェットをやや融通が利かないんですが、「計算通りに」配置していく為のクッションです。


プロパティウィンドウ


ここで、ウィジェットツリーでframe_1が選択されているのを確認してから、プロパティウィンドウを見てみます。



まずタブが三つ(Common、Widget、Code)あるのを確認してください。これがframe_1のプロパティウィンドウです。そしてCommonタブに於いてNameがframe_1、classがMyFrameになってるのを確認する。ここでNameをwine、ClassはpyWine、そしてSizeのチェックボックスにチェックを入れて、640, 480にしてみましょうか。



ウィジェットツリーを見ると名前の変更が反映されていますね。



次にwine(pyWine)のプロパティウィンドウのタブををWidgetに切り替えます。



ここでTitleをPyWineにします。そしてHas MenuBar、Has ToolBar、Has StatusBarの3つにチェックを入れます。これらはアプリケーションに、メニューバー、ツールバーステータスバー(聞きなれないかもしれませんが、ウィンドウの下に逐一情報を表示してくれる便利っ子)の三つを追加してくれます。



またウィジェットツリーを見てみると、sizer_1の下にwine_menubar、wine_toolbar(wxToolBar)、wine_statusbarの三つが追加されてるのが分かります。




名前、クラス名、タイトル・・・全部似た様な意味で紛らわしいんですけど。


全くです(笑)。一応ちょっと説明しておきます。


まず、Titleはウィンドウの上方にあるタイトルバーに「何を表示するか」と言う意味です。この場合はソフト名のPyWineになりますね。


Classはそのままクラス名です。GUIの中心を担う部分にどう言う名前を付けるのか、と言う事です。


Name、と言うのは今から作るクラスのインスタンスのエイリアス、平たく言うと変数名です。でも何でこんなのいきなり付けるんでしょうか?


と言うのも、wxGladeが作るPythonファイルの雛形は、スクリプトの形式を取っています。つまり例の悪名高い(笑)'__main__'が絡んできます。ここでmainloop()を行うインスタンスが代入された変数名をどうするのか、と言うのがここで言うNameの役割なんです。少なくとも、スクリプトファイルとして実行する場合、どう言う変数名でプログラム全体が参照されるのか、って事ですね。


つまり、Pythonの名前空間を考えた場合、Title = ClassはOK、Title = NameもOKなんですけど、Class = Nameは非常に危険です。名前の衝突が起きる可能性がある。少なくとも、ClassとNameは別々になるように名付けた方が良いでしょう。



メニューバー



まずはウィジェットツリーでwine_menubarを選びます。プロパティウィンドウを見ると、wine_munubarのプロパティに切り替わってる事が分かると思います。



ここで[Edit menus...]ボタンをクリックします。



そうするとMenu editorってのが立ち上がりますね。ここでメニューバーに色々なプルダウンメニューを追加出来るわけです。


まずは[Add]ボタンをクリックして、メニューを追加します。



そしてLabelを「追加」に変更します。



もう一回[Add]ボタンを押しましょうか。



今度はLabelを「ワイン」にします。



「ワイン」が選択されたままになってるのを確認して、[>]ボタンを押します。そうすると「ワイン」が一段右にズレるのが確認出来るでしょう。



これは「ワイン」が、「追加」の子になった事を意味します。分かりにくければ、要するに、プルダウンメニューとして「追加」があって、その中に「ワイン」と言う選択項目がある、と言う意味です。


次に、再び「ワイン」が選択されている事を確認して、右側にあるEvent Handleron_AddWineと記述します。



そこまで終わったら[適用]ボタンを押して[OK]を押しましょう。その後、ウィジェットツリーからwine(pyWine)を右クリックしてPreview(wine)を選びます。



そうすると、プレビューが現れます(これが残念ながらGladeには存在しない、wxGladeの優れたトコロです)。ウィンドウ上方に[追加]があって、メニューに[ワイン]があるのが確認出来るでしょう。このように、プレビューはウィジェットツリーのApplicationの直接の子(もちろん複数あり得ますが)で確認出来るので、GUI画面作成に於いて活用しましょう。



なお、オリジナルではメニューバーに[ファイル]、[編集]、[表示]、[ヘルプ]も存在しますが、ここでは端折ります。これはGladeで作成するとデフォルトで追加されるんですが、生憎wxGladeではそうはなりません。つまりメンド臭いんで端折ります(笑)。


ただ、これらに関してはチュートリアルがあるので(例えばここここここここここここ等)それを参照すればすぐ実装出来ると思います。


ツールバー


次はツールバーに取り掛かります。先ほどと同じように、今度はウィジェットツリーでwine_toolbar(wxToolBar)を選択、そしてプロパティウィンドウを見ます。



一番下に[Edit tools...]ボタンがあるんで、そいつをクリックするとToolbar editorが立ち上がります。



[Add]ボタンを押して、itemを追加。Labelを「Add Wine」にする。Event Handlerを再び「on_AddWine」にします。



次に、Normal Bitmapから(Ubuntuだったら)


/usr/share/icons/gnome/24x24/actions/gtk-add.png

と言うパスを選びます。他のLinuxディストロだったらどうなんでしょうね(笑)。あるいはWindowsだったら(笑)。知りません(笑)。まあ、今回はUbuntuのデフォルトアイコンを使ってますが、ひょっとしたら画像フォルダを自分で作って、そこに自作アイコンを突っ込んで、そいつを参照した方が良いかもしれませんね。ええと、ちなみに画像サイズは24 x 24くらいみたいです。



また[適用]をクリックして[OK]をクリック。そしてウィジェットツリーのwine (pyWine)を右クリックしてプレビューしてみますか。



いい感じですね(笑)。ソフトウェアらしい見た目になってきました(笑)。


リストコントロール


さて、今までほったらかしてきたデザイナですが(笑)、現在こんな状況になっております。



いつの間にやら[追加]メニューやらツールバーが生えております(笑)。


ところで中央の灰色の部分ですが、もう一度言うとこいつがSizerです。ウィジェットを配置する為のクッションですね。ウィジェットは必ずコイツの上に乗せる、ってのがwxGladeのルールです。


今回何を乗せるか。それはリストコントロール、と言います。GTK+ではTreeView等と呼ばれてるみたいですが、元々はリスト何とやら、と言う呼び名だった模様です。どんどん複雑化していって、TreeViewに枝分かれしたみたいですね。wxWidgetsでは古典的な名称を採用している模様です。


そのリストコントロールはパレットのここにあります。



右から1列目、上から4行目ですか。って言い方好きじゃないんですけどね、ホントは。wxGladeもヴァージョン上がるとボタンの配置がチョコチョコ変わったりするみたいなんで。


いずれにせよ、そいつをクリックした後、デザイナのSizer部分をクリックします。



Sizerにリストコントロールが乗りました。そいつを確認したらリストコントロールのプロパティウィンドウを確認してみますか。



リストコントロールにはタブが5つ(Common、Layout、Widget、Events、Code)あります。でも今回は特に弄る必要がないんで、CommonのNameだけをwineViewに変更しましょう。



ウィジェットツリーのListCtrl_1もwineViewに変更されましたね。



ダイアログの作成


元ページには次のように書かれています。


それで、次にするのは、ユーザが新しいワインを追加するためのダイアログを作成することだ。ここではシンプルに、ワインの名前と、ワイン醸造所(ワイナリー)、製造年、あとブドウの種類を入力できるようにしよう。

よしなに。


まずはダイアログのデザインから。



これを目標にやっていきましょうか。


先ほどからの続きです。そのままで構いません。まずはパレットからAdd a Dialog/Panelを選びます。



こいつをクリックするとSelect widget typeと言うポップアップが現れます。



取り敢えずデフォルトのままでいいですか。[OK]ボタンをクリックしましょう。そうするとウィジェットツリーは次のようになります。



先ほど作ったwine (pyWine)と同じ階層です。つまり、両者ともApplicationを親に持つ直接の子だ、って事になりますね。


ついでにダイアログ用のデザイナも現れると思います。



では、ダイアログのプロパティウィンドウを見てみます。



タブはCommon、Widget、Codeの三つですね。Common内のNameとClassを共にwineDialogにします。



WidgetタブでTitleをAdd Wineに変更します。



Sizerを使う


ではAdd Wineをデザインしていきましょうか。それにはSizerを使います。まずはAdd Wineを上下二分割しましょう。


パレットからAdd a BoxSizerを選びます。



こいつをクリックして、Add Wineのデザイナをクリックすると、次のようなポップアップが現れます。



Orientationとは方向の事で、左右に分割したい場合はHorizontal、上下に分割したい場合にはVerticalを選びます。今は上下に分割したいので、Verticalを選びます。また、Slotsってのは分割数ですね。繰り返しますが、上下二分割なんで2にします。



[OK]ボタンをクリックするとAdd Wineのデザイナは次のようになります。



上下二分割になりましたね。次は上半分を4行2列に分割したい。こう言う場合はAdd a Grid Sizerを使います。



こいつをクリックした後、Add Wineのデザイナの上半分をクリックすると、次のようなポップアップが現れます。



Rows(行)に4を入力、Cols(列)に2を入力した後、[OK]ボタンを押します。



するとAdd Wineのデザイナは次のようになります。



次はAdd Wineの下半分をHorizontalに3分割します。これはもう出来るでしょう。最終的にこう言う状態に持って行きます。



何となく画面デザイン目標に近づいてきたカンジです。ちなみに、現時点ではウィジェットツリーは次のようになっています。



スタティックテキスト


では必要なウィジェットを配置していきましょう。まずは上半分から、です。最初に入力欄に対する説明の為の「ラベル」を配置します。それには、Add a StaticTextを用います。



こいつをクリックして、Add Wineデザイナの上半分の左半分を埋めていきましょう。



こんなカンジになりますね。ウィジェットツリーは次のようなカンジになります。



ウィジェットツリーにはlabel_1label_4と表示されています。そしてそれぞれのプロパティウィンドウはCommon、Layout、Widget、Codeと4つのタブを持っています。


変更箇所はたった一つです。label_1〜label_4のWidgetタブ内のLabelをそれぞれワインワイナリーグレープの種類製造年に変更します。そうすると、Add Wineのデザイナは次のようになってる筈です。



テキストコントロール


次は入力欄を作ります。そのためにはAdd a TextCtrlを用います。



今度はAdd Wineデザイナの上半分の右半分にウィジェットを追加していきます。デザイナは次のようになります。



ウィジェットツリーは次のようになりますね。



text_ctrl_1text_ctrl_4のプロパティウィンドウを見ます。今度はタブがCommon、Layout、Widget、Events、Codeの5つありますが、今回はCommonタブのNameをtext_ctrl_1〜text_ctrl_4の順番で、enWineenWineryenGrapeenYearと名づけていきます。ウィジェットツリーに反映されて以下のようになりますね。



一回ウィジェットツリーのwineDialogの右クリックを利用してプレビューしてみますか。



まあまあいいカンジですね。


ボタンの配置


では次はAdd Wineの下半分を何とかしましょう。まず、一番左側にはスペーサーを嵌めます。



例によってAdd Wineデザイナの左側をクリックします。するとポップアップが現れます。



取り敢えずデフォのままで良いです。[OK]ボタンをクリックしちゃってください。


次は真ん中です。ここには[キャンセル]ボタンを嵌めたいんですが、一般的なボタンはパレットからAdd a Buttonを選びます。



ではAdd Wineデザイナに配置しましょう。



ここで一旦、ウィジェットツリーでbutton_1と表示されている、たった今配置したボタンのプロパティを弄ります。プロパティウィンドウへ行ってください。


ボタンにはCommon、Layout、Widget、Events、Codeと5つのタブがある筈です。まずCommonタブでNameをCancelに変更します。次にWidgetタブに切り替えて、stockitemにチェックを入れます。そしてリストからCANCELを選びます。


同様に、Add Wineデザイナの下半分の右側にボタンを配置します。プロパティを弄ってNameをOK、stockitemでもOKを選びます。


Add Wineデザイナは次のようになってる筈です。



プレビューは次のようになってる筈ですね。



ウィジェット配置のバランス調整


まあ、これで可と言えば可なんですよ。元ページで要求している機能のスペックには到達している。


ですが。


見た目のバランスが悪いんですよね(笑)。果たしてこれをどーするか。


恐らく日本で殆ど一番最初にwxGladeを紹介し、また現在でも参照ページとして恐らく最もアクセスが多いこのページに詳しいんですが、色々とSizerの比率やらラベルのセンタリングやら入力欄周りのピクセル数調整等をしないとなりません。んで、この辺はプログラミングじゃあないんですよね(笑)。色々パラメータ弄っては「フィーリング」で適正値らしきものを探して行かないとなりません。


んで細かい説明は先ほどのページに任しておきますが(良く書けてます)、基本的には各ウィジェットのプロパティウィンドウのLayoutで調整していきます。


まずはAdd Wineの上半分に対して行う事を箇条書きにしてみます。



  1. label_1〜label_4のAlignmentwxALIGN_CENTER_VERTICALにチェックを入れてそれらを置かれている升目の縦方向の中央に持ってくる。

  2. 入力欄それぞれのBorderの値を3にする。これによって入力欄の周りに3ピクセル持たせる準備をする。

  3. 入力欄それぞれのwxALLにチェックを入れ、上記を実行する。

  4. 入力欄それぞれのAlignmentwxEXPANDにチェックを入れる。


この時点でAdd Wineのデザイナは次のようになってる筈です。



プレビューは次のようになってる筈ですね。



下段に於いてやるべき事は次の事です。スペーサーのプロパティウィンドウのLayoutで、Widthを100、Heightを30にします。これでAdd Wineデザイナは次のようになります。



プレビューは次のようになります。



だいぶ良くなってはきました。ただまだ上下比率等が良くない。と言うのもSizerが画面分割する際、デフォルトでは必ず1:1の比率に分割するからです。つまり比率を変えればもっと見た目が良くなる。特に下のボタン配置スペースは結構無駄が多いですからね。


これを調整するのがプロパティウィンドウのWidgetタブにあるProportion、つまりまんま比率調整です。ちなみに、これはSizerに「乗ってる側」で調整していきます。例えば最初、sizer_2でAdd_Wineを上下二分割しましたが、Proportionはsizer_2で調整出来ません。その上に乗ってるgrid_sizer_1とsizer_3のProportionの値で比率を調整します。


デフォルトではgrid_sizer_1とsizer_3の比率はまんま1:1なんですが、この比率を変えていきます。まあ、この辺は個人の好みなんですが、僕がやった限り4:1辺りに持って行っても構わないカンジでした。つまり、grid_sizer_1のProportionの値を4まで上げると言う事です。これでAdd Wineのデザイナは次のようになります。



ちょっとみっともないですか(笑)。でもデザイナは完全に見た目を反映しているわけではありません。ってのは実はデザイナとしては困るんじゃねーの、とも思うんですが(笑)、実際どう言うカンジで反映されてるかは、プレビューで見ないとなりません。プレビューは以下の通りですね。



なかなか良いカンジになってきています。これで良しとしましょうかね。


xmlファイルを保存する


ではデザイン情報を保存しましょう。wxGladeではデザイン情報をxmlとして保存します。ただし、通例、ファイルの拡張子は*.wxgとするようです。


[File]メニューから[Save As...]を選んで、mainWindow.wxgとして、そうですね、例えば~/projects/PyWineフォルダにでも保存してください。



Python実行ファイルを出力する。


次はPythonコードを生成します。ウィジェットツリーからルートのApplicationを選んで、プロパティウィンドウを見ます。下の方にスクロールして、まずはLanguageのチェックボックスでpythonが選ばれている事を確認してください。



ご覧になれば分かりますが、実はwxWidgetsは他にLisp(ANSI Common Lisp)、XRC、C++、PerlでGUIコードの雛形を吐く事が出来ます。まあ、ここではPythonを使ってるんで当然PythonでGUIのコードを吐かせましょう。


次にOutput pathを設定します。先ほどの~/projects/PyWineにPythonコードを吐かせましょうかね。ファイル名はpywine.pyとします。



最後にプロパティウィンドウの一番下にある[Generate code]ボタンをクリックします。以下のメッセージボックスが出てくれば無事Pythonコードが生成されています。



[OK]ボタンをクリックしてGUI画面制作に付いては全て終了です。お疲れ様でした。wxGladeを閉じましょう。


PyWineのスケルトンを実行してみる


さて、~/projects/PyWineを開くと多分次のようになっている筈です。



Windowsならpywine.pyをダブルクリックすればGUI画面のスケルトンが立ち上がる(筈だ)と思いますし、UNIX系OSなら通常、端末から


~$ projects/PyWine/pywine.py

とコマンドを打てば同様にスケルトンが立ち上がります。なお、Ubuntuでもファイルをダブルクリックするとポップアップメニューが現れて実行するか表示するか尋ねられます。



[実行する]ボタンをクリックすればスケルトンが立ち上がりますね。



ホントにスケルトンなんで、このままでは全然動作しないわけなんですけれども。まあ、GUI部分に付いては制作は確かに終了しているわけです。


まあ、通常では「wxGladeの使い方」ってぇんで、このスケルトン作成だけで終了しちゃうわけなんですけれども(色々調べるのは実際メンドくせえし・笑)、ここではそうは問屋が卸しません(笑)。実際に動くトコまで持って行ってみます。少なくとも元ネタのページが示唆しているところまでは行きましょうか。


GladeとwxGladeの違い


閑話休題。余談です。ここのセクションは実際にGladeを使った事がある人向けの話です。


実はGladeは相当強力です。と言うのも、パレット部分に用意されているウィジェット配置用ボタンの数がwxGladeより遥かに多い。一方、wxGladeは相当端折ってます。wxWidgetsの主要な機能殆どを網羅している、とはとても言えません。これは大きな差で、実際問題、どんなにバックグラウンドのライブラリが優秀にせよ、GUIでのウィジェット配置の数が限られている以上、画面デザインはどんどんどんどん難しくなっていく。つまり、手書きに頼る部分が増えていく、と言うわけです。


言い換えるとGUI Builderの性能はパレットに用意されたウィジェットボタンの数で決まると言って過言じゃないんです。実はwxPythonのライブラリを眺めてたんですが、恐らくGTK+のライブラリに匹敵すると思います(と言うのも、実際問題、Linux上ではGTK+へのラッパーなので)。機能的にはね。ただし、GUIフロントエンドを持たない以上、事実上「画餅」なんです。誰も積極的に使ってみよう、とは思わないでしょう。


一方、今まで見てきた通り、プレビュー機能がある、と言うのがwxGladeの最大の特徴で、これだけはGladeが逆立ちしても勝てません。そしてコードの自動生成機能がある。この二つのGUI Buiderは、両者ともxmlファイルを吐き出しますが、その扱いが大きく違うんです(両者ともウィジェット配置等の画面情報をxmlに保存しておく、って点は変わりませんが)。


Gladeの場合、まずはさっきも書きましたが、プレビュー機能がない。すなわち、実際作成している画面がどうなってくのか、デザイン中には全く分からないんです。本当にWYSIWYGなんでしょうか(笑)。


そして、xmlにデザイン情報を記録して(通常、*.uiファイルとして)吐き出します。ただし、これでもまだ実行ファイルは入手していません。ここがGladeの肝で、手書きでコードを作り、この*.uiを実際コードに「喰わせて」そこで初めて画面情報を解釈してGUIを表示する、と言う仕組みになってるんです。つまり、GUIアプリを作る上に於いて、xmlファイルの存在「自体」が極めて重要な鍵になっています。そして、実際にコードを手書きしてみないと、どう言った画面が現れるのかも分からないわけです。


一方、wxGladeの場合、xmlファイル(つまり*.wxg)は単に画面情報を記録する媒体なだけで、作成するプログラムでこのxml自体を利用する、って事はありません。そして、スケルトンとしての目的のコードは別に吐きます。つまり、コード自体を弄ってGUIを調整するのは、xmlファイルへの依存がないので、「アリ」なんです。この辺で両者の設計思想の違いが分かります。


つまり、Gladeの場合は、「手書きのコードを強要するワリには」画面デザインに関してはGladeに任せてくれ、と言う意図があるようです。ウィジェット配置なんかの情報に関して、ユーザーが直接xmlを弄って調整する、なんて事は考えていないでしょう(もちろんxmlが得意な人を除く)。


一方、単なる画面情報の保存媒体としてしかxmlを考えていないwxGlade。こっちは構造的に、実行時にxmlを利用しない以上、「どーぞお好きに自動生成したコードを弄ってください」と言わんばかりです(笑)。こっちは「俺等は全機能をGUIで提供出来ねえからよお。雛形はやるからよお。後は勝手にやってちょんまげ(死語)」と言う意図が見え隠れしますね。


両者とも一長一短ですね(笑)。何でこーもOSSのGUI Builderは一癖もふた癖もあるのでしょうか(笑)。正直、パレットのウィジェットボタンの豊富さと、コードの自動生成機能とプレビュー機能は全部一遍に欲しいものです(笑)。


まあ、現時点両者とも、明らかにRADとしては完璧じゃないでしょう。性能的にはVSには敵わないでしょうね。あとはユーザーの「好み」の問題だと思います。



注:ちなみに、最初にGladeの仕組みを聞いた時には、


「最初にxmlファイルを読み込ませる・・・?って事はGUI立ち上げ時にコストかかるんじゃねえの?」

って思いました(爆)。まあ、多分その通りでしょう。現在のマシンパワーじゃ大したコストじゃないんでしょうけどね。この仕組みを鑑みる以上、起動時にxmlのパーズが必要だ、って事でしょうから。


ああ、でもGladeが元々C対応、って前提だと、「最初に全てコンパイルしちゃう」って意図なのかな。良く分かんねえや(笑)。



wxGladeが自動生成したPythonコード


ではまず、wxGladeが自動生成したPythonコードを見てみますかね。どう改造していくにせよ、雛形を最初に把握しておかなければなりませんので。ではどうぞ。



良いニュースと悪いニュースがあります。どっちから先に聞きますか(笑)?


では最初に悪いニュースから(笑)。コード量がメチャクチャ多いですね(笑)。しかも自動生成コードだから読解するのが大変です。美しさの欠片もありません(笑)。だからこの手の部分は隠蔽しちゃった方が良いつってるんですよ(笑)。そもそもPythonに大して詳しくもない僕が読むのは頭痛の種以外の何物でもありません。


では良いニュースを。これだけのコードを吐き出してる、って事は、実はwxGladeは元ネタのページで説明している事のほぼ殆どを実装し終わってる、って事でもあるんです。何せGladeを使ったGUIアプリ作成に於いては手書きが前提だからです。その点、wxGladeを使った我々は相当工程を短縮出来る、って意味になるんです。


と言う事は後は、機能的な部分をピンポイントで実装していけば良い。キチンと上記のコードの意味を把握したら、って事ですけどね(笑)。


ところで特に注目して欲しいのは次の二箇所です。まずは37行目と38行目のこの部分。



もう一箇所は61行目から始まるこの部分です。



この2箇所は何なのか?実はメニューバーとツールバーを作成した時にそれぞれでEvent Handlerなるモノを設定しました。両者ともon_AddWineにした。


実はwxGladeでは、選択したウィジェットによっては、この通り、GUI Builder上でメニューやボタンを設定する際にEvent Handlerを指定する事によって、自動で対象メソッドへのバインドを行い、そのメソッドのテンプレートを自動作成してくれるのです。


この辺も「手書きがシコシコ」が前提のGladeとは大幅に違います。Gladeの場合はスロット指定をしても、GTK+は機構的には遥かにプリミティブなんで、スロットへの通信指定や、ましてやそこと情報をやり取りするメソッドのテンプレートを用意してくれる、と言う事はあり得ません。その辺、wxGladeの方がより初心者に優しい設計にはなっています。


では元ネタのページに従い、作業をはじめましょう。


Wineクラスを作る


元ネタのページの最初の部分は大幅にスキップ出来ます(何故ならwxGladeが大幅に面倒を見てくれたから)。そこでいきなりワインの情報を保管するのに使うための Wine クラスを最初に作っちゃいましょう。117行目辺り、つまり、



の間辺りに新しくクラスを作成します。



基本的にはオリジナルのコードのまんま、ですね。ただ、Pythonの場合は、一気に変数を同時に代入出来るので、こっちの方が好きかな。まあ、好みに依るのかもしれませんが、Lisp経験者だとこんな風に書くかも(笑)。Norvig先生、如何でしょうか(笑)。


wineDialogクラスの__init__()への変数の付け足し


さて、オリジナルだと極めてシンプルに終わってるんですが、ここが我らwxGladeがメチャクチャ複雑なコードを吐いたトコロです(笑)。比較してみましょう。



メチャクチャ違いますね(笑)。オリジナルではほぼ何もしてない。一方、wxGlade版はダイアログに乗ってるウィジェット情報を用いてクラスを初期化しています。


こう言う場合移植のポイントになるのは、オリジナルで行ってる事で、wxGlade版で行なってない事を探す事、です。幸いオリジナルが短いんで、それはすぐに見つかります。ここです。



一つヒントを言うと、昔のGladeだと、子ウィンドウ毎に別々のxmlファイルを吐いてたようで、要するにダイアログを出す、って事は本体と別のxmlファイルを吐く、って事を意味してたようです。って事は、僕らは"mainWindow.wxg"一つしかxmlファイルを入手していない、かつプレビューで画面が出ていた事を考えるとsetup用のgladeファイルに関する情報はまるっきり要らない、と言う話になる。


って事は消去法により、上のコードしか残らないのです。んで、ここで何をやってるか、と言うとself.wineと言う変数に、先ほど作成したWineクラスのインスタンスをぶっ込んでいます。ここがwxGlade版が行なってない初期化ですね(だってさっきWineクラスを作ったばっかだし!!!)。


従って、83行目辺りに次の一文をぶっこみます。



全体としてはこうですね。



なお、与えてる引数が違います(そもそも__init__の引数が違う)がイイんです。Wineクラスは初期化で全ての(第一引数のself以外の)引数は空文字列として初期化されています。せっかくそうしてるのに、ここでまた初期化する、ってなぁ愚の骨頂としか思えません。素直に無引数でWineクラスのインスタンスを受け取った方がなぼかマシでありんす。


runメソッドを作る


次にwineDialogクラス内にrunメソッドを作成します。なお、原文でもfunctionって書かれていますが、正確にはメソッドです。メソッドと関数だと全然違います。少なくともあるクラスに属させるのか、そうじゃなくってクラス外に置くべきなのか変わってくるんで、こう言う誤用は混乱を招きますね。まあ、引数に忌々しい(笑)selfがあるんで、どっちかは分かりますけど。


ところでオリジナルのコードをちょっと見てみますか。



まあ、大変長い(笑)。これは大変長いメソッドなんですよ(笑)。初め何やろうとしてるんだかサッパリ分かりませんでした(爆)。


コメントとか見てみると、このメソッドは二つの仕事を行ってる模様です。って事は規範的なコードじゃない(笑)!!!関数型言語の人間なら既にアタマが狂ってくる作法ですね(笑)。本来なら一つの関数には一つの役割しか持たせるべきじゃないってのが構造化を持ち出すまでもない鉄則でしょう。この原則はメソッドにも当てはまる筈です。


大まかに言うと、ここのコードは



  1. 前半は表示に関して受け持つ部分

  2. 後半はダイアログで入力されたテキストを保持する部分


の二つから成り立ってるらしい。実際問題悩んだのは、中間の



この辺りをどう解釈すべきか、って事だったんですけど。いずれにせよ、既にプレビューで見た通り、「表示に関する部分」はwxGladeが作成してくれている。プレビュー無しのGladeらしい作法がこの辺絡んできてるんでしょうね。表示に関する部分を作成しながら、関連する動作を(と言うか使うメソッドの関連性を重視して)恐らく一気にまとめ上げようとしたんでしょう(あまり褒められた方法論じゃないと思う・笑)。


そうなると、僕らがコーディングしなきゃならないのは後半に絞る事が出来ます。と言うのも既に表示に関してはwxGladeが面倒を見てくれたからです。要するにやらなきゃならないのはダイアログで入力された文字情報を保持する一点です。もっと技術的に言うと(使ったウィジェットを思い出す事!)テキストコントロール内に入力した文字をここで受け取る事です。


テキストコントロールで入力された文字を得るにはGetValue()と言うメソッドを用います。


# 書式
GetValue(self)

つまりオリジナルのコード例に従うと、



って書くべきところを



と書けば良い事になる。これだけです。


と言う事はこの比較部分に於いては大して差がないんですけど、恐らく中間部分は、まずGTK+流で入力部分のオブジェクト化の明示じゃないか、って思えるんですよね。いや、詳しくは知りませんよ。どうもウィジェット情報を一々取ってるトコを見る限り、GTK+らしいプリミティブな動作をまずは明示的に行わないと、テキストを取得出来ないんじゃないか、と予想出来るのです。


つまり、pyGTKだと次の3文が実はワンセットで、



一方、wxPythonだとこれがたった一文で済む、と。



多分そんなトコなんじゃないか、って思います。多分、ですけどね。


ところでwine.wineとかwine.wineryとか変数名が気になりますよね。僕も「何じゃこりゃ?」とか最初は思ったんですが、これは先ほどwineDialog内で定義したself.wineと言う変数から大域変数よろしく引っ張ってきたものです。


一つだけ注意点を。実はPythonのクソ忌々しい文字コードに関わるトラブルがここでも出てきます。ファイル冒頭でUTF-8を指定しているにも関わらず、wxPythonのGetValue()メソッドは、何と受け取る文字をASCIIだって想定してるんですよね。んなバカな(怒)。


このクソ大馬鹿Pythonを説得するには、古典的なencode('utf_8')メソッドを用いなければなりません。つまり、実際は、



のように記述する必要性が出てきます。アホか、ホントにもう(笑)。


従って、wxPython版のrun()メソッドは次のようになります。


目辺り、__do_layout()メソッドの後辺りにぶっ込みます。


なおオリジナルのコードの最後の辺り



は無視します(笑)。これはpyGTK系の流儀の制御法の基本らしいんですが、wxPythonでは別な方策を用いるんで、無視して結構です。取り敢えず必要なのは、返り値としてself.wineを返す事。それだけでありんす。


リストコントロールとリスト保管


まずはリストコントロールを使って列に名称を付けていきます。これはpyWineクラスの__init__メソッドに於いて行います。つまりこれも初期化の一貫ですね。


オリジナルのコードではこれは次のように行われています。



ふうむ。まずやっぱりGTK+系のライブラリは何はともあれ、まずはウィジェットを明示的に引き寄せないとならないようです。ここがそれですね。



そうしないとリストにアイテムを突っ込めないらしい。


一方、wxPythonでは同じ機能はもっと簡易に使う事が出来ます。


# 書式
InsertColumn(self, col, heading, format, width)

5つ引数がありますが、例によってselfはともかくとして必須パラメータはAddWineListColumnと同じ二個です。colでコラム(列)番号(id)を与えて、headingで列の名前を与える。


さて、こうやって比較してみるとオリジナルのコードはかなり無駄があるように見えてきますね。そもそもc何とやらに数値を代入してs何とやらで列名を代入しておく。そしてそれをバラバラにAddWineListColumnに代入する……。う〜〜〜む。


例えばこのような場合、最初に名称で構成されたリストなりタプルを作った方が良いのではないでしょうか。例えば以下のようにして。



リストもタプルも「順序」がある。と言う事は既にこれでインデックスとして成り立っている、って事です。


そうすれば、あとはfor文を使って突っ込んでしまえば良い、って事です。例えばこのようにして。



この方がシンプルですよね。他にも書き方があるかもしれない。例えばこのようにする。



あるいは、この頃流行りの表記法、お尻の小さな表記法、こっちを向いてよ内包表記、って手を使う事も考えられます。



これはリストを返すのが目的なんじゃなくって、実はInsertColumnが極めて手続き的(関数型言語の言葉で言うと破壊的操作)なんで行えるんですが。要するに返り値を得る事が目的じゃない。


いずれにせよ、最初の例より遥かに簡単なやり方じゃないか、と思います。これを18行目辺りに突っ込んでおきます。


一旦ここで実行してみて、どう言った結果になってるか試してみようと思います。



キチンと列名が表示されていますね。


なお、オリジナルのコードではこの後、実際にAddWineListColumnメソッドを作成してるんですが、僕らは組み込みメソッドで逃げたんで、 んなもん実装する必要はありません。悪しからず。


on_AddWineメソッドの作成


最後にするのは、OnAddWineメソッドの作成です。実はここが一番困って、最終的には@kaorin_linux氏に手伝ってもらった部分です。凄いですよ、彼は。コードザーッと見て間違い指摘してくれて、あっと言う間に動くトコまで持って行ってくれました。ありがとうございます。


実は、EmacsとPythonってのがそれほど相性が良くなくって(ry


それはともかく(笑)。@kaorin_linux氏に手伝ってもらって書いたコードは以下の通りです。



まず注目して欲しいのはラストのDestroy()メソッドです。オリジナルのコードでは、wineDlg側の方でDestroy()を呼んでました。基本的にオリジナルのコードではwineDialogクラス内でwineDialogを呼び出して、そして最後の処理としてDestroy()を呼び出して作業を終了しています。


ところがこっちでは、On_AddWineメソッドでwineDialogのインスタンスを呼び出すようにしています。元々on_AddWineメソッドとは何なのか。それはメニュー、あるいはツールバーのボタンからワインの追加を選んだら呼び出されるメソッドでした。このメソッドが何をしなければならないのか。それは入力用ダイアログ(Add Wine)を呼び出す事です。従って、最後にダイアログを「閉じる」(要するにDestroy()する)のもOn_AddWineの役割、だと考えられますよね。そっちの方が自然だと思います。GTK+では不思議な流儀を採用している模様です(ひょっとしたら当時のXMLファイルの扱い上しょうがなかったのかもしれません)。


もうひとつ不思議な名前のメソッドがあります。それはShowModal()です。基本的にはShowModal()はユーザーがダイアログ立ち上げのボタンをクリックするなり何なりした場合、ダイアログを立ち上げる機能です。しかし、立ち上げただけではダメで、ダイアログ上の、例えば「閉じる」ボタンをクリックした場合、一体どう言う返り値が来るのか、その情報を保持してくれる役割があります。


つまり、ShowModal()の返り値によって行わなければならない動作がここにはあります。それは入力欄から入力された文字列のリストコントロールへの読み込みです。ここでは次の組み込みメソッドを用いてこれを行っています。


# 書式
Append(self, entry)
アイテムをリストコントロールに追加する

Appendはエントリとしてリストを受け取ります。リストはインデックスを持っているので、そのままリストの要素は位置番号に対応したコラム下へと挿入されます。なかなか便利ですね。


いずれにせよ、もう一回整理しましょう。ここでの流れはこの通りです。



  1. 変数wineDlgにwineDialog()のインスタンスを代入する。

  2. 変数resultにwineDlg.ShowModal()の返り値を代入する。

  3. 変数newWineにwineDlg.run()から得たWineクラスのインスタンスを代入する。

  4. もし、変数resultにwineDlgで[OK]ボタンがクリックされた結果が入ったら、newWineをgetListメソッドでリストに変換したあと、wineViewにAppendする。

  5. ダイアログを閉じる。


いずれにせよ、on_AddWine()メソッドをpyWineクラスの最後、66行目辺りに付け加えます。


ところでgetList()メソッドですが、これはWineクラスのメソッドとして実装します。元ページでも次のように言ってますね。


このコードをもうちょっと読みやすくするために、wine クラスでシンプルな getList 関数を使うとこうなる

実はここでの実装では「読みやすくする」以上の理由があるんですけど、取り敢えずオリジナルのコードを見てみましょう。



一方、こちらで作成したgetList()メソッドは次のようなものです。



勘の良い人ならもう分かったでしょう(笑)。またもやPythonの文字コード問題です(爆)。つまり、ダイアログの入力欄から文字を受け取る時もデフォルトでASCIIだと大騒ぎされ、ここでまた、リストコントロールにAppendする際にもデフォルトでASCIIだと大騒ぎするんです(笑)。いくらファイルでUTF-8指定しても全く関係無し、です。っつーかPythonの問題と言うよりwxPythonの問題でしょうね。そこでここではそれを避ける為に、リスト内包表記でAppendに渡すリストに含まれた文字列はUTF-8だと宣言したリストを返すようにしてるんです。実用的な理由でしょ(笑)?


では完成したPyWineを見てみますか。



いい感じですね(笑)。Gladeでトライした時は失敗しましたが、今回は成功です。良かった良かった。元ページでも次のように締めています。


これで、このサンプル・アプリケーションについては終わり。情報を保存したりといったことはまだできないけど、フルの PyGTK アプリケーションの作成のための初期の概略が示せたんじゃないかと思う。

ここでも全くそう言う気持ちでいます。


なおこちらでもここでソースコードとwxgファイルを公開しています。

0 件のコメント:

コメントを投稿