両方向から換算可能なシート
1.背景
例えば電気関係には「電流(A)x電圧(V)=電力(W)」のような関係式があり、その式を使って電圧と電力から残りの電流を求める場面は良くあります。Excelのワークシート関数でも簡単に計算できるので、値を入れると答えが計算される様なシートを作っている方も多いと思います。但し上記のように常に「電流を求めたい」時ばかりではなく、「電圧や電力を求めたい」時だってありますので、計算シートだって「電力を求めるシート」「電圧を求めるシート」・・・と分けて作っているのではないでしょうか。
そうしなければいけない理由は、出力用セルには計算式が入っているため、入力用セルと出力用セルをゴッチャにできない為です。
今回は、入力・出力を気にせず「3つのセルの2つに入力すれば、残りの1つのセルに計算結果が表示される」ものを紹介します。
なお、ワークシート関数の方については説明を聞けば「なーんだ、しょうもな」と思われるはずですので、マクロで実現する方法についても併行して紹介いたします。
2.概要
2-1.ワークシート関数編
「ワークシート関数による両方向換算」の画面は、図2-1の様になります。図2-1
図2-1の一番左側が何も入力されていないシートで、3つの黄色いセル(電流・電力・電圧)の内2つに値を入力することで、残りの1つのセルに計算結果(赤文字)が表示されます。
尚、3つのセル全てに値を入力した際には、関係式が崩れてしまっている可能性がありますので、図2-2の様にセルが赤くなります。
図2-2
2-2.マクロ編
「マクロによる両方向換算」の画面は、図2-3の様になります。図2-3
マクロによるものは、ワークシート関数によるものと外見はほぼ同じです。また3つのセル全てを入力値にするとワークシート関数の時と同様にセルが赤くなります。
(「全てを入力値にする」のはマクロ内容を知っていれば可能ですが、ワークシート関数編よりは難しくなっています)
3.ワークシート関数での両方向換算
3-1.設定方法
ワークシート上は図3-1のようなレイアウト・配色にしています。・入力セル(D3,D5,D7セル)は黄色の背景にし、「ここが入力セル」であることを明確にする。
・ワークシート関数は「入力セル(黄色セル)の1つ左のセル(C列)」に記入する。
・ワークシート関数を記入したセルの文字色は赤色にする。
・ワークシート関数を入力してあるセル(C列)は、列幅を極細にする。
図3-1
数式の内容を、例として「C3セル(電流の項目)」で説明します。(図3-2)
図3-2
見た通り数式はIF文で出来ており、まず条件式は「AND(ISNUMBER(D5),ISNUMBER(D7))」となってます。
D5セル・D7セルの値をISNUMBERで調べていますので、数値であればTrueとなります。ANDでつないでいますので「D5とD7の両方が数値」であればIF文としてはTrueになり「REPT(" ",10)&ROUND(D5/D7,2)」を実行します。またFalseであれば「""」で何も表示しません。
True時の「REPT(" ",10)&ROUND(D5/D7,2)」の後方の「D5/D7」は「「電力 ÷ 電圧」の計算で、その結果をROUND(数式,2)で小数点2桁で四捨五入をしています。
「REPT(" ",10)」は、半角スペースを10個連続させたもので、それを「D5/D7」の計算結果の先頭に「&」で文字列結合させています。
ですので「REPT(" ",10)&ROUND(D5/D7,2)」は「文字列」として出力されます。
出力している文字列は、セル幅(=C列の極細の列幅)よりも長くなりますので、もし隣のセル(D3セル)が空白であれば「はみ出して表示」されますので、あたかも「D3セルに計算結果が表示」されたように見える、という訳です。
なお「半角スペースを10個連続」は今回のサンプルファイル用に調整した間隔です。計算結果は「入力セルと同等の位置に表示」されるとカッコイイのですが、計算結果としての値の桁数で表示位置が左右されるため、「ほぼ中央寄り」の無難な位置にしているつもりです。
スペースを増やして「文字列がD3セルの位置を超える」と、その隣のE3セルには「A(アンペア)」の文字が入っているため、文字列が欠落してしまいますので注意が必要です。
また、もしD7セルにゼロを入れる(ゼロで除算)と、計算結果は「#DIV/0!」とエラー表示になりますが、文字列ではないのでD3セルにはみ出して表示されることはありません。
なお、もしゼロ除算などのエラーを表示するのであれば、下記のようにIF文全体をISERRORでエラー検出をし、文字列として出力する必要があります。
=IF(ISERROR(IF(AND(ISNUMBER(D5), ISNUMBER(D7)), REPT(" ",10)&ROUND(D5/D7,2),"")),
REPT(" ",10)&"Error",IF(AND(ISNUMBER(D5), ISNUMBER(D7)), REPT(" ",10)&ROUND(D5/D7,2),""))
図3-3
C5セル・C7セルの数式も基本的には同じ構造で、異なるのは計算をする部分だけです。REPT(" ",10)&"Error",IF(AND(ISNUMBER(D5), ISNUMBER(D7)), REPT(" ",10)&ROUND(D5/D7,2),""))
図3-3
なお、3つの黄色セル全てに値が入れられてしまうと「計算結果が黄色セル上に表示されない」ことになり、3つの黄色セル上の値は無関係になります。ですので「セル間に関係が無い(=式が異常)」ことを示すため、3つのセルの色を赤くしています。
赤くするのには、図3-4の通り「条件付き書式」を使用しています。
図3-4
操作的には、D3・D5・D7セルを選択し、「ホーム」タブ→「スタイル」グループ→「条件付き書式」ボタンを押して、図3-4の右図のように数式を記入し、成立した時の書式を設定します。
3-2.数式の保護等
今回のデメリットはC列の列幅が極細で目立ちやすいことと、計算式がユーザーに見えてしまうことだと思いますので、その対策として以下の設定をするのが良いと思います。(サンプルファイルは、以下の設定はしていません。)3-2-1.枠線の非表示
目立っている枠線を非表示にするのが、図3-5の設定です。
「ファイル」→「その他」→「オプション」→「詳細設定」→「次のシートで作業するときの表示設定」項の中にある「枠線を表示する」のレ点を外すと、アクティブシートの枠線が非表示になり列幅が目立たなくなります。
図3-5
3-2-2.シート保護設定
ユーザーが入力できるセル(今回は黄色セル)以外を「入力不可」とするためには、図3-6の様にシート保護を設定します。
まずは①黄色セルをCtrlキーを押しながら複数選択し、②「選択したセルを編集可にする」ために「範囲の編集を許可する」ボタンをクリックし、最後に③シート保護を設定します。
(パスワードを設定しないとユーザー側から解除できてしまいますので、適切なパスワードを設定して下さい。)
図3-6
シート保護の保護内容については、図3-7の様に細かく設定ができます。もしC列の数式も見せたくないのであれば「黄色セル以外は選択できない」ようにするため、「ロックされていないセル範囲の選択」のみにレ点が付くようにします。
図3-7
4.マクロでの両方向換算
4-1.シートモジュールのコード
マクロによる換算シートは、サンプルファイルではSheet2に作成しました。マクロはシート上のセル値の書き換えを察知して動かしますので、Sheet2のシートモジュール上に「Worksheet_Change」イベントプロシージャを記載しています。(図4-1)
- Private Sub Worksheet_Change(ByVal Target As Range)
- '========== ⇩① 変数宣言と変数値の設定 ====================
- Dim Rng(1 To 3) As Range
- Dim sumRng As Range
- Dim RngNo As Long
- Dim AnsColor As Long
- Dim InputColor As Long
- Dim i As Long
- AnsColor = RGB(255, 0, 0)
- InputColor = RGB(0, 0, 0)
- Set Rng(1) = Sheet2.Range("C3")
- Set Rng(2) = Sheet2.Range("C5")
- Set Rng(3) = Sheet2.Range("C7")
- Set sumRng = Union(Rng(1), Rng(2), Rng(3))
- '========== ⇩② Changeされたセルの判断 ====================
- If Intersect(Target, sumRng) Is Nothing Then Exit Sub
- '========== ⇩③ 入力値種類の判定 ====================
- Application.EnableEvents = False
- For i = 1 To 3
- If Rng(i).Font.Color = AnsColor Then Rng(i).Value = ""
- If Not IsNumeric(Rng(i).Value) = True Then
- MsgBox Rng(i).Address & " は、数値ではありません。"
- Application.EnableEvents = True
- Exit Sub
- End If
- Next i
- '========== ⇩④ どのセルに値が入力されてるか計算 ====================
- RngNo = (Not (Rng(1) = "")) * -1 + (Not (Rng(2) = "")) * -2 + (Not (Rng(3) = "")) * -4
- '========== ⇩⑤ 書式の初期化 ====================
- sumRng.Font.Color = InputColor
- sumRng.Interior.Color = RGB(255, 255, 0)
- '========== ⇩⑥ 計算実行と結果の出力 ====================
- Select Case RngNo
- Case 0, 1, 2, 4 'どのセルにも値が入っていないか、1つにしか値が入っていない時
- Case 3 '1番目、2番目に値が入っている時
- If Rng(1).Value = 0 Then
- Rng(3).Value = "0除算"
- Else
- Rng(3).Value = Rng(2).Value / Rng(1).Value
- End If
- Rng(3).Font.Color = AnsColor
- Case 5 '1番目、3番目に値が入っている時
- Rng(2).Value = Rng(1).Value * Rng(3).Value
- Rng(2).Font.Color = AnsColor
- Case 6 '2番目、3番目に値が入っている時
- If Rng(3).Value = 0 Then
- Rng(1).Value = "0除算"
- Else
- Rng(1).Value = Rng(2).Value / Rng(3).Value
- End If
- Rng(1).Font.Color = AnsColor
- Case 7 '全ての3つのセルに値が入っている時
- sumRng.Interior.Color = RGB(255, 0, 0)
- End Select
- Application.EnableEvents = True
- End Sub
図4-1
4-2.①変数宣言と変数値の設定
3~8行目は変数宣言、9~15行目はその変数への値の設定を行っています。
3行目のRng配列は、ユーザーが入力する黄色セルのRangeオブジェクトです。今回3つのセルが対象ですので1~3の要素として宣言しています。
4行目のsumRng変数は、Rng配列の集合体のオブジェクトです。
5行目のRngNoは、3つの黄色セルの内「どのセルに値が入っているか」を計算するための変数です。32行目以降で使用します。
6行目のAnsColorは、「計算された値」を示すための文字色です。色はLong型で表わされますのでLong型で宣言しています。
7行目のInputColorは、AnsColorとは反対に「入力された値」を示すための文字色です。
8行目の i は、黄色セルの数を表すカウンター変数です。
9行目はAnsColor(計算された値)に色を設定しています。RGB(255, 0, 0)は「赤色」です。
10行目はInputColor(入力された値)に色を設定しています。RGB(0, 0, 0)は「黒色」です。
12~14行目は、Rng配列に黄色セルをオブジェクトとして代入します。
本当は定数としてセル位置を宣言出来れば良いのですが、オブジェクトは定数では宣言できません。他の方法として「"C3"」「"C5"」等のセル位置を示す文字列を定数(Const)として宣言する方法もありますが、今回は「セルへの値代入」や「セルの書式(文字色など)設定」をRangeオブジェクトに対して実行するコードが多かったため、配列変数として宣言をしています。
15行目はsumRng変数へ、12~14行目で設定した各黄色セルを「Unionメソッド」で集合体にした後代入します。
入力セルを集合体にすることで、全ての入力セルの書式をまとめて変更できると共に、18行目で使っているような「変更されたセルが対象セルか否か」を判断する時にもうまく利用できます。
4-3.②Changeされたのが対象セル(黄色セル)か否かの判定
18行目は、Worksheet_Changeイベントの引数である「Target(変更されたセル範囲)」と、15行目で代入された「入力セル(黄色セル)の集合体」をIntersectメソッドで比較しています。
Intersectは「重なっているセル範囲」を返しますが、もし重なっていない時は「Nothing」を返します。ですので重なっているか否かを判断するには「Is Nothing」がTrueかFalseかで判別します。
ですので18行目は「重なっていない時はプロシージャを抜ける(Exit Sub)」という意味になります。
4-4.③黄色セルへの入力値種類の判定
22~29行目はFor~Nextで3つの入力セルの値を確認しています。
一見引数のTargetセルだけを調べれば良さそうですが、複数セルをまとめてChangeされたり、黄色セル以外のセルも含めた複数セルをChangeされたりすると、Targetを分解して1つ1つのセルを調べる必要が出てきます。ですので、18行目で「Changeされたセルの中に黄色セルが含まれている」ことが分かったら、3つの黄色セル全てを確かめることにしています。
(対象セルが多数になる場合には、Targetに含まれるセルの位置を調べた方が処理時間が短くなる場合があるかもしれません。)
プログラムの流れを考えるために、まずユーザーの操作と画面の遷移のあるべき姿を図4-2に整理しました。
例として「C3セルとC5セルにユーザーが値を入力し、C7セルに計算結果が表示されいている」状態を一番上に表しています。プログラム側から見て「各セルをどう操作すれば良いか」について見ていきます。
図4-2
ユーザーが続けて換算作業をする場合、まず入力値(ここではC5セル)を操作した時にどうなるかを考えます。
図4-2の①は入力値を上書きするパターンです。この場合C5セルが書き換わることでWorksheet_Changeイベントが発生し、C3とC5の値を使って計算しC7に代入します。
C7セルは、どうせ上書きされますので、事前に「C7セルをクリアしてもしなくてもOK」です。
②はC5セルの値を(DELキー等で)削除するパターンです。C5が削除されてWorksheet_Changeイベントが発生するのですが、入力値はC3だけ(C7は計算結果だから入力値ではない)になりますので、計算できません。ですので計算結果である「C7はクリア」する必要があります。
以上①②は「C5セルに対するユーザー操作」でしたが、「C3セルに対する場合」でも同様になります。
続いて計算結果セル(ここではC7セル)を操作した時にどうなるかを考えてみます。
③は、計算結果であるC7セルに対して、ユーザーが値を入力しようとするパターンです。しかしここで入力を許してしまったら、3つのセル全てが入力値になってしまい「電流x電圧=電力」という関係が崩れる可能性があります。
ですのでプログラム側としては「C7への入力を拒否してC7セルの値を守る」か「入力した値は無視し、C3とC5セルの値を使って再計算しC7セルに代入する」かの実行が必要です。
④は、計算結果であるC7セルの値を(DELキー等で)削除するパターンです。C7が削除さると、C3・C5の入力値だけが残りますので「入力値が揃った段階」と同等となりますので、計算をしその結果をC7セルに代入すれば良いことになります。
これをプログラム側の立場でまとめてみると、図4-3のようになります。
まずは①黄色セルをCtrlキーを押しながら複数選択し、②「選択したセルを編集可にする」ために「範囲の編集を許可する」ボタンをクリックし、最後に③シート保護を設定します。
(パスワードを設定しないとユーザー側から解除できてしまいますので、適切なパスワードを設定して下さい。)
図3-6
シート保護の保護内容については、図3-7の様に細かく設定ができます。もしC列の数式も見せたくないのであれば「黄色セル以外は選択できない」ようにするため、「ロックされていないセル範囲の選択」のみにレ点が付くようにします。
図3-7
4.マクロでの両方向換算
4-1.シートモジュールのコード
マクロによる換算シートは、サンプルファイルではSheet2に作成しました。マクロはシート上のセル値の書き換えを察知して動かしますので、Sheet2のシートモジュール上に「Worksheet_Change」イベントプロシージャを記載しています。(図4-1)- Private Sub Worksheet_Change(ByVal Target As Range)
- '========== ⇩① 変数宣言と変数値の設定 ====================
- Dim Rng(1 To 3) As Range
- Dim sumRng As Range
- Dim RngNo As Long
- Dim AnsColor As Long
- Dim InputColor As Long
- Dim i As Long
- AnsColor = RGB(255, 0, 0)
- InputColor = RGB(0, 0, 0)
- Set Rng(1) = Sheet2.Range("C3")
- Set Rng(2) = Sheet2.Range("C5")
- Set Rng(3) = Sheet2.Range("C7")
- Set sumRng = Union(Rng(1), Rng(2), Rng(3))
- '========== ⇩② Changeされたセルの判断 ====================
- If Intersect(Target, sumRng) Is Nothing Then Exit Sub
- '========== ⇩③ 入力値種類の判定 ====================
- Application.EnableEvents = False
- For i = 1 To 3
- If Rng(i).Font.Color = AnsColor Then Rng(i).Value = ""
- If Not IsNumeric(Rng(i).Value) = True Then
- MsgBox Rng(i).Address & " は、数値ではありません。"
- Application.EnableEvents = True
- Exit Sub
- End If
- Next i
- '========== ⇩④ どのセルに値が入力されてるか計算 ====================
- RngNo = (Not (Rng(1) = "")) * -1 + (Not (Rng(2) = "")) * -2 + (Not (Rng(3) = "")) * -4
- '========== ⇩⑤ 書式の初期化 ====================
- sumRng.Font.Color = InputColor
- sumRng.Interior.Color = RGB(255, 255, 0)
- '========== ⇩⑥ 計算実行と結果の出力 ====================
- Select Case RngNo
- Case 0, 1, 2, 4 'どのセルにも値が入っていないか、1つにしか値が入っていない時
- Case 3 '1番目、2番目に値が入っている時
- If Rng(1).Value = 0 Then
- Rng(3).Value = "0除算"
- Else
- Rng(3).Value = Rng(2).Value / Rng(1).Value
- End If
- Rng(3).Font.Color = AnsColor
- Case 5 '1番目、3番目に値が入っている時
- Rng(2).Value = Rng(1).Value * Rng(3).Value
- Rng(2).Font.Color = AnsColor
- Case 6 '2番目、3番目に値が入っている時
- If Rng(3).Value = 0 Then
- Rng(1).Value = "0除算"
- Else
- Rng(1).Value = Rng(2).Value / Rng(3).Value
- End If
- Rng(1).Font.Color = AnsColor
- Case 7 '全ての3つのセルに値が入っている時
- sumRng.Interior.Color = RGB(255, 0, 0)
- End Select
- Application.EnableEvents = True
- End Sub
4-2.①変数宣言と変数値の設定
3~8行目は変数宣言、9~15行目はその変数への値の設定を行っています。3行目のRng配列は、ユーザーが入力する黄色セルのRangeオブジェクトです。今回3つのセルが対象ですので1~3の要素として宣言しています。
4行目のsumRng変数は、Rng配列の集合体のオブジェクトです。
5行目のRngNoは、3つの黄色セルの内「どのセルに値が入っているか」を計算するための変数です。32行目以降で使用します。
6行目のAnsColorは、「計算された値」を示すための文字色です。色はLong型で表わされますのでLong型で宣言しています。
7行目のInputColorは、AnsColorとは反対に「入力された値」を示すための文字色です。
8行目の i は、黄色セルの数を表すカウンター変数です。
9行目はAnsColor(計算された値)に色を設定しています。RGB(255, 0, 0)は「赤色」です。
10行目はInputColor(入力された値)に色を設定しています。RGB(0, 0, 0)は「黒色」です。
12~14行目は、Rng配列に黄色セルをオブジェクトとして代入します。
本当は定数としてセル位置を宣言出来れば良いのですが、オブジェクトは定数では宣言できません。他の方法として「"C3"」「"C5"」等のセル位置を示す文字列を定数(Const)として宣言する方法もありますが、今回は「セルへの値代入」や「セルの書式(文字色など)設定」をRangeオブジェクトに対して実行するコードが多かったため、配列変数として宣言をしています。
15行目はsumRng変数へ、12~14行目で設定した各黄色セルを「Unionメソッド」で集合体にした後代入します。
入力セルを集合体にすることで、全ての入力セルの書式をまとめて変更できると共に、18行目で使っているような「変更されたセルが対象セルか否か」を判断する時にもうまく利用できます。
4-3.②Changeされたのが対象セル(黄色セル)か否かの判定
18行目は、Worksheet_Changeイベントの引数である「Target(変更されたセル範囲)」と、15行目で代入された「入力セル(黄色セル)の集合体」をIntersectメソッドで比較しています。Intersectは「重なっているセル範囲」を返しますが、もし重なっていない時は「Nothing」を返します。ですので重なっているか否かを判断するには「Is Nothing」がTrueかFalseかで判別します。
ですので18行目は「重なっていない時はプロシージャを抜ける(Exit Sub)」という意味になります。
4-4.③黄色セルへの入力値種類の判定
22~29行目はFor~Nextで3つの入力セルの値を確認しています。一見引数のTargetセルだけを調べれば良さそうですが、複数セルをまとめてChangeされたり、黄色セル以外のセルも含めた複数セルをChangeされたりすると、Targetを分解して1つ1つのセルを調べる必要が出てきます。ですので、18行目で「Changeされたセルの中に黄色セルが含まれている」ことが分かったら、3つの黄色セル全てを確かめることにしています。
(対象セルが多数になる場合には、Targetに含まれるセルの位置を調べた方が処理時間が短くなる場合があるかもしれません。)
プログラムの流れを考えるために、まずユーザーの操作と画面の遷移のあるべき姿を図4-2に整理しました。
例として「C3セルとC5セルにユーザーが値を入力し、C7セルに計算結果が表示されいている」状態を一番上に表しています。プログラム側から見て「各セルをどう操作すれば良いか」について見ていきます。
図4-2
ユーザーが続けて換算作業をする場合、まず入力値(ここではC5セル)を操作した時にどうなるかを考えます。
図4-2の①は入力値を上書きするパターンです。この場合C5セルが書き換わることでWorksheet_Changeイベントが発生し、C3とC5の値を使って計算しC7に代入します。
C7セルは、どうせ上書きされますので、事前に「C7セルをクリアしてもしなくてもOK」です。
②はC5セルの値を(DELキー等で)削除するパターンです。C5が削除されてWorksheet_Changeイベントが発生するのですが、入力値はC3だけ(C7は計算結果だから入力値ではない)になりますので、計算できません。ですので計算結果である「C7はクリア」する必要があります。
以上①②は「C5セルに対するユーザー操作」でしたが、「C3セルに対する場合」でも同様になります。
続いて計算結果セル(ここではC7セル)を操作した時にどうなるかを考えてみます。
③は、計算結果であるC7セルに対して、ユーザーが値を入力しようとするパターンです。しかしここで入力を許してしまったら、3つのセル全てが入力値になってしまい「電流x電圧=電力」という関係が崩れる可能性があります。
ですのでプログラム側としては「C7への入力を拒否してC7セルの値を守る」か「入力した値は無視し、C3とC5セルの値を使って再計算しC7セルに代入する」かの実行が必要です。
④は、計算結果であるC7セルの値を(DELキー等で)削除するパターンです。C7が削除さると、C3・C5の入力値だけが残りますので「入力値が揃った段階」と同等となりますので、計算をしその結果をC7セルに代入すれば良いことになります。
これをプログラム側の立場でまとめてみると、図4-3のようになります。
① | ② | ③ | ④ | |
---|---|---|---|---|
C3セル | 実値入力済 | 実値入力済 | 実値入力済 | 実値入力済 |
C5セル | 新入力値 | 空白セル | 実値入力済 | 実値入力済 |
C7セル | 計算結果を代入 | 値削除 | 入力拒否、又は計算結果を代入 | 計算結果を代入 |
まず考えなければいけないのは「計算の条件(≒タイミング)」です。「計算ボタン」をユーザーにクリックしてもらえない場合には重要です。
今回は「電流x電圧=電力」という3要素の式ですので、3つの内2つが決まらないと計算できません。また、残りの1つが空で無いと計算結果を書き込めませんので、条件としては「2つの入力値+結果欄が空」ということになります。
また③のC7セルの「入力拒否」ですが、方法としてはいくつか考えられます。
1つ目は「Excelの機能を利用して拒否(2個目の値を入力した時点で、残りの1つにはシート保護をかける等)」、2つ目は「記憶する(前回表示時の計算結果を保持しておく)」方法です。他にもあるかもしれません。
1つ目は少しプログラムが面倒そうです。2つ目はStatic変数を使えば簡単そうですが、計算結果を残したままファイルを保存されると、次回ファイルを開いた時には「記憶が消えている」ので、うまく行きません。
しかし上記の「計算の条件」を見ると、「2つの入力済の値が既にある」のですから、残りの「結果欄を空」にするためにC7セルをクリアすれば勝手に計算して値を入れてくれることに気が付きます。
同様に①④の場合も、C7をクリアするだけで計算してくれますし、②にあってはC7をクリアすることは必須です。
つまり、どういう場面であっても「計算結果のセルをクリア」すればスムーズに進むことになります。
ということで、23行目では「計算結果である文字色が赤(AnsColor)のセルはクリア(.Value="")」しています。
24行目は、黄色セルに入れられた値が数値か否か(IsNumeric関数)を調べ、数値でなかったら25行目でコメントを出し、27行目でマクロ終了(Exit Sub)します。
なお、26行目の「Application.EnableEvents = True」ですが、21行目で「Application.EnableEvents = False」を実行している状態のままマクロ終了してしまうと、その後イベントが発生しなくなってしまいます。(「EnableEvents」は、「ScreenUpdating」などと違いプロシージャを抜けてもTrueにならない)
ですのでマクロを終了する前に「Application.EnableEvents = True」を実行し、正常な状態に戻しています。
ついでに21行目の「Application.EnableEvents = False」について説明しておきます。
23行目や44・46行目、50行目、54・56行目では、セルに何かしらの値を書き込んでいます。書き込むと書き込んだ時点で新たに「Worksheet_Change」イベントが発生してしまいます。
たとえば23行目を実行して計算結果セルをクリアするとイベントが発生し、また23行目で計算結果セルをクリアしイベントが発生し、・・・と無限にイベントが発生します。このような異常を避けるために、黄色セルを処理中はイベントが発生しないように「Application.EnableEvents = False」を実行させています。
4-5.④どのセルに値が入力されているかの計算
32行目は、黄色セルの内、どのセルに値が入っているかを数値として表しています。今回黄色セルは3つありますので、何番目に値が入っているかを知り、値が入っていないセルに計算結果を入れる必要があります。方法としては色々あると思いますが、ここでは2進数を活用した方法で行っています。
まず、図4-4のように「各セルの状態を入れる配列」のようなものを考えます。入れる値としては「値有り=1、値無し=0」とします。
図4-4
この「配列のようなもの」は、1と0が並んでいる「2進数」として見る事ができますので、人間が扱い易いようにこの2進数を10進数にします。すると「3つのON-OFFの状態は、10進数の0~7で表される」ことになります。
32行目では「値がある(=1)、値が無い(=0)」になるように、まず「Rng(1) = ""」の数式で「値がある(=False)、値が無い(=True)」を計算し、次に「Not」で逆転し「値がある(=True)、値が無い(=False)」にします。
そのあと各桁をあらわす値(1桁目=20,2桁目=21、3桁目=22)を掛けます。なお、ExcelでのTrueは「-1」になりますので、
「1桁目= -1,2桁目= -2、3桁目= -4」とします。
「True × -1 = -1」というのがピンと来ない場合は、「CInt(Not (Rng(1) = "")) * -1」のように「CIntで Trueを-1、Falseを0 に変換」した後で「-1 × -1 = 1」と掛け算をするような式にすれば分かり易いと思います。
その値を変数RngNoに代入し、39行目以降で分岐させます。
4-6.⑤書式の初期化
35~36行目は、3つの入力・出力セル(セルの集合体であるsumRng)の書式を標準(黒文字+黄色背景)に戻しています。これは「今回の実行結果が正しく表示させる」ために、「1つ前の計算時に、39~61行目の処理で文字色と背景色を変更している可能性がある」ので「書式を初期化」しているものです。
このような初期化を行わず、「実行ごとに全ての対象セルの書式を設定する」という方法もあります。
多くの対象セルに対して何らかの実行が必要な場合には、「どうせセルに対して別々の処理をするのだから、ついでに書式も」という考え方はあると思います。
しかし今回は「処理するのは、計算結果を書き込む1セルのみ」ですので、「ユーザーが操作したセルをわざわざ別々に処理」する必要は無いと考え、「セルの処理をする前」に「全セルの書式の初期化」をしています。
なお、36行目の「RGB(255, 255, 0)」は黄色を指していますが、もちろん先頭で変数化してもOKです。
(今回変数化しなかったのは、この36行目の1箇所でしか使用しなかったからです。)
4-7.⑥入力値で計算を実行し、その計算結果を出力
39~61行目は、32行目での変数RngNo(どのセルに値が入っているかの値)の値に従って、各セルの処理をしている部分です。RngNoが取る値は「0~7」の全8種類ですので、Select Caseで分岐をさせています。分岐先で実行する内容を図4-5に整理しました。
RngNo値 | 各セルの値(有=〇) | 状態 | 実行内容 | ||
---|---|---|---|---|---|
C7 | C5 | C3 | |||
0 | 全てのセルが空白 | 何もしない | |||
1 | 〇 | 1セルしか値が無く計算不可 | 何もしない | ||
2 | 〇 | 1セルしか値が無く計算不可 | 何もしない | ||
3 | 〇 | 〇 | C3・C5の値で計算可能 | 計算し、C7セルに結果を書込み | |
4 | 〇 | 1セルしか値が無く計算不可 | 何もしない | ||
5 | 〇 | 〇 | C3・C7の値で計算可能 | 計算し、C5セルに結果を書込み | |
6 | 〇 | 〇 | C7・C5の値で計算可能 | 計算し、C3セルに結果を書込み | |
7 | 〇 | 〇 | 〇 | 全セルに値があり計算不可 | 異常を知らせる為、全セルを赤くする |
図4-5からも分かるように、「何もしない」のは RngNoが0・1・2・4 の時ですので、その分岐を40行目に設定しました。
なお「何もしない」のですから、この40行目は無くても問題ありません。
「RngNo=3」の時は43~48行目を実行します。「3」は「C3セル・C5セル」に値が入ってることを示していますが、計算式は「C5セル値 ÷ C3セル値」ですので、例えC3に値が入っていても「C3セル=0」では計算式の戻り値は「無限大(=エラー)」になります。
ですので43行目でC3セルの値を調べ、ゼロの場合にはC7セルに「0除算」とコメントを出力しています。(44行目)
ゼロで無い場合に限り46行目で計算を行い、その計算結果をC7セルに記入します。
48行目は、計算結果であることを表すために、C7セルの文字色を赤色(AnsColor)に設定します。
「RngNo=5」の時は50~51行目を実行します。「5」は「C3セル・C7セル」に値が入ってることを示し、計算式は「C3セル値 × C7セル値」ですので、「RngNo=3」の時のようなゼロ除算がありませんので、そのまま素直に掛け算の値をC5セルに記入します。(50行目)
51行目は48行目と同様に、計算結果であることを表すために、C5セルの文字色を赤色(AnsColor)に設定します。
「RngNo=6」の時は53~58行目を実行します。「6」は「C5セル・C7セル」に値が入ってることを示し、計算式は「C5セル値 ÷ C7セル値」ですので「RngNo=3」の時と同様に、C7セルがゼロの時は「0除算」のコメントを出力します。
C7=0以外の時には割り算を実行しC3セルに記入します。(56行目)
58行目は、48・51行目と同様に、計算結果であることを表すために、C3セルの文字色を赤色(AnsColor)に設定します。
最後の「RngNo=7」は、3つ全てのセルに入力されたことを示し、関係式が成立していない可能性があるのはもちろん、計算結果を記入するセルもありません。ですので、60行目で3つ全てのセル(sumRng)の背景色を赤色(RGB(255, 0, 0))にしています。
なお「RngNo=7」を発生させる方法には、下記の様にいくつかあります。
1つ目は、C3セルに値を入れ、C3セル右下の「フィルハンドル」をマウスで下方向にドラッグする。
2つ目は、違う場所でC3・C5・C7の値を作り、1つのセル範囲としてコピーし、黄色セル上でペーストする。
3つ目は、計算結果の文字列の色を、手動で赤以外の色にする。
他にも方法はあると思いますが、その対策としてはワークシート関数の所でも紹介したシート保護が有効と思います。ただし3つ目の「手動で文字色を変える」は残念ながら防げません。
かと言って、入力のインターフェースをフォームを使ったダイアログにしてしまうと、ワークシートの手軽さが失われてしまう気がします。
取扱説明書で注意する等の、運用でカバーするのが良いかと思います。
62行目の「Application.EnableEvents = True」は、21行目で「Application.EnableEvents = False」と設定したイベント中止を解除し、マクロ終了と共に正常な状態に戻すものです。
5.最後に
今回は「電流x電圧=電力」という簡単な式でしたが、複雑な式の場合になるとワークシート関数方式では数式を作るのが、かなり大変かもしれません。その点マクロ方式は対応範囲が広く一般的だと思います。しかし、マクロ付きのExcelファイルはセキュリティ的に難しい場合、ワークシート関数方式の考え方も役に立つのでは、と思っています。
なお、マクロ方式でのセルの表示桁数は指定していませんので、計算結果が極めて大きかったり小さかったりすると指数で表示されます。たぶん使われる業種により桁数の範囲があると思いますので、「V → kV」にするとかの工夫をして下さい。
両方向から換算可能なシート(it-040.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始) 解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。 |