これは「VBA Advent Calendar 2025」の2日目です
https://adventar.org/calendars/11327
MsForms.LabelのAutoSizeプロパティについて
ユーザーフォームに配置したラベルのAutoSizeがTrueの時、そのサイズはCaptionの内容によって変動します。あまり意識することが無かったのですが、このサイズ変更はラベルのWidthだけでなく、Heightにも及びます。
実験

このようなフォームを用意しました。ボタン押下時のコードは以下の通り。
Private Sub CommandButton1_Click()
Label1.Caption = TextBox1.Text
Label2.Caption = "高さ:" & Label1.Height
End Sub
テキストボックスの内容をラベルに反映し、AutoSizeによって変更された「高さ」を確認できます。
テスト1 半角英数
テスト2 日本語文字列
テスト3 絵文字を含む文字列

高さが18.75になりました。
テキストボックス内のフォントサイズも大きくなっているように見えます。
テスト4 文字が空の場合
この仕様が原因で、ラベルを等間隔に隙間なく配置したくても、各コントロールに設定されるCaptionの影響で想定通りの間隔になりません。
今回はこの対策を考えてみます。
高さが勝手に変わる原因(ChatGPT調べ)
VBAのユーザーフォーム(MSForms)上で Label コントロールに AutoSize = True を設定している場合、「フォントサイズが同じでも文字の種類によって高さが変わる」現象は仕様的な問題に近いです。
特に "❓", "✔", "★" のような Unicode 絵文字・記号系文字を含むと、GDI側の文字描画エンジンが異なるフォント(フォールバックフォント)を内部的に使うため、行高さが変動します。この、元のフォントに存在しない文字を別フォントを使って描画することを「フォントフォールバック」と言うそうです。
🔍 なぜ高さが変わるのか
MSゴシックなどでも、
"A" や "あ" は MS ゴシックで描画されますが、
"❓" のような文字は MS UI Gothic や Segoe UI Symbol など別フォントに自動的に置き換えられます。
これにより、フォントメトリクス(行間・高さ)が異なってしまい、AutoSizeがその影響を受けてしまいます。
対策
①:AutoSizeを使わず、固定高さにする
最もシンプルですが、ユーザー側にフォントサイズを自由に設定させたいツールを設計する場合には不都合が生じます。
②:フォールバックしないフォントに統一する
Unicode記号を多く含む場合、フォントを Segoe UI Symbol に統一すると行高さが安定します。ただし、日本語が入ると逆に崩れるので日本語環境だとこの手法は使えないことになります。
③:行間を吸収するためのダミー領域を設ける
ラベルを動的に配置していくとき、行間を含めた高さを定めておき、Caption設定後にその高さを調べ、それを元に行間の幅を動的に割り出すことで全体としての高さをキープすることができます。これは割と現実的かもしれません。
④:フォントフォールバックを抑制する(高度)
これはWindowsレベルの話になりますが、
フォーム上で使用するフォントを記号を含む文字セットに対応した等幅フォントに差し替えることで多少抑制可能です。
- 「MS UI Gothic」より「Meiryo UI」の方が安定しやすい。
- 「Yu Gothic UI」も比較的高さが変動しにくい。
※これは高さ対策としては有用ですが、肝心の「等幅フォント」ありませんでした。等幅でなくても良い環境なら効果的かもしれません。
⑤:WindowsAPIで非対応フォントを別文字に置換
Debug.Printでイミディエイトウィンドウに絵文字などの非対応の文字を出力すると?に置換されます。これを実現しているAPI関数を流用することで、フォントフォールバックを抑制することができます。
Private Declare PtrSafe Function WideCharToMultiByte Lib "kernel32" ( _
ByVal CodePage As Long, _
ByVal dwFlags As Long, _
ByVal lpWideCharStr As LongPtr, _
ByVal cchWideChar As Long, _
ByVal lpMultiByteStr As LongPtr, _
ByVal cbMultiByte As Long, _
ByVal lpDefaultChar As LongPtr, _
ByVal lpUsedDefaultChar As LongPtr) As Long
Private Const CP_ACP As Long = 0 'システム既定のANSIコードページ
Function NormalizeToAnsi(Text As String) As String
Dim ansiBytes() As Byte
Dim outSize As Long
'1回目の呼び出しで必要なサイズを取得
outSize = WideCharToMultiByte(CP_ACP, 0, StrPtr(Text), -1, 0, 0, 0, 0)
If outSize = 0 Then
NormalizeToAnsi = Text
Exit Function
End If
ReDim ansiBytes(1 To outSize)
'実際に変換
WideCharToMultiByte CP_ACP, 0, StrPtr(Text), -1, VarPtr(ansiBytes(1)), outSize, 0, 0
'戻り値へ(ANSI→Unicodeへ戻す)
NormalizeToAnsi = StrConv(ansiBytes, vbUnicode)
End Function
'利用サンプル
Private Sub CommandButton1_Click()
'Label1.Caption = TextBox1.Text 'これを
Label1.Caption = NormalizeToAnsi(TextBox1.Text) 'こうする
Label2.Caption = "高さ:" & Label1.Height
Debug.Print Label1.Caption
End Sub

これならフォントフォールバックは発生しないため、ラベルの高さは変動しません。そのかわり、図のように非対応の文字は??などの文字に置換されることになります。
今回、TreeViewを自作するにあたって各ノードの高さが不定になるのを抑制したかったので、絵文字表記を妥協し、この方法を採用しました。
参考:WideCharToMultiByte関数の解説
int WideCharToMultiByte(
UINT CodePage, // 1.変換先のコードページ(例:932=Shift-JIS、65001=UTF-8、0=ANSI)
DWORD dwFlags, // 2.各種フラグ(変換方法を制御)
LPCWCH lpWideCharStr, // 3.入力(ワイド文字列:UTF-16)
int cchWideChar, // 4.入力文字数(–1ならヌル終端まで)
LPSTR lpMultiByteStr, // 5.出力バッファ
int cbMultiByte, // 6.出力バッファのサイズ
LPCCH lpDefaultChar, // 7.変換できない文字の代用(NULLでシステム既定)
LPBOOL lpUsedDefaultChar // 8.代用文字が使われたかを返す
);
1.CodePage
「どの文字コードに変換するか」を指定します。
CodePage = 932(Shift-JIS)だと以下のような感じです。
| 文字 | UTF-16 | Shift-JIS対応? | 出力 |
|---|---|---|---|
| ❓ | 0x2753 | ×(非対応) |
"?" に置換(既定動作) |
| A | 0x0041 | ○ | 0x41 |
| 漢 | 0x6F22 | ○ | 0x8ACF |
2.dwFlags
Unicodeからマルチバイト文字列への変換挙動を制御するための複数のフラグが指定可能です。特に合成文字や変換不能文字の扱いに関与します。
以下に、代表的なフラグとその動作を詳しく解説します。
🧩 dwFlagsで指定可能なフラグ一覧と詳細
| フラグ名 | 説明 | 使用条件・補足 |
|---|---|---|
WC_NO_BEST_FIT_CHARS |
Unicode文字が直接マルチバイトに変換できない場合、既定の文字に置き換える。 |
lpDefaultCharが指定されているときに有効。 |
WC_COMPOSITECHECK |
合成文字(基本文字+送りなし文字)を構成済み文字に変換する。 | 以下の3つのフラグと併用可能。 |
WC_DISCARDNS |
合成文字の送りなし文字(Nonspacing character)を破棄する。 | |
WC_SEPCHARS |
合成文字を基本文字と送りなし文字に分離して変換する。 | 既定の動作。 |
WC_DEFAULTCHAR |
合成文字が構成済み文字に変換できない場合、既定の文字に置き換える。 |
🧠 フラグの組み合わせ例と挙動
-
WC_COMPOSITECHECK | WC_SEPCHARS
→ 合成文字を分離して変換(例:é → e + アクサングラーブ) -
WC_COMPOSITECHECK | WC_DISCARDNS
→ 合成文字の送りなし文字を破棄(例:é → e) -
WC_COMPOSITECHECK | WC_DEFAULTCHAR
→ 合成文字が構成済みに変換できない場合、既定文字に置換(例:é → ?) -
WC_NO_BEST_FIT_CHARSのみ
→ 変換不能文字はすべて既定文字に(例:漢 → ?)
⚠️ 注意点
WC_COMPOSITECHECKを指定しない場合、WC_DISCARDNSなどは無視されます。
lpDefaultCharとlpUsedDefaultCharをNULLにすると、処理速度が向上しますが、変換不能文字の扱いが不明瞭になります。
逆変換(MultiByteToWideChar)との整合性を重視する場合、WC_NO_BEST_FIT_CHARSの使用が推奨されます。
3.lpWideCharStr
変換したい文字列のポインタを渡します。VBAだとStrPtr(文字列)のようになります。
4.cchWideChar
渡した文字列のうち何文字目までを変換するか指定します。-1にすると、終端のNull文字も含めて変換します。
5.lpMultiByteStr
出力バッファです。ここに指定したアドレスに、変換後の文字列がバイト配列として書き込まれます。
Dim buffer(10) As Byte
のように準備したバイト配列を
VarPtr(buffer(0))
のように渡しますが、このバイトサイズは変換後のサイズを指定する必要があります。詳しくは後述の「戻り値」の項目を参照してください。
6.cbMultiByte
出力バッファのサイズを指定します。
UBound(buffer) + 1
のように指定します。
7.lpDefaultChar
変換できない文字を何に変換するかを指定します。Null文字(0)を渡すとVBAでは"?"に変換されます。他の文字に変換したい時は
Dim defaultChar(0) As Byte
defaultChar(0) = Asc("x") ' 既定文字を 'x' に設定
のように用意したバイト型配列の先頭要素を、VarPtr(defaultChar(0))のように渡します。
8.lpUsedDefaultChar
変換が行われたかどうかを受け取るboolean型変数のポインタを渡します。
戻り値
変換後のデータのサイズを受け取ることができます。これを利用して、
Dim ansiBytes() As Byte
Dim outSize As Long
'1回目の呼び出し
outSize = WideCharToMultiByte(CP_ACP, 0, StrPtr(Text), -1, 0, 0, 0, 0)
ReDim ansiBytes(1 To outSize) '戻り値を利用して出力バッファのサイズを決める
'2回目の呼び出しで実際に変換後のバイト配列を取得する
WideCharToMultiByte CP_ACP, 0, StrPtr(Text), -1, VarPtr(ansiBytes(1)), outSize, 0, 0
このように、WideCharToMultiByteを2回実行することが常套手段となっているそうです。
変換後のデータを文字列として受け取る
WideCharToMultiByteを実行して得られるのはあくまでもByte型の配列であり、これをコード内で文字列として活用するためにはString型に変換しなければなりません。
これにはVBAではStrConv関数を活用するのが良いでしょう。
Dim 変換結果 As String
変換結果 = StrConv(ansiBytes, vbUnicode)
これらをまとめたものが、⑤の冒頭で提示したサンプルコードのNormalizeToAnsi関数になります。
WideCharToMultiByte関数適用例
自作TreeViewで、デスクトップ上のウィンドウのリストを列挙してみます。





