Files
Shrlalgo.RvKits/Wpf.Ui.Extend/Layouts/AutoGrid.cs
2024-09-22 11:05:41 +08:00

779 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
///
/// </summary>
/// <remarks>注意整个XAML文档控件的顺序使用Grid.Row和Grid.Column应逐行添加控件对于嵌套的AutoGrid其ChildMarginHorizonAlignmentVerticalAlignment会继承</remarks>
public class AutoGrid : Grid
{
int rowOrColumnCount;
bool shouldReindex = true;
public AutoGrid()
{
Loaded += AutoGrid_Loaded;
}
/// <summary>
/// 应用默认子边距和对齐等布局效果
/// </summary>
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);
}
}
/// <summary>
/// 应用子边距和对齐等布局效果
/// </summary>
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);
}
}
///// <summary>
///// 将某一数值限制在最大值。
///// </summary>
//int Clamp(int value, int max)
//{
// return value > max ? max : value;
//}
/// <summary>
/// 从逗号分隔符文本中解析网格长度数组
/// </summary>
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<int>();
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)
)
);
/// <summary>
/// 改变【子水平对齐方式】时调用。
/// </summary>
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)
)
);
/// <summary>
/// 【子布局更改】时调用。
/// </summary>
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)
)
);
/// <summary>
/// 改变【子垂直排列方式】时调用。
/// </summary>
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)
)
);
/// <summary>
/// 处理重绘属性更改事件
/// </summary>
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((AutoGrid)d).shouldReindex = true;
}
/// <summary>
/// 获取或设置子项水平对齐方式。
/// </summary>
/// <value>子项水平对齐。</value>
[Category("Layout"), Description("预设所有子控件的水平对齐方式")]
public HorizontalAlignment? ChildHorizontalAlignment
{
get { return (HorizontalAlignment?)GetValue(ChildHorizontalAlignmentProperty); }
set { SetValue(ChildHorizontalAlignmentProperty, value); }
}
///// <summary>
///// 行数
///// </summary>
//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))
//);
///// <summary>
///// 处理行数修改事件
///// </summary>
//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 });
// }
//}
///// <summary>
///// 列数量
///// </summary>
//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))
//);
///// <summary>
///// 处理列数更改事件
///// </summary>
//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 });
// }
//}
/// <summary>
/// 获取或设置子项边距。
/// </summary>
/// <value>子项边距</value>
[
Category("Layout"),
Description("预设所有子控件的边距")
]
public Thickness? ChildMargin
{
get { return (Thickness?)GetValue(ChildMarginProperty); }
set { SetValue(ChildMarginProperty, value); }
}
/// <summary>
/// 获取或设置子项垂直对齐方式。
/// </summary>
/// <value>子项垂直排列。</value>
[Category("Layout"), Description("预设所有子控件的垂直对齐方式")]
public VerticalAlignment? ChildVerticalAlignment
{
get { return (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty); }
set { SetValue(ChildVerticalAlignmentProperty, value); }
}
/// <summary>
/// 获取或设置列优先级高于ColumnCount
/// </summary>
[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)
)
);
/// <summary>
/// 处理列更改事件
/// </summary>
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 });
}
}
/// <summary>
/// 获取或设置固定列宽,默认自动
/// </summary>
[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)
)
);
/// <summary>
/// 处理固定列宽更改事件
/// </summary>
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;
}
}
/// <summary>
/// 获取或设置表示是否自动索引子项的值。
/// <remarks>
/// 默认值为<c>true</c>.
/// 请注意,如果子项已有索引,将此属性设置为 <c>false</c> 将不会移除其索引。
/// </remarks>
/// </summary>
[Category("Layout"), Description("设置为 false 则禁用自动布局功能")]
public bool IsAutoIndexing
{
get { return (bool)GetValue(IsAutoIndexingProperty); }
set { SetValue(IsAutoIndexingProperty, value); }
}
/// <summary>
/// 获取或设置方向。
/// <remarks>默认值为垂直。</remarks>
/// </summary>
/// <value>定向。</value>
[Category("Layout"), Description("定义自动布局的方向性。列优先布局使用垂直方向,行优先布局使用水平方向。")]
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// 获取或设置固定行高
/// </summary>
[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)
)
);
/// <summary>
/// 处理固定行高更改事件
/// </summary>
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;
}
}
/// <summary>
/// 获取或设置行数,优先级高于RowCount
/// </summary>
[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)
)
);
/// <summary>
/// 处理行修改事件
/// </summary>
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
/// <summary>
/// 测量 <see cref="T:System.Windows.Controls.Grid"/> 的子网格,以便在 <see cref="M:ArrangeOverride"/> 传递期间整理它们。
/// </summary>
/// <param name="constraint">表示不应超过的上限大小。</param>
/// <returns>
/// <see cref="Size"/> 表示安排子内容所需的大小。
/// </returns>
protected override Size MeasureOverride(Size constraint)
{
PerformLayout();
return base.MeasureOverride(constraint);
}
/// <summary>
/// 当 <see cref="Grid"/> 元素的可视化子元素发生变化时调用。
/// <remarks>用于标记网格已更改子项。</remarks>
/// </summary>
/// <param name="visualAdded">标识添加的Visual子项。</param>
/// <param name="visualRemoved">标识移除的Visual子项。</param>
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
shouldReindex = true;
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
}
#endregion
}