如何在 WPF UserControl 中实现自定义接口并避免动态转换?

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

我有几个 WPF 用户控件。这些控件中的每一个都实现了一个接口 IStepEditor

class声明如下:

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

IStepEditor不是很重要,但目前定义如下:

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

然后我有几个 classes 控制这些控件,像这样:

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

当我到处使用 IStepEditor 时,所有这些都按预期工作,但是要将这些控件从 StepConfigController 添加到 WPF window,我最终必须将它们转换为 UserControl,像这样:

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

由于 IStepEditor 实现实际上是一个 UserControl,因此它可以工作。我可以为该演员添加支票,但整个想法似乎是错误的。我通过一些我相信其他人会遵循的弱关系将一个对象投射到另一个非常不相关的对象。

我试图创建一个继承自 UserControlIStepEditor 的抽象 class 并让我的实际 UserControl 派生自它,但是由于 WPF UserControls 是 partial classes,它没有用。它是这样的:

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

所以这个 class 是合法的并且已编译,但在 WPF 中尝试从中派生失败。

public partial class EditorLoadCsv : StepEditorControl
{
}

此代码生成:

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

这是对的,因为 xaml 标记仍然引用 UserControl。尝试将 xaml 标记从 UserControl 更改为 StepEditorControl 失败,但这可能是解决整个问题的正确方法。我也尝试过实现一个 WPF 自定义控件库,但似乎只需要用 2 个方法实现一个接口就需要做很多工作。

XAML是通过Visual Studio设计器自动生成的:

<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>

因此,对以下任一问题的回答都可以解决我的问题:

1- 在使用自定义接口实现 WPF UserControl 时如何避免(可能)危险的动态转换?
2- 如何让 WPF XAML 标记接受自定义 class 而不是 UserControl?
3-首先如何重构代码以避免这种丑陋?

Panel.Children 属性 是一个 UIElementCollection,所以任何应该添加的东西都需要是一个 UIElement,它是基础 class 所有 WPF UI 组件。

不要使用 as 运算符,只需将您的编辑器对象转换为 UIElement。如果它们中的任何一个偶然不是 UI 元素,您将正确地得到一个 InvalidCastException.

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

为了为所有 StepEditors 创建一个 UserControl 派生基础 class,您正确地完成了第一步:

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

但是,从 StepEditorControl 派生的控件的 XAML 必须如下所示:

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