属于 UserControl 子级的错误与 UserControl 而不是子级相关联
Posted
技术标签:
【中文标题】属于 UserControl 子级的错误与 UserControl 而不是子级相关联【英文标题】:Errors belonging to child of UserControl are associated with UserControl in stead of the child 【发布时间】:2021-05-21 18:37:48 【问题描述】:我创建了一个为重复使用而设计的 UserControl,它必须完全独立。它具有依赖属性,不将其 DataContext 设置为任何内容,并且在其模板中它仅绑定到自己的依赖属性。
这是一个示例情况:
该控件用于输入地址。它包含一些字段(城市、街道、邮政信箱等)。
该控件具有依赖属性,如CityProperty
、StreetProperty
、...
每个字段都绑定到相应的依赖属性。所以 street TextBox
Text
与 UserControl 的 StreetProperty
DP 绑定。
因为控件内没有明确设置数据上下文,所以它总是从其父级继承数据上下文。
当通过将我的控件嵌入到另一个控件(视图)中来使用我的控件时,我使用它的依赖属性来定义与它所在视图的 ViewModel(数据上下文)的绑定。
<StackPanel x:Name="someView" DataContext=" ... a viewmodel ... "> <!-- Some view that uses my Address control -->
<TextBlock>Enter an address</TextBlock> <!-- Just some parts of the view -->
<myControls:AddressControl
City="Binding ShippingCity"
Street="Binding ShippingStreet"
...
/>
</StackPanel>
我的控件的依赖属性绑定到控件所在视图的 ViewModel 属性。
ViewModel 实现了INotifyDataErrorInfo
。
当存在验证错误时,应将错误“附加”到 UserControl 中相应的TextBox
es。用户控件中的 TextBox 应处于错误状态,并将其外观更改为其错误模板。
但实际发生的是,UserControl 检测到有一个或多个错误与其依赖属性的绑定相关联。该框架将控件解释为具有“某些”错误的事物。显示整个 UserControl 周围的红色边框(默认行为)。
我想将错误显示到 UserControl 中的相应字段。但我不知道该怎么做。 如何处理绑定到 UserControl 的依赖属性的验证错误,以便可以将验证错误转发到该 UserControl 中的正确子控件。从 ViewModel 到 UserControl 中右子控件的验证错误之间的唯一关联是通过依赖属性的虚构“路线”。
【问题讨论】:
【参考方案1】:我给你一个名为AddressControl
的UserControl
类,它可以在不依赖外部对象的情况下被重用。以及从外部控制到内部控制的任何变化。
AddressControl.cs
public partial class AddressControl : UserControl, IDataErrorInfo
public string City
get return (string)GetValue(CityProperty);
set SetValue(CityProperty, value);
// Using a DependencyProperty as the backing store for City. This enables styling, binding, etc...
public static readonly DependencyProperty CityProperty =
DependencyProperty.Register("City", typeof(string), typeof(AddressControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnCityChanged)));
private static void OnCityChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
AddressControl ac = new AddressControl();
ac.ChangeCityText();
public void ChangeCityText()
txtCity.Text = City;
public string Street
get return (string)GetValue(StreetProperty);
set SetValue(StreetProperty, value);
// Using a DependencyProperty as the backing store for Street. This enables styling, binding, etc...
public static readonly DependencyProperty StreetProperty =
DependencyProperty.Register("Street", typeof(string), typeof(AddressControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnStreetChanged)));
private static void OnStreetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
AddressControl ac = new AddressControl();
ac.ChangeStreetText();
public void ChangeStreetText()
txtStreet.Text = Street;
public AddressControl()
InitializeComponent();
DataContext = this;
public string Error
get
return null;
public string this[string name]
get
string result = null;
if (name == "City")
if (string.IsNullOrEmpty(City) || string.IsNullOrWhiteSpace(City))
result = "please enter city";
if (name == "Street")
if (string.IsNullOrEmpty(Street) && string.IsNullOrWhiteSpace(Street))
result = "please enter street";
return result;
AddressControl.xaml
<UserControl x:Class="WpfApp1.AddressControl"
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:WpfApp1"
mc:Ignorable="d" Width="600">
<UserControl.Resources>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="FontWeight" Value="Normal"></Setter>
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="FontFamily" Value="Arial"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="x:Type TextBox">
<Border KeyboardNavigation.IsTabStop="False" x:Name="bg" BorderBrush="TemplateBinding BorderBrush" BorderThickness="TemplateBinding BorderThickness" Background="TemplateBinding Background" CornerRadius="2">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="bg" Value="0.56"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="bg" Value="#FF7EB4EA"/>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" TargetName="bg" Value="#FF7EB4EA"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="Binding RelativeSource=RelativeSource Self,Path=(Validation.Errors)[0].ErrorContent"/>
<Setter Property="BorderThickness" TargetName="bg" Value="2"/>
<Setter Property="BorderBrush" TargetName="bg" Value="Red"/>
</Trigger.Setters>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid FlowDirection="LeftToRight" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition MinWidth="460"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="City :" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="5 0 0 0"></TextBlock>
<TextBox Style="StaticResource CustomTextBoxTextStyle" TabIndex="1" Grid.Row="0" Grid.Column="1" x:Name="txtCity" Padding="10 10 10 3" BorderThickness="2" MinWidth="260" Height="38" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBox.Text>
<Binding Path="City"
ValidatesOnExceptions="True"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<TextBlock FontSize="11" FontStyle="Italic" Foreground="Red" Margin="10 10 0 0" DockPanel.Dock="Right"
Text="Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent" />
<AdornedElementPlaceholder x:Name="ErrorAdorner"
></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
<TextBlock Text="Street :" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5 0 0 0"></TextBlock>
<TextBox Style="StaticResource CustomTextBoxTextStyle" TabIndex="2" Grid.Row="1" Grid.Column="1" x:Name="txtStreet" Padding="10 10 10 3" BorderThickness="2" MinWidth="260" Height="38" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBox.Text>
<Binding Path="Street"
ValidatesOnExceptions="True"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<TextBlock FontSize="11" FontStyle="Italic" Foreground="Red" Margin="10 10 0 0" DockPanel.Dock="Right"
Text="Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent" />
<AdornedElementPlaceholder x:Name="ErrorAdorner" KeyboardNavigation.IsTabStop="False"
></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
</Grid>
</UserControl>
现在使用:您可以立即绑定或更改债务金额
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel VerticalAlignment="Center">
<local:AddressControl x:Name="addressControl" City="Paris" Street="Street Menia"></local:AddressControl>
<Button x:Name="btnTest" Content="Test" Width="100" Click="btnTest_Click" Margin="0 10 0 0"></Button>
</StackPanel>
</Window>
下面这行可以替换
<local:AddressControl x:Name="addressControl" City="Paris" Street="Street Menia"></local:AddressControl>
ٌ有了这个代码
<local:AddressControl x:Name="addressControl" City="Binding ShippingCity" Street="Binding ShippingStreet"></local:AddressControl>
主窗口中的按钮点击事件
private void btnTest_Click(object sender, RoutedEventArgs e)
string result = "";
if (Validation.GetHasError(addressControl.txtCity))
result = Validation.GetErrors(addressControl.txtCity).FirstOrDefault().ErrorContent.ToString();
MessageBox.Show(result);
【讨论】:
非常感谢您的回答。但关键是验证是(并且必须)在 ViewModel 中完成,而不是在用户控件本身的代码隐藏中。 我知道这一点和这段代码对应。您在视图模型中进行验证。 我无法完全解释,因为这是一个简单的问题,看起来很复杂。可以给viewmodel添加一些参数,传给usercontrol,如果发现有什么无效的,就引用给控件,就是超时了。 一切看程序员的口味以上是关于属于 UserControl 子级的错误与 UserControl 而不是子级相关联的主要内容,如果未能解决你的问题,请参考以下文章
setSizePolicy() 与 QSizePolicy.Expanding 不起作用:子级没有扩展到父级的大小
使用nodejs TypeError在firebase中获取子级的值