Reputation: 2276
What's the best way to asynchronously load an BitmapImage in C# using WPF?
Upvotes: 13
Views: 20951
Reputation: 469
This will allow you to create the BitmapImage on the UI thread by using the HttpClient to do the async downloading:
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;
}
}
Upvotes: 3
Reputation: 2503
BitmapCacheOption.OnLoad
var bmp = await System.Threading.Tasks.Task.Run(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(path);
img.EndInit();
ImageBrush brush = new ImageBrush(img);
}
Upvotes: 2
Reputation: 71
I was just looking into this and had to throw in my two cents, though a few years after the original post (just in case any one else comes looking for this same thing I was looking into).
I have an Image control that needs to have it's image loaded in the background using a Stream, and then displayed.
The problem that I kept running into is that the BitmapSource, it's Stream source and the Image control all had to be on the same thread.
In this case, using a Binding and setting it's IsAsynch = true will throw a cross thread exception.
A BackgroundWorker is great for WinForms, and you can use this in WPF, but I prefer to avoid using the WinForm assemblies in WPF (bloating of a project is not recommended, and it's a good rule of thumb too). This should throw an invalid cross reference exception in this case too, but I didn't test it.
Turns out that one line of code will make any of these work:
//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();
Upvotes: 7
Reputation: 3363
To elaborate onto aku's answer, here is a small example as to where to set the IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
That's what you would do in XAML.
Upvotes: 2
Reputation: 33406
Use or extend System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Personally, I find this to be the easiest way to perform asynchronous operations in client apps. (I've used this in WinForms, but not WPF. I'm assuming this will work in WPF as well.)
I usually extend Backgroundworker, but you dont' have to.
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 */
}
}
}
This is how you would use it in your form:
//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
}
Upvotes: 0
Reputation: 123966
Assuming you're using data binding, setting Binding.IsAsync property to True seems to be a standard way to achieve this. If you're loading the bitmap in the code-behind file using background thread + Dispatcher object is a common way to update UI asynchronous
Upvotes: 3