How can I handle WPF routed commands in my ViewModel without code-behind?
Asked Answered
W

3

15

According to my understanding of MVVM, it is a good practice to handle routed commands directly in the ViewModel.

When a routed command is defined in a ViewModel as a RelayCommand (or DelegateCommand), it is easy to bind directly to the command like this: Command={Binding MyViewModelDefinedCommand}.

Actually, for routed command that are defined outside of my ViewModel, I handle those commands in the Code behind of the View and forward calls to the ViewModel. But I find it awkward that I have to do so. It goes against recommended MVVM good practices. I think that there should be a more elegant way to achieve the job.

How can I handle a "System.Windows.Input.ApplicationCommands" or any routed command defined outside of the Viewmodel directly in the ViewModel. In other words, for command defined outside of the ViewModel, how can I handle CommandBinding callback "CommandExecute" and/or "CommandCanExecute" to the ViewModel directly? Is that possible or not? If yes how? If no, why?

Woke answered 23/3, 2016 at 16:16 Comment(33)
Without a good minimal reproducible example showing exactly what you've tried, the implementation of the commands, and that implementation's relationship to your various objects (view, model, etc.), it's impossible to offer a specific answer to your question. Most likely, you just want to declare CommandBinding elements in the CommandBindings collection of one of your UI objects (such as the window itself). But there are lots of possibilities.Rhizotomy
In the meantime, there is a wealth of existing information on the topic here on Stack Overflow. See e.g. #1281678, #601893, and especially this specific answer: stackoverflow.com/a/9239 (IMHO the accepted answer is not going to be very useful to you, but all those other links will).Rhizotomy
If there is so much possibilities.. why don't you go and tell one? I'll be more than happy to see anyone of your answers. All the links you gave me does not answer my question or does not help to find an answer. It is not because one or two words can be matched that answers are related. Sometimes the problem lies in the combinaison of 2 problems together ie: like binding actions to the View model (kind of the root of the problem). Binding to a command directly in a ViewModel is easy. Binding to a view is easy. But binding command actions to a ViewModel, I don't know how to do that?Woke
I'm actually working on a short sample.Woke
Post just one! Just one that works fine!Woke
It's the second time you are doing that to me. Each time you just don't know the answer. The first time, the question was totally good. The question was required me to mix 2 different answers to fits my question and one of the answer was very very hard to find and understand. But I removed it and nobody could benefit of it now.Woke
Do you really think I did ask without taking time to search for an answer before? Do you really think that I wouldn't be really mad and sad if somebody could ever give me a link with the answer, anywhere ???Woke
Peter, it is not fair to try to close a valid question just because you don't have an answer to it. You don't just give frustration to user trying to find an answer to its question. By doing so, you deprive peoples from having answer to their questions in the future. Perhaps I would have already found my answer if there wouldn't be such peoples with such behavior.Woke
That code is certainly not minimal, but also I believe this is a duplicate of #601893 which Peter already linked to youSpinal
Also, it is not all questions that should have sample code with it. In most case they do and will really help to solve the problem. But when the question is about doing something where you have no idea how to do it, a sample could be not just irrelevant, it could add more confusion than clarity.Woke
@GordonAllocman, No the link given is not forwarding command to the ViewModel but to the View directly. That's partly the reason of my question.Woke
Your question "How can I do Command binding properly for a standard command like "Cut" "Copy" or "Open" (System.Windows.Input.ApplicationCommands) when the command itself is not defined in the ViewModel?" is answered exactly in that post, you don't need to define it in the ViewModel you can just bind to it in the View. If that isn't your question what is it?Spinal
All that said, while I appreciate you've attempted to improve your question, your code example still does not make clear what you're asking. You've posted an empty view model class, and shown no attempt for your CommandBinding.Executed event handler to actually do something. At the same time, you've included the RelayCommand type, which is not used anywhere in the rest of the code example; all that does is add clutter and make it harder to know what you're asking.Rhizotomy
@GordonAllocman, The convention to execute command, according to good usage of MVVM, it to handle them in the ViewModel, not in the View. My question itself state that I want to bind to the ViewModel.Woke
@PeterDuniho, I try to do my best to fits your need. I really think my question is clear and could have very nice answers for those who master the subject. I added the code you required to fits your own request. I though that RelayCommand could also be a nice to have because I mentionned it in the question. But I just removed it to fits your need.Woke
"I want to bind to the ViewModel" -- nothing in the code you posted shows any attempt to "bind to the ViewModel". A CommandBinding has events; if subscribed in XAML the event handler for each must be declared in the same class as that being defined for the XAML in which the CommandBinding object itself is declared. That handler can call whatever you want. You can, of course, create the CommandBinding itself in code-behind, in which case you can subscribe any event handler you have access to. You could create an ICommand implementation separate from the model, and bind to that. Etc.Rhizotomy
That's three different options right there, in just one comment. But your code example still doesn't have enough information for anyone to know which of those options, if any, is actually what you want to do. Show us what you've tried. Explain precisely why what you tried didn't work. What do you want instead?Rhizotomy
@PeterDuniho, You seems to know good solutions. I'm not sure to understand them properly. As I understand, one way to do it is to create a class instantiated in the ViewModel, like a CommandForwarder that would subscribe to a command and execute actions (Execute and CanExecute). Then I could bind to it. But actually I don't know how to attach that class to each part of the framework to make it works. I will try to write something starting from that idea. I expecteded that anybody could have shared its similar solution. But thanks for the idea, I will try to create something that works from it.Woke
See also #7642156. I don't understand why (based on the incomplete code example you've provided) you don't just have a method in the view model that implements the command, and call that method from the CommandBinding.Executed event handler declared and subscribed in the view (i.e. your Window). It seems like you are making this much harder than it really is.Rhizotomy
@PeterDuniho, You are right. I can do like you said and it should work fine. But as far as I understand from every MVVM documentation I have read, it sounds like it is better to handle command directly in ViewModel. I almost always did like you just proposed, but I try to improve the way I code (closer to what expert recommend). If you can find anything that would not say so (command handling in ViewModel), I'd be really happy to read it.Woke
"from every MVVM documentation I have read" -- please cite one example. "it is better to handle command directly in ViewModel" -- how is implementing the command, via a method called by an event handler in the view, not "directly in the ViewModel"? Not that I agree with this hypothetical concern, but it seems to me yours is the same as the person who posted the question I referenced in my previous comment, i.e. your question is a duplicate of that one. Please explain how it's not.Rhizotomy
This question is kind of hard to understand...Lederer
@H.B., I just added some text in an attempt to make it easier to understand. I think that the reception is not good because it is far from being simple to answer, although it could look like too at first sight. Potentially, there is no solution to it (clean way). More I look at it and more I think it is not possible to do it in a clean way, without too much artifact. But a good explanation of why it can't be achieve would also be an excellent answer. I still continue to look for a solution on my side.Woke
Do you just want to invoke something on your VM when the command is executed? If so, what is the point in using the application commands in the first place, as you would not use their routing behaviour at all?Lederer
@H.B. Actually I need to be able to enable or disable the button according to a state. In fact, I have a control which offer a selection of data that the user should choose. That control is part of a Docking-Toolbox. The only interface to that Toolbox is 1- the command to the button "Load" where I can access it from a Command and 2- The LoadData() function. I could have used a property to enable/disable the button and an event to tell me that it has been pressed.Woke
@H.B. ... But using the command enable me to use the command mechanism of the framework to enable or disable it according the Docking-ActiveDocument which simplify the binding and keep it less tied up (no referencing required). My question was about SystemCommand only to simplify my question, in fact I want to use CommandBinding on a command that is not defined outside of the ModelView.Woke
@H.B. Typo: I want to use a command defined outside of my ViewModel. Being able to bind to a SystemCommand would give me the behavior I want in the same time. But I wanted to keep my question the simplier as I could because short question have more readers... And It seems I was wrong. It was'nt clear enough. It seems that I was wrong, taking too much things for granted.Woke
What exactly is the problem? The binding should be the same, the only thing that changes is how the logic of the command is implemented (CommandBindings), which does not concern the control to which it is bound.Lederer
@H.B. When adding a command binding, you set delegates "Execute" and "CanExecute" (last is optional). If I'm right, they are tied to the View code behind. There is no way to bind them to the ViewModel. On the other side: to use command, for example a button can bind directly the command itself to the ViewModel (like described on the question). I want to respond to command directly in the ViewModel without having to pass through the View. I want that my view reacts to command defined elsewhere than the ViewModel but where the "Execute" and "CanExecute" are bound to the ViewModel directly.Woke
@H.B. The problem is not that I can't achieve to do something. The problem is the way I have to achieve it. I could easily handle "Exeute" in view and/or forward to the ViewModel (in fact I actually do that). For non-UI command, it is more logical to handle "Execute" and "CanExecute" directly in the ViewModel. That's the main reason of RoutedCommand (at least for what I understand of it). The problem then reside for command that are defined outside of the ViewModel which it would be nice to do something similar as routedCommand. It's only a matter of doing clean code: separation of concerns.Woke
RoutedCommands are routed through the view, thus they should be handled there. As Peter Duniho pointed out you appear to have the common misconception that MVVM means nothing may be done in the view.Lederer
@H.B. I know that command could be handled in the codeBehind of the view. I used to do that always. But I want to improve my code to be more inline with good practices. I updated my question by adding some more explanation. I hope it could help a vast audience to understand my though/intends.Woke
@EricOuellet I think a lot of the confusion stems from your use of "UI" commands instead of "routed commands". If I had to summarize your entire question, I would word it as: How can I handle WPF routed commands in my ViewModel without code-behind?Roye
R
35

I would rephrase the question as:

How can I handle WPF routed commands in my ViewModel without code-behind?

To which, I would respond: Great Question!

WPF does not provide a built-in way to do this, which is especially annoying when you're first starting WPF and everybody tells you that "Code-Behind is evil" (it really is). So you have to build it yourself.

Building it Ourselves

So, how do go about creating such functionality ourselves? Well, first we need an equivalent of a CommandBinding:

/// <summary>
///  Allows associated a routed command with a non-routed command.  Used by
///  <see cref="RoutedCommandHandlers"/>.
/// </summary>
public class RoutedCommandHandler : Freezable
{
  public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
    "Command",
    typeof(ICommand),
    typeof(RoutedCommandHandler),
    new PropertyMetadata(default(ICommand)));

  /// <summary> The command that should be executed when the RoutedCommand fires. </summary>
  public ICommand Command
  {
    get { return (ICommand)GetValue(CommandProperty); }
    set { SetValue(CommandProperty, value); }
  }

  /// <summary> The command that triggers <see cref="ICommand"/>. </summary>
  public ICommand RoutedCommand { get; set; }

  /// <inheritdoc />
  protected override Freezable CreateInstanceCore()
  {
    return new RoutedCommandHandler();
  }

  /// <summary>
  ///  Register this handler to respond to the registered RoutedCommand for the
  ///  given element.
  /// </summary>
  /// <param name="owner"> The element for which we should register the command
  ///  binding for the current routed command. </param>
  internal void Register(FrameworkElement owner)
  {
    var binding = new CommandBinding(RoutedCommand, HandleExecuted, HandleCanExecute);
    owner.CommandBindings.Add(binding);
  }

  /// <summary> Proxy to the current Command.CanExecute(object). </summary>
  private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
    e.CanExecute = Command?.CanExecute(e.Parameter) == true;
    e.Handled = true;
  }

  /// <summary> Proxy to the current Command.Execute(object). </summary>
  private void HandleExecuted(object sender, ExecutedRoutedEventArgs e)
  {
    Command?.Execute(e.Parameter);
    e.Handled = true;
  }
}

And then we need a class that will actually associated the RoutedCommandHandler with a specific element. For this, we'll make a collection of RoutedCommandHandlers as an attached property, like so:

/// <summary>
///  Holds a collection of <see cref="RoutedCommandHandler"/> that should be
///  turned into CommandBindings.
/// </summary>
public class RoutedCommandHandlers : FreezableCollection<RoutedCommandHandler>
{
  /// <summary>
  ///  Hide this from WPF so that it's forced to go through
  ///  <see cref="GetCommands"/> and we can auto-create the collection
  ///  if it doesn't already exist.  This isn't strictly necessary but it makes
  ///  the XAML much nicer.
  /// </summary>
  private static readonly DependencyProperty CommandsProperty = DependencyProperty.RegisterAttached(
    "CommandsPrivate",
    typeof(RoutedCommandHandlers),
    typeof(RoutedCommandHandlers),
    new PropertyMetadata(default(RoutedCommandHandlers)));

  /// <summary>
  ///  Gets the collection of RoutedCommandHandler for a given element, creating
  ///  it if it doesn't already exist.
  /// </summary>
  public static RoutedCommandHandlers GetCommands(FrameworkElement element)
  {
    RoutedCommandHandlers handlers = (RoutedCommandHandlers)element.GetValue(CommandsProperty);
    if (handlers == null)
    {
      handlers = new RoutedCommandHandlers(element);
      element.SetValue(CommandsProperty, handlers);
    }

    return handlers;
  }

  private readonly FrameworkElement _owner;

  /// <summary> Each collection is tied to a specific element. </summary>
  /// <param name="owner"> The element for which this collection is created. </param>
  public RoutedCommandHandlers(FrameworkElement owner)
  {
    _owner = owner;

    // because we auto-create the collection, we don't know when items will be
    // added.  So, we observe ourself for changes manually. 
    var self = (INotifyCollectionChanged)this;
    self.CollectionChanged += (sender, args) =>
                              {
                                // note this does not handle deletions, that's left as an exercise for the
                                // reader, but most of the time, that's not needed! 
                                ((RoutedCommandHandlers)sender).HandleAdditions(args.NewItems);
                              };
  }

  /// <summary> Invoked when new items are added to the collection. </summary>
  /// <param name="newItems"> The new items that were added. </param>
  private void HandleAdditions(IList newItems)
  {
    if (newItems == null)
      return;

    foreach (RoutedCommandHandler routedHandler in newItems)
    {
      routedHandler.Register(_owner);
    }
  }

  /// <inheritdoc />
  protected override Freezable CreateInstanceCore()
  {
    return new RoutedCommandHandlers(_owner);
  }
}

Then, it's as simple as using the classes on our element:

<local:RoutedCommandHandlers.Commands>
  <local:RoutedCommandHandler RoutedCommand="Help" Command="{Binding TheCommand}" />
</local:RoutedCommandHandlers.Commands>

Interaction.Behavior implementation

Knowing the above, you might then ask:

Wow, that's great, but that's a lot of code. I'm using Expression Behaviors already, so is there a way to simplify this a bit?

To which, I would respond: Great Question!

If you're already using Interaction.Behaviors, then you can use the following implementation instead:

/// <summary>
///  Allows associated a routed command with a non-ordinary command. 
/// </summary>
public class RoutedCommandBinding : Behavior<FrameworkElement>
{
  public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
    "Command",
    typeof(ICommand),
    typeof(RoutedCommandBinding),
    new PropertyMetadata(default(ICommand)));

  /// <summary> The command that should be executed when the RoutedCommand fires. </summary>
  public ICommand Command
  {
    get { return (ICommand)GetValue(CommandProperty); }
    set { SetValue(CommandProperty, value); }
  }

  /// <summary> The command that triggers <see cref="ICommand"/>. </summary>
  public ICommand RoutedCommand { get; set; }

  protected override void OnAttached()
  {
    base.OnAttached();

    var binding = new CommandBinding(RoutedCommand, HandleExecuted, HandleCanExecute);
    AssociatedObject.CommandBindings.Add(binding);
  }

  /// <summary> Proxy to the current Command.CanExecute(object). </summary>
  private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
    e.CanExecute = Command?.CanExecute(e.Parameter) == true;
    e.Handled = true;
  }

  /// <summary> Proxy to the current Command.Execute(object). </summary>
  private void HandleExecuted(object sender, ExecutedRoutedEventArgs e)
  {
    Command?.Execute(e.Parameter);
    e.Handled = true;
  }
}

With the corresponding XAML:

<i:Interaction.Behaviors>
  <local:RoutedCommandBinding RoutedCommand="Help" Command="{Binding TheCommand}" />
</i:Interaction.Behaviors>
Roye answered 28/3, 2016 at 1:31 Comment(11)
Thanks a lots MackieChan, it look like you got the proper answer. But I want to verify at the job on Tuesday. I have a question, where do you define "local:RoutedCommandHandlers.Commands" or "i:Interaction.Behaviors" ? Do you put that into resources section? I mean I think that "CommandBindings" only accepts object of type "CommandBinding" ? I feel dumb, but I'm really not sure where to put them?Woke
About the question, I prefer to keep it as is. That's because I feel it cover exactly my intends. Other people that may have same question would not necessarily know that they will have to bind to a RoutedCommand. I think that it is possible that other potential solutions could exists that won't require RoutedCommand.Woke
@EricOuellet RoutedCommandHandlers.Commands and i:Interaction.Behaviors are both Attached Properties. We're actually defining the attached property RoutedCommandHandlers.Commands in the RoutedCommandHandlers class (WPF ultimately calls RoutedCommandHandlers.GetCommands). If you don't know what attached properties are I'd recommend looking them up at some point, but they're basically dependency properties that are defined by external classes.Roye
@EricOuellet and just an FYI, if you're not using expression behaviors ignore the second example altogether, as it's a whole new concept, that while useful, is out of scope for now and may just complicate your understanding. But I included it because others may find it useful, and I ultimately use it over the first approach, as it's less code to maintain :: )Roye
I know (but do not master) attached DP and its Blend version. I wrote few simple of them. I used to use them for controls of the windows and did not realise that a Window is also a Dependency Object and can benefits of them. If I'm right you attach your behavior directly to the Window, which is logical. Your solution look like very awesome !!! I look forward to test it on tuesday. Thanks so much!Woke
Just FYI, I pick the Blend Behavior :-) !Woke
Sorry about the bounty, I though it was awarded automatically to the accepted answer. It's done now. Thanks!Woke
@EricOuellet I think it would have if you hadn't done anything about it in the next ~3 days or something.Roye
I think you are right. In fact I just awarded few minutes ago because of a message about the 3 days left. :-)Woke
HandleCanExecute should check == true not != null. Otherwise, the RoutedCommand will ignore the CanExecute value of the bound ICommand.Cartie
@Roye I'm currently trying this solution as well and I have some questions. First: Shouldn't the RoutedCommand property also have an equivalent DependencyProperty? Second: Is there a way to set the handled-state from inside the Command, instead of in the binding? I would like to have several objects react to the RoutedCommand. :)Hemorrhoidectomy
H
4

The accepted answer is very nice, but it seems the OP didn't quite understand how RoutedCommands work and that caused some confusion. Quoting from the question:

When a routed command is defined in a ViewModel as a RelayCommand (or DelegateCommand), it is easy to bind directly to the command like this: Command={Binding MyViewModelDefinedCommand}.

This is ambigous, but either way it's incorrect:

  1. Either - one can't define a RoutedCommand as a Relay/DelegateCommand because RoutedCommand is a different implementation of ICommand interface.
  2. Or - if a VM exposes an actual RoutedCommand, one will still face the same issue as with those RoutedCommands that are defined outside of VM (because of the way RoutedCommands work).

RoutedCommand is a specific implementation of ICommand

RoutedCommand's Execute/CanExecute methods do not contain our application logic (when you instantiate a RoutedCommand, you don't pass Execute/CanExecute delegates). They raise routed events which, like other routed events, traverse the element tree. These events (PreviewCanExecute, CanExecute, PreviewExecuted, Executed) are looking for element that has CommandBinding for that RoutedCommand. CommandBinding object has event handlers for those events, and that is where our application logic goes (now it's clear why exposing a RoutedCommand from your VM doesn't solve the issue).

// The command could be declared as a resource in xaml, or it could be one 
// of predefined ApplicationCommands
public static class MyCommands {
    public static readonly RoutedCommand FooTheBar = new RoutedCommand();
}

xaml:

<Window x:Class...
        xmlns:cmd="clr-namespace:MyCommands.Namespace">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static cmd:MyCommands.FooTheBar}"
                        Executed="BarFooing_Executed"/>
    </Window.CommandBindings>

<Grid>
...
// When command is executed, event goes up the element tree, and when
// it finds CommandBinding on the Window, attached handler is executed
<Button Command="{x:Static cmd:MyCommands.FooTheBar}"
        Content="MyButton"/>
...
</Grid>
</Window>

CommandBinding object

CommandBinding class does not inherit from DependencyObject (it's Command property can't be bound to a command exposed on VM). You can use event handlers attached to a CommandBinding to forward the call (in code-behind) to the VM - there is nothing important there, no logic (nothing to test). If you want no code-behind, then the accepted answer has nice solution (does that forwarding for you).

Homophonous answered 4/11, 2017 at 16:0 Comment(0)
I
0

Here i have a simple example for Binding a Command to a button:

MainWindow.xaml

<Window x:Class="csWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow">
    <Canvas>
        <Button Name="btnCommandBounded" Command="{Binding cmdExecuteSubmit}" Height="29" Width="68" Content="Submit"></Button>
    </Canvas>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }

MainWindowViewModel.cs

class MainWindowViewModel
    {
        public ICommand cmdExecuteSubmit { get; set; }
        public MainWindowViewModel()
        {
            cmdExecuteSubmit = new RelayCommand(doSubmitStuff);
        }
        public void doSubmitStuff(object sender)
        {
            //Do your action here
        }
   }
Ibbetson answered 28/3, 2016 at 1:29 Comment(5)
Thanks un-lucky, the problem is the Command is defined into the ViewModel. The problem of my question reside mainly when the command is not defined into the ViewModel... only the handler of "CommandExecute" and/or "CommandCanEXecute" should be defined in the ViewModel.Woke
@EricOuellet : Sorry i don't get you. Still your problem remains or it is solved?Ibbetson
You are quick... :-) !!! I'm still trying to understand MackieChan answer but at first sight it seems that he got the right solution. It could take me some times to understand it. It's not as simple as I would expected. But he sounds to have found a nice way to do it. Thanks a lots for your try!!!Woke
I can't answer right now. As far as I understand the MackieChan solution, he got a very nice answer and I will probably accept it. But I want to verify that it works fine before accepting something. I can't verify now because I'm at home and my problem is at the job. I will check on tuesday (monday is an holiday in Canada for many peoples).Woke
The question doesn't ask how to bind to a command on a button. It asked how to connect a RoutedCommand to an ICommand on a viewmodel.Nonfeasance

© 2022 - 2024 — McMap. All rights reserved.