Reputation: 191
I am trying to populate a ListView with an ObservableCollection of SearchResult
containing a ObservableCollection<Inline>
. My (simplified) data structure is:
public class SearchResult
{
public static ObservableCollection<Inline> FormatString(string s)
{
ObservableCollection<Inline> inlineList = new ObservableCollection<Inline>
{
new Run("a"),
new Run("b") { FontWeight = FontWeights.Bold },
new Run("c")
};
return inlineList;
}
public ObservableCollection<Inline> Formatted { get; set; }
public string Raw { get; set; }
}
It contains a ObservableCollection<Inline>
because these SearchResults will be displayed with a custom BindableTextBlock
which supports rich text:
public class BindableTextBlock : TextBlock
{
public ObservableCollection<Inline> InlineList
{
get { return (ObservableCollection<Inline>)GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}
public static readonly DependencyProperty InlineListProperty = DependencyProperty.Register("InlineList", typeof(ObservableCollection<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
BindableTextBlock textBlock = (BindableTextBlock)sender;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange((ObservableCollection<Inline>)e.NewValue);
}
}
However when populating the ListView
<ListView Name="allSearchResultsListView">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<local:BindableTextBlock InlineList="{Binding Formatted}" />
<TextBlock Text="{Binding Raw}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
with the following BackgroundWorker
public partial class MainWindow : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
ObservableCollection<SearchResult> searchResults = new ObservableCollection<SearchResult>();
public MainWindow()
{
InitializeComponent();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (long i = 0; i < 1000; i++)
{
searchResults.Add(new SearchResult()
{
Formatted = SearchResult.FormatString("a*b*c"),
Raw = "abc"
});
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
allSearchResultsListView.ItemsSource = searchResults;
}
}
the program crashes with
Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll
Inner Exception: The calling thread cannot access this object because a different thread owns it
The problem is that within the background worker UI elements (Inline
) get created which don't belong to the UI thread. When assigning the ItemsSource after the worker has completed the exception gets thrown.
Similar questions seem to have been asked a lot but I couldn't find anything for my particular case.
Any help is appreciated!
Upvotes: 0
Views: 163
Reputation: 4784
To interact with UI elements you have to use "Invoke" or "BeginInvoke" in the UI thread dispatcher
Application.Current.Dispatcher.Invoke((Action)delegate
{
//CHANGE DATA BOUND TO THE UI HERE
});
I like to use a static method:
public static class Helpers
{
public static void RunInUIThread(Action method)
{
if (Application.Current == null)
{
return;
}
Application.Current.Dispatcher.BeginInvoke((Action)delegate
{
method();
});
}
}
And you use it like this:
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Helpers.RunInUIThread(()=>allSearchResultsListView.ItemsSource = searchResults);
}
BTW you should use background threads for long running operations, like getting data from a web service.
Upvotes: 2