Updating Image control in UI thread from a timer callback in WPF - c#

I have an image control in a Xaml file as follows:
<Viewbox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Center">
<Image Name="Content"/>
</Viewbox>
I'd like to update this image with a different image every 10 seconds.
I create a system.threading.Timer instance, initialize it with a callback, pass in the UI control as a state object and set the interval to 10 seconds as follows:
contentTimer = new Timer(OnContentTimerElapsed, Content , 0 , (long) CONTENT_DISPLAY_TIME);
The callback looks as follows:
private void OnContentTimerElapsed( object sender )
{
Image content = (Image)sender;
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
//get the url of the image file, and create bitmap from the jpeg
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + nCurrentImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
//update the image control, by launching this code in the UI thread
content.Dispatcher.BeginInvoke(new Action(() => { content.Source = bitmap; }));
}
I still keep getting the following exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object because a different thread owns it.
I was able to get a solution by updating just the numCurrentImage variable, and then updating the Content.Source in the MainWindow class in callbacks running on the UI thread, something as follows (note, I'm getting frames at 30fps from a kinect):
int nCurrentImage;
Public MainWindow()
{
InitializeComponent();
nCurrentImage = 1;
System.Timers.Timer contentTimer = new System.Timers.Timer(OnContentTimerElapsed, CONTENT_DISPLAY_TIME);
contentTimer.Elapsed += OnContentTimerElapsed;
...
//Some kinect related initializations
...
kinect.multiSourceReader.MultiSourceFrameArrived += OnMultiSourceFrameArrived;
}
private void OnContentTimerElapsed( object sender )
{
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
}
private void OnMultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
UpdateContent(nCurrentImage);
}
private void UpdateContent(int numImage)
{
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + numImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
Content.Source = bitmap;
}
Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Any Ideas what I'm doing wrong?

Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Actually this is exactly what you want to be doing. You want to be doing your non-UI work in a non-UI thread, and doing your UI work in a UI thread.
That said, while this is fundamentally what you want to be doing, you don't need to do all of this yourself so explicitly. You can simply use a DispatcherTimerand it will fire the callback in the UI thread, rather than a thread pool thread. It is, more or less, doing much of what you're doing manually.

Update the XAML image element, I like to name them all with an X to remind me it's a XAML element.
<Image Name="XContent"/>
When timer fires,
...
bitmap.Freeze();
XContent.Dispatcher.Invoke(()=>{
XContent.Source = bitmap;
}

Related

Loading a web image asynchronously

I've encountered a problem with displaying an image from the web in my WPF app:
I used a BitmapImage for this task. My first attempt was to execute it in the UI thread but I quickly understood that's a no-no since the application became unresponsive until the image was completely loaded. My second attempt was to use a BackgroudWorker:
var worker = new BackgroundWorker();
worker.DoWork += worker_LoadImage;
worker.RunWorkerCompleted+=worker_RunWorkerCompleted;
worker.RunWorkerAsync(someURI);
and the worker functions:
private void worker_LoadImage(object sender, DoWorkEventArgs e)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = e.Argument as Uri;
image.DownloadFailed += new EventHandler<ExceptionEventArgs>(image_DownloadFailed);
image.EndInit();
e.Result = image;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if I understand correctly, this code runs in the UI thread so the
//access to the component image1 is valid.
image1.Source = e.Result as BitmapImage;
}
after that, I still got an InvalidOperationException: "The calling thread cannot access this object because a different thread owns it."
I've researched a bit and found out that since BitmapImage is Freezable, I have to call Freeze before accessing the object from another thread.
So I've tried to replace the last row in worker_LoadImage with:
image.Freeze();
e.Result = image;
But then I got an exception that my image cannot be frozen, I found out that it's probably
because the image wasn't done being downloaded when I tried to invoke Freeze(). So I added the following code to the image creation:
image.DownloadCompleted += image_DownloadCompleted;
where:
void image_DownloadCompleted(object sender, EventArgs e)
{
BitmapImage img = (BitmapImage)sender;
img.Freeze();
}
So now we get to the real question:
How do I make the background worker to wait until the image is completely downloaded and the event is fired?
I've tried many things: looping while the image's isDownloading is true, Thread.Sleep, Thread.Yield, Semaphores, Event wait handles and more.
I dont know how the image downloading actually works behind the scenes but what happens when I try one of the methods above is that the image never finishes to download (isDownloading is stuck on True)
Is there a better, simpler way to achieve the rather simple task im trying to accomplish?
Some things to notice:
this answer actually works, but only once: when I try to load another image it says the dispatcher is closed. Even after reading a bit about Dispatchers, I don't really understand how the OP achieved that or if it's possible to extend the solution for more than one image.
When I put a message box before the worker exits his DoWork function, I click OK and the image apears which means the download continued while the message box was opened and finished before I clicked OK.
Given that you're using the bitmap's ability to asynchronously load the image you don't need a BackgroundWorker in the first place. Rather than creating a BGW to start an asynchronous operation and wait for it to finish, just use the asynchronous operation directly.
All you have to do is update the UI from your image_DownloadCompleted handler (after freezing the image) and you no longer need a BGW anymore:
private void FetchImage(Uri uri)
{
var context = SynchronizationContext.Current;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = uri;
image.DownloadFailed += image_DownloadFailed;
image.DownloadCompleted += (s, args) =>
{
image.Freeze();
context.Post(_ => image1.Source = image, null);
};
image.EndInit();
}

UI Element is not updating

If you can picture this as a loading bar:
for (int i = 0; i < r.Width / 2; i++) //Fills half the loading bar.
{
Rectangle rect = can.Children[3] as Rectangle; //getting the 3rd element of my Canvas
if (rect != null)
{
rect.Fill = brush;
rect.Width = i;
Thread.Sleep(50);
}
}
So, the application will not show the progress of adding additional rectangles. Instead it just freezes up for the total time of the thread sleep and when the for loop is finished it will show the entire bar.
Why is this?
I'm aware there is a loading bar built in, but this is just an example of problems I have been having.
I'd recommend you start reading about Threads, background workers, dispatchers. Unless you add threads to your solution, the entire applications runs with the help of UI thread.
If you have got task which can be performed serially, you need not implement Threads or background worker. In your case, you are trying to do 2 task.
Update the rectangle and draw it on window
Sleep thread for 50 milliseconds
When UI thread executes thread.Sleep(50); it cannot draw (show progress in rectangle). That's the reason your application kind of freezes for seconds and gets completed before you could see any progress. I am going to try and explain this with 2 examples.
Example:1
Add a progress bar with IsIndeterminate="True" and Button1 to your demo application.
<ProgressBar IsIndeterminate="True" Height="20" Name="progressBar1" Width="177" Visibility="Hidden" />
In code behind,
private void button1_Click(object sender, RoutedEventArgs e)
{
progressBar1.Visibility = Visibility.Visible;
Thread.Sleep(5000);
progressBar1.Visibility = Visibility.Hidden;
}
If you run this code, You will not see any progress bar as the code is getting executed serially. To make sure that progressbar is getting shown and hide properly, do following changes.
Example 2:
private void button1_Click(object sender, RoutedEventArgs e)
{
progressBar1.Visibility = Visibility.Visible;
Thread t = new Thread(DoMyWork);
t.Start();
}
private void DoMyWork()
{
Thread.Sleep(5000);
//hide the progress bar after completing your work
this.Dispatcher.Invoke((Action)delegate { progressBar1.Visibility = Visibility.Hidden; });
}
Run this code and you can see that while UI thread updates progressbar, new thread created executes DoMyWork function.
You will need this.Dispatcher.Invoke((Action)delegate as progressBar1 was created in UI thread and you cannot access it in a another thread. i.e.Thread t = new Thread(..)
P.S.- This is dirty of way to coding. It is just to give an idea that how things should work in OP's case.

Create control in thread and loading photo async

I have a Windows Form, inside I have a Button and a Panel.
On button click I'm adding a control to the panel... as many as I want. This process is using Task Factory. Example:
private void ButtonClick()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
CustomControl.PersonResult newControl = new CustomControl.PersonResult();
this.panel1.Controls.Add(newControl);
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
My PersonResult.cs control has the following layout:
The control has a picturebox and 3 lables.
Now, when a new PersonResult control is added to the form, I want to perform other background worker in order to get an image from the internet and place it in the picturebox.
So, the scenario is:
I press the button many times and immediately I will see the customcontrol added to the panel, but the picturebox of every control will be empty, but then images will start appearing as soon as the worker loads the image from internet and place it on the picturebox.
Any clue on how do implement this?
Thanks a lot
Any time you touch a control you must do so from the UI thread. You can do the actual downloading of the picture and other long-running tasks from your thread/task. From your thread you use Control.Invoke to invoke a delegate on the UI thread.
This MSDN article has a good example of using Control.Invoke with simple variables like strings and such:
http://msdn.microsoft.com/en-us/library/a1hetckb(v=vs.100).aspx
But often a helper class is used so you can pass more complex data between your delegates without resorting to big nasty object arrays.
Here's some sample code I did up:
private void button1_Click(object sender, EventArgs e) {
Task.Factory.StartNew(() =>
{
DataClass dataClass = new DataClass(this.textBox1);
Thread.Sleep(5000); // simulate long running task
dataClass.TextToPass = "set some text";
dataClass.updateTargetControl();
});
}
private class DataClass {
delegate void updateTextBoxOnUiThreadDelegate();
private TextBox _targetControl= null;
private updateTextBoxOnUiThreadDelegate _updateDelegate;
internal DataClass(TextBox targetControl) {
_updateDelegate = new updateTextBoxOnUiThreadDelegate(updateOnUiThread);
_targetControl = targetControl;
}
internal string TextToPass = "";
internal void updateTargetControl() {
_targetControl.Invoke(_updateDelegate);
}
private void updateOnUiThread() {
_targetControl.Text = this.TextToPass;
}
}
Something along the lines:
You don't have to add controls asynchronously. Add them in the GUI thread and every time create a new worker thread supplying it with the delegate from your control which will be called asynchronously (using BeginInvoke?) when the worker finished loading the image.
I am not quite sure I understand why you've wrapped a UI operation in its own Task.. when it isn't chained to an async Task.
Anyway.. PictureBoxes have a LoadAsync method. Why make this harder than it needs to be?
private void ButtonClick()
{
CustomControl.PersonResult newControl = new CustomControl.PersonResult();
this.panel1.Controls.Add(newControl);
newControl.PictureBox.WaitOnLoad = false;
newControl.PictureBox.LoadAsync("http://url-here.com/image.jpg");
}

Thread-safe calls to WPF controls

I am just writing my first program using WPF and C#. My windows just contains a simple canvas control:
<StackPanel Height="311" HorizontalAlignment="Left" Name="PitchPanel" VerticalAlignment="Top" Width="503" Background="Black" x:FieldModifier="public"></StackPanel>
This works fine and from the Window.Loaded event I can access this Canvas called PitchPanel.
Now I have added a class called Game which is initialized like this:
public Game(System.Windows.Window Window, System.Windows.Controls.Canvas Canvas)
{
this.Window = Window;
this.Canvas = Canvas;
this.GraphicsThread = new System.Threading.Thread(Draw);
this.GraphicsThread.SetApartmentState(System.Threading.ApartmentState.STA);
this.GraphicsThread.Priority = System.Threading.ThreadPriority.Highest;
this.GraphicsThread.Start();
//...
}
As you can see, there is a thread called GraphicsThread. This should redraw the current game state at the highest possible rate like this:
private void Draw() //and calculate
{
//... (Calculation of player positions occurs here)
for (int i = 0; i < Players.Count; i++)
{
System.Windows.Shapes.Ellipse PlayerEllipse = new System.Windows.Shapes.Ellipse();
//... (Modifying the ellipse)
Window.Dispatcher.Invoke(new Action(
delegate()
{
this.Canvas.Children.Add(PlayerEllipse);
}));
}
}
But although I have used a dispatcher which is invoked by the main window which is passed at the creation of the game instance, an unhandled exception occurs: [System.Reflection.TargetInvocationException], the inner exceptions says that I cannot access the object as it is owned by another thread (the main thread).
The Game is initialized in the Window_Loaded-event of the application:
GameInstance = new TeamBall.Game(this, PitchPanel);
I think this is the same principle as given in this answer.
So why does this not work? Does anybody know how to make calls to a control from another thread?
You cannot create a WPF object on a different thread - it must also be created on the Dispatcher thread.
This:
System.Windows.Shapes.Ellipse PlayerEllipse = new System.Windows.Shapes.Ellipse();
must go into the delegate.

MainWindow() and InitializeComponent() building in WPF and C#

This code is from my C# WPF application:
public MainWindow()
{
InitializeComponent();
status("Getting dl links");
getLinks();
}
The procedure getLinks currently displays a couple of links in a messagebox. Those links are displayed in a messagebox before the WPF application becomes visible.
In this is case not a problem, but how would I show progress (like a progressbar) of any
procedure I want to load at startup?
Here is an example on how you can do it. To simplify it a bit, I added the controls directly in the MainWindow constructor, but I would prefer to do this with XAML.
public MainWindow()
{
InitializeComponent();
var progressBar = new ProgressBar();
progressBar.Height = 40;
progressBar.Width = 200;
progressBar.Margin = new Thickness(100, 100, 100, 100);
Task.Factory.StartNew(() =>
{
// getLinks();
for (var i = 0; i < 5; i++)
{
Dispatcher.Invoke(new Action(() => { progressBar.Value += 20; }));
Thread.Sleep(500);
}
});
var stackPanel = new StackPanel();
stackPanel.Children.Add(progressBar);
Content = stackPanel;
}
I first add a ProgressBar somewhere on the interface to make it visible for this demo and then I add it to a new StackPanel, it could be any panel at all, in this case it doesn't matter.
To load the links on another thread, I create a new Task, this is a part of the TPL (Task Parallel Library) in .NET 4.0. In this case I am simulating that getLinks() takes 5 * 500 milliseconds to run and that it in fact is five links that will be loaded, hence 20% each iteration.
What I do then is that I add 20 to the progressBar value, which indicates that it increased with 20%.
This line might confuse you a bit
Dispatcher.Invoke(new Action(() => { progressBar.Value += 20; }));
But it is in fact quite common when you do cross-thread programming with GUI. So the problem is that you are on another thread here, we started of a Task that will run on a separate thread, and you cannot update your UI thread from another thread. So what you need is something called a Dispatcher, and this is accessable from within your Window-class.
Then you Invoke an action on it, which means that you simply say "Run this piece of code on this thread for me".
And if you want to display a MessageBox when everything is loaded, you can simply add a MessageBox.Show("Loaded!"); after the for-loop.
Any 'loading' tasks need to happen on a background thread (see the BackgroundWorker class - google for lots of examples). That way, the UI thread is free to show your window and update your window with status messages. Otherwise, the UI thread is blocked from doing anything until your loading is complete.

Categories

Resources