What's the best way to asynchronously load an BitmapImage in C# using WPF?
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();
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
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;
}
}
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.
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);
}
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
}
Related
I have the following code snippet that I use to create a List to add to a scrollviewer as a binding in a WPF application:
private void LoadThumbs(object sender, DoWorkEventArgs e)
{
//ClearScreen();
int max = (int)e.Argument;
int current = 0;
foreach (string filename in filenames)
{
Image thumbnail = new Image();
Uri image_path = new Uri(filename);
BitmapImage image = new BitmapImage(image_path);
Thickness thumb_margin = thumbnail.Margin;
thumb_margin.Bottom = 2.5;
thumb_margin.Top = 2.5;
thumb_margin.Left = 2.5;
thumb_margin.Right = 2.5;
thumbnail.Margin = thumb_margin;
thumbnail.Width = 100;
image.DecodePixelWidth = 200;
thumbnail.Source = image;
thumbnail.Tag = filename;
thumbnail.MouseDown += image_Click;
thumbnail.MouseEnter += hand_Over;
thumbnail.MouseLeave += normal_Out;
images.Add(thumbnail);
thumbnail = null;
}
}
This worked fine until I added a BackgroundWorker to process this. Now, when execution gets to
Image thumbnail = new Image();
I get the following exception:
System.InvalidOperationException: 'The calling thread must be STA, because many UI components require this.'
Two questions:
(1) How can I process this code to allow the background worker to work on Image, or, (2) is there a better way to do what I am doing to allow for the BackgroundWorker to work?
I have zero experience working in a multi-threaded environment. I want it to work this way because the largest record I process has 180 images and creates about a 10-15 second hang.
Do not create Image elements in code behind. Instead, use an ItemControl with an appropriate ItemTemplate:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Bind it to a view model like shown below, which is capable of asynchronously loading the image files. It is important that the BitmapImages are frozen to make them cross-thread accessible.
public class ViewModel
{
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
public async Task LoadImagesAsync(IEnumerable<string> filenames)
{
foreach (var filename in filenames)
{
Images.Add(await Task.Run(() => LoadImage(filename)));
}
}
public ImageSource LoadImage(string filename)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.DecodePixelWidth = 200;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}
which is initialized like this:
private ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
...
await viewModel.LoadImagesAsync(..., "*.jpg"));
}
An alternative view model method could load the BitmapImages directly from a FileStream instead of an Uri:
public ImageSource LoadImage(string filename)
{
using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.DecodePixelWidth = 200;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}
I'm trying to have a rotating ad display in my WPF application. When I load the application the GetNewAd() method properly displays the advertisement. When I try to update the ad by calling my GetNewAd() method again, the chatHost is returning the new ad, but the image does not update in the UI. I have tried updating the image without the animation, but I still have the same problem. What am I missing here?
public class ncWindow
{
public ncWindow(Grid grid)
{
imgAd = new Image();
imgAd.Margin = new Thickness(2,2,2,2);
imgAd.HorizontalAlignment = HorizontalAlignment.Left;
imgAd.MouseDown += imgAd_MouseDown;
adTimer.Interval = 60000;
adTimer.Elapsed += adTimer_Elapsed;
adTimer.AutoReset = true;
adTimer.Start();
grid.Children.Add(imgAd);
}
public void GetNewAd()
{
DisplayedAd = chatHost.GetNewAd();
debug.Print("GetNewAd: " + DisplayedAd.VendorName + ", ImageData.Length = " + DisplayedAd.ImageData.Length);
BitmapImage image = new BitmapImage();
if (DisplayedAd.ImageData!=null && DisplayedAd.ImageData.Length>0)
{
using (var mem = new MemoryStream(DisplayedAd.ImageData))
{
mem.Position = 0;
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = mem;
image.EndInit();
}
}
else
return;
image.Freeze();
imgAd.Source = image;
}
private void adTimer_Elapsed(object source, ElapsedEventArgs e)
{
GetNewAd();
}
}
If the timer that calls your GetNewAd() method is not a DispatcherTimer, you'll explicitly have to invoke the assignment of the Image control's Source property in the UI thread:
image.Freeze(); // necessary for cross-thread access
imgAd.Dispatcher.BeginInvoke(new Action(() => imgAd.Source = image));
I am Capturing image from a finger print Scanner and i want to display the captured image live in an Image control.
//Onclick of a Button
Thread WorkerThread = new Thread(new ThreadStart(CaptureThread));
WorkerThread.Start();
So i created a thread as above and called the method that captures the image from the device and sets the source of the Image control as follows.
private void CaptureThread()
{
m_bScanning = true;
while (!m_bCancelOperation)
{
GetFrame();
if (m_Frame != null)
{
MyBitmapFile myFile = new MyBitmapFile(m_hDevice.ImageSize.Width, m_hDevice.ImageSize.Height, m_Frame);
MemoryStream BmpStream = new MemoryStream(myFile.BitmatFileData);
var imageSource = new BitmapImage();
imageSource.BeginInit();
imageSource.StreamSource = BmpStream;
imageSource.EndInit();
if (imgLivePic.Dispatcher.CheckAccess())
{
imgLivePic.Source = imageSource;
}
else
{
Action act = () => { imgLivePic.Source = imageSource; };
imgLivePic.Dispatcher.BeginInvoke(act);
}
}
Thread.Sleep(10);
}
m_bScanning = false;
}
Now when i Run the project it throws an Exception on line Action act = () => { imgLivePic.Source = imageSource; }; Saying "The Calling thread Can Not Access this object because a different thread owns it".
i did some research and i found out that if i want to use UI controls over a NON-UI thread i should use Dispatcher.Invoke method, which as you can see i have but i am still getting the same exception.
can someone please tell me what am i doing wrong?
The BitmapImage does not necessarily need to be created in the UI thread. If you Freeze it, it will later be accessible from the UI thread. Thus you will also reduce the resource consumption of your application. In general, you should try to freeze all Freezables if possible, especially bitmaps.
using (var bmpStream = new MemoryStream(myFile.BitmatFileData))
{
imageSource.BeginInit();
imageSource.StreamSource = bmpStream;
imageSource.CacheOption = BitmapCacheOption.OnLoad;
imageSource.EndInit();
}
imageSource.Freeze(); // here
if (imgLivePic.Dispatcher.CheckAccess())
{
imgLivePic.Source = imageSource;
}
else
{
Action act = () => { imgLivePic.Source = imageSource; };
imgLivePic.Dispatcher.BeginInvoke(act);
}
The BitmapImage itself needs to be constructed on the Dispatcher thread.
I have a method which is a time consuming one and therefore I have been trying to implement a BackgroundWorker, but it does not allow accessing UI controls which I have read and tried (hacking it) but to no avail.
What my method does: Creates a new BitmapImage, sets the source local or streamed (the parameter), writes it to a new WriteableBitmap, which is used for ConvertToGrayscale and then saves the BW Copy to IsolatedStorage in a folder.
So all this happens quite fast. But, only when I have say less than 25 Source Images. If I have about 100+ Images, this takes considerably long like 20 seconds or more and therefore, I would like to show a ProgressBar in the same PhoneApplicationPage but I have been struggling with how to not block the UI and show the ProgressBar while the method is doing its work.
This is the code that I have:
void GetImages()
{
if (!myIsolatedStorage.DirectoryExists("ImagesBW") && !_appsettings.Contains("_update"))
{
myIsolatedStorage.CreateDirectory("ImagesBW ");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings["_firstLaunch"] = "false";
_appsettings.Save();
}
else if (myIsolatedStorage.DirectoryExists("ImagesBW ") && _appsettings.Contains("_update"))
{
string[] files = myIsolatedStorage.GetFileNames("ImagesBW/*");
for (int s = 0; s < files.Length; s++)
{
myIsolatedStorage.DeleteFile("ImagesBW/" + s + ".jpg");
}
myIsolatedStorage.DeleteDirectory("ImagesBW");
myIsolatedStorage.CreateDirectory("ImagesBW");
for (int i = 0; i < coll.Desserts.Count; i++)
{
BitmapImage bmp = new BitmapImage();
bmp.CreateOptions = BitmapCreateOptions.None;
if (coll.Desserts[i].HasAssociatedImage)
{
bmp.SetSource(coll.Desserts[i].GetImage());
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
else
{
bmp.UriSource = new Uri("/Assets/Images/MissingArt.png", UriKind.Relative);
WriteableBitmap wb = new WriteableBitmap(bmp);
ConvertToGrayscale(wb);
BitmapImage bit = ConvertWBtoBI(wb);
SaveBWCopy(bi, i.ToString());
}
}
_appsettings.Remove("_update");
_appsettings.Save();
}
btnStart.IsEnabled = true;
}
Backgroundorker is your best bet. There are huge amount of resources on the web how to implement it. But the idea behind is that Backgroundworker runs in a separate thread from the UI. You can access main thread UI by two methods, a delegate or through the Backgroundworker's ProgressChanged method, which gives access to you UI thread.
Lets say you have this.
public MainWindow()
{
InitializeComponent();
//if you want to disable you button you can do it here
BackgroundWorker _bw = new BackgroundWorker();
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.WorkerReportsProgress = true;
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
//or here
//Display progress bar here too
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//this can give you access to the UI after work is completed
// to check that everything is ok or hide progress bar
}
void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//this will give you access to the UI in the middle of you work like update progress bar
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//actual work here including GetImages
//work work work
GetImages();
}
In you getimages method after the SaveBWCopy you can add this to update the progress bar
_bw.ReportProgress(int progress )
//progress is the percentage you want to send to progress bar to display that is going to be in the e eventargument you passed.
How to call method sendResponse to cross threaded call?
void watcher_Created(object sender, FileSystemEventArgs e) {
System.Diagnostics.Debug.WriteLine(e.FullPath);
sendResponse(e.FullPath); //this method must causes cross threaded call
}
I was trying :
Deployment.Current.Dispatcher.BeginInvoke(() => {
});
or Application.Current.Dispatcher.Invoke
But there is no Current .
How to deal with that?
I tried also:
if (this.InvokeRequired) {...}
But there is no InvokeRequired.
EDIT I get unsupported exception when I create any object like Bitmap in sendResponse method. This method is called after firing event. So I thought it is crossthreading.
void watcher_Created(object sender, FileSystemEventArgs e) {
System.Diagnostics.Debug.WriteLine(e.FullPath);
sendResponse(e.FullPath);
}
private void sendResponse(string path) {
try {
BitmapImage bmi = new BitmapImage(new Uri(#path, UriKind.Relative));
byte[] data;
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmi));
using (MemoryStream ms = new MemoryStream()) {
encoder.Save(ms);
data = ms.ToArray();
}
clientStream.Write(data, 0, data.Length);
} catch (Exception e) { System.Diagnostics.Debug.WriteLine(e); }
}
EDIT2 Client is widnows phone.. this(client) stopped worked after using code from the Nikita's answer(server stopped trhowing exceptions)
try {
Deployment.Current.Dispatcher.BeginInvoke(() => {
MemoryStream stream = new MemoryStream(e.Buffer, e.Offset, e.BytesTransferred);
BitmapImage im = new BitmapImage();
im.SetSource(stream);
MainPage.page.imagePanel.Source = im;
});
If you want to perform a call on UI thread, then you should save the reference to dispatcher by calling
_dispatcher = Application.Current.Dispatcher;
on UI thread. Then you can access it on non-UI thread and use _dispatcher.BeginInvoke or _dispatcher.Invoke in your watcher_Created method.
If you simply want to make your code threadsafe - you can wrap the call in the lock statement:
private readonly object _sendLock = new object();
void watcher_Created(object sender, FileSystemEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.FullPath);
lock(_sendLock)
{
sendResponse(e.FullPath);
}
}
Edit: you do not need to use wpf components for opening a bitmap and you should not. Instead, for example, you can do the following:
using (MemoryStream ms = new MemoryStream())
using (Image img = Image.FromStream(File.OpenRead(#path)))
{
img.Save(ms, ImageFormat.Jpeg);
var data = ms.ToArray();
clientStream.Write(data, 0, data.Length);
}
I don't have much of experience with WP8, but I think you should be able to use synchronization context, which is a universal concept. When your ViewModel or a UI element is created on the UI thread, remember the thread's current synchronization context:
SynchronizationContext _uiSyncContext;
// ...
_uiSyncContext = SynchronizationContext.Current;
Later from the background thread, use SynchronizationContext.Post (asynchronous callback) or SynchronizationContext.Send (synchronous callback) to execute code on the UI thread:
_uiSyncContext.Post((_) => { this.textBox.Text = "Hello"; }, null);