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的技能点:比如:依赖属性,附件属性,数据绑定,自定义样式,模板,转化器等,真是一个学习的好例子!


  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值