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>

图示

相关推荐
Aevget5 小时前
DevExpress WPF中文教程:Data Grid - 如何使用虚拟源?(一)
c#·wpf·界面控件·devexpress·ui开发
The Sheep 20232 天前
WPF自定义路由事件
大数据·hadoop·wpf
阳光雨滴2 天前
使用wpf用户控件编程落石效果动画
c++·wpf
wuty0072 天前
WPF 调用 ChangeWindowMessageFilterEx 修改指定窗口 (UIPI) 消息筛选器的用户界面特权隔离
wpf·sendmessage·changewindowmessagefilterex·uip·消息筛选器的用户界面特权隔离·window message
攻城狮CSU2 天前
WPF中核心接口 INotifyPropertyChanged
wpf
c#上位机2 天前
wpf之Interaction.Triggers
c#·wpf
是木子啦2 天前
wpf passwordbox控件 光标移到最后
c#·wpf
The Sheep 20232 天前
wpf 命令理解
wpf
布伦鸽2 天前
C# WPF DataGrid使用Observable<Observable<object>类型作为数据源
开发语言·c#·wpf
分布式存储与RustFS3 天前
告别复杂配置:用Milvus、RustFS和Vibe Coding,60分钟DIY专属Chatbot
wpf·文件系统·milvus·对象存储·minio·rustfs·vibe