【问题标题】:Errors belonging to child of UserControl are associated with UserControl in stead of the child属于 UserControl 子级的错误与 UserControl 而不是子级相关联
【发布时间】: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 中右子控件的验证错误之间的唯一关联是通过依赖属性的虚构“路线”。

【问题讨论】:

    标签: c# wpf xaml data-binding user-controls


    【解决方案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,如果发现有什么无效的,就引用给控件,就是超时了。
    • 一切看程序员的口味
    猜你喜欢
    • 2011-06-25
    • 2012-02-24
    • 2014-01-07
    • 2011-07-03
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 2012-09-02
    • 2015-11-29
    相关资源
    最近更新 更多