WPF ComboBox SelectedItem - change to previous value

大憨熊 提交于 2019-11-30 06:12:00
Kent Boogaart

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.

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");
        }
    }
Sorin Comanescu

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

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

From the link: Another solution for issue of recursive calling of event handler without global variable is to cancel handler assignment before programmatic selection change, and reassign it after that.

Ex:

cmb.SelectionChanged -= ComboBox_SelectionChanged;
cmb.SelectedValue = oldSel.Key;
cmb.SelectionChanged += ComboBox_SelectionChanged;

My way of doing it is to let the change go through and perform validation in a lambda that is BeginInvoked in the Dispatcher.

    public ObservableCollection<string> Items { get; set; }
    private string _selectedItem;
    private string _oldSelectedItem;
    public string SelectedItem
    {
        get { return _selectedItem; }
        set {
            _oldSelectedItem = _selectedItem;
            _selectedItem = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
            }
            Dispatcher.BeginInvoke(new Action(Validate));                
        }
    }

    private void Validate()
    {            
        if (SelectedItem == "Item 5")
        {
            if (MessageBox.Show("Keep 5?", "Title", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                SelectedItem = _oldSelectedItem;
            }
        }
    }

or in your ViewModel:

   Synchronization.Current.Post(new SendOrPostCallback(Validate), null);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!