【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上に日付選択コントロールを作成します。
大まかな処理の流れは以下の通りです。
- UserFormのウィンドウハンドルを取得する
- UserFormのクライアント領域を取得する
CreateWindowExで日付選択コントロールを作成する- 必要に応じて初期日付を設定する
- ボタン押下時に選択された日付を取得する
- UserForm終了時に作成したコントロールを破棄する
少し難しく見えますが、仕組みとしては「Windows側に用意されている日付選択部品を、VBAのUserForm上に後から表示する」というイメージです。
UserFormに配置するコントロール
まず、UserForm上に以下のコントロールを配置しておきます。
| コントロール | オブジェクト名 | 用途 |
|---|---|---|
| ComboBox | ComboBox1 | 日付選択コントロールを配置する位置の目印 |
| CommandButton | CommandButton1 | 選択された日付を取得するボタン |
ここで配置する 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 | 役割 |
|---|---|
| FindWindow | UserFormのウィンドウハンドルを取得する |
| FindWindowEx | UserForm内のクライアント領域を取得する |
| 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を前提に PtrSafe と LongPtr を使用しています。
古い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アプリ開発から始めませんか?


