【完成品】エクセルVBAでカレンダーフォームを作成してみた

「他の人に配布するエクセルでカレンダーを使いたい…」という方のために、プログラマーの私が自作したカレンダーフォームを公開します。

このページを読んでいる方はすでにご存じかと思いますが、Microsoft標準のカレンダーは専用のDLLがいるみたいで、配布用のファイルには使いにくいのです。

ちなみに、当ブログで公開しているエクセル家計簿にもこのフォームを組み込んでいて、1万回以上はダウンロードされているので、品質と実績はけっこうある方です。

以下の順番で、できるだけこまかく説明していきます。


  1. カレンダーフォームの使い方
  2. カレンダーフォームの呼び出し方
  3. カレンダーフォームの仕組み
  4. VBA(frmCalendar)のダウンロード

Contents

カレンダーフォームの使い方

カレンダーの基本的な使い方としては、以下のGIF画像をご覧ください。

セルをクリックしてカレンダーを表示していますが、ボタンなどを含めて他のイベントでも表示することができます。


※矢印クリックで再生

実際に動かしてみたい方は、下記ページのエクセル家計簿をダウンロードして、「家計簿」というシートにある「日付」列をクリックしてみてください。

VBAを公開しているので、呼び出し方の確認やステップ実行なども可能です。

⇒【エクセル家計簿】貯金が得意なプログラマーがテンプレートを作ってみた

カレンダーフォームの呼び出し方

セルのクリックでカレンダーを表示する場合は、WorksheetオブジェクトのSelectionChangeイベントを使います。

VBAエディター左部に表示されるSheet1などをクリックした後、上部のWorksheet_SelectionChangeを選ぶと、イベントハンドラーが自動生成されます。

ボタンなどでカレンダーを表示する場合は、標準モジュールなどお好きなところで大丈夫です。

カレンダーフォームの呼び出し方
⇒大きい画像を開く

カレンダーを呼び出すコードは、下記のサンプルを参考にしてください。

▼A1セルを押したときにカレンダーを表示する

A1セル以外のセルが押されたらカレンダーを非表示、A1セルが押されたらカレンダーを表示します。

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
          
    If Target.Address <> "$A$1" Then
       
        frmCalendar.Hide
        Exit Sub
        
    End If
    
    Set frmCalendar.TargetRange = Target
    frmCalendar.Initialize
    frmCalendar.Show vbModeless
    
End Sub

▼テーブル(表)で特定の列のみカレンダーを表示する

最初のIf文は、複数のセルが同時選択されたときに、カレンダー表示をキャンセルするのが目的です。

複数セルに日付を同時入力したい場合は、このIf文は不要です。

ふたつ目のIf文は、テーブルの日付列以外が選択された場合のキャンセル処理。

三つ目のIf文は、テーブルの範囲外にある行が選択されたときのキャンセル処理です。

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
        
    '範囲選択を無視
    If Target.Count > 1 Then
        frmCalendar.Hide
        Exit Sub
    End If

    '日付の入力欄以外を無視
    If Target.Column <> Hoge Then
        frmCalendar.Hide
        Exit Sub
    End If

    If Target.row <= Fuga Then
        frmCalendar.Hide
        Exit Sub
    End If

    Set frmCalendar.TargetRange = Target
    frmCalendar.Initialize
    frmCalendar.Show vbModeless
    
End Sub

とりあえずカレンダーを表示させるには、ふたつ目のサンプル、20~22行目をコピペしてもらえれば動きます。

日付を入力したいセルをRangeオブジェクトとして取得し、カレンダー(frmCalendar)のTargetRangeという変数にセットします。

frmCalendarを閉じるときは、UnloadではなくHideを使ってください。

詳しいことは後でお話ししますが、カレンダーを再表示したときに表示位置を復元するためです。

カレンダーフォームの仕組み

カレンダーがどのように実装されているかについても、解説したいと思います。

中身をしっかり吟味したい方や自分なりにカスタマイズしたい方は、参考にしてみてください。

定数と変数の宣言~初期化

定数と変数の宣言、初期化のコードです。

Option Explicit

Private Const COLOR_NORMAL = &HE0E0E0
Private Const COLOR_TODAY = &H99FFFF

Public TargetRange As Range

Public Sub Initialize()

    Dim dt As Date
    
    If IsDate(TargetRange.value) = False Then
        dt = Date
    Else
        dt = TargetRange.value
    End If

    SetCalendar year(dt), month(dt)

End Sub

定数のCOLOR_NORMAL と COLOR_TODAYは、日付ボタン(1~31日)の配色に使います。

今日の日付は黄色、その他は灰色となっています。

定数と変数の宣言~初期化

TargetRangeは、カレンダーを表示する前に呼び出し元でセットするRangeオブジェクトです。

最終的には、日付ボタンを押したタイミングでこのTargetRangeに日付をセットし、セルに反映…となります。

Initializeは、名前のとおり初期化のための関数です。

TargetRangeになにも入力されていないときは今月、すでに入力されているときはその月をカレンダーの初期表示とします。

前月・次月ボタン

左矢印ボタン、右矢印ボタンをクリックすると、前月または次月のカレンダーを表示します。

前月・次月ボタン

コードはこんな感じになっています。

Private Sub lblPreMonth_Click()

    MovePreMonth
    
End Sub

Private Sub lblPreMonth_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

    MovePreMonth
    
End Sub

Private Sub MovePreMonth()

    Dim dt As Date
    dt = DateSerial(lblYear.Caption, lblMonth.Caption - 1, 1)

    SetCalendar year(dt), month(dt)
    
End Sub

Private Sub lblNextMonth_Click()

    MoveNextMonth
    
End Sub

Private Sub lblNextMonth_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

    MoveNextMonth

End Sub

Private Sub MoveNextMonth()

    Dim dt As Date
    dt = DateSerial(lblYear.Caption, lblMonth.Caption + 1, 1)

    SetCalendar year(dt), month(dt)

End Sub

前月ボタン(lblPreMonth)、次月ボタン(lblNextMonth)に、それぞれClickとDblClickのイベントハンドラーを実装しています。

ダブルクリックすると、Clickイベントが2回ではなく、ClickイベントとDblClickイベントが1回ずつ発生します。

そのため、ダブルクリックしたときに2ヵ月先のカレンダーを表示するためには、DblClickのイベントハンドラーが必要となります。

もしわかりにくければ、DblClickのイベントハンドラーを消去して検証してみてください。

ボタンを連打してもなかなか月が変わらないので、イラっとすると思います。

MovePreMonth、MoveNextMonthに登場するlblYearとlblMonthは、カレンダー上部に表示されている年月のラベルです。

前月ボタンと次月ボタンがクリックされると、ここに表示されている年月の前月または次月をカレンダー表示の共通処理に渡します。

前月・次月ボタン

カレンダーの描画

初期表示、前月次月ボタンがクリックされたときに呼ばれる、カレンダーを描画するための共通処理です。

Private Sub SetCalendar(ByVal year As Integer, ByVal month As Integer)

    '年月を表示
    lblYear.Caption = year
    lblMonth.Caption = month

    Dim i As Integer
    Dim lblDay As Object
    
    'カレンダーを初期化
    For i = 1 To 37
    
        Set lblDay = Me.Controls("lblDay" & i)
        
        lblDay.Caption = ""
        lblDay.BackColor = COLOR_NORMAL
        
    Next

    '始めの日
    Dim firstDate As Date
    firstDate = DateSerial(year, month, 1)
    
    '曜日によって位置をずらす
    Dim position As Integer
    position = weekday(firstDate) - 1
    
    '終わりの日
    Dim endDay As Integer
    endDay = day(DateSerial(year, month + 1, 0))
    
    For i = 1 To endDay
    
        Set lblDay = Me.Controls("lblDay" & (i + position))
        
        lblDay.Caption = i
        
        If DateSerial(year, month, i) = Date Then
            lblDay.BackColor = COLOR_TODAY
        End If
    
    Next
    
End Sub

最初に、カレンダー上部にある年月のラベルに表示対象の年月をセットします。

その後は、日付ボタン(1~31日)の値と背景色をいったんクリアして、日付を埋めるループに入ります。

日付ボタンの中に今日のものがあれば、その背景色は定数のCOLOR_TODAY(黄色)となります。

日付ボタン(1~31日)

カレンダーの中央にある日付ボタンを押すと、その日付を対象のセル(TargetRange)に出力します。

日付ボタン(1~31日)

コードはこんな感じになっています。

Private Sub OutputDate(ByVal day As String)

    If day = "" Then
        Exit Sub
    End If
    
    TargetRange.value = DateSerial(lblYear.Caption, lblMonth.Caption, day)

    Me.Hide

End Sub

Private Sub lblDay1_Click()

    OutputDate lblDay1.Caption

End Sub

Private Sub lblDay2_Click()

    OutputDate lblDay2.Caption

End Sub

'以下省略 lblDay31_Click()まで続く

lblDay1_Click、lblDay2_Click…というように、ボタンの数だけクリックイベントを拾うイベントハンドラーがあって、それぞれが共通処理のOutputDateを呼びます。

共通処理では、カレンダー上部の年月ラベルと日付ボタンのラベルから日付をつくり、入力対象のセル(TargetRange)にセットします。

表示位置の復元

カレンダーを2回、3回と再表示したときに、前と同じ位置に表示させるため、ちょっとだけ工夫しています。

コードはこんな感じ。

Private Sub UserForm_Layout()

    'フォームの位置が変わった場合は、
    '再表示時にその位置を復元する
    StartUpPosition = 0
    
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

    '表示位置がリセットされないようにする
    Cancel = True
    Me.Hide

End Sub

カレンダーフォーム(frmCalendar)のStartUpPositionプロパティは、「1 - オーナーフォームの中央」となっていて、初期表示ではエクセルの中央に表示されます。

カレンダーを好きなところに動かすと、UserForm_Layoutが実行されて、同プロパティを「0 - 手動」に変更します。

これにより、カレンダーを表示しなおした際、エクセルの中央ではなく前回と同じ位置に表示されるようになります。

また、画面右上の閉じるボタン(×ボタン)が押された場合は、UserForm_QueryCloseが実行されます。

そのまま閉じてしまうと、StartUpPositionが初期値の「1 - オーナーフォームの中央」にもどり、表示位置を復元することができません。

そのため、画面を"閉じる"のはキャンセルして、“隠す"ようにしています。

VBA(frmCalendar)のダウンロード

カレンダーフォームは、以下のリンクをクリックするとダウンロードできます。

⇒カレンダー(frmCalendar)をダウンロード

ZIPファイルを解凍したら、VBAエディターにある「ファイルのインポート」からカレンダーフォームを取り込んでください。

VBA(frmCalendar)のダウンロード

VBA(frmCalendar)のダウンロード