2021/10/02

図形カレンダーをクリックし日付入力




1.背景

実務では日付を入力する場面は多いと思います。しかし日付を決める時には、曜日を考えたり他の予定との関係を考えたりする必要がありますし、日付の数字をキーボードから打ち込むよりは、カレンダーの日付をクリックする方が便利だと考え、以前このサイトで「セルへの日付入力をカレンダー日付クリックで選定する」を紹介しました。
表示されたカレンダー
図1-1(以前紹介したクリック式カレンダー)

この時は、フォーム上にコマンドボタンを6行×7列に並べ、ボタンの表面文字列を日付にしカレンダーの形を作りました。そしてWithEventsによりボタンクリックのイベントを共有し、クリックされたボタンの表面文字列(=日付)から日付を計算し出力する、というものでした。
この方法は色々なサイトでも紹介されており、完成するとカッコイイのですが、フォーム上にボタンを数十個揃えながら並べるのは結構根気がいります。

そこで今回は、「ワークシート上で永久カレンダーを作る」で紹介した「ワークシート上に作ったカレンダー」をフォーム上に画像表示し、そのカレンダーをクリックすることで日付を出力するものを紹介します。
また、フォーム上のLabelにカレンダーを表示する「ラベルカレンダーをクリックし日付入力」も参考にして下さい。

2.システム概要

今回システムはExcelにアドイン登録しておき、日付を入力するセルに対し「呼出しマクロ」を仕込むことによってカレンダーが呼び出されるものです。この仕込む「マクロ」の内容については、「作業用シート 」の項で説明します。

その「呼出しマクロ」が、図2-1のワークシートに仕込んであるとします。「C4セルをダブルクリック①」すると呼び出されたり、「ボタンをクリック②」すると呼び出されたりするものです。
例えばC4セルをダブルクリックすると、カレンダーが表示③されます。今日の日付は赤字で表示されてます。
カレンダー表示と年月切り替え
図2-1

カレンダーは、上部のスクロールバーの操作で表示年月を変更できます。スクロールバーの「スクロール矢印(図2-2参照)」部をクリックした際には1か月ずつ移動し、レール部をクリックすると1年移動します。
また「今月」というボタンをクリックすることで、どこが表示されていても今月にジャンプします。

スクロールバー各部の呼び名
図2-2

必要な年月まで移動させ、図2-3のように日付の部分をマウスクリック⑤すると、ダイアログ下部に「指定した日付」が表示されます。そしてダイアログ下部のOKボタンをクリック⑥すると、ダイアログが消えて「指定した日付がセルに出力」されます。
日付選択と決定
図2-3

なお図2-4のように「既に日付が入っている場合」は、起動時のカレンダーは「日付のある年月」を表示し、また下部にはその日付が指定日として表示されます。
既に日付が入っている場合のカレンダー表示
図2-4

「OKボタン」をクリックした時には、上述した通りダイアログ下部の「指定日」がセルに出力されることになります。ですので「どこの日も指定していない状態」でOKボタンをクリックすると、セルは空白セルとなります。
また「Cancelボタン」をクリックした時には、何も出力しないため、元のセル値のままとなります。

3.プログラムの流れ

今回プログラムでは、カレンダーの表示に関わる情報(基準日、指定日など)は全てワークシート上に保存し、その値を使った数式および条件付き書式でカレンダーを作成しています。ですので、日付入力セルから呼び出される「CalSelectDate」では、そのセルの情報をシートカレンダーに書き込み、出来上がったシート上のカレンダーを画像としてフォーム上で表示し起動します。

起動したフォーム上で、表示カレンダーの年月を変更した際も、変更年月をシートカレンダーに書き込み、変更されたカレンダーを画像としてフォーム上で表示します。
ユーザーがフォーム上のカレンダーの日付を指定した際にも、そのクリック位置からシートカレンダー上のセル位置を割り出し、シートカレンダー値を変更します。

ユーザーが指定した日付でOKであれば、シートカレンダー上の指定日を呼出し元の日付入力セルに戻します。
カレンダーの流れ
図3-1

4.作業用シートのマクロ(サンプルファイルではSheet1)

日付を入力する作業用シートを図4-1とします。
 ①N4セルをダブルクリックするとカレンダーを表示し、同じN4セルに指定した日付を入力
 ②ボタンをクリックするとカレンダーを表示し、P4セルに指定した日付を入力
するためのマクロを考えます。
作業用シート
図4-1

まず①のマクロは図4-2のように、ワークシートのBeforeDoubleClickイベントを使用します。
  1. '========== ⇩(1) ダブルクリックでカレンダーを表示 ============
  2. Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
  3.  If Target.Address = Range("N4").Address Then
  4.   Cancel = True
  5.   Call CalSelectDate(Target)
  6.  End If
  7. End Sub
図4-2

4行目「If Target.Address = Range("N4").Address Then」で、ダブルクリックしたセルのアドレスを調べ、「N4セル」の場合に5~6行目を実行します。
なお「If Target.Address = "N4" Then」とすると、N4セルで引っ掛かってくれません。せめて「If Target.Address = "$N$4" Then」だったら引っ掛かります(nが小文字なら、やはりNGです)が、このような部分に気を遣うのはもったいないので、4行目のように「Address値同士を比較」した方が確実です。

5行目「Cancel = True」ではCancelを有効にし、ダブルクリックによる「セルへの編集」を中止します。このコードが無くてもカレンダーは起動しますが、ダイアログを閉じた時に「セル編集モード」になってしまいます。

6行目「Call CalSelectDate(Target)」で、図4-4の「CalSelectDate」プロシージャを呼び出します。このプロシージャ名は、図6-2のプロシージャ名とワザと同じにしていますが、呼び出されるのは図4-4の方です。
引数には日付を入力するセルを指定します。今回は「ダブルクリックするセル = 日付を入力するセル」であるため、BeforeDoubleClickイベントの引数のTargetを使っていますが、例えば「N4セルをダブルクリックして、N5セルに日付を入れる」ようなことも可能です。
呼び出される「CalSelectDate」では、アドインされたファイル内の「CalSelectDate」プロシージャを呼出して、カレンダーを表示させます。

次に②のボタンによる操作では、図4-3のようにボタンのClickイベントを使用します。ボタンはActiveXコントロールで作った場合です。フォームコントロールでボタンを作る場合は、マクロ名を適当なものに変えて下さい。
内容的には単純に、14行目「Call CalSelectDate(Range("P4"))」で、図4-4の「CalSelectDate」プロシージャを呼出します。CalSelectDateへの引数としては、日付を入力する「P4セル」をRange型で指定します。
  1. '========== ⇩(2) ボタン操作でカレンダーを表示 ============
  2. Private Sub CommandButton1_Click()
  3.  Call CalSelectDate(Range("P4"))
  4. End Sub
図4-3

上記イベントから呼び出されるCalSelectDateプロシージャが図4-4です。引数として「単一セル範囲」を受取ります。
  1. '========== ⇩(3)  ============
  2. Private Sub CalSelectDate(D As Range)
  3.  Dim P As String     '←呼び出すファイルのフルパス
  4.  Dim F As String     '←呼び出すファイル名
  5.  P = Application.UserLibraryPath
  6.  F = "it-065.xlam"
  7. ' P = ThisWorkbook.Path & "¥"     '←サンプルファイル用
  8. ' F = ThisWorkbook.Name       '←サンプルファイル用
  9.  Application.Run "'" & P & F & "'!" & "CalSelectDate", D
  10. End Sub
図4-4

今回システムは、サンプルファイル「it-065.xlsm」をExcelにアドインし、そのアドインファイルのマクロを呼び出す方法としました。
アドインでは無く、どこかの場所に「it-065.xlsm」を置いておき、そのxlsmファイルのマクロを呼び出す方法も考えられますが、やってみると「マクロ呼出し時に、xlsmファイルのワークシートが一瞬表示」されるため、見栄えの点からアドイン方式としました。(Application.ScreenUpdating = False をうまく使えば、一瞬表示は避けられるかもしれません。)

アドインファイル保存先は各PCで異なりますが、そのPathを取得するのが23行目「P = Application.UserLibraryPath」です。
なおアドイン保存先は、通常「Environ("userprofile") & "¥AppData¥Roaming¥Microsoft¥AddIns¥"」の場所になります。
(「Environ("userprofile")」の部分で、ユーザーごとに異なる部分を環境変数userprofileで吸収しています。)

ファイル名は、サンプルファイル「it-065.xlsm」であれば「it-065.xlam」へと拡張子が変わります(.xlsm → .xlam)ので、24行目「F = "it-065.xlam"」でアドインファイル名を設定します。

なお「サンプルファイル」は、カレンダー作成シートと作業用シートを同じにし、そのまま試行が出来るようにしていますので、23~24行目の代わりに26~27行目を生かしています。
 26行目「P = ThisWorkbook.Path & "¥"」
 27行目「F = "it-065.xlsm"」
なお「ThisWorkbook.Path」で得られるPathには、「Application.UserLibraryPath」の時と異なり最後に「¥」が付いて来ません。ですので、ファイル名との結合のために「¥」を追加しています。

29行目「Application.Run "'" & P & F & "'!" & "CalSelectDate", D」では、Runメソッドを使い「アドイン保存先にあるアドインファイル」の中のマクロ「CalSelectDate」を呼び出しています。引数には、カンマの後ろに「D(引数として受け取ったセル範囲)」を指定します。
Runメソッドは、他のブックのプロシージャを呼び出すもので、呼び出すものがPrivateであっても呼び出せてしまいます。

「パス名+ファイル名」は、図4-5のように両端を「'(シングルクォーテーション)」で囲み、またマクロ名との間には「!(エクスクラメーション:感嘆符)」を入れます。アドインファイルを保存後、「アドイン無効」「アドイン有効」のどちらの設定でも「シングルクォーテーション無しでは実行されない」ようです。

マクロ名の指定方法
図4-5

またRunメソッドを使った場合に、マクロの引数に「Range型」を指定する例は、どのサイトにも載っていなかったため心配したのですが、実際にやってみるとちゃんとRange型として伝わっているようです。
なお、「Run」はApplicationのメソッドであるため、ここでは「Application.Run」と記していますが、Runがグローバルとして定義されているために単に「Run」だけでも動作します。

5.カレンダーシート(Sheet1)

カレンダー自体は、全てワークシート関数で作成しています。「ワークシート上で永久カレンダーを作る」も参考にして下さい。

5-1.カレンダー数式

まず、カレンダー作成の基準値について、図5-1の様に位置を決め、数式を記入します。
カレンダー作成のための基準値
図5-1

カレンダーは日曜始まりとしています。これは、WEEKDAY関数で得られる数値が日曜始まりになっているためです。月曜始まりも可能ですが、数式の見直しは必要になります。

「D1~J1セル」は、曜日を表す数値を手入力しています。
「B3セル」は、「カレンダー上のクリックした日付」をマクロ側から記入します。
「B4セル」は、今日の日付とするため「=TODAY()」と数式を入力します。
「B6セル」は、表示カレンダー年月の初日(〇月1日)です。マクロ側から記入します。
「B7セル」は、表示カレンダー年月の初日の曜日(数値)です。数式として「=WEEKDAY(B6)」を入力します。
「B8セル」は、表示カレンダー年月の最終日(=日数)です。「=DAY(DATE(YEAR(B6),MONTH(B6)+1,0))」を入力します。

カレンダー内部の数式は図5-2の様に入力します。なお、D2~J2セルの「日~土」の文字列は手入力です。
カレンダー内の数式
図5-2

カレンダーの数字の入る部分は、7列×6行で作成しています。これは「初日が土曜日で、日数が31日」の月であっても、6行目の2コマ目までしか使われないことから6行としています。

数式を入れるエリアは、図5-2の様に①~⑤のエリアに分けて考えます。
まず①のエリアは、初日が入るエリアです。初日の曜日は「B7セル」で分かっていますので、その値とカレンダー上部の「曜日を表す数値(D1~J1セル)」を比較し、合致したセルに数値「1」を入れます。
その後ろ(初日の曜日値より右側)は、1つ左のセルの値に+1をして連番を作ります。これを数式で表したのが「=IF($B$7>D$1,"",(IF($B$7=D$1,1,C3+1)))」(D3セルでの数式)となります。D3に数式を記入したら、同じエリア内の他のセルへは「フィルハンドルを持ってドラッグ」します。

次に②③のエリアです。カレンダーの枠が最も少なくて済むのは「初日が日曜日で、日数が28日」の場合で、ちょうど7列×4行に収まります。つまり、1行目を除く「2~4行目は必ず数値が入る」ことになりますので、②③のエリアは空白になることはありません。単純に「一つ前の数値に+1」をすれば良いすることになります。
数式としては、③の方は左隣セルに1を足した「=D4+1」、②の方は一つ上の行の最終日を参照して「=J3+1」とします。

最後に④⑤のエリアは、最終日が来る可能性があります(28日の月で、初日が日曜から始まる場合は④⑤エリアは全て空白)。最後の日が来たら、その後ろは全て空白となります。
ですので「1つ前のセル値が最終日だったら空白」「1つ前のセル値が最終日で無かったら+1」「1つ前のセル値が空白だったら空白」ということになります。これを数式にしたのが、⑤では「=IF(D7="","",IF(D7=$B$8,"",D7+1))」、④では「=IF(J6="","",IF(J6=$B$8,"",J6+1))」となります。
なお、④の先頭セル(D7セル)では、その1つ前のセル(J6セル)には「必ず数値が入っている」ので、数式前半の「=IF(J6="","", 」の部分は実行されることはありません。式としては「=IF(J6=$B$8,"",J6+1)」でもOKですが、今回はD8セルと数式の共通化を図るために、この数式としてあります。

5-2.カレンダーの条件付き書式

カレンダーへの彩色・文字色設定は、全て「カレンダー範囲への条件付き書式」で行っています。図5-3のように4種の設定をしています。
彩色等のための条件付き書式
図5-3

条件付き書式の適用先は「D2~J8」ですので、左上角であるD2セルに対する数式を図5-4のように組み立てます。
目的数式書式
ユーザー指定日=$B$6+D2-1=$B$3背景:緑色
今日の日付=$B$6+D2-1=$B$4赤字+太字
日曜日列=D$2="日"背景:薄オレンジ色
土曜日列=D$2="土"背景:薄水色
図5-4

例えばユーザー指定日の数式「=$B$6+D2-1=$B$3」は、「表示カレンダーの初日」+「セルの数値」-1 が「指定日($B$3)」になっている時に背景を緑色にしています。
また、日曜日列の「=D$2="日"」は、各セルの2行目の値が「日」であれば、背景色を薄オレンジ色にします。
なお書式は上にあるルールほど優先されますので、例えば「指定日が日曜日」だった場合は、上にある「ユーザー指定日の書式」が「日曜日の書式」よりも優先され、緑色の背景色セルとなります。

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

標準モジュールでは、システム全体で使用する変数の設定、及びカレンダーの基準値の設定をした後、フォーム(ユーザー操作用カレンダー)を起動します。またカレンダー終了時には作業用シートのセル値を書き換えます。

図6-1の宣言部では、システム全体で使用する変数の宣言をします。直値を使う方法もありますが、変数を使っていればもしカレンダー描画位置が変更になった時でも図6-2内の変数設定の修正で済みますし、またコードを短く出来たりするメリットもあります。
  1. '========== ⇩(4) 変数宣言 ============
  2. Public CalSh As Worksheet   '←カレンダーのシート
  3. Public CalRange As Range    '←カレンダー表示範囲
  4. Public CalstdD As Range    '←カレンダーの基準日セル
  5. Public DesigD As Range     '←指定日のセル(Designated Dateの略)
図6-1

作業用シートである図4-4の29行目から呼び出されるのが「CalSelectDate」プロシージャです。39行目でPrivate設定になっていますが、Application.Runメソッドを使って呼び出されているため、問題無く実行されます。
引数として「日付を入力するセル」且つ「既存の日付が入力されている可能性があるセル」のセル範囲を受取ります。
  1. '========== ⇩(5) 起動プロシージャ ============
  2. Private Sub CalSelectDate(D As Range)
  3.  Dim OkCancel As Boolean   '←ユーザーがクリックしたのが、OKボタンかCancelボタンか
  4.  Set CalSh = ThisWorkbook.Sheets(1)
  5.  Set CalRange = CalSh.Range("D2:J8")
  6.  Set CalstdD = CalSh.Range("B6")
  7.  Set DesigD = CalSh.Range("B3")
  8.  If IsDate(D.Value) = True Then
  9.   DesigD.Value = D.Value
  10.   CalstdD.Value = DateSerial(Year(D.Value), Month(D.Value), 1)
  11.  Else
  12.   DesigD.Value = ""
  13.   CalstdD.Value = DateSerial(Year(Date), Month(Date), 1)
  14.  End If
  15.  OkCancel = UserForm1.UFstart
  16.  If OkCancel = True Then
  17.   D.Value = DesigD.Value
  18.  End If
  19. End Sub
図6-2

42~45行目は、システム内で使用する変数への値代入です。
42行目「Set CalSh = ThisWorkbook.Sheets(1)」は、シートカレンダーを作成しているシートを変数CalShに代入します。
43行目「Set CalRange = CalSh.Range("D2:J8")」は、カレンダー範囲を変数CalRangeに代入します。
44行目「Set CalstdD = CalSh.Range("B6")」は、カレンダーの表示年月初日を変数CalstdDに代入します。
45行目「Set DesigD = CalSh.Range("B3")」は、ユーザーが指定した日付を変数DesigDに代入します。

47~53行目は、シートカレンダーを作成するための基準値をシートカレンダー脇に書き込んでいます。
47行目「If IsDate(D.Value) = True Then」では、日付入力セルの値が「日付」である場合に48~49行目を実行します。
48行目「DesigD.Value = D.Value」で、指定日付セルにその日付を書き込みます。
49行目「CalstdD.Value = DateSerial(Year(D.Value), Month(D.Value), 1)」では、その日付のある年月の初日を基準日セルに書き込みます。

日付入力セルの値が日付では無かった(空白セル、文字列など)場合は、51~52行目を実行します。
51行目「DesigD.Value = ""」では、指定日付セルを空白にしています。
52行目「CalstdD.Value = DateSerial(Year(Date), Month(Date), 1)」は、「本日の年月」の初日を基準日セルに書き込みます。

なお、呼び出す側で間違えて「CalSelectDate」の引数に「複数セル範囲」を渡してしまった場合は、「D.Value」はエラーになりますが、「IsDate(D.Value)」はFalseになりますので、「指定日無し」の状態となります。
ここまでの段階で、表示するためのカレンダーは完成しています。

55行目「OkCancel = UserForm1.UFstart」では、UserForm1内の「UFstart関数プロシージャ」を呼び出します。UFstartはフォームを起動し、ユーザーのダイアログ操作を受けて「OKボタンを押した→True」「Cancelボタンを押した→False」を戻して来ます。
そのTrue・False値を変数OkCancelで受け取ります。

57行目「If OkCancel = True Then」で、ユーザーが「OKボタンを押した」時だけ、58行目「D.Value = DesigD.Value」で「日付入力セルに、ユーザーが指定した日付を記入」します。
一方ユーザーが「Cancelボタンを押した」時には何もしないため、日付入力セルは元のままとなります。

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

7-1.レイアウト

カレンダーを操作するためのフォームは、図7-1のようなレイアウトにしました。
フォームのレイアウト
図7-1

フォームの中央に「カレンダーを表示するImageコントロール」を配置します。その上部には年月を移動するScrollBar1と今月に移動するためのCommandButton3、年月を表示するLabel1を配置します。
また下部には、ユーザーが選択した日付を表示するLabel2、選択した日付を出力するためのCommandButton1(OKボタン)、カレンダー操作をキャンセルするCommandButton2(Cancelボタン)を配置します。

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

7-2-1.起動時初期設定

フォーム内で共通して使用する変数を図7-2のように宣言部で宣言しています。
  1. '========== ⇩(6) 変数宣言 ============
  2. Dim IsMoveSB As Boolean   '←マクロ側からScrollBarを動かしているというフラグ
  3. Dim OkCancel As Boolean   '←ユーザーがOKをクリックしたかCancelをクリックしたか
  4. Dim ImgWidth As Single    '←Image1の幅
  5. Dim ImgHeight As Single    '←Image1の高さ
  6. Dim CalLeft As Double     '←シートカレンダーの横位置(ポイント)
  7. Dim CalTop As Double     '←シートカレンダーの縦位置(ポイント)
  8. Dim CalWidth As Double    '←シートカレンダーの幅(ポイント)
  9. Dim CalHeight As Double    '←シートカレンダーの高さ(ポイント)
図7-2

フォームモジュール内では、ユーザーがImage1上のどの位置をクリックしたか、その位置がシートカレンダー上のどこに相当するかを計算して、カレンダーの日付を割り出しています。その基準となるImage1のサイズ、およびシートカレンダーの位置・サイズは「フォームが起動している間は固定」です。67~72行目で宣言している変数は、その固定している値を入れるためのものです。

フォーム起動時に最初に呼び出されるInitializeイベントプロシージャが、図7-3です。
  1. '========== ⇩(7) フォーム初期設定 ============
  2. Private Sub UserForm_Initialize()
  3.  Me.Image1.PictureSizeMode = fmPictureSizeModeStretch
  4.  IsMoveSB = True
  5.   Me.ScrollBar1.SmallChange = 1
  6.   Me.ScrollBar1.LargeChange = 12
  7.   Me.ScrollBar1.Max = 12
  8.   Me.ScrollBar1.Min = -12
  9.   Me.ScrollBar1.Value = 0
  10.  IsMoveSB = False
  11.  Me.Label1.Font.Size = 14
  12.  Me.Label1.Width = 75
  13.  Me.CommandButton1.Caption = "OK"
  14.  Me.CommandButton2.Caption = "Cancel"
  15.  Me.CommandButton2.Cancel = True
  16.  Me.CommandButton2.SetFocus
  17.  Me.CommandButton3.Caption = "今月"
  18.  ImgWidth = Me.Image1.Width
  19.  ImgHeight = Me.Image1.Height
  20.  CalLeft = CalRange.Left
  21.  CalTop = CalRange.Top
  22.  CalWidth = CalRange.Width
  23.  CalHeight = CalRange.Height
  24. End Sub
図7-3

77行目「Me.Image1.PictureSizeMode = fmPictureSizeModeStretch」では、カレンダーを表示するImage1の表示モードを設定しています。PictureSizeModeプロパティに設定可能な値は図7-4です。
定数内容
fmPictureSizeModeClip0はみ出ている画像はトリミング(既定値)
fmPictureSizeModeStretch1コントロールのサイズに合わせて引き延ばし
fmPictureSizeModeZoom3縦横比を保って引き延ばし
図7-4

今回は、クリックした位置を正確に割り出すためにも、Imageコントロールには目一杯のサイズでカレンダーを表示する必要がありますので、「fmPictureSizeModeStretch」を使用します。なおシートカレンダーの縦横比とImageコントロールの縦横比が大きく異なると文字が変形してしまうため、ある程度は合わせておく必要があります。

80~84行目は、年月移動用のスクロールバーの設定をしています。しかしスクロールバーのValue値が変化してしまうと、図7-6のChangeイベントが発生し、シートカレンダーの基準日(CalstdDセルの値)がズレてしまう(=狙いの年月のカレンダーにならない)可能性があります。
ですのでスクロールバーの設定をしている間は、79行目「IsMoveSB = True」でフラグを立て、図7-6のChangeイベントをスルーさせるようにしています。代わりに「Application.EnableEvents = False」を思いつく方もいるかもしれませんが、フォームのイベントは停止できませんので使えません。

スクロールバーは、図2-2で説明したように「スクロール矢印」のクリックで「1か月移動」、「レール部」のクリックで「1年移動」させるようにしています。ですので、80行目「Me.ScrollBar1.SmallChange = 1」で、最小移動量を1に、81行目「Me.ScrollBar1.LargeChange = 12」で最大移動量を12に設定します。
また、操作後すぐに「スクロールボックス」は中央位置に戻るようにするため、82行目「Me.ScrollBar1.Max = 12」で最大値を12に、83行目「Me.ScrollBar1.Min = -12」で最小値を-12にし、84行目「Me.ScrollBar1.Value = 0」でスクロールボックス(Value値)をゼロにしています。
最大値・最小値をそれ以上の値に設定してもOKですが「レール部を連打」されると、想定外の動作をするような気がしますので、制限を掛ける意味で「最大値・最小値は1年」としています。(試したところ、3連打すれば正常に3年移動するようですので、気にしなくても良いのかもしれません。)

Label1は「表示しているカレンダーの年月」を表示するものです。少し目立つように、87行目「Me.Label1.Font.Size = 14」でフォントサイズを大きくします。また88行目「Me.Label1.Width = 75」で、「YYYY年MM月」の文字が入りきるサイズにLabelの幅を設定しています(設定幅の75は、トライ&エラーの結果です)。

89行目「Me.CommandButton1.Caption = "OK"」は、ユーザーが選択した日付をシートの日付入力セルに書き出すOKボタンの表面文字を設定しています。
90行目「Me.CommandButton2.Caption = "Cancel"」は、カレンダー操作をキャンセルするCancelボタンの表面文字を設定しています。また91行目「Me.CommandButton2.Cancel = True」では、ユーザーがESCキーを押した時に「カレンダーのフォームを閉じる」設定をしています。更に92行目「Me.CommandButton2.SetFocus」では、フォーム起動時にキャンセルボタン(CommandButton2)にフォーカスを当てることで、フォーム起動後すぐにEnterキーを押した時に「カレンダーのフォームを閉じる」設定をしています。
93行目「Me.CommandButton3.Caption = "今月"」では、「今月に移動するボタン」の表面文字を設定しています。

前述した通り、Image1のサイズ、及びシートカレンダーの位置・サイズは「フォームが起動している間は固定」しているため、処理時間短縮を目的としフォーム起動時に変数に値を代入します。
まずImage1のサイズを、95行目「ImgWidth = Me.Image1.Width」、96行目「ImgHeight = Me.Image1.Height」で変数に代入します。
またシートカレンダーの位置を、97行目「CalLeft = CalRange.Left」、98行目「CalTop = CalRange.Top」で取得。サイズを99行目「CalWidth = CalRange.Width」、100行目「CalHeight = CalRange.Height」で取得します。
この95~100行目で設定した変数の関係を図で表したのが、図7-19になります。

7-2-2.フォームの起動・戻し

図6-2の55行目から呼び出される「UFstart関数プロシージャ」が、図7-5です。「関数」にしている意味は、標準モジュール側へ「ユーザーの指示(OKボタンを押したか、Cancelボタンを押したか)」を戻すためです。もちろん標準モジュール側に渡す方法は色々ありますが、今回は関数方式を使ってみました。
  1. '========== ⇩(8) フォーム起動・戻り値設定 ============
  2. Public Function UFstart() As Boolean
  3.  Call TitleYYMM     '←表示カレンダーの年月表示
  4.  Call DesigYYMM     '←指定日(下段)の表示
  5.  Call makeCal      '←カレンダーの表示
  6.  Me.Show
  7.  UFstart = OkCancel    '←「OK・Cancel」のどちらが押されたか
  8. End Function
図7-5

107行目「Call TitleYYMM」で図7-23を呼び出し、表示するカレンダーの年月を表示します。
108行目「Call DesigYYMM」で図7-24を呼び出し、作業シート側の日付入力セルに既に入力済みの日付を、カレンダー下部の指定日に表示します。日付入力セルが空白の場合は指定日も空白です。
109行目「Call makeCal」で、カレンダー本体をフォーム上のImageコントロールに表示します。

111行目「Me.Show」でフォームを表示し、そのフォームに対してユーザーが操作することになります。そしてユーザーが「選択した日付で確定」するために「OKボタン」をクリックすれば、変数OkCancelにTrueが入ります。また、日付入力セル値は「元の値のままで良い」という意味で「Cancelボタン」をクリックした場合には、変数OkCancelにFalseが入った後でフォームが閉じるようにしています。
ですので113行目「UFstart = OkCancel」で、「ユーザーの指示」を呼出し側(図6-2の55行目)へ戻しています。

7-2-3.表示年月の移動

年月を移動するためのツールとして、今回システムでは「スクロールバーによる年月設定(図7-6)」および「今月へのジャンプ(図7-7)」の2種を考えました。
スクロールバーでは、1か月単位・1年単位での移動となります。なお、スクロールボックスを使えば「1~12か月での移動」も可能ですが、今回Scrollイベントを使っていないため、何か月移動しようとしているのかはユーザーには見えません。
  1. '========== ⇩(9) スクロールバーの移動 ============
  2. Private Sub ScrollBar1_Change()
  3.  If IsMoveSB = True Then Exit Sub
  4.  CalstdD.Value = DateAdd("m", Me.ScrollBar1.Value, CalstdD.Value)
  5.  Call TitleYYMM
  6.  Call makeCal
  7.  IsMoveSB = True
  8.   Me.ScrollBar1.Value = 0
  9.  IsMoveSB = False
  10.  Me.CommandButton2.SetFocus
  11. End Sub
図7-6

119行目「If IsMoveSB = True Then Exit Sub」は、変数IsMoveSBがTrueの時はスクロールバーのChangeイベントをスルーするという意味です。変数IsMoveSBをTrueにするのは、今回システムでは以下の2箇所です。
 ・図7-3の79行目で、フォームの初期設定としてスクロールバーの設定を行っている時
 ・図7-6の125行目で、ユーザーが動かしたスクロールボックスを元の中央位置に戻す時
2箇所とも「その時点での表示カレンダーの年月が動いてしまっては困る」状況ですので、変数IsMoveSBをTrueにしChangeイベントをスルーすることで「表示カレンダーの年月はそのまま」にしています。

121行目「CalstdD.Value = DateAdd("m", Me.ScrollBar1.Value, CalstdD.Value)」は、表示カレンダーの年月を「ユーザーが移動させたスクロールバーの値(Me.ScrollBar1.Value)」の月数だけ移動させています。移動には今回「DateAdd関数」を使用し、「月数」を動かすため第一引数には「"m"」と指定しています。
この121行目を実行すると、ワークシート上の数式により、新しい年月のカレンダーになります。

122行目「Call TitleYYMM」では、図7-23を呼出し、フォーム上のカレンダー年月の文字列を書き換えます。
123行目「Call makeCal」では、図7-8を呼び出し、シートカレンダーを画像コピーし、フォームのImageコントロールに貼り付けます。

この時点では、ユーザーがスクロールバーを動かしたままの状態(スクロールボックスは中央からズレた場所にある)です。このままだと、次にスクロールバーのレール部をクリックして「1年移動」しようとしても移動できなかったり、2年も移動してしまったりと、ユーザーの意図通りの動きをしてくれなくなります。
ですので、126行目の「Me.ScrollBar1.Value = 0」でスクロールボックスを中央位置に移動させ、次のユーザー操作に備えます。但しスクロールバーのValue値を変更すると、Changeイベント(自分自身を再帰呼び出し)が発生してしまいますので、125行目「IsMoveSB = True」でフラグを立ててChangeイベントをスルーするようにしています。
(なおフラグを立てなくても、カレンダーはユーザーの意思通りには動きます。但しフォームのImageコントロールへの貼付け動作が増えますので、処理時間は長くなります。)

この時点ではスクロールバーにフォーカスが当たっていますので、129行目「Me.CommandButton2.SetFocus」で、「Cancelボタン」にフォーカスを当て、ESCキーで終了できるようにします。

フォーム右上の「今月」ボタンをクリックした時に呼び出されるのが、図7-7です。内容としては図7-6と似通っています。
  1. '========== ⇩(10) 今月への移動 ============
  2. Private Sub CommandButton3_Click()
  3.  CalstdD.Value = DateSerial(Year(Date), Month(Date), 1)
  4.  Call TitleYYMM
  5.  Call makeCal
  6.  Me.CommandButton2.SetFocus
  7. End Sub
図7-7

135行目「CalstdD.Value = DateSerial(Year(Date), Month(Date), 1)」は、表示カレンダーの年月を「今月」にします。シートカレンダーの基準日は月の初日ですので、DateSerial関数の第三引数に「1」を指定することになります。
136行目「Call TitleYYMM」では、図7-23を呼出し、フォーム上のカレンダー年月の文字列を書き換えます。
137行目「Call makeCal」では、図7-8を呼び出し、シートカレンダーを画像コピーし、フォームのImageコントロールに貼り付けます。

この時点では今月ボタンにフォーカスが当たっていますので、139行目「Me.CommandButton2.SetFocus」で、「Cancelボタン」にフォーカスを当て、ESCキーで終了できるようにします。

7-2-4.カレンダーの表示

シートカレンダーを画像コピーすると、その画像はパソコン内のクリップボードに一時保存されます。今回システムでは、そのクリップボードの画像を取り出し「ファイル名」を付けて保存し、その保存先をImageコントロールに指定することでカレンダー画像をフォーム上に表示しています。
この内「クリップボードにカレンダー画像を送付」+「画像ファイルとして受け取り、フォーム上に表示」の部分が図7-8です。
  1. '========== ⇩(11) Imageコントロールへのカレンダー表示 ============
  2. Public Sub makeCal()
  3.  CalRange.CopyPicture Appearance:=xlScreen, Format:=xlBitmap
  4.  Me.Image1.Picture = LoadPicture(ImgSave(CalRange))
  5.  Me.Repaint
  6. End Sub
図7-8

145行目「CalRange.CopyPicture Appearance:=xlScreen, Format:=xlBitmap」では、シートカレンダーのセル範囲である「CalRange」に対して、画面コピーをしています。Appearanceパラメータには図7-9の「画像のコピー形式」を、Formatパラメータには図7-10の「画像形式」を指定します。
Appearanceパラメータ
定数内容
xlScreen1画面表示に出来る限り近い形でコピー(既定値)
xlPrinter2印刷時と同じ形でコピー
図7-9
Formatパラメータ
定数内容
xlBitmap2ビットマップ形式でコピーされた画像(bmp,jpg,gif,png)
xlPicture-4147ベクター形式でコピーされた画像(emf,wmf)(既定値)
図7-10

色々なサイトでの説明を読むと「FormatパラメータにxlPictureを指定した方が画像拡大した時にもぼやけない」とありました。今回は画像を縮小しているため、どうなるか試してみた結果が図7-11です。
CopyPictureのパラメータ違いでの画像
図7-11

この結果を見て分かる通り、何度やってもxlPictureを指定すると「カレンダーの横罫線がうまく出ない」ようです。今回の縮小の比率が悪いのかもしれませんが、日付をマウスで選択するのに境界線がはっきりしていないと困るため、今回は「xlScreen」×「xlBitmap」の組み合わせで実行することにしました。

147行目「Me.Image1.Picture = LoadPicture(ImgSave(CalRange))」は、カレンダーを表示するImageコントロールのPictureプロパティに、シートカレンダーの画像を設定しています。プロパティウィンドウを使ってPictureプロパティの設定を行う時には「パス名+ファイル名を直接指定」しますが、コードで指定する場合は、LoadPicture関数を使用し、その引数に「画像のパス名+ファイル名」を指定します。
そのLoadPictureの引数に指定している「ImgSave(CalRange)」は、図7-12のImgSave関数プロシージャを使用しています。ImgSave関数は、画像ファイルのパス名+ファイル名を戻してくれます。

148行目「Me.Repaint」では、フォームの再描画をして強制的に表示更新をし、現在の表示カレンダーにしています。
寄り道
なお、このRepaintメソッドが無い場合でも、カレンダー起動後のスクロールバーや今月ボタンによる年月の移動では、カレンダーはちゃんと更新してくれるのですが、なぜか一旦Imageコントロールをクリックしてしまうと、そこからあとは年月移動でのカレンダー更新が行われない現象が発生しました。

なぜImageコントロールのMouseDownイベント(MouseUpイベントでも同様の現象が発生)が一度発生すると自動更新が行われないのか、原因は結局分かりませんでした。フォーム上の表示を変更した時(もしかしたら、ボタン表面文字の変更等も含めて?)には、安心のためRepaintメソッドを実行した方が良いのかもしれません。

上記図7-8の中間工程の、クリップボードに送ったカレンダー画像を「カレンダー画像ファイルに変換」するのが、図7-12です。引数として、クリップボードにコピーしたセル範囲Rを受け取ります。
このクリップボードの画像をファイルに変換する手法にはいくつか種類がありますが、今回は「Excelからグラフ画像としてExport」する方法を使います。これ以外ではWindows APIを使う方法もあります。
  1. '========== ⇩(12) クリップボードの画像をファイル化 ============
  2. Private Function ImgSave(R As Range) As Variant
  3.  Dim Cht As Chart         '←グラフオブジェクト
  4.  Dim OutputPath As String     '←出力するパス
  5.  Dim OutputFname As String    '←出力するファイル名
  6.  Dim buf As String         '←既存の画像ファイル名
  7.  Dim Fso As Object         '←FileSystemObjectオブジェクト
  8.  Set Cht = CalSh.ChartObjects.Add(0, 0, R.Width, R.Height).Chart
  9.  Cht.Parent.Select
  10.  Cht.Paste
  11.  OutputPath = ThisWorkbook.Path
  12.  OutputFname = "it-065img.bmp"
  13.  Set Fso = CreateObject("Scripting.FileSystemObject")
  14.  buf = Dir(OutputPath & "¥" & OutputFname)
  15.  If Not buf = "" Then
  16.   Fso.getfile(OutputPath & "¥" & OutputFname).Delete
  17.  End If
  18.  Set Fso = Nothing
  19.  Cht.Export Filename:=OutputPath & "¥" & OutputFname, filtername:="BMP"
  20.  Cht.Parent.Delete
  21.  ImgSave = OutputPath & "¥" & OutputFname
  22. End Function
図7-12

160行目「Set Cht = CalSh.ChartObjects.Add(0, 0, R.Width, R.Height).Chart」は、図7-13のように「CalSh(シートカレンダーのあるワークシート)」にグラフ枠を作成しています。
グラフ枠の作成
図7-13

グラフ枠のサイズは、シートカレンダーの縦(R.Height)横(R.Width)サイズと同じ大きさにしています。もしコピー元のサイズより大きくしてしまうと、図7-14の一番右側のように余白が出来てしまうことになります。
逆に小さくすると、ファイルサイズは小さくなり処理速度も上がるのですが、図7-14の左側のように「画像が粗く」なってしまうデメリットがあります。今回は最も画像が良い「コピー元と同じサイズのグラフ枠」としています。
グラフ枠の大きさと画像の粗さ
図7-14

次に162行目「Cht.Parent.Select」では、図7-15の左側のように「作成したグラフ枠を選択」しています。コード的に見ると、意味の分からないコードですが、これが無いと(私のExcel2016では)画像が貼り付きませんでした。
一説によると「Excel2016の不具合回避」「貼り付けまでの時間を確保する」とのことですが、「選択をせずに、数秒待ち時間を入れる」処理をしてもダメでしたので、単なるバグなのかもしれません。

163行目「Cht.Paste」で、図7-15の右側のように「グラフ枠に、クリップボードのシートカレンダー画像を貼り付け」ています。貼り付けられる側と貼り付ける側の大きさは合わせていますので、ピッタリの大きさとなります。
グラフ枠をSelectし、クリップボードから画像貼り付け
図7-15

165行目「OutputPath = ThisWorkbook.Path」では、画像ファイルを保存する場所を指定します。今回は、システム(ブック)が保存されている場所としていますので、アドインして使用する場合にはアドイン保存先になります。なお、実在する場所であればOKです。
166行目「OutputFname = "it-065img.bmp"」では、画像ファイルのファイル名を指定しています。今回はBMP形式の画像ファイルにしています。

168~175行目では、これから保存しようとしている画像ファイルが、既に保存先に存在するか否かを調べ、存在していたら事前に削除しています。ファイル削除にはFileSystemObjectオブジェクトのDeleteメソッドを使用します。

168行目「Set Fso = CreateObject("Scripting.FileSystemObject")」でFileSystemObjectオブジェクトを生成します。
170行目「buf = Dir(OutputPath & "¥" & OutputFname)」で、Dir関数を使って「画像ファイルが存在するか」を確かめています。もし画像ファイルが存在する場合には「Dir関数は、そのファイル名」を返し、存在しない場合は「""(長さゼロの文字列)」を返してきます。

ですので、171行目「If Not buf = "" Then」で変数bufの値を調べ「存在する」ならば、172行目「Fso.getfile(OutputPath & "¥" & OutputFname).Delete」で既存ファイルを削除します。
もちろんWindows本体や他のアプリで必要なファイルを削除してしまっては困るので、166行目で設定する画像ファイル名は「今回システムでしか使われないようなファイル名」をつける必要があります。
最後に175行目「Set Fso = Nothing」で、FileSystemObjectオブジェクトを解除します。

177行目「Cht.Export Filename:=OutputPath & "¥" & OutputFname, filtername:="BMP"」では、Exportメソッドを使って「グラフ(今回はカレンダー画像)を画像ファイルとして書き出し」をします。
この「画像ファイルとして書き出す」機能はグラフ(Chart)以外には見当たらないので、「グラフの上にカレンダー画像を乗せて、それを画像ファイルにする」手段を取っています。
Exportメソッドのパラメータとして、Filenameにはパス名+ファイル名を指定し、filternameにはグラフィックフィルター名である「BMP」を指定しています。

これで、画像ファイルとして保存されましたので、178行目「Cht.Parent.Delete」で不要になったグラフ枠+カレンダー画像を削除します。ここで削除するのは「Cht(Chart)」では無く「Cht.Parent(ChartObject)」です。「Chart」はグラフの軸やタイトルなどグラフ各部の代表(Object)なので、グラフそのものを削除するにはChartObjectを削除する必要があります。

180行目「ImgSave = OutputPath & "¥" & OutputFname」で、ImgSave関数プロシージャの戻り値を「カレンダー画像のパス名+ファイル名」に設定し、図7-8の147行目に戻しています。

寄り道
今回、保存する画像ファイルはBMP形式にしました。しかしBMPは「ファイルサイズが大きい」のが良く知られています。
ですのでJPG形式でも試してみました。コードの変更内容としては図7-16のように、166行目・177行目をBMP→JPGに変更しています。
  • '========== 画像ファイル形式をBMP → JPEG  ============
  •  OutputFname = "it-065img.bmp"
  •  OutputFname = "it-065img.jpg" '//166行目の代替
  •  
  •  Cht.Export Filename:=OutputPath & "¥" & OutputFname, filtername:="BMP"
  •  Cht.Export Filename:=OutputPath & "¥" & OutputFname, filtername:="JPG" '//177行目の代替
図7-16

結果は図7-17です。サイズの大きいBMPの方が遅いと思ったら逆の結果です。ファイル圧縮に時間がかかるのかもしれません。また貼り付けたカレンダー画像には、差は感じられませんでした。
画像ファイルの形式の違い
図7-17

以上の結果から、今回はBMP形式を使用しています。

7-2-5.日付の選択

フォームのImageコントロール上に表示されたカレンダーをクリックした時に呼び出されるのが図7-18です。
引数を4つ受け取りますが、今回は第三・第四引数の「Imageコントロール上の、どの位置をクリックしたかを表す座標X(水平方向)・座標Y(垂直方向)」を使用します。

なお、ImageコントロールにはClickイベントというものが無いので、ユーザーがマウスのボタンを押したことに対して反応する「MouseDown」イベントを使用しています。また今回は第一引数のButton、第二引数のShiftの値は無視していますので、マウスを右クリックしても、Shiftを押しながらクリックしても、同じ動作として受け取ります。
  1. '========== ⇩(13) Imageコントロール上のクリック ============
  2. Private Sub Image1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
  3.  Dim Rx As Single         '←カレンダーシート上のX位置
  4.  Dim Ry As Single         '←カレンダーシート上のY位置
  5.  Dim CalR As Range        '←カレンダー上のセル位置
  6.  Rx = CalLeft + CalWidth * x / ImgWidth
  7.  Ry = CalTop + CalHeight * y / ImgHeight
  8.  Set CalR = Point2Range(Rx, Ry)
  9.  If IsNumeric(CalR.Value) = True Then
  10.   DesigD.Value = CalstdD.Value + CalR.Value - 1
  11.   Call DesigYYMM
  12.   Call makeCal
  13.  End If
  14.  Me.CommandButton1.SetFocus
  15. End Sub
図7-18

まず、フォームのImageコントロール上の画像カレンダーをクリックした位置を、シートカレンダー上に置き換える考え方を図7-19で説明します。
Imageコントロール上の位置からシートカレンダー上の位置を把握
図7-19

変数CalRangeのセル範囲のシートカレンダーをコピーして「Imageコントロール上の画像カレンダー」を作っていますので、両カレンダーは縦横比は多少異なっていますが、「Imageコントロール上の画像カレンダーをクリックしたX・Y相対位置は、シートカレンダーのX・Y相対位置と合致」している事になります。
もう少し簡単に言うと、Imageコントロールの中央をクリックしたとしたら、その位置はシートカレンダー範囲の中央をクリックしているのと同じことになるのです。つまりは「縦寸法の何%の位置」「横寸法の何%の位置」という見方で見れば、Imageもシートカレンダーも一緒であると言えるのです。

但し、変数CalRangeのセル範囲の方は、Excelとしての原点(ワークシートの左上角)からズレて作られています。この分は補正する必要があり、その量は図7-19の右側のように「CalLeft」「CalTop」(図7-3の97~98行目で計算)で得られます。

190行目「Rx = CalLeft + CalWidth * x / ImgWidth」は、X方向の両カレンダーの相対位置を使用してワークシート上のX座標を計算しています。
また191行目「Ry = CalTop + CalHeight * y / ImgHeight」は、Y方向の両カレンダーの相対位置を使用してワークシート上のY座標を計算します。

193行目「Set CalR = Point2Range(Rx, Ry)」では、190~191行目の計算で得られた「ワークシート上のX・Y座標(Rx・Ry)」をPoint2Range関数プロシージャ(図7-20)に渡すことで、「セル位置(Range型)」を取得し変数CalRに代入します。

195行目「If IsNumeric(CalR.Value) = True Then」は、クリックしたカレンダー上の値が数値だった時に、196~198行目を実行します。なお「日・月・火・・・などの曜日」または「空白セル」の場合は、数値では無いので何もしません。
まず、196行目「DesigD.Value = CalstdD.Value + CalR.Value - 1」では、クリックした値(日付)を「年月日」の形にして、シートカレンダーの指定日(変数DesigD)に書き込みます。
197行目「Call DesigYYMM」では、その指定日をフォームの指定日に表示させ、198行目「Call makeCal」でフォーム上のカレンダーを更新することで「緑色に着色された指定日のカレンダー」がフォーム上に表示されます。

最後に、201行目「Me.CommandButton1.SetFocus」で、OKボタンにフォーカスを当て、このままEnterキーを押せば日付入力セルに指定日が入るようにしています。

図7-18の193行目から呼び出される「ワークシート上のX・Y座標をセル位置に変換」する関数プロシージャが図7-20です。引数としてX座標値・Y座標値(単位ポイント)を受取ります。
  1. '========== ⇩(14) ワークシート上の座標をセル位置に変換 ============
  2. Private Function Point2Range(x As Single, y As Single) As Range
  3.  With Sheet1.Shapes.AddShape(msoShapeRectangle, x, y, 0, 0)
  4.   Set Point2Range = .TopLeftCell
  5.   .Delete
  6.  End With
  7. End Function
図7-20

ワークシート上の座標をセル位置に変換する方法はいくつか考えられますが、今回は「指定された座標に図形を描画し、その図形のTopLeftCellを取得」する方法としました。図形を描くのは処理時間的に心配でしたが、私のPCで試してみても0.015秒程度です。なお「Application.ScreenUpdating = False」で画面更新停止させると、逆に2倍くらいに処理時間が延びてしまいます。

207行目「With Sheet1.Shapes.AddShape(msoShapeRectangle, x, y, 0, 0)」で、指定されたX座標・Y座標に大きさゼロの四角形を描画します。この大きさは処理時間には無関係の様です。
208行目「Set Point2Range = .TopLeftCell」で、その図形のTopLeftCellプロパティ(Range型)を取得し、関数プロシージャの戻り値に設定します。
209行目「.Delete」で、作成した図形を削除します。

7-2-6.OK/Cancelボタン

フォーム下段の「OKボタン」をクリックした時には、図7-21が呼び出されます。
  1. '========== ⇩(15) OKボタン ============
  2. Private Sub CommandButton1_Click()
  3.  OkCancel = True
  4.  Unload Me
  5. End Sub
図7-21

216行目「OkCancel = True」では「ユーザーの意思(選択した日付を入力セルに記入するのか、元の状態にしておくのか)」のフラグをTrue(選択した日付を入力セルに記入する)にします。
そして、217行目「Unload Me」でフォームを閉じます。

「Cancelボタン」をクリックした時には、図7-22が呼び出されます。
  1. '========== ⇩(16) Cancelボタン ============
  2. Private Sub CommandButton2_Click()
  3.  Unload Me
  4. End Sub
図7-22

222行目「Unload Me」で、単にフォームを閉じます。
尚、OKボタンの所で実行した「ユーザーの意思」のフラグ設定は、Cancelボタンで行う必要はありません。これは、フラグである変数OkCancelは図7-2の65行目で宣言しているだけですので、初期値であるFalse(=元の状態にしておく)になっている為です。

7-2-7.表示年月/指定日の表示

フォームの起動時、及び表示しているカレンダーを変更した時に呼び出されるのが、図7-23です。
  1. '========== ⇩(17) 表示カレンダーの年月表示 ============
  2. Private Sub TitleYYMM()
  3.  Me.Label1.Caption = Format(CalstdD.Value, "yyyy年mm月")
  4. End Sub
図7-23

227行目「Me.Label1.Caption = Format(CalstdD.Value, "yyyy年mm月")」では、シートカレンダーの「基準日(CalstdD)」を「〇〇年〇〇月」という表示にして、フォーム上のLabel1に表示します。

フォームの起動時、及びフォーム上のカレンダーの日付をクリックし指定日を変更した時に呼び出されるのが、図7-24です。
  1. '========== ⇩(18) 指定日の表示 ============
  2. Private Sub DesigYYMM()
  3.  Dim D As Date       '←指定日の日付
  4.  D = DesigD.Value
  5.  If D = 0 Then
  6.   Me.Label2.Caption = ""
  7.  Else
  8.   Me.Label2.Caption = Format(D, "yyyy/mm/dd")
  9.  End If
  10. End Sub
図7-24

233行目「D = DesigD.Value」は、「シートカレンダーの指定日の日付」を変数Dに代入します。もし指定日欄が空白セルだった場合は、変数Dにはゼロが代入されます。
235行目「If D = 0 Then」で変数Dの値を調べ、ゼロ(日付を指定していない空白セルに対して、システムを呼び出した)の場合は、236行目「Me.Label2.Caption = ""」でフォーム上の指定日は空白にします。
一方「日付を指定してあるセルに対してシステムを呼び出した」または「フォーム上のカレンダーの日付をクリックし指定日を変更した」時には、238行目「Me.Label2.Caption = Format(D, "yyyy/mm/dd")」でフォーム上の指定日に日付を表示します。

なお232~233行目で、わざわざ変数Dを使っているのは、出来るだけワークシート上にアタックしない方が処理時間的に有利と考えたからですが、コード数が増えただけであまり効果は無いかもしれません。

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

このマクロをExcelのアドインに登録することで、「図形カレンダーをクリックし日付入力」を利用することが出来ます。アドイン方法については「年賀状リスト等の宛名検索と追記 アドイン登録」を参照下さい。
なお今回システムでは、ファイルをアドインとして保存するまででOKです。アドインを有効にしなくても動作しますし、リボンのボタンに登録する必要もありません(起動マクロに引数が付いているので、登録する事はできません)。

9.最後に

ワークシート上の図形をフォームのImageコントロール上に表示する手法については、様々なサイトで紹介されていました。しかし、どのような形で実用的に使えるか以前から悩んでいました。
そんな時、当サイトの使用状況を調べてみると「セルへの日付入力をカレンダー日付クリックで選定」に多くのアクセスがあることより、シートカレンダーをフォーム上に表示する方法を思い付き、今回の紹介となりました。

Image上のカレンダー画像がもう少し鮮明であれば良いのですが、コードも少なく面白い手法かもしれません。カレンダー以外にグラフや図形をフォーム上に表示することも可能なはずなので、応用すれば活用範囲が広がると思います。


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