WPF ComboBox SelectedItem - изменить на предыдущее значение - PullRequest
20 голосов
/ 06 апреля 2010

У меня есть ComboBox, у которого SelectedItem привязан к ViewModel.

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

Когда пользователь выбирает новый элемент в View ComboBox, я хочу отобразить подсказку и убедиться, что он хочет внести изменения.

В установщике свойств SetItem в View Model я отображаю диалог, чтобы подтвердить выбор. Когда они говорят, да, это работает нормально.

Моя проблема в том, что когда пользователь нажимает «Нет», я не уверен, кому нужно получить ComboBox чтобы вернуться к предыдущему значению. Свойство в ViewModel имеет правильный старое значение, однако в представлении ComboBox отображает новое выбранное значение.

Я хочу, чтобы пользователь выбрал элемент, подтвердил, что хочет продолжить его, и если он решите не делать, я хочу, чтобы ComboBox вернулся к предыдущему элементу.

Как я могу это сделать? Спасибо!

Ответы [ 4 ]

25 голосов
/ 06 апреля 2010

Когда пользователь говорит «нет», WPF не знает, что значение изменилось. Что касается WPF, то это значение выбирается пользователем.

Вы можете попытаться поднять уведомление об изменении свойства:

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

        OnPropertyChanged("SelItem");
    }
}

Проблема в том, что уведомление об изменении происходит в том же контексте события выбора. Таким образом, WPF игнорирует его, потому что он уже знает, что свойство изменилось - на элемент, выбранный пользователем!

Вам нужно вызвать событие уведомления в отдельном сообщении:

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

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

WPF затем обработает это сообщение после того, как завершит обработку измененного события выбора и поэтому вернет значение в представлении обратно к тому, каким оно должно быть.

Ваша виртуальная машина, очевидно, нуждается в доступе к текущему Dispatcher. См. мое сообщение в блоге о базовом классе виртуальных машин, если вам нужны некоторые указания о том, как это сделать.

13 голосов
/ 26 апреля 2010

Спасибо за этот вопрос и ответы. Dispatcher.BeginInvoke помог мне и был частью моего окончательного решения, но вышеупомянутое решение не совсем работало в моем приложении WPF 4.

Я собрал небольшой образец, чтобы выяснить, почему. Мне пришлось добавить код, который фактически временно изменил значение базовой переменной-члена, чтобы, когда WPF запросил геттер, он увидел, что значение изменилось. В противном случае пользовательский интерфейс не отображал должным образом отмены, а вызов BeginInvoke () ничего не делал.

Вот мой пост в блоге с моим примером, показывающим нерабочую и работающую реализацию.

Мой сеттер выглядел так:

    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");
        }
    }
1 голос
/ 03 октября 2014

Мой способ сделать это - позволить изменениям пройти и выполнить проверку в лямбда-выражении, которое BeginInvoked в Диспетчере.

    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;
            }
        }
    }

или в вашей ViewModel:

   Synchronization.Current.Post(new SendOrPostCallback(Validate), null);
1 голос
/ 06 апреля 2010

Еще один способ сделать это (обязательно прочитайте также комментарии):

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

По ссылке: Другое решение проблемы рекурсивного вызова обработчика событий без глобальной переменнойзаключается в отмене назначения обработчика до изменения программного выбора и переназначении его после этого.

Пример:

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