LoginSignup
13
11

More than 1 year has passed since last update.

[WPF]便利なPanelを作りました

Last updated at Posted at 2021-11-11

こんばんは。

結構便利で重宝しているPanelを作ったので、共有したいと思います。

SimpleGrid

左の赤い枠内が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を使うと良い具合になります。

2021-11-11.png

<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}}}";
            }
        }
    }
}

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11