Tackling inheritance in WPF user controls

on

Instead of talking about lofty research ideas today, how about something more prosaic that lots of us do behind the scenes here — programming. Specifically, a topic I’ve wrestled with recently in my first big project using WPF: How do you productively use object inheritance in your custom user controls?

I have an application that displays an array of tiles, each tile being a user control. There are several different types and styles of tiles. I want to be able to use the visual designer to create these tiles, but I also want them to be manipulated in generic ways by the application. This seems like a classic object-oriented scenario. You define a base class for the tiles, along with some methods that they’ll respond to, and then subclass it for the many tile types.

Curiously, this is not a topic covered in the otherwise excellent tome from which I learned WPF. But a little searching on the web quickly reveals some of the basics. You can’t have a xaml-based class inherit from another xaml-based class, but you can inherit from a conventional C#-based class. (You can also have a C#-based class inherit from a xaml-based one, but that’s not useful for my purposes.) So the basic idea is you create the base class as an ordinary class in code:


  public class TileBase : UserControl
  {
    . . .
  }

and then you can define subclasses with the Visual Studio control designer. VS gives you initial xaml that looks like


  
   ...
    

You have to change this into something like the following (“local” is an obvious name to give the local assembly, but you can choose whatever you want):


  <local:TileBase x:Class="FXPAL.Samples.TileType1" 
      xmlns:local="clr-namespace:FXPAL.Samples.MyProject"
      ... >
   ...
  local:TileBase>

and also change the partial class definition (in TileType1.xaml.cs) to inherit from TileBase instead of UserControl.

So far so good. I can now create multiple tile classes to my heart’s content, and the application can traffic in a list of TileBase objects. I can add virtual methods to TileBase that all tiles can override in their own way, e.g., a Refresh method for when the information they display might be stale.

But suppose there are more things in common among my tiles, specifically, design elements. All of my tiles have somewhere a TextBlock element containing the tile’s title, as well as an element whose background should change color when the application tells it to. At first blush, this is easy. Define methods in the base class


  public virtual void SetTitle(string title) { }
  public virtual void SetBackgroundColor(Color color) { }

and override them in all the subclasses. But this approach is a little tedious, as I end up with every subclass defining a method with literally the same text:


  public override void SetTitle(string title) {
      this.titleBlock.Text = title;
  }

I’d really rather have code like that in the base class, especially if it’s more complicated than this sample one-liner. But there’s no nice way for the base class to know about the child’s TextBlock element. I can’t, in TileBase, write


  protected virtual TextBlock titleBlock { get { ... } }

because I can’t tell the designer that one of my elements is an override of a property in the base class. I could, of course, add the following to the code in each subclass:


  protected override TextBlock titleBlock {
      get { return this.myTitleBlock; }
  }

but that’s similar to overriding SetTitle in the first place.

The solution I settled on is to use Data Binding. This might seem obvious to you WPF experts, but I haven’t run into any discussions of it in this context. When getting up to speed on WPF I had originally skipped the chapter on Binding, since it seemed to be all about getting stuff out of a database, or maybe linking a Slider control to the scale transform of some other element. But it seems like a great way for a base class to communicate with subclasses. The strategy is to define dependency properties in the base class, and then use Binding expressions in the subclass xaml files.

So in TileBase, I define a dependency property:


  public string TitleText {
    get { return (string)GetValue(TitleTextProperty); }
    set { SetValue(TitleTextProperty, value); }
  }

  public static readonly DependencyProperty TitleTextProperty =
    DependencyProperty.Register("TitleText", 
                       typeof(string), typeof(TileBase), 
                       new UIPropertyMetadata(""));

(The syntax is a little baroque, but VS supplies a “Dependency Property” Snippet to make it easy.)

Then in a subclass, I can write xaml like this:


  {Binding ElementName=Root, Path=TitleText}">

where “Root” is the name (could be anything) I assign to the top-level element in the xaml file.

This approach seems relatively clean, and also more versatile than the naïve method of using overrides. For example, in the case of my BackgroundColor property, I can use a binding expression to set not only the Background property of any old element, but also the Foreground of a TextBlock element, the BorderBrush of a Border element, or even a property of a pixel shader Effect element.

Finally, I’ll mention some minor gotchas that make the whole subclassing experience less than complete fun:

  • If there’s any error in your class, the C# compiler will give you scary errors about not being able to find the assembly defining the base class (this is assuming the common case where the base class and subclasses are in the same assembly). You have to overlook those errors and concentrate on the more sensible errors it reports. It makes me think the compiler must be doing some fancy dancing to make references to the local assembly work at all.
  • If there are methods in your base class that you want to require subclasses to override, you might be tempted to make them abstract. This doesn’t work — your code might compile, but the visual designer requires a concrete base class, and will give you a somewhat unhelpful error message if it doesn’t find one.

1 Comment

  1. Twitter Comment


    Posted “Tackling inheritance in WPF user controls” by Bill van Melle [link to post] #wpf #xaml

    Posted using Chat Catcher

Comments are closed.