今回の記事は、Excelの表から各列の最大文字数を求め、その文字数に基づいて適切なサイズのテキストボックスを新規フォームに配置するというものである。
記事タイトルからすると既存フォームに対して実行するように思われたかもしれない。
なかなかキャッチーで、かつ簡潔なタイトルが思い浮かばなかったのだ。
だましてごめん。
目次
- 前談
- 列ごとの最大文字数を取得する「各列最大文字数」関数を作る。
- 「各列最大文字数」関数の使い方
- フォームの自動生成
- テキストボックスのサイズを算出する
- 完成コード
- おまけ
- フォーム関連のオススメ書籍
前談
時々、Excel表をユーザーフォームで扱いたくなる。
特に列数が多いと1レコード見るのに横スクロールで行ったり来たりする必要があるので、フォームがあると便利だ。
イメージとしてはこんな感じ。(普通はラベルも付けるけど今回はパス)
しかし、表に合わせて最適なフォームをデザインするのは結構面倒だ。何が一番面倒かというと、テキストボックスのサイズと配置である。
最近VBComponentの操作もこなれてきたので、ユーザーフォームのデザインもVBAでやってしまおう。
列ごとの最大文字数を取得する「各列最大文字数」関数を作る。
テキストボックスの大まかなサイズは、中に入れる文字数で決まる。
そこで、各列の最大文字数を取得する関数を作成することにした。
一旦、普通のプロシージャとして作成し、あとで関数化する。
まずは、選択範囲を配列に入れて、ループで列を移動してみよう。
コードは次のとおり。
Sub 各列最大文字数_未完() Dim Arr: Arr = Selection.Value For i = LBound(Arr, 2) To UBound(Arr, 2) Debug.Print Arr(1, i) Next End Sub
このように表全体を選択し、
上記のマクロを実行すると、イミディエイトウィンドウに次のように出力される。
No 列1 列2 列3 列4 列5
次に、これを改良し、列ごとに要素の文字長を出力してみる。
Sub 各列最大文字数_未完2() Dim Arr: Arr = Selection.Value For i = LBound(Arr, 2) To UBound(Arr, 2) Debug.Print Arr(1, i) 'ヘッダの出力 For j = LBound(Arr, 1) To UBound(Arr, 1) Debug.Print Len(Arr(j, i)); Next Debug.Print '改行 Next End Sub
結果はこうなる。
No 2 1 1 1 1 1 1 1 1 1 2 列1 2 16 13 14 9 9 17 5 17 18 16 列2 2 52 62 99 53 69 68 74 57 74 63 列3 2 1 1 3 3 2 3 3 1 3 3 列4 2 4 1 3 1 1 4 2 1 2 2 列5 2 192 167 55 283 200 157 123 41 238 143
あとは最大数を取るようにして、関数化しておく。
Function 各列最大文字数(取得範囲 As Range) As String Dim ret As String Dim Arr: Arr = 取得範囲 For i = LBound(Arr, 2) To UBound(Arr, 2) ret = ret & Arr(1, i) & " " Dim MaxLen: MaxLen = 0 For j = LBound(Arr, 1) To UBound(Arr, 1) If MaxLen < Len(Arr(j, i)) Then MaxLen = Len(Arr(j, i)) Next ret = ret & CStr(MaxLen) & "," Next ret = Left(ret, Len(ret) - 1) 各列最大文字数 = ret End Function
「各列最大文字数」関数の使い方
表を選択した状態で、Selectionを引数として関数「各列最大文字数」呼び出してみる。
Sub test() Debug.Print 各列最大文字数(Selection) End Sub
すると、以下のような文字列が取得できる。
No 2,列1 18,列2 99,列3 3,列4 4,列5 283
この文字列は、2種類の区切り文字(カンマとスペース)がある。
まず最初にカンマで区切ると、
Sub test2() For Each x In Split(各列最大文字数(Selection), ",") Debug.Print x Next End Sub
こうなる。
No 2 列1 18 列2 99 列3 3 列4 4 列5 283
これを更にSplitするとまた配列ができるので、直接添え字を書いて出力してみる。
Sub test3() For Each x In Split(各列最大文字数(Selection), ",") Debug.Print "「"; Debug.Print Split(x)(0); Debug.Print "」の最大文字数は「"; Debug.Print Split(x)(1); Debug.Print "」文字です。" Next End Sub
※区切り文字のデフォルトがスペースなので、スペース区切りの場合は第二引数を書かなくて良い。
結果はこのようになる。
「No」の最大文字数は「2」文字です。 「列1」の最大文字数は「18」文字です。 「列2」の最大文字数は「99」文字です。 「列3」の最大文字数は「3」文字です。 「列4」の最大文字数は「4」文字です。 「列5」の最大文字数は「283」文字です。
フォームの自動生成
ここからは、以下2点の参照設定が必要になる。
- Microsoft Forms 2.0 Object Library
- Microsoft Visual Basic for Applications Extensibility 5.3
また、マクロのセキュリティ設定でVBA プロジェクト オブジェクト モデルへのアクセスを信頼するのチェックが必要。
(…最近毎回この文言書いている気がする。なんか省力化したい。)
フォームの生成自体はお決まりのパターンなので、以下のサンプルを参考に、あとはループで回すなりなんなりといったところ。
Sub フォーム生成() Dim f As UserForm Set f = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm).Designer Dim C As Control Set C = f.Controls.Add("Forms.TextBox.1") C.Top = 10 C.Left = 20 C.Height = 15 C.Width = 300 End Sub
実行すると、こんな感じでフォームが出来上がる。
残念ながらフォームそのもののサイズをマクロで変更する方法は見つかってないので、マウスで端のハンドルを掴んでサイズ変更しよう。
テキストボックスのサイズを算出する
列ごとの最大文字数の求め方が分かったので、テキストボックスのサイズはその文字数を基準に決めれば良い。
まず一行に何文字入れるかを決める。
これは読みやすい文書の文字数ということでいくつかネットに模範解答が出ている。
https://www.okidata.co.jp/special/tips1/tips10a.htmlwww.okidata.co.jp
ということで、35文字を採用した。
行数は文字数を35で割れば算出できるが、これも画面サイズの都合があるので表示できる行数は制限しておきたい。
今回は適当に最大7行としておいた。(根拠はない)
コードにするとこんな感じ。(断片なのでこれだけでは動かない)
Const 折り返し文字数 = 35 Const 最大表示行数 = 7 行数 = (文字数 \ 折り返し文字数) + 1 If 文字数 > 折り返し文字数 Then 文字数 = 折り返し文字数 If 行数 > 最大表示行数 Then 行数 = 最大表示行数
次に行の高さと文字の幅を調査する。
これは地道に検証を繰り返して、フィット感を確認し、文字幅を9、行高を9.2とした。
画面スケールに影響を受けそうなので、うまくフィットしない場合は別途調整が必要。
完成コード
以下が完成コード。
これだけ見る人もいるかもしれないので繰り返すと、
以下2点の参照設定が必要になる。
- Microsoft Forms 2.0 Object Library
- Microsoft Visual Basic for Applications Extensibility 5.3
また、マクロのセキュリティ設定でVBA プロジェクト オブジェクト モデルへのアクセスを信頼するのチェックが必要。
まずは冒頭で作った関数(以下)を張り付けて、
Function 各列最大文字数(取得範囲 As Range) As String Dim ret As String Dim Arr: Arr = 取得範囲 For i = LBound(Arr, 2) To UBound(Arr, 2) ret = ret & Arr(1, i) & " " Dim MaxLen: MaxLen = 0 For j = LBound(Arr, 1) To UBound(Arr, 1) If MaxLen < Len(Arr(j, i)) Then MaxLen = Len(Arr(j, i)) Next ret = ret & CStr(MaxLen) & "," Next ret = Left(ret, Len(ret) - 1) 各列最大文字数 = ret End Function
次に、表を選択した状態で、
以下のコードを実行する。
Sub 自動で最適なサイズのテキストボックスを配置() Dim 行別文字数 As String 行別文字数 = 各列最大文字数(Selection) Const 全角文字幅 = 9 Const 行高 = 9.2 Const 折り返し文字数 = 35 '文字 Const 最大表示行数 = 7 '行 Const コントロールマージン = 5 コントロールY座標 = 20 '(フォーム上端から) コントロールX座標 = 20 '(フォーム左端から) Dim f As UserForm Set f = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm).Designer For Each x In Split(行別文字数, ",") '文字数と行数を算出 文字数 = Split(x)(1) 行数 = (文字数 \ 折り返し文字数) + 1 If 文字数 > 折り返し文字数 Then 文字数 = 折り返し文字数 If 行数 > 最大表示行数 Then 行数 = 最大表示行数 'コントロールを配置 Dim C As Control Set C = f.Controls.Add("Forms.TextBox.1") With C .Name = "txt" & Split(x)(0) .Text = Split(x)(0) .Width = 15 + (文字数 * 全角文字幅) '15はサイズの微調整 If 行数 > 1 Then .MultiLine = True End If .Top = コントロールY座標 .Left = コントロールX座標 .Height = 5 + (行高 * 行数) '5はサイズの微調整 コントロールY座標 = コントロールY座標 + .Height + コントロールマージン End With Next End Sub
すると、こんな感じでフォームが表示されるので、
ハンドルを引っ張って適当なサイズに変更する。
これで完成。
なお、ラベルを配置したい場合はf.Controls.Add("Forms.Label.1")とする。
おまけ
こういう検証をしていると、フォームを作りすぎて消すのに困る。
削除用マクロも用意した。
実行すると「Are you sure?」と聞かれるので、sureと入力すると、そのワークブックにある全てのユーザーフォームが削除される。
ActiveWorkbookではなく、このマクロを配置したブックのフォームがすべて消えるので注意。
Sub 全フォーム削除() If Not "sure" = InputBox("Are you sure?") Then Exit Sub Dim x As VBComponent For Each x In ThisWorkbook.VBProject.VBComponents Set C = x If x.Type = vbext_ct_MSForm Then ThisWorkbook.VBProject.VBComponents.Remove (C) End If Next MsgBox "Done.", vbInformation End Sub
フォーム関連のオススメ書籍
知識ゼロから学ぶ Excel VBA プログラミング ユーザーフォーム&コントロール
- 作者: 横山達大
- 出版社/メーカー: 秀和システム
- 発売日: 2014/11/14
- メディア: Kindle版
- この商品を含むブログを見る
※書籍ではフォームデザイナではなく、FormのInitialize後に整列させる方法が紹介されている。
同じシリーズの以下もおススメ
アプリ作成で学ぶExcelVBAプログラミングユーザーフォーム&コントロール
- 作者: 横山達大
- 出版社/メーカー: 秀和システム
- 発売日: 2012/03/16
- メディア: 単行本
- 購入: 2人 クリック: 2回
- この商品を含むブログ (1件) を見る
VBAのフォーム関連ならこの2冊は外せない。
ただ2冊とも一から通しで読むような構成になっているので、それなりに根気がいる。
もう少しざくっと、リファレンス的に使いたいなら以下の書籍がおススメ
かんたんプログラミング Excel 2010 VBA コントロール・関数編
- 作者: 大村あつし
- 出版社/メーカー: 技術評論社
- 発売日: 2011/12/27
- メディア: 大型本
- この商品を含むブログ (1件) を見る
以上