目录

WPF-Grid自动排列(一个附加属性)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp7
{
    static class GridAssist
    {
        #region AutoRowColumn

        public static string GetAutoRowColumn(DependencyObject obj)
        {
            return (string)obj.GetValue(AutoRowColumnProperty);
        }

        public static void SetAutoRowColumn(DependencyObject obj, string value)
        {
            obj.SetValue(AutoRowColumnProperty, value);
        }

        /// <summary>
        /// 自动排列 Grid 容器中的所有控件
        /// </summary>
        /// <remarks>
        /// 值为一个逗号隔开的字符串,形如:<br/>
        /// - 2,3(2 行 3 列,列宽默认为 1*,行高为 Auto)<br/>
        /// - _,3(3 列,行数根据子控件而定)<br/>
        /// - 2,3,Auto(列的宽度为 Auto)<br/>
        /// Grid 中的所有控件将自动按照从左到右、从上到下的顺序进行排列<br/>
        /// 控件各自的 RowSpan 以及 ColumnSpan 也会被考虑
        /// </remarks>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// <Window xmlns:ap="clr-namespace:NemoDemo.AttachedProperties">
        ///     <Grid ap:GridHelper.AutoRowColumn="2,3">
        ///         <Button />                     // 1,1
        ///         <Label />                      // 1,2
        ///         <TextBox />                    // 1,3
        ///         <Button />                     // 2,1
        ///         <Label Grid.ColumnSpan="2" />  // 2,2-3
        ///     </Grid>
        /// </Window>
        /// ]]>
        /// </code>
        /// </example>
        public static readonly DependencyProperty AutoRowColumnProperty = DependencyProperty.RegisterAttached(
            "AutoRowColumn",
            typeof(string),
            typeof(GridAssist),
            new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsMeasure, OnAutoRowColumnChanged)
        );

        private static void OnAutoRowColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is Grid grid))
            {
                return;
            }

            var value = e.NewValue as string;
            if (string.IsNullOrEmpty(value))
            {
                grid.RowDefinitions.Clear();
                grid.ColumnDefinitions.Clear();

                grid.Loaded -= OnGridLoaded;
                return;
            }

            grid.Loaded += OnGridLoaded;

            if (grid.IsLoaded)
            {
                OnGridLoaded(grid, null);
            }
        }

        private static void OnGridLoaded(object sender, RoutedEventArgs e)
        {
            var grid = sender as Grid;

            // 列宽,默认为 Star,即平均分布
            var width = new GridLength(1.0, GridUnitType.Star);
            var split = GridAssist.GetAutoRowColumn(grid).Split(',');
            var r = split[0] != "_" ? int.Parse(split[0]) : 1;
            var c = int.Parse(split[1]);
            // 如果有第三个参数且值为 auto,则宽度为 Auto
            if (split.Length == 3 && split[2].Equals("auto", StringComparison.OrdinalIgnoreCase))
            {
                width = GridLength.Auto;
            }

            grid.RowDefinitions.Clear();
            grid.ColumnDefinitions.Clear();
            for (int i = 0; i < r; i++)
            {
                grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            }

            for (int i = 0; i < c; i++)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition { Width = width });
            }

            var cols = grid.ColumnDefinitions.Count;

            var map = new List<bool[]>();

            int x = 0,
                y = 0;
            foreach (UIElement item in grid.Children)
            {
                var rowSpan = Grid.GetRowSpan(item);
                var colSpan = Grid.GetColumnSpan(item);

                // 默认从上到下,从左到右,即任何时候控件的下方和右方都是空的
                // 可能会出现中途有一个 RowSpan > 1 导致其左下方的右侧不为空的情况,暂不处理
                // 同时默认当前的 (x, y) 位置是一个可用位置

                // 当前控件占据的格子
                for (int i = 0; i < rowSpan; i++)
                {
                    // 如果 RowDefinition 不够用,则自动添加
                    if (map.Count <= y + i)
                    {
                        while (map.Count <= y + i)
                        {
                            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                            map.Add(new bool[cols]);
                        }
                    }

                    map[y + i][x] = true;
                }

                for (int i = 0; i < colSpan; i++)
                {
                    if (x + i >= cols)
                    {
                        break;
                    }

                    map[y][x + i] = true;
                }

                Grid.SetRow(item, y);
                Grid.SetColumn(item, x);

                // 寻找下一个可用的格子
                while (map[y][x])
                {
                    x++;
                    if (x >= cols)
                    {
                        x = 0;
                        y++;
                        if (y >= map.Count)
                        {
                            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                            map.Add(new bool[cols]);
                        }
                    }
                }
            }
        }

        #endregion
    }
}
<Window
    x:Class="WpfApp7.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp7"
    Title="MainWindow"
    Width="500"
    Height="350">
    <Grid Margin="20" local:GridAssist.AutoRowColumn="2,3">
        <Button Margin="5" Content="Button 1" />
        <Label
            Margin="5"
            Background="LightBlue"
            Content="Label 1" />
        <TextBox Margin="5" Text="TextBox 1" />

        <Button Margin="5" Content="Button 2" />

        <Label
            Grid.ColumnSpan="2"
            Margin="5"
            Background="LightGreen"
            Content="Span 2 Columns" />
    </Grid>
</Window>

自动行数

AutoRowColumn="_,3"

含义:

3 columns
rows auto expand

Auto 列宽

AutoRowColumn="2,3,Auto"

列宽:

ColumnWidth = Auto

不是 *