WPF Datagrid and MVVM - Binding CellStyle to a ViewModel Property
-
Hi, I'm trying desperately to get the following working, with no success :( : So I hope someone here can help me. I have a application following the MVVM pattern. A WPF datagrid should have all it's cells backgrounds set either to Yellow or White, depending on a property on the ViewModel. If the property returns "Locked", all cells should be Yellow. If the property returns "Editable", all cells should be White. Here's my XAML:
<DataGrid x:Name="grdAnsprechpartner" Style="{Binding Path=StyleDataGrid}" AutoGenerateColumns="False" Height="auto" Width="756" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Path=AktuellerDatensatz.Personen}" > <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Background" Value="{Binding Path=CellBackground}"/> </Style> </DataGrid.CellStyle>
...
The Property "CellBackground" looks like this:
Public Overridable Property CellBackground() As SolidColorBrush
Get
Return p_CellBackground
End Get
Set(ByVal value As SolidColorBrush)
If value.Equals(p_CellBackground) Then Return
p_CellBackground = value
OnPropertyChanged("CellBackground")
End Set
End PropertyI'm setting myViewModel.CellBackground to - for example - Brushes.Yellow. Nothing happens. What am I doing wrong? If I set the CellStyle's Background-property to a StaticResource, it works. Kind regards, Nico
-
Hi, I'm trying desperately to get the following working, with no success :( : So I hope someone here can help me. I have a application following the MVVM pattern. A WPF datagrid should have all it's cells backgrounds set either to Yellow or White, depending on a property on the ViewModel. If the property returns "Locked", all cells should be Yellow. If the property returns "Editable", all cells should be White. Here's my XAML:
<DataGrid x:Name="grdAnsprechpartner" Style="{Binding Path=StyleDataGrid}" AutoGenerateColumns="False" Height="auto" Width="756" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Path=AktuellerDatensatz.Personen}" > <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Background" Value="{Binding Path=CellBackground}"/> </Style> </DataGrid.CellStyle>
...
The Property "CellBackground" looks like this:
Public Overridable Property CellBackground() As SolidColorBrush
Get
Return p_CellBackground
End Get
Set(ByVal value As SolidColorBrush)
If value.Equals(p_CellBackground) Then Return
p_CellBackground = value
OnPropertyChanged("CellBackground")
End Set
End PropertyI'm setting myViewModel.CellBackground to - for example - Brushes.Yellow. Nothing happens. What am I doing wrong? If I set the CellStyle's Background-property to a StaticResource, it works. Kind regards, Nico
Are you getting any binding errors? They won't pop up as exceptions, but if you look at the Output window in Visual Studio, any data binding issues should print a line there. That's usually your best way to determine whether your binding sources are correct.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels) -
Are you getting any binding errors? They won't pop up as exceptions, but if you look at the Output window in Visual Studio, any data binding issues should print a line there. That's usually your best way to determine whether your binding sources are correct.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)Hi, thanks a lot for your answer :) I checked the output and there IS an error:
System.Windows.Data Error: 40 : BindingExpression path error: 'CellBackground' property not found on 'object' ''Person' (HashCode=51777710)'. BindingExpression:Path=CellBackground; DataItem='Person' (HashCode=51777710); target element is 'DataGridCell' (Name=''); target property is 'Background' (type 'Brush')
Now I see, the datagrid's ItemsSource is bound to an ObservableCollection of "Personen". But I don't know how to get this solved. How do I tell the CellStyle's binding, it should look for "CellBackground" on the ViewModel? Regards, Nico
-
Hi, thanks a lot for your answer :) I checked the output and there IS an error:
System.Windows.Data Error: 40 : BindingExpression path error: 'CellBackground' property not found on 'object' ''Person' (HashCode=51777710)'. BindingExpression:Path=CellBackground; DataItem='Person' (HashCode=51777710); target element is 'DataGridCell' (Name=''); target property is 'Background' (type 'Brush')
Now I see, the datagrid's ItemsSource is bound to an ObservableCollection of "Personen". But I don't know how to get this solved. How do I tell the CellStyle's binding, it should look for "CellBackground" on the ViewModel? Regards, Nico
If you want to bind to properties on the ViewModel, then your grid needs to be bound to the ViewModel. Each row of the grid is bound to one item in the ItemsSource, so that's where it's looking for "CellBackground"
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels) -
If you want to bind to properties on the ViewModel, then your grid needs to be bound to the ViewModel. Each row of the grid is bound to one item in the ItemsSource, so that's where it's looking for "CellBackground"
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)I'm not sure I understand your answer, sorry. The datagrid's ItemsSource is bound to the ViewModel (it is bound to a ViewModel's property of type ObservableCollection). What I did now is:
<DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Foreground" Value="Black"/> <Setter Property="Background" Value="{Binding Path=DataContext.CellBackground, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}"/> </Style> </DataGrid.CellStyle>
This works, but I'm sure that's not the most elegant solution...
-
Hi, I'm trying desperately to get the following working, with no success :( : So I hope someone here can help me. I have a application following the MVVM pattern. A WPF datagrid should have all it's cells backgrounds set either to Yellow or White, depending on a property on the ViewModel. If the property returns "Locked", all cells should be Yellow. If the property returns "Editable", all cells should be White. Here's my XAML:
<DataGrid x:Name="grdAnsprechpartner" Style="{Binding Path=StyleDataGrid}" AutoGenerateColumns="False" Height="auto" Width="756" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Path=AktuellerDatensatz.Personen}" > <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Background" Value="{Binding Path=CellBackground}"/> </Style> </DataGrid.CellStyle>
...
The Property "CellBackground" looks like this:
Public Overridable Property CellBackground() As SolidColorBrush
Get
Return p_CellBackground
End Get
Set(ByVal value As SolidColorBrush)
If value.Equals(p_CellBackground) Then Return
p_CellBackground = value
OnPropertyChanged("CellBackground")
End Set
End PropertyI'm setting myViewModel.CellBackground to - for example - Brushes.Yellow. Nothing happens. What am I doing wrong? If I set the CellStyle's Background-property to a StaticResource, it works. Kind regards, Nico
What you need to do is bind the background property to the actual property that contains the Locked/Editable value, and then use a value converter to change the colour as appropriate. Here's a sample of the converter:
namespace MySample.Converters
{
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;/// <summary>
/// Converter to determine whether or not a number is negative, and apply
/// conditional formatting if it is.
/// </summary>
public class StatusTextColorConverter : IValueConverter
{
#region IValueConverter Members
/// <summary>
/// Convert from the value to the colour brush.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="targetType">The parameter is not used.</param>
/// <param name="parameter">The parameter is not used.</param>
/// <param name="culture">The parameter is not used.</param>
/// <returns>The populated colour brush.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var brush = new SolidColorBrush(Colors.White);if (value != null && value == "Locked") { brush = Colors.Yellow; } return brush; } /// <summary> /// Unused in this implementation. /// </summary> /// <param name="value">The parameter is not used.</param> /// <param name="targetType">The parameter is not used.</param> /// <param name="parameter">The parameter is not used.</param> /// <param name="culture">The parameter is not used.</param> /// <returns>The parameter is not used.</returns> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } #endregion
}
}Then you declare your converter as a reference in your XAML resources:
<conv:StatusTextColorConverter x:Key="statusTextColorConverter"/>
(Here conv is a link to the converter namespace). Finally, you add it to your cell grid style:
<Setter Property="Background" Value="{Binding Path=PropertyContainingEditableText, Converter={StaticResource statusTextColorConverter}}}"/>
By doing this, you remove the need to implement a UI
-
I'm not sure I understand your answer, sorry. The datagrid's ItemsSource is bound to the ViewModel (it is bound to a ViewModel's property of type ObservableCollection). What I did now is:
<DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Setter Property="Foreground" Value="Black"/> <Setter Property="Background" Value="{Binding Path=DataContext.CellBackground, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}"/> </Style> </DataGrid.CellStyle>
This works, but I'm sure that's not the most elegant solution...
Ahh, ok, I see where you're going with this now... I thought you wanted each cell's background to be based on the underlying data for that record, not that you wanted the entire grid to change color based on one property. A better solution might be to have each "Person" object keep a reference to the ViewModel that owns it, like a "Parent" property. That way, you can bind to Parent.CellBackground... Or if you're going to use FindAncestor (Which is a perfectly-legitimate method), you might want to source it to the DataGrid, not all the way up to the UserControl. That way, if you decide to move things around later, the grid stays modular.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels) -
Ahh, ok, I see where you're going with this now... I thought you wanted each cell's background to be based on the underlying data for that record, not that you wanted the entire grid to change color based on one property. A better solution might be to have each "Person" object keep a reference to the ViewModel that owns it, like a "Parent" property. That way, you can bind to Parent.CellBackground... Or if you're going to use FindAncestor (Which is a perfectly-legitimate method), you might want to source it to the DataGrid, not all the way up to the UserControl. That way, if you decide to move things around later, the grid stays modular.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)Okay - perfect. Thanks a lot! :)
-
What you need to do is bind the background property to the actual property that contains the Locked/Editable value, and then use a value converter to change the colour as appropriate. Here's a sample of the converter:
namespace MySample.Converters
{
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;/// <summary>
/// Converter to determine whether or not a number is negative, and apply
/// conditional formatting if it is.
/// </summary>
public class StatusTextColorConverter : IValueConverter
{
#region IValueConverter Members
/// <summary>
/// Convert from the value to the colour brush.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="targetType">The parameter is not used.</param>
/// <param name="parameter">The parameter is not used.</param>
/// <param name="culture">The parameter is not used.</param>
/// <returns>The populated colour brush.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var brush = new SolidColorBrush(Colors.White);if (value != null && value == "Locked") { brush = Colors.Yellow; } return brush; } /// <summary> /// Unused in this implementation. /// </summary> /// <param name="value">The parameter is not used.</param> /// <param name="targetType">The parameter is not used.</param> /// <param name="parameter">The parameter is not used.</param> /// <param name="culture">The parameter is not used.</param> /// <returns>The parameter is not used.</returns> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } #endregion
}
}Then you declare your converter as a reference in your XAML resources:
<conv:StatusTextColorConverter x:Key="statusTextColorConverter"/>
(Here conv is a link to the converter namespace). Finally, you add it to your cell grid style:
<Setter Property="Background" Value="{Binding Path=PropertyContainingEditableText, Converter={StaticResource statusTextColorConverter}}}"/>
By doing this, you remove the need to implement a UI
Thanks a lot for your answer, i'll try this out asap. What do you mean specially with "UI based logic item"?
-
Thanks a lot for your answer, i'll try this out asap. What do you mean specially with "UI based logic item"?
In this case, it means that you don't have to have a property in your ViewModel that returns a colour. The converter takes care of that for you.
Forgive your enemies - it messes with their heads
My blog | My articles | MoXAML PowerToys | Mole 2010 - debugging made easier - my favourite utility