using System.Windows.Controls; using WPFluent.Extensions; namespace WPFluent.Layout { /// /// /// /// 注意整个XAML文档控件的顺序,使用Grid.Row和Grid.Column应逐行添加控件,对于嵌套的AutoGrid,其ChildMargin,HorizonAlignment,VerticalAlignment会继承 public class AutoGrid : Grid { // 附加属性 AutoIndex public static readonly DependencyProperty AutoIndexProperty = DependencyProperty.RegisterAttached( "AutoIndex", typeof(bool), typeof(AutoGrid), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsParentMeasure)); public static readonly DependencyProperty ChildHorizontalAlignmentProperty = DependencyProperty.Register( nameof(ChildHorizontalAlignment), typeof(HorizontalAlignment?), typeof(AutoGrid), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnChildHorizontalAlignmentChanged)); public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register( nameof(ChildMargin), typeof(Thickness?), typeof(AutoGrid), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnChildMarginChanged)); public static readonly DependencyProperty ChildVerticalAlignmentProperty = DependencyProperty.Register( nameof(ChildVerticalAlignment), typeof(VerticalAlignment?), typeof(AutoGrid), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnChildVerticalAlignmentChanged)); public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register( nameof(Columns), typeof(string), typeof(AutoGrid), new FrameworkPropertyMetadata( string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnColumnsChanged)); public static readonly DependencyProperty ColumnWidthOverrideProperty = DependencyProperty.RegisterAttached( "ColumnWidthOverride", typeof(GridLength?), typeof(AutoGrid), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register( nameof(ColumnWidth), typeof(GridLength), typeof(AutoGrid), new FrameworkPropertyMetadata( GridLength.Auto, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnFixedColumnWidthChanged)); public static readonly DependencyProperty IsAutoIndexingProperty = DependencyProperty.Register( nameof(IsAutoIndexing), typeof(bool), typeof(AutoGrid), new FrameworkPropertyMetadata( true, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnPropertyChanged)); public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( nameof(Orientation), typeof(Orientation), typeof(AutoGrid), new FrameworkPropertyMetadata( Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnPropertyChanged)); // RowHeight 重载附加属性 public static readonly DependencyProperty RowHeightOverrideProperty = DependencyProperty.RegisterAttached( "RowHeightOverride", typeof(GridLength?), typeof(AutoGrid), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register( nameof(RowHeight), typeof(GridLength), typeof(AutoGrid), new FrameworkPropertyMetadata( GridLength.Auto, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnFixedRowHeightChanged)); public static readonly DependencyProperty RowsProperty = DependencyProperty.Register( nameof(Rows), typeof(string), typeof(AutoGrid), new FrameworkPropertyMetadata( string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, OnRowsChanged)); int rowOrColumnCount; bool shouldReindex = true; public AutoGrid() { Loaded += AutoGrid_Loaded; } /// /// 应用默认子边距和对齐等布局效果 /// void ApplyChildDefaultLayout(UIElement child) { if (ChildMargin != null) { child.SetIfDefault(MarginProperty, ChildMargin.Value); } if (ChildHorizontalAlignment != null) { child.SetIfDefault(HorizontalAlignmentProperty, ChildHorizontalAlignment.Value); } if (ChildVerticalAlignment != null) { child.SetIfDefault(VerticalAlignmentProperty, ChildVerticalAlignment.Value); } } /// /// 应用子边距和对齐等布局效果 /// void ApplyChildLayout(UIElement child) { if (ChildMargin.HasValue) { child.SetValue(ChildMarginProperty, ChildMargin); } if (ChildHorizontalAlignment.HasValue) { child.SetValue(HorizontalAlignmentProperty, ChildHorizontalAlignment); } if (ChildVerticalAlignment.HasValue) { child.SetValue(VerticalAlignmentProperty, ChildVerticalAlignment); } } private void AutoGrid_Loaded(object sender, RoutedEventArgs e) { Loaded -= AutoGrid_Loaded; if (Children.Count == 0) { return; } foreach (UIElement elem in Children) { ApplyChildLayout(elem); } } /// /// 改变【子水平对齐方式】时调用。 /// private static void OnChildHorizontalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AutoGrid grid) { foreach (UIElement child in grid.Children) { child.SetValue(HorizontalAlignmentProperty, grid.ChildHorizontalAlignment ?? DependencyProperty.UnsetValue); } } } /// /// 【子布局更改】时调用。 /// private static void OnChildMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AutoGrid grid) { foreach (UIElement child in grid.Children) { child.SetValue(MarginProperty, grid.ChildMargin ?? DependencyProperty.UnsetValue); } } } /// /// 改变【子垂直排列方式】时调用。 /// private static void OnChildVerticalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AutoGrid grid) { foreach (UIElement child in grid.Children) { child.SetValue(VerticalAlignmentProperty, grid.ChildVerticalAlignment ?? DependencyProperty.UnsetValue); } } } /// /// 处理列更改事件 /// private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((string)e.NewValue == string.Empty) { return; } if (d is AutoGrid grid) { grid.ColumnDefinitions.Clear(); var defs = Parse((string)e.NewValue); foreach (var def in defs) grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = def }); } } /// /// 处理固定列宽更改事件 /// private static void OnFixedColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AutoGrid grid) { // 如果缺少,添加默认列 if (grid.ColumnDefinitions.Count == 0) grid.ColumnDefinitions.Add(new ColumnDefinition()); // 将所有现有列设置为该宽度 for (var i = 0; i < grid.ColumnDefinitions.Count; i++) grid.ColumnDefinitions[i].Width = (GridLength)e.NewValue; } } /// /// 处理固定行高更改事件 /// private static void OnFixedRowHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is AutoGrid grid) { // 如果缺失,则添加默认行 if (grid.RowDefinitions.Count == 0) grid.RowDefinitions.Add(new RowDefinition()); // 将所有现有行设置为该高度 for (var i = 0; i < grid.RowDefinitions.Count; i++) grid.RowDefinitions[i].Height = (GridLength)e.NewValue; } } /// /// 处理重绘属性更改事件 /// static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((AutoGrid)d).shouldReindex = true; } /// /// 处理行修改事件 /// private static void OnRowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((string)e.NewValue == string.Empty) { return; } if (d is AutoGrid grid) { grid.RowDefinitions.Clear(); var defs = Parse((string)e.NewValue); foreach (var def in defs) grid.RowDefinitions.Add(new RowDefinition() { Height = def }); } } ///// ///// 将某一数值限制在最大值。 ///// //int Clamp(int value, int max) //{ // return value > max ? max : value; //} /// /// 从逗号分隔符文本中解析网格长度数组 /// private static GridLength[] Parse(string text) { var tokens = text.Split(','); var definitions = new GridLength[tokens.Length]; for (var i = 0; i < tokens.Length; i++) { var str = tokens[i]; double value; // 占比 if (str.Contains('*')) { if (!double.TryParse(str.Replace("*", string.Empty), out value)) value = 1.0; definitions[i] = new GridLength(value, GridUnitType.Star); continue; } // 像素 if (double.TryParse(str, out value)) { definitions[i] = new GridLength(value); continue; } if (str.Equals("auto", StringComparison.CurrentCultureIgnoreCase)) { definitions[i] = GridLength.Auto; } //definitions[i] = GridLength.Auto; } return definitions; } private void PerformLayout() { var isVertical = Orientation == Orientation.Vertical; if (shouldReindex || IsAutoIndexing && (isVertical && rowOrColumnCount != ColumnDefinitions.Count || !isVertical && rowOrColumnCount != RowDefinitions.Count)) { shouldReindex = false; if (IsAutoIndexing) { rowOrColumnCount = ColumnDefinitions.Count != 0 ? ColumnDefinitions.Count : RowDefinitions.Count; if (rowOrColumnCount == 0) rowOrColumnCount = 1; var cellCount = 0; foreach (UIElement child in Children) { if (GetAutoIndex(child) == false) { continue; } cellCount += ColumnDefinitions.Count != 0 ? GetColumnSpan(child) : GetRowSpan(child); } // 更新行/列数 if (ColumnDefinitions.Count != 0) { var newRowCount = (int)Math.Ceiling(cellCount / (double)rowOrColumnCount); while (RowDefinitions.Count < newRowCount) { var rowDefinition = new RowDefinition { Height = RowHeight }; RowDefinitions.Add(rowDefinition); } if (RowDefinitions.Count > newRowCount) { RowDefinitions.RemoveRange(newRowCount, RowDefinitions.Count - newRowCount); } } else // 已定义的行 { var newColumnCount = (int)Math.Ceiling(cellCount / (double)rowOrColumnCount); while (ColumnDefinitions.Count < newColumnCount) { var columnDefinition = new ColumnDefinition { Width = ColumnWidth }; ColumnDefinitions.Add(columnDefinition); } if (ColumnDefinitions.Count > newColumnCount) { ColumnDefinitions.RemoveRange(newColumnCount, ColumnDefinitions.Count - newColumnCount); } } } // 更新子项索引 var cellPosition = 0; var cellsToSkip = new Queue(); foreach (UIElement child in Children) { if (IsAutoIndexing && GetAutoIndex(child)) { if (cellsToSkip.Any() && cellsToSkip.Peek() == cellPosition) { cellsToSkip.Dequeue(); cellPosition += 1; } if (!isVertical) // 水平(默认) { var rowIndex = cellPosition / ColumnDefinitions.Count; SetRow(child, rowIndex); var columnIndex = cellPosition % ColumnDefinitions.Count; SetColumn(child, columnIndex); var rowSpan = GetRowSpan(child); if (rowSpan > 1) { Enumerable .Range(1, rowSpan) .ToList() .ForEach(x => cellsToSkip.Enqueue(cellPosition + ColumnDefinitions.Count * x)); } var overrideRowHeight = GetRowHeightOverride(child); if (overrideRowHeight != null) { RowDefinitions[rowIndex].Height = overrideRowHeight.Value; } var overrideColumnWidth = GetColumnWidthOverride(child); if (overrideColumnWidth != null) { ColumnDefinitions[columnIndex].Width = overrideColumnWidth.Value; } cellPosition += GetColumnSpan(child); } else { var rowIndex = cellPosition % RowDefinitions.Count; SetRow(child, rowIndex); var columnIndex = cellPosition / RowDefinitions.Count; SetColumn(child, columnIndex); var columnSpan = GetColumnSpan(child); if (columnSpan > 1) { Enumerable .Range(1, columnSpan) .ToList() .ForEach(x => cellsToSkip.Enqueue(cellPosition + RowDefinitions.Count * x)); } var overrideRowHeight = GetRowHeightOverride(child); if (overrideRowHeight != null) { RowDefinitions[rowIndex].Height = overrideRowHeight.Value; } var overrideColumnWidth = GetColumnWidthOverride(child); if (overrideColumnWidth != null) { ColumnDefinitions[columnIndex].Width = overrideColumnWidth.Value; } cellPosition += GetRowSpan(child); } } // 设置默认页边距和对齐方式 ApplyChildDefaultLayout(child); } } } /// /// 测量 的子网格,以便在 传递期间整理它们。 /// /// 表示不应超过的上限大小。 /// /// 表示安排子内容所需的大小。 /// protected override Size MeasureOverride(Size constraint) { PerformLayout(); return base.MeasureOverride(constraint); } /// /// 当 元素的可视化子元素发生变化时调用。 用于标记网格已更改子项。 /// /// 标识添加的Visual子项。 /// 标识移除的Visual子项。 protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { shouldReindex = true; base.OnVisualChildrenChanged(visualAdded, visualRemoved); } public static bool GetAutoIndex(DependencyObject element) { return (bool)element.GetValue(AutoIndexProperty); } public static GridLength? GetColumnWidthOverride(DependencyObject element) { return (GridLength?)element.GetValue(ColumnWidthOverrideProperty); } public static GridLength? GetRowHeightOverride(DependencyObject element) { return (GridLength?)element.GetValue(RowHeightOverrideProperty); } public static void SetAutoIndex(DependencyObject element, bool value) { element.SetValue(AutoIndexProperty, value); } public static void SetColumnWidthOverride(DependencyObject element, GridLength? value) { element.SetValue(ColumnWidthOverrideProperty, value); } public static void SetRowHeightOverride(DependencyObject element, GridLength? value) { element.SetValue(RowHeightOverrideProperty, value); } /// /// 获取或设置子项水平对齐方式。 /// /// 子项水平对齐。 [Category("Layout"), Description("预设所有子控件的水平对齐方式")] public HorizontalAlignment? ChildHorizontalAlignment { get => (HorizontalAlignment?)GetValue(ChildHorizontalAlignmentProperty); set => SetValue(ChildHorizontalAlignmentProperty, value); } ///// ///// 行数 ///// //public int RowCount //{ // get { return (int)GetValue(RowCountProperty); } // set { SetValue(RowCountProperty, value); } //} //// Using a DependencyProperty as the backing store for RowCount. This enables animation, styling, binding, etc... //public static readonly DependencyProperty RowCountProperty = DependencyProperty.Register( // nameof(RowCount), // typeof(int), // typeof(AutoGrid), // new PropertyMetadata(1, new PropertyChangedCallback(OnRowCountChanged)) //); ///// ///// 处理行数修改事件 ///// //private static void OnRowCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) //{ // if (d is AutoGrid grid) // { // if ((int)e.NewValue < 0 || grid.Rows != string.Empty) // { // return; // } // // 查找现有行以获取高度 // var height = GridLength.Auto; // //var height = new GridLength(1, GridUnitType.Star); // if (grid.RowDefinitions.Count > 0) // height = grid.RowDefinitions[0].Height; // // 清理和重建 // grid.RowDefinitions.Clear(); // for (var i = 0; i < (int)e.NewValue; i++) // grid.RowDefinitions.Add(new RowDefinition() { Height = height }); // } //} ///// ///// 列数量 ///// //public int ColumnCount //{ // get { return (int)GetValue(ColumnCountProperty); } // set { SetValue(ColumnCountProperty, value); } //} //// Using a DependencyProperty as the backing store for RowCount. This enables animation, styling, binding, etc... //public static readonly DependencyProperty ColumnCountProperty = DependencyProperty.Register( // nameof(ColumnCount), // typeof(int), // typeof(AutoGrid), // new PropertyMetadata(1, new PropertyChangedCallback(OnColumnCountChanged)) //); ///// ///// 处理列数更改事件 ///// //private static void OnColumnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) //{ // if (d is AutoGrid grid) // { // if ((int)e.NewValue < 0 || grid.Columns != string.Empty) // { // return; // } // // 查找高度的现有列定义 // var width = GridLength.Auto; // //均分 // //var width = new GridLength(1, GridUnitType.Star); // if (grid.ColumnDefinitions.Count > 0) // width = grid.ColumnDefinitions[0].Width; // // 清理和重建 // grid.ColumnDefinitions.Clear(); // for (var i = 0; i < (int)e.NewValue; i++) // grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = width }); // } //} /// /// 获取或设置子项边距。 /// /// 子项边距 [Category("Layout"), Description("预设所有子控件的边距")] public Thickness? ChildMargin { get => (Thickness?)GetValue(ChildMarginProperty); set => SetValue(ChildMarginProperty, value); } /// /// 获取或设置子项垂直对齐方式。 /// /// 子项垂直排列。 [Category("Layout"), Description("预设所有子控件的垂直对齐方式")] public VerticalAlignment? ChildVerticalAlignment { get => (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty); set => SetValue(ChildVerticalAlignmentProperty, value); } /// /// 获取或设置列,优先级高于ColumnCount /// [Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有列")] public string Columns { get => (string)GetValue(ColumnsProperty); set => SetValue(ColumnsProperty, value); } /// /// 获取或设置固定列宽,默认自动 /// [Category("Layout"), Description("预设使用 ColumnCount 属性设置的所有列的宽度")] public GridLength ColumnWidth { get => (GridLength)GetValue(ColumnWidthProperty); set => SetValue(ColumnWidthProperty, value); } /// /// 获取或设置表示是否自动索引子项的值。 默认值为true. 请注意,如果子项已有索引,将此属性设置为 false 将不会移除其索引。 /// [Category("Layout"), Description("设置为 false 则禁用自动布局功能")] public bool IsAutoIndexing { get => (bool)GetValue(IsAutoIndexingProperty); set => SetValue(IsAutoIndexingProperty, value); } /// /// 获取或设置方向。 默认值为垂直。 /// /// 定向。 [Category("Layout"), Description("定义自动布局的方向性。列优先布局使用垂直方向,行优先布局使用水平方向。")] public Orientation Orientation { get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } /// /// 获取或设置固定行高 /// [Category("Layout"), Description("预设使用行数属性设置的所有行的高度")] public GridLength RowHeight { get => (GridLength)GetValue(RowHeightProperty); set => SetValue(RowHeightProperty, value); } /// /// 获取或设置行数,优先级高于RowCount /// [Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有行")] public string Rows { get => (string)GetValue(RowsProperty); set => SetValue(RowsProperty, value); } } }