ユーザーフォームの処理でFor Eachの罠にハマった話

【スポンサーリンク】


f:id:teali_s:20220220202118p:plain

今までFor Eachを使っていて処理順に困ったことがなかったので、こんな仕様だったことを初めて知りました。


問題編

ユーザーフォームにトグルボタンを100個設置し、ボタン全てに名前を付け、ボタンと同じ名前をシートにも付ける。
ボタンを押して選択状態にし、処理実行したら、選択状態のボタンと同名のシートを別ブックからコピー。
という処理の中で、『選択状態のボタン』を探す処理をFor Eachで行っていた。

シートには決まった順番があり、それの通りにコピーする必要があるが、コピーの順番がおかしい箇所が発見された。
ボタンの名前に間違いはなく、フォーム上の設置個所も合っている。
タブオーダーの順かとも思ったが順番に問題はない。何故?

調べたところ、For Eachでコントロールを取得して処理する場合、処理の順番は「コントロールの作成順」になるらしい。

For Eachで取得して処理するテスト

フォームにトグルボタンを10個設置。
左側は作成順とボタン名を一致させ、右側は作成順とボタン名をバラバラにし(キャプションの括弧内が作成順)、タブオーダーはボタン名と一致させた。
(今回は選択状態関係なくボタン名を取得し、イミディエイトウィンドウに表示させる処理をしている)

Private Sub CommandButton1_Click() 'テスト
    Dim cont As Object

    For Each cont In Controls
        Debug.Print cont.Name
    Next
End Sub


f:id:teali_s:20220220195917j:plain
左側はボタン名が上から取得されている。
右側は上からではなく作成順で取得されている。

解決編

問題

ボタン名・シート名を処理したい順の連番にできれば早いが、今回は余計な情報を入れることはできない。
対象のボタンが100個あり、作成順を辿るのもかなり面倒なため、並べ替えて名前を変更するのも現実的ではない。
また、データが追加された場合のことを考えると他の手を使うべきかと思われる。

解決

Do Loopを使い、タブオーダー順にコントロールを取得する処理を作成した。

Do Loopで取得して処理するテスト

(本来はボタンが選択状態かの判定(If Me.Controls(cntC)=True Then)などを入れるが、テストなので省略)

Private Sub CommandButton2_Click() 'テスト2
    Dim cntT As Integer: cntT = 0 'タブオーダー用
    Dim cntC As Integer: cntC = 0 'コントロール用

    Do
        If Me.Controls(cntC).TabIndex = cntT Then 'コントロールと処理したいタブオーダー番号が一致
            Debug.Print Me.Controls(cntC).Name
            
            cntT = cntT + 1
            cntC = -1 'ループの最初に戻る(すぐ+1するのでここでは-1にする)
        End If

        cntC = cntC + 1 '次のコントロールへ
        If cntC = Me.Controls.Count Then cntC = 0 'コントロール数が最大まで行ったらループの最初に戻る
    Loop Until cntT = Me.Controls.Count '処理対象がなくなったら(タブオーダー用変数がコントロール数と同じになったら)終わり
End Sub


f:id:teali_s:20220220200013j:plain
Do Loopでフォーム内のコントロールを全てチェック。
タブオーダー変数の値(cntT)と、コントロールのタブオーダー番号(Me.Controls(cntC).TabIndex)が一致したら処理。
タブオーダー変数の値を増やして処理対象を変更し、コントロールはまた最初から検索。

こうすることで右側も作成順ではなくタブオーダー順になり、ボタン名に関係なく好みの順に処理することができる。

【スポンサーリンク】