简体   繁体   中英

Relaycommand ICommand.CanExecute not firing

i have the following problem:

I have a relaycommand with a execute an a canexecute method, but everytime i call raisecanexecutechanged(); it calls raisecanexecutechanged in relaycommand, sets a new delegate for it and then returns back to the view model.

The same setup works in another viewmodel. I checked like 1000 times what's different but i don't find anything.

I would really appreciate if you could help me.

    public RelayCommand UpdateAMSCommand { get; private set; }

    public AMSSettingsViewModel(IEventAggregator eventAggregator)
    {
        UpdateAMSCommand = new RelayCommand(OnUpdateAMS, CanUpdateAms);
        CustomAMSOffices.ListChanged += listChanged;
        CustomAMSContacts.ListChanged += listChanged;
    }

    private void listChanged(object sender, ListChangedEventArgs e)
    {
        if (sender != null)
        {
            if (sender is BindingList<CustomAMSOffice>)
            {
                BindingList<CustomAMSOffice> temp =  (BindingList<CustomAMSOffice>)sender;

                if (temp.Count > _amsOfficesItemsCounter)
                {
                    _amsOfficesItemsCounter = temp.Count;

                    for (int i = 0; i < temp.Count; i++)
                    {
                        temp[i].ErrorsChanged += RaiseCanExecuteChanged;
                    }
                }   
            }
            else if (sender is BindingList<CustomAMSContact>)
            {
                BindingList<CustomAMSContact> temp = (BindingList<CustomAMSContact>)sender;

                if (temp.Count > _amsContactsItemsCounter)
                {
                    _amsContactsItemsCounter = temp.Count;

                    for (int i = 0; i < temp.Count; i++)
                    {
                        temp[i].ErrorsChanged += RaiseCanExecuteChanged;
                    }
                }
            }
        }

        UpdateAMSCommand.RaiseCanExecuteChanged();
    }

    private void RaiseCanExecuteChanged(object sender, DataErrorsChangedEventArgs e)
    {
        UpdateAMSCommand.RaiseCanExecuteChanged();
    }

    private bool CanUpdateAms()
    {
        foreach (var cao in CustomAMSOffices)
        {
            if (!cao.Check() || cao.HasErrors)
            {
                return false;
            }
        }

        foreach (var cac in CustomAMSContacts)
        {
            if (!cac.Check() || cac.HasErrors)
            {
                return false;
            }
        }
        return true;
    }

Edit: the relaycommand i use: https://github.com/briannoyes/WPFMVVM-StarterCode/blob/master/ZzaDashboard/ZzaDashboard/RelayCommand.cs

Ok, I'm just going to copy paste some code that I have in use, so that you should be able to pop these into your project and use.

First off, the RelayCommand() class. I lifted this code from this msdn page :

public class RelayCommand : ICommand
{
    #region Fields
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;
    #endregion

    #region Constructors
    public RelayCommand(Action<object> execute) : this(execute, null) { }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion

    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

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

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

Now our ModelView.cs class needs to inherit from INotifyPropertyChanged and will have our RaisePropertyChanged() . Now I usually make just make this a it's own file and have all my ModelViews inherit from it so the code is a little cleaner, but you can do as you please.

Here's how I have it setup though:

BaseViewModel.cs:

public class BaseViewModel : INotifyPropertyChanged
{
    internal void RaisePropertyChanged(string prop)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }
    public event PropertyChangedEventHandler PropertyChanged;

    // Any other code we want all model views to have
}

Now for our MainViewModel.cs we will just inherit from BaseViewModel, add our event handlers in, and run it!

Example: ServerViewModel.cs

public class ServerViewModel : BaseViewModel
{
    public RelayCommand BroadcastMessageCommand { get; set; }

    private string _broadcastmessage;
    public string broadcastmessage
    {
        get { return _broadcastmessage; }
        set { _broadcastmessage = value; RaisePropertyChanged("broadcastmessage"); }
    }

    Server server;

    public ServerViewModel()
    {
        server = new Server();
        server.run();
        BroadcastMessageCommand = new RelayCommand(BroadcastMessage, CanBroadcast);
    }

    private bool CanBroadcast(object param)
    {
        if (string.IsNullOrWhiteSpace(broadcastmessage))
            return false;
        if (!server.running)
            return false;
        return true;
    }

    public void BroadcastMessage(object param)
    {
        server.BroadcastMessage(broadcastmessage);
        broadcastmessage = "";
        RaisePropertyChanged("broadcastmessage");
    }
}

Now anything in our MainView.xaml that is bound with Command="{Binding broadcastmessage}" will update appropriately. In my case I have this bound to a button and the button will be disabled if there message is empty, or if we are not connected to the server.

Hopefully that's enough code example to get you headed in the right direction! Let me know if you have any questions on it.

Let's try simplifying the code as much as we can until we get this working properly, and then we will slowly add code back until we find the code(s) that are causing trouble.

So let's reduce this to it's barebones and see if we have any success. Try this code:

public RelayCommand UpdateAMSCommand { get; private set; }

public AMSSettingsViewModel(IEventAggregator eventAggregator)
{
    UpdateAMSCommand = new RelayCommand(OnUpdateAMS, CanUpdateAms);
    CustomAMSOffices.ListChanged += listChanged;
    CustomAMSContacts.ListChanged += listChanged;
}

private void listChanged(object sender, ListChangedEventArgs e)
{
    UpdateAMSCommand.RaiseCanExecuteChanged();
}

private void RaiseCanExecuteChanged(object sender, DataErrorsChangedEventArgs e)
{
    UpdateAMSCommand.RaiseCanExecuteChanged();
}

// This will simply flip from true to false every time it is called.
private bool _canupdate = false;
private bool CanUpdateAms()
{
    _canupdate = !_canupdate;
    return _canupdate;
}

Edit: I don't know why it doesn't work.

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