I have my data stored in a Dictionary<string, List<string>>.
I want to bind it to a datagrid in WinUI 3. The number of elements in the List<string> is variable for each key and unknown at compile time.
I apologise for being lengthy, but I want to provide as much context as possible.
Since the number of columns in my data is unknown, I excluded the recommended approach of creating a custom class.
I also had to exclude the use of ExpandoObject class.
And, if I'm not mistaken, a DataTable cannot be directly bound to a datagrid in WinUI 3.
I know I need an ObservableCollection as my ItemSource so I'm using a class ObservableDictionary. Not recommended by many based on what I could read.
The keys in my dictionary represent the first column of my datagrid (the header being "Tag"). Each element in the List<string>, properly converted (see method ConvertDictionaryToTagData) will become headers and elements of my datagrid.
Below is an example of the original data (actual data comes from an external file):
public ObservableDictionary<string, List<string>> Source = new();
...
Source.Add("AAA", new List<string> { "Prop1", "Prop2" });
Source.Add("BBB", new List<string> { "Prop1", "Prop4" });
Source.Add("CCC", new List<string> { "Prop3", "Prop1", "Prop5", "Prop2" });
Source.Add("DDD", new List<string> { "Prop6" });
Source.Add("EEE", new List<string> { "Prop1", "Prop7" });
The result I'm expecting in my datagrid is:
| Tag | Prop1 | Prop2 | Prop3 | Prop4 | Prop5 | Prop6 | Prop7 |
|---|---|---|---|---|---|---|---|
| AAA | true | true | false | false | false | false | false |
| BBB | true | false | false | true | false | false | false |
| CCC | true | true | true | false | true | false | false |
| DDD | false | false | false | false | false | true | false |
| EEE | true | false | false | false | false | false | true |
These are my attempts at a solution.
I created a class TagData and a ConvertDictionaryToTagData method for converting the data:
public class TagData
{
public string Tag { get; set; }
public bool[] Values { get; set; }
}
private ObservableCollection<TagData> ConvertDictionaryToTagData(ObservableDictionary<string, List<string>> data)
{
var allHeaders = data.SelectMany(x => x.Value).Distinct().Order().ToList();
var tagDataList = new ObservableCollection<TagData>();
foreach (var item in data)
{
var tag = new TagData
{
Tag = item.Key,
Values = new bool[allHeaders.Count] headers
};
for (int i = 0; i < allHeaders.Count; i++)
{
tag.Values[i] = item.Value.Contains(allHeaders[i]);
}
tagDataList.Add(tag);
}
return tagDataList;
}
I'm preparing my data:
ObservableCollection<TagData> Data = ConvertDictionaryToTagData(Source);
My datagrid is as follows:
<controls:DataGrid x:Name="dataGrid"
AutoGenerateColumns="True"
GridLinesVisibility="Horizontal"
ItemsSource="{x:Bind ViewModel.Data, Mode=OneWay}">
But all I get is the headers Tag and Values with no data.
I modified the datagrid to:
<controls:DataGrid x:Name="dataGrid"
AutoGenerateColumns="False"
GridLinesVisibility="Horizontal"
ItemsSource="{x:Bind ViewModel.Data, Mode=OneWay}">
<controls:DataGridTextColumn Binding="{Binding [Tag]}"
Header="Tag" />
with no luck. I also used the AutoGeneratingColumn event:
<controls:DataGrid x:Name="dataGrid"
AutoGenerateColumns="True"
AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"
GridLinesVisibility="Horizontal"
ItemsSource="{x:Bind ViewModel.Data, Mode=OneWay}">
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
e.Cancel = true;
if (dataGrid != null)
{
DataGridTextColumn tag = new()
{
Header = "Tag",
Binding = new Binding()
{
Source = ViewModel.Data,
Path = new PropertyPath("Tag"),
Mode = BindingMode.OneWay
}
};
dataGrid.Columns.Add(tag);
var allHeaders = ViewModel.Source.SelectMany(x => x.Value).Distinct().Order().ToList();
foreach (string str in allHeaders)
{
DataGridCheckBoxColumn column = new()
{
Header = str,
Binding = new Binding()
{
Path = new PropertyPath(string.Format("Values")),
Mode = BindingMode.OneWay
}
};
dataGrid.Columns.Add(column);
}
}
}
This time I get the right headers twice (created once for Tag and one more time for Values). But, again, no data.
And I had to use the e.Cancel = true; at the beginning of the event to avoid the creation of additional headers "Tag"and "Values".
I'm stuck because I don't know how to create the content of my columns or, better, how to bind those columns to the source data. I'm indeed unfamiliar with the binding part:
DataGridTextColumn tag = new()
{
Header = "Tag",
Binding = new Binding()
{
Source = ViewModel.Data,
Path = new PropertyPath("Tag"),
Mode = BindingMode.OneWay
}
};
and its correct syntax.
I'd appreciate any suggestions I can get on how to solve this problem I'm facing. I'm flexible to changing the type I use for storing the data if that can help.