I have an application where when a person types or selects a listbox there's a portion of the screen that dynamically updates to a new view.
The problem is since WPF runs everything in a single thread the displaying of the view can interfer with typing or navigating making the app less responsive. What i'd like to do is run the view portion in a different thread.
My first thought was to use a window running on a different thread, but more than being something of a hack there's the problem of the window losing focus and being placed behind the mainwindow when the mainwindow is clicked. I could make it topmost but I also need to place other windows in front of it.
So what's the best way to achieve this, can I place the view in a frame and run it in a different thread?
You can load / generate the data in a backround thread and then update the UI using Dispatcher.BeginInvoke.
I would propose you use the Visibility property of this piece of the screen that you want to make appear and use a trigger to set it from Invisible or Collapsed to Visible whenever the user types or selecs. Or you can animate the Opacity property to produce a cool fading effect ;-) I will add some code to illustrate the point.
EDIT: a time consuming backgroundtask, like File operations, can be accomplished using a BackgroundWorker
<Window x:Class="VisibleOnTypingSpike.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">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Name="TypingSnooper"
Visibility="{Binding TypingSnooperVisibility}">
You are typing!</Label>
<Label>
<Label.Style>
<Style>
<Setter Property="Label.Opacity" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasListBoxNewSelection}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard >
<Storyboard>
<DoubleAnimation From="0" To="1"
Duration="0:0:1"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard >
<Storyboard>
<DoubleAnimation From="1" To="0"
Duration="0:0:1"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
You selected!
</Label>
</StackPanel>
<TextBox TextChanged="TextBox_TextChanged"></TextBox>
<ListBox Name="SimpleListBox"
SelectionChanged="SimpleListBox_SelectionChanged">
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
</ListBox>
</StackPanel>
using System.Windows;
using System.Windows.Controls;
namespace VisibleOnTypingSpike
{
public partial class Window1 : Window
{
public Visibility TypingSnooperVisibility
{
get { return (Visibility)GetValue(TypingSnooperVisibilityProperty); }
set { SetValue(TypingSnooperVisibilityProperty, value); }
}
public static readonly DependencyProperty TypingSnooperVisibilityProperty =
DependencyProperty.Register("TypingSnooperVisibility",
typeof(Visibility),
typeof(Window1),
new UIPropertyMetadata(System.Windows.Visibility.Collapsed));
public bool HasListBoxNewSelection
{
get { return (bool)GetValue(HasListBoxNewSelectionProperty); }
set { SetValue(HasListBoxNewSelectionProperty, value); }
}
public static readonly DependencyProperty HasListBoxNewSelectionProperty =
DependencyProperty.Register("HasListBoxNewSelection",
typeof(bool),
typeof(Window1),
new UIPropertyMetadata(false));
public Window1()
{
InitializeComponent();
DataContext = this;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textbox = (TextBox) sender;
if (textbox.Text.Length > 0) TypingSnooperVisibility = Visibility.Visible;
else TypingSnooperVisibility = Visibility.Hidden;
}
private void SimpleListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
HasListBoxNewSelection = true;
HasListBoxNewSelection = false;
}
}
}
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 want to give some feedback to users when they click on a button, which starts a request that can be long.
I'm using WPF with mvvm and I'd like to start blink the clicked image.
This is the XAML code:
<Button Style="{DynamicResource BtnToolBar}" Command="{Binding refreshAll}">
<Image x:Name="imgUpd" Style="{DynamicResource ImageStyleUpd}" ToolTip="{StaticResource UpdateData}"/>
</Button>
I'd like something like:
isBlinking="{Binding isBlinking}"
Does it exist? How can I make a blinking image from the ViewModel? Is it possible?
EDIT: I have written this with the solution I have found.
You can use viewmodel to start blinking. To do what you want, you need to:
Add new DataTrigger to your ImageStyleUpd style
Bind it to your isBlinking property with "True" value
In the trigger you can animate your image however you want (for example, change Opacity of the image)
Example
<Style x:Key="ImageStyleUpd" TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsBlinking}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="blinking">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetProperty="Opacity" AutoReverse="True"
To="0.5" Duration="0:0:0.5">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="blinking"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Hope, it helps.
Blinking is typically an animation in the view, which can be started/stopped by IsBlinking property in viewmodel. You can achieve blinking effect by varying DropShadowEffect (smooth blinking) or by a simple switching of two brushes:
<DataTrigger Binding="{Binding IsBlinking}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="blinking">
<Storyboard RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="item"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame Value="Red" KeyTime="0:0:0"/>
<DiscreteObjectKeyFrame Value="White" KeyTime="0:0:0.3"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.5"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="blinking"/>
</DataTrigger.ExitActions>
</DataTrigger>
item - is some visual which Background (or Foreground/Fill, etc.) you want to animate.
<!-- to example path, use Storyboard.TargetProperty="Fill" -->
<Path x:Name="item" Fill="SomeDefaultNonBlinkingBrush" ... />
I like to do this kind of stuff in a behavior, it is reusable and you can set this property on any UIElement.
public static class FlickrBehavior
{
#region IsFlickering
public static bool GetIsFlickering(UIElement element)
{
return (bool)element.GetValue(IsFlickeringProperty);
}
public static void SetIsFlickering(UIElement element, bool value)
{
element.SetValue(IsFlickeringProperty, value);
}
public static readonly DependencyProperty IsFlickeringProperty =
DependencyProperty.RegisterAttached("IsFlickering", typeof(bool), typeof(FlickrBehavior), new UIPropertyMetadata(false, OnIsFlickeringChanged));
static void OnIsFlickeringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
StartAnimation(d as UIElement);
else
StopAnimation(d as UIElement);
}
private static void StartAnimation(UIElement element)
{
DoubleAnimation da = new DoubleAnimation();
da.From = 1;
da.To = 0;
da.Duration = new Duration(TimeSpan.FromSeconds(2));
da.AutoReverse = true;
da.RepeatBehavior = RepeatBehavior.Forever;
element.BeginAnimation(UIElement.OpacityProperty, da);
}
private static void StopAnimation(UIElement element)
{
element.BeginAnimation(UIElement.OpacityProperty, null);
}
#endregion
}
Similar to #Novitchi's answer, I would also like to create a behaviour with an attached property. But I will attach the behaviour to the mouse click:
So you can create your behaviour as below:
public static class BlinkingBehaviour
{
public static bool GetIsBlinkingWhenClick(UIElement element)
{
return (bool)element.GetValue(IsBlinkingWhenClickProperty);
}
public static void SetIsBlinkingWhenClick(UIElement element, bool value)
{
element.SetValue(IsBlinkingWhenClickProperty, value);
}
public static readonly DependencyProperty IsBlinkingWhenClickProperty =
DependencyProperty.RegisterAttached(
"IsBlinkingWhenClick",
typeof(bool),
typeof(BlinkingBehaviour),
new FrameworkPropertyMetadata(false, OnIsBlinkingWhenClickChanged));
static void OnIsBlinkingWhenClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
(d as UIElement).PreviewMouseLeftButtonDown -= BlinkingWhenClickBehavior_PreviewMouseLeftButtonDown;
(d as UIElement).PreviewMouseLeftButtonDown += BlinkingWhenClickBehavior_PreviewMouseLeftButtonDown;
}
else
{
(d as UIElement).PreviewMouseLeftButtonDown -= BlinkingWhenClickBehavior_PreviewMouseLeftButtonDown;
}
}
static void BlinkingWhenClickBehavior_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DoubleAnimation blink = new DoubleAnimation() {
To = 1,
From = 0,
Duration = TimeSpan.FromMilliseconds(200) };
(sender as UIElement).BeginAnimation(UIElement.OpacityProperty, blink);
}
}
Then in your XAML, you can attach it to your image:
<Window x:Class="YourNameSpace.YourWindowClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNameSpace"
<Button ...>
<Image local:BlinkingBehaviour.IsBlinkingWhenClick="True" .../>
</Button>
</Window>
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 want to begin a storyboard, every time my Image source changes.
I have implemented INotifyPropertyChanged.
Can anyone help me achieve this?
Thanks,
<Image Name="pic" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding ElementName=uc, Path=Image}">
<Image.Resources>
<Storyboard x:Key="picStory" x:Name="picStory">
<DoubleAnimation
Storyboard.TargetProperty="(Image.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"
From="0" To="20" Duration="0:0:0.7" />
<DoubleAnimation Storyboard.TargetProperty="(Image.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" From="100" To="0" Duration="0:0:0.7" />
</Storyboard>
</Image.Resources>
<Image.Style>
<Style TargetType="{x:Type Image}" BasedOn="{StaticResource {x:Type Image}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Source}">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource picStory}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
<Image.RenderTransform>
<TransformGroup>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
Code bound to "uc":
private BitmapImage image;
public BitmapImage Image
{
get { return image; }
set
{
image = value;
OnPropertyChanged("Image");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
you can achieve this in another simple way by building a very basic custom control, which inherits from Image.
Here the code for "MyImage":
public class MyImage : Image
{
public static readonly RoutedEvent ImageUpdatedEvent =
EventManager.RegisterRoutedEvent("ImageUpdated", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyImage));
public event RoutedEventHandler ImageUpdated
{
add { this.AddHandler(ImageUpdatedEvent, value); }
remove { this.RemoveHandler(ImageUpdatedEvent, value); }
}
public static readonly DependencyProperty MyImageSourceProperty = DependencyProperty.Register(
"MyImageSource",
typeof(ImageSource),
typeof(MyImage),
new PropertyMetadata(null, new PropertyChangedCallback(OnMyImageSourceChanged)));
public ImageSource MyImageSource
{
get { return (ImageSource)GetValue(MyImageSourceProperty); }
set
{
Source = value;
SetValue(MyImageSourceProperty, value);
}
}
private static void OnMyImageSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
MyImage img = obj as MyImage;
img.Source = args.NewValue as ImageSource;
img.RaiseEvent(new RoutedEventArgs(ImageUpdatedEvent));
}
}
The MyImage control has it's own image source property and an own routed event called "ImageUpdated", which will later cause the storyboard to be triggerd. I have simplified your image code:
<Button Click="Button_Click" Grid.Row="0">Set Image through view model</Button>
<local:MyImage Grid.Row="1" x:Name="pic" MyImageSource="{Binding MySource}">
<Image.Triggers>
<EventTrigger RoutedEvent="local:MyImage.ImageUpdated">
<BeginStoryboard >
<Storyboard >
<DoubleAnimation Storyboard.TargetProperty="(Image.Opacity)" From="0" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</local:MyImage>
The button sets a new value for the image source property of the bound viewmodel, which implements INotifyPropertyChanged:
private void Button_Click(object sender, RoutedEventArgs e)
{
int randomValue = new Random(DateTime.Now.Second).Next(0, 2);
if (randomValue == 0)
{
_viewModel.MySource = new BitmapImage(new Uri(#"test.bmp", UriKind.Relative));
}
else
{
_viewModel.MySource = new BitmapImage(new Uri(#"test2.bmp", UriKind.Relative));
}
}
The setter in the viewmodel updates the MyImage with property changed pattern:
public ImageSource MySource
{
get { return _mySource; }
set
{
_mySource = value;
RaisePropertyChanged("MySource");
}
}
In my example, the opacity property is animated.
Hope this was helpful
Jan
You have no Value-tag defined in your DataTrigger.
The DataTrigger is listening to updates to find the value you define in the Value-Tag - since you haven't set it - it defaults to null (and I'm guessing your image is never null).
If you want it to fire everytime it changes - just put a valueconverter in the Binding tag that always returns True and set Value="True".
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.