WPF DataTrigger / DataBinding failing? - c#

EDIT: So, it turns out that it was a problem with the code in the VM (embarrassingly enough checking on a property that always returned true [after a refactoring session] ) - I'd kind of assumed that I'd buggered up the databinding as that's the usual suspect (for me at least)
Thank you for all the help, and apologies for wasting your time.
Hi, I'm trying to get this to simply change text colour to either Red or Green depending on a boolean Dependency Property in the viewmodel. The triggers are where the problem is... I think?
<TextBlock>
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsNegativeChange}" Value="true">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding IsNegativeChange}" Value="false">
<Setter Property="TextBlock.Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="ReturnedData.Change" />
<Binding Path="ReturnedData.ChangePercentage" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The IsNegativeChange is a member of the ViewModel object itself and so it doesn't need the 'ReturnedData' qualification.
As it stands, the text always appears as green. The ViewModel is correctly returning true/false depending on input.. Help! Is there something stupid I'm missing?
[edited for formatting]
Edit, in the debug window it says:
BindingExpression:Path=IsNegativeChange; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')
Isn't the target set by the ??

The triggers look fine to me, does the output window in Visual Studio show any binding errors?
If not maybe this is a case where the value of the trigger is overwritten, see this article about dependency property value precedence for more information. If you set the value explicitly to green somewhere the trigger will not do anything.

You can probably do away with the second trigger like so:
<Style>
<Setter Property="TextBlock.Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.IsNegativeChange}" Value="false">
<Setter Property="TextBlock.Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
That doesn't explain why one works and the other doesn't though.

I think your problem might be having the Style inline with the element. The Binding error message in your Console indicates that the binding target is being obscured inside your Style. However, you mention that adding another Label element with a binding shows the correct value.
I would also consider converting to a known default in your style, instead of two opposing triggers.
Try defining the style outside the TextBlock --
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Setter Property="TextBlock.Foreground" Value="Green" />
<DataTrigger Binding="{Binding IsNegativeChange}" Value="True">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="ReturnedData.Change" />
<Binding Path="ReturnedData.ChangePercentage" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
Hope that helps!

Related

c# WPF determine if DataGrid default visual validation has errors

I am a newbie in WPF, I have always done validation for various UI controls using custom ValidationRule classes, however, when using DataGrid for the first time and binding it with a simple DataTable, I found that the DataGrid has a pretty good default validation that detects the type of DataTable columns and gives a visual error if a cell value is not of the same expected type. This is pretty enough for me that I thought no need to create custom validation rules as the default one is fitting my purpose. However, I have a Submit button that I need to disable if this DataGrid has any errors, so I thought that this would be easy utilizing the Validation.HasError property using the following code:
<Button x:Name="btnSubmit" Content="Submit">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError),ElementName=dataGrid}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But unfortunately, it seems that Validation.HasError is always False whatever the value I enter in the datagrid cells in Runtime. The default visual validation is working properly, the cell gets a red border when an incorrect value is entered, however, no notification is sent that there is an error coming from the dataGrid.
Is there any way to detect within XAML that the default visual validation of the dataGrid is producing an error? or do I have to use a custom validation rule for this purpose?
You can create a global validation on your Ap.xaml file. So Your control will have a red asterisk and the error message as Tooltip . Any control of your project can use the same validation.
In App.xaml file:
<Style x:Key="AsteriskTemplate" TargetType="Control">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Red"
FontSize="14pt"
Margin="-15,0,0,0" FontWeight="Bold">*
</TextBlock>
<Border>
<AdornedElementPlaceholder Name="myControl"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGrid" BasedOn="{StaticResource AsteriskTemplate}" />

WPF Style DataTrigger with binding to DataContext not working

I have a TextBox with a style that has a DataTrigger which changes the text, like this:
<Grid>
<TextBlock Text="Foo">
<TextBlock.Style>
<Style BasedOn="{StaticResource TextStyle}" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding MyBool}" Value="True">
<Setter Property="Text" Value="Bar"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
But it's not working, the text never changes to "Bar". I have tested using another TextBlock with Text="{Binding MyBool}" and this text changes from "False" to "True". Snoop reveals no errors that I can see and there is nothing in the output.
This question may seem like a duplicate of WPF Trigger binding to MVVM property, but my code does not seem different from the accepted answer there (http://www.thejoyofcode.com/Help_Why_cant_I_use_DataTriggers_with_controls_in_WPF.aspx, section "Using a style") in any relevant way. And using a DataTemplate as suggested in the actual answer seems wrong since I only want this to apply to a single TextBlock, but if it is correct, I'm not sure how to write a DataTemplate for this...
EDIT:
This is what the property I'm binding to looks like:
public bool MyBool
{
get { return _myBool; }
set
{
if (_myBool== value)
return;
_myBool= value;
NotifyPropertyChanged();
}
}
private bool _myBool;
Dependency Properties can be set from many different places; inline, animations, coercion, triggers, etc. As such a Dependency Property Value Precedence list was created and this dictates which changes override which other changes. Because of this order of precedence, we can't use a Trigger to update a property that is explicitly set inline in your XAML. Try this instead:
<Grid>
<TextBlock>
<TextBlock.Style>
<Style BasedOn="{StaticResource TextStyle}" TargetType="TextBlock">
<!-- define your default value here -->
<Setter Property="Text" Value="Foo" />
<Style.Triggers>
<DataTrigger Binding="{Binding MyBool}" Value="True">
<!-- define your triggered value here -->
<Setter Property="Text" Value="Bar" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>

Reuse Trigger Conditions or MultiBinding in WPF

Aim is to Reuse this MultiBinding in Various Places
<MultiTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource ValidationBooleanConverter}">
<Binding X" />
<Binding Y" />
<Binding Z" />
</MultiBinding>
</Condition.Binding>
</Condition>
</MultiTrigger.Conditions>
Current not very DRY conditions (n+)
<Style x:Key="AnErrorTemplate" TargetType="FrameworkElement">
<Style.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource DirtyErrorControlTemplate}" />
</MultiTrigger>
</Style.Triggers>
<Style x:Key="AnotherStyle" TargetType="FrameworkElement">
<Style.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Other" Value="SomeValueIfTheSameConditionsAreTrue" />
</MultiTrigger>
</Style.Triggers>
In fact, the requirements are broader, because I also need to re-use these same conditions in ControlTemplates.
<ControlTemplate x:Key="InlineErrorControlTemplate" TargetType="{x:Type Control}">
<TextBlock Text="{Binding ElementName=InputView, Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red"/>
<ControlTemplate.Triggers>
<MultiTrigger>
<!-- Repeating the whole MultiTrigger.Conditions block here --->
<Setter Property="Visibility" Value="SetMeIfTheseSameTriggerConditionsHold" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
An umbrella for easy maintenance?
Any ideas how I could specify the MultiTrigger.Conditions or the MultiBinding once only, and then use it in multiple styles and control templates?
In XAML only?
This is exactly why Style inheritance was supplied in WPF. Plus in your case I will recommend to use DataTrigger with MultiBinding instead of MultiTrigger with MultiBinding ...
To demonstrate this lets assume I am creating a style that checks if the Tooltip or Name assigned to any FrameworkElement is empty. If so, it will make the Foreground red if the FrameworkElement is a ComboBox or its Background yellow if the FrameworkElement is TextBox.
I am using the miscellaneous field Tag of FrameworkElement....
So an all XAML (plus a C# converter) solution to this is as follows...
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Height="300" Width="300">
<Window.Resources>
<local:AtleastOneEmptyConverter x:Key="AtleastOneEmptyConverter"/>
<Style x:Key="BaseFrameworkElementStyle"
TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource AtleastOneEmptyConverter}">
<Binding Path="ToolTip"
RelativeSource="{RelativeSource Self}"/>
<Binding Path="Name"
RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Tag" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ApplyToComboStyle"
TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource BaseFrameworkElementStyle}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ApplyToTextBoxStyle"
TargetType="{x:Type TextBox}"
BasedOn="{StaticResource BaseFrameworkElementStyle}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Tag,
RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<ComboBox Style="{StaticResource ApplyToComboStyle}"
x:Name="NotRedComboBox"
ToolTip="You will not see red text here">
<ComboBoxItem IsSelected="True">I am not Red!</ComboBoxItem>
</ComboBox>
<ComboBox Style="{StaticResource ApplyToComboStyle}">
<ComboBoxItem IsSelected="True">I am Red!</ComboBoxItem>
</ComboBox>
<Separator Margin="5"/>
<TextBox Style="{StaticResource ApplyToTextBoxStyle}"
Text="I am yellow"
x:Name="YellowTextBox"/>
<TextBox Style="{StaticResource ApplyToTextBoxStyle}"
Text="I am not yellow"
x:Name="NotYellowTextBox"
ToolTip="You will not see yellow background here"/>
</StackPanel>
</Window>
C# Converter:
public class AtleastOneEmptyConverter : IMultiValueConverter
{
public object Convert
(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return values.Cast<string>().Any(val => string.IsNullOrEmpty(val));
}
public object[] ConvertBack
(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've been in this situation many times where I want to reuse commonly used Bindings that set Converters, Validation Rules within those Converters, Notifying on Source/Target update, etc. by inheriting from Binding and reusing them by specifying:
Foreground="{b:CustomColorBinding CustomDateTime}"
to reduce the amount of XAML that I need to type for bindings, but don't believe there's a way to keep it all in XAML. Trying to set the x:Key and use it as you would a Static or Dynamic Resource isn't possible, unfortunately. I've become so accustomed to using this convention that I've created a BindingCatalog for my projects to store my common (multi) binding scenarios for different controls that bind to different types.
I'm sure you have good reasons to want to avoid code-behind but if you can create your MultiBinding in code once and reuse it to DRY up your XAML -- then I think it more than justifies the (overly-demonized, IMHO) code that will be required to do so.
Hope that helps in deciding what to do!
An extended binding
namespace MyNamespace
{
public class CustomBinding : Binding
{
public CustomBinding(String path)
: base(path)
{
this.Converter = new CustomValueConverter();
this.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
this.ValidatesOnDataErrors = true;
}
}
}
Its XAML usage
<UserControl x:Class="MyNamespace.UserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace">
<StackPanel>
<TextBox Text="{local:CustomBinding MyViewModelProperty}"/>
</StackPanel>
</UserControl>

Source of an Image linked to a Boolean

I want to set the Source of an Image depending of the value of a Boolean.
Here is the code I have :
<Image DockPanel.Dock="Left">
<Image.Source>
[...]
</Image.Source>
</Image>
And in [...] I can access to a Boolean (Path="Item2" - I got a Tuple) and I want to set the value of my Source depending of the value of the Boolean.
I Absolutely got no idea how to do it...
I googled it and found some tips about Setters but I didnt managed to get it work
Any help will be very appreciated !
You could use a Style and DataTriggers:
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding ThatBool}" Value="true">
<Setter Property="Source" Value="Path to image"/>
</DataTrigger>
<DataTrigger Binding="{Binding ThatBool}" Value="false">
<Setter Property="Source" Value="Path to another image"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
(You should be familiar with data binding)
You need a DataTrigger... which by the way requires a Style. Check this link.

how to change the visual state of a WPF control based on evaluation of other Visual Elements?

I want to do the following:
<TextBlock Text="{Binding Result}">
I want to Color this based on an equality check on Result, what's the view centric way to do this? I remember reading about template selector, is that the right choice here?
example:
Text="Pass" Color="Green"
Text="Fail" Color="Red"
I'd like this to be dynamic so that if Text Changes it is re-evaluated.
You can use triggers inside a style:
<TextBlock Text="{Binding Result}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Result}" Value="Pass">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Result}" Value="Fail">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Alternatively you could create an IValueConverter implementation which converts strings to brushes (according to your rules) and use a binding directly:
<TextBlock
Text="{Binding Result}"
Foreground="{Binding Result,Converter={StaticResource my:ResultBrushConverter}} />
I won't go into details for this option because I think the pure-XAML option is the better way to go.

Categories

Resources