2022/02/07

〇月〇日と入力した日付の年を指定




1.背景

ワークシートのセルに日付を入力する際、12月25日であれば「12/25」のように「〇月〇日」という形式で入れる場合があると思います。その際には図1-1のように、Excel側が「ユーザーは日付を入力した。しかも今年の日付」と解釈して日付の書式にしてくれるので大助かりです。
セルへの日付入力
図1-1

しかし入力したかったのが「今年」の12月25日であればOKなのですが、「昨年」の12月25日にしたかった場合には大迷惑です。再びセルに入り直し、年を変更する編集作業が発生してしまいます。Excelは「今年の日付を入れている」のを前提に日付型に変更しているのです。

ではどうするかと言うと、「2021/12/25」や「21/12/25」と西暦年や西暦年の後ろ2桁を添付して入力するのが正式な入力の仕方のようです。
しかし、何とかならないか と思う人はいるようで、「教えて!goo」のサイトにも質問「エクセルでの日付」が寄せられています。
そこでの解決策は、オートコレクトで年の代わりの文字列を使ったり、数式を使って結合したりです。少し乱暴なのはパソコンの日付(年)を変えてから入力する というのもありますが、これはお勧めできません。

そこで今回はマクロを使い、Excelに入力するのは「〇月〇日」なのに「ユーザーが指定した年に変換」するものを紹介します。

2.システム概要

今回システムはExcelアドインに登録して使用することを想定しています。アドイン登録方法は「アドインとしてExcelにマクロを登録」を参照下さい。

登録したボタン(図2-1の①)をクリックすると、年を切り替える操作ダイアログが表示(図2-1の②)されます。
なお「サンプルファイル」では、シート上に「起動ボタン」を設けましたので、それをクリックすることで操作ダイアログが表示されます。
システム起動
図2-1

操作ダイアログの年表示部の横にある上下スピンボタン(図2-2の③)をクリックすることにより、年を切り替えることが出来ます。スピンボタンの上をクリックすると年が増え、下をクリックすると減ります。
入力したい日付の年に合わせた後、セルに「〇月〇日」の形で日付を入力(図2-2の④)します。
入力を確定する(リターンキー等を押す)と、通常は「入力日の年」の日付になりますが、本システムを使用すると「操作ダイアログで設定した年」の日付(図2-2の⑤)になります。
年の設定と日付入力
図2-2

システムを終了するには「閉じる」ボタン(図2-2の⑥)をクリックするか、ダイアログ右上の×印をクリックします。システム終了すると、通常のExcel状態(年の既定は、入力日の年)に戻ります。

本システムの特徴を列記します。
 ・「〇月〇日」という入力でも、設定した年の日付となる。
 ・時刻を含んだ日時データも、年だけを設定した年に変換する。
 ・全角入力でも日付型であれば、Excelは日付と判断するため、設定年に変換する。
 ・数式(例:=TODAY() など)による日付入力はそのまま数式を残す。
 ・先頭に「'(アポストロフィ)」を入れたものは、文字列入力と判断しそのままとする。

一方、注意する点は、以下の通りです。
 ・年を指定して「〇〇年〇月〇日」と入力しても、設定年の日付に変換する。
 ・オートフィルで連続した日付を入れた時、年を跨いでも「同じ設定年」となるため非連続の日付になる。
 ・日付型書式のセルに対し数値(ゼロを含め)を入れると、日付と判断されてしまうため、設定年の日付に変換される。
 ・うるう年以外の年に「2/29」と入力してもExcelは日付型と認識しないため、指定年がうるう年でもそのままとなる。
 ・うるう年に「2/29」と入力し、その設定年がうるう年で無い時は、「2/28」付けの日付となる。

3.プログラムの流れ

起動した操作ダイアログ上のスピンボタンを操作することで、年を表すラベルの数値を切り替えますが、これは単にスピンボタンのValue値をラベルの数値に加減算しているだけです。

ワークシートにはSheetChangeイベントを仕掛けておき、セルに入力した値の種類を調べます。入力値が日付だった場合には「操作ダイアログ上の年 と 現在の年 の差」を求め、DateAdd関数を使用して「入力した日付」の年を移動させ、値を置き換えます。
プログラムの流れ
図3-1

なお今回システムはExcelアドインとしていますので、入力するワークシートそのものにSheetChangeイベントを仕掛ける事が出来ません。そこでクラスにWithEventsを設定し、Excel内の全てのSheetChangeイベントを取得しています。

4.クラスモジュール(Class1)

VBAコードは、クラスモジュール、標準モジュール、フォームモジュールの3ヶ所に記述しています。通常、ブック(システム)を開いた時にイベント発生(Workbook_Open)するブックモジュールに、クラスを起動させるためのコードを記述するのですが、今回は「フォーム(操作ダイアログ)を起動した時に、クラスを起動させる」ようにしています。

  1. '========== ⇩(1) 変数宣言 ============
  2. Public WithEvents App As Application
図4-1

クラスモジュールでは宣言部(図4-1)の2行目「Public WithEvents App As Application」で、WithEventsでイベントに対応するためのオブジェクト変数「App」の定義をしています。「As Application」は、Excelアプリケーションを意味します。

  1. '========== ⇩(2) イベントプロシージャ ============
  2. Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
  3.  Call Sh_Cng(Target)
  4. End Sub
図4-2

そして図4-2の5行目「Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)」で、Excel全体のSheetChangeイベントを取得するイベントプロシージャを設定しています。引数として、変更されたシート(Sh)、変更されたセル範囲(Target)を受け取ります。なお、引数Targetの親(Parentプロパティ)がShとなります。

どこかのセル範囲が変更された時にApp_SheetChangeイベントが発生し、6行目「Call Sh_Cng(Target)」で、図5-4の Sh_Cngプロシージャを呼び出します。Sh_Cngに渡す引数Targetは、受け取った引数Targetそのままです。

5.標準モジュール(Module1)

5-1.変数の宣言、及びAPI参照宣言

標準モジュールの宣言部では、変数の宣言およびDeclare文を使ったWindowsAPIの参照宣言を行っています。
  1. '========== ⇩(3) 変数宣言、及びAPI参照宣言 ============
  2. Public Declare Function SetFocus Lib "user32" (ByVal hwnd As Long) As Long
  3. Private X As New Class1
図5-1

10行目「Public Declare Function SetFocus Lib "user32" (ByVal hwnd As Long) As Long」は、WindowsAPI「SetFocus関数」の参照を宣言しています。このSetFocusは、ダイアログを操作後すぐにワークシート上で作業が出来るようにするために使用します。
なお、10行目の表記の仕方は32ビット版のExcel用です。64ビット版を使っている場合は「Public Declare PtrSafe Function SetFocus Lib "user32" (ByVal hwnd As LongPtr) As LongPtr」と書き換えて下さい。

11行目「Private X As New Class1」は、Class1クラスのモジュールレベル変数Xの宣言と併せて、インスタンス生成(New句)を行っています。

5-2.システムの起動

システムを起動するのが、図5-2のYearSpecifyプロシージャです。Excelアドインした際は、このプロシージャをボタンに登録して下さい。
  1. '========== ⇩(4) システム起動 ============
  2. Public Sub YearSpecify()
  3.  Call AppOn(True)
  4.  UserForm1.Show 0
  5. End Sub
図5-2

16行目「Call AppOn(True)」では、図5-3を呼出し、図4-1の2行目で宣言したAppオブジェクトをExce(Application)に設定・解除します。引数にTrueを設定することで「設定」を行っています。設定することで、図4-2のApp_SheetChangeイベントが働くようになります。
17行目「UserForm1.Show 0」では、ユーザーフォーム(操作ダイアログ)をモードレスで起動します。

5-3.Application(App)オブジェクトの設定・解除

図5-2の16行目、図5-4の41行目、および43行目、図6-9の100行目から呼び出されるのが、図5-3です。
引数としてTrueを受取ると、図4-1の2行目で宣言したAppオブジェクトをExceに設定し、Falseを受け取ると解除するプロシージャです。
  1. '========== ⇩(5) Application(App)オブジェクトの設定・解除 ============
  2. Public Sub AppOn(OnOff As Boolean)
  3.  If OnOff = True Then
  4.   Set X.App = Application
  5.  Else
  6.   Set X.App = Nothing
  7.  End If
  8. End Sub
図5-3

24行目「If OnOff = True Then」で、引数OnOffの種類を調べています。Trueの場合は25行目を、Falseの場合は27行目を実行します。
25行目「Set X.App = Application」では、AppオブジェクトをApplication(Exce)に設定することで、図4-2のApp_SheetChangeイベントが働き、Excel上のセル範囲の変更に反応出来るようになります。
一方Falseの場合の27行目「Set X.App = Nothing」では、Appオブジェクトを解除することで、App_SheetChangeイベントを無効にします。

5-4.日付を設定年に変更

図4-2の6行目(クラス内のApp_SheetChangeイベントプロシージャ)から呼び出されるのが、図5-4です。
Excel上のどこかのセル範囲が変更された際に実行されます。
  1. '========== ⇩(6) 日付を設定年に変更 ============
  2. Public Sub Sh_Cng(Target As Range)
  3.  Dim TG As Range      '←変更された単一セル
  4.  Dim YearDiff As Integer   '←現在年と設定年の差
  5.  For Each TG In Target
  6.   If TypeName(TG.Value) = TypeName(Date) And Not TG.HasFormula Then
  7.    YearDiff = CInt(UserForm1.Label1.Caption) - Year(TG.Value)
  8.    Call AppOn(False)
  9.     TG.Value = DateAdd("yyyy", YearDiff, TG.Value)
  10.    Call AppOn(True)
  11.   End If
  12.  Next TG
  13. End Sub
図5-4

37行目「For Each TG In Target」では、引数として受け取った「変更されたセル範囲」を単一セル範囲の単位ごとに処理をしていきます。
このプロシージャの発生元の「App_SheetChangeイベント」は、単一セルが変更された場合にはもちろん発生しますが、複数のセル範囲がまとめて変更された場合も「1回だけ」発生します。「複数のセル範囲がまとめて変更」とは、どこか別の複数セル範囲をコピペした時や、セル右下をマウスで引っ張った時のオートフィルなどを指します。

単一セルでも複数セル範囲でも「Range型」ですので、同じ引数Targetとしてイベントプロシージャが受け取り、このSh_Cngプロシージャに回ってきます。
単一セルの変更でしたら37行目で分割しないですぐに処理をすれば良いのですが、複数セル範囲が変更された場合には「セル毎に内容が異なる(日付だったり、日付でなかったり も含めて)」可能性がありますので、1つ1つのセル毎の判断が必要になるために「For Each」を使って分割処理をしています。

38行目「If TypeName(TG.Value) = TypeName(Date) And Not TG.HasFormula Then」では、単一セル(TG)の内容を調べています。調査するための関数やプロパティは色々ありますが、今回は図5-5のように3種類を考え、入力値によってどんな値が戻ってくるかを調べました。
No.セル入力値IsDateTypeNameHasFormula
112/25 (半角)True"Date"False
212/25 (全角)True"Date"False
3'12/25 (' アポストロフィ付)True"String"False
4=TODAY() (数式)True"Date"True
52021/12/25 (年付き)True"Date"False
61225 (数値)False"Double"False
7ABC (文字列)False"String"False
8_ (スペースのみ)False"String"False
9"" (長さ0文字列)False"String"False
図5-5

まず、No.4の数式で日付を表した場合は、「変化に対応させるために数式を用いているのだから、数式はそのまま」とする必要があるだろう と考え、38行目後半の「Not TG.HasFormula」をIf文に付けました。
あとはNo.3の「アポストロフィ付きの日付」をどうするかで、「IsDate」「TypeName」のどちらを使用するかを考えました。日付だけでなく数字も全てアポストロフィを付けて入力する人も存在しますが、一般的には「文字列として入力したい」と考えてアポストロフィを付けると思いますので、No.3を取り除ける「TypeName」を今回は使用することにしました。

なお「TypeName(TG.Value) = "date"」というように、全て小文字で書いてしまうと式が成立しなくなります。値が日付の時にTypeName関数から戻る値は「"Date"」と、先頭大文字です。しかし、そこまで気を回すのであれば「TypeName(Date)」を使用して「TypeName(TG.Value) = TypeName(Date)」という数式を用いた方が確かだと思います。

また、No.5のように「わざわざ年を含めて日付を入力」しても、その入力した年を無視してVBA側で設定した年の日付に変換してしまいます。気持ちとしては「わざわざ年を入力」した時には、そちらを優先にしたかったのですが、良い方法が思いきませんでした(普通の方法は思いついたが、中途半端な処理になるため、採用せず)。
寄り道
中途半端な方法とは、入力した日付の年が「現在の年とは異なる」場合に「ユーザーが年まで入力した」と判断する方法です。
しかし、操作ダイアログで「現在の年とは異なる年を設定(例:2020)」している時に、現在の年の日付にしたいと考えて「2022/2/13」と入力しても、その値は単に「2/13」と入力したのと見分けがつかないために「2020/2/13」と変換されてしまうことになります。
使えそうだけど使えないと判断しました。

また図5-5のNo.6で、入力値が「数値」の場合はIf文が成立しないため、今回の年変換の処理をしないのですが、注意しておくことがあります。
セルの書式が「日付型」の場合(原因:前に日付を入力した、又は書式設定で日付型にした等)、そのセルに数値を入力すると「Excelが日付と判断」してしまうため、TypeNameは "Date" になってしまいます。ですので年変換の処理を実行してしまいます。
寄り道
また「0(ゼロ)」を入力しても、書式が日付型であればセル表示値は「1900/1/0(シリアル値=ゼロ)」となります。その値をVBA側で加工するために受け取ると、ゼロはVBAの中では「1899年12月30日」となるために、例えば「2021年12月30日」のような日付に変換されます。
セル上で「1月0日(=12月31日)」だったのが、VBAを通すと「12月30日」と1日前にズレるのは面白い現象です。

39行目「YearDiff = CInt(UserForm1.Label1.Caption) - Year(TG.Value)」では、設定した年「CInt(UserForm1.Label1.Caption)」と、入力された日付の年「Year(TG.Value)」の差を計算しています。
そして42行目「TG.Value = DateAdd("yyyy", YearDiff, TG.Value)」で、DateAdd関数を使って年を変換し、入力セル値を書き換えています。DateAddの第一引数の「"yyyy"」は、「年数を移動」という指定です。

この42行目のコードは「セル値を書き換え」ますので、書き換えた瞬間に図4-2のApp_SheetChangeイベントが発生してしまいます。イベント発生後、このSh_Cngプロシージャが呼ばれますが、39行目の「年の差」はゼロとなりますので42行目では「同じ日付値で書き換える」ことになります。値は変わりませんが、書き換えると再びApp_SheetChangeイベントが発生・・・ということを繰り返すことになります。
永久ループになるかと思ったのですが、試したところ数十回繰り返したところでループを抜けました。どうして抜けるのかまでは分かりませんでしたが、再イベントを発生させないために41行目「Call AppOn(False)」で図5-3をFalseで呼出して「App_SheetChangeイベントを無効」にした状態でセルへの書き換えを行っています。
書き換えが完了したら、43行目「Call AppOn(True)」で「App_SheetChangeイベントを有効」に戻しています。

なお、39行目の「年の差」計算で、ゼロ(=年を変更する必要なし)だった場合には、42行目のセルの書き換えをしない という処理方法でも良いかもしれません。

またDateAdd関数は、存在しない日付は戻しません。例えば操作ダイアログで「2021」年を設定し、「2024/2/29(存在する)」を入力した場合、「2021/2/29」は存在しませんので、その前の日「2021/2/28」に年変換されることになりますので注意が必要です。

更に、あるセルに「12/25」と入力し、オートフィル機能を使って連続日としようとすると、年を跨いだ日付は連続しなくなってしまいます。つまり設定年が2020年とすると「2020/12/31」の次が「2020/1/1」と「年を揃える」ことになってしまいます。このような場合は、基準日付を入力して希望の年に変換後、今回システムをOFFにしてからオートフィル機能で連続日を作成する等の手間が必要です。

6.ユーザーフォーム(UserForm1)

6-1.コントロール類の配置

操作ダイアログ上の各コントロールの配置は図6-1のようにしました。
コントロール類の配置
図6-1

設定年を表示するラベルの隣に、その年を増減するスピンボタンを配置します。また、システムを終了させるためのコマンドボタンを右端に置きました。ボタン表面文字は、設定時に手書きで変更しています。

6-2.フォームモジュール

6-2-1.フォームの初期設定

フォームモジュールの宣言部(先頭)では、図6-2の51行目「Dim EventOff As Boolean」と、イベントの再帰呼び出しを回避するフラグ変数EventOffを宣言しています。このフラグ変数は、図6-5のスピンボタンのChangeイベントプロシージャ内で使用します。
  1. '========== ⇩(7) 変数の宣言 ============
  2. Dim EventOff As Boolean   '←再帰呼び出し回避用フラグ
図6-2

フォームが図5-2の17行目から呼び出され、最初に発生するイベントが図6-3のInitializeです。ここではフォームの静的な設定を行っています。
  1. '========== ⇩(8) フォーム起動時の設定 ============
  2. Private Sub UserForm_Initialize()
  3.  Me.Caption = "日付入力時 年固定"
  4.  Me.Label1.BorderStyle = fmBorderStyleSingle
  5.  Me.Label1.Font.Size = 16
  6.  Me.Label1.TextAlign = fmTextAlignCenter
  7.  Me.SpinButton1.Max = 1
  8.  Me.SpinButton1.Min = -1
  9.  Me.SpinButton1.Value = 0
  10. End Sub
図6-3

55行目「Me.Caption = "日付入力時 年固定"」では、フォームのタイトルを表示しています。

57~59行目は、設定年を表示するラベルの書式を設定します。
57行目「Me.Label1.BorderStyle = fmBorderStyleSingle」は、ラベルの外枠を表示しています。
58行目「Me.Label1.Font.Size = 16」は、ラベルの文字サイズを少し大きくし16ポイントに設定します。
59行目「Me.Label1.TextAlign = fmTextAlignCenter」は、ラベルの文字を中央揃えにしています。

61~63行目は、スピンボタンの設定です。
今回のスピンボタンでは、上下どちらかにクリックすると「1年移動」するようにしています。そしてその移動量はスピンボタンのValue値をそのまま使用するようにしています。ですので、61行目「Me.SpinButton1.Max = 1」で最大値を「+1」とし、62行目「Me.SpinButton1.Min = -1」で最小値を「-1」としています。
また最大値・最小値のどちらにも移動できるようにするため、63行目「Me.SpinButton1.Value = 0」で中立位置をゼロと置いています。

図6-3でフォームの形を整えた後、フォームが表示された直後に発生するのが、図6-4のActivateイベントです。ここでは、フォーム上の動的な設定を行います。
  1. '========== ⇩(9) フォーム表示時の設定 ============
  2. Private Sub UserForm_Activate()
  3.  Me.Label1.Caption = Year(Date)
  4. End Sub
図6-4

68行目「Me.Label1.Caption = Year(Date)」では、現在の年をラベルに表示しています。
今回システムでは、操作ダイアログを閉じる時に「Unload」を使用していますので、起動するたびに「現在の年」に再設定されることになります。元の設定年を残す仕様にするには、フォームを「Hide」で閉じれば良いのですが、単にUnload→Hide と書き換えただけだと「App_SheetChangeイベントが有効のまま」となってしまいますので、無効化の為のコード追加と、起動時の再有効化のコード追加が必要となります。

6-2-2.スピンボタンの操作

スピンボタンをクリックしてValue値が変化すると、図6-5のChangeイベントが発生します。
  1. '========== ⇩(10) スピンボタンの操作 ============
  2. Private Sub SpinButton1_Change()
  3.  If EventOff = True Then Exit Sub
  4.  Me.Label1.Caption = Me.Label1.Caption + Me.SpinButton1.Value
  5.  EventOff = True
  6.   Me.SpinButton1.Value = 0
  7.  EventOff = False
  8.  Me.Repaint
  9.  SetFocus Application.hwnd
  10. End Sub
図6-5

73行目「If EventOff = True Then Exit Sub」の意味は、77行目の所で説明します。
75行目「Me.Label1.Caption = Me.Label1.Caption + Me.SpinButton1.Value」は、現在のラベルの値(Me.Label1.Caption:設定年)とスピンボタンのクリックされた方向(上方向=+1、下方向=-1)を足して、新たにラベルの値にしています。
もちろん「Me.Label1.Caption」のデータ型はString型で、「Me.SpinButton1.Value」はLong型ですが、「数値」として足し算をしてくれます。気持ちの悪い方は、「CInt(Me.Label1.Caption)」や「CLng(Me.Label1.Caption)」と数値型にしてから計算をしてもOKです。

ラベルの設定年を書き換えましたので、次の操作に備えて「スピンボタンを中立の位置に戻す」のが、78行目「Me.SpinButton1.Value = 0」です。しかし、この時に「スピンボタンのValue値を変更(+1→0、または -1→0)」させましたので、再び「SpinButton1_Changeイベント」が発生し、図6-5を再帰呼び出しすることになります。
再帰呼び出しされても、75行目の「Me.SpinButton1.Value」はゼロですので、ラベルの設定年は同じ値で書き換えるだけですので、特に問題はありません。ただし、無駄な動作ですので「再帰呼び出しを回避」するために77行目「EventOff = True」でフラグ変数EventOffをONにしてからスピンボタンのValue値の変更(78行目)を行っています。
こうすることで再帰呼び出しされた先では、73行目「If EventOff = True Then Exit Sub」のIf文が成立し、すぐに再帰呼出しプロシージャを抜け出し(Exit Sub)てくれます。
なお、ユーザーがスピンボタンを操作した際は、フラグ変数EventOffは初期値のFalse、または79行目でFalseにした後ですので、73行目はスルーすることになります。

81行目「Me.Repaint」では、フォームを再描画します。
82行目「SetFocus Application.hwnd」では、図5-1で宣言したWindows APIのSetFocus関数を実行します。引数に与えている「Application.hwnd」は「Excel(ワークシート)側」という意味で、全体として「Excelワークシートをアクティブ」にする事になります。
つまり、フォーム上にあるスピンボタンをクリックするたびに、ワークシート側をアクティブにしているのです。これは、操作ダイアログ上で年を設定後、すぐにワークシートで作業できるようにするために設定しました。(この設定が無いと、スピンボタンを操作した後もフォーム側がアクティブになっていて、ワークシートで作業するためには一度ワークシート側をクリックしないといけなくなる。)

しかし、スピンボタンをクリックした直後に82行目でワークシート側をアクティブにしてしまうと、フォーム上のラベルの再表示が追いつかず、現象としては「スピンボタンをクリックしているのに、ラベルが切り替わらない」ことになります。
これを防ぐために、81行目「Me.Repaint」でフォームを再描画(=ラベルの年設定を新たな数字にする)した後、82行目でフォーカスを切り替えています。

6-2-3.フォーム移動後のフォーカス移動

操作ダイアログ(フォーム)のスピンボタンを操作した時には、図6-5の82行目でワークシート側がアクティブになり、すぐにセル入力が可能になります。
しかし、それ以外のフォーム上での操作では、フォームからフォーカスは移動しません。例えば「フォームの位置をユーザーの都合の良い場所に移動」した後も、セル入力するためにはワークシート側をマウスクリックする必要があります。
(フォームの「閉じる」ボタンをクリックした後は、フォームが消えますので自動的にワークシート側にフォーカスが移動します。)

そこで、フォームで作業(例:フォーム上部をマウスで掴んで移動)した後、フォーム上をマウスが通過(=移動)した時に反応するMouseMoveイベントが図6-6です。4つの引数を受取りますが、今回は全く使いませんので説明は省略します。
  1. '========== ⇩(11) フォーム移動後のフォーカス移動 ============
  2. Private Sub UserForm_MouseMove _
  3.     (ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  4.  SetFocus Application.hwnd
  5. End Sub
図6-6

まず、MouseMoveイベントが反応する範囲は、図6-7となります。フォーム上全てでは無く「タイトル部以外」「フォーム上に配置したコントロール以外」の場所になります。
MouseMoveが反応する範囲
図6-7

ですので、タイトル部からマウスを上部に移動したり、真横に移動してしまうと、MouseMoveイベントが発生しなくなってしまいますので、完璧にシート側をアクティブに出来る訳ではありません。
またフォーム上にコントロールを配置する際、コントロールを「タイトル部(上端)や、フォームの右端・左端・下端にくっつけて配置」してしまうと、「マウスの逃げ道」が出来てしまうため MouseMoveイベントの効果が薄れてしまいます。

89行目「SetFocus Application.hwnd」では、図6-5と同様に「Excelワークシートをアクティブ」にしています。

6-2-4.システム終了処置

操作ダイアログ上の「閉じる」ボタンをクリックした時に呼び出されるのが図6-8です。
  1. '========== ⇩(12) 閉じるボタン ============
  2. Private Sub CommandButton1_Click()
  3.  Unload Me
  4. End Sub
図6-8

95行目「Unload Me」で、操作ダイアログ(Me)を閉じます。メモリー上から削除(Unload)されますので、次回起動時はInitializeイベントからスタートします。

フォーム右上×印をクリックした時に呼び出されるのが図6-9のQueryCloseイベントです。また図6-8でUnloadした後も、このQueryCloseイベントを通過します。
  1. '========== ⇩(13) システム終了 ============
  2. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  3.  Call AppOn(False)
  4. End Sub
図6-9

100行目「Call AppOn(False)」で、図5-3の「Appオブジェクト設定・解除プロシージャ」を呼び出します。引数にFalseを設定しているので、App_SheetChangeイベントを無効にします。
ですのでシステムを終了(閉じるボタンをクリック、またはダイアログ右上×印をクリック)すると、Excel全体でのSheetChangeイベントが発生しないことになり、よって日付入力も元のオリジナル状態に戻ります。

7.アドインとしてExcelにマクロを登録

このマクロ付ファイル(サンプルファイル)をExcelのアドインに登録することで、今回の「日付入力時の年設定」を利用することが出来ます。アドイン方法については「年賀状リスト等の宛名検索と追記 アドイン登録」を参照下さい。
また、リボン上のボタンには、図5-2の「YearSpecify」プロシージャをマクロ登録して下さい。

8.最後に

今回システムでは、セルにユーザーが入力した後、Excel側で確定した値をChangeイベントで受け取って加工しています。ですので「年から入力しても無視」される形になってしまいます。
値入力後のセル移動方向を切り替える」でも使用したkeybd_eventなどを使用すれば「ユーザーが何を入力しているか」が把握できそうですが、たぶん実際にやり始めるとものすごく複雑になりそうな予感がします。
完璧を目指さずに入力軽減と考えれば、この程度でも使えるのかもしれません。


〇月〇日と入力した日付の年を指定(it-074.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。