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

Time:03-21

I'm trying to implement a solution where I can 'wrap' a WPF DataGrid - by that I mean that the entire columns and rows wrap, not their text or content (see image below).

I have a set of data comprised of columns and rows (with column headers) that I want to wrap when constricting the window's width constraints, rather than instead using a horizontal scroll bar which would mean data is presented off-screen.

enter image description here

I had a look at using a WrapPanel as the ItemsPanelTemplate of my DataGrid, however I was not able to build on this further to achieve what I wanted.

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

If there is an answer that achieves what I want using another control, i.e. a ListView or GridView without compromises, I would be happy with that.

My current solution is to manually modify my ItemsSource and break that up, and then create multiple DataGrids of a pre-determined size, which is not very flexible.

CodePudding user response:

Testing DataGrid.ItemsPanel and WrapPanel

The DataGrid only displays rows of data. So, if we set WrapPanel.Orientation to "Horizontal":

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

Each row will be positioned side by side horizontally: DataGrid and WrapPanel

ListView and GridView

If we want to display Property columns separately, we should use a ListView for each property. The GridView will be used for displaying the header. In the XAML document we set a Name for the WrapPanel.

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

The code behind is implemented as:

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

We call our CreateListViewFor() method to provide ListView objects to the WrapPanel. We create a callback for the Selection Changed event to update the selection for each list. The styling is up to you.

The next gif shows the final result: Final result

  • Related