以下の内容はhttps://www.limecode.jp/entry/fungo/067-comboBox-listBoxより取得しました。


67本目:ComboBoxとListBox

Excel&VBA解説サイト「エクセルの神髄」様出題の問題集、
VBA100本ノック」に対する私の回答と解説のページです。

100本ノックの出題リストはこちらから
excel-ubara.com

出題:ComboBoxとListBox

#VBA100本ノック 67本目
ユーザーフォームのListBoxに、ComboBoxの値で「リスト」シートのA列を絞りこんだデータを表示します。
ListBoxにはB列~D列を列見出しを付けて表示してください。
適宜作業シートを使用。
※オブジェクト名は任意
※コンボは都道府県が設定済としてイベントの作成です。

ComboBoxとListBoxを使ったユーザーフォーム

◇ 出題ページはこちら

ソースコード

定義モジュール

Option Explicit

' リスト
Public Const R1stリスト = 2
Public Const C1stリスト = 1
Public Enum CNoリスト
    都道府県 = C1stリスト
    名前
    性別
    誕生日
End Enum
Public Const CLastリスト = CNoリスト.誕生日

' lst個人
Public Const R1stlst個人 = 2
Public Const C1stlst個人 = 1
Public Enum CNolst個人
    名前 = C1stlst個人
    性別
    誕生日
End Enum
Public Const CLastlst個人 = CNolst個人.誕生日

標準モジュール

Sub UserForm1を表示する()

    UserForm1.Show vbModeless

End Sub

Formモジュール(UserForm1)

Option Explicit

' コンボボックスの変更時
Private Sub cmb都道府県_Change()
    
    Dim tgt都道府県 As String: tgt都道府県 = cmb都道府県.Value
    
    ' リストボックス連動シートの更新
    Call リストを都道府県で抽出してlst個人シートへ出力する(tgt都道府県)

    ' リストボックスの更新
    With lst個人
        .ColumnCount = CLastlst個人
        .ColumnWidths = "100;30"
        .ColumnHeads = True
        .RowSource = GetRangeフィルターデータ部分(WSlst個人).Address(External:=True)
    End With

End Sub

' リストボックス連動シートの更新
Sub リストを都道府県で抽出してlst個人シートへ出力する(tgt都道府県 As String)
    
    ' シートのクリア
    Call 指定行より下をすべて削除する(WSlst個人, R1stlst個人)
    
    ' 対象件数の取得
    Dim count対象データ As Long
    count対象データ = Fx.CountIf(WSリスト.Columns(CNoリスト.都道府県), tgt都道府県)
    If count対象データ = 0 Then Exit Sub
    
    ' 出力用の配列を準備
    Dim Arr出力 As Variant
    ReDim Arr出力(1 To count対象データ, 1 To CLastlst個人)
    
    ' リスト全行をループ
    Dim R_リスト As Long
    Dim R_出力 As Long: R_出力 = 1
    For R_リスト = R1stリスト To Get最終行(WSリスト)
        
        ' 対象都道府県のデータを配列へ出力
        If WSリスト.Cells(R_リスト, CNoリスト.都道府県) = tgt都道府県 Then
            
            Arr出力(R_出力, CNolst個人.名前) = WSリスト.Cells(R_リスト, CNoリスト.名前)
            Arr出力(R_出力, CNolst個人.性別) = WSリスト.Cells(R_リスト, CNoリスト.性別)
            Arr出力(R_出力, CNolst個人.誕生日) = WSリスト.Cells(R_リスト, CNoリスト.誕生日)
            R_出力 = R_出力 + 1
            
        End If
        
    Next ' リスト全行をループ

    ' 配列をシートに出力
    Call 二次元配列をセルに出力する(WSlst個人.Cells(R1stlst個人, C1stlst個人), Arr出力)

End Sub

汎用関数モジュール

Option Explicit

' WorksheetFunctionの短縮取得
' 参考:https://www.limecode.jp/entry/utility/shortcall-worksheetfunction
Function Fx() As WorksheetFunction
    Set Fx = WorksheetFunction
End Function

' 指定行より下の削除
' 参考:https://www.limecode.jp/entry/utility/delete-rows-below-to-last
Sub 指定行より下をすべて削除する(対象シート As Worksheet, 指定行 As Long)
    With 対象シート
    
        Call フィルターをクリアする(対象シート)
        
        .Range(.Rows(指定行), .Rows(.Rows.Count)).Delete
        
    End With
End Sub

' フィルターのクリア
' ◆ ブログ未掲載
Sub フィルターをクリアする(対象シート As Worksheet)
    If 対象シート.AutoFilterMode = True Then
        If 対象シート.AutoFilter.FilterMode = True Then
            対象シート.AutoFilter.ShowAllData
        End If
    End If
End Sub

' 最終行の取得
' 参考:https://www.limecode.jp/entry/library/get-lastrow-lastcolumn
Function Get最終行(指定オブジェクト As Variant, Optional ByVal C As Long = -1) As Long

    ' 渡されたオブジェクトからセル範囲を取得
    Dim 対象セル範囲 As Range
    Select Case TypeName(指定オブジェクト)
    Case "Range"
        If 指定オブジェクト.Cells.CountLarge = 1 Then ' 単独セルにはCurrentRegionを取る
            Set 対象セル範囲 = 指定オブジェクト.CurrentRegion
        Else
            Set 対象セル範囲 = 指定オブジェクト
        End If
    Case "Worksheet"
        Set 対象セル範囲 = 指定オブジェクト.UsedRange
    Case "AutoFilter", "ListObject"
        Set 対象セル範囲 = 指定オブジェクト.Range
    Case Else
        Err.Raise 1000, , "対象外のオブジェクト「" & TypeName(指定オブジェクト) & "」が指定されました。"
    End Select

    ' エリアの最終行を取得
    Get最終行 = 対象セル範囲.Rows.Count + 対象セル範囲.Row - 1

    ' 列が指定されていればその列の入力最終行を取得
    If C <> -1 Then
        Do While 対象セル範囲.Worksheet.Cells(Get最終行, C) = ""
            Get最終行 = Get最終行 - 1
            If Get最終行 < 対象セル範囲.Row Then
                Get最終行 = 0
                Exit Function
            End If
        Loop
    End If

End Function

' フィルターデータ部
' 参考:https://www.limecode.jp/entry/utility/get-autofilter-datarange
Function GetRangeフィルターデータ部分(指定シート As Worksheet) As Range
    If 指定シート.AutoFilterMode = False Then Exit Function

    Dim フィルターデータ部 As Range
    Set フィルターデータ部 = 指定シート.AutoFilter.Range

    If フィルターデータ部.Rows.Count = 1 Then Exit Function

    Set フィルターデータ部 = フィルターデータ部.Offset(1)
    Set フィルターデータ部 = フィルターデータ部.Resize(フィルターデータ部.Rows.Count - 1)

    Set GetRangeフィルターデータ部分 = フィルターデータ部

End Function

' 二次元配列 → セル
' 参考:https://www.limecode.jp/entry/utility/output-array-to-range
Sub 二次元配列をセルに出力する(出力始点セル As Range, Arr出力配列 As Variant)

    出力始点セル.Resize(Count配列の要素数(Arr出力配列, 1) _
                              , Count配列の要素数(Arr出力配列, 2)).Value = Arr出力配列

End Sub

' 配列の要素数の取得
' 参考:https://www.limecode.jp/entry/syntax/ubound-lbound-count-array-elemens
Function Count配列の要素数(Arr, Optional 次元 = 1) As Long
    Count配列の要素数 = UBound(Arr, 次元) - LBound(Arr, 次元) + 1
End Function

解説

ComboBoxとListBoxを使ったユーザーフォームの問題です。


ListBoxの表示を簡単に実行する方法として、
出力するリストを二次元配列にしてListBox.Listに直接代入する方法があります。

対象ListBox.List = 二次元配列

 
しかしこの方法は「見出し」機能を使うことが出来ないため、
今回の問題のように見出しを使う場合は、出力セル範囲を指定するしかありません。

よって問題文にある通り、作業シートを使用して対応します。


今回は個人名のデータだったため、件数が膨大でもラグが出てしまわないよう、
リストシート⇒作業シートの出力は、今回は配列を使用して高速化しました。

割と単純なコードで記述できますので、転記マクロ高速化の参考にしてみてください。




以上の内容はhttps://www.limecode.jp/entry/fungo/067-comboBox-listBoxより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14