1

I have a ComboBox of Employers. Upon selecting an Employer, a table is populated with Employer specific data:

        <ComboBox Name="EmployerListBox"
                  IsReadOnly="True"
                  ItemsSource="{Binding EmployerCollection, UpdateSourceTrigger=PropertyChanged}"
                  SelectedItem="{Binding SelectedEmployer, UpdateSourceTrigger=PropertyChanged}" 
                  Width="150" />

Here is the property to which it is binding and a method that checks to see if the table is dirty. If the table is dirty, then the user is prompted that changes will be lost if they change Employer:

    /// <summary>
    /// Selected Employer
    /// </summary>
    public String SelectedEmployer
    {
        get
        {
            return _SelectedEmployer;
        }
        set
        {
            if (_SelectedEmployer != value && CanChangeEmployer())
            {
                _SelectedEmployer = value;
                NotifyPropertyChanged(m => m.SelectedEmployer);
                GetGarnishmentsTableView();
            }
        }
    }
    private String _SelectedEmployer = "";


    /// <summary>
    /// Method that executes each time user wants to change employers
    /// </summary>
    public Boolean CanChangeEmployer()
    {
        Boolean _returnValue = true;

        if (GarnishmentsTableIsDirty)
        {
            _returnValue = false;

            MessageBoxResult _change =
                MessageBox.Show("There are unsaved changes.  " +
                                "Changing Employers will lose any unsaved changes.  \n\n" +
                                "Are you sure you want to change Employers?", "Unsaved Changes", MessageBoxButton.YesNo);
            if (_change == MessageBoxResult.Yes)
            {
                // OK to switch employers
                _returnValue = true;
            }
        }

        return _returnValue;
    }

Everything appears to work correctly:

  • User selects Employer ('KMH') which updates table.
  • User makes a change to the table.
  • User then selects a different Employer ('MPC')
  • User is prompted that changes will be lost
  • User selects 'No' and CanChangeEmployers returns 'false'
  • SelectedEmployer does NOT change (skips the if {} block)

Yet back in the GUI the Employer option changes to the Employer the user selected ('MPC'), even though SelectedEmployer hasn't changed.

When I Snoop the ComboBox, I see that that ComboBox SelectedItem is correctly set to the original Employer ('KMH') but SelectedValue and SelectionBoxItem are both set to the new Employer ('MPC').

I then tried binding ComboBox->SelectedValue to SelectedEmployer:

        <ComboBox Name="EmployerListBox"
                  IsReadOnly="True"
                  ItemsSource="{Binding EmployerCollection, UpdateSourceTrigger=PropertyChanged}"
                  SelectedValue="{Binding SelectedEmployer, UpdateSourceTrigger=PropertyChanged}" 
                  Width="150" />

And the GUI is the same, incorrect company being displayed. But this time Snoop shows that ComboBox->SelectedValue is correctly set to the original Employer ('KMH') but SelectedItem and SelectionBoxItem are both set to the new Employer ('MPC').

How do I correctly bind SelectedEmployer so that the GUI matches the selected employer?

BrianKE
  • 4,035
  • 13
  • 65
  • 115
  • Try calling NotifyPropertyChanged(...) in the case where CanChangeEmployer() returns false, otherwise the binding subsystem may be assuming that the SelectedEmployer property has actually been set to the value specified whereas in reality it has not been updated. – Ben Jackson Jun 30 '16 at 14:21
  • 1
    Possible duplicate of [Cancel combobox selection in WPF with MVVM](http://stackoverflow.com/questions/7800032/cancel-combobox-selection-in-wpf-with-mvvm) – myermian Jun 30 '16 at 14:49

1 Answers1

3

What's happening is the ComboBox changes its selection, the Binding updates your viewmodel, and then the viewmodel elects not to change its private field. But how is the ComboBox supposed to know the change was rejected? You're not telling it.

You might think SelectedEmployer.set could simply raise PropertyChanged for SelectedEmployer after deciding to retain the old value, but I can't get that to work.

The "right" way to do this is with validation: Have your viewmodel implement IDataErrorInfo. Here's a tutorial, and here's another article. With validation, your IDataErrorInfo.this[propName].get method gets called before the ComboBox accepts the new selection, and is given the option of rejecting the new selection. This is what you are looking for. If you don't want the red-outline error UI stuff, you can fiddle with templates to get rid of that.

It's tempting to try to handle SelectionChanged, but the event args doesn't have a Cancel, so you're stuck setting SelectedItem back to what it used to be, which raises SelectionChanged again, so you'll need some logic there to stop it from recursing infinitely. Once you fix that, there's still visible "flicker" due to the initial selection completing before you set it back to the previous value. Personally I'd prefer the mild drudgery of implementing IDataErrorInfo over trying to outsmart the framework.

UPDATE

Here's a kinda-kludgey variant of my first idea that actually works. However, it's got the same flicker issue. Far from ideal, but it's a lot less work than than the validation thing. DispatcherPriority.ApplicationIdle is what makes it work: It waits to actually execute the lambda until all the dust has settled from the selection change event stuff from the ComboBox. DispatcherPriority.Background works for me too.

Community
  • 1
  • 1