如何仅在 WPF 中使用 ICommand 填充所有三个文本框时启用按钮?

How to enable a button only when all three textboxes are filled using ICommand in WPF?

我是 MVVM 的新手。我的视图中有三个文本框和一个按钮。当所有这三个文本框都被填充时,我希望启用该按钮。我的观点如下:

<StackPanel Margin="1,1,1,1" Grid.Row="0">
                <!--<Label Margin="2,2,2,2" Content="ID:"/>
                <dxe:TextEdit Margin="2,2,2,2" Text="{Binding ElementName=StudentGrid, Path=SelectedItem.Id}"/>-->
                <Label Margin="2,2,2,2" Content="Name:"/>
                <dxe:TextEdit Margin="2,2,2,2" x:Name="Name" Text="{Binding Path=Name}" />
                <Label Margin="2,2,2,2" Content="Last Name:"/>
                <dxe:TextEdit Margin="2,2,2,2" x:Name="LastName" Text="{Binding Path=LastName}" />
                <Label Margin="2,2,2,2" Content="Age:"/>
                <dxe:TextEdit Margin="2,2,2,2" x:Name="Age" Text="{Binding Path=Age}" />
            </StackPanel>
            <ListView Name="StudentGrid" Grid.Row="1" Margin="1,1,1,1" ItemsSource="{Binding studentList}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="ID" Width="50" DisplayMemberBinding="{DXBinding Id}"/>
                        <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{DXBinding Name}"/>
                        <GridViewColumn Header="Last Name" Width="80" DisplayMemberBinding="{DXBinding LastName}"/>
                        <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{DXBinding Age}"/>
                    </GridView>
                </ListView.View>
            </ListView>
            <StackPanel Grid.Row="2" Margin="1,2,1,1">
                <dx:SimpleButton x:Name="applybtn" Content="Insert" Width="60" HorizontalAlignment="Left" Margin="5,0,0,0" Command="{Binding Path=_myCommand}"/>
            </StackPanel>

InserCommand代码为:

 public class InsertCommand : ICommand
    {
        public StudentListViewModel _viewModel { get; set; }
        public InsertCommand(StudentListViewModel viewModel)
        {
            _viewModel = viewModel;
        }
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            _viewModel.InsertStudent();
        }
    }

StudentListViewModel代码为:

public class StudentListViewModel : INotifyPropertyChanged
    {
        private string _name;
        private string _lastName;
        private int _age;
        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            } 
        }
        public string LastName
        { get => _lastName;
            set
            { 
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
        public int Age 
        { get => _age;
            set 
            {
                _age = value;
                OnPropertyChanged("Age");
            }
        }
        public InsertCommand _myCommand { get; set; }
        private ObservableCollection<Student> _studentList;

        public StudentListViewModel()
        {
            _myCommand = new InsertCommand(this);
            using (MyContext context = new MyContext())
            {
                _studentList = new ObservableCollection<Student>(context.Students.ToList());
            };
        }
        public void InsertStudent()
        {
                Student st = new Student()
                {
                    Name = _name,
                    LastName = _lastName,
                    Age = _age
                };
            using (var context = new MyContext())
            {
                context.Add(st);
                context.SaveChanges();
                _studentList.Add(st); //For Getting instatnt UI update
            }
        }
        public ObservableCollection<Student> studentList
        {
            get 
            {
                return _studentList;
            }
            set
            {
                _studentList = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

为每个命令创建一个新的 class 并不常见。

而是创建一个通用命令 class,其中包含 action / func 参数以指定每个命令的逻辑。

public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public RelayCommand(Action execute) : this(execute, () => true)
    {
    }

    public RelayCommand(Action execute, Func<bool> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute.Invoke();

    public void Execute(object parameter) => _execute.Invoke();

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

然后,在您的 ViewModel 中创建此 class 的实例,您可以将按钮绑定到该实例。

public class StudentListViewModel : INotifyPropertyChanged
{
    private string _name;
    private string _lastName;
    private int _age;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged("Name");
            InsertCommand.RaiseCanExecuteChanged();
        } 
    }

    public string LastName
    { get => _lastName;
        set
        { 
            _lastName = value;
            OnPropertyChanged("LastName");
            InsertCommand.RaiseCanExecuteChanged();
        }
    }

    public int Age 
    { get => _age;
        set 
        {
            _age = value;
            OnPropertyChanged("Age");
            InsertCommand.RaiseCanExecuteChanged();
        }
    }

    public RelayCommand InsertCommand { get; }

    public StudentListViewModel()
    {
        InsertCommand = new RelayCommand(() => InsertStudent(),
            () => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(LastName) && Age > 0);
        ...
    }

    public void InsertStudent()
    {
        ....
    }
}

为了更简洁地处理命令刷新,这样每个 属性 都不需要关心它是如何使用的,请查看我的 blog post

通常我会创建 Binding Validations 以便我可以在其他文本框上重复使用。

然后在按钮上创建触发器以根据定义的验证启用或禁用按钮。

<Button Comman="{Binding InsertCommand}" Content="Insert">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="IsEnabled" Value="False" />
            
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=Name, Path=(Validation.HasError)}" Value="False"/>
                        <Condition Binding="{Binding ElementName=LastName, Path=(Validation.HasError)}" Value="False"/>
                        <Condition Binding="{Binding ElementName=Age, Path=(Validation.HasError)}" Value="False"/>
                    </MultiDataTrigger.Conditions>

                    <Setter Property="IsEnabled" Value="True" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

对于非常简单的非空验证,我没有创建验证,而是针对文本框的文本 属性 进行测试

<Button Comman="{Binding InsertCommand}" Content="Insert">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="IsEnabled" Value="True" />
            
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=Name, Path=Text}" Value=""/>
                        <Condition Binding="{Binding ElementName=LastName, Path=Text}" Value=""/>
                        <Condition Binding="{Binding ElementName=Age, Path=Text}" Value=""/>
                    </MultiDataTrigger.Conditions>

                    <Setter Property="IsEnabled" Value="False" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

button to be enabled when all those three textboxes are filled.

创建一个名为 IsButtonEnabled 的新 bool 属性。

每当文本更改时(two-way 绑定和更新绑定中的源触发器?-> How-to bind, my answer)将当前字符串实时推送到 VM 的属性。

那些有问题的属性,它们的值将在 IsButtonEnabled getter 中检查。然后在字符串的每个设置器中,为 IsButtonEnabled 添加一个 OnPropertyChanged。同时将 IsButtonEnabled 绑定到正确的按钮 属性 以达到您想要的效果。

例子

// Doesn't need its own OnPropertyChanged, because that is set
// for every keystroke.
public bool IsButtonEnabled { get { return !string.IsNullOrEmpty(Name)     &&
                                     !string.IsNullOrEmpty(LastName) &&
                                     !string.IsNullOrEmpty(Age); }} 

public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged("Name");
            OnPropertyChanged("IsButtonEnabled");
            InsertCommand.RaiseCanExecuteChanged();
        } 
     }


public string LastName
    ...      
   set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
            OnPropertyChanged("IsButtonEnabled");
     ...

 public string Age
     ...
        set
        {
            _age= value;
            OnPropertyChanged("Age");
            OnPropertyChanged("IsButtonEnabled");
         ....

Xaml

<dx:SimpleButton x:Name="applybtn" IsEnabled="{Binding IsButtonEnabled}" ...

给出了相似的答案