Skip to content

Building a control which holds content: CustomControl vs. MarkupSubclassing vs. UserControl

June 21, 2007

[Updated on 2007-09-10 with a pointer to the source code of the project that I talk about here: http://robrelyea.com/demos/ControlsWhichHoldContent/HowToSubclassAnExpander.zip]

When you want to build a control that holds content (like a Panel, a content control, an items control), please build a custom control (in .cs/.vb/etc…) instead of using MarkupSubclassing (via xaml).  MarkupSubclassing works well for defining Pages, Windows and UserControls.  It doesn’t work well for building controls that will host more content…

[Update: a good example of a UserControl that can hold more content, requiring an explicit PropertyElement to host the content is mentioned here: MasterPages in WPF. However, I’m not sure if elements that live inside of that control can have a named element.]

Here is a bitmap showing a sample app that I wrote showing 3 different ways to build a subclass of Expander. 

The Columns – 3 different controls

Across the top I list the 3 different techniques I used:

1) Subclass of Expander (using .cs/.vb) via the Custom Control (WPF) item template in VS 2008 ("Orcas")

2) MarkupSubclassing — created Page.xaml/Page.xaml.cs and replaced the root element and the base class with "Expander".

3) Usercontrol containing Expander — created a UserControl (WPF) item template in VS 2008 ("Orcas").  I then replaced the grid inside of the UserControl tag with an Expander.

The Rows – each control without content and then with content

I created 2 rows and put an instance of each of those 3 controls, the top row didn’t add any content to the control in Window1.xaml, the bottom row added a stackpanel with a rectangle and an ellipse.

The Results

image

Subclassing:

Worked perfectly

Markup Subclassing:

Appeared to work in the simple case, but is the wrong path to take…you will run into problems if you add content to the definition markup file (put something in the expander) or set local properties in the definition markup file.  We try to prevent you from going down this path by erroring if you put an element with a name in it…(yes, we can make this experience better…)

UserControl:

Didn’t work…the content that I added to this control wiped out the content of the UserControl, which was the Expander.

The Fundamental Problem

Xaml doesn’t understand the difference of how children should be treated in the Definition scope vs the Usage scope.

Definition of "foo" xaml file:

<UserControl x:Class="foo">
    <Button />
</UserControl>

Usage of "foo" xaml file:

<my:foo>
   <TextBox />
</my:foo>

Sample of Window1.xaml

<Window
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
   xmlns:HowToSubclassAnExpander="clr-namespace:HowToSubclassAnExpander"
   x:Class="HowToSubclassAnExpander.Window1"
   Title="How to subclass an Expander" Height="426.0000192266" Width="900">
   <Grid ShowGridLines="True">
       <Grid.ColumnDefinitions>
           <ColumnDefinition Width=".2in"/>
     <ColumnDefinition Width="0.333333333333333*" />
     <ColumnDefinition Width="0.333333333333333*"/>
           <ColumnDefinition Width="0.333333333333333*"/>
   </Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
           <RowDefinition Height="0.2in" />
     <RowDefinition Height="*" />
     <RowDefinition Height="*" />
       </Grid.RowDefinitions>
       <HowToSubclassAnExpander:CCExpander Margin="4,3.994,4,4.078" Header="SubClass of Expander" IsExpanded="True" Grid.RowSpan="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.Column="1"/>
       <HowToSubclassAnExpander:PageLikeExpander Margin="4,3.994,4,4.078" Grid.Column="2" Header="MarkupSubclass of Expander"
        IsExpanded="True" Grid.RowSpan="1" Grid.Row="1"/>
       <HowToSubclassAnExpander:UCExpander Margin="4,3.994,4,4.078" Grid.Column="3" Header="UserControl Containing Expander"
        IsExpanded="True" Grid.RowSpan="1" Grid.Row="1"/>
       <HowToSubclassAnExpander:CCExpander Margin="4,3.922,4,0" Header="SubClass of Expander" Grid.Row="2"
        IsExpanded="True" Grid.ColumnSpan="1" Grid.Column="1" VerticalAlignment="Top" Height="124">
           <StackPanel>
               <Rectangle Height="10" Fill="Red" />
               <Ellipse Name="e1r1" Height="50" Fill="Yellow" />
           </StackPanel>
       </HowToSubclassAnExpander:CCExpander>
       <HowToSubclassAnExpander:PageLikeExpander Margin="4,3.922,4,0" Grid.Column="2" Grid.Row="2" Header="MarkupSubclass of Expander"
        IsExpanded="True" VerticalAlignment="Top" Height="124">
           <StackPanel>
               <Rectangle Height="10" Fill="Red" />
               <Ellipse Tag="name would error here" Height="50" Fill="Yellow" />
           </StackPanel>
       </HowToSubclassAnExpander:PageLikeExpander>
       <HowToSubclassAnExpander:UCExpander Margin="4,3.922,4,0" Grid.Column="3" Grid.Row="2" Header="UserControl Containing Expander"
        IsExpanded="True" VerticalAlignment="Top" Height="124">
           <StackPanel>
               <Rectangle Height="10" Fill="Red" />
               <Ellipse Tag="name would error here" Height="50" Fill="Yellow" />
           </StackPanel>
       </HowToSubclassAnExpander:UCExpander>
   <TextBox Margin="42.614,0,294.666,0.006" Grid.ColumnSpan="3" Text="Subclass of Expander" />
   <TextBox Grid.Column="2" Margin="0,0,0,0.006" Text="MarkupSubclass of Expander" />
   <TextBox Grid.Column="2" Margin="294.666,0,0,0.006" Grid.ColumnSpan="2" Text="UserControl containing Expander" />
   <TextBox Grid.Column="0" Grid.Row="1" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Left" Margin="-60.153,67.787,0,89.314" Width="144.601" Grid.ColumnSpan="2" Text="No content added">
       <TextBox.RenderTransform>
           <TransformGroup>
               <ScaleTransform ScaleX="1" ScaleY="1"/>
               <SkewTransform AngleX="0" AngleY="0"/>
               <RotateTransform Angle="-89.656"/>
               <TranslateTransform X="0" Y="0"/>
           </TransformGroup>
       </TextBox.RenderTransform>
</
TextBox>
       <TextBox RenderTransformOrigin="0.5,0.5" Width="144.601" Text="Content added" HorizontalAlignment="Left" Margin="-58.153,69.387,0,87.714" Grid.ColumnSpan="2" Grid.Row="2">
           <TextBox.RenderTransform>
               <TransformGroup>
                   <ScaleTransform ScaleX="1" ScaleY="1"/>
                   <SkewTransform AngleX="0" AngleY="0"/>
                   <RotateTransform Angle="-89.656"/>
                   <TranslateTransform X="0" Y="0"/>
               </TransformGroup>
           </TextBox.RenderTransform>
       </TextBox>
 </Grid>
</
Window>

More to come

I will post my code sample and clarify this post based on your questions…I can’t take more time right now…

From → WPF

6 Comments
  1. Nidonocu permalink

    Would you be able to in a future follow up post be able to provide some further details on the tricks to using custom controls and links to any good guides that exist? I found the MSDN docs a little confusing when it came to this and have so far been mashing my controls together with usercontrols and resource templates.

  2. Rob permalink

    Start with the document I redirect to from: http://robrelyea.com/wpf/docs/buildingControls and the link listed on the redirecting page.
    And please give feedback in the MSDN content about what may be missing/wrong/etc… (click add community content)

  3. Andres permalink

    I gave your example a go in both 2K5 w/WPF EXT and 2K8 Orcas Beta 1, and the results were the same:
     
    1. Subclass of Expander: Empty rows2. MarkupSubclass of Expander: Filled rows w/ empty expander and opened, filled expander3. UserControl containing Expander: Filled rows w/ empty expander and expander content
     
    I did not receive the same results you describe. My results show that the second implementation, MarkupSubclass of Expander, seem the most successful at the objective of the sample.
     
    Subclass of Expander:Nothing displayed, at all.
     
    UserControl containing Expander:I agree. Adding content will only replace the existing content in the UserControl; in this case the Expander control. Also, what other implementation did you add to get the IsExpanded and Header properties to be populated in XAML? I received an error when I did this. Recieved [The property was not found in type \’UCExpander\’.] design-time errors for both \’IsExpanded\’ and \’Header\’.
     
    Also, thank you for the post on my current investigation regarding the ContentPropertyAttribute class:
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2110521&SiteID=1 
     
    Before you posting I was heading towards the MarkupSubclass of Expander pattern because the useful benefit for a developer would be to have a graphical representations, but did not yet explore the disadvantages of this usage.
     
    I already did an example of the \’Subclass of Expander\’ patttern for a base Window class, but the graphical aspect was represented in templates, which is why i asked about the extra implementation for the \’Subclass of Expander\’ example in your demonstration.
     
    Again, If I did something wrong please let me know and thank you in advance,
     
    Andres OlivaresApplications Developer
     

  4. Rob permalink

    Just put a link to my zipped up project, so you can see how our code differs.  Sorry it took so long to post.
    Thanks, Rob

  5. Andres permalink

    Thanks for the code. The solutions for both the Subclassing and Markup Subclassing look identical. It seems like the only difference between the two is the Markup support in design-time. Based on your comment:
    "We try to prevent you from going down this path by erroring if you put an element with a name in it…(yes, we can make this experience better…)"
    Does this mean Subclassing is the most preferred for now or Markup Subclassing will/can be the better experience or that Markup Subclassing is the least preferred until it is ready to be the better experience? I would think that it would be the most naturally preferred option since you have mark up reference. I have read about this in other posts; about problems interpreting XAML inheritance. I am just trying to define a simple pattern to complete a simple task.
     
    FYI – The reason I ended up with the results for Subclassing [empty cells] was because I did not comment out the OverrideMetadata call from the DefaultStyleKeyProperty. Once commented all worked.
     
    Again, thank you so much for your time and advice,
     
    Andres OlivaresAoplications Developer 

  6. Adrian permalink

    Hi Rob.
     
    I want to build a MyUserControl derived from UserControl. And I want MyUserControl to expose some content control at design time, just like the Expander does. I mean in the Blend designer I would like MyUserControl to display in the visual tree, under it, a Grid or something like that, so that I could add child controls from the designer.
    Can you give me a hint about how should I implement this?
     
    Thank you in advance and sorry for my poor English.

Leave a comment