10

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.

Sonic Soul
  • 23,855
  • 37
  • 130
  • 196
  • Are you sure the binding remains intact? I would use Snoop to verify that the binding is updating the VisibleItems property correctly. – default.kramer Apr 26 '11 at 20:57
  • You imply but aren't explicit: are you sure that `VisibleItems` actually changes to a different value without the property changed callback being called? – Rick Sladkey Apr 26 '11 at 21:13
  • Rick, yes I am sure, i can step through it and see that it gets called, and now i create a "new List" on every call to be sure it is not the same instance. but also if you look at the code you can see that it gets the list from database every time. – Sonic Soul Apr 26 '11 at 22:23
  • 1
    Proving that CurrentTables is changing does not prove that VisibleItems is changing. Your binding might be broken or something. I suspect that VisibleItems is not actually changing, which is why I recommend using Snoop to make sure the binding is working. – default.kramer Apr 26 '11 at 22:30
  • kramer, right on. i did a different test and it does indeed appear that the problem is somewhere between VisibleItems and CurrentTables... need to do some investigation to see why they work on first try but not subsequent.. – Sonic Soul Apr 27 '11 at 14:52

4 Answers4

17

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.

Vimes
  • 10,577
  • 17
  • 66
  • 86
Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • 1
    Nope, not setting VisibleItems from within the event. The event performs a translation and sets another property on my user control. – Sonic Soul Apr 26 '11 at 22:26
  • @Luke Woodward: One thousand times thanks! Your post spared me at least a day of debugging. – Dimitri C. Jun 14 '12 at 13:07
5

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();
    }
Eric
  • 59
  • 1
  • 1
1

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;
        }
    }
bendewey
  • 39,709
  • 13
  • 100
  • 125
1

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)

default.kramer
  • 5,943
  • 2
  • 32
  • 50
  • right on. i did assign that property directly and it does work every time.. so the issue seems to stem from one dep property watching another.. somehow the notification of change happens only once... – Sonic Soul Apr 27 '11 at 14:57