Using a background worker to efficiently write to a GUI thread - c#

I am using a BackgroundWorker to pull video from a camera and write it to a PictureBox on my WinForms form. In the BW thread I simply pull a frame from the camera, put it into the PictureBox, sleep, and continue:
while (CaptureThreadRunning)
{
Thread.Sleep(5);
Image tmp = Camera.GetFrame(500);
pbCameraFeed.Image = tmp;
//this.BeginInvoke(new MethodInvoker(delegate { pbCameraFeed.Image = Camera.GetFrame(500); }));
}
The issue is that eventually adjusting or moving the form around my screen will throw the exception System.InvalidOperationException with the message Additional information: Object is currently in use elsewhere. on the line pbCameraFeed.Image = tmp;
I assume that the library is trying to paint something to do with the PictureBox at the same time as my while loop is, so I switched to the this.BeginInvoke implementation that is commented out above. Unfortunately that cuts my framerate significantly. I am running this code on a very slow Mini PC which may be contributing to the issue.
What I really want is a way to update my GUI with the image reliably that doesn't drop my framerate by nearly half. Are there other standard ways to do this? A BW thread seemed perfect for this application, but am I missing something?
Thanks

If I were you I would definitely check out the AForge.NET Framework. No need to reinvent the wheel ;)
http://www.aforgenet.com/framework/samples/video.html
AForge.NET is an open source C# framework designed for developers and
researchers in the fields of Computer Vision and Artificial
Intelligence - image processing, neural networks, genetic algorithms,
fuzzy logic, machine learning, robotics, etc.
The framework is comprised by the set of libraries and sample
applications, which demonstrate their features:
AForge.Imaging - library with image processing routines and filters;
AForge.Vision - computer vision library;
AForge.Video - set of libraries for video processing;
...

I would recommend not to use a PictureBox, and instead directly draw to a UserControl surface. This can be easily done by addig code to the Paint and Invalidate events of a UserControl.
This example below, creates a user control which has a BitMap property that it's drawed to its surface every time the control is invalidated.
So, for example, to randomly render JPG images from folder D:\MyPictures, you can do the following:
using System.Windows.Forms;
using System.Drawing;
void Main()
{
var pictures = Directory.GetFiles(#"D:\MyPictures", "*.jpg");
var rnd = new Random();
var form = new Form();
var control = new MyImageControl() { Dock = DockStyle.Fill };
form.Controls.Add(control);
var timer = new System.Windows.Forms.Timer();
timer.Interval = 50;
timer.Tick += (sender, args) =>
{
if (control.BitMap != null)
control.BitMap.Dispose();
control.BitMap = new Bitmap(pictures[rnd.Next(0, pictures.Length)]);
control.Invalidate();
};
timer.Enabled = true;
form.ShowDialog();
}
public class MyImageControl : UserControl // or PictureBox
{
public Bitmap BitMap { get; set; }
public MyImageControl()
{
this.Paint += Graph_Paint;
this.Resize += Graph_Resize;
}
private void Graph_Paint(object sender, PaintEventArgs e)
{
if (this.BitMap != null)
{
lock (this.BitMap)
{
Graphics g = e.Graphics;
g.DrawImage(this.BitMap, this.ClientRectangle);
}
}
}
private void Graph_Resize(object sender, System.EventArgs e)
{
this.Invalidate();
}
}
I think this can be easily changed to render the camera images instead of the picture files.
The code was tested on LinqPad

Related

Kinect camera freeze

I have started developing in c# for the kinect with WPF.
When I start the sample program "colorBasics" from Kinect for Windows Developer Toolkit, the camera works fine, but freezes after a couple of seconds.
I copied over the relevant code ( so only the code for viewing the camera ) and it also happens in my own program.
Anyone know what I'm doing wrong ?
I don't get any error's.
Here is the code
namespace Testapp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private KinectSensor sensor;
private WriteableBitmap colorBitmap;
private byte[] colorPixels;
public MainWindow()
{
InitializeComponent();
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
foreach (var potentialSensor in KinectSensor.KinectSensors)
{
if (potentialSensor.Status == KinectStatus.Connected)
{
this.sensor = potentialSensor;
break;
}
}
if (null != this.sensor)
{
// Turn on the color stream to receive color frames
this.sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
// Allocate space to put the pixels we'll receive
this.colorPixels = new byte[this.sensor.ColorStream.FramePixelDataLength];
// This is the bitmap we'll display on-screen
this.colorBitmap = new WriteableBitmap(this.sensor.ColorStream.FrameWidth, this.sensor.ColorStream.FrameHeight, 96.0, 96.0, PixelFormats.Bgr32, null);
// Set the image we display to point to the bitmap where we'll put the image data
this.Image.Source = this.colorBitmap;
// Add an event handler to be called whenever there is new color frame data
this.sensor.ColorFrameReady += this.SensorColorFrameReady;
// Start the sensor!
try
{
this.sensor.Start();
}
catch (IOException)
{
this.sensor = null;
}
}
}
private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (colorFrame != null)
{
// Copy the pixel data from the image to a temporary array
colorFrame.CopyPixelDataTo(this.colorPixels);
// Write the pixel data into our bitmap
this.colorBitmap.WritePixels(
new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
this.colorPixels,
this.colorBitmap.PixelWidth * sizeof(int),
0);
}
}
}
}
}
Are you using any sort of extension with the USB cable, or is it plugged directly into the machine? I've seen what you are describing happen when the latency was too long between the sensor and the machine. In that instance it was caused by the Kinect USB cable being plugged into an extension cord.
Had same problem here, camera worked untill eventually just freezed and powered off, coming back after a few seconds only to repeat the freeze loop.
After many tests, my conclusion is that the issue is caused by three things:
PC is not fast/powerful as it should be to run your code.
Kinect is getting too hot. Even if you touch it and it's "not so hot", the sensor is very sensitive to overheat.
The Kinect is being "disturbed" somehow. This refers to vibrations or motion physically and/or too many things in the image that resembles a human, so the soft is trying it to compute it at every frame at 30fps, this is a lot of calculus, and this fact might lead to the two other problems listed above.
This also also may cause the latency issue described by Michal

Graphics.DrawImage speed

In my program, I'm coding a basic image editor. Part of this allows the user to draw a rectangular region and I pop up a display that shows that region zoomed by 3x or so (which they can adjust further with the mouse wheel). If they right click and drag this image, it will move the zoom region around on the original image, basically acting as a magnifying glass.
The problem is, I'm seeing some serious performance issues even on relatively small bitmaps. If the bitmap showing the zoomed region is around 400x400 it's still updating as fast as mouse can move and is perfectly smooth, but if I mouse wheel the zoom up to around 450x450, it immediately starts chunking, only down to around 2 updates per second, if that. I don't understand why such a small increase incurs such an enormous performance problem... it's like I've hit some internal memory limit or something. It doesn't seem to matter the size of the source bitmap that is being zoomed, just the size of the zoomed bitmap.
The problem is that I'm using Graphics.DrawImage and a PictureBox. Reading around this site, I see that the performance for both of these is typically not very good, but I don't know enough about the inner workings of GDI to improve my speed. I was hoping some of you might know where my bottlenecks are, as I'm likely just using these tools in poor ways or don't know of a better tool to use in its place.
Here are some snippets of my mouse events and related functions.
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// slide the zoomed part to look at a different area of the original image
if (zoomFactor > 1)
{
isMovingZoom = true;
// try saving the graphics object?? are these settings helping at all??
zoomingGraphics = Graphics.FromImage(displayImage);
zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
}
}
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMovingZoom)
{
// some computation on where they moved mouse ommitted here
zoomRegion.X = originalZoomRegion.X + delta.X;
zoomRegion.Y = originalZoomRegion.Y + delta.Y;
zoomRegionEnlarged = scaleToOriginal(zoomRegion);
// overwrite the existing displayImage to prevent more Bitmaps being allocated
createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
}
}
private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
{
Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));
outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);
if (displayImage != originalDisplayImage && displayImage != output)
displayImage.Dispose();
setImageInBox(output);
}
// sets the picture box image, as well as resizes the window to fit
void setImageInBox(Bitmap bmp)
{
pictureBox.Image = bmp;
displayImage = bmp;
this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (isMovingZoom)
{
isMovingZoom = false;
zoomingGraphics.Dispose();
}
}
}
As you can see, I'm not declaring a new Bitmap every time I want to draw something, I'm reusing an old Bitmap (and the Bitmap's graphics object, though I don't know if there is much cost with calling Graphics.FromImage repeatedly). I tried adding Stopwatches around to benchmark my code, but I think DrawImage passes functionality to another thread so the function claims to be done relatively quickly. I'm trying to Dispose all my Bitmap and Graphics objects when I'm not using them, and avoid repeated calls to allocate/deallocate resources during the MouseMove event. I'm using a PictureBox but I don't think that's the problem here.
Any help to speed up this code or teach me what's happening in DrawImage is appreciated! I've trimmed some excess code to make it more presentable, but if I've accidentally trimmed something important, or don't show how I'm using something which may be causing problems, please let me know and I'll revise the post.
The way I handle issues like that is when receiving the Paint event, I draw the whole image to a memory bitmap, and then BLT it to the window.
That way, all visual flash is eliminated, and it looks fast, even if it actually is not.
To be more clear, I don't do any painting from within the mouse event handlers.
I just set up what's needed for the main Paint handler, and then do Invalidate.
So the painting happens after the mouse event completes.
ADDED: To answer Tom's question in a comment, here's how I do it. Remember, I don't claim it's fast, only that it looks fast, because the _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0)); appears instantaneous. It just bips from one image to the next.
The user doesn't see the window being cleared and then repainted, thing by thing.
It gives the same effect as double-buffering.
Graphics grToDrawOn = null;
Bitmap bmToDrawOn = null;
private void DgmWin_Paint(object sender, PaintEventArgs _e){
int w = ClientRectangle.Width;
int h = ClientRectangle.Height;
Graphics gr = _e.Graphics;
// if the bitmap needs to be made, do so
if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
// if the bitmap needs to be changed in size, do so
if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
bmToDrawOn = new Bitmap(w, h, gr);
}
// hook the bitmap into the graphics object
grToDrawOn = Graphics.FromImage(bmToDrawOn);
// clear the graphics object before drawing
grToDrawOn.Clear(Color.White);
// paint everything
DoPainting();
// copy the bitmap onto the real screen
_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
}
private void DoPainting(){
grToDrawOn.blahblah....
}

Creating a custom graph in Winforms

I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;

creating an advanced metronome in WPF (problems with code created animation and completed event)

Good afternoon,
over the last few weeks I have been working on a project to create an advanced metronome. the metronome is made up of the following things
a swinging arm
a light flash
a collection of dynamically created user controls that represent beats (4 of them that are either on, accented or off).
a usercontrol that displays an LCD numeric display and calculates the number of milliseconds between beats for the selected BPM (60000/BPM=milliseconds)
the user selects a BPM and presses start and the following happens
the arm swings between two angles at a rate of n milliseconds per sweep
the light flashes at the end of each arm sweep
the indicators are created and they flash in sequence (one at the end of each sweep).
now the problem
the Arm and light flash animation are created in code and added to a story board with repeat forever and auto reverse.
the indicators are created in code and need to fire an event at the end of each Arm sweep animation.
So, what I did after much messing around was create a timer that runs at the same pace as the storyboard.
the problem, over 30 seconds the timer and the storyboard go out of sync and therefore the indicators and the arm sweep are not in time (not good for a metronome!!).
I was trying to catch the completed event of the animations and use that as a trigger to stop and restart the timer, this was all I could come up with to keep the two in perfect sync.
the moving out of sync is caused by the storyboard slipping and the fact that the storyboard is invoked with begin on the line before the timer is invoked with .start, this although microseconds I think means that they start impossibly close but not at exactly the same time.
my question,
when I try to bind to the completed event of the animation it never fires. I was under the impression that completed even fires regardless of autoreverse (i.e in between each iteration). is this not the case?
can anyone think of another (more cunning) way to keep the two things in sync.
lastly, I did look to see if I could fire a method from a storyboard (which would of made my life really easy, however it would appear that this cannot be done).
if there are any suggestions I am not precious, I just want to get this finished!!
final point of interest,
the bpm can be adjusted whilst the metronome is running, this is achieved by calculating the millisecond duration on the fly (mouse down of a button) and scale the storyboard by the difference between the current speed and the new speed. obviously the timer running the indicators has to be changed at the same time (using interval).
code below is from my project so far (not the XAML just the C#)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;
namespace MetronomeLibrary
{
public partial class MetronomeLarge
{
private bool Running;
//Speed and time signature
private int _bpm = 60;
private int _beats = 4;
private int _beatUnit = 4;
private int _currentBeat = 1;
private readonly int _baseSpeed = 60000 / 60;
private readonly DispatcherTimer BeatTimer = new DispatcherTimer();
private Storyboard storyboard = new Storyboard();
public MetronomeLarge()
{
InitializeComponent();
NumericDisplay.Value = BPM;
BeatTimer.Tick += new EventHandler(TimerTick);
SetUpAnimation();
SetUpIndicators();
}
public int Beats
{
get
{
return _beats;
}
set
{
_beats = value;
SetUpIndicators();
}
}
public int BPM
{
get
{
return _bpm;
}
set
{
_bpm = value;
//Scale the story board here
SetSpeedRatio();
}
}
public int BeatUnit
{
get
{
return _beatUnit;
}
set
{
_beatUnit = value;
}
}
private void SetSpeedRatio()
{
//divide the new speed (bpm by the old speed to get the new ratio)
float newMilliseconds = (60000 / BPM);
float newRatio = _baseSpeed / newMilliseconds;
storyboard.SetSpeedRatio(newRatio);
//Set the beat timer according to the beattype (standard is quarter beats for one sweep of the metronome
BeatTimer.Interval = TimeSpan.FromMilliseconds(newMilliseconds);
}
private void TimerTick(object sender, EventArgs e)
{
MetronomeBeat(_currentBeat);
_currentBeat++;
if (_currentBeat > Beats)
{
_currentBeat = 1;
}
}
private void MetronomeBeat(int Beat)
{
//turnoff all indicators
TurnOffAllIndicators();
//Find a control by name
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[Beat-1];
//illuminate the control
theIndicator.TurnOn();
theIndicator.PlaySound();
}
private void TurnOffAllIndicators()
{
for (int i = 0; i <= gridContainer.Children.Count-1; i++)
{
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[i];
theIndicator.TurnOff();
}
}
private void SetUpIndicators()
{
gridContainer.Children.Clear();
gridContainer.ColumnDefinitions.Clear();
for (int i = 1; i <= _beats; i++)
{
MetronomeLargeIndicator theNewIndicator = new MetronomeLargeIndicator();
ColumnDefinition newCol = new ColumnDefinition() { Width = GridLength.Auto };
gridContainer.ColumnDefinitions.Add(newCol);
gridContainer.Children.Add(theNewIndicator);
theNewIndicator.Name = "Indicator" + i.ToString();
Grid.SetColumn(theNewIndicator, i - 1);
}
}
private void DisplayOverlay_MouseDown(object sender, MouseButtonEventArgs e)
{
ToggleAnimation();
}
private void ToggleAnimation()
{
if (Running)
{
//stop the animation
((Storyboard)Resources["Storyboard"]).Stop() ;
BeatTimer.Stop();
}
else
{
//start the animation
BeatTimer.Start();
((Storyboard)Resources["Storyboard"]).Begin();
SetSpeedRatio();
}
Running = !Running;
}
private void ButtonIncrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value++;
BPM = NumericDisplay.Value;
}
private void ButtonDecrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value--;
BPM = NumericDisplay.Value;
}
private void ButtonIncrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button-over.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonIncrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonDecrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button-over.png"))
};
ButtonDecrement.Background = theBrush;
}
private void ButtonDecrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button.png"))
};
ButtonDecrement.Background = theBrush;
}
private void SweepComplete(object sender, EventArgs e)
{
BeatTimer.Stop();
BeatTimer.Start();
}
private void SetUpAnimation()
{
NameScope.SetNameScope(this, new NameScope());
RegisterName(Arm.Name, Arm);
DoubleAnimation animationRotation = new DoubleAnimation()
{
From = -17,
To = 17,
Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds)),
RepeatBehavior = RepeatBehavior.Forever,
AccelerationRatio = 0.3,
DecelerationRatio = 0.3,
AutoReverse = true,
};
Timeline.SetDesiredFrameRate(animationRotation, 90);
MetronomeFlash.Opacity = 0;
DoubleAnimation opacityAnimation = new DoubleAnimation()
{
From = 1.0,
To = 0.0,
AccelerationRatio = 1,
BeginTime = TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds - 0.5),
Duration = new Duration(TimeSpan.FromMilliseconds(100)),
};
Timeline.SetDesiredFrameRate(opacityAnimation, 10);
storyboard.Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds * 2));
storyboard.RepeatBehavior = RepeatBehavior.Forever;
Storyboard.SetTarget(animationRotation, Arm);
Storyboard.SetTargetProperty(animationRotation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
Storyboard.SetTarget(opacityAnimation, MetronomeFlash);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));
storyboard.Children.Add(animationRotation);
storyboard.Children.Add(opacityAnimation);
Resources.Add("Storyboard", storyboard);
}
}
}
This might not be easily implemented with WPF animations. Instead, a good method would be a game loop. A little research should turn up lots of resources about this. The first one that jumped out at me was http://www.nuclex.org/articles/3-basics/5-how-a-game-loop-works.
In your game loop, you would follow one or the other of these basic procedures:
Calculate how much time has elapsed since the last frame.
Move your displays appropriately.
or
Calculate the current time.
Position your displays appropriately.
The advantage of a game loop is that although the timing may drift slightly (depending on what sort of timing you use), all displays will drift by the same amount.
You can prevent clock drift by calculating time by the system clock, which for practical purposes does not drift. Timers do drift, because they do not run by the system clock.
Time sync is a vaster field than you'd think.
I suggest you take a look at Quartz.NET which is renowned for scheduling/timers issues.
Syncing a WPF animation is tricky because Storyboards are not part of the logical tree, therefore you can't bind anything in them.
That's why you can't define dynamic/variable Storyboards in XAML, you have to do it in C# as you did.
I suggest you make 2 Storyboards: one for the tick to the left, the other one to the right.
In between each animation, fire a method to do your calculations/update another part of the UI, but do it in a separate Task so that the timings aren't messed up (a few µs for the calculations make up for quite some time after 30s already!)
Keep in mind that you will need to use Application.Current.Dispatcher from your Task to update the UI.
And lastly, at least set the Task flag TaskCreationOptions.PreferFairness so that Tasks run in the order they were started.
Now since that just gives a hint to the TaskScheduler and doesn't guarantees them to run in order, you may want to use a queueing system instead for full guarantee.
HTH,
Bab.
You could try 2 animations , one for the right swing and one for the left. In the animation complete on each, start the other animation (checking for cancellation flags) and update your indicators (possibly via BeginInvoke on the Dispatcher so you don't interfere with the next animation start.)
I think getting the timer to sync with an animation is difficult - it is a dispatcher based timer which is based on messages - sometimes it can skip a bit of time, ie if you click fast with the mouse a lot I think the animation timer also is dispatcher based, so they will easily get out of sync.
I would suggest to abandon the syncing and let the timer handle it. Can't you let it update a property with notification and let your metronome arm position bind to that?
To get the accelaration/deceleration you just have to use a Sine or Cosine function.

GDI+ Paint Queue Problem

comrades) I've found some interesting behavior of Invalidate method in multithreaded applications. I hope you could help me with a problem...
I experience troubles while trying to invalidate different controls at one time: while they're identical, one succesfully repaints itself, but another - not.
Here is an example: I have a form (MysticForm) with two panels (SlowRenderPanel) on it. Each panel has a timer and with a period of 50ms Invalidate() method is called. In OnPaint method I draw number of current OnPaint call in the centre of panel. But notice that in OnPaint method System.Threading.Thread.Sleep(50) is called to simulate long time draw procedure.
So the problem is that the panel added first repaints itself much more often than another one.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MysticForm());
}
}
public class MysticForm : Form {
public SlowRenderPanel panel1;
public SlowRenderPanel panel2;
public MysticForm() {
// add 2 panels to the form
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width / 2 });
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width / 2 });
}
}
public class SlowRenderPanel : Panel {
// synchronized timer
private System.Windows.Forms.Timer timerSafe = null;
// simple timer
private System.Threading.Timer timerUnsafe = null;
// OnPaint call counter
private int counter = 0;
// allows to use one of the above timers
bool useUnsafeTimer = true;
protected override void Dispose(bool disposing) {
// active timer disposal
(useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose();
base.Dispose(disposing);
}
public SlowRenderPanel() {
// anti-blink
DoubleBuffered = true;
// large font
Font = new Font(Font.FontFamily, 36);
if (useUnsafeTimer) {
// simple timer. starts in a second. calls Invalidate() with period = 50ms
timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50);
} else {
// safe timer. calls Invalidate() with period = 50ms
timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true };
timerSafe.Tick += (sender, e) => { Invalidate(); };
}
}
protected override void OnPaint(PaintEventArgs e) {
string text = counter++.ToString();
// simulate large bitmap drawing
System.Threading.Thread.Sleep(50);
SizeF size = e.Graphics.MeasureString(text, Font);
e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width / 2f - size.Width / 2f, Height / 2f - size.Height / 2f));
base.OnPaint(e);
}
}
}
Debug info:
1) Each panel has a bool field useUnsafeTime (set to true by default) which allows using System.Windows.Forms.Timer (false) insted of System.Threading.Timer (true). In the first case (System.Windows.Forms.Timer) everything works fine. Removing System.Threading.Sleep call in OnPaint also makes execution fine.
2) Setting timer interval to 25ms or less prevents second panel repainting at all (while user doesn't resize the form).
3) Using System.Windows.Forms.Timer leads to speed increasement
4) Forcing control to enter synchronization context (Invoke) doesn't make sense. I mean that Invalidate(invalidateChildren = false) is "thread-safe" and could possibly have different behavior in diffenent contexts
5) Nothing interesting found in IL comparison of these two timers... They just use different WinAPI functions to set and remove timers (AddTimerNative, DeleteTimerNative for Threading.Timer; SetTimer, KillTimer for Windows.Forms.Timer), and Windows.Forms.Timer uses NativeWindow's WndProc method for rising Tick event
I use a similar code snippet in my application and unfortunately there is no way of using System.Windows.Forms.Timer) I use long-time multithreaded image rendering of two panels and Invalidate method is called after rendering is completed on each panel...
That would be great if someone could help me to understand what's different happening behind the scenes and how to solve the problem.
P.S. Interesting behavior isn't it?=)
Nice demonstration of what goes wrong when you use members of a control or form on a background thread. Winforms usually catches this but there's a bug in the Invalidate() method code. Change it like this:
timerUnsafe = new System.Threading.Timer(state => { Invalidate(true); }, null, 1000, 50);
to trip the exception.
The other panel is slower because lots of its Invalidate() calls are getting canceled by the paint event. Which is just slow enough to do so. Classic threading race. You cannot call Invalidate() from a worker thread, the synchronous timer is an obvious solution.
Invalidate() invalidates the client area or rectangle ( InvalidateRect() ) and "tells" Windows that next time Windows paints; refresh me, paint me. But it does not cause or invoke a paint message. To force a paint event, you must force windows to paint after an Invalidate call. This is not always needed, but sometimes it's what has to be done.
To force a paint you have to use the Update() function. "Causes the control to redraw the invalidated regions within its client area."
You have to use both in this case.
Edit: A common technique to avoid these kinds of problems is keep all your paint routines and anything related in a single (generally main) thread or timer. The logic can run elsewhere but where the actual paint calls are made should all be in one thread or timer.
This is done in games and 3D simulations.
HTH

Categories