I was having trouble updating the UI of a WPF app on an event handler.
I could update text, but as soon as I tried anything with
BitmapImage I'd get an
InvalidOperationException
The calling thread cannot access this object because a different thread owns it.
A simplified version of the code looks like this:
<Window x:Class="WpfApplication7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" >
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Message,
UpdateSourceTrigger=PropertyChanged}"
FontSize="20" Height="40" Width="300"
Background="AliceBlue"/>
<Image Source="{Binding
Path=Image,UpdateSourceTrigger=PropertyChanged}"
Height="100" Width="100"/>
</StackPanel>
</Window>
With the C#:
using System;
using System.ComponentModel;
using System.Timers;
using System.Windows;
using System.IO;
using System.Windows.Threading;
using System.Windows.Media.Imaging;
namespace WpfApplication7
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public BitmapImage Image { get; private set; }
public string Message { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Timer timer;
public Window1()
{
InitializeComponent();
this.DataContext = this;
this.timer = new Timer { Enabled = true, Interval = 100 };
this.timer.Elapsed += (s, e) =>
{
//happy :)
this.Message = File.ReadAllText(@"c:\windows\win.ini").Substring(0, 20);
PropertyChanged(this, new PropertyChangedEventArgs("Message"));
//not happy :(
this.Image = new BitmapImage(
new Uri(@"C:\WINDOWS\Web\Wallpaper\Ascent.jpg"));
//right here:
//"The calling thread cannot access this object because a different thread owns it."
PropertyChanged(this, new PropertyChangedEventArgs("Image"));
};
}
}
}
The "calling thread" error would lead to you a solution involving threading. Which you can do by managing the UI updates within a Dispatcher.Invoke:
this.timer.Elapsed += (s, e) =>
{
this.Message = File.ReadAllText(@"c:\windows\win.ini").Substring(0, 20);
PropertyChanged(this, new PropertyChangedEventArgs("Message"));
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send, new Action(delegate
{
this.Image = new BitmapImage(new Uri(@"C:\WINDOWS\Web\Wallpaper\Ascent.jpg"));
PropertyChanged(this, new PropertyChangedEventArgs("Image"));
}));
};
However, the real point is that
BitmapImage is a DependencyObject. You can see this by going up the inheritance hierarchy:
System.Object
System.Windows.Threading.DispatcherObject
System.Windows.DependencyObject
System.Windows.Freezable
System.Windows.Media.Animation.Animatable
System.Windows.Media.ImageSource
System.Windows.Media.Imaging.BitmapSource
System.Windows.Media.Imaging.BitmapImage
So the more direct way to handle this is to call Freezable():
this.timer.Elapsed += (s, e) =>
{
this.Message = File.ReadAllText(@"c:\windows\win.ini").Substring(0, 20);
PropertyChanged(this, new PropertyChangedEventArgs("Message"));
this.Image = new BitmapImage(new Uri(@"C:\WINDOWS\Web\Wallpaper\Ascent.jpg"));
this.Image.Freeze();
PropertyChanged(this, new PropertyChangedEventArgs("Image"));
};
Thanks to the answers on
StackOverflow for helping me track this down.