ユーザーフォームへのコントロール配置の効率化に挑む
コントロールの配置の効率化
コントロールを規則的に配置する
前回、
は、コントロールを配列にぶち込んでまとめて処理、という方法を試みた。
配列にぶち込みさえすれば、後はループを回すだけなのだが、そもそも配列にぶち込む段階で、
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で全てのコントロールをループして、オブジェクト名が条件に合うときだけ処理したら良いと考えた。
準備
ユーザーフォームを挿入して、オブジェクト名を「TestForm」にし、コマンドボタンを10個配置した。画面では整然と並べているけれども、別にグチャグチャに並べておいても良い。
それぞれのコマンドボタンには、「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 列数]
で良いはずなんだが、その通りに式を書くと、
このような残念な形になってしまう。
よって、定数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
コードについては特に説明はいらないと思う。
実行結果
ボタンが美しく整列している。
おわりに
ただ、このやり方でも、そもそも各コマンドボタンにオブジェクト名をポチポチ書いていかないといけないのはメンドクサイ。配置するコントロールの数を指定して、一気に大量のコントロールを配置する方法もあるんだろうなあ……。
やっぱり、現状では何かの役には立ちそうもないねえ。