none
MVVM, ICommand and validation

    Question

  • Hi Guys,

    Need an idea..

    There is a grid with some TextBoxes binded to ModelView. If all validation is sucessfull I need submit button to be enabled.

    something like this:

    <Grid>
    <TextBox Text={Binding Name, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}/>
    </Grid>
    <Button Command={Binding ContinueCommand}/>
    


    ViewModel class:

    class ViewModel 
    {
       public string Name{get;set;}
       public ICommand ContinueCommand {get;set;}
    
    public ViewModel(){
       CountinueCommand = new DelegateCommand(nextAction, canGoNext);
    } 
    
    bool canGoNext(object o){
        return !string.IsNullOrEmpty(Name)
    }
    
    void NextAction(object o)
    {
    }


     

    I want the button to be enabled only if field name is not empty. (Well, it is simplified version - in real project I check a lot of fields and a lot of business logic - so I can not use just DataForm).

    Problem is that canGoNext method is invoked only when xaml page is constructed. How to force calling this function - so Button will update its state?

    Thursday, August 19, 2010 10:12 AM

Answers

  • If you're still having trouble getting it to work correctly using ValidationSummary and HasErrors (I did), you might try this approach. Whenever you detect in the ViewModel that one of the input fields changed, call CanExecute and do the validation within CanExecute (my preferences is to pass the 'buffer' object containing the fields as the parameter). If the valid state changes, CanExecute raises the CanExecuteChanged event. ButtonBase subscribes to CanExecuteChanged by virtue of the button binding to the ICommand, so will catch the event and call CanExecute to get the current value and set its enabled state accordingly. If you pass the edit buffer as the parameter you also need to make it the CommandParameter in the button binding. That has the advantage that there will be one last validation just before the operation is carried out, because when you click on the button, ButtonBase will call CanExecute before it calls Execute.

    The main disadvantage is that you have to do the validation separately from the validation you're currently doing in your setters, but I find it very difficult to sync up that validation, which supports the error message binding to each field with having the button enabled at the right time. You either have to force all the setter validations, which means you have error messages before the user even enters anything, or the button enabled state is not correct until the user has tabbed through all the fields.

    At least that's my experience. I'm open to correction.

    I'm doing a CodeProject post where I had to sort out how CanExecute worked which I'm posting today, hopefully. I'll let you know when it's up.

    Monday, August 23, 2010 11:01 AM

All replies

  • I see that Button has binding

    Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedItem, ElementName=dataGrid}"


    But I can not get how I can check if all TextBoxes are validated sucesfully in my grid and triger button CanExecute?

     

    I will give more detailed example..

    <Grid>
    <TextBox Grid Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" x:Name="AccountHolderName"/>
    
    
    <TextBox Grid Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" x:Name="AccountHolderName"/>
    
    <Button Content="Next"
    Command="{Binding ContinueCommand}"/>
    
    </Grid>


     

    public class AddResidentialCustomerViewModel : ModelBase
        {
    
            private bool _Validated;
            public bool Validated
            {
                get { return _Validated; }
                set { _Validated = value; RaisePropertyChangeEvent("Validated"); }
            }
    
            private ICommand _ContinueCommand;
            public ICommand ContinueCommand { get { return _ContinueCommand; } }
            public ObservableCollection<Relationship> Relationships { get; set; }
            private Relationship _SelectedRelationship;
            public Relationship SelectedRelationship
            {
                get { return _SelectedRelationship; }
                set { _SelectedRelationship = value; RaisePropertyChangeEvent("SelectedRelationship"); }
            }
    
            private string _Name;
            [Required(ErrorMessage = "First Name is required.")]
            [RegularBLOCKED EXPRESSION]
            public string Name
            {
                get { return _Name; }
                set
                {
                    Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Name" });
                    _Name = value;
                }
            }
    
            private string _LastName;
            [Required(ErrorMessage = "Last Name is required.")]
            [RegularBLOCKED EXPRESSION]
            public string LastName
            {
                get { return _LastName; }
                set
                {
                    Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "LastName" });
                    _LastName = value;
                }
            }
    
      bool canGoNext(object o)
            {
                return Validated;
            }
            void NextAction(object o)
            {
            }


    Button must be enabled only if all fields validated.

    Thursday, August 19, 2010 12:30 PM
  • Update.

     

    I added 

    <sdk:ValidationSummary x:Name="validationSummary" />

    and bind button to this controll

    <Button Command="{Binding ContinueCommand}" CommandParameter="{Binding HasErrors, ElementName=validationSummary}" />

     

    Looks like it makes trick - Everytime ValidationSummary changes - Button's command CanExecute is called...

    And here I check like this :

    bool canGoNext(object o)
            {
                if (o != null)
                {
                    return !((bool)o);
                }
                return false;
            }

     

    Is it right way? or there is more elegant way?

    Pls advise

    Thursday, August 19, 2010 12:46 PM
  • Is it right way? or there is more elegant way?
     

    I can think of no better way. Perhaps others may.

    I just went through this with a developer friend of mine and you may have found an answer to his problem :)

    Thursday, August 19, 2010 12:54 PM
  • I think the reason the call to CanExecute started happening after you added ValidationSummary is that ValidationSummary changes the value of HasErrors, which causes ButtonBase.OnCommandPropertyChanged to call ButtonBase.UpdateCanExecute(). I'm not clear on whether HasErrors will be false until the user has been through all the input fields.

    I'm going to try your approach instead of implementing IDataErrorInfo because it looks like you can use the same validation that binds errors to your textboxes to control the enabled state of your button, so thanks for the example.

     

     

    Saturday, August 21, 2010 4:48 PM
  • Please declare a property of type bool in your view model , lets call that as bool EnableButton =false;

    And Bind this property to the button’s enable property in Mode=Twoway

    And set EnableButton= true only while all your conditions got satisfies

    NB: remember that each time you add a row or add some value to the textbox the property change “Set” will fire ; 

    Sunday, August 22, 2010 10:18 AM
  • Please declare a property of type bool in your view model , lets call that as bool EnableButton =false;

    And set EnableButton= true only while all your conditions got satisfies

    NB: remember that each time you add a row or add some value to the textbox the property change “Set” will fire ; 

     

     

    Well, I tried this approach - but how to call method that handles EnableButton and checks fields. If I go this way I need to put CheckFields method in each settler of the model...I can't say it is impossible , I would call it not ellegant -).

    Also, why u say 

    And Bind this property to the button’s enable property in Mode=Twoway
    . Why TwoWay in this case? Model will control value of EnableButton, so OneWay should be ok..

    Monday, August 23, 2010 9:10 AM
  • If you're still having trouble getting it to work correctly using ValidationSummary and HasErrors (I did), you might try this approach. Whenever you detect in the ViewModel that one of the input fields changed, call CanExecute and do the validation within CanExecute (my preferences is to pass the 'buffer' object containing the fields as the parameter). If the valid state changes, CanExecute raises the CanExecuteChanged event. ButtonBase subscribes to CanExecuteChanged by virtue of the button binding to the ICommand, so will catch the event and call CanExecute to get the current value and set its enabled state accordingly. If you pass the edit buffer as the parameter you also need to make it the CommandParameter in the button binding. That has the advantage that there will be one last validation just before the operation is carried out, because when you click on the button, ButtonBase will call CanExecute before it calls Execute.

    The main disadvantage is that you have to do the validation separately from the validation you're currently doing in your setters, but I find it very difficult to sync up that validation, which supports the error message binding to each field with having the button enabled at the right time. You either have to force all the setter validations, which means you have error messages before the user even enters anything, or the button enabled state is not correct until the user has tabbed through all the fields.

    At least that's my experience. I'm open to correction.

    I'm doing a CodeProject post where I had to sort out how CanExecute worked which I'm posting today, hopefully. I'll let you know when it's up.

    Monday, August 23, 2010 11:01 AM
  • Mode=Twoway is not needed ,

     

    Monday, August 30, 2010 1:52 PM
  • Are you talking abt your earlier message:....

    ...

    And Bind this property to the button’s enable property in Mode=Twoway

    .....?

    Yeh, Twoway doesn't make any sense here...

    Monday, August 30, 2010 2:41 PM
  • In case someone wants to go the INotifyDataErrorInfo way, then have a look into the ValidatingObservableObject; it's an initial implementation of INotifyDataErrorInfo and IDataErrorInfo:

    http://goodiesforria.svn.sourceforge.net/viewvc/goodiesforria/trunk/Casimodo/Casimodo.Lib.Silverlight/ComponentModel/

    Whenever any errors change on the observed object, one updates the command's state:

    using Casimodo.Lib.ComponentModel;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;

    namespace MyStuff
    {
        public class MyBlueThingy : ValidatingObservableObject
        {
            static MyBlueThingy()
            {
                ValidationRules.Add(typeof(MyBlueThingy));
            }

            public MyBlueThingy()
            {
                DoSomethingCommand = CommandFactory.Create(
                    () => { },
                    () => !HasErrors);

                ErrorsChanged += (s, e) => DoSomethingCommand.RaiseCanExecuteChanged();            
            }

            public ICommandEx DoSomethingCommand { get; private set; }

            [Range(1, 100)]
            [Required]
            public int? Number
            {
                get { return _number; }
                set { SetProperty("Number", ref _number, value); }
            }
            int? _number;        
        }   
    }

    This obviously only handles the validity of the observed object itself. If I recall correctly, then this is sometimes not sufficient, because the UI might use a TextBox for binding the integer "Number" member and the user can type "hello" into this TextBox; this will obviously not change the value of "Number", thus the observed object might be still valid, while the value in the UI definitely doesn't look valid.

    Regards,

    Kasimier Buchcik


    Wednesday, September 01, 2010 10:16 AM