WPF ComboBox SelectedItem - изменение предыдущего значения
У меня есть ComboBox, у которого SelectedItem связан с ViewModel.
<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">
Когда пользователь выбирает новый элемент в представлении ComboBox, я хочу отобразить приглашение и убедиться, что он хочет внести изменения.
В SetEtem Property Setter в Модели просмотра я показываю диалог, чтобы подтвердить выбор. Когда они говорят "да", все работает нормально.
Моя проблема в том, что когда пользователь нажимает "Нет", я не уверен, кто получит ComboBox
чтобы вернуться к предыдущему значению. Свойство в ViewModel имеет правильную
более старого значения, однако в представлении ComboBox отображается новое выбранное значение.
Я хочу, чтобы пользователь выбирал элемент, подтверждал, что хочет продолжить его, и если они
решите не делать этого, я хочу, чтобы ComboBox возвращался к предыдущему элементу.
Как я могу это сделать?
Спасибо!
Ответы
Ответ 1
Когда пользователь говорит "нет", 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
. См. мой пост в блоге на базовом классе VM, если вам нужны указатели на то, как это сделать.
Ответ 2
Спасибо за этот вопрос и ответы. 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 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");
}
}
Ответ 3
Другой способ сделать это (убедитесь, что вы также прочитали комментарии):
http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html
Из ссылки:
Еще одно решение для рекурсивного вызова обработчика событий без глобальной переменной - это отменить назначение обработчика перед изменением программного выбора и переназначить его после этого.
Пример:
cmb.SelectionChanged -= ComboBox_SelectionChanged;
cmb.SelectedValue = oldSel.Key;
cmb.SelectionChanged += ComboBox_SelectionChanged;
Ответ 4
Мой способ сделать это, чтобы пройти проверку и выполнить проверку в лямбда, которая 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);