C# wpf - Как передать ошибку текстового поля родителю в пользовательском элементе управления - PullRequest
0 голосов
/ 01 февраля 2020

Я пытаюсь создать текстовое поле с многоуровневой проверкой, поэтому уведомляйте пользователя, если есть поля, которые должны быть заполнены, но не обязательны для заполнения. Я создал элемент управления, который действительно хорошо работает, у меня есть только одна проблема. Я хочу использовать это новое текстовое поле в другом пользовательском контроле. У меня есть кнопка сохранения, и я хочу, чтобы эта кнопка была включена, когда в этом текстовом поле нет ошибки проверки, но кажется, что ошибка проверки находится внутри моего пользовательского элемента управления texbox, поэтому кнопка всегда включена. Вот код элемента управления xaml:

MultiLevelValidationTextBox.xaml

<Style x:Key="MultiLevelValidationTextBoxStyle"  TargetType="local:MultiLevelValidationTextBoxControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MultiLevelValidationTextBoxControl">
                <Grid>
                    <Grid.Resources>
                        <local:IsNullConverter x:Key="IsNullConverter" />
                    </Grid.Resources>
                    <TextBox
                        x:Name="PART_TextBox"
                        Text="{Binding Path=BindingText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}">
                        <TextBox.Style>
                            <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
                                <Style.Triggers>
                                    <MultiDataTrigger>
                                        <MultiDataTrigger.Conditions>
                                            <Condition Binding="{Binding Path=Recommended, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="True"/>
                                            <Condition Binding="{Binding Path=BindingText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource IsNullConverter}}" Value="True"/>
                                        </MultiDataTrigger.Conditions>
                                        <Setter Property="BorderBrush" Value="#fcba03" />
                                    </MultiDataTrigger>
                                    <DataTrigger Binding="{Binding Path=Required, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="True">
                                        <Setter Property="Text">
                                            <Setter.Value>
                                                <Binding Path="BindingText" RelativeSource="{RelativeSource Mode=TemplatedParent}" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
                                                    <Binding.ValidationRules>
                                                        <local:TextBoxTextValidation ValidatesOnTargetUpdated="True"/>
                                                    </Binding.ValidationRules>
                                                </Binding>
                                            </Setter.Value>
                                        </Setter>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </TextBox.Style>
                    </TextBox>
                    <Polygon x:Name="PART_Polygon" Points="0,0 5,0 0,5 0,0" Margin="0,3,2,0" HorizontalAlignment="Right" FlowDirection="RightToLeft" ToolTip="A mező kitöltése ajánlott!">
                        <Polygon.Style>
                            <Style TargetType="Polygon">
                                <Style.Triggers>
                                    <MultiDataTrigger>
                                        <MultiDataTrigger.Conditions>
                                            <Condition Binding="{Binding Path=Recommended, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="True"/>
                                            <Condition Binding="{Binding Path=BindingText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource IsNullConverter}}" Value="True"/>
                                        </MultiDataTrigger.Conditions>
                                        <Setter Property="Fill" Value="#fcba03" />
                                    </MultiDataTrigger>
                                    <MultiDataTrigger>
                                        <MultiDataTrigger.Conditions>
                                            <Condition Binding="{Binding Path=Recommended, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="True"/>
                                            <Condition Binding="{Binding Path=BindingText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource IsNullConverter}}" Value="False"/>
                                        </MultiDataTrigger.Conditions>
                                        <Setter Property="Fill" Value="Transparent" />
                                    </MultiDataTrigger>
                                    <MultiDataTrigger>
                                        <MultiDataTrigger.Conditions>
                                            <Condition Binding="{Binding Path=Recommended, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="False"/>
                                        </MultiDataTrigger.Conditions>
                                        <Setter Property="Fill" Value="Transparent" />
                                    </MultiDataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Polygon.Style>
                    </Polygon>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

А вот MultiLevelValidationTextBoxControl.cs

    [TemplatePart(Name = PART_TextBox, Type = typeof(TextBox))]
[TemplatePart(Name = PART_Polygon, Type = typeof(Polygon))]
public class MultiLevelValidationTextBoxControl : TextBox
{

    private const string PART_TextBox = "PART_TextBox";
    private const string PART_Polygon = "PART_Polygon";

    private TextBox _textBox = null;
    private Polygon _polygon = null;

    static MultiLevelValidationTextBoxControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiLevelValidationTextBoxControl), new FrameworkPropertyMetadata(typeof(MultiLevelValidationTextBoxControl)));
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _textBox = GetTemplateChild(PART_TextBox) as TextBox;
        _polygon = GetTemplateChild(PART_Polygon) as Polygon;
    }

    public static readonly DependencyProperty RequiredProperty = DependencyProperty.Register("Required", typeof(bool), typeof(MultiLevelValidationTextBoxControl), new UIPropertyMetadata(false));

    public bool Required
    {
        get
        {
            return (bool)GetValue(RequiredProperty);
        }
        set
        {
            SetValue(RequiredProperty, value);
        }
    }

    public static readonly DependencyProperty RecommendedProperty = DependencyProperty.Register("Recommended", typeof(bool), typeof(MultiLevelValidationTextBoxControl), new UIPropertyMetadata(false));

    public bool Recommended
    {
        get
        {
            return (bool)GetValue(RecommendedProperty);
        }
        set
        {
            SetValue(RecommendedProperty, value);
        }
    }

    public static readonly DependencyProperty BindingTextProperty = DependencyProperty.Register("BindingText", typeof(string), typeof(MultiLevelValidationTextBoxControl), new UIPropertyMetadata(string.Empty));

    public string BindingText
    {
        get
        {
            return (string)GetValue(BindingTextProperty);
        }
        set
        {
            SetValue(BindingTextProperty, value);
        }
    }

    public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register("LabelText", typeof(string), typeof(MultiLevelValidationTextBoxControl), new UIPropertyMetadata(string.Empty));

    public string LabelText
    {
        get
        {
            return (string)GetValue(LabelTextProperty);
        }
        set
        {
            SetValue(LabelTextProperty, value);
        }
    }
}

И у меня есть другое окно, где я Я хочу использовать это, и есть кнопка, которая должна быть отключена, если требуется texbox:

<Button
                         Grid.Row="1"
            Grid.Column="6"
            Margin="10,0,0,0" 
            MinWidth="80" 
            Height="30">

Так есть ли способ передать ошибки проверки родителю? Все отлично работает, только эта часть не работает.

                    <Button.Style>
                        <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
                            <Setter Property="IsEnabled" Value="False"/>
                            <Style.Triggers>
                                <MultiDataTrigger>
                                    <MultiDataTrigger.Conditions>
                                        <Condition Binding="{Binding ElementName=CustomTextBoxName, Path=(Validation.HasError), UpdateSourceTrigger=PropertyChanged}" Value="False" />
                                    </MultiDataTrigger.Conditions>
                                    <Setter Property="IsEnabled" Value="True"/>
                                </MultiDataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>

                    Save
                </Button>

1 Ответ

1 голос
/ 02 февраля 2020

Прежде всего, ваш код слишком сложен и поэтому сложен для понимания. Вам не нужно помещать TextBox в ControlTemplate из TextBox. Это абсурд. Если вы не знаете, как устроено дерево элемента управления, всегда обращайтесь к Документам Microsoft: стили и шаблоны элементов управления и найдите нужный элемент управления.
В вашем случае это Стили TextBox и шаблоны . Здесь вы можете узнать, что содержимое TextBox размещено в ScrollViewer, который на самом деле ContentControl предлагает прокрутку содержимого:

Это уменьшенный ControlTemplate из TextBox (перейдите по предыдущей ссылке, чтобы увидеть полный код):

<Style TargetType="{x:Type TextBox}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type TextBoxBase}">
        <Border Name="Border"
                CornerRadius="2"
                Padding="2"
                BorderThickness="1">
          <VisualStateManager.VisualStateGroups>
            ...
          </VisualStateManager.VisualStateGroups>

          <!-- The host of the TextBox's content -->
          <ScrollViewer Margin="0"
                        x:Name="PART_ContentHost" />
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Но поскольку WPF обеспечивает готовую проверку данных и обратную связь об ошибках, вам не нужно переопределять значение по умолчанию ControlTemplate , Так как ваш пользовательский MultiLevelValidationTextBoxControl не добавляет никаких дополнительных функций, вы можете go с простой ванилью TextBox.

Это способ реализации простой проверки данных с использованием Проверка привязки
и визуальная обратная связь по ошибкам с использованием прикрепленного свойства Validation.ErrorTemplate :

Определение TextBox

<TextBox Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
         Style="{StaticResource TextBoxStyle}">
  <TextBox.Text>
    <Binding Path="BindingText" 
             RelativeSource="{RelativeSource TemplatedParent}"
             UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <local:TextBoxTextValidation ValidatesOnTargetUpdated="True"/>
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

ControlTemplate, которое отображается при сбое проверки

<ControlTemplate x:Key="ValidationErrorTemplate">
  <DockPanel>
    <TextBlock Text="!"
               Foreground="#FCBA03" 
               FontSize="20" />
    <Border BorderThickness="2" 
            BorderBrush="#FCBA03"
            VerticalAlignment="Top">
      <AdornedElementPlaceholder/>
    </Border>
  </DockPanel>
</ControlTemplate>

Style для добавления дополнительного поведения, например, отображение ошибки ToolTip

<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
  <Style.Triggers>
    <Trigger Property="Validation.HasError" Value="true">
      <Setter Property="ToolTip"
        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)/ErrorContent}"/>
    </Trigger>
  </Style.Triggers>
</Style>

Чтобы связать эти TextBox ошибки проверки с другим элементом управления (в случае использования Binding Validation) просто go:

<StackPanel>

  <!-- Button will be disabled when the TextBox has validation errors -->
  <Button Content="Press Me" Height="40">
    <Button.Style>
      <Style TargetType="Button">
        <Style.Triggers>
          <DataTrigger Binding="{Binding ElementName=ValidatedTextBox, Path=(Validation.HasError)}" 
                       Value="True">
            <Setter Property="IsEnabled" Value="False" />
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </Button.Style>
  </Button>
  <TextBox x:Name="ValidatedTextBox" 
           Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
           Style="{StaticResource TextBoxStyle}">
      <TextBox.Text>
        <Binding Path="BindingText" 
                 RelativeSource="{RelativeSource TemplatedParent}"
                 UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <local:TextBoxTextValidation ValidatesOnTargetUpdated="True"/>
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
</StackPanel>

Это рабочий пример. Вам просто нужно расширить его, добавив условную проверку на основе некоторого свойства Required. Просто добавьте триггер к TextBoxStyle. Нет необходимости выводить из TextBox и создавать пользовательский элемент управления.


Примечания

Я настоятельно рекомендую реализовать проверку данных в модели представления путем реализации INotifyDataErrorInfo ( пример ). Это добавляет больше гибкости и упрощает дизайн пользовательского интерфейса, поскольку пути привязки будут упрощены, а пользовательский интерфейс будет удален. Он также обеспечивает инкапсуляцию (улучшает дизайн) и дает возможность модульного тестирования полной логики проверки c.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...