Search code examples
c#wpfuser-controlswrappanel

WPF WrapPanel Dynamic Height


I have a wrap panel that will contain a variable amount of controls.

I want the orientation to be vertical (as objects within will have a fixed width but variable height).

But the issue I'm having is that when a scrollbar is present the height is inifinite so the items never wrap onto the second column. The scrollbar is necessary as there will frequently be more objects than it's possible to fit on one screen. I can stop this by setting a fixed height, but this isn't an acceptable solution as a reasonable fixed height will differ for each selection.

Essentially I'd like a WrapPanel whose height changes dynamically based on the width of the panel and the amount of items contained within.

To illustrate:

If the panel is wide enough to show 3 columns it will:

| 1 5 9 |

| 2 6 - |

| 3 7 - | Height=4

| 4 8 - |

But if the user changes the size of the window to the point where it can only accomodate 2 columns the height will increase:

| 1 6 |

| 2 7 |

| 3 8 | Height = 5

| 4 9 |

| 5 - |

Also, I'm not sure how feasible this is but I would ideally like the order the items horizonatally but keep the orientation vertical, so they'd be ordered:

| 1 2 3 |

| 4 5 6 |

| 7 8 9 |

Could anyone tell me how to go about getting started with this? I'm assuming it's possible with a custom implementation of the WrapPanel, but I'm slightly confused how to get started.

Thanks,


Solution

  • Manged to achieve what I needed with the following code:

    public class InvertedWrapPanel : WrapPanel
    {
        private int itemsPerRow = 0;
    
        protected override Size MeasureOverride(Size availableSize)
        {
            if (Orientation == Orientation.Horizontal)
            {
                return base.MeasureOverride(availableSize);
            }
            else //Orientation is vertical
            {
                double w = availableSize.Width;
    
                double maxChildWidth = 0;
    
                foreach (UIElement child in Children)
                {
                    //Get the current childs desired size parameters
                    child.Measure(availableSize);
    
                    //Store off the maximum child width
                    if (child.DesiredSize.Width > maxChildWidth)
                        maxChildWidth = child.DesiredSize.Width;
                }
    
                //See how many items we can fit in a row
                itemsPerRow = Convert.ToInt32(Math.Floor(w / maxChildWidth));
    
                return base.MeasureOverride(availableSize);
            }
        }
    
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Orientation == Orientation.Horizontal)
            {
                return base.ArrangeOverride(finalSize);
            }
            else //Orientation is vertical
            {
                double currentX = 0;
                double currentY = 0;
    
                int col = 0;
    
                double lastX = 0;
                double lastWidth = 0;
    
                //Arrays to store differing column heights
                double[] lastY = new double[itemsPerRow];
                double[] lastHeight = new double[itemsPerRow];
    
                double[] colHeights = new double[itemsPerRow];
    
                foreach (UIElement child in Children)
                {
                    //If we've reached the end of a row
                    if (col >= itemsPerRow)
                    {
                        col = 0;
                        currentX = 0; //reset the x-coordinate for first column
                    }
                    else
                        currentX = lastX + lastWidth; //Increase the x-coordinate
    
                    //Increase the y-coordinates for the current column
                    currentY = lastY[col] + lastHeight[col];
    
                    //Draw the element
                    child.Arrange(new Rect(currentX, currentY, child.DesiredSize.Width, child.DesiredSize.Height));
    
                    //Store off the current child's parameters
                    lastX = currentX;
                    lastWidth = child.DesiredSize.Width;
    
                    lastY[col] = currentY;
                    lastHeight[col] = child.DesiredSize.Height;
    
                    colHeights[col] += child.DesiredSize.Height;
    
                    col++;
                }
    
                //Set the height of the panel to the max column height.
                //Otherwise scroll bar will set height to infinity.
                double maxHeight = 0;
    
                foreach (double d in colHeights)
                {
                    if (d > maxHeight)
                        maxHeight = d;
                }
    
                base.Height = maxHeight;
    
                return finalSize;
            }
        }