简体   繁体   中英

Command in C# and WPF

I have really simple code but it does not work.

I have a command :

public class SetYesCommand : ICommand , INotifyPropertyChanged
    {
        public event EventHandler CanExecuteChanged;
        public event PropertyChangedEventHandler PropertyChanged;

        ViewModelBase view = new ViewModelBase();

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            view.Sname = "Yes";
            MessageBox.Show("Command works");
            onpropertychanged("Sname");
        }

        private void onpropertychanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }
    }
}

and it is my base view model class :

 public class ViewModelBase : INotifyPropertyChanged
    {
        private  string _s;
        public  string Sname {
            get { return _s; }
            set { _s = value;
                onPropertyChanged("Sname");
            }

        }

        private void onPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

and this is my XAML code :

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        xmlns:viewmodel="clr-namespace:Test.ViewModel"
         xmlns:commands="clr-namespace:Test.ViewModel.Commands"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <viewmodel:ViewModelBase  x:Key="base" ></viewmodel:ViewModelBase>
        <viewmodel:Converters  x:Key="convert" ></viewmodel:Converters>
        <commands:SetYesCommand x:Key="setyes"/>
    </Window.Resources>
    <StackPanel>
        <Button Width="100" Height="30" Command="{StaticResource setyes}"/>
        <TextBox DataContext="{StaticResource base}" Text="{Binding Sname , Mode=TwoWay}"/>
        <CheckBox DataContext="{StaticResource base}" IsChecked="{Binding Sname , Mode=TwoWay , Converter={StaticResource convert}}"  />
        <TextBox />
    </StackPanel>
</Window>

As you can see simply I bound two element in my UI to a string property in my view model base class and I defiend a command for my button in UI. The command works because as you see I check it with a messagebox but the value of two other element ( textbox and checkbox) in my UI do not change.

You are creating new instance of ViewModelBase inside SetYesCommand, so view.Sname = "Yes" does not change Sname property on view model bound to elements.

This is working:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        xmlns:viewmodel="clr-namespace:Test.ViewModel"
        xmlns:commands="clr-namespace:Test.ViewModel.Commands"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <viewmodel:ViewModelBase  x:Key="base" ></viewmodel:ViewModelBase>
        <viewmodel:Converters  x:Key="convert" ></viewmodel:Converters>
    </Window.Resources>
    <StackPanel>
        <Button Width="100" Height="30" Command="{Binding Source={StaticResource base},Path=YesCommand}"/>
        <TextBox DataContext="{StaticResource base}" Text="{Binding Source={StaticResource base},Path=Sname , Mode=TwoWay}"/>
        <CheckBox DataContext="{StaticResource base}" IsChecked="{Binding Sname , Mode=TwoWay , Converter={StaticResource convert}}"  />
        <TextBox />
    </StackPanel>
</Window>

public class ViewModelBase : INotifyPropertyChanged
    {
        private string _s;

        private Commands.SetYesCommand _yesCommand;

        public ViewModelBase()
        {
            _yesCommand = new SetYesCommand(this);
        }

        public string Sname
        {
            get { return _s; }
            set
            {
                _s = value;
                onPropertyChanged("Sname");
            }

        }

        public SetYesCommand YesCommand => _yesCommand;

        private void onPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

public class SetYesCommand : ICommand, INotifyPropertyChanged
{
    public event EventHandler CanExecuteChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    private ViewModelBase view;

    public SetYesCommand(ViewModelBase view)
    {
        this.view = view;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        view.Sname = "Yes";
        MessageBox.Show("Command works");
        onpropertychanged("Sname");
    }

    private void onpropertychanged(string propertyname)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }
    }
}

You are creating a new instance of ViewModelBase in your command. It's is the view model that should create the command and not the other way around.

An typical implementation of the ICommand interface generally accepts an Action<object> to be executed and a Predicate<object> that decides whether to execute the command:

public class SetYesCommand : ICommand
{
    public event EventHandler CanExecuteChanged;
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    public SetYesCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;
        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        if (_execute != null)
            _execute(parameter);
    }
}

The logic is then implemented in the view model:

public class ViewModelBase : INotifyPropertyChanged
{
    private string _s;
    public string Sname
    {
        get { return _s; }
        set
        {
            _s = value;
            onPropertyChanged("Sname");
        }

    }

    public ViewModelBase()
    {
        TheCommand = new SetYesCommand(OnCommandExecuted, null);
    }

    private void OnCommandExecuted(object parameter)
    {
        Sname = "Yes";
        MessageBox.Show("Command works");
    }

    public ICommand TheCommand { get; private set; }

    private void onPropertyChanged(string propertyname)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

In the view, you bind to a command property of the view model that returns an instance of your ICommand implementation:

<Button Width="100" Height="30" DataContext="{StaticResource base}" Command="{Binding TheCommand}"/>

You seem rather confused regarding the relationship between command and viewmodel.

The command object should be a property of the viewmodel, not the other way around.

Start with a basic generic ICommand implementation

public class BasicCommand: ICommand
{
    private readonly Action _execute;

    public BasicCommand(Action execute)
    {
        _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _execute?.Invoke();
    }

    public event EventHandler CanExecuteChanged;
}

Than in your viewmodel, define the command as a property something like this

public class MyViewModel: ViewModelBase
{
    public MyViewModel()
    {
        DoSomethingCommand = new BasicCommamd(()=>OnDoSomething());
    }

    public ICommand DoSomethingCommand {get;}

    private void OnDoSomething()
    {
        // put whatever you want the command to do here
    }
}

You can now set the DataContext of your Window to be an instance of MyViewModel, and bind the button's command property to DoSomethingCommand.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM