フォームコントロールの内側サイズ
1.概要
ユーザーフォーム上に配置するコントロールの内、「Frame」と MultiPageの「Page」部分は操作するコントロールでは無く、他のコントロールの「ベース」となるものです。またUserForm自体もコントロールのベースとなっています。これら「UserForm」「Frame」「Page」には、他のコントロールには無いプロパティがあります。
全てのコントロールには、その寸法を取得/設定する「Width」「Heitht」というプロパティがあるのですが、「UserForm」「Frame」「Page」にはベースの役割をする上で必要な「InsideWidth」「InsideHeight」プロパティがあるのです。
今回は、この「InsideWidth」「InsideHeight」プロパティに注目します。
2.ユーザーフォーム(UserForm)
2ー1.WidthとHeight
まず「Width」と「Height」ですが、フォームの見た目の幅・高さでは無いようです。例えば図1のような設定でユーザーフォームを起動すると、フォームの左上角が「Excelのドキュメント座標原点」に一致します。
- '========== ⇩(1) フォーム設定(左上角を原点に合わせる) ============
- Private Sub UserForm_Initialize()
- Me.StartUpPosition = 0
- Me.Left = ActiveWindow.PointsToScreenPixelsX(0) * 0.75
- Me.Top = ActiveWindow.PointsToScreenPixelsY(0) * 0.75
- End Sub
02行目「Me.StartUpPosition = 0」で、フォームの起動位置を手動モードにします。
03行目「Me.Left = ActiveWindow.PointsToScreenPixelsX(0) * 0.75」では、フォームの左端の位置をExcelドキュメント座標原点(X:左右方向)に合わせます。なお「PointsToScreenPixelsX(0)」の単位はピクセルですので、フォームの位置指定に使用するポイント単位に変換するため「0.75」を掛け算します。なおこれは、Excelの表示倍率が100%の時です。
04行目「Me.Top = ActiveWindow.PointsToScreenPixelsY(0) * 0.75」では、フォームの上端の位置をExcelドキュメント座標原点(Y:上下方向)に合わせます。
一方、図2のような設定でユーザーフォームを起動すると、フォーム右下角が「Excelのドキュメント座標原点」に一致します。
- '========== ⇩(2) フォーム設定(右下角を原点に合わせる) ============
- Private Sub UserForm_Initialize()
- Me.StartUpPosition = 0
- Me.Left = ActiveWindow.PointsToScreenPixelsX(0) * 0.75 - Me.Width
- Me.Top = ActiveWindow.PointsToScreenPixelsY(0) * 0.75 - Me.Height
- End Sub
12行目「Me.StartUpPosition = 0」は図1と同じく、フォームの起動位置を手動モードにしています。
13行目「Me.Left = ActiveWindow.PointsToScreenPixelsX(0) * 0.75 - Me.Width」は、図1の03行目に対して「フォームの幅」を差し引いていますので、フォームの右端が原点に合うことになります。
14行目「Me.Top = ActiveWindow.PointsToScreenPixelsY(0) * 0.75 - Me.Height」は、図1の04行目に対して「フォームの高さ」を差し引いていますので、フォームの下端が原点に合うことになります。
13~14行目を合わせて、フォームの右下角を原点に合わせています。
図1の設定でフォームを起動した結果が図3の左側、図2の設定で起動した結果が右側です。
図3
図3でも分かるように、フォームの実際の左右端は見かけの左右端よりも少しだけ外側です。あたかも両端が透明になっているような形です。
また図3の右側では、フォームの下端も見かけより外側となっています。なお上端は、ほぼ見た目通りです。
2ー2.InsideWidthとInsideHeight
UserFormのInsideWidthとInsideHeightの範囲を調べる為に、まずユーザーフォーム内に「Imageコントロール」を配置します。そのImageをフォーム内部一杯に広げるのが図4のコードです。なお数多くあるコントロールの中からImageを選択した理由は、色々試してみて最も境界が明確だったからです。- '========== ⇩(3) フォーム設定(フォーム内部一杯にコントロールを表示) ============
- Private Sub UserForm_Initialize()
- Me.Image1.Left = 0
- Me.Image1.Top = 0
- Me.Image1.Width = Me.InsideWidth
- Me.Image1.Height = Me.InsideHeight
- Me.Image1.BorderColor = RGB(255, 0, 0)
- End Sub
22行目「Me.Image1.Left = 0」で、Imageコントロールの左端をフォーム内側左端に合わせます。
23行目「Me.Image1.Top = 0」で、Imageコントロールの上端をフォーム内側上端に合わせます。
24行目「Me.Image1.Width = Me.InsideWidth」で、Imageコントロールの横幅を内側一杯に延ばします。
25行目「Me.Image1.Height = Me.InsideHeight」で、Imageコントロールの高さを内側一杯に延ばします。
26行目「Me.Image1.BorderColor = RGB(255, 0, 0)」で、Imageコントロールの外枠線を赤色にします。
図4のコードでフォームを起動すると、図5のようになります。
赤線(Imageコントロールの外枠線)で示されている範囲が、フォームのInsideWidth × InsideHeight になります。
図5
図5のフォームの寸法を調べてみると、以下のようになりました。
項目 | 寸法(ポイント) | |
---|---|---|
幅 | Width | 216.75 |
InsideWidth | 204.75 | |
高 さ | Height | 189 |
InsideHeight | 159.75 |
もちろん絶対値はどうでも良いのですが、内側と外側の「差」はフォームのサイズに関係無く一定のようです。但し、フォームのプロパティ(例えばBorderStyle)によっては、内側寸法が変化します。
また、この値を検証するために図7のように「セルのサイズ」で測定してみました。
図7
フォーム内側の赤枠に沿うようにセル幅・高さを合わせ、マウスで列・行の境界線をクリックすると図7の緑字のように寸法が表示されます。幅の表示値は「文字数」ですが、カッコ内にピクセル単位での値が表示されますし、高さは「ポイント数」とカッコ内にピクセル値が表示されます。
ピクセル値は「ディスプレイ拡大率=100%」「Excel表示倍率=100%」であれば、 ピクセル値 × 0.75 = ポイント値 ですので、計算すると図6の値に一致することが分かります。
図7とは別な方法での検証も考えてみました。図8のようにフォームレイアウト画面でのグリッドの数を数える方法です。 図8 グリッドの間隔を数えてみると、ちょうど34個です。これに、VBEの「ツール」→「オプション」→「全般」で設定可能な「グリッドの表示幅」を掛けると「34 × 6 = 204」となります。「グリッドの表示幅」の単位は「twip」なので「20 twip = 1ポイント」で計算をして「204 ÷ 20 = 10.2」・・・ と、ここまで計算して「なに?10.2ポイント!?」と気が付きました。あまりにも小さい値だからです。 そこで、10.2ポイントに近い「10ポイントのフォント」でフォーム上に文字列を配置してみたのが図9です。 図9 やはり横幅は「10.2ポイント」では無いように思えます。そこで見返してみると「204twip ≒ 図6の204.75」に気づきます。 と言うことは、VBEのグリッド間隔は 「×:twip単位 〇:ポイント単位」という事になります。 グリッド間隔の単位「twip」は、今まで気にもしていなかったのですが「誤記」の可能性があります。と言って、この単位を見ながらコントロールを配置している方は少ないと思うので、実害は無さそうです。 |
3.Frameコントロール
3ー1.WidthとHeight
フォーム上でのFrameコントロールのWidthとHeightを調べる為に、フォーム上にFrameを1つ配置し図10の設定をします。- '========== ⇩(4) フォーム設定(フォーム内部一杯にFrameを表示) ============
- Private Sub UserForm_Initialize()
- Me.Frame1.Left = 0
- Me.Frame1.Top = 0
- Me.Frame1.Width = Me.InsideWidth
- Me.Frame1.Height = Me.InsideHeight
- Me.Frame1.BorderStyle = fmBorderStyleSingle
- Me.Frame1.BorderColor = RGB(255, 0, 0)
- End Sub
32行目「Me.Frame1.Left = 0」で、Frameの左端をフォーム内側左端に合わせます。
33行目「Me.Frame1.Top = 0」で、Frameの上端をフォーム内側上端に合わせます。
34行目「Me.Frame1.Width = Me.InsideWidth」で、Frameの横幅をフォーム内側一杯に延ばします。
35行目「Me.Frame1.Height = Me.InsideHeight」で、Frameの高さをフォーム内側一杯に延ばします。
36行目「Me.Frame1.BorderStyle = fmBorderStyleSingle」で、Frameの枠線を「線あり」にします。
37行目「Me.Frame1.BorderColor = RGB(255, 0, 0)」で、Frameの枠線色を赤色にします。
なお36行目のBorderStyle設定をしない(既定は枠線無し)と、37行目で枠線色を設定しても着色しません。
フォームを起動した結果が図11の中央図です。
図11
Frameの左右端は「フォーム内側一杯」に設定される事が分かります。また下端もフォーム内側一杯です。
一方、上端は「Frameの文字列の上端」になっています。Frameの枠線は「文字列の中央辺り」の高さになります。
なお図11の中央図の元は、図11の右側図です。下でInsideWidthとInsideHeightを説明するために、Frameの中にImageコントロールを1つ配置しています。
3ー2.InsideWidthとInsideHeight
FrameのInsideWidthとInsideHeightを確認する為に、図11の右側図のようなフォームを作成し図12のような設定をします。- '========== ⇩(5) フォーム設定(Frame内側にImageを一杯に表示) ============
- Private Sub UserForm_Initialize()
- Me.Frame1.Left = 0
- Me.Frame1.Top = 0
- Me.Frame1.Width = Me.InsideWidth
- Me.Frame1.Height = Me.InsideHeight
- Me.Image1.Left = 0
- Me.Image1.Top = 0
- Me.Image1.Width = Me.Frame1.InsideWidth
- Me.Image1.Height = Me.Frame1.InsideHeight
- Me.Image1.BorderColor = RGB(255, 0, 0)
- End Sub
52~55行目は図10の32~35行目と同じで、フォーム内一杯にFrameを表示させています。枠線に着色はしていません。
57行目「Me.Image1.Left = 0」で、Imageの左端を「Frame内部」の左端に合わせています。
今回は図11の右側図のように「Frame1の内部にImage1コントロールを配置」しています。このように配置すると、Image1はFrame1のコントロール配下になる(Frame1のControlプロパティにImage1が登録される)ので、Image1の位置は「Frame1ベース」という事になります。 また、Frame内に配置されたImageのParentを調べる(Me.Image1.Parent)事で、「Frame1」オブジェクトが親だと分かります。もしFrame外にImageを置いた場合は、Parentを調べると「UserForm」オブジェクトが親となります。 子供にとっての親(Parent)は「コンテナ」と呼ばれるので、Frameの配下になる事は「コンテナ化」する と言っても良いかもしれません。 |
58行目「Me.Image1.Top = 0」で、Imageの上端を「Frame内部」の上端に合わせています。
59行目「Me.Image1.Width = Me.Frame1.InsideWidth」で、Imageの横幅をFrame内部一杯に延ばします。
60行目「Me.Image1.Height = Me.Frame1.InsideHeight」で、Imageの高さをFrame内部一杯に延ばします。
61行目「Me.Image1.BorderColor = RGB(255, 0, 0)」で、Image外枠線の色を赤色に設定します。
起動したフォームが図13です。(一番左側の図は、フォームの内側領域を明示しているものです。)
図13
図13の中央と右側では、FrameコントロールのFontサイズが異なります。中央が(標準の)9ポイント、右側が18ポイントに設定変更したものです。
Frame枠線(薄い灰色)に沿った赤い枠線が、Frame内側の領域(=Imageの外側)です。その枠線は、FrameのFontサイズによって違ってきます。サイズが大きくなる(図13の右側図)と、枠線が下がってきます。
また「Frameの文字列のところで、赤いImageの枠線が切れている」ことも確認できます。切れていると言うことは「Frame内に配置したコントロールの一部が見えない」事になります。
そこで「Frame内側の上部から、何ポイント下げればコントロールが隠れないか」を調べたのが図14です。
図14
ここではFontサイズのみを変えていますが、他のプロパティ(例えばBorder)でも影響を受けますので御注意下さい。
大雑把な考え方では、「Fontサイズの半分(例えばFontが9ポイントであれば4.5ポイント、Fontが18ポイントであれば9ポイント)以上」を下げれば、Frame内のコントロールが隠れることは無さそうです。
4.MultiPageコントロール
ここで扱うMultiPageコントロールは、複数のPageの集合体です。MultiPage内の1つ1つのページが「Page」となります。以下で説明する「WidthとHeight」はMultiPageのプロパティであり、また「InsideWidthとInsideHeight」はPageのプロパティです。しかし、MultiPageには「InsideWidthとInsideHeight」は無く、Pageには「WidthとHeight」はありません。
4ー1.WidthとHeight
MultiPageコントロールのWidthとHeightを調べるために、フォーム上にMultiPageを1つ配置し、図15の設定をします。- '========== ⇩(6) フォーム設定(フォーム内部一杯にMultiPageを表示) ============
- Private Sub UserForm_Initialize()
- Me.MultiPage1.Left = 0
- Me.MultiPage1.Top = 0
- Me.MultiPage1.Width = Me.InsideWidth
- Me.MultiPage1.Height = Me.InsideHeight
- Me.MultiPage1.BackColor = RGB(200, 200, 200)
- End Sub
72行目「Me.MultiPage1.Left = 0」で、MultiPageの左端をフォーム内側左端に合わせます。
73行目「Me.MultiPage1.Top = 0」で、MultiPageの上端をフォーム内側上端に合わせます。
74行目「Me.MultiPage1.Width = Me.InsideWidth」で、MultiPageの横幅をフォーム内側一杯に延ばします。
75行目「Me.MultiPage1.Height = Me.InsideHeight」で、MultiPageの高さをフォーム内側一杯に延ばします。
76行目「Me.MultiPage1.BackColor = RGB(200, 200, 200)」で、MultiPageの背景色を「灰色」にします。背景色を変更したのは、MultiPageには枠線というものが無い為の代用です。
フォームを起動した結果が図16の中央図です。
図16
MultiPageの左右は「フォーム内側一杯」に設定される事が分かります。
一方、上下方向の下端はフォーム内側一杯で、上端は「MultiPageのタブ上端」となっています。
なお図16の中央図の元は、図16の右側図です。下でInsideWidthとInsideHeightを説明するために、MultiPageの1ページ目の中にImageコントロールを1つ配置しています。
4ー2.InsideWidthとInsideHeight
PageのInsideWidthとInsideHeightを確認する為に、図16の右側図のようなフォームを作成し、図17の設定をします。- '========== ⇩(7) フォーム設定(MultiPage内部一杯にImageを表示) ============
- Private Sub UserForm_Initialize()
- Me.MultiPage1.Left = 0
- Me.MultiPage1.Top = 0
- Me.MultiPage1.Width = Me.InsideWidth
- Me.MultiPage1.Height = Me.InsideHeight
- DoEvents: DoEvents
- Me.Image1.Left = 0
- Me.Image1.Top = 0
- Me.Image1.Width = Me.MultiPage1.Pages(0).InsideWidth
- Me.Image1.Height = Me.MultiPage1.Pages(0).InsideHeight
- Me.Image1.BorderColor = RGB(255, 0, 0)
- End Sub
92~95行目は、図15の72~75行目と同じで、MultiPageをフォーム内側一杯に表示しています。
97行目「DoEvents: DoEvents」では制御を一旦O/Sへ戻し、「Pageの内側寸法の変更内容をフォームに反映」させています。
この「DoEvents」が担っているのは「変更内容の反映」という機能です。 普通に考えれば94~95行目の実行と共に「内側寸法が変更」されるはずなのですが、試行してみるとDoEventsを実行する直前では「元サイズのPageの内側寸法」を保持しているのです(PC環境により異なるかもしれません)。そのため、ここでDoEventsを実行しないと、101~102行目で変更設定するImageコントロールのサイズが旧データ(最大には広がらない)となってしまいます。 しかし、Frame内にImageを配置した図12の場合には「DoEventsが不要」でしたので、何か理由がありそうです。例えばFrameにはWidthとInsideWidthが両方存在するのに対し、MultiPageにはWidthがあるがInsideWidthは無く、子どもに相当する「Pageに存在」する等です。 また「コンボボックスのテキストボックス部に複数列を表示」でも、DoEventsを使ってListBoxのサイズを反映させる必要がありましたが、何か今回と状況が似ているような気もします。 また図16の右図のように、1ページ目(=Me.MultiPage1.Pages(0) )を開いた形でMultiPageを配置すると、DoEventsを実行した時に正しい値になるのは1ページ目だけです。 且つ、途中で「MultiPage1.Value = 1」等を実行して「ページを切り替える」と、切り替えられたページも正しい値に切り替わります。切り替えられていないページ(= 一度も表面に出てきていないページ)は古い値のままなのです。 結局、原因は分からないのですが、ActiveX(ユーザーフォーム)のコントロールでサイズ変更が反映されない状況が発生したら「DoEventsを入れてみる」というのが、1つの解決法のようです。 |
99行目「Me.Image1.Left = 0」で、Imageの左端を「Page内部」の左端に合わせています。
100行目「Me.Image1.Top = 0」で、Imageの上端を「Page内部」の上端に合わせています。
101行目「Me.Image1.Width = Me.MultiPage1.Pages(0).InsideWidth」で、Imageの横幅をPage内部一杯に延ばします。ここでInsideWidthを取得するページは「配置時に表面に出ているページ」または「途中で表面にしたページ」にする必要がありますので、1ページ目( Pages(0) )を指定しています。
102行目「Me.Image1.Height = Me.MultiPage1.Pages(0).InsideHeig」で、Imageの高さをPage内部一杯に延ばします。
103行目「Me.Image1.BorderColor = RGB(255, 0, 0)」で、Image外枠線の色を赤色に設定します。
起動したフォームが図18です。(一番左側の図は、フォームの内側領域を明示しているものです。)
図18
図18の中央と右側では、PageのFontサイズが異なっています。中央が(標準の)9ポイント、右側が18ポイントに設定変更したものです。
赤い枠線で囲まれ「Page1」「Page2」・・・のタブの下側が、Page内側の領域(=Imageの外側)です。Frameコントロールよりは分かり易いですし、タブで領域が隠れてしまうこともありません。
なおFontサイズにより内側寸法が変わるのは同じで、サイズが大きくなる(図18の右側図)と、内側領域も小さくなります。
5.まとめ
UserForm/Frame/MultiPage(Page)の「Width」「Heitht」及び「InsideWidth」「InsideHeight」の寸法は、図19のようになります。青色が外側寸法(WidthとHeitht)、緑色が内側寸法(InsideWidthとInsideHeight)です。図19
今回説明した内部寸法(InsideWidth、InsideHeight)は、そのコントロールのサイズ(Width、Heitht)が同じでも、Fontサイズや他プロパティにより値が変わってきます。また「InsideWidth」「InsideHeight」は、取得のみが可能なプロパティなので、必要なサイズを直接指定する訳にもいきません。
最終的には手調整しながらコントロールを配置するしか無いのでしょうが、今回の説明内容が少しでも参考になれば幸いです。
アプリ実例
「MonthViewコントロールを使ったカレンダー」「Excel図形等の位置、ディスプレイ上の位置の取得」
「Book内で完結するフォーム表示のHelp画面」
「ラベルカレンダーをクリックし日付入力」
「年賀状リスト等の宛名検索(ブック内検索可)」