Reputation: 13
I've got a view model working perfectly which basically has the following properties:
-ItemCollection
- Name
- Type
- DayCollection
- DateOfDay
- Value
I'm now trying to get the view working to present data as follows:
Name | Type | DateOfDay1 | DateOfDay2 | DateOfDay3 ...
Dave | Actual| 1.2 | 1.8 | 5.4
Mary | Actual| 3.4 | 2.3 | 6.4
...
The issue is that when i have bound the ItemCollection to the Datagrid in the view, the view presents:
Name | Type | DayCollection
Dave | Actual| (Collection)
Mary | Actual| (Collection)
Crucially - unlike some of the samples below, my use case is more simple - all items in the ItemCollection will always have the exact same number of days in the DayCollection - i.e. all rows of my table will all have the same number of columns. Although note that different ItemCollections could have a different number of days (but within that ItemCollection all Items would have the same number of days) - so i can't just flatten out the above view model and have a ItemCollection such as:
-ItemCollection
- Name
- Type
- Day1
- Day2
- Day3
as one ItemCollection (table) could have 5 days another 30 days.
I have found the following articles which talk about dynamically adding columns - i have no need to add or remove columns during my edit - the number of columns is fixed:
https://www.codeproject.com/Articles/899637/Dynamic-Columns-in-a-WPF-DataGrid-Control-Part
http://www.unixresources.net/faq/320089.shtml
https://www.codeproject.com/Tips/676530/How-to-Add-Columns-to-a-DataGri
I found the above samples overly complex for something i thought was a remarkably simple use case - a variable number of items (rows), but a fixed (pre-determined) number of columns in each row [the last of the above articles is very well written though]. I kind of hoped that i could simply define the columns using a repeating xaml something like:
<DataGrid ...>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}" />
<Repeater ItemsSource="{Binding DayCollection}">
<DataGridTextColumn Header="{Binding DateOfDay}" Binding="{Binding Value}" />
</Repeater>
</DataGrid.Columns>
</DataGrid>
Something like the above would be a simple solution to a simple usecase, but obviously is not supported
I'm sure there is a simple solution to this simple problem, but i cannot find any way to achieve the above - the articles referenced above seem excessive and are designed for a much more complicated use case (variable number of columns and columns that can be added and removed dynamically) - i don't need this flexibility and i don't want to implement it just to end up with a fixed number of columns.
The only other thought i had was to have a property such as ToDataTable() in my view model, which would output a datatable from my viewmodel that contains the full rows and columns, which i can bind the datagrid to, but again this seems a waste - and then would become much harder to check for updates and channel these through to the business logic already present in the above working viewmodel.
Is there a simple solution, or am i pot committed into a lot of code for a simple use case?
Upvotes: 0
Views: 1499
Reputation: 13
After a good few days on the hunt for a nicer solution, i finally went with the third of my links above. I still feel this is a very complex solution for something that should be quite simple, however it works and is very reusable.
https://www.codeproject.com/Tips/676530/How-to-Add-Columns-to-a-DataGri
Upvotes: 0
Reputation: 16119
The whole point of a view model is to prepare data in a format that the view can readily consume. If your view model isn't doing that then it's not "working perfectly", in fact it's not even doing the one job it's supposed to be doing! What you need to do is create an intermediate structure with your data unpacked in the correct format and then use data-binding to create your columns dynamically.
As you've already discovered, generic solutions to this problem do exist using things like behaviors etc. Behaviors are often more clean and generic than code-behind, but they're still part of the view layer, so these solutions are really just band-aids trying to shoe-horn bad view model data into the view. What you should be doing is addressing the more fundamental, underlying problem and fix the problem in your view model layer.
There are many different ways to do this, but an easy solution is to just pack all your data into a DataTable and bind your DataGrid to that instead. This code should give a very rough idea of one way to do it:
<DataGrid ItemsSource="{Binding MyTable}" />
...
public class MainViewModel : ViewModelBase
{
public DataTable MyTable { get; } = new DataTable();
private int NumColumns = 3;
public MainViewModel()
{
this.MyTable = new DataTable();
this.MyTable.Columns.Add("Name");
this.MyTable.Columns.Add("Type");
for (int i = 1; i <= NumColumns; i++)
this.MyTable.Columns.Add($"Day {i}");
var row = this.MyTable.NewRow();
row.ItemArray = new object[] { "Tom", "Type A", "1.2", "2.3", "3.4" };
this.MyTable.Rows.Add(row);
row = this.MyTable.NewRow();
row.ItemArray = new object[] { "Dick", "Type B", "2.3", "3.4", "4.5" };
this.MyTable.Rows.Add(row);
row = this.MyTable.NewRow();
row.ItemArray = new object[] { "Harry", "Type C", "3.4", "4.5", "5.6" };
this.MyTable.Rows.Add(row);
}
}
Upvotes: 3