Disable button if validation in model has error

Feb 5, 2011 at 4:45 PM

Hi I make validation on error in my model class.

 

    public class CurrentUser:IDataErrorInfo, INotifyPropertyChanged
    {
//...

        private string _validationResult;
        private string _nick;

        public string Nick
        {
            get { return _nick; }
            set
            {
                _nick = value;
                NotifyPropertyChanged("Nick");
            }
        }

        public string ValidationResult
        {
            get { return _validationResult; }
            private set
            {
                _validationResult = value;
                NotifyPropertyChanged("ValidationResult");
            }
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion

        #region Implementation of IDataErrorInfo

        private string NickValid()
        {
            if (string.IsNullOrEmpty(Nick))
            {
                return NickNull;
            }
            if (Regex.IsMatch(Nick, "[^a-zA-Z0-9-_.]"))
            {
                return NickInvalidCharacters;
            }
            return string.Empty;
        }

        public string Error
        {
            get { throw new NotImplementedException(); }
        }

        public string this[string propertyName]
        {
            
            get
            {
                ValidationResult = string.Empty;

                switch (propertyName)
                {
                    case "Nick":
                        ValidationResult = NickValid();
                        break;
                    default:
                        break;
                }
                return ValidationResult;

            }
        }

        #endregion

    }

 

This model class I use in view model and I bind Nick property of model class to the Text property of comboBox control.

Also I bind method LogOn from view model class on button click event in view. I would like disabale button if validation in model class has error:

View model:

 

    [Export(typeof(ILogOnViewModel))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class LogOnViewModel : Screen, ILogOnViewModel,
        IPartImportsSatisfiedNotification
    {

        public CurrentUser CurrentUser { get; set; }

        public bool CanLogOn
        {
            get
            {
                return string.IsNullOrWhiteSpace(CurrentUser.ValidationResult);
            }
        }

         //bind on button click event
        public void LogOn()
        {}
     }

Solution is simple set CanLogOn property on false if  validation in CurrentUser (object) property has error.

But I don’t how notify property CanLogOn that in model class is not error. I run app and button is still disabled.

I need achive this behavior in model:

        public string ValidationResult
        {
            get { return _validationResult; }
            private set
            {
                _validationResult = value;
                NotifyPropertyChanged("ValidationResult");
                //notify property CanLogOn in view model class
            }
        }

Any advice? Thank.

 

 

Apr 14, 2011 at 11:54 AM

That's exactly my current problem. And I have it twice: I have a dialog, where the edited model has a validation. The absence of any error should enable the "Save" button as above.

Second: I have a listview with an ObservableCollection<SingleRowViewModel> where the SingleRowViewModel holds is selection state in a property. But when this property changes, how can I tell it to the list view, so that "Edit" and "Remove" may could be enabled?

Apr 14, 2011 at 3:01 PM

How about subscribing to CurrentUser.PropertyChanged event from the VM?
You can hook it in the LogOnViewModel.CurrentUser property setter: when ValidationResult changes on (data)model, you can call

NotifyPropertyChanged("CanLogOn");

in the VM.

Does it make sense?

Apr 14, 2011 at 3:05 PM

The same goes for the property holding the current selection: whenever the value is set, you can notify for the guard properties (CanXXX). 

You can also have a look at http://code.google.com/p/notifypropertyweaver/ which greatly simplifies the handling of INPC, expecially with calculated properties.

Apr 14, 2011 at 3:20 PM

(CodeSnippets does not work)

In theory yes, it would make sense. On the other hand, when I try it out with the listview, then I have

AllUnitTypes = new ObservableCollection<UnitTypeRowViewModel>(_dbConversation.Query(new AllUnitTypesQuery()).Select(x=>new UnitTypeRowViewModel(x));

 

...
AllUnitTypes
 = new ObservableCollection<UnitTypeRowViewModel>(_dbConversation .Query(new AllUnitTypesQuery()) .Select(x=>new UnitTypeRowViewModel(x)));
foreach (var unitType in AllUnitTypes)
  unitType.PropertyChanged += OnUnitTypeRowPropertyChanged;
...
void OnUnitTypeRowPropertyChanged(object sender, PropertyChangedEventArgs e)
{
  if (e.PropertyName != "IsSelected")
    return;
  NotifyOfPropertyChange(()=>CanEdit());
  NotifyOfPropertyChange(()=>CanRemove());
}

But this string comparision smells a bit, doesn't it?

 

Mmm... above code does not run, with NotifyOfPropertyChange("CanEdit") it runs, but CanEdit itself is not called, so the button remains disabled.

 

Apr 14, 2011 at 3:32 PM

Additionally added calls to

RaisePropertyChangedEventImmediately(e.PropertyName);
this.RaisePropertyChangedEventImmediately("Edit");
this.RaisePropertyChangedEventImmediately("CanEdit");
this.Refresh();

 without success. Maybe I should use ICommand Binding instead?

Apr 14, 2011 at 5:42 PM
Edited Apr 14, 2011 at 5:43 PM

If you want to signal their change with INPC, CanEdit and CanRemove shouldn't be methods (like I guess they are) but properties.

The string check could be improved using the same technique used in CM for NotifyOfPropertyChange: you can have a look at it in PropertyChangedBase code.
The helper methods used by CM itself to get the property name from a lambda expression should be publicly available to user code as well.

Apr 14, 2011 at 9:22 PM

Yeah, ok. I did not realize that CanXXX were functions instead of properties. Strange, in the edit dialog they were.

 Anyway: great, now edit and remove buttons are enabled the right way (but still got this "IsSelected" string. Maybe I can remove it.)

 

One problem is left: my EditModel is strictly based on IDataErrorInfo, because I don't need to have INotifyPropertyChanged to get data validation work.

I assume I have to change this to get the save button enabling work..

 

Apr 14, 2011 at 10:59 PM

Yes, generally speaking INPC is the preferred mechanism whenever you have to communicate to external actors that the state of an object is changed.
For this very reason, it is required by WPF/SL in order to use bidirectional bindings against UI elements, so I think you have to implement it anyway.