对UserControl进行验证工作

时间:2014-12-19 17:22:23

标签: c# wpf validation xaml idataerrorinfo

我有以下用户控件(现在是Realy一个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;                                
        }
    }
}

当我使用&#34; usercontrol&#34;为:

 <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}" />

但验证错误不再触发:(。换句话说,如果我在哪里输入&#34; foo foo&#34;文本框将在第一个示例中变为橙色但不在最后一个示例中,根控件是用户控件而不是TextBox。

我怎样才能使验证工作?


修改

感谢alek kowalczyk的回答,我搜索了他的解决方案,因为我不理解他的答案,并提出了这个解决方案:

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

2 个答案:

答案 0 :(得分:1)

您的UserControl的DataContext与您的WindowControl不同,因此验证错误没有到达文本框,我建议您从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>

答案 1 :(得分:1)

您的问题出在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,并将在“Focus Leave”上更新。您需要在元数据中覆盖它。

将Txt绑定到文本 - 告诉此绑定是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));
}
}