WPF Trigger not null - wpf

How to trigger an action in WPF when the Property is not null?
This is a working solution when is null:
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
I know that you cant "turn around" the condition and do what you need, but want to know

Unfortunately, you can't. But actually it's not necessary : you just need to specify the background for when the value is not null in the style setters, not in the trigger :
<Style.Setters>
<!-- Background when value is not null -->
<Setter Property="Background" Value="Blue" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>

You can use DataTrigger class in Microsoft.Expression.Interactions.dll that come with Expression Blend.
Code Sample:
<i:Interaction.Triggers>
<ie:DataTrigger Binding="{Binding YourProperty}" Value="{x:Null}" Comparison="NotEqual">
<ie:ChangePropertyAction PropertyName="YourTargetPropertyName" Value="{Binding YourValue}"/>
</ie:DataTrigger>
</i:Interaction.Triggers>
Using this method you can trigger against GreaterThan and LessThan too.
In order to use this code you should reference two dll's:
System.Windows.Interactivity.dll
Microsoft.Expression.Interactions.dll
And add the corresponding namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ie="http://schemas.microsoft.com/expression/2010/interactions"

it is an old question, but I want to answer. Actually you can. Just you have to use Converter in binding. Converter must return is null or not. So you will check statement is true or false. It provide you can check two condition if return value is false, it means it is not null. If it is true, it means it is null.
<converters:IsNullConverter x:Key="IsNullConverterInstance"/>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DataContext, Converter={StaticResource IsNullConverterInstance}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers></Style>
public class IsNulConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}

Related

How do you handle a RelativeSource binding for a source that may or may not exist?

We have a control that may or may not be hosted in a popup control. In the case when it is, we want to set properties on the popup using RelativeSource and OneWayToSource bindings. In cases where it's not hosted in a popup, we want the binding to basically be ignored/do nothing.
Only thing I can think of is binding to self with a custom converter which internally walks the visual tree looking for the popup. If found, do the magic. If not, do nothing. But I'm wondering if it can be done purely with XAML binding syntax.
In cases where it's not hosted in a popup, we want the binding to basically be ignored/do nothing.
Since you have a control one can create a Boolean dependency property, a flag, which can trigger either one of two hidden controls which behaves in a specific way due to which way the boolean is set.
I would call this the standard way, for the control is not required to know anything about the consumer, the consumer specifies the state.
Or
to set properties on the popup using RelativeSource and OneWayToSource bindings.
Similar to above, with two distinct hidden controls but then have the a style look for a specific window and a specific property. Then either hide or show the controls depending on what is found:
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsPopup,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}"
Value="True">
<Setter Property="IsEnabled"
Value="True" />
</DataTrigger>
</Style.Triggers>
Try following code:
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Exists"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}}" Value="{x:Null}">
<Setter Property="Text" Value="No stackpanel"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
It will display both "No stackpanel" and error in Output window. If you place StackPanel inside Border "Exists" will be displayed. Set anything you wish inside DataTrigger when condition is fulfilled.
In case you want to avoid receiving error:
<Window.Resources>
<local:IsParentTypePresentToBoolConverter x:Key="IsParentTypePresentToBoolConverter"/>
</Window.Resources>
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No StackPanel"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsParentTypePresentToBoolConverter}}" Value="True">
<Setter Property="Text" Value="Stackpanel exists as parent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
converter which detects whether such a type is present as a parent:
class IsParentTypePresentToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var isPresent = FindParent<StackPanel>((DependencyObject) value);
return isPresent != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
var parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
return parent?? FindParent<T>(parentObject);
}
}
And here you have more generic equivalent where you make a use of reflection in order to find parent type.
class IsParentTypePresentToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var method = GetType().GetMethod("FindParent").MakeGenericMethod(new Type[1] { (Type)parameter });
var foundObject = method.Invoke(this, new object[] { (DependencyObject)value });
return foundObject != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public T FindParent<T>(DependencyObject child) where T : DependencyObject
{
var parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
return parent ?? FindParent<T>(parentObject);
}
}
The only distinction in XAML is that you indicate searching type of object.
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No StackPanel"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsParentTypePresentToBoolConverter},
ConverterParameter={x:Type Border}}" Value="True">
<Setter Property="Text" Value="Stackpanel exists as parent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>

WPF one trigger for multiple values

I'm trying to create a single trigger will trigger on multiple possible options. I want to set the background to green when options are either "Reviewed" or "Completed". Then I want a second trigger to change the background to Yellow when "Pending" or "Yellow".
This answer pointed me towards it, but was incomplete and i couldn't make sense of it: https://stackoverflow.com/a/4660030/526704
Here's what I have now:
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Reviewed">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="Text" Value="Completed">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="Text" Value="Pending">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
<Trigger Property="Text" Value="Pending Review">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
Here's the kind of thing i'm looking for: (some way to specify multiple values of the property that trigger the same setters. I have many more of these that I'd like to condense without repeating the same trigger many times)
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Reviewed" Value2="Completed">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="Text" Value="Pending" Value2="Pending Review">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
You can create your custom IValueConverter that would convert string into SolidColorBrush
public class TextToBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((string)value)
{
case "Reviewed":
case "Completed":
return new SolidColorBrush(Colors.Green);
case "Pending":
case "Pending Review":
return new SolidColorBrush(Colors.Yellow);
}
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and then you don't even need Trigger. You can use Binding with Converter
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Setter
Property="Background"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource TextToBackgroundConverter}}"/>
</Style>
where TextToBackgroundConverter is defined somewhere in resources as
<Window.Resources>
<local:TextToBackgroundConverter x:Key="TextToBackgroundConverter"/>
</Window.Resources>
I devised a solution. I'm now using a converter to determine whether or not the value is given among the provided parameters. I send the options "Reviewed,Completed" as a parameter to the converter, which returns true if the text from the TextBlock is found anywhere in the parameter.
XAML:
<Style TargetType="TextBlock" x:Key="StatusStyle">
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource OrConverter}, ConverterParameter=Reviewed;Completed}">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
Converter:
[ValueConversion(typeof(string), typeof(bool))]
public class MultiValueOrConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)parameter).Split(';').Contains((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

Hide DataTrigger if RelativeSource doesn't exist

I want to add a DataTrigger to my base TextBox style so that it sets the foreground color to a different value if it is inside of a DataGridCell that is selected. Here is what my trigger looks like:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True">
<Setter Property="Foreground"
Value="White" />
</DataTrigger>
</Style.Triggers>
This works great, except that when my TextBox is not in a DataGrid the Binding fails and writes an exception to the output window. How can I prevent this.
I basically want to say if Parent is a DataGridCell then apply this trigger otherwise ignore it.
In general just only apply the style where applicable. If you want implicit application use nested styles:
<Style TargetType="{x:Type DataGrid}">
<Style.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
If you have other parts which you want to apply to all TextBoxes take out those parts in a serarate style and use BasedOn in the style which applies to the TextBoxes inside the DataGrid.
Edit: MultiDataTrigger seems to return right away if a condition is not met so you can avoid binding errors:
<Style TargetType="{x:Type TextBox}">
<Style.Resources>
<vc:HasAncestorOfTypeConverter x:Key="HasAncestorOfTypeConverter" AncestorType="{x:Type DataGridCell}" />
</Style.Resources>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource HasAncestorOfTypeConverter}}"
Value="True" />
<Condition
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}, Path=IsSelected}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="Red" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
public class HasAncestorOfTypeConverter : IValueConverter
{
public Type AncestorType { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return false;
DependencyObject current = value as DependencyObject;
while (true)
{
current = VisualTreeHelper.GetParent(current);
if (current == null)
{
return false;
}
if (current.GetType() == AncestorType)
{
return true;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
This of course causes quite som overhead so it might not be such a good solution, then again if the RelativeSource-binding fails it also had to go up the tree first.

WPF - XAML - Data Trigger without Binding / Value only trigger

I have a grid of values in a DataGrid. In each cell I have one of 4 values, I want each value to have an associated colour.
In the past all my data triggers have been of the format;
<DataTrigger Binding="{Binding Status}" Value="R">
<Setter Property="xcdg:DataRow.Background" Value="Pink" />
</DataTrigger>
But I need something of the format;
<DataTrigger Binding="{Binding *}" Value="R">
<Setter Property="xcdg:DataRow.Background" Value="Pink" />
</DataTrigger>
So that any cell's value will be checked. The reason I can't use explicit bindings is that the number of columns and their names is dynamic, I use extend CustomTypeDescriptor to expose the columns and rows to enter the grid.
Thanks in advance!
Is it an option to use a ValueConverter in your binding? That way you could check in the converter which value it should return.
<DataTrigger Binding="{Binding Path=Content, RelativeSource={x:Static RelativeSource.Self}}" Value="Failure">
<Setter Property="xcdg:DataRow.Background" Value="Red" />
</DataTrigger>
I needed to use a Relative source.
Further to that what I ended up using was;
<xcdg:DataGridControl.Resources>
<c:ColorConverter x:Key="colorConverter" />
<Style TargetType="{x:Type xcdg:DataCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}" Value="False">
<Setter Property="xcdg:DataCell.Background" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource colorConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsDirty, RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}" Value="True">
<Setter Property="xcdg:DataCell.Background" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource colorConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</xcdg:DataGridControl.Resources>
Where my Colour converter looks a bit like this;
[ValueConversion(typeof(DataCell), typeof(Brush))]
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var cell = (DataCell)value;
var content = (string)cell.Content;
if (content == null || cell.ParentRow.IsSelected)
{
return DependencyProperty.UnsetValue;
}
if (content == "Unknown")
{
return new SolidColorBrush(Colors.LightYellow);
}
...
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
My only problem now is that when I scroll my background colours aren't updated when the DataRows are reused.
I can't find an event to hook into when a DataRow is reused either...

DataTrigger where value is NOT null?

I know that I can make a setter that checks to see if a value is NULL and do something. Example:
<TextBlock>
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeField}" Value="{x:Null}">
<Setter Property="TextBlock.Text" Value="It's NULL Baby!" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
But how can I check for a "not" value... as in "NOT NULL", or "NOT = 3"? Is that possible in XAML?
Results: Thanks for your answers... I knew I could do a value converter (which means I would have to go in code, and that would not be pure XAML as I hoped for). However, that does answer the question that effectively "no" you can't do it in pure XAML. The answer selected, however, shows probably the best way to create that kind of functionality. Good find.
This is a bit of a cheat but I just set a default style and then overrode it using a DataTrigger if the value is null...
<Style>
<!-- Highlight for Reviewed (Default) -->
<Setter Property="Control.Background" Value="PaleGreen" />
<Style.Triggers>
<!-- Highlight for Not Reviewed -->
<DataTrigger Binding="{Binding Path=REVIEWEDBY}" Value="{x:Null}">
<Setter Property="Control.Background" Value="LightIndianRed" />
</DataTrigger>
</Style.Triggers>
</Style>
You can use an IValueConverter for this:
<TextBlock>
<TextBlock.Resources>
<conv:IsNullConverter x:Key="isNullConverter"/>
</TextBlock.Resources>
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeField, Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="TextBlock.Text" Value="It's NOT NULL Baby!"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Where IsNullConverter is defined elsewhere (and conv is set to reference its namespace):
public class IsNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
A more general solution would be to implement an IValueConverter that checks for equality with the ConverterParameter, so you can check against anything, and not just null.
I ran into a similar limitation with DataTriggers, and it would seem that you can only check for equality. The closest thing I've seen that might help you is a technique for doing other types of comparisons other than equality.
This blog post describes how to do comparisons such as LT, GT, etc in a DataTrigger.
This limitation of the DataTrigger can be worked around to some extent by using a Converter to massage the data into a special value you can then compare against, as suggested in Robert Macnee's answer.
Compare with null (As Michael Noonan said):
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
Compare with not null (without a converter):
<Style>
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
I'm using this to only enable a button if a listview item is selected (ie not null):
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=lvMyList, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
You can use DataTrigger class in Microsoft.Expression.Interactions.dll that come with Expression Blend.
Code Sample:
<i:Interaction.Triggers>
<i:DataTrigger Binding="{Binding YourProperty}" Value="{x:Null}" Comparison="NotEqual">
<ie:ChangePropertyAction PropertyName="YourTargetPropertyName" Value="{Binding YourValue}"/>
</i:DataTrigger
</i:Interaction.Triggers>
Using this method you can trigger against GreaterThan and LessThan too.
In order to use this code you should reference two dll's:
System.Windows.Interactivity.dll
Microsoft.Expression.Interactions.dll
<StackPanel.Style>
<Style>
<Setter Property="StackPanel.Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ProfileSelectorComboBox, Path=SelectedItem.Tag}" Value="{x:Null}">
<Setter Property="StackPanel.Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
I just used the inverse logic here...setting my stackpanel to invisible when my comboitem is not populated, it works pretty well!
Stop! No converter! I dont want to "sell" the library of this guy, but I hated the fact of doing converter everytime I wanted to compare stuff in XAML.
So with this library : https://github.com/Alex141/CalcBinding
you can do that [and a lot more] :
First, In the declaration of the windows/userControl :
<Windows....
xmlns:conv="clr-namespace:CalcBinding;assembly=CalcBinding"
>
then, in the textblock
<TextBlock>
<TextBlock.Style>
<Style.Triggers>
<DataTrigger Binding="{conv:Binding 'MyValue==null'}" Value="false">
<Setter Property="Background" Value="#FF80C983"></Setter>
</DataTrigger>
</Style.Triggers>
</TextBlock.Style>
</TextBlock>
The magic part is the conv:Binding 'MYValue==null'. In fact, you could set any condition you wanted [look at the doc].
note that I am not a fan of third party. but this library is Free, and little impact (just add 2 .dll to the project).
My solution is in the DataContext instance (or ViewModel if using MVVM). I add a property that returns true if the Not Null condition I want is met.
Public ReadOnly Property IsSomeFieldNull() As Boolean
Get
Return If(SomeField is Null, True, False)
End Get
End Property
and bind the DataTrigger to the above property.
Note: In VB.NET be sure to use the operator If and NOT the IIf function, which doesn't work with Null objects.
Then the XAML is:
<DataTrigger Binding="{Binding IsSomeFieldNull}" Value="False">
<Setter Property="TextBlock.Text" Value="It's NOT NULL Baby!" />
</DataTrigger>
If you are looking for a solution that does not use IValueConverter, you can always go with below mechanism
<StackPanel>
<TextBlock Text="Border = Red when null value" />
<Border x:Name="border_objectForNullValueTrigger" HorizontalAlignment="Stretch" Height="20">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding ObjectForNullValueTrigger}" Value="{x:Null}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<TextBlock Text="Border = Green when not null value" />
<Border HorizontalAlignment="Stretch" Height="20">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Background, ElementName=border_objectForNullValueTrigger}" Value="Red">
<Setter Property="Background" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Button Content="Invert Object state" Click="Button_Click_1"/>
</StackPanel>
Converter:
public class NullableToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
}
Binding:
Visibility="{Binding PropertyToBind, Converter={StaticResource nullableToVisibilityConverter}}"
You can use a converter or create new property in your ViewModel like that:
public bool CanDoIt
{
get
{
return !string.IsNullOrEmpty(SomeField);
}
}
and use it:
<DataTrigger Binding="{Binding SomeField}" Value="{Binding CanDoIt}">

Resources