【问题标题】:Caliburn.Micro and DataGrid: Reliable approach for detecting individual cell changes?Caliburn.Micro 和 DataGrid:检测单个单元格变化的可靠方法?
【发布时间】:2012-10-16 17:02:51
【问题描述】:

我有一个基于 MVVM 的 WPF 应用程序,它依赖于 Caliburn.Micro

在一个视图中,我显示DataGridButtonDataGrid 显示项目集合,其中项目类派生自 PropertyChangedBase
应根据可编辑的DataGrid 单元格中的内容启用或禁用该按钮。 Caliburn.Micro 实现这一目标的最可靠方法是什么?

从示意图上看,这就是我的代码现在的样子:

public class ItemViewModel : PropertyChangedBase { }

...

public class ItemsViewModel : PropertyChangedBase
{
    private IObservableCollection<ItemViewModel> _items;

    // This is the DataGrid in ItemsView
    public IObservableCollection<ItemViewModel> Items
    {
        get { return _items; }
        set
        {
            _items = value;
            NotifyOfPropertyChange(() => Items);
        }
    }

    // This is the button in ItemsView
    public void DoWork() { }

    // This is the button enable "switch" in ItemsView
    public bool CanDoWork
    {
        get { return Items.All(item => item.NotifiableProperty == some_state); }
    }
}

就代码而言,当一个NotifiableProperty 被更改时,例如当用户编辑ItemsViewDataGrid 中的一个单元格时,没有通知ItemsViewModel.CanDoWork。因此,DoWork 按钮的启用状态永远不会改变。

一种可能的解决方法是为Items 集合中的每个项目添加一个(匿名)事件处理程序:

foreach (var item in Items)
    item.PropertyChanged += 
        (sender, args) => NotifyOfPropertyChange(() => CanDoWork);

但我还需要跟踪何时(如果)从 Items 集合中添加或删除项目,或者是否完全重新初始化了 Items 集合。

有没有更优雅和可靠的解决方案来解决这个问题?我确定有,但到目前为止我还没有找到它。

【问题讨论】:

    标签: c# wpf mvvm wpfdatagrid caliburn.micro


    【解决方案1】:

    我认为这是 INPC 运作良好的一个案例;为了简化注册/注销添加和删除,只需将 CollectionChanged 处理程序添加到您的 Items 集合:

    Items.CollectionChanged += OnItemsCollectionChanged;
    
    private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
        if (e.NewItems != null && e.NewItems.Count != 0) {
            foreach (ItemViewModel  vm in e.NewItems)
                vm.PropertyChanged += OnDetailVmChanged;
        }
        if (e.OldItems != null && e.OldItems.Count != 0) {
            foreach (ItemViewModel  vm in e.OldItems) {
                vm.PropertyChanged -= OnDetailVmChanged;
            }
        }
    }
    

    Josh Smith 编写了一个 PropertyObserver 类 here,我发现它比 Shotgun INPC 跟踪更优雅,但在像您这样的主细节场景中,您仍然需要跟踪添加和删除。

    安德斯·古斯塔夫森编辑
    请注意,要在一般情况下运行上述代码,需要在附加事件处理程序之前使用默认构造函数初始化 Items。为确保正确添加和删除 OnDetailVmChanged 事件处理程序,Items 属性设置器需要扩展为如下内容:

    public IObservableCollection<ItemViewModel> Items
    {
        get { return _items; }
        set
        {
            // If required, initialize empty _items collection and attach
            // event handler
            if (_items == null) {
                _items = new BindableCollection<ItemViewModel>();
                _items.CollectionChanged += OnItemsCollectionChanged;
            }
            // Clear old contents in _items
            _items.Clear();
            // Add value item:s one by one to _items
            if (value != null) foreach (var item in value) _items.Add(item);
    
            NotifyOfPropertyChange(() => Items);
        }
    }
    

    (当然,使用上面的Items setter,最上面的Items.CollectionChanged 事件处理程序附件不应该包含在代码中。)

    理想情况下,我会使用if (value != null) _items.AddRange(value);,但是当AddRange 方法触发OnItemsCollectionChanged 事件处理程序时,e.NewItems 似乎为空(或null)。当调用Clear() 方法时,我没有明确验证e.OldItems 是非空的;否则Clear() 也需要替换为在_items 中逐一删除项目:s。

    【讨论】:

    • 谢谢,好点子!我会尽快试用并报告。
    • 我现在尝试在上面应用您的解决方案,并且原则上它可以工作,尽管代码存在一些问题。首先,e.NewItemse.OldItems 包含object:s,因此在附加OnDetailVmChanged 事件处理程序之前需要进行某种类型的转换。 (我采用了foreach var vm in e.NewItems.OfType&lt;ItemViewModel&gt;()) 的方法,但当然还有其他选择)。其次,开箱即用的上述方法仅在 Items 使用 empty 集合初始化后才有效,并且一旦您重新-初始化Items。 ...
    • @AndersGustafsson。是的,这是 var 不是你的朋友的情况,这就是我强烈键入变量的原因。您可以在 OfType() 之后 linq-out 并添加一个 Cast,但对我来说它看起来很吵。为什么不用你最终得到的代码来编辑你的原始帖子呢?很难真正理解这些 cmets 中的代码。干杯
    • 抱歉,我关于选角的错误,我实际上错过了 vm 的强类型在这种情况下会起作用。关于评论中的代码,如果您想用自己的“触摸”更新答案,我会将其包含在其中,但我会在您的答案中添加代码以使其更完整。之后根据需要进行编辑。再次感谢您的回答。
    【解决方案2】:

    每当属性更改时,按钮 cummand 命令会触发 RaiseCanExecuteChanged?

    例如:

    public DelegateCommand<object> MyDeleteCommand { get; set; }
    
    string _mySelectedItem;
        public string MySelectedItem
        {
            get { return _mySelectedItem; }
            set
            {
                _mySelectedItem = value;
                OnPropertyChanged("MySelectedItem");
                MyDeleteCommand.RaiseCanExecuteChanged();
            }
        }
    

    【讨论】:

    • 谢谢,@SarafTalukder。但是,上述内容并不具体适用于 Caliburn.Micro,对吧?而且它也没有为我的收藏中添加/删除的项目提供有效附加/分离事件处理程序的指导,对吧?我不是忘恩负义,但在这个阶段我会否决答案,因为我看不到你试图回答我的问题。如果您能明确解决我的问题,或者更彻底地解释我是否误解了您的回答,我将非常乐意再次投票。
    猜你喜欢
    • 2019-12-01
    • 1970-01-01
    • 2017-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 1970-01-01
    相关资源
    最近更新 更多