I am animating some property using DoubleAnimation. Before animation is triggered any local or Setter changes are properly reflected in the property. After animation completes nothing seems to be able to change the value of the property. I have even tried ClearValue and InvalidateProperty as well set calling SetValue but the value leftover from animation persists. If animation is repeated, the property continues to be animated as expected so it only appears to be locked for non-animation changes.
Is there a way to rectify this behavior? I want to use the animation to change the property value but still be able to change it manually or via a Setter to anything else. I know a thing or two about Dependency Property Value Precedence but the behavior I am currently experiencing is a bit strange. I'd hate to have to use "manual animations".
EDIT: Added sample XAML + code.
<Window x:Class="WpfApplication7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
x:Name="_this"
Background="Red">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center" >
<Button Click="ToggleOnClick">Toggle!</Button>
<Button Click="SetHalfOnClick">Set to 0.5!</Button>
</StackPanel>
<TextBox DockPanel.Dock="Bottom" IsReadOnly="True" Text="{Binding ElementName=_viewbox,Path=Opacity}" />
<Viewbox x:Name="_viewbox">
<Viewbox.Style>
<Style TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsToggled,ElementName=_this,Mode=OneWay}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0.2"
Duration="0:0:0.5"
Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="1"
Duration="0:0:0.5"
Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Viewbox.Style>
<TextBlock Text="Sample!" />
</Viewbox>
</DockPanel>
</Window>
Here is the code:
using System.Windows;
namespace WpfApplication7
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1
{
public bool IsToggled
{
get { return (bool)GetValue(IsToggledProperty); }
set { SetValue(IsToggledProperty, value); }
}
public static readonly DependencyProperty IsToggledProperty = DependencyProperty.Register("IsToggled", typeof(bool), typeof(Window1), new UIPropertyMetadata(false));
public Window1()
{
InitializeComponent();
}
private void ToggleOnClick(object sender, RoutedEventArgs e)
{
IsToggled = !IsToggled;
}
private void SetHalfOnClick(object sender, RoutedEventArgs e)
{
_viewbox.Opacity = 0.5;
}
}
}
Edit 2 in response to comments:
In your example you can work around the problem by:
Setting FillBehaviour to Stop on the animation
Adding a handler in code to the Completed event:
<Storyboard Completed="FadeOut_Completed">
Finally, set the desired 'final' value in the Completed handler (either explicitly or by using the current value of the property
private void FadeOut_Completed(object sender, EventArgs e)
{
_viewbox.Opacity = _viewbox.Opacity; //this sets the DP value to the animated value
}
This works in your sample; hopefully it will work in your problem!
Original Answer
If you set the FillBehaviour property of the Storyboard to Stop (instead of the default value of HoldEnd) it will revert to the pre-animation value of the property once the animation completes. HoldEnd causes the animation to maintain its final value on the property
Update in response to comments:
As noted in the comments, the animation value will override the value set against the property when HoldEnd is specified as the FillBehaviour.
This makes it slightly tricky to set the value to something else.
I am not sure if there is a better way to achieve this, but the example below shows one way to work around it. Its hard to judge how applicable this is without a sample usage from the OP, but in this example I am animating the width of a Rectangle on load, and then resetting it to another value when a button is clicked:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Target" Storyboard.TargetProperty="Width"
From="10" To="100" Duration="0:00:01" FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Height="10" Width="10" Fill="Red" x:Name="Target"/>
<Button Grid.Row="1" Content="Resize">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Target" Storyboard.TargetProperty="Width">
<DiscreteDoubleKeyFrame Value="50" KeyTime="0:00:00" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</Window>
This works because the new animation overrides the value set in the original.
You can use UIElement.BeginAnimation with the animation parameter set to null. It will clear all animations attached to your property.
Related
We are trying to set Height and Width of the button based on the window size. We want to achieve this using Visual States. We want to set Storyboard Target to a button which is inside a DataTemplate. Directly setting target by name using StoryBoard.TargetName is not possible because of the namescope of DataTemplate. Is there any way to do this in XAML.
Refer to-
XAML:
<UserControl x:Class="ResizeSampleApp.Controls.ResizeUserControl"
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"
xmlns:local="clr-namespace:ResizeSampleApp.Controls"
mc:Ignorable="d"
SizeChanged="CurrentWindow_SizeChanged"
x:Name="DashBoard"
>
<Grid x:Name="grid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ResizeStates">
<VisualState x:Name="FirstHorizontalBreakpoint">
<Storyboard >
<DoubleAnimation To="116" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height"></DoubleAnimation>
<DoubleAnimation To="182" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width"></DoubleAnimation>
</Storyboard>
</VisualState>
<VisualState x:Name="MinSize">
<Storyboard>
<DoubleAnimation To="100" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height"></DoubleAnimation>
<DoubleAnimation To="197" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width"></DoubleAnimation>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel Orientation="Vertical">
<Grid Margin="10">
<ItemsControl Name="icTodoList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="TargetBtn" Content="{Binding Title}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</StackPanel>
</Grid>
</UserControl>
C#:
public class TodoItem
{
public string Title { get; set; }
}
public partial class ResizeUserControl : UserControl
{
public ResizeUserControl()
{
InitializeComponent();
List<TodoItem> items = new List<TodoItem>();
items.Add(new TodoItem() { Title = "Testing 1 " });
items.Add(new TodoItem() { Title = "Testing 2" });
items.Add(new TodoItem() { Title = "Testing 3" });
icTodoList.ItemsSource = items;
}
public void CurrentWindow_SizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
if (this.ActualWidth > 847)
{
VisualStateManager.GoToState(this.DashBoard, "FirstHorizontalBreakpoint", false);
}
else
{
VisualStateManager.GoToState(this.DashBoard, "MinSize", false);
}
}
}
Thanks in Advance.
It is not possible to use visual state animations to animate properties inside a DataTemplate. The DataTemplate is just a template, that is not part of the visual tree, the moment the Storyboard is defined. The template will be duplicated for each templated target e.g each item container. This means there will be multiple buttons with the name TargetBtn - when viewed from outside the template's scope. So you would have to move the animations to the DataTemplate scope, which means inside the template.
In addition to the control's visual state definitions, you should add properties to reflect the visual state as object state.
It is best practice and recommended by Microsoft Docs to do this for each state: add properties like IsMouseOver to indicate the MouseOver state or IsFocused which goes in tandem with the Focused state, just to name some framework examples.
This allows your control to express the visual state as real instance state, which can be consumed outside the context of the control's direct content - visual states are only consumable by the VisualStateManager.
You can then use this usually public read-only dependency properties to trigger e.g. a DataTrigger, which you define inside the DataTemplate to animate the template's content:
ResizeUserControl.xaml.cs
public partial class ResizeUserControl : UserControl
{
#region IsExpandedViewEnabled read-only dependency property
protected static readonly DependencyPropertyKey IsExpandedViewEnabledPropertyKey = DependencyProperty.RegisterReadOnly(
"IsExpandedViewEnabled",
typeof(bool),
typeof(ResizeUserControl),
new PropertyMetadata(default(bool)));
public static readonly DependencyProperty IsExpandedViewEnabledProperty = MyControl.IsExpandedViewEnabledPropertyKey.DependencyProperty;
public bool IsExpandedViewEnabled
{
get => (bool) GetValue(MyControl.IsExpandedViewEnabledProperty);
private set => SetValue(MyControl.IsExpandedViewEnabledPropertyKey, value);
}
#endregion IsExpandedViewEnabled read-only dependency property
public ResizeUserControl()
{
InitializeComponent();
}
#region Overrides of FrameworkElement
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (this.ActualWidth > 847)
{
VisualStateManager.GoToState(this, "FirstHorizontalBreakpoint", false);
this.IsExpandedViewEnabled = true;
}
else
{
VisualStateManager.GoToState(this, "MinSize", false);
this.IsExpandedViewEnabled = false;
}
}
}
ResizeUserControl.xaml
<UserControl>
<Grid x:Name="grid">
<StackPanel Orientation="Vertical">
<Grid Margin="10">
<ItemsControl Name="icTodoList"
ItemsSource="{Binding DataItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="TargetBtn" Height="60"
Content="{Binding Title}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IsExpandedViewEnabled}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="116" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height" />
<DoubleAnimation To="182" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="100" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Height" />
<DoubleAnimation To="197" Storyboard.TargetName="TargetBtn" Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</StackPanel>
</Grid>
</UserControl>
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 was just hoping you could help fill a gap in my WPF knowledge.
(please forgive the generic naming, not sure if it helps)
I've a custom object, MyObject, that implements INotifyPropertyChanged. It has a property called MyCustomProperty, as follows;
public int MyCustomProperty
{
get { return this._myCustomProperty; }
set
{
if (this._myCustomProperty == value)
return;
this._myCustomProperty= value;
OnPropertyChanged("MyCustomProperty");
}
}
This all works.
In my WPF app I have these 3 functions;
private void DoStuff()
{
AddItemsToCanvas();
ChangeValues();
}
private void AddItemsToCanvas()
{
DataTemplate dt = (DataTemplate)FindResource("myDataTemplate");
foreach (MyObject temp in ListOfMyObjects)
{
ContentControl cc = new ContentControl();
cc.ContentTemplate = dt;
cc.Content = temp;
myCanvas.Children.Add(cc);
}
}
private void ChangeValues()
{
// this simply changes the MyCustomPropery in each of the objects
}
The DataTemplate looks like this;
<DataTemplate x:Key="myDataTemplate">
<Canvas>
<TextBlock Name="tb_debug" Text="{Binding Path=MyCustomProperty, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="tb_debug" Storyboard.TargetProperty="(Canvas.Top)" From="0" To="350" Duration="0:0:1.6" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Canvas>
</DataTemplate>
It simply moves the textbox from the top to the bottom of the canvas when MyCustomProperty changes.
When the UserControl is Loaded, I call both AddItemsToCanvas() and ChangeValues(). The Text value updates and displays the correct value, but the Trigger doesn't fire (ie the TextBox doesn't move).
Any time after that, when I call ChangeValues() the Text updates AND the TextBox moves.
Why would the EventTrigger be failing that initial time?
thanks in advance
I'm not sure why the Binding.TargetUpdated does not get called, but it could be only called when a the target is updated once already set and not when its first set (maybe).
But you could just add another EventTrigger on TextBlock Loaded in your DataTremplate to make sure it fires on UserControl Load.
Example:
<DataTemplate x:Key="myDataTemplate">
<DataTemplate.Resources>
<Storyboard x:Key="animation" >
<DoubleAnimation Storyboard.TargetName="tb_debug" Storyboard.TargetProperty="(Canvas.Top)" From="0" To="350" Duration="0:0:1.6" />
</Storyboard>
</DataTemplate.Resources>
<Canvas>
<TextBlock Name="tb_debug" Text="{Binding Path=MyCustomProperty, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard Storyboard="{StaticResource animation}" />
</EventTrigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard Storyboard="{StaticResource animation}" />
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</Canvas>
</DataTemplate>
i am using the MVVM design pattern and do not want much code in my code behind. coding in XAML and C#.
when a user saves a new record i would like "record saved" to appear in a text Block then fade away.
this is the sort of thing i would like to work:
<TextBlock Name="WorkflowCreated" Text="Record saved">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=NewWorkflowCreated}">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="WorkflowCreated"
Storyboard.TargetProperty="(TextBlock.Opacity)"
From="1.0" To="0.0" Duration="0:0:3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</TextBlock.Triggers>
so when NewWorkflowCreated is changed in the viewmodel it would trigger the animation, unfortunately this does not work. i have also tried this:
<TextBlock Name="Message" Text="This is a test.">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="TextBlock.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="Message"
Storyboard.TargetProperty="(TextBlock.Opacity)"
From="1.0" To="0.0" Duration="0:0:3"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
any help would be much appreciated. Maybe there is away that requires code in the View model?
You're using a DataTrigger which needs to be in a style.
<Window.DataContext>
<WpfApplication2:TestViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NewWorkflowCreated}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(TextBlock.Opacity)"
From="1.0" To="0.0" Duration="0:0:3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<TextBlock Name="WorkflowCreated" Style="{StaticResource textBoxStyle}" Text="Record saved" />
<Button Content="press me" Grid.Row="1" Click="Button_Click_1"/>
</Grid>
public class TestViewModel : INotifyPropertyChanged
{
private bool _newWorkflowCreated;
public bool NewWorkflowCreated
{
get { return _newWorkflowCreated; }
set {
_newWorkflowCreated = value;
PropertyChanged(this, new PropertyChangedEventArgs("NewWorkflowCreated"));
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
This sort of UI-specific behavior should definitely be handled in the View, not the ViewModel
I would suggest looking into the TextChanged event, and see about kicking off the animation in there
Not my blog but I pretty much found what I was looking for here:
https://michaelscherf.wordpress.com/2009/02/23/how-to-trigger-an-animation-when-textblocks-text-is-changed-during-a-databinding/