(2019.12.22. 公開)
「手軽にできる」って言っているからには、ウィンドウぐらい「さくっ」と出せないといけませんね。やってみましょう。
その前に、「何を作るか」を決めておきます。ここでは、下のように「数式を入れたらそのグラフを表示してくれる」アプリを作ることにします。
このアプリを選んだ理由はいくつかあります。
まあ、三番目はやってみないとわからないので、眉に唾をつけといてください。
ウィンドウの構成を考えます。必要な部品は、下の通りです。
これらを wxWidgets でどう実現するかを知らないといけません。wxWidgets のドキュメントは膨大で、慣れないとなかなか大変です。ここでは "List of Available Classes" を紹介しておきます。日本語版がないのが残念です。英語ドキュメントを頑張って読んでください。(この時点ですでに「手軽じゃない」かも?)
上の部品は、それぞれ次のようなクラスで実現できます。
wxStaticText
wxButton
wxTextCtrl
wxPanel
(単に wxWindow
でもいいかも知れないけど、あとでボタンなどを配置することを考えて wxPanel
にしておきます)
wxLua のコードで、必要な部品を作成していきましょう。まず、ウィンドウ本体の作成です。ウィンドウ本体は、wxFrame
というクラスです。
frame = wx.wxFrame(wx.NULL, -1, "LuaAppMaker グラフ計算機")
frame:SetClientSize(640, 480) -- ウィンドウサイズを 640x480 にする
wxLua では、wxWidgets のクラスは wx.wxXXXX
という形で記述します。wx
がかぶっとるやないかい、という文句は wxLua の原作者に言ってください。wxLua が世に出てから結構時間がたっていますので、今さら変えられません。
wx.wxFrame(...)
は、wxFrame
のコンストラクタ、つまり wxFrame オブジェクトを作成するための関数呼び出しです。最初の引数は「この wxFrame の親になるウィンドウ」ですが、ここではウィンドウは1個しか作らないので、wx.NULL
を置きます。(Lua 的には nil
を渡せるようにして欲しかったところですが。) 2番目の引数は、ウィンドウの ID(識別番号)です。ここでは、特に必要ないので、-1
を渡しておきます。3番目の引数はウィンドウのタイトルです。
続いて、この wxFrame
の子ウィンドウとして、wxStaticText
, wxButton
, wxTextCtrl
, wxPanel
を作成します。
frame.stext = wx.wxStaticText(frame, -1, "数式")
frame.button = wx.wxButton(frame, -1, "更新")
frame.textctrl = wx.wxTextCtrl(frame, -1, "", wx.wxDefaultPosition, wx.wxSize(100, 32), wx.wxTE_MULTILINE)
frame.panel = wx.wxPanel(frame, -1, wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxFULL_REPAINT_ON_RESIZE)
wxTextCtrl
の引数にある wx.wxTE_MULTILINE
, および wxPanel
の引数にある wx.wxFULL_REPAINT_ON_RESIZE
は、「ウィンドウスタイル」の指定です。wx.wxTE_MULTILINE
は、「複数行にわたるテキストが入力できる」というスタイルです。wx.wxFULL_REPAINT_ON_RESIZE
は、「サイズが変更された時に全体を書き換える」というスタイルです。なぜこのスタイルを指定するかは、グラフを描画する段階で説明することにします。
frame
は、あとで何度も使うので、グローバル変数として定義します(Lua では、local
をつけない変数はグローバル変数と解釈されます)。他の部品もあとで使うのですが、グローバル変数があまり多くなると収拾がつかなくなるので、ここでは frame
のテーブルフィールドとして格納しています。wxLua では、wx.wxXXXX
タイプのオブジェクトは Lua の「ユーザーデータ」型として定義されていますが、このユーザーデータ型は、テーブルフィールドを持つことができるようになっています (内部で __index
, __setindex
メタメソッドを持っているのでしょう)。関係の深いオブジェクトをまとめて記述できるので、この機能は積極的に使っていきたいところです。
wxStaticText
と wxButton
への引数は、先ほどと同様です。wxTextCtrl
には、4番目・5番目の引数として、位置とサイズを渡しています。位置はあとで自動調整するのですが、サイズはここで決めておく必要があります。6番目の引数 wx.wxTE_MULTILINE
は、複数行を入力できるようにするための設定です。
wxWidgets では、ウィンドウ内の部品の位置を自動調整するために「サイザー (Sizer)」という仕組みを使います。ここでは、最も単純な wxBoxSizer
を使うことにします。wxBoxSizer
は、水平方向または垂直方向に部品を並べて、与えられた規則に従ってその大きさを調節するものです。wxBoxSizer
の中には、ボタンやテキストなどの GUI 部品の他に、別の wxBoxSizer
を入れることもできます。
今回のウィンドウは、下のように2つの wxBoxSizer
を使ってレイアウトすることができます。
最初に、StaticText と Button を含む水平方向のサイザーから考えます。水平方向のサイザーに部品を入れるときは、その部品について「水平方向に伸び縮みできるか」、および「隣の部品との間にスペースを入れるかどうか」をまず考えます。この場合は、StaticText は伸び縮みできて、ボタンは伸び縮みしないことにしましょう。また、隣の部品との間隔は 5 ポイントとします。
サイザーに入れる部品の設定は、wxSizerFlags
を使って記述します。StaticText の設定は、下のようになります。
Button の設定は、下のようになります。
これらの設定を使って、水平方向のサイザーに部品を入れます。下のようなコードになります。
local hsizer1 = wx.wxBoxSizer(wx.wxHORIZONTAL)
hsizer1:Add(frame.stext, wx.wxSizerFlags(1):Border(wx.wxLEFT + wx.wxRIGHT, 5))
hsizer1:Add(frame.button, wx.wxSizerFlags():Border(wx.wxRIGHT, 5))
今度は、垂直方向のサイザーを考えます。先ほどの水平方向のサイザーと、TextCtrl, Panel を入れます。今度は「縦方向に伸び縮みできるか」を決めなくてはなりません。今回は、水平サイザーと TextCtrl の高さは固定にして、Panel だけが縦方向に伸び縮みできることにしましょう。余白も、図に示したように入れることにします。先ほどは使わなかった設定として、3つの部品の「幅を揃える」ようにします。これを指定しておかないと、ウィンドウのサイズが変わっても、部品の幅は変わらないことになってしまいます。
コードは下のようになります。Expand()
というのが、「幅を揃える」指示です。なお、水平サイザーの部品に Expand()
を指定すると、「高さを揃える」指示になります。
local vsizer1 = wx.wxBoxSizer(wx.wxVERTICAL)
vsizer1:Add(hsizer1, wx.wxSizerFlags():Expand():Border(wx.wxTOP, 5))
vsizer1:Add(frame.textctrl, wx.wxSizerFlags():Expand():Border(wx.wxTOP + wx.wxLEFT + wx.wxRIGHT, 5))
vsizer1:Add(frame.panel, wx.wxSizerFlags(1):Expand():Border(wx.wxTOP, 5))
全部の部品を入れ終わったら、一番外側のサイザーを frame
に設定して、Layout()
を呼びます。これで、ウィンドウのサイズに合わせて部品がレイアウトされます。最後に Show()
を呼べば、ウィンドウが画面に表示されます。
frame:SetSizer(vsizer1)
vsizer1:Layout()
frame:Show(true)
ウィンドウの作成はできました。それでは、このウィンドウを表示する「だけ」のプログラムを作ってみましょう。
function NewFrame()
-- wxFrame を作る
frame = wx.wxFrame(wx.NULL, -1, "LuaAppMaker グラフ計算機")
frame:SetClientSize(640, 480)
-- 部品を配置する
...(中略)...
-- ウィンドウを表示
frame:Show(true)
end
NewFrame()
……あれ、これだけ? ウィンドウを表示したらすぐ終了しちゃうんじゃない?
実は、LuaAppMaker は、このプログラムを読み込むと、まず書いてあることをすべて実行します。それが終わったら、自動的に wxWidgets の「イベントループ」に入ります。イベントループは、ユーザーからの入力を待って、それに応じて「イベント」を発生させる仕組みです。実行中のプログラムは、「イベント」を受け取って、それに対応する動作を行います。このプログラムでは、「イベント」を受け取る記述がどこにもないので、発生したイベントは wxWidgets または LuaAppMaker が受け取って、決められた動作を行います。「ウィンドウのクローズボタンが押されたら、ウィンドウを閉じて、プログラムは終了する」などです。
LuaAppMaker のプログラムを実行するには、作成したプログラムのファイルを LuaAppMaker のアイコン上にドラッグ&ドロップしてください。下のように、ウィンドウが開くはずです。ウィンドウのサイズを変更してみて、ボタンの位置や TextCtrl のサイズが狙い通りに変わることを確かめてください。
どこかでエラーが起きると、下のような画面になります。これは、32行目の frame.button
を frame.botton
とミスタイプしたときのエラー表示です。なかなか手強いですね……
stack traceback:
の下を見ると、32行目でエラーが出たことが示されています。まずはエラーの出た行を丁寧に見ることから始めましょう。
プログラムが正しく走っている時、TextCtrl 上で文字をタイプすれば、文字が入力されます。「更新」ボタンを押すこともできます。ただし、上にも書いた通り、イベントを受け取る記述を何も書いていないので、何の動作もしてくれません。
次にやるべきことは、「更新」ボタンが押された時に、そのイベントを受け取って、画面にグラフを表示することです。次章以降で、そのようなコーディングを進めていきましょう。
本章のプログラム: [graphcalc02.wx.lua]
目次