WPF Combo Boxes and difficulties with Error Templates

2015-12-03 at 11:07 0 comments

Recently, one of my former students contacted me about a problem he had with the WPF Combo Box. He wanted to display a hint that a wrong value is currently selected, like so:ComboBox Invalid Value

I thought this is an easy answer because WPF supports Error Templates that can be used to display additional UI Elements on the WPF Adorner Layer. But of course, the problem was not that easy to solve.

The WPF ComboBox does not display the Error Template until you select an item

The special case that the student wanted to model was that a new view was loaded and that the WPF ComboBox should display an error initially without the user ever having interacted with it. And of course, the ComboBox did not show the error template, which was a real surprise to me as I thought that the Binding is also validated when it is instantiated by the WPF Binding Engine. My student used custom validation rules in his code, but I also tried it with an MVVM approach using the INotifyDataErrorInfo interface and the outcome was the same. In fact, I could see that the error object was added to the ComboBox after the binding was established when I debugged the example, but it was not displayed until the user made a selection. Thus I guess it is a bug (or a feature?) within the ComboBox class.

Customizing the Control Template

So how do you get around this problem? I decided to customize the Combo Box’s control template, hoping that I could circumvent the bug this way. To extend the existing template, you can create a copy of it in Visual Studio 2015 by selecting the ComboBox in the Document Outline window and selecting Format >> Edit Style >> Edit a Copy… as you can see in the picture below. The Format menu only shows up when you select an item in the document outline, so don’t be afraid if you cannot see this menu immediately.

Creating a copy of the ComboBox style

You then have to select a location – I would advise you to put it in a dedicated Resource Dictionary in this case because the Control Template and the associated styles are approx. 400 lines of XAML code – but don’t get scared by that. The important part is the ControlTemplate with the x:Key “ComboBoxTemplate” which consists of a Grid called templateRoot containing a Popup, a ToogleButton, and a ContentPresenter. Below that is a Collection of ControlTemplate.Triggers that change the visual state of the control in various conditions. I won’t put the code in this post because it is rather long, but you can look at the code by using the corresponding GitHub repository.

Now my first idea was to create another trigger that uses the Validation.HasError attached property to change the border color of the ToggleButton so that the user perceives a visual feedback when validation fails. However this did not work at all – I think the problem is the style ComboBoxToggleButton that is applied to it (but I did not investigate this any further).

Therefore, the right way to do this is to introduce three new visual elements to the template: I wrapped the Grid called templateRoot in a Border whose purpose is to display a red stroke when error objects are attached to the control. Below the actual control, I want to display another TextBlock that shows some static text when an error object is attached. To display these two new elements correctly, I put them in another Grid, like so:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Border x:Name="ErrorBorder" Padding="3 2">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <!-- The actual content of the ComboBox Control Template -->
        </Grid>
    </Border>
    <TextBlock Grid.Row="1" x:Name="ErrorTextBlock" 
               Text="You have to provide a value here" 
               Margin="3 12 3 0" Visibility="Collapsed" 
               Foreground="Red" />
</Grid>

The only thing that’s left is the additional Trigger. As stated before, the actual error object is set on the ComboBox, thus one can just use the Validation.HasError property to set the border color and the visibility of the TextBlock:

<ControlTemplate.Triggers>
    <Trigger Property="Validation.HasError" Value="True">
        <Setter Property="Background" Value="Red" TargetName="ErrorBorder" />
        <Setter Property="Visibility" Value="Visible" TargetName="ErrorTextBlock" />
    </Trigger>

    <!-- Other triggers that came with the original template are listed here -->
</ControlTemplate.Triggers>

And that’s mainly it – the ComboBox displays a notification even when the user has not selected an item yet and at least one error is present:

Combo Box Displaying Error Without The User Selecting An Item

Please note that you should explicitly set the Validation.ErrorTemplate property to x:Null to ensure that no error template is used.

Where to go from here

Of course, this is only a simple example illustrating how you can circumvent the Error Template problem with ComboBoxes, but you should extend this example to make it production-ready. Here are some hints for improvements:

  • You can fade in and fade out the error related elements (instead of just showing and hiding them) by providing Storyboards to the Trigger EnterActions and ExitActions. This animation should run relatively fast, so choose a animation duration of about 200 ms to 400 ms.
  • Instead of just showing static text, you could display all the errors that are associated with the corresponding property in the view model. To achieve this goal, you could exchange the TextBlock with an ItemsControl that binds to the Validation.Errors attached property and provide a Data Template for error objects. This way you can easily display several error messages for a property if your View Model also implements INotifyDataErrorInfo.

Too long, didn’t read

The Error Template for WPF Combo Boxes works fine, except when it should be displayed after the ComboBox is loaded and before the user selected an item. One way to circumvent this problem is to extend the Control Template of the Combo Box with additional elements being bound to the attached properties of the Validation class. Please ensure in this case to set Validation.ErrorTemplate to x:Null on the ComboBox.

Check out the source code for this post on GitHub.

Comments

Currently there are no comments for this post. Write the first one!

Leave a Reply

Your email address will not be published. Required fields are marked *