WPF: DataTrigger with multi properties condition - wpf

So I have this DataTrigger:
<DataTrigger Binding="{Binding Path=IsFilesSelected}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="EndAnimation"/>
<BeginStoryboard Name="NewAnimation">
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="Margin"
From="0,50,0,0"
To="0,0,0,0"
DecelerationRatio=".9"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
And as you can see this DataTrigger is started when my IsFilesSelected property changes to True and I wonder how to assign another property and consider its value too when determine if I need to start my DataTrigger.

What you are looking for is the MultiDataTrigger with documentation here.
Represents a trigger that applies property values or performs actions when the bound data meet a set of conditions.
Example lifted from the MSDN website
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Name}" Value="Portland" />
<Condition Binding="{Binding Path=State}" Value="OR" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Cyan" />
</MultiDataTrigger>
The above example will set the Background property when both the Name == "Portland" AND the State == "OR".

Related

Why does my MultiDataTrigger run only once even if it satisfies the conditions?

In an attempt to further reduce my c# code, I've tried handling the Hamburger Menu's animation on XAML. I've applied a MultiDataTrigger with a condition that takes the current Width and the Button press from the Hamburger Menu Button. It animates the first Width 70 -> 150 and Width 150 -> 70 but after that it doesn't work anymore.
<Grid x:Name="NavigationGrid" Grid.RowSpan="2" Background="Black">
<StackPanel Margin="0">
<Button x:Name="HamburgerMenuBtn" Style="{DynamicResource NavigationBtn_Style}"/>
</StackPanel>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Width" Value="70"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=NavigationGrid,Path=ActualWidth}" Value="70"/>
<Condition Binding="{Binding ElementName=HamburgerMenuBtn,Path=IsPressed}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="70"
To="150"
Duration="0:0:0.5"
Storyboard.TargetProperty="Width"/>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=NavigationGrid,Path=ActualWidth}" Value="150"/>
<Condition Binding="{Binding ElementName=HamburgerMenuBtn,Path=IsPressed}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="150"
To="70"
Duration="0:0:0.5"
Storyboard.TargetProperty="Width"/>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
IsPressed is only true momentarily. Lose that second animation. Just use the first animation but make it reverse. So it grows then reduces the size. AutoReverse="true".
You might also have to increase duration a little.

Animate Grid Background color

I want to change the grid background via binding, if a condition is true.
I am using the MVVM light framework.
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding SuccessBooked, UpdateSourceTrigger=PropertyChanged}"
Value="True">
<!--Setter Property="Background" Value="LimeGreen" />-->
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="LimeGreen"
Storyboard.TargetName="ActualWeightBg"
Storyboard.TargetProperty="Background"
FillBehavior="Stop"
Duration="0:0:12"/>
<!--<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:15"/>-->
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding LowerBooked, UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HigherBooked, UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Background" Value="DarkOrange" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
The compiler complains:
If I remove Storyboard.TargetName="ActualWeightBg":
<ColorAnimation To="LimeGreen"
Storyboard.TargetProperty="Background"
FillBehavior="Stop"
Duration="0:0:12"/>
I get this exception:
Exception thrown: 'System.InvalidOperationException' in PresentationFramework.dll
I want that background color is going to change for 5s, after that it should change back to the standard color.
Update
So when a background color is not set, it will throw the error:
Cannot resolve all property references in the property path
'Background.Color'. Verify that applicable objects support the
properties.
As you see here, the background color is not set
then the error will occur:
But when the background is set:
Then it will work as expected.
How can I set an animated background color, without background color being set.
It is an usercontrol.
First of all, StyleTriggers in general dont support TargetNames.
AFAIK only TemplateTriggers do support them.
Your DataTrigger should look like this.
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" To="LimeGreen"
FillBehavior="Stop" AutoReverse="True" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
Notice the Storyboard.TargetProperty="Background.Color". Also your TimeSpan was too high. Another thing is setting the AutoReverse to true.
EDIT
To get this Trigger to work, in the Grid's Style there has an initial Background to be set.
<Style TargetType="Grid">
<Setter Property="Background" Value="Red"/>

Changing Property in ThicknessAnimation

I'm using a label in my project to show some news in moving style by thicknessanimation, but I can't change the content of label after the animation completes. What can I do?
First when the window loads I activate a thicknessanimation with the code below
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard TargetName="MyLabel">
<ThicknessAnimation Storyboard.TargetProperty="Margin" SpeedRatio="0.8" RepeatBehavior="Forever"
FillBehavior="HoldEnd" From="0,0,0,0" To="100,0,0,0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
Then I Set a Style on the label
<Style TargetType="Label">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=MyLabel, Path=Margin}" Value="100,0,0,0" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapes" />
</MultiDataTrigger>
</Style.Triggers>
I tried to change the content with IsVisibleChanged but it didn't work.
Based on your code snippet the first issue is your MultiDataTrigger. You have the condition and the setter backwards try something like this and see if it works
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="Visibility" Value="Collapsed" />
</MultiDataTrigger.Conditions>
<Setter Binding="{Binding ElementName=Label1, Path=Margin}" Value="0,0,0,0"/>
The Condition tag is for the event or property you're looking for to trigger the Setter tag which will set the properties of the element you want to change.
Also a MultiDataTrigger is probably not the tag you need to change the margin this code should be a bit simpler
<Style TargetType="Label">
<Style.Triggers>
<Trigger Property="Visibility" Value="Collapsed">
<Setter Property="Margin" TargetName="MyLabel" Value="0,0,0,0"/>
</Trigger>
</Style.Triggers>

Data Trigger Will Not Fire

I have the following Data Trigger set-up on a Control Template
<DataTrigger Binding="{Binding Path=IsDragged}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Active}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource Unactive}" />
</DataTrigger.ExitActions>
</DataTrigger>
Generally it will fire at least once (not always) and at some point will cease. Some additional interesting notes:
The same property is set-up in a MultiDataTrigger, this trigger will always fire
The same storyboards are referenced in another trigger, they continue to run after this trigger fails
Edit: The MultiDataTriggers is set-up as follows:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsActive}"
Value="True" />
<Condition Binding="{Binding Path=IsDragged}"
Value="False" />
<Condition Binding="{Binding Path=IsInCart}"
Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard x:Name="ShowTag_BeginStoryboard"
Storyboard="{StaticResource ShowTag}" />
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard x:Name="HideTag_BeginStoryboard"
Storyboard="{StaticResource HideTag}" />
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
Just an educated guess, but I've run into something like this before and it turned out that I needed to stop each storyboard before I started another one, because they were conflicting with each other.
Try adding two StopStoryboard actions to your DataTrigger, one to stop the Active storyboard and the other to stop the Unactive storyboard.

WPF - Best way of responding to changes in a ViewModel at Page/Window level

I'm developing an XBAP and i have a simple requirement.
The DataContext of the whole main page is set to an instance of my UserViewModel. The UserViewModel has a DependencyProperty called AuthenticationState which is an enum with values like 'Authenticated','NotAutheticated' and 'AuthenticationFailed'.
Now, i need to respond to any change in this value by hiding/displaying various elements on the page.
What (and where) is the best way of doing that?
As you mentioned you can't use a DataTrigger directly on a control. A work around would be to use a style on each Control that needs to be hidden.
<Grid>
<Rectangle Fill="Red" />
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding Test}" Value="true">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
A preferable method would be to use a Converter called "AuthenticationStateToVisibilityConverter" that is used in binding the control's Visibility property to the data context's AuthenticationState property.
The best way would be to use a DataTrigger. So something like this:
<Window.Triggers>
<DataTrigger Binding="{Binding AuthenticationState}" Value="NotAuthenticated">
<Setter TargetName="nameOfControl" Property="Visibility" Value="Collapsed" />
</DataTrigger>
...
<TextBox x:Name="nameOfControl" />
</Window.Triggers>
As long as you UserViewModel object is in the DataContext of the Window then this should work!
Managed to sort it using styles. It's a pain but it works!
The full source is below.
<Grid x:Name="contentGrid" Grid.Row="1">
<!--login-->
<controls:LoginControl>
<controls:LoginControl.Style>
<Style>
<Setter Property="Control.Opacity" Value="0"/>
<Setter Property="Control.IsHitTestVisible" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
Value="{x:Static model:AuthenticationState.NotAuthenticated}">
<Setter Property="Control.IsHitTestVisible" Value="True"/>
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="1" Duration="0:0:2"
Storyboard.TargetProperty="Opacity"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0" Duration="0:0:2"
Storyboard.TargetProperty="Opacity"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</controls:LoginControl.Style>
</controls:LoginControl>
<!--slider-->
<slider:PageSlider>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<slider:PageSlider.Style>
<Style>
<Setter Property="Control.Opacity" Value="0"/>
<Setter Property="Control.IsHitTestVisible" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource UserViewModel},Path=UserAuthenticationState}"
Value="{x:Static model:AuthenticationState.Authenticated}">
<Setter Property="Control.IsHitTestVisible" Value="True"/>
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="1" Duration="0:0:2"
Storyboard.TargetProperty="Opacity"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0" Duration="0:0:2"
Storyboard.TargetProperty="Opacity"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</slider:PageSlider.Style>
</slider:PageSlider>
</Grid>
Actually, the best way to do this is to expose the appropriate properties from your view model. This makes your logic more centralized and easier to test. Also, it performs better than converters. It is, after all, a view model. Therefore, it should model the view. If the view needs a property to tell it when to hide / show a panel, add such a property to your view model.

Resources