Domanda

Sto usando un WrapPanel come ItemsPanel di un ItemsControl . In questo momento, gli elementi nel controllo si avvolgono in questo modo:

|1234567  |
|890      |

Vorrei che si avvolgessero così:

| 1234567 |
|   890   |

Concettualmente, il processo di layout dovrebbe allineare ogni riga di elementi in modo tale che sia centrata nei limiti del WrapPanel .

Qualcuno può spiegare come sia possibile, per favore?

È stato utile?

Soluzione

Il WrapPanel integrato non ti permetterà di allinearne il contenuto, ma solo se stesso. Ecco una tecnica che ti consente di impostare HorizontalContentAlignment :

using System;
using System.Windows;
using System.Windows.Controls;

public class AlignableWrapPanel : Panel
{
    public HorizontalAlignment HorizontalContentAlignment
    {
        get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
        set { SetValue(HorizontalContentAlignmentProperty, value); }
    }

    public static readonly DependencyProperty HorizontalContentAlignmentProperty =
        DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));

    protected override Size MeasureOverride(Size constraint)
    {
        Size curLineSize = new Size();
        Size panelSize = new Size();

        UIElementCollection children = base.InternalChildren;

        for (int i = 0; i < children.Count; i++)
        {
            UIElement child = children[i] as UIElement;

            // Flow passes its own constraint to children
            child.Measure(constraint);
            Size sz = child.DesiredSize;

            if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
            {
                panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
                panelSize.Height += curLineSize.Height;
                curLineSize = sz;

                if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line                    
                {
                    panelSize.Width = Math.Max(sz.Width, panelSize.Width);
                    panelSize.Height += sz.Height;
                    curLineSize = new Size();
                }
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += sz.Width;
                curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
            }
        }

        // the last line size, if any need to be added
        panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
        panelSize.Height += curLineSize.Height;

        return panelSize;
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        int firstInLine = 0;
        Size curLineSize = new Size();
        double accumulatedHeight = 0;
        UIElementCollection children = this.InternalChildren;

        for (int i = 0; i < children.Count; i++)
        {
            Size sz = children[i].DesiredSize;

            if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
            {
                ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);

                accumulatedHeight += curLineSize.Height;
                curLineSize = sz;

                if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line                    
                {
                    ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
                    accumulatedHeight += sz.Height;
                    curLineSize = new Size();
                }
                firstInLine = i;
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += sz.Width;
                curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
            }
        }

        if (firstInLine < children.Count)
            ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);

        return arrangeBounds;
    }

    private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
    {
        double x = 0;
        if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
        {
            x = (boundsWidth - lineSize.Width) / 2;
        }
        else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
        {
            x = (boundsWidth - lineSize.Width);
        }

        UIElementCollection children = InternalChildren;
        for (int i = start; i < end; i++)
        {
            UIElement child = children[i];
            child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height));
            x += child.DesiredSize.Width;
        }
    }
}

Altri suggerimenti

Sfortunatamente lo stock WrapPanel non farà ciò che stai chiedendo, perché la sua intenzione è di riempire tutto lo spazio disponibile in orizzontale (o in verticale) prima di avvolgere. Probabilmente dovrai progettare il tuo WrapPanel per gestire questo caso. Puoi iniziare con questo esempio che mostra come creare il tuo WrapPanel .

Versione con VerticalContentAlignment (per pannelli verticali):

public class AlignableWrapPanel : Panel {
    public AlignableWrapPanel() {
        _orientation = Orientation.Horizontal;
    }

    private static bool IsWidthHeightValid(object value) {
        var v = (double)value;
        return (double.IsNaN(v)) || (v >= 0.0d && !double.IsPositiveInfinity(v));
    }

    public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double),
            typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
            IsWidthHeightValid);

    [TypeConverter(typeof(LengthConverter))]
    public double ItemWidth {
        get { return (double)GetValue(ItemWidthProperty); }
        set { SetValue(ItemWidthProperty, value); }
    }

    public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double),
            typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
            IsWidthHeightValid);

    [TypeConverter(typeof(LengthConverter))]
    public double ItemHeight {
        get { return (double)GetValue(ItemHeightProperty); }
        set { SetValue(ItemHeightProperty, value); }
    }

    public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(AlignableWrapPanel),
            new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure, OnOrientationChanged));

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

    private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var p = (AlignableWrapPanel)d;
        p._orientation = (Orientation)e.NewValue;
    }

    private Orientation _orientation;

    private struct UvSize {
        internal UvSize(Orientation orientation, double width, double height) {
            U = V = 0d;
            _orientation = orientation;
            Width = width;
            Height = height;
        }

        internal UvSize(Orientation orientation) {
            U = V = 0d;
            _orientation = orientation;
        }

        internal double U;
        internal double V;
        private readonly Orientation _orientation;

        internal double Width {
            get { return (_orientation == Orientation.Horizontal ? U : V); }
            private set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
        }

        internal double Height {
            get { return (_orientation == Orientation.Horizontal ? V : U); }
            private set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
        }
    }

    protected override Size MeasureOverride(Size constraint) {
        var curLineSize = new UvSize(Orientation);
        var panelSize = new UvSize(Orientation);
        var uvConstraint = new UvSize(Orientation, constraint.Width, constraint.Height);
        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 > uvConstraint.U) {
                //need to switch to another line 
                panelSize.U = Math.Max(curLineSize.U, panelSize.U);
                panelSize.V += curLineSize.V;
                curLineSize = sz;

                if (!(sz.U > uvConstraint.U)) continue;
                //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);
    }

    protected override Size ArrangeOverride(Size finalSize) {
        var firstInLine = 0;
        var itemWidth = ItemWidth;
        var itemHeight = ItemHeight;
        double accumulatedV = 0;
        var itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
        var curLineSize = new UvSize(Orientation);
        var uvFinalSize = new UvSize(Orientation, finalSize.Width, finalSize.Height);
        var itemWidthSet = !double.IsNaN(itemWidth);
        var itemHeightSet = !double.IsNaN(itemHeight);
        var useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);

        var children = InternalChildren;

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

            var sz = new UvSize(
                    Orientation,
                    (itemWidthSet ? itemWidth : child.DesiredSize.Width),
                    (itemHeightSet ? itemHeight : child.DesiredSize.Height));

            if (curLineSize.U + sz.U > uvFinalSize.U) {
                //need to switch to another line 
                ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, i, useItemU, itemU);

                accumulatedV += curLineSize.V;
                curLineSize = sz;

                if (sz.U > uvFinalSize.U) {
                    //the element is wider then the constraint - give it a separate line 
                    //switch to next line which only contain one element 
                    ArrangeLine(finalSize, accumulatedV, sz, i, ++i, useItemU, itemU);

                    accumulatedV += sz.V;
                    curLineSize = new UvSize(Orientation);
                }

                firstInLine = i;
            } else {
                //continue to accumulate a line
                curLineSize.U += sz.U;
                curLineSize.V = Math.Max(sz.V, curLineSize.V);
            }
        }

        //arrange the last line, if any
        if (firstInLine < children.Count) {
            ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, children.Count, useItemU, itemU);
        }

        return finalSize;
    }

    private void ArrangeLine(Size finalSize, double v, UvSize line, int start, int end, bool useItemU, double itemU) {
        double u;
        var isHorizontal = Orientation == Orientation.Horizontal;

        if (_orientation == Orientation.Vertical) {
            switch (VerticalContentAlignment) {
                case VerticalAlignment.Center:
                    u = (finalSize.Height - line.U) / 2;
                    break;
                case VerticalAlignment.Bottom:
                    u = finalSize.Height - line.U;
                    break;
                default:
                    u = 0;
                    break;
            }
        } else {
            switch (HorizontalContentAlignment) {
                case HorizontalAlignment.Center:
                    u = (finalSize.Width - line.U) / 2;
                    break;
                case HorizontalAlignment.Right:
                    u = finalSize.Width - line.U;
                    break;
                default:
                    u = 0;
                    break;
            }
        }

        var children = InternalChildren;
        for (var i = start; i < end; i++) {
            var child = children[i];
            if (child == null) continue;
            var childSize = new UvSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
            var layoutSlotU = (useItemU ? itemU : childSize.U);
            child.Arrange(new Rect(
                    isHorizontal ? u : v,
                    isHorizontal ? v : u,
                    isHorizontal ? layoutSlotU : line.V,
                    isHorizontal ? line.V : layoutSlotU));
            u += layoutSlotU;
        }
    }

    public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(nameof(HorizontalContentAlignment), typeof(HorizontalAlignment),
            typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));

    public HorizontalAlignment HorizontalContentAlignment {
        get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
        set { SetValue(HorizontalContentAlignmentProperty, value); }
    }

    public static readonly DependencyProperty VerticalContentAlignmentProperty = DependencyProperty.Register(nameof(VerticalContentAlignment), typeof(VerticalAlignment),
            typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsArrange));

    public VerticalAlignment VerticalContentAlignment {
        get { return (VerticalAlignment)GetValue(VerticalContentAlignmentProperty); }
        set { SetValue(VerticalContentAlignmentProperty, value); }
    }
}

La semplice risposta è che non è possibile allineare al centro il contenuto di un WrapPanel. Puoi allineare al centro il pannello stesso, ma l'ultima riga rimarrà comunque allineata a sinistra all'interno del pannello.

Suggerimenti alterati:

Usa griglia con righe e colonne. Se non aggiungi elementi alla raccolta in modo dinamico, questo potrebbe funzionare correttamente.

Crea la tua versione di WrapPanel che funziona nel modo desiderato. Questo documento MSDN descrive come funzionano i pannelli e include una sezione su creazione di pannelli personalizzati. Ha anche un collegamento a un pannello personalizzato di esempio.

Ecco la versione di Silverlight

Un ringraziamento speciale a @DTig ,

using System.Windows.Controls;
using System.Windows;
using Telerik.Windows;
using System;
using System.Linq;

public class AlignableWrapPanel : Panel
{
    public HorizontalAlignment HorizontalContentAlignment
    {
        get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
        set { SetValue(HorizontalContentAlignmentProperty, value); }
    }

    public static readonly DependencyProperty HorizontalContentAlignmentProperty =
        DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));

    protected override Size MeasureOverride(Size constraint)
    {
        Size curLineSize = new Size();
        Size panelSize = new Size();

        UIElementCollection children = base.Children;

        for (int i = 0; i < children.Count; i++)
        {
            UIElement child = children[i] as UIElement;

            // Flow passes its own constraint to children
            child.Measure(constraint);
            Size sz = child.DesiredSize;

            if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
            {
                panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
                panelSize.Height += curLineSize.Height;
                curLineSize = sz;

                if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line                    
                {
                    panelSize.Width = Math.Max(sz.Width, panelSize.Width);
                    panelSize.Height += sz.Height;
                    curLineSize = new Size();
                }
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += sz.Width;
                curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
            }
        }

        // the last line size, if any need to be added
        panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
        panelSize.Height += curLineSize.Height;

        return panelSize;
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        int firstInLine = 0;
        Size curLineSize = new Size();
        double accumulatedHeight = 0;
        UIElementCollection children = this.Children;

        for (int i = 0; i < children.Count; i++)
        {
            Size sz = children[i].DesiredSize;

            if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
            {
                ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);

                accumulatedHeight += curLineSize.Height;
                curLineSize = sz;

                if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line                    
                {
                    ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
                    accumulatedHeight += sz.Height;
                    curLineSize = new Size();
                }
                firstInLine = i;
            }
            else //continue to accumulate a line
            {
                curLineSize.Width += sz.Width;
                curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
            }
        }

        if (firstInLine < children.Count)
            ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);

        return arrangeBounds;
    }

    private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
    {
        double x = 0;
        if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
        {
            x = (boundsWidth - lineSize.Width) / 2;
        }
        else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
        {
            x = (boundsWidth - lineSize.Width);
        }

        UIElementCollection children = Children;
        for (int i = start; i < end; i++)
        {
            UIElement child = children[i];
            var rect = new System.Windows.Rect(x, y, child.DesiredSize.Width, lineSize.Height);
            child.Arrange(rect);
            x += child.DesiredSize.Width;
        }
    }
}

Avevo bisogno di un WrapPanel che potesse estendere il suo contenuto, quindi basato su https://stackoverflow.com/a/7747002/121122 e un po 'di confusione mi sono inventato questo:

public class AlignableWrapPanel : Panel
{
    public HorizontalAlignment HorizontalContentAlignment
    {
        get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
        set => SetValue(HorizontalContentAlignmentProperty, value);
    }

    public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(
        nameof(HorizontalContentAlignment),
        typeof(HorizontalAlignment),
        typeof(AlignableWrapPanel),
        new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));

    protected override Size MeasureOverride(Size constraint)
    {
        var curLineSize = new Size();
        var panelSize = new Size();

        var children = InternalChildren;

        for (var i = 0; i < children.Count; i++)
        {
            var child = children[i];

            // flow passes its own constraint to children
            child.Measure(constraint);
            var sz = child.DesiredSize;

            if (curLineSize.Width + sz.Width > constraint.Width) // need to switch to another line
            {
                panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
                panelSize.Height += curLineSize.Height;
                curLineSize = sz;

                if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line                    
                {
                    panelSize.Width = Math.Max(sz.Width, panelSize.Width);
                    panelSize.Height += sz.Height;
                    curLineSize = new Size();
                }
            }
            else // continue to add to the line
            {
                curLineSize.Width += sz.Width;
                curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
            }
        }

        // the last line size, if any need to be added
        panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
        panelSize.Height += curLineSize.Height;

        return panelSize;
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        var firstInLine = 0;
        var curLineSize = new Size();
        var accumulatedHeight = 0D;
        var children = InternalChildren;

        for (var i = 0; i < children.Count; i++)
        {
            var desiredSize = children[i].DesiredSize;

            if (curLineSize.Width + desiredSize.Width > arrangeBounds.Width) // need to switch to another line
            {
                ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);

                accumulatedHeight += curLineSize.Height;
                curLineSize = desiredSize;

                if(
                    desiredSize.Width > arrangeBounds.Width || 
                    children[i] is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
                {
                    // the element is wider then the constraint or it stretches - give it a separate line
                    ArrangeLine(accumulatedHeight, desiredSize, arrangeBounds.Width, i, ++i);
                    accumulatedHeight += desiredSize.Height;
                    curLineSize = new Size();
                }
                firstInLine = i;
            }
            else // continue to add to the line
            {
                curLineSize.Width += desiredSize.Width;
                curLineSize.Height = Math.Max(desiredSize.Height, curLineSize.Height);
            }
        }

        if (firstInLine < children.Count)
        {
            ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
        }

        return arrangeBounds;
    }

    private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
    {
        var children = InternalChildren;

        var x = 0D;
        if (HorizontalContentAlignment == HorizontalAlignment.Center)
        {
            x = (boundsWidth - lineSize.Width) / 2;
        }
        else if (HorizontalContentAlignment == HorizontalAlignment.Right)
        {
            x = boundsWidth - lineSize.Width;
        }

        var stretchChildren = new List<UIElement>();
        for (var i = start; i < end; i++)
        {
            var child = children[i];
            if (child is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
            {
                stretchChildren.Add(child);
            }
        }

        var spaceAvailableForStretchChildren = boundsWidth - lineSize.Width;
        var spaceAvailablePerStretchChild = 0D;
        if (stretchChildren.Any())
        {
            x = 0; // all available space will be filled so start at 0
            spaceAvailablePerStretchChild = spaceAvailableForStretchChildren / stretchChildren.Count;
            spaceAvailablePerStretchChild = spaceAvailablePerStretchChild >= 0 ? spaceAvailablePerStretchChild : 0;
        }

        for (var i = start; i < end; i++)
        {
            var child = children[i];
            double itemWidth;
            if(stretchChildren.Contains(child))
            {
                itemWidth = child.DesiredSize.Width + spaceAvailablePerStretchChild;
            }
            else
            {
                itemWidth = child.DesiredSize.Width;
            }

            child.Arrange(new Rect(x, y, itemWidth, lineSize.Height));
            x += itemWidth;
        }
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top