Подтвердить что ты не робот

Показать ошибку проверки в UserControl

Я не уверен, почему состояние проверки не отражено в моем пользовательском элементе управления. Я бросаю исключение, но по какой-то причине элемент управления не показывает состояние проверки... Когда я использую стандартный Textbox (который сейчас комментируется в моем примере), на моем MainPage отображается состояние ошибки, а не конечно, почему это не когда его обернули.

Я уменьшил это, так что в основном это пользовательский элемент управления, который обертывает Textbox. Что мне не хватает?

MyUserControl XAML:

<UserControl x:Class="ValidationWithUserControl.MyUserControl"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <TextBox x:Name="TextBox"/>
    </Grid>
</UserControl>

Код MyUserControl:

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MyUserControl_Loaded);
        this.TextBox.Unloaded += new RoutedEventHandler(TextBox_Unloaded);
    }

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

    public static DependencyProperty ValueProperty =
        DependencyProperty.Register(
        "Value",
        typeof(string),
        typeof(MyUserControl),
        new PropertyMetadata(null));

    private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
    {
        this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
        {
            Source = this,
            Path = new PropertyPath("Value"),
            Mode = BindingMode.TwoWay,
            ValidatesOnExceptions = true,
            NotifyOnValidationError= true
        });  
    }

    private void TextBox_Unloaded(object sender, RoutedEventArgs e)
    {
        this.TextBox.ClearValue(TextBox.TextProperty);
    }
}

My MainPage XAML:

<Grid x:Name="LayoutRoot" Background="LightBlue">
    <StackPanel>
        <uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay}" Height="20" Width="100" />
        <!--TextBox x:Name="MS" Text="{Binding Path=Value, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}" Height="20" Width="100" /-->
    </StackPanel>
</Grid>

Мой код главной страницы:

public partial class MainPage : UserControl
{
    private Model model;
    //private Model model2;

    public MainPage()
    {
        InitializeComponent();
        this.model = new Model("UC");
        //this.model2 = new Model("MS");
        this.UC.DataContext = this.model;
        //this.MS.DataContext = this.model2;
    }
}

Моя модель:

public class Model
{
    public Model(string answer)
    {
        this.answer = answer;
    }

    private string answer;
    public string Value
    {
        get
        {
            return this.answer;
        }
        set
        {
            if (!String.IsNullOrEmpty(value))
                this.answer = value;
            else
                throw new Exception("Error");
        }
    }
}
4b9b3361

Ответ 1

Хорошо, я наконец понял, как с этим справиться.

Что вам нужно сделать, это скопировать подтверждение из оригинальной привязки и отправить его в привязку к текстовому полю.

Первое, что вам нужно сделать для достижения этой цели, - реализовать интерфейс INotifyDataErrorInfo в пользовательском элементе управления. Затем вам нужно будет проверить элемент управления пользователя, чтобы получить точный текст проверки с помощью функции GetErrors (это можно сделать с помощью Validation.GetErrors).

Это базовая реализация, и это в VB, но я уверен, что вы поняли.

    Public Event ErrorsChanged(ByVal sender As Object, ByVal e As System.ComponentModel.DataErrorsChangedEventArgs) Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged

Public Function GetErrors(ByVal propertyName As String) As System.Collections.IEnumerable Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors
    Dim returnValue As System.Collections.IEnumerable = Nothing

    Dim errorMessage As String = Nothing


    If propertyName = "Value" Then

        If Validation.GetErrors(Me).Count = 0 Then
            errorMessage = ""
        Else
            errorMessage = Validation.GetErrors(Me).First.ErrorContent.ToString
        End If

        If String.IsNullOrEmpty(errorMessage) Then
            returnValue = Nothing
        Else
            returnValue = New List(Of String)() From {errorMessage}
        End If

    End If

    Return returnValue

End Function

Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
    Get
        Return Validation.GetErrors(Me).Any()
    End Get
End Property

Следующее, что нужно сделать, - уведомить вас об управлении, становится недействительным. Вам нужно будет сделать это в двух местах.

Первый будет находиться в событии BindingValidationError. Второй будет находиться в Value PropertyChangedCallback function (он должен быть указан при регистрации DependencyProperty)

Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(XDateTimePicker), New PropertyMetadata(Nothing, AddressOf ValuePropertyChangedCallback))

Public Shared Sub ValuePropertyChangedCallback(ByVal dependencyObject As DependencyObject, ByVal dependencyPropertyChangedEventArgs As DependencyPropertyChangedEventArgs)
    DirectCast(dependencyObject, MyUserControl).NotifyErrorsChanged("Value")
End Sub

Private Sub MyUserControl_BindingValidationError(ByVal sender As Object, ByVal e As System.Windows.Controls.ValidationErrorEventArgs) Handles Me.BindingValidationError
    Me.NotifyErrorsChanged("Value")
End Sub

Public Sub NotifyErrorsChanged(ByVal propertyName As String)
    RaiseEvent ErrorsChanged(Me, New System.ComponentModel.DataErrorsChangedEventArgs(propertyName))
End Sub

Большая часть работы выполняется сейчас, но вам все равно нужно внести некоторые изменения в привязки.

При создании привязки TextBox вам необходимо установить NotifyOnValidationError в False, чтобы избежать цикла уведомлений между исходной привязкой и привязкой к текстовому полю. ValidatesOnExceptions, ValidatesOnDataErrors и ValidatesOnNotifyDataErrors необходимо установить значение True.

        Dim binding As New System.Windows.Data.Binding

    binding.Source = Me
    binding.Path = New System.Windows.PropertyPath("Value")
    binding.Mode = Data.BindingMode.TwoWay
    binding.NotifyOnValidationError = False 
    binding.ValidatesOnExceptions = True
    binding.ValidatesOnDataErrors = True
    binding.ValidatesOnNotifyDataErrors = True

    Me.TextBox1.SetBinding(TextBox.TextProperty, binding)

Наконец, вам нужно установить свойство NotifyOnValidationError и ValidatesOnNotifyDataErrors в значение True в вашем XAML.

<uc:MyUserControl x:Name="UC" Value="{Binding Path=Value, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnNotifyDataErrors=True}" Height="20" Width="100" />

Ответ 2

Такое поведение вызвано добавленным уровнем привязки. Привязки не поддерживают ошибки проверки пересылки.

Что происходит за кулисами:

  • Пользователь вводит текст в TextBox, а привязка, определенная в MyUserControl_Loaded, передает значение в MyUserControl.ValueProperty.
  • Далее, привязка, определенная в MainPage XAML для MyUserControl, передает значение модели.
  • Исключение, созданное в Model.Value.set(), связано с привязкой, которая была настроена в MainPage XAML.
  • Никакое исключение не передается в привязку, связанную с TextBox.
  • Так как UserControl не имеет ValidatesOnExceptions, установленного в true, визуальная индикация не отображается.

Чтобы устранить эту проблему, вы можете привязать текстовое поле непосредственно к модели следующим образом:

this.TextBox.SetBinding(TextBox.TextProperty, new Binding()
{
    Source = this.DataContext, // bind to the originating source
    Path = new PropertyPath("Value"),
    Mode = BindingMode.TwoWay,
    ValidatesOnExceptions = true,
    NotifyOnValidationError= true
});  

С тех пор прошло 6 месяцев, интересно, как и как вы преодолели эту проблему.

Ответ 3

Если кто-то приходит искать хорошего (читай: "не написано в VBA и полностью" ) решение для этой проблемы, я написал базовый класс (хотя я тестировал его только с помощью управления без проблем, не знаю, работает ли он с UserControl s) на основе ответа @The_Black_Smurf на С#:

namespace MyApplication.Controls
{
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;

    public abstract class ControlBaseWithValidation : Control, INotifyDataErrorInfo
    {
        public ControlBaseWithValidation()
        {
            // remove the red border that wraps the whole control by default
            Validation.SetErrorTemplate(this, null);
        }

        public delegate void ErrorsChangedEventHandler(object sender, DataErrorsChangedEventArgs e);

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public bool HasErrors
        {
            get
            {
                var validationErrors = Validation.GetErrors(this);
                return validationErrors.Any();
            }
        }

        public IEnumerable GetErrors(string propertyName)
        {
            var validationErrors = Validation.GetErrors(this);
            var specificValidationErrors =
                validationErrors.Where(
                    error => ((BindingExpression)error.BindingInError).TargetProperty.Name == propertyName).ToList();
            var specificValidationErrorMessages = specificValidationErrors.Select(valError => valError.ErrorContent);
            return specificValidationErrorMessages;
        }

        public void NotifyErrorsChanged(string propertyName)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        protected static void ValidatePropertyWhenChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            ((ControlBaseWithValidation)dependencyObject).NotifyErrorsChanged(dependencyPropertyChangedEventArgs.Property.Name);
        }
    }
}

используйте ControlBaseWithValidation класс как базовый класс для ваших беззаботных элементов управления вместо класса Control и добавьте обратный вызов ValidatePropertyWhenChangedCallback как PropertyChangedCallback для любых свойств зависимостей, которые вы хотите проверить.

Ответ 4

Вы должны иметь возможность отображать привязку свойства зависимостей непосредственно к текстовому полю usercontrol. Это приведет к ошибкам проверки так же, как привязки в родительском представлении. В вашей функции MyUserControl_Loaded:

var valueBinding = BindingOperations.GetBindingBase(this, ValueProperty);
if (valueBinding != null) TextBox.SetBinding(TextBox.TextProperty, valueBinding);