WPF ListView with horizontal arrangement of items?

WpfListviewLayout

Wpf Problem Overview


I want to lay out items in a ListView in a similar manner to the WinForms ListView in List mode. That is, where items are laid out not just vertically but horizontally in the ListView as well.

I don't mind if the items are laid out like this:

1 4 7
2 5 8
3 6 9

Or like this:

1 2 3
4 5 6
7 8 9

As long as they are presented both vertically and horizontally in order to maximize the use of available space.

The closest I could find was this question:

https://stackoverflow.com/questions/359217/how-do-i-make-wpf-listview-items-repeat-horizontally-like-a-horizontal-scrollbar

Which only lays out the items only horizontally.

Wpf Solutions


Solution 1 - Wpf

It sounds like what you are looking for is a WrapPannel, which will lay the items out horizontally until there is no more room, and then move to the next line, like this:

(MSDN)
alt text

You also could use a UniformGrid, which will lay the items out in a set number of rows or columns.

The way we get the items to arange using these other panels in a ListView, ListBox, or any form of ItemsControl is by changing the ItemsPanel property. By setting the ItemsPanel you can change it from the default StackPanel that is used by ItemsControls. With the WrapPanel we also should set the widths as shown here.

<ListView>
   <ListView.ItemsPanel>
      <ItemsPanelTemplate>
         <WrapPanel Width="{Binding (FrameworkElement.ActualWidth), 
            RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
            ItemWidth="{Binding (ListView.View).ItemWidth, 
            RelativeSource={RelativeSource AncestorType=ListView}}"
            MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
            ItemHeight="{Binding (ListView.View).ItemHeight, 
            RelativeSource={RelativeSource AncestorType=ListView}}" />
      </ItemsPanelTemplate>
   </ListView.ItemsPanel>
...
</ListView>

Solution 2 - Wpf

I recently research how to achieve this in WPF and found a good solution. What I wanted was to the replicate the List mode in Windows Explorer, i.e. top-to-bottom, then left-to-right.

Basically what you want to do override the ListBox.ItemsPanel property to use a WrapPanel with it's orientation set to Vertical.

<ListBox>
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>      
      <WrapPanel Orientation="Vertical"/>
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
</ListBox>

However this WILL be slow when loading a large data set as it the wrap panel is not virtualised. This is important. So this task now becomes a little more as now you need to write your own VirtualizedWrapPanel by extending VirtualizedPanel and implementing IScrollInfo.

public class VirtualizedWrapPanel : VirtualizedPanel, IScrollInfo
{
   // ...
}

This is as far as I got in my research before having to go off to another task. If you want more information or examples, please comment.

UPDATE. Ben Constable's has a great series on how to implement IScrollInfo.

There are 4 articles in total. A really good read.

I have since implemented a virtualized wrap panel, it is not an easy task even with the help of the above series of articles.

Solution 3 - Wpf

In my case, the best option was to use:

        <ListView.ItemsPanel>
			<ItemsPanelTemplate>
				<WrapPanel Orientation="Vertical"
					MaxHeight="{Binding (FrameworkElement.ActualHeight), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
                               ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
                               MinHeight="{Binding ItemHeight, RelativeSource={RelativeSource Self}}"
                               ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}"/>
			</ItemsPanelTemplate>
		</ListView.ItemsPanel>

This gave me a decent analog to Windows Explorer's List option

Solution 4 - Wpf

for left to right then top to bottom use

      <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" 
                     MaxWidth="{Binding ActualWidth, Mode=OneWay, 
                       RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type er:MainWindow}}}"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>

Solution 5 - Wpf

In addition to @Dennis's answer, about the WrapPanel losing Virtualization, I have found a nice class that correctly implements this. While the suggested post by Ben Constable (Part 1, Part 2, Part 3, Part 4) is a nice introduction, I couldn't quite complete the task for a Wrap Panel.

Here is an implementation: https://virtualwrappanel.codeplex.com/ I've tested it with total of 3.300 video's and photo's, loading the list itself is of course a bit long, but eventually it is correctly virtualizing the list, no scroll lag whatsoever.

  • There are some issues to this code, see the issues tab on the page above.

After adding the source code to your project, example source code:

   <!--in your <Window> or <UserControl> tag -->
  <UserControl
        xmlns:hw="clr-namespace:Project.Namespace.ToClassFile" >
   <!--...-->

    <ListView x:Name="lvImages" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Margin="10" Height="auto" 
             ItemsSource="{Binding ListImages}"
              ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <hw:VirtualizingWrapPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Vertical" Margin="5" MaxHeight="150">
                    <TextBlock Text="{Binding title}" FontWeight="Bold"/>
                    <Image Source="{Binding path, IsAsync=True}" Height="100"/>
                    <TextBlock Text="{Binding createDate, StringFormat=dd-MM-yyyy}"/>
                   
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

MVVM style back-end, so this is inside the ViewModel:

    public ObservableCollection<Media> ListImages
    {
        get
        {
            return listImages;
        }
        set { listImages = value; OnPropertyChanged(); }
    }
     

     //Just load the images however you do it, then assign it to above list.
//Below is the class defined that I have used.
public class Media
{
    private static int nextMediaId = 1;
    public int mediaId { get; }
    public string title { get; set; }
    public string path { get; set; }
    public DateTime createDate { get; set; }
    public bool isSelected { get; set; }

    public Media()
    {
        mediaId = nextMediaId;
        nextMediaId++;
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGrokysView Question on Stackoverflow
Solution 1 - WpfrmooreView Answer on Stackoverflow
Solution 2 - WpfDennisView Answer on Stackoverflow
Solution 3 - WpfArsen ZahrayView Answer on Stackoverflow
Solution 4 - Wpfkns98View Answer on Stackoverflow
Solution 5 - WpfCularBytesView Answer on Stackoverflow