DataTrigger to a control's Validation.HasError - c#

I have a user control which uses the INotifyDataErrorInfo interface and it turns red when it has errors, inside of this user control I put a TextBlock and the following DataTrigger doesn't seem to work:
<TextBlock Text="{Binding DurationText}"
Grid.Row="1">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ActivityUserControl, Path=(Validation.HasError)}"
Value="True">
<Setter Property="Foreground"
Value="White">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I made sure that the x:Name of my user control is correct (ActivityUserControl), the user control itself turns red when it has errors but the TextBlock's DataTrigger doesn't work (text stays black).

Your should set the Value of the DataTrigger to begin with:
<DataTrigger Binding="{Binding ElementName=ActivityUserControl, Path=(Validation.HasError)}"
Value="True">
...
You should also make sure that the ActivityUserControl is in the same namescope as the TextBlock and that it acually contains some validation errors.
You can solve the namescope issue by binding to the parent UserControl using the RelativeSource property:
Binding="{Binding Path=(Validation.HasError),
RelativeSource={RelativeSource AncestorType=UserControl}}"

Related

WPF:Using checkbox IsChecked property in DataTrigger

I am trying to use WPF checkbox's IsChecked property in a DataTrigger.Based on the value i am setting particular DataGridRow's background.
My NOT WORKING code
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkbox, Path=IsChecked}" Value="true">
<Setter Property="Background" Value="LightCyan" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataGrid x:Name="dataGrid1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn MinWidth="40" Width="Auto" Header="Select">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chkbox" IsChecked="{Binding Selected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Then I checked this link and changed the code as below and it is working fine.Here Selected is my public property.
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Selected}" Value="true">
<Setter Property="Background" Value="LightCyan" />
</DataTrigger>
</Style.Triggers>
</Style>
Please help me to understand why my original code is not working? am i missing anything.
google did not help.surprisingly there is no thread for this on SO also! Thank you for the help.
The original code is not working because you're trying to locate an object via ElementName which exists as a templated object, and thus isn't created until after the binding tries to resolve. Generally, you should only use ElementName when referring to ancestor objects in the visual tree, and not to children, particularly templated children.
As mentioned in comments, it would also not be possible to use a {RelativeSource FindAncestor... binding here because the CheckBox is a child, not an ancestor, of the DataGridRow

Validation on DataGrid

I have a datagrid displaying ObservableCollection in my xaml. One of my validation rule on one of the columns is "Name cannot be blank" when the Name field is blank. It all works fine.
My problem is that when my Name field validation is triggered (if name was blank), the Name field is outlined with red box. Imagine at this stage the user fills in a name but the red box still remains even if you click on the other fields of the same row. The red box goes away only when the user clicks on a different row. Is there any way to make the red box disappear when the user click on different fields of the same row?
My xaml for the Name field is
<Window.Resources>
<Style x:Key="EditCellStyleError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="CellStyleError" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
The Name field
<DataGridTextColumn Header="Name" Binding="{Binding Name,ValidatesOnDataErrors=True, NotifyOnValidationError=True, ValidatesOnExceptions=True}" EditingElementStyle="{StaticResource EditCellStyleError}" ElementStyle="{StaticResource CellStyleError}"/>
I think you need to add UpdateSourceTrigger to your binding
eg
<DataGridTextColumn Header="Name"
Binding="{Binding Name,ValidatesOnDataErrors=True, NotifyOnValidationError=True,
ValidatesOnExceptions=True, UpdateSourceTrigger=LostFocus}" />
here Ive used LostFocus but PropertyChanged might be an option
UpdateSourceTrigger=PropertyChanged definitely helped. Another problem was to get rid of red exclamation mark. To achieve this I added a blank Rowvalidationtemplate
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>

WPF Style Triggers for DataTemplates

I would like to set Triggers for the controls in a DataTemplate. Whenever I set a property of the control within the DataTemplate, it seems not working. However, If do not set the property within the TextBlock inside the DataTemplate, then I can see the effect of Trigger in the style (it works). I am not sure whether using Style Triggers with DataTemplate is good or not! The XAML is below;
<Grid>
<Grid.Resources>
<Style TargetType="TextBlock" x:Key="BlockOf">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
...........
DataTemplate for the button,
<Button.ContentTemplate>
<DataTemplate DataType="Button">
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
FontStyle="Italic" FontSize="9"/>
</DataTemplate>
</Button.ContentTemplate>
I can see two problems here. First one is that current trigger will work only for TextBlock inside Button, not over whole Button. You can change that by using DataTrigger with RelativeSource binding. Second problem is that even when mouse is over TextBlock Style.Trigger cannot overwrite local value that you set against TextBlock so you need to bring default values as Setter into your Style. Check Dependency Property Setting Precedence List
<Style TargetType="TextBlock" x:Key="BlockOf">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontSize" Value="9"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsMouseOver}" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</DataTrigger>
</Style.Triggers>
</Style>
and then TextBlock simply
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />

DataTrigger for Textblock

I have a Textblock that I'm trying to change the value of the Text property if a property is True or False. The issue I'm having is that the flag could be changed on different events on the screen (onchange events from other combo boxes).
I'm not sure how to get this datatrigger to work as I don't think it know when the value has been changed.
<TextBlock Grid.Row="9" HorizontalAlignment="Right" Text="Some Old Value:" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="False" >
<Setter Property="Text" Value="Different Text:"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I see in some comboboxes there is the UpdateSourceTrigger=PropertyChanged, but I don't see a way to implement that in the TextBlock.
First of all set default Text in style setter, otherwise no matter whether your triggers fires successfully or not, Text won't take value from Style setter because of Dependency property value precedence order. Local value has higher precedence than style setter values.
<TextBlock Grid.Row="9" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="Some Old Value:"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="False" >
<Setter Property="Text" Value="Different Text:"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Second, if IsChecked property resides in your ViewModel (implementing INotifyPropertyChanged) and TextBlock DataContext is rightly pointing to ViewModel instance, you don't have to worry about it.
Just make sure whenever property IsChecked changes in ViewModel a PropertyChanged event gets raised so that UI can listen to that and updates itself.

Change property depending on property in other control - WPF

I want to use some kind of trigger to change a button in my window to IsEnabled = False when the textbox property Validation.HasErrors of my textbox is True.
Can I do this with Triggers of some kind on the Button?
If yes, some example would be nice!
You can do this by using a Style. If you want to tie it directly to the text box, you could do something like this:
<Style x:Key="DisabledOnErrors" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasErrors)}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox x:Name="myTextBox" />
<Button Style="{StaticResource DisabledOnErrors}"
DataContext="{Binding ElementName=myTextBox}" />
This works if the button is not already binding to the DataContext for other properties. In this case, the Style is reusable for other button and text box pairings.
Yes, you can do this with triggers, but because you are referring to another element, you must use a DataTrigger rather than a normal Trigger:
<Button>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=tb}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Note you must use a Style rather than the Button.Triggers collection, because Button.Triggers can contain only EventTriggers.

Categories