我有一些类似的东西,它会弹出给用户确认更改。如果他点击否,我将在视图模型中将选定的值设置为以前的选择。但在视图中显示不正确。请帮忙。
我有一些类似的东西,它会弹出给用户确认更改。如果他点击否,我将在视图模型中将选定的值设置为以前的选择。但在视图中显示不正确。请帮忙。
.NET 4.5.1+的非常简单的解决方案:
<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}" />
这对我在所有情况下都有效。只需触发NotifyPropertyChanged而不进行值分配即可回滚。
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);
WPF似乎在更新UI之前验证绑定属性是否已更改。因此,仅调用NotifyPropertyChanged()/OnPropertyChanged()并不能解决问题。
问题在于,由于属性没有更改,WPF认为它不需要刷新组合框。
以下是我在ViewModels中处理它的可怕方法;
private int _someProperty = -1;
public int SomeProperty
{
if (_someProperty != -1 && doYesNo() == Yes)
{
_someProperty = value;
}
else if (_someProperty != -1)
{
int oldValue = _someProperty;
_someProperty = -1; // Set to a different/invalid value so WPF sees a change
Dispatcher.BeginInvoke(new Action(() => { SomeProperty = oldValue; }));
}
else
{
_someProperty = value;
}
NotifyPropertyChanged("SomeProperty");
}
这不太好看,但它可靠地工作。
假设:
- 当用户从ComboBox中选择某个值时,您会显示一个带有消息和OKCancel按钮的对话框。
- 如果用户按下OK,则一切正常。 :)
- 如果用户按下取消,则您会说vmPropSelectedValue=previousValue。
这不起作用。为什么?
我没有确切的答案,但我相信当您显示对话框时,系统刚刚更改了所选值,并通过绑定基础结构通知了源。如果此时(当源具有控制权)您现在从VM代码更改ViewModel属性的值,您期望会触发INotifyPropertyChanged的OnPropertyChanged,您期望会要求WPF使用您请求的值更新目标。但是,WPF尚未完成循环-它仍在等待源将控件返回给它。因此,它只是拒绝您的请求(否则它将进入无限循环)。
如果这很令人困惑,请尝试以下操作:
循环开始:
1. 用户更改UI上的值。 WPF更改目标。
2.绑定基础结构请求源更新自身。
3.源更新自身(VM属性)。
4.源将控件返回到绑定基础架构。
循环结束。
专家们:在这方面找不到一些文档。以上是我对事情运作方式的看法。如果不正确,请纠正。
简短回答:
据我所知,这不能仅通过纯虚拟机代码完成。您将不得不放置一些代码后端代码。
以下是一种方法:http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
如果您尝试异步触发属性更改事件会怎样呢?这类似于Shaun和NebulaSleuth的示例。
public int SomeProperty
{
get { return m_someProperty; }
set
{
if (value == m_someProperty)
return;
if (doYesNo() == No)
{
// Don't update m_someProperty but let the UI know it needs to retrieve the value again asynchronously.
Application.Current.Dispatcher.BeginInvoke((Action) (() => NotifyOfPropertyChange("SomeProperty")));
}
else
{
m_someProperty = value;
NotifyOfPropertyChange("SomeProperty");
}
}
}
"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"
当你想要真正保存到ViewModel时,调用:
UpdateSource();
我知道这是一个旧帖子,但似乎没有人以正确的方式完成这个任务。我使用了System.Interactivity.Triggers和Prism来处理SelectionChanged事件,并手动触发SelectedItem。这将防止UI和View-Model中不必要的选定项更改。
我的视图:
<Window x:Class="Lind.WPFTextBlockTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Lind.WPFTextBlockTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
Title="MainWindow" Height="649" Width="397">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding SelectedData, Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding TryChangeSelectedData}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
我的视图模型(来自Prism 5的BindeableBase和DelegateCommand):
public class MainWindowViewModel : BindableBase
{
public ObservableCollection<string> Data { get; private set; }
private string selectedData;
public string SelectedData
{
get { return selectedData; }
set
{
SetProperty(ref selectedData, value);
}
}
public DelegateCommand<SelectionChangedEventArgs> TryChangeSelectedData { get; private set; }
public MainWindowViewModel()
{
Data = new ObservableCollection<string>() { "Foo", "Bar", "Dick", "Head" };
SelectedData = Data.First();
TryChangeSelectedData = new DelegateCommand<SelectionChangedEventArgs>(args =>
{
var newValue = args.AddedItems.Cast<string>().FirstOrDefault();
if (newValue == "Dick")
this.OnPropertyChanged(() => this.SelectedData);
else
SelectedData = newValue;
});
}
}
这是我通常使用的一般流程:
我将任何撤消逻辑放在处理程序中,并使用SynchronizationContext.Post()调用它(BTW: SynchronizationContext.Post也适用于Windows Store应用程序。因此,如果您有共享的ViewModel代码,则此方法仍将起作用)。
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<string> Items { get; set; }
private string _selectedItem;
private string _previouslySelectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
_previouslySelectedItem = _selectedItem;
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
SynchronizationContext.Current.Post(selectionChanged, null);
}
}
private void selectionChanged(object state)
{
if (SelectedItem != Items[0])
{
MessageBox.Show("不能选择该项");
SelectedItem = Items[0];
}
}
public ViewModel()
{
Items = new List<string>();
for (int i = 0; i < 10; ++i)
{
Items.Add(string.Format("项目 {0}", i));
}
}
}