3.4 グリッド
3.4.1 表レイアウト
行と列で構成される表のようなレイアウトを実現するには System.Windows.Controls.Grid クラスを使います。Grid は、HTML のレイアウトで例えるならば table タグに相当するパネルです。StackPanel、DockPanel で実現できないレイアウトは、Grid を用いることで実現できるでしょう。ほとんどのアプリケーションでは、これらのパネルの組み合わせで十分にデザインすることができます。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.Media.Visual System.Windows.UIElement System.Windows.FrameworkElement System.Windows.Controls.Panel System.Windows.Controls.Grid
public class Grid : Panel, IAddChild
Grid クラスのコンストラクタは、パラメータを受け取りません。
public Grid ()
Grid クラスの Children プロパティから子要素を追加するだけでは、オブジェクトが表として配置されません。まず最初に、生成した Grid オブジェクトに行と列を定義しなければなりません。規定では、Grid クラスは、行、列ともに 0 に設定されています。よって、表のセルが 1 つしか存在していない状態です。
行と列は、それぞれ個別に定義しなければなりません。最初に、列の定義から見てみましょう。列を定義したり、定義されている列を調べるには ColumnDefinitions プロパティを利用します。
public ColumnDefinitionCollection ColumnDefinitions { get; }
ColumnDefinitions プロパティは、列定義オブジェクトの配列を管理する System.Windows.Controls.ColumnDefinitionCollection クラスのオブジェクトを返します。
public sealed class ColumnDefinitionCollection : IList<ColumnDefinition>, ICollection<ColumnDefinition>, IEnumerable<ColumnDefinition>, IList, ICollection, IEnumerable
このクラスは、IList や ICollection を実装するコレクションです。よって、基本的な操作方法は、Panel オブジェクトが子要素を管理するために使っていた UIElementCollection クラスと同じです。Add() メソッドや Remove() メソッドを使って、列定義オブジェクトを追加したり、削除することができます。
ColumnDefinitionCollection が管理する列定義オブジェクトとは System.Windows.Controls.ColumnDefinition クラスのオブジェクトを表します。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.ContentElement System.Windows.FrameworkContentElement System.Windows.Controls.DefinitionBase System.Windows.Controls.ColumnDefinition
public class ColumnDefinition : DefinitionBase
ColumnDefinition クラスのオブジェクトは、Grid 上の 1 列に対応し、列に関する情報を提供します。このクラスのコンストラクタは、パラメータを受け取りません。
public ColumnDefinition ()
ColumnDefinition クラスには、様々なプロパティが存在していますが、この場では特に設定することなく初期の状態のままで ColumnDefinitionCollection オブジェクトに追加します。通常の表であれば、ColumnDefinition オブジェクトは初期状態のままで十分でしょう。
次に、行の定義です。行の定義は RowDefinitions プロパティから行います。
public RowDefinitionCollection RowDefinitions { get; }
基本的な考え方は ColumnDefinitions とまったく同じです。RowDefinitions プロパティは、行定義オブジェクトを管理する System.Windows.Controls.RowDefinitionCollection クラスのオブジェクトを返します。
public sealed class RowDefinitionCollection : IList<RowDefinition>, ICollection<RowDefinition>, IEnumerable<RowDefinition>, IList, ICollection, IEnumerable
RowDefinitionCollection は、やはり IList や ICollection を実装する、行定義オブジェクトの配列を管理するためのコレクションです。
行定義オブジェクトは System.Windows.Controls.RowDefinition クラスで表されます。
System.Object System.Windows.Threading.DispatcherObject System.Windows.DependencyObject System.Windows.ContentElement System.Windows.FrameworkContentElement System.Windows.Controls.DefinitionBase System.Windows.Controls.RowDefinition
public class RowDefinition : DefinitionBase
列を管理するための ColumnDefinitionCollection と、行を管理するための RowDefinitionCollection や、列を定義する ColumnDefinition と、行を定義する RowDefinition など、行と列に関連するそれぞれのプロパティやクラスが対になっていることが分かります。
例えば、2 列 3 行の 6 マスの表を作ろうと考えた場合、ColumnDefinition オブジェクトを 2 つ、ColumnDefinitions プロパティが返す ColumnDefinitionCollection オブジェクトの Add() メソッドから追加し、同時に、RowDefinition オブジェクトを 3 つ、RowDefinitions プロパティが返す RowDefinitionCollection オブジェクトの Add() メソッドから追加します。
表を正しく定義することができれば、これまでと同じ方法で Grid に子要素を追加し、表の各セルに表示させることができます。ただし、子要素には明示的にどのセルに表示するのか、行番号と列番号で位置を指定しなければなりません。
子要素を配置する列は SetColumn() メソッドから設定することができます。設定した列番号は GetColumn() メソッドから取得することができます。
public static void SetColumn (UIElement element, int value)
[AttachedPropertyBrowsableForChildrenAttribute] public static int GetColumn (UIElement element)
element には、列番号を設定、または取得する子要素を、value には 0 からの列番号を指定します。当然、この番号は Grid に定義されている列の数に従わなければなりません。
行番号は SetRow() メソッドから設定することができ、GetRow() メソッドから取得することができます。
public static void SetRow (UIElement element, int value)
[AttachedPropertyBrowsableForChildrenAttribute] public static int GetRow (UIElement element)
これらのメソッドは、SetColumn() や GetColumn() と利用方法は同じです。element に行番号を設定、または取得する対象の子要素を、value に 0 空始まる行番号を指定します。
using System; using System.Windows; using System.Windows.Controls; class Test { [STAThread] public static void Main() { string[,] texts = { { "Melancholy" , "Sighs" , "Boredom" } , { "Vanishing" , "Rampage" , "Agitation" } , { "Intigues" , "Indignation", "禁則事項" } }; Grid panel = new Grid(); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); for(int i = 0 ; i < texts.GetLength(0) ; i++) { for(int j = 0 ; j < texts.GetLength(1) ; j++) { Button button = new Button(); button.Content = texts[i, j]; Grid.SetRow(button, i); Grid.SetColumn(button, j); panel.Children.Add(button); } } Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード1は、行数 3、列数 3、すなわち 3 × 3 マスのグリッド レイアウトを実現しています。このプログラムでは、最初に Grid オブジェクを生成し、生成したオブジェクトに行と列を定義しています。ColumnDefinitions プロパティと RowDefinitions プロパティから、それぞれ ColumnDefinition クラスのオブジェクトと RowDefinition クラスのオブジェクトを 3 つ追加しています。
グリッドに表示しているボタンは、Main() メソッドの先頭で宣言している string 型の多次元配列 texts から、入れ子になった for 文を使って生成しています。
子要素は、必ずしも全てのセルを埋める必要はありません。明示的に SetColumn() や SetRow() で子要素が割り当てられていないセルは、空白の状態で表示されます。
3.4.2 セルの境界線
開発中、コントロールがグリッド上にどのように配置されているのかをデバッグするために、Grid がセルを分割している領域を視覚的に確認したいと思うことでしょう。幸い、Grid クラスは各セルの境界線を表示する ShowGridLines プロパティを用意しています。
public bool ShowGridLines { get; set; }
既定では、このプロパティには false が設定されていますが、true を設定することで Grid が分割したセルの境界線を描画してくれます。通常、リリース時にこのプロパティを true にすることはありませんが、コントロールのレイアウトを行うときに役に立つでしょう。
using System; using System.Windows; using System.Windows.Controls; class Test { [STAThread] public static void Main() { Grid panel = new Grid(); panel.ShowGridLines = true; panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード2は、Grid オブジェクトの ShowGridLines プロパティを true にした状態です。このプログラムでは、Grid のセルに子要素は追加していませんが、行と列を定義しているので、行と列に従って適切に分割されていることが視覚的に確認することができます。
3.4.3 行と列のサイズ
Grid のセルのサイズは行の高さ、列の幅の組み合わせで決定されます。既定では、行も列も自動的に、Grid 全体のサイズから分割され、決定されます。しかし、多くの場合は、性質に応じて列の幅や行の高さを具体的に指定したいと考えるでしょう。こうした、特定の行や列の設定を行うために、Grid は行や列を定義するオブジェクトを保有しているのです。
列の幅は ColumnDefinition クラスの Width プロパティから設定することができます。
public GridLength Width { get; set; }
Width プロパティは、ピクセルで数値を直接設定するのではなく、System.Windows.GridLength 構造体を用いていることに注意してください。これは、単純な数値表現の他に、自動的にサイズ調整を行うことを表す値などを表現しなければならないためです。
[TypeConverterAttribute(typeof(GridLengthConverter))] public struct GridLength : IEquatable<GridLength>
GridLength 構造体のコンストラクタには、グリッドの長さを表すピクセル単位の値を指定します。
public GridLength (double pixels)
この構造体の値を ColumnDefinition オブジェクトの Width に指定することで、pixels に指定した値が列の幅として利用されます。GridLength 構造体の値は Value プロパティから取得することができます。
public double Value { get; }
Value プロパティは、コンストラクタで指定した値を返します。
using System; using System.Windows; using System.Windows.Controls; class Test { [STAThread] public static void Main() { ColumnDefinition column = new ColumnDefinition(); column.Width = new GridLength(100); Grid panel = new Grid(); panel.ShowGridLines = true; panel.ColumnDefinitions.Add(column); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード3は、第1列の幅を明示的に 100 ピクセルに設定しています。実行結果を見ると、指定した値に従って一番左側の列の幅が固定されていることが確認できます。
因みに、既定の自動的に列幅を調整するという設定は GridLength 構造体の静的な読み取り専用の Auto プロパティから取得することができます。実質的に Auto は 1 ピクセルの長さを表す GridLenght 構造体の値です。
public static GridLength Auto { get; }
行の高さを設定する方法も、列の幅とほとんど同じです。行の高さを指定したい場合、その行を表す RowDefinition オブジェクトの Height プロパティを設定してください。
public GridLength Height { get; set; }
using System; using System.Windows; using System.Windows.Controls; class Test { [STAThread] public static void Main() { RowDefinition row = new RowDefinition(); row.Height = new GridLength(100); Grid panel = new Grid(); panel.ShowGridLines = true; panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(row); panel.RowDefinitions.Add(new RowDefinition()); Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード4では、3 つの行のうち、中央を定義する RowDefinition オブジェクトの Height プロパティを明示的に 100 ピクセルに固定しています。
3.4.4 複数のセルを横断する
より複雑なレイアウトを実現しようとした場合、複数のセルをまたいで子要素を配置したいと考えることでしょう。複数の行、または複数の列を使ってコントロールを配置させたい場合は、 行番号と列番号に加え、子要素に対して複数の行にまたがることを表す情報を付加しなければなりません。
複数の列にまたがる子要素には、Grid クラスの静的な SetColumnSpan() メソッドを用いて使用する列数を指定します。対して GetColumnSpan() メソッドから、またがる列数を取得することも可能です。
public static void SetColumnSpan (UIElement element, int value)
[AttachedPropertyBrowsableForChildrenAttribute] public static int GetColumnSpan (UIElement element)
element には、またがる列数を設定または取得する UIElement オブジェクトを、value には列数を指定します。
複数の行にまたがる子要素は SetRowSpan() メソッドから設定することができます。同様に GetRowSpan() メソッドから、またがる行数を取得することも可能です。
public static void SetRowSpan(UIElement element, int value)
[AttachedPropertyBrowsableForChildrenAttribute] public static int GetRowSpan (UIElement element)
使い方は SetColumnSpan()と同じです。element に値を設定または取得する UIElement オブジェクトを指定し、value にまたがる行数を指定します。
using System; using System.Windows; using System.Windows.Controls; class Test { [STAThread] public static void Main() { Grid panel = new Grid(); panel.ShowGridLines = true; panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.ColumnDefinitions.Add(new ColumnDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); panel.RowDefinitions.Add(new RowDefinition()); Button button1 = new Button(); button1.Content = "これを衆目に晒すというのか"; Grid.SetRow(button1, 0); Grid.SetColumn(button1, 0); Grid.SetColumnSpan(button1, 3); Button button2 = new Button(); button2.Content = "眼鏡属性って何…?"; Grid.SetRow(button2, 1); Grid.SetColumn(button2, 1); Grid.SetRowSpan(button2, 2); panel.Children.Add(button1); panel.Children.Add(button2); Window wnd = new Window(); wnd.Content = panel; Application app = new Application(); app.Run(wnd); } }
コード5は、複数の列にまたがるボタンと、複数の行にまたがるボタンを表示するプログラムです。それぞれの Button オブジェクトに対して SetColumnSpan() や SetRowSpan() メソッドでまたがる行数や列数を設定している部分に注目してください。もちろん、複数の行と列を同時にまたぐことも可能です。