XAML 为什么在WPF中从UI线程向ICollectionView添加项会导致错误,而在ObservableCollection中却可以正常工作?

ckocjqey  于 2023-05-11  发布在  其他
关注(0)|答案(1)|浏览(103)

我正在使用WPF应用程序,在使用ICollectionView向ObservableCollection添加项时遇到了一个问题。我有一个IOPortsViewModel类,它包含一个名为DataPorts的ObservableCollection。在这个类的构造函数中,我使用CollectionViewSource.GetDefaultView(DataPorts)创建了一个名为CollectionDataPorts的ICollectionView示例。
在我的MainWindowViewModel类中,我有一个名为ReloadMainTreeViewSeparateThread()的方法,它在一个单独的工作线程中创建一些新的MenuItem对象,并将它们添加到一个名为MenuItemsLeft的ObservableCollection中,该集合在UI线程上通过Dispatcher定义。这个很好用。
但是,当我尝试从UI线程向CollectionDataPorts添加项时,我得到以下错误:

System.NotSupportedException: 'This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.'

如果我直接绑定到IOPortsView中的DataPorts而不是CollectionDataPorts,一切都正常。
下面是我的代码以供参考:

internal class IOPortsViewModel : ObservableObject
{
    public IOPortsViewModel()
    {
        CollectionDataPorts = CollectionViewSource.GetDefaultView(DataPorts);
    }

    private ICollectionView m_collectionDataPorts;

    public ICollectionView CollectionDataPorts
    {
        get => m_collectionDataPorts;
        set => SetProperty(ref m_collectionDataPorts, value);
    }
    
    private ObservableCollection<VMDataPorts> m_dataPorts;

    public ObservableCollection<VMDataPorts> DataPorts
    {
        get => m_dataPorts;
        set => SetProperty(ref m_dataPorts, value);
    }   
}

IOPortsView:

<DataGrid Name="dataGrid" ItemsSource="{Binding CollectionDataPorts}">
internal class MainWindowViewModel : CommunityToolkit.Mvvm.ComponentModel.ObservableObject
{
    ObservableCollection<MenuItem> MenuItemsLeft = new();
    
    public void ReloadMainTreeViewSeparateThread()
    {
        Thread t = new Thread(() =>
        {
            App.Current.Dispatcher.Invoke(() => { IsDialogLoadingOpen = true; });

            try
            {
                ObservableCollection<MenuItem> NewProcessedLeftMenuTree = new()
                {
                    new MenuItem ( new IOPortsViewModel(), "Title"),    
                };

                //Adding menuItems into UI thread which were created on this worker thread (Thread t)
                App.Current.Dispatcher.Invoke(()=>
                {
                    foreach (MenuItem menuItem in NewProcessedLeftMenuTree)
                    {
                        MenuItemsLeft.Add(menuItem);
                    }
                });
            }
            catch (AggregateException aggrEx)
            {
                App.Current.Dispatcher.BeginInvoke(() =>
                {
                    IsDialogLogOpen = true;
                });
            }
            finally
            {
                App.Current.Dispatcher.Invoke(() =>
                {
                    IsDialogLoadingOpen = false;
                });
            }
        });
        
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }

    private bool m_isDialogLoadingOpen;
    public bool IsDialogLoadingOpen
    {
        get => m_isDialogLoadingOpen;
        set => SetProperty(ref m_isDialogLoadingOpen, value);
    }

    private bool m_isDialogLogOpen;
    public bool IsDialogLogOpen
    {
        get => m_isDialogLogOpen;
        set => SetProperty(ref m_isDialogLogOpen, value);
    }
}

当我直接绑定到IOPortsView中的ObservableCollection DataPorts时,它为什么有效:<DataGrid Name="dataGrid" ItemsSource="{Binding DataPorts}">?所以我只能在提供的代码中更改它,然后它就可以工作了。只有当我绑定到ICollectionView CollectionDataPort时,它才不起作用。
为什么会发生这种情况?这种在WPF应用程序中使用线程的方式有缺陷吗?任何帮助将不胜感激。

elcex8rz

elcex8rz1#

发生您遇到的错误是因为ICollectionView示例CollectionDataPorts绑定到UI,因此必须在UI线程上修改。在您的代码中,您试图从单独的工作线程修改CollectionDataPorts,这是不允许的,并且会导致您提到的NotSupportedException。
若要解决此问题,应确保在UI线程上执行对CollectionDataPorts的修改。您可以使用Dispatcher在正确的线程上调用修改。

internal class IOPortsViewModel : ObservableObject
{
   public IOPortsViewModel()
  {
    DataPorts = new ObservableCollection<VMDataPorts>();
    CollectionDataPorts = CollectionViewSource.GetDefaultView(DataPorts);
  }

private ICollectionView m_collectionDataPorts;
public ICollectionView CollectionDataPorts
  {
    get => m_collectionDataPorts;
    set => SetProperty(ref m_collectionDataPorts, value);
  }

private ObservableCollection<VMDataPorts> m_dataPorts;
public ObservableCollection<VMDataPorts> DataPorts
  {
    get => m_dataPorts;
    set => SetProperty(ref m_dataPorts, value);
  }
}

internal class MainWindowViewModel : 
CommunityToolkit.Mvvm.ComponentModel.ObservableObject
{
private ObservableCollection<MenuItem> menuItemsLeft = new();

public ObservableCollection<MenuItem> MenuItemsLeft
{
    get => menuItemsLeft;
    set => SetProperty(ref menuItemsLeft, value);
}

public void ReloadMainTreeViewSeparateThread()
{
    Thread t = new Thread(() =>
    {
        App.Current.Dispatcher.Invoke(() => { IsDialogLoadingOpen = true; });

        try
        {
            ObservableCollection<MenuItem> newProcessedLeftMenuTree = new()
            {
                new MenuItem(new IOPortsViewModel(), "Title"),
            };

            App.Current.Dispatcher.Invoke(() =>
            {
                foreach (MenuItem menuItem in newProcessedLeftMenuTree)
                {
                    MenuItemsLeft.Add(menuItem);
                }
              
                var ioPortsViewModel = newProcessedLeftMenuTree[0].ViewModel as IOPortsViewModel;
                if (ioPortsViewModel != null)
                {
                    ioPortsViewModel.CollectionDataPorts = CollectionViewSource.GetDefaultView(ioPortsViewModel.DataPorts);
                }
            });
        }
        catch (AggregateException aggrEx)
        {
            App.Current.Dispatcher.BeginInvoke(() =>
            {
                IsDialogLogOpen = true;
            });
        }
        finally
        {
            App.Current.Dispatcher.Invoke(() =>
            {
                IsDialogLoadingOpen = false;
            });
        }
    });

    t.SetApartmentState(ApartmentState.STA);
    t.Start();
}

}

我将DataPorts集合的初始化移到了IOPortsViewModel的构造函数中。然后,在创建newProcessedLeftMenuTree时,我访问IOPortsViewModel示例并更新UI线程上的CollectionDataPorts属性。
通过在UI线程上执行修改,可以避免NotSupportedException并确保ICollectionView得到正确更新。

相关问题