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