删除项目时绑定到 ObservableCollection 的 DataGrid 不会更新

Posted

技术标签:

【中文标题】删除项目时绑定到 ObservableCollection 的 DataGrid 不会更新【英文标题】:DataGrid bound to ObservableCollection doesn't update when an Item is Removed 【发布时间】:2021-11-15 03:58:41 【问题描述】:

我有这个有四个主要视图的 WPF 应用程序。其中一个视图,我们称之为“OrdersView”,有一个包含订单的 DataGrid。为此,DataContext 是一个 ViewModel,即“OrdersViewModel”,当然,它实现了 INotifyPropertyChanged。网格的每一行都有一个按钮来查看订单的详细信息。此按钮通过窗口服务打开一个新窗口,允许用户通过处理订单、添加更多项目或删除订单来更新订单。该窗口包含一个名为“OrderDetailsView”的用户控件,它当然有一个“OrderDetailsViewModel”作为 DataContext。我对“编辑订单”和“处理订单”功能没有任何问题。问题是“删除订单”。此按钮绑定到 ViewModel 中的命令,该命令引发事件并将订单 Id 发送到应用程序主窗口中的侦听器,然后主窗口将此 Id 传递给“OrdersViewModel”中具有查找功能的方法在 ObservableCollection 中排序并将其删除。 该项目已删除,我在调试器中看到了 Collection 的变化,但是当“OrderDetailsView”关闭时,Orders DataGrid 仍然有血腥的订单,只有当我导航到不同的视图并返回时才会刷新它。 我试图将 RaisePropertyChanged 方法放在集合的 setter 中,在 delete 方法的末尾,属性名称为 Orders 并且不起作用。

数据网格开始如下:

<DataGrid ItemsSource="Binding Path=Orders"
              Style="DynamicResource DataGridStyle">

ViewDetails 按钮是:

<DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <Button x:Name="BtnOrderDetails"
                      Content="View"
                      Margin="1"
                      Command="Binding DataContext.SeeOrderDetailsCommand, RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=Window"
                      CommandParameter="Binding" />
            </DataTemplate>

OrdersViewModel 类似于:

public class OrdersViewModel : ViewModelBase
    
        private ObservableCollection<Order> _orders;

        public OrdersViewModel(List<Order> orders)
        
            Orders = new ObservableCollection<Order>(orders);
        
        public ObservableCollection<Order> Orders
        
            get => _orders;
            set
            
                _orders = value;
                RaisePropertyChanged();
            
        
        public void DeleteOrder(int id)
        
            Orders.Remove(Orders
                .FirstOrDefault(order => order.Id.Equals(id)));
            RaisePropertyChanged(nameof(Orders));
        
    

“查看详细信息”按钮会将您带到一个新窗口,该窗口包含“OrderDetailsView”并具有如下按钮:

<Button x:Name="DeleteOrder"
              Content="Delete order"
              Grid.Row="2"
              Margin="10,0,0,10"
              Style="StaticResource DeleteOrderButton"
              Command="Binding DeleteOrderCommand"/>

在此视图的视图模型中,我有以下内容:

public DelegateCommand DeleteOrderCommand  get; private set; 

在构造函数中:

DeleteOrderCommand = new DelegateCommand(DeleteOrder, () => CanDelete);

该方法引发了一个事件(你是这么说的吗?):

private void DeleteOrder()

    DeleteOrderRequested(Order.Id);
    CloseWindow();


public event Action<int> DeleteOrderRequested = delegate  ;

我的主窗口是传达不同视图模型的东西,它捕获了这个事件:

private void OnDeleteOrderRequested(int id)

    OrdersViewModel.DeleteOrder(id);

我知道,它的功能是一个愚蠢的复杂应用程序,对吧?

为什么,为什么,为什么视图没有收到有关项目已被删除的通知???

有人可以帮忙吗?或者至少告诉我在哪里继续寻找?

谢谢大家!

【问题讨论】:

很难理解你的逻辑是如何工作的,因为你选择只发布松散的代码 sn-ps。您应该检查是否调用了命令并监听事件并在正确的实例上调用 delete - 确保您仅使用单个 OrdersViewModel 实例。您定义事件委托的方式非常不同寻常。您应该改用Eventhandler&lt;TEventArgs&gt;。但更重要的是考虑让您的 OrdersViewModel 为当前选定的订单公开 OrdersDetailsViewModel。这可以显着简化逻辑并很有可能解决问题。 非常感谢您的回复。完全同意,就是代码太多了,就是觉得不合适贴200行代码来解释viewModel的作用。我不知道如何使用你提到的 eventHandler,不过我会调查的,保证。无论如何,我发现了问题。与启动时 currentViewModel 的分配有关。在这里解释太长了。再次感谢。 【参考方案1】:

我发现了问题。我使用一个不太优雅的 switch 语句处理导航,该语句在 MainWindow 中切换 CurrentViewModel 并使用数据模板进行更改,因此当 VM 更改时,视图会更改。可能不是很传统,它有点工作。问题是,在启动时,我将 CurrentViewModel 分配给了 OrdersViewModel,然后将该 viewModel 放在一个属性中,以便稍后我想返回时使用它。所以我应该反过来做,我已经做过了,所以当应用程序启动时,我首先创建 OrderViewModel,放入一个属性,然后我将 currentVM 分配给那个 VM。我注意到这是问题所在,因为当我离开第一个视图然后返回时,删除功能实际上起作用了。 MainWindowViewModel 是这样的:

 public MainWindowViewModel(IOrdersDataProvider ordersData, ViewModelBase currentViewModel, AddItemViewModel addItemViewModel, ViewModelFactory vMFactory)

    _Data = ordersData;
    _isModalOpen = false;
    _currentViewModel = currentViewModel;
    _addItemViewModel = addItemViewModel;
    NavigateCommand = new RelayCommandT<string>(Navigate, () => OrderDetailsWindowsOpen);
    SeeOrderDetailsCommand = new DelegateCommand<Order>(ViewOrderDetails);
    CreateNewOrderCommand = new DelegateCommand(CreateNewOrder);
    _vMFactory = vMFactory;

app.xaml.cs 中的 ComposeObjects 方法正在做一些非常愚蠢的事情:

private static void ComposeObjects(ViewModelFactory vMFactory)

    var currentViewModel = vMFactory.CreateViewModel("Orders");
    var addOrderViewModel = vMFactory.CreateViewModel("AddOrder");
    var mainWindowViewModel = (MainWindowViewModel)vMFactory.CreateViewModel("MainWindow");
    // Set up main ViewModel
    mainWindowViewModel.AddOrderViewModel = (AddOrderViewModel)addOrderViewModel;
    mainWindowViewModel.OrdersViewModel = (OrdersViewModel)currentViewModel;
    mainWindowViewModel.SubscribeHandlersToEvents();
    Application.Current.MainWindow = new MainWindow
    
        DataContext = mainWindowViewModel
    ;

我是这样改的:

private static void ComposeObjects(ViewModelFactory vMFactory)

    // Get ViewModels
    var addOrderViewModel = vMFactory.CreateViewModel("AddOrder");
    var mainWindowViewModel = (MainWindowViewModel)vMFactory.CreateViewModel("MainWindow");
    // Set up main ViewModel
    mainWindowViewModel.AddOrderViewModel = (AddOrderViewModel)addOrderViewModel;
    mainWindowViewModel.SubscribeHandlersToEvents();
    Application.Current.MainWindow = new MainWindow
    
        DataContext = mainWindowViewModel
    ;

我将 MainWindowVM 构造函数调整为:

 public MainWindowViewModel(IOrdersDataProvider ordersData, ViewModelBase ordersViewModel, AddItemViewModel addItemViewModel, ViewModelFactory vMFactory)

    _Data = ordersData;
    _isModalOpen = false;
    OrdersViewModel = (OrdersViewModel)ordersViewModel;
    _currentViewModel = OrdersViewModel;
    _addItemViewModel = addItemViewModel;
    NavigateCommand = new RelayCommandT<string>(Navigate, () => OrderDetailsWindowsOpen);
    SeeOrderDetailsCommand = new DelegateCommand<Order>(ViewOrderDetails);
    CreateNewOrderCommand = new DelegateCommand(CreateNewOrder);
    _vMFactory = vMFactory;

首先分配 OrdersViewModel 属性就成功了,当然,这很麻烦,这是一项正在进行的工作,我将在接下来的几天内实现一个像 Ninject 这样的容器。 我现在可以去睡觉了。

【讨论】:

以上是关于删除项目时绑定到 ObservableCollection 的 DataGrid 不会更新的主要内容,如果未能解决你的问题,请参考以下文章

根据数据绑定组合框中的选定项目从访问数据库中删除

如何在绑定到ObservableCollection时删除DataGrid的空行 ?

绑定到 ObservableCollection<T> 时如何删除 DataGrid 的空白行?

枚举 - 组合框与一个项目绑定作为例外

WPF ListView在运行时不更新

Entity Framework 6 在从绑定的 DataGridView 删除时应该使用 DELETE 时使用 UPDATE