ユーザーフォームへのコントロール配置の効率化に挑む

コントロールの配置の効率化

コントロールを規則的に配置する

前回、

akashi-keirin.hatenablog.com

は、コントロールを配列にぶち込んでまとめて処理、という方法を試みた。

配列にぶち込みさえすれば、後はループを回すだけなのだが、そもそも配列にぶち込む段階で、

Dim btns(2) As CommandButton
Set btns(0) = Me.BtnLeft
Set btns(1) = Me.BtnCenter
Set btns(2) = Me.BtnRight

こんな書き方をするなんて、エレガントさのかけらもない。もっと言えば、ダサい。

かと言って、Array関数を使っても、

Dim btns As Variant
btns = Array(Me.BtnLeft, Me.BtnCenter, Me.BtnRight)

になるだけなので、ダサいことには違いない。

オブジェクト名を変数で合成して呼び出せるか

分かりにくい見出しだが、要するに、

オブジェクト名を「Object1」、「Object2」、……とかにしておいて、「Object & i」とかで指定できるか

ということなんだが、当然そんなことはできない。

For Each を使えばいいんじゃね?

オブジェクト名は、Control型オブジェクトのNameプロパティで取得できるから、For Each XX In Controlsで全てのコントロールをループして、オブジェクト名が条件に合うときだけ処理したら良いと考えた。

準備

f:id:akashi_keirin:20170923120516j:plain

ユーザーフォームを挿入して、オブジェクト名を「TestForm」にし、コマンドボタンを10個配置した。画面では整然と並べているけれども、別にグチャグチャに並べておいても良い。

f:id:akashi_keirin:20170923120527j:plain

それぞれのコマンドボタンには、「Btn01」~「Btn10」と通し番号付きのオブジェクト名を付けておく。めんどくさいけど。

あとは、コーディング。

リスト1 フォームモジュール
Option Explicit

Private numOfBtns_ As Integer

Public Sub init(ByVal btnHeight As Double, _
                ByVal btnWidth As Double, _
                ByVal offsetBtnsRow As Double, _
                ByVal offsetBtnsCol As Double, _
                ByVal numOfCols As Integer, _
                ByVal btnName As String)    '……(*)'
  Dim ctrl As Control
  Dim i As Integer
  numOfBtns_ = 0
  For Each ctrl In Controls    '……(1)'
    If Left(ctrl.name, 3) = "Btn" Then    '……(2)'
      numOfBtns_ = numOfBtns_ + 1
      With ctrl    '……(3)'
        .Height = btnHeight
        .Width = btnWidth
        i = numOfBtns_
        .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
        .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
        .Caption = btnName & StrConv(i, vbWide) & "号"
      End With
    End If
  Next
  With Me    '……(4)'
    .Height = offsetBtnsRow _
              + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
              + TOP_BOTTOM_MARGIN
    .Width = offsetBtnsCol + (btnWidth + offsetBtnsCol) * numOfCols + offsetBtnsCol
  End With
End Sub

まずは(*)のところ。まさに引数祭りwww

ByVal btnHeight As Double, _
ByVal btnWidth As Double, _
ByVal offsetBtnsRow As Double, _
ByVal offsetBtnsCol As Double, _
ByVal numOfCols As Integer, _
ByVal btnName As String

上から順に、

  • btnHeight――ボタンの高さ
  • btnWidth――ボタンの幅
  • offsetBtnsRow――タテ方向のボタンとボタンの間隔
  • offsetBtnsCol――ヨコ方向のボタンとボタンの間隔
  • numOfCols――1行あたりのボタンの数
  • btnName――ボタンの名前

とまあ、これだけの引数を渡して初期化することにしている。

(1)の

For Each ctrl In Controls

からNextまでのブロックで、フォーム内の全コントロールをループ。

(2)の

If Left(ctrl.name, 3) = "Btn" Then

でコントロールのオブジェクト名を調べる。今回配置したコマンドボタンは、全て最初の3字が「Btn」となっているので、この条件がTrueになるということは、Control型変数ctrlにコマンドボタンがぶち込まれているということ。

(3)からの8行

 With ctrl
  .Height = btnHeight
  .Width = btnWidth
  i = numOfBtns_
  .Top = offsetBtnsRow + ((offsetBtnsRow + btnHeight) * ((i - 1) \ numOfCols))
  .Left = offsetBtnsCol + ((offsetBtnsCol + btnWidth) * ((i - 1) Mod numOfCols))
  .Caption = btnName & StrConv(i, vbWide) & "号"
End With

は、ボタンに対する処理。

Top及びLeftプロパティでは、一見ややこしそうな計算をしているようだが、中身は実に単純。図を書いてみたら良いと思う。説明はめんどくさいので省略。

(4)からの6行(実際は4行)

With Me
  .Height = offsetBtnsRow _
            + ((btnHeight + offsetBtnsRow) * (((numOfBtns_ - 1) \ numOfCols) + 1)) _
            + TOP_BOTTOM_MARGIN
  .Width = offsetBtnsCol + (btnWidth + offsetBtnsCol) * numOfCols + offsetBtnsCol
End With

は、フォーム本体の設定。

高さ、幅ともに、
[上下 or 左右の余白]プラス
([ボタンの高さ or 幅]+[ボタン間の隙間の寸法])×[行数 or 列数]
で良いはずなんだが、その通りに式を書くと、

f:id:akashi_keirin:20170923120535j:plain

このような残念な形になってしまう。

よって、定数TOP_BOTTOM_MARGIN(中身は「20」)をプラスすることにしている。

実行

次のコードで実行する。

スト2 標準モジュール
Public Sub controlTest()
  Dim testFrm As TestForm
  Set testFrm = New TestForm
  With testFrm
    .init btnHeight:=20, _
          btnWidth:=50, _
          offsetBtnsRow:=10, _
          offsetBtnsCol:=5, _
          numOfCols:=5, _
          btnName:="アホ"
    .Caption = "ち~んw"
    .Show
  End With
End Sub

コードについては特に説明はいらないと思う。

実行結果

f:id:akashi_keirin:20170923120545j:plain

ボタンが美しく整列している。

おわりに

ただ、このやり方でも、そもそも各コマンドボタンにオブジェクト名をポチポチ書いていかないといけないのはメンドクサイ。配置するコントロールの数を指定して、一気に大量のコントロールを配置する方法もあるんだろうなあ……。

やっぱり、現状では何かの役には立ちそうもないねえ。

@akashi_keirin on Twitter