27

来自 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.

从 调用基本构造函数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();            
        }
    }
}

派生控件.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>

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

你觉得我的解决方案怎么样?是否有意义?

4

2 回答 2

74

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

来自Java

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

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

WPF(它是基于 XAML 的兄弟)与周围的任何其他框架根本不同,因为它们增强了通过样式和模板进行自定义的能力以及对DataBinding的支持。

因此,在 WPF 上开始时需要显着的思维转变。


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

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

例如,aButton可以这样定义:

<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 标记的内容所<Button> </Button>代表的内容的属性。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>
  • 请注意,我们已经删除了<Button.Content>标签,因为该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”链接中介绍)或通过实现可重用的 附加行为附加属性来实现。只有VIEW-Specific代码应该放在代码后面,它不处理数据或业务逻辑

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

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

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


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


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

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

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

于 2013-08-31T16:33:51.597 回答
0

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

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

和派生的用户界面:

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

混合后会是什么结果?

一个直截了当的答案可能是:

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

这是不可能的。

控件嵌入/兼容性问题呢?

于 2013-08-31T10:25:42.577 回答