I have a button as follows:
<Button x:Name ="Btn_Import" Grid.Row="33" Grid.Column="15" Grid.ColumnSpan="36" Grid.RowSpan="36" >
<Button.Template>
<ControlTemplate>
<Grid RenderTransformOrigin="0.5,0.5" x:Name="bg">
<Image x:Name ="import_image" Source="{Binding ImportBtnBaseImagePath}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="import_image" Property="Source" Value="{Binding ImportBtnOverImagePath}" />
</Trigger>
<Trigger Property="ButtonBase.IsPressed" Value ="True">
<!-- press effect -->
<Setter TargetName="bg" Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.9" ScaleY="0.9"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
<Button.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown" >
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Studio" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Completed">
<i:InvokeCommandAction Command="{Binding NavigateCommand}" CommandParameter="ImportButtonClickParmeters" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
I want this button to triger an animation on some other control to fade out for 2 seconds, and then once the animation is completed to navigate to some other view through 'NavigateCommand'. But I get the following error:
Additional information: Specified value of type
'System.Windows.Interactivity.EventTrigger' must have IsFrozen set to
false to modify.
Your issue depends on a well know bug. Unluckly I found that the common solution does not properly work in this case.
Anyway if you wish to keep your application MVVM compliant, I suggest you to create a "fake" animation, whose task is to execute a command. Of course this animation has to be the last one in your storyboard.
This is the CommandFakeAnimation code:
public class CommandFakeAnimation : AnimationTimeline
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandFakeAnimation), new UIPropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandFakeAnimation), new PropertyMetadata(null));
public CommandFakeAnimation()
{
Completed += new EventHandler(CommandAnimation_Completed);
}
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
private void CommandAnimation_Completed(object sender, EventArgs e)
{
if (Command != null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
protected override Freezable CreateInstanceCore()
{
return new CommandFakeAnimation();
}
public override Type TargetPropertyType
{
get
{
return typeof(Object);
}
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
return defaultOriginValue;
}
}
As you can see you can apply this animation to whatever dependecy property that you wish, since it does not change its value. It just execute a command when it is completed.
Now we can use the new animation in the XAML:
<Button.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2" />
<local:CommandFakeAnimation Duration="0:0:0" Command="{Binding Path=YourCommand, Mode=OneWay}"
CommandParameter="{Binding Path=YourParameter, Mode=OneWay}"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
I hope it can help you.
Related
I have a StackPanel with multiple buttons. I want all the buttons except one to trigger an animation when the user clicks on them, so in the StackPanel.Triggers I have added this code:
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource animationName}" />
</EventTrigger>
</StackPanel.Triggers>
In the particular button I have added this code:
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource anotherAnimation}" />
</EventTrigger>
</Button.Triggers>
When clicking the button, both animations begin, so it appears that the second EventTrigger is just added to the first one and not override it.
How can I override the first EventTrigger so only the second one will be triggered when clicking on that particular button?
Note: I need the answer to be in pure XAML without any code-behind involved.
EDIT: Here is the storyboard:
<Storyboard x:Key="animationName">
<DoubleAnimation
Storyboard.TargetName="PageFrame"
Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.25" />
</Storyboard>
Just use x:Key property for necessary buttons. For example:
<Window>
<Window.Resources>
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Style.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource animationName}" />
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Button Style="{StaticResource myStyle}">Styles are cool!</Button>
<Button>No Animation:)</Button>
<Button Style="{StaticResource myStyle}">Yes to animation!</Button>
</StackPanel>
</Window>
Update:
If you want to avoid use Style just for a few buttons, just create Style for all Button controls and set Style="{x:Null}" to controls where you want to avoid animation. See the following example:
<Window>
<Window.Resources>
<!--This style will be applied to all Buttons, except where Style="{x:Null}"-->
<Style TargetType="Button">
<Style.Resources>
<Storyboard x:Key="animationName">
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="0" Duration="0:0:0.25" />
</Storyboard>
</Style.Resources>
<Setter Property="Background" Value="Green"/>
<Style.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource animationName}" />
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Button Content="Yes to Animation"/>
<Button Content="No Animation:)" Style="{x:Null}"/>
<Button Content="Yes to Animation"/>
</StackPanel>
</Window>
Update 1:
you have deleted the TargetName, but I really need to set it so the animation will be applied to the correct element.
Since a style can be reused in multiple places in a WPF application, we can't reference to a UIElement from within the style. This behavior is by design.
As promised I took #RayBurns answer from this link and modified it, to answer your question. The ConditionalEventTrigger is now looking like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Markup;
namespace Trigger
{
[ContentProperty("Actions")]
public class ConditionalEventTrigger : FrameworkContentElement
{
private static readonly RoutedEvent TriggerActionsEvent = EventManager.RegisterRoutedEvent("", RoutingStrategy.Direct, typeof(EventHandler), typeof(ConditionalEventTrigger));
public RoutedEvent RoutedEvent { get; set; }
public static readonly DependencyProperty ExcludedSourceNamesProperty = DependencyProperty.Register(
"ExcludedSourceNames", typeof (List<string>), typeof (ConditionalEventTrigger), new PropertyMetadata(new List<string>()));
public List<string> ExcludedSourceNames
{
get { return (List<string>) GetValue(ExcludedSourceNamesProperty); }
set { SetValue(ExcludedSourceNamesProperty, value); }
}
public static readonly DependencyProperty ActionsProperty = DependencyProperty.Register(
"Actions", typeof (List<TriggerAction>), typeof (ConditionalEventTrigger), new PropertyMetadata(new List<TriggerAction>()));
public List<TriggerAction> Actions
{
get { return (List<TriggerAction>) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
// "Triggers" attached property
public static ConditionalEventTriggerCollection GetTriggers(DependencyObject obj) { return (ConditionalEventTriggerCollection)obj.GetValue(TriggersProperty); }
public static void SetTriggers(DependencyObject obj, ConditionalEventTriggerCollection value) { obj.SetValue(TriggersProperty, value); }
public static readonly DependencyProperty TriggersProperty = DependencyProperty.RegisterAttached("Triggers", typeof(ConditionalEventTriggerCollection), typeof(ConditionalEventTrigger), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
// When "Triggers" is set, register handlers for each trigger in the list
var element = (FrameworkElement)obj;
var triggers = (List<ConditionalEventTrigger>)e.NewValue;
foreach (var trigger in triggers)
element.AddHandler(trigger.RoutedEvent, new RoutedEventHandler((obj2, e2) =>
trigger.OnRoutedEvent(element, e2)));
}
});
// When an event fires, check the condition and if it is true fire the actions
void OnRoutedEvent(FrameworkElement element, RoutedEventArgs args)
{
var originalSender = args.OriginalSource as FrameworkElement;
if(originalSender == null) return;
DataContext = element.DataContext; // Allow data binding to access element properties
if (!ExcludedSourceNames.Any(x=>x.Equals(originalSender.Name)))
{
// Construct an EventTrigger containing the actions, then trigger it
var dummyTrigger = new EventTrigger { RoutedEvent = TriggerActionsEvent };
foreach (var action in Actions)
dummyTrigger.Actions.Add(action);
element.Triggers.Add(dummyTrigger);
try
{
element.RaiseEvent(new RoutedEventArgs(TriggerActionsEvent));
}
finally
{
element.Triggers.Remove(dummyTrigger);
}
}
}
}
public class ConditionalEventTriggerCollection: List<ConditionalEventTrigger>{}
}
It can be used in your XAML like this. Take care that all SourceNames you don´t want to be recognized on execution of your actions are inside the ExcludedSourceNames section.:
<trigger:ConditionalEventTrigger.Triggers>
<trigger:ConditionalEventTriggerCollection>
<trigger:ConditionalEventTrigger RoutedEvent="Button.Click">
<trigger:ConditionalEventTrigger.ExcludedSourceNames>
<system:String>buttonTriggeringAnotherAnimation</system:String>
</trigger:ConditionalEventTrigger.ExcludedSourceNames>
<BeginStoryboard Storyboard="{StaticResource Storyboard1}"></BeginStoryboard>
</trigger:ConditionalEventTrigger>
</trigger:ConditionalEventTriggerCollection>
</trigger:ConditionalEventTrigger.Triggers>
To give you an ready to start example here is a window:
<Window x:Class="ConditionalEventTriggerExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ConditionalEventTriggerExample"
xmlns:trigger="clr-namespace:Trigger;assembly=Trigger"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle">
<EasingColorKeyFrame KeyTime="0:0:1" Value="#FF5151FD"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard2">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle1">
<EasingColorKeyFrame KeyTime="0:0:1" Value="#FFFF7400"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<StackPanel>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="buttonTriggeringAnotherAnimation">
<BeginStoryboard Storyboard="{StaticResource Storyboard2}"/>
</EventTrigger>
</StackPanel.Triggers>
<trigger:ConditionalEventTrigger.Triggers>
<trigger:ConditionalEventTriggerCollection>
<trigger:ConditionalEventTrigger RoutedEvent="Button.Click">
<trigger:ConditionalEventTrigger.ExcludedSourceNames>
<system:String>buttonTriggeringAnotherAnimation</system:String>
</trigger:ConditionalEventTrigger.ExcludedSourceNames>
<BeginStoryboard Storyboard="{StaticResource Storyboard1}"></BeginStoryboard>
</trigger:ConditionalEventTrigger>
</trigger:ConditionalEventTriggerCollection>
</trigger:ConditionalEventTrigger.Triggers>
<Button x:Name="button" Content="Button"/>
<Button x:Name="button1" Content="Button"/>
<Button x:Name="buttonTriggeringAnotherAnimation" Content="triggering another animation"/>
<Button x:Name="button3" Content="Button"/>
<Button x:Name="button4" Content="Button"/>
<Button x:Name="button5" Content="Button"/>
<Rectangle x:Name="rectangle" Fill="#FFF4F4F5" Height="100" Stroke="Black"/>
<Rectangle x:Name="rectangle1" Fill="#FFF4F4F5" Height="100" Stroke="Black"/>
</StackPanel>
If you don´t get it to work I can upload the solution on GitHub.
I am trying to change Background of a Border when user is dragging a file on it.
I want to define the effect using XAML only.
I tried the below but the Background is not changed when dragging a file on the Border.
<Border Name="dropBorder" BorderThickness="1" AllowDrop="True">
<Border.Triggers>
<EventTrigger RoutedEvent="DragOver">
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Background">
<ColorAnimation From="Transparent" To="#FF444444" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<TextBlock Text="Drag and drop file(s) here" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10"/>
</Border>
I also tried to use DragEnter as below with no results
<EventTrigger RoutedEvent="Border.DragEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="dropBorder"
Storyboard.TargetProperty="Background"
Duration="0:0:0.5"
From="Transparent" To="#FF444444"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
I didnt quite meet your 100% requirement. I created an attached property, which I set via code-behind, so you will want to assess this. Also, moved the color animation around as you were trying to animate a brush, not a color.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="SharedBackgroundBrush" Color="Transparent" />
</Window.Resources>
<Border Name="dropBorder" BorderThickness="1" AllowDrop="True" DragEnter="DropBorder_OnDragEnter" DragLeave="DropBorder_OnPreviewDragLeave" Background="{StaticResource SharedBackgroundBrush}">
<Border.Style>
<Style>
<Style.Triggers>
<Trigger Property="wpfApplication1:DragDropHelper.IsDragOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.Target="{StaticResource SharedBackgroundBrush}" Storyboard.TargetProperty="Color">
<ColorAnimation From="Transparent" To="Yellow" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.Target="{StaticResource SharedBackgroundBrush}" Storyboard.TargetProperty="Color">
<ColorAnimation From="Yellow" To="Transparent" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="Drag and drop file(s) here" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10"/>
</Border>
</Window>
Code:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DropBorder_OnDragEnter(object sender, DragEventArgs e)
{
DragDropHelper.SetIsDragOver((DependencyObject)sender, true);
}
private void DropBorder_OnPreviewDragLeave(object sender, DragEventArgs e)
{
DragDropHelper.SetIsDragOver((DependencyObject)sender, false);
}
}
public class DragDropHelper
{
public static readonly DependencyProperty IsDragOverProperty = DependencyProperty.RegisterAttached(
"IsDragOver", typeof (bool), typeof (DragDropHelper), new PropertyMetadata(default(bool)));
public static void SetIsDragOver(DependencyObject element, bool value)
{
element.SetValue(IsDragOverProperty, value);
}
public static bool GetIsDragOver(DependencyObject element)
{
return (bool) element.GetValue(IsDragOverProperty);
}
}
}
I am trying to develop a menu that looks like windows media center menu. So I made a usercontrol as a container which will contain menuitems (each menuitems is a usercontrol).I want to hide or display the container after a click on a button.So in click event of the button, i set to true a dependencyproperty "DisappearProperty" (located in the container). But this method doesn't work.
to disappear the container, i trigger a storyboard based on "DisappearProperty" DependencyProperty. here the code :
//The container
<UserControl x:Class="MyUserControls.WMCBorder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Name="UC"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ControlTemplate x:Key="ctContainer">
<ControlTemplate.Resources>
<Storyboard x:Key="SBDisappear">
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleX"
From="1" To="4.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleY"
From="1" To="4.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="container"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Key="SBDisplay">
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleX"
From="4" To="1.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="borderZooming"
Storyboard.TargetProperty="ScaleY"
From="4" To="1.0"
Duration="0:0:0.1"/>
<DoubleAnimation Storyboard.TargetName="container"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.1"/>
</Storyboard>
</ControlTemplate.Resources>
<Border x:Name="container">
<Border.RenderTransform>
<ScaleTransform x:Name="borderZooming" ScaleX="0.1" ScaleY="0.1"/>
</Border.RenderTransform>
<Grid>
<ContentPresenter />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=DisappearProperty}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SBDisappear}"/>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DisplayProperty}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SBDisplay}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
</Grid>
//Code behind of the container
public partial class WMCBorder : UserControl
{
public WMCBorder()
{
InitializeComponent();
}
public bool Disappear
{
get { return (bool)GetValue(DisappearProperty); }
set { SetValue(DisappearProperty, value); }
}
// Using a DependencyProperty as the backing store for Disappear. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisappearProperty =
DependencyProperty.Register("Disappear", typeof(bool), typeof(WMCBorder), new PropertyMetadata(false));
public bool Display
{
get { return (bool)GetValue(DisplayProperty); }
set { SetValue(DisplayProperty, value); }
}
// Using a DependencyProperty as the backing store for Disappear. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayProperty =
DependencyProperty.Register("Display", typeof(bool), typeof(WMCBorder), new PropertyMetadata(false));
}
//The click event of the Hide button
private void Button_Click(object sender, RoutedEventArgs e)
{
wmcb.Disappear = true;
}
//The click event of the Hide button
private void Button2_Click(object sender, RoutedEventArgs e)
{
wmcb.Display= true;
}
Thanks in advance.
As I received no response so far, so I simplified the question on this post
I have a data object -- a custom class called Notification -- that exposes a IsCritical property. The idea being that if a notification will expire, it has a period of validity and the user's attention should be drawn towards it.
Imagine a scenario with this test data:
_source = new[] {
new Notification { Text = "Just thought you should know" },
new Notification { Text = "Quick, run!", IsCritical = true },
};
The second item should appear in the ItemsControl with a pulsing background. Here's a simple data template excerpt that shows the means by which I was thinking of animating the background between grey and yellow.
<DataTemplate DataType="Notification">
<Border CornerRadius="5" Background="#DDD">
<Border.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Background.Color"
From="#DDD" To="#FF0" Duration="0:0:0.7"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</DataTemplate>
What I'm unsure about is how to make this animation conditional upon the value of IsCritical. If the bound value is false, then the default background colour of #DDD should be maintained.
The final part of this puzzle is... DataTriggers. All you have to do is add one DataTrigger to your DataTemplate, bind it to IsCritical property, and whenever it's true, in it's EnterAction/ExitAction you start and stop highlighting storyboard. Here is completely working solution with some hard-coded shortcuts (you can definitely do better):
Xaml:
<Window x:Class="WpfTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Notification Sample" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="NotificationTemplate">
<Border Name="brd" Background="Transparent">
<TextBlock Text="{Binding Text}"/>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsCritical}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="highlight">
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"
Storyboard.TargetName="brd"
From="#DDD" To="#FF0" Duration="0:0:0.5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="highlight"/>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding Notifications}"
ItemTemplate="{StaticResource NotificationTemplate}"/>
<Button Grid.Row="1"
Click="ToggleImportance_Click"
Content="Toggle importance"/>
</Grid>
</Window>
Code behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace WpfTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new NotificationViewModel();
}
private void ToggleImportance_Click(object sender, RoutedEventArgs e)
{
((NotificationViewModel)DataContext).ToggleImportance();
}
}
public class NotificationViewModel
{
public IList<Notification> Notifications
{
get;
private set;
}
public NotificationViewModel()
{
Notifications = new List<Notification>
{
new Notification
{
Text = "Just thought you should know"
},
new Notification
{
Text = "Quick, run!",
IsCritical = true
},
};
}
public void ToggleImportance()
{
if (Notifications[0].IsCritical)
{
Notifications[0].IsCritical = false;
Notifications[1].IsCritical = true;
}
else
{
Notifications[0].IsCritical = true;
Notifications[1].IsCritical = false;
}
}
}
public class Notification : INotifyPropertyChanged
{
private bool _isCritical;
public string Text { get; set; }
public bool IsCritical
{
get { return _isCritical; }
set
{
_isCritical = value;
InvokePropertyChanged("IsCritical");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void InvokePropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
Hope this helps :).
What I would do is create two DataTemplates and use a DataTemplateSelector. Your XAML would be something like:
<ItemsControl
ItemsSource="{Binding ElementName=Window, Path=Messages}">
<ItemsControl.Resources>
<DataTemplate
x:Key="CriticalTemplate">
<Border
CornerRadius="5"
Background="#DDD">
<Border.Triggers>
<EventTrigger
RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Background.Color"
From="#DDD"
To="#FF0"
Duration="0:0:0.7"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<TextBlock
Text="{Binding Path=Text}" />
</Border>
</DataTemplate>
<DataTemplate
x:Key="NonCriticalTemplate">
<Border
CornerRadius="5"
Background="#DDD">
<TextBlock
Text="{Binding Path=Text}" />
</Border>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<this:CriticalItemSelector
Critical="{StaticResource CriticalTemplate}"
NonCritical="{StaticResource NonCriticalTemplate}" />
</ItemsControl.ItemTemplateSelector>
And the DataTemplateSelector would be something similar to:
class CriticalItemSelector : DataTemplateSelector
{
public DataTemplate Critical
{
get;
set;
}
public DataTemplate NonCritical
{
get;
set;
}
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
Message message = item as Message;
if(item != null)
{
if(message.IsCritical)
{
return Critical;
}
else
{
return NonCritical;
}
}
else
{
return null;
}
}
}
This way, WPF will automatically set anything that is critical to the template with the animation, and everything else will be the other template. This is also generic in that later on, you could use a different property to switch the templates and/or add more templates (A Low/Normal/High importance scheme).
It seems to be an odity with ColorAnimation, as it works fine with DoubleAnimation. You need to explicity specify the storyboards "TargetName" property to work with ColorAnimation
<Window.Resources>
<DataTemplate x:Key="NotificationTemplate">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsCritical}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Background.Color"
Storyboard.TargetName="border"
From="#DDD" To="#FF0" Duration="0:0:0.7"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
<Border x:Name="border" CornerRadius="5" Background="#DDD" >
<TextBlock Text="{Binding Text}" />
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl x:Name="NotificationItems" ItemsSource="{Binding}" ItemTemplate="{StaticResource NotificationTemplate}" />
</Grid>
Here's a solution that only starts the animation when the incoming property update is a certain value. Useful if you want to draw the user's attention to something with the animation, but afterwards the UI should return to it's default state.
Assuming IsCritical is bound to a control (or even an invisible control) you add NotifyOnTargetUpdated to the binding and tie an EventTrigger to the Binding.TargetUpdated event. Then you extend the control to only fire the TargetUpdated event when the incoming value is the one you are interested in. So...
public class CustomTextBlock : TextBlock
{
public CustomTextBlock()
{
base.TargetUpdated += new EventHandler<DataTransferEventArgs>(CustomTextBlock_TargetUpdated);
}
private void CustomTextBlock_TargetUpdated(object sender, DataTransferEventArgs e)
{
// don't fire the TargetUpdated event if the incoming value is false
if (this.Text == "False") e.Handled = true;
}
}
and in the XAML file ..
<DataTemplate>
..
<Controls:CustomTextBlock x:Name="txtCustom" Text="{Binding Path=IsCritical, NotifyOnTargetUpdated=True}"/>
..
<DataTemplate.Triggers>
<EventTrigger SourceName="txtCustom" RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>..</Storyboard>
</BeginStoryboard>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You use style triggers in this case. (I'm doing this from memory so there might be some bugs)
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCritical}" Value="true">
<Setter Property="Triggers">
<Setter.Value>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Background.Color"
From="#DDD" To="#FF0" Duration="0:0:0.7"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
I'm trying to animate the ScaleY property of a LayoutTransform based on a DataTrigger bound to a boolean on my ViewModel class. The animation happens when the value is first seen to be false by the DataTrigger (when the application first starts) and when i first change it to true in a checkbox's checked event but not when i set it to false in the same checkbox's unchecked event.
A simplified version of what i'm doing is listed below.
The ViewModel class is very simple, containing a single boolean DependencyProperty called Selected.
public class VM : DependencyObject
{
public bool Selected
{
get { return (bool)GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for Selected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedProperty =
DependencyProperty.Register("Selected", typeof(bool), typeof(VM), new UIPropertyMetadata(false));
}
The Window.xaml contains a button and a checkbox. When the checkbox is checked, i set the ViewModel's 'Selected' property to true and false when it is unchecked. Here's the code for both the xaml and it's code-behind.
<Window x:Class="DataTriggers.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:y="clr-namespace:DataTriggers"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<y:VM x:Key="VM"/>
<Style TargetType="Button" x:Key="but">
<Style.Triggers>
<DataTrigger Binding="{Binding Selected}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="0"
Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Selected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="1"
Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Button Style="{StaticResource but}" DataContext="{StaticResource VM}">
<Button.LayoutTransform>
<ScaleTransform></ScaleTransform>
</Button.LayoutTransform>
me
</Button>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"/>
</StackPanel>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
VM vm = this.FindResource("VM") as VM;
vm.Selected = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
VM vm = this.FindResource("VM") as VM;
vm.Selected = false;
}
}
I know that the DataTrigger fires when the property is false because if i change the DoubleAnimation to a simple Setter operating on the Opacity property then i see the correct results. So it would seem to be a problem with how I'm using the DoubleAnimation.
Any help would be appriciated.
This is ODD behavior but i decided to refactor the 'False' case into the DataTrigger's ExitActions like this -
<DataTrigger Binding="{Binding Selected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="1"
Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
To="0"
Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
That works as intended. I don't know what the difference is between the two cases but at least it's an answer.