功能更新

This commit is contained in:
GG Z
2026-02-12 21:29:00 +08:00
parent a9faf251be
commit 5f1adc5ad2
341 changed files with 4293 additions and 1874 deletions

View File

@@ -0,0 +1,306 @@
namespace Melskin.Layout
{
/// <summary>
/// 定义 FlexibleRowPanel 子元素的默认布局行为。
/// </summary>
public enum FlexibleRowLayoutMode
{
/// <summary>
/// (默认) 子元素默认按比例分配空间 (等效于 Span = 1)。
/// </summary>
Proportional,
/// <summary>
/// 子元素默认根据其内容自动调整大小 (等效于 Span = -1)。
/// </summary>
Auto
}
/// <summary>
/// 一个全能的单行布局面板,完美支持等分、权重、自动填充以及控件间距。
/// </summary>
public class FlexibleRowPanel : Panel
{
#region 1. (Spacing) -
/// <summary>
/// 获取或设置子控件之间的间距。
/// </summary>
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置子控件之间的间距。
/// 该属性定义了 FlexibleRowPanel 中相邻子控件间的距离。通过调整此值,可以控制布局中各元素的紧凑程度。
/// </summary>
public double Spacing
{
get => (double)GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
#endregion
#region 2. (LayoutMode)
/// <summary>
/// 获取或设置 FlexibleRowPanel 的布局模式,决定子控件的排列方式。
/// </summary>
public static readonly DependencyProperty LayoutModeProperty =
DependencyProperty.Register(nameof(LayoutMode), typeof(FlexibleRowLayoutMode), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(FlexibleRowLayoutMode.Proportional, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置布局模式,决定子控件在面板中的排列方式。
/// </summary>
public FlexibleRowLayoutMode LayoutMode
{
get => (FlexibleRowLayoutMode)GetValue(LayoutModeProperty);
set => SetValue(LayoutModeProperty, value);
}
#endregion
#region 3. 便 (IsFill)
/// <summary>
/// 获取或设置是否允许控件在FlexibleRowPanel中自动填充剩余空间。
/// </summary>
public static readonly DependencyProperty IsFillProperty =
DependencyProperty.RegisterAttached("IsFill", typeof(bool), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(false, OnIsFillChanged));
/// <summary>
/// 设置指定控件是否在FlexibleRowPanel中自动填充剩余空间。
/// </summary>
/// <param name="element">要设置属性的UI元素。</param>
/// <param name="value">布尔值,指示是否允许该控件自动填充剩余空间。</param>
public static void SetIsFill(UIElement element, bool value) => element.SetValue(IsFillProperty, value);
/// <summary>
/// 获取指定控件是否在FlexibleRowPanel中自动填充剩余空间的设置。
/// </summary>
/// <param name="element">要获取属性的UI元素。</param>
/// <returns>布尔值,指示该控件是否被允许自动填充剩余空间。</returns>
public static bool GetIsFill(UIElement element) => (bool)element.GetValue(IsFillProperty);
private static void OnIsFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element && (bool)e.NewValue)
{
element.SetValue(SpanProperty, 1.0);
}
}
#endregion
#region 4. (Span)
/// <summary>
/// 获取或设置控件在FlexibleRowPanel中的跨度。
/// </summary>
public static readonly DependencyProperty SpanProperty =
DependencyProperty.RegisterAttached("Span", typeof(double), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// 设置指定控件在FlexibleRowPanel中的跨度。
/// </summary>
/// <param name="element">要设置属性的UI元素。</param>
/// <param name="value">双精度浮点数表示该控件在FlexibleRowPanel中的跨度。</param>
public static void SetSpan(UIElement element, double value) => element.SetValue(SpanProperty, value);
/// <summary>
/// 获取控件在FlexibleRowPanel中的跨度。
/// </summary>
/// <param name="element">要获取跨度的UI元素。</param>
/// <returns>返回一个double值表示该控件在FlexibleRowPanel中的跨度。</returns>
public static double GetSpan(UIElement element) => (double)element.GetValue(SpanProperty);
#endregion
private double GetEffectiveSpan(UIElement child)
{
var localValue = child.ReadLocalValue(SpanProperty);
if (localValue != DependencyProperty.UnsetValue)
{
return (double)localValue;
}
return LayoutMode switch
{
FlexibleRowLayoutMode.Auto => -1.0,
_ => 1.0,
};
}
//protected override Size MeasureOverride(Size availableSize)
//{
// double spaceTakenByAutoChildren = 0;
// double totalSpanForFillChildren = 0;
// double maxHeight = 0;
// // 【修改】计算所有间距占用的总宽度
// var totalSpacing = Math.Max(0, InternalChildren.Count - 1) * Spacing;
// // 【修改】计算真正可用于子控件的宽度
// var effectiveWidth = Math.Max(0, availableSize.Width - totalSpacing);
// // 第一遍测量:自动大小的控件
// foreach (UIElement child in InternalChildren)
// {
// if (child == null) continue;
// var span = GetEffectiveSpan(child);
// if (span < 0)
// {
// child.Measure(new Size(double.PositiveInfinity, availableSize.Height));
// spaceTakenByAutoChildren += child.DesiredSize.Width;
// }
// else
// {
// totalSpanForFillChildren += span;
// }
// }
// var remainingWidth = Math.Max(0, effectiveWidth - spaceTakenByAutoChildren);
// var widthPerSpanUnit = (totalSpanForFillChildren > 0 && !double.IsInfinity(remainingWidth)) ? remainingWidth / totalSpanForFillChildren : 0;
// // 第二遍测量:比例填充的控件
// foreach (UIElement child in InternalChildren)
// {
// if (child == null) continue;
// var span = GetEffectiveSpan(child);
// if (span >= 0)
// {
// child.Measure(new Size(widthPerSpanUnit * span, availableSize.Height));
// }
// if (child.DesiredSize.Height > maxHeight)
// {
// maxHeight = child.DesiredSize.Height;
// }
// }
// // 返回的宽度应该是面板请求的总宽度
// return new Size(availableSize.Width, maxHeight);
//}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
double totalAutoWidth = 0;
double totalSpanUnits = 0;
double maxHeight = 0;
bool isWidthInfinite = double.IsInfinity(availableSize.Width);
var childrenCount = InternalChildren.Count;
var totalSpacing = Math.Max(0, childrenCount - 1) * Spacing;
// 第一遍测量:收集信息
foreach (UIElement child in InternalChildren)
{
if (child == null || child.Visibility == Visibility.Collapsed) continue;
var span = GetEffectiveSpan(child);
if (span < 0 || isWidthInfinite)
{
// 如果宽度是无穷大或者子项是Auto
// 我们给它无穷大空间,看看它到底想占多大。
child.Measure(new Size(double.PositiveInfinity, availableSize.Height));
totalAutoWidth += child.DesiredSize.Width;
}
if (span >= 0)
{
totalSpanUnits += span;
}
}
double finalWidth = 0;
if (isWidthInfinite)
{
// 情况 A: 宽度无穷大 (例如在 StackPanel 或 ScrollViewer 中)
// 此时 Span 的逻辑无法分配“剩余空间”,通常做法是让 Span 项以 DesiredSize 形式并存
// 或者你可以给 Span 项一个默认基础宽度(比如 0或者它们内容的宽度
// 这里我们把所有子项的 DesiredSize 加起来作为我们需要的总宽度
finalWidth = totalAutoWidth + totalSpacing;
// 既然是无穷大,我们需要再次测量那些还没有正确 Measure 的 Span 项(可选)
foreach (UIElement child in InternalChildren)
{
if (child == null || child.Visibility == Visibility.Collapsed) continue;
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
}
}
else
{
// 情况 B: 宽度是有限的 (正常的 Grid 或固定宽度的容器)
double remainingWidth = Math.Max(0, availableSize.Width - totalSpacing - totalAutoWidth);
double widthPerSpanUnit = totalSpanUnits > 0 ? remainingWidth / totalSpanUnits : 0;
foreach (UIElement child in InternalChildren)
{
if (child == null || child.Visibility == Visibility.Collapsed) continue;
var span = GetEffectiveSpan(child);
if (span >= 0)
{
// 给 Span 项分配计算后的精确宽度
child.Measure(new Size(widthPerSpanUnit * span, availableSize.Height));
}
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
}
finalWidth = availableSize.Width; // 在有限宽度下,通常占用全部可用宽度
}
// 重要:返回计算出的有限尺寸,绝不返回 availableSize.Width (如果是 Infinity)
return new Size(finalWidth, maxHeight);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
double spaceTakenByAutoChildren = 0;
double totalSpanForFillChildren = 0;
// 【修改】计算所有间距占用的总宽度
var totalSpacing = Math.Max(0, InternalChildren.Count - 1) * Spacing;
// 【修改】计算真正可用于子控件的宽度
var effectiveWidth = Math.Max(0, finalSize.Width - totalSpacing);
// 重新计算一遍各项数值,因为 Measure 和 Arrange 的 Size 可能不同
foreach (UIElement child in InternalChildren)
{
if (child == null) continue;
var span = GetEffectiveSpan(child);
if (span < 0)
{
spaceTakenByAutoChildren += child.DesiredSize.Width;
}
else
{
totalSpanForFillChildren += span;
}
}
var remainingWidth = Math.Max(0, effectiveWidth - spaceTakenByAutoChildren);
var widthPerSpanUnit = (totalSpanForFillChildren > 0) ? remainingWidth / totalSpanForFillChildren : 0;
double currentX = 0;
// 遍历并排列每个子元素
for (var i = 0; i < InternalChildren.Count; i++)
{
var child = InternalChildren[i];
if (child == null) continue;
var span = GetEffectiveSpan(child);
var childWidth = (span < 0) ? child.DesiredSize.Width : widthPerSpanUnit * span;
child.Arrange(new Rect(currentX, 0, childWidth, finalSize.Height));
// 【修改】移动 currentX并为下一个控件加上间距
currentX += childWidth;
if (i < InternalChildren.Count - 1)
{
currentX += Spacing;
}
}
return finalSize;
}
}
}

794
Melskin/Layout/Grid.cs Normal file
View File

@@ -0,0 +1,794 @@
using System.ComponentModel;
using Melskin.Extensions;
namespace Melskin.Layout;
/// <summary>
/// Grid 类继承自 Grid提供自动布局功能。它允许子元素根据设定的列宽、对齐方式等属性自动排列。
/// 该类扩展了标准 Grid 的功能,添加了如自动索引和自定义列宽覆盖等特性,以简化复杂布局的创建。
/// </summary>
/// <remarks>
/// 通过使用依赖属性来控制子元素的水平和垂直对齐方式、边距以及整个网格的列宽和方向,
/// Grid 提供了一种灵活的方式来管理和调整用户界面中的布局结构。
/// 注意整个XAML文档控件的顺序使用Grid.Row和Grid.Column应逐行添加控件对于嵌套的AutoGrid其ChildMarginHorizonAlignmentVerticalAlignment会继承
/// </remarks>
public class Grid : System.Windows.Controls.Grid
{
/// <summary>
/// 获取或设置是否自动索引。
/// </summary>
/// <value>布尔值,表示是否启用自动索引。</value>
public static readonly DependencyProperty AutoIndexProperty = DependencyProperty.RegisterAttached(
"AutoIndex",
typeof(bool),
typeof(Grid),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// 获取或设置所有子控件的水平对齐方式。
/// </summary>
/// <value>一个可为空的 HorizontalAlignment 值,表示子控件的水平对齐方式。</value>
public static readonly DependencyProperty ChildHorizontalAlignmentProperty = DependencyProperty.Register(
nameof(ChildHorizontalAlignment),
typeof(HorizontalAlignment?),
typeof(Grid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnChildHorizontalAlignmentChanged));
/// <summary>
/// 获取或设置子控件的边距。
/// </summary>
/// <value>表示子控件边距的 Thickness? 类型值。</value>
public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register(
nameof(ChildMargin),
typeof(Thickness?),
typeof(Grid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnChildMarginChanged));
/// <summary>
/// 获取或设置所有子控件的默认垂直对齐方式。
/// </summary>
/// <value>一个可为空的 VerticalAlignment 值,表示子控件的默认垂直对齐方式。</value>
public static readonly DependencyProperty ChildVerticalAlignmentProperty = DependencyProperty.Register(
nameof(ChildVerticalAlignment),
typeof(VerticalAlignment?),
typeof(Grid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnChildVerticalAlignmentChanged));
/// <summary>
///
/// </summary>
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register(
nameof(Columns),
typeof(string),
typeof(Grid),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnColumnsChanged));
/// <summary>
/// 获取或设置列宽覆盖值。
/// </summary>
/// <value>GridLength? 类型的值,表示列宽的覆盖值。如果未设置,则使用默认列宽。</value>
public static readonly DependencyProperty ColumnWidthOverrideProperty = DependencyProperty.RegisterAttached(
"ColumnWidthOverride",
typeof(GridLength?),
typeof(Grid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
///
/// </summary>
public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register(
nameof(ColumnWidth),
typeof(GridLength),
typeof(Grid),
new FrameworkPropertyMetadata(
GridLength.Auto,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnFixedColumnWidthChanged));
/// <summary>
/// 获取或设置是否启用自动布局索引。
/// </summary>
/// <value>布尔值,表示是否启用自动布局索引功能。</value>
public static readonly DependencyProperty IsAutoIndexingProperty = DependencyProperty.Register(
nameof(IsAutoIndexing),
typeof(bool),
typeof(Grid),
new FrameworkPropertyMetadata(
true,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnPropertyChanged));
/// <summary>
/// 获取或设置布局的方向。
/// </summary>
/// <value>表示布局方向的枚举值,可以是水平或垂直。</value>
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(Grid),
new FrameworkPropertyMetadata(
Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnPropertyChanged));
// RowHeight 重载附加属性
/// <summary>
/// 获取或设置行高度的重载值。
/// </summary>
/// <value>GridLength? 类型,表示行的高度。如果设置为 null则使用默认行高。</value>
public static readonly DependencyProperty RowHeightOverrideProperty = DependencyProperty.RegisterAttached(
"RowHeightOverride",
typeof(GridLength?),
typeof(Grid),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置网格中所有行的预设高度。
/// </summary>
/// <value>GridLength 类型,表示行的高度。默认值为 GridLength.Auto。</value>
public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register(
nameof(RowHeight),
typeof(GridLength),
typeof(Grid),
new FrameworkPropertyMetadata(
GridLength.Auto,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnFixedRowHeightChanged));
/// <summary>
///
/// </summary>
public static readonly DependencyProperty RowsProperty = DependencyProperty.Register(
nameof(Rows),
typeof(string),
typeof(Grid),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange,
OnRowsChanged));
int rowOrColumnCount;
bool shouldReindex = true;
/// <summary>
/// 用于在WPF应用程序中创建和管理网格布局的类。该类扩展了System.Windows.Controls.Grid提供了额外的功能如自动索引、列宽覆盖以及子元素对齐方式和边距的设置。
/// </summary>
public Grid()
{
Loaded += Grid_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 Grid_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= Grid_Loaded;
if (Children.Count == 0)
{
return;
}
foreach (UIElement elem in Children)
{
ApplyChildLayout(elem);
}
}
/// <summary>
/// 改变【子水平对齐方式】时调用。
/// </summary>
private static void OnChildHorizontalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
foreach (UIElement child in grid.Children)
{
child.SetValue(HorizontalAlignmentProperty, grid.ChildHorizontalAlignment ?? DependencyProperty.UnsetValue);
}
}
}
/// <summary>
/// 【子布局更改】时调用。
/// </summary>
private static void OnChildMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
foreach (UIElement child in grid.Children)
{
child.SetValue(MarginProperty, grid.ChildMargin ?? DependencyProperty.UnsetValue);
}
}
}
/// <summary>
/// 改变【子垂直排列方式】时调用。
/// </summary>
private static void OnChildVerticalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
foreach (UIElement child in grid.Children)
{
child.SetValue(VerticalAlignmentProperty, grid.ChildVerticalAlignment ?? DependencyProperty.UnsetValue);
}
}
}
/// <summary>
/// 处理列更改事件
/// </summary>
private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((string)e.NewValue == string.Empty)
{
return;
}
if (d is Grid grid)
{
grid.ColumnDefinitions.Clear();
var defs = Parse((string)e.NewValue);
foreach (var def in defs)
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = def });
}
}
/// <summary>
/// 处理固定列宽更改事件
/// </summary>
private static void OnFixedColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
// 如果缺少,添加默认列
if (grid.ColumnDefinitions.Count == 0)
grid.ColumnDefinitions.Add(new ColumnDefinition());
// 将所有现有列设置为该宽度
foreach (var t in grid.ColumnDefinitions)
t.Width = (GridLength)e.NewValue;
}
}
/// <summary>
/// 处理固定行高更改事件
/// </summary>
private static void OnFixedRowHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Grid grid)
{
// 如果缺失,则添加默认行
if (grid.RowDefinitions.Count == 0)
grid.RowDefinitions.Add(new RowDefinition());
// 将所有现有行设置为该高度
foreach (var t in grid.RowDefinitions)
t.Height = (GridLength)e.NewValue;
}
}
/// <summary>
/// 处理重绘属性更改事件
/// </summary>
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ ((Grid)d).shouldReindex = true; }
/// <summary>
/// 处理行修改事件
/// </summary>
private static void OnRowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((string)e.NewValue == string.Empty)
{
return;
}
if (d is Grid grid)
{
grid.RowDefinitions.Clear();
var defs = Parse((string)e.NewValue);
foreach (var def in defs)
grid.RowDefinitions.Add(new RowDefinition() { Height = def });
}
}
///// <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("*", 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<int>();
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);
}
}
}
/// <summary>
/// 测量 <see cref="T:System.Windows.Controls.Grid"/> 的子网格,以便在 <see cref="M:System.Windows.Controls.Grid.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="System.Windows.Controls.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);
}
/// <summary>
/// 获取指定元素的自动索引状态。
/// </summary>
/// <param name="element">要获取自动索引状态的依赖对象。</param>
/// <returns>如果元素启用了自动索引则返回true否则返回false。</returns>
public static bool GetAutoIndex(DependencyObject element)
{
return (bool)element.GetValue(AutoIndexProperty); }
/// <summary>
/// 获取指定元素的列宽覆盖值。
/// </summary>
/// <param name="element">要获取列宽覆盖值的依赖对象。</param>
/// <returns>如果为该元素设置了列宽覆盖则返回其GridLength值否则返回null。</returns>
public static GridLength? GetColumnWidthOverride(DependencyObject element)
{
return (GridLength?)element.GetValue(ColumnWidthOverrideProperty); }
/// <summary>
/// 获取指定元素的行高覆盖值。
/// </summary>
/// <param name="element">要获取行高覆盖值的依赖对象。</param>
/// <returns>返回一个GridLength?类型的值表示指定元素的行高覆盖。如果未设置则返回null。</returns>
public static GridLength? GetRowHeightOverride(DependencyObject element)
{
return (GridLength?)element.GetValue(RowHeightOverrideProperty); }
/// <summary>
/// 设置指定元素的自动索引属性。
/// </summary>
/// <param name="element">要设置自动索引属性的依赖对象。</param>
/// <param name="value">一个布尔值,表示是否启用自动索引功能。</param>
public static void SetAutoIndex(DependencyObject element, bool value)
{
element.SetValue(AutoIndexProperty, value); }
/// <summary>
/// 设置指定元素的列宽覆盖值。
/// </summary>
/// <param name="element">要设置列宽覆盖值的依赖对象。</param>
/// <param name="value">新的列宽覆盖值类型为GridLength?允许设置为null以清除覆盖。</param>
public static void SetColumnWidthOverride(DependencyObject element, GridLength? value)
{
element.SetValue(ColumnWidthOverrideProperty, value); }
/// <summary>
/// 设置指定元素的行高覆盖值。这允许开发者自定义网格中特定行的高度。
/// </summary>
/// <param name="element">要设置行高覆盖值的UI元素。</param>
/// <param name="value">新的行高值,可以是绝对值、自动或星形尺寸。</param>
public static void SetRowHeightOverride(DependencyObject element, GridLength? value)
{
element.SetValue(RowHeightOverrideProperty, value);
}
/// <summary>
/// 获取或设置子项水平对齐方式。
/// </summary>
/// <value>子项水平对齐。</value>
[Category("Layout"), Description("预设所有子控件的水平对齐方式")]
public HorizontalAlignment? ChildHorizontalAlignment
{
get => (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(Grid),
// new PropertyMetadata(1, new PropertyChangedCallback(OnRowCountChanged))
//);
///// <summary>
///// 处理行数修改事件
///// </summary>
//private static void OnRowCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
//{
// if (d is Grid 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(Grid),
// new PropertyMetadata(1, new PropertyChangedCallback(OnColumnCountChanged))
//);
///// <summary>
///// 处理列数更改事件
///// </summary>
//private static void OnColumnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
//{
// if (d is Grid 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 => (Thickness?)GetValue(ChildMarginProperty);
set => SetValue(ChildMarginProperty, value);
}
/// <summary>
/// 获取或设置子项垂直对齐方式。
/// </summary>
/// <value>子项垂直排列。</value>
[Category("Layout"), Description("预设所有子控件的垂直对齐方式")]
public VerticalAlignment? ChildVerticalAlignment
{
get => (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty);
set => SetValue(ChildVerticalAlignmentProperty, value);
}
/// <summary>
/// 获取或设置列优先级高于ColumnCount
/// </summary>
[Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有列")]
public string Columns
{
get => (string)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
/// <summary>
/// 获取或设置固定列宽,默认自动
/// </summary>
[Category("Layout"), Description("预设使用 ColumnCount 属性设置的所有列的宽度")]
public GridLength ColumnWidth
{
get => (GridLength)GetValue(ColumnWidthProperty);
set => SetValue(ColumnWidthProperty, value);
}
/// <summary>
/// 获取或设置表示是否自动索引子项的值。 <remarks> 默认值为<c>true</c>. 请注意,如果子项已有索引,将此属性设置为 <c>false</c> 将不会移除其索引。</remarks>
/// </summary>
[Category("Layout"), Description("设置为 false 则禁用自动布局功能")]
public bool IsAutoIndexing
{
get => (bool)GetValue(IsAutoIndexingProperty);
set => SetValue(IsAutoIndexingProperty, value);
}
/// <summary>
/// 获取或设置方向。 <remarks>默认值为垂直。</remarks>
/// </summary>
/// <value>定向。</value>
[Category("Layout"), Description("定义自动布局的方向性。列优先布局使用垂直方向,行优先布局使用水平方向。")]
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// 获取或设置固定行高
/// </summary>
[Category("Layout"), Description("预设使用行数属性设置的所有行的高度")]
public GridLength RowHeight
{
get => (GridLength)GetValue(RowHeightProperty);
set => SetValue(RowHeightProperty, value);
}
/// <summary>
/// 获取或设置行数,优先级高于RowCount
/// </summary>
[Category("Layout"), Description("使用逗号分隔的网格长度符号定义所有行")]
public string Rows
{
get => (string)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
}

View File

@@ -0,0 +1,298 @@
using System.Windows.Markup;
namespace Melskin.Layout;
/// <summary>
/// StackPanel 类继承自 Panel用于在用户界面中以垂直或水平方式排列子元素。它支持设置子元素之间的间距、方向以及单个子项的填充方式。
/// </summary>
[ContentProperty(nameof(Children))]
public class StackPanel : Panel
{
/// <summary>
/// 设置某子项附加属性控制其是否填充到当前StackPanel所在的面板控制单个子项的在面板填充方式
/// </summary>
public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached(
"Fill",
typeof(FillType),
typeof(StackPanel),
new FrameworkPropertyMetadata(
FillType.Auto,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// 定义 StackPanel 的排列方向,可以设置为水平或垂直。此属性影响子元素的布局方式。
/// </summary>
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 定义子元素之间的间距。此属性允许用户指定StackPanel中相邻子元素间的距离。
/// </summary>
public static readonly DependencyProperty SpacingProperty = DependencyProperty.Register(
nameof(Spacing),
typeof(double),
typeof(StackPanel),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
static double CalculateTotalMarginToAdd(UIElementCollection children, double marginBetweenChildren)
{
//可见的子项数量
var visibleChildrenCount = children
.OfType<UIElement>()
.Count(x => x.Visibility != Visibility.Collapsed && GetFill(x) != FillType.Ignored);
var marginMultiplier = Math.Max(visibleChildrenCount - 1, 0);
var totalMarginToAdd = marginBetweenChildren * marginMultiplier;
return totalMarginToAdd;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size arrangeSize)
{
var children = InternalChildren;
var totalChildrenCount = children.Count;
double accumulatedLeft = 0;
double accumulatedTop = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var spacing = Spacing;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, spacing);
var allAutoSizedSum = 0.0;
var countOfFillTypes = 0;
foreach (var child in children.OfType<UIElement>())
{
var fillType = GetFill(child);
if (fillType != FillType.Auto)
{
if (child.Visibility != Visibility.Collapsed && fillType != FillType.Ignored)
countOfFillTypes += 1;
}
else
{
var desiredSize = isHorizontal ? child.DesiredSize.Width : child.DesiredSize.Height;
allAutoSizedSum += desiredSize;
}
}
var remainingForFillTypes = isHorizontal
? Math.Max(0, arrangeSize.Width - allAutoSizedSum - totalMarginToAdd)
: Math.Max(0, arrangeSize.Height - allAutoSizedSum - totalMarginToAdd);
var fillTypeSize = remainingForFillTypes / countOfFillTypes;
for (var i = 0; i < totalChildrenCount; ++i)
{
var child = children[i];
if (child == null)
{
continue;
}
var childDesiredSize = child.DesiredSize;
var fillType = GetFill(child);
var isCollapsed = child.Visibility == Visibility.Collapsed || fillType == FillType.Ignored;
var isLastChild = i == totalChildrenCount - 1;
var marginToAdd = isLastChild || isCollapsed ? 0 : spacing;
var rcChild = new Rect(
accumulatedLeft,
accumulatedTop,
Math.Max(0.0, arrangeSize.Width - accumulatedLeft),
Math.Max(0.0, arrangeSize.Height - accumulatedTop));
if (isHorizontal)
{
rcChild.Width = fillType == FillType.Auto || isCollapsed ? childDesiredSize.Width : fillTypeSize;
rcChild.Height = arrangeSize.Height;
accumulatedLeft += rcChild.Width + marginToAdd;
}
else
{
rcChild.Width = arrangeSize.Width;
rcChild.Height = fillType == FillType.Auto || isCollapsed ? childDesiredSize.Height : fillTypeSize;
accumulatedTop += rcChild.Height + marginToAdd;
}
child.Arrange(rcChild);
}
return arrangeSize;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
var children = InternalChildren;
double parentWidth = 0;
double parentHeight = 0;
double accumulatedWidth = 0;
double accumulatedHeight = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, Spacing);
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
if (child == null)
{
continue;
}
// 先处理Auto的情况再计算Fill的剩余空间
if (GetFill(child) != FillType.Auto)
{
continue;
}
// 子项约束为剩余大小; 即总大小减去前一个子项目消耗的大小。
var childConstraint = new Size(
Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight));
// 计算子项
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// 在计算剩余空间之前,将所有边距添加到累积大小中,以便填充元素。
if (isHorizontal)
{
accumulatedWidth += totalMarginToAdd;
}
else
{
accumulatedHeight += totalMarginToAdd;
}
//Fill类型的总数量
var totalCountOfFillTypes = children
.OfType<UIElement>()
.Count(x => GetFill(x) == FillType.Fill && x.Visibility != Visibility.Collapsed);
//可用的剩余空间
var availableSpaceRemaining = isHorizontal
? Math.Max(0, constraint.Width - accumulatedWidth)
: Math.Max(0, constraint.Height - accumulatedHeight);
var eachFillTypeSize = totalCountOfFillTypes > 0 ? availableSpaceRemaining / totalCountOfFillTypes : 0;
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
if (child == null)
{
continue;
}
// 处理所有的填充,给它们一部分剩余空间
if (GetFill(child) != FillType.Fill)
{
continue;
}
// 子项限制是剩余大小;即总大小减去前一个子项限制所消耗的大小。
var childConstraint = isHorizontal
? new Size(eachFillTypeSize, Math.Max(0.0, constraint.Height - accumulatedHeight))
: new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), eachFillTypeSize);
// 测量子项.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// 确保最终累积的大小反映在 parentSize 中。
parentWidth = Math.Max(parentWidth, accumulatedWidth);
parentHeight = Math.Max(parentHeight, accumulatedHeight);
var parent = new Size(parentWidth, parentHeight);
return parent;
}
/// <summary>
/// 获取指定元素的填充类型。
/// </summary>
/// <param name="element">要获取填充类型的依赖对象。</param>
/// <returns>返回指定元素的填充类型。</returns>
public static FillType GetFill(DependencyObject element)
{
return (FillType)element.GetValue(FillProperty); }
/// <summary>
/// 设置指定元素的填充方式属性。
/// </summary>
/// <param name="element">要设置填充方式的依赖对象。</param>
/// <param name="value">要设置的填充类型值可以是Auto、Fill或Ignored。</param>
public static void SetFill(DependencyObject element, FillType value)
{
element.SetValue(FillProperty, value); }
/// <summary>
/// 堆叠方向
/// </summary>
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// 子项间距
/// </summary>
public double Spacing
{
get => (double)GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
}
/// <summary>
///
/// </summary>
public enum FillType
{
/// <summary>
/// 实际大小填充
/// </summary>
Auto,
/// <summary>
/// 填充剩余空间相当于Stretch
/// </summary>
Fill,
/// <summary>
/// 忽略当前项
/// </summary>
Ignored
}

194
Melskin/Layout/TableGrid.cs Normal file
View File

@@ -0,0 +1,194 @@
namespace Melskin.Layout
{
/// <summary>
/// 表格网格控件继承自Grid用于以表格形式排列子元素。
/// 通过设置列数和间距属性,可以控制子元素在表格中的布局方式。
/// </summary>
public class TableGrid:System.Windows.Controls.Grid
{
#region Spacing
/// <summary>
/// 获取或设置表格网格中单元格之间的间距。
/// 该属性控制表格中每个单元格在水平和垂直方向上的间隔大小。
/// </summary>
/// <value>一个表示间距的 <see cref="Size"/> 结构,其中 <see cref="Size.Width"/> 控制列间距,<see cref="Size.Height"/> 控制行间距。</value>
/// <remarks>
/// 更改此属性将触发布局更新,以确保所有子元素根据新的间距值重新排列。
/// </remarks>
public Size Spacing
{
get => spacing;
set
{
if (value != spacing)
SetValue(SpacingProperty, value);
}
}
private Size spacing;
/// <summary>
/// 表示表格网格中单元格间距的依赖属性。
/// 该属性用于定义表格内每个单元格在水平和垂直方向上的间隔大小,通过<see cref="Size"/>结构来表示,其中<see cref="Size.Width"/>部分控制列间距,<see cref="Size.Height"/>部分控制行间距。
/// </summary>
/// <remarks>
/// 当此属性值发生变化时,会触发布局更新机制,以确保所有子元素能够根据新的间距值重新排列。这有助于保持布局的一致性和美观性。
/// </remarks>
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register(
nameof(Spacing),
typeof(Size),
typeof(TableGrid),
new PropertyMetadata(
default(Size),
(s, e) =>
{
var self = (TableGrid) s;
self.spacing = (Size) e.NewValue;
self.UpdateColumns();
}));
#endregion
#region Columns
/// <summary>
/// 获取或设置表格网格中的列数。
/// </summary>
/// <remarks>
/// 当更改此属性时,会自动调整行和列的布局以适应新的列数,并重新排列子元素的位置。此属性影响测量过程。
/// </remarks>
public int Columns
{
get => columns;
set
{
if (value != columns)
SetValue(ColumnsProperty, value);
}
}
private int columns;
/// <summary>
/// 获取或设置表格网格中的列数。
/// 该属性定义了表格中将包含多少列,从而影响子元素的布局方式。
/// </summary>
/// <value>一个整数值,表示表格网格中的列数。</value>
/// <remarks>
/// 更改此属性会触发布局更新,确保所有子元素根据新的列数重新排列。如果设置了新的列数,则会对整个表格进行测量和排列以适应更改。
/// </remarks>
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
nameof(Columns),
typeof(int),
typeof(TableGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(s, e) =>
{
var self = (TableGrid) s;
self.columns = (int) e.NewValue;
self.UpdateColumns();
}));
#endregion
private void UpdateColumns()
{
var childCount = VisualTreeHelper.GetChildrenCount(this);
{
while (ColumnDefinitions.Count < childCount)
{
var cd = new ColumnDefinition
{
Width = new GridLength(0, GridUnitType.Auto)
};
ColumnDefinitions.Add(cd);
}
while (ColumnDefinitions.Count > childCount)
ColumnDefinitions.RemoveAt(ColumnDefinitions.Count - 1);
}
{
var rows = childCount / Columns;
while (RowDefinitions.Count < rows)
{
var rd = new RowDefinition
{
Height = new GridLength(0, GridUnitType.Star)
};
RowDefinitions.Add(rd);
}
while (RowDefinitions.Count > rows)
RowDefinitions.RemoveAt(RowDefinitions.Count - 1);
}
{
var columnCount = 0;
var rowCount = 0;
var rows = childCount / Columns;
var isLastRow = rowCount == rows - 1;
for (var i = 0; i != childCount; ++i)
{
var child = (FrameworkElement) VisualTreeHelper.GetChild(this, i);
SetColumn(child, columnCount);
SetRow(child, rowCount);
var isLastItem = i == childCount - 1;
var px = columnCount == Columns - 1 || isLastItem
? 0d
: Spacing.Width;
var py = isLastRow || isLastItem
? 0d
: Spacing.Height;
child.Margin = new Thickness(0d, 0d, px, py);
++columnCount;
if (columnCount == Columns)
{
columnCount = 0;
++rowCount;
isLastRow = rowCount == rows - 1;
}
}
}
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size arrangeSize)
{
if (!isRequestUpdateColumns) return base.ArrangeOverride(arrangeSize);
UpdateColumns();
isRequestUpdateColumns = false;
return base.ArrangeOverride(arrangeSize);
}
private bool isRequestUpdateColumns;
/// <inheritdoc />
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
isRequestUpdateColumns = true;
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
}
}
}

View File

@@ -0,0 +1,235 @@
namespace Melskin.Layout
{
/// <summary>
/// 一个自定义布局面板,可将其子元素排列在网格中。
/// 关键功能: 可将最后一行的子元素进行水平拉伸,以填满整行。
/// </summary>
public class UniformGrid : Panel
{
#region Dependency Properties
/// <summary>
/// 表示UniformGrid控件的列数属性的依赖属性。此属性用于定义网格中的列数必须大于0默认值为1。
/// </summary>
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(UniformGrid),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置网格的列数。必须大于0。默认为1。
/// </summary>
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
/// <summary>
/// 表示UniformGrid控件的行数属性的依赖属性。此属性用于定义网格中的行数可以设置为0以自动计算行数默认值为0。
/// </summary>
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(nameof(Rows), typeof(int), typeof(UniformGrid),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置网格的行数。
/// 如果为0默认行数将根据子元素数量和列数自动计算。
/// </summary>
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
/// <summary>
/// 表示UniformGrid控件的列间距属性的依赖属性。此属性用于定义网格中每列之间的间距类型为double默认值为0.0。
/// </summary>
public static readonly DependencyProperty ColumnSpacingProperty =
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示UniformGrid控件的列间距属性。此属性用于定义网格中每列之间的间距值为double类型默认值为0.0。
/// </summary>
public double ColumnSpacing
{
get => (double)GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}
/// <summary>
/// 表示UniformGrid控件的行间距属性的依赖属性。此属性用于定义网格中各行之间的间距默认值为0。
/// </summary>
public static readonly DependencyProperty RowSpacingProperty =
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示UniformGrid控件的行间距属性。此属性用于定义网格中各行之间的间距默认值为0.0。
/// </summary>
public double RowSpacing
{
get => (double)GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}
/// <summary>
/// 表示UniformGrid控件的最后一行是否填充属性的依赖属性。此属性用于决定当最后一行未被完全填满时是否拉伸子元素以填满整行默认值为true。
/// </summary>
public static readonly DependencyProperty IsFillLastRowProperty =
DependencyProperty.Register(nameof(IsFillLastRow), typeof(bool), typeof(UniformGrid),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange));
/// <summary>
/// 获取或设置是否水平拉伸最后一行的子元素,以填满该行的全部宽度。
/// 例如一个3列的网格有5个元素当此属性为 true 时最后两个元素会自动变宽以填满3个列的空间。
/// </summary>
public bool IsFillLastRow
{
get => (bool)GetValue(IsFillLastRowProperty);
set => SetValue(IsFillLastRowProperty, value);
}
/// <summary>
/// 表示UniformGrid控件的圆角半径属性的依赖属性。此属性用于定义网格边角的圆角大小使用CornerRadius结构表示默认值为默认构造的CornerRadius实例。
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(UniformGrid),
new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// 表示UniformGrid控件的圆角半径属性。此属性用于定义网格每个角落的圆角大小可以通过设置不同的值来实现四角的不同圆角效果默认值为所有角均为0。
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
#endregion
private int computedRows;
private int computedColumns;
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
computedColumns = Math.Max(1, Columns);
var visibleChildren = InternalChildren.Cast<UIElement>().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (Rows > 0)
{
computedRows = Rows;
}
else
{
computedRows = (visibleChildren.Count + computedColumns - 1) / computedColumns;
if (computedRows == 0) computedRows = 1;
}
double maxChildWidth = 0;
double maxChildHeight = 0;
// 所有子元素共享相同的可用尺寸
foreach (var child in visibleChildren)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
maxChildWidth = Math.Max(maxChildWidth, child.DesiredSize.Width);
maxChildHeight = Math.Max(maxChildHeight, child.DesiredSize.Height);
}
var totalWidth = maxChildWidth * computedColumns + ColumnSpacing * (computedColumns - 1);
var totalHeight = maxChildHeight * computedRows + RowSpacing * (computedRows - 1);
return new Size(totalWidth, totalHeight);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var visibleChildren = InternalChildren.Cast<UIElement>().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (visibleChildren.Count == 0) return finalSize;
var cellWidth = Math.Max(0, (finalSize.Width - (computedColumns - 1) * ColumnSpacing) / computedColumns);
var cellHeight = Math.Max(0, (finalSize.Height - (computedRows - 1) * RowSpacing) / computedRows);
var itemsInFullRows = (computedRows - 1) * computedColumns;
var lastRowItemCount = visibleChildren.Count - itemsInFullRows;
var lastRowCellWidth = cellWidth;
if (IsFillLastRow && lastRowItemCount > 0)
{
// **核心逻辑**: 为最后一行重新计算单元格宽度
lastRowCellWidth = (finalSize.Width - (lastRowItemCount - 1) * ColumnSpacing) / lastRowItemCount;
}
for (var i = 0; i < visibleChildren.Count; i++)
{
var child = visibleChildren[i];
var row = i / computedColumns;
var col = i % computedColumns;
var currentCellWidth = (row == computedRows - 1) ? lastRowCellWidth : cellWidth;
// 如果是最后一行x坐标的计算需要基于该行内的索引和新的单元格宽度
double x;
if (row == computedRows - 1 && IsFillLastRow)
{
var colInLastRow = i - itemsInFullRows;
x = colInLastRow * (currentCellWidth + ColumnSpacing);
}
else
{
x = col * (cellWidth + ColumnSpacing);
}
var y = row * (cellHeight + RowSpacing);
child.Arrange(new Rect(x, y, currentCellWidth, cellHeight));
}
return finalSize;
}
#region Clipping Logic ()
/// <summary>
/// 绘制UniformGrid的视觉效果。
/// </summary>
/// <param name="dc">用于绘制的DrawingContext。</param>
protected override void OnRender(DrawingContext dc)
{
ApplyClip();
base.OnRender(dc);
}
private static bool IsZero(double value) => Math.Abs(value) < 1.53E-06;
private void ApplyClip()
{
var cr = CornerRadius;
if (IsZero(cr.TopLeft) && IsZero(cr.TopRight) &&
IsZero(cr.BottomLeft) && IsZero(cr.BottomRight))
{
this.Clip = null;
return;
}
var clipGeometry = new RectangleGeometry(new Rect(this.RenderSize), cr.TopLeft, cr.TopLeft);
if (cr.TopLeft == cr.TopRight && cr.TopLeft == cr.BottomLeft && cr.TopLeft == cr.BottomRight)
{
clipGeometry.RadiusX = cr.TopLeft;
clipGeometry.RadiusY = cr.TopLeft;
}
else
{
clipGeometry.RadiusX = (cr.TopLeft + cr.TopRight) / 2;
clipGeometry.RadiusY = (cr.TopLeft + cr.BottomLeft) / 2;
}
clipGeometry.Freeze();
this.Clip = clipGeometry;
}
#endregion
}
}

View File

@@ -0,0 +1,84 @@
using System.Windows.Markup;
namespace Melskin.Layout;
/// <summary>
/// WaterfallPanel 类继承自 VirtualizingPanel用于实现瀑布流布局。这种布局方式中元素按照指定的列数排列并且在每列之间保持一定的间距。当一列的高度不足以容纳下一个元素时该元素会自动放置到下一列中从而形成类似瀑布流动的效果。
/// </summary>
[ContentProperty(nameof(Children))]
public class WaterfallPanel : VirtualizingPanel
{
/// <summary>
/// 用于获取或设置 WaterfallPanel 中的列数。此属性定义了布局中元素将被分配到多少列中。
/// </summary>
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(WaterfallPanel), new PropertyMetadata(3));
/// <summary>
/// 用于获取或设置 WaterfallPanel 中列与列之间的间距。此属性定义了布局中每列元素之间的距离,以确保元素之间有足够的空间间隔。
/// </summary>
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(WaterfallPanel), new PropertyMetadata(5.0));
private List<double> columnHeights = [];
/// <summary>
/// 重写基类的 MeasureOverride 方法,以实现瀑布流布局特有的测量逻辑。此方法根据可用空间和当前设置的列数及间距来计算每个子元素的位置,并返回整个面板期望的大小。
/// </summary>
/// <param name="availableSize">一个 Size 结构,表示 WaterfallPanel 可用的空间。</param>
/// <returns>一个 Size 结构,代表经过测量后 WaterfallPanel 的期望尺寸。</returns>
protected override Size MeasureOverride(Size availableSize)
{
columnHeights.Clear();
var panelDesiredSize = new Size(0, 0);
columnHeights = new double[Columns].ToList();
double currentX = 0;
var width = availableSize.Width / Columns - (Columns * Spacing);
for(var i = 0; i < InternalChildren.Count; i++)
{
if(InternalChildren[i] is not FrameworkElement child)
continue;
child.Measure(availableSize);
child.Width = width;
var columnIndex = i % Columns;
var x = columnIndex != 0 ? currentX + Spacing : 0;
var y = columnHeights[columnIndex];
if(i >= Columns)
y = y + Spacing;
var size = new Size(width, child.DesiredSize.Height);
child.Arrange(new Rect(new Point(x, y), size));
panelDesiredSize.Width = Math.Max(panelDesiredSize.Width, x + child.DesiredSize.Width);
panelDesiredSize.Height = Math.Max(panelDesiredSize.Height, y + child.DesiredSize.Height);
currentX = x + size.Width;
if(currentX >= Width)
currentX = 0;
columnHeights[columnIndex] += child.DesiredSize.Height + (i >= Columns ? Spacing : 0);
}
return panelDesiredSize;
}
/// <summary>
/// 向 WaterfallPanel 中添加一个新的子元素。
/// </summary>
/// <param name="element">要添加到 WaterfallPanel 中的 UIElement。</param>
public void AddChild(UIElement element)
{
Children.Add(element); }
/// <summary>
/// 获取或设置 WaterfallPanel 中的列数。此属性决定了布局中元素将被分配到多少列中,从而影响瀑布流布局的整体外观。
/// </summary>
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
/// <summary>
/// 用于获取或设置 WaterfallPanel 中列与列之间的间距。此属性定义了布局中每列元素之间的距离,以确保元素之间有足够的空间间隔。
/// </summary>
public double Spacing
{
get => (double)GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
}

404
Melskin/Layout/WrapPanel.cs Normal file
View File

@@ -0,0 +1,404 @@
using System.Windows.Markup;
namespace Melskin.Layout;
/// <summary>
/// WrapPanel 是一种布局面板,它允许子元素根据指定的方向(水平或垂直)自动换行排列。
/// 该面板提供了一种灵活的方式来控制子元素的宽度、高度以及它们之间的间距。
/// </summary>
/// <remarks>
/// 通过设置 Orientation 属性,可以指定子元素的排列方向是水平还是垂直。
/// ItemWidth 和 ItemHeight 属性用于统一设置所有子元素的宽度和高度。
/// 如果不需要统一设置,则可以忽略这两个属性,每个子元素将根据其自身的内容大小进行布局。
/// VerticalSpacing 和 HorizontalSpacing 属性分别定义了垂直和水平方向上相邻子元素间的间距。
/// </remarks>
[ContentProperty(nameof(Children))]
public class WrapPanel : Panel
{
/// <summary>
/// 表示WrapPanel中子元素的排列方向属性。通过设置此属性可以控制子元素是水平排列还是垂直排列。
/// </summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(WrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示WrapPanel中子元素的排列方向属性。通过设置此属性可以控制子元素是水平排列还是垂直排列。
/// </summary>
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// 表示WrapPanel中所有子元素的统一宽度属性。通过设置此属性可以为面板中的所有子元素指定相同的宽度。
/// </summary>
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示WrapPanel中每个子元素的宽度。通过设置此属性可以统一控制面板内所有子元素的宽度。如果设置了该值则所有子元素将具有相同的宽度若未设置即保持默认值NaN则每个子元素将根据其自身内容确定宽度。
/// </summary>
public double ItemWidth
{
get => (double)GetValue(ItemWidthProperty);
set => SetValue(ItemWidthProperty, value);
}
/// <summary>
/// 表示WrapPanel中所有子元素的统一高度属性。通过设置此属性可以控制面板内所有子元素的高度一致。
/// 如果不需要统一设置,则可以忽略此属性,每个子元素将根据其自身的内容大小进行布局。
/// </summary>
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示WrapPanel中每个子元素的高度属性。通过设置此属性可以统一控制所有子元素的高度。
/// 如果未设置或设置为NaN则子元素将使用其自身的高度。
/// </summary>
public double ItemHeight
{
get => (double)GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
/// <summary>
/// 表示垂直方向上相邻子元素之间的间距属性。通过设置此属性可以控制WrapPanel中垂直排列的子元素间的距离。
/// </summary>
public static readonly DependencyProperty VerticalSpacingProperty =
DependencyProperty.Register(nameof(VerticalSpacing), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示WrapPanel中子元素之间的垂直间距属性。通过设置此属性可以控制子元素在垂直方向上的间距大小。
/// </summary>
public double VerticalSpacing
{
get => (double)GetValue(VerticalSpacingProperty);
set => SetValue(VerticalSpacingProperty, value);
}
/// <summary>
/// 表示WrapPanel中子元素在水平方向上的间距属性。通过设置此属性可以控制相邻子元素之间的水平间距。
/// </summary>
public static readonly DependencyProperty HorizontalSpacingProperty =
DependencyProperty.Register(nameof(HorizontalSpacing), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 表示WrapPanel中子元素在水平方向上的间距属性。通过设置此属性可以控制子元素之间的水平距离。
/// </summary>
public double HorizontalSpacing
{
get => (double)GetValue(HorizontalSpacingProperty);
set => SetValue(HorizontalSpacingProperty, value);
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var verticalSpacing = VerticalSpacing;
var horizontalSpacing = HorizontalSpacing;
var offsetX = 0d;
var offsetY = 0d;
var maxLineLength = 0d;
var currentLineSize = 0d;
double currentLineLength;
Func<Size, double> childWidthGetter = double.IsNaN(itemWidth) ? size => size.Width : _ => itemWidth;
Func<Size, double> childHeightGetter = double.IsNaN(itemHeight) ? size => size.Height : _ => itemHeight;
var internalChildren = InternalChildren;
if (Orientation == Orientation.Horizontal)
{
availableSize.Height = double.PositiveInfinity;
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
child.Measure(availableSize);
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
offsetX += childWidth;
if (child.Visibility != Visibility.Collapsed)
{
offsetX += horizontalSpacing;
}
if (offsetX - horizontalSpacing > availableSize.Width)
{
currentLineLength = offsetX - horizontalSpacing - childWidth - horizontalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetX = childWidth + horizontalSpacing;
offsetY += currentLineSize;
offsetY += verticalSpacing;
currentLineSize = 0;
}
if (childHeight > currentLineSize)
{
currentLineSize = childHeight;
}
}
currentLineLength = offsetX - horizontalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetY += currentLineSize;
offsetY += verticalSpacing;
return new Size(maxLineLength, offsetY - verticalSpacing);
}
else
{
availableSize.Width = double.PositiveInfinity;
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
child.Measure(availableSize);
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
offsetY += childHeight;
if (child.Visibility != Visibility.Collapsed)
{
offsetY += verticalSpacing;
}
if (offsetY - verticalSpacing > availableSize.Height)
{
currentLineLength = offsetY - horizontalSpacing - childHeight - verticalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetY = childHeight + verticalSpacing;
offsetX += currentLineSize;
offsetX += horizontalSpacing;
currentLineSize = 0;
}
if (childWidth > currentLineSize)
currentLineSize = childWidth;
}
currentLineLength = offsetY - verticalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetX += currentLineSize;
offsetX += horizontalSpacing;
return new Size(offsetX - horizontalSpacing, maxLineLength);
}
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var verticalSpacing = VerticalSpacing;
var horizontalSpacing = HorizontalSpacing;
var tempOffset = 0d;
Func<Size, double> childWidthGetter = double.IsNaN(itemWidth) ? size => size.Width : _ => itemWidth;
Func<Size, double> childHeightGetter = double.IsNaN(itemHeight) ? size => size.Height : _ => itemHeight;
var internalChildren = InternalChildren;
var currentLineSize = 0d;
var currentLineOffsetX = 0d;
var currentLineOffsetY = 0d;
var currentLineIndexStart = 0;
if (Orientation == Orientation.Horizontal)
{
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
tempOffset += childWidth;
if (child.Visibility != Visibility.Collapsed)
{
tempOffset += horizontalSpacing;
}
if (tempOffset - horizontalSpacing > finalSize.Width)
{
ArrangeLineHorizontal(
internalChildren,
currentLineIndexStart,
i,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
horizontalSpacing,
childWidthGetter,
childHeightGetter);
currentLineOffsetX = 0;
currentLineOffsetY += currentLineSize;
currentLineOffsetY += verticalSpacing;
currentLineIndexStart = i;
currentLineSize = 0;
tempOffset = childWidth + horizontalSpacing;
}
if (childHeight > currentLineSize)
{
currentLineSize = childHeight;
}
}
ArrangeLineHorizontal(
internalChildren,
currentLineIndexStart,
internalChildren.Count,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
horizontalSpacing,
childWidthGetter,
childHeightGetter);
}
else
{
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
tempOffset += childHeight;
if (child.Visibility != Visibility.Collapsed)
{
tempOffset += verticalSpacing;
}
if (tempOffset - verticalSpacing > finalSize.Height)
{
ArrangeLineVertical(
internalChildren,
currentLineIndexStart,
i,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
verticalSpacing,
childWidthGetter,
childHeightGetter);
currentLineOffsetY = 0;
currentLineOffsetX += currentLineSize;
currentLineOffsetX += horizontalSpacing;
currentLineIndexStart = i;
currentLineSize = 0;
tempOffset = childHeight + verticalSpacing;
}
if (childWidth > currentLineSize)
{
currentLineSize = childWidth;
}
}
ArrangeLineVertical(
internalChildren,
currentLineIndexStart,
internalChildren.Count,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
verticalSpacing,
childWidthGetter,
childHeightGetter);
}
return finalSize;
static void ArrangeLineHorizontal(
UIElementCollection children,
int childIndexStart,
int childIndexEnd,
double currentLineOffsetX,
double currentLineOffsetY,
double currentLineSize,
double spacing,
Func<Size, double> widthGetter,
Func<Size, double> heightGetter)
{
var lineChildOffset = 0d;
for (var j = childIndexStart; j < childIndexEnd; j++)
{
var lineChild = children[j];
var lineChildDesiredSize = lineChild.DesiredSize;
var lineChildWidth = widthGetter.Invoke(lineChildDesiredSize);
heightGetter.Invoke(lineChildDesiredSize);
lineChild.Arrange(new Rect(currentLineOffsetX + lineChildOffset, currentLineOffsetY, lineChildWidth, currentLineSize));
lineChildOffset += lineChildWidth;
if (lineChild.Visibility != Visibility.Collapsed)
{
lineChildOffset += spacing;
}
}
}
static void ArrangeLineVertical(
UIElementCollection children,
int childIndexStart,
int childIndexEnd,
double currentLineOffsetX,
double currentLineOffsetY,
double currentLineSize,
double spacing,
Func<Size, double> widthGetter,
Func<Size, double> heightGetter)
{
var lineChildOffset = 0d;
for (var j = childIndexStart; j < childIndexEnd; j++)
{
var lineChild = children[j];
var lineChildDesiredSize = lineChild.DesiredSize;
widthGetter.Invoke(lineChildDesiredSize);
var lineChildHeight = heightGetter.Invoke(lineChildDesiredSize);
lineChild.Arrange(new Rect(currentLineOffsetX, currentLineOffsetY + lineChildOffset, currentLineSize, lineChildHeight));
lineChildOffset += lineChildHeight;
if (lineChild.Visibility != Visibility.Collapsed)
{
lineChildOffset += spacing;
}
}
}
}
}