Провести валидацию в UserControl

У меня есть следующий пользовательский элемент управления (действительно элемент управления TextBox сейчас):

<TextBox:Class="IM.Common.UIControls.IMTextBox"
             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" 
             >
        <Validation.ErrorTemplate>
            <ControlTemplate>                    
                    <!--Show this if there is a validation error-->
                    <StackPanel Orientation="Horizontal" ToolTip="{Binding [0].ErrorContent}"  >
                        <Border BorderThickness="2" BorderBrush="Orange"  >
                            <AdornedElementPlaceholder Margin="-1"   />
                        </Border>
                    </StackPanel>                        
            </ControlTemplate>
        </Validation.ErrorTemplate>    
</TextBox>

Код позади:

namespace IM.Common.UIControls
{
    public partial class IMTextBox 
    {
        public IMTextBox()
        {
            InitializeComponent();
        }
     }
  }

У меня есть следующая модель:

public class User : IDataErrorInfo, INotifyPropertyChanged
{        
    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    #endregion

    // used just to know if passwords match
    public string Password2
    {
        get { return _password2; }
        set
        {                
            _password2 = value;
            OnPropertyChanged("Password2");
        }
    }
    private string _password2;

    public string Error
    {
        get
        {
            throw new NotImplementedException();                
        }
    }

    public string this[string columnName]
    {
        get
        {                
            if (columnName == "Password2")
            {
                if (string.IsNullOrEmpty(Password2))
                    return "required";
                if (Regex.Match(Password2, "\\s").Success)
                    return "Password cannot contain spaces";
            }

            return null;                                
        }
    }
}

Когда я использую этот usercontrol как:

 <myControls:IMTextBox Text="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />

Это работает потрясающе! Ошибки проверки показывают, и это работает как ожидалось.

Теперь вот моя проблема:/

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

<UserControl:Class="IM.Common.UIControls.IMTextBox"
             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" 
             >
  <StackPanel>
    <TextBlock Text="{Binding LabelTxt}" />
    <TextBox Text="{Binding Txt, ValidatesOnDataErrors=true, NotifyOnValidationError=true}">
        <Validation.ErrorTemplate>
            <ControlTemplate>                    
                    <!--Show this if there is a validation error-->
                    <StackPanel Orientation="Horizontal" ToolTip="{Binding [0].ErrorContent}"  >
                        <Border BorderThickness="2" BorderBrush="Orange"  >
                            <AdornedElementPlaceholder Margin="-1"   />
                        </Border>
                    </StackPanel>                        
            </ControlTemplate>
        </Validation.ErrorTemplate>    
    </TextBox>
  </StackPanel>
</UserControl>

Код позади теперь выглядит так:

namespace IM.Common.UIControls
{
    public partial class IMTextBox : UserControl
    {
        public IMTextBox()
        {
            InitializeComponent();

            this.DataContext = this;
        }

        public string Txt
        {
            get
            {
                return (string)GetValue(TxtProperty);
            }
            set
            {
                SetValue(TxtProperty, value);
            }
        }
        public static DependencyProperty TxtProperty = DependencyProperty.Register(
                name: "Txt",
                propertyType: typeof(string),
                ownerType: typeof(IMTextBox),

                typeMetadata: new FrameworkPropertyMetadata(
                    defaultValue: string.Empty
                )
        );          
    }
}

Теперь, когда я пытаюсь использовать usercontrol, я могу сделать:

<myControls:IMTextBox Txt="{Binding SomeUser.Password2, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" />

Но ошибка проверки больше не генерируется:( . Другими словами, если я введу "foo foo", текстовое поле станет оранжевым в первом примере, но не в последнем примере, где корневым элементом управления является UserControl вместо TextBox.

Как я могу все еще сделать проверку работы?


редактировать

Благодаря ответу alek kowalczyk я погуглил его решение, потому что не понял его ответа и придумал это решение:

http://dutton.me.uk/tag/xnamepart_contenthost/

2 ответа

Ваша проблема в привязке UserControl.

<TextBox Text="{Binding Txt, Mode=TwoWay, NotifyOnValidationError=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}">

и в объявлении свойства зависимостей.

public static DependencyProperty TxtProperty = DependencyProperty.Register("Txt", typeof(string), typeof(IMTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, null , false, UpdateSourceTrigger.PropertyChanged)

Когда вы связываете свойство Txt со свойством TextBox.Text - TextBox не знает контекст, в котором он должен найти свойство Txt. Вы должны сказать, что это свойство существует в родительском элементе типа IMTextBox. Кроме того, свойство Txt имеет привязку по умолчанию OneWay и будет обновлено в "Оставить фокус". Вам нужно переопределить его в метаданных.

В Binding Txt to Text - скажите, что эта привязка является TwoWay и будет обновляться при каждом изменении.

UPD: рабочий пример: xaml:

    <UserControl x:Class="IM.Common.UIControls.IMTextBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:IM.Common.UIControls">
    <StackPanel>
    <TextBox Name="tb" Text="{Binding Txt, Mode=TwoWay, NotifyOnValidationError=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Validation.ErrorTemplate="{x:Null}">
    </TextBox>
    <StackPanel Orientation="Vertical">
        <ItemsControl ItemsSource="{Binding Path=(Validation.Errors), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:IMTextBox}}}">
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type ValidationError}">
                    <Border BorderThickness="2" BorderBrush="Green"  >
                        <TextBlock Text="{Binding ErrorContent}"></TextBlock>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" Background="Green"></StackPanel>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
        <ContentPresenter></ContentPresenter>
    </StackPanel>
</StackPanel>

CS:

namespace IM.Common.UIControls
{
public partial class IMTextBox : UserControl
{
    public IMTextBox()
    {
        InitializeComponent();
    }

    public string Txt
    {
        get
        {
            return (string)GetValue(TxtProperty);
        }
        set
        {
            SetValue(TxtProperty, value);
        }
    }
    public static DependencyProperty TxtProperty = DependencyProperty.Register("Txt", typeof(string), typeof(IMTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, null, false, UpdateSourceTrigger.PropertyChanged));
}
}

DataContext вашего UserControl отличается от вашего Window, поэтому ошибка проверки не попадает в текстовое поле, я бы предложил создать пользовательский элемент управления, полученный из TextBox, вместо пользовательского элемента управления.

Здесь у вас есть шаблон элемента управления для текстового поля с меткой, вы можете сохранить шаблон элемента управления в словаре ресурсов, если вы хотите использовать его в нескольких текстовых полях:

    <TextBox Text="{Binding txt}">
        <TextBox.Template>
                    <ControlTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding labelTxt}" />
                    <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
                </StackPanel>
            </ControlTemplate>
        </TextBox.Template>
    </TextBox>
Другие вопросы по тегам