I've got a combobox in my WPF application:
<ComboBox ItemsSource="{Binding CompetitorBrands}" DisplayMemberPath="Value"
SelectedValuePath="Key" SelectedValue="{Binding Path=CompMfgBrandID, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Text="{Binding CompMFGText}"/>
Bound to a collection of KeyValuePair<string, string>
Here is the CompMfgBrandID property in my ViewModel:
public string CompMfgBrandID
{
get { return _compMFG; }
set
{
if (StockToExchange != null && StockToExchange.Where(x => !string.IsNullOrEmpty(x.EnteredPartNumber)).Count() > 0)
{
var dr = MessageBox.Show("Changing the competitor manufacturer will remove all entered parts from the transaction. Proceed?",
"Transaction Type", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (dr != DialogResult.Yes)
return;
}
_compMFG = value;
StockToExchange.Clear();
...a bunch of other functions that don't get called when you click 'No'...
OnPropertyChanged("CompMfgBrandID");
}
}
If you choose "yes", it behaves as expected. Items are cleared and the remaining functions are called. If I choose 'No', it returns and doesn't clear my list or call any of the other functions, which is good, but the combobox still displays the new selection. I need it to revert back to the original selection, as if nothing had changed, when the user picks 'No'. How can I accomplish this? I also tried adding e.Handled = true
in codebehind, to no avail.
The problem is that once WPF updates the value with the property setter, it ignores any further property changed notifications from within that call: it assumes that they will happen as a normal part of the setter and are of no consequence, even if you really have updated the property back to the original value.
The way I got around this was to allow the field to get updated, but also queue up an action on the Dispatcher to "undo" the change. The action would set it back to the old value and fire a property change notification to get WPF to realize that it's not really the new value it thought it was.
Obviously the "undo" action should be set up so it doesn't fire any business logic in your program.
I think the problem is that the ComboBox sets the selected item as a result of the user action after setting the bound property value. Thus the Combobox item changes no matter what you do in the ViewModel. I found a different approach where you don't have to bend the MVVM pattern. Here's my example (sorry that it is copied from my project and does not exactly match the examples above):
The difference is that I completely clear the items collection and then fill it with the items stored before. This forces the Combobox to update as I'm using the ObservableCollection generic class. Then I set the selected item back to the selected item that was set previously. This is not recommended for a lot of items because clearing and filling the combobox is kind of expensive.
I would like to complete splintor's answer because I stumbled upon a problem with the delayed initialization in
OnSelectedItemChanged
:When OnSelectedItemChanged is raised before AssociatedObject is assigned, using the
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke
can have unwanted side effects, such as trying to initialize the newValue with the default value of the combobox selection.So even if your ViewModel is up to date, the behaviour will trigger a change from the ViewModel's
SelectedItem
current value to the default selection of the ComboBox stored ine.NewValue
. If your code triggers a Dialog Box, the user will be warned of a change although there is none. I can't explain why it happens, probably a timing issue.Here's my fix
Here is the general flow that I use (doesn't need any behaviors or XAML modifications):
I put any undo logic in a handler and call that using SynchronizationContext.Post() (BTW: SynchronizationContext.Post also works for Windows Store Apps. So if you have shared ViewModel code, this approach would still work).
I prefer "splintor's" code sample over "AngelWPF's". Their approaches are fairly similar though. I have implemented the attached behavior, CancellableSelectionBehavior, and it works as advertised. Perhaps it was just that the code in splintor's example was easier to plug into my application. The code in AngelWPF's attached behavior had references to a KeyValuePair Type that would have called for more code alteration.
In my application, I had a ComboBox where the items that are displayed in a DataGrid are based on the item selected in the ComboBox. If the user made changes to the DataGrid, then selected a new item in the ComboBox, I would prompt the user to save changes with Yes|NO|Cancel buttons as options. If they pressed Cancel, I wanted to ignore their new selection in the ComboBox and keep the old selection. This worked like a champ!
For those who frighten away the moment they see references to Blend and System.Windows.Interactivity, you do not have to have Microsoft Expression Blend installed. You can download the Blend SDK for .NET 4 (or Silverlight).
Blend SDK for .NET 4
Blend SDK for Silverlight 4
Oh yeah, in my XAML, I actually use this as my namespace declaration for Blend in this example:
I had the same issue, causes by UI thread and the way that biding works. Check the this link: SelectedItem on ComboBox
The structure in the sample uses code behind but the MVVM is exactly the same.