WPF中Grid自动布局

控件功能

根据自定义行和列,快速进行排列,能够进行自定义控件间距离,减少元素进行定义间距,同时能更好的维护界面排序。

代码部分

csharp 复制代码
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace WPFApp
{
    public class AutoGrid : Grid
    {
        /// <summary>
        /// 列定义 例如: "100,*,100"  "100 * 100"
        /// </summary>
        [Category("Layout")]
        public string Columns
        {
            get { return (string)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register(nameof(Columns), typeof(string), typeof(AutoGrid), new PropertyMetadata("", ColumnsChanged));

        /// <summary>
        /// 行定义 例如: "100,*,100"  "100 * 100"    
        /// </summary>
        [Category("Layout")]
        public string Rows
        {
            get { return (string)GetValue(RowsProperty); }
            set { SetValue(RowsProperty, value); }
        }

        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register(nameof(Rows), typeof(string), typeof(AutoGrid), new PropertyMetadata("", RowsChange));

        /// <summary>
        /// 排列方式
        /// </summary>
        [Category("Layout")]
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }
        public static readonly DependencyProperty OrientationProperty =
    DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(AutoGrid), new PropertyMetadata(Orientation.Horizontal));

        /// <summary>
        /// 元素之间Margin
        /// </summary>
        [Category("Layout")]
        public Thickness? ChildMargin
        {
            get { return (Thickness?)GetValue(ChildMarginProperty); }
            set { SetValue(ChildMarginProperty, value); }
        }

        public static readonly DependencyProperty ChildMarginProperty =
DependencyProperty.Register(nameof(ChildMargin), typeof(Thickness?), typeof(AutoGrid), new PropertyMetadata((Thickness?)null, OnChildMarginChanged));

        /// <summary>
        /// 移除多余的元素
        /// </summary>
        public bool IsRemoveEleme { get; set; } = true;

        private static void OnChildMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var grid = d as AutoGrid;
            foreach (UIElement child in grid.Children)
            {
                if (grid.ChildMargin.HasValue)
                    child.SetValue(FrameworkElement.MarginProperty, grid.ChildMargin);
                else
                    child.SetValue(FrameworkElement.MarginProperty, DependencyProperty.UnsetValue);
            }
        }

        protected override Size MeasureOverride(Size constraint)
        {
            PerformLayout();
            return base.MeasureOverride(constraint);
        }

        private void PerformLayout()
        {
            int colCount = this.ColumnDefinitions.Count;
            int rowCount = this.RowDefinitions.Count;
            if (colCount == 0 || rowCount == 0) return;

            var position = 0;

            foreach (UIElement child in Children)
            {
                var childIsCollapsed = child.Visibility == Visibility.Collapsed;
                if (childIsCollapsed) continue;

                if (this.Orientation == Orientation.Horizontal)
                {
                    //从左往右,再从上往下
                    var row = Clamp(position / colCount, rowCount - 1);
                    var col = Clamp(position % colCount, colCount - 1);
                    Grid.SetRow(child, row);
                    Grid.SetColumn(child, col);
                    Grid.SetRowSpan(child, 1);
                    position += Grid.GetColumnSpan(child);
                }
                else
                {
                    //从上往下,再从左往右
                    var row = Clamp(position % rowCount, rowCount - 1);
                    var col = Clamp(position / rowCount, colCount - 1);
                    Grid.SetRow(child, row);
                    Grid.SetColumn(child, col);
                    Grid.SetColumnSpan(child, 1);
                    position += Grid.GetRowSpan(child);
                }

                //设置Margin
                if (ChildMargin.HasValue)
                {
                    child.SetValue(FrameworkElement.MarginProperty, ChildMargin);
                }
                else
                {
                    child.SetValue(FrameworkElement.MarginProperty, DependencyProperty.UnsetValue);
                }
            }

            //移除无法分配的元素
            if (IsRemoveEleme && Children.Count > colCount * rowCount)
            {
                Children.RemoveRange(colCount * rowCount, Children.Count - colCount * rowCount);
            }
        }

        private int Clamp(int value, int max)
        {
            return (value > max) ? max : value;
        }

        private static void RowsChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (string.IsNullOrEmpty((string)e.NewValue)) return;

            var grid = (AutoGrid)d;
            grid.RowDefinitions.Clear();
            GridLength[] defs = GridLengthParse((string)e.NewValue);
            foreach (var def in defs)
            {
                grid.RowDefinitions.Add(new RowDefinition()
                {
                    Height = def
                });
            }
        }

        private static void ColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (string.IsNullOrEmpty((string)e.NewValue)) return;

            var grid = (AutoGrid)d;
            grid.ColumnDefinitions.Clear();
            GridLength[] defs = GridLengthParse((string)e.NewValue);
            foreach (var def in defs)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition()
                {
                    Width = def
                });
            }
        }

        public static GridLength[] GridLengthParse(string text)
        {
            string[] tokens = text.Split([' ', ','], StringSplitOptions.RemoveEmptyEntries);
            GridLength[] defineResult = new GridLength[tokens.Length];

            for (int i = 0; i < tokens.Count(); i++)
            {
                string curStr = tokens[i];
                double value = 0.0;

                //Star
                if (curStr.Contains('*'))
                {
                    if (!double.TryParse(curStr.Replace("*", ""), out value))
                    {
                        value = 1.0;
                    }
                    defineResult[i] = new GridLength(value, GridUnitType.Star);
                }

                //Pixel
                if (double.TryParse(curStr, out value))
                {
                    defineResult[i] = new GridLength(value, GridUnitType.Pixel);
                    continue;
                }

                //Auto
                defineResult[i] = GridLength.Auto;
            }

            return defineResult;
        }
    }
}

使用示例

csharp 复制代码
<Window
    x:Class="WPFApp.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:local="clr-namespace:WPFApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <local:AutoGrid
        ChildMargin="10,10,0,0"
        Columns="100 200 300 400"
        Orientation="Horizontal"
        Rows="200 *">
        <TextBlock Text="1" />
        <TextBlock Text="2" />
        <TextBlock Text="3" />
        <TextBlock Text="4" />
        <TextBlock Text="5" />
        <TextBlock Text="6" />
        <TextBlock Text="7" />
        <TextBlock Text="8" />
        <TextBlock Text="9" />
        <TextBlock Text="10" />
        <TextBlock Text="11" />
        <TextBlock Text="12" />
    </local:AutoGrid>
</Window>

图示

相关推荐
玖笙&3 天前
✨WPF编程基础【2.1】布局原则
c++·wpf·visual studio
玖笙&3 天前
✨WPF编程基础【2.2】:布局面板实战
c++·wpf·visual studio
SEO-狼术3 天前
.NET WPF 数据编辑器集合提供列表框控件
.net·wpf
FuckPatience7 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白7 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu7 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu7 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野8 天前
WPF——效果和可视化对象
wpf
He BianGu8 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者9 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf