XAML 用户控件继承

Posted

技术标签:

【中文标题】XAML 用户控件继承【英文标题】:XAML UserControl inheritance 【发布时间】:2013-09-03 23:34:48 【问题描述】:

来自 Java,在制作 GUI 组件时我真的习惯了一种常见的做法:我通常会做某种基类,其中包含我的 GUI 组件的所有常见对象,然后我扩展它。

所以,基本上,这就是我想用 C# 和 XAML 实现的目标。

为了让问题更清楚,这里有一个我正在做的例子(这是行不通的!):

我们有一个带有自己的 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>

然后我们有了一个扩展第一个类的类

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

从 2 个 XAML 代码开始,我想做的是将 DerivedClass 放入 BaseClass 容器中。 这将允许我在各个派生类之间共享组件,而无需每次需要时都编写代码。

例如,如果我希望我的所有组件都具有圆形边框,我想将它放在低音类中,然后将它放在所有派生类中,而无需重写它。

当然,每个 c# 类都有自己的 InitializeComponent() 方法,这可能意味着派生组件将通过删除基类的内容来构建自己的内容。

DerivedClass 构造函数中删除该方法为我提供了即使在派生类中的基本内容,但是,当然,我丢失了我在DerivedClass 的XAML 设计窗口中所做的一切。

DerivedClass 调用基本构造函数没有任何效果,因为它在派生的InitializeComponent() 之前调用。

所以问题是:如何在不破坏派生类的 XAML 设计的情况下将 XAML 设计从基类应用到派生类?有什么方法可以简单地将内容添加到基类,同时仍然与设计器本身合作?

(我知道我可以删除派生类的 XAML 并通过代码执行我想要执行的操作,但我想知道我是否可以仅通过设计器执行此操作,因为我不想编写我的 GUI当我有可用的设计师时)

编辑:

根据 HighCore 的回复,我做了一些适用于 Windows Phone 的操作,但我不确定我做的是否正确(是的,它有效,但可能只是错误!)。

这就是我所做的:

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>

请注意DerivedClassBaseClass 的一个实例,因为出于其他原因我需要它们具有一些通用属性/方法。

您对我的解决方案有何看法?有意义吗?

【问题讨论】:

是的,基本上这是我所描述的更“自制”的版本。我想这是可以接受的,因为 WPF 和 Windows Phone 之间的 XAML 实现存在差异。 嗨@StepTNT 如果我输入您的解决方案并亲自尝试,我会收到错误消息:“WpfApplication18.BaseControl”不能是 XAML 文件的根,因为它是使用 XAML 定义的。第 1 行位置 20。我做错了什么? 你应该打开一个新问题并添加一些代码,复制和粘贴并不总是有效:) 在 Silverlight(和 Windows Phone)上,也可能在 WinRT(以及通用应用程序)上,您可以扩展 UserControls,它们应该在 Visual Studio 设计器中工作。从我所看到的情况来看,WPF 并非如此。 Visual Studio 中 WPF 的 XAML 设计器(尚)不支持此功能。 对于带有可视继承的 WPF 解决方法,请参阅:svetoslavsavov.blogspot.gr/2009/09/… 或对于在祖先中显式定义 GUI,请参阅 support.microsoft.com/kb/957231 【参考方案1】:

好的,让我把它分成几部分:

来自 Java

忘记java。这是一种真正过时的语言,自 90 年代以来就没有发展过。 C# 好一百万倍,WPF 是迄今为止最好的 UI 框架。

据我所知,swing 等 java UI 框架在概念上类似于 .Net 的 winforms,后者也已被 WPF 取代。

WPF(它是基于 XAML 的兄弟)与周围的任何其他框架根本不同,因为它们通过Styles and Templates 增强了自定义功能并支持DataBinding。

因此,在 WPF 上启动时需要 Significant Mindshift


我通常会做一些包含所有常见的基类 我的 GUI 组件的对象,然后我扩展它。

在 WPF 中,有 Content Model,它通过引入“将任何东西放入任何东西”的功能,消除了对继承和其他臃肿不必要的做法的需求。

例如,Button 可以这样定义:

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

导致

没有必要仅仅为了定义它的内容而从 Button 继承。

WPF 提供了一个额外的优势并且非常方便,ContentProperty 属性定义了 XAML 标记 &lt;Button&gt; &lt;/Button&gt; 的内容代表什么。 Button 派生自ContentControl,声明如下:

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

   //...

这意味着以下 XAML 在功能上与上述相同:

<Button>
   <StackPanel Orientation="Horizontal">
       <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
       <TextBlock Text="Click Me"/>
    </StackPanel>
</Button>
请注意,我们已删除 &lt;Button.Content&gt; 标记,因为 ContentProperty 属性负责处理。

这一切都归功于名为ControlTemplates 的功能,它定义了控件的视觉外观,与它的行为无关。


我想做的是将 DerivedClass 放入 BaseClass 容器。

有几种方法可以实现这一点,其中之一是利用 ControlTemplates 并在 XAML 中定义一个特定的容器来承载内容:

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

然后你可以像这样重用这个模板:

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

导致:

不需要继承或任何程序代码。


从 WPF 开始的另一个非常重要的方面,在上面的“重要的思维转变”链接中得到了广泛的解释,这是我在这里告诉大家的:

在 WPF 中编写单行代码之前学习 MVVM

大多数时候不要在 WPF UI 元素中放置任何代码,因为大多数事情都可以通过DataBinding 来实现(在上面的“DataBinding”链接中介绍),或通过实现 可重用 Attached Behaviors 或 Attached Properties。只有VIEW-Specific代码应该放在代码后面,does not deal with Data or Business Logic

您可能在其他框架中使用的样板,例如:

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

由于 DataBinding,WPF 中完全不需要类似的东西。


另一个在 UI 中显示数据时具有高度灵活性的概念是 WPF 的DataTemplates,它允许您定义在某些数据类型在屏幕上“呈现”时使用的特定 UI。


由于上述所有因素,WPF 与大多数 UI 框架有着根本的不同,因此无需使用其他框架中常见的所有可怕的样板和 hack,

我建议您阅读提供的所有链接,并在定义应用程序的一般结构和 UI 时牢记所有这些概念和实践。

如果您需要进一步的帮助,请告诉我。

【讨论】:

@StepTNT WPF、Silverlight、WinRT 和 Windows Phone 之间的 XAML 实现略有不同,但更高级别的概念(样式、ControlTemplates、DataTemplates、内容模型、MVVM、DataBinding)所有一样。 +1 这句话“忘记 java。它是一种真正过时的语言,自 90 年代以来就没有发展过” 真的吗?这就是为什么存在 20 多个不同的 MVVM 第三方(大多数开源)库来帮助实现这种混乱的原因,因为它易于实现并且比现有的任何其他库都好得多?我建议您以更诚实的方式展示您的专业知识。您刚刚在***.com/questions/27625181/… 上用与正在讨论的主题完全无关的批评忽略了一些有效的担忧和问题。你的最后一句话一定是个玩笑(我希望如此)。说得够多了。 市面上有几十种语言和工具,有些是根据工作选择的,有些是因为部署要求而选择的,还有一些是因为客户或所在公司施加的限制。有时是只是个人品味的问题。我认为 Java 并不逊色,只是它还没有赶上 .NET 中的所有优点,因为它过去的“所有权”陷入困境。 您关于 Java 与 WPF 的说法大多只是意见,而基于事实的说法(即 Java 自 90 年代以来就没有发展)是完全不真实的。我自己不是 Java 的粉丝,但你的开头是题外话,删除它会改善答案。【参考方案2】:

据我所知,除非资源 XAML(ex 的样式),否则无法按原样派生 UI。 可能原因是 UI 管理器无法猜测扩展 XAML 代码的放置位置: 想象一下基本 UI:

<UserControl > 
 <Grid>
  <Border/>
 </Grid>
</UserControl>

以及派生的用户界面:

<UserControl> 
 <DockPanel>
  <Button/>
 </DockPanel>
</UserControl>

混合后会是什么结果?

一个直接的答案可能是:

  <UserControl > 
    <Grid>
     <Border/>
    </Grid>
    <DockPanel>
     <Button/>
    </DockPanel>
  </UserControl>

这是不可能的。

控件嵌入/兼容性问题如何?

【讨论】:

以上是关于XAML 用户控件继承的主要内容,如果未能解决你的问题,请参考以下文章

如何从继承的用户控件访问 viewModel 依赖属性?

如何在一个 xaml 页面中使用多个用户控件?

WPF 引发异常解析 XAML,其中包括 Winforms 用户控件

从其他 Xaml 文件绑定到用户控件中的元素

wpf 带参数的用户控件,如何在xaml中引入

如何在自定义嵌套用户控件中引用另一个 XAML 元素?