簡體   English   中英

如何在可重用的 UserControl 中將驗證錯誤傳遞給子控件

[英]How to pass validation error to child control in reusable UserControl

我創建了自己的UserControl ,稱為PersonNameControl ,旨在重用。 該控件具有三個TextBox字段,並在其類文件中具有三個依賴項屬性。

  • 插入

每個依賴屬性值都綁定到一個字段,因此依賴屬性Firstname綁定到Firstname TextBox ,依此類推。

我有意識地沒有明確設置 UserControl 的 DataContext。 控制應該盡可能松散。 它應該只通過其依賴屬性獲取它的值(對於字段)。 它甚至不應該尋找像 DataContext 這樣的東西。

    <UserControl x:Class="WpfApplication1.PersonNameControl">
        <StackPanel>

            <Label>Firstname:</Label>
            <TextBox Text="{Binding Firstname, Mode=TwoWay,
                RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
            </TextBox>

            <Label>Insertion:</Label>
            <TextBox Text="{Binding Insertion, Mode=TwoWay,
                RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
            </TextBox>

            <Label>Lastname:</Label>
            <TextBox Text="{Binding Lastname, Mode=TwoWay,
                RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
            </TextBox>

        </StackPanel>
    </UserControl>

和控制類:

public partial class PersonNameControl : UserControl
{
    public PersonNameControl()
    {
        InitializeComponent();
    }

    public string Firstname
    {
        get { return (string)GetValue(FirstnameProperty); }
        set { SetValue(FirstnameProperty, value); }
    }
    public static readonly DependencyProperty FirstnameProperty =
        DependencyProperty.Register("Firstname", typeof(string), typeof(PersonNameControl), 
            new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


    public string Insertion
    {
        get { return (string)GetValue(InsertionProperty); }
        set { SetValue(InsertionProperty, value); }
    }
    public static readonly DependencyProperty InsertionProperty =
        DependencyProperty.Register("Insertion", typeof(string), typeof(PersonNameControl), 
            new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


    public string Lastname
    {
        get { return (string)GetValue(LastnameProperty); }
        set { SetValue(LastnameProperty, value); }
    }
    public static readonly DependencyProperty LastnameProperty =
        DependencyProperty.Register("Lastname", typeof(string), typeof(PersonNameControl), 
            new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}

該控件應該在另一個視圖中使用,如下所示:

<!-- 
Here we are inside a view or some other control.
The bindings here provide the dependency properties of the UserControl with a value.
The DataContext of the view where my UserControl is used, is a ViewModel that implements INotifyDataErrorInfo 
-->

<myControls:PersonNameControl 
    Firstname="{Binding SomeFirstnameFromVM, Mode=TwoWay}"
    Insertion="{Binding SomeInsertionFromVM, Mode=TwoWay}"
    Lastname="{Binding SomeLastnameFromVM, Mode=TwoWay}">
</myControls:PersonNameControl>

當 ViewModel(實現 INotifyDataErrorInfo)創建驗證錯誤時,我的PersonNameControl UserControl 沒有任何反應。 我設法制作了一個獨立的控件,因為它不依賴於特定的 DataContext,不在其代碼隱藏文件中設置自己的 DataContext,而只是通過依賴屬性獲取其值。 這些值通過綁定交換並顯示出來,但不顯示驗證錯誤。 我想要的是將驗證錯誤傳遞給 UserControl。

互聯網上的一些解決方案使用了ValidationAdornerSite ,我嘗試了這個。 但這僅適用於一個TextBox

如果不讓我的控制依賴於外部世界或引入丑陋的額外屬性來解決它,我看不到任何解決方案。 我認為這些錯誤就像一條信息,通過所有綁定到值到達的最后一個級別。 但這似乎不是正確的考慮。

編輯:

我添加了我的 ViewModel 類。

public class CustomerFormViewModel : ViewModelBase, INotifyDataErrorInfo
{
    protected string _clientNumber;
    protected DateTime _date;
    protected string _firstname;
    protected string _insertion;
    protected string _lastname;
    protected Address _address;
    protected ObservableCollection<Email> _emails;
    protected ObservableCollection<PhoneNumber> _phoneNumbers;
    protected string _note;

    protected bool _hasErrors;
    protected IList<ValidationFailure> _validationErrors;

    public IList<ValidationFailure> ValidationErrors
    {
        get { return _validationErrors; }
        set { _validationErrors = value; OnPropertyChanged("ValidationErrors"); }
    }

    public string ClientNumber
    {
        get { return _clientNumber; }
        set { _clientNumber = value; OnPropertyChanged("ClientNumber"); }
    }
    public DateTime Date
    {
        get { return _date; }
        set { _date = value; OnPropertyChanged("Date"); }
    }
    public string Firstname
    {
        get { return _firstname; }
        set { _firstname = value; OnPropertyChanged("Firstname"); }
    }
    public string Insertion
    {
        get { return _insertion; }
        set { _insertion = value; OnPropertyChanged("Insertion"); }
    }
    public string Lastname
    {
        get { return _lastname; }
        set { _lastname = value; OnPropertyChanged("Lastname"); }
    }
    public Address Address
    {
        get { return _address; }
        set { _address = value; OnPropertyChanged("Address"); }
    }
    public ObservableCollection<Email> Emails
    {
        get { return _emails; }
        set { _emails = value; OnPropertyChanged("Emails"); }
    }
    public ObservableCollection<PhoneNumber> PhoneNumbers
    {
        get { return _phoneNumbers; }
        set { _phoneNumbers = value; OnPropertyChanged("PhoneNumbers"); }
    }
    public string Note
    {
        get { return _note; }
        set { _note = value; OnPropertyChanged("Note"); }
    }

    private DelegateCommand _saveCustomerCommand;

    public DelegateCommand SaveCustomerCommand
    {
        get { return _saveCustomerCommand; }
        private set { _saveCustomerCommand = value; OnPropertyChanged("SaveCustomerCommand"); }
    }

    public CustomerFormViewModel()
    {
        ValidationErrors = new List<ValidationFailure>();
        SaveCustomerCommand = new DelegateCommand(SaveCustomer, CanSaveCustomer);
    }

    protected void ValidateInput()
    {
        ValidationErrors.Clear();

        CustomerFormValidator validator = new CustomerFormValidator();
        FluentValidation.Results.ValidationResult result = validator.Validate(this);

        ValidationErrors = result.Errors;

        foreach (ValidationFailure f in ValidationErrors)
        {
            Console.WriteLine(f.ErrorMessage);
        }

        _hasErrors = result.Errors.Count != 0;

        List<string> vmProperties = new List<string>() { "Firstname", "Lastname", "Address", "ClientNumber", "Date" };

        foreach (string propertyName in vmProperties)
        {
            OnErrorsChanged(propertyName);
        }
    }

    public bool HasErrors
    {
        get { return _hasErrors; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    protected void OnErrorsChanged(string name)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(name));
    }

    public IEnumerable GetErrors(string propertyName)
    {
        return ValidationErrors.Where<ValidationFailure>(x => x.PropertyName == propertyName);
    }

    public void SaveCustomer(object parameter)
    {
        this.ValidateInput();

        if( ! HasErrors)
        {
            Customer customer = new Customer(-1, ClientNumber, Date, Firstname, Insertion, Lastname, Address);

            ICustomerRepository repo = new CustomerRepository();
            bool res = repo.SaveCustomer(customer);

            if(res) {
                // ...
            }
            // ...

        } else
        {
            MessageBox.Show("One or more fields are not filled in correctly.", "Invalid input", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    public bool CanSaveCustomer(object parameter)
    {
        return true;
    }
}

所以,我准備了一個演示用戶控件。 它是一個子用戶控件,從其 MainViewModel 獲取所有驗證信息

在此處輸入圖片說明

主窗口

<Window
    x:Class="ValidationSubUI.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:local="clr-namespace:ValidationSubUI"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Name="MyWindow"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <local:SubUserControl
            FirstName="{Binding FirstName, Mode=TwoWay}"
            LastName="{Binding LastName, Mode=TwoWay}"
            ValidationSource="{Binding ElementName=MyWindow, Path=DataContext}" />
    </Grid>
</Window>

主視圖模型

using GalaSoft.MvvmLight;
using System.ComponentModel;

namespace ValidationSubUI
{
    public class MainViewModel : ViewModelBase, IDataErrorInfo
    {
        public string Error
        {
            get
            {
                return string.Empty;
            }
        }

        private string m_FirstName;
        public string FirstName
        {
            get { return m_FirstName; }
            set
            {
                m_FirstName = value;
                RaisePropertyChanged();
            }
        }

        private string m_LastName;
        public string LastName
        {
            get { return m_LastName; }
            set
            {
                m_LastName = value;
                RaisePropertyChanged();
            }
        }


        public string this[string columnName]
        {
            get
            {
                if (columnName == nameof(FirstName))
                {
                    return GetFirstNameError();
                }
                else if (columnName == nameof(LastName))
                {
                    return GetLastNameError();
                }

                return null;
            }
        }

        private string GetFirstNameError()
        {
            string result = string.Empty;

            if (string.IsNullOrEmpty(FirstName))
            {
                result = "First name required";
            }

            return result;
        }

        private string GetLastNameError()
        {
            string result = string.Empty;

            if (string.IsNullOrEmpty(LastName))
            {
                result = "Last name required";
            }

            return result;
        }
    }
}

SubUserControl 從 MainViewModel 獲取所有驗證邏輯

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace ValidationSubUI
{
    /// <summary>
    /// Interaction logic for SubUserControl.xaml
    /// </summary>
    public partial class SubUserControl : UserControl, IDataErrorInfo
    {
        public SubUserControl()
        {
            InitializeComponent();
        }

        public IDataErrorInfo ValidationSource
        {
            get { return (IDataErrorInfo)GetValue(ValidationSourceProperty); }
            set { SetValue(ValidationSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ValidationSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValidationSourceProperty =
            DependencyProperty.Register("ValidationSource", typeof(IDataErrorInfo), typeof(SubUserControl), new PropertyMetadata(null));



        public string FirstName
        {
            get { return (string)GetValue(FirstNameProperty); }
            set { SetValue(FirstNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FirstName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FirstNameProperty =
            DependencyProperty.Register("FirstName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));



        public string LastName
        {
            get { return (string)GetValue(LastNameProperty); }
            set { SetValue(LastNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LastName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LastNameProperty =
            DependencyProperty.Register("LastName", typeof(string), typeof(SubUserControl), new PropertyMetadata(string.Empty));


        public string Error
        {
            get
            {
                return string.Empty;
            }
        }

        public string this[string columnName]
        {
            get
            {
                if (ValidationSource != null)
                {
                    return ValidationSource[columnName];
                }

                return null;
            }
        }
    }
}

和子用戶控件

<UserControl
    x:Class="ValidationSubUI.SubUserControl"
    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"
    x:Name="CustomControl"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">

    <UserControl.Resources>

        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel>
                            <Border BorderBrush="Red" BorderThickness="1">
                                <AdornedElementPlaceholder x:Name="controlWithError" />
                            </Border>
                            <TextBlock
                                Margin="5,0,0,0"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center"
                                FontSize="12"
                                FontWeight="DemiBold"
                                Foreground="Red"
                                Text="{Binding ElementName=controlWithError, Path=AdornedElement.ToolTip, Mode=OneWay}" />
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>

    </UserControl.Resources>

    <Grid DataContext="{x:Reference Name=CustomControl}">
        <StackPanel>
            <TextBox
                Width="120"
                Height="30"
                Margin="5"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}"
                TextWrapping="Wrap" />

            <TextBox
                Width="120"
                Height="30"
                Margin="5"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}"
                TextWrapping="Wrap" />
        </StackPanel>
    </Grid>
</UserControl>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM