【VBA×WindowsAPI】UserFormに日付選択コントロール(カレンダー)を作成する

VBAで日付を入力させる際にカレンダーから選択させたいという場面に出くわすことがあります。少し前ですとAccessのカレンダーコントロールを使うことで実現できましたが、現在のバージョンでは廃止されて使うことが出来なくなっており、VBAだけで再現することは実質不可能となっています。

自作でカレンダーフォームを作るのもアリですが、WindowsAPIを使うことで旧Accessのカレンダーコントロールと同等の日付選択コントロールを作成することができます。コードの難易度としては高いものとなっていますが、Windowsで用意されているコントロールのためかなり使い勝手の良いコントロールとなっており、もちろんアプリケーションによる縛りはありません。

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

WindowsAPIを使うことで、上画像のようにUserForm上に日付選択用のカレンダーコントロールを作成することができます。コントロールの仕様としてはほとんど旧Accessのカレンダーコントロールと同じで、説明がなくとも簡単に扱うことのできるUIとなっています。日付の表記は「YYYY/MM/DD」や「YYYY年MM月DD日」などのように任意のフォーマットに切り替えることも可能です。

この日付選択コントロールはWindowsAPIを使って新たに生成されたコントロールのため、UserFormの標準コントロールとは違いオブジェクトとして存在はしません。そのため値変更や位置の変更、イベントの設定などは基本的には行うことができないので注意が必要です。

VBAでUserFormに日付選択コントロールを作成するには下記のWindows APIを利用します。
それぞれ関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。

icon-check-square FindWindow関数               :ウィンドウのハンドルを取得する
icon-check-square FindWindowEx関数           :子ウィンドウのハンドルを取得する
icon-check-square GetWindowLongPtr関数    :アプリケーションインスタンスのハンドルを取得する
icon-check-square InitCommonControlsEx関数:コモンコントロールを初期化(クラス登録)する
icon-check-square CreateWindowEx関数        :ウィンドウ(コントロール)を作成する
icon-check-square DestroyWindow関数            :ウィンドウ(コントロール)を破棄する
icon-check-square SendMessage関数             :ウィンドウにメッセージを送信する

「そもそもWindows APIって何?」という方はコチラ(メインページ)も併せて参照下さい。

サンプルコード

UserFormに日付選択コントロールを作成するためのサンプルコードは下記の通りです。
上画像の通り「CommandButton1」と「ComboBox1」の2つのコントロールを作成したUserFormのコードとして下記コードを使用してください。下記コードコピペ後、そのUserFormを表示するだけで、ComboBoxの位置に日付選択コントロールが作成されます。

コード解説

icon-edit UserFormのクライアント領域のウィンドウハンドル取得

ウィンドウにはそれぞれを識別するためにウィンドウハンドルと呼ばれるIDが割り当てられています。WindowsAPIでウィンドウの情報を取得したり操作する際には、このウィンドウハンドルを使って行います。今回のケースでは、日付選択コントロールを作成する場所の情報として必要になります。

ウィンドウハンドルを取得する方法はいくつも存在しますがここでは常套手段のFindWindow関数を使ってウィンドウハンドルを取得しています。FindWindow関数は下記のように記載することで指定のクラス名もしくはウィンドウ名から該当のウィンドウへのハンドルを取得することができます。(UserFoemの場合はクラス名に「”ThunderDFrame”」、ウィンドウ名に「Me.Caption」で取得可能)

icon-code FindWindow関数 

hWnd = FindWindow(クラス名”, “ウィンドウ名”)

UserFormのウィンドウハンドル取得後、クライアント領域のウィンドウハンドルを取得します。

クライアント領域とはウィンドウのタイトルバーやメニューバーなどを除いたエリアのことを指し、UserFormでいうとコントロールの作成ができるエリアのことです。FindWindow関数で取得したウィンドウハンドルはUserForm全体のハンドルであり、クライアント領域と非クライアント領域が合わさったエリアとなっています。(非クライアント領域=クライアントエリア以外のエリア)

日付選択コントロールを作成するためにはコントロールを作成するエリア、つまりはUserFormのクライアント領域のハンドルが必要となります。UserFormは構造上、1つ子供のウィンドウハンドルを取得すればそれがクライアント領域のハンドルとなっています。そのため子ウィンドウのハンドルを取得できるFindWindowEx関数を使って下記のように書くことでUserFormのクライアント領域のハンドルを取得することができます。(第1引数のhWndはUserFormのウィンドウハンドル)

icon-code FindWindowEx関数 

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

FindWindowEx関数は引数の値から複数ある子ウィンドウから条件に合う対象の子ウィンドウへのハンドルを取得する関数ですが、UserFormの場合は子ウィンドウは1つしかないため上記のような無条件の引数でクライアント領域へのハンドルが取得ができます。
 

icon-edit コモンコントロール初期化

Windowsにはツールバーやステータスバー、ツリービューをはじめとした「コモンコントロール」と呼ばれるコントロール群が登録されており、日付選択コントロールもこのコモンコントロールに含まれています。コモンコントロールを新規作成するには事前にInitCommonControlsEx関数を呼使って対象のコモンコントロールをメモリ上にロードさせておく必要があります。

本関数には引数にINITCOMMONCONTROLSEX構造体を入力しますが、予め構造体の要素に値を入力しておく必要があります。dwICCには新規作成するコモンコントロールを表すフラグを入力しておく必要があり、日付選択コントロールの場合は「ICC_DATE_CLASSES (&H100)」を入力します。dwSizeには構造体のサイズ、つまりは「Len(構造体)」を入力すれば問題ありません。

icon-code InitCommonControlsEx関数 

With tInitCmnCtrl

    .dwICC = ICC_DATE_CLASSES  ‘&H100

    .dwSize = Len(tInitCmnCtrl)

End With

lRet = InitCommonControlsEx(tInitCmnCtrl)

上記のように書くことで日付選択コントロールの初期化は完了です。
その他のコモンコントロールを表すフラグ値はリンク先に表記されています。
  

icon-edit アプリケーションのインスタンスハンドル取得

コモンコントロールを作成する場合はどのアプリケーションに作成するかを定義する必要があります。このとき使われるのがアプリケーションのインスタンスハンドルです。ウィンドウハンドルはウィンドウを表していたのに対して、インスタンスハンドルは”アプリケーションそのもの”を表しています。

icon-code GetWindowLongPtr関数 

hWndExcel = FindWindow(“XLMAIN”, Application.Caption)   
hInst = GetWindowLongPtr(hWndExcel, GWL_HINSTANCE)

インスタンスハンドルはGetWindowLongPtr関数の第1引数に該当のアプリケーションのウィンドウハンドル、第2引数に「GWL_HINSTANCE (-6)」を入力することで戻り値として取得することができます。ウィンドウハンドルは前項と同じくFindWindow関数を使ってExcelのウィンドウハンドルを取得しています。(※ウィンドウハンドルの取得さえできれば他のアプリケーションでも使用可能)
 

icon-edit 日付選択コントロール作成

コモンコントロールを新規作成する場合はCreateWindowEx関数を使います。

引数は非常に多く、基本的には上記の通り記載すれば問題ありません。
このとき、値を変えることのある引数は第4、第5~8引数で、それ以外は基本的に定数値です。

第4引数には作成するコントロールのウィンドウスタイルを入力していますが、日付選択コントロールの場合、「DTS_SHORTDATEFORMAT」や「DTS_LONGDATEFORMAT」などの専用のフラグがあり、日付の表示フォーマットを変更することができます。年月日を/区切りにするというような設定であったり、さらに細かくフォーマットを設定することができるスタイルも用意されています。

第5~8引数は作成するコントロールの位置と大きさを入力します。サンプルコードでは事前に作成しておいたComboBoxと同じ位置、サイズになるような値を入力しています。本関数の入力値の単位はピクセル(px)のため、UserForm上の単位ポイント(pt)を変換するための関数を通しています。位置調整のためにComboBoxを用意していますが、定数を直接入力しても問題ありません。

CreateWindowEx関数で作成したコントロールは不要になったら、DestroyWindow関数を使って明示的に破棄する必要があります。本関数を通さずに処理を終えてしまうとメモリリークを起こす原因となるため注意が必要です。(サンプルコードではTerminateイベントで実行)

icon-code SendMessage関数 

Call DestroyWindow(hWndDate)

引数として破棄するコントロール(ウィンドウ)のハンドルを入力します。CreateWindowEx関数は作成したコントロールのウィンドウハンドルを戻り値として返すため、その値をそのまま入力すれば作成した日付選択コントロールを破棄することができます。
 

icon-edit 日付選択コントロールの値取得

日付選択コントロールの値を取得するには、SendMessage関数を使って値を取得する対象の日付選択コントロールのウィンドウハンドルに値取得メッセージを送信する必要があります。

icon-code SendMessage関数 

lRet = SendMessage(hWndDate, DTM_GETSYSTEMTIME, 0, tSysTime)

第1引数には日付選択コントロールのウィンドウハンドル、第2引数には送信するメッセージ(値取得)、第4引数には取得した時間を格納するためのSYSTEMTIME構造体をそれぞれ入力します。

これにより、日付選択コントロールの現在の値を取得することができます。このとき年月日や曜日などはSYSTEMTIME構造体の各要素ごとに分かれてそれぞれ取得されます。各要素の定義についてはSYSTEMTIME構造体ページを参照下さい。

日付選択コントロール自体の値変更イベントはないため、UserFormの標準で用意されている何らかのイベントで本処理を行う必要があります。今回の内容よりさらに深い領域であるUserFormのサブクラス化をすることで日付選択コントロールの値変更イベントの作成も可能です。

関連情報

icon-share-square VBA×WindowsAPIまとめページ

その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。

icon-share-square 参考

Microsoft公式:日付と時刻の選択コントロールについて – Win32 apps

Excel, VBA, Windows API

Posted by Lic