Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XAML UserControl inheritance

Coming from Java, I'm really used to a common practice when it comes to make GUI components: I usually do some sort of base class which contains all the common objects for my GUI components and then I extend it.

So, basically, this is what I'd like to achieve with C# and XAML.

To make the question clear, here's an example (that is not working!) of what I'm doing:

We've got a base class with its own XAML

<UserControl x:Class="BaseClass"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
        <Border BorderBrush="Aqua" BorderThickness="10" CornerRadius="10" x:Name="Border" HorizontalAlignment="Left" Height="480" VerticalAlignment="Top" Width="480"/>

    </Grid>
</UserControl>

and then we've got a class which extends the first one

<base:BaseClass x:Class="DerivedClass"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:base="clr-namespace:BaseClass"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="60" d:DesignWidth="200">

    <Grid x:Name="LayoutRoot" Margin="0" Width="200" Height="60" MaxWidth="200" MaxHeight="60" Background="{StaticResource PhoneAccentBrush}">        
        <TextBlock x:Name="dummyText" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Dummy Plugin" VerticalAlignment="Top" Height="40" Width="180" Foreground="White" TextAlignment="Center"/>
    </Grid>
</base:BaseClass>

Starting from the 2 XAML codes, what I'd like to do is to have the DerivedClass into the BaseClass container. This will allow me to share components between the various derived classes without having to write the code everytime that I need it.

For example, if I want all my components to have that rounded border, I'd like to just put it in the bass class and then have it in all the derived ones without having to rewrite it.

Of course, each c# class has its own InitializeComponent() method and this probably means that the derived component will build its own content by removing the base class' one.

Removing the method from the DerivedClass constructor gives me the base content even in the derived class, but, of course, I lose everything I made in the XAML design window of the DerivedClass.

Calling the base constructor from the DerivedClass has no effect, as it's called before the derived InitializeComponent().

So the question is: how can I use the XAML design from a base class into the derived one without breaking the XAML design of the derived class? Is there any way to simply add content to the base class while still working with the designer itself?

(I know that I can remove the XAML for the derived class and do what I want to do by code, but I want to know if I can do this just with the designer as I don't want to write my GUI when I have a designer available)

EDIT:

Following HighCore's reply, I did something that works on Windows Phone but I'm not sure that I'm doing the right thing (yeah, it works, but maybe is just wrong!).

Here's what I did:

BaseControl.xaml

<UserControl x:Class="TestInheritance.BaseControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">


     <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">        
        <TextBlock HorizontalAlignment="Center">BASE</TextBlock>        
        <ContentPresenter Name="Presenter" Content="{Binding PresenterContent}"/>
    </Grid>
</UserControl>

BaseControl.xaml.cs

namespace TestInheritance
{
    public partial class BaseControl : UserControl
    {

        public Grid PresenterContent { get; set; }        

        public BaseControl()
        {
            DataContext = this;
            InitializeComponent();            
        }
    }
}

DerivedControl.xaml

<local:BaseControl x:Class="TestInheritance.DerivedControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestInheritance"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <local:BaseControl.PresenterContent>
        <Grid>
            <TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Center">DERIVED</TextBlock>
        </Grid>
    </local:BaseControl.PresenterContent>
</local:BaseControl>

Please note that the DerivedClass is an instance of BaseClass as I need them to have some common properties/methods for other reasons.

What do you think about my solution? Does it make sense?

like image 629
StepTNT Avatar asked Aug 31 '13 09:08

StepTNT


1 Answers

Ok, let me split this into parts:

Coming from Java

Forget java. It's a really antiquated language which has not evolved since the 90's. C# is a million times better and WPF is the best UI framework up to date.

from what I've seen, java UI frameworks such as swing are conceptually similar to .Net's winforms, which has also been replaced by WPF.

WPF (and it's XAML-based brethren) are fundamentally different from any other frameworks around because of their enhanced capability for customization via Styles and Templates and support for DataBinding.

Because of this, a Significant Mindshift is required when starting on WPF.


I usually do some sort of base class which contains all the common objects for my GUI components and then I extend it.

In WPF, there's the Content Model, which removes the need for inheritance and other bloated unnecesary practices, by introducing the capability to put "anything inside anything".

For example, a Button can be defined like this:

<Button>
    <Button.Content>
        <StackPanel Orientation="Horizontal">
            <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
            <TextBlock Text="Click Me"/>
        </StackPanel>
    <Button.Content>
 </Button>

which results in

A button with a red dot

There's no need to inherit from Button just to define it's content.

There is an additional advantage WPF provides and is really handy, the ContentProperty Attribute which defines what the content of the XAML tags <Button> </Button> represents. Button is derived from ContentControl, which is declared like this:

//Declaration of the System.Windows.Control.ContentControl class,
//inside the PresentationFramework.dll assembly
//...  
[ContentProperty("Content")]
public class ContentControl: Control //...
{
   //...
}

This means that the following XAML is functionally identical to the above:

<Button>
   <StackPanel Orientation="Horizontal">
       <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
       <TextBlock Text="Click Me"/>
    </StackPanel>
</Button>
  • Notice we have removed the <Button.Content> tag, because the ContentProperty attribute takes care of that.

All this is made possible thanks to a feature called ControlTemplates, which define the Visual appearance of a Control, independently of it's behavior.


what I'd like to do is to have the DerivedClass into the BaseClass container.

There are several ways to achieve that, one of them is to leverage ControlTemplates and define a specific container inside the XAML that will host the content:

<UserControl x:Class="BaseClass">
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <DockPanel>
                <TextBlock DockPanel.Dock="Top" Text="I'm the Container"/>

                <!-- This is where the Hosted Content will be placed -->
                <ContentPresenter ContentSource="Content"/>
            </DockPanel>
        </ControlTemplate>
     </UserControl.Template>
</UserControl>

Then you could reuse this template like this:

<Window>
   <my:BaseClass>
       <Border Background="Gray" BorderBrush="Blue" BorderThickness="2"
               VerticalAlignment="Center" HorizontalAlignment="Center">
           <TextBlock Text="Im the Hosted Content" Foreground="AliceBlue"/>
       </Border>
   </my:BaseClass>
</Window>

which results in:

An application window

No need for inheritance or any procedural code stuff.


Another very important aspect when starting in WPF, which is extensively explained in the "Significant Mindshift" link above, is what I tell everyone here:

Learn MVVM before you ever write a single line of code in WPF

  • Most of the time you don't put any code in WPF UI elements, because most things can be achieved by DataBinding (covered in the "DataBinding" link above), or by implementing Reusable Attached Behaviors or Attached Properties. Only VIEW-Specific code should be placed in code behind, which does not deal with Data or Business Logic

  • The boilerplate you might be used to in other frameworks, such as:

    txtLastName.Text = person.LastName;
    txtFirstName.Text = person.FirstName;
    btnSubmit.IsEnabled = person.IsActive;
    

    and stuff like that, is completely unneeded in WPF, again, because of DataBinding.


Another concept which enables high flexibility when it comes to showing data in the UI is WPF's DataTemplates, which allow you to define a specific UI to be used when some Data Type is "rendered" on screen.


Because of all of the above, WPF is fundamentally different from most UI frameworks out there, and thus removes the need for all the horrible boilerplate and hacks which are common in other frameworks,

I suggest you read up on all the links provided and keep in mind all these concepts and practices when defining an application's structure and UI in general.

Let me know if you need further assistance.

like image 116
Federico Berasategui Avatar answered Nov 08 '22 22:11

Federico Berasategui