WPF:PropertyChangedCallback 仅触发一次

发布于 2024-11-03 11:33:41 字数 1720 浏览 1 评论 0原文

我有一个用户控件,它公开一个名为 VisibileItems 的 DependencyProperty 每次更新该属性时,我都需要触发另一个事件。 为了实现这一点,我添加了带有 PropertyChangedCallback 事件的 FrameworkPropertyMetadata。

由于某种原因,此事件仅被调用一次,并且下次 VisibleItems 更改时不会触发。

XAML:

<cc:MyFilterList VisibleItems="{Binding CurrentTables}"  />

CurrentTables 是 MyViewModel 上的 DependencyProperty。 CurrentTables 经常更改。我可以将另一个 WPF 控件绑定到 CurrentTables,并且可以看到 UI 中的更改。

将 VisibleItems 与 PropertyChangedCallback 连接的方式

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)。不知何故,绑定适用于第一个属性更改,但不适用于后续属性更改? 正如你们中的一些人所建议的,尝试用窥探来检查这个问题。

I have a user control, which exposes a DependencyProperty called VisibileItems
Every time that property gets updated, i need to trigger another event.
To achieve that, i added a FrameworkPropertyMetadata with PropertyChangedCallback event.

For some reason, this event gets called only once, and doesn't trigger the next time VisibleItems is changed.

XAML:

<cc:MyFilterList VisibleItems="{Binding CurrentTables}"  />

CurrentTables is a DependencyProperty on MyViewModel. CurrentTables gets changed often. I can bind another WPF control to CurrentTables, and i see the changes in the UI.

Here is the way i wired VisibleItems with PropertyChangedCallback

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); }
}

by stepping into VisiblePropertyChanged, i can see that it gets triggered the first time CurrentTables gets set. but not subsequent times.

UPDATE

as some of you questioned the way CurrentTables is modified, it is re-assigned completely on change:

OnDBChange()...
CurrentTables = new List<string>(MainDatabaseDataAdapter.GetTables(this.SelectedServer, this.SelectedDatabase));

this line gets called on every change, but my VisiblePropertyChanged handler gets called only the first time.

UPDATE

if i assign VisibleItems directly, the handler does get called every time!

TestFilterList.VisibleItems = new List<string>( Enumerable.Range(1, DateTime.Now.Second).ToList().Select(s => s.ToString()).ToList() );

So, it looks like the problem stems from the DependencyProperty (VisibleItems) watching another DependencyProperty (CurrentTables). Somehow the binding works on first property change, but not on subsequent ones?
Attempting to inspect this issue with snoop as some of you suggested.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

鸵鸟症 2024-11-10 11:33:41

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

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

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

绑定消失后,当绑定的源属性更改其值时,将不再调用 PropertyChangedCallback。这可能就是回调未被调用的原因。

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

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

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

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

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

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

Are you setting a 'local' value (i.e. assigning directly to a dependency property setter) to a dependency property that also has a OneWay binding on it? If so, setting the local value will remove the binding, as mentioned on the the MSDN dependency property overview:

Bindings are treated as a local value, which means that if you set another local value, you will eliminate the binding.

The dependency property mechanism doesn't have much else it can do when it gets asked to store a local value on a dependency property. It can't send the value through the binding because the binding 'points' the wrong way. After being set to the local value, it's no longer showing the value it got from the binding. Since it's not showing the value from the binding any more, it removes the binding.

Once the binding's gone, the PropertyChangedCallback will no longer get called when the source property for the binding changes its value. This may be why the callback isn't being called.

If you set the binding to be TwoWay, the binding system does have somewhere to store the 'local' value you've set: in the binding's source property. In this case, there's no need to eliminate the binding as the dependency property mechanism can store the value in the source property.

This situation does not cause a stack-overflow because the following happens:

  • Dependency property receives 'local' value.
  • Dependency property mechanism sends value 'backwards' along binding to source property,
  • Source property sets property value and fires PropertyChanged,
  • Dependency property mechanism receives PropertyChanged event, checks new value of source property, finds that it hasn't changed and does nothing further.

The key point here is that if you fire a PropertyChanged event for a property whose value hasn't changed, any PropertyChangedCallbacks on dependency properties bound to your property will not be called.

For simplicity I've ignored IValueConverters in the above. If you do have a converter, make sure that it is correctly converting values in both directions. I've also assumed that the property at the other end is a view-model property on an object implementing INotifyPropertyChanged. There could have been another dependency property at the source end of the binding. The dependency property mechanism can handle that as well.

As it happens, WPF (and Silverlight) contain no detection of stack overflows. If, in a PropertyChangedCallback, you set the value of the dependency property to be different to its new value (e.g. by incrementing an integer-valued property or appending a string to a string-valued property), you will get a stack overflow.

海夕 2024-11-10 11:33:41

我的代码中也有同样的问题,卢克是对的。我在 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();
    }

I have the same issue in my code and Luke is right. I called SetValue by mystake inside the PropertyChangedCallback causing a potential infinite loop. WPF prevent this disabling silently the callback !!

my WPF UserControl is

PatchRenderer

my C# property is Note:

    [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 */ }
    }

my WPF property

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();
    }
早乙女 2024-11-10 11:33:41

您可能会遇到一个问题:集合的内容正在更改,但实际实例却没有更改。在这种情况下,您需要使用 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;
        }
    }

You might be having an issue where the contents of the collection is changing but not the actual instance. in this case you'll want to use an ObservableCollection and do something like this:

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;
        }
    }
情话已封尘 2024-11-10 11:33:41

如果您只是实例化一个 MyFilterList 并通过如下代码设置 VisibleItems

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

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

If you just instantiate a MyFilterList and set VisibleItems via code like this:

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

You will likely see the PropertyChangedCallback happen every time. Meaning, the problem is with the binding, not the callback. Make sure you don't have binding errors, you're raising PropertyChanged, and you're not breaking the binding (e.g. by setting VisibleItems in code)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文