WPF 按钮绑定TextBox的 Validation.HasError
程序员文章站
2022-05-29 19:36:25
...
先看下实现界面效果:
实现的效果主要有:
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的技能点:比如:依赖属性,附件属性,数据绑定,自定义样式,模板,转化器等,真是一个学习的好例子!
上一篇: PyQt 按键控制文本框的输出