C# WPF MVVM(Caliburn Micro)实现Apply按钮启用校验的问题
基于Caliburn Micro的WPF MVVM应用Apply按钮校验逻辑实现问题
我正基于Caliburn Micro约定构建WPF MVVM应用,已完成以下功能:
- 定义包含
FirstName、LastName、Age三个属性的PersonModel类 - 在
ShellViewModel中使用BindableCollection存储3个PersonModel实例 - 通过XAML的
ItemsControl以每行三个文本框的形式展示所有Person数据 - 新增、删除Person的功能已正常运行
当前需求是为"Apply"按钮实现启用校验逻辑(CanApply),要求仅当所有Person的所有文本框均填写完整时,按钮才启用。目前遇到的问题:修改文本框内容时,变更仅能通知到对应的PersonModel属性,但无法传递到定义了BindableCollection和CanApply()方法的ShellViewModel,导致按钮状态无法实时更新。
现有代码
PersonModel
public class PersonModel { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } }
ShellViewModel
public class ShellViewModel { public BindableCollection<PersonModel> People { get; set; } public ShellViewModel() { DataAccess da = new DataAccess(); People = new BindableCollection<PersonModel>() { new PersonModel { FirstName = "John", LastName= "Smith", Age = 30}, new PersonModel { FirstName = "Bob", LastName= "Marry", Age = 21}, new PersonModel { FirstName = "", LastName= "Milito", Age = 16} }; } public void AddPerson() { DataAccess dataAccess = new DataAccess(); if (People.Count > 0) { People.Add(new PersonModel()); } } public void RemovePerson() { DataAccess dataAccess = new DataAccess(); if (People.Count == 0) { return; } PersonModel randomPerson = dataAccess.GetRandomItem(People.ToArray()); People.Remove(randomPerson); } public bool CanApply() { bool output = false; foreach (var item in People) { output = false; if (!string.IsNullOrEmpty(item.FirstName)) { output = true; } } return output; } public void Apply() { } }
XAML
<Window x:Class="WPFDemoUI.Views.ShellView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFDemoUI.Views" mc:Ignorable="d" FontSize="20" WindowStartupLocation="CenterScreen" Title="ShellView" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Row="0"> <Button x:Name="AddPerson">Add New Person</Button> <Button x:Name="RemovePerson" Margin="10 0 0 0">Remove Random Person</Button> <Button x:Name="Apply" Margin="10 0 0 0">Apply</Button> </StackPanel> <ItemsControl x:Name="People" Grid.Row="1"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapPanel /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Border BorderBrush="Black" BorderThickness="1" Margin="10" Padding="10"> <StackPanel Orientation="Horizontal"> <TextBox Text="{Binding FirstName, Mode=TwoWay}" MinWidth="50" /> <TextBox Text="{Binding LastName, Mode=TwoWay}" MinWidth="50" /> <TextBox Text="{Binding Age, Mode=TwoWay}" MinWidth="50"/> </StackPanel> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </Window>
解决方案
要实现需求,需要完成三个核心步骤:让PersonModel支持属性变更通知、在ShellViewModel中监听Person的属性变化、修正CanApply的校验逻辑。
1. 修改PersonModel,实现INotifyPropertyChanged接口
只有实现该接口,属性变更时才能触发通知,让绑定系统感知到变化。
using System.ComponentModel; using System.Runtime.CompilerServices; public class PersonModel : INotifyPropertyChanged { private string _firstName; private string _lastName; private int _age; public string FirstName { get => _firstName; set { if (_firstName != value) { _firstName = value; OnPropertyChanged(); } } } public string LastName { get => _lastName; set { if (_lastName != value) { _lastName = value; OnPropertyChanged(); } } } public int Age { get => _age; set { if (_age != value) { _age = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 修改ShellViewModel,监听Person属性变化并通知校验状态
让ShellViewModel继承Caliburn Micro的Screen(内置INotifyPropertyChanged实现),然后订阅每个Person的PropertyChanged事件,当属性变化时通知CanApply状态更新。
using Caliburn.Micro; using System.ComponentModel; public class ShellViewModel : Screen { public BindableCollection<PersonModel> People { get; set; } public ShellViewModel() { People = new BindableCollection<PersonModel>() { new PersonModel { FirstName = "John", LastName= "Smith", Age = 30}, new PersonModel { FirstName = "Bob", LastName= "Marry", Age = 21}, new PersonModel { FirstName = "", LastName= "Milito", Age = 16} }; // 为已有Person订阅属性变更事件 foreach (var person in People) { person.PropertyChanged += Person_PropertyChanged; } // 监听集合变更,新增/移除Person时自动订阅/取消订阅事件 People.CollectionChanged += People_CollectionChanged; } private void People_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (PersonModel newPerson in e.NewItems) { newPerson.PropertyChanged += Person_PropertyChanged; } } if (e.OldItems != null) { foreach (PersonModel oldPerson in e.OldItems) { oldPerson.PropertyChanged -= Person_PropertyChanged; } } } private void Person_PropertyChanged(object sender, PropertyChangedEventArgs e) { // 通知Caliburn重新计算CanApply状态,自动更新按钮启用状态 NotifyOfPropertyChange(() => CanApply); } public void AddPerson() { People.Add(new PersonModel()); } public void RemovePerson() { if (People.Count == 0) return; DataAccess dataAccess = new DataAccess(); PersonModel randomPerson = dataAccess.GetRandomItem(People.ToArray()); People.Remove(randomPerson); } public bool CanApply() { // 校验所有Person的必填字段:FirstName/LastName不为空,Age大于0 foreach (var person in People) { if (string.IsNullOrEmpty(person.FirstName) || string.IsNullOrEmpty(person.LastName) || person.Age <= 0) { return false; } } // 没有Person时也禁用按钮 return People.Count > 0; } public void Apply() { // 这里添加Apply的业务逻辑 } }
3. 优化XAML的Age绑定(可选但推荐)
由于Age是int类型,空输入会导致绑定报错,添加TargetNullValue和UpdateSourceTrigger优化用户体验:
<TextBox Text="{Binding Age, Mode=TwoWay, TargetNullValue='', UpdateSourceTrigger=PropertyChanged}" MinWidth="50"/>
UpdateSourceTrigger=PropertyChanged:输入时立即更新绑定,实时触发校验TargetNullValue='':处理空输入时的绑定兼容问题
原理说明
PersonModel实现INotifyPropertyChanged后,属性变更时会触发PropertyChanged事件ShellViewModel订阅每个Person的该事件,当任何属性变化时,通过NotifyOfPropertyChange(() => CanApply)通知Caliburn Micro重新计算CanApply()方法的返回值- Caliburn Micro会自动根据
CanApply()的结果更新Apply按钮的IsEnabled状态
内容的提问来源于stack exchange,提问作者Needy.Coder
相关产品推荐
相关产品推荐

