How do I load images in the background?

11,955

Solution 1

What I tend to use the ThreadPool.QueueUserWorkItem to load the image, then when the operation completes I call back to the UI thread using the thread-safe Dispatcher object. The image source is not thread safe, you will have to use something like the JpegBitmapDecoder, there is also a PngBitmapDecoder.

For Example:

public Window()
{
    InitializeComponent();

    ThreadPool.QueueUserWorkItem(LoadImage,
         "http://z.about.com/d/animatedtv/1/0/1/m/simp2006_HomerArmsCrossed_f.jpg");
}

public void LoadImage(object uri)
{
    var decoder = new JpegBitmapDecoder(new Uri(uri.ToString()), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
    decoder.Frames[0].Freeze();
    this.Dispatcher.Invoke(DispatcherPriority.Send, new Action<ImageSource>(SetImage), decoder.Frames[0]);
}

public void SetImage(ImageSource source)
{
    this.BackgroundImage.Source = source;
}

Solution 2

One more thing we had in our project. Since ImageSource is placed into UI you have to check if it is frozen:

public void SetImage(ImageSource source)
{
   ImageSource src = null;
   if(!source.IsFrozen)
       src = source.GetAsFrozen();
   else
       src = source; 
   this.BackgroundImage.Source = src;
}

Solution 3

You want to incorporate multiple threads in your WPF application. As stated in the article below, WPF forces you to do all UI work on the thread that created the UI.

Check this out: http://pjbelfield.wordpress.com/2007/10/29/worker-threads-and-the-dispatcher-in-wpf/

and for intensive UI work:

http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/

Share:
11,955
kevindaub
Author by

kevindaub

Updated on August 22, 2022

Comments

  • kevindaub
    kevindaub over 1 year

    I am trying to load an image in the background and then update the UI. I have been playing with this all day and I don't know what I am missing. I keep getting the following error:

    "The calling thread cannot access this object because a different thread owns it."

    I've hunted around following example after example, but I cannot seem to find an answer. I also wrapped the code that is touching the UI in another BeginInvoke.

    Update 3: The moral of the story. ImageSource is not thread safe for access.

    Update 2: This has got to be a simple solution :). I tried the cloning, but that didn't result in success, but I did get a different error: "Exception has been thrown by the target of an invocation."

    Update 1: I tried the BackgroundWorker, but I am still getting the same error, but it is occurring on the brush.ImageSource.Height. Am I signaling the UI correctly? Any suggestions?

    Here is my XAML:

    <Window x:Class="Slideshow.Show"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <DockPanel>
            <Canvas Background="Black" Name="canvas">
                <Viewbox Name="background" Stretch="Uniform">
                    <Rectangle name="background" />
                </Viewbox>
            </Canvas>
        </DockPanel>
    </Window>
    

    Here is some of the code behind:

    namespace Slideshow
    {
        public class Show 
        {
            public Show()
            {
                BackgroundWorker bw = new BackgroundWorker();
                bw.DoWork += new DoWorkEventHandler(bw_DoWork);
                bw.RunWorkerCompleted += 
                    new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
                bw.RunWorkerAsync();
            }
    
            void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                BitmapSource bitmap = e.Result as BitmapSource;
    
                if (bitmap != null)
                {
                    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal 
                        (ThreadStart)delegate()
                    {
                        Image image = new Image();
                        image.Source = bitmap;
                        background.Child = image;
                     });
                }
            }
    
            void bw_DoWork(object sender, DoWorkEventArgs e)
            {
                BitmapSource bitmapSource = CreateBrush(GetRandomFile());
                e.Result = bitmapSource;
             }
        }
    }