具有覆盖的 OnVisualChildrenChanged 的​​自定义 WPF WrapPanel 将无法按预期工作

Cutom WPF WrapPanel with overridden OnVisualChildrenChanged won't work as expected

我正在 Windows 上开发一个应用程序,我想要一个像这样布局其子项的面板:

我在 WPF 面板中找到的最接近的是垂直方向的 WrapPanel,其布局如下:

到目前为止,我的解决方案是从 WrapPanel 创建派生的 class 并将其及其每个子项旋转 180 度,以便实现我的目标:

public class NotificationWrapPanel : WrapPanel
{
    public NotificationWrapPanel()
    {
        this.Orientation = Orientation.Vertical;
        this.RenderTransformOrigin = new Point(.5, .5);
        this.RenderTransform = new RotateTransform(180);
    }
    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        var addedChild = visualAdded as UIElement;
        if (addedChild != null)
        {
            addedChild.RenderTransformOrigin = new Point(.5, .5);
            addedChild.RenderTransform = new RotateTransform(180);
        }

        base.OnVisualChildrenChanged(addedChild, visualRemoved);
    }
}

问题是覆盖的 OnVisualChildrenChanged 中的 addedChild.RenderTransform = new RotateTransform(180) 部分似乎不起作用。欢迎任何解决方案(即使与我的方法无关)。

更新:

我重新检查了我的代码并意识到我在其他地方更改了 RenderTransform,阻止了它的工作。所以我的问题解决了。但是,如果您提供任何可能更优雅的解决方案,我将不胜感激,例如使用 MeasureOverride/ArrangeOverride.

根据您的要求,下面是一个自定义面板,我认为它应该可以满足您的需求。它以从下到上然后从右到左的方式排列子元素,在一列中最多堆叠 MaxRows 个元素(如果是 null,则堆叠所有元素)。所有插槽的大小都相同。它还确实考虑了元素的 Visibility 值,即如果一个项目是 Hidden,它会留下一个空槽,如果它是 Collapsed,它会被跳过并且下一个元素 "jumps" 到位。

public class NotificationWrapPanel : Panel
{
    public static readonly DependencyProperty MaxRowsProperty =
        DependencyProperty.Register(
            name: nameof(MaxRows),
            propertyType: typeof(int?),
            ownerType: typeof(NotificationWrapPanel),
            typeMetadata: new FrameworkPropertyMetadata
            {
                AffectsArrange = true,
                AffectsMeasure = true,
            },
            validateValueCallback: o => o == null || (int)o >= 1);

    public int? MaxRows
    {
        get { return (int?)GetValue(MaxRowsProperty); }
        set { SetValue(MaxRowsProperty, value); }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var children = InternalChildren
            .Cast<UIElement>()
            .Where(e => e.Visibility != Visibility.Collapsed)
            .ToList();
        if (children.Count == 0) return new Size();
        var rows = MaxRows.HasValue ?
            Math.Min(MaxRows.Value, children.Count) :
            children.Count;
        var columns = children.Count / rows +
            Math.Sign(children.Count % rows);
        var childConstraint = new Size
        {
            Width = constraint.Width / columns,
            Height = constraint.Height / rows,
        };
        foreach (UIElement child in children)
            child.Measure(childConstraint);
        return new Size
        {
            Height = rows * children
                .Cast<UIElement>()
                .Max(e => e.DesiredSize.Height),
            Width = columns * children
                .Cast<UIElement>()
                .Max(e => e.DesiredSize.Width),
        };
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        var children = InternalChildren
            .Cast<UIElement>()
            .Where(e => e.Visibility != Visibility.Collapsed)
            .ToList();
        if (children.Count == 0) return finalSize;
        var rows = MaxRows.HasValue ?
            Math.Min(MaxRows.Value, children.Count) : 
            children.Count;
        var columns = children.Count / rows +
            Math.Sign(children.Count % rows);
        var childSize = new Size
        {
            Width = finalSize.Width / columns,
            Height = finalSize.Height / rows
        };
        for (int i = 0; i < children.Count; i++)
        {
            var row = i % rows; //rows are numbered bottom-to-top
            var col = i / rows; //columns are numbered right-to-left
            var location = new Point
            {
                X = finalSize.Width - (col + 1) * childSize.Width,
                Y = finalSize.Height - (row + 1) * childSize.Height,
            };
            children[i].Arrange(new Rect(location, childSize));
        }
        return finalSize;
    }
}