0

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

在一个视图中,我正在显示 aDataGrid和 a Button。显示DataGrid项目集合,其中项目类派生自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); }
    }
}

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

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

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

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

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

4

2 回答 2

2

我认为这是 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 类,我发现它比猎枪 INPC 跟踪更优雅,但在像你这样的主细节场景中,你仍然需要跟踪添加和删除。

由 Anders Gustafsson 编辑
请注意,要使上述代码在一般情况下工作,需要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设置器,最上面的Items.CollectionChanged事件处理程序附件不应该包含在代码中。)

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

于 2012-10-26T12:37:51.140 回答
-1

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

例如 :

public DelegateCommand<object> MyDeleteCommand { get; set; }

string _mySelectedItem;
    public string MySelectedItem
    {
        get { return _mySelectedItem; }
        set
        {
            _mySelectedItem = value;
            OnPropertyChanged("MySelectedItem");
            MyDeleteCommand.RaiseCanExecuteChanged();
        }
    }
于 2012-10-27T11:10:04.863 回答