0

I have an MVVM-based WPF application that relies on Caliburn.Micro.

In one view, I am displaying a DataGrid and a Button. The DataGrid displays a collection of items, where the item class derives from PropertyChangedBase.
The button should be enabled or disabled based on the contents in the editable DataGrid cells. What is the most reliable approach to achieve this with Caliburn.Micro?

Schematically, this is what my code looks right now:

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); }
    }
}

As the code stands, there is no notification to ItemsViewModel.CanDoWork when one NotifiableProperty is changed, for example when the user edits one cell in the ItemsView´s DataGrid. Hence, the DoWork button enable state will never be changed.

One possible workaround is to add an (anonymous) event handler to every item in the Items collection:

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

but then I also need to keep track of when (if) items are added or removed from the Items collection, or if the Items collection is re-initialized altogether.

Is there a more elegant and reliable solution to this problem? I am sure there is, but so far I have not been able to find it.

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トリガーすると、空 (または ) のように見えます。メソッドが呼び出されたときに nullでないことを明示的に確認していません。それ以外の場合は、アイテムを 1 つずつ削除して置き換える必要もあります。OnItemsCollectionChangede.NewItemsnulle.OldItemsClear()Clear()_items

于 2012-10-26T12:37:51.140 に答える
-1

プロパティが変更されるたびに、buttoncummandコマンドの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 に答える