简体   繁体   English

通知ViewModel已选择集合中的对象

[英]Notify a ViewModel that an object in a collection has been selected

Consider the following object, part of a WPF MVVM application: 考虑以下对象,它是WPF MVVM应用程序的一部分:

public class MyObject : INotifyPropertyChanged
{
    // INotifyPropertyChanged gubbins

    private bool _isSelected;
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            _isSelected = value;
            OnPropertyChanged("IsSelected");
        }
    }
}

And its use in the following ViewModel: 及其在以下ViewModel中的使用:

public class MyViewModel : INotifyPropertyChanged
{
    // INotifyPropertyChanged gubbins

    private List<MyObject> _myObjects;
    public List<MyObject> MyObjects
    {
        get
        {
            return _myObjects;
        }
        set
        {
            _myObjects = value;
            OnPropertyChanged("MyObjects");
        }
    }

    public bool CanDoSomething
    {
        get
        {
            return MyObjects.Where(d => d.IsSelected).Count() > 0;
        }
    }
}

In this situation, I can track which of my objects have been selected, and selecting them will fire OnPropertyChanged and so can notify the parent view. 在这种情况下,我可以跟踪选择了哪些对象,选择它们将触发OnPropertyChanged,因此可以通知父视图。

However, CanDoSomething will always be false because there's nowhere I can fire an OnPropertyChanged to create a notification. 但是,CanDoSomething始终为false,因为没有地方可以触发OnPropertyChanged来创建通知。 If I put it in MyObject, it doesn't know anything about the property and so does nothing. 如果我将其放在MyObject中,则它对属性一无所知,因此一无所获。 There's nowhere to put it in the ViewModel because there's nothing that reacts when an object in the list is selected. 无处可放到ViewModel中,因为在选择列表中的对象时没有任何反应。

I've tried substituting the List for an ObservableCollection and a custom "TrulyObservableCollection" (see Notify ObservableCollection when Item changes ) but neither work. 我尝试用List代替ObservableCollection和自定义“ TrulyObservableCollection”(请参阅Item更改时通知ObservableCollection ),但两者均无效。

How can I get round this, without resorting to click events? 我该如何解决这个问题,而不必借助点击事件?

I feel like if I had a better idea of what your end goal was I might be able to recommend a better approach. 我觉得,如果我对您的最终目标有更好的了解,也许可以推荐一种更好的方法。 There is some stuff going on that just feels a little out place. 发生了一些事情,只是感觉有些不适。 Like maybe 'CanDoSomething' should be part of a command object. 也许“ CanDoSomething”应该是命令对象的一部分。 And I am wondering if more than one MyObject be selected at a time? 我想知道是否一次选择了多个MyObject If not then I would approach this in an entirely differnt way. 如果不是这样,我将以完全不同的方式来解决这个问题。

So anyway, you want to update CanDoSomething any time the IsSelected property of one of the items in MyObjects changes. 因此,无论如何,只要MyObjects中一项之一的IsSelected属性发生更改,您都希望更新CanDoSomething It sounds like you were using an ObservableCollection at one point and then abandoned it. 听起来您曾经在某个时候使用ObservableCollection ,然后放弃了它。 That was a mistake. 那是个错误。 You need to update CanDoSomething any time one of two events occur; 每当两个事件之一发生时,您就需要更新CanDoSomething the first is when items are added to or removed from MyObjects and the second is when the IsSelected property of any of the objects in MyObjects changes. 第一个是当项目被添加到或从去除MyObjects和第二是当IsSelected的任何中的对象的属性MyObjects变化。 For the first event you need something that implements INotifyCollectionChanged , ie an ObservableCollection . 对于第一个事件,您需要实现INotifyCollectionChanged ,即ObservableCollection You already have the second event covered because the objects implement INotifyPropertyChanged . 由于对象实现了INotifyPropertyChanged因此您已经涵盖了第二个事件。 So you just have to combine those two things. 因此,您只需要结合这两件事。

In the following example I have taken your code and made some changes. 在以下示例中,我采用了您的代码并进行了一些更改。 To start with I changed MyObjects back to an ObservableCollection<MyObject> . 首先,我将MyObjects改回了ObservableCollection<MyObject> It does not have a setter because I have found that there usually is not good reason to change an observable collection; 它没有设置器,因为我发现通常没有充分的理由来更改可观察的集合。 just add and remove objects as necessary. 只需根据需要添加和删除对象。 Then in the viewmodel's constructor I am register for the CollectionChanged event of MyObjects . 然后在视图模型的构造函数中,我注册MyObjectsCollectionChanged事件。 In that handler I am grabbing items that are added to the collection and hooking up their PropertyChanged event to the OnIsSelectedChanged event handler and I am unhooking the PropertyChanged event from OnIsSelectedChanged for any objects that were removed from the collection. 在该处理程序中,我要抓取添加到集合中的项目,并将其PropertyChanged事件连接到OnIsSelectedChanged事件处理程序,然后从OnIsSelectedChanged中取消从集合中删除的所有对象的PropertyChanged事件。 Because items have been added or removed we have no idea what the state of IsSelected may be of the objects in MyObjects so this is a good opportunity to update CanDoSomething , and I do at the bottom of the event handler. 因为已经添加或删除了项目,所以我们不知道MyObjects对象的IsSelected状态可能是什么,所以这是更新CanDoSomething的好机会,我将在事件处理程序的底部进行操作。 Finally, the OnIsSelectedChanged is where the other half of the magic happens. 最后, OnIsSelectedChanged是魔术发生的另一半。 Every object in MyObjects will have their PropertyChanged event hooked up to this event handler. MyObjects每个对象都会将其PropertyChanged事件连接到此事件处理程序。 Whenever the IsSelected property on any of these objects changes the event handler will update CanDoSomething . 每当这些对象中的任何一个的IsSelected属性发生更改时,事件处理程序都会更新CanDoSomething

public class MyViewModel : INotifyPropertyChanged
{
    // INotifyPropertyChanged gubbins
    public MyViewModel()
    {
        this._myObjects.CollectionChanged += (o, e) =>
        {
            if (e.NewItems != null)
            {
                foreach (var obj in e.NewItems.OfType<MyObject>())
                {
                    obj.PropertyChanged += this.OnIsSelectedChanged;
                }
            }

            if (e.OldItems != null)
            {
                foreach (var obj in e.OldItems.OfType<MyObject>())
                {
                    obj.PropertyChanged -= this.OnIsSelectedChanged;
                }
            }

            if (e.PropertyName == "IsSelected")
            {
                this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
            }
        };
    }

    private readonly ObservableCollection<MyObject> _myObjects =
         new ObservableCollection<MyObject>();
    public ObservableCollection<MyObject> MyObjects
    {
        get
        {
            return _myObjects;
        }
    }

    private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsSelected")
        {
            this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
        }
    }

    private bool _canDoSomething;
    public bool CanDoSomething
    {
        get { return this._canDoSomething; }
        private set
        {
            if (_canDoSomething != value)
            {
                _canDoSomething = value;
                OnPropertyChanged("CanDoSomething");
            }
        }
    }
}

First create a class that defines this attached property: 首先创建一个定义此附加属性的类:

public static class ItemClickCommand 
{ 
    public static readonly DependencyProperty CommandProperty = 
    DependencyProperty.RegisterAttached("Command", typeof(ICommand), 
    typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));

    public static void SetCommand(DependencyObject d, ICommand value) 
    { 
        d.SetValue(CommandProperty, value); 
    }

    public static ICommand GetCommand(DependencyObject d) 
    { 
        return (ICommand)d.GetValue(CommandProperty); 
    }

    private static void OnCommandPropertyChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e) 
    { 
        var control = d as ListViewBase; 
        if (control != null) 
            control.ItemClick += OnItemClick; 
    }

    private static void OnItemClick(object sender, ItemClickEventArgs e) 
    { 
        var control = sender as ListViewBase; 
        var command = GetCommand(control);

        if (command != null && command.CanExecute(e.ClickedItem)) 
            command.Execute(e.ClickedItem); 
    } 
}

Then just bind this attached property to a delegate command in your view model: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}" 然后,只需将此附加属性绑定到视图模型中的委托命令即可: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"

You can find more detail in this blog post: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/ 您可以在此博客文章中找到更多详细信息: https : //marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-点击它的项目/

Let me know if it works 让我知道是否有效

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM