Eric
Eric

Reputation: 19863

How to implement custom interfaces in WPF UserControl and avoid dynamic casts?

I have several WPF UserControls. Each of these controls implement an interface IStepEditor

The class declaration is as follow:

 public partial class EditorLoadCsv : UserControl, IStepEditor { ... }

IStepEditor is not very important, but it is currently defined as follow:

public interface IStepEditor
{
    StepConfig Save();
    void Load(StepConfig config);
}

I then have several classes holding those controls, like this:

public class StepConfigController
{
    public IStepEditor EditorControl { get; }
}

All of this is working as expected when I use IStepEditor all around, but to add those controls to a WPF window from a StepConfigController, I eventually have to cast them in a UserControl, like this:

((StackPanel)panEditorControl).Children.Add(currentControl as UserControl);

Since the IStepEditor implementation is actually a UserControl, it works. I could add checks for that cast, but the whole idea seems wrong. I cast an object to another very unrelated object through some weak relationship I trust others to follow.

I tried to create an abstract class inheriting from both UserControl and IStepEditor and have my actual UserControls derive from that, but since the WPF UserControls are partial classes, it didn't work. It was something like this:

public abstract class StepEditorControl : UserControl, IStepEditor
{
    abstract public StepConfig Save();
    abstract public Load(StepConfig config);
}

So this class was legit and compiled, but trying to derive from it in WPF failed.

public partial class EditorLoadCsv : StepEditorControl
{
}

This code generates:

error CS0263: Partial declarations of 'EditorLoadCsv' must not specify different base classes

Which is right, because the xaml markup still references a UserControl. Trying to change the xaml markups from UserControl to a StepEditorControl failed, but it could be the proper solution to the whole problem. I also tried to implement a WPF Custom Control Library, but it seemed like a lot of work just to implement an interface with 2 methods.

The XAML is generated automagically through Visual Studio designer:

<UserControl x:Class="MyNamespace.EditorLoadCsv"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyNamespace"
             mc:Ignorable="d" 
             d:DesignHeight="50" d:DesignWidth="623.077">

<!--- Actual UserControl content ---!>

</UserControl>

So an answer to either of these questions could solve my problem:

1- How to avoid a (possibly) dangerous dynamic cast when implementing WPF UserControl with custom interfaces?
2- How to get WPF XAML markup to accept a custom class instead of UserControl?
3- How to refactor code to avoid this ugliness in the first place?

Upvotes: 1

Views: 1699

Answers (1)

Clemens
Clemens

Reputation: 128061

The Panel.Children property is a UIElementCollection, so anything that should be added needs to be a UIElement, which is the base class of all WPF UI components.

Do not use the as operator, but simply cast your editor objects to UIElement. If any of them is by accident not a UIElement, you would correctly get an InvalidCastException.

((Panel)panEditorControl).Children.Add((UIElement)currentControl);

In order to create a UserControl-derived base class for all your StepEditors, you did the first step correctly:

public abstract class StepEditorControl : UserControl, IStepEditor
{
    public abstract void Load(StepConfig config);
    public abstract StepConfig Save();
}

However, the XAML of a control derived from StepEditorControl would have to look like this:

<local:StepEditorControl
    x:Class="MyNamespace.EditorLoadCsv"
    xmlns:local="clr-namespace:MyNamespace"
    ...>
    ...
</local:StepEditorControl>

Upvotes: 1

Related Questions