runerback
runerback

Reputation: 483

wpf custom control not show validation error

Validation error will not show if I transfer Binding through Dependency Property in custom control.

DETAIL

I have a viewmodel which always have a validation error on one property

class ViewModel : IDataErrorInfo
{
    public string Value { get; set; }

    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get { return "Error"; }
    }
}

and a TextBox on view

<TextBox 
    Text="{Binding Value, 
        Mode=TwoWay, 
        UpdateSourceTrigger=PropertyChanged, 
        ValidatesOnDataErrors=True, 
        NotifyOnValidationError=True}" />

so it will be surrounded by a red border.

ui picture

Then I created a custom control named WrappedTextBox which contains a Text Dependency Property

class WrappedTextBox : Control
{
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(
            "Text",
            typeof(string),
            typeof(WrappedTextBox));
}

and template

<Style TargetType="local:WrappedTextBox">
    <Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:WrappedTextBox">
                <Grid>
                    <AdornerDecorator>
                        <TextBox
                            Text="{Binding Text, 
                                Mode=TwoWay, 
                                UpdateSourceTrigger=PropertyChanged, 
                                ValidatesOnDataErrors=True, 
                                NotifyOnValidationError=True, 
                                RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                    </AdornerDecorator>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

put it on view

<local:WrappedTextBox
    Text="{Binding Value, 
        Mode=TwoWay, 
        UpdateSourceTrigger=PropertyChanged, 
        ValidatesOnDataErrors=True, 
        NotifyOnValidationError=True}" />

as the picture above shows, the second control has no red border on it.

if I don't remove Validation.ErrorTemplate of WrappedTextBox, it will be

picture 2

How do I show error template on TextBox inside WrappedTextBox?

Upvotes: 0

Views: 1450

Answers (1)

nosale
nosale

Reputation: 818

As far as I know your problem is that IDataErrorInfo needs to be implemented on the class you are binding to, in your ControlTemplate you are binding to the Text-Property of your WrappedTextBox, therefore your WrappedTextBox itself has to implement IDataErrorInfo to make validation work on your TextBox.

Read also this article what you also can do instead of creating a new control.

For what you want to achieve 2 options came to my mind (Note: This options are created under the assumption that you are doing more stuff so the options of the previously mentioned article does not apply to you)

Option 1: Derive direct from TextBox

Code behind:

class WrappedTextBox : TextBox
{

}

Style:

<Style TargetType="local:WrappedTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <AdornerDecorator>
                    <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                    </Border>
                </AdornerDecorator>
                <-- ControlTemplate.Triggers etc. -->
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Usage: (same as before)

<local:WrappedTextBox
    Text="{Binding Value, 
        Mode=TwoWay, 
        UpdateSourceTrigger=PropertyChanged, 
        ValidatesOnDataErrors=True, 
        NotifyOnValidationError=True}" />

Option 2: Pass the Binding itself

Code behind:

[TemplatePart(Name = "PART_TEXTBOX", Type = typeof(TextBox))]
class WrappedTextBox : Control
{
    private TextBox _partTextBox;

    private BindingBase _textBinding;
    public BindingBase TextBinding
    {
        get => _textBinding;
        set
        {
            if (_textBinding != value)
            {
                _textBinding = value;
                ApplyTextBinding();
            }
        }
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _partTextBox = base.GetTemplateChild("PART_TEXTBOX") as TextBox;
        ApplyTextBinding();
    }

    private void ApplyTextBinding()
    {
        if (_partTextBox != null)
            BindingOperations.SetBinding(_partTextBox, TextBox.TextProperty, _textBinding);
    }
}

Style:

<Style TargetType="local:WrappedTextBox">
    <Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:WrappedTextBox">
                <Grid>
                    <AdornerDecorator>
                        <TextBox x:Name="PART_TEXTBOX" />
                    </AdornerDecorator>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Usage:

<local:WrappedTextBox
    TextBinding="{Binding Value, 
        Mode=TwoWay, 
        UpdateSourceTrigger=PropertyChanged, 
        ValidatesOnDataErrors=True, 
        NotifyOnValidationError=True}" />

Upvotes: 1

Related Questions