基于INotifyDataErrorInfo的WPF自定义TextBox验证问题
自定义TextBox控件内部绑定Validation.Errors显示错误信息失败问题
我基于TextBox实现了自定义控件AppTextBox,原本通过重写OnTextChanged实现按键触发验证。现在ViewModel改用点击保存按钮时,通过INotifyDataErrorInfo进行验证。目前已经通过Validation.HasError实现了红色边框和标签变色,但无法在控件内部显示错误信息——只能在外部Window的ListBox中显示,内部绑定Validation.Errors的尝试全部失败。
窗口中添加控件的XAML代码
<ctrl:AppTextBox ButtonCommand="{Binding AddNewWordCommand}" ButtonLabel="+" Label="New Word" Text="{Binding NewWord, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" x:Name="txtNewWord" />
自定义控件AppTextBox的XAML代码
<Style TargetType="{x:Type ctrl:AppTextBox}" BasedOn="{StaticResource {x:Type TextBox}}"> <Setter Property="Background" Value="Transparent" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="FontFamily" Value="{DynamicResource Inter400}" /> <Setter Property="FontSize" Value="{DynamicResource Font_Medium}" /> <Setter Property="Foreground" Value="{DynamicResource Theme_Foreground}" /> <Setter Property="Margin" Value="20,8" /> <Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Validation.ErrorTemplate" Value="{x:Null}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ctrl:AppTextBox}"> <StackPanel Orientation="Vertical"> <Border Background="{TemplateBinding Background}" BorderBrush="{DynamicResource TextBox_Border_Focus}" BorderThickness="1" CornerRadius="{DynamicResource Control_CornerRadius}" Cursor="IBeam" Focusable="False" MinHeight="{DynamicResource TextBox_Height}" Padding="10,2,5,2" x:Name="bdrOutline" > <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <StackPanel Background="Transparent" Grid.Column="0" Orientation="Vertical" VerticalAlignment="Center" x:Name="stpLayout" > <Label Background="Transparent" Content="{TemplateBinding Label}" Focusable="False" FontFamily="{DynamicResource Inter600}" FontSize="12" Foreground="{DynamicResource TextBox_Label_Focus}" IsTabStop="False" VerticalAlignment="Center" Visibility="{TemplateBinding LabelVisibility}" x:Name="lblHeading" /> <ScrollViewer Background="Transparent" BorderBrush="Transparent" Cursor="IBeam" Foreground="{DynamicResource Theme_Foreground}" HorizontalAlignment="Stretch" Margin="3,0,0,0" VerticalAlignment="Center" VerticalScrollBarVisibility="Auto" Visibility="Visible" x:Name="PART_ContentHost" > <i:Interaction.Triggers> <i:KeyTrigger Key="Return"> <i:InvokeCommandAction Command="{Binding ButtonCommand}" /> </i:KeyTrigger> </i:Interaction.Triggers> </ScrollViewer> </StackPanel> <ContentControl Focusable="False" Grid.Column="1" HorizontalAlignment="Center" Margin="5,0" Style="{DynamicResource ErrorIcon_Canvas}" VerticalAlignment="Center" Visibility="Collapsed" x:Name="cvsError" /> <Button Command="{TemplateBinding ButtonCommand}" Content="+" Cursor="Hand" Grid.Column="2" Margin="5,0" Padding="10,7" Style="{DynamicResource FilledSquareButton_Base}" VerticalAlignment="Center" Visibility="Visible" x:Name="btnAction" /> </Grid> </Border> <!-- List Errors Here --> <ListBox ItemContainerStyle="{DynamicResource ErrorMessagesListBoxItem}" ItemsSource="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)/ErrorContent}" Style="{DynamicResource ErrorMessagesListBox}" /> </StackPanel> <ControlTemplate.Triggers> <Trigger Property="ButtonIsActive" Value="False"> <Setter TargetName="btnAction" Property="Visibility" Value="Collapsed" /> </Trigger> <Trigger Property="HasBorder" Value="False"> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="Transparent" /> </Trigger> <Trigger Property="Validation.HasError" Value="True"> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource Theme_ErrorBrush}" /> </Trigger> <Trigger Property="Layout" Value="Inline"> <Setter TargetName="lblHeading" Property="VerticalAlignment" Value="Top" /> <Setter TargetName="PART_ContentHost" Property="Margin" Value="40,5,0,0" /> <Setter TargetName="stpLayout" Property="Orientation" Value="Horizontal" /> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="HasBorder" Value="True" /> <Condition Property="Validation.HasError" Value="True" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource Theme_ErrorBrush}" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="TextFillState" Value="Empty" /> <Condition Property="IsKeyboardFocusWithin" Value="False" /> <Condition Property="Validation.HasError" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource TextBox_Border_Empty}" /> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource TextBox_Label_Empty}" /> <Setter TargetName="PART_ContentHost" Property="Visibility" Value="Collapsed" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="TextFillState" Value="Empty" /> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="IsKeyboardFocusWithin" Value="False" /> <Condition Property="Validation.HasError" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource TextBox_Border_Empty_Hover}" /> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource TextBox_Label_Empty_Hover}" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="TextFillState" Value="Filled" /> <Condition Property="IsMouseOver" Value="False" /> <Condition Property="IsKeyboardFocusWithin" Value="False" /> <Condition Property="Validation.HasError" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource TextBox_Border_Filled}" /> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource TextBox_Label_Filled}" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="TextFillState" Value="Filled" /> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="Validation.HasError" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource TextBox_Border_Filled_Hover}" /> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource TextBox_Label_Filled_Hover}" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsKeyboardFocusWithin" Value="True" /> <Condition Property="Validation.HasError" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="bdrOutline" Property="BorderBrush" Value="{DynamicResource TextBox_Border_Focus}" /> <Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource TextBox_Label_Focus}" /> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
更新说明
已实现部分验证功能,可通过<Condition Property="Validation.HasError" Value="True" />设置红色边框和标签颜色,但无法在控件内部显示错误信息,只能在Window中添加ListBox显示:
<ListBox ItemContainerStyle="{DynamicResource ErrorMessagesListBoxItem}" ItemsSource="{Binding ElementName=txtEntry, Path=(Validation.Errors)}" Style="{DynamicResource ErrorMessagesListBox}" />
尝试在自定义控件内添加以下代码但均失败:
<ListBox ItemContainerStyle="{DynamicResource ErrorMessagesListBoxItem}" ItemsSource="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)}" Style="{DynamicResource ErrorMessagesListBox}" />
<ListBox ItemContainerStyle="{DynamicResource ErrorMessagesListBoxItem}" ItemsSource="{Binding ElementName=PART_ContentHost, Path=(Validation.Errors)}" Style="{DynamicResource ErrorMessagesListBox}" />
解决方法
问题出在控件模板内部绑定的相对源指向错误,以及Validation.Errors的绑定方式。需要修改控件内ListBox的绑定,使其指向自定义控件本身的Validation.Errors集合:
- 修改控件内的ListBox绑定:
<ListBox x:Name="lstErrors" ItemContainerStyle="{DynamicResource ErrorMessagesListBoxItem}" ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent" Style="{DynamicResource ErrorMessagesListBox}" />
- 使用
RelativeSource TemplatedParent指向自定义控件实例(而非ListBox自身),确保获取到正确的验证错误集合。 - 通过
DisplayMemberPath="ErrorContent"直接指定显示错误内容,无需在绑定路径中嵌套/ErrorContent。
- 可选:添加触发器控制错误提示的可见性
在ControlTemplate.Triggers中添加触发器,无错误时隐藏ListBox:
<Trigger Property="Validation.HasError" Value="False"> <Setter TargetName="lstErrors" Property="Visibility" Value="Collapsed" /> </Trigger>
内容的提问来源于stack exchange,提问作者Richard Gale
相关产品推荐
相关产品推荐

