简体   繁体   English

在嵌套的用户控件上绑定嵌套的验证规则

[英]Binding nested validation rules on nested User Controls

This is my first question here on SO...I have been a ready for a long time and never needed to ask for help because I usually find what I need, but I am having a hard time with this one... 这是我在这里的第一个问题...我已经准备好很长时间了,不需要寻求帮助,因为我通常会找到所需的东西,但是与此同时我却很难过...

I am working on a tools suite in WPF. 我正在使用WPF中的工具套件。 I created a few User Controls as follow: 我创建了一些用户控件,如下所示:

  1. LabelTextBox (Label on the left and TextBox on the right) LabelTextBox (标签在左侧, TextBox在右侧)
  2. LabelTextBoxToggle ( LabelTextBox on the left and Checkbox on the right) LabelTextBoxToggle (左侧为LabelTextBox ,右侧为Checkbox
  3. LabelTextBoxBrowseFile ( LabelTextBox on the left and Browse File Button on the right) LabelTextBoxBrowseFile (左侧为LabelTextBox ,右侧为“浏览文件” 按钮

I use Dependency Properties to bind all the properties I need and they all work fine. 我使用依赖项属性来绑定我需要的所有属性,并且它们都可以正常工作。 The problem I ran into recently is getting ValidationRules to work correctly on the base TextBox I use in LabelTextBox when those rules are applied to the LabelTextBoxToggle and LabelTextBoxBrowseFile UserControls, since I have to bind 2 levels down in order to update controls in LabelTextBox . 我最近遇到的问题是,当将这些规则应用于LabelTextBoxToggleLabelTextBoxBrowseFile UserControls时,我要在ValidationRules上正确使用在LabelTextBox上使用的基本TextBox ,因为我必须向下绑定2个级别才能更新LabelTextBox中的控件。 I can get the Validation Rule to run, but I can't get the TextBox control to update its background color accordingly when errors are found, like I do when LabelTextBox isn't nested within another User Control. 我可以运行验证规则,但是当发现错误时,我无法让TextBox控件相应地更新其背景颜色,就像当LabelTextBox没有嵌套在另一个用户控件中时一样。

So, here's my code below: 因此,这是下面的代码:

Style used for TextBox: 用于TextBox的样式:

<!-- TextBox Default Style, Supports Validation Rules -->
<Style TargetType="{x:Type TextBox}">
    <Setter Property="Background" Value="{StaticResource TextBoxBGDefault}" />
    <Style.Triggers>
        <Trigger Property="IsKeyboardFocused" Value="True">
            <Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
        </Trigger>
        <Trigger Property="IsMouseOver" Value="true">
            <Setter Property="Background" Value="{StaticResource TextBoxBGHasFocus}" />
        </Trigger>
        <DataTrigger Binding="{Binding Path=(Validation.HasError)}" Value="true">
            <Setter Property="Background" Value="{StaticResource TextBoxBGHasError}" />
            <Setter Property="BorderBrush" Value="Firebrick" />
            <Setter Property="BorderThickness" Value="1.5" />
            <Setter Property="ToolTipService.InitialShowDelay" Value="2" />
            <Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors)[0].ErrorContent}" />
        </DataTrigger>

    </Style.Triggers>
</Style>

LabelTextBox.xaml: LabelTextBox.xaml:

<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=ControlRoot, Mode=OneWay, ValidatesOnDataErrors=True}">
    <Grid.RowDefinitions>
        <RowDefinition Height="24" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Label
        x:Name="NameLabel"
        Width="{Binding Path=LabelWidth, Converter={StaticResource WidthToAutoConverter}}"
        Margin="0"
        HorizontalAlignment="{Binding Path=HorizontalContentAlignment}"
        HorizontalContentAlignment="{Binding Path=LabelHAlign, Converter={StaticResource valueToStringConverter}}"
        VerticalContentAlignment="Center"
        Content="{Binding Path=LabelContent}"
        Padding="10,2,5,2" />
    <TextBox
        x:Name="ValueTextBox"
        Grid.Column="1"
        KeyDown="TextBox_KeyDown_Enter"
        Padding="5,0"
        Text="{Binding TextBoxContent, Mode=TwoWay}"
        TextChanged="TextBox_TextChanged" VerticalContentAlignment="Center" Height="22" VerticalAlignment="Center" />
    <TextBlock
        x:Name="ErrorMsgTextBlock"
        Grid.Row="1"
        Grid.Column="1"
        Margin="0"
        HorizontalAlignment="Left"
        VerticalAlignment="Top"
        Style="{DynamicResource ValidationErrorLabel}"
        Text="{Binding Path=(Validation.Errors)[0].ErrorContent, ElementName=ControlRoot}"
        Visibility="{Binding Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}, ElementName=ControlRoot, Mode=OneWay}" TextWrapping="Wrap" />
</Grid>

LabelTextBoxBaseClass: LabelTextBoxBaseClass:

#region TextBox Dependency Properties
public string TextBoxContent
{
    get { return (string)GetValue( TextBoxContentProperty ); }
    set { SetValue( TextBoxContentProperty, value ); }
}
public static readonly DependencyProperty TextBoxContentProperty =
    DependencyProperty.Register( "TextBoxContent"
    , typeof( string )
    , typeof( LabelTextBoxBaseClass ), new PropertyMetadata( "" )
);

LabelTextBoxToggle.xaml: LabelTextBoxToggle.xaml:

<!-- This is the nested UserControl -->
<local:LabelTextBox
    x:Name="LTBControl"
    Margin="0"
    VerticalContentAlignment="Center"
    IsEnabled="{Binding Path=IsChecked, ElementName=ToggleCheckBox}"
    LabelContent="{Binding Path=LabelContent}"
    LabelHAlign="{Binding Path=LabelHAlign}"
    LabelWidth="{Binding Path=LabelWidth}"
    RaiseEnterKeyDownEvent="{Binding RaiseEnterKeyDownEvent, Mode=TwoWay}"
    RaiseTextChangedEvent="{Binding RaiseTextChangedEvent, Mode=TwoWay}"
    TextBoxContent="{Binding Path=TextBoxContent, Mode=TwoWay}" />
<CheckBox
    x:Name="ToggleCheckBox"
    Grid.Column="1"
    Margin="5,0"
    HorizontalAlignment="Center"
    VerticalAlignment="Center"
    HorizontalContentAlignment="Center"
    VerticalContentAlignment="Center"
    Click="ToggleCheckBox_Click"
    IsChecked="{Binding CheckBoxChecked, Mode=TwoWay}" />

MaterialBuilder.xaml: MaterialBuilder.xaml:

<UserControl.Resources>
    <BindingGroup x:Key="SRBindingGroup" Name="PropertiesBindingGroup">
        <BindingGroup.ValidationRules>
            <local:AddMaterialRule ValidationStep="ConvertedProposedValue" />
        </BindingGroup.ValidationRules>
    </BindingGroup>
    <srvalidators:StringNullOrEmptyValidationRule x:Key="stringNullOrEmptyValidationRule" ErrorMessage="Custom Dir cannot be null!" />
    <srconverters:ListToStringConverter x:Key="ListToStringConverter" />
    <srconverters:ListToStringConverter x:Key="listToStringConverter" />
    <sys:String x:Key="newLine">\n</sys:String>
</UserControl.Resources>

<StackPanel x:Name="spSetup">

    <!-- This contains a nested UserControl (LabelTextBox), and I can't get its TextBox background to change color, I just get the red border around the whole control on Validation Errors. -->
    <srcontrols:LabelTextBoxBrowseFile
        x:Name="ltbMaterialBlueprint"
        Height="Auto"
        Margin="0,5"
        LabelContent="Material Blueprint:"
        LabelWidth="120"
        LostFocus="ltbMaterialBlueprint_UpdateUI"
        OnButtonClick="ltbMaterialBlueprint_UpdateUI"
        OnTextBoxEnterKeyDown="ltbMaterialBlueprint_UpdateUI"
        TextBoxContent="{Binding MaterialBlueprintFilePath, Mode=TwoWay}">
        <srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
            <Binding
                Mode="TwoWay"
                Path="CustomDirName"
                UpdateSourceTrigger="PropertyChanged"
                ValidatesOnDataErrors="True">
                <Binding.ValidationRules>
                    <srvalidators:StringNullOrEmptyValidationRule ErrorMessage="Custom Dir cannot be empty!" />
                </Binding.ValidationRules>
            </Binding>
        </srcontrols:LabelTextBoxBrowseFile.TextBoxContent>
    </srcontrols:LabelTextBoxBrowseFile>

    <!-- Here I use the base LabelTextBox control by itself and everything works as intended. The TextBox's background color changes to red on Validation Errors. -->
    <srcontrols:LabelTextBox
        x:Name="ltbMaterialName"
        Margin="0,5,10,5"
        LabelContent="Name:"
        LabelWidth="60"
        OnTextBoxTextChange="ltbMaterialName_Validate"
        RaiseEnterKeyDownEvent="True"
        RaiseTextChangedEvent="True">
        <!--  Set-up the TextBox Content to use the ValidationRule by passing this GroupBox's BindingGroup resource as a parameter  -->
        <srcontrols:LabelTextBox.TextBoxContent>
            <Binding
                Mode="TwoWay"
                Path="MaterialName"
                UpdateSourceTrigger="Explicit"
                ValidatesOnDataErrors="True">
                <Binding.ValidationRules>
                    <local:AddMaterialRule
                    BGroup="{StaticResource SRBindingGroup}"
                    CheckForDuplicates="True"
                    CheckForEmptyName="True"
                    IsMaterialName="True"
                    ValidationStep="ConvertedProposedValue" />
                </Binding.ValidationRules>
            </Binding>
        </srcontrols:LabelTextBox.TextBoxContent>
    </srcontrols:LabelTextBox>
</StackPanel>

I know it's probably a DataContext issue, but unlike the other controls and dependency properties, I cannot figure out how to make the base UserControl ui elements update their look when Validation Errors are found. 我知道这可能是DataContext问题,但是与其他控件和依赖项属性不同,当发现“验证错误”时,我无法弄清楚如何使基本UserControl ui元素更新其外观。 Here's some images of what I mean: 这是我的意思的一些图片:

Working TextBox (LabelTextBox control used here): 工作文本框(此处使用LabelTextBox控件):

Working TextBox Example 工作文本框示例

Broken TextBox (LabelTextBoxToggle control used here, with nested LabelTextBox): 损坏的文本框(此处使用的LabelTextBoxToggle控件,带有嵌套的LabelTextBox):

Broken TextBox (nested in UserControl) 损坏的文本框(嵌套在UserControl中)

Any help or suggestion is very welcomed of course! 任何帮助或建议当然都非常受欢迎! Thanks for your time! 谢谢你的时间!

Your problem is similar to mine. 您的问题类似于我的。 I've also created custom control containing text block (as label) and text box (as input). 我还创建了包含文本块(作为标签)和文本框(作为输入)的自定义控件。 The goal is to have universal control for data input with simple label. 目标是对带有简单标签的数据输入进行通用控制。 The problem was validation. 问题是验证。 I've also managed easily to bind and validate data, but displaying errors with template on specified textbox that was inside my control... that was the issue and if I understand correctly you have the same problem. 我也很容易地绑定和验证数据,但是在控件内部的指定文本框中显示模板错误,这就是问题所在,如果我正确理解,您也会遇到同样的问题。 So my solution is: 所以我的解决方案是:

<UserControl x:Class="CapMachina.Common.Controls.FormField_UC" x:Name="FormFieldCtrl"
         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:CapMachina.Common.Controls"
         xmlns:Converters="clr-namespace:CapMachina.Common.Converters"
         xmlns:metro="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

<UserControl.Resources>
    <Converters:ConditionalValueConverter x:Key="conditionalValueConverter" />
    <Converters:NullableObjectToVisibilityConverter x:Key="nullableObjectToVisibilityConverter" />
  </UserControl.Resources>
  <StackPanel>
    <TextBlock FontWeight="Bold" Text="{Binding Header, ElementName=FormFieldCtrl}" Margin="1" />
    <TextBox x:Name="MainTxtBx" metro:TextBoxHelper.Watermark="{Binding WaterMarkText, ElementName=FormFieldCtrl}" TextWrapping="Wrap"
             Text="{Binding Text, ElementName=FormFieldCtrl, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" 
             Margin="1" IsReadOnly="{Binding IsReadOnly, ElementName=FormFieldCtrl}" TextChanged="MainTxtBx_TextChanged" Loaded="MainTxtBx_Loaded">
      <TextBox.Style>
        <MultiBinding Converter="{StaticResource conditionalValueConverter}">
          <Binding Path="IsReadOnly" ElementName="FormFieldCtrl" />
          <Binding Path="ReadOnlyStyle" ElementName="FormFieldCtrl" />
          <Binding Path="DefaultStyle" ElementName="FormFieldCtrl" />
        </MultiBinding>
      </TextBox.Style>
    </TextBox>
  </StackPanel>
</UserControl>

And code behind: 后面的代码:

    using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace CapMachina.Common.Controls
{
  public partial class FormField_UC : UserControl
  {
public string Header
{
  get { return (string)GetValue(HeaderProperty); }
  set { SetValue(HeaderProperty, value); }
}

public static readonly DependencyProperty HeaderProperty =
    DependencyProperty.Register("Header", typeof(string), typeof(FormField_UC));

public string Text
{
  get { return (string)GetValue(TextProperty); }
  set { SetValue(TextProperty, value); }
}

public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register("Text", typeof(string), typeof(FormField_UC));

public string WaterMarkText
{
  get { return (string)GetValue(WaterMarkTextProperty); }
  set { SetValue(WaterMarkTextProperty, value); }
}

public static readonly DependencyProperty WaterMarkTextProperty =
    DependencyProperty.Register("WaterMarkText", typeof(string), typeof(FormField_UC));

public bool IsReadOnly
{
  get { return (bool)GetValue(IsReadOnlyProperty); }
  set { SetValue(IsReadOnlyProperty, value); }
}

public static readonly DependencyProperty IsReadOnlyProperty =
    DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(FormField_UC), new PropertyMetadata(true));

public Style ReadOnlyStyle { get; set; }
public Style DefaultStyle { get; set; }

public FormField_UC()
{
  ReadOnlyStyle = Application.Current.FindResource("ReadOnlyTextBox") as Style;
  DefaultStyle = Application.Current.FindResource("DefaultTextBox") as Style;
  InitializeComponent();
}

private void MainTxtBx_TextChanged(object sender, TextChangedEventArgs e)
{
  if (string.IsNullOrEmpty(MainTxtBx.Text) && IsReadOnly)
    Visibility = Visibility.Collapsed;
  else
    Visibility = Visibility.Visible;
}

private void MainTxtBx_Loaded(object sender, RoutedEventArgs e)
{
  BindingExpression mainTxtBxBinding = BindingOperations.GetBindingExpression(MainTxtBx, TextBox.TextProperty);
  BindingExpression textBinding = BindingOperations.GetBindingExpression(this, TextProperty);

  if (textBinding != null && mainTxtBxBinding != null && textBinding.ParentBinding != null && textBinding.ParentBinding.ValidationRules.Count > 0 && mainTxtBxBinding.ParentBinding.ValidationRules.Count < 1)
  {
    foreach (ValidationRule vRule in textBinding.ParentBinding.ValidationRules)
      mainTxtBxBinding.ParentBinding.ValidationRules.Add(vRule);
  }
    }
  }
}

Usage: 用法:

<Controls:FormField_UC Header="First name" IsReadOnly="False" HorizontalAlignment="Left" VerticalAlignment="Top">
  <Controls:FormField_UC.Text>
    <Binding Path="Person.FirstName" Mode="TwoWay">
      <Binding.ValidationRules>
        <VDRules:NamesValidationRule InventoryPattern="{StaticResource NamesRegex}">
          <VDRules:NamesValidationRule.Attributes>
            <Validation:ValidationAttributes IsRequired="True" />
          </VDRules:NamesValidationRule.Attributes>
        </VDRules:NamesValidationRule>
      </Binding.ValidationRules>
    </Binding>
  </Controls:FormField_UC.Text>
</Controls:FormField_UC>

What i did was copy validation rules to nested text box after all bindings were created. 创建所有绑定后,我所做的就是将验证规则复制到嵌套文本框中。 You cannot modify binding after use, but you can add validation rules to it :) 使用后无法修改绑定,但可以向其中添加验证规则:)

It is very important to set certain properties inside custom control like: 在自定义控件中设置某些属性非常重要,例如:

<UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True>

cos you cannot set them afterwards. 因为您以后不能设置它们。 So setting them in usage line is not needed. 因此,不需要在使用情况行中设置它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM