WPF ListView可以横向排列项目吗?

57

我希望以类似于WinForms ListView列表模式的方式在ListView中布局项目。也就是说,项目不仅垂直排列,还水平排列。

我不介意这样布局:

1 4 7
2 5 8
3 6 9

或者这样布局:

1 2 3
4 5 6
7 8 9

只要它们既垂直又水平地呈现,以最大化利用可用空间。

我找到的最接近的问题是:

如何使WPF ListView项目像水平滚动条一样水平重复?

但是它只能水平排列项目。

5个回答

116

看起来你需要的是WrapPanel,它会将项目水平放置直到没有更多空间,然后移到下一行,就像这样:

(MSDN)
alt text http://i.msdn.microsoft.com/Cc295081.b1c415fb-9a32-4a18-aa0b-308fca994ac9(en-us,Expression.10).png

你也可以使用UniformGrid,它会按照固定的行数或列数排列项目。

我们可以通过改变ItemsPanel属性来使用这些面板在ListView、ListBox或任何形式的ItemsControl中排列项目。设置ItemsPanel后,可以将它从ItemsControls默认使用的StackPanel更改为其他面板。对于WrapPanel,我们还应该根据此处所示的方式设置宽度。

<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>

2
谢谢,我之前尝试过使用WrapPanel,正如Dennis建议的那样,但需要宽度绑定,而我正在使用的示例中没有。 - Grokys
显然,唯一重要的是宽度。其他的都是可选的。 - Tigraine
当窗口最大化时,我在使用WrapPanel时遇到了一些问题,所以我将Width更改为MaxWidth,现在它可以平稳运行了。 - kiewic
在这个示例中,有没有办法自动设置项目宽度,使其对应于最大的ListView项目宽度? - Arsen Zahray

26

最近我研究了如何在WPF中实现这个功能,并找到了一个好的解决方案。我想要的是复制Windows资源管理器中的列表模式,即从上到下,然后从左到右。

基本上,你需要覆盖 ListBox.ItemsPanel 属性,使用一个将其方向设置为垂直的 WrapPanel。

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

但是,当加载大量数据时,使用WrapPanel会很慢,因为它没有虚拟化。这非常重要。所以现在这个任务变得有点复杂了,因为你需要通过扩展VirtualizedPanel并实现IScrollInfo来编写自己的VirtualizedWrapPanel。

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

在我进行研究之前,这就是我所得到的。如果您需要更多信息或示例,请评论。

更新。Ben Constable有一篇关于如何实现IScrollInfo的系列文章,共4篇。非常值得一读。

我已经实现了一个虚拟化的包装面板,即使有上述系列文章的帮助,这也不是一项容易的任务。


1
源代码不应该是ListBox.ItemsPanel包装在ItemsPanelTemplate中吗?你的版本无法编译。 - Sam
1
@Sam:你说得对。我一定是直接把那段代码写到了StackOverflow上(也就是没有检查它)。谢谢你指出来。 - Dennis
啊,我真的搞不清楚如何编写这个“VirtualizedWrapPanel”。Ben的教程有点不清楚在哪里放置代码。对我来说,如何实现这个“VirtualizedWrapPanel”有点困惑。而且,“VirtualizedPanel”从哪里来呢? - CularBytes

10
在我的情况下,最佳选择是使用:
        <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>

这给了我一个相当不错的类似于Windows资源管理器的列表选项


4
从左到右,然后从上到下使用。
      <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" 
                     MaxWidth="{Binding ActualWidth, Mode=OneWay, 
                       RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type er:MainWindow}}}"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>

2
除了 @Dennis 的答案之外,关于 WrapPanel 失去虚拟化的问题,我找到了一个很好的类来正确实现它。虽然 Ben Constable 建议的文章(Part 1, Part 2, Part 3, Part 4)是一个不错的介绍,但我无法完全完成 Wrap Panel 的任务。
这里有一个实现: https://virtualwrappanel.codeplex.com/ 我已经使用3300个视频和照片进行了测试,加载列表本身当然需要一点时间,但最终它可以正确地虚拟化列表,没有任何滚动延迟。
  • 这段代码存在一些问题,请参阅上面页面的“问题”选项卡。

将源代码添加到您的项目后,示例源代码:

   <!--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风格的后端,因此这是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++;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接