属于 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 设置为任何内容,并且在其模板中它仅绑定到自己的依赖属性。

这是一个示例情况:

该控件用于输入地址。它包含一些字段(城市、街道、邮政信箱等)。 该控件具有依赖属性,如CityPropertyStreetProperty、... 每个字段都绑定到相应的依赖属性。所以 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 中相应的TextBoxes。用户控件中的 TextBox 应处于错误状态,并将其外观更改为其错误模板。

但实际发生的是,UserControl 检测到有一个或多个错误与其依赖属性的绑定相关联。该框架将控件解释为具有“某些”错误的事物。显示整个 UserControl 周围的红色边框(默认行为)。

我想将错误显示到 UserControl 中的相应字段。但我不知道该怎么做。 如何处理绑定到 UserControl 的依赖属性的验证错误,以便可以将验证错误转发到该 UserControl 中的正确子控件。从 ViewModel 到 UserControl 中右子控件的验证错误之间的唯一关联是通过依赖属性的虚构“路线”。

【问题讨论】:

【参考方案1】:

我给你一个名为AddressControlUserControl 类,它可以在不依赖外部对象的情况下被重用。以及从外部控制到内部控制的任何变化。

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 而不是子级相关联的主要内容,如果未能解决你的问题,请参考以下文章

带有所选元素的子级的 JQuery 选择器 not()

setSizePolicy() 与 QSizePolicy.Expanding 不起作用:子级没有扩展到父级的大小

使用nodejs TypeError在firebase中获取子级的值

传递给反应组件子级的函数不接收函数上下文

如何从UserControl访问父级的DataContext

如何将焦点设置为 UserControl(使其可选择)?