使用WPF在C#中异步加载BitmapImage

时间:2020-03-05 18:45:38  来源:igfitidea点击:

在CPF中异步加载BitmapImage的最佳方法是什么?似乎存在许多解决方案,但是是否存在标准模式或者最佳实践?

谢谢!

解决方案

回答

假设我们正在使用数据绑定,那么将Binding.IsAsync属性设置为True似乎是实现此目的的一种标准方法。
如果使用后台线程+ Dispatcher对象在代码隐藏文件中加载位图,则是更新UI异步的一种常见方法

回答

使用或者扩展System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/zh-CN/library/system.componentmodel.backgroundworker.aspx

我个人认为这是在客户端应用程序中执行异步操作的最简单方法。 (我在WinForms中使用了此功能,但未在WPF中使用过。我假设这在WPF中也适用。)

我通常会扩展Backgroundworker,但我们不必这样做。

public class ResizeFolderBackgroundWorker : BackgroundWorker
{

    public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
    {
        this.sourceFolder = sourceFolder;
        this.destinationFolder = destinationFolder;
        this.resizeTo = resizeTo;

        this.WorkerReportsProgress = true;
        this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
    }

    void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
        FileInfo[] files = dirInfo.GetFiles("*.jpg");

        foreach (FileInfo fileInfo in files)
        {
            /* iterate over each file and resizing it */
        }
    }
}

这是我们在表单中使用它的方式:

//handle a button click to start lengthy operation
    private void resizeImageButtonClick(object sender, EventArgs e)
    {
        string sourceFolder = getSourceFolderSomehow();
        resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
        resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
        resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);

        progressBar1.Value = 0;
        progressBar1.Visible = true;

        resizer.RunWorkerAsync();
    }

    void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        progressBar1.Visible = false;
        //signal to user that operation has completed
    }

    void genericProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
        //I just update a progress bar
    }

回答

为了详细说明aku的答案,以下是有关在何处设置IsAsync的一个小示例:

ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"

那就是在XAML中要做的事情。

回答

我只是在调查这个问题,不得不投入两分钱,尽管在原始帖子发布后已经过了几年(以防万一其他人都在寻找我正在调查的同一件事)。

我有一个Image控件,需要使用Stream在后台将其图像加载,然后显示。

我一直遇到的问题是BitmapSource,它的Stream源和Image控件都必须在同一线程上。

在这种情况下,使用Binding并将其设置为IsAsynch = true将引发跨线程异常。

BackgroundWorker非常适合WinForms,我们可以在WPF中使用它,但是我宁愿避免在WPF中使用WinForm程序集(不建议对项目进行膨胀,这也是一个很好的经验法则)。在这种情况下,这也应该引发无效的交叉引用异常,但是我没有对其进行测试。

事实证明,一行代码可以完成以下任何一项工作:

//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};

//Create a seperate thread to load the image
ThreadStart thread = delegate
     {
         //Load the image in a seperate thread
         BitmapImage bmpImage = new BitmapImage();
         MemoryStream ms = new MemoryStream();

         //A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(@"C:\ImageFileName.jpg", FileMode.Open);
         MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);

         bmpImage.BeginInit();
         bmpImage.StreamSource = ms;
         bmpImage.EndInit();

         //**THIS LINE locks the BitmapImage so that it can be transported across threads!! 
         bmpImage.Freeze();

         //Call the UI thread using the Dispatcher to update the Image control
         Dispatcher.BeginInvoke(new ThreadStart(delegate
                 {
                         img.Source = bmpImage;
                         img.Unloaded += delegate 
                                 {
                                         ms.Close();
                                         ms.Dispose();
                                 };

                          grdImageContainer.Children.Add(img);
                  }));

     };

//Start previously mentioned thread...
new Thread(thread).Start();