2

Ok, so I found WPF MVVM navigate views in which Sheridan does a (mostly) great explanation on how to navigate multiple views in a single window. Following the code and explanation I was able to get the views switching using the code behind, however I can't manage to get his XAML Command="" version of the view switch working.

I had to search around to find a version of ActionCommand() that matches his inputs, but I'm not even sure it's the correct one, and I can't find anything on the IsViewModelOfType<T>() method at all. Which means the final solution given is partially broken.

How do I get that last part working without 3rd party libraries?

Unfortunately because StackOverflow hates new users participating in anything I couldn't just ask in the same thread.

From the above post, this is the part that doesn't work, because the two parts -- ActionCommand and IsViewModelOfType -- don't seem to exist anywhere and there's no information for them searching online

Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:

public ICommand DisplayPersonView
{
    get { return new ActionCommand(action => ViewModel = new PersonViewModel(), 
        canExecute => !IsViewModelOfType<Person>()); }
}

In the child view XAML:

<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
    {RelativeSource AncestorType={x:Type MainViewModel}}, Mode=OneWay}" />

Update: I tried returning both True and False from my own IsViewModelOfType<T>() (as well as actually testing) but to no avail. The key to the problem appears to be either the ActionCommand() method, for which I have no basis to go on, or that the XAML itself is incorrect.

The closest match I can find for ActionCommand() is from Microsoft.Expression.Interactivity.Core but it only accepts a single parameter and doesn't appear to behave correctly for this usage.

I also tried this code snippet I found online, but it doesn't do anything when using Sheridan's code either

class ActionCommand : ICommand
{
    private readonly Action<object> _executeHandler;
    private readonly Func<object, bool> _canExecuteHandler;

    public ActionCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("Execute cannot be null");
        _executeHandler = execute;
        _canExecuteHandler = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _executeHandler(parameter);
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecuteHandler == null)
            return true;
        return _canExecuteHandler(parameter);
    }
}
Community
  • 1
  • 1
  • I think that `Command` binding is wrong... let's wait for Sheridan to read this and explain a little more. – Federico Berasategui Mar 13 '14 at 16:20
  • what did you try to do? – La-comadreja Mar 13 '14 at 16:31
  • No, we don't hate new users, but we hate new users that do not read the documentation on how to ask an answerable question. Most of what you have described above is poorly defined. Where is the `IsViewModelOfType()` used in Sheriden's code, why/how is it relevant to your question? What have your tried so far (where is the code and which part of it is not working?)? It is not our job to navigate multiple links to glean information in order to help you. Be explicit. So far, the only person that can help you without starting from scratch is Sheriden himself. – MoonKnight Mar 13 '14 at 16:31
  • Well show that code here, in your question. – MoonKnight Mar 13 '14 at 16:33
  • Killercam, I didn't think it was necessary for me to repeat the two lines of code that all of my question pertained to from the bottom of Sheridan's answer when everything else is working just fine except the XAML portion (which is clearly marked). Sorry for using normal conversation to ask questions. You're more reason why I hate using this site. – ShadowDrakken Mar 13 '14 at 16:34
  • La-comadreja, as I already stated in my post, I tried to locate the ActionCommand and IsViewModelOfType methods via web searches. Because, as I said in my original post, those two are the only part causing problem, as they don't exist anywhere I can find via the already mentioned searching. – ShadowDrakken Mar 13 '14 at 16:37
  • I having the same problem now. If you have found a better solution, please edit your answer. – Tito Sep 27 '17 at 07:51

2 Answers2

1

I would say from the code that I have seen in the answer you reference that IsViewModelOfType<Person>() method is using some type of mapping to get the view model type from a given object type. So the code

public ICommand DisplayPersonView
{
    get { return new ActionCommand(action => ViewModel = new PersonViewModel(), 
        canExecute => !IsViewModelOfType<Person>()); }
}

would be referencing the generic method that might look like

public bool IsViewModelOfType<T>()
{
    Type type = typeof(T);
    if (!viewModelMapping.ContainsKey(type))
        return false;
    return viewModelMapping[type] == typeof(this);
}

where this in the above refers to the current View Model and where you might have view model type to object type mapping dictionary like

public Dictionary<Type, Type> viewModelMapping = 
    new Dictionary<Type, Type>()
{
    { typeof(Person), typeof(PersonViewModel) }, 
    ...
};

All that Sheriden is saying above, in the command is that if the type is not a Person then do not allow the display of the PersonViewModel. You can impose this logic anyway you like and it does not have to follow Sheriden's methodology.

I hope this helps.


Having read your edit, I would first try to remove the RelativeSource code, I don't think this is needed. Okay, now for the command class. Here is the one I used before I started using MVVM frameworks:

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace ResourceStudio.Commands
{
   /// <summary>
   /// A command whose sole purpose is to relay its functionality to other
   /// objects by invoking delegates. The default return value for the CanExecute
   /// method is 'true'.
   /// </summary>
   public class RelayCommand : ICommand
   {
      readonly Action<object> execute;
      readonly Predicate<object> canExecute;

      #region Constructors
      /// <summary>
      /// Creates a new command that can always execute.
      /// </summary>
      /// <param name="execute">The execution logic.</param>
      public RelayCommand(Action<object> execute)
         : this(execute, null)
      {
      }

      /// <summary>
      /// Creates a new command.
      /// </summary>
      /// <param name="execute">The execution logic.</param>
      /// <param name="canExecute">The execution status logic.</param>
      public RelayCommand(Action<object> execute, Predicate<object> canExecute)
      {
         if (execute == null)
            throw new ArgumentNullException("execute");
         this.execute = execute;
         this.canExecute = canExecute;
      }
      #endregion // Constructors

      #region ICommand Members
      [DebuggerStepThrough]
      public bool CanExecute(object parameter)
      {
         return this.canExecute == null ? true : this.canExecute(parameter);
      }

      /// <summary>
      /// Can execute changed event handler.
      /// </summary>
      public event EventHandler CanExecuteChanged
      {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }

      public void Execute(object parameter)
      {
         this.execute(parameter);
      }
      #endregion // ICommand Members
   }
}

How this is used for a MenuItem click is:

<MenuItem Header="E_xit" 
          Command="{Binding CloseApplicationCommand}"/>

Where CloseApplicationCommand is

private RelayCommand closeApplicationCommand;
public RelayCommand CloseApplicationCommand
{
    get
    {
        return closeApplicationCommand ?? (closeApplicationCommand =
            new RelayCommand(o => this.CloseApplication(), o => CanCloseApplication));
    }
}

where CanCloseApplication is some boolean property.

Okay, now, if you want to set the command to fire on a particular event, say a button MouseOver event, then you will have to write something like

<Button Content="SomeButton">
    <Interactivity:Interaction.Triggers>
      <Interactivity:EventTrigger EventName="MouseOver">
         <Interactivity:InvokeCommandAction Command="{Binding SomeButtonMouseOverCommand}" />
      </Interactivity:EventTrigger>
   </Interactivity:Interaction.Triggers>
</Button>

using xmlns:Interactivity="http://schemas.microsoft.com/expression/2010/interactivity" namespace. A much better way however, is to use AttachedCommandBehaviours (if you're adverse to switching to an MVVM framework (e.g. Claiburn.Micro et al.)). You can down load the AttachedCommandBehavious here. Then you can write

<Button Content="Select" 
        AttachedCommand:CommandBehavior.Event="Click" // Or what ever event you want!
        AttachedCommand:CommandBehavior.Command="{Binding DoSomethingCommand}" />

I would think seriously about using a MVVM framework. However, it is good to understnad this stuff before moving to one.

MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • While this didn't directly solve the problem, it did give me enough details about what's going on to hunt down the various problems my code had. So I'm marking it as a solution anyway. I'll come back at some point when I have more time to share what ultimately fixed the issues. – ShadowDrakken Mar 13 '14 at 20:20
0

Ok, so turns out the problem was here

In the child view XAML:

<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
    {RelativeSource AncestorType={x:Type MainViewModel}}, Mode=OneWay}" />

This wasn't firing anything at all. Using this instead based on Killercam's feedback

<Button Command="{Binding DisplayPersonView}" />

where DisplayPersonView() also fires an event OnViewModelChanged contained within the BaseViewModel that all the view models inherit.

Unfortunately I had to break MVVM practice slightly, by adding a handler in the window's code behind

private void UpdateView(object sender, EventArgs e)
{
    DataContext = (BaseViewModel)sender;
    ((BaseViewModel)DataContext).OnViewModelChange += UpdateView;
}

And in the window's constructor I call UpdateView() to set the initial ViewModel

This works with either the above described ActionCommand() or RelayCommand() just fine, and with or without the canExecute() just fine.