【问题标题】:Return value of a Property from child ViewModel to parent ViewModel从子视图模型到父视图模型的属性值返回
【发布时间】:2013-05-11 12:06:55
【问题描述】:

在我的 WPF MVVM 应用程序中,使用 Caliburn.Micro,我有一个 ViewModel CreateServiceViewModel,单击按钮会在单独的窗口中打开一个 GridView,供用户从中选择一个行。

我为此创建了另一个 ViewModel,MemberSearchViewModel,它有两个属性:

    private Member selectedMember;

    public Member SelectedMember
    {
        get { return selectedMember; }
        set { selectedMember = value; }
    }

    private IList<Member> members;

    public IList<Member> Members
    {
        get { return members; }
        set { members = value; }
    }

如何将 SelectedMember 值返回给调用 ViewModel?该 ViewModel 的属性为 Service.SelectedMember

【问题讨论】:

  • 如果可能的话,我会避免在两个虚拟机之间创建依赖关系。如果您使用 Prism 或类似工具,EventAggregator 或类似模式可能会很有用。
  • 我正在使用 Caliburn.Micro。可能应该提到...似乎EventAggregaterIChild 实现在这里可以帮助我,但我无法理解如何...
  • 为了进一步参考,我需要从这个 ViewModel 中选择 Selected GridView 行。它弹出,用户选择一行,然后关闭。
  • 我将一些代码放在了答案格式中。如果您需要更多信息,请告诉我。
  • 一般来说,在这种情况下使用事件还是直接将值传回其父级更好?

标签: c# .net wpf mvvm caliburn.micro


【解决方案1】:

一种选择是将MemberSearchViewModel 存储为CreateServiceViewModel 的字段并定义CreateServiceViewModel.SelectedMember 属性如下:

public Member SelectedMember
{
    get
    {
        return _memberSearchViewModel.SelectedMember;
    }
    set
    {
        _memberSearchViewModel.SelectedMember = value;
    }
}

【讨论】:

    【解决方案2】:

    一种选择是使用NotifyPropertyChanged。由于您使用的是 ViewModel,它们很可能实现了 INotifyPropertyChanged,您可以像框架一样使用它。

    当您的 CreateServiceViewModel 创建 MemberSearchViewModel 时,它只会订阅 PropertyChanged 事件:

    //This goes wherever you create your child view model
    var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
    memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
    
    private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "SelectedMember")
        {
            //Code to respond to a change in the Member
        }
    }
    

    然后在您的 MemberSearchViewModel 中,当用户从网格中选择一个成员时,您只需引发 NotifyPropertyChanged 事件。

    编辑: 正如@DNH 在 cmets 中正确指出的那样,如果没有正确清理,使用这样的事件处理程序可能会导致内存泄漏。因此,当您完成 MemberSearchViewModel 后,请确保取消订阅 PropertyChanged 事件。因此,例如,如果您只在用户选择成员之前需要它,您可以将它放在 Property Changed Handler 本身中(我已将其切换为使用类级变量来保存 ViewModel):

    private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "SelectedMember")
        {
            //Code to respond to a change in the Member
    
            //Unsubscribe so the view model can be garbage collected
            _memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
            _memberSearchViewModel = null;
        }
    }
    

    【讨论】:

    • 闻起来像一个很好的内存泄漏。当不再需要 memberSearchViewModel 时,您将不得不删除事件处理程序。否则,只要监听 VM 存在,它就会一直保留在内存中。
    • 同意,EventHandlers 是内存泄漏的常见来源,但在不了解更多架构的情况下,我不能说父级应该何时取消订阅。不过,我会在答案中添加一条注释,以确保明确指出这一点。感谢您关注它。
    【解决方案3】:

    怎么样?

    public interface INotifyMe<T>
    {
        T ResultToNotify { get; set; }
    }
    
    public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
    {
        // implement the interface as you like...
    }
    
    public class MemberSearchViewModel : ViewModelBase
    {
        public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
        {
            // initialize field and so on...
        }
    }
    

    现在您可以让CreateServiceViewModel 监听它自己的属性,而不必考虑删除事件侦听器。

    当然要采用更经典的方式,您也可以使用这样的接口。

    public interface INotifyMe<T>
    {
        void Notify(T result);
    }
    

    【讨论】:

      【解决方案4】:

      作为我评论的后续,这是一个使用 Prism 的示例 - 我从未使用过 Caliburn

      创建一个事件 - 事件的有效负载将是您的 SelectedMember:

      public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
      

      发布活动:

      EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
      

      订阅活动:

      EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);
      

      【讨论】:

        【解决方案5】:

        EventAggregator 是您可以使用的……我确信这是众多解决方案之一。

        public class MessageNotifier{
          public object Content{get;set;}
          public string Message {get;set;}
        }
        
        
        //MEF bits here
        public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
           private readonly IEventAggregator _eventAggregator
        
          //MEF constructor bits
          public YourViewModel(IEventAggregator eventAggregator){
            _eventAggregator = eventAggregator;
          }
        
           public override OnActivate(){
               _eventAggregator.Subscribe(this);
           }
           public override OnDeactivate(){
             _eventAggregator.UnSubscribe(this);
           }
        
           //I Handle all messages with this signature and if the message applies to me do something
           //
           public void Handle(MesssageNotifier _notifier){
                if(_notifier.Message == "NewSelectedItem"){
                    //do something with the content of the selectedItem
                    var x = _notifier.Content
                }
           } 
        }
        
        //MEF attrs
        public class HelloWorld2ViewModel: Screen{
           private readonly IEventAggregator _eventAggregator
            //MEF attrs
            public HelloWorld2ViewModel(IEventAggregator eventAggregator){
               _eventAggregator = eventAggregator;
            }
        
            public someobject SelectedItem{
              get{ return _someobject ;}
              set{ _someobject = value;
                  NotifyOfPropertyChange(()=>SelectedItem);
                  _eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
            }
        }
        

        【讨论】:

        • 我已经广泛使用了这种变体。它使 ViewModel 之间的消息/信息交换变得轻而易举。
        • 酷,这绝对有效。如果可以的话,很少有后续问题。这是特定于 Caliburn Micro 的吗?只是好奇......另外,OnActivateOnDeactivate 是必需的或被认为是最佳实践。 CM 文档似乎说除了特定情况外不需要它。最后,您对使用MessageNotifier 类与实现特定Handle&lt;T&gt; 的想法。您是否发现它更灵活、更简单等。再次感谢!
        • 该模式并非特定于 CM,有许多不同的实现。显然,您需要控制对聚合器的订阅,或者您可以让 VM 接收不应该接收它们的消息,但此控制由您决定。我自己更喜欢IHandle&lt;T&gt; 的方法,感觉简单但灵活
        • 这只是模式的一个例子,但具体的实现是特定于 Caliburn.Micro 的。我使用 OnActivate/Deactivate 将其标记为 GC,但尚未完成。
        • 还有其他我不确定的可能性,您是否使用 windowmanager 调用来显示窗口?如果是这样,那么您可以访问窗口的结果,您可以为其嵌入 SelectedMember。
        猜你喜欢
        • 2017-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-01
        • 1970-01-01
        • 2018-06-24
        相关资源
        最近更新 更多