Saturday, September 12, 2009

User Control Inheritance in WPF

User control inheritance is very useful in a lot of cases. But it seems like WPF doesn't want us to do that. Have you tried to create a user control (defined with XAML and code-behind) , and then inherit it? The compiler gets very mad at us and says there is an error:

'SomeBaseControl' cannot be the root of a XAML file because it was defined using XAML

Now, what can we do to avoid this unpleasant message?

Variant 1,
Use a custom control for the base class (defined only with code, not XAML)

This is handy sometimes. What you do is create the base control as a custom control, using just the code



public class TestBaseCtrl : Control
{
// Add some base logic here...
}



You can add your controls from the code and create the base logic. Then you can inherit that control even in XAML, like this.



This is a working solution, but it's not very convenient. You don't have the flexibility of XAML in the base control. So let's take a look at the other solution.

Variant 2,
Create the base control using XAML

OK, but how on earth do we do that? We all saw the error message - we cannot define a base control in XAML and then inherit it again in XAML, according to the compiler. However, there is an easy way to trick the compiler to allow us to do that.
The main idea is to create a base control as a custom control again, and then define a control template in XAML. It's basically the same as defining the base control with XAML. Here is what I mean. This is the code-behind for the control:


public class TestBaseCtrl : UserControl
{
public static RoutedCommand TestButton1 = new RoutedCommand();
public static RoutedCommand TestButton2 = new RoutedCommand();

static TestBaseCtrl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestBaseCtrl), new FrameworkPropertyMetadata(typeof(TestBaseCtrl)));
}

public TestBaseCtrl()
{
this.CommandBindings.Add(new CommandBinding(TestButton1, TestButton1_Executed));
this.CommandBindings.Add(new CommandBinding(TestButton2, TestButton2_Executed));
}

private void TestButton1_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("This is button 1 from the base control");
}

private void TestButton2_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("This is button 2 from the base control");
}
}


And this is the control template, defined in XAML:



You can see that we have specified an area of the base control, in which the content of the child controls would be displayed, using ContentPresenter.
Now, let's inherit that control:



Now, the WPF compiler is totally OK with it, and even displays the inherited control in the designer:

And there you go - now you have a base control and an inherited control which both use the power of XAML. Everything goes well in the compilation and the compiler is no longer mad at us:

========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

Download sample application

The sample can be downloaded here: Download sample

18 comments:

  1. This works great in C#...but the implementation doesn't work in VB.Net. :( Seems in VB when you specify to inherit from the base control, it doesn't update it in the generated file. So you end up with two partial classes inheriting from two different objects. :(

    ReplyDelete
  2. After looking at this again...it does work in VB. I forgot to change the base control to inherit from UserControl instead of Control. After doing that, the derived control was able to inherit from the base. Sorry for the confusion.

    ReplyDelete
  3. How on Earth did you get this to work with VB? It seems to be easy using C#, but when I try doing this with VB, in my xaml (InheritedUserControl) the compiler does not find the "TestBaseCtrl" in the namespace "UserControlInheritance". :-(

    ReplyDelete
  4. Hi Svetoslav,
    The control template, if its defined inside Generic.xaml then its working fine. But when I want to define the control template in another class library its not working. Can you please look into that..

    ReplyDelete
  5. If you define the control template in another XAML file, you'll have to include it explicitly somewhere, for example add a Merged Dictionary in generic.xaml.

    ReplyDelete
  6. Hi Svetoslav,

    Thanks for adding nice article about control inheritance in WPF. Can you explain how to access base control (if protected) in designer?

    ReplyDelete
  7. Hello,
    You should have access to all properties of the base control in the designer of the inherited control, simply by using something like
    ...
    You cannot directly access elements defined in the generic template though. If you want to do that, you should create some kind of wrapper for them in the code-behind of the base control and use it instead.

    ReplyDelete
  8. Can't seem to make this work in VB.Net. This doesn't work in VB: "<local:TestBaseCtrl"

    The design time error is: "Error 39 The type 'Local:TestBaseCtrl' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built."

    ReplyDelete
  9. How do I download this example.

    The link does not seem to be working.

    ReplyDelete
  10. The link is working, you'll just have to wait a few seconds (a limitation of the free access to the file hosting provider). But if you still can't download the example, contact me by e-mail.

    ReplyDelete
  11. 1. The link is working, but the content of the downloaded zip seems to be wrong somehow...
    2. Svetoslav, your work is excellent as far it is shown here. But I have the promblem, that I can't use the "InheritedUserControl". I mean I can drop it on a window (included in a grid), compile it without any error but no control is shown on my window. Why is that?

    ReplyDelete
  12. Hello Michael,
    Can you contact me by e-mail? I will send you the zip file so you can use it as a starting point or as an example.

    ReplyDelete
  13. I understand the trick and example you provided but what is real purpose of doing that...

    If needed i can also put the controls itself that i will be showing in contentpresenter directly in template itself so what will be real use.

    ReplyDelete
  14. I want to develop wpf application and i want to display module's icon on main window and i want to move that icon set with left mouse click, do u have any idea how to develop this application.

    ReplyDelete
  15. I tried it for myself, but I can't get it to work.
    Can you look at http://stackoverflow.com/questions/21728012/customcontrol-with-templateparts and help?

    ReplyDelete
  16. Thanks a million!
    Saved my posterior in a project of mine :)

    ReplyDelete
  17. How can you bind properties to controls in your base class?

    ReplyDelete