I'm trying to write an application that is collecting settings from a remote endpoint and compares them in a DataGrid. These units can have different number of settings depending on which software version they are running and settings can also have been removed in a newer SW-version, not just added. There are ~500 settings in each unit.
Due to the difference in settings information I can't write a proper class and describe each setting so I'm building a dictionary where the setting names are the keys and the setting parameter value is the value. When the dictionary is done for each unit I add it to an ObservableCollection. This collection is a shared resource between the "settingsCollection" view and the "compareSettings" view and is dependency injected in both.
WPF Datagrid are not really working with dictionaries so I'm building a DataTable that the DataGrid can be bound to. If it would be a non MVVM app I would build my DataGrid from the .xaml.cs file and add all attributes to it here.
I get the visualization to work alright but I would like the cell backgrounds change colours depending on the selected row. If I have two rows like looking like this:
| Units | Setting 1 | Setting 2 | Setting 3 |
|---|---|---|---|
| Unit 1 | 1 | 2 | 3 |
| Unit 2 | 2 | 2 | 2 |
and the first row selected, I would like the 2 in the columns for "Setting 1" and "Setting 3" to be red while 2 in column "Setting 2" should be green. If I'm changing the selected row, the backgrounds in the cells should change colour depending if they are the same or not.
I built a fake "settingsCollector" in my viewmodel constructor just make it more easy to work with it.
public class MainWindowViewModel : ObservableObject
{
private DataTable _settingsdataTable = new DataTable();
public DataTable SettingsDataTable
{
get => _settingsdataTable;
set {
if (SettingsDataTable != value) {
_settingsdataTable = value;
OnPropertyChanged(nameof(SettingsDataTable));
}
}
}
private Dictionary<string, string> _selectedUnit = new();
public Dictionary<string, string> SelectedUnit
{
get => _selectedUnit;
set {
if (SelectedUnit != value) {
_selectedUnit = value;
OnPropertyChanged(nameof(SelectedUnit));
}
}
}
public ICommand CellSelectionChangedCommand { get; }
private void CellSelected(object o)
{
DataGrid dataGrid = o as DataGrid;
Dictionary<string, string> newSelectedUnit = new();
for (int i = 0; i < dataGrid.SelectedCells.Count; i++) {
DataRowView rowView = dataGrid.SelectedCells[i].Item as DataRowView;
newSelectedUnit.Add(dataGrid.SelectedCells[i].Column.Header.ToString(), rowView.Row[i].ToString());
};
SelectedUnit = newSelectedUnit;
}
public MainWindowViewModel()
{
ObservableCollection<Dictionary<string, string>> DictionaryCollection = new();
// Make up some settings from multiple diffrent endpoints
for (int i = 0; i < 2; i++) {
Dictionary<string, string> incomingSettings = new();
incomingSettings.Add($"Setting {i}", $"{i}");
incomingSettings.Add($"Setting {i + 1}", $"{i}");
incomingSettings.Add($"Setting {i + 2}", $"{i}");
incomingSettings.Add($"Setting {i + 3}", $"{i}");
incomingSettings.Add($"Setting {i + 4}", $"{i}");
incomingSettings.Add($"Setting {i + 6}", $"{i + 3}");
//... Could continue forever
DictionaryCollection.Add(incomingSettings);
};
SelectedUnit = DictionaryCollection.First();
// Go through the settings parameters in the incoming units
// and add them to a list of all possible existing settings parameters
List<string> AllDictionaryKeys = new();
foreach (var item in DictionaryCollection)
{
foreach (var key in item.Keys)
{
if (!AllDictionaryKeys.Contains(key))
AllDictionaryKeys.Add(key);
}
}
// Order keys to get all settings alphabetically.
AllDictionaryKeys = AllDictionaryKeys.OrderBy(x => x.Length).ThenBy(x => x).ToList();
// Create the full dataTable
// Start with adding the headers in the table
DataTable newDataTable = new();
foreach (var header in AllDictionaryKeys)
{
newDataTable.Columns.Add(header, typeof(string));
}
// Add values in the table depending on the header keys
// If the setting does not exists in the unit, add a "-" instead
foreach (var item in DictionaryCollection)
{
DataRow row = newDataTable.NewRow();
foreach (var key in AllDictionaryKeys)
{
if (!item.ContainsKey(key))
row[key] = "-";
else
row[key] = item[key];
}
newDataTable.Rows.Add(row);
}
// Update the dataTable bound to the view
SettingsDataTable = newDataTable;
// RelayCommands from MVVM Toolkit
CellSelectionChangedCommand = new RelayCommand<object>(o =>
{
Debug.WriteLine("CellSelectionChanged");
CellSelected(o);
});
}
}
<UserControl x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:WpfApp1"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:WpfApp1"
d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel}"
mc:Ignorable="d"
Background="AntiqueWhite">
<UserControl.Resources>
<local:CellBackgroundConverter x:Key="CellBackgroundConverter"/>
<CollectionViewSource x:Key="MyDataViewSource" Source="{Binding SettingsDataTable.DefaultView}" />
</UserControl.Resources>
<Grid
Margin="10"
>
<DataGrid
x:Name="MyDataGrid"
CanUserAddRows="True"
AutoGenerateColumns="True"
ItemsSource="{Binding Source={StaticResource MyDataViewSource}}"
Margin="5,90,5,5"
SelectionUnit="FullRow"
SelectionMode="Single"
IsReadOnly="True">
<!--Add interaction triggers-->
<i:Interaction.Triggers>
<!--Add event trigger-->
<i:EventTrigger
EventName="SelectedCellsChanged">
<i:InvokeCommandAction
Command="{Binding CellSelectionChangedCommand}"
CommandParameter="{Binding ElementName=MyDataGrid}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<!-- Set the default background color -->
<Setter Property="Background" Value="Blue" />
<Style.Triggers>
<!-- Set the background color for unselected cells in the selected column -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}, Path=IsSelected}" Value="False">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
</UserControl>
I tried to add a multibinding and build a converter but it was triggered too many times. I also tried the aoutoGeneratingColumn event but that doesn't really change the cell background.
Can I somehow change the cell bacground colour depending on the selected row values? If the other cells in the columns have a diffrent value than the selected one, it should be red, otherwise green.
Fixed it like this with the help of the answer of mm8.
and the converter looking like this
So now the DataGrid updates the fields in each column depending on if the value is the same or not as can be seen below. Im getting a binding error with the single selected cell when its looking for it's border brush after sorting the column when clicking the column header but I can live with that.