【问题标题】:WPF: Exception when Binding to an ObservableCollection that has been filled asynchronouslyWPF:绑定到已异步填充的 ObservableCollection 时出现异常
【发布时间】:2018-11-26 18:08:51
【问题描述】:

我在将项目添加到通过异步等待在后台线程中创建的 ObservableCollection 时遇到问题。我将 MenuItemViewModels 绑定到 TreeView ContextMenu 中的分层数据模板,该模板在为 Treeview 编写的 ViewModel 中动态加载(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 都派生自一个 MenuItemBaseViewModel,它还包含一个 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>

我的问题是,当通过 NotifyPropertyChanged 更新 MenuItemBaseViewModel 中的 MenuItemSource 时,通过异步等待加载 MenuItems 会给我一个异常:

System.InvalidOperationException: 调用线程无法访问此对象,因为不同的 线程拥有它。

(见代码中的注释)

将 ContextMenu 绑定到 TreeNodesViewModel 中的 MenuItems-Collection 不会产生异常,因为 Collection 是在 UI 线程上创建的,但绑定到 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/),但没有成功。有人可以帮我解决这个问题吗?

【问题讨论】:

  • downvote 按钮的工具提示指出,“这个问题没有显示任何研究工作;它不清楚或没有用”。我认为这些都不适用于这里。即使有些事情不清楚——为什么不让我澄清一下呢?很明显,在我发布这个问题之前,我已经付出了一些努力来制定这个问题,并且一直在研究但没有找到答案。这种情况已经不是第一次发生了——在我看来,我对一个完全有效的问题投了反对票。
  • CollectionViewSource 是一个 DependencyObject 意味着它绑定到创建它的线程。您正在后台线程上创建这些菜单项视图模型,因此必须在该线程上使用它们。也许您可以在后台线程上进行所有过滤,然后在 UI 线程上创建它们。或者另一种可能是完全放弃视图模型中的视图源并将源绑定到 SubItems 并将菜单项可见性绑定到 IsVisible 属性(使用合适的转换器)。
  • 非常感谢,成功了!我不知道它可能是导致问题的 ICollectionView 的事实。我更改了 MenuItemSource Getter 的代码,如下所示:“public ICollectionView MenuItemSource { get { return new CollectionViewSource { Source = SubItems.Where(x => x.IsVisible) }.View; } }” 现在一切都很好:-)如果您愿意,请发布答案,我会接受。

标签: c# wpf async-await observablecollection


【解决方案1】:

正如 cmets 中的Mike Zboray 所述,ICollectionView 需要在正确的线程中创建。更改 MenuItemBaseViewModel 中 ICollectionView 的 getter 解决了问题,因为之前的 ICollectionView 是在后台线程中创建的。返回一个新创建的解决了我的问题:

public ICollectionView MenuItemSource {
    get { return new CollectionViewSource { Source = SubItems.Where(x => x.IsVisible) }.View; }
}

【讨论】:

    猜你喜欢
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2017-11-03
    • 2016-12-18
    • 2013-05-01
    • 2017-12-10
    • 2018-01-21
    相关资源
    最近更新 更多