WPF: исключение при привязке к коллекции ObservableCollection, которая была заполнена асинхронно - PullRequest
0 голосов
/ 26 ноября 2018

У меня проблема с добавлением элементов в коллекцию ObservableCollection, которая создается в фоновом потоке с помощью async await.Я связываю MenuItemViewModels с иерархической таблицей данных в ContextMenu TreeView, которые загружаются динамически в ViewModel, записанную для TreeView (TreeNodesViewModel -> LoadMenuItemsAsync; UI-Thread):

public class TreeNodesViewModel: BaseViewModel {
    private FullyObservableCollection<MenuItemBaseViewModel> menuitems = new FullyObservableCollection<MenuItemBaseViewModel>();
    private CollectionViewSource viewsourcemenuitems = new CollectionViewSource();

    public FullyObservableCollection<MenuItemBaseViewModel> MenuItems {
        get { return menuitems; }
        set { SetProperty(ref menuitems, value); }
    }

    public TreeNodesViewModel() {
        GenerateCompleteTree();
    }

    public void GenerateCompleteTree(bool setSelectLastSelectedItem = true, string path = null) {
        LoadMenuItemsAsync();
        ...
    }

    public async void LoadMenuItemsAsync() {
        Task load = Task.Run(async () => {
            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && x.BaseType == typeof(MenuItemBaseViewModel) && x.Namespace.Equals("***.UI.Tree.MenuItems"))) {
                MenuItems.Add((MenuItemBaseViewModel)Activator.CreateInstance(type));
            }
        });

        await load;
    }
}

Каждый MenuItemViewModel наследует от MenuViewModelкоторый также содержит подэлементы ObservableCollection для создания иерархической менструкции с HierachicalDataTemplate:

public class MenuItemAddNewSystem : MenuItemBaseViewModel {

    public MenuItemAddNewSystem() : base(PackIconModernKind.Add, Colors.Green) {
        Uid = "...";
        Header = "Add new System";
    }

    #region Overrides
    public override bool IsVisible {
        get {
            return true;
        }
    }


    public override void ExecuteMouseLeftButtonDownCommand(object parameter) {
        throw new NotImplementedException();
    }
    #endregion
}


public abstract class MenuItemBaseViewModel : BaseViewModel {
    private FullyObservableCollection<MenuItemBaseViewModel> subitems = new FullyObservableCollection<MenuItemBaseViewModel>();
    private CollectionViewSource viewsource = new CollectionViewSource();

    public FullyObservableCollection<MenuItemBaseViewModel> SubItems {
        get { return subitems; }
        set { if (SetProperty(ref subitems, value)) OnPropertyChanged("MenuItemSource"); }
    }

    public ICollectionView MenuItemSource {
        get {
            if (SubItems.Count(x => x.IsVisible) == 0) return null;
            viewsource.Source = SubItems.Where(x => x.IsVisible); //<--- Exception here!
            return viewsource.View;
        }
    }

    public MenuItemBaseViewModel(Enum packiconkind, Color? iconcolor = null) {
        SetPackIcon(packiconkind, iconcolor);
        AddEventHandler(SubItems);
        ...
    }

    #region NotifyChanged-Events
    public override void NotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
        OnPropertyChanged("MenuItemSource");
    }

    public override void NotifyItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e) {
        if (e.PropertyName.Equals("IsVisible")) OnPropertyChanged("MenuItemSource");
    }
    #endregion

    ...
}

XAML:

<ContextMenu x:Key="MenuItemContextMenu" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=TreeViewModel.MenuItemSource}">
    <ContextMenu.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type model:MenuItemBaseViewModel}" ItemsSource="{Binding Path=MenuItemSource, UpdateSourceTrigger=PropertyChanged}">
            <TextBlock Text="{Binding Header}"/>
        </HierarchicalDataTemplate>
    </ContextMenu.ItemTemplate>
    <ContextMenu.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="ToolTip" Value="{Binding ToolTip}"/>
            <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
            <Setter Property="Command" Value="{Binding MouseLeftButtonDownCommand}"/>
        </Style>
    </ContextMenu.ItemContainerStyle>
</ContextMenu>

Моя проблема заключается в том, что загрузка MenuItems через асинхронное ожидание дает мнеисключение при обновлении MenuItemSource в MenuItemBaseViewModel через NotifyPropertyChanged:

System.InvalidOperationException: Вызывающий поток не может получить доступ к этому объекту, так как он принадлежит другому потоку.

(см. Комментарий в коде)

Привязка ContextMenu к коллекции MenuItems в TreeNodesViewModel не дает исключения, поскольку коллекция создается в потоке пользовательского интерфейса, но привязка кподэлементы 2-го иерархического уровня в MenuItemBaseViewModel выдают исключение.Я пробовал следующие варианты:

  • Использование BindingOperations.EnableCollectionSynchronization и блокировка во время операций добавления или удаления
  • с использованием Application.Current.Dispatcher
  • с использованием viewsource.Dispatcher.BeginInvoke
  • с использованием Dispatcher.CurrentDispatcher.BeginInvoke

Ничего не работает, и я продолжаю получать сообщение об ошибке.Я также попытался реализовать AsyncObservableCollection (https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/), но безуспешно. Может ли кто-нибудь помочь мне с этим?

1 Ответ

0 голосов
/ 27 ноября 2018

Как указано в комментариях Mike Zboray , ICollectionView необходимо создать в правильной ветке.Изменение метода получения ICollectionView в MenuItemBaseViewModel устранило проблему, поскольку предыдущий ICollectionView был создан в фоновом потоке.Возвращение вновь созданного исправляет проблему для меня:

public ICollectionView MenuItemSource {
    get { return new CollectionViewSource { Source = SubItems.Where(x => x.IsVisible) }.View; }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...