using System;
using System.Linq;
using System.Windows.Controls;
namespace WPFluent.Layout;
///
/// 堆叠面板
///
public class StackPanel : Panel
{
///
/// 设置某子项附加属性,控制其是否填充到当前StackPanel所在的面板(控制单个子项的在面板填充方式)
///
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()
.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())
{
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()
.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); }
///
/// 堆叠方向
///
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
///
/// 子项间距
///
public double Spacing
{
get { return (double)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
}
public enum FillType
{
[Description("实际大小填充")]
Auto,
[Description("填充剩余空间相当于Stretch")]
Fill,
[Description("忽略当前项")]
Ignored
}