2

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

Revooo96
  • 31
  • 6

2 Answers2

0

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 INotifyPropertyChangedand 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.

Xander Luciano
  • 3,753
  • 7
  • 32
  • 53
  • Really thanks for your answer, but as written in my question, i have the EXACT same setup of code in another viewmodel and that works, i just dont know why this is not working, theres nothing wrong with the relaycommand class or sth – Revooo96 Feb 25 '17 at 08:20
  • @Revooo96 this may seem like a basic step, but did you check to make sure you bound the `datacontext` on the XAML with your modelview? – Xander Luciano Feb 25 '17 at 08:22
  • Yes its bound correctly, it's displaying the data from my lists correctly, only not raising the can execute from my relay command, i dont know why – Revooo96 Feb 25 '17 at 08:29
  • Where is your `OnUpdateAMS()` method? – Xander Luciano Feb 25 '17 at 08:33
  • I didnt put it in the code cause it can't be the source of the problem, cause the method is never called, that's my problem – Revooo96 Feb 25 '17 at 08:36
  • Ok few things I want you to do. First, can you share your code for your relay command? You might need to change your validation method to be `private bool CanUpdateAms(object param)` if you have the same RelayCommand as I do, also can you very that it is returning true and not false by adding `System.Diagnositcs.Debug.WriteLine(CanUpdateAms());` in your `RaiseCanExecuteChanged` method and confirm it's returning true. Thanks – Xander Luciano Feb 25 '17 at 08:43
  • I put a link of the relay command in my question cause i'm currently not at home, i will check for the other things at home. Thanks for your time. – Revooo96 Feb 25 '17 at 08:59
  • Ok, I'm going to add another answer with some suggestions for you to try when you get home. I'm going to leave this one up because I occasionally just post questions and answer them myself as a way to document the process I used in case I need to repeat it in the future and forgot how I did it. – Xander Luciano Feb 25 '17 at 09:06
  • Where did you grab this line from? `temp[i].ErrorsChanged += RaiseCanExecuteChanged;`? I'm curious as to why you are re-subscribing to the `ErrorsChanged` event every time the the list is changed. Kinda hard to get the big picture and nothing is quite sticking out to me just yet. – Xander Luciano Feb 25 '17 at 09:19
  • I'm just subscribing to each element of the list, so if someone adds an item in the data grid, it will subscribe again, each element only subscribes one time, because i have the counter of my list so i know if something has been added or not – Revooo96 Feb 25 '17 at 09:23
  • Everything in my vm is running on the same thread, only my data access is done asyncronously – Revooo96 Feb 25 '17 at 09:31
  • Are any of the async data access function using `configureawait(false)` or are "fire and forget" methods? If yes, do any of these functions fire off `listChanged()`? – Xander Luciano Feb 25 '17 at 09:32
  • In my repository for data access configureawait(false) is set, no they dont fire off list changed – Revooo96 Feb 25 '17 at 11:06
0

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.

Xander Luciano
  • 3,753
  • 7
  • 32
  • 53
  • _"I think these 2 lines are incorrect"_ -- the version you say is incorrect is _exactly_ the same as the version you say is incorrect. They will both compile to the same IL. What are you talking about? – Peter Duniho Feb 25 '17 at 17:48
  • *"the version you say is incorrect is exactly the same as the version you say is incorrect."* - Huh? They both run and compile fine for me. I don't know how he has every setup though so I was throwing ideas out. – Xander Luciano Feb 25 '17 at 17:56
  • My point is that the idea you are "throwing out" here makes no sense. You are suggesting that the OP change working code to some other way of expressing the exact same code, as if that will make a difference. Of _course "they both run and compile fine for [you]"_. They are exactly the same. Why are you claiming that they are not? I.e. one you have labelled _"potentially wrong"_, while the other you have labelled _"probably correct"_. That's not possible. Either they are both _"potentially wrong"_ or they are both _"probably correct"_, because they are exactly the same. – Peter Duniho Feb 25 '17 at 18:01
  • Cool, I removed the suggestion. Didn't realize trying to help out another dev by throwing out ideas was such a big deal. Sorry I don't examine the IL output of my SO suggestions. I provided sample code, a suggestion for debugging, and quick edit on the only code discrepancy that I could find. Thanks for letting me know it doesn't make a difference, but you could lose the attitude. We're all on here to learn and help others out so you don't need to criticize me for making a mistake. – Xander Luciano Feb 25 '17 at 18:13
  • Please show me where I've criticized you. All my comments are still above. Feel free to quote the part where you think I'm showing some "attitude" that I need to "lose". Every statement was factual. Every question was genuine. You are quick to criticize me, without any evidence to support your anger. You may find value in not carrying that chip around on your shoulder. It's making you see offense where none exists. – Peter Duniho Feb 25 '17 at 18:18
  • 1
    "What are you talking about?", "the idea you are *'throwing out'* here makes no sense.", "..*as if* that will make a difference.", "They are exactly the same. Why are you claiming that they are not?", "That's not possible." IMO, You came off as a 'know-it-all' kind of guy mocking me for not knowing that the 2 statements were functionally the same. I'm not ashamed to be wrong, I'm not an expert and don't claim to be. I even said "potentially" because I wasn't sure about it. I'm not making any claims about *knowing* what is wrong, I am only proposing ideas that may help narrow down the issue. – Xander Luciano Feb 25 '17 at 18:34
  • _"What are you talking about?"_ -- just a question. _'the idea you are 'throwing out' here makes no sense"_ -- it's a logical contradiction. Do you think that logical contradictions actually _do_ make sense? (The "throwing out" is quoting _your_ words). Likewise all the others; nothing offensive, just factual statements and questions. If you perceived me as a "know-it-all", that's your own ego talking, not mine. Sorry your inadequacies have left you feeling so trod upon, but there's nothing I wrote that you have any reason to interpret that way. – Peter Duniho Feb 25 '17 at 18:59
  • @XanderLuciano I wouldn't worry too much about comments from this person. I have also been on the receiving end of his way of interacting with people, and I've set out to review some of his past comments, only to find out that he probably doesn't even realize how he comes across. I'm sure he doesn't do it intentionally. – Luc Morin Mar 20 '17 at 23:36
  • @LucMorin Thank you. After the fact I had that same thought and I have one friend who is similar. He can be rude to people yet won't understand why until I explain it to him. Also if he is a non native English speaker then he may not be familiar with the *connotation* of what he said. Either way, the comments were not constructive to the original question so I figured it best to just let it go and carry on with my own programming struggles haha :) Thank you though! – Xander Luciano Mar 20 '17 at 23:44