【问题标题】:Is there any way to occupy blank space in WrapPanel automatically?有什么办法可以自动占用 WrapPanel 中的空白空间?
【发布时间】:2018-05-30 07:08:36
【问题描述】:

WrapPanel 的子项按附加的屏幕截图顺序填充。

因此,Panel会根据每个child的长度,留出长长的空格。

如何利用空白空间重新安排孩子?

目前似乎只有少数人使用 WrapPanel,而且没有足够的例子。

有一些自动的方法吗? 还是我只需要自己做算法?

WrapPanel 对显示事物起着非常重要的作用,但显示的空间有限。

谢谢!

【问题讨论】:

  • 我不认为有,也许你已经想出了自己的算法来计算孩子的宽度并相应地重新排列。
  • 您可能正在寻找“树图”或“标签云”。
  • @KayLee 请注意,您要求的是一种特殊情况,其中所有子元素具有相同的高度。但是,WrapPanel 会排列任意尺寸的元素。
  • 我的意思是通用算法(针对不同高度的元素)会复杂得多。
  • 所以你根本不关心元素的顺序?目标是尽可能高效地使用可用空间?

标签: c# wpf space children wrappanel


【解决方案1】:

WrapPanel 中的所有元素的高度是否相同?无论哪种情况,为什么不从 https://marketplace.visualstudio.com/items?itemName=DevExpress.WPFTreeMapControl 这样的 TreeMap 控件开始并对其进行自定义呢?

编辑 1 - 基于评论

实现起来应该不会太难 - 我很确定您需要的最小功能可以通过从面板继承并在其 MeasureOverrideArrangeOverride 方法中挂钩重新排列代码来实现。 也看看这里(实现类似的控件),如果您选择这样做可能是一个好的开始:https://codeblitz.wordpress.com/2009/03/20/wpf-auto-arrange-animated-panel/

【讨论】:

  • 非常感谢您介绍好主意。如果你这么说,自定义 Treemap 可能是一个很好的解决方案,但是..我已经为非营利应用程序投入了太多资源,似乎超出了我的编码能力..
【解决方案2】:

为此,您需要一个自定义算法。我相信StretchyWrapPanel 提到的here 是一个好的开始。测量子项后,您可以使用自定义逻辑对它们进行排序(例如bin packing)。

【讨论】:

  • Surfin Bird 所附的图片看起来符合我的目的,但说的是拉伸元素。我将深入研究并测试代码。请稍等。感谢您分享链接。
  • 您好,链接中的作者提供了真实的答案。我将通过您的个人资料投票来提供非官方赏金 100。谢谢介绍。
【解决方案3】:

这个简单的面板应该可以完成这项工作:

public class MySpecialWrapPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in InternalChildren)
        {
            child.Measure(availableSize);
        }

        return new Size();
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        var x = 0d;
        var y = 0d;
        var height = 0d;
        var children = InternalChildren.Cast<UIElement>().ToList();

        while (children.Count > 0)
        {
            var child = children.First();

            if (x > 0d && x + child.DesiredSize.Width > finalSize.Width)
            {
                // try to find child that fits
                var fit = children.FirstOrDefault(
                    c => x + c.DesiredSize.Width <= finalSize.Width);

                child = fit ?? child;

                if (x + child.DesiredSize.Width > finalSize.Width)
                {
                    x = 0d;
                    y = height;
                }
            }

            children.Remove(child);
            child.Arrange(
                new Rect(x, y, child.DesiredSize.Width, child.DesiredSize.Height));

            x += child.DesiredSize.Width;
            height = Math.Max(height, y + child.DesiredSize.Height);
        }

        return finalSize;
    }
}

【讨论】:

  • 非常感谢您的解决方案。请理解我昨天与伯德先生进行了沟通。当然,我也会测试这个解决方案,如果它值得另一个答案,我会为你的好意提供非官方的赏金。请等到周末进行测试。
  • @KayLee 请注意,您通常应该接受最能解决您的问题的答案。我认为这里显示的代码要简单得多,主题也更丰富,因为它完全符合您在问题中描述的内容,不多也不少。
【解决方案4】:

这是一个 WrapPanel,它可以选择使用 FFDH algorithm 重新排列元素,也可以选择拉伸它们以删除空白区域 (example)。

public class StretchyWrapPanel : Panel {
    public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(nameof(ItemWidth), typeof(double),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
                ((StretchyWrapPanel)o)._itemWidth = (double)e.NewValue;
            }));

    private double _itemWidth = double.NaN;

    [TypeConverter(typeof(LengthConverter))]
    public double ItemWidth {
        get => _itemWidth;
        set => SetValue(ItemWidthProperty, value);
    }

    public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(nameof(ItemHeight), typeof(double),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
                ((StretchyWrapPanel)o)._itemHeight = (double)e.NewValue;
            }));

    private double _itemHeight = double.NaN;

    [TypeConverter(typeof(LengthConverter))]
    public double ItemHeight {
        get => _itemHeight;
        set => SetValue(ItemHeightProperty, value);
    }

    public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
                ((StretchyWrapPanel)o)._orientation = (Orientation)e.NewValue;
            }));

    private Orientation _orientation = Orientation.Horizontal;

    public Orientation Orientation {
        get => _orientation;
        set => SetValue(OrientationProperty, value);
    }

    public static readonly DependencyProperty StretchToFillProperty = DependencyProperty.Register(nameof(StretchToFill), typeof(bool),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
                ((StretchyWrapPanel)o)._stretchToFill = (bool)e.NewValue;
            }));

    private bool _stretchToFill = true;

    public bool StretchToFill {
        get => _stretchToFill;
        set => SetValue(StretchToFillProperty, value);
    }

    public static readonly DependencyProperty StretchProportionallyProperty = DependencyProperty.Register(nameof(StretchProportionally), typeof(bool),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
                ((StretchyWrapPanel)o)._stretchProportionally = (bool)e.NewValue;
            }));

    private bool _stretchProportionally = true;

    public bool StretchProportionally {
        get => _stretchProportionally;
        set => SetValue(StretchProportionallyProperty, value);
    }

    public static readonly DependencyProperty RearrangeForBestFitProperty = DependencyProperty.Register(nameof(RearrangeForBestFit), typeof(bool),
            typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
                ((StretchyWrapPanel)o)._rearrangeForBestFit = (bool)e.NewValue;
            }));

    private bool _rearrangeForBestFit;

    public bool RearrangeForBestFit {
        get => _rearrangeForBestFit;
        set => SetValue(RearrangeForBestFitProperty, value);
    }

    private struct UVSize {
        internal UVSize(Orientation orientation, Size size) {
            U = V = 0d;
            _isHorizontal = orientation == Orientation.Horizontal;
            Width = size.Width;
            Height = size.Height;
        }

        internal UVSize(Orientation orientation, double width, double height) {
            U = V = 0d;
            _isHorizontal = orientation == Orientation.Horizontal;
            Width = width;
            Height = height;
        }

        internal UVSize(Orientation orientation) {
            U = V = 0d;
            _isHorizontal = orientation == Orientation.Horizontal;
        }

        internal double U;
        internal double V;
        private bool _isHorizontal;

        internal double Width {
            get => _isHorizontal ? U : V;
            set {
                if (_isHorizontal) {
                    U = value;
                } else {
                    V = value;
                }
            }
        }

        internal double Height {
            get => _isHorizontal ? V : U;
            set {
                if (_isHorizontal) {
                    V = value;
                } else {
                    U = value;
                }
            }
        }
    }

    protected override Size MeasureOverride(Size constraint) {
        return RearrangeForBestFit ? MeasureBestFit(constraint) : MeasureKeepInOrder(constraint);
    }

    private Size MeasureKeepInOrder(Size constraint) {
        var orientation = Orientation;
        var uLimit = new UVSize(orientation, constraint.Width, constraint.Height).U;
        var curLineSize = new UVSize(orientation);
        var panelSize = new UVSize(orientation);
        var itemWidth = ItemWidth;
        var itemHeight = ItemHeight;
        var itemWidthSet = !double.IsNaN(itemWidth);
        var itemHeightSet = !double.IsNaN(itemHeight);

        var childConstraint = new Size(
                itemWidthSet ? itemWidth : constraint.Width,
                itemHeightSet ? itemHeight : constraint.Height);

        var children = InternalChildren;

        for (int i = 0, count = children.Count; i < count; i++) {
            var child = children[i];
            if (child == null) continue;

            // Flow passes its own constrint to children
            child.Measure(childConstraint);

            // This is the size of the child in UV space
            var sz = new UVSize(orientation,
                    itemWidthSet ? itemWidth : child.DesiredSize.Width,
                    itemHeightSet ? itemHeight : child.DesiredSize.Height);

            if (curLineSize.U + sz.U > uLimit) {
                // Need to switch to another line
                panelSize.U = Math.Max(curLineSize.U, panelSize.U);
                panelSize.V += curLineSize.V;
                curLineSize = sz;

                if (sz.U > uLimit) {
                    // The element is wider then the constrint - give it a separate line
                    panelSize.U = Math.Max(sz.U, panelSize.U);
                    panelSize.V += sz.V;
                    curLineSize = new UVSize(orientation);
                }
            } else {
                // Continue to accumulate a line
                curLineSize.U += sz.U;
                curLineSize.V = Math.Max(sz.V, curLineSize.V);
            }
        }

        // The last line size, if any should be added
        panelSize.U = Math.Max(curLineSize.U, panelSize.U);
        panelSize.V += curLineSize.V;

        // Go from UV space to W/H space
        return new Size(panelSize.Width, panelSize.Height);
    }

    private Size MeasureBestFit(Size constraint) {
        var orientation = Orientation;
        var uLimit = new UVSize(orientation, constraint.Width, constraint.Height).U;
        var itemWidth = ItemWidth;
        var itemHeight = ItemHeight;
        var itemWidthSet = !double.IsNaN(itemWidth);
        var itemHeightSet = !double.IsNaN(itemHeight);

        var childConstraint = new Size(
                itemWidthSet ? itemWidth : constraint.Width,
                itemHeightSet ? itemHeight : constraint.Height);

        var children = InternalChildren;

        // First-Fit Decreasing Height (FFDH) algorithm
        var lines = new List<UVSize>();

        for (int i = 0, count = children.Count; i < count; i++) {
            var child = children[i];
            if (child == null) continue;

            // Flow passes its own constrint to children
            child.Measure(childConstraint);

            // This is the size of the child in UV space
            var childSize = new UVSize(orientation,
                    itemWidthSet ? itemWidth : child.DesiredSize.Width,
                    itemHeightSet ? itemHeight : child.DesiredSize.Height);

            for (var j = 0; j < lines.Count; j++) {
                var line = lines[j];
                if (line.U + childSize.U <= uLimit) {
                    lines[j] = new UVSize(orientation) { U = childSize.U, V = Math.Max(childSize.V, line.V) };
                    goto Next;
                }
            }

            lines.Add(childSize);

            Next:
            { }
        }

        var panelSize = new UVSize(orientation);
        for (var i = 0; i < lines.Count; i++) {
            var line = lines[i];
            panelSize.U = Math.Max(line.U, panelSize.U);
            panelSize.V += line.V;
        }

        // Go from UV space to W/H space
        return new Size(panelSize.Width, panelSize.Height);
    }

    protected override Size ArrangeOverride(Size finalSize) {
        return RearrangeForBestFit ? ArrangeBestFit(finalSize) : ArrangeKeepInOrder(finalSize);
    }

    private static UVSize GetChildSize(Orientation orientation, UIElement child, UVSize fixedChildSize) {
        var childSize = new UVSize(orientation, child.DesiredSize);
        if (!double.IsNaN(fixedChildSize.U)) childSize.U = fixedChildSize.U;
        if (!double.IsNaN(fixedChildSize.V)) childSize.V = fixedChildSize.V;
        return childSize;
    }

    private Size ArrangeKeepInOrder(Size finalSize) {
        var orientation = Orientation;
        var fixedChildSize = new UVSize(orientation, ItemWidth, ItemHeight);
        var children = InternalChildren;
        var firstInLine = 0;
        var uLimit = new UVSize(orientation, finalSize).U;
        var currentLineSize = new UVSize(orientation);
        var accumulatedV = 0d;

        for (int i = 0, count = children.Count; i < count; i++) {
            var child = children[i];
            if (child == null) continue;

            var childSize = GetChildSize(orientation, child, fixedChildSize);
            if (currentLineSize.U + childSize.U > uLimit) {
                // Need to switch to another line
                if (!double.IsNaN(fixedChildSize.U)) {
                    ArrangeLineFixedSize(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i, fixedChildSize.U);
                } else if (!StretchToFill) {
                    ArrangeLineDefault(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i);
                } else {
                    ArrangeLineStretch(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i, uLimit, StretchProportionally);
                }

                accumulatedV += currentLineSize.V;
                currentLineSize = childSize;
                firstInLine = i;
            } else {
                // Continue to accumulate a line
                currentLineSize.U += childSize.U;
                currentLineSize.V = Math.Max(childSize.V, currentLineSize.V);
            }
        }

        // Arrange the last line, if any
        if (!double.IsNaN(fixedChildSize.U)) {
            ArrangeLineFixedSize(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count, fixedChildSize.U);
        } else if (!StretchToFill) {
            ArrangeLineDefault(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count);
        } else {
            ArrangeLineStretch(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count, uLimit, StretchProportionally);
        }

        return finalSize;
    }

    private static void ArrangeLineDefault(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end) {
        var position = new UVSize(orientation){ U = 0d, V = v };
        for (var i = start; i < end; i++) {
            var child = children[i];
            if (child != null) {
                var childSize = new UVSize(orientation, child.DesiredSize) { V = lineV };
                child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                position.U += childSize.U;
            }
        }
    }

    private static void ArrangeLineStretch(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end,
            double limitU, bool stretchProportionally) {
        var totalU = 0d;
        for (var i = start; i < end; i++) {
            totalU += new UVSize(orientation, children[i].DesiredSize).U;
        }

        var position = new UVSize(orientation) { U = 0d, V = v };
        var uExtra = stretchProportionally ? limitU / totalU : (limitU - totalU) / (end - start);
        for (var i = start; i < end; i++) {
            var child = children[i];
            if (child != null) {
                var childSize = new UVSize(orientation, child.DesiredSize) { V = lineV };
                childSize.U = stretchProportionally ? childSize.U * uExtra : Math.Max(childSize.U + uExtra, 1d);
                child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                position.U += childSize.U;
            }
        }
    }

    private static void ArrangeLineFixedSize(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end, double itemU) {
        var position = new UVSize(orientation) { U = 0d, V = v };
        var childSize = new UVSize(orientation){ U = itemU, V = lineV };
        for (var i = start; i < end; i++) {
            var child = children[i];
            if (child != null) {
                child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                position.U += childSize.U;
            }
        }
    }

    private class ArrangeBestFitLine {
        public UVSize Size;
        public readonly List<int> ItemIndices = new List<int>();

        public void ArrangeDefault(Orientation orientation, UIElementCollection children, double v) {
            var position = new UVSize(orientation){ U = 0d, V = v };
            for (var i = 0; i < ItemIndices.Count; i++) {
                var child = children[ItemIndices[i]];
                if (child != null) {
                    var childSize = new UVSize(orientation, child.DesiredSize) { V = Size.V };
                    child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                    position.U += childSize.U;
                }
            }
        }

        public void ArrangeStretch(Orientation orientation, UIElementCollection children, double v, double limitU, bool stretchProportionally) {
            var totalU = 0d;
            for (var i = 0; i < ItemIndices.Count; i++) {
                totalU += new UVSize(orientation, children[ItemIndices[i]].DesiredSize).U;
            }

            var position = new UVSize(orientation) { U = 0d, V = v };
            var uExtra = stretchProportionally ? limitU / totalU : (limitU - totalU) / ItemIndices.Count;
            for (var i = 0; i < ItemIndices.Count; i++) {
                var child = children[ItemIndices[i]];
                if (child != null) {
                    var childSize = new UVSize(orientation, child.DesiredSize) { V = Size.V };
                    childSize.U = stretchProportionally ? childSize.U * uExtra : Math.Max(childSize.U + uExtra, 1d);
                    child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                    position.U += childSize.U;
                }
            }
        }

        public void ArrangeFixedSize(Orientation orientation, UIElementCollection children, double v, double itemU) {
            var position = new UVSize(orientation) { U = 0d, V = v };
            var childSize = new UVSize(orientation){ U = itemU, V = Size.V };
            for (var i = 0; i < ItemIndices.Count; i++) {
                var child = children[ItemIndices[i]];
                if (child != null) {
                    child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
                    position.U += childSize.U;
                }
            }
        }
    }

    private Size ArrangeBestFit(Size finalSize) {
        var orientation = Orientation;
        var fixedChildSize = new UVSize(orientation, ItemWidth, ItemHeight);
        var uLimit = new UVSize(orientation, finalSize).U;

        // First-Fit Decreasing Height (FFDH) algorithm
        var lines = new List<ArrangeBestFitLine>();
        var children = InternalChildren;
        for (int i = 0, count = children.Count; i < count; i++) {
            var child = children[i];
            if (child == null) continue;

            var childSize = GetChildSize(orientation, child, fixedChildSize);
            for (var j = 0; j < lines.Count; j++) {
                var line = lines[j];
                if (line.Size.U + childSize.U <= uLimit) {
                    line.Size.U += childSize.U;
                    line.Size.V = Math.Max(childSize.V, line.Size.V);
                    line.ItemIndices.Add(i);
                    goto Next;
                }
            }

            lines.Add(new ArrangeBestFitLine {
                Size = childSize,
                ItemIndices = { i }
            });

            Next:
            { }
        }

        var accumulatedV = 0d;
        for (var i = 0; i < lines.Count; i++) {
            var line = lines[i];

            if (!double.IsNaN(fixedChildSize.U)) {
                line.ArrangeFixedSize(orientation, children, accumulatedV, fixedChildSize.U);
            } else if (!StretchToFill) {
                line.ArrangeDefault(orientation, children, accumulatedV);
            } else {
                line.ArrangeStretch(orientation, children, accumulatedV, uLimit, StretchProportionally);
            }

            accumulatedV += line.Size.V;
        }

        return finalSize;
    }
}

【讨论】:

  • 您好,Bird 先生,如果此代码确实显示了您的带有各种选项的示例,那么没有办法将其标记为答案。我对您的贡献表示最深切的感谢!
猜你喜欢
  • 2017-03-18
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
  • 2021-04-04
  • 2019-03-10
  • 2012-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多