una poner imagenes imagen ejemplo como añadir atributos atributo agregar c# wpf xaml data-binding textbox

c# - poner - Enlace de reglas de validación anidadas en controles de usuario anidados



ejemplo de atributo alt html (1)

Esta es mi primera pregunta aquí en SO ... He estado listo durante mucho tiempo y nunca tuve que pedir ayuda porque normalmente encuentro lo que necesito, pero estoy teniendo un momento difícil con este ...

Estoy trabajando en un conjunto de herramientas en WPF. Creé algunos controles de usuario de la siguiente manera:

  1. LabelTextBox (etiqueta a la izquierda y TextBox a la derecha)
  2. LabelTextBoxToggle ( LabelTextBox a la izquierda y Checkbox a la derecha)
  3. LabelTextBoxBrowseFile ( LabelTextBox a la izquierda y botón Examinar archivo a la derecha)

Utilizo Dependency Properties para enlazar todas las propiedades que necesito y todas funcionan bien. El problema que me encontré recientemente es conseguir que ValidationRules funcione correctamente en el TextBox base que uso en LabelTextBox cuando esas reglas se aplican a LabelTextBoxToggle y LabelTextBoxBrowseFile UserControls, ya que tengo que vincular 2 niveles para actualizar los controles en LabelTextBox . Puedo ejecutar la Regla de Validación, pero no puedo hacer que el control TextBox actualice su color de fondo en consecuencia cuando se encuentran errores, como hago cuando LabelTextBox no está anidado dentro de otro Control de Usuario.

Entonces, aquí está mi código a continuación:

Estilo utilizado para 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:

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

Sé que probablemente sea un problema de DataContext, pero a diferencia de otros controles y propiedades de dependencia, no puedo entender cómo hacer que los elementos base UserControl ui actualicen su aspecto cuando se encuentran los errores de validación. Aquí hay algunas imágenes de lo que quiero decir:

Working TextBox (control LabelTextBox utilizado aquí):

Ejemplo de Working TextBox

Broken TextBox (control LabelTextBoxToggle utilizado aquí, con LabelTextBox anidado):

Broken TextBox (anidado en UserControl)

Cualquier ayuda o sugerencia es muy bienvenida, por supuesto! ¡Gracias por tu tiempo!


Tu problema es similar al mío También creé un control personalizado que contiene un bloque de texto (como etiqueta) y un cuadro de texto (como entrada). El objetivo es tener un control universal para la entrada de datos con una etiqueta simple. El problema fue la validación. También logré vincular y validar datos fácilmente, pero mostrar errores con la plantilla en el cuadro de texto especificado que estaba dentro de mi control ... ese era el problema y si lo entendía correctamente, tenías el mismo problema. Entonces mi solución es:

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

Y código detrás:

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

Uso:

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

Lo que hice fue copiar las reglas de validación al cuadro de texto anidado después de que se crearan todos los enlaces. No puede modificar el enlace después de su uso, pero puede agregarle reglas de validación :)

Es muy importante establecer ciertas propiedades dentro del control personalizado como:

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

porque no puedes establecerlos después. Por lo tanto, no es necesario establecerlos en la línea de uso.