WPF Обновление ObservableList из асинхронной задачи вызывает XamlParseException

Когда я пытаюсь обновить ObservableCollection, который я использую в своем XAML, из отдельного потока, а не потока пользовательского интерфейса, я получаю XamlParseException, в котором говорится, что DependencySource должен быть создан в том же потоке, что и DependencyObject. Я использую Caliurn Micro для привязки ViewModel к представлению.

Я попробовал несколько способов достичь своей цели, и тот, что ниже, кажется мне наиболее разумным. Я передаю SyncronizationContext из пользовательского интерфейса в задачу, чтобы он мог обновить пользовательский интерфейс после выполнения большой рабочей нагрузки.

Что я делаю неправильно?

Просмотреть

<Window x:Class="QuickScope.Views.NavigatorView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:QuickScope.Views"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:quickScope="clr-namespace:QuickScope"
    mc:Ignorable="d">
<Grid>
    <TextBox Grid.Row="0" 
             Name="TextBox"
             HorizontalContentAlignment="Stretch"
             VerticalContentAlignment="Center"
             Margin="5,0,5,5"
             Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    </TextBox>
    <Popup PlacementTarget="{Binding ElementName=TextBox}">
        <ListView x:Name="ItemList" ItemsSource="{Binding Items}" SelectedItem ="{Binding SelectedItem}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <WrapPanel>
                        <Image Source="{Binding IconSource}" Width="20" Height="20"></Image>
                        <Label Content="{Binding SearchName}"></Label>
                    </WrapPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Popup>
</Grid>

ViewModel

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;
using Caliburn.Micro;
using QuickScope.Views;

namespace QuickScope.ViewModels
{
public class NavigatorViewModel : Screen
{
    private readonly ItemService _itemService;

    public NavigatorViewModel()
    {
        _itemService = new ItemService();
        Items = new ObservableCollection<ItemViewModel>();

    }

    public ObservableCollection<ItemViewModel> Items { get; }

    public ItemViewModel SelectedItem { get; set; }

    private string _searchText;

    public string SearchText
    {
        get => _searchText;
        set
        {
            _searchText = value;
            NotifyOfPropertyChange(() => SearchText);
            UpdateItemList(_searchText);
        }
    }

    private void UpdateItemList(string searchText)
    {
        Items.Clear();

        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        Task.Factory.StartNew(() =>
        {
            //the long running workload
            var items = _itemService.GetByFilter(searchText);

            //update the ui
            Task.Factory.StartNew(() =>
            {
                foreach (var item in items)
                    Items.Add(item);

                if (items.Any())
                {
                    SelectedItem = items.First();
                    NotifyOfPropertyChange(() => SelectedItem);
                }
            }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
        });
    }
}
}

ИЗМЕНИТЬ

Я попробовал подход Шадрикса, но, к сожалению, он тоже не сработал. Я также попробовал ответы от Питера Дунихоса , отмеченные дубликат, но мне это тоже не удалось (я получаю то же исключение XamlParseException, что описано выше).

Последнее, что я пробовал, это сборка CM в OnUIThread, который в основном является оболочкой Dispatcher.CurrentDispatcher. Очевидно (учитывая две мои последние неудачные попытки), это тоже не удалось. Текущая реализация выглядит примерно так (я удалил другие, не относящиеся к делу свойства и методы):

public class NavigatorViewModel : Screen
{
public NavigatorViewModel()
{
    Items = new ObservableCollection<ItemViewModel>();
}

public ObservableCollection<ItemViewModel> Items { get; set; }

public ItemViewModel SelectedItem { get; set; }

private string _searchText;

public string SearchText
{
    get => _searchText;
    set
    {
        _searchText = value;
        NotifyOfPropertyChange(() => SearchText);
        UpdateItemList(_searchText);
    }
}

private void UpdateItemList(string searchText)
{
    Items.Clear();

    var updater = new ItemUpdater();
    updater.ItemsUpdated += (s, e) => {
        OnUIThread(() =>
        {
            var items = ((ItemsUpdatedEventArgs) e).Items;
            foreach (var item in items)
            {
                Items.Add(item);
            }
            NotifyOfPropertyChange(() => Items);
        });
    };
    var updateThread = new Thread(updater.GetItems);
    updateThread.Start(searchText);
}
}

public class ItemUpdater
{
public event EventHandler ItemsUpdated;
private readonly ItemService _itemService;

public ItemUpdater()
{
    _itemService = new ItemService();
}

public void GetItems(object searchText)
{
    var items = _itemService.GetByFilter((string)searchText);

    ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(items));
}
}

public class ItemsUpdatedEventArgs : EventArgs
{
public ItemsUpdatedEventArgs(IList<ItemViewModel> items)
{
    Items = items;
}

public IList<ItemViewModel> Items { get; }
}

Меня бесит, что я не могу решить эту проблему, поэтому, если есть кто-то, кто хотел бы помочь юному юниору, я был бы очень признателен. :)

Вы можете найти полный исходный код здесь.

Спасибо вам всем!


person CiniMod    schedule 27.09.2017    source источник


Ответы (1)


Используйте текущий поток пользовательского интерфейса Dispatcher:

//update the ui
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
    foreach (var item in items)
    {
        Items.Add(item);
    }

    if (items.Any())
    {
        SelectedItem = items.First();
        NotifyOfPropertyChange(() => SelectedItem);
    }
}));
person Shadrix    schedule 27.09.2017
comment
спасибо за ваш ответ, к сожалению, это не решило мою проблему. Я все еще получаю описанное исключение (см. отредактированный вопрос для получения дополнительной информации) - person CiniMod; 28.09.2017