Заполните ListView с помощью BackgroundWorker: элементы пользовательского интерфейса не принадлежат потоку - PullRequest
0 голосов
/ 05 марта 2019

Я пытаюсь заполнить ListView набором ObservableCollection SearchResult, содержащим ObservableCollection<Inline>.Моя (упрощенная) структура данных:

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; }
}

Содержит ObservableCollection<Inline>, потому что эти результаты поиска будут отображаться с пользовательским BindableTextBlock, который поддерживает форматированный текст:

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);
    }
}

Однакопри заполнении ListView

<ListView Name="allSearchResultsListView">
    <ListView.ItemTemplate>
        <DataTemplate>
            <WrapPanel>
                <local:BindableTextBlock InlineList="{Binding Formatted}" />
                <TextBlock Text="{Binding Raw}" />
            </WrapPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

следующим 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;
    }
}

программа аварийно завершает работу с

Exception thrown: 'System.Windows.Markup.XamlParseException' in PresentationFramework.dll

Inner Exception: The calling thread cannot access this object because a different thread owns it

Проблема в том, что внутри фонового рабочего элемента создаются элементы (Inline), которые не принадлежат потоку пользовательского интерфейса.При назначении ItemsSource после того, как работник завершил работу, возникает исключение.

Подобные вопросы, похоже, задавались много, но я не смог найти ничего для своего конкретного случая.

Любая помощьоценили!

1 Ответ

2 голосов
/ 05 марта 2019

Для взаимодействия с элементами пользовательского интерфейса вы должны использовать «Invoke» или «BeginInvoke» в диспетчере потоков пользовательского интерфейса.

 Application.Current.Dispatcher.Invoke((Action)delegate
       {
           //CHANGE DATA BOUND TO THE UI HERE
       });

Мне нравится использовать статический метод:

public static class Helpers
{
 public static void RunInUIThread(Action method)
   {
       if (Application.Current == null)
       {
           return;
       }
       Application.Current.Dispatcher.BeginInvoke((Action)delegate
       {
           method();
       });
   }
}

И вы используете это так:

  private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  { 
   Helpers.RunInUIThread(()=>allSearchResultsListView.ItemsSource = searchResults);
  }

Кстати, вы должны использовать фоновые потоки для длительных операций, таких как получение данных из веб-службы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...