Feeds:
Posts
Comments

Archive for July, 2008

Oh wow!  This is one I’ve been wanting almost since the day I started learning WPF.  It seemed like such a huge thing missing from WPF.  How in the heck do you manage to make an attached read-only dependency property with a collection type?

At first blush, many of you are thinking "What’s the big deal?  Attached read-only dependency properties are clearly documented and easy to implement."  Very true.  But if you’re thinking that, you’re glossing over the critical bit here: the type of this property is supposed to be a collection.  "So what?" is your response?  Well, smarty, how do you initialize that property?  See, if we rely on the dependency property’s "default value" mechanism, since this is an attached property every instance will have that value.  Now, what you need to be able to do here is lazily create the collection on first access.  However, WPF doesn’t provide you with any callbacks for retrieving the value, only for setting the value, and the XAML parser bypasses the CLR "get wrapper".  So, there’s no place to lazily instantiate the collection.  I searched high and low for a solution here.  I even talked directly with several Microsoft WPF developers.  I think I may have even made an official request for an event to hook into for this purpose.  In the end, though, I was left wanting.

That is, until today.  John Gossman e-mailed me today with a solution to this problem that works with the current WPF system.  Holy smokes!

The key to his solution lies with the fact that I didn’t fully understand WPF (because this something that’s implied and not spelled out in the documentation).  Remember when I said the XAML parser bypasses the CLR "get wrapper"?  Well, that’s not always true.  It seems that if you register the dependency property with a "name" that differs from the CLR name, the XAML parser will call your CLR "get wrapper" instead of bypassing it and going directly to GetValue.  This may not make a lot of sense with out an example.  Here’s such an example (I’m not including the code for the Foo type, as it’s not really relevant):

public class FooCollection : ObservableCollection<Foo>
{
    public static readonly DependencyPropertyKey InstancePropertyKey =
        DependencyProperty.RegisterAttachedReadOnly("InstanceInternal",
        typeof(FooCollection),
        typeof(FooCollection),
        new UIPropertyMetadata(null));

    public static readonly DependencyProperty InstanceProperty = InstancePropertyKey.DependencyProperty;

    private static void SetInstance(DependencyObject obj, FooCollection value)
    {
        obj.SetValue(FooCollection.InstancePropertyKey, value);
    }

    public static FooCollection GetInstance(DependencyObject obj)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        FooCollection foos = obj.GetValue(FooCollection.InstanceProperty) as FooCollection;
        if (foos == null)
        {
            foos = new FooCollection();
            SetInstance(obj, foos);
        }
        return foos;
    }
}

The key above is the first parameter passed to RegisterAttachedReadOnly: "InstanceInternal".  Since the name is different than the implied CLR name ("Instance"), the XAML parser will now call our GetInstance method, which does the lazy creation.  With this in place, the following XAML code actually works (it doesn’t do anything useful, mind you, as this isn’t a meaningful example beyond illustrating the concept of lazily creating the read-only attached collection dependency property).

<Window x:Class="WpfAttachedCollection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ui="clr-namespace:WpfAttachedCollection"
    Title="Window1" Height="300" Width="300">
    <ui:FooCollection.Instance>
        <ui:Foo Name="Bar"/>
    </ui:FooCollection.Instance>
    <Grid>
        
    </Grid>
</Window>

This is one case where the VisualStudio designer fails me.  I get an error "The attachable property ‘Instance’ was not found in type ‘FooCollection’."  I’m not sure what the cause of the failure here is, but when I running the executable, it actually does work.  A little more work and I can add a button that displays the added Foo objects in a MessageBox.Show, just to prove to myself that everything is working.

Like I said, this isn’t exactly documented in the MSDN.  It’s only implied.  The relevant documentation given to me by Mr. Gossman is this:

Implications for Custom Dependency Properties
Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.
Similarly, other aspects of the XAML processor that obtain property values from XAML processing also use GetValue rather than using the wrapper. Therefore, you should also avoid any additional implementation in the get definition beyond the GetValue call.
The following example is a recommended dependency property definition with wrappers, where the property identifier is stored as a public static readonly field, and the get and set definitions contain no code beyond the necessary property system methods that define the dependency property backing.

Mr. Gossman assures me the behavior here won’t change, because it’s relied upon by too much existing code.  So, maybe it will be documented in the future, but for now you can feel safe in make use of this "hack".  I owe Mr. Gossman a huge thank you for providing this to me, and I’m very happy to provide a post here for future Googlers.

Update:  Looks like Mr. Gossman blogged about this as well.

Read Full Post »

Rant: $%!#(* Grid

I’m a tad frustrated by the default templates used by VisualStudio and Expression Blend for WPF.  The XAML always starts you out with a Grid.  WTF?

The Grid panel is the most inefficient of the panels.  It’s also not all that easy to use for most layouts.  In other words, its the layout I would expect most developers to use the second least often (Canvas takes the bottom spot).

Don’t get me wrong.  There’s plenty of scenarios in which Grid is your best layout choice.  However, there’s even more scenarios where it’s not.  Rarely is it the panel I’ll want my Window/Page to use by default.  I almost always rip that Grid out of the generated XAML and replace it with either a DockPanel or a StackPanel.

So why the harsh tone to this rant?  After all, it’s not that big of a deal to replace it, if it’s not appropriate.

The tone is harsh, because having Grid as the default panel generated by tools is leading the unwashed developers into some bad habits.  Most developers don’t understand why Grid isn’t as appropriate as other panels.  When they see it being used by default, they think it’s the panel they should fall back on by default.  This leads to an overuse of the panel.  That really bothers me.  How many times have you seen a Grid used when there’s a single column and/or row, for instance?

Sorry, this is partially a religious debate.  The performance of a Grid isn’t so bad as to matter most of the time.  But I still have the hairs on the back of my neck stand on end the majority of the time I see a Grid used in WPF.  The fact that the generated XAML encourages this doesn’t help.

Update:  Here’s a classic example.  The recently released Prism contains a "view" called OrdersToolbar.  The OrdersToolbar.xaml file contains some lovely code in which the default template’s Grid element was left in the source.  It’s only child element is a horizontal StackPanel that really defines the layout for the user control.  Remove the Grid and the resulting user control is functionally identical, with easier to understand XAML markup and theoretically better performance (yeah, the perf hit here is not noticeable to the user and probably very difficult to measure, so I don’t want to make too much out of that, but it is still there).  Maybe the template shouldn’t include anything but the Window/Page/UserControl, but it certainly shouldn’t include a Grid, IMHO.

Read Full Post »