February 20, 2009, 10:30 pm
Update: Sample source code demonstrating this technique can be downloaded here: WpfNullableComboBox.zip
By default, combo boxes in WPF have some really annoying behaviour. When the control is initialized, if the SelectedItem is Nothing then the default selection will be blank, but as soon as you choose an item in the combo box, you can not reselect the blank/null option. One quick way around this is to add a null placeholder object to your ItemsSource. There are a few problems with this approach though:
- The null placeholder cannot actually be
Nothing/null or else selecting the value will have no affect. Instead, it needs to be some object that represents “null”. This means that if you want your setter on the property bound to the SelectedItem to be set to null, you need to convert the object representing null to actually be Nothing/null.
- Ideally, you should not have to alter the collection in your model view/controller/presenter that is bound to the
ItemsSource just to add a null option. It would be better if we could just specify in XAML that this combo box should have a null option that actually sets the SelectedItem to Nothing.
Since we don’t want to alter the collection in the controller and we cannot have a combo box item of Nothing (we need a null place holder object instead) but we don’t want the SelectedItem property to ever have the null place holder object as its value (we want it to just be Nothing when that is chosen) we can do one of two things:
- Use two converters: one on the
ItemsSource to add in the null place holder object and one on SelectedItem to convert the place holder to Nothing.
- Create a user control that acts as a wrapper around the combo box control. All the necessary logic could be handled within the user control.
Option one would look like this:
<ComboBox
ItemsSource="{Binding MyItems, Converter={StaticResource addNullPlaceHolderConverter}}"
SelectedItem="{Binding MySelectedItem, Converter={StaticResource placeHolderToNullConverter}}" />
In my opinion, that method really sucks. You have to add the converters in you resources section and then specify them in two places. Another issue is that we could run into some major converter explosion if it turns out that you already need some other converter on one of the properties. Then you have to make a new converter that combines the two. I don’t like it.
Option two looks like this:
<local:NullableComboBox
ItemsSource="{Binding MyItems}"
SelectedItem="{Binding MySelectedItem}" />
Much better!
The XAML for the user control is extremely simple. You just need to create a combo box with a name:
<UserControl x:Class="NullableComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ComboBox x:Name="combo" />
</UserControl>
In the code-behind you need to expose two dependency properties: SelectedItem and ItemsSource so that the control has the same interface as a regular combo box.
By listening to the combo box’s SelectionChanged event you can update the SelectedItem property on the NullableComboBox except with the place holder converted to Nothing.
February 12, 2009, 8:26 pm
By default, validation is not enforced on a binding until after the value has been changed once. Consider this situation: A form has a text box whose contents should not be empty, but its starting value is the empty string. The text box will not show an error when the form is loaded. Instead, it will only show its error style after the user types in some text and then deletes it. Some people would call this a feature, but I prefer to have the validation checked right away so that I immediately know what the required fields are, and which starting values are invalid.
A quick hack
One way to tackle this is to hook a loaded event to the control and then call UpdateSource() from code:
Private Sub myTextBox_Loaded(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs) Handles myTextBox.Loaded
BindingOperations.GetBindingExpression(myTextBox, _
TextBox.TextProperty).UpdateSource()
End Sub
Unfortunately, this really isn’t a desirable solution because it requires some pretty ugly code-behind (not that there’s any other kind) and needs to be done over again for every binding. What we really want is for this functionality to be a part of the binding markup extension.
Creating a wrapper around the binding markup extension
At first I thought it would be easy to just inherit from the Binding class and override ProvideValue, but of course, it is marked as NotOverridable (sealed in C#). Instead, I create a class called ValidationBinding that inherits directly from MarkupExtension and has the new class manage an instance of Binding. The code looks like this:
Imports System.Windows.Markup
Public Class ValidationBinding
Inherits MarkupExtension
Private _binding As New Binding
Private _dependencyObject As DependencyObject
Private _dependencyProperty As DependencyProperty
Public Sub New()
_binding.ValidatesOnDataErrors = True
_binding.ValidatesOnExceptions = True
End Sub
Public Sub New(ByVal path As String)
Me.New()
_binding.Path = New PropertyPath(path)
End Sub
Public Overrides Function ProvideValue _
(ByVal serviceProvider As System.IServiceProvider) As Object
Dim valueTarget = _
DirectCast(serviceProvider.GetService(GetType(IProvideValueTarget)), _
IProvideValueTarget)
_dependencyObject = valueTarget.TargetObject
_dependencyProperty = valueTarget.TargetProperty
If TypeOf _dependencyObject Is FrameworkElement Then
Dim element = DirectCast(_dependencyObject, FrameworkElement)
If element.IsLoaded Then
ForceValidation()
Else
AddHandler element.Loaded, AddressOf ElementLoaded
End If
Else
ForceValidation()
End If
Return _binding.ProvideValue(serviceProvider)
End Function
Private Sub ForceValidation()
BindingOperations.GetBindingExpression(_dependencyObject, _
_dependencyProperty).UpdateSource()
End Sub
Private Sub ElementLoaded(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
ForceValidation()
End Sub
Public Property Path() As PropertyPath
Get
Return _binding.Path
End Get
Set(ByVal value As PropertyPath)
_binding.Path = value
End Set
End Property
... the rest of the binding properties go here
End Class
The binding can then be used like this (where my is an imported namespace containing ValidationBinding):
<TextBox Margin="5" Text="{my:ValidationBinding Path=Text}" />
As an example, I have exposed the Binding’s Path property, but you actually have to do this for all of the public properties in Binding. A reference of all the properties that should be implemented can be found here.
ProvideValue returns the result of the Binding’s ProvideValue function, but first it checks whether the binding target has finished loading. If it has already finished loading then it forces validation by calling UpdateSource() on the target. In the much more likely scenario that the control has not yet loaded (this will be the case when you set your binding in XAML) it attaches a handler to the Loaded event so that the ForceValidation subroutine can be deferred until it is finished loading.
Also, notice that I set both ValidatesOnDataErrors and ValidatesOnExceptions to True in the constructor so that I didn’t need to specify it in the XAML. Chances are that whenever you use this markup extension you will want those enabled anyways.
It may seem like a lot of work, but it is a very reusable solution that gives you significantly more power.