10

我有一个用户控件,它公开了一个名为 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

4 回答 4

15

您是否将“本地”值(即直接分配给依赖属性设置器)设置为也具有OneWay绑定的依赖属性?如果是这样,设置本地值将删除绑定,如MSDN 依赖属性概述中所述:

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

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

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

如果您将绑定设置为TwoWay,则绑定系统确实有某个地方可以存储您设置的“本地”值:在绑定的源属性中。在这种情况下,不需要消除绑定,因为依赖属性机制可以将值存储在源属性中。

这种情况不会导致堆栈溢出,因为会发生以下情况:

  • 依赖属性接收“本地”值。
  • 依赖属性机制将值“向后”发送到源属性的绑定,
  • 源属性设置属性值并触发PropertyChanged
  • 依赖属性机制接收PropertyChanged事件,检查源属性的新值,发现它没有改变,不再做任何事情。

这里的关键点是,如果您为值未更改的属性触发PropertyChanged事件,则不会调用绑定到您的属性的依赖属性上的任何s。PropertyChangedCallback

为简单起见,我IValueConverter在上面忽略了 s 。如果您有转换器,请确保它在两个方向上都正确转换了值。我还假设另一端的属性是实现INotifyPropertyChanged. 绑定的源端可能存在另一个依赖属性。依赖属性机制也可以处理这个问题。

碰巧的是,WPF(和 Silverlight)不包含堆栈溢出检测。如果在 a 中PropertyChangedCallback,您将依赖属性的值设置为与其新值不同(例如,通过递增整数值属性或将字符串附加到字符串值属性),您将获得堆栈溢出。

于 2011-04-26T22:20:01.023 回答
5

我的代码中有同样的问题,卢克是对的。我在 PropertyChangedCallback 中通过 mystake 调用了 SetValue,导致潜在的无限循环。WPF 防止这种静默地禁用回调!

我的 WPF 用户控件是

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();
    }
于 2012-06-05T17:22:47.307 回答
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;
        }
    }
于 2011-04-26T20:10:49.050 回答
1

如果您只是实例化 aMyFilterListVisibleItems通过如下代码设置:

var control = new MyFilterList();
control.VisibleItems = new List<string>();
control.VisibleItems = new List<string>();

您可能会看到 PropertyChangedCallback 每次都发生。意思是,问题在于绑定,而不是回调。确保你没有绑定错误,你正在提升PropertyChanged,并且你没有破坏绑定(例如通过VisibleItems在代码中设置)

于 2011-04-26T22:50:28.720 回答