(2019.12.31. 公開)
本プログラムの動作としては、以下のものを想定しています。
テキストの入力は現時点でも可能です。今回は、「更新ボタンのクリックへの応答」、そして「数式の解釈」について考えてみます。
wxWidgets では、ユーザーの動作はすべて「イベント」としてプログラムに知らされます。プログラム側は、イベントをつかまえて、必要な動作を実行すればよいことになります。
イベントは、基本的には「そのイベントが発生したウィンドウ」に送られます(wxWidgets では、ボタンやテキストなど、画面上の部品も含めてすべて「ウィンドウ」と呼びます)。ボタンをクリックした場合は、そのボタンにイベントが送られます。ボタンに「イベントを処理するプログラムコード」を登録しておけば、イベントが発生した時にそのコードを実行してくれます。
イベントを処理するプログラムコードのことを「イベントハンドラ」と呼び、「関数」の形で登録します。「関数」とは、Lua では function ... end
で定義されるものです。例として、ボタンがクリックされた時の処理を OnClick
という関数に記述したとしましょう。この関数は、下のようになります。
function OnClick(event) -- 必ず event という引数を持つ
-- 中身はあとで考える
end
この関数=イベントハンドラをボタンに登録するには、Connect()
を使います。Connect()
は wxEvtHandler
クラスのメソッドです。ボタンを含むすべてのウィンドウは wxEvtHandler
のサブクラスなので、Connect()
を使うことができます。
frame.button:Connect(wx.wxEVT_BUTTON, OnClick)
-- wx.wxEVT_BUTTON は「ボタンがクリックされたイベント」を表す
Connect()
の第1引数には、イベントの種類を表す定数を置きます。この定数を探すためには、イベントを発生する GUI 部品のクラス、この場合は wxButton
のドキュメントを開きます。すると、以下のような記述が見つかるでしょう。
Event macros for events emitted by this class:
- EVT_BUTTON(id, func):
Process awxEVT_BUTTON
event, when the button is clicked.
EVT_BUTTON(id, func)
と書いてあるところは、C++ でプログラムを作成するときの話なので、ここでは無視して構いません。その次の、"Process a wxEVT_BUTTON
event, when the button is clicked." の記述に注目します。この記述から、ボタンがクリックされた時には wxEVT_BUTTON
というイベントが発生することがわかります。このイベントを捕まえてください、というのが、上の Connect()
関数の第1引数の意味です。
Connect()
の第2引数は、関数名です。これを OnClick()
と書いてはいけません。カッコの有無で、意味が変わってしまいます。OnClick
は「OnClick
という関数そのもの」、OnClick()
は「OnClick
という関数を呼び出した結果」を表します。Connect()
で必要なのは、「関数を呼び出した結果」ではなくて「関数そのもの」ですから、カッコなしの OnClick
を使います。
これで、「更新」ボタンを押すと、関数 OnClick
が呼び出されるようになりました。次に OnClick
の中身を記述しますが、その前に、入力された数式をどう解釈するかを考えておきましょう。
下のように、y = x^3
という数式を入力して、「更新」ボタンを押したとします。OnClick
が呼び出されますが、そこではどういう処理をするべきでしょうか。
まず、「この数式はそもそもどう使われるものなのか」を考えます。このプログラムで実現したいのは、「ここに入力した数式をグラフ化して表示する」ことです。数式は y = (x の関数)
という形で記述されるものとします。すると、グラフを書く時に必要なのは、x
の値を変化させて、それぞれに対する y
の値をこの式を使って計算することでしょう。
いろいろな方法が考えられますが、ここでは、入力された数式から、下のような「Lua の関数」を作ることにしましょう。
入力された文字列の前に function (x) \n local y
を置き、文字列の後に return y \n end
を置きます(\n
は改行)。こうすると、入力された文字列の中の x
は関数の仮引数、y
はローカル変数として解釈され、「与えられた x
の値に対して y
を計算し、その値を返す」という関数ができあがります。
この「Lua 関数を表す文字列」を Lua プログラムとして読み込むためには、Lua の組み込み関数である loadstring()
を使います。loadstring("文字列")
は、文字列を Lua プログラムとして解釈して、問題なければそのプログラムの内容を関数として返し、問題があれば nil
とエラーメッセージを返します。次のように使います。
f, errmsg = loadstring(str)
if f then
f() -- str の内容を実行する
else
-- errmsg を表示する
end
一つ注意があります。実は、loadstring()
の仕様である「関数として返す」という動作が、慣れないとわかりにくいのです。上の図に示した「Lua の関数」を見てみてください。function (x) \n local y \n y = x^3 \n return y \n end
というのは、確かに「Lua の関数」です。ところが、loadstring()
にこの文字列を直接渡すと、以下のように苦情を言われます。
% f, errmsg = loadstring("function (x) \n local y \n y = x^3 \n return y \n end")
% print(f); print(errmsg)
nil
[string "function (x) ..."]:1: '<name>' expected near '('
'('
の近くに '<name>'
が必要だ、と言っております。どういうことかと言うと、loadstring()
に渡す文字列は、Lua の「文」(または「文」の並び)でないといけないのです。
Lua の文法では、「文」と「式」は明確に区別されます。上の「関数」を少し簡潔にして、次の例を見てみましょう。
function (x) return x^3 end -- 「関数」を返す式
func = function (x) return x^3 end -- 'func' という名前の「関数」を定義する文
function func (x) return x^3 end -- 上と全く同じ(シンタックスシュガー)
loadstring()
に渡す文字列は「文」でないといけないので、1行目の文字列を渡すとエラーになります。2行目・3行目の文字列ならば、正しく解釈できます。
それでは、「loadstring()
が関数を返す」という Lua マニュアルの説明は、何を意味しているのでしょうか。その答えは、「loadstring(str)
が返した『関数』を実行すると、文字列 str
が表す『文』を実行したことになる」ということです。つまり、「文字列 str
の内容を Lua の文として実行したい」場合は、次の手順を踏めばよいわけです。
loadstring(str)
で、str
の内容が正しい Lua の文(の並び)になっていることを確かめる。loadstring(str)
の戻り値として、「これを呼び出したら str
を実行したことになるよ」という「関数」が返される。()
をつければよい。例えば、f = loadstring(str)
と書けば、戻り値が変数 f
に入るので、f()
と書けば、f
を「呼び出す」ことができる。
まとめると、loadstring()
を使って、上の例のように「func
という名前の関数」を定義するには、下のように書けばよいことになります。
f, errmsg = loadstring("func = function (x) return x^3 end")
if f then
f() -- ここで「loadstring() が返した『関数』」を実行する
-- つまり、関数 func が定義される
else
-- errmsg を表示する
end
このプログラムでは、数式を表す関数を frame
(メインウィンドウを表すものでしたね)のテーブルフィールドとして保存することにします。そうすると、ボタンが押された時の処理は、下のようになります。
local eq = frame.textctrl:GetValue() -- 文字列を textctrl から得る
local str = "frame.func = function (x) local y\n" .. eq .. "\nreturn y; end"
-- 関数定義のための文字列を作成する
local f, errmsg = loadstring(str) -- 解釈する
if f then
f() -- frame.func が関数として定義される
-- 画面を更新する
else
-- エラーメッセージを表示する
end
エラーが出た時には、ユーザーに知らせなくてはなりません。ここではシンプルに、loadstring()
が返したエラーメッセージを画面にそのまま表示してみます。「数式」という文字列と、「更新」ボタンの間に、エラーメッセージを表示する wxStaticText
を1つ追加します。SetForegroundColour
で、表示色を赤色にしましょう。(Colour の綴りがイギリス英語なので、注意してください。)
frame.stext = wx.wxStaticText(frame, -1, "数式")
frame.etext = wx.wxStaticText(frame, -1, "")
frame.etext:SetForegroundColour(wx.wxRED)
frame.button = wx.wxButton(frame, -1, "更新")
-- (中略) --
local hsizer1 = wx.wxBoxSizer(wx.wxHORIZONTAL)
hsizer1:Add(frame.stext, wx.wxSizerFlags():Border(wx.wxLEFT + wx.wxRIGHT, 5))
-- wxSizerFlags() の引数 "1" が無くなっているのに注意。
-- つまり、「数式」という文字列の幅は一定。
-- その代わり、次のエラーメッセージの文字列を横幅いっぱいに広げる。
hsizer1:Add(frame.etext, wx.wxSizerFlags(1):Border(wx.wxLEFT + wx.wxRIGHT, 10))
hsizer1:Add(frame.button, wx.wxSizerFlags():Border(wx.wxRIGHT, 5))
これを使って、OnClick
を下のように書くことができます。
function OnClick(event)
local eq = frame.textctrl:GetValue()
local str = "frame.func = function (x) local y\n" .. eq .. "\nreturn y; end"
local f, errmsg = loadstring(str)
if f then
f()
frame.etext:SetLabel("")
else
frame.etext:SetLabel(errmsg)
end
end
これで、「更新」ボタンを押すと、数式の解釈を行うようになります。グラフ表示はまだ実装していないので、何も起きませんけど。
エラーがあれば知らせてくれます。といっても、不親切ですけどね。ユーザーから見れば「"return" なんか使ってないぞ?」と思いますよね。あとで改良することにしましょう。
本章のプログラム: [graphcalc03.wx.lua]
目次