如何 'wrap' WPF DataGrid 的列(而不是 text/content)?

How to 'wrap' the columns (not the text/content) of a WPF DataGrid?

我正在尝试实现一个可以 'wrap' WPF DataGrid 的解决方案 - 我的意思是整个列和行都换行,不是 他们的文字或内容(见下图)。

我有一组由列和行组成的数据(列 headers),我想在限制 window 的宽度限制时换行,而不是使用水平滚动表示数据显示的栏 off-screen.

我看过使用 WrapPanel 作为我的 DataGrid 的 ItemsPanelTemplate,但是我无法在此基础上进一步实现我想要的。

    <DataGrid.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </DataGrid.ItemsPanel>

如果有一个答案可以使用另一个控件(即 ListView 或 GridView 不折不扣地实现我想要的),我会很高兴。

我目前的解决方案是手动修改 ItemsSource 并将其分解,然后创建多个 pre-determined 大小的 DataGrid,这不是很灵活。

测试 DataGrid.ItemsPanel 和 WrapPanel

DataGrid 只显示数据行。因此,如果我们将 WrapPanel.Orientation 设置为“水平”:

<DataGrid x:Name="dg" ItemsSource="{Binding _humans}">
    <DataGrid.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </DataGrid.ItemsPanel>
</DataGrid>

每行将水平并排放置:

ListView 和 GridView

如果我们想单独显示 属性 列,我们应该为每个 属性 使用一个 ListView。 GridView 将用于显示 header。在 XAML 文档中,我们为 WrapPanel 设置了名称。

<Grid>
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <WrapPanel Name="wraper" Orientation="Horizontal">
        </WrapPanel>
    </ScrollViewer>
</Grid>

后面的代码实现为:

public partial class MainWindow : Window
    {
        private IList<Human> _humans;
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            CreateHumans();
            wraper.Children.Add(CreateListViewFor("FirstName"));
            wraper.Children.Add(CreateListViewFor("LastName"));
            wraper.Children.Add(CreateListViewFor("Age"));
            wraper.Children.Add(CreateListViewFor("Bday", "Birthday"));
            wraper.Children.Add(CreateListViewFor("Salary"));
            wraper.Children.Add(CreateListViewFor("Id", "Identification"));
        }

        private void CreateHumans()
        {
            _humans = new List<Human>();
            for (int i = 10; i < 20; i++)
            {
                var human = new Human();
                human.FirstName = "Zacharias";
                human.LastName = "Barnham";
                human.Bday = DateTime.Parse("1.3.1990");
                human.Id = "ID-1234-zxc";
                human.Salary = 2_000_000;
                _humans.Add(human);
            }
        }

        private ListView CreateListViewFor(string propertyName, string header)
        {
            var lv = new ListView();
            var gv = new GridView();
            lv.ItemsSource = _humans;
            lv.View = gv;
            lv.SelectionChanged += UpdateSelectionForAllListViews;
            gv.Columns.Add(new GridViewColumn() { Header = header, DisplayMemberBinding = new Binding(propertyName), Width = 100 });
            return lv;
        }

        private ListView CreateListViewFor(string propertyName)
        {
            return CreateListViewFor(propertyName, propertyName);
        }

        private void UpdateSelectionForAllListViews(object sender, SelectionChangedEventArgs e)
        {
            int index = (sender as ListView).SelectedIndex;
            foreach (var child in wraper.Children)
            {
                (child as ListView).SelectedIndex = index;
            }
        }
    }

我们调用 CreateListViewFor() 方法为 WrapPanel 提供 ListView objects。我们为 Selection Changed 事件创建回调以更新每个列表的选择。样式由您决定。

下一张 gif 显示了最终结果: