和風スパゲティのレシピ

日本語でコーディングするExcelVBA

95本目:図形のテキストを検索するフォーム作成

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

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

出題:図形のテキストを検索するフォーム作成

Activeブックの全シート・全図形のテキストから指定の文字列を探すフォームを作成。
「検索」で「検索文字列」を含む図形をリスト表示。
シート名!左上セル番地;テキスト文字列 ←列幅は随意
表示されたリストをクリック選択したら当該図形を選択してください。

図形テキスト検索フォーム

◇ 出題ページはこちら

ソースコード

サンプルファイルはこちら
【和風スパ】VBA100本ノック#095(図形のテキストを検索するフォーム作成).xlsm

モジュール一覧

UF_ユーザーフォーム表示

Option Explicit

' 実行プロシージャ
Sub ★図形テキスト検索起動()
    Call UF図形テキスト検索.Show(vbModeless)
End Sub

 

UF図形テキスト検索

Option Explicit

' Dic内の配列レイアウト
Enum CNoLBox図形リスト
    設置場所 = 0
    図形テキスト
    図形オブジェクト
End Enum

' ListBox用Dictionary
' Key:ListBoxIndex Item:「図形位置,図形テキスト,オブジェクト本体」の一次元配列
Private Dic図形リスト As Dictionary

' 対象ブックと検索ワード
Private wb対象ブック As Workbook
Private key検索文字列 As String

' 検索ボタン実行
Private Sub btn検索ボタン_Click()

    key検索文字列 = TextBox検索文字列.Text
    If key検索文字列 = "" Then Exit Sub
    
    ' ■ 検索結果をDictionaryに格納
    Call 対象図形を検索してDictionaryに格納する
    
    If Dic図形リスト.Count = 0 Then
        ListBox図形リスト.Clear
    End If
    
    ' ListBoxにDictionaryを反映
    ListBox図形リスト.List = GetArr二次元配列←ジャグ配列(Dic図形リスト.Items)

End Sub

' 検索結果をDictionaryに格納
Private Sub 対象図形を検索してDictionaryに格納する()
    
    Dic図形リスト.RemoveAll
    
    Dim wb対象ブック As Workbook
    Set wb対象ブック = ActiveWorkbook
    
    ' 全シート・全図形をループ
    Dim ws As Worksheet
    For Each ws In wb対象ブック.Worksheets
        
        Dim shp As Shape
        For Each shp In ws.Shapes
        
            ' テキストがあり、かつ検索文字列を含むか判定
            If shp.TextFrame2.HasText Then
                If InStr(shp.TextFrame2.TextRange.Text, key検索文字列) > 0 Then
            
                    ' 図形位置,図形テキスト,オブジェクト本体 を一次元配列に
                    Dim Arr各図形情報(0 To 2): Erase Arr各図形情報
                    Arr各図形情報(CNoLBox図形リスト.設置場所) _
                        = ws.Name & "!" & shp.TopLeftCell.Address(False, False)
                    Arr各図形情報(CNoLBox図形リスト.図形テキスト) _
                        = 文中改行を指定文字に置き換える(shp.TextFrame2.TextRange.Text, "<br>")
                    Set Arr各図形情報(CNoLBox図形リスト.図形オブジェクト) = shp
            
                    ' Dictionaryに格納(Key:ListBoxIndex Item:一次元配列)
                    Dic図形リスト.Add Dic図形リスト.Count, Arr各図形情報
                
                End If
            End If
        
        Next
        
    Next
    
End Sub

' 図形リスト(ListBox)の選択
Private Sub ListBox図形リスト_Click()

    Dim R_選択行 As Long
    R_選択行 = ListBox図形リスト.ListIndex

    Dim 対象図形 As Shape
    Set 対象図形 = Dic図形リスト.Item(R_選択行)(CNoLBox図形リスト.図形オブジェクト)
    
    ' 対象の図形を選択
    対象図形.Parent.Activate
    対象図形.Select

End Sub

' コンストラクタ
Private Sub UserForm_Initialize()
    
    ListBox図形リスト.ColumnCount = 2
    ListBox図形リスト.ColumnWidths = "100,1000"
    
    Set Dic図形リスト = New Dictionary
    
End Sub

 

Ut_汎用関数

Option Explicit

' 改行文字の置き換え
Function 文中改行を指定文字に置き換える(元テキスト As String, Optional 置換テキスト As String = " ") As String

    Dim 結果テキスト As String
    結果テキスト = Replace(元テキスト, vbCrLf, 置換テキスト)
    結果テキスト = Replace(結果テキスト, vbCr, 置換テキスト)
    結果テキスト = Replace(結果テキスト, vbLf, 置換テキスト)

    文中改行を指定文字に置き換える = 結果テキスト

End Function

' 配列の要素数の取得
Function Count配列の要素数(Arr, Optional 次元 = 1) As Long
    Count配列の要素数 = UBound(Arr, 次元) - LBound(Arr, 次元) + 1
End Function

' 二次元配列←ジャグ配列
Function GetArr二次元配列←ジャグ配列(Arrジャグ配列 As Variant) As Variant
    
    Dim i As Long, j As Long ' ジャグ配列のインデックス
    Dim R As Long, C As Long ' 二次元配列のインデックス

    Dim Arr出力配列()

    Dim 最大要素数 As Long: 最大要素数 = 0

    ' 配列内の配列の中で最も大きい要素数を取得
    For i = LBound(Arrジャグ配列) To UBound(Arrジャグ配列)
        
        Dim 現要素数 As Long: 現要素数 = Count配列の要素数(Arrジャグ配列(i))
        If 現要素数 >= 最大要素数 Then 最大要素数 = 現要素数

    Next
    
    ' 出力二次元配列を準備
    ReDim Arr出力配列(1 To Count配列の要素数(Arrジャグ配列), 1 To 最大要素数)
    
    ' 全要素を代入
    R = 1
    For i = LBound(Arrジャグ配列) To UBound(Arrジャグ配列)
        C = 1
        For j = LBound(Arrジャグ配列(i)) To UBound(Arrジャグ配列(i))
            
            If IsObject(Arrジャグ配列(i)(j)) Then
                Set Arr出力配列(R, C) = Arrジャグ配列(i)(j)
            Else
                Let Arr出力配列(R, C) = Arrジャグ配列(i)(j)
            End If
            
            C = C + 1
        Next
        R = R + 1
    Next
    
    GetArr二次元配列←ジャグ配列 = Arr出力配列

End Function

解説

今回はユーザーフォームに関する問題でした。

私は普段ユーザーフォームをほとんど使っていないので、
ソースコードよりフォームの設計にかなり手間取りました笑


今回のマクロは図形のリストをListBoxに表示する必要がありますが、
これをDictionaryで実装しています。


ListBoxに記録していき、その図形を選択しようにも、

  • 指定された仕様が図形名称を表示しないリスト
  • そもそも図形は名前の重複を許すためNameプロパティではオブジェクトを特定できない

という問題があって記録された図形を選択できません。


これを解決するために、

  • まずは検索結果をDictionaryに入れる
  • KeyはListBoxのIndex
  • Itemは「図形の位置,記載テキスト,オブジェクト本体」の一次元配列
  • フォームのリストを選択した際はDictionaryから呼び出して選択

という方法をとっています。


Variant型の配列はSetを使えばオブジェクトそのものを格納できるため、
これならシェイプの特定問題(同名図形が存在できる)も関係ないですね。


今回DictionaryのItemには「String,String,Shape」の配列が入っていますが、
ListBoxに配列ごと代入する際ははみ出た部分は無視される便利な仕様があります。

よって、List側のColumnsを「1(0はじまりなので要素数は2)」としておけば、
第3要素にShapeオブジェクトが入っていても無視してフォームに表示してくれます。


それにしても、Itemが配列のDictionaryはItemsがジャグ配列(配列の配列)で、
セルやこういったListに出力するには二次元配列を使用するため、
ジャグ配列⇒二次元配列の汎用関数は本当に便利ですね。

このノックを解いていてもかなりの頻度で使用しています。


これを用意しておけばDictionaryに配列を入れた時の出力コードが簡単になり、
Redim Preserveみたいなクソコードに頼る必要がなくなります。

是非とも活用して、Array In Dictionary を便利に使い倒してください。
www.limecode.jp