help in making a button enabled unig mvvm

Eduardo Gomez 3,416 Reputation points
2021-05-09T20:22:57.417+00:00

Hello

I am practicing MVVM, and I can get my button to activate when all my fields are filled

After all-day researching, I kind of make it work, but there are two problems

1) I can get the Button to validate my two textbox

2) I need to move the logic, to my VM, so that when I have another VM's, it will be easier

https://github.com/eduardoagr/Notes // code is in github

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,706 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
786 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. DaisyTian-1203 11,621 Reputation points
    2021-05-10T02:22:57.297+00:00

    Not knowing your CustomPasswordBox , sfTextInputLayout and Command, I used the XAML code and create MyCommand as a test:

      <Window.DataContext>  
            <local:LoginVM></local:LoginVM>  
        </Window.DataContext>  
        <StackPanel>  
            <TextBox Foreground="BlanchedAlmond" Text="{Binding user.Username, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="120" Height="30" />  
        <TextBox Text="{Binding user.Password, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="120" Height="30" />  
            <Button  
                 Margin="80,20,80,0"  
                 Height="30"  
                 Content="Sign in"  
                 Command="{Binding ClickToLogCommand}"  
                 >  
            </Button>  
        </StackPanel>  
    

    The whole c# code is:

    public class User: NotifyObject  
        {  
            [PrimaryKey, AutoIncrement]  
            public int Id { get; set; }  
            [MaxLength(50)]  
            public string Name { get; set; }  
            [MaxLength(50)]  
            public string Lastname { get; set; }  
            private string _Username;  
            public string Username  
            {  
                get { return _Username; }  
                set  
                {  
                    if (_Username != value)  
                    {  
                        _Username = value;  
                        RaisePropertyChanged("Username");  
                    }  
                }  
            }  
            private string _Password;  
            public string Password  
            {  
                get { return _Password; }  
                set  
                {  
                    if (_Password != value)  
                    {  
                        _Password = value;  
                        RaisePropertyChanged("Password");  
                    }  
                }  
            }  
        }  
      
        public class NotifyObject : INotifyPropertyChanged  
        {  
            public event PropertyChangedEventHandler PropertyChanged;  
      
            protected void RaisePropertyChanged(string propertyName)  
            {  
                if (PropertyChanged != null)  
                {  
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));  
                }  
            }  
      
        }  
    public class LoginVM : NotifyObject  
        {  
            private User _user;  
            public User user  
            {  
                get { return _user; }  
                set  
                {  
                    if (_user != value)  
                    {  
                        _user = value;  
                        RaisePropertyChanged("user");  
                         
                    }  
                }  
            }  
      
            private MyCommand _ClickToLogCommand;  
      
            public MyCommand ClickToLogCommand  
            {  
                get  
                {  
                    if (_ClickToLogCommand == null)  
                        _ClickToLogCommand = new MyCommand(new Action<object>  
                        (  
                            o =>  
                            {  
                                Debug.WriteLine($" user name = {user.Username} password = {user.Password}");  
                            }  
                        ),  
                        new Func<object, bool>(o =>(!string.IsNullOrEmpty(user.Username)&&!string.IsNullOrEmpty(user.Password))));  
                    return _ClickToLogCommand;  
                }  
            }  
      
      
      
            public LoginVM()  
            {  
                user = new User();  
            }  
        }  
      
      
        public class MyCommand : ICommand  
        {  
            public event EventHandler CanExecuteChanged  
            {  
                add  
                {  
                    if (_canExecute != null)  
                    {  
                        CommandManager.RequerySuggested += value;  
                    }  
                }  
                remove  
                {  
                    if (_canExecute != null)  
                    {  
                        CommandManager.RequerySuggested -= value;  
                    }  
                }  
            }  
            private Func<object, bool> _canExecute;  
      
            private Action<object> _execute;  
           
            public MyCommand(Action<object> execute) : this(execute, null)  
            {  
            }  
      
          
            public MyCommand(Action<object> execute, Func<object, bool> canExecute)  
            {  
                _execute = execute;  
                _canExecute = canExecute;  
            }  
      
            public MyCommand(Action<object> execute, bool keepTargetAlive = false)  
            {  
                _execute = execute;  
            }  
      
           
            public bool CanExecute(object parameter)  
            {  
                if (_canExecute == null) return true;  
                return _canExecute(parameter);  
            }  
      
           
            public void Execute(object parameter)  
            {  
                if (_execute != null && CanExecute(parameter))  
                {  
                    _execute(parameter);  
                }  
            }  
      
        }  
    

    The result picture is:
    95408-3.gif


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Peter Fleischer (former MVP) 19,311 Reputation points
    2021-05-10T06:48:54.313+00:00

    Hi,
    try this demo:

    namespace WpfApp053
    {
      public class LoginVM : MVVM_Base
      {
        public User user { get; set; }
    
        public ICommand ClickToLogCommand { get=> new Command((state) =>
        {
          Debug.WriteLine($" user name = {user.Username} password = {user.Password}");
        },
          (state) => (!string.IsNullOrEmpty(user.Username) && !string.IsNullOrEmpty(user.Password)));
        }
    
        public LoginVM()
        {
          user = new User();
          user.PropertyChanged += (sender, e) => RaisePropertyChanged(nameof(ClickToLogCommand));
        }
        public bool IsButtonEnabled { get; set; } = true;
      }
    
      public class User : MVVM_Base
      {
        private string _Username;
        public string Username
        {
          get { return _Username; }
          set
          {
            if (_Username != value)
            {
              _Username = value;
              RaisePropertyChanged();
            }
          }
        }
        private string _Password;
        public string Password
        {
          get { return _Password; }
          set
          {
            if (_Password != value)
            {
              _Password = value;
              RaisePropertyChanged();
            }
          }
        }
      }
    
      public class MVVM_Base : INotifyPropertyChanged
      {
        public MVVM_Base() => sc = SynchronizationContext.Current;
        SynchronizationContext sc;
        public event PropertyChangedEventHandler PropertyChanged;
        internal void RaisePropertyChanged([CallerMemberName] string propName = "") =>
          sc.Post(new SendOrPostCallback((p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName))), null);
      }
    
      public class Command : ICommand
      {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;
        public Command(Action<object> execute) : this(execute, canExecute: null) { }
        public Command(Action<object> execute, Predicate<object> canExecute)
        {
          if (execute == null) throw new ArgumentNullException("execute");
          this._execute = execute;
          this._canExecute = canExecute;
        }
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter) => this._canExecute == null ? true : this._canExecute(parameter);
        public void Execute(object parameter) => this._execute(parameter);
        public void RaiseCanExecuteChanged() => this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
      }
    }
    

    And XAML:

     <StackPanel>
         <Image Source="/Icons/writing.png"
                Width="100"
                Height="100"
                VerticalAlignment="Top"
                HorizontalAlignment="Center" />
         <inputLayout:SfTextInputLayout x:Name="sfTextInputLayout"
                                        Hint="Username"
                                        Margin="20,20,20,0">
             <TextBox Foreground="BlanchedAlmond"
                      Text="{Binding user.Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
         </inputLayout:SfTextInputLayout>
         <controls:CustomPasswordBox Password="{Binding user.Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
         <Button Style="{StaticResource MyBtnStyle}"
                 Margin="80,20,80,0"
                 Height="30"
                 Content="Sign in"
                 Command="{Binding ClickToLogCommand}"
                 IsEnabled="{Binding IsButtonEnabled, Mode=TwoWay}">
         </Button>
     </StackPanel>
    

  3. Emon Haque 3,176 Reputation points
    2021-05-12T17:32:48.793+00:00

    Enabling/Disabling Button isn't that complicated BUT validation of notifiable object/property is. Out of the box, you've IDataErrorInfo and INotifyDataErrorInfo in WPF for validation and adorners for visualization BUT you've to work with indexers, adorners, validation rules, etc. The best way, to me, is create your own custom controls and keep rooms for displaying validation errors in that custom control. Instead of cluttering your models with validation rules make organized mess, create validation properties, in ViewModels. Previously, I've experimented on those with xaml BUT this time I've gone a bit crazy, doing everything without xaml and, in my opinion, this route is the best if you can visualize your view in your mind. Here's an example of a model, Plot:

    public class Plot : Notifiable  
    {  
        public int? Id { get; set; }  
        string name;  
        public string Name {  
            get { return name; }  
            set {  
                if (name != value) {  
                    name = value;  
                    OnPropertyChanged(nameof(Name));  
                }  
             }  
        }  
        string description;  
        public string Description {  
            get { return description; }  
            set {   
                if (description != value) {  
                    description = value;   
                    OnPropertyChanged(nameof(Description));  
                }  
            }  
        }  
    }  
    

    in the ViewModel TObject is the Plot model and I've validation rules for its name and description:

    public class AddPlotVM : AddBase<Plot>  
    {  
        public string ErrorName { get; set; }  
        public string ErrorDescription { get; set; }  
        public bool IsValid { get; set; }  
        public AddPlotVM() : base() {  
            TObject.Id = MainVM.GetId(MainVM.plots);  
            initializeValidationProperties();  
            TObject.PropertyChanged += validate;  
        }  
        #region validation rules  
        void initializeValidationProperties() {  
            IsValid = false;  
            ErrorName = nameof(Plot.Name) + Constants.IsRequired;  
            ErrorDescription = nameof(Plot.Description) + Constants.IsRequired;  
        }  
        void validate(object sender, PropertyChangedEventArgs e) {  
            switch (e.PropertyName) {  
                case nameof(Plot.Name): validateName(); break;  
                case nameof(Plot.Description): validateDescription(); break;  
            }  
            IsValid = ErrorName == string.Empty && ErrorDescription == string.Empty;  
            OnPropertyChanged(nameof(IsValid));  
        }  
        void validateName() {  
            ErrorName = string.Empty;  
            if (string.IsNullOrWhiteSpace(TObject.Name)) {  
                ErrorName = "Name is required";  
            }  
            else {  
                for (int i = 0; i < MainVM.plots.Count; i++) {  
                    if (string.Equals(MainVM.plots[i].Name, TObject.Name.Trim(), StringComparison.OrdinalIgnoreCase)) {  
                        ErrorName = "Name exits";  
                        break;  
                    }  
                }  
            }  
            OnPropertyChanged(nameof(ErrorName));  
        }  
        void validateDescription() {  
            ErrorDescription = string.Empty;  
            if (string.IsNullOrWhiteSpace(TObject.Description)) {  
                ErrorDescription = "Description is required";  
            }  
            OnPropertyChanged(nameof(ErrorDescription));  
        }  
        #endregion  
        #region base implementation  
    }  
    

    In the View, I've two EditText, a custom control that contains a TextBox for input and a TextBlock to display error, and a CommandButton, another custom control, and are put into a Grid:

    class AddPlot : CardView  
    {  
        public override string Header => "Plot";  
        EditText name, description;  
        CommandButton button;  
        AddPlotVM viewModel;  
        public AddPlot() : base() {  
            viewModel = new AddPlotVM();  
            DataContext = viewModel;  
            initializeUI();  
            bind();  
        }  
        void initializeUI() {  
            name = new EditText() {   
                Hint = "Name",   
                IsRequired = true,   
                Icon = Icons.Plot   
            };  
            description = new EditText() {   
                Hint = "Description",   
                IsMultiline = true,   
                IsRequired = true,   
                MaxLength = 100,   
                Icon = Icons.Description   
            };  
            button = new CommandButton() {  
                Width = 24,  
                Height = 24,  
                Icon = Icons.Add,   
                Command = viewModel.Add,   
                HorizontalAlignment = HorizontalAlignment.Right   
            };  
            Grid.SetRow(description, 1);  
            Grid.SetRow(button, 2);  
            var grid = new Grid() {  
                RowDefinitions = {  
                    new RowDefinition() { Height = GridLength.Auto },  
                    new RowDefinition(),  
                    new RowDefinition() { Height = GridLength.Auto },  
                },  
                Children = { name, description, button }  
            };  
            setContent(grid);  
        }  
        void bind() {  
            name.SetBinding(EditText.TextProperty, new Binding($"{nameof(viewModel.TObject)}.{nameof(Plot.Name)}"));  
            name.SetBinding(EditText.ErrorProperty, new Binding(nameof(viewModel.ErrorName)));  
            description.SetBinding(EditText.TextProperty, new Binding($"{nameof(viewModel.TObject)}.{nameof(Plot.Description)}"));  
            description.SetBinding(EditText.ErrorProperty, new Binding(nameof(viewModel.ErrorDescription)));  
            button.SetBinding(IsEnabledProperty, new Binding(nameof(viewModel.IsValid)));  
        }  
    }  
    

    and validation properties have been bound to those custom controls in bind function. Now have a look at this enabling/disabling and validation rules in action:

    96081-test.gif

    The top left Plot section is AddPlot View. The right most Lease section is another view, a bit more complicated. I click here and there and stare at this program, I'm recreating, at least for an hour everyday because of those animations! I wanted to defer rewriting it until the official and final release of WinUI 3 BUT I couldn't because of the excitement of learning new things!

    0 comments No comments