using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Wpf.Ui.Extend.Extensions;
namespace Wpf.Ui.Extend.Layouts;
///
///
///
/// 注意整个XAML文档控件的顺序,使用Grid.Row和Grid.Column应逐行添加控件,对于嵌套的AutoGrid,其ChildMargin,HorizonAlignment,VerticalAlignment会继承
public class AutoGrid : Grid
{
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);
}
}
/////
///// 将某一数值限制在最大值。
/////
//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("*", ""), 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) == true)
{
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);
}
}
}
#region 附加属性
// 附加属性 AutoIndex
public static readonly DependencyProperty AutoIndexProperty = DependencyProperty.RegisterAttached(
"AutoIndex",
typeof(bool),
typeof(AutoGrid),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsParentMeasure)
);
public static readonly DependencyProperty ColumnWidthOverrideProperty = DependencyProperty.RegisterAttached(
"ColumnWidthOverride",
typeof(GridLength?),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
default(GridLength?),
FrameworkPropertyMetadataOptions.AffectsParentMeasure
| FrameworkPropertyMetadataOptions.AffectsArrange
| FrameworkPropertyMetadataOptions.AffectsMeasure
)
);
// RowHeight 重载附加属性
public static readonly DependencyProperty RowHeightOverrideProperty = DependencyProperty.RegisterAttached(
"RowHeightOverride",
typeof(GridLength?),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
default(GridLength?),
FrameworkPropertyMetadataOptions.AffectsParentMeasure
| FrameworkPropertyMetadataOptions.AffectsArrange
| FrameworkPropertyMetadataOptions.AffectsMeasure
)
);
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);
}
#endregion
#region 依赖属性
public static readonly DependencyProperty ChildHorizontalAlignmentProperty = DependencyProperty.Register(
nameof(ChildHorizontalAlignment),
typeof(HorizontalAlignment?),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnChildHorizontalAlignmentChanged)
)
);
///
/// 改变【子水平对齐方式】时调用。
///
private static void OnChildHorizontalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AutoGrid grid)
{
foreach (UIElement child in grid.Children)
{
if (grid.ChildHorizontalAlignment.HasValue)
child.SetValue(HorizontalAlignmentProperty, grid.ChildHorizontalAlignment);
else
child.SetValue(HorizontalAlignmentProperty, DependencyProperty.UnsetValue);
}
}
}
public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register(
nameof(ChildMargin),
typeof(Thickness?),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnChildMarginChanged)
)
);
///
/// 【子布局更改】时调用。
///
private static void OnChildMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AutoGrid grid)
{
foreach (UIElement child in grid.Children)
{
if (grid.ChildMargin.HasValue)
child.SetValue(MarginProperty, grid.ChildMargin);
else
child.SetValue(MarginProperty, DependencyProperty.UnsetValue);
}
}
}
public static readonly DependencyProperty ChildVerticalAlignmentProperty = DependencyProperty.Register(
nameof(ChildVerticalAlignment),
typeof(VerticalAlignment?),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnChildVerticalAlignmentChanged)
)
);
///
/// 改变【子垂直排列方式】时调用。
///
private static void OnChildVerticalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AutoGrid grid)
{
foreach (UIElement child in grid.Children)
{
if (grid.ChildVerticalAlignment.HasValue)
child.SetValue(VerticalAlignmentProperty, grid.ChildVerticalAlignment);
else
child.SetValue(VerticalAlignmentProperty, DependencyProperty.UnsetValue);
}
}
}
public static readonly DependencyProperty IsAutoIndexingProperty = DependencyProperty.Register(
nameof(IsAutoIndexing),
typeof(bool),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
true,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnPropertyChanged)
)
);
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnPropertyChanged)
)
);
///
/// 处理重绘属性更改事件
///
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((AutoGrid)d).shouldReindex = true;
}
///
/// 获取或设置子项水平对齐方式。
///
/// 子项水平对齐。
[Category("Layout"), Description("预设所有子控件的水平对齐方式")]
public HorizontalAlignment? ChildHorizontalAlignment
{
get { return (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 { return (Thickness?)GetValue(ChildMarginProperty); }
set { SetValue(ChildMarginProperty, value); }
}
///
/// 获取或设置子项垂直对齐方式。
///
/// 子项垂直排列。
[Category("Layout"), Description("预设所有子控件的垂直对齐方式")]
public VerticalAlignment? ChildVerticalAlignment
{
get { return (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty); }
set { SetValue(ChildVerticalAlignmentProperty, value); }
}
///
/// 获取或设置列,优先级高于ColumnCount
///
[Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有列")]
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 FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnColumnsChanged)
)
);
///
/// 处理列更改事件
///
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 });
}
}
///
/// 获取或设置固定列宽,默认自动
///
[Category("Layout"), Description("预设使用 ColumnCount 属性设置的所有列的宽度")]
public GridLength ColumnWidth
{
get { return (GridLength)GetValue(ColumnWidthProperty); }
set { SetValue(ColumnWidthProperty, value); }
}
public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register(
nameof(ColumnWidth),
typeof(GridLength),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
GridLength.Auto,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnFixedColumnWidthChanged)
)
);
///
/// 处理固定列宽更改事件
///
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;
}
}
///
/// 获取或设置表示是否自动索引子项的值。
///
/// 默认值为true.
/// 请注意,如果子项已有索引,将此属性设置为 false 将不会移除其索引。
///
///
[Category("Layout"), Description("设置为 false 则禁用自动布局功能")]
public bool IsAutoIndexing
{
get { return (bool)GetValue(IsAutoIndexingProperty); }
set { SetValue(IsAutoIndexingProperty, value); }
}
///
/// 获取或设置方向。
/// 默认值为垂直。
///
/// 定向。
[Category("Layout"), Description("定义自动布局的方向性。列优先布局使用垂直方向,行优先布局使用水平方向。")]
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
///
/// 获取或设置固定行高
///
[Category("Layout"), Description("预设使用行数属性设置的所有行的高度")]
public GridLength RowHeight
{
get { return (GridLength)GetValue(RowHeightProperty); }
set { SetValue(RowHeightProperty, value); }
}
public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register(
nameof(RowHeight),
typeof(GridLength),
typeof(AutoGrid),
new FrameworkPropertyMetadata(
GridLength.Auto,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnFixedRowHeightChanged)
)
);
///
/// 处理固定行高更改事件
///
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;
}
}
///
/// 获取或设置行数,优先级高于RowCount
///
[Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有行")]
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 FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
new PropertyChangedCallback(OnRowsChanged)
)
);
///
/// 处理行修改事件
///
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 });
}
}
#endregion
#region 重写
///
/// 测量 的子网格,以便在 传递期间整理它们。
///
/// 表示不应超过的上限大小。
///
/// 表示安排子内容所需的大小。
///
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);
}
#endregion
}