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.
This can be achieved in a generic and compact way using Blend's Generic Behavior.
The behavior defines a dependency property named
SelectedItem
, and you should put your binding in this property, instead of in the ComboBox'sSelectedItem
property. The behavior is in charge of passing changes in the dependency property to the ComboBox (or more generally, to the Selector), and when the Selector'sSelectedItem
changes, it tries to assign it to the its ownSelectedItem
property. If the assignment fails (probably because the bound VM proeprty setter rejected the assignment), the behavior updates the Selector’sSelectedItem
with the current value of itsSelectedItem
property.For all sorts of reasons, you might encounter cases where the list of items in the Selector is cleared, and the selected item becomes null (see this question). You usually don't want your VM property to become null in this case. For this, I added the IgnoreNullSelection dependency property, which is true by default. This should solve such problem.
This is the
CancellableSelectionBehavior
class:This is the way to use it in XAML:
and this is a sample of the VM property:
To achieve this under MVVM....
1] Have an attached behavior that handles the
SelectionChanged
event of the ComboBox. This event is raised with some event args that haveHandled
flag. But setting it to true is useless forSelectedValue
binding. The binding updates source irrespective of whether the event was handled.2] Hence we configure the
ComboBox.SelectedValue
binding to beTwoWay
andExplicit
.3] Only when your check is satisfied and messagebox says
Yes
is when we performBindingExpression.UpdateSource()
. Otherwise we simply call theBindingExpression.UpdateTarget()
to revert to the old selection.In my example below, I have a list of
KeyValuePair<int, int>
bound to the data context of the window. TheComboBox.SelectedValue
is bound to a simple writeableMyKey
property of theWindow
.XAML ...
Where
MyDGSampleWindow
is the x:Name of theWindow
.Code Behind ...
And the attached behavior
In the behavior I use
ComboBox.Tag
property to temporarily store a flag that skips the rechecking when we revert back to the old selected value.Let me know if this helps.
Very simple solution for .NET 4.5.1+:
It's works for me in most cases. You can rollback selection in combobox, just fire NotifyPropertyChanged without value assignment.
I found a much simpler answer to this question by user shaun on another thread: https://stackoverflow.com/a/6445871/2340705
The basic problem is that the property changed event gets swallowed. Some would called this a bug. To get around that use BeginInvoke from the Dispatcher to force the property changed event to be put back onto the end of UI event queue. This requires no change to the xaml, no extra behavior classes, and a single line of code changed to the view model.
I did it in a similar way to what splintor has above.
Your view:
Below is the code for the event handler "ComboBox_SelectionChanged" from the code file behind the view. For example, if you view is myview.xaml, the code file name for this event handler should be myview.xaml.cs