Arhn - архитектура программирования

Как заставить проверку WPF всплывать до родительского элемента управления?

Итак, у меня есть элемент управления, подобный этой упрощенной версии:

<local:ImageMapField x:Class="ImageApp.WPF.Controls.ImageMapContentField"
             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:ImageApp.WPF.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
                        x:Name="Me">

    <Grid HorizontalAlignment="Left">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" HorizontalAlignment="Stretch" Style="{DynamicResource BaseLabelStyle}">
            <TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=local:ImageMapContentField, Mode=FindAncestor}}" TextWrapping="WrapWithOverflow"></TextBlock>
        </Label>
        <StackPanel Grid.Column="1">
            <Image />
            <Border Margin="20,5,5,2">
                <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
            </Border>
        </StackPanel>
    </Grid>

</local:ImageMapField>

И я использую его так:

<controls:ImageMapContentField Header="Foo Date" 
                                FieldName="FooDate"
                                ImageSource="{Binding MyImage, Mode=TwoWay}"
                                ItemsSource="{Binding Map.Items}"
                                Zoom="{Binding MapFieldZoom}">
    <controls:ImageMapContentField.DataEntryContent>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBox Grid.Column="0" Text="{Binding MyDate, StringFormat=MM/dd/yyyy, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
                <controls:WatermarkService.Watermark>
                    <TextBlock>Date</TextBlock>
                </controls:WatermarkService.Watermark>
            </TextBox>
            <TextBox Grid.Column="1" Text="{Binding MyTime, StringFormat=HH\:mm}">
                <controls:WatermarkService.Watermark>
                    <TextBlock>Time</TextBlock>
                </controls:WatermarkService.Watermark>
            </TextBox>
        </Grid>
    </controls:ImageMapContentField.DataEntryContent>
</controls:ImageMapContentField>

Проблема в том, что, поскольку я не привязываю свойство моей модели к чему-либо на ImageMapContentField, Validation.HasError на ImageMapContentField всегда ложно и никогда не срабатывает.

Вместо этого я получаю проверку TextBox по умолчанию.

Я действительно хочу, чтобы ImageMapContentField имел розовый фон. Это работает для других моих элементов управления, где я привязываюсь напрямую к чему-либо, но я не могу заставить это работать для элементов управления, у которых есть ContentPresenter.

Я надеюсь, что мне просто не хватает чего-то, что позволило бы родителю зафиксировать проверку.


В соответствии с запросом здесь приведен минимальный пример проблемы:

MainWindow.xaml

<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="350" Width="525">


    <Window.Resources>
        <Style TargetType="local:CustomTextField">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                            <AdornedElementPlaceholder/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Setter Property="Background" Value="LightPink"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <Style TargetType="local:CustomContentControl">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                            <AdornedElementPlaceholder/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
                    <Setter Property="Background" Value="LightPink"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Window.DataContext>
        <local:MyModel />
    </Window.DataContext>
    <StackPanel>
        <local:CustomTextField LabelText="Number 1" Value="{Binding Number1, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
        <local:CustomContentControl LabelText="Number 2">
            <local:CustomContentControl.DataEntryContent>
                <TextBox Text="{Binding Number2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
            </local:CustomContentControl.DataEntryContent>    
        </local:CustomContentControl>
    </StackPanel>
</Window>

CustomTextField.xaml

<UserControl x:Class="WpfApp1.CustomTextField"
             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" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="Me">
        <StackPanel>
            <Label Content="{Binding ElementName=Me, Path=LabelText}" />
            <TextBox Text="{Binding ElementName=Me, Path=Value}" />
        </StackPanel>
</UserControl>

CustomTextField.cs

public partial class CustomTextField : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                                                        "LabelText", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
                                                        "Value", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));

        public string Value
        {
            get { return (string) GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

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

        public CustomTextField()
        {
            InitializeComponent();
        }
    }

CustomContentControl.xaml

<UserControl x:Class="WpfApp1.CustomContentControl"
             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" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="Me">
        <Grid>
            <StackPanel>
                <Label Content="{Binding ElementName=Me, Path=LabelText}" />
                <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
            </StackPanel>
        </Grid>
</UserControl>

CustomContentControl.cs

public partial class CustomContentControl : UserControl
    {
        public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                                                        "LabelText", typeof(string), typeof(CustomContentControl), new PropertyMetadata(default(string)));

        public static readonly DependencyProperty DataEntryContentProperty = DependencyProperty.Register(
                                                        "DataEntryContent", typeof(object), typeof(CustomContentControl), new PropertyMetadata(default(object)));

        public object DataEntryContent
        {
            get { return (object) GetValue(DataEntryContentProperty); }
            set { SetValue(DataEntryContentProperty, value); }
        }

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

        public CustomContentControl()
        {
            InitializeComponent();
        }
    }

MyModel.cs

public class MyModel : INotifyPropertyChanged
    {
        int _number1;
        int _number2;

        public int Number1
        {
            get { return _number1; }
            set
            {
                _number1 = value;
                OnPropertyChanged();
            }
        }

        public int Number2
        {
            get { return _number2; }
            set
            {
                _number2 = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }


  • У вас есть готовый проект, воспроизводящий проблему? 24.04.2017
  • @SimonMourier Добавлен пример. 24.04.2017

Ответы:


1

Проверка WPF уже распространяется на родительский элемент управления (даже если дочерний элемент управления находится внутри ContentPresenter) — Validation.ErrorEvent

Проблема здесь в том, что, хотя событие всплывает, присоединенное свойство Validation.HasError не обновляется — в основном это связано с тем, что в привязках свойств элемента управления нет ошибок. И, следовательно, вы не видите изменения фона.

Чтобы исправить это - вы можете использовать этот код:

Обновить стиль в MainWindow.xaml

    <Style TargetType="local:CustomContentControl">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
                        <AdornedElementPlaceholder/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="HasErrors" Value="True">
                <Setter Property="Background" Value="LightPink"/>
            </Trigger>
        </Style.Triggers>
    </Style>

И обновите CustomContentControl, чтобы добавить свойство зависимостей HasErrors и обработчик событий ошибки проверки

    public static readonly DependencyProperty HasErrorsProperty = DependencyProperty.Register("HasErrors", typeof(bool), typeof(CustomContentControl), new PropertyMetadata(false));

    public bool HasErrors
    {
        get { return (bool)GetValue(HasErrorsProperty); }
        set { SetValue(HasErrorsProperty, value); }
    }

    public CustomContentControl()
    {
        InitializeComponent();

        Validation.AddErrorHandler(this, (s, args) => {
            if (args.Action == ValidationErrorEventAction.Added)
            {
                this.ToolTip = args.Error.ErrorContent;
                HasErrors = true;
            }
            else
            {
                this.ToolTip = null;
                HasErrors = false;
            }
        });  
    }

И ваш фон будет обновлен. введите здесь описание изображения

25.04.2017
  • Спасибо! Я знал, что должно быть что-то, чего мне не хватало. Надеялся каким-то образом вызвать стандартный Validation.HasError, чтобы он был таким же, как и все остальное, но это будет работать и довольно просто. 25.04.2017
  • Да, моим первым побуждением было вручную добавить ошибку в коллекцию Validation.Errors, чтобы вызвать обновление в Validation.HasError. Но введение пользовательского свойства зависимости казалось намного проще; особенно если у вас есть только одна проверка привязки для отслеживания. Если у вас есть несколько привязок для отслеживания (несколько дочерних элементов управления), рекомендуется использовать Validation.Errors для отслеживания ошибок. stackoverflow .com/questions/2174251/ 25.04.2017
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..