Get parent ViewModel

May 21, 2012 at 1:47 PM

Is there a way to get the parent ViewModel from (say) a Control-ViewModel.

I'm asking, because I'm not sure how to best handle the following topic: I have a Window with two user controls. One is a search control, the second is basically a DataGrid. I host the data for the list on the MainWindow and pass it to the second control (correctly to the associated ViewModel).

But now I have some logic as to when a certain button in the search control should be enabled. I "know" this at the MainWindow. So I can create e.g. a Command there that gets executed when the button is hit, and provides the enable-logic. But the Button is on the Search control. How can I put those together. Well, fur executing I can use the InterestedIn attribute, but that will not cover the enable-logic. So I thought I could bind the Command from the MainWindow in my UserControl. Like {Binding Path=GetVMParent.ABCCommand}

Thanks for any help or idea,

G.

May 21, 2012 at 2:08 PM
Edited May 21, 2012 at 2:08 PM

I found a way to do it, although its not the most elegant solution probably. I created two commands - one in the Window and one in the UserControl,. I bind to the Control ViewModel, and relay the execution like this:

private bool OnSaveCommandCanExecute()
{
    return ParentViewModel != null && ((MainViewModel)ParentViewModel).SaveCommand.CanExecute();
}
 
private void OnSaveCommandExecute()
{
    if (ParentViewModel != null) ((MainViewModel)ParentViewModel).SaveCommand.Execute();
}
May 21, 2012 at 2:45 PM

It is better to do something like this:

var mainViewModel = ParentViewModel as MainViewModel;
if (mainViewModel != null)
{
    return mainViewModel.SaveCommand.CanExecute();
}

May 21, 2012 at 2:48 PM

Thats true of course ;-)

May 25, 2012 at 2:27 PM
Edited May 25, 2012 at 2:29 PM

I now have a partial solution for my problem. I was not really content creating these two dummy methods, that just relay to the real command, so I wrote my own  RelayCommand : ICommand implementation which gets initialized with a location string like "../SaveCommand". On runtime, the RelayCommand searches for the SaveCommand one level up (that is the ParentViewModel) and calls the appropriate methods there. So basically this works as above, but has less code in the ViewModel.

BUT

This works nice if I just have to go up in the hierarchy. In the above mentioned case, I call a command on a window within a UserControl. But I also want to do the opposite, or even call from one UserControl to another like "../SearchControl/SearchCommand". But I don't know how to find a child ViewModel. I was inspecting the _childViewModel field, but it contains only one of the two UserControls of my window (weird!), and I do not really want to check this field (in this case the location string would contain the name of the child ViewModel).

The other downside is, that - using the type of the child-UserControl - there could potentially be more than one child control with the same type. So what I'd really like would be to specify the Control (= SearchControl) on the Window, that should be the indicator as to which child-ViewModel is to be used. But how can I access the View from the ViewModel - this seems to be wrong from MVVM approach.

Last (and best?) idea is the property on the (Window-ViewModel) that gets bound to the user control. But how to get the ViewModel for this property?

This is my current implementation of my RelayCommand for all who are interested. Maybe someone has a comment or a suggestion as to how I could proceed, or if the idea is just bad.

 

public class RelayCommand : ICatelCommand, ICommand, IDisposable
{
    Command innerCommand = null;

    IViewModel viewModel = null;

    bool isInitialized = false;

    string locator;

    /// <summary>
    /// List of subscribed event handlers so the commands can be unsubscribed upon disposing.
    /// </summary>
    private readonly List<EventHandler> _subscribedEventHandlers = new List<EventHandler>();

    public RelayCommand(IViewModel viewModel, string locator)
    {
        this.viewModel = viewModel;
        this.locator = locator;
    }

    private void InitOnDemand()
    {
        try
        {
            if (!isInitialized)
            {
                isInitialized = true;

                IViewModel currentViewModel = viewModel;
                var path = locator.Split('/').ToList();

                // get correct viewModel

                while (path.Count > 1)
                {
                    if (path[0] == "..")
                        currentViewModel = (IViewModel)currentViewModel.GetType()
                            .GetProperty("ParentViewModel", 
BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(currentViewModel, null); else { currentViewModel = currentViewModel
/* and do some magic here */
; } path.RemoveAt(0); } // get correct command var property = currentViewModel.GetType().GetProperty(path[0]); if (property == null) throw new ArgumentException(string.Format(
"Cannot find command {0} within {1}", path[0], currentViewModel.GetType())); var command = property.GetValue(currentViewModel, null) as Command; if (command == null) throw new ArgumentException(string.Format(
"The property {0} within {1} is no Command.", path[0],
currentViewModel.GetType())); innerCommand = command; } } catch (Exception ex) { Trace.WriteLine(ex.ToString()); } } public bool CanExecute() { InitOnDemand(); if (innerCommand == null) return false; return innerCommand.CanExecute(); } public void Execute() { InitOnDemand(); if (innerCommand == null) return; innerCommand.Execute(); } public event EventHandler<CommandExecutedEventArgs> Executed; public void RaiseCanExecuteChanged() { InitOnDemand(); if (innerCommand == null) return; innerCommand.RaiseCanExecuteChanged(); foreach (var handler in _subscribedEventHandlers) { if (handler != null) handler(this, EventArgs.Empty); } CommandManager.InvalidateRequerySuggested(); } public object Tag { get { InitOnDemand(); if (innerCommand == null) return null; return innerCommand.Tag; } } public bool CanExecute(object parameter) { InitOnDemand(); if (innerCommand == null) return false; return innerCommand.CanExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; lock (_subscribedEventHandlers) { _subscribedEventHandlers.Add(value); } } remove { lock (_subscribedEventHandlers) { _subscribedEventHandlers.Remove(value); } CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { InitOnDemand(); if (innerCommand == null) return; innerCommand.Execute(parameter); } public void Dispose() { if (innerCommand != null) innerCommand.Dispose(); lock (_subscribedEventHandlers) { foreach (var eventHandler in _subscribedEventHandlers) { CommandManager.RequerySuggested -= eventHandler; } _subscribedEventHandlers.Clear(); } } }