1

My question is how to trigger a ViewModel method from the model.

I am developing a WPF application using MVVM. So I have a button, SubmitMedPrescCommand, (implemented using Relay Command) and a Combobox (SelectedMedPrescRepeat) that is binded to a model. When the user selects the dropdown the PropertyChange event is raised in the property of the model but I need to call the CanExecute (in theViewModel) in order to enable the button.

A sample of my code is listed below. Any help would be appreciated ! Thanks in advance !

The viewmodel is this:

public class EpCreateMedicineViewModel : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    public ICommand SubmitMedPrescCommand { get; set; }
    
    public EpCreateMedicineViewModel()
    {
        SubmitMedPrescCommand = new RelayCommand<MedicinePrescriptionForSubmission>(ExecuteSubmitMedPrescCommand, CanExecuteSubmitMedPrescCommand);
    }
    
    private MedicinePrescriptionForSubmission _medicinePrescForSubm;
    public MedicinePrescriptionForSubmission MedicinePrescForSubm
    {
        get { return _medicinePrescForSubm; }
        set
        {
            if (value != this._medicinePrescForSubm)
            {
                this._medicinePrescForSubm = value;
                OnPropertyRaised("MedicinePrescForSubm");
            }
        }
    }
        
    public bool CanExecuteSubmitMedPrescCommand(object parameter)
    {
        if (_medicinePrescForSubm.MedicineForSubmGeneralInfo.SelectedMedPrescRepeat!=null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

and the Model where the property belongs:

public class MedicinePrescriptionForSubmission
{
    public MedicineForSubmGeneralInfo MedicineForSubmGeneralInfo { get; set; }
    
    public class MedicineForSubmGeneralInfo : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        
        private MedicinePrescriptionRepeat _selectedMedPrescRepeat; // THE PROPERTY THAT THE COMBOBOX IS BINDED TO
        public MedicinePrescriptionRepeat SelectedMedPrescRepeat
        {
            get { return _selectedMedPrescRepeat; }
            set
            {
                _selectedMedPrescRepeat = value;
                OnPropertyRaised("SelectedMedPrescRepeat");
                //CanExecuteSubmitMedPrescCommand(_selectedMedPrescRepeat); // THE METHOD OF THE VIEWMODEL THAT I WANT TO BE TRIGERRED WHEN MedicinePrescriptionRepeat changes
            }
        }
        private void OnPropertyRaised(string propertyname)
        {
            PropertyChangedEventHandler handle = PropertyChanged;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }
    }
}
nitse
  • 21
  • 7
  • If you implement INotifyPropertyChanged properly and make your CanExecute method depend directly on that, this should work automatically. That is how the whole concept is supposed to work. Otherwise you could(still can) just make a dedicated property IsThisButtonEnabled, make it raise the PropertyChanged event and bind your Buttons IsEnabled to that. – themightylc Nov 24 '20 at 12:55
  • Thank you for your response @themightylc ! I appreciate it ! My thought was to implement INotifyPropertyChanged to every variable that is involved meaning all the classes that are involved: MedicinePrescForSubm -> MedicineForSubmGeneralInfo ->SelectedMedPrescRepeat Is that way of thinking correct? – nitse Nov 24 '20 at 17:07
  • If I understand you correctly yes, that is the right way to go. "INotifyPropertyChanged" hooks your properties to the Dependency-Framework (without the need to use DependencyProperties and -Objects). Then you let the Framework take care of the rest by utilizing it's features like e.g. Command-Binding and the CanExecute methods. Think about it like this: If you would really have to implement what you ask for yourself for every Button - what would you need a framework for? :) – themightylc Nov 25 '20 at 06:28
  • Thanks again for your time ! Although I implemented INotifiPropertyChanged to all the classes involved it didn't work. When the combobox changes the INotifyProperty is trigerred but the class that it belongs is not considered changed in order to trigger the father class. **Actually, my question is:** if the property that you want to check, in order to enable a button, belongs to a different class than the button (and its CanExecute method) how can you handle this scenario? – nitse Nov 25 '20 at 19:32

1 Answers1

0

EDIT to clarify: the only thing that makes the following example work as intended is raising the PropertyChanged event on the model property and using the Framework's CommandBinding. All of the remaining code is absoluteley just to "have something to show" and separate Model from ViewModel logic.

Please consider this demonstration I put together for you (using MVVM Light Libs so I don't have to implement INotifyPropertyChanged myself).

CanExecute example on Github

Upon starting the Program you should see a ListView on the left with two entries. Click one to select one and "Edit" it on the right side.

Demo Application

Note the following behavior:

  • Any other value than "male" "female" "diverse" and "unknown" entered in the Gender Box will be denied by the ViewModel
  • The column "Gender" in the ListView will not update because the ViewModel does not raise the PropertyChanged Event
  • Most importantly: enter "unkown" in the Gender Box and the Button will become disabled immediately and vice-versa

Disabled Button

All i did was implement a "stupid" Model (no business logic) and a "silly" ViewModel (that translates int values to strings and vice-versa). Then I implemented the Command that is bound to the Button like this:

    public ICommand DisplayGenderValueCommand { get { return new RelayCommand(this.DisplayGenderValueExecute, this.DisplayGenderValueCanExecute); } }

    private void DisplayGenderValueExecute()
    {
        MessageBox.Show($"Gender {this.Gender} has a model value of {this._MyModel.Gender}");
    }

    private bool DisplayGenderValueCanExecute()
    {
        return this._MyModel.Gender > 0;
    }

The property in the ViewModel does nothing, but update the model:

    public string Gender
    {
        get
        {
            if (_MyModel.Gender == 0) { return "unknown"; }
            if (_MyModel.Gender == 1) { return "female"; }
            if (_MyModel.Gender == 2) { return "male"; }
            if (_MyModel.Gender == 3) { return "diverse"; }
            return "error";
        }
        set
        {
            if (value == "unknown") _MyModel.Gender = 0;
            if (value == "female") _MyModel.Gender = 1;
            if (value == "male") _MyModel.Gender = 2;
            if (value == "diverse") _MyModel.Gender = 3;
        }
    }

While only the model raises the PropertyChanged Event (this is in line with your scenario that the dependent property "belongs to a different class").

The Framework takes care of the updates needed to re-check the dependencies. It's called "Magic"

There are ways to "force" a requery like here but if you feel the need to do that, then IMHO there is something wrong with your design.

Harvest the power of the Framework - even if it means a slower startup to your project because you have to learn and reas things. This is the easy way - believe me.

themightylc
  • 304
  • 2
  • 15
  • It's not really Magic. It's years of optimized code, written by talented people, that does exactly what you want in your original question, as long as you use it correctly. And learning how to use it correctly will yield *enormous* long-time benefits compared to "hacking" things yourself. – themightylc Nov 26 '20 at 06:33
  • First of all I want to thank you for the time you spent to prepare this example ! I appreciate it !! The problem was that the INotifyPropertyChanged was not sufficient for my scenario. I needed a framework. I tried your sample and everything ran smoothly. I ended up using Prism. Specifically: DelegateCommands, ObservesProperty (which support complex object ) & Command.RaiseCanExecuteChanged() and I finally made it work ! Your guidance was really helpful. – nitse Dec 17 '20 at 17:04
  • that's good to hear! Being able to find and utilize an existing framework that suits your needs (whether it be native to .NET or an external library) is such an important "skill" to have for .NET development - so much better than reinventing the wheel. Good for you! – themightylc Jan 04 '21 at 04:49