wxPythonで電卓をつくってみる

シンプルな電卓アプリケーション

tags:gui, tips
created:2007-09-11T19:38:34

シンプルな電卓アプリケーション 電卓はGUI作成入門の定番らしい。

ソースコード

一式ファイルを取得する方法は以下の手順で:

mkdir wxpython
cd wxpython
hg init
hg pull -u http://python.matrix.jp/repos/wxpython

wxpython/calculatorフォルダに一式が展開されます。

mercurialの使い方は Pythonで分散バージョン管理 を参照してください。

リソースファイル解説

リソースのツリー:
../../../_images/calc_xrc.png

XRCedの使い方のコツを解説します。

基本の学習には XRCedの使い方 を参照してください。 (最新版にてoptionはpropotionという名称に変わっていますので読み替えてください。)

Fileメニューの「Generate Python」をクリック。
../../../_images/calc_filemenu.png
ここでの設定内容は以下のようにしています。
../../../_images/calc_xrc_dialog.png

このようにすると、参照した画像リソースやIDを設定したコントロールへのアクセスを ジェネレートしたpythonコードのなかですべて準備してくれるようになります。

そして、XRCedでの編集内容を保存するたびにresource_xrc.pyを自動的に生成するようになります。

以下では要望ごとにどうやって実現したかを解説します。 苦労しますが、こうやってサイザーベースで作成すると、 ウインドウリサイズに柔軟に対応するレイアウトが実現できます。

../../../_images/calc_frame2.png

3つの領域に分けたい

まず、以下の3つの領域に分けたいと考えました。

  • 表示部分
  • クリアボタン群
  • その他のボタン群

wxWidgetsでのサイザーを使ったレイアウトでは 「wxWindowクラスの子はひとつだけにすると自動的に最大限広がって配置される」 というルールを最大限利用するのが常套手段です。

トップフレーム「CalculatorFrame」に 複数の子を直接置いてしまうと 、 フレームリサイズに対してサイズ調整機能は連動してくれませんので、 上記3つの群をwxBoxSizerで包みました。

注釈

サイザーレイアウトの常識
サイザーレイアウトでは、複数のウィジェットは必ずサイザーで包む。 複数のサイザーを一定のルールでレイアウトする時もさらにサイザーで包むこと。

wxBoxSizerはバーチカルレイアウト(縦並び)設定で、 中には上記の3つの群にあたる wxStaticBoxSizer [1] を3つを配置しています。

[1]wxStaticBoxSizer: HTMLでいうところのfieldset-legendタグのようなもので、 枠で囲んだ左上に題銘がかけるというものです。 なおかつ内容をサイザーレイアウトできます。

「表示部分」の領域は横方向に拡大フィットして欲しい

「表示部分」に相当するwxStaticBoxSizerのwxEXPANDフラグをONにしまして、 wxTextCtrl(Display)のwxExpandフラグもONにしました。

「クリアボタン群」の領域は横方向に拡大フィットして欲しい

「クリアボタン群」に相当するwxStaticBoxSizerのwxEXPANDフラグをONにしました。

「クリアボタン群」は右詰で配置したい

右詰などのレイアウトには2種類の方法があります。

方法①:
この群をさらにサイザーで包んでwxALIGN_RIGHTフラグをON、 また、wxStaticBoxSizerをバーチカルレイアウトにします。
方法②:
wxStaticBoxSizerはホリゾンタルレイアウトにしておき、 wxStaticBoxSizerの子としてダミースペーサに続いてクリアボタン群を配置。 ダミースペーサを「propotion=1」に設定しておきます。 クリアボタン群は「propotion=0」のまま。

今回は後者を採用してみました。

「その他のボタン群」の領域は縦にも横にも拡大フィットして欲しい

「その他のボタン群」に相当するwxStaticBoxSizerのwxEXPANDフラグをONにし、 propotionに1を設定します。

その他の群はエラー時に押せなくしたい

演算がオーバーフローしたときなどに、 一斉に数値ボタンや演算ボタンを押せなくしたいと考えた時、 普通にレイアウトしていた場合、 そのすべてのボタン各々にディゼーブルメソッドを呼ぶ必要があります。

そこで、パネルの子孫としてそれらのボタン群をレイアウトしました。

こうしておくと、パネルの有効無効が子孫にも反映されます。

パネルの子にボタン群を置く事は前述のルールに反するので、 パネルの直接の子にサイザーを、そのサイザーの子にボタン群を配置しました。

メインスクリプト解説

詳細は ソースコード のコメントを参照。

このコードの独特なところは、ボタンイベントハンドリングを3つのグループに分けたところです。

  • [0]..[9]と[.]を数値入力グループ。
  • [x],[/][+][-][=]は演算指定グループ
  • 上記に該当しないボタン

上記に該当しないボタンはごくフツーに1対1のイベントハンドリングとしましたが、 数値入力や演算指定ではグループごとにイベントハンドリングを設け、 一つのイベントハンドルに対し、複数のボタンイベントを受け付けるようにバインドしました。

このような指定が出来るようバインダーデコレータは設計してあります。 バインダーデコレータの詳細な解説は、 wxPythonヘルパーモジュールの作成 を参照してください。

フリーズスクリプト解説

バイナリフリーズを作成するのに最近「py2exe」から「bbfreeze」に乗り換えました。 特徴は以下のページを比較して見てください。

以下では、アプリケーション独自のパラメータを設定します。
MAIN_SCRIPT = 'calculator.py'
NO_CONSOLE = True
DIST_FOLDER = 'dist'
以下では、実際のバイナリを「DIST_FOLDER」に出力します(結構重たいです)
from bbfreeze import Freezer
f = Freezer(DIST_FOLDER, includes=(), excludes=())
f.addScript(MAIN_SCRIPT, NO_CONSOLE)
f()
以下では、バイナリに対しWindowsXP用のマニフェストファイルを追加します。
import os
import wx_utils
app_name, ext = os.path.splitext(MAIN_SCRIPT)
manifest_fname = os.path.join(DIST_FOLDER, app_name + '.exe.manifest')
file(manifest_fname,'w').write(wx_utils.manifest(app_name))

最後の処理は他のOSでは不要です。

このfreeze.pyを実行すると、distフォルダにバイナリが作成されます。

「dist\calculator.exe」を実行して動くことが確認できると思います。

まとめ

電卓をGUI学習の最初に持ってくることが多いのですが、 意外と電卓の動きってトリッキーですね。 うまくまとめるのに意外と観察力が要求されます。 「電卓をつくれ」というアバウトな仕様から、 実際に実現する電卓仕様を固めるのにも 意外と取捨選択センスが要求されるような気がします。

なので、うまくまとまらないうちに書き始めるとかなり乱れたコードになってしまうでしょう。

こういったときにデザインとロジックが分離された 今回のようなコードがやはり見通しが良くなります。

皆さんも是非XRCedベースのGUI作成にトライして見てください。