在嵌套的用户控件嵌套绑定验证规则(Binding nested validation rules o

2019-10-22 22:12发布

这是在这里,所以我的第一个问题......我已经准备了很长时间,从来没有需要寻求帮助,因为我经常发现我所需要的,但我有一个艰难的时间与这一个...

我工作的一个工具套件在WPF。 我创建了一些用户控制如下:

  1. LabelTextBox(标签左边和文本框右侧)
  2. LabelTextBoxToggle(LabelTextBox左侧和复选框右侧)
  3. LabelTextBoxBrowseFile(LabelTextBox在左边,浏览右边文件按钮

我使用依赖属性绑定我需要的所有特性,它们都做工精细。 我跑进最近越来越ValidationRules的问题在基础文本框LabelTextBox使用时,这些规则适用于LabelTextBoxToggleLabelTextBoxBrowseFile用户控件正常工作,因为我必须向下绑定,才能在LabelTextBox更新控制2倍的水平。 我可以得到验证规则运行,但我不能让TextBox控件进行相应的更新背景色当发现错误时,像我这样做的时候是不是嵌套在其他用户控制LabelTextBox。

所以,这里是我下面的代码:

用于文本框样式:

<!-- 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:

<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:

#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:

<!-- 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:

<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>

我知道这可能是一个DataContext的问题,但不同于其他控件和依赖属性,我无法弄清楚如何使基本用户控件UI元素更新它们的外观时发现验证错误。 下面是我的意思是一些图片:

工作文本框(这里使用LabelTextBox控制):

工作文本框示例

残破的文本框(这里使用,嵌套LabelTextBox LabelTextBoxToggle控制):

破碎的文本框(嵌套在用户控件)

任何帮助或建议十分欢迎,当然! 谢谢你的时间!

Answer 1:

你的问题是与我相似。 我也创建了一个包含文本块(如标签)自定义控件和文本框(输入)。 我们的目标是有一个与简单的标签数据输入通用控制。 问题是验证。 我也设法容易绑定和验证数据,但显示与指定文本模板的错误,是我控制的内心深处,是这个问题,如果我理解正确的话,你有同样的问题。 所以我的解决办法是:

<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>

而后面的代码:

    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);
  }
    }
  }
}

用法:

<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>

我所做的就是创建所有绑定后复制的验证规则,以嵌套的文本框中。 您不能修改使用后绑定,但您可以添加验证规则给它:)

设置,如自定义控制内部的某些特性是非常重要的:

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

因为你不能使以后。 因此,在使用习惯设置它们是没有必要的。



文章来源: Binding nested validation rules on nested User Controls