Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF how to update CanExecute

Tags:

c#

mvvm

wpf

xaml

I have the following question.

I have the following simple xaml:

<TextBox Name="NameBox" Text ="{Binding Name}" />
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />

And i bind DataContext of this Window to following View Model

public class MyViewModel
{
    public SimpleModel Entity { get; set; }

    private ICommand _saveCommand;

    public ICommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }

    public void OnSaveItem(object parameter) 
    {
        // some code
    }

    public virtual bool CanSaveItem()
    {
        return !String.IsNullOrWhiteSpace(Entity.Name);
    }

}      

SimpleModel is

public class SimpleModel
{        
    public int Id { get; set; }

    public string Name { get; set; }       
}

This code works mostly correct but i can not make method CanSaveItem to work properly. I don't know how to tell to SaveCommand that properties of ViewModel was changed. I know that i have to use CanExecuteChanged or CommandManager.InvalidateRequerySuggested and i tried to use their some times but i don't know how to do it properly and it didn't take an effect. Could you help me with this problem?

UPD.

public class MyCommand : ICommand
{        
    public MyCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

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

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

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

    private readonly Predicate<object> _canExecute;

    private readonly Action<object> _execute;
}
like image 500
Junior222 Avatar asked Aug 13 '14 15:08

Junior222


2 Answers

It appears you are on an early learning curve, and this can be confusing... and sometimes still is to me too.

Anyhow, I've made some slight changes to what you had and explain what I did to them.

public class MyViewModel
{
    public SimpleModel Entity { get; set; }

    private MyCommand _saveCommand;

    public MyCommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }

    public MyViewModel()
    {
        //------ You need to create an instance of your entity to bind to
        Entity = new SimpleModel();

        //-- I added an event handler as your "Entity" object doesn't know 
        //-- about the button on the view model.  So when it has something
        //-- change, have it call anybody listening to its exposed event.
        Entity.SomethingChanged += MyMVVM_SomethingChanged;
    }

    void MyMVVM_SomethingChanged(object sender, EventArgs e)
    {
        // Tell our mvvm command object to re-check its CanExecute
        SaveCommand.RaiseCanExecuteChanged();
    }

    public void OnSaveItem(object parameter)
    {
        // some code
    }

    public virtual bool CanSaveItem()
    {
        //-- Checking directly to your Entity object
        return !String.IsNullOrWhiteSpace(Entity.Name);
    }
}


public class SimpleModel
{
    //-- Simple constructor to default some values so when you run
    //-- your form, you SHOULD see the values immediately to KNOW
    //-- the bindings are correctly talking to this entity. 
    public SimpleModel()
    {
        _name = "test1";
        _Id = 123;
    }

    //-- changed to public and private... and notice in the setter
    //-- to call this class's "somethingChanged" method
    private int _Id;
    public int Id
    {
        get { return _Id; }
        set
        {
            _Id = value;
            somethingChanged("Id");
        }
    }

    private string _name;
    public string Name 
    { get { return _name; }
        set { _name = value;
                somethingChanged( "Name" );
        }
    }

    //-- Expose publicly for anything else to listen to (i.e. your view model)
    public event EventHandler SomethingChanged;

    //-- So, when any property above changes, it calls this method with whatever
    //-- its property is just as a reference.  Then checks.  Is there anything
    //-- listening to our exposed event handler?  If so, pass the information on
    private void somethingChanged( string whatProperty)
    {
        // if something is listening
        if (SomethingChanged != null)
            SomethingChanged(whatProperty, null);
    }


}


public class MyCommand : ICommand
{
    public MyCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _canExecute = canExecute;
        _execute = execute;
    }

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

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

    private readonly Predicate<object> _canExecute;

    private readonly Action<object> _execute;

    //-- Change to the event handler definition, just expose it
    public event EventHandler CanExecuteChanged;

    //-- Now expose this method so your mvvm can call it and it rechecks 
    //-- it's own CanExecute reference
    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, new EventArgs());
    }
}

Finally, the bindings in the form. I don't know how you have set the "DataContext" of your view to your view model, but assuming that is all correct and no issue, adjust the textbox and command button to something like

<TextBox Name="NameBox" Text ="{Binding Entity.Name, 
    NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}"  />

<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}"  />

Notice the text binding is to the "Entity" object on your MVVM and then the ".Name" property of your Entity object. The important thing here is the UpdateSourceTrigger. This forces an update back to your data binding for every character change, so as soon as you remove the last character, or start typing the first character, the "Save" button will then be refreshed respectively.

like image 135
DRapp Avatar answered Oct 24 '22 00:10

DRapp


I would try invoking CommandManager.InvalidateRequerySuggested.

http://msdn.microsoft.com/en-us/library/system.windows.input.commandmanager.invalidaterequerysuggested(v=vs.110).aspx

like image 34
Xcalibur37 Avatar answered Oct 23 '22 23:10

Xcalibur37