I’m new to multithreading and WPF.
I have an ObservableCollection<RSSFeed>, at app startup items are added to this collection from UI thread. Properties of RSSFeed are bind to WPF ListView. Later, I want to update each RSSFeed asynchronously. So I’m thinking of implementing something like RSSFeed.FetchAsync() and raising PropertyChanged on its updated properties.
I know that ObservableCollection doesn’t support updates from threads other than the UI thread, it throws NotSupportedException. But since I’m not manipulating the ObservableCollection itself but rather updating properties on its items, can I expect this to work and see ListView items updated? Or would it threw an exception anyway due to PropertyChanged?
Edit: code
RSSFeed.cs
public class RSSFeed
{
public String Title { get; set; }
public String Summary { get; set; }
public String Uri { get; set; }
public String Encoding { get; set; }
public List<FeedItem> Posts { get; set; }
public bool FetchedSuccessfully { get; protected set; }
public RSSFeed()
{
Posts = new List<FeedItem>();
}
public RSSFeed(String uri)
{
Posts = new List<FeedItem>();
Uri = uri;
Fetch();
}
public void FetchAsync()
{
// call Fetch asynchronously
}
public void Fetch()
{
if (Uri != "")
{
try
{
MyWebClient client = new MyWebClient();
String str = client.DownloadString(Uri);
str = Regex.Replace(str, "<!--.*?-->", String.Empty, RegexOptions.Singleline);
FeedXmlReader reader = new FeedXmlReader();
RSSFeed feed = reader.Load(str, new Uri(Uri));
if (feed.Title != null)
Title = feed.Title;
if (feed.Encoding != null)
Encoding = feed.Encoding;
if (feed.Summary != null)
Summary = feed.Summary;
if (feed.Posts != null)
Posts = feed.Posts;
FetchedSuccessfully = true;
}
catch
{
FetchedSuccessfully = false;
}
}
}
UserProfile.cs
public class UserProfile : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event CollectionChangeEventHandler CollectionChanged;
private ObservableCollection<RSSFeed> feeds;
public ObservableCollection<RSSFeed> Feeds
{
get { return feeds; }
set { feeds = value; OnPropertyChanged("Feeds"); }
}
public UserProfile()
{
feeds = new ObservableCollection<RSSFeed>();
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void OnCollectionChanged(RSSFeed feed)
{
CollectionChangeEventHandler handler = CollectionChanged;
if (handler != null)
{
handler(this, new CollectionChangeEventArgs(CollectionChangeAction.Add, feed));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
// My ListView is bound to this
// ItemsSource="{Binding Posts}
public List<FeedItem> Posts
{
get
{
if (listBoxChannels.SelectedItem != null)
return ((RSSFeed)listBoxChannels.SelectedItem).Posts;
else
return null;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// here I load cached feeds
// called from UI thread
// now I want to update the feeds
// since network operations are involved,
// I need to do this asynchronously to prevent blocking the UI thread
}
}
Thanks.
For this kind of application, I usually use a BackgroundWorker with ReportsProgress set to True. Then you can pass one object for each call as the userState parameter in the ReportProgress method. The ProgressChanged event will run on the UI thread, so you can add the object to the ObservableCollection in the event handler.
Otherwise, updating the properties from a background thread will work, but if you are filtering or sorting the ObservableCollection, then the filter will not be reapplied unless some collection change notification event has been raised.
You can cause filters and sorts to be reapplied by finding the index of the item in the collection (e.g. by reporting it as progresspercentage) and setting the list.item(i) = e.userstate, i.e. replacing the item in the list by itself in the ProgressChanged event. This way, the SelectedItem of any controls bound to the collection will be preserved, while filter and sorting will respect any changed values in the item.