Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

The Case of the Confused ComboBox – A WPF/MVVM Bedtime Story

Once upon a time there were four friends – two TextBoxes and two ComboBoxes. They all lived happily on a WPF Window bound together in blissful MVVM harmony. The TextBoxes were bound to the ViewModel’s TextBoxText property and the ComboBoxes to the ComboBoxSelectedItem property.

MVVM ComboBox

<Window x:Class="MvvmComboBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mvvm="clr-namespace:MvvmComboBox"
        Title="MVVM ComboBox" Width="300" Height="150">
    <Window.Resources>
        <mvvm:MainWindowViewModel x:Key="viewModel"/>
    </Window.Resources>
    <StackPanel DataContext="{StaticResource viewModel}">
        <TextBox Text="{Binding TextBoxText.Value}"/>
        <TextBox Text="{Binding TextBoxText.Value}"/>
        <ComboBox ItemsSource="{Binding ComboBoxItems}"
                SelectedItem="{Binding ComboBoxSelectedItem.Value}"/>
        <ComboBox ItemsSource="{Binding ComboBoxItems}"
                SelectedItem="{Binding ComboBoxSelectedItem.Value}"/>
    </StackPanel>
</Window>

TextBoxText and ComboBoxItem are both Observable<T> so that INotifyPropertyChanged notifications are broadcast properly to all interested parties.

public class Observable<T> : INotifyPropertyChanged where T:class {
    public event PropertyChangedEventHandler PropertyChanged = (o,e) => { };

    private T value;
    public T Value {
        get { return value; }
        set {
            if(this.value == value) return;
            this.value = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Value"));
        }
    }
}

ASIDE: Notice the event definition in line 2. By assigned a NOP (no operation) lambda, I don’t have to check whether any listeners are registered for the event. I can simply fire the event and worse case scenario, I execute an empty function body rather than throwing a NullReferenceException. This is tidier in my opinion than the if(PropertyChanged != null) nonsense that we’ve been using for years.

The view model is similarly simple.

public class MainWindowViewModel {
    public MainWindowViewModel() {
        TextBoxText = new Observable<string>();
        ComboBoxSelectedItem = new Observable<string>();
    }

    public Observable<string> TextBoxText { get; set; }
    public Observable<string> ComboBoxSelectedItem { get; set; }

    public IEnumerable<string> ComboBoxItems {
        get {
            yield return "One";
            yield return "Two";
            yield return "Three";
            yield return "Four";
            yield return "Five";
        }
    }
}

With the INotifyPropetyChanged in place, changing one TextBox will cause its twin to display the same value and same for the two ComboBoxes.

MVVM ComboBox synchronized

We get a strange new requirement that every second change should be ignored. (The actual business requirement was more sensible. I was displaying a list of entities for editing. If the previous entity wasn’t saved, the user would be presented with a save dialog and standard “yes/no/cancel” options. On cancel, I wanted to disregard the ComboBox change and revert back to the previously selected entity.)

public class Observable<T> : INotifyPropertyChanged where T:class {
    public event PropertyChangedEventHandler PropertyChanged = (o,e) => { };

    private T value;
    private int count;
    public T Value {
        get { return value; }
        set {
            if(this.value == value) return;

            if(count++ % 2 == 0) return;

            this.value = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Value"));
        }
    }
}

So based on business rules, we’ve ignored a change to the view model. The TextBoxes always remain synchronized – reverting back to the previous value if the change is ignored. Unfortunately the changed ComboBoxes becomes unsynchronized with the view model if the view model ignores a change.

MVVM ComboBox unsynchronized

If you examine the view model in the debugger, the TextBoxText is “Hello, world! Hello, again!” as expected. You can change either TextBox as many times as you want. Every other edit is ignored and causes its changes to revert, as expected. Examining the current state of the view model’s ComboBoxSelectedItem, its value is “Four” and not “Two”. I selected “Two” in the first ComboBox, which was the ignored change. The first ComboBox is now unsynchronized with the view model. If you change the selection again, both ComboBoxes have the correct value. The problem is only with changes ignored by the view model.

Let’s create a new type derived from ComboBox to fix this problem.

namespace JamesKovacs.MvvmComboBox {

 

    public class ComboBox : System.Windows.Controls.ComboBox {
        protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
            base.OnSelectionChanged(e);
            var binding = GetBindingExpression(SelectedItemProperty);
            if(binding != null) {
                binding.UpdateTarget();
            }
        }

 

    }
}

I perform the normal OnSelectionChanged event and then update the ComboBox’s SelectedItemProperty from the bound ViewModel if a binding exists. This ensures that if the view model ignores changes or modifies the selection in some way, the ComboBox displays the correct value. Minor note: I am defining a derived ComboBox in my own namespace, which is also called ComboBox. I do this so that in my XAML, I can apply the fix by prepending <ComboBox/> with my namespace <mvvm:ComboBox/>. With this fix in place, my MVVM ComboBox remains synchronized with its view model.

image

In most cases, MVVM model binding dramatically simplifies your WPF code. Unfortunately there are some cases where bugs in the framework prevent it from working properly. Fortunately in many cases you can work around these limitations by simply deriving your own custom control from the one supplied with WPF to fix these problems.

For those of you who want to play around with the code, you can find it here. Happy coding!

ADDENDUM: As noted by Michael L. Perry in this comment, WPF behaviours can also be used to inject code without subclassing ComboBox. The code sample now includes his behaviour code in addition to the subclassing option. Also to note is that a bug in .NET 4.0 prevents the fix from working. The ComboBox (regulard, subclassed, or behavioured) becomes unsynchronized from the view model. The subclassing code works correctly in .NET 3.5. If there are any WPF gurus out there who can understand why it works in .NET 3.5, but not .NET 4.0, I would love to hear an explanation.

About James Kovacs

James Kovacs is a Technical Evangelist for JetBrains. He is passionate in sharing his knowledge about OO, SOLID, TDD/BDD, testing, object-relational mapping, dependency injection, refactoring, continuous integration, and related techniques. He blogs on CodeBetter.com as well as his own blog, is a technical contributor for Pluralsight, writes articles for MSDN Magazine and CoDe Magazine, and is a frequent speaker at conferences and user groups. He is the creator of psake, a PowerShell-based build automation tool, intended to save developers from XML Hell. James is the Ruby Track Chair for DevTeach, one of Canada’s largest independent developer conferences. He received his Bachelors degree from the University of Toronto and his Masters degree from Harvard University.
This entry was posted in .NET, WPF. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.mindstick.com/ ashish

    Thanks for your useful post.
    I also refer very helpful article about WPF ComboBox
    Visit this helpful article.
    http://www.mindstick.com/blog/156/WPF%20ComboBox#.VgPdsZc0Xcc
    https://msdn.microsoft.com/en-us/library/system.windows.controls.combobox%28v=vs.110%29.aspx

  • Pravesh Singh

     Hi,

    I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of WPF Combox Control. It helped me lot in completing my task. There are some articles too which helped me lot as….
    http://msdn.microsoft.com/en-us/library/ms753382%28v=vs.85%29.aspx

    http://mindstick.com/Articles/347881ff-3980-4d5a-9481-42e34a8d8c0e/?Combo%20Box%20control%20in%20WPF

    http://www.c-sharpcorner.com/uploadfile/mahesh/wpf-combobox/

  • Derek Wickern

    I would advise against using BeginInvoke in this way as it can cause unpredictable behavior. For example, if you set the property twice:
    MyProperty = 1; // should fire PropertyChanged, but dispatcher thread is busy
    MyProperty = 2;
    // PropertyChanged fires (value == 2)
    // PropertyChanged fires again (value == 2)

    It should not be necessary to fire PropertyChanged on the dispatcher thread because WPF already dispatches these under the hood with bindings.

    Also, you will not be able to unit test the view model as Application.Current is null.

  • Lars

    Hi, we queued the call to propertychanged of our property “TrackingMode” on the dispatcherthread, that made the combo update correctly:

                Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                                                                    {
                                                                        OnPropertyChanged(“TrackingMode”);
                                                                    }));

  • Michael Studer

    Trying to synchronize the SelectedItem between ViewModel und ComboBox in .Net 4.0, I faced the same problem with the malfunctioning binding.UpdateTarget().

    However, when I replaced the “return” statement with an Exception, UpdateTarget() works:
    public T Value {    get { return value; }    [System.Diagnostics.DebuggerStepThrough()]    set {        if(this.value == value) return;        if(count++ % 2 == 0) throw new ArgumentException(“Value”);        this.value = value;        PropertyChanged(this, new PropertyChangedEventArgs(“Value”));    }}

    The DebuggerStepThroughAttribute prevents Visual Studio from complaining about unhandled exceptions.

  • Derek Wickern

    I’ve found that the following works in WPF 4.0:
    – Use the derived ComboBox or behavior that updates the binding target.
    – In the property setter, throw an exception in order to cancel the change.

  • Anonymous

    I can reproduce your results. The fix works in 3.5, but not in 4. My best guess is that Microsoft “optimized” the combo box in 4. I remember Rocky blogging about a similar optimization a while back.

  • Dump

    You can also do:

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    which has the very slight advantage that you don’t need to know the number of parameters.

  • Anonymous

    I just tried it again – both your behaviour code and my subclassed code – and both are failing now in .NET 4.0. My subclassed code works if you change the framework version to .NET 3.5. I’m stumped as to why it would work in .NET 3.5, but not .NET 4.0. Any ideas?

  • Anonymous

    Thanks for the patch. I haven’t really dug into behaviours in WPF, but that sounds like a good route to go.

    As for the fix not working… I had a problem with the fix working in .NET 3.5, but not .NET 4.0 awhile back. I’m not sure which Windows or Framework update fixed the problem, but it is now working on my machine. So make sure you’ve got all updates applied and also try switching to .NET 3.5. HTH.

  • Anonymous

    I sent you a pull request. Rather than subclassing the combo box, you can use a behavior to add this fix. But I must admit that your fix (whether applied through base class or behavior) didn’t seem to work on my system. The combo box still accepted the change, even when the view model didn’t.