Updating row colour when a value changes in WPF/xaml - c#

I've looked at a number of answers already on stackoverflow, but none of the replies seems to be able to give me what I need. This question seemed to describe my situation quite closely, but the solution did not work for me.
I have a check box column in a DataGrid, and when a user clicks the checkbox, since the data is bound to a BindableList, the value (IsOnList) associated with it gets updated. The type object of the bindable list also stores a bool to indicate if the value has been changed (Changed), which gets flipped when IsOnList changes.
If the value has been changed, I want to indicate this to the user, by setting the back ground to red. Relevant parts of the xaml are below. I have tried setter and trigger style on the data grid row, but with little luck (for testing, I also put in mouse over to make sure these were getting picked up, and it worked fine, as well as trying things like TwoWay). The DataTriggers will be called when the grid is loaded, so, since changed is always false when the grid is loaded, the background will be blue. When I click the check box, that updates Changed to true, the background does not change colour. I have even added a column for the Changed property to see if that value changes, but it does not.
<UserControl.Resources>
<vm:ProductListEditViewModel x:Key="ViewModel"/>
<vm:ChangedHighlightConverter x:Key="ChangedHighlightConverter"/>
</UserControl.Resources>
<tk:DataGrid ItemsSource="{Binding EditableLists}" x:Name="dataGrid" Grid.Row="1"
...
SelectedItem="{Binding Path=SelectedAttribute, Mode=TwoWay}">
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="Background" Value="{Binding Changed, Converter={StaticResource ChangedHighlightConverter}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Changed, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Changed, Mode=TwoWay}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Changed}" Value="False">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</tk:DataGrid.RowStyle>
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Group Name" Binding="{Binding GroupDescription}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="List Name" Binding="{Binding ListDescription}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="List ID" Binding="{Binding ListId}" Width="50" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" Width="50" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Comment" Binding="{Binding Description}" Width="120" IsReadOnly="True"/>
<tk:DataGridTextColumn Header="Changed" Binding="{Binding Changed, Mode=TwoWay}" Width="40" IsReadOnly="True"/>
<tk:DataGridTemplateColumn Header="On List" Width="50">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding Path=IsOnList, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
The relevant parts of my class are:
public bool IsOnList
{
get { return isOnList; }
set {
Changed = !changed;
isOnList = value;
}
}
public bool Changed
{
get { return changed; }
set { this.changed = value; }
}
I'm pretty new to C# and xaml, so have been struggling trying to make this work. Am I missing something obvious?

Without seeing the entire code of your class, you may need to implement INotifyPropertyChanged in your class so that WPF bindings know when properties are changing. You aren't firing any PropertyChanged events so the background style will never update (nor will the Changed property in the DataGridRow).

Related

WPF Changing DataGrid Row Background Colour if JobTimeStart Column is 1 hour away from JobTimeFinish Column

In my database "JobTimeStart" and "JobTimeFinish" are both stored as 'Time' Datatypes. I need the row background colour to change to red in my DataGrid if "JobTimeStart" is 1 hour away from "JobTimeFinish".
I made a trigger for my "JobDate" column so that if it matches todays date it will change colour to orange. I have been trying to see if I could maybe do it the same way using triggers, but have had no luck finding anything similar to what I want.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Height" Value="40"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding JobDate.Date}" Value="{x:Static System:DateTime.Today}">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
..
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Job" Binding="{Binding JobType}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Path=JobDate, StringFormat=\{0:dd/MM/yyyy\}}" x:Name="JobDate"/>
<DataGridTextColumn Header="Start Time" Binding="{Binding JobTimeStart}"/>
<DataGridTextColumn Header="Finish Time" Binding="{Binding JobTimeFinish}" x:Name="JobTimeFinish"/>
</DataGrid.Columns>
Anything to help push me in the right direction would be appreciated.
If those properties are never to be changed, you can bind the trigger to the data item itself, rather than the DateProperty and then use a converter to check whether those properties match the predicate. However, if those are changed at run-time, the data trigger would not be notified:
<Style.Triggers>
<DataTrigger Binding="{Binding, Converter={StatocResource SomeConverter}}" Value="true">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
If using triggers is not a requirement, I would use the RowStyleSelector feature.
There is no way to subtract times and compare the result to a pre-defined TimeSpan in XAML.
What you should to is to add a property to your view model or model that returns a value that indicates whether Background property should be set in the view:
public bool Warn => JobTimeFinish.Subtract(JobTimeStart).TotalHours >= 1;
You can then trigger on this property:
<DataTrigger Binding="{Binding Warn}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
This way you also keep your logic in your view model or model where it belongs and can be tested separately. XAML is a markup language.

How to enable or disable the checkbox based on property value?

In my DataGrid I've a DataGridCheckBoxColumn implementation:
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Selected}"
ElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnStyle}"
EditingElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnEditingStyle}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="CheckBox.Unchecked" Handler="Match_Unchecked"/>
<Style.Triggers>
<!-- Enabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="false">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<!-- Disabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style>
</DataGridCheckBoxColumn.CellStyle>
as you can see I've inserted a DataTrigger on the Style of DataGridCheckBoxColumn, so essentially when the property IsAdding is false, the Cell with the content inside should be enabled, otherwise, disabled.
This doesn't seems to working, the Cell is even enabled, why?
I suspect the problem is with the LogicalTree of your application. The code you have there should actually work just fine, but it appears that you may be testing this inside of the demo application. Normally this would be fine, however there was an addition of a control to display XAML that is breaking the logical tree (I have a fix in progress but it is not complete yet).
You can work around this by making two changes to the demo application:
In the MainWindow.xaml replace the XamlDisplayerHost with a regular ContentControl
In Grids.xaml replace the XamlDisplayerPanel with a regular StackPanel
After that your style should work as you expect.

WPF Disabling DataGridCheckBoxColumn based on observable collection property

I am a new WPF starter and am having trouble achieving a minor thing.
I have a viewModel that has an ObservableCollection
Order and that has two props
IsSelected(to indicate that order has been selected in grid)
EnableOrder(to indicate if an order can be selected).
However the checkbox is not disabled when Orders/EnableOrder is false.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding Orders/EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
It works when I hard-code False like below
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="False" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Also I was not able to specify binding as below as EnableOrder is not visible in XAML intellisense.
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
Please note that there were other simple DataGridTextColumn bindings that I had inside the grid and the bindings worked without any problem
I think the binding was wrong. Check with below code. Also check the output window for any binding errors.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Copied Comment as Answer Since the OP Found it to Work:
I would imagine that intellisense wouldn't pull up EnableOrder since you are in a Style and that Style may apply to multiple items which may have different DataContexts. So if you bind to EnableOrder even though there isn't any intellisense are you still able to build and run, and if so does it work?

Validation.HasError not firing for a DataGrid validating against IDataErrorInfo

I have a DataGrid using an inline style that adds a tooltip for reporting error messages - I am binding it to a collection implementing IDataErrorInfo.
In particular, I have a column bound to an integer with IDataErrorInfo logic to not permit the value outside a certain range - when I violate this rule, the default error behavior applies (eg the Textbox is highlighted red) instead of activating my error style, however if I trigger an error by entering text into the textbox and causing an InvalidInputString format, it will trigger my error style, like the way I want it to.
Here is my XAML:
<DataGrid ItemsSource="{x:Static local:WeatherForecast.TomorrowsForecast}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="-2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="PeachPuff"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="City" Binding="{Binding Path=Planet}"/>
<DataGridTextColumn Header="Low Temperature" Binding="{Binding Path=LowestTemp, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" EditingElementStyle="{StaticResource errorStyle}" />
<DataGridTextColumn Header="High Temperature" Binding="{Binding Path=HighestTemp, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" EditingElementStyle="{StaticResource errorStyle}" />
</DataGrid.Columns>
</DataGrid>
My simple IDateErrorInfo logic is:
public string this[string columnName]
{
get
{
// Temperature range checks.
if ((columnName == "LowestTemp") || (columnName == "HighestTemp"))
{
if ((this.LowestTemp < -273) || (this.HighestTemp < -273))
{
return "Temperature can not be below the absolute cold (-273°C).";
}
}
// All validations passed successfully.
return null;
}
}
Why is the default error validation behavior of a red border firing, but not my style?
UPDATE:
This seems to work fine when done OUTSIDE the DataGrid eg if I have two stray textboxes binding to one instance of my object
<TextBlock>Lowest Temp</TextBlock>
<TextBox Width="100" DataContext="{StaticResource instance}" Text="{Binding Path=LowestTemp, NotifyOnValidationError=True, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" Style="{StaticResource errorStyle}" />
<TextBlock>Highest Temp</TextBlock>
<TextBox Width="100" DataContext="{StaticResource instance}" Text="{Binding Path=HighestTemp, NotifyOnValidationError=True, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" Style="{StaticResource errorStyle}" />
It works fine! Any idea what it is about the DataGrid's internals that may be preventing this behavior from working?
You are missing NotifyOnValidationError=True on your DataGridTextColumn

How can I change a cell of a DataGrid depending on the data it is bound to using MVVM?

I'm using the MVVM Light Toolkit and I have a DataGrid bound to an ObservableCollection. There is only one text column displayed. I'd like the text of the cell to be Bold or Normal depending on a boolean that is inside the object displayed. I figured I could use RelayCommands but they only take 1 parameter and I need at least 2 to get the CellContent (the DataGridRowEventArgs and the DataGrid itself). I tried to fire a RelayCommand Execute delegate on "LoadingRow" event but with only one parameter I couldn't do it.
Here is the DataGrid in the XAML:
<DataGrid x:Name="dataGrid1" HorizontalAlignment="Left" Margin="112,34,0,8" Width="100" IsReadOnly="True" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" CanUserResizeRows="False" ItemsSource="{Binding CurrentNewsList}" AutoGenerateColumns="False" SelectedIndex="0">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Title}" MinWidth="92" Width="Auto" FontFamily="Segoe UI" Foreground="Black" FontWeight="{Binding CurrentNewsList[0].MyFont}"/>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Custom:EventToCommand Command="{Binding NewsSelectedCommand}" CommandParameter="{Binding SelectedIndex, ElementName=dataGrid1}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
I set the grid in Blend. Notice that the FontWeight is bound like "{Binding CurrentNewsList[0].MyFont}". Is it right ? I also tried "{Binding MyFont}" but both got the same result: No BOld :(
MyFont is set in the Object constructor with a boolean:
MyFont = newIsRead ? FontWeights.Normal : FontWeights.Bold;
Please help.
Thx
You could just use an implicit style and a trigger:
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding MyBoolean}" Value="True">
<Setter Property="TextElement.FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
(If you ever have more columns you could just use the styles on the column (ElementStyle & ElementEditingStyle) instead to limit the effect)
In a situation like that, I generally create a "model object" that is specifically for binding. So, instead of binding to an observable collection of "Customer", you bind to an observable collection of "CustomerModel", where the model object has a "CustomerName" property and then some other property that corresponds to the desired font (either an actual Font object, or some kind of enumeration that you parse via a value converter, if you don't want your VM layer knowing about view concerns). This model object could figure out what to make available depending on the boolean property you mention.
Here is how I managed to make it using H.B solution:
<DataGrid.Resources>
<Style x:Key="Style1" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsRead}" Value="False">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Title}" ElementStyle="{StaticResource ResourceKey=Style1}" />
</DataGrid.Columns>

Categories