2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Excel VBA ユーザーフォームのLabelの"高さ"がCaptionの内容で変動する対策

Posted at

これは「VBA Advent Calendar 2025」の2日目です
https://adventar.org/calendars/11327

MsForms.LabelのAutoSizeプロパティについて

ユーザーフォームに配置したラベルのAutoSizeがTrueの時、そのサイズはCaptionの内容によって変動します。あまり意識することが無かったのですが、このサイズ変更はラベルのWidthだけでなく、Heightにも及びます。

実験

image.png
このようなフォームを用意しました。ボタン押下時のコードは以下の通り。

Private Sub CommandButton1_Click()
    Label1.Caption = TextBox1.Text
    Label2.Caption = "高さ:" & Label1.Height
End Sub

テキストボックスの内容をラベルに反映し、AutoSizeによって変更された「高さ」を確認できます。

テスト1 半角英数

image.png
ラベルのHeightは14.25になりました。

テスト2 日本語文字列

image.png
これも同じく14.25です。

テスト3 絵文字を含む文字列

image.png
高さが18.75になりました。
テキストボックス内のフォントサイズも大きくなっているように見えます。

テスト4 文字が空の場合

image.png
高さが11.25になりました。

この仕様が原因で、ラベルを等間隔に隙間なく配置したくても、各コントロールに設定される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で非対応フォントを別文字に置換

image.png

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

image.png
これならフォントフォールバックは発生しないため、ラベルの高さは変動しません。そのかわり、図のように非対応の文字は??などの文字に置換されることになります。
今回、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などは無視されます。

lpDefaultCharlpUsedDefaultCharNULLにすると、処理速度が向上しますが、変換不能文字の扱いが不明瞭になります。

逆変換(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で、デスクトップ上のウィンドウのリストを列挙してみます。

適用前

image.png
矢印の行の文字が不自然にずれているのが分かります。

適用後

image.png
絵文字が"??"に置換され、ラベルの高さを一定にキープすることができました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?