Click 手続きを書けるこの章の作りについて。
この教材の処理系は Excel のユーザフォーム(VBA の画面部品)を扱えません。
そこでページ上の HTML の部品を「フォーム」に見立て、
ボタンを押すと対応する VBA の手続きが実行されるようにしました。
プログラムの書き方も、考え方も、本物のユーザフォームと同じです
(違うのは部品の作り方だけで、それは Excel の画面上でマウスで並べる作業です)。
下の欄に「どのイベントで、どの手続きが呼ばれたか」が記録されるので、
イベント駆動の動きが目で追えます。
セルに値を入力する時にガイドをするウィンドウを作成することができる。 これをユーザフォームという。(元資料 3 枚目)
作り方は、ツールボックスからコントロールを選び、フォームに貼り付けるだけです (プログラムを書かずに、マウスで並べます)。
| コントロール | 英名 | 何をするもの |
|---|---|---|
| ラベル | Label | 説明の文字を出す(入力はできない) |
| テキストボックス | TextBox | 値を入力してもらう |
| コンボボックス | ComboBox | 一覧から選ぶ(折りたたみ式) |
| リストボックス | ListBox | 一覧から選ぶ(開いたまま) |
| チェックボックス | CheckBox | はい/いいえ(複数選べる) |
| オプションボタン | OptionButton | いくつかから 1 つだけ選ぶ |
| トグルボタン | ToggleButton | 押した状態を保つボタン |
| フレーム | Frame | 部品をまとめる枠 |
| コマンドボタン | CommandButton | 押すと処理が始まる |
| スクロールバー/スピンボタン | ScrollBar / SpinButton | 数値を増減させる |
| イメージ | Image | 画像を置く |
最初に覚えるのは 4 つだけで足ります。
Label(説明)、TextBox(入力)、
OptionButton(1 つ選ぶ)、CommandButton(実行)。
この章の例題はすべてこの 4 つで作れます。
なお元資料の一覧にある MultiPage・TabStop・RefEdit は、
いまではほとんど使われません(RefEdit はセル範囲を選ばせる特殊な部品で、
不具合が多いことで知られています)。
フォーム上でクリック、選択することによりイベントが発生し、 イベント駆動プログラムが実行される。(元資料 9 枚目)
ここがこの章の唯一の、そして重要な新しい考え方です。 第 1〜12 章のプログラムは、すべて「実行ボタンを押したら、上から下へ最後まで走る」ものでした。 イベント駆動はそうではありません。
| これまで(第 1〜12 章) | イベント駆動(この章) | |
|---|---|---|
| 始まり | 1 つ(マクロを実行する) | いくつもある(ボタンごと、部品ごと) |
| 走る順番 | 書いた順に上から下へ | 利用者の操作した順。書いた順とは関係ない |
| 終わり方 | 最後の行まで行ったら終わり | 1 回の処理はすぐ終わる。次の操作を待つ |
| 値の持ち方 | 変数に入れておけばよい | 手続きが終わると消えるので、残す工夫が必要 |
手続きの名前が「部品の名前 + アンダースコア + イベント名」になっているのが要点です。
Private Sub CommandButton1_Click() ──
「CommandButton1 が Click されたときに、これを実行する」という意味です。
名前でつながっているので、綴りを 1 文字間違えると何も起こりません
(エラーも出ません)。イベント駆動でいちばん多いつまずきです。
身のまわりのソフトはほとんどイベント駆動です。 ボタンを押す、文字を入れる、マウスを動かす、時間が来る、通信が届く ── すべてイベントで、それぞれに対応する処理が待っています。 「プログラムが利用者を待っている」という形が、 実用的なソフトウェアの標準的な姿です。 第 1〜12 章で書いてきた「一気に走って終わる」形は、 実はバッチ処理(一括処理)と呼ばれる一部の用途にあたります。
第 3 章と同じ計算を、フォームからやってみます。 違うのは「半径をどこから受け取るか」だけです ── セルではなくテキストボックスから受け取ります。
' TextBox1 の値は A1、結果は B1〜B4 に置く約束にする
Private Sub CommandButton1_Click()
Dim r As Double
Dim pi As Double
r = Cells(1, 1).Value ' TextBox1 から受け取る
pi = Application.WorksheetFunction.Pi
Cells(1, 2) = 2 * pi * r ' 円の円周
Cells(2, 2) = pi * r ^ 2 ' 円の面積
Cells(3, 2) = 4 * pi * r ^ 2 ' 球の表面積
Cells(4, 2) = 4 / 3 * pi * r ^ 3 ' 球の体積
End Sub
Private Sub CommandButton2_Click()
Cells(1, 2) = ""
Cells(2, 2) = ""
Cells(3, 2) = ""
Cells(4, 2) = ""
End Sub
半径を 3 にしてみてください。 球の表面積と球の体積が同じ値(113.10)になります ── 誤りではありません。 4πr2 = (4/3)πr3 となるのは r = 3 のときだけで、 たまたま一致するのです。「同じ値が出たから間違い」と決めつけない ── 検算するときに気をつけたいところです。
ボタンが 2 つあるので、手続きも 2 つあります。 そしてこの 2 つはどちらが先に呼ばれるか分かりません ── 利用者が押した順に呼ばれるだけです。 だから、どの手続きも「いつ呼ばれても正しく動く」ように書く必要があります。 これがイベント駆動プログラムを書くときの心構えです。
なお Private は「このフォームの中だけで使う手続き」という指定で、
イベント駆動プログラムには慣例としてこれを付けます
(付けなくても動きますが、他から呼ぶものではないので付けるのが自然です)。
果物を購入する際の合計金額を計算せよ。
' A 列に個数、B 列に単価。会員かどうかは A4(True / False)
Private Sub CommandButton1_Click()
Dim i As Integer
Dim kingaku As Long
Dim goukei As Long
Dim kaiin As Boolean
goukei = 0
For i = 1 To 3
kingaku = Cells(i, 1).Value * Cells(i, 2).Value
Cells(i, 3) = kingaku ' 品目ごとの金額
goukei = goukei + kingaku
Next i
Cells(5, 3) = goukei ' 小計
kaiin = Cells(4, 1).Value
If kaiin Then
Cells(6, 3) = Int(goukei * 0.05) ' 割引額(1 円未満は切り捨て)
goukei = goukei - Cells(6, 3).Value
Else
Cells(6, 3) = 0
End If
Cells(7, 3) = goukei ' 割引後
Cells(8, 3) = Int(goukei * 0.1) ' 消費税 10 %
Cells(9, 3) = goukei + Cells(8, 3).Value ' 税込合計
End Sub
Private Sub CommandButton2_Click()
Dim i As Integer
For i = 1 To 9
Cells(i, 3) = ""
Next i
End Sub
単価をプログラムに書かず、シート(B 列)に置いてあります。
第 2 章の「データはシートに、手順はプログラムに」です。
値段が変わっても、プログラムを直す必要がありません。
また Int(goukei * 0.05) と切り捨てているのは、
1 円未満の端数を出さないためです。
金額を Double のまま扱うと第 4 回・第 12 章の誤差の問題が出るので、
金額は整数で持つのが原則です。
以下の仕様を満たす自動販売機を作成せよ。
・ソーダ・コーラ・オレンジ・お茶の 4 種類を購入することができる
・「入金」ボタンを押すと入金することができる
・4 種類の飲み物から 1 つを選び、購入ボタンを押すことで購入できる
・購入する前に、入金をしなければいけない
ここで初めて「状態を持つプログラム」が必要になります。 入金ボタンを押して 1000 円入れたあと、購入ボタンを押すときには 「いま 1000 円入っている」ことを覚えていなければなりません。 ところが手続きは終わると局所変数が消えます(第 10 章 §4)。 だから残高をどこかに残す必要があります。
| 方法 | 書き方 | 向き・不向き |
|---|---|---|
| モジュール変数 | Sub の外で Dim zandaka As Long |
手軽。ただしブックを閉じると消える/どこから変わったか追いにくい(第 10 章 §4) |
| シートに書く | Cells(1, 2) = zandaka |
保存すれば残る。目で見て確かめられる。元資料の「Record 手続き」がこれ |
| フォームの部品に持たせる | Label1.Caption |
表示と兼用できるが、数値を文字列として扱うことになる |
元資料 22 枚目に「変数の宣言、記録 Record 手続き」、 23 枚目に「ユーザフォームの初期化」とあります。 つまり元資料はモジュール変数で状態を持ち、Record 手続きでシートに記録する設計です。 この教材では処理系の都合で状態をシートに置く形にしましたが、 考え方は同じ ── 「イベントをまたいで残す場所を決める」ことが設計の第一歩です。
' シートの使い方(=状態を残す場所)
' A1: 残高 B1: 売上金額 C1: 売れた本数
' A3:A6 品名 B3:B6 値段 C3:C6 在庫 D3:D6 売れた本数
' A8: 選ばれた番号(1〜4) B8: 入金額
' A10 以下: 記録(Record 手続き)
Private Sub UserForm_Initialize()
' フォームを開いたときに 1 回だけ実行される
Cells(1, 1) = 0 ' 残高
Cells(1, 2) = 0 ' 売上
Cells(1, 3) = 0 ' 売れた本数
Cells(3, 1) = "ソーダ": Cells(3, 2) = 120: Cells(3, 3) = 3
Cells(4, 1) = "コーラ": Cells(4, 2) = 130: Cells(4, 3) = 3
Cells(5, 1) = "オレンジ": Cells(5, 2) = 150: Cells(5, 3) = 2
Cells(6, 1) = "お茶": Cells(6, 2) = 110: Cells(6, 3) = 3
Dim i As Integer
For i = 3 To 6
Cells(i, 4) = 0
Next i
For i = 10 To 40 ' 記録も消す
Cells(i, 1) = ""
Next i
Call Record("初期化しました")
End Sub
Private Sub Nyuukin_Click()
Dim kingaku As Long
kingaku = Cells(8, 2).Value ' 入れた金額
Cells(1, 1) = Cells(1, 1).Value + kingaku ' 残高に足す
Call Record(kingaku & " 円 入金 残高 " & Cells(1, 1).Value & " 円")
End Sub
Private Sub Kounyuu_Click()
Dim n As Integer
Dim nedan As Long
Dim zaiko As Integer
n = Cells(8, 1).Value ' 選ばれた番号(1〜4)
If n = 0 Then
Call Record("飲み物が選ばれていません")
Exit Sub
End If
nedan = Cells(n + 2, 2).Value
zaiko = Cells(n + 2, 3).Value
' 買えない場合を先にはじく(第 6 章 §2.1)
If zaiko <= 0 Then
Call Record(Cells(n + 2, 1).Value & " は売り切れです")
Exit Sub
End If
If Cells(1, 1).Value < nedan Then
Call Record("残高が足りません(あと " & nedan - Cells(1, 1).Value & " 円)")
Exit Sub
End If
' ここまで来たら購入できる
Cells(1, 1) = Cells(1, 1).Value - nedan ' 残高を減らす
Cells(1, 2) = Cells(1, 2).Value + nedan ' 売上を増やす
Cells(1, 3) = Cells(1, 3).Value + 1
Cells(n + 2, 3) = zaiko - 1 ' 在庫を減らす
Cells(n + 2, 4) = Cells(n + 2, 4).Value + 1 ' その商品の売れた本数
Call Record(Cells(n + 2, 1).Value & " を購入(" & nedan _
& " 円) 残高 " & Cells(1, 1).Value & " 円")
End Sub
Private Sub Otsuri_Click()
Dim z As Long
z = Cells(1, 1).Value
If z = 0 Then
Call Record("残高がありません")
Exit Sub
End If
Cells(1, 1) = 0
Call Record("おつり " & z & " 円 返却しました")
End Sub
' 記録する手続き(元資料の Record 手続き)
Sub Record(ByVal s As String)
Dim r As Integer
r = 10
Do While Cells(r, 1).Value <> "" ' 空いている行を探す
r = r + 1
If r > 40 Then Exit Do
Loop
Cells(r, 1) = s
End Sub
試してみてほしい順番。
① いきなり「購入」を押す(→ 選ばれていない)
② 飲み物を選んで「購入」(→ 残高が足りません)
③ 100 円だけ入金して「購入」(→ まだ足りない)
④ 1000 円入金して次々に購入(→ 在庫が減っていく)
⑤ オレンジを 3 本買おうとする(→ 売り切れ)
⑥「おつり」を押す(→ 残高が 0 に戻る)
どの順番で押しても、おかしなことにならないように書かれていることを確かめてください。
それがイベント駆動プログラムの品質です。
このプログラムのどこが「よい設計」か。
実物の自動販売機なら、さらに考えることがあります。 釣り銭の在庫(お釣りが出せないときは高額紙幣を受け付けない)、 2 人が同時に操作したらどうなるか、 停電したときに入金した額をどう扱うか ── 「状態を持つ」プログラムは、状態の数だけ考えることが増えます。 第 12 章までの「入力から出力を計算する」プログラムとは、難しさの質が違うのです。
| 操作 | |
|---|---|
| ① | VBA の画面(Alt+F11)で「挿入」→「ユーザーフォーム」 |
| ② | ツールボックスから部品を選び、フォームの上にドラッグして置く |
| ③ | 部品を選んでプロパティで名前(Name)と表示文字(Caption)を決める。
名前は意味のあるものに変えておく(CommandButton1 → Nyuukin など) |
| ④ | 部品をダブルクリックすると、
Private Sub 名前_Click() の枠が自動で作られる。中身を書く |
| ⑤ | 実行タブから「Sub/ユーザー フォームの実行」(または F5)で動かす (元資料 11 枚目) |
| ⑥ | シートのボタンから開きたいときは、標準モジュールに
UserForm1.Show と書いたマクロを作る |
いちばん多いつまずき。 「ボタンを押しても何も起こらない」── たいていは手続きの名前が部品の名前と一致していないのが原因です。 部品の名前をあとから変えると、手続きの名前は自動では変わりません。 部品をダブルクリックして枠を作らせるのが確実です(綴りを間違えません)。
_ + イベント名。綴り違いは無反応になるもとにしたのは「情報学入門(VBA)170-2019」全 35 枚です。
| 元資料 | この教材 | 種別 |
|---|---|---|
| イベント駆動が、これまでのプログラムとどう違うかが 「イベントが発生し、イベント駆動プログラムが実行される」の 1 文だけ | この章の唯一の新しい概念なので §2 として立て、 始まりが 1 つでない/走る順は利用者が決める/手続きが終わると値が消えるという 3 点を表で対比した | 不足 |
| 手続きの名前が部品の名前とつながっていることの説明がない | 部品名_イベント名 という規則を明示。
綴りが違うと無反応(エラーも出ない)という、いちばん多いつまずきを書いた | 不足 |
| 自動販売機で「変数の宣言、記録 Record 手続き」「ユーザフォームの初期化」と 見出しだけがあり、なぜ必要かが説明されていない | 「イベントをまたいで状態を残す」という問題として立て、 3 つの方法(モジュール変数/シート/部品)を比較した | 不足 |
| 自動販売機の異常な操作(未選択・売り切れ・残高不足)への言及がない | 3 つとも先にはじく形で実装し、お金を減らす前に在庫を確かめるという 順序の重要性を明記。試す順番も示した | 不足 |
| コントロール一覧に MultiPage・TabStop・RefEdit が並列で載っている | 現在ほとんど使われない。最初は 4 つで足りると絞った | 古い |
| 「円と球」「標準体重」は第 3 章・第 7 章と同じ計算 | 円と球だけを残し、「受け取り先がセルからテキストボックスに変わるだけ」という 点に絞った(標準体重は第 7 章を参照) | 重複 |
| 35 枚のうち大半が Excel 画面のスクリーンショットで、 プログラムが画像の中にしかない | 3 つの実際に動くフォームにした。 本物の Excel での作成手順は 6 段の表にまとめた(画面が変わっても読める形に) | 体裁 |
| 球の表面積・体積の式(12 枚目) | 元資料では上付き文字つきで正しく書かれている (4πr2、(4/3)πr3)。第 3 章と同じ | ─ |
元資料で正しかったこと。 ユーザフォームの定義(値を入力する時にガイドをするウィンドウ)、 コントロール 15 種類の名称、 「クリック・選択によりイベントが発生し、イベント駆動プログラムが実行される」という説明、 実行方法(実行タブ →「Sub/ユーザー フォームの実行」)、 そして自動販売機という題材の選び方── いずれも適切です。 とくに自動販売機は「購入する前に入金をしなければいけない」という仕様が 状態を持つ必要を自然に生む点で、イベント駆動を学ぶ題材として的確です。 「入金 → 購入 → 残高不足 → 追加入金 → 購入」という 操作の流れを画面つきで 6 枚使って示しているのも、動きが伝わる良い構成です。