I'm trying to load images from the web in my wpf application.
The idea is the following: When I click on a button, a popup with additional information is raised. In this popup I'm using some images from the web.
The problem: When the popup is being loaded the systems hangs while waiting for the images. I'm binding the images from my code behind. The images are stored in an ObservableCollection. I tried using a thread for loading the images but everytime I run into an exception saying the thread is not the owner of the object.
I tried using an Invoke to get the downloaded images to the UserinterfaceThread but I can't reach it. My code is the following:
IList<Image> imagesFromWeb = downloadImagesFromWeb(url);
DispatcherHelper.UIDispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
foreach (Image img in imagesFromWeb
{
this.ObservableCollection_Images.Add(img);
}
}
As soon as the images are downloaded and it tries to add the images to the (already opened) popup I get the exception saying the thread is not the owner of the object
Can someone please point me into the right direction?
If you have the image available on a public web server which can be adressed using a normal HTTP URI then you can set the source directly to that:
<Image Source="http://www.someserver.com/myimage.png" />
WPF will take care of downloading it - it'll even do it asynchronously I think although I'm not 100% sure.
You can of course do this with databinding as well:
<Image Source="{Binding TheImage}" />
And in the viewmodel
public string TheImage
{
get { return "http://www.someserver.com/myimage.png"; }
}
You can get a variety of issues with collections, WPF, binding and threading
The best thing (in my opinion) is to use a dispatcher-safe observable collection
here is an implementation, with also includes thread-safety:
public class SafeObservable<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
private readonly IList<T> collection = new List<T>();
private readonly Dispatcher dispatcher;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
private readonly ReaderWriterLock sync = new ReaderWriterLock();
public SafeObservable()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
public void Add(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoAdd(item);
else
dispatcher.BeginInvoke((Action)(() => DoAdd(item)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoAdd(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Add(item);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
sync.ReleaseWriterLock();
}
public void Clear()
{
if (Thread.CurrentThread == dispatcher.Thread)
DoClear();
else
dispatcher.BeginInvoke((Action)(DoClear));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoClear()
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Clear();
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public bool Contains(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Contains(item);
sync.ReleaseReaderLock();
return result;
}
public void CopyTo(T[] array, int arrayIndex)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.CopyTo(array, arrayIndex);
sync.ReleaseWriterLock();
}
public int Count
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.Count;
sync.ReleaseReaderLock();
return result;
}
}
public bool IsReadOnly
{
get { return collection.IsReadOnly; }
}
public bool Remove(T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
return DoRemove(item);
var op = dispatcher.BeginInvoke(new Func<T, bool>(DoRemove), item);
if (op == null || op.Result == null)
return false;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
return (bool)op.Result;
}
private bool DoRemove(T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
var index = collection.IndexOf(item);
if (index == -1)
{
sync.ReleaseWriterLock();
return false;
}
var result = collection.Remove(item);
if (result && CollectionChanged != null)
CollectionChanged(this, new
NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
return result;
}
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
public int IndexOf(T item)
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection.IndexOf(item);
sync.ReleaseReaderLock();
return result;
}
public void Insert(int index, T item)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoInsert(index, item);
else
dispatcher.BeginInvoke((Action)(() => DoInsert(index, item)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoInsert(int index, T item)
{
sync.AcquireWriterLock(Timeout.Infinite);
collection.Insert(index, item);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
sync.ReleaseWriterLock();
}
public void RemoveAt(int index)
{
if (Thread.CurrentThread == dispatcher.Thread)
DoRemoveAt(index);
else
dispatcher.BeginInvoke((Action)(() => DoRemoveAt(index)));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
private void DoRemoveAt(int index)
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection.RemoveAt(index);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
sync.ReleaseWriterLock();
}
public T this[int index]
{
get
{
sync.AcquireReaderLock(Timeout.Infinite);
var result = collection[index];
sync.ReleaseReaderLock();
return result;
}
set
{
sync.AcquireWriterLock(Timeout.Infinite);
if (collection.Count == 0 || collection.Count <= index)
{
sync.ReleaseWriterLock();
return;
}
collection[index] = value;
sync.ReleaseWriterLock();
}
}
}
I figured there's a better way to load the image.
Instead of binding to an image in the code behind it's better to bind to a string containing the location of the image. After that I use a converter in the xaml code which converts the string to an image. (the image downloader is now inside the converter class)
the code in xaml:
<Image Source="{Binding imageUrl, Converter={StaticResource url}}" Height="200" Width="200"></Image>
The code for the converter:
class ImageDownloader : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string url =(string)value;
return getImage(url);
}
private object getImage(string imagefile)
{
/// IMPLEMENT FUNCTION TO DOWNLOAD IMAGE FROM SERVER HERE
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
and ofcourse don't forget to set-up the resource in the app.xaml with:
<Application.Resources>
<ResourceDictionary>
<namespace:ImageDownloader x:Key="ImageDownloader" />
</ResourceDictionary>
</Application.Resources>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With