Home > other >  How to create a base class for controls
How to create a base class for controls

Time:03-18

I have a base library that defines a user control

BaseLib.dll:

UC.xaml.cs

<UserControl x:Class="BaseLib.UC" ...

UC.xaml.cs

public abstract partial class UC : UserControl, IFoo
{
    public UC()
    {
        InitializeComponent();
    }

    protected abstract Foo();
}

Then, different libraries can inherit from the control and overload the methods. They only implement abstract methods, and do nothing to the underlying XAML

Lib2.dll

public class NewControl : UC
{
    protected override Foo(){ /* do stuff*/}
}

The base lib compiles just fine, and so do the derived classes. But, when I try to create an instance of the derived controls, I get this error on InitializeComponent() in the UC constructor:

System.Exception: 'The component 'NewControl' does not have a resource identified by the URI '/BaseLib;component/UC.xaml'.'

Is this something I could fix with pack URI somewhere to point to the baselib xaml? I don't know where I would put it. I've looked at similar questions, but they don't exactly fit my intention. I'm not trying to inherit WPF elements and change their appearance or layout

CodePudding user response:

So I did a similar project structure to test. This works fine. The line xmlns:local="clr-namespace:PoopToTest" would reference your BaseLib.dll namespace.

<Window
    x:Class="PoopToTest.MainWindow"
    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:local="clr-namespace:PoopToTest"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <local:ActualControl>
            <TextBlock
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="50"
                Text="Hello" />
        </local:ActualControl>
    </Grid>
</Window>

The base class.

namespace PoopToTest
{
    public abstract class BaseControl : UserControl
    {
        public BaseControl()
        {
            //InitializeComponent() is not in UserControl.  Needed a place to break.
            int x = int.MaxValue;
        }

        protected abstract void Foo();
    }
}

Implementation.

namespace PoopToTest
{
    public class ActualControl : BaseControl
    {
        /// <inheritdoc />
        protected override void Foo()
        {
            //Do Stuff.
        }
    }
}

CodePudding user response:

To create a base class that extends UserControl, you must follow a few rules.

  • The base type must not be a partial class (have a XAML file).
  • Since the base class is not allowed to have a partial XAML class definition, the base class must not call InitializeComponent().
  • The derived non abstract type that extends the base class must be a partial class that has a XAML class definition.
  • The XAML root element of this derived control must be the base class.

If you want to reuse the actual view (the XAML definition) you must define it as a resource e.g. as a ControlTemplate.

The recommended approach is to extend ContentControl instead of UserControl (see second example below). This approach has no limitations compared to extending UserControl. It is very straight forward especially if you want to extend features without overriding the default appearance.

Solution 1: Creating a base class using UserControl as superclass (not recommended)

BaseUserControl.cs
The abstract base class to be used in libraries.

public abstract class BaseUserControl : UserControl, IFoo
{
  protected abstract void Foo();
}

App.xaml
The BaseUserControlTemplate definition.

<Application.Resources>
  <ControlTemplate x:Key="BaseUserControlTemplate" 
                   TargetType="BaseUserControl">
    <TextBlock Text="Reusable view from template" />
  </ControlTemplate>
</Application.Resources>

Library1UserControl.xaml.cs
A custom library implementation of BaseUserControl.

public partial class Library1UserControl : BaseUserControl
{
  public Library1UserControl()
  {
    InitializeComponent();
  }

  protected override void Foo()
  {}
}

Library1UserControl.xaml.cs
The view definition of the Library1UserControl which reuses the predefined (e.g., in App.xaml) BaseControlTemplate.

<BaseControl x:Class="Library1UserControl"
             Template="{StaticResource BaseUserControlTemplate}" />

Solution 2: Creating a base class using ContentControl or Control as superclass (recommended)

In case you want to explicitly define the view as part of the base class, then let your base class extent ContentControl (or Control) and define the default Style in the Generic.xaml resources (the Generic.xaml file is located in the Themes folder of the control library).
This way, all derived classes will automatically inherit the default view (unless they explicitly override the default Style).
Extending ContentControl or Control is generally the recommended approach over using UserControl.
You should consider to extend ContentControl instead of UserControl.

BaseControl.cs

public abstract class BaseControl : Control, IFoo
{
  static CustomControl1()
  {    
    DefaultStyleKeyProperty.OverrideMetadata(typeof(BaseControl), new FrameworkPropertyMetadata(typeof(BaseControl)));
  }

  protected abstract void Foo();
}

Generic.xaml
The Generic.xaml file is located in the Themes folder of the control library.

<ResourceDictionary>
  <Style TargetType="BaseControl">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="BaseControl">          
          <TextBlock Text="Reusable view from template" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

Library1Control.cs
The derived type automatically inherits the default Style (and ControlTemplate) that was defined in the base type.

public class Library1Control : BaseControl
{
  public Library1Control()
  {}

  protected override void Foo()
  {}
}
  • Related