如何仅在 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}" ...
给出了相似的答案
我是 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}" ...
给出了相似的答案