我有一个 ComboBox,其中 SelectedItem 绑定到 ViewModel。
<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">
当用户在视图组合框中选择一个新项目时,我想显示一条提示并验证他们是否想要进行更改。
在视图模型的 SetItem 属性设置器中,我显示一个对话框来确认选择。当他们说“是”时,效果就很好。
我的问题是,当用户单击“否”时,我不确定谁可以获得组合框 恢复到之前的值。 ViewModel 中的属性具有正确的 较旧的值,但是在视图中,组合框显示新选择的值。
我希望用户选择一个项目,确认他们想要继续操作,如果他们愿意 决定不这样做,我希望组合框恢复到上一个项目。
我怎样才能做到这一点? 谢谢!
当用户说“不”时,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 基类的
我的博客文章。
我整理了一个小样本来找出原因。我必须添加实际上临时更改底层成员变量值的代码,以便当 WPF 重新查询 getter 时,它会看到值发生了变化。否则,UI 无法正确反映取消,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");
}
}
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);
<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay, Delay=200}" ItemsSource="{Binding MyItems}">