Какой лучший способ асинхронно загрузить BitmapImage в C # с помощью WPF?
Асинхронная загрузка BitmapImage в C # с использованием WPF
Ответы (5)
Я просто изучал это и должен был вложить свои два цента, хотя через несколько лет после первоначального сообщения (на случай, если кто-то еще придет искать то же самое, что и я).
У меня есть элемент управления Изображение, изображение которого необходимо загрузить в фоновом режиме с помощью потока, а затем отобразить.
Проблема, с которой я постоянно сталкивался, заключалась в том, что BitmapSource, источник Stream и элемент управления Image должны были находиться в одном потоке.
В этом случае использование Binding и установка IsAsynch = true вызовет исключение между потоками.
BackgroundWorker отлично подходит для WinForms, и вы можете использовать его в WPF, но я предпочитаю избегать использования сборок WinForm в WPF (раздувание проекта не рекомендуется, и это тоже хорошее практическое правило). Это должно вызвать исключение недопустимой перекрестной ссылки и в этом случае, но я не проверял его.
Оказывается, одна строка кода выполнит любое из этих действий:
//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();
ThreadPool.QueueUserWorkItem(_ => { ... })
и вот ваша ветка;
- person SandRock; 28.01.2013
Предполагая, что вы используете привязку данных, установка Binding .IsAsync в True кажется стандартным способом добиться этого. Если вы загружаете растровое изображение в файл кода программной части, используя фоновый поток, + объект Dispatcher - это распространенный способ асинхронного обновления пользовательского интерфейса.
Binding.IsAsync
позволяет указывать Uri / путь в Image.Source
и автоматически загружать изображение асинхронно. WPF позаботится о любых проблемах с перекрестными потоками в этом сценарии. Если вы создаете растровые изображения в фоновом потоке, нужно просто вызвать Freeze()
для объекта растрового изображения, прежде чем передать его потоку пользовательского интерфейса. Он вызывает исключение только в том случае, если вы делаете это неправильно.
- person Peter Duniho; 08.03.2017
Это позволит вам создать BitmapImage в потоке пользовательского интерфейса, используя HttpClient для асинхронной загрузки:
private async Task<BitmapImage> LoadImage(string url)
{
HttpClient client = new HttpClient();
try
{
BitmapImage img = new BitmapImage();
img.CacheOption = BitmapCacheOption.OnLoad;
img.BeginInit();
img.StreamSource = await client.GetStreamAsync(url);
img.EndInit();
return img;
}
catch (HttpRequestException)
{
// the download failed, log error
return null;
}
}
Чтобы подробнее рассказать об ответе aku, вот небольшой пример того, где установить IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
Это то, что вы сделали бы в XAML.
Используйте или расширьте System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/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
}