tags:

views:

2259

answers:

2

I'm trying to understand what it is about the following code that is perfectly happy with loading a text file and displaying its contents, but isn't happy with loading a BitmapImage and displaying it on a timer.Elapsed event handler.

I understand it has to do with the UI thread.

But why is this not a problem for the textfile example?

First, the XAML:

<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>

and the C#, which raises a PropertyChangedEventHandler on a timer:

using System;
using System.ComponentModel;
using System.Timers;
using System.Windows;
using System.IO;
using System.Windows.Threading;
using System.Windows.Media.Imaging;

and

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 loading from text file. UI updates :)
        this.Message = File.ReadAllText(@"c:\windows\win.ini").Substring(0, 20);
        PropertyChanged(this, new PropertyChangedEventArgs("Message"));

        //---not happy loading a BitmapImage. PropertyChanged unhappy :(
        // (Don't make me have to: ! )
        //Application.Current.Dispatcher.Invoke(
        //DispatcherPriority.Send, new Action(delegate
        //{

          this.Image = new BitmapImage(new Uri(@"C:\WINDOWS\Web\Wallpaper\Ascent.jpg"));

          //Edit --Ah hah, thanks Daniel ! 
          // DependencyObject-> Freezable-> Animatable-> 
          // ImageSource-> BitmapSource-> BitmapImage
          this.Image.Freeze();  //<--- this will fix it, no need for Dispatcher

          //Without Dispatcher or Freeze() ... right here:
          //"The calling thread cannot access this object because a different thread owns it."
          PropertyChanged(this, new PropertyChangedEventArgs("Image"));
        //}));
      };
    }
  }
}

I know I can fix this with a "Application.Current.Dispatcher.Invoke". So fixing it isn't the problem. Not understanding why I should have to is the problem :)

Similar questions

+1  A: 

I think the critical difference between the two scenarios is that BitmapImage is a dependency object, which means it has the concept of an "owning" thread (the thread that created the object). When your main UI thread tries to access the BitmapImage object created on (and owned by) another thread...boom!

Strings, on the other hand, do not have a concept of an "owning" thread.

Daniel Pratt
Daniel thanks this really helped me understand the root of the problem.
Jeffrey Knight
+1  A: 

I think it's because BitmapImage are DispatchObject's; you are creating a DispatcherObject on the non-UI thread thus the exception. You don't see error with the text assignment since the text isn't a threaded object.

Scott