How do I cancel a user selection in a databound WPF ListBox? The source property is set correctly, but the ListBox selection is out of sync.
I have an MVVM app that needs to cancel a user selection in a WPF ListBox if certain validation conditions fail. Validation is triggered by a selection in the ListBox, rather than by a Submit button.
The ListBox.SelectedItem
property is bound to a ViewModel.CurrentDocument
property. If validation fails, the setter for the view model property exits without changing the property. So, the property to which ListBox.SelectedItem
is bound doesn't get changed.
If that happens, the view model property setter does raise the PropertyChanged event before it exits, which I had assumed would be enough to reset the ListBox back to the old selection. But that's not working--the ListBox still shows the new user selection. I need to override that selection and get it back in sync with the source property.
Just in case that's not clear, here is an example: The ListBox has two items, Document1 and Document2; Document1 is selected. The user selects Document2, but Document1 fails to validate. The ViewModel.CurrentDocument
property is still set to Document1, but the ListBox shows that Document2 is selected. I need to get the ListBox selection back to Document1.
Here is my ListBox Binding:
<ListBox
ItemsSource="{Binding Path=SearchResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=CurrentDocument, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I did try using a callback from the ViewModel (as an event) to the View (which subscribes to the event), to force the SelectedItem property back to the old selection. I pass the old Document with the event, and it is the correct one (the old selection), but the ListBox selection doesn't change back.
So, how do I get the ListBox selection back in sync with the view model property to which its SelectedItem
property is bound? Thanks for your help.
For future stumblers on this question, this page is what ultimately worked for me: http://blog.alner.net/archive/2010/04/25/cancelling-selection-change-in-a-bound-wpf-combo-box.aspx
It's for a combobox, but works for a listbox just fine, since in MVVM you don't really care what type of control is calling the setter. The glorious secret, as the author mentions, is to actually change the underlying value and then change it back. It was also important to run this “undo” on a separate dispatcher operation.
Note: The author uses
ContextIdle
for theDispatcherPriority
for the action to undo the change. While fine, this is a lower priority thanRender
, which means that the change will show in the UI as the selected item momentarily changing and changing back. Using a dispatcher priority ofNormal
or evenSend
(the highest priority) preempts the display of the change. This is what I ended up doing. See here for details about theDispatcherPriority
enumeration.Got it! I am going to accept majocha's answer, because his comment underneath his answer led me to the solution.
Here is wnat I did: I created a
SelectionChanged
event handler for the ListBox in code-behind. Yes, it's ugly, but it works. The code-behind also contains a module-level variable,m_OldSelectedIndex
, which is initialized to -1. TheSelectionChanged
handler calls the ViewModel'sValidate()
method and gets a boolean back indicating whether the Document is valid. If the Document is valid, the handler setsm_OldSelectedIndex
to the currentListBox.SelectedIndex
and exits. If the document is invalid, the handler resetsListBox.SelectedIndex
tom_OldSelectedIndex
. Here is the code for the event handler:Note that there is a trick to this solution: You have to use the
SelectedIndex
property; it doesn't work with theSelectedItem
property.Thanks for your help majocha, and hopefully this will help somebody else down the road. Like me, six months from now, when I have forgotten this solution...
Bind
ListBox
's property:IsEnabled="{Binding Path=Valid, Mode=OneWay}"
whereValid
is the view-model property with the validation algoritm. Other solutions look too far-fetched in my eyes.When the disabled appearance is not allowed, a style could help out, but probably the disabled style is ok because changing the selection is not allowed.
Maybe in .NET version 4.5 INotifyDataErrorInfo helps, I dont'know.
If you are serious about following MVVM and don't want any code behind, and also don't like the use of the
Dispatcher
, which frankly is not elegant either, the following solution works for me and is by far more elegant than most of the solutions provided here.It is based on the notion that in code behind you are able to stop the selection using the
SelectionChanged
event. Well now, if this is the case, why not create a behavior for it, and associate a command with theSelectionChanged
event. In the viewmodel you can then easily remember the previous selected index and the current selected index. The trick is to have binding to your viewmodel onSelectedIndex
and just let that one change whenever the selection changes. But immediately after the selection really has changed, theSelectionChanged
event fires which now is notified via the command to your viewmodel. Because you remember the previously selected index, you can validate it and if not correct, you move the selected index back to the original value.The code for the behavior is as follows:
Using it in XAML:
The code that is appropriate in the viewmodel is as follows:
In the constructor of the viewmodel:
RelayCommand
is part of MVVM light. Google it if you don't know it. You need to refer toand hence you need to reference
System.Windows.Interactivity
.I came up against this recently, and came up with a solution that works well with my MVVM, without the need for and code behind.
I created a SelectedIndex property in my model and bound the listbox SelectedIndex to it.
On the View CurrentChanging event, I do my validation, if it fails, I simply use the code
It seems to work perfectly ATM. There may be edge cases where it doesnt, but for now, it does exactly what I want.
-snip-
Well forget what I wrote above.
I just did an experiment, and indeed SelectedItem goes out of sync whenever you do anything more fancy in the setter. I guess you need to wait for the setter to return, and then change the property back in your ViewModel asynchronously.
Quick and dirty working solution (tested in my simple project) using MVVM Light helpers: In your setter, to revert to previous value of CurrentDocument
it basically queues the property change on the UI thread, ContextIdle priority will ensure it will wait for UI to be in consistent state. it Appears you cannot freely change dependency properties while inside event handlers in WPF.
Unfortunately it creates coupling between your view model and your view and it's an ugly hack.
To make DispatcherHelper.UIDispatcher work you need to do DispatcherHelper.Initialize() first.