navigation
 Wednesday, September 03, 2008

In a previous post, I explained how to use WPF validation rules and error templates to validate user input and provide meaningful feedback for invalid input in the form of tooltips.  Recently, I needed to use these techniques on a WPF textbox which already had non-error tooltip, and observed an unexpected behavior.  The local tooltip overrides the error tooltip, even when an error occurs.  For example, using the code from my previous blog, the following XAML adds a tooltip to the validated TextBox.

<TextBox Margin="10" Width="100" ToolTip="Enter the number of seconds">
    <TextBox.Text>
        <Binding Path="seconds" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:posintValidationRule/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Unfortunately, now my tooltip which displays the validation error no longer appears, although the rest of the error template still works.

image

This behavior actually makes sense if you consider the WPF property precedence rules concerning styles.  Any property that is set locally on the control will always override any setting in the style.  With the precedence rules in mind, there are a few ways we can correct the TextBox's behavior.  As long as the tooltip values are set at the same precedence level, neither value will override the other.

If multiple TextBoxes will use the same standard tooltip when not in an error state, a derivative style would be a simple fix.

<Style x:Key="tbValidatedNumber" TargetType="{x:Type TextBox}" BasedOn="{StaticResource tbValidated}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="false">
            <Setter Property="ToolTip"
                Value="Enter a number"/>
        </Trigger>
    </Style.Triggers>
</Style>

If you would rather keep your tooltip value inside the control locally, or if each tooltip is different, you could accomplish the same thing by putting the derived style inside the TextBox.

<TextBox Margin="10" Width="100">
    <TextBox.Text>
        <Binding Path="seconds" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:posintValidationRule/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <TextBox.Style>
        <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource tbValidated}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="false">
                    <Setter Property="TextBox.ToolTip"
                Value="Enter a number"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>                        
</TextBox>

Either option will fix our problem, and let both tooltips work properly.

image  image

An alternative solution is to add an additional element to the error template, and show the error message as the new element's tooltip, as shown below.

<Style x:Key="tbValidated" TargetType="{x:Type TextBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Border Background="{TemplateBinding Background}" 
                        x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="5">
                    <ScrollViewer x:Name="PART_ContentHost"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <StackPanel Orientation="Horizontal">
                <Border BorderBrush="Yellow" BorderThickness="1" CornerRadius="5">
                    <AdornedElementPlaceholder x:Name="adorner"></AdornedElementPlaceholder>
                </Border>
                <TextBlock Margin="2,0,0,0" Foreground="Yellow" 
                           ToolTip="{Binding ElementName=adorner, 
                    Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">*</TextBlock>
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now the error message is shown as the tooltip of the star element displayed to the right of the TextBox when a validation fails.  Using the above style, tooltip text can be set locally without affecting the error template behavior.

image  image

There are probably even more ways to achieve similar results, but I have found that one of the three listed here usually meets my needs and allows me to display validation messages easily without sacrificing descriptive tooltips on my controls.