前景:
我需要一个WrapPanel,里面有很多的控件,但是,WrapPanel 换行后是左对齐的,问题是我可能就3个控件,下面一个控件又有点长,左对齐太丑了,于是找了一下解决方案。哎,您说巧不巧,没搜到......
内心痛骂,某某dn真不愧是业界毒瘤,搜了半天,一个成用的没有,全是ctrl c + ctrl v,cv也行啊,但是不解决问题。真乌鸡鲅鱼,于是就有了这篇文章。
秉承着能用就行,能跑就行,又不是不能用的原则和方针,代码直接放到最后,拿来就用。
思路:
通过自定义控件的方式实现这个WrapPanel,然后引用控件,用法还是WrapPanel的正常用法,无非就是多加了个依赖属性,重写了一下ArrangeOverride,下面是具体思路:
1. 定义对齐方式的枚举
首先,定义一个枚举 LineAlignment 来表示行内元素的对齐方式。这个枚举包含三种值:Left、Center 和 Right,用于表示左对齐、居中对齐和右对齐。
2. 创建自定义的 WrapPanel
继承 WrapPanel 类,创建一个自定义的 AlignedWrapPanel,并添加一个依赖属性LineAlignment,用于在 XAML 或代码中指定对齐方式。
3. 重写 ArrangeOverride 方法
ArrangeOverride 方法是 WPF 布局系统的一部分,负责排列子元素。在这个方法中,你需要遍历所有子元素,并按顺序计算它们的位置。
在计算子元素位置时,需要判断当前行剩余的空间。如果当前行已满,或者即将换行,则根据对齐方式调整这一行的元素位置。
4. 实现行内元素的对齐逻辑
通过一个辅助方法 AdjustLine 来调整当前行中元素的水平位置:
Left 对齐:子元素从左到右排列,无需额外调整。
Center 对齐:计算行内元素的总宽度,并将剩余空间平均分配到行内每个元素的前面,以实现居中效果。
Right 对齐:计算行内元素的总宽度,并将剩余空间全部分配到行内第一个元素的前面,使得整行右对齐。
5. 处理换行
在 ArrangeOverride 中,遍历每个子元素时,如果当前行的宽度超出了 WrapPanel 的可用宽度,则需要换行,并在换行后调用 AdjustLine 方法来调整上一行的子元素对齐。
代码:
public enum LineAlignment
{
Left,
Center,
Right
}
public class AlignedWrapPanel : WrapPanel
{
public static readonly DependencyProperty LineAlignmentProperty =
DependencyProperty.Register(
nameof(LineAlignment),
typeof(LineAlignment),
typeof(AlignedWrapPanel),
new FrameworkPropertyMetadata(LineAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
public LineAlignment LineAlignment
{
get => (LineAlignment)GetValue(LineAlignmentProperty);
set => SetValue(LineAlignmentProperty, value);
}
protected override Size ArrangeOverride(Size finalSize)
{
double currentLineHeight = 0;
double currentX = 0;
double currentY = 0;
foreach (UIElement child in InternalChildren)
{
Size childDesiredSize = child.DesiredSize;
if (currentX + childDesiredSize.Width > finalSize.Width)
{
// 根据对齐方式调整行内子元素的起始位置
AdjustLine(finalSize.Width, ref currentX, ref currentY, currentLineHeight);
currentX = 0;
currentY += currentLineHeight;
currentLineHeight = 0;
}
child.Arrange(new Rect(new Point(currentX, currentY), childDesiredSize));
currentX += childDesiredSize.Width;
currentLineHeight = Math.Max(currentLineHeight, childDesiredSize.Height);
}
// 调整最后一行的元素对齐
AdjustLine(finalSize.Width, ref currentX, ref currentY, currentLineHeight);
return finalSize;
}
private void AdjustLine(double panelWidth, ref double currentX, ref double currentY, double currentLineHeight)
{
double spaceLeft = panelWidth - currentX;
double offset = 0;
switch (LineAlignment)
{
case LineAlignment.Center:
offset = spaceLeft / 2;
break;
case LineAlignment.Right:
offset = spaceLeft;
break;
case LineAlignment.Left:
default:
offset = 0;
break;
}
if (offset > 0)
{
foreach (UIElement lineChild in InternalChildren)
{
Rect arrangeRect = lineChild.TransformToAncestor(this).TransformBounds(new Rect(lineChild.RenderSize));
if (arrangeRect.Top >= currentY && arrangeRect.Top < currentY + currentLineHeight)
{
lineChild.Arrange(new Rect(new Point(arrangeRect.X + offset, arrangeRect.Y), lineChild.DesiredSize));
}
}
}
}
}