ユーザーフォーム カレンダーフォームから日付をテキストボックスに取得する。

エクセルVBAには、カレンダーの機能がない…

HTML画面だと、カレンダーから日付を選択してテキストボックスに入れるって普通に使っていますね。しかし、エクセルVBAにはカレンダーがないのです。昔は、Accessのカレンダーコントロールを使えばツールボックスからつくれたみたいですが…。そんな中、自作で作っているサイトがあったので参考に家計簿の日付取得部分を、スピンボタンのUP、DWONからカレンダーフォームを開いて、クリックした日付を取得する方式に変更しました。

f:id:bimori466:20200526133436p:plain

カレンダーフォームの作成方法について、作り方を解説していきます。
(祝日には対応してません)


1 閲覧対象者

ユーザーフォームのテキストボックスに、カレンダフォームをクリックして日付を代入したい人。

2 得られる効果

日付入力がスマートにできる。
1 カレンダ表示をクリックしてカレンダフォームを表示。
2 日付をクリックする。
3 テキストボックスに日付が返る。

f:id:bimori466:20200526132947p:plain

3 設計

日付はTodayから±3年でコンボボックスから入力可能。(家計簿入力しているだけなので、±1ヶ月あれば充分ですが)。クリックした日付がテキストボックスに代入される。
日付のユーザーフォームはラベルで作る。
スピンボタンは月を1ヶ月増減する。

4 カレンダフォームの作成手順(1~7)


 手順

1 MainFromを作る。
2 カレンダフォームを作る。
3 標準モジュール作成(MainFrom.Show)
4 コマンドボタンクリックイベント作成(カレンダFromの呼出)
5 カレンダフォームのinitialize処理を作る
6 カレンダフォームのカレンダ日付を作成する処理。
7 日付をクリックしたときのイベント処理作成(クラスモジュール)。

1 MainFromを作る。

カレンダフォームを呼び出すためのMainFromを作成します。

f:id:bimori466:20200527075227p:plain

カレンダー表示をクリックして、カレンダフォームを呼び出しテキストボックスに日付を入れます。

2 カレンダフォームを作る。

f:id:bimori466:20200527075534p:plain
最初は、年、月を変更できるコマンドボックスと、月をUPDOWNさせるスピンボタンのみ作っておきます。

最終的な表示は下の写真のようになります。
f:id:bimori466:20200527075801p:plain

曜日、日付のラベルは、カレンダフォームのInitialize処理で作るのでここではまだ作りません。あくまで、必要な側のみ作ります。

3 標準モジュール作成(MainFrom.Show)

標準モジュールにMainFromを呼び出す処理を記述する。

Sub start()

MainForm.Show vbModeless

End Sub

MainFromが出現します。

4 コマンドボタンクリックイベント作成(カレンダFromの呼出)

カレンダフォームを表示します。

Private Sub cmd_カレンダー表示_Click()

clndr_date = Date
Calender_Form.Show

End Sub


ここまでで、MainFormからカレンダフォームを表示するところまでできました。
次にカレンダフォームを作り込んでいきます。

5 カレンダFromのinitialize処理を作る

作成のイメージは下の写真の通りです。
f:id:bimori466:20200527085235p:plain

曜日、日付はラベルで作成します。
土曜日は文字色「青」、日曜日は文字色「赤」にします。
日付ラベルは37個必要です。


まずはコードを見てください。

Private Sub UserForm_Initialize()

Dim Day_of_the_week(6) As String
Day_of_the_week(0) = "日": Day_of_the_week(1) = "月": Day_of_the_week(2) = "火"
Day_of_the_week(3) = "水": Day_of_the_week(4) = "木": Day_of_the_week(5) = "金"
Day_of_the_week(6) = "土"



'曜日のラベルを作成--------------------------------------------------
Dim newLabel As MSForms.Label

For k_Day_of_the_week = 0 To 6
    Set newLabel = Me.Controls.Add("Forms.Label.1", "lab_" & Day_of_the_week(k_Day_of_the_week))       '名前をつけてラベルを追加
        With newLabel
            .Caption = Day_of_the_week(k_Day_of_the_week)   'キャプション
            .Font.Size = 14    '文字サイズ
            .Width = 35             '横幅
            .Height = 18          '縦幅
            .Left = 24 + (35 * k_Day_of_the_week)        '左からの距離
            .Top = 60          '上からの距離
            '.BorderStyle = fmBorderStyleSingle      'ラベルに線で囲う。
            
            If Day_of_the_week(k_Day_of_the_week) = "日" Then
                .ForeColor = &HFF&      '文字色を赤
            
            ElseIf Day_of_the_week(k_Day_of_the_week) = "土" Then
                .ForeColor = &HFF0000   '文字色を青
            Else
                .ForeColor = &H0&       '文字色を黒
            End If
                
            .TextAlign = fmTextAlignCenter
        End With
    Set newLabel = Nothing
Next
'____________________________________________________________________



'日付のラベルを作成--------------------------------------------------
Dim DayLabel As MSForms.Label

Left_IX = 0: Top_IX = 0: n = 0

For k_DayLabel = 1 To 37
    
    
    Set DayLabel = Me.Controls.Add("Forms.Label.1", "lab_Day_" & k_DayLabel)       '名前をつけてラベルを追加
        With DayLabel
            .Caption = k_DayLabel   '後で削除
            .TextAlign = fmTextAlignCenter
            .Font.Size = 14         '文字サイズ
            .Width = 35             '横幅
            .Height = 18            '縦幅
            .Left = 24 + (35 * Left_IX)     '左からの距離
            .Top = 100 + (18 * Top_IX)        '上からの距離
            '.BorderStyle = fmBorderStyleSingle      'ラベルに線で囲う。
            
            If k_DayLabel Mod 7 = 0 Then
                .ForeColor = &HFF0000   '文字色を青
            ElseIf k_DayLabel Mod 7 = 1 Then
                .ForeColor = &HFF&      '文字色を赤
            Else
                .ForeColor = &H0&       '文字色を黒
            End If
                
        End With
    Set DayLabel = Nothing
    
    Left_IX = Left_IX + 1
    n = n + 1
    
    '7個作ったら、下に作る判定
    If n = 7 Then
        n = 0
        Top_IX = Top_IX + 1
        Left_IX = 0
    End If
    
Next
'____________________________________________________________________


'コンボボックス登録処理----------------------------------------------
For i = -3 To 3 '前後3年分の年を登録
    Me.cmb_year.AddItem CStr((Year(clndr_date)) + i)
  Next i
  For i = 1 To 12 '月を登録
    Me.cmb_month.AddItem CStr(i)
  Next i
  For i = LBound(ctrl) To UBound(ctrl) 'ラベルのクリックイベントを拾うための処理
    ctrl(i).SetCtrl Me("lab_Day_" & i)
  Next
    
  Me.cmb_year = Year(clndr_date) '年を指定
  Me.cmb_month = Month(clndr_date) '月を指定
'____________________________________________________________________


End Sub


コードの説明

1) 曜日の配列「Day_of_the_week(6)」を作成する。
2) 曜日のラベルを作成する。
3) 日付のラベルを作成。あとでクラスモジュールの共通イベント処理を登録するので、ラベル名は連番を振る。ラベルを7個作ったら折り返す処理がいるので少し面倒。土曜日、日曜日は「Mod 7」で判断する。
4) コンボボックス登録処理


ここまでのコードを実行すると、下の写真のような結果が得られます。

f:id:bimori466:20200531085317p:plain

コンボボックスに「年」「月」は入っていますが、日付ラベルは単純に1~37が入っているだけです。
次は「「年」「月」ごとに正しくカレンダーを作成する処理」を作っていきます。

ちなみに、なぜInitialize時に日付ラベルを作成するかと言うと、あとでサイズ変更する際に楽だからです。

6 カレンダフォームのカレンダ日付を作成する処理。

以下のコードを作成します。

Private Sub clndr_set() 'カレンダーの作成と表示
  Dim yy As String, mm As Integer, i As Integer, n As Integer, endDay As Integer
   
  If Me.cmb_year = "" Or Me.cmb_month = "" Then Exit Sub '年か月どちらか入ってなければ中止
  yy = Me.cmb_year '年
  mm = Me.cmb_month '月
    
  For i = 1 To 37 'ラベルの初期化
    Me("lab_Day_" & i).Caption = ""
    Me("lab_Day_" & i).BackColor = Me.BackColor
  Next
   
  n = Weekday(yy & "/" & mm & "/" & 1) - 1 'その月の1日の曜日番号に、マイナス1したもの
  endDay = Day(DateAdd("d", -1, DateAdd("m", 1, yy & "/" & mm & "/" & "1"))) '月末日の算出
  For i = 1 To endDay
    Me("lab_Day_" & i + n).Caption = i '日を入れる
    If CDate(yy & "/" & mm & "/" & i) = clndr_date Then Me("lab_Day_" & i + n).BackColor = RGB(200, 200, 200) 'TextBoxの日と同じなら色をつける
  Next i
End Sub


また、このclndr_setを実行するタイミングを、コンボボックス「年」「月」が変更されたときにします。
(コンボボックスのChangeイベントから、clndr_setをCallする)


コードの解説

1)ラベルの「キャプション」と「背景色」を初期化。
2)変数nに月頭(1日)の曜日番号にー1したものを代入する。
3)変数endDayに月末の日付を代入する。
4)ループ処理で日付ラベルのキャプションに日付を入れる。


ここまでで、カレンダーの日付作成まで完了しました。

f:id:bimori466:20200531132556p:plain

あとは日付のラベルをクリックしたときに、MainFormのテキストボックスに日付を代入する処理を作ります。
クラスモジュールに作成し、カレンダフォームのInitialize時にクラスを適応させます。

7 日付をクリックしたときのイベント処理作成(クラスモジュール)。

実装の手順は、
1)クラスモジュールの作成
2)カレンダフォームのInitialize時にクラスをセットする

 です。


1)クラスモジュールの作成
変数Traget(日付ラベル)に、共通処理するためのコードを記述します(WithEventsで設定)。


クラスモジュールのコード

Private WithEvents Target As MSForms.Label
 
Public Sub SetCtrl(new_ctrl As MSForms.Label)
  Set Target = new_ctrl
End Sub
 
Private Sub Target_Click()
  With Calender_Form
    If Target.Caption = "" Then Exit Sub 'ラベルが空だったら中止
    clndr_date = .cmb_year & "/" & .cmb_month & "/" & Target.Caption '日付を生成して変数に格納
  End With
  'clndr_flg = True 'フラグを立てる
  
  MainForm.txt_date = clndr_date
  Unload Calender_Form 'カレンダーを閉じる
End Sub


TragetをMSFROMのラベルとして先頭で宣言。
SetCtrlでTragetにラベルを登録する処理を記載。
Target_Clickに、Tragetをクリックしたときの処理を記載しています。


2)カレンダフォームのInitialize時にクラスをセットする


Private Sub UserForm_Initialize()
 ’~省略~

'コンボボックス登録処理----------------------------------------------
For i = -3 To 3 '前後3年分の年を登録
    Me.cmb_year.AddItem CStr((Year(clndr_date)) + i)
  Next i
  For i = 1 To 12 '月を登録
    Me.cmb_month.AddItem CStr(i)
  Next i
  
  For i = LBound(ctrl) To UBound(ctrl) 'ラベルのクリックイベントを拾うための処理
    ctrl(i).SetCtrl Me("lab_Day_" & i)
  Next
    
  Me.cmb_year = Year(clndr_date) '年を指定
  Me.cmb_month = Month(clndr_date) '月を指定
'____________________________________________________________________

End Sub


「ctrl(i).SetCtrl Me("lab_Day_" & i)」でラベルにクリックイベントを登録しています。
クリックしたら、MainFormのテキストボックスに日付を代入して、カレンダフォームを閉じます。


終結果(2020/5/26を選択)

f:id:bimori466:20200531183724p:plain


以上になります。

5 作ってみての感想

HTMLだとカレンダーコントロールをさくっと作成できます。しかし、1から作ってみて勉強になることが多くありました。クラスモジュールの共通処理イベントの作成という点ではとても参考になる作成例だと思います。


参考サイト↓↓
ateitexe.com