List Items Vertically on a WrapPanel and take advantage of multiple columns
Asked Answered
W

5

18

I need to list items (all of same size) vertically (with a ScrollViewer). I want the items to spread through x columns if the container is large enough to display x columns

I first tried that :

<ScrollViewer>
    <toolkit:WrapPanel Orientation="Horizontal" ItemHeight="30" ItemWidth="100">
        <Button Content="1" />
        <Button Content="2" />
        <Button Content="3" />
        <Button Content="4" />
        <Button Content="5" />
    </toolkit:WrapPanel>
</ScrollViewer>

Result - The WrapPanel works like I want but my items are ordered from "Left to Right" (not vertically

Then I tried to seet the Orientation of the WrapPanel to "Vertical" :

Result - My items are ordered vertically but not spreaded on multiple columns.

Here is how I'd like items to be rendered :

I'd really like to avoid having to write code monitoring the control size to create/remove columns depending on its size.

Waltner answered 5/7, 2012 at 13:4 Comment(0)
I
10

If you set Orientation to Vertical you should also set render height. For example to WrapPanel, Height="150".

Ikkela answered 5/7, 2012 at 13:18 Comment(2)
I want the height of the WrapPanel to be dynamic to take the vertical space it needs to display items in 2 columns (or 3, or 4, etc.. depending on the Width of the control)Waltner
@danbord, it doesn't work like that. The WrapPanelwill place all items that fit in the first column vertically and all items left spread to next column. In your case the WrapPanel will place all items in one column because there is limitation to height.Ikkela
W
3

Finally got something that works but it needs code. I agree with all of you when you say that we need to resize height of the WrapPanel to make that works. Here is my solution :

<ScrollViewer x:Name="scroll1" SizeChanged="ScrollViewer_SizeChanged" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <toolkit:WrapPanel x:Name="wp1" Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Left" ItemHeight="30" ItemWidth="250" >
        <Button Content="1" />
        <Button Content="2" />
        <Button Content="3" />
        <Button Content="4" />
        <Button Content="5" />
        <Button Content="6" />
        <Button Content="7" />
        <Button Content="8" />
    </toolkit:WrapPanel>
</ScrollViewer>

Here is the CodeBehind :

private void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
{
    // Stupid magical number because ViewPortHeight is sometimes not accurate
    Double MAGICALNUMBER = 2;

    // Ensure ViewPortSize is not 0
    if (scroll1.ViewportWidth <= MAGICALNUMBER || scroll1.ViewportHeight <= MAGICALNUMBER)
        return;

    Size contentSize = new Size(scroll1.ViewportWidth - MAGICALNUMBER, scroll1.ViewportHeight - MAGICALNUMBER);
    Size itemSize = new Size(wp1.ItemWidth, wp1.ItemHeight);

    Size newSize = CalculateSizeBasedOnContent(contentSize, wp1.Children.Count, itemSize);

    wp1.Width = newSize.Width;
    wp1.Height = newSize.Height;
}


private Size CalculateSizeBasedOnContent(Size containerSize, int itemsCount, Size itemSize)
{

    int iPossibleColumns = (int)Math.Floor(containerSize.Width / itemSize.Width);
    int iPossibleRows = (int)Math.Floor(containerSize.Height / itemSize.Height);

    // If all items can fit in first column without scrolling (or if container is narrow than the itemWidth)
    if (itemsCount <= iPossibleRows || containerSize.Width < itemSize.Width)
    return new Size(itemSize.Width, (itemsCount * itemSize.Height));

    // If all items can fit in columns without scrollbar
    if (iPossibleColumns * iPossibleRows > itemsCount)
    {
    int columnsNeededForDisplay = (int)Math.Ceiling((itemsCount/(Double) iPossibleRows));
    return new Size(columnsNeededForDisplay * itemSize.Width, containerSize.Height);
    }

    // Here scrolling is needed even after spreading in columns
    int itemsPerColumn = (int)Math.Ceiling(wp1.Children.Count / (Double)iPossibleColumns);
    return new Size(containerSize.Width, itemsPerColumn * itemSize.Height);

}
Waltner answered 6/7, 2012 at 12:49 Comment(3)
I was just messing with ScrollViewer.ViewportHeight recently... perhaps the bottom part of this answer might help you out?Ferrigno
+1: This worked great for me with some very minor tweaks. Thanks for the solution! :)Brow
Just an FYI: If you are using an ItemsControl instead of a WrapPanel and you dynamically change the ItemsSource, then it will not follow until you resize the window. I got it working by using the LayoutUpdated event rather than the SizeChanged event, which will be called more often, but as you can read on MSDN is preferred when messing with the layout.Brow
F
2

That sort of behavior is not possible with a WrapPanel without defining its Height

One alternative you could use is a Grid, where in OnLoaded and OnSizeChanged, it calculates how many columns will fit, then sets the Row/Column definitions of the Grid and the Grid.Row/Grid.Column of each object in the code-behind. It's not pretty, but it should be fairly simple to put together and will work.

The other option would be creating your own Custom Panel that arranges the items how you want. You might even be able to find something available online that already does that

Ferrigno answered 5/7, 2012 at 18:3 Comment(0)
A
1

The only way to do this with a WrapPanel is to explicitly set the Height.

It looks like you want the items to be spread out evenly over the columns with the left column having at most one more item than the column to the right. If that is what you are looking for then you'll need to create your own custom panel. Take a look at this to see how to get started. You'll need an ItemWidth and ItemHeight dependency properties and calculate how many columns you can have by using the ItemWidth and available width.

Antarctica answered 5/7, 2012 at 17:54 Comment(0)
G
0

Disable the vertical scrollbar. This tells WPF to use the horizontal space.
<WrapPanel Orientation="Vertical" ScrollViewer.VerticalScrollBarVisibility="Disabled" />

Frode, try this (VS2017 .NET 4.7.2):

<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WpfApp1.MainWindow" Title="Wrap Panel Test" Height="100" Width="250">
    <ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Disabled">
        <WrapPanel Orientation="Vertical">
            <Button Width="100" Content="1" />
            <Button Width="100" Content="2" />
            <Button Width="100" Content="3" />
            <Button Width="100" Content="4" />
            <Button Width="100" Content="5" />
        </WrapPanel>
    </ScrollViewer>
</Window>

Wrap Panel Demo

Galicia answered 3/3, 2020 at 3:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.