In my view model, I have a timer that needs to make a border background blink every 5 minutes.
The border in my view:
<Border Name="btnBorder" Grid.Row="0" Grid.Column="0" Opacity="1" CornerRadius="10,10,0,0">
<Border.Style>
<Style TargetType="Border">
<Style.Setters>
<Setter Property="Background" Value="#e2e2e2"></Setter>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ViewEventTrigger}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="00:00:00.000" Value="#e2e2e2"/>
<EasingColorKeyFrame KeyTime="00:00:00.500" Value="#163f6b"/>
<EasingColorKeyFrame KeyTime="00:00:01.000" Value="#e2e2e2"/>
<EasingColorKeyFrame KeyTime="00:00:01.500" Value="#163f6b"/>
<EasingColorKeyFrame KeyTime="00:00:02.000" Value="#e2e2e2"/>
<EasingColorKeyFrame KeyTime="00:00:02.500" Value="#163f6b"/>
<EasingColorKeyFrame KeyTime="00:00:03.000" Value="#e2e2e2"/>
<EasingColorKeyFrame KeyTime="00:00:03.500" Value="#163f6b"/>
<EasingColorKeyFrame KeyTime="00:00:04.000" Value="#e2e2e2"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Property from view model:
private string _viewEventTrigger = "";
public string ViewEventTrigger
{
get => _viewEventTrigger ?? (_viewEventTrigger = "");
private set
{
if (_viewEventTrigger == value)
return;
_viewEventTrigger = value;
OnPropertyChanged();
}
}
And the method that needs to trigger the... well, trigger:
private void ShowInfocenterIfAnyItinirary(object sender, ElapsedEventArgs e)
{
ViewEventTrigger = "";
ViewEventTrigger = "True";
}
I testet the Storyboard by triggering it with MouseEnter. But I am not able to make it work by binding it to my property.
EDIT:
I set the datacontext like this:
d:DataContext="{d:DesignInstance local:ItineraryViewModel}"
Also, I have lots og other databindings that work fine, like data to show and commands for buttons. But I just can't make the trigger execute the storybord.
The method ShowInfocenterIfAnyItinirary() is executed by a timer, started in the viewmodel.
Timer code:
private readonly Timer _timer = new Timer();
public ItineraryViewModel()
{
_timer.Interval = 5000;
_timer.Elapsed += ShowInfocenterIfAnyItinirary;
_timer.Start();
}
EDIT 2:
I have delayed when the animation should start when the program runs. I have discovered that the animation DOES run, but only once. I did not see it before, because the window starts minimized.
Why does it only run once, no matter how many times the trigger event happens?
This only sets the design time data context:
d:DataContext="{d:DesignInstance local:ItineraryViewModel}"
This won't have any effect when you actualy run the application.
You should set the DataContext property of the view to an instance of your view model:
<Window ...>
<Window.DataContext>
<local:ItineraryViewModel />
</Window.DataContext>
I ended up solving the problem like this:
private void ShowInfocenterIfAnyItinirary(object sender, ElapsedEventArgs e)
{
if (Items.Count <= 0) return;
GlobalEvents.TriggerShowMainWindowEvent();
ViewEventTrigger = "True";
Task.Run(async () =>
{
Thread.Sleep(4000);
ViewEventTrigger = "False";
});
}
This stops the animation, instead of leaving it paused at the end point.
Related
I am using a datatrigger to start an animation whenever a property TargetValue in my viewmodel is set to a specific value Value1.
But I don't want this animation to happen during program startup. What is the best way to prevent the animation to run at program start?
Part of the view model:
public enum TargetValue
{
Value1,
Value2,
}
public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
public TargetValue _targetValue = TargetValue.Value1;
public TargetValue TargetValue
{
get => _targetValue;
set => SetProperty(ref _targetValue, value);
}
Part of the xaml:
<Border Background="Green" >
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding TargetValue}" Value="Value1">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.Opacity)">
<LinearDoubleKeyFrame KeyTime="00:00:0" Value="0.5"/>
<LinearDoubleKeyFrame KeyTime="00:00:3" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Part of the MainWindow.cs:
public partial class MainWindow : Window
{
MainWindowViewModel myViewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = myViewModel;
}
Add another property to your viewmodel. For example a bool DoAnimation.
Make that false by default.
Use this to drive your animation instead of TargetValue and compare it to a value of true.
In the setter of Value1 you can call a method encapsulating your logic. Which is presumably to check if the value changes to "Value1" and to set DoAnimation to True when that happens.
When you translate your data from your model into the viewmodel, set DoAnimation to false. Do this before you present it to the ui.
You could just put that logic in the constructor.
myViewModel.DoAnimation=false;
DataContext = myViewModel;
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>
How can I highlight other pieces (columns, bars etc.) in a chart created with wpf toolkit. I am using a control template to style my own chart. So far I used a trigger to get a fading effect on the element on which the mouse is residing. I want to invert this; to fade other elements (a popular charting visual gimmick) on to which mouse is not pointing. Following image shows the selected column Faded, I want it to be the other way around.
Just set the default value to faded and use the trigger to bring it up to full opacity. You have done some other styling but here is an example based on the default style:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point>1,20</Point>
<Point>2,40</Point>
<Point>3,30</Point>
</PointCollection>
<Style x:Key="dimEffectStyle" TargetType="{x:Type charting:ColumnDataPoint}" BasedOn="{StaticResource {x:Type charting:ColumnDataPoint}}">
<Setter Property="Opacity" Value="0.25"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.25" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<charting:Chart>
<charting:ColumnSeries
Title="A"
ItemsSource="{StaticResource sampleData}"
IndependentValueBinding="{Binding X}"
DependentValueBinding="{Binding Y}"
DataPointStyle="{StaticResource dimEffectStyle}"
/>
</charting:Chart>
</Grid>
Edit:
If you want to change all the other data points except the data point the mouse is over, that is a bit harder and can't be done simply by restyling the controls. But you can create your own series control that has that capability. Here is a chart with an unstyled column series class called MouseNotOverColumnSeries with a new MouseNotOverOpacity property:
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point>1,20</Point>
<Point>2,40</Point>
<Point>3,30</Point>
</PointCollection>
</Grid.Resources>
<charting:Chart Name="chart1">
<local:MouseNotOverColumnSeries
Title="A"
ItemsSource="{StaticResource sampleData}"
IndependentValueBinding="{Binding X}"
DependentValueBinding="{Binding Y}"
MouseNotOverOpacity="0.5"
/>
</charting:Chart>
Here is the MouseNotOverColumnSeries class:
public class MouseNotOverColumnSeries : ColumnSeries
{
public double MouseNotOverOpacity { get; set; }
protected override void OnDataPointsChanged(IList<DataPoint> newDataPoints, IList<DataPoint> oldDataPoints)
{
base.OnDataPointsChanged(newDataPoints, oldDataPoints);
foreach (var dataPoint in oldDataPoints)
{
dataPoint.MouseEnter -= new MouseEventHandler(dataPoint_MouseEnter);
dataPoint.MouseLeave -= new MouseEventHandler(dataPoint_MouseLeave);
}
foreach (var dataPoint in newDataPoints)
{
dataPoint.MouseEnter += new MouseEventHandler(dataPoint_MouseEnter);
dataPoint.MouseLeave += new MouseEventHandler(dataPoint_MouseLeave);
}
}
void dataPoint_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
foreach (var dataPoint in ActiveDataPoints)
if (e.OriginalSource != dataPoint) dataPoint.Opacity = MouseNotOverOpacity;
}
void dataPoint_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
foreach (var dataPoint in ActiveDataPoints)
dataPoint.Opacity = 1;
}
}
We just pay attention to when the data points change and register mouse enter/leave handlers that manipulate the opacity of all the other data points that the mouse is not over. This could be expanded to support storyboards, etc.
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;
}
}
}
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.