TextBlock trigger instead of using converter - wpf

I want to show a number on screen. If that number is 0 I don't want it to show at all.
<TextBlock Text="{Binding Path=Class.Count}" FontSize="20" FontWeight="Bold">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=Class.Count}" Value="0">
<Setter Property="TextBlock.Text" Value=""/>
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
I attempted the above piece of code after a regular trigger failed to solve my problem. I do not want to write a converter to act upon one specific number. Is there a way to create a trigger that will hide the number if it is 0?
EDIT: When I try to use a regular trigger or a data trigger I get a xaml parse error telling me I need to use an event trigger.
I tried to set the value in the setter to a number to make sure that having a blank value was not causing the problem

You are fighting with the binding to set the Text Property.
I'd make the control collapsed/hidden instead of setting the text to String.Empty.
Less confusion.
EDIT
<TextBox>
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Name.Length}" Value="0">
<Setter Property="UIElement.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
OR
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Name.Length}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Only a Style allows DataTriggers.
The xaml parser wants to check the property's existence so it needs the ownertype. Visibility is declared on the UIElement class. There are two ways of specifying that as I have shown in both examples.

Related

Get ValidationError in specific xaml element using a template

i have the following code snippet:
<ContentControl Height="16">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtDistanceH, Path=(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding ElementName=txtDistanceH, Path=(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Now i want to put the style in a separate file instead of inline. However i would like to be able to specify which element it should get the Validation.Errors from, so i can use a single template for several different controls.
Is there any way to tell the template where it should get the Validation.Errors from, OTHER than binding to an element by name?
I tried setting the ContentControls DataContext to the element txtDistanceH, but then i just get a binding error saying that the property cannot be found on the root-element.
thanks for taking the time to answer my question. I've tried it and it works!
However i do have a comment and another related question.
The code i have now is:
<!-- Set content of contentcontrol to the ValidationError of a control stored in Tag, if there is one -->
<Style x:Key="ShowValidationError" TargetType="ContentControl">
<Style.Resources>
<x:Static x:Key="EmptyString" Member="System:String.Empty" />
</Style.Resources>
<Setter Property="Content" Value="{StaticResource EmptyString}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors).CurrentItem}" />
</DataTrigger>
</Style.Triggers>
</Style>
(Validation.Errors).CurrentItem is better than (Validation.Errors)[0], because the latter gives an out of range exception in the debug window when the error is resolved, see This Link for more information. The empty string ensures the control has the same size when its empty as when it has an error.
However even though it compiles and works, i still get some errors during design time. The code responsible is (Validation.HasError) and (Validation.Errors), respectively, in the above snippet.
Property 'Errors' is not attachable to elements of type 'Object'.
The property 'HasError' was not found in type 'Validation'.
Is there any way to fix / suppress these errors?
Bind the Tag property of the ContentControl to the target element using the element name binding and then update the style to use relative source self bindings to the tag to get at the validation errors.
Somewhere in Resources:
<Style x:Key=“ValidationStyle” TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
And use it thusly:
<ContentControl Style=“{StaticResource ValidationStyle}” Tag=“{Binding ElementName=txtDistanceH}” />

How to use the Calendar's Display Mode as a DataTrigger's Value

So I need to know how to set up a xmlns to let me use the CalendarMode in a Trigger's value.
I have tried using xmlns:cal="clr-namespace:System.Windows.Controls", xmlns:cal="clr-namespace:System.Windows.Controls.Calendar" and I've built the project each time, but I got error telling me that the CLR namespace is undefined and cannot be found.
Here is where I used it
<DataTrigger Binding="{Binding Source=_Calendar, Path=Calendar.DisplayMode}">
<DataTrigger.Value>
<cal:CalendarMode>Month</cal:CalendarMode>
</DataTrigger.Value>
<Setter Property="Grid.Opacity" Value="1" />
</DataTrigger>
I guess I could just listen to the DisplayModeChanged event on the calendar but since I've been searching online for this solution all day, I'd really like to know how I can approach this problem in this way.
Any input will be highly appreciated. Thanks!
actually I did not understand exactly what you need. But I'll try to help.
the definition we see:
then able to use the xaml we have to do:
xmlns:presentation="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
Now, if you want something to happen with a dependency property of the own control, you should use Triggers and not DataTriggers
Sample:
<Calendar Height="170" HorizontalAlignment="Left" Margin="83,112,0,0" Name="calendar1" VerticalAlignment="Top" Width="180">
<Calendar.Style>
<Style TargetType="Calendar">
<Setter Property="Opacity" Value="0.4"/>
<Style.Triggers>
<Trigger Property="SelectionMode" Value="{x:Static presentation:CalendarMode.Month}">
<Setter Property="Opacity" Value="1.0"/>
</Trigger>
</Style.Triggers>
</Style>
</Calendar.Style>
</Calendar>
Normally DataTriggers are used for objects created by you, which implementational INotifyPropertyChanged. Do not mess.
Now, if you want to change another control (when CalendarMode changes) you should do:
<Calendar Height="170" HorizontalAlignment="Left" Margin="83,112,0,0"
Name="calendar1" VerticalAlignment="Top" Width="180"/>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Opacity" Value="0.5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=calendar1, Path=CalendarMode}">
<DataTrigger.Value>
<presentation:CalendarMode>Month</presentation:CalendarMode>
</DataTrigger.Value>
<Setter Property="Opacity" Value="1.0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
I suggest you read more about triggers, datatriggers and bindings.

Why can't I add a DataTrigger to my control's Triggers collection?

Why cant I code like this
<Border Width="130" Height="70">
<Border.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorder}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorderInactive}"/>
</DataTrigger>
</Border.Triggers>
</Border>
I get this error
Failed object initialization (ISupportInitialize.EndInit).
Triggers collection members must be of type EventTrigger.
Error at object '4_T' in markup file
What am I doing wrong plz help.
Abe is correct and explains the limitations well. One thing you might want to consider is:
Instead of having two border styles, and trying to pick between them based on a trigger...
Use a single style on your border, this style's setters represent your 'normal' look.
This style also contains your DataTrigger, and your DataTrigger has a collection of setters which essentially represents your second style (which have higher priority than the standard setters when this trigger evaluates to true!
Edit:
Something like this -
<Style TargetType="Border" x:Key="BorderStyle">
<!-- These setters are the same as your normal style when none of your triggers are true -->
<Setter Property="BorderBrush" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<!-- These setters are the same as your ListBoxItemBorder style -->
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<!-- These setters are the same as your ListBoxItemBorderInactive style -->
<Setter Property="BorderBrush" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
Unfortunately, only EventTriggers can be applied directly to elements. If you want to use a Trigger or DataTrigger, they have to be in a Style, ControlTemplate, or DataTemplate.
From the resource names, it looks like this is a Border inside a ListBoxItem ControlTemplate. You could easily move the triggers into the template's triggers collection.
Here is a way for no limitations triggers.
Example:
<Border Width="130" Height="100" Grid.Row="1">
<ListBox x:Name="lstItems" ItemsSource="{Binding TestItems}">
</ListBox>
<tg:TriggerExtensions.Triggers>
<tg:TriggerCollections>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxRed}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0" IsInvert="True">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxBlue}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
</tg:TriggerCollections>
</tg:TriggerExtensions.Triggers>
</Border>
Link Sample
Link Component Github

Changing TextBlock.Text in trigger didn't work

I have the next code in my view:
<Style x:Key="documentFileNameStyle">
<Setter Property="TextBlock.Foreground" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Untitled}" Value="True">
<Setter Property="TextBlock.FontStyle" Value="Italic"/>
<Setter Property="TextBlock.Text" Value="no file name"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="documentTemplate">
<TextBlock Text="{Binding Path=FileName}" Style="{StaticResource documentFileNameStyle}"/>
</DataTemplate>
But setting TextBlock.Text to a string didn't work. TextBlock.FontStyle changes to Italic, so whole trigger works properly. What is wrong?
Local assignment of Properties has a higher precedence than setting the values in triggers.
Also you are using Binding (Path=FileName) to set the Text-Property of the TextBlock. So changing the Text in Triggers doesn´t effect the Property.
As you are using Binding. I would change the Property "FileName" to return "no file name" if the Property "Untitled" is "true".

Databinding to XML in a DataTrigger in WPF

In a WPF application, I have correctly bound a DataTemplate to an XML node that looks like:
<answer answer="Tree", correct="false" score="10" />
In my application, I have a TextBlock with the answer in it. At first, I want it invisible, but when the correct attribute in the XML file changes to "true", it must become visible.
My DataTemplate is hooked up correctly, because everything else works. For example, if I change the answer attribute in the XML file (just for testing), it changes in my WPF view. But I'm having troubles with the visibility. This is my XAML:
<TextBlock Text="{Binding XPath=#answer}" Visibility="Hidden">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'm guessing the Databinding in the DataTrigger isn't working correctly. Anyone have a clue?
I have run into the same problem with databound ToggleButtons. Try removing the Visibility="False" and replacing it with another DataTrigger that handles the incorrect case.
I think the issue is that the Visibility property is hard-coded. Try setting the Visibility in the style:
<TextBlock Text="{Binding XPath=#answer}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Sure, it works if you give a specific else case instead of just false. As in my case, it was {x:Null} and value. So when its value to bind is present, it will be true and TextBlock.Visibilty will be set using setters value and when binding path does not have any value inside it, i.e. null in my case, its simply {x:Null} :)

Resources