Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't WPF ComboBox reflect properly the bound value?

I have a ComboBox whose properties ItemsSource and SelectedValue are bound to a model. Sometimes, the model needs to adjust the selected item to a different one, but when I do it in the model, the model value is not being reflected in the View, even though the SelectedValue is properly set (checked both with snoop and in SelectionChanged event handler).

To illustrate the problem, here is a simple xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
      <ComboBox Height="25" Width="120" SelectedValue="{Binding SelectedValue}" SelectedValuePath="Key" ItemsSource="{Binding PossibleValues}" DisplayMemberPath="Value"/>
   </Grid>
</Window>

And here is the model:

using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;

namespace WpfApplication1
{
   public partial class MainWindow : Window, INotifyPropertyChanged
   {
      int m_selectedValue = 2;
      Dictionary<int, string> m_possibleValues = new Dictionary<int, string>() { { 1, "one" }, { 2, "two" }, { 3, "three" }, {4,"four"} };

      public int SelectedValue
      {
         get { return m_selectedValue; }
         set 
         {
            if (value == 3)
            {
               m_selectedValue = 1;
            }
            else
            {
               m_selectedValue = value;
            }
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
         }
      }
      public Dictionary<int, string> PossibleValues
      {
         get { return m_possibleValues; }
         set { m_possibleValues = value; }
      }


      public MainWindow()
      {
         InitializeComponent();
      }

      public event PropertyChangedEventHandler PropertyChanged;
   }
}

I Expected the behaviour to be as follows:

  1. Initially, two is selected
  2. Select "one" -> ComboBox displays "one"
  3. Select "two" -> ComboBox displays "two"
  4. Select "three" -> ComboBox displays "one"
  5. Select "four" -> ComboBox displays "four"

However, in #4, "three" is displayed. Why? The value in the model was changed to 1 ("one"), but the View still displays 3 ("three").

I have found a workaround by explicitly updating the binding target in a SelectionChanged event handler, but this seems wrong. Is there another way to achieve this?

like image 616
Rado Avatar asked Apr 09 '12 20:04

Rado


2 Answers

When you select an item, the binding engine will update the model, and will ignore any PropertyChanged for the property it just changed during the time it's invoking the property setter. If it didn't, things could potentially go into an infinite loop.

The system assumes that the setter won't change the value it was passed. The workaround here is simple: use Dispatcher.BeginInvoke() or set IsAsync=True on your binding to get the behavior you're looking for. However, I personnaly strongly discourage you to do this. Not only because it introduces a whole bunch of new problems related to timings, but mainly because it's a workaround to a problem that shouldn't exist in the first place.

Simply don't do this. It's not only applying to WPF: anything changing a setter could expect that the value it just set was correctly set is no exception is thrown. Changing the value inside the setter is counter intuitive and could bite you later. Plus, it can be really disturbing for the user of your application to see the combobox ignoring its selection.

If you don't like the value being passed, throw an exception and use Binding.ValidatesOnExceptions to tell WPF that it's expected. If you don't want the user to select this value, don't put it inside the list in the first place. If the item shouldn't be available conditionally because of other business rules, filter the list dynamically or apply triggers.

like image 164
Julien Lebosquain Avatar answered Oct 06 '22 13:10

Julien Lebosquain


I have had persistent problems with proper updating of the control when binding to the SelectedValue and/or SelectedItem properties of a ComboBox control.

To solve this, I had to bind to the SelectedIndex property instead. It was more hassle to deal with an 'index' property in my ViewModel, but it solved the problem with the ComboBox not updating.

like image 2
Stewbob Avatar answered Oct 06 '22 12:10

Stewbob