Blink an image from ViewModel - wpf

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>

Related

How to properly execute a command upon animation is completed?

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.

TextBox readonly "on/off" between "double click and lost focus events" in wpf

I have a control like below xaml with Read only enabled.
<TextBox Text="{Binding Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="Transparent" IsReadOnly="True" BorderThickness="0" TextWrapping="Wrap" >
Now when i double click this text box , i should be able to enter a text.
Readonly property should become false
If I move to another item in the window other than this text box , then the text box should become readonly again.
I am trying to do it with Triggers. but not getting the right hint . Can anyone help me here ?
You can make this with 2 events, MouseDoubleClick and LostFocus
<Grid>
<TextBox IsReadOnly="True"
MouseDoubleClick="TextBox_MouseDoubleClick"
LostFocus="TextBox_LostFocus"/>
</Grid>
In you procedural code:
private void TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
TextBox textBox = sender as TextBox;
textBox.IsReadOnly = false;
//textBox.CaretIndex = textBox.Text.Count();
textBox.SelectAll();
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
textBox.IsReadOnly = true;
}
You can use Style and EventTrigger to do that
<Window xmlns:sys="clr-namespace:System;assembly=mscorlib" ...>
<Window.Resource>
<Style x:Key="MyTextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<EventTrigger RoutedEvent="LostFocus">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0"
Storyboard.TargetProperty="(TextBox.IsReadOnly)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="MouseDoubleClick">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0"
Storyboard.TargetProperty="(TextBox.IsReadOnly)">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>False</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resource>
...
<TextBox Style="{StaticResource MyTextBoxStyle}" .../>
</Window>
You can use System.Windows.Interactivity assembly (msdn) to do that.
First: create helper class to set properties:
public class SetterAction : TriggerAction<DependencyObject>
{
public SetterAction()
{
Setters = new List<Setter>();
}
public List<Setter> Setters { get; set; }
protected override void Invoke(object parameter)
{
foreach (var item in Setters)
{
AssociatedObject.SetValue(item.Property, item.Value);
}
}
}
XAML:
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent" IsReadOnly="True" BorderThickness="0" TextWrapping="Wrap"
Height="30" Width="200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:EventTrigger.Actions>
<local:SetterAction>
<local:SetterAction.Setters>
<Setter Property="TextBox.IsReadOnly" Value="False" />
<Setter Property="TextBox.Background" Value="Green" />
</local:SetterAction.Setters>
</local:SetterAction>
</i:EventTrigger.Actions>
</i:EventTrigger>
<i:EventTrigger EventName="LostFocus">
<i:EventTrigger.Actions>
<local:SetterAction>
<local:SetterAction.Setters>
<Setter Property="TextBox.IsReadOnly" Value="True" />
<Setter Property="TextBox.Background" Value="Red" />
</local:SetterAction.Setters>
</local:SetterAction>
</i:EventTrigger.Actions>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Where i is:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
The best answer is in the form of Attached Dependency Property. Usage:
xmlns:e="clr-namespace:Extensions"
<TextBox e:TextBoxExtensions.IsEditableOnlyOnDoubleClick="True"/>
#nullable enable
namespace Extensions;
public static class TextBoxExtensions
{
#region IsEditableOnlyOnDoubleClick
public static readonly DependencyProperty IsEditableOnlyOnDoubleClickProperty =
DependencyProperty.RegisterAttached(
nameof(IsEditableOnlyOnDoubleClickProperty).Replace("Property", string.Empty),
typeof(bool),
typeof(TextBoxExtensions),
new PropertyMetadata(false, OnIsEditableOnlyOnDoubleClickChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsEditableOnlyOnDoubleClick(DependencyObject element)
{
return (bool)element.GetValue(IsEditableOnlyOnDoubleClickProperty);
}
public static void SetIsEditableOnlyOnDoubleClick(DependencyObject element, bool value)
{
element.SetValue(IsEditableOnlyOnDoubleClickProperty, value);
}
private static void OnIsEditableOnlyOnDoubleClickChanged(
DependencyObject element,
DependencyPropertyChangedEventArgs args)
{
if (element is not TextBox textBox)
{
throw new ArgumentException($"{nameof(element)} should be {nameof(TextBox)}.");
}
if (args.OldValue is true)
{
textBox.MouseDoubleClick -= TextBox_MouseDoubleClick;
textBox.LostFocus -= TextBox_LostFocus;
textBox.IsReadOnly = false;
}
if (args.NewValue is true)
{
textBox.MouseDoubleClick += TextBox_MouseDoubleClick;
textBox.LostFocus += TextBox_LostFocus;
textBox.IsReadOnly = true;
}
}
private static void TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not TextBox textBox)
{
return;
}
textBox.IsReadOnly = false;
textBox.SelectAll();
}
private static void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (sender is not TextBox textBox)
{
return;
}
textBox.IsReadOnly = true;
}
#endregion
}

How to clear the contents of a PasswordBox when login fails without databinding?

I have a wpf application and I am following the mvvm pattern carefully for reasons beyond my control. I do not want to databind to my PasswordBox for security reasons beyond my control. How do I clear the contents of the password box when the login fails? I would prefer a way to do so in xaml.
You can create your attached DependencyProperty and use it as a XAML or in code. Example:
Listing of PasswordBehaviors:
public static class PasswordBehaviors
{
public static void SetIsClear(DependencyObject target, bool value)
{
target.SetValue(IsClearProperty, value);
}
public static readonly DependencyProperty IsClearProperty =
DependencyProperty.RegisterAttached("IsClear",
typeof(bool),
typeof(PasswordBehaviors),
new UIPropertyMetadata(false, OnIsClear));
private static void OnIsClear(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
PasswordBox MyPasswordBox = sender as PasswordBox;
if (MyPasswordBox != null)
{
MyPasswordBox.Clear();
}
}
}
}
Using with EventTrigger:
<EventTrigger SourceName="Clear" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MyPasswordBox" Storyboard.TargetProperty="(local:PasswordBehaviors.IsClear)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
Using with DataTrigger (in Style/DataTemplate/etc):
<DataTrigger Binding="{Binding ElementName=LoginElementFailed, Path=Status), Mode=OneWay}" Value="True">
<Setter Property="(local:PasswordBehaviors.IsClear)" Value="True" />
</DataTrigger>
Using with Trigger (in Style):
<Trigger Property="LoginFailed.IsChecked" Value="True">
<Setter Property="(local:PasswordBehaviors.IsClear)" Value="True" />
</Trigger>
Using behind code:
private void Clear_Click(object sender, RoutedEventArgs e)
{
PasswordBehaviors.SetIsClear(MyPasswordBox, true);
}
Copmlete example:
XAML
<Window x:Class="ClearPasswordBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearPasswordBox"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.Triggers>
<EventTrigger SourceName="Clear" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MyPasswordBox" Storyboard.TargetProperty="(local:PasswordBehaviors.IsClear)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger SourceName="ResetClear" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MyPasswordBox" Storyboard.TargetProperty="(local:PasswordBehaviors.IsClear)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>False</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<PasswordBox Name="MyPasswordBox" local:PasswordBehaviors.IsClear="False" Width="100" Height="30" />
<Button Name="Clear" Width="100" Height="30" HorizontalAlignment="Right" Content="Clear" />
<Button Name="ResetClear" Width="100" Height="30" HorizontalAlignment="Left" Content="ResetClear" />
</Grid>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//private void Clear_Click(object sender, RoutedEventArgs e)
//{
// PasswordBehaviors.SetIsClear(MyPasswordBox, true);
//}
//private void ResetClear_Click(object sender, RoutedEventArgs e)
//{
// PasswordBehaviors.SetIsClear(MyPasswordBox, false);
//}
}
public static class PasswordBehaviors
{
public static void SetIsClear(DependencyObject target, bool value)
{
target.SetValue(IsClearProperty, value);
}
public static readonly DependencyProperty IsClearProperty =
DependencyProperty.RegisterAttached("IsClear",
typeof(bool),
typeof(PasswordBehaviors),
new UIPropertyMetadata(false, OnIsClear));
private static void OnIsClear(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
PasswordBox MyPasswordBox = sender as PasswordBox;
if (MyPasswordBox != null)
{
MyPasswordBox.Clear();
}
}
}
}

WPF DataTrigger on any data (binding) change

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".

WPF Frame in separate thread?

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;
}
}
}

Resources