[教えて!VBA] 第8回 変更前のセルの値を保持/利用するにはどうすればいいの??

概要

この記事について

かんたんな概要と結論

VBAマクロで、特定の操作で処理を実行する仕組みをイベントと呼びます。
イベントを利用し、セル入力時に処理を実行させて、変更前の値を取り扱うことができます。

こんにちは、dedeです。

この記事では、
VBAマクロに関する質問のうち、
皆が疑問に思っているトピックについて解説いたします。

今回は、 変更前のセルの値を記録・保持して利用したり、変更値をチェックして入力前の値に戻したりする方法
を解説いたします。

※この記事は、Office VBAマクロのうち
Excel VBAマクロに関するトピックです。

レベル:中級者向け

やりたいこと

シートのセルにデータを入力すると、
通常は変更前の古いデータは消去されます(「元に戻す」ボタンで戻すことはできますが)。

入力したタイミングに応じて、変更前の値をシートのどこかに移したい場合は、
VBAマクロの出番です。

変更前の値をシートのどこかに移す

このとき、
イベント と呼ばれるVBAマクロの仕組みを利用することになります。

また、変更したデータを入力した時に、
適切ではない値だった場合に自動でチェックして前の値に戻したいときもあるでしょう。

入力値の自動チェック

そのような各パターンについて見ていきます。

パターン① 変更前の値を他のセルに移したい

ABOUT

入力するたびに、
前の値をシートのどこかのセルに記録していくようなマクロを作成したいという希望があるとしましょう。

「入力するたび」というタイミングの検知のために、
イベントという仕組みを利用します。

イベントとは?

ブックやシートに対して、
ある特定の操作が行われた際に自動でVBAマクロが発火(処理の開始)されるような仕組みです。

例としては次のようなものがあります

対象 イベント名 内容
ブック
(WorkBook)
Open ファイルを起動して
ブックが開かれたときに実行
BeforeClose 閉じるボタンなどでブックが閉じられる時に実行
シート
(Worksheet)
SelectionChange 現在のセルから別のセルを選択した時に実行
Change セルの内容を変更した時に実行

コード

イベントの実装は、
イベントプロシージャというSubプロシージャの記入により行います。

イベントプロシージャは、
イベントを設定したいシートのコードウインドウ上に記入します。

マクロ記述シートの指定

シートのコードウインドウ

変更前の値を他のセルに移すマクロのコードは下記です。

 1Private Sub Worksheet_Change(ByVal Target As Range)
 2    Dim OldValue As Variant
 3    Dim currentCellAddress
 4    
 5    '//ポイント① 変更前の値を調べたいセル以外では処理を行わないようにする
 6    If Target.Address(False, False) <> "B3" Then Exit Sub
 7    
 8    '//ポイント いったんイベントを無効化しイベントの発火を停止する
 9    Application.EnableEvents = False
10    
11    '//カーソル位置を退避させる
12    currentCellAddress = Selection.Address
13    
14    '//ポイント③ いったんセルの値をもとに戻し、古い値を変数に退避させてから再度変更後の値に戻す
15    Application.Undo
16    OldValue = Target.Value
17    Application.Undo
18    
19    '//変更前の値を別のセルに移す
20    Me.Range("D3").Value = OldValue
21    
22    '//カーソル位置を元に戻す
23    Me.Range(currentCellAddress).Select
24    
25    '//イベントを再度有効にする
26    Application.EnableEvents = True
27    
28End Sub
29

ポイントは三点あり、
①変更前の値を調べたいセル以外では処理を行わないようにするため、Target(変更したセルのRangeを参照する引数)のアドレスについて絞り込みを行います。
上では、B3セル以外では処理を行わないようにします。

②処理の最中に他のイベントが発火しないように、EnableEventsを変更しておきます。

③いったんセルの値をもとに戻し、古い値を変数に退避させてから再度変更後の値に戻します。

デモ

変更前に東京都が入っています。

セル入力前

新しいデータを入力後、別のセルに東京都の文字が移されます。

セル入力後

パターン② 変更前の値を他のセルに移し、削除した値を別のセルに移したい

ABOUT

変更ではなく、
削除した場合(空欄として入力した場合)には別のセルに値を移すという仕様にしたいとしましょう。

その場合、いくらか条件分岐が増え、
また、削除したことを示すフラグを変数に持たせる必要が発生します。

コード

 1Private Sub Worksheet_Change(ByVal Target As Range)
 2    Dim inputValue As Variant
 3    Dim OldValues(0 To 1) As Variant
 4    Dim currentCellAddress
 5    
 6    '//変更前の値を調べたいセル以外では処理を行わないようにする
 7    If Target.Address(False, False) <> "B3" Then Exit Sub
 8    
 9    '//いったんイベントを無効化しイベントの発火を停止する
10    Application.EnableEvents = False
11    
12    '//カーソル位置を退避させる
13    currentCellAddress = Selection.Address
14    
15    '//いったんセルの値をもとに戻し、古い値を変数に退避させてから再度変更後の値に戻す
16    inputValue = Target.Value                    '//入力した値を変数に退避
17    Application.Undo
18    '//ポイント① 入力した値が空欄であるかどうかで削除処理か変更処理かを分ける
19    If inputValue = "" Then
20        OldValues(0) = "delete"
21    Else
22        OldValues(0) = "change"
23    End If
24    OldValues(1) = Target.Value
25    Application.Undo
26    
27    '//変更前の値を別のセルに移す
28    '//ポイント② 削除処理であればF3セルに、変更処理であればD3セルに移す
29    If OldValues(0) = "delete" Then
30        Me.Range("F3").Value = OldValues(1)
31    Else
32        If OldValues(1) <> "" Then Me.Range("D3").Value = OldValues(1)
33        
34    End If
35    
36    '//カーソル位置を元に戻す
37    Me.Range(currentCellAddress).Select
38    
39    '//イベントを再度有効にする
40    Application.EnableEvents = True
41    
42End Sub

ポイントは、
①入力した値が空欄であるかどうかで、
削除であるか変更であるか判別できるようなフラグを持っておきます。

②削除処理、変更処理ごとに反映するセルを分けています。

デモ

県名を変更すると、
「前回の入力値」欄に変更前の値が入ります。

セル入力前 セル入力後

また、データ削除すると、
「削除された値」欄に削除前のデータが入ります。
前回の入力値欄のデータには変更がありません。

セルの値の削除後

パターン③ 変更時に値をチェックし、不正な値であれば入力前に戻したい

ABOUT

イベントの仕組みを使って、
入力した際に即座に入力値のチェックをして不正な値を弾きたい時の実装を記します。

コード

 1
 2Private Const HOKURIKU As String = "富山;石川;福井;"
 3
 4Private Sub Worksheet_Change(ByVal Target As Range)
 5    Dim OldValue As Variant
 6    Dim currentCellAddress
 7    Dim HOKURIKUs() As String
 8    Dim oneHokuriku As Variant
 9    Dim isHokuriku As Boolean
10    
11    '//変更前の値を調べたいセル以外では処理を行わないようにする
12    If Target.Address(False, False) <> "B3" Then Exit Sub
13    
14    '//いったんイベントを無効化しイベントの発火を停止する
15    Application.EnableEvents = False
16    
17    '//カーソル位置を退避させる
18    currentCellAddress = Selection.Address
19    
20    '//北陸県を配列として取得する
21    HOKURIKUs = Split(HOKURIKU, ";")
22    isHokuriku = False
23    
24    '//ポイント① 入力値が北陸県のいずれかに該当すれば戻さない
25    '//      該当しなければ変更前に戻すようにする
26    For Each oneHokuriku In HOKURIKUs
27        If Target.Value = oneHokuriku Then
28            isHokuriku = True
29            Exit For
30        End If
31    Next oneHokuriku
32    If Not isHokuriku Then Application.Undo
33    
34    '//カーソル位置を元に戻す
35    Me.Range(currentCellAddress).Select
36    
37    '//イベントを再度有効にする
38    Application.EnableEvents = True
39    
40End Sub

ポイントとしては、
入力値チェックの機構を導入しています。

入力値が北陸県(富山;石川;福井)のいずれかに該当すればそのまま入力を続行しますが、
もし北陸県以外の文字列が入力されれば、変更前に戻します。

デモ

入力したときに北陸県以外の文字であれば、
空欄に戻されます。

セル入力前 東京都と入力中 入力後空欄に戻る

北陸県のどれかであれば、
チェックが通り、通常通り入力が完了できます。

北陸県ならば変更可能

パターン④ 変更した値を複数保持し、リストとして他のセルに出力したい

ABOUT

変更前の値として複数持てるようにしたいという希望があるとします。

次のように、一つのセルに改行で分けられたリストの形で
最大10個の値を格納していきます。

リストとして格納

コード

 1Private Sub Worksheet_Change(ByVal Target As Range)
 2    Dim OldValue As Variant
 3    Dim currentCellAddress
 4    Dim lists() As String
 5    Dim firstCount As Long
 6    Dim valueFromList As Variant
 7    Dim i As Long
 8    
 9    '//変更前の値を調べたいセル以外では処理を行わないようにする
10    If Target.Address(False, False) <> "B3" Then Exit Sub
11    
12    '//いったんイベントを無効化しイベントの発火を停止する
13    Application.EnableEvents = False
14    
15    '//カーソル位置を退避させる
16    currentCellAddress = Selection.Address
17    
18    '//いったんセルの値をもとに戻し、古い値を変数に退避させてから再度変更後の値に戻す
19    Application.Undo
20    OldValue = Target.Value
21    Application.Undo
22    
23    '//変更前の値を別のセルに移す
24    '////配列の形で現在のリストを取得する
25    lists = Split(Me.Range("D3").Value, vbLf)
26    firstCount = LBound(lists)
27    
28    '////リスト項目が10以上であれば、最初の値を消すようにする
29    If (UBound(lists) - LBound(lists) + 1) = 10 Then firstCount = firstCount + 1
30    
31    '////新しいリストを作成する
32    For i = firstCount To UBound(lists)
33        valueFromList = valueFromList & lists(i) & vbLf
34    Next i
35    valueFromList = valueFromList & OldValue
36    
37    Me.Range("D3").Value = valueFromList
38    
39    '//カーソル位置を元に戻す
40    Me.Range(currentCellAddress).Select
41    
42    '//イベントを再度有効にする
43    Application.EnableEvents = True
44    
45End Sub
46
47

改行コードでリストを分割して配列として取り扱い、
再度格納する際には分割したそれぞれを改行コードで結合します。

デモ

これまでにデータ1~10を入力済みで、
現在の値がデータ11である状態です。

データ12の入力前

あたらしく「データ12」を入力すると、
最も古い値が消去され、
データ2~データ11がリストに格納されています。

データ12の入力後

終わりに

イベントを使いこなすことで、
Excel単独でも高度なアプリケーションのように
さまざまな機能をもたせることができます。

イベントの中でも、
Changeイベントはよく使われるので、
これを理解するとマクロが上達するでしょう。

関連記事

comments powered by Disqus