2
votes

In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.

I created another ViewModel for this, MemberSearchViewModel which has two properties:

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

How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.

5
I'd avoid creating dependencies between the 2 VMs, if possible. If you're using Prism or similar, the EventAggregator, or a similar pattern, may be useful. - Big Daddy
I'm using Caliburn.Micro. Probably should have mentioned that... It seems like, either, the EventAggregater or the IChild implementation could help me here but I just can't wrap my head around how... - Refracted Paladin
For further reference, all I need from this ViewModel is the Selected GridView row. It pops up, user chooses a row, it closes. - Refracted Paladin
I put some code in an answer format. Let me know if you need more. - Big Daddy
In general, then, is it better to use an event in this situation versus passing the value back to it's parent directly? - Refracted Paladin

5 Answers

3
votes

EventAggregator is what you could use... One of many solutions I am sure.

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"});
    }
}
1
votes

One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.

When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:

//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
    }
}

And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.

EDIT: As @DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the 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;
    }
}
0
votes

One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:

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

How about?

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...
    }
}

Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.

Well of course to do the more classical way you could alternatively use an interface like this.

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

As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.

Create an event - the event's payload will be your SelectedMember:

public class YourEvent:CompositePresentationEvent<YourEventPayload>{}

Publish the event:

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

Subscribe to the event:

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