WPF:PropertyChangedCallback只触发一次

时间:2011-04-26 20:01:19

标签: c# .net wpf dependency-properties propertychanged

我有一个用户控件,它公开了一个名为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检查这个问题。

4 个答案:

答案 0 :(得分:14)

您是否将“本地”值(即直接分配给依赖项属性设置器)设置为依赖项属性,该属性也具有OneWay绑定?如果是这样,设置本地值将删除绑定,如the MSDN dependency property overview:

中所述
  

绑定被视为本地值,这意味着如果您设置另一个本地值,您将消除绑定。

当要求在依赖项属性上存储本地值时,依赖项属性机制没有太多其他功能。它不能通过绑定发送值,因为绑定'指向'错误的方式。设置为本地值后,它不再显示从绑定中获得的值。由于它不再显示绑定的值,因此会删除绑定。

一旦绑定消失,当绑定的source属性更改其值时,将不再调用PropertyChangedCallback。这可能就是没有调用回调的原因。

如果将绑定设置为TwoWay,绑定系统确实可以存储您设置的“本地”值:绑定的source属性。在这种情况下,不需要消除绑定,因为依赖属性机制可以将值存储在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