Files
Shrlalgo.RvKits/WPFluent/Layout/StackPanel.cs
2025-04-24 20:56:44 +08:00

270 lines
9.4 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.Linq;
using System.Windows.Controls;
namespace WPFluent.Layout;
/// <summary>
/// 堆叠面板
/// </summary>
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));
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
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;
}
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;
}
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;
}
public static FillType GetFill(DependencyObject element) { return (FillType)element.GetValue(FillProperty); }
public static void SetFill(DependencyObject element, FillType value) { element.SetValue(FillProperty, value); }
/// <summary>
/// 堆叠方向
/// </summary>
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// 子项间距
/// </summary>
public double Spacing
{
get { return (double)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
}
public enum FillType
{
[Description("实际大小填充")]
Auto,
[Description("填充剩余空间相当于Stretch")]
Fill,
[Description("忽略当前项")]
Ignored
}