简体   繁体   English

XAML UserControl继承

[英]XAML UserControl inheritance

Coming from Java, I'm really used to a common practice when it comes to make GUI components: I usually do some sort of base class which contains all the common objects for my GUI components and then I extend it. 来自Java,我真的习惯于制作GUI组件的常用做法:我通常会做一些基类,它包含GUI组件的所有常用对象,然后我扩展它。

So, basically, this is what I'd like to achieve with C# and XAML. 所以,基本上,这是我想用C#和XAML实现的。

To make the question clear, here's an example (that is not working!) of what I'm doing: 为了使问题清楚,这是我正在做的一个例子(那是行不通的!):

We've got a base class with its own 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>

and then we've got a class which extends the first one 然后我们有一个扩展第一个类的类

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

Starting from the 2 XAML codes, what I'd like to do is to have the DerivedClass into the BaseClass container. 从2个XAML代码开始,我想要做的是将DerivedClass放入BaseClass容器中。 This will allow me to share components between the various derived classes without having to write the code everytime that I need it. 这将允许我在各种派生类之间共享组件,而不必在每次需要时编写代码。

For example, if I want all my components to have that rounded border, I'd like to just put it in the bass class and then have it in all the derived ones without having to rewrite it. 例如,如果我希望我的所有组件都有这个圆形边框,我想把它放在bass类中,然后将它放在所有派生类中,而不必重写它。

Of course, each c# class has its own InitializeComponent() method and this probably means that the derived component will build its own content by removing the base class' one. 当然,每个c#类都有自己的InitializeComponent()方法,这可能意味着派生组件将通过删除基类'one来构建自己的内容。

Removing the method from the DerivedClass constructor gives me the base content even in the derived class, but, of course, I lose everything I made in the XAML design window of the DerivedClass . DerivedClass构造函数中删除方法即使在派生类中也提供了基本内容,但是,当然,我丢失了在DerivedClass的XAML设计窗口中DerivedClass

Calling the base constructor from the DerivedClass has no effect, as it's called before the derived InitializeComponent() . DerivedClass调用基础构造函数没有任何效果,因为它在派生的InitializeComponent()之前调用。

So the question is: how can I use the XAML design from a base class into the derived one without breaking the XAML design of the derived class? 所以问题是:如何在不破坏派生类的XAML设计的情况下,将基类的XAML设计用于派生类? Is there any way to simply add content to the base class while still working with the designer itself? 有没有办法简单地将内容添加到基类,同时仍然使用设计器本身?

(I know that I can remove the XAML for the derived class and do what I want to do by code, but I want to know if I can do this just with the designer as I don't want to write my GUI when I have a designer available) (我知道我可以删除派生类的XAML并通过代码执行我想要做的事情,但我想知道我是否可以只使用设计器来执行此操作,因为我不想编写GUI时设计师可用)

EDIT: 编辑:

Following HighCore's reply, I did something that works on Windows Phone but I'm not sure that I'm doing the right thing (yeah, it works, but maybe is just wrong!). 在HighCore的回复之后,我做了一些适用于Windows Phone但我不确定我做的是正确的事情(是的,它有效,但可能是错的!)。

Here's what I did: 这是我做的:

BaseControl.xaml 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 BaseControl.xaml.cs

namespace TestInheritance
{
    public partial class BaseControl : UserControl
    {

        public Grid PresenterContent { get; set; }        

        public BaseControl()
        {
            DataContext = this;
            InitializeComponent();            
        }
    }
}

DerivedControl.xaml 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>

Please note that the DerivedClass is an instance of BaseClass as I need them to have some common properties/methods for other reasons. 请注意, DerivedClassBaseClass一个实例,因为我需要它们出于其他原因而拥有一些常见的属性/方法。

What do you think about my solution? 您对我的解决方案有何看法? Does it make sense? 是否有意义?

Ok, let me split this into parts: 好吧,让我把它分成几部分:

Coming from Java 来自Java

Forget java. 忘了java。 It's a really antiquated language which has not evolved since the 90's. 这是一种非常陈旧的语言,自90年代以来就没有发展过。 C# is a million times better and WPF is the best UI framework up to date. C#是一百万倍,WPF是迄今为止最好的UI框架。

from what I've seen, java UI frameworks such as swing are conceptually similar to .Net's winforms, which has also been replaced by WPF. 从我所看到的,诸如swing之类的java UI框架在概念上类似于.Net的winforms,它也被WPF取代。

WPF (and it's XAML-based brethren) are fundamentally different from any other frameworks around because of their enhanced capability for customization via Styles and Templates and support for DataBinding . WPF(以及它基于XAML的兄弟)与其他任何框架都有着根本的不同,因为它们通过样式和模板增强了自定义功能,并支持DataBinding

Because of this, a Significant Mindshift is required when starting on WPF. 因此,在WPF上启动时需要重要的Mindshift


I usually do some sort of base class which contains all the common objects for my GUI components and then I extend it. 我通常做一些基类,它包含我的GUI组件的所有常见对象,然后我扩展它。

In WPF, there's the Content Model , which removes the need for inheritance and other bloated unnecesary practices, by introducing the capability to put "anything inside anything". 在WPF中,有一个内容模型 ,它通过引入将“任何东西放在任何东西”中的能力来消除对继承和其他膨胀的不必要实践的需要。

For example, a Button can be defined like this: 例如, Button可以这样定义:

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

which results in 结果

带红点的按钮

There's no need to inherit from Button just to define it's content. 只需定义它的内容就不需要继承Button。

There is an additional advantage WPF provides and is really handy, the ContentProperty Attribute which defines what the content of the XAML tags <Button> </Button> represents. WPF提供了一个额外的优点,并且非常方便, ContentProperty属性定义了XAML标记<Button> </Button>代表的内容。 Button is derived from ContentControl , which is declared like this: Button来自ContentControl ,它的声明如下:

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

This means that the following XAML is functionally identical to the above: 这意味着以下XAML在功能上与上述相同:

<Button>
   <StackPanel Orientation="Horizontal">
       <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
       <TextBlock Text="Click Me"/>
    </StackPanel>
</Button>
  • Notice we have removed the <Button.Content> tag, because the ContentProperty attribute takes care of that. 请注意,我们删除了<Button.Content>标记,因为ContentProperty属性负责处理。

All this is made possible thanks to a feature called ControlTemplates , which define the Visual appearance of a Control, independently of it's behavior. 所有这一切都归功于一个名为ControlTemplates的功能,该功能定义了Control的视觉外观,与其行为无关。


what I'd like to do is to have the DerivedClass into the BaseClass container. 我想做的是将DerivedClass放入BaseClass容器中。

There are several ways to achieve that, one of them is to leverage ControlTemplates and define a specific container inside the XAML that will host the content: 有几种方法可以实现这一点,其中之一是利用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>

Then you could reuse this template like this: 然后你可以像这样重用这个模板:

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

which results in: 这导致:

应用程序窗口

No need for inheritance or any procedural code stuff. 不需要继承或任何程序代码。


Another very important aspect when starting in WPF, which is extensively explained in the "Significant Mindshift" link above, is what I tell everyone here: 在WPF中启动的另一个非常重要的方面,在上面的“重大Mindshift”链接中进行了广泛的解释,我在这里告诉大家:

Learn MVVM before you ever write a single line of code in WPF 在WPF中编写单行代码之前,先学习MVVM

  • Most of the time you don't put any code in WPF UI elements , because most things can be achieved by DataBinding (covered in the "DataBinding" link above), or by implementing Reusable Attached Behaviors or Attached Properties . 大多数情况下,您不会在WPF UI元素中放置任何代码 ,因为大多数事情都可以通过DataBinding实现(在上面的“DataBinding”链接中介绍),或者通过实现Reusable Attached BehaviorsAttached Properties Only VIEW-Specific code should be placed in code behind, which does not deal with Data or Business Logic 只有特定VIEW的代码才能放在代码后面, 而不涉及数据或业务逻辑

  • The boilerplate you might be used to in other frameworks, such as: 您可能习惯在其他框架中使用的样板,例如:

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

    and stuff like that, is completely unneeded in WPF, again, because of DataBinding. 因为DataBinding,在WPF中完全不需要这样的东西。


Another concept which enables high flexibility when it comes to showing data in the UI is WPF's DataTemplates , which allow you to define a specific UI to be used when some Data Type is "rendered" on screen. 另一个在UI中显示数据时具有高度灵活性的概念是WPF的DataTemplates ,它允许您定义在屏幕上“呈现”某些数据类型时要使用的特定UI。


Because of all of the above, WPF is fundamentally different from most UI frameworks out there, and thus removes the need for all the horrible boilerplate and hacks which are common in other frameworks, 由于上述所有原因,WPF与大多数UI框架根本不同,因此不需要在其他框架中常见的所有可怕的样板和黑客,

I suggest you read up on all the links provided and keep in mind all these concepts and practices when defining an application's structure and UI in general. 我建议您阅读所有提供的链接,并在定义应用程序的结构和UI时记住所有这些概念和实践。

Let me know if you need further assistance. 如果您需要进一步的帮助,请告诉我。

As far as I know, deriving UI as is, is unavailable unless for resources XAML (styles for ex). 据我所知,按原样导出UI是不可用的,除非资源XAML(样式为ex)。 Maybe the reason is the UI manager cannot guess where to put the extended XAML code: Imagine the base UI as follows: 也许原因是UI管理器无法猜测放置扩展XAML代码的位置:想象一下基本UI如下:

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

and the derived UI : 和派生的UI:

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

What would be the result after the mix? 混合后会有什么结果?

a straight forward answer could be: 一个直接的答案可能是:

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

which is not possible. 这是不可能的。

What about controls embedding/compatibility issues ? 那么控制嵌入/兼容性问题呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM