添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Introduction

In this tip, we will learn about WPF Commands. Commands go very well with MVVM pattern (Model- View-ViewModel). We will also see how actually the view knows about its ViewModel and how the view invokes the operations on ViewModel which can be done by using Commands in WPF.

Background

We will follow this in a step by step approach rather than looking at the complete code at once which will help us to understand what each part of code does in a very better way.

Let’s have a look at the MVVM architecture.

We use the standard conventions for naming the classes as follows:

  • View s are suffixed with View after the name of the View (e.g.: StudentListView )
  • ViewModel s are suffixed with ViewModel after the name of the ViewModel . (e.g.: StudentListViewModel )
  • Model s are suffixed with Model after the name of the Model (e.g.: StudentModel ).
  • Using the Code

    So enough of theory now, let’s dive into the code and see a working example for MVVM and how to use commands in MVVM.

    Create a new WPF project in Visual Studio. Rename the MainWindow as MainWindowView to follow up our conventions.

    Next, we need to create a new class with name MainWindowViewModel that will act as the ViewModel for the MainWindowView .

    What we do here in MVVM is that we tell the View about what its ViewModel will be. This can be done by setting the Data Context for that view. Model s are used in the ViewModel and they do not have any connection between some specific views.

    Example code for setting the Datacontext goes like this.

    Open the MainWindowView.xaml.cs and set the data context as follows.

    MainWindowView.xaml.cs

    <Window x:Class="WpfExample.MainWindowView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            xmlns:local="clr-namespace:WpfExample">
        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
        </Grid>
    </Window>

    Here the local is an alias to our namespace WpfExample . It’s required so that the framework knows where MainWindowViewModel is available.

    Now we have set the MVVM pattern. Now the MainWindowView knows that’s its ViewModel is MainWindowViewModel .

    We will verify this by using a simple binding.

    Let's add a button to view and set the button content using a instance from ViewModel .

    The View

    We will add the button to the view and we will set its content using binding as follows:

    MainWindowView.xaml.cs

    <Window x:Class=" WpfMvvmExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:local="clr-namespace:WpfMvvmExample"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
            <Button Width="100" 
            Height="100" Content="{Binding ButtonContent}"/>
        </Grid>
    </Window>
    The ViewModel
    namespace WpfExample
        class MainWindowViewModel
            public string ButtonContent
                    return "Click Me";
    

    In the above code, we are telling the View to fetch the content of button from ButtonContent property present in the ViewModel.

    <Button Width="100" Height="100" Content="{Binding ButtonContent}"/>

    Now if we run the application, we can see that the button content is set to string Click Me”.

    So this interprets that our MVVM is working properly.

    Now We Move In To the ICommand Interface

    Now, let’s try to add click functionality to the button using Commands in WPF.

    Commands provide a mechanism for the view to update the model in the MVVM architecture.

    First, we have a look at the ICommand interface.

    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;

    We will create a sample application that displays a message box with message “HI” when a button is clicked and we will add another button that toggles whether the Hi button can be clicked.

    We create a class called RelayCommand which implements ICommand interface. This class acts as Enhancement for the ICommand and extracts the boiler plate code to a separate class.

    public class RelayCommand : ICommand
           private Action<object> execute;
           private Predicate<object> canExecute;
           private event EventHandler CanExecuteChangedInternal;
           public RelayCommand(Action<object> execute)
               : this(execute, DefaultCanExecute)
           public RelayCommand(Action<object> execute, Predicate<object> canExecute)
               if (execute == null)
                   throw new ArgumentNullException("execute");
               if (canExecute == null)
                   throw new ArgumentNullException("canExecute");
               this.execute = execute;
               this.canExecute = canExecute;
           public event EventHandler CanExecuteChanged
                   CommandManager.RequerySuggested += value;
                   this.CanExecuteChangedInternal += value;
               remove
                   CommandManager.RequerySuggested -= value;
                   this.CanExecuteChangedInternal -= value;
           public bool CanExecute(object parameter)
               return this.canExecute != null && this.canExecute(parameter);
           public void Execute(object parameter)
               this.execute(parameter);
           public void OnCanExecuteChanged()
               EventHandler handler = this.CanExecuteChangedInternal;
               if (handler != null)
                   //DispatcherHelper.BeginInvokeOnUIThread(() => handler.Invoke(this, EventArgs.Empty));
                   handler.Invoke(this, EventArgs.Empty);
           public void Destroy()
               this.canExecute = _ => false;
               this.execute = _ => { return; };
           private static bool DefaultCanExecute(object parameter)
               return true;
    

    The CommandManager.RequerySuggested is responsible for enabling and disabling of "Click to Hii" button.

    TheView

    <Window x:Class="WpfExample.MainWindowView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            xmlns:local="clr-namespace:WpfExample">
        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Button Grid.Row="0" Command="{Binding HiButtonCommand}" 
            CommandParameter="Hai" Content="{Binding HiButtonContent}"
                    Width="100"
                    Height="100"  />
            <Button Grid.Row="1" Content="Toggle Can Click" 
            Command="{Binding ToggleExecuteCommand}"  Width="100" Height="100"/>
        </Grid>
    </Window>

    TheViewModel

    class MainWindowViewModel
            private ICommand hiButtonCommand;
            private ICommand toggleExecuteCommand { get; set; }
            private bool canExecute = true;
            public string HiButtonContent
                    return "click to hi";
            public bool CanExecute
                    return this.canExecute;
                    if (this.canExecute == value)
                        return;
                    this.canExecute = value;
            public ICommand ToggleExecuteCommand
                    return toggleExecuteCommand;
                    toggleExecuteCommand = value;
            public ICommand HiButtonCommand
                    return hiButtonCommand;
                    hiButtonCommand = value;
            public MainWindowViewModel()
                HiButtonCommand = new RelayCommand(ShowMessage, param => this.canExecute);
                toggleExecuteCommand = new RelayCommand(ChangeCanExecute);
            public void ShowMessage(object obj)
                MessageBox.Show(obj.ToString());
            public void ChangeCanExecute(object obj)
                canExecute = !canExecute;
    

    Final application looks like this:

    I have attached the sample project along with this tip. Hope you find this tip helpful.

    Very nice article. I want to use Command with MVVM. But I am very poor with XAML.
    It will be very helpful if will get a code only conversion of your example.
    Thanks in advance.
    Sign In·View Thread  Questiona question about the MainWindowViewModel constructor ¿is it right? Pin
    EdoFro25-Jan-18 10:47
    EdoFro25-Jan-18 10:47  Hello,
    in the MainWindowViewModel constructor I don`t understand why we should use the private ICommand toggleExecuteCommand and not the public ICommand ToggleExecuteCommand. It's a mistake?
    from the TheViewModel class:
    public MainWindowViewModel() HiButtonCommand = new RelayCommand(ShowMessage, param => this.canExecute); toggleExecuteCommand = new RelayCommand(ChangeCanExecute); I'm a beginner so I really don't know!Confused | :confused:
    Can anybody explain this please!!
    thanks in advance!!
    Sign In·View Thread  Nomesh, your article claims license protection, but the diagram you include in it looks like a direct copy from the Microsoft site: https://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx.
    You'll need to either call this out in your article or provide your own diagram, but you can't claim a license on it.
    Perhaps you've already chased down ownership and licensing and I missed it.
    Sign In·View Thread  Hi, at first, let me thank you for this nice introductory article. I must apologize for doing an advertisement here, but I think it's good to know, that there is a technology which automates implementation of the ICommand interface. See http://doc.postsharp.net/command[^].
    Sign In·View Thread  QuestionHiButtonCommand = new RelayCommand(ShowMessage, param => this.canExecute); Pin
    Member 131025173-Apr-17 2:02
    Member 131025173-Apr-17 2:02  I receive an error: "Conversion of method group in Action<object> not possible", whats wrong here.

    Sign In·View Thread  AnswerRe: HiButtonCommand = new RelayCommand(ShowMessage, param => this.canExecute); Pin
    Nomesh G3-Apr-17 2:36
    Nomesh G3-Apr-17 2:36 
    Change the open_file() method to open_file(Object obj) and it should work fine. Let me know the result please.
    Sign In·View Thread  Good article, but you should also explain the problem with Command Parameter Binding which still exists in WPF 4.5 - first time the commands binds, the command parameter is null. There are workarounds including one I found on StackOverflow for a ButtonHelper to force the binding to take place. Other than that, good article.
    Sign In·View Thread  Hi Nomesh,
    Good article for beginners, but I would suggest that you use weak references (weak event pattern) for your internal event, so you can make sure that you are not leaking memory. Check the following:
    Weak event patterns on MSDN
    Regards
    Fitim

    modified 28-Dec-16 20:50pm.

    Sign In·View Thread  Hi Nomesh,
    Could you please explain what the Destroy() function is used for and when this would be called?
    Kind wishes ~ Patrick
    Thank you to anyone taking the time to read my posts.

    Sign In·View Thread  Hi Nomesh, I can't understand that why you defined this below property public bool CanExecute return this.canExecute; if (this.canExecute == value) return; this.canExecute = value; and you added and removed CanExecuteChangedInternal in the accessor in the RelayCommand class. public event EventHandler CanExecuteChanged CommandManager.RequerySuggested += value; this.CanExecuteChangedInternal += value; //This one remove CommandManager.RequerySuggested -= value; this.CanExecuteChangedInternal -= value; //And this one Is these really required?

    Sign In·View Thread  I switched the functionality of the two test buttons around, and this helped me learn a lot about ICommand and the Constructors in the RelayCommand Class.
    Sign In·View Thread 
    Thanks for your appreciation. But i did not understood your point about switching the functionality of the two buttons around?
    Sign In·View Thread  Sorry for butchering your name in my prior comment Nomesh. I would like to add that a lot of people post classes on various posts I find useful and enlightening such as yours. However, it seems strange to me that they often leave out the namespaces referenced in the class. So the example code does not work unless you include the proper namespaces.
    Sign In·View Thread 
    Yeah, i agree with you that it would be helpful if all the namespaces used are specified in code snippet. I'll keep this in mind in my next article. Anyway for this article you can download the source code if you have problem identifying the namespace to be used.
    Sign In·View Thread  Naresh
    I really liked your simple layout of the MVVM pattern. I am just learning what it takes to implement the MVVM pattern on my next project. I would have liked to see in the example a deeper discussion about the "RelayCommand" class.
    Thanks Scott
    Sign In·View Thread