概要
企業配布向けに再利用しやすくクラス化した UI コンポーネントセットを提供します。
各コンポーネントは「Host(UserForm 上のコンテナ)」に取り付けて使う設計です。
以下を含みます:clsCalendar(カレンダー入力), clsProgressBar(進捗バー), clsTableList(表表示/選択), clsDateRangePicker(日付範囲ピッカー), clsDropdownList(ドロップダウン付き一覧) — 各クラスのコード、API(公開メソッド/プロパティ)、使い方サンプルをステップバイステップで示します。
前準備(必須)
- VBA プロジェクトに下記の Class Module を追加(名前を指定した上でコードを貼る)。
clsCalendar、clsProgressBar、clsTableList、clsDateRangePicker、clsDropdownList
- 標準モジュール
modUIHelperを追加(補助関数を置く)。 - UserForm を作成し、キャンバス用に
Frame(例:fraCanvas)を置いておくとホストしやすい。 - 参照設定は不要(MSForms は標準)。ただし高度な操作で
Scripting.Dictionaryを使う場合はMicrosoft Scripting Runtimeを参照推奨。
全体の使い方(ステップ)
- UserForm 上に
Frameまたは任意のコンテナを配置(例:fraCalendarHost)。 - 標準モジュールまたは UserForm のコードでクラスを
NewしてInitにホストコントロールを渡す。 - クラスの公開メソッドで初期化/表示/取得を行う。
- 必要に応じて Host 側で
WithEventsを使いイベントを受け取る。
clsProgressBar(進捗バー)
概要
UserForm の Frame / Label を利用して簡単に進捗表示。モードレスフォーム処理に便利。
クラスコード(Class Module 名:clsProgressBar)
Option Explicit
' Public API:
' Init hostContainer As MSForms.Frame, Optional width As Single, Optional height As Single
' SetProgress percent As Double ' 0..1
' SetMessage msg As String
' Show / Hide
Private host As MSForms.Frame
Private bar As MSForms.Label
Private msgLbl As MSForms.Label
Private totalWidth As Single
Public Sub Init(hostContainer As MSForms.Frame, Optional w As Single = 200, Optional h As Single = 22)
Set host = hostContainer
host.Width = w
host.Height = h + 18
' create bar
Set bar = host.Controls.Add("Forms.Label.1", "pbBar", True)
bar.Left = 2
bar.Top = 2
bar.Height = h
bar.Width = 0
bar.Caption = ""
bar.BorderStyle = fmBorderStyleNone
' message label
Set msgLbl = host.Controls.Add("Forms.Label.1", "pbMsg", True)
msgLbl.Left = 2
msgLbl.Top = h + 4
msgLbl.Width = w
msgLbl.Height = 14
msgLbl.Caption = ""
totalWidth = w - 4
End Sub
Public Sub SetProgress(percent As Double)
If percent < 0 Then percent = 0
If percent > 1 Then percent = 1
bar.Width = totalWidth * percent
DoEvents
End Sub
Public Sub SetMessage(msg As String)
msgLbl.Caption = msg
DoEvents
End Sub
Public Sub Show()
host.Visible = True
DoEvents
End Sub
Public Sub Hide()
host.Visible = False
DoEvents
End Sub
VB使い方例(UserForm 側)
' UserForm に Frame: fraProgressHost を置く(幅は任意)
Private pbar As clsProgressBar
Private Sub UserForm_Initialize()
Set pbar = New clsProgressBar
pbar.Init Me.fraProgressHost, 300, 18
pbar.SetMessage "準備中..."
pbar.Show
End Sub
Sub RunWork()
Dim i As Long
For i = 1 To 100
pbar.SetProgress i / 100
pbar.SetMessage "処理中:" & i & "%"
' 処理...
DoEvents
Next
pbar.Hide
End Sub
VBclsCalendar(カレンダー入力)
概要
ホスト Frame に日付ボタンを描画し、日付選択時に Host に選択日を返す。ホストは WithEvents で受け取ることを想定。
クラスコード(Class Module 名:clsCalendar)
Option Explicit
' Public API:
' Init hostContainer As MSForms.Frame, Optional baseDate As Date
' ShowMonth dt As Date
' SelectedDate As Date (Property)
' OnDateSelected Event (raise on selection)
Public Event DateSelected(ByVal d As Date)
Private host As MSForms.Frame
Private displayedMonth As Date
Private selectedDateVal As Date
Public Property Get SelectedDate() As Date
SelectedDate = selectedDateVal
End Property
Public Sub Init(hostContainer As MSForms.Frame, Optional baseDate As Date)
Set host = hostContainer
If baseDate = 0 Then baseDate = Date
displayedMonth = DateSerial(Year(baseDate), Month(baseDate), 1)
DrawCalendar
End Sub
Public Sub ShowMonth(dt As Date)
displayedMonth = DateSerial(Year(dt), Month(dt), 1)
DrawCalendar
End Sub
Private Sub DrawCalendar()
Dim y As Long, x As Long, cell As MSForms.Label
Dim firstWeekday As Long, lastDay As Long, dayIdx As Long
host.Visible = True
host.Controls.Clear
' header
Dim hdr As MSForms.Label
Set hdr = host.Controls.Add("Forms.Label.1", "hdr", True)
hdr.Caption = Format(displayedMonth, "yyyy/mm")
hdr.Left = 2: hdr.Top = 2: hdr.Width = host.Width - 4
firstWeekday = Weekday(displayedMonth, vbSunday) ' 1..7
lastDay = Day(DateSerial(Year(displayedMonth), Month(displayedMonth) + 1, 0))
dayIdx = 1
Dim r As Long, c As Long
Dim cellLeft As Single, cellTop As Single, cellW As Single
cellW = (host.Width - 8) / 7
cellTop = 18
For r = 0 To 5
For c = 1 To 7
Set cell = host.Controls.Add("Forms.Label.1", "d_" & r & "_" & c, True)
cell.Left = 2 + (c - 1) * cellW
cell.Top = cellTop + r * 20
cell.Width = cellW - 2
cell.Height = 18
cell.BorderStyle = fmBorderStyleSingle
cell.BackColor = vbWhite
If (r * 7 + c) >= firstWeekday And dayIdx <= lastDay Then
cell.Caption = CStr(dayIdx)
cell.Tag = CStr(dayIdx)
dayIdx = dayIdx + 1
Else
cell.Caption = ""
End If
Next
Next
' attach click handlers via host's event model is not trivial in class; instead we use host to trap Click via naming convention
End Sub
' Host UserForm should call this when a label is clicked:
Public Sub HandleLabelClick(lblName As String)
Dim ctrl As MSForms.Control
On Error Resume Next
Set ctrl = host.Controls(lblName)
On Error GoTo 0
If Not ctrl Is Nothing Then
If ctrl.Tag <> "" Then
selectedDateVal = DateSerial(Year(displayedMonth), Month(displayedMonth), CLng(ctrl.Tag))
RaiseEvent DateSelected(selectedDateVal)
End If
End If
End Sub
VBHost(UserForm)での利用例
Private cal As clsCalendar
Private Sub UserForm_Initialize()
Set cal = New clsCalendar
cal.Init Me.fraCalendarHost, Date
' 注意: カレンダー内のラベルがクリックされたら以下を呼ぶ仕組みを作る必要あり
End Sub
' 以下を UserForm の Click イベントで切り分ける簡易例(実務では個別ラベルの Click を割り当てる)
Private Sub fraCalendarHost_Click()
' クリックされたコントロール名を取得する方法は簡易では難しいため、
' 各ラベルに直接 Click イベントを作るか、ラベルを生成した時に名前を Host に記録しておく方法を採る
End Sub
' あるラベルの Click イベントから:
Private Sub d_0_1_Click()
cal.HandleLabelClick "d_0_1"
End Sub
' クラスのイベント受信
Private Sub cal_DateSelected(ByVal d As Date)
MsgBox "選択日: " & Format(d, "yyyy/mm/dd")
End Sub
VB実装ノート:UserForm 上で動的に生成したラベルの個別 Click を扱うためには、
WithEventsを使うラッパークラスを別途作り、生成時に各ラベルにラッパーを割り当てる手法が望ましい(前の設計文のclsDraggableと同様)。
clsTableList(表表示/選択)
概要
シート範囲または二次元配列を ListBox に流し込み、選択行の取得・ソート・列表示(ColumnCount)を提供。
クラスコード(Class Module 名:clsTableList)
Option Explicit
' Public API:
' Init hostList As MSForms.ListBox, Optional colCount As Long = 1
' LoadFromRange rng As Range
' LoadFromArray arr
' GetSelectedRowIndex() As Long
' GetSelectedRow() As Variant
Private lst As MSForms.ListBox
Private colCnt As Long
Public Sub Init(hostList As MSForms.ListBox, Optional columns As Long = 1)
Set lst = hostList
colCnt = columns
lst.Clear
lst.ColumnCount = colCnt
End Sub
Public Sub LoadFromRange(rng As Range)
Dim data
If rng.Rows.Count = 0 Then Exit Sub
data = rng.Value
lst.List = data
lst.ColumnCount = rng.Columns.Count
End Sub
Public Sub LoadFromArray(arr As Variant)
lst.List = arr
End Sub
Public Function GetSelectedRowIndex() As Long
GetSelectedRowIndex = lst.ListIndex
End Function
Public Function GetSelectedRow() As Variant
If lst.ListIndex = -1 Then
GetSelectedRow = Empty
Else
Dim i As Long, tmp()
ReDim tmp(1 To lst.ColumnCount)
For i = 0 To lst.ColumnCount - 1
tmp(i + 1) = lst.List(lst.ListIndex, i)
Next
GetSelectedRow = tmp
End If
End Function
VB使い方例(UserForm)
' UserForm 上に ListBox: lstTable を配置
Private t As clsTableList
Private Sub UserForm_Initialize()
Set t = New clsTableList
t.Init Me.lstTable, 3
t.LoadFromRange Sheet1.Range("A1:C20")
End Sub
Private Sub cmdSelect_Click()
Dim idx As Long
idx = t.GetSelectedRowIndex()
If idx = -1 Then MsgBox "未選択": Exit Sub
Dim row As Variant
row = t.GetSelectedRow()
MsgBox "選択: " & row(1) & " | " & row(2) & " | " & row(3)
End Sub
VBclsDateRangePicker(日付範囲ピッカー)
概要
clsCalendar を2つ使って From/To を選択し、日付の整合チェックを行う小型 API。
クラスコード(Class Module 名:clsDateRangePicker)
Option Explicit
' Public API:
' Init hostFrom As MSForms.Frame, hostTo As MSForms.Frame
' Show / Hide
' GetRange -> (DateFrom As Date, DateTo As Date)
' Event RangeSelected(DateFrom, DateTo)
Public Event RangeSelected(ByVal d1 As Date, ByVal d2 As Date)
Private calFrom As clsCalendar
Private calTo As clsCalendar
Private dateFrom As Date, dateTo As Date
Public Sub Init(hostFrom As MSForms.Frame, hostTo As MSForms.Frame)
Set calFrom = New clsCalendar
calFrom.Init hostFrom, Date
Set calTo = New clsCalendar
calTo.Init hostTo, Date
' 別途、各 cal の DateSelected を受け取る仕組みが必要
End Sub
Public Sub HandleFromSelected(ByVal d As Date)
dateFrom = d
If dateTo <> 0 Then ValidateAndRaise
End Sub
Public Sub HandleToSelected(ByVal d As Date)
dateTo = d
If dateFrom <> 0 Then ValidateAndRaise
End Sub
Private Sub ValidateAndRaise()
If dateFrom > dateTo Then
MsgBox "開始日が終了日より後です。"
Exit Sub
End If
RaiseEvent RangeSelected(dateFrom, dateTo)
End Sub
Public Function GetRange() As Variant
Dim out(1 To 2) As Date
out(1) = dateFrom
out(2) = dateTo
GetRange = out
End Function
VB使い方(UserForm)
' UserForm に fraFromHost, fraToHost を配置
Private drp As clsDateRangePicker
Private Sub UserForm_Initialize()
Set drp = New clsDateRangePicker
drp.Init Me.fraFromHost, Me.fraToHost
End Sub
' それぞれの calendar ラベル Click から:
Private Sub dFrom_0_1_Click()
drp.HandleFromSelected DateSerial(Year(Date), Month(Date), 1) ' 実際は HandleLabelClick を経由
End Sub
Private Sub drp_RangeSelected(ByVal d1 As Date, ByVal d2 As Date)
MsgBox "範囲: " & Format(d1, "yyyy/mm/dd") & " - " & Format(d2, "yyyy/mm/dd")
End Sub
VBclsDropdownList(ドロップダウンつき一覧)
概要
コンボボックスで絞り込み、ListBox に結果を表示する小型コンポーネント。
クラスコード(Class Module 名:clsDropdownList)
Option Explicit
' Public API:
' Init cmb As MSForms.ComboBox, lst As MSForms.ListBox, srcRange As Range
' RefreshList
' GetSelected As Variant
Private cmbBox As MSForms.ComboBox
Private lstBox As MSForms.ListBox
Private src As Range
Public Sub Init(cmb As MSForms.ComboBox, lst As MSForms.ListBox, srcRange As Range)
Set cmbBox = cmb
Set lstBox = lst
Set src = srcRange
' populate combo with distinct values from 2nd column (example)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim r As Range
For Each r In src.Rows
dict(r.Cells(1, 2).Value) = 1
Next
cmbBox.Clear
cmbBox.AddItem ""
Dim k
For Each k In dict.Keys
cmbBox.AddItem k
Next
RefreshList
End Sub
Public Sub RefreshList()
Dim key As String: key = cmbBox.Value
Dim buf(), cnt As Long
Dim r As Range, matchCount As Long
matchCount = 0
For Each r In src.Rows
If key = "" Or r.Cells(1, 2).Value = key Then
matchCount = matchCount + 1
ReDim Preserve buf(1 To matchCount, 1 To src.Columns.Count)
Dim c As Long
For c = 1 To src.Columns.Count
buf(matchCount, c) = r.Cells(1, c).Value
Next
End If
Next
If matchCount > 0 Then
lstBox.List = buf
Else
lstBox.Clear
End If
End Sub
Public Function GetSelected() As Variant
If lstBox.ListIndex = -1 Then
GetSelected = Empty
Else
Dim out()
ReDim out(1 To lstBox.ColumnCount)
Dim i As Long
For i = 0 To lstBox.ColumnCount - 1
out(i + 1) = lstBox.List(lstBox.ListIndex, i)
Next
GetSelected = out
End If
End Function
VB使い方(UserForm)
' Place ComboBox: cmbFilter, ListBox: lstFiltered
Private dd As clsDropdownList
Private Sub UserForm_Initialize()
Set dd = New clsDropdownList
dd.Init Me.cmbFilter, Me.lstFiltered, Sheet1.Range("A1:C100")
End Sub
Private Sub cmbFilter_Change()
dd.RefreshList
End Sub
VB配布用パッケージ化の注意(短く)
- 各クラスは ホスト側のコントロール命名規則(例:ラベル名
d_r_c)に依存する部分があるため、ドキュメント(命名規則)を同梱してください。 - カレンダー等のクリックイベントは動的生成ラベルの Click を捕捉する仕組み(ラッパークラス + WithEvents)を推奨します。サンプルにそのラッパー実装を追加すると即戦力になります。
Scripting.Dictionaryを使う場合はMicrosoft Scripting Runtimeを参照するか、CreateObjectを使って参照不要にしてください(上のサンプルでは後者を使っています)。


