MVVM实现ViewModel获取View输入验证状态
由于Binding只把Convert成功的值送往Source,当目标中的值Convert失败时Source的值依然是旧值,所以ViewModel必须获取View的输入验证状态,以下是本人的实现。
当“+”号两边输入正确时,“Add”可用,当所有“+”号两边输入正确时,“Add All”可用。
通过Behavior添加Validation.ErrorEvent路由事件的事件处理器,在该事件处理器中把HasError状态写入自定义的附加属性,附加属性可以绑定。
Behavior派生类引用System.Windows.Interactivity.dll,代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; namespace Calculater { public class NotifyErrorBehavior : Behavior<UIElement> { protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(HasErrorChanged)); } protected override void OnDetaching() { base.OnDetaching(); this.AssociatedObject.RemoveHandler(Validation.ErrorEvent, new RoutedEventHandler(HasErrorChanged)); } private static void HasErrorChanged(object sender, RoutedEventArgs e) { DependencyObject d = e.OriginalSource as DependencyObject; HasErrorHelper.SetHasError(d, Validation.GetHasError(d)); } } }
附加属性代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace Calculater { public class HasErrorHelper { public static bool GetHasError(DependencyObject obj) { return (bool)obj.GetValue(HasErrorProperty); } public static void SetHasError(DependencyObject obj, bool value) { obj.SetValue(HasErrorProperty, value); } public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached("HasError", typeof(bool), typeof(HasErrorHelper), new PropertyMetadata(false)); } }
View代码:
<UserControl x:Class="Calculater.ChildCalculater" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Calculater" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d" d:DesignHeight="80" d:DesignWidth="500"> <i:Interaction.Behaviors> <local:NotifyErrorBehavior></local:NotifyErrorBehavior> </i:Interaction.Behaviors> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBox Margin="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" local:HasErrorHelper.HasError="{Binding Path=HasErrorX,Mode=OneWayToSource}"> <Binding Path="X" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" TargetNullValue="" /> </TextBox> <Label Margin="3" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Column="1">+</Label> <TextBox Margin="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="2" local:HasErrorHelper.HasError="{Binding Path=HasErrorY,Mode=OneWayToSource}"> <Binding Path="Y" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" TargetNullValue="" /> </TextBox> <Label Margin="3" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Grid.Column="3">=</Label> <TextBox Margin="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="4" IsReadOnly="True"> <Binding Path="Sum" TargetNullValue="" /> </TextBox> <Button Margin="3" Grid.Column="5" Padding="3" Command="{Binding CalculateCommand}">Add</Button> <Button Margin="3" Grid.Column="6" Padding="3" Command="{Binding ResetCommand}">Reset</Button> </Grid> </UserControl>
ViewModel引用Microsoft.Practices.Prism.dll,代码如下:
using Microsoft.Practices.Prism.Commands; using Microsoft.Practices.Prism.ViewModel; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Calculater { class ChildCalculaterViewModel : NotificationObject { public ChildCalculaterViewModel() { this._calculateCommand = new DelegateCommand(this.Calculate, this.CanCalculate); this._resetCommand = new DelegateCommand(this.Reset); CalculaterCommand.CalculateAllCommand.RegisterCommand(this.CalculateCommand); CalculaterCommand.ResetAllCommand.RegisterCommand(this.ResetCommand); } private double? _x; public double? X { get { return _x; } set { if (_x != value) { _x = value; this.RaisePropertyChanged("X"); this.Sum = null; this.CalculateCommand.RaiseCanExecuteChanged(); } } } private double? _y; public double? Y { get { return _y; } set { if (_y != value) { _y = value; this.RaisePropertyChanged("Y"); this.Sum = null; this.CalculateCommand.RaiseCanExecuteChanged(); } } } private double? _sum; public double? Sum { get { return _sum; } set { if (_sum != value) { _sum = value; this.RaisePropertyChanged("Sum"); } } } private bool _hasErrorX; public bool HasErrorX { get { return _hasErrorX; } set { if (_hasErrorX != value) { _hasErrorX = value; this.Sum = null; this.CalculateCommand.RaiseCanExecuteChanged(); } } } private bool _hasErrorY; public bool HasErrorY { get { return _hasErrorY; } set { if (_hasErrorY != value) { _hasErrorY = value; this.Sum = null; this.CalculateCommand.RaiseCanExecuteChanged(); } } } private DelegateCommand _calculateCommand; public DelegateCommand CalculateCommand { get { return _calculateCommand; } } private bool CanCalculate() { if (this.HasErrorX || this.HasErrorY || !this.X.HasValue || !this.Y.HasValue) { return false; } return true; } private void Calculate() { try { double x = this.X.Value; double y = this.Y.Value; this.Sum = x + y; } catch { return; } } private DelegateCommand _resetCommand; public DelegateCommand ResetCommand { get { return _resetCommand; } } private void Reset() { this.X = null; this.Y = null; this.Sum = null; } } }
主窗体代码:
<Window x:Class="Calculater.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Calculater" xmlns:prism="http://www.codeplex.com/prism" Title="Calculater" Height="300" Width="600"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition /> </Grid.RowDefinitions> <local:ChildCalculater></local:ChildCalculater> <local:ChildCalculater Grid.Row="1"></local:ChildCalculater> <local:ChildCalculater Grid.Row="2"></local:ChildCalculater> <Grid Grid.Row="3"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Margin="3" HorizontalAlignment="Right" Padding="3" Content="Add All" Command="{x:Static local:CalculaterCommand.CalculateAllCommand}"></Button> <Button Margin="3" HorizontalAlignment="Left" Padding="3" Grid.Column="1" Content="Reset All" Command="{x:Static local:CalculaterCommand.ResetAllCommand}"></Button> </Grid> </Grid> </Window>
using Microsoft.Practices.Prism.Commands; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Calculater { public static class CalculaterCommand { private static CompositeCommand _calculateAllCommand = new CompositeCommand(); public static CompositeCommand CalculateAllCommand { get { return _calculateAllCommand; } } private static CompositeCommand _resetAllCommand = new CompositeCommand(); public static CompositeCommand ResetAllCommand { get { return _resetAllCommand; } } } }