こんばんは。
結構便利で重宝しているPanelを作ったので、共有したいと思います。
左の赤い枠内がOrientation=Vertical、右の青い枠内がOrientation=Horizontalです。
上記の図のように描画させるには以下のXAMLコードを書くだけです。
SimpleGridはRowsとColumnsとOrientationを設定すれば、あとは縦x横=(Rows, Columns)のグリッドに、Orientationの向きで順番に要素を埋めていってくれます。特にGridクラスのように一々Grid.Columnプロパティや、Grid.Columnプロパティを設定する必要がないので楽です。
<Window x:Class="SimpleGridSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SimpleGridSample"
mc:Ignorable="d"
Title="MainWindow"
SizeToContent="WidthAndHeight">
<local:SimpleGrid Rows="1" Columns="2" Orientation="Horizontal">
<Border BorderBrush="Red" BorderThickness="3">
<local:SimpleGrid Rows="4" Columns="6" Orientation="Vertical">
<Label>1</Label>
<Label>2</Label>
<Label>3</Label>
<Label>4</Label>
<Label>5</Label>
<Label>6</Label>
<Label>7</Label>
<Label>8</Label>
<Label>9</Label>
<Label>10</Label>
<Label>11</Label>
<Label>12</Label>
<Label>13</Label>
<Label>14</Label>
<Label>15</Label>
<Label>16</Label>
<Label>17</Label>
<Label>18</Label>
<Label>19</Label>
<Label>20</Label>
<Label>21</Label>
<Label>22</Label>
<Label>23</Label>
<Label>24</Label>
</local:SimpleGrid>
</Border>
<Border BorderBrush="Blue" BorderThickness="3">
<local:SimpleGrid Rows="6" Columns="4" Orientation="Horizontal">
<Label>21</Label>
<Label>22</Label>
<Label>23</Label>
<Label>24</Label>
<Label>25</Label>
<Label>26</Label>
<Label>27</Label>
<Label>28</Label>
<Label>29</Label>
<Label>30</Label>
<Label>31</Label>
<Label>32</Label>
<Label>33</Label>
<Label>34</Label>
<Label>35</Label>
<Label>36</Label>
<Label>37</Label>
<Label>38</Label>
<Label>39</Label>
<Label>40</Label>
<Label>40</Label>
<Label>41</Label>
<Label>42</Label>
<Label>43</Label>
</local:SimpleGrid>
</Border>
</local:SimpleGrid>
</Window>
例
boiler's Graphics で統計機能を作ったのですが、ここでSimpleGridが大活躍しました。縦:横=20:3のグリッドになります。(コード上の定義では20x5と広めに設定していますが、子要素の数が足りなくて20x3で表示されています)
このSimpleGrid、子要素としてDockPanelを使うと良い具合になります。
<UserControl x:Class="boilersGraphics.Views.Statistics"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:boilersGraphics.Controls"
xmlns:converter="clr-namespace:boilersGraphics.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:viewModel="clr-namespace:boilersGraphics.ViewModels"
d:DataContext="{d:DesignInstance viewModel:StatisticsDialogViewModel}"
prism:ViewModelLocator.AutoWireViewModel="True"
Background="White"
mc:Ignorable=" d">
<UserControl.Resources>
<converter:TimeSpanRoundConverter x:Key="TimeSpanRound" />
</UserControl.Resources>
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</Style>
</prism:Dialog.WindowStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<controls:SimpleGrid Columns="5"
Orientation="Vertical"
Rows="20">
<controls:SimpleGrid.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
</controls:SimpleGrid.Resources>
<DockPanel>
<Label>起動回数</Label>
<Label Content="{Binding NumberOfBoots.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>累計稼働時間</Label>
<Label Content="{Binding Uptime.Value, StringFormat={}{0:hh\\:mm\\:ss}, ConverterCulture=ja-jP, Converter={StaticResource TimeSpanRound}}" d:Content="00:00:00" />
</DockPanel>
<DockPanel>
<Label>ファイルを指定して開いた回数</Label>
<Label Content="{Binding NumberOfTimesTheFileWasOpenedBySpecifyingIt.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>自動保存ファイルを指定して開いた回数</Label>
<Label Content="{Binding NumberOfTimesTheAutoSaveFileIsSpecifiedAndOpened.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>ポインターツールでクリックした回数</Label>
<Label Content="{Binding NumberOfClicksWithThePointerTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>なげなわツールで選択したアイテムの累計</Label>
<Label Content="{Binding CumulativeTotalOfItemsSelectedWithTheLassoTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>直線ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheStraightLineTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>四角形ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheRectangleTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>円ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheEllipseTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>画像ファイルツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheImageFileTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>文字ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheLetterTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label>縦書きツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfTheVerticalLetterTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 多角形ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfPolygonTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> ベジエ曲線ツールの描画回数</Label>
<Label Content="{Binding NumberOfDrawsOfBezierCurveTool.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> スナップポイントツールの設置回数</Label>
<Label Content="{Binding NumberOfSnapPointToolInstallations.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> ブラシツールの描画回数</Label>
<Label Content="{Binding BrushToolDrawCount.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 消しゴムツールの使用回数</Label>
<Label Content="{Binding NumberOfTimesTheEraserToolHasBeenUsed.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 上書き保存した回数</Label>
<Label Content="{Binding NumberOfTimesSaved.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 名前を付けて保存した回数</Label>
<Label Content="{Binding NumberOfTimesYouHaveNamedAndSaved.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> エクスポートした回数</Label>
<Label Content="{Binding NumberOfExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> Jpegエクスポートした回数</Label>
<Label Content="{Binding NumberOfJpegExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> PNGエクスポートした回数</Label>
<Label Content="{Binding NumberOfPngExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> GIFエクスポートした回数</Label>
<Label Content="{Binding NumberOfGifExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> BMPエクスポートした回数</Label>
<Label Content="{Binding NumberOfBmpExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> TIFFエクスポートした回数</Label>
<Label Content="{Binding NumberOfTiffExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> WMPエクスポートした回数</Label>
<Label Content="{Binding NumberOfWmpExports.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> グループ化した回数</Label>
<Label Content="{Binding NumberOfTimesGrouped.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> グループ化解除した回数</Label>
<Label Content="{Binding NumberOfUngrouped.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 最前面へ移動した回数</Label>
<Label Content="{Binding NumberOfMovesToTheFrontend.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 前面へ移動した回数</Label>
<Label Content="{Binding NumberOfMovesToTheFront.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 背面へ移動した回数</Label>
<Label Content="{Binding NumberOfMovesToTheBack.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 最背面へ移動した回数</Label>
<Label Content="{Binding NumberOfMovesToTheBackend.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 上揃えした回数</Label>
<Label Content="{Binding NumberOfTopAlignment.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 上下中央揃えした回数</Label>
<Label Content="{Binding NumberOfTimesTheTopAndBottomAreCentered.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 下揃えした回数</Label>
<Label Content="{Binding NumberOfBottomAlignment.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 左揃えした回数</Label>
<Label Content="{Binding NumberOfLeftAlignment.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 左右中央揃えした回数</Label>
<Label Content="{Binding NumberOfTimesLeftAndRightCentered.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 右揃えした回数</Label>
<Label Content="{Binding NumberOfRightAlignment.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 左右に整列した回数</Label>
<Label Content="{Binding NumberOfTimesAlignedLeftAndRight.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 上下に整列した回数</Label>
<Label Content="{Binding NumberOfTimesAlignedUpAndDown.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 幅を合わせた回数</Label>
<Label Content="{Binding NumberOfTimesToMatchTheWidth.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 高さを合わせた回数</Label>
<Label Content="{Binding NumberOfTimesToMatchTheHeight.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> Unionした回数</Label>
<Label Content="{Binding NumberOfUnions.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> Intersectした回数</Label>
<Label Content="{Binding NumberOfIntersects.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> Xorした回数</Label>
<Label Content="{Binding NumberOfXors.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> Excludeした回数</Label>
<Label Content="{Binding NumberOfExcludes.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 切り取りした回数</Label>
<Label Content="{Binding NumberOfCuts.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> コピーした回数</Label>
<Label Content="{Binding NumberOfCopies.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 貼り付けした回数</Label>
<Label Content="{Binding NumberOfPasted.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 元に戻した回数</Label>
<Label Content="{Binding NumberOfUndos.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> やり直しした回数</Label>
<Label Content="{Binding NumberOfRedoes.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 自動保存した回数</Label>
<Label Content="{Binding NumberOfTimesAutomaticallySaved.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 新規作成したレイヤー数</Label>
<Label Content="{Binding NumberOfNewlyCreatedLayers.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> 削除したレイヤー数</Label>
<Label Content="{Binding NumberOfDeletedLayers.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> アイテムを描画した回数</Label>
<Label Content="{Binding NumberOfTimesTheItemWasDrawn.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> アイテムを削除した回数</Label>
<Label Content="{Binding NumberOfTimesTheItemWasDeleted.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> ログレベルを変更した回数</Label>
<Label Content="{Binding NumberOfLogLevelChanges.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> バージョン情報ダイアログを表示した回数</Label>
<Label Content="{Binding NumberOfTimesTheVersionInformationDialogWasDisplayed.Value}" d:Content="0" />
</DockPanel>
<DockPanel>
<Label> アプリケーションログを表示した回数</Label>
<Label Content="{Binding NumberOfTimesTheApplicationLogWasDisplayed.Value}" d:Content="0" />
</DockPanel>
</controls:SimpleGrid>
</UserControl>
注意
Grid.ColumnSpanプロパティやGrid.RowSpanプロパティはSimpleGridの子要素に使っても効果がないので、そこだけ注意してください。
ライセンス
MITライセンスです。
追記(2021/11/16 15時頃)
SimpleGridのソースコードを載せ忘れましたので載せときます。
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace SimpleGridSample
{
[DesignTimeVisible(true)]
public class SimpleGrid : Panel
{
#region 依存プロパティ
public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows",
typeof(int),
typeof(SimpleGrid),
new FrameworkPropertyMetadata(1, new PropertyChangedCallback(SimpleGrid.OnRowsChanged)));
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns",
typeof(int),
typeof(SimpleGrid),
new FrameworkPropertyMetadata(1, new PropertyChangedCallback(SimpleGrid.OnColumnsChanged)));
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation",
typeof(Orientation),
typeof(SimpleGrid),
new FrameworkPropertyMetadata(Orientation.Horizontal));
#endregion //依存プロパティ
#region 依存プロパティコールバック
private static void OnRowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SimpleGrid ctrl = d as SimpleGrid;
if (ctrl != null)
{ }
}
private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SimpleGrid ctrl = d as SimpleGrid;
if (ctrl != null)
{ }
}
#endregion //依存プロパティコールバック
#region CLR プロパティ
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
#endregion //CLR プロパティ
private List<Cell> _cells;
/// <summary>
/// 子要素を配置し、パネルのサイズを決定する。
/// </summary>
/// <param name="finalSize">パネル自体と子要素を配置するために使用する親の末尾の領域。</param>
/// <returns>使用する実際のサイズ。</returns>
protected override Size ArrangeOverride(Size finalSize)
{
Rect viewport = new Rect(new Point(0, 0), finalSize);
if (Orientation == Orientation.Horizontal)
{
LinkedList<List<UIElement>> rows = GetChildrenStructure();
var iterator = rows.First;
for (int y = 0; y < rows.Count; ++y)
{
var topHeight = _cells.Where(a => a.Y < y).GroupBy(b => b.Y).Sum(c => c.Max(d => d.Height));
var heightOn = _cells.Where(a => a.Y == y).Max(b => b.Height);
var row = iterator.Value;
if (row == null) continue;
for (int x = 0; x < row.Count; ++x)
{
var cell = row[x];
var leftWidth = _cells.Where(a => a.X < x).GroupBy(b => b.X).Sum(c => c.Max(d => d.Width));
var widthOn = _cells.Where(a => a.X == x).Max(b => b.Width);
Rect finalRect = new Rect(leftWidth, topHeight, widthOn, heightOn);
cell.Arrange(finalRect);
}
iterator = iterator.Next;
}
}
else
{
LinkedList<List<UIElement>> cols = GetChildrenStructure();
var iterator = cols.First;
for (int x = 0; x < cols.Count; ++x)
{
var leftWidth = _cells.Where(a => a.X < x).GroupBy(b => b.X).Sum(c => c.Max(d => d.Width));
var widthOn = _cells.Where(a => a.X == x).Max(b => b.Width);
var row = iterator.Value;
if (row == null) continue;
for (int y = 0; y < row.Count; ++y)
{
var cell = row[y];
var topHeight = _cells.Where(a => a.Y < y).GroupBy(b => b.Y).Sum(c => c.Max(d => d.Height));
var heightOn = _cells.Where(a => a.Y == y).Max(b => b.Height);
Rect finalRect = new Rect(leftWidth, topHeight, widthOn, heightOn);
cell.Arrange(finalRect);
}
iterator = iterator.Next;
}
}
return finalSize;
}
/// <summary>
/// 子要素に必要なレイアウトのサイズを測定し、パネルのサイズを決定する。
/// </summary>
/// <param name="availableSize">子要素に与えることができる使用可能なサイズ。</param>
/// <returns>レイアウト時にこのパネルが必要とするサイズ。</returns>
protected override Size MeasureOverride(Size availableSize)
{
LinkedList<List<UIElement>> rows = GetChildrenStructure();
_cells = getCellsSize(Orientation, availableSize, rows);
double totalMaxWidth = _cells.GroupBy(a => a.X).Sum(b => b.Max(c => c.Width));
double totalMaxHeight = _cells.GroupBy(a => a.Y).Sum(b => b.Max(c => c.Height));
return new Size(totalMaxWidth, totalMaxHeight);
}
private static List<Cell> getCellsSize(Orientation Orientation, Size availableSize, LinkedList<List<UIElement>> rows)
{
List<Cell> cells = new List<Cell>();
if (Orientation == Orientation.Horizontal)
{
var iterator = rows.First;
for (int y = 0; y < rows.Count; ++y)
{
var row = iterator.Value;
for (int x = 0; x < row.Count; ++x)
{
var cell = row[x];
cell.Measure(availableSize);
var disiredSize = cell.DesiredSize;
cells.Add(new Cell(x, y, disiredSize.Width, disiredSize.Height));
}
iterator = iterator.Next;
}
}
else
{
var iterator = rows.First;
for (int x = 0; x < rows.Count; ++x)
{
var row = iterator.Value;
for (int y = 0; y < row.Count; ++y)
{
var cell = row[y];
cell.Measure(availableSize);
var disiredSize = cell.DesiredSize;
cells.Add(new Cell(x, y, disiredSize.Width, disiredSize.Height));
}
iterator = iterator.Next;
}
}
return cells;
}
private LinkedList<List<UIElement>> GetChildrenStructure()
{
LinkedList<List<UIElement>> rows = new LinkedList<List<UIElement>>();
if (Orientation == Orientation.Horizontal)
{
foreach (UIElement child in this.InternalChildren)
{
var currentList = rows.Last?.Value;
if (currentList == null || (currentList.Count == Columns && rows.Count < Rows))
{
var list = new List<UIElement>();
list.Add(child);
rows.AddLast(list);
}
else if (currentList.Count < Columns)
{
currentList.Add(child);
}
}
}
else
{
foreach (UIElement child in this.InternalChildren)
{
var currentList = rows.Last?.Value;
if (currentList == null || (currentList.Count == Rows && rows.Count < Columns))
{
var list = new List<UIElement>();
list.Add(child);
rows.AddLast(list);
}
else if (currentList.Count < Rows)
{
currentList.Add(child);
}
}
}
return rows;
}
private class Cell
{
internal int X { get; set; }
internal int Y { get; set; }
internal double Width { get; set; }
internal double Height { get; set; }
internal Cell(int x, int y, double width, double height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
public override string ToString()
{
return $"{{X={X}, Y={Y}, W={Width}, H={Height}}}";
}
}
}
}