Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5

.NET, MVVM, Technical stuff, WPF, XAML
See comments

In October, I published V5 of the MVVM Light Toolkit. Amongst other things, this is the first version where the main assemblies (GalaSoft.MvvmLight and GalaSoft.MvvmLight.Extras) are portable class libraries (PCL). This is good for you because you can now develop PCLs that reference MVVM Light and use its components. It’s good for me too, because it creates less clutter when I want to support multiple Windows frameworks (and boy, do I support them! All XAML frameworks are officially supported by MVVM Light V5 as well as Xamarin.iOS, Xamarin.Android and Xamarin.Forms!)

For you who are using MVVM Light as your framework of choice in an application, it should mean much change. In WPF 4 and WPF 4.5, however, there is a catch: The CommandManager will stop working after you upgrade MVVM Light to V5. What you will observe is that your UI elements (buttons, etc) will stop getting disabled/enabled when the RelayCommand’s CanExecute delegate returns false.

TLDR; (too long, didn’t read)

If you are in a hurry, here is the fix: In any class that uses the RelayCommand, replace the line saying:

using GalaSoft.MvvmLight.Command;

with:

using GalaSoft.MvvmLight.CommandWpf;

So what is the CommandManager?

The CommandManager in WPF probably sounded like a great idea when the Avalon team came up with it. In practice however, it’s not that great…

This object is managing the commands (yes!). Notably, it is managing the enabling and disabling of UI elements that are using a Command. In practice, these are elements deriving from ButtonBase, i.e. any element that has a Command property. This property can be databound to a property of type ICommand.

The ICommand interface specifies two methods and an event:

The idea is that any consumer of an implementation of this interface can ask the ICommand if it can be executed in the current conditions, by calling the CanExecute method that returns a bool. If the conditions change, and the CanExecute method should be re-evaluated, the ICommand instance should raise the CanExecuteChanged event, which instructs the consumer to call CanExecute again.

In theory this is simple enough. In practice, the WPF architects thought it would be nice to have an object that manages that automatically, and re-evaluates all the commands’ CanExecute methods every time that something happens on the UI. This could be a user click, an object getting or losing the focus, a window being brought to front, etc. This, you guessed it, is the CommandManager’s task.

I am not a fan of the CommandManager, because it is too magic. I like to see what is happening, and why some elements are enabled/disabled. This has always been a principle in MVVM Light, and why I prefer configuration over conventions. But in WPF, it is here and many people are relying on it.

Why does it stop working? How did you fit it?

In the portable class library version of MVVM Light, there is no CommandManager. Remember that PCL is a subset of the full .NET, and this subset can be larger or smaller depending on the number of frameworks that you support. When you support WPF and other frameworks like Windows Phone or Windows Store, there is no CommandManager. What it means is that suddenly, the magic stops working, and the UI elements are not enabled/disabled automatically anymore.

In order to solve this, I had a difficult choice to make. The first obvious solution I considered was this: Move the RelayCommand out of the GalaSoft.MvvmLight.dll assembly and into the GalaSoft.MvvmLight.Platform.dll. Because this DLL is not a PCL (hence the “platform” name), it can contains platform-specific elements. If I move the RelayCommand there, it will work with the CommandManager just like before. However, it will not be available for PCL-based assemblies anymore, which is a real issue. That’s why I decided against this decision.

Instead, I decided to duplicate the RelayCommand class. If you check the WPF source code, you will see that this class is linked in the GalaSoft.MvvmLight assembly, and also in the GalaSoft.MvvmLight.Platform assembly. This is the same class! To avoid conflicts however, the one in the Platform assembly is declared in the GalaSoft.MvvmLight.CommandWpf namespace.

Am I fully satisfied with this solution?

Short answer: no. It’s kind of a dirty hack, and it’s not usually my style. But I have to say, given the circumstances, I didn’t really have a choice. The alternative would be to not have RelayCommand in the PCL assembly, which would be bad, or to exclude WPF from the PCL altogether, which would kind of defeat the purpose of using a PCL in the first place. Because of this, I feel that this is the “least bad” solution to this problem.

Hopefully this helps to understand why the change was made, and how to restore the initial functionality. Note that if your application is using MVVM Light V4, or if you are using WPF 3.5 or WPF 4, you don’t have to worry about this issue.

Happy coding
Laurent

Previous entry | Next blog entry

Responses to “Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5”

  1. Phil Says:

    Does this mean that if I no longer want to use CommandManager (i.e., leave my “using” references unchanged and/or take advantage of PCL in future projects) do I have to now wire each RelayCommand button’s IsEnabled property to a ViewModel property implementing INPC (to control the enabling/disabling of the button)? If this is the intention, where does this leave a RelayCommand’s CanExecute method? (i.e., is it still needed/used?).

    I never really like how the CanExecute methods get called outside of my control so I’d be happy to drop CommandManager and add some extra code to raise the necessary events to refresh a button’s state but I’m not sure if that’s best way to implement RelayCommands without the CommandManager.

    Sorry if I’ve misunderstood – I’m just wondering what I need to change to make WPF RelayCommand buttons work (enable/disable) without the “magic” CommandManager :)

  2. Ruslan Says:

    changing to GalaSoft.MvvmLight.CommandWpf doesn’t help. I have to click anywhere on the app in order to enable the button due to CanExecute

  3. lbugnion Says:

    Hi,

    I am not sure I understand your issue, can you send me a repro?

    Thanks
    Laurent

  4. Xamarin Weekly Newsletter #22 | Xamarin Fire Says:

    […] Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5 Laurent Bugnion, from GalaSoft, gets the CanExecute delegate working again in MMVMLight v5. […]

  5. tgz Says:

    Oh, how I wish I’ve read this article two days ago…

  6. MVVM框架从WPF移植到UWP遇到的问题和解决方法 – durow | 查问题 Says:

    […] 之前在WPF中实现ICommand接口时,将检查Command是否可以执行的_canExecute添加到CommandManager的RequerySuggested事件中(详细见MVVM模式解析和在WPF中的实现(三)命令绑定),这样在出现可能会影响命令执行状态的事件时,CommandManager会触发事件对命令的可执行状态进行检查,并将检查方法的返回值赋给命令绑定控件的IsEnable属性上。这样我们只要指定检查命令执行状态的方法即可,系统会自动检查。不过在UWP中这个CommandManager没有了,因此将无法再执行自动的命令可执行状态的检查。刚开始遇到这个问题时,按下Ctrl+.的组合键提示我引用WPF中的dll可以解决问题,按照提示操作了之后红色下划线果然没了,可是编译时提示我有冲突还是什么,后来去网上查如何解决这个冲突,自然没搞定。不过找到了MVVMLight的作者写的一篇文章https://galasoft.ch/posts/2015/01/re-enabling-the-commandmanager-feature-with-relaycommand-in-mv…,看来CommandManager是真没了。 […]

  7. Licantrop0 Says:

    what if we use this directly?

  8. Licantrop0 Says:

    <i: InvokeCommandAction Command="{Binding BuildSelectedCommand}"

    (sorry, previous markup was removed from comments)

  9. lbugnion Says:

    You still need the CommandWpf namespace as shown in this article.

    Thanks
    Laurent

  10. lbugnion Says:

    To be clear this namespace is to be used in the ViewModel where your command is defined (in your case, the VM that has the BuildSelectedCommand declaration).

  11. Martin Says:

    But how can this work, if the ViewModel (containing the RelayCommand) itself is in the PCL? I can’t use the CommandWpf in this case…

  12. Gianni Says:

    I have the same issue on Xamarin.Forms 2.3.
    If the button starts disabled (that makes it invisible in the form), enabling it won’t have any effect.

    I tired both with inline declaration:
    OnLoginCommand = new RelayCommand(OnLoginCommandHandler,
    () =>
    {
    return UserName.Length > 0 && Password.Length > 0;
    });

    and with explicit function declaration:
    OnLoginCommand = new RelayCommand(OnLoginCommandHandler, OnLoginCommandCanExecute);

    The XAML just binds to the command:

    Is there any solution to it?

    Thanks.

Comments for Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5