欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

WPF 按钮绑定TextBox的 Validation.HasError

程序员文章站 2022-05-29 19:36:25
...

先看下实现界面效果:

WPF 按钮绑定TextBox的 Validation.HasError

实现的效果主要有:

1:输入用户姓名验证不通过(/^[\u4e00-\u9fa5\w]{3,16}$/),文本框边框显示红色,鼠标悬浮有ToolTip提示错误信息;

2:当输入验证不通过时,保存按钮不可使用;

这里实现步骤如下:

1:定义带清除按钮的文本框;

<ControlTemplate x:Key="ErrorTemplate">
   <DockPanel LastChildFill="true">
                <Border Background="Red" DockPanel.Dock="right"   CornerRadius="4" Margin="5,0,0,0" Width="20" Height="20"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                    <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white"/>
                    <Border.Effect>
                        <DropShadowEffect Color="#FF9300" BlurRadius="6" ShadowDepth="0" Opacity="0.6" />
                    </Border.Effect>
                </Border>
                <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                    <Border BorderBrush="red" CornerRadius="4" BorderThickness="2"></Border>
                </AdornedElementPlaceholder>
    </DockPanel>
</ControlTemplate>

<Style x:Key="editTextBox" TargetType="ctrl:ImageTextBox">
            <Setter Property="Validation.ErrorTemplate" Value="{StaticResource ErrorTemplate}"></Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ctrl:ImageTextBox">
                        <Border x:Name="border" CornerRadius="4" Margin="{TemplateBinding Margin}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
                            <Grid>
                                <Grid.ColumnDefinitions>                                    
                                    <ColumnDefinition/>
                                    <ColumnDefinition Width="32"/>
                                </Grid.ColumnDefinitions>
                                <Button Width="{TemplateBinding ImageWidth}"  Height="{TemplateBinding ImageHeight}"
                                        Style="{StaticResource clearTextBtnStyle}" ctrl:TextBoxHelper.Clearable="True" Grid.Column="2" Focusable="False" Cursor="Hand"  />
                                <!--<Image   Margin="{TemplateBinding ImageMargin}" Source="{TemplateBinding ImageSource}"/>-->
                                <ScrollViewer Grid.Column="0" Margin="5,0,8,0" VerticalContentAlignment="Center"  VerticalAlignment="Center" x:Name="PART_ContentHost" Focusable="True" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"></ScrollViewer>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsFocused" Value="True">
                                <Setter TargetName="border" Property="BorderBrush" Value="#FF9300"/>
                                <Setter TargetName="border" Property="Effect">
                                    <Setter.Value>
                                        <DropShadowEffect Color="#FF9300" BlurRadius="3" ShadowDepth="0" Opacity="0.6" />
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>

其中ImageTextBox类定义为:

public class ImageTextBox : TextBox
    {

        public ImageSource ImageSource
        {
            get { return (ImageSource)GetValue(ImageSourceProperty); }
            set { SetValue(ImageSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImageSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImageSourceProperty =
            DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ImageTextBox), new PropertyMetadata(null));


        public double ImageWidth
        {
            get { return (double)GetValue(ImageWidthProperty); }
            set { SetValue(ImageWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImageWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImageWidthProperty =
            DependencyProperty.Register("ImageWidth", typeof(double), typeof(ImageTextBox), new PropertyMetadata(20d));

        public double ImageHeight
        {
            get { return (double)GetValue(ImageHeightProperty); }
            set { SetValue(ImageHeightProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImageHeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImageHeightProperty =
            DependencyProperty.Register("ImageHeight", typeof(double), typeof(ImageTextBox), new PropertyMetadata(20d));


        public Thickness ImageMargin
        {
            get { return (Thickness)GetValue(ImageMarginProperty); }
            set { SetValue(ImageMarginProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImageMargin.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImageMarginProperty =
            DependencyProperty.Register("ImageMargin", typeof(Thickness), typeof(ImageTextBox), new PropertyMetadata(new Thickness()));


    }

    public class TextBoxHelper
    {
        #region 附加属性 Clearable
        public static readonly DependencyProperty ClearableProperty =
            DependencyProperty.RegisterAttached("Clearable", typeof(bool), typeof(TextBoxHelper), new PropertyMetadata(false, ClearText));


        public static bool GetClearable(DependencyObject obj)
        {
            return (bool)obj.GetValue(ClearableProperty);
        }

        public static void SetClearable(DependencyObject obj, bool value)
        {
            obj.SetValue(ClearableProperty, value);
        }

        #endregion

        #region 
      
        private static void ClearText(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Button btn = d as Button;
            if (d != null && e.OldValue != e.NewValue)
            {
                btn.Click -= ClearTextClicked;
                if ((bool)e.NewValue)
                {
                    btn.Click += ClearTextClicked;
                }
            }
        }
      
        public static void ClearTextClicked(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            if (btn != null)
            {
                var parent = VisualTreeHelper.GetParent(btn);
                while (!(parent is TextBox))
                {
                    parent = VisualTreeHelper.GetParent(parent);
                }
                TextBox txt = parent as TextBox;
                if (txt != null)
                {
                    txt.Clear();
                }
            }
        }

        #endregion
    }

    public class UserNameValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            if (string.IsNullOrEmpty(value.ToString()))
                return new ValidationResult(false, "userNameRequired".ToGlobal());
            string rule = @"^[\u4e00-\u9fa5\w]{2,16}$";
            bool result= Validator.IsMatch(value.ToString(), rule);
            if (!result)
            {
                return new ValidationResult(false, "userNameValidation".ToGlobal());
            }
            return new ValidationResult(true, null);
        }
    }

2:  XAML中关键的布局为:

一个文本框,一个编辑文本框,处于同一个位置,用显示和隐藏来控制,用了一个ToggleButton来实现,文本框的显示和隐藏同ToggleButton的IsChecked属于相绑定;

<ctrl:ImageTextBox Style="{StaticResource editTextBox}" x:Name="txtUserName"
                               ctrl:FocusExtension.IsFocused="{Binding Visibility, RelativeSource= {RelativeSource Mode=Self}, Converter={StaticResource visibleToBool}}"
                               Height="35px" Width="290px" 
                               Visibility="{Binding IsChecked,  ElementName=btnEditName, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource boolToVisible}, ConverterParameter=false}"
                               Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right"
                               BorderBrush="#dddddd" ImageWidth="20" MaxLength="16"
                               FontSize="16">
                <ctrl:ImageTextBox.Text>
                    <Binding Path="UserName" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True" NotifyOnValidationError="True">
                        <Binding.ValidationRules>
                            <ctrl:UserNameValidationRule/>
                        </Binding.ValidationRules>
                    </Binding>
                </ctrl:ImageTextBox.Text>
            </ctrl:ImageTextBox>

<ToggleButton x:Name="btnEditName" IsChecked="False" 
                          IsEnabled="{Binding ElementName=txtUserName, Path=(Validation.HasError), Converter={StaticResource reserveBoolCvt}}"
                          Command="{Binding EditUserNameCommand}" CommandParameter="{Binding IsChecked,RelativeSource={RelativeSource Mode=Self}}" Grid.Row="0" Grid.Column="2" Style="{StaticResource editToggleButtonStyle}" HorizontalAlignment="Right" Margin="10,0,0,0"/>

这里用了一个WPF中的附加属性,ctrl:FocusExtension.IsFocused 来控制文本输入框显示时自动获取光标:

public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsFocusedProperty);
        }


        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }


        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
             "IsFocused", typeof(bool), typeof(FocusExtension),
             new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));


        private static void OnIsFocusedPropertyChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {

            var uie = (UIElement)d;
         
            if ((bool)e.NewValue)
            {
                bool result = uie.Focus();
                if (!result)
                {
                    if (d is FrameworkElement)
                    {
                        var fe = d as FrameworkElement;
                        if (!fe.IsLoaded)
                        {
                            RoutedEventHandler loadedHandler = null;
                            loadedHandler = (sender, args) =>
                            {
                                (sender as FrameworkElement).Focus();
                                (sender as FrameworkElement).Loaded -= loadedHandler;
                            };
                            fe.Loaded += loadedHandler;
                        }
                    }
                }
            }
        }
    }

当文本框输入错误时Validation.HasError=true, 此时绑定到ToggleButton的IsEnabled 属性,

IsEnabled="{Binding ElementName=txtUserName, Path=(Validation.HasError),

重点是红色的部分,一定要用括号括起来,否则没有效果,在这里折腾了一会,没有找到原因,后来发现是这里的问题,特别注意!

  <Style x:Key="editToggleButtonStyle" TargetType="ToggleButton" BasedOn="{StaticResource NoFocusableStyle}">
            <Setter Property="Cursor" Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToggleButton">
                        <Grid>
                            <Image Name="img" Source="../../Resources/Images/LoginView/icon_modify.png" HorizontalAlignment="Center" Stretch="Uniform" Width="17"  VerticalAlignment="Center"/>
                            <Rectangle Height="18" Width="17" x:Name="rect" Visibility="Collapsed" Fill="Gray" Opacity="0.5"></Rectangle>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="img" Property="Source" Value="../../Resources/Images/LoginView/icon_preservation.png"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="rect" Property="Visibility" Value="Visible"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

总结下这个功能用到了蛮多WPF的技能点:比如:依赖属性,附件属性,数据绑定,自定义样式,模板,转化器等,真是一个学习的好例子!