Making smooth effect in WPF manually in C# with DispatcherTimer - wpf

I'm trying to make pretty effect with not using Storyboard or another ready/already done stuff in WPF.
I want to make smooth effect, where on some event (like click) the UI element resizes for 2-3 seconds and bluring with changing color. All these items I want to make in smooth pretty way.
I have prepared such class to render each frame of my effect:
public static class ApplicationHelper
{
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents(DispatcherPriority priority)
{
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation oper = Dispatcher.CurrentDispatcher.
BeginInvoke(priority,
new DispatcherOperationCallback(ExitFrameOperation),
frame);
Dispatcher.PushFrame(frame);
if (oper.Status != DispatcherOperationStatus.Completed)
{
oper.Abort();
}
}
private static object ExitFrameOperation(object obj)
{
((DispatcherFrame)obj).Continue = false;
return null;
}
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
DoEvents(DispatcherPriority.Background);
}
}
Here I'm trying to make it work with DispatcherTimer:
void vb1_click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 500);
dt.Tick += new System.EventHandler(dt_Tick);
dt.Start();
}
void dt_Tick(object sender, System.EventArgs e)
{
for(int i = 0; i < 20; i++)
{
this.vb2_blur_eff.Radius = (double)i;
ApplicationHelper.DoEvents();
}
}
The main problem is, that when I'm launcing it, I'm only waiting and at the final time ( when must last frame be rendered ) , I'm getting in a very quick speed all frames, but perviously there was nothing.
How to solve it and make perfect smooth effect in pure C# way with not using some ready/done stuff?
Thank you!

The ApplicationHelper.DoEvents() in dt_Tick probably does nothing, since there are no events to process. At least not the ones you're probably expecting.
If I'm not mistaken, your code will just quickly set the Radius to 0, then 1, 2, and so on in quick succession, and finally to 19. All of that will happen every 500 milliseconds (on every Tick, that is).
I think you might believe that each Tick will only set Radius to one value and then wait for the next Tick, but it does not. Every Tick will set the Radius to all the values, ending at 19. That is one possible explanation for what you're experiencing.
I would also like to comment on the DoEvents approach. It's most likely a bad idea. Whenever I see a DoEvents I get chills up my spine. (It reminds me of some seriously bad Visual Basic 5/6 code I stumbled across 10-15 years ago.) As I see it, an event handler should return control of the GUI thread as quickly as possible. If the operation takes a not insignificant amount of time, then you should delegate that work to a worker thread. And nowadays, you have plenty of options for writing asynchronous code.

Related

How to achieve smooth UI updates every 16 ms?

I am trying to create sort of a radar. Radar is VisualCollection that consists of 360 DrawingVisual's (which represent radar beams). Radar is placed on Viewbox.
class Radar : FrameworkElement
{
private VisualCollection visuals;
private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here
public Radar()
{
visuals = new VisualCollection(this);
for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
{
DrawingVisual dv = new DrawingVisual();
visuals.Add(dv);
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
}
}
DrawingVisual line = new DrawingVisual();
visuals.Add(line);
// DISCRETES_AMOUNT is about 500
this.Width = DISCRETES_AMOUNT * 2;
this.Height = DISCRETES_AMOUNT * 2;
}
public void Draw(int beamIndex, Brush brush)
{
using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
{
dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
}
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
}
Each DrawingVisual has precalculated geometry for DrawingContext.DrawGeometry(brush, pen, geometry). Pen is null and brush is a LinearGradientBrush with about 500 GradientStops. The brush gets updated every few milliseconds, lets say 16 ms for this example. And that is what gets laggy. Here goes the overall logic.
In MainWindow() constructor I create the radar and start a background thread:
private Radar radar;
public MainWindow()
{
InitializeComponent();
radar = new Radar();
viewbox.Child = radar;
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
In Run() method there is an infinite loop, where random brush is generated, Dispatcher.Invoke() is called and a delay for 16 ms is set:
private int beamIndex = 0;
private Random r = new Random();
private const int turnsPerMinute = 20;
private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
private long deltaDelay = delay;
public void Run()
{
int beginTime = Environment.TickCount;
while (true)
{
GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
{
byte color = (byte)r.Next(255);
gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
}
LinearGradientBrush lgb = new LinearGradientBrush(gsc);
lgb.StartPoint = Beam.GradientStarts[beamIndex];
lgb.EndPoint = Beam.GradientStops[beamIndex];
lgb.Freeze();
viewbox.Dispatcher.Invoke(new Action( () =>
{
radar.Draw(beamIndex, lgb);
}));
beamIndex++;
if (beamIndex >= BEAM_POSITIONS_AMOUNT)
{
beamIndex = 0;
}
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
}
}
Every Invoke() call it performs one simple thing: dc.DrawGeometry(), which redraws the beam under current beamIndex. However, sometimes it seems, like before UI updates, radar.Draw() is called few times and instead of drawing 1 beam per 16 ms, it draws 2-4 beams per 32-64 ms. And it is disturbing. I really want to achieve smooth movement. I need one beam to get drawn per exact period of time. Not this random stuff. This is the list of what I have tried so far (nothing helped):
placing radar in Canvas;
using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting different Thread Priorities;
using different ways of implementing delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
changing LinearGradientBrush to predefined SolidColorBrush;
using BeginInvoke() instead of Invoke() and changing Dispatcher Priorities;
using InvalidateVisuals() and ugly DoEvents();
using BitmapCache, WriteableBitmap and RenderTargetBitmap (using DrawingContext.DrawImage(bitmap);
working with 360 Polygon objects instead of 360 DrawingVisuals. This way I could avoid using Invoke() method. Polygon.FillProperty of each polygon was bound to ObservableCollection, and INotifyPropertyChanged was implemented. So simple line of code {brushCollection[beamIndex] = (new created and frozen brush)} led to polygon FillProperty update and UI was getting redrawn. But still no smooth movement;
probably there were few more little workarounds I could forget about.
What I did not try:
use tools to draw 3D (Viewport) to draw 2D radar;
...
So, this is it. I am begging for help.
EDIT: These lags are not about PC resources - without delay radar can do about 5 full circles per second (moving pretty fast). Most likely it is something about multithread/UI/Dispatcher or something else that I am yet to understand.
EDIT2: Attaching an .exe file so you could see what is actually going on: https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) did not help aswell.
For smooth WPF animations you should make use of the
CompositionTarget.Rendering event.
No need for a thread or messing with the dispatcher. The event will automatically be fired before each new frame, similar to HTML's requestAnimationFrame().
In the event update your WPF scene and you're done!
There is a complete example available on MSDN.
You can check some graphics bottleneck using the WPF Performance Suite:
http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx
Perforator is the tool that will show you performance issues. Maybe you are using a low performance VGA card?
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
The sequence above blocks the thread. Use instead "await Task.Delay(...)" which doesn't block the thread like its counterpart Thread.Sleep(...).

C# Winforms: BeginInvoke still running on same thread?

I'm web developer and I'm trying to step into multithreading programming.
On one form I'm trying to run a method computing values in a second thread using asynchronous delegates.
I also want a progress bar showing actual progress in UI thread been notified.
delegate void ShowProgressDelegate(int total, int value);
delegate void ComputeDelegate(int value);
//Some method simulating sophisticated computing process
private void Compute(int value)
{
ShowProgress(value, 0);
for (int i = 0; i <= value; i++)
{
ShowProgress(value, i);
}
}
//Method returning values into UI thread
private void ShowProgress(int total, int value)
{
if (!this.InvokeRequired)
{
ComputeButton.Text = value.ToString();
ProgressBar.Maximum = total;
ProgressBar.Value = value;
}
else
{
ShowProgressDelegate showDel = new ShowProgressDelegate(ShowProgress);
this.BeginInvoke(showDel, new object[] { total, value });
}
}
//firing all process
private void ComputeButton_Click(object sender, EventArgs e)
{
ComputeButton.Text = "0";
ComputeDelegate compDel = new ComputeDelegate(Compute);
compDel.BeginInvoke(100000, null, null);
}
When I run this, everything is computing without any problem except it is still running in UI thread (I suppose so, because it freezes when I click some button on the form).
Why? I also attach buildable sample project (VS2010) with same code: http://osmera.com/windowsformsapplication1.zip
Thanks for helping neewbie.
In the code you've shown, you're doing nothing other than updating the progress bar - so there are thousands of UI messages to marshal, but nothing significant happening in the non-UI thread.
If you start simulating real work in Compute, you'll see it behave more reasonably, I suspect. You need to make sure you don't swamp the UI thread with progress updates like you are doing now.

Update GUI using BackgroundWorker

I've been searching and found that a good way to perform background work and update the GUI is using background workers. However, doing this (stupid) little task (counting from 1 to 10000) it doesn't update the label content but prints to the debug! (This is just a spike solution for another project of course...)
Here's the code:
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("DONE");
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = "going here: "+e.ProgressPercentage;
Debug.WriteLine(e.ProgressPercentage);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i=0; i < 10000; i++)
{
bw.ReportProgress((i*100)/10000);
}
}
}
The ProgressChanged event is raised on the UI thread, not the worker thread. In your code, the worker thread is doing almost nothing (just loop from 0 to 10000 and call ReportProgress), most of the work is done on the UI thread. Basically, you're sending too many progress notifications. Because of this, the UI thread is almost always busy and has no time to render the new content of the label.
Rendering in WPF is not performed immediately when you change a property of a control, it is done on a separate dispatcher frame, which is processed when the dispatcher has nothing more urgent to do, based on the priority of the task. The priority used for rendering has a value of 7 (DispatcherPriority.Render); the ProgressChanged event is marshalled to the UI thread with a priority of 9 (DispatcherPriority.Normal), as specified on MSDN. So the ProgressChanged notifications always have a higher priority than rendering, and since they keep coming, the dispatcher never has time to process the rendering tasks.
If you just decrease the frequency of the notifications, your app should work fine (currently you're sending 100 notifications for each percentage value, which is useless):
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
if (i % 100 == 0)
bw.ReportProgress(i / 100);
}
}
this.Dispatcher.BeginInvoke( (Action) delegate(){
label1.Content = "going here: "+e.ProgressPercentage;
});
Try to change the label using womething like this:
string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
label1.Content = newText;
});
Note that i'm not sure it will work. I can not test it now. If it does not work, let me know and I will delete the answer.
If you need the a canonical way to do exactly what you want, look at the Hath answer in this post: How do I update the GUI from another thread?

How to play multiple audio sources simultaneously in Silverlight

I want to play simultaneous multiply audio sources in Silverlight.
So I've created a prototype in Silverlight 4 that should play a two mp3 files containing the same ticks sound with an intervall 1 second. So these files must be sounded as one sound if they will be played together with any whole second offsets (0 and 1, 0 and 2, 1 and 1 seconds, etc.)
I my prototype I use two MediaElement (me and me2) objects.
DateTime startTime;
private void Play_Clicked(object sender, RoutedEventArgs e) {
me.SetSource(new FileStream(file1), FileMode.Open)));
me2.SetSource(new FileStream(file2), FileMode.Open)));
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1) };
timer.Tick += RefreshData;
timer.Start();
}
First file should be played at 00:00 sec. and the second in 00:02 second.
void RefreshData(object sender, EventArgs e) {
if(me.CurrentState != MediaElementState.Playing) {
startTime = DateTime.Now;
me.Play();
return;
}
var elapsed = DateTime.Now - startTime;
if(me2.CurrentState != MediaElementState.Playing &&
elapsed >= TimeSpan.FromSeconds(2)) {
me2.Play();
((DispatcherTimer)sender).Stop();
}
}
The tracks played every time different and not simultaneous as they should (as one sound).
Addition:
I've tested a code from the Bobby's answer.
private void Play_Clicked(object sender, RoutedEventArgs e) {
me.SetSource(new FileStream(file1), FileMode.Open)));
me2.SetSource(new FileStream(file2), FileMode.Open)));
// This code plays well enough.
// me.Play();
// me2.Play();
// But adding the 2 second offset using the timer,
// they play no simultaneous.
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
timer.Tick += (source, arg) => {
me2.Play();
((DispatcherTimer)source).Stop();
};
timer.Start();
}
Is it possible to play them together using only one MediaElement or any implementation of MediaStreamSource that can play multiply sources?
Addition 2: Debug the playing positions
Adding the debug information shows definitively that the me plays different compare to the timer's ticks
...
me2.Play();
System.Diagnostics.Debug.WriteLine(
me.Position.TotalMilliseconds + " -> " + me2.Position.TotalMilliseconds);
// 1820 -> 0 (but not 2000 -> 0)
Addition3: Experience with markers
I have experienced with the Time in the TimeLineMarker and following code works well enough on my pc
me.Markers.Clear();
me.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMilliseconds(1892) });
me.MarkerReached += (source, args) => {
me2.Play();
me.Markers.Clear();
};
me.Play();
Since you're using FileStreams to load the files, I assume you're not reading them over the web, but rather from the local file system.
You don't want to call Play() from the timer tick handlers if it's not necessary - ie for the first ME.
Also, you're running the timer every millisecond, not second (not sure if that's actually what you meant to say). If you want to kick of the second play 2 seconds after the first, then try calling me.Play() then create the timer to run on a 2-sec interval, and all it does is call me2.Play() and stop itself.
Also, in line with the comment above, to see if the behavior is reasonably deterministic (sufficiently synchronized), try running just this a few times to see if, in principle, they can play together well enough for you.
private void Play_Clicked(object sender, RoutedEventArgs e) {
me.SetSource(new FileStream(file1), FileMode.Open)));
me2.SetSource(new FileStream(file2), FileMode.Open)));
me.Play();
me2.Play();
}
.. Edit:
Timers are not guaranteed to execute
exactly when the time interval occurs,
but they are guaranteed to not execute
before the time interval occurs. This
is because DispatcherTimer operations
are placed on the Dispatcher queue
like other operations. When the
DispatcherTimer operation executes is
dependent on the other jobs in the
queue and their priorities. (msdn :
DispatcherTimer)
It can be very difficult to achieve perfect synchronization in multi-threaded contexts.. The DispatcherTimer is going to place the call to the handler in the UI Dispatcher when the interval ticks, but that doesn't mean it will get immediately invoked. The only thing that might be worth a shot at this point is to adjust the DispatcherPriority (e.g. to 'Send' (the highest)).
Do so with this constructor:
public DispatcherTimer(
DispatcherPriority priority
)

Silverlight 1 second DispatcherTimer only firing every 30 seconds or so on certain machines

I've got a Silverlight 3 app that works great, except on 4 machines. DispatcherTimer and Storyboards are not firing consistently on these 4 machines. I've created a very simple test app to try to figure this out, I'll list the code below.
Basically the test app updates two TextBlocks every second. One using DispatcherTimer, one using a Storyboard Animation. This works great - the text boxes update "1, 2, 3..." every second. But on the 4 affected machines the TextBlocks don't update every second, they update between 27 and 33 seconds. The DispatcherTimer and Storyboard updates are done at the exact same time.
CPU, Memory, HD are all fine. Task Manager and SilverlightSpy shows that everything this is fine. These are all 3 Ghz workstations with 3GB of RAM with nothing else running on them.
XAML:
<TextBlock Text="0" Name="DispatcherTimerText" Grid.Column="0" />
<TextBlock Text="0" Name="SBLoopTimerText" Grid.Column="1" />
C#:
Storyboard _sbLoop = new Storyboard();
public MainPage()
{
InitializeComponent();
Storyboard_Start();
Timer_Start();
}
void Timer_Start()
{
System.Windows.Threading.DispatcherTimer dt1 = new System.Windows.Threading.DispatcherTimer();
dt1.Interval = new TimeSpan(0, 0, 1); // 1 second
dt1.Tick += new EventHandler(Timer_Tick);
dt1.Start();
}
void Timer_Tick(object sender, EventArgs e)
{
TextBlock txt = ((TextBlock)LayoutRoot.Children.Single(t => ((TextBlock)t).Name == "DispatcherTimerText"));
txt.Text = (int.Parse(txt.Text) + 1).ToString();
}
void Storyboard_Start()
{
_sbLoop.Duration = TimeSpan.FromSeconds(1);
_sbLoop.Completed += new EventHandler(StoryboardLoop);
_sbLoop.Begin();
}
void StoryboardLoop(object sender, EventArgs e)
{
TextBlock txt = ((TextBlock)LayoutRoot.Children.Single(t => ((TextBlock)t).Name == "SBLoopTimerText"));
txt.Text = (int.Parse(txt.Text) + 1).ToString();
_sbLoop.Begin(); // Restart sb animation
}
Just a guess, but it could be the fact that you're trying to update the UI directly in the events. I'd be curious to see if this code would work better for you and fix the problem on the machines that don't work.
public partial class MainPage : UserControl
{
Storyboard _sbLoop = new Storyboard();
private int _slCounter = 0;
private int _tmrCounter = 0;
private TimeSpan interval = TimeSpan.FromMilliseconds(100);
public MainPage()
{
InitializeComponent();
Storyboard_Start();
Timer_Start();
}
void Timer_Start()
{
System.Windows.Threading.DispatcherTimer dt1 = new System.Windows.Threading.DispatcherTimer();
dt1.Interval = interval;
// 1 second
dt1.Tick += new EventHandler(Timer_Tick);
dt1.Start();
}
void Timer_Tick(object sender, EventArgs e)
{
_tmrCounter++;
Dispatcher.BeginInvoke(() => DispatcherTimerText.Text = _tmrCounter.ToString());
}
void Storyboard_Start()
{
_sbLoop.Duration = interval;
_sbLoop.Completed += new EventHandler(StoryboardLoop);
_sbLoop.Begin();
}
void StoryboardLoop(object sender, EventArgs e)
{
_slCounter++;
Dispatcher.BeginInvoke(() => SBLoopTimerText.Text = _slCounter.ToString());
_sbLoop.Begin();
// Restart sb animation
}
}
This code uses BeginInvoke to update the UI instead of directly updating it. You might need to change my interval since I put it as 100 ms instead of one second just for testing.
I'm not sure why this is happening, but I'm now positive this isn't a code issue.
Everything works great if I'm actively interacting with the workstation. Opening browsers, copying files, etc. But if the workstation is sitting idle - just running the Silverlight test app, it quickly goes back to taking 30 seconds to fire the timers.
I've verified this happens not only with my app, and my test app, but also with other Silverlight apps like the demos here:
http://wpierdalaj.pl/SWG/SilverlightCountDown/Run/SilverlightCountDownTimerExampleTestPage.html
For a workaround I created a batch file that copies a 100MB file, deletes it, and then starts over. As long as this batch file is running everything works great. :)
For a long term solution IT is going to figure out why these workstations go to "sleep" so quickly.
I wrote a simple extension method that uses a Task. Your more than welcome to alter the main DelayInvoke method to use a DispatcherTimer.
See This Post for my solution.

Resources