Delphiでフローレイアウト

By: Hitoshi Fujii

Abstract: DelphiのUIレイアウト管理のための2つの新しいコンポーネント(TFlowPanelとTGridPanel)を検証します。

    はじめに

Delphiは、常に、すばらしいユーザーインターフェイスを作成する開発環境の主役の座を務めてきました。良いユーザーインターフェイスのために必要とされる要素は多数ありますが、フォーム上のコントロールのビジュアルレイアウトは、最も基本的な要素のひとつです。旧バージョンのDelphiでは、レイアウトを簡単にするために、Align、Anchor、Constraintプロパティがありましたが、Delphi 2006では、新たにTFlowPanelとTGridPanelが導入されました。この記事のサンプルプロジェクトは、こちら.からダウンロードできます。

    TFlowPanel

TFlowPanelは、非常に単純なコントロールです。このパネルは、その中に置かれたコントロールを自動的に位置合わせします。FlowStyleプロパティは、コントロールの配置順を制御します。デフォルトでは、fsLeftRightTopBottomに設定されており、コンポーネントは、左から右、上から下に、余白なしで配置されます。図は、その例です。

Hide image

大きさを変更すると、配置は自動的に更新されます。

Hide image

他のレイアウトは以下のとおりです。

fsLeftRightTopBottom:
Hide image

fsLeftRightBottomTop:
Hide image

fsRightLeftTopBottom:
Hide image

fsRightLeftBottomTop:
Hide image

fsTopBottomLeftRight:
Hide image

fsTopBottomRightLeft:
Hide image

fsBottomTopLeftRight:
Hide image

fsBottomTopRightLeft:
Hide image


ひとつ留意しておくべきポイントは、フローパネルがスクロールバーをサポートしていないという点です。そのため、いくつかのコンポーネントは、一部しか表示されなかったり、完全に隠れてしまうかもしれません。

Hide image

ボタンに限らず、どのようなコントロールも使用できます。これは、ボタンとエディットボックス、チェックボックスを使用した例です。

Hide image

もし、コントロールを隣接させたくない場合には、MarginsとAlignWithMarginsプロパティによって、コントロールの間隔を調整します。

    設計時のコントロールの移動

設計時にコントロールを再配置したいときには、どうすればよいでしょうか?これは、そう簡単ではありません。単にマウスでコントロールをドラッグしてもだめです。コントロールは、最初の位置に戻ってしまいます。コントロールをカットアンドペーストすれば、最後の位置に移動できますが、これではあまり柔軟性がありません。

幸いなことに、ControlIndexプロパティというマジックがあります。これは、面白い機能です。TButtonコントロールをフローパネルに配置すると、ControlIndexプロパティがオブジェクトインスペクタの下の方にリストされます。しかし、同じTButtonをフォームに直接配置すると、このControlIndexプロパティは表示されません。

そのため、TButtonは(あるいは、同じような他のどのコントロールも)、フローパネル上に置かれているかどうかに依存するControlIndexプロパティを持っているような、いないようなといった状態です。

このControlIndexプロパティに関するリファレンスが、VCLソースコードのどこにもありません。しかし、TFlowPanelは、GetControlIndexとSetControlIndexという2つの関数を持っており、これらは、コントロールの現在のインデックスを返したり、変更したりします。どのようにしてか、Delphi IDEは、必要に応じて自動的にこのプロパティを追加しています。同じテクニックがTGridPanelでも見ることができます。

とにかく、ボタンのControlIndexプロパティを変更することが、フローパネルのコントロールの位置を変更する方法です。値を0に設定すると先頭の場所へ、1に変更すると2番目の場所へと移動します。

    TGridPanel

グリッドパネルは、コントロールをグリッド(格子状)に配置するために用います。きわめてシンプルです。

各々のコントロールは、グリッドのセルの中央に配置されます。そして、各セルには、最大で1つのコントロールが配置されます。このパネルをリサイズすると、コントロールは、セルの中央に位置するように配置変更されます。必要に応じて、コントロールの大きさは縮小されます。

Hide image

大きさを変更すると次のようになります。

Hide image

ここでも、コントロールの位置を変更するためのマジックプロパティがあります。ColumnプロパティとRowプロパティは、TFlowPanelのControlIndexプロパティと同じように働きます。違いは、ColumnプロパティかRowプロパティに新しい値を設定すると、これが、対象となるセルに位置するコントロールとの入れ替えを引き起こすという点です。たとえば、上の画面ショットにあるButton6のRowプロパティを1から3に変更すると、(元々Row 3に位置していた)Edit2と位置を入れ替えます(下図参照)。

Hide image

他にも2つマジックプロパティがあります。ColumnSpanとRowSpanです。これらは、HTMLテーブルと同じように働きます。下の画面ショットでは、Button2とButton5のColumnSpanプロパティを2に、Button4とButton11のRowSpanプロパティを2に設定しています。Button2とButton5、Button11は、予想通りの動作をします。Button4は、若干問題があります。これは正しく配置されますが、結果としてButton8とButton12の位置がおかしくなります。

Hide image

Button4の下のセルが空ならば、すべては正しく動作します。

Hide image

    行と列のレイアウト

グリッドパネルは、ColumnCollectionプロパティによって、列の追加、削除、幅の変更ができます。グリッドの列または行は、現在のセル数以上のコントロールを追加しようとすると、自動的に追加されます。グリッドパネルのExpandStyleプロパティをemAddColumnsかemAddRowsに設定すれば、列または行の追加が決定されますが、emFixedSizeに設定すると、コントロールが追加されても、これを抑止します。

列幅を設定するオプションは3つあります。ssAbsoluteスタイルは、列幅をピクセル単位で指定します。ssAutoスタイルは、各行の対象列をスキャンして、最大幅を持つコントロールを見つけ、これを列幅に設定します。

Hide image

Hide image

ssPercentスタイルは、全体の幅の割合(パーセント)で幅を指定します。あるいは、そのようなスタイルであると考えるでしょう。しかし、実際には、まだそのようには働きません。まず、ssAutoあるいはssAbsoluteタイルを指定したカラムの幅を無視します。100ピクセルの幅を持つグリッドパネルを思い浮かべてください。最初の列は、30ピクセルの絶対値で指定します。そして2つの残りの列は、25%と75%の幅に設定します。この2つの列の実際の幅は、それぞれ17ピクセルと52ピクセルになるはずです(100–30=70ピクセルの25%と75%)。

トリッキーなのは、パーセントで指定している部分があることです。もし、2つの列を持つグリッドのそれぞれの列幅が50%と指定されており、最初の列を25%に設定したとしましょう。そうすると、その幅は33.33%と66.67%になってしまいます。おそらく、25%と75%になることを期待していたはずです。

問題はここにあります。最初の列幅を25%に設定したとき、TColumnItemオブジェクトはアイテムが変更されたことをコレクションに通知します。そして、コレクションは、グリッドパネルにコレクションが(個々のアイテムではありません)変更されたことを通知します。この時点で、グリッドパネルは、ひとつの列が25%に、そしてもうひとつが50%に設定されていることを知ることができますが、どちらが変更されたかを知ることはできません。そして、どのようにこの値が33.33%になるのでしょうか?すべてのカラムのパーセンテージの合計で、列幅を分割するのです。最初の列は、25÷(25+55)=33.33%、2番目の列は、50÷(25+50)=66.67% になります。

もし、何度も25%を入力し続ければ、やがてその値は25%に近づいていきます。これは、たしかに有効ですが、ちょっと厄介です。

幸いにも、もっと簡単な方法があります。BeginUpdate/EndUpdateの間に列幅を指定するコードを記述した、以下のコードを用います。

    procedure TfrmMain.btnSetColumnWidthsClick(Sender: TObject);
    begin
      gridPanel.ColumnCollection.BeginUpdate;
      gridPanel.ColumnCollection[0].SizeStyle := ssPercent;
      gridPanel.ColumnCollection[0].Value     := 25;
      gridPanel.ColumnCollection[1].SizeStyle := ssPercent;
      gridPanel.ColumnCollection[1].Value     := 75;
      gridPanel.ColumnCollection.EndUpdate;
    end;

列幅は、期待通りに、RowCollectionプロパティを通してまったく同じ方法でハンドルされます。以下は、25%、25%と50%に設定された列を表示した画面ショットです。

Hide image

    セルのレイアウト

セルの中にコントロールをどのように配置するかを制御する方法は、いくつもあります。コントロールのAlignプロパティを使うのは、通常のやり方です。これには、コントロールのMarginsプロパティとAlignWithMarginsプロパティを使うこともできます。

また、Anchorぷ炉パティを使って、Alignプロパティを使って実現できるのとは若干異なる方法で、セル端にコントロールを位置合わせすることができます。ボタンをグリッドパネルに配置してください。ここで、Anchorプロパティが[] に設定されていることが分かります。これは、コントロールを中央に配置します。Anchorプロパティを[alLeft]に設定すると、最初、何も変わらないように見えるでしょう。しかし、グリッドパネルをリサイズするか、ボタンを移動させようと試みると、ボタンは直ちにその高さや幅を変更することなく、その幅の左端に位置合わせされます。MarginプロパティもAlignWithMarginが設定するのと同様に動きます。

以下の画面ショットでは、Button1がTop-Leftにアンカーされています。Button2はTop-Bottomへ、Button3はTop-Rightにというように。注意点は、Button9では、グリッドパネルの端からボタンまでの距離に、そのMarginプロパティとAlignWithMarginsプロパティを使用していることです。

Hide image

なお、TopプロパティとLeftプロパティを使って、レイアウトを変更することはできません。これらのプロパティは、コントロールをグリッドパネルに配置したときに、オブジェクトインスペクタからマジックのように隠されます。

    さらなるマジック

すでにControlIndex、Column、ColumnSpan、Row、RowSpan、Top、Leftといったマジックプロパティを見てきました。筆を置く前に、もうひとつのマジックプロパティについて触れておきます。グリッドパネルの上にもうひとつのグリッドパネルを埋め込むと、埋め込まれたグリッドパネルは、その上に配置される各々のコントロールであるTControlItemのコレクションへのアクセスを提供する、ControlCollectionプロパティを使えるようになります。TControlItemのインスタンスは、Column、ColumnSpan、Row、RowSpanプロパティを持っています。これは、組み込まれたコントロールにあるプロパティと同じ機能を提供するものです。コレクションエディタウィンドウを用いれば、TControlItemのインスタンスの順序を変更できますが、グリッド上のコントロールの順序には影響がありません。

    実践編へ…

さて、それではグリッドパネルの実践的なサンプルを紹介して、終りにしたいと思います。以下の2つの画面ショットは、同じフォームですが、サイズが違います。それぞれのケースで、コントロールは適当な大きさになっています。コードは一行も記述していません。

Hide image
Hide image
Click to see full-sized image

Server Response from: ETNASC01