【Excel VBA】UserFormにカレンダー形式の日付選択コントロールを作成する方法

Excel VBAで業務用ツールを作成していると、UserForm上で日付を入力させたい場面があります。

  • 予約日を入力する
  • 開始日・終了日を入力する
  • 対象期間を指定する
  • 請求日や納品日を入力する
  • 検索条件として日付を指定する

このような場合、TextBoxに直接「2026/07/08」のように入力してもらう方法でも実装は可能です。

しかし、日付を手入力にすると、入力形式が人によってバラバラになったり、存在しない日付が入力されたり、入力ミスが発生しやすくなります。

そこで今回は、Excel VBAのUserForm上に、カレンダー形式で日付を選択できるコントロールを作成する方法を紹介します。

今回作成するもの

今回作成するのは、UserForm上で日付を選択できるカレンダー形式のコントロールです。

日付欄の右側にあるボタンをクリックするとカレンダーが表示され、そこから日付を選択できるような入力欄をイメージするとわかりやすいです。

VBAの標準コントロールだけでは、このような日付選択用のカレンダーコントロールを簡単に配置することが難しいため、今回は Windows API を使って実装します。

Windows APIを使う理由

以前のExcel環境では、カレンダーコントロールを使用してUserForm上に日付選択欄を配置する方法がありました。

しかし、現在のExcel環境では、以前利用されていたカレンダーコントロールが標準では使えないケースも多くなっています。

そのため、UserFormで日付選択を実現したい場合は、主に以下のような方法が考えられます。

  • TextBoxに日付を直接入力してもらう
  • 自作のカレンダーフォームを作成する
  • Windows APIを使って日付選択コントロールを作成する

TextBoxへの直接入力は実装が簡単ですが、入力ミスが発生しやすいです。

自作カレンダーは自由度が高い一方で、作り込みに手間がかかります。

そこで今回は、Windowsに用意されている日付選択コントロールを、VBAのUserForm上に作成する方法を紹介します。

実装の考え方

今回の処理では、UserForm上に直接カレンダーコントロールを配置するのではなく、Windows APIを使ってUserForm上に日付選択コントロールを作成します。

大まかな処理の流れは以下の通りです。

  1. UserFormのウィンドウハンドルを取得する
  2. UserFormのクライアント領域を取得する
  3. CreateWindowEx で日付選択コントロールを作成する
  4. 必要に応じて初期日付を設定する
  5. ボタン押下時に選択された日付を取得する
  6. UserForm終了時に作成したコントロールを破棄する

少し難しく見えますが、仕組みとしては「Windows側に用意されている日付選択部品を、VBAのUserForm上に後から表示する」というイメージです。

UserFormに配置するコントロール

まず、UserForm上に以下のコントロールを配置しておきます。

コントロールオブジェクト名用途
ComboBoxComboBox1日付選択コントロールを配置する位置の目印
CommandButtonCommandButton1選択された日付を取得するボタン

ここで配置する ComboBox1 は、実際にComboBoxとして使用するわけではありません。

日付選択コントロールを作成する位置とサイズを決めるための目印として使用します。

実際の運用では、ComboBox1 を非表示にしたり、同じ位置に日付選択コントロールを重ねて表示するような形になります。

サンプルコード

以下のコードをUserFormのコード画面に記述します。

Option Explicit

'------------------------------------------------------------
' Windows API 宣言
'------------------------------------------------------------
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr

Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
    (ByVal hWndParent As LongPtr, ByVal hWndChildAfter As LongPtr, _
     ByVal lpszClass As String, ByVal lpszWindow As String) As LongPtr

Private Declare PtrSafe Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" _
    (ByVal dwExStyle As Long, ByVal lpClassName As String, _
     ByVal lpWindowName As String, ByVal dwStyle As Long, _
     ByVal x As Long, ByVal y As Long, _
     ByVal nWidth As Long, ByVal nHeight As Long, _
     ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, _
     ByVal hInstance As LongPtr, ByVal lpParam As LongPtr) As LongPtr

Private Declare PtrSafe Function DestroyWindow Lib "user32" _
    (ByVal hwnd As LongPtr) As Long

Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
    (ByVal hwnd As LongPtr, ByVal wMsg As Long, _
     ByVal wParam As LongPtr, ByRef lParam As Any) As LongPtr

Private Declare PtrSafe Function GetDC Lib "user32" _
    (ByVal hwnd As LongPtr) As LongPtr

Private Declare PtrSafe Function ReleaseDC Lib "user32" _
    (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long

Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" _
    (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long

'------------------------------------------------------------
' 定数
'------------------------------------------------------------
Private Const WS_CHILD As Long = &H40000000
Private Const WS_VISIBLE As Long = &H10000000
Private Const WS_GROUP As Long = &H20000

Private Const DTS_SHORTDATEFORMAT As Long = &H0
Private Const DTS_LONGDATEFORMAT As Long = &H4

Private Const DTM_FIRST As Long = &H1000
Private Const DTM_GETSYSTEMTIME As Long = DTM_FIRST + 1
Private Const DTM_SETSYSTEMTIME As Long = DTM_FIRST + 2

Private Const LOGPIXELSX As Long = 88

'------------------------------------------------------------
' SYSTEMTIME構造体
'------------------------------------------------------------
Private Type SYSTEMTIME
    wYear As Integer
    wMonth As Integer
    wDayOfWeek As Integer
    wDay As Integer
    wHour As Integer
    wMinute As Integer
    wSecond As Integer
    wMilliseconds As Integer
End Type

'------------------------------------------------------------
' 日付選択コントロールのハンドル
'------------------------------------------------------------
Private hWndDatePicker As LongPtr

'------------------------------------------------------------
' UserForm 初期化時
'------------------------------------------------------------
Private Sub UserForm_Initialize()

    Dim hWndForm As LongPtr
    Dim hWndClient As LongPtr
    Dim initDate As SYSTEMTIME

    ' UserForm本体のウィンドウハンドルを取得
    hWndForm = FindWindow("ThunderDFrame", Me.Caption)

    ' UserFormのクライアント領域を取得
    hWndClient = FindWindowEx(hWndForm, 0, vbNullString, vbNullString)

    ' 日付選択コントロールを作成
    hWndDatePicker = CreateWindowEx( _
        0, _
        "SysDateTimePick32", _
        vbNullString, _
        WS_CHILD Or WS_VISIBLE Or WS_GROUP Or DTS_SHORTDATEFORMAT, _
        PtToPx(Me.ComboBox1.Left), _
        PtToPx(Me.ComboBox1.Top), _
        PtToPx(Me.ComboBox1.Width), _
        PtToPx(Me.ComboBox1.Height), _
        hWndClient, _
        0, _
        0, _
        0 _
    )

    ' 初期日付を設定
    initDate.wYear = Year(Date)
    initDate.wMonth = Month(Date)
    initDate.wDay = Day(Date)

    Call SendMessage(hWndDatePicker, DTM_SETSYSTEMTIME, 0, initDate)

End Sub

'------------------------------------------------------------
' ボタン押下時:選択された日付を取得
'------------------------------------------------------------
Private Sub CommandButton1_Click()

    Dim selectedDate As SYSTEMTIME
    Dim resultText As String

    ' 日付選択コントロールの値を取得
    Call SendMessage(hWndDatePicker, DTM_GETSYSTEMTIME, 0, selectedDate)

    resultText = selectedDate.wYear & "/" & _
                 Format(selectedDate.wMonth, "00") & "/" & _
                 Format(selectedDate.wDay, "00")

    MsgBox "選択された日付は " & resultText & " です。", vbInformation

End Sub

'------------------------------------------------------------
' UserForm終了時:作成したコントロールを破棄
'------------------------------------------------------------
Private Sub UserForm_Terminate()

    If hWndDatePicker <> 0 Then
        Call DestroyWindow(hWndDatePicker)
    End If

End Sub

'------------------------------------------------------------
' ポイントをピクセルに変換する関数
'------------------------------------------------------------
Private Function PtToPx(ByVal pointValue As Double) As Long

    Dim hdc As LongPtr
    Dim dpiX As Long

    hdc = GetDC(0)
    dpiX = GetDeviceCaps(hdc, LOGPIXELSX)
    Call ReleaseDC(0, hdc)

    PtToPx = CLng(pointValue * dpiX / 72)

End Function

コードの解説

ここからは、コードの内容を順番に解説していきます。

今回のコードは通常のVBAだけで完結する処理ではなく、Windows APIを呼び出してUserForm上に日付選択コントロールを作成しています。

Windows APIの宣言

今回のコードでは、VBAからWindows APIを呼び出しています。

通常、VBAだけで作成できるUserFormのコントロールには限りがあります。

そこで、Windowsが持っている機能をVBAから呼び出すことで、標準のUserFormには用意されていない日付選択コントロールを作成しています。

主に使用しているAPIは以下です。

API役割
FindWindowUserFormのウィンドウハンドルを取得する
FindWindowExUserForm内のクライアント領域を取得する
CreateWindowEx日付選択コントロールを作成する
SendMessage日付の取得・設定を行う
DestroyWindow作成したコントロールを破棄する
GetDC / GetDeviceCaps画面のDPIを取得する
ReleaseDC取得したデバイスコンテキストを解放する

特に重要なのは CreateWindowEx です。

この関数を使うことで、Windowsのコントロールを新しく作成できます。

今回の場合は、SysDateTimePick32 というクラス名を指定することで、日付選択用のコントロールを作成しています。

UserFormのハンドルを取得する

以下の部分では、UserFormのウィンドウハンドルを取得しています。

hWndForm = FindWindow("ThunderDFrame", Me.Caption)

Windows上の画面やコントロールには、それぞれを識別するための番号のようなものがあります。

これを ウィンドウハンドル と呼びます。

Windows APIで画面やコントロールを操作する場合、このウィンドウハンドルを使って「どの画面を操作するのか」を指定します。

UserFormの場合、クラス名として ThunderDFrame を指定することで、対象のUserFormを取得できます。

クライアント領域を取得する

次に、UserFormのクライアント領域を取得しています。

hWndClient = FindWindowEx(hWndForm, 0, vbNullString, vbNullString)

クライアント領域とは、タイトルバーなどを除いた、実際にコントロールを配置できる領域のことです。

今回の日付選択コントロールは、UserFormの上に作成する必要があります。

そのため、UserForm本体ではなく、コントロールを配置できる領域のハンドルを取得しています。

日付選択コントロールを作成する

日付選択コントロールを作成しているのが、以下の部分です。

hWndDatePicker = CreateWindowEx( _
    0, _
    "SysDateTimePick32", _
    vbNullString, _
    WS_CHILD Or WS_VISIBLE Or WS_GROUP Or DTS_SHORTDATEFORMAT, _
    PtToPx(Me.ComboBox1.Left), _
    PtToPx(Me.ComboBox1.Top), _
    PtToPx(Me.ComboBox1.Width), _
    PtToPx(Me.ComboBox1.Height), _
    hWndClient, _
    0, _
    0, _
    0 _
)

SysDateTimePick32 を指定することで、Windowsの日付選択コントロールを作成しています。

また、以下の定数を組み合わせて指定しています。

定数意味
WS_CHILD親ウィンドウ上に作成する
WS_VISIBLE表示状態にする
WS_GROUPグループ化の対象にする
DTS_SHORTDATEFORMAT短い日付形式で表示する

今回のコードでは、配置位置とサイズに ComboBox1 の値を使っています。

PtToPx(Me.ComboBox1.Left)
PtToPx(Me.ComboBox1.Top)
PtToPx(Me.ComboBox1.Width)
PtToPx(Me.ComboBox1.Height)

UserForm上の位置やサイズはポイント単位で管理されていますが、Windows APIではピクセル単位で指定する必要があります。

そのため、PtToPx 関数を使って、ポイントをピクセルに変換しています。

初期日付を設定する

以下の部分では、日付選択コントロールの初期値に本日の日付を設定しています。

initDate.wYear = Year(Date)
initDate.wMonth = Month(Date)
initDate.wDay = Day(Date)

Call SendMessage(hWndDatePicker, DTM_SETSYSTEMTIME, 0, initDate)

SYSTEMTIME 構造体に年・月・日を設定し、SendMessage で日付選択コントロールに値を渡しています。

初期値を固定したい場合は、以下のように直接指定することもできます。

initDate.wYear = 2026
initDate.wMonth = 7
initDate.wDay = 8

選択された日付を取得する

ボタンをクリックしたときに、日付選択コントロールで選択されている日付を取得します。

Call SendMessage(hWndDatePicker, DTM_GETSYSTEMTIME, 0, selectedDate)

取得した日付は SYSTEMTIME 構造体に格納されます。

そのため、年・月・日は以下のように取得できます。

selectedDate.wYear
selectedDate.wMonth
selectedDate.wDay

今回のサンプルでは、取得した日付を yyyy/mm/dd 形式に整えて、メッセージボックスで表示しています。

resultText = selectedDate.wYear & "/" & _
             Format(selectedDate.wMonth, "00") & "/" & _
             Format(selectedDate.wDay, "00")

作成したコントロールを破棄する

Windows APIで作成したコントロールは、UserFormの標準コントロールとは違い、VBA側で明示的に破棄する必要があります。

そのため、UserForm終了時に以下の処理を実行しています。

If hWndDatePicker <> 0 Then
    Call DestroyWindow(hWndDatePicker)
End If

これを忘れると、不要なウィンドウリソースが残ってしまう可能性があります。

Windows APIを使ってコントロールを作成した場合は、終了時に破棄するところまでセットで考えるようにしましょう。

実務で使う場合のポイント

今回の方法は便利ですが、いくつか注意点があります。

まず、この日付選択コントロールは、UserFormの標準コントロールとして追加されるわけではありません。

そのため、VBAのプロパティウィンドウから直接設定したり、通常のTextBoxやComboBoxのようにイベントを簡単に扱ったりすることはできません。

また、値を取得したい場合は、今回のように SendMessage を使って取得する必要があります。

実務で使う場合は、以下のような流れがおすすめです。

  • UserForm表示時に日付選択コントロールを作成する
  • 登録ボタンや検索ボタンを押したタイミングで日付を取得する
  • 取得した日付をセルや変数に格納する
  • UserForm終了時に作成したコントロールを破棄する

この流れで使えば、日付入力用のUIとして十分実用的に使えます。

注意点

今回のコードは、64bit版Officeを前提に PtrSafeLongPtr を使用しています。

古い32bit版Officeで使用する場合は、API宣言部分の調整が必要になる場合があります。

また、Windows APIを使用するため、Mac版Excelでは同じ方法では動作しません。

Windows版Excelで業務用ツールを作成している場合に向いている方法と考えてください。

まとめ

今回は、Excel VBAのUserForm上に、Windows APIを使って日付選択用のカレンダーコントロールを作成する方法を紹介しました。

VBA標準のUserFormだけでは、日付選択用のカレンダーを簡単に作ることは難しいですが、Windows APIを活用することで、UserForm上に日付選択コントロールを表示できます。

今回のポイントは以下の通りです。

  • FindWindow でUserFormのハンドルを取得する
  • FindWindowEx でUserFormのクライアント領域を取得する
  • CreateWindowEx で日付選択コントロールを作成する
  • SendMessage で日付の設定・取得を行う
  • DestroyWindow で作成したコントロールを破棄する

少し難しい内容ではありますが、日付入力のUIを整えることで、VBAツールの使いやすさはかなり向上します。

業務用のExcelツールを作成している場合、日付入力欄はよく登場します。

入力ミスを減らしたい場合や、ユーザーにわかりやすい画面を作りたい場合は、ぜひ今回の方法を試してみてください。

最後に

いかがでしたでしょうか。

VBAを1から実装する場合、UserFormの作成やWindows APIの扱いなど、最初は少し難しく感じる部分もあるかと思います。

しかし、今回のように日付選択コントロールを実装できるようになると、入力ミスを減らしながら、ユーザーにとって使いやすいExcelツールを作成できるようになります。

特に、業務で使用するVBAツールでは、日付入力・期間指定・検索条件の指定など、日付を扱う場面が非常に多くあります。

そのような場面で、ただTextBoxに手入力してもらうのではなく、カレンダー形式で日付を選択できるようにしておくと、ツール全体の使いやすさも大きく変わってきます。

今回の記事が、少しでもVBAツール作成の参考になれば幸いです。

開発依頼について

ココナラにて、VBAツール開発やWebスクレイピング開発サービスを出品しております。

自分で開発をしようと思ったけれど、VBAでの実装方法がわからない、既存のExcel業務をもっと効率化したい、Webサイトから情報を自動取得したい、といったお悩みがありましたら、お気軽にご相談ください。

また、本ブログからご依頼いただいた方については割引特典もご用意しておりますので、ご不明点と合わせてメッセージをいただけると幸いです。

Excelにてブラウザ操作自動化ツールを作成します その作業、webスクレイピングを使って自動化しましょう!

Excelにてブラウザ操作自動化ツールを作成します。その作業、Webスクレイピングを使って自動化しましょう!

Webスクレイピング以外にも、Webアプリの開発サービスも出品しております。

Excel VBAだけでは対応しきれない業務管理や、複数人で利用するWebシステムを作成したい場合なども、お気軽にご相談ください。

こちらについても、本ブログからご依頼いただいた方については割引特典がございます。

スモールスケールのWebアプリ開発します 先ずはスモールスケールのWebアプリから始めませんか?

スモールスケールのWebアプリ開発します。まずは小さく始めるWebアプリ開発から始めませんか?

ご興味がある方はこちら!!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA