Search code examples
c#wpfxamlwrappanel

Using Nested WrapPanel


I have three buttons in 2 nested WrapPanels oriented horizontally, using these codes:

<Grid>
  <WrapPanel Orientation="Horizontal">
    <Button Content="b1" Width="200" />
    <WrapPanel Orientation="Horizontal">
      <Button Content="b2" Width="200" />
      <Button Content="b3" Width="200" />
    </WrapPanel>
  </WrapPanel>
</Grid>

When the space is not enough for the b3 button (e.g. by resizing the window), the b2 and b3 buttons move under the b1 button, like this:

enter image description here

What I want is b2 button remains in its position and b3 button moves under b2 button, like this:

enter image description here

and finally if we make the window smaller, it will look like this:

enter image description here

How could I accomplish this?


Solution

  • Try this:

    <Window x:Class="WpfApp4.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:custom="clr-namespace:Custom"
            xmlns:local="clr-namespace:WpfApp4"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <custom:AlignableWrapPanel HorizontalContentAlignment="Right">
                <Button Content="b1" Width="200" HorizontalAlignment="Stretch" />
                <Button Content="b2" Width="200" HorizontalAlignment="Stretch" />
                <Button Content="b3" Width="200" HorizontalAlignment="Stretch" />
            </custom:AlignableWrapPanel>
        </Grid>
    </Window>
    

    add this file AlignableWrapPanel.cs

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace Custom
    {
        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;
                }
            }
        }
    
    }
    

    Source: https://stackoverflow.com/a/7747002/194717