当我绑定到ObservableCollection
时,我的行为很奇怪。如果集合在设置值期间通过TextBox
中的绑定发生更改,则TextBox
将不会绑定到新对象,但仍保持绑定到旧对象(现在无效)。
TextBox
(然后点击此处,以便将更改更新为源代码)ItemVM
的设置者更新了支持集合,反过来触发事件通知View
该项目已被替换TextBlock
和其他 TextBox
成功反弹到集合中的新项目,但您用来编辑此项目的TextBox
仍然是绑定到旧的ItemVM
TextBox
再次编辑值,则会失败(因为该项目中不再存在该项目)TextBox
编辑值,则第一个TextBox
会反弹并再次运行,但现在此TextBox
已损坏TextBox
es想要多少次,但必须始终在它们之间交替我认为,这是因为原始TextBox
正在执行代码,所以它忽略了更改后的值,一旦完成执行该代码,它就会再次侦听绑定事件并简单地将自身重新绑定到新对象。
问题是:如何防止这种情况?
PS:这个例子真的很简单,在实际情况下ItemVM
不直接更新集合,而是一些通用对象 - 直到运行时才知道,所以类型为&的建议#34;不要在那里更新集合"是无关紧要的。
请随意询问,我会尝试添加其他信息。
using System;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
class Program {
[STAThread]
static void Main() {
var dc = new VM {
Items = new ObservableCollection<ItemVM>(),
};
for(int i = 0; i < 10; i++) {
dc.Items.Add(new ItemVM(i.ToString(), dc));
}
var w = new MainWindow {
DataContext = dc,
};
new Application().Run(w);
}
}
class VM {
public ObservableCollection<ItemVM> Items { get; set; }
}
class ItemVM : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public VM ParentVM { get; set; }
string value;
public string Value {
get { return value; }
set {
int index = ParentVM.Items.IndexOf(this);
ParentVM.Items[index] = new ItemVM(value, ParentVM);
this.value = value + " in old VM";
var h = PropertyChanged;
if(h != null)
h(this, new PropertyChangedEventArgs("Value"));
}
}
public ItemVM(string value, VM parent) {
this.value = value;
ParentVM = parent;
}
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<StackPanel>
<TextBlock Text="{Binding Value}" />
<TextBox Text="{Binding Value}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Window>
答案 0 :(得分:1)
您正在完全更改DataContext
TextBox
所绑定的public string Value
{
get { return value; }
set
{
int index = ParentVM.Items.IndexOf(this);
App.Current.Dispatcher.BeginInvoke((Action)delegate <-- HERE
{
ParentVM.Items[index] = new ItemVM(value, ParentVM);
});
this.value = value + " in old VM";
var h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs("Value"));
}
}
}
。所以,我怀疑在更新早期DataContext的绑定属性时,textBox无法选择新的DataContext。
您可以做的是异步更新新的DataContext在UI调度程序上,以便所有UI组件都可以获取DataContext中的更新,包括正在获取更新的更新。即,异步地在UI调度程序上对集合更新代码进行排队。
{{1}}
答案 1 :(得分:0)
Rohit的解决方案有效,但之后无法使用验证。我想出了两个解决方案。
<TextBox Text="{Binding Value, IsAsync=True}" />
它与在属性setter中异步调用的工作方式基本相同。由于这是内置功能,因此更清楚作者的意图是什么,因此更容易维护。
此外,从.NET 4.5开始,我们可以实现接口INotifyDataErrorInfo
,并且即使在异步绑定上也可以进行验证。
此解决方案适用于.NET 4.5之前的版本,除了ValidatesOnExceptions
之外,还允许使用ValidatesOnDataErrors
,这是第一个解决方案的唯一选项。
同步调用属性setter,同步使用绑定(IsAsync=False
)。区别在于绑定的ObservableCollection
必须异步调用事件。可以通过在类中包装ObservableCollection
来完成它:
class MarshaledObservableCollection<T> : IEnumerable<T>, INotifyCollectionChanged, IWeakEventListener {
readonly IEnumerable<T> backingCollection;
public event NotifyCollectionChangedEventHandler CollectionChanged {
add {
var info = new NotifyCollectionChangedEventHandlerInfo {
Handler = value,
Dispatcher = Dispatcher.CurrentDispatcher,
};
collectionChangedHandlers.Add(info);
}
remove {
var info = new NotifyCollectionChangedEventHandlerInfo {
Handler = value,
Dispatcher = Dispatcher.CurrentDispatcher,
};
collectionChangedHandlers.Remove(info);
}
}
readonly List<NotifyCollectionChangedEventHandlerInfo> collectionChangedHandlers = new List<NotifyCollectionChangedEventHandlerInfo>();
public MarshaledObservableCollection(IEnumerable<T> collection) {
if (collection == null)
throw new ArgumentNullException("collection", "collection is null.");
var obs = collection as INotifyCollectionChanged;
if (obs == null)
throw new ArgumentException("collection must be INotifyCollectionChanged", "collection");
backingCollection = collection;
CollectionChangedEventManager.AddListener(obs, this);
}
public IEnumerator<T> GetEnumerator() {
return backingCollection.GetEnumerator();
}
protected virtual void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
foreach (var item in collectionChangedHandlers) {
//Important line - invoking asynchronously with the DataBind priority
item.Dispatcher.BeginInvoke(item.Handler, DispatcherPriority.DataBind, sender, e);
}
}
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
if (managerType == typeof(CollectionChangedEventManager)) {
OnCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);
return true;
} else {
return false;
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
struct NotifyCollectionChangedEventHandlerInfo {
public Dispatcher Dispatcher { get; set; }
public NotifyCollectionChangedEventHandler Handler { get; set; }
}
}