views:

924

answers:

3

Hello, I have a ComboBox that has the SelectedItem bound to the ViewModel.

<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">

When the user selects a new Item in the View ComboBox, I want to display a prompt and verify that they want to make the change.

In the SetItem Property setter in the View Model, I display a Dialog to confirm the selection. When they say yes, it works fine.

My problem is, when the user clicks on "No" I am not sure who to get the ComboBox to revert back to the previous value. The Property in the ViewModel has the correct older value, however in the View the ComboBox displays the newly Selected Value.

I want the user to select an item, confirm they want to go ahead with it, and if they decide not to, I want the ComboBox to revert back to the previous item.

How can I accomplish this? Thanks!

+1  A: 

When the user says "no", WPF is unaware that the value has changed. As far as WPF is concerned, the value is whatever the user selected.

You might try raising a property changed notification:

public object SelItem
{
    get { ... }
    set
    {
        if (!CancelChange())
        {
            this.selItem = value;
        }

        OnPropertyChanged("SelItem");
    }
}

The problem is, the change notification happens within the same context of the selection event. Thus, WPF ignores it because it already knows the property has changed - to the item the user selected!

What you need to do is raise the notification event in a separate message:

public object SelItem
{
    get { ... }
    set
    {
        if (CancelChange())
        {
            Dispatcher.BeginInvoke((ThreadStart)delegate
            {
                OnPropertyChanged("SelItem");
            });
            return;
        }

        this.selItem = value;
        OnPropertyChanged("SelItem");
    }
}

WPF will then process this message after it's done processing the selection changed event and will therefore revert the value in the view back to what it should be.

Your VM will obviously need access to the current Dispatcher. See my blog post on a base VM class if you need some pointers on how to do this.

HTH,
Kent

Kent Boogaart
this worked great -thanks! I was not sure how to re-send the message so the View would update.
Taylor
A: 

Another way to do it (make sure you also read the comments):

http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html

Sorin Comanescu
A: 

Thanks for this question and answers. The Dispatcher.BeginInvoke helped me and was part of my final solution, but the above solution didn't quite work in my WPF 4 app.

I put together a small sample to figure out why. I had to add code that actually changed the underlying member variable's value temporarily so that when WPF re-queried the getter, it would see that the value chaned. Otherwise, the UI didn't properly reflect the cancellation and the BeginInvoke() call did not do anything.

Here's a my blog post with my sample showing a non-working and a working implementation.

My setter ended up looking like this:

    private Person _CurrentPersonCancellable;
    public Person CurrentPersonCancellable
    {
        get
        {
            Debug.WriteLine("Getting CurrentPersonCancellable.");
            return _CurrentPersonCancellable;
        }
        set
        {
            // Store the current value so that we can 
            // change it back if needed.
            var origValue = _CurrentPersonCancellable;

            // If the value hasn't changed, don't do anything.
            if (value == _CurrentPersonCancellable)
                return;

            // Note that we actually change the value for now.
            // This is necessary because WPF seems to query the 
            //  value after the change. The combo box
            // likes to know that the value did change.
            _CurrentPersonCancellable = value;

            if (
                MessageBox.Show(
                    "Allow change of selected item?", 
                    "Continue", 
                    MessageBoxButton.YesNo
                ) != MessageBoxResult.Yes
            )
            {
                Debug.WriteLine("Selection Cancelled.");

                // change the value back, but do so after the 
                // UI has finished it's current context operation.
                Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            Debug.WriteLine(
                                "Dispatcher BeginInvoke " + 
                                "Setting CurrentPersonCancellable."
                            );

                            // Do this against the underlying value so 
                            //  that we don't invoke the cancellation question again.
                            _CurrentPersonCancellable = origValue;
                            OnPropertyChanged("CurrentPersonCancellable");
                        }),
                        DispatcherPriority.ContextIdle,
                        null
                    );

                // Exit early. 
                return;
            }

            // Normal path. Selection applied. 
            // Raise PropertyChanged on the field.
            Debug.WriteLine("Selection applied.");
            OnPropertyChanged("CurrentPersonCancellable");
        }
    }
NathanAW