Showing a text indicator before freezing the Silverlight UI thread - silverlight

At some point in my Silverlight application I need to perform a heavy operation which freezes the UI thread for about 4 seconds. Before actually performing the operation I am trying to display a simple text indicator via a TextBlock control.
StatusTextBlock.Text = "Performing Some Operation...";
System.Threading.Thread.Sleep(4000); // Just as an example
The problem is that the UI thread freezes before the text of the TextBlock control gets updated. How can I get the notification text shown before the operation begins?
Also, taking the heavy operation to a background thread is not an option for me, as it deals with UI objects (it switches the visual root of the application) and should be executed on the UI thread.

My suggestion is to take it off UI thread and use background thread...
StatusTextBox.Text = "Before Sleep";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
void bw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(8000);}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusTextBox.Text = "after Sleep";
}

I found a solution with the help of Jeff Prosise's blog post: http://www.wintellect.com/cs/blogs/jprosise/archive/2008/10/25/cool-silverlight-trick-5.aspx
The idea is to delay the call performing a long running task till a Silverlight UI rendering event fires. For this I used the CompositionTarget.Rendering event. I subscribed to it in the constructor of the user control:
CompositionTarget.Rendering += this.CompositionTargetRendering;
After I update the text of a TextBlock control I set a private flag, which indicates that some processing should be made in the event handler:
StatusTextBlock.Text = "Performing Some Operation...";
this.processRenderingEvent = true;
And here is the code of the handler:
private void CompositionTargetRendering(Object sender, EventArgs e)
{
if (this.processRenderingEvent)
{
if (++this.renderingEventCounter == 2)
{
System.Threading.Thread.Sleep(4000); // Example of long running task
this.processRenderingEvent = false;
}
}
}
An important point to mention here is that I use a private integer field renderingEventCounter to begin the long running task not the first time the event fires, but the second. The reason for this is that the CompositionTarget.Rendering event is fired just before the Silverlight UI rendering engine draws a new frame on the application's display surface, which means that at the first time the event fires the text of the TextBlock control is not yet updated. But it will be updated the second time.

I think you should implement the BackgroundWorker thread is tsiom's answer, but use the Dispatcher.BeginInvoke to operate on the UI objects, here is a MSDN article on how to use the method: http://msdn.microsoft.com/en-us/library/cc190824%28v=vs.95%29.aspx
Also, see another StackOverflow question for a more comprehensive scenario using the Dispatcher: Understanding the Silverlight Dispatcher

I just ran into this situation myself. The problem (I think) is that before the text gets updated you have already begun the intensive operation, so you have to wait.
What you can do is to attach a listened to some method on the textbox that only gets called once the text is updated (textChanged perhaps?) and THEN call your intensive operation.
This seems hackish to me though...

This is ugly but it works. By delaying the initiliazation of the long running operation using a DispatcherTimer we can allow the UI to be updated before the operation is started.
XAML:
<UserControl x:Class="SilverlightApplication13.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="LayoutRoot"
Background="White">
<StackPanel>
<Border x:Name="Brd01"
Visibility="Collapsed"
Background="Red">
<TextBlock VerticalAlignment="Center"
Margin="30">Sleeping for 4 seconds...</TextBlock>
</Border>
<Border x:Name="Brd02"
Visibility="Collapsed"
Background="Lime">
<TextBlock VerticalAlignment="Center"
Margin="30">Done!</TextBlock>
</Border>
<Button Content="Start Operation"
Click="Button_Click_1"></Button>
</StackPanel>
</Grid>
</UserControl>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace SilverlightApplication13
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Show the "working..." message
Brd01.Visibility = System.Windows.Visibility.Visible;
//Initialize a timer with a delay of 0.1 seconds
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
//Start the long running operation
Thread.Sleep(4000);
Brd01.Visibility = System.Windows.Visibility.Collapsed;
Brd02.Visibility = System.Windows.Visibility.Visible;
//Kill the timer so it will only run once.
(sender as DispatcherTimer).Stop();
(sender as DispatcherTimer).Tick -= Timer_Tick;
}
}
}

Related

WPF not load a control

I write a very easy UserControl
here the Xaml code
<UserControl x:Name="Test1" x:Class="WpfAppXtesting.UserControl1"
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:WpfAppXtesting"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Loaded="Test1_Loaded">
<Grid x:Name="GridRoot" Background="Aqua">
<TextBlock x:Name="status" HorizontalAlignment="Left" Height="137" Margin="100,137,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Width="483" FontSize="48"/>
</Grid>
and here the code behind
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
this.GridRoot.DataContext = this;
}
private void UserControl1_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Connected":
status.Text = ((App)sender).Connected.ToString() ;
break;
}
}
private void Test1_Loaded(object sender, RoutedEventArgs e)
{
(Application.Current as App).PropertyChanged += UserControl1_PropertyChanged;
}
}
the problem is , when import this control in a Window in same project The design mode gets this error.
NullReferenceException: Object reference not set to an instance of an object.
if I run the project everything was good.
If I commented the line in Loaded method
the control was right shown in design mode.
Any Idea?
thanks
Do not assume that Application.Current is your application at design time. For example, when you are using Expression Blend, Current is Expression Blend. At design time, MainWindow is not your application's main window. Typically operations that cause a user/custome control to fail at design time include the following.
Casting Current to your custom subclass of App.
Casting MainWindow to your custom subclass of Window.
Here are two approaches to writing code for design time. The first approach is to write defensive code by checking the null condition. The second approach is to check whether design mode is active by calling the GetIsInDesignMode method. You can read about GetIsInDesignMode at here.
Solution 1:
private void Test1_Loaded(object sender, RoutedEventArgs e)
{
var app = Application.Current as App;
if( app != null)
{
app.PropertyChanged += UserControl1_PropertyChanged;
}
}
Solution 2 :
private void Test1_Loaded(object sender, RoutedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(this))
{
// Design-mode specific functionality
(Application.Current as App).PropertyChanged += UserControl1_PropertyChanged;
}
}

canvas draw shape on MouseMove lag

I have very simple scenario: there is a canvas and I need to draw a line on canvas using MouseMove. But when I move the mouse pointer, second line's point (which is set in mouse move) doesn't match current mouse position.
UPD 2:
Delta depends on speed of mouse, if speed is large - delta is large and noticeable(lag). I've noticed that this bug is more visible if you move your mouse not very fast and not very slow.
You can download sample project here.
Something like on the picture when mouse moves fast:
Some source code:
<Window x:Class="WpfApplication32.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Canvas x:Name="MainCanvas"
MouseLeftButtonDown="MainCanvas_OnMouseLeftButtonDown"
MouseMove="MainCanvas_OnMouseMove"
Background="White"
/>
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication32
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Line _currentLine;
private bool _isDrawing;
public MainWindow()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
MainCanvas.Focus();
}
private void MainCanvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_isDrawing)
{
_currentLine = null;
_isDrawing = false;
return;
}
_isDrawing = true;
_currentLine = new Line(){Stroke = Brushes.Green};
var p = e.GetPosition(MainCanvas);
_currentLine.X1 = p.X;
_currentLine.Y1 = p.Y;
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
MainCanvas.Children.Add(_currentLine);
}
private void MainCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
if (_currentLine == null)
return;
var p = e.GetPosition(MainCanvas);
_currentLine.X2 = p.X;
_currentLine.Y2 = p.Y;
}
}
}
I've tried to use CompositeTarget.Render, also timer to change second point every 20ms but it didn't help.
I have legacy project in which code depends a lot on this approach(canvas mouseMove and shapes). So I need easiest way to eliminate this lag or some ideas about a reason of this bug) Thanks.
UPD:
I've tried to record video with this problem but I'm not good at it. Here is some screen from my recorded to show the problem:
http://prntscr.com/64hueg
UPD 2:
I've tried to use OnRender of Window object to do the same without canvas. I've used DataContext to draw the line - same issue here. DataContext is considered faster than Canvas and Line (Shape). So this is not Canvas issue.
I've also tried to use WritableBitmap to draw the line - no difference.
I thought that there might be a problem with MouseMove event - I read if there is a lot of objects(not my case but still) MouseMove might fire with delays so I used Win32 WM_MOUSEMOVE but it didn't helped as well. In my case delay between MW_MOUSEMOVE and wpf MouseMove event was <1000 ticks.
The only answer I see so far is render delay. I don't know how to improve it because it is wpf internals =(.
By the way Paint.net seems to use wpf and this problem occurs there as well.
This cannot be fixed because this is due to WPF's internal render system. There will always be a lag even if visual tree is simple. Complex visual tree results to more delay. I've spend a lot of time trying solving this.
Try using MouseMove event not on main canvas but on Window itself.
I recently got same problem using MouseMove on Image and it was laggy as hell
Switching to window's event helped me a lot.
<Window x:Name="Window1" x:Class="WpfApp2.MViewer"
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:WpfApp2"
mc:Ignorable="d"
Title="MViewer" Height="454.411" Width="730.515" Loaded="Window_Loaded" Closing="Window1_Closing" ContentRendered="Window1_ContentRendered" MouseMove="Window1_MouseMove">
<Grid>
<Image x:Name="Image1" MouseMove="Image1_MouseMove"/>
<Line Name="Line1" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
<Line Name="Line2" Visibility="Visible" Stroke="Red" StrokeThickness="0.75" />
</Grid>
</Window>
and
private void Window1_MouseMove(object sender, MouseEventArgs e)
{
Line1.Visibility = Visibility.Visible;
Line1.X1 = Mouse.GetPosition(this).X;
Line1.X2 = Mouse.GetPosition(this).X;
Line1.Y1 = 0;
Line1.Y2 = Window1.Height;
Line2.Visibility = Visibility.Visible;
Line2.X1 = 0;
Line2.X2 = Window1.Width;
Line2.Y1 = Mouse.GetPosition(this).Y;
Line2.Y2 = Mouse.GetPosition(this).Y;
}

Kill one button event when new button clicked

How to kill one button's event when a new button is clicked.
I have one event (Button G) running.(has a while loop waiting for some input).
I have a another button for quit operation.
Now. I cannot click any other button when button G's event is running.
How can I solve that?
Thanks
Hi, #Grokodile thank you for your code. So I commented your code here, Should I put my job logic code where I commented below? Thans
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
       //I should Put my job logic here, right?
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
//I should Put my job logic here, right?
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
Futher to what H.B. and dowhilefor have said, here is a sample that shows starting a task on a background thread using BackgroundWorker with one Button and ending it with another Button, note the use of Dispatcher.BeginInvoke:
XAML
<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="80"
Width="640"
FontSize="16">
<DockPanel VerticalAlignment="Center">
<Button Margin="10,0"
x:Name="runButton"
DockPanel.Dock="Left"
Click="RunButtonClickHandler">Run</Button>
<Button Margin="10,0"
x:Name="stopButton"
DockPanel.Dock="Left"
Click="StopButtonClickHandler">Stop</Button>
<TextBlock Margin="10,0">The Time Is Now:</TextBlock>
<TextBlock x:Name="timeTextBlock"
Margin="10,0" />
</DockPanel>
</Window>
Code Behind
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BackgroundWorker _worker;
public MainWindow()
{
InitializeComponent();
}
private void RunButtonClickHandler(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker {WorkerSupportsCancellation = true};
_worker.DoWork += BackgroundWorkerTask;
_worker.RunWorkerAsync();
}
private void StopButtonClickHandler(object sender, RoutedEventArgs e)
{
if (_worker != null && _worker.IsBusy) _worker.CancelAsync();
}
private void BackgroundWorkerTask(object sender, DoWorkEventArgs e)
{
// this runs on the BackgroundWorker thread.
while (_worker.CancellationPending == false)
{
Thread.Sleep(500);
// You have to use the Dispatcher to transfer the effects of
// work done in the worker thread back onto the UI thread.
Dispatcher.BeginInvoke(new Action(UpdateTime), DispatcherPriority.Normal, null);
}
}
private void UpdateTime()
{
// Dispatcher runs this on the UI thread.
timeTextBlock.Text = DateTime.Now.ToString();
}
}
}
EDIT - A Little More Explanation
RunButtonClickHandler
Creates and initializes a BackgroundWorker so that it supports cancellation.
Attaches a DoWorkEventHandler to the DoWork event, i.e. BackgroundWorkerTask.
Starts excecution of the background operation with the call to RunWorkerAsync, i.e. creates a new thread (actually it uses a thread from the thread pool) and runs the code in BackgroundWorkerTask on that thread.
BackgroundWorkerTask
If you want to do work that would otherwise cause the UI to freeze when running on the main UI thread (e.g. search for undiscovered prime numbers) then you do it here in the background thread.
UpdateTime
All WPF Controls inherit from DispatcherObject and are associated with a Dispatcher which manages the execution of work done in the UI's single thread. If you need to do work such as setting the text of a TextBlock, you can't do it from the background thread, trying to do so will cause an exception to be thrown. UpdateTime is queued back onto the UI thread by the Dispatcher when Dispatcher.BeginInvoke is called from BackgroundWorkerTask. This way you can get the results of work done in the background before cancelling the background threads execution.
StopButtonClickHandler
Changes _worker.CancellationPending to true with the call to CancelAsync causing the while loop to exit and thus for execution to leave the BackgroundWorkerTask event handler.
In short, you can do work in two places, either in BackgroundWorkerTask or in UpdateTime, but you can only carry out changes to UI elements from UpdateTime.
Don't do spin-waiting operations on the UI-thread, use a new Thread or a Timer for example.
You can't. The UI is running in a single thread, usually called the main or ui thread. With your while loop you are blocking the whole ui thread, thus you can't receive any further input.
I suggest you check out BackgroundWorker class and maybe check some more MSDN articles about how the threading and background tasks should be designed to work properly in an ui enviroment.

100% responsive UI in WPF

I have a WPF application that uses a component that sends a bitmap to my application as they become available, I receive those bitmaps in a delegate I pass to this component.
I created a new thread for this process and it works very well, the bitmaps comes as MemoryStream and I just create the BitmapSource object from this stream inside a Dispatcher.BeginInvoke method call. After I have the BitmapSource object, I add them to a StackPanel so the user can see a queue of images available to work. So far so good...
The problem is that those bitmaps are quite big, like 3000x2000+ pixels, and it takes about 50~ms to create these bitmaps and add to the queue, and when this code is executed, the onde inside the BeginInvoke call, it blocks the UI for this time, causing a very annoying behavior, (to reproduce this, just call Thread.Sleep(50) every 5 seconds).
How can I fix this so the user is always responsive?
thanks!
There are two ideas you might want to consider:
Don't create the BitmapSource inside Dispatcher.Invoke. That would practically create it inside the UI thread, slowing things down. Instead, create it in the background thread, freeze it, and then pass the frozen BitmapSource to the foreground thread.
Depending on your application, perhaps the StackPanel doesn't need the full 3000x2000 resolution? If that is the case, consider resizing down the images in the background thread, just before you freeze them.
The following code does #1 above:
Window1.xaml
<Window x:Class="BitmapFrameDemo.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">
<Grid>
<Image Name="image"/>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace BitmapFrameDemo {
public partial class Window1 : Window {
private Thread thread = null;
private Dispatcher dispatcher = null;
private void ThreadMain() {
// obtain the image memory stream
WebRequest request = WebRequest.Create("http://stackoverflow.com/content/img/so/logo.png");
WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
// create a bitmap source while still in the background thread
PngBitmapDecoder decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame frame = decoder.Frames[0];
// freeze the bitmap source, so that we can pass it to the foreground thread
BitmapFrame frozen = (BitmapFrame) frame.GetAsFrozen();
dispatcher.Invoke(new Action(() => { image.Source = frozen; }), new object[] { });
}
public Window1() {
InitializeComponent();
dispatcher = Dispatcher.CurrentDispatcher;
thread = new Thread(new ThreadStart(this.ThreadMain));
thread.Start();
}
}
}
alt text http://www.freeimagehosting.net/uploads/66bdbce78a.png
Really all you need to do is set the IsAsync to True on your binding to the image. I would however recomend using a PriorityBinding and preparing some form of default image that the user can see so they know it isn't fully loaded.
<StackPanel>
<Image>
<Image.Source>
<PriorityBinding>
<Binding Path="SlowImage"
IsAsync="True" />
<Binding Path="DefaultImage" />
</PriorityBinding>
</Image.Source>
</Image>
</StackPanel>
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
DefaultImage = new BitmapImage(new Uri("http://stackoverflow.com/content/img/so/logo.png"));
SlowImage = new BitmapImage(new Uri("http://serverfault.com/content/img/sf/logo.png"));
this.DataContext = this;
}
private BitmapImage myDefaultImage;
public BitmapImage DefaultImage
{
get { return this.myDefaultImage; }
set
{
this.myDefaultImage = value;
this.NotifyPropertyChanged("Image");
}
}
private BitmapImage mySlowImage;
public BitmapImage SlowImage
{
get
{
Thread.Sleep(5000);
return this.mySlowImage;
}
set
{
this.mySlowImage = value;
this.NotifyPropertyChanged("SlowImage");
}
}
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Two possibilities come to mind quickly.
The first is to use a BackgroundWorker to perform the transformation of the MemoryStream to a Bitmap.
the Second is to pass off that transformation to a ThreadPool.
While not an entirely satifsfactory answer for you, I would suggest you listen to this Hanselminutes podcast with Ian Griffiths as it covers a very similar program and the proper way to architect it to get the performance you are looking for.
Some specifics may be found on Ian's blog: http://www.interact-sw.co.uk/iangblog/

WPF Storyboard does not pause

I have a very simple Storyboard with an Int32Animation that targets a custom DP on my class.
I have an OnChanged callback for my DP, that does a Console.WriteLine("Current Value: " + MyDP).
When I run the storyboard, I can see the Console output just fine, and when I pause the storyboard, the Console output stops, BUT, when I resume the storyboard, the DP is NOT the next value at it should be. It continues increasing even though the storyboard has stopped.
Has anyone had anything like this happen to them?
here is a code snippet of what Im doing
Int32Animation element = new Int32Animation();
element.From = 0;
element.To = targetTo;
element.Duration = dur;
Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));
_filmstripStoryboard = new Storyboard {SpeedRatio = this.FrameRate};
_filmstripStoryboard.Children.Add(element);
public void Start()
{
_filmstripStoryboard.Begin(this, true);
}
public void Pause()
{
_filmstripStoryboard.Pause(this);
}
public void Unpause()
{
_filmstripStoryboard.Resume(this);
}
There's a thread in the MSDN forums where Microsoft confirms that this behavior (the current value continuing even when the storyboard is paused for a while) is a bug in the then-current (as of 2006) release of WPF. The forum thread includes a suggested workaround, specifically, save off the current position when you pause, and manually seek back to that same position when you resume.
The thread mentions that they were thinking about fixing the bug in a future version, but I do not know whether .NET 3.5 or 4.0 actually fixed this bug or not.
EDIT: It appears that the bug is fixed in .NET 4.0 -- I was able to pause and then resume an animation without it jumping forward across the intervening time. (I didn't test in 3.5.)
Now there is no bug in 4.0 . Below code works fine.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfAnimation.Win1805787"
xmlns:Local="clr-namespace:WpfAnimation"
x:Name="MyWindow"
Title="Win1805787"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Text="{Binding CurrentFrame, ElementName=MyWindow}" Height="20" Margin="5,0,5,5"/>
<Button x:Name="Play1" Content="Play" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Play_Click" Margin="5,0,5,5"/>
<Button x:Name="Pause1" Content="Pause" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Click="Pause_Click" Margin="5,0,5,5"/>
<Button x:Name="Resume1" Content="Resume" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75" Click="Resume_Click" Margin="5,0,5,5"/>
</StackPanel>
</Grid>
</Window>
/////////////////////////
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
namespace WpfAnimation
{
/// <summary>
/// Interaction logic for Win1805787.xaml
/// </summary>
public partial class Win1805787 : Window
{
public Win1805787()
{
this.InitializeComponent();
_createStoryBoard();
// Insert code required on object creation below this point.
}
public int CurrentFrame
{
get { return (int)GetValue(CurrentFrameProperty); }
set { SetValue(CurrentFrameProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentFrame. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentFrameProperty =
DependencyProperty.Register("CurrentFrame", typeof(int), typeof(Win1805787),
new PropertyMetadata(0, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
Storyboard _filmstripStoryboard;
void _createStoryBoard()
{
Int32Animation element = new Int32Animation();
element.From = 0;
element.To = 100;
element.Duration = Duration.Plus(new Duration(new TimeSpan(1000000000)));
Storyboard.SetTargetProperty(element, new PropertyPath(CurrentFrameProperty));
_filmstripStoryboard = new Storyboard { SpeedRatio = 0.5 };
_filmstripStoryboard.Children.Add(element);
}
private void Play_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Begin(this, true);
}
private void Pause_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Pause(this);
}
private void Resume_Click(object sender, System.Windows.RoutedEventArgs e)
{
_filmstripStoryboard.Resume(this);
}
}
}

Resources