我有一个用户控件,它公开了一个名为VisibileItems的DependencyProperty 每次该属性更新时,我都需要触发另一个事件。 为此,我添加了一个带有PropertyChangedCallback事件的FrameworkPropertyMetadata。
由于某种原因,此事件仅被调用一次,并且在下次VisibleItems更改时不会触发。
XAML:
<cc:MyFilterList VisibleItems="{Binding CurrentTables}" />
CurrentTables是MyViewModel上的DependencyProperty。 CurrentTables经常变化。我可以将另一个WPF控件绑定到CurrentTables,我看到UI中的更改。
以下是我使用PropertyChangedCallback
连接VisibleItems的方法public static readonly DependencyProperty VisibleItemsProperty =
DependencyProperty.Register(
"VisibleItems",
typeof(IList),
typeof(MyFilterList),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(VisiblePropertyChanged))
);
public IList VisibleItems {
get { return (IList)GetValue(VisibleItemsProperty); }
set { SetValue(VisibleItemsProperty, value); }
}
通过单步进入VisiblePropertyChanged,我可以看到第一次设置CurrentTables时会触发它。但不是后来的时间。
更新
正如你们中的一些人质疑CurrentTables的修改方式,它会在变化时完全重新分配:
OnDBChange()...
CurrentTables = new List<string>(MainDatabaseDataAdapter.GetTables(this.SelectedServer, this.SelectedDatabase));
每次更改都会调用此行,但我的VisiblePropertyChanged处理程序仅在第一次调用时才会被调用。
更新
如果我直接分配VisibleItems,每次都会调用处理程序!
TestFilterList.VisibleItems = new List<string>( Enumerable.Range(1, DateTime.Now.Second).ToList().Select(s => s.ToString()).ToList() );
因此,看起来问题源于DependencyProperty(VisibleItems)观看另一个DependencyProperty(CurrentTables)。不知何故,绑定适用于第一次属性更改,但不适用于后续属性? 正如你们中的一些人所建议的,试图用snoop检查这个问题。
答案 0 :(得分:14)
您是否将“本地”值(即直接分配给依赖项属性设置器)设置为依赖项属性,该属性也具有OneWay
绑定?如果是这样,设置本地值将删除绑定,如the MSDN dependency property overview:
绑定被视为本地值,这意味着如果您设置另一个本地值,您将消除绑定。
当要求在依赖项属性上存储本地值时,依赖项属性机制没有太多其他功能。它不能通过绑定发送值,因为绑定'指向'错误的方式。设置为本地值后,它不再显示从绑定中获得的值。由于它不再显示绑定的值,因此会删除绑定。
一旦绑定消失,当绑定的source属性更改其值时,将不再调用PropertyChangedCallback
。这可能就是没有调用回调的原因。
如果将绑定设置为TwoWay
,绑定系统确实可以存储您设置的“本地”值:绑定的source属性。在这种情况下,不需要消除绑定,因为依赖属性机制可以将值存储在source属性中。
这种情况不会导致堆栈溢出,因为会发生以下情况:
PropertyChanged
,PropertyChanged
事件,检查源属性的新值,发现它没有更改,并且不做任何进一步的操作。这里的关键点是,如果为值的属性触发PropertyChanged
事件,则依赖属性的任何PropertyChangedCallback
绑定到您的属性将不会被召唤。
为简单起见,我忽略了上面的IValueConverter
。如果您有转换器,请确保它正确地在两个方向上转换值。我还假设另一端的属性是实现INotifyPropertyChanged
的对象的视图模型属性。在绑定的源端可能有另一个依赖属性。依赖属性机制也可以处理它。
碰巧,WPF(和Silverlight)不包含堆栈溢出检测。如果在PropertyChangedCallback
中,您将依赖项属性的值设置为与其新值不同(例如,通过递增整数值属性或将字符串附加到字符串值属性),您将得到一个堆栈溢出。
答案 1 :(得分:5)
我的代码中有同样的问题,Luke是对的。我在PropertyChangedCallback中通过mystake调用了SetValue,导致潜在的无限循环。 WPF阻止静默禁用回调!!
我的WPF UserControl是
PatchRenderer
我的C#属性是注意:
[Description("Note displayed with star icons"),
Category("Data"),
Browsable(true),
EditorBrowsable(EditorBrowsableState.Always),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Note
{
get { return (int)GetValue(NoteProperty); }
set { SetValue(NoteProperty, value); /* don't put anything more here */ }
}
我的WPF属性
public static readonly DependencyProperty
NoteProperty = DependencyProperty.Register("Note",
typeof(int), typeof(PatchRenderer),
new PropertyMetadata(
new PropertyChangedCallback(PatchRenderer.onNoteChanged)
));
private static void onNoteChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// this is the bug: calling the setter in the callback
//((PatchRenderer)d).Note = (int)e.NewValue;
// the following was wrongly placed in the Note setter.
// it make sence to put it here.
// this method is intended to display stars icons
// to represent the Note
((PatchRenderer)d).UpdateNoteIcons();
}
答案 2 :(得分:1)
您可能遇到集合内容正在发生变化而不是实际实例的问题。在这种情况下,您将要使用ObservableCollection并执行以下操作:
public static readonly DependencyProperty VisibleItemsProperty =
DependencyProperty.Register(
"VisibleItems",
typeof(IList),
typeof(MyFilterList),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(VisibleItemsChanged)));
private static void VisibleItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myList = d as MyFilterList;
if (myList == null) return;
myList.OnVisibleItemsChanged(e.NewValue as IList, e.OldValue as IList);
}
protected virtual void OnVisibleItemsChanged(IList newValue, IList oldValue)
{
var oldCollection = oldValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= VisibleItems_CollectionChanged;
}
var newCollection = newValue as INotifyCollectionChanged;
if (newCollection != null)
{
newCollection.CollectionChanged += VisibleItems_CollectionChanged;
}
}
答案 3 :(得分:1)
如果您只是通过以下代码实例化MyFilterList
并设置VisibleItems
:
var control = new MyFilterList();
control.VisibleItems = new List<string>();
control.VisibleItems = new List<string>();
每次都可能会看到PropertyChangedCallback发生。意思是,问题在于绑定,而不是回调。确保您没有绑定错误,您正在提出PropertyChanged
,并且您没有违反绑定(例如,通过在代码中设置VisibleItems
)