WPF、罫線つきのGridを作ってみる
WPFのGridは、Column、Rowを指定することで内部のコントロールを配置することができますが、この各セルを枠線、罫線で囲って区切って欲しいと言われ。
この件、過去にもいろいろな方法で試行錯誤してみたことがあったのですが、一番個人的には美しく仕上がるかなと思ったのが、罫線用の行と列を追加してしまう方法。列A、列B、列C と、実際には3列なのを、(左罫線列)、列A、(AB間の罫線列)、列B、(BC間の罫線列)、列C、(右罫線列)と、列と列の間、および全体の外側に、罫線を引くためだけの列を追加して、ここにlineなりrectangleなりで線を引いてしまうというものです。
ただこれ、行数や列数が増えると大変です。n行m列とすると、2n+1行、2m+1列まで増えますし。配置してるコントロールの座標も全部修正必要になるし、Spanを設定している場合はそれも修正が必要になる。
手作業で行や列を追加してだと、時間も手間もかかりますし、間違いも多くなりますし。だったら、自動でその修正をしてくれるカスタムコントロールを作ってしまえと思って、今日、土曜の朝に、試しに作ってみました。
using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; namespace Bell { class RuledLineGrid : Grid { protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); //罫線の太さはここで指定。 var thickness = new GridLength(1); // 罫線用の行・列を追加 var columns = ColumnDefinitions.ToArray(); ColumnDefinitions.Clear(); if (columns.Any()) { foreach (var c in columns) { ColumnDefinitions.Add(new ColumnDefinition {Width = thickness}); ColumnDefinitions.Add(c); } } else { ColumnDefinitions.Add(new ColumnDefinition {Width = thickness}); ColumnDefinitions.Add(new ColumnDefinition()); } ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(0.5) }); var rows = RowDefinitions.ToArray(); RowDefinitions.Clear(); if (rows.Any()) { foreach (var r in rows) { RowDefinitions.Add(new RowDefinition {Height = thickness}); RowDefinitions.Add(r); } } else { RowDefinitions.Add(new RowDefinition {Height = thickness}); RowDefinitions.Add(new RowDefinition()); } RowDefinitions.Add(new RowDefinition { Height = thickness }); //行・列を追加した分、Column,Row,ColumnSpan,RowSpanがずれるのでその補正再設定 foreach (UIElement c in Children) { SetColumn(c, GetColumn(c)*2 + 1); SetColumnSpan(c, GetColumnSpan(c) * 2 - 1); SetRow(c, GetRow(c) * 2 + 1); SetRowSpan(c, GetRowSpan(c) * 2 - 1); } //罫線用に追加した行・列にRectangleを配置 for (var i = 0; i < ColumnDefinitions.Count; i+=2) { var rectangle = new Rectangle() { Fill = Brushes.Black }; Children.Add(rectangle); SetColumn(rectangle,i); SetRowSpan(rectangle,RowDefinitions.Count); SetZIndex(rectangle,int.MinValue); } for (var i = 0; i < RowDefinitions.Count; i+=2) { var rectangle = new Rectangle() { Fill = Brushes.Black }; Children.Add(rectangle); SetRow(rectangle, i); SetColumnSpan(rectangle, ColumnDefinitions.Count); SetZIndex(rectangle,int.MinValue); } } } }
使用するXAML側では、
<local:RuledLineGrid Margin="10" Background="LightGreen"> <local:RuledLineGrid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="50"/> </local:RuledLineGrid.ColumnDefinitions> <local:RuledLineGrid.RowDefinitions> <RowDefinition/> <RowDefinition/> <local:RuledLineGrid.RowDefinitions> </local:RuledLineGrid>
こんな感じで通常のGridに置き換えて使えばOK。間の罫線列・罫線行は無視して、普通のGridと同じ感覚で指定します。
次のSSで、上が普通のGrid、下がRuledLineGridに置き換えたもの。
欠点としては、結合セル(ColumnSpanまたはRowSpanが2以上のもの)に関しては、コントロールそのものが罫線の上に配置されることで罫線を隠しているので、背景が透明なTextBlockなんかを置いた場合は、背後の罫線が隠れずそのまま見えてしまうこと。
上の例のように、Backgroundに色を付けたり、透明以外にしておけばそれで解決する話なので、まぁいいかな…? という感じです。
4/20 少しコードを修正しました。ColumnDefinitionやRowDefinitionを全く定義していないGridだとうまく行かなかったので、その場合にも対応。