Welcome to Manicprogrammer Sign in | Join | Help

Rediscovering the Obvious

An occasional journey through one man's perspectives as he fumbles along in the footsteps of many great men.
Confused about DependencyProperties

I have a custom control that serves as a generic container for selecting a specific instance of one of a number of types of objects. This container handles the different types by swapping out other user controls as appropriate to what's being selected, for instance I may have a FooPicker user control and a BarPicker user control. Each of these specific UserControl instances needs to have a value for CurrentSelection and the ability to announce CurrentSelectionChanged. Rather than create a common base class and push a bunch of code into it, I thought I could add a DependencyProperty and RoutedEvent on the container to take care of it for me.

This worked and it didn't, and here's why... first, my definitions:

In PickerPanel (the container), I coded:

        public static readonly RoutedEvent CurrentSelectionChangeEvent =
            EventManager.RegisterRoutedEvent(
                "CurrentSelectionChange",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof(PickerPanel));

and

         public static readonly DependencyProperty CurrentSelectionProperty =
            DependencyProperty.Register(
                "CurrentSelection",
                typeof(object),
                typeof(PickerPanel),
                new PropertyMetadata(
                    null,
                    new PropertyChangedCallback(OnCurrentSelectionChanged) )
            );
        private static void OnCurrentSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement picker = (UIElement) d;

            RoutedEventArgs args = new RoutedEventArgs(PickerPanel.CurrentSelectionChangeEvent, d);
            picker.RaiseEvent(args);
        }

Then, in the entity-specific UserControl, I handed the change event with:

         private void _fooLookupGrid_RecordActivated(object sender, RecordActivatedEventArgs e)
        {
            DataRecord dr = (DataRecord) e.Record;
            SetValue(PickerPanel.CurrentSelectionProperty, dr.DataItem);
        }

My assumption is that the call to SetValue should cause the firing of OnCurrentSelectionChanged with the user control being passed as the parameter "d". OnCurrentSelectionChanged never called, ever. I have no idea why, either.

I worked around the issue by replacing the code in the derived class with the below code, but I don't like it.

        private void _storeLookupGrid_RecordActivated(object sender, RecordActivatedEventArgs e)
        {
            DataRecord dr = (DataRecord) e.Record;
            SetValue(PickerPanel.CurrentSelectionProperty, dr.DataItem);
           
            RoutedEventArgs args = new RoutedEventArgs(PickerPanel.CurrentSelectionChangeEvent, this);
            RaiseEvent(args);
        }

 Having made the above workaround, I can now successfully receive the event from PickerPanel's parent with:
        private void _pickerPanel_CurrentSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
        {

        }

        In XAML:
        <someNamespace:PickerPanel
            x:Name="_pickerPanel"
            someNamespace:PickerPanel.CurrentSelectionChanged="_pickerPanel_CurrentSelectionChanged"/>
 

I'm not very happy with this, because I have clue why the event on the dependency property doesn't fire.  Since I'm allowed to do so, since I'm writing this blog, I'm going to try something now, and then finish this post (I'm breaking all sorts of blogging rules here, right?)

I'm back!
My suspicion is confirmed, but I'm still very confused. In the handler above, I added a line of code as such:
        private void _pickerPanel_CurrentSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
        {
            _pickerPanel.SetValue(Pickers.PickerPanel.CurrentSelectionProperty, "String's an object, right?");
        }

Sure enough, my handler was called, and it fired the event again and would have started up ye olde infinite event loop o' doom if the changed event wasn't smart enough to actually check that the object changed.

So, does anybody know why the PropertyChangedCallback provided as part of the PropertyMetadata for a DependencyProperty isn't called when it's set on another object? Am I required to treat it as an AttachedProperty at that point? I thought AttachedProperty was a XAML-specific helper based on http://msdn2.microsoft.com/en-us/library/ms749011.aspx.

 


 

Published Monday, July 23, 2007 1:47 PM by willeke

Filed under: ,

Comments

# re: Confused about DependencyProperties @ Monday, July 23, 2007 5:32 PM

I THINK I understand what you're trying to do.

Have you tried using MVC to decouple the two controls from each other?

Let's say your PickerPanel control had a PickerPanelController and it exposed a "CurrentSelection" property (either as a dependency property or through the INotifyPropertyChanged paradigm).

The PickerPanelController could also expose properties that helped PickerPanel decide which entity specific user control to show (there are several methods you can choose for this). But it would ultimately be responsible for handling the property changed events and for all the logic behind the UI. Your control itself should be like a supermodel: pretty to look at, but not too bright.

(For all you Supermodels who just took offense to that, please realize I wasn't talking about you in particular...just the ones that it applies to.) If you want to discuss further, send me a note. I can give you some pointers. Also look at Paul Stovell's blog at http://www.paulstovell.net he talks about it a lot under the topic of Binding Oriented Programming.

mdizzy

# re: Confused about DependencyProperties @ Tuesday, July 24, 2007 9:50 AM

You're right that there's other design patterns that can work, and that would be inherently more testable, useful, etc.

However, the core question I'm trying to understand here is "Why does the DependencyProperty's metadata-specified function not fire unless the DependencyProperty is modified on the declaring class?"

I went back and checked my "suspicion" regarding it needing to be an Attached Property, and sure enough, changing the declaration of the dependency property to the following works like a charm:

       public static readonly DependencyProperty CurrentSelectionProperty =

           DependencyProperty.RegisterAttached(

//            DependencyProperty.Register(

               "CurrentSelection",

               typeof(object),

               typeof(PickerPanel),

               new PropertyMetadata(

                   null,

                   new PropertyChangedCallback(OnCurrentSelectionChanged) )

           );

willeke

# re: Confused about DependencyProperties @ Tuesday, July 24, 2007 2:02 PM

It's hard to explain how it works. When you register a (non-attached) dependency property, you are telling the framework to create a unique key that you can use to store and retrieve a value for a dependency object.

When you call SetValue passing in a dependency property and value, you're telling the framework that for this particular dependency object (SetValue is a method defined on DO), I want to store a given value under the key provided by this dependency property. Think of it as a multikey dictionary with the two keys being the instance dependency object itself and the dependency property.

Basically, your call to SetValue from a different class did not fire your property changed event because it hadn't been wired to fire from the other class. The value had however been set on the StoreLookUpGrid. For instance if you had called

GetValue(PickerPanel.CurrentSelectionProperty) after your call to SetValue in your StoreLookupGrid, you will see that the value has been set on the SLG itself.

Also although the attached property does appear to work, it's not doing what you think it's doing. Basically the attached property is still being set directly on the SLG not on the PickerPanel. What's happening here is that when you register an attached property, the dp system knows that you want to be informed when something lower in the visual tree has set the value. Think of how Grid.Column and Grid.Row work. You can set these properties on multiple controls beneath a Grid. The Grid then gets notified for each change and uses those values to properly layout its child controls. The value never gets set directly on the Grid itself (unless of course the grid is a child of another grid).

It's somewhat similar to how LayoutEngine works in Winforms.

mdizzy

# re: Confused about DependencyProperties @ Tuesday, July 24, 2007 3:08 PM

Wonderful! It WAS working how I thought it was working, then, I just didn't realize that the framework required a call to RegisterAttached(...) instead of Register(...) for the Metadata events to be wired up across controls.

I recognized that each object had its own copy of the data, I just didn't want to have to push the eventing logic into the specific lookup grids, that's properly a container behavior. Nicely, the RoutedEvent is structured in such a way that the arg's OriginalSource is the picker (entirely untyped), which I can then cast to a DependencyObject and call (ignore the missing cast)

e.OriginalSource.GetValue(PickerPanel.CurrentSelectionProperty);

willeke

Anonymous comments are disabled