I have a tongue-in-cheek thing I say a lot about WPF compared to Windows Forms:

In Windows Forms, there are two ways to do everything: a good way and a bad way.

In WPF, there are ten ways to do everything: two that are amazing, 3 that are good, 1 that is bad, and 4 that suck.

My goal in this post is to help move people upstream towards amazing, specifically how and when to use Panel (and its subclasses).

Markup subclassing

Everytime you go into VS and create a new WPF project, you see markup subclassing. This is the Window x:Class="WPFApplication1.Window1" stuff. It allows you to associate a piece of XAML to a subclass you define in Code. There are four approved places you'll see this in WPF: Application, Window, Page, and UserControl.

More than once, though, I've seen clever people (both outside and inside Microsoft) take this approach one step further. They want to reuse a piece of UI several times. Maybe they have a Grid defined with a set of columns, rows, and controls defined. It's pretty easy to take that Xaml-defined Grid (with all of its XML child elements), put it in it's own Xaml file, slap on an x:Class statement and start rolling. Things work great.

Rock-n-roll, right?

Well, not really.

Inheritance for polymorphism

I read a great internal paper at MS once--written by one of those brilliant old-guard devs with decades of perspective. The point the author pushed: use inheritance only for polymorphism. Translated: when making MyGrid, only subclass Grid if you mean for people to treat your new MyGrid just like another Grid. Code (and a user, for the most part) that deals with Grid should be able to "deal" with MyGrid without any issues.

Where things break down

So let's say you define your BadGrid Xaml like this (with an associated BadGrid.xaml.cs file with properties, events, etc):

<Grid x:Class="PanelFun.BadGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<
Grid.RowDefinitions>
<
RowDefinition/>
<
RowDefinition/>
<
RowDefinition/>
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition/>
<
ColumnDefinition Width="100"/>
</
Grid.ColumnDefinitions>

<
Label Grid.Row="0" Grid.Column="0" Content="_First Name" Target="{Binding ElementName=FirstName}" />
<
TextBox Grid.Row="0" Grid.Column="1" Name="FirstName" />

<
Label Grid.Row="1" Grid.Column="0" Content="_Middle Name" Target="{Binding ElementName=MiddleName}" />
<
TextBox Grid.Row="1" Grid.Column="1" Name="MiddleName" />

<
Label Grid.Row="2" Grid.Column="0" Content="_Last Name" Target="{Binding ElementName=LastName}" />
<
TextBox Grid.Row="2" Grid.Column="1" Name="LastName" />

</
Grid>

Looks okay, right?


Let's use it.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PanelFun">
    <local:BadGrid/>        
</Window>

Run the app. No problem!


Now let's say a naive user (maybe someone in your company or a customer) takes your control and sees that it's a Grid. They write this Xaml:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PanelFun">

<local:BadGrid>
<
Grid.RowDefinitions>
<
RowDefinition/>
</
Grid.RowDefinitions>

<
Label Grid.Row="3" Grid.Column="0" Content="_Favorite Color"/>
<
TextBox Grid.Row="3" Grid.Column="1" />
</
local:BadGrid>
</Window>

Things compile fine. They are using BadGrid just like a Grid. No problem, right?


The problem, of course, is that you didn't mean people to use BadGrid like a Grid. You wanted them to use it like a simple Control. Weird things happen, dogs and cats living together, mass hysteria.


In this case, you get the user-created row inserted into your grid. If the user tries to put a name on the TextBlock, the compiler blows-up because of naming conflicts between the definition scope (in BadGrid.Xaml) and the usage scope (in the Window). The user has no clue why things blew up. (I'm not all that sure myself, actually.)


If you want to use markup subclassing, stay inside the fences


Application, Window, Panel, UserControl. It's pretty simple to paste your desired Xaml into a UserControl, with no unplanned weirdness.


If you want to extend markup subclassing further, we should talk to Rob. He's the Xaml guy. :-)


If you want to do any subclassing, pick the right base class


In general (even if you're not using Markup subclassing), make sure you "mean it" when you use inheritance in WPF (or any object orientated framework, for that matter). If you subclass a ContentControl, do you mean people to treat it like a ContentControl all the time? Same thing for ItemsControl, Decorator, and Panel.


When would I subclass a Panel?


Well, if you want to make your own panel, start with Panel. AniTilePanel from my bag-o-tricks is a good example.


You might want to subclass a bit deeper, but only in special cases.You could make OrderedStackPanel, where you introduce your own AttachedProperty: OrderedStackPanel.Order. This can be applied to items and your panel will order the items in the stack based on the property. The cool thing: without the property set, your panel should act just like StackPanel. This holds to the theory about inheritance and polymorphism.


Make Sense? Sound good? Hack on!