【问题标题】:What's the correct way to create a custom control创建自定义控件的正确方法是什么
【发布时间】:2013-12-18 11:27:39
【问题描述】:

编辑(如评论:XY-Problem) - 问题:

我想创建自己的控件,该控件具有预定义的样式和特殊元素(按钮、...)的位置,但总的来说,所有内容都应该能够放置在我的自定义控件中。在我的例子中,自定义控件只是一个“菜单栏”,它应该能够在“GUI 代码”中的任何地方使用——但不需要它必须在那里。但是 使用它时,它应该在任何地方都是相同的样式和行为。一种样式 - 我认为 - 还不够,因为此菜单栏中还有预定义的元素(例如,帮助已经在菜单栏中)

编辑结束。

我想在 WPF 中构建一个自定义控件(只是一个特殊的堆栈面板),具有以下要求: 可用作 xaml 中的任何其他控件 已为自定义控件中的控件定义样式

首先,我只是尝试创建一个 UserControl,其中包含一个具有已定义样式(在 xaml 中)的堆栈面板,用于包含元素(例如 Button)。此 UserControl 包含

<ContentPresenter />

在 xaml 中。使用此方法无法命名包含的元素。例如:

<mynamespace:MyStackPanel>
  <Button Name="w00t">This does not work!</Button>
</mynamespace:MyStackPanel>

接下来的尝试是创建一个“真正的”自定义控件。这个自定义控件只是一个没有 xaml 的类。代码非常简单。类继承自 UserControl,只包含:

StackPanel sp = new StackPanel();
sp.Children.Add(new ContentPresenter());
this.AddChild(sp);

Woooohoooo,现在可以命名包含的元素了。但仍然是一个大问题:如何定义样式?

我可以在 ResourceDictionary 中为我自己的自定义控件定义样式。但我必须将 ResourceDictionary 添加到全局(App.xaml)资源中。然后我可以只为我的自定义控件定义样式 - 而不是包含元素? - 但无论如何……这样做感觉不对!

所以主要问题是:创建可以像任何其他控件一样在 xaml 中使用的自定义控件的“正确”方法是什么?如果第二种方法是正确的方法 - 怎么可能像我在 xaml 中那样设置样式(例如,这个元素中的每个 Button 都有一个特殊的样式)并让它成为一个“全局”ResourceDictionary?

它是如何在第三方产品中实现的?

【问题讨论】:

  • 你为什么要“命名”东西开头?
  • 不能使用Name属性时可以使用x:Name属性, but as @HighCore said, why would you want to do that? To answer your question, the 'correct' way to create a CustomControl`可以在MSDN的Control Authoring Overview页面找到。
  • 元素必须命名,以便我可以在后面的代码中使用它们 - 例如动态创建一个 TabView。
  • 如果你想在容器内设置 UI 元素的样式,只需为这些 UI 元素添加样式(例如 &lt;Style TargetType="Button.../&gt; 到容器的 Resources,例如 &lt;MyStackPanel.Resources&gt;&lt;ResourceDictionary Source="MyStyles.xaml"/&gt;&lt;/MyStackPanel&gt; 或其他东西。跨度>
  • @marty The elements has to be named so I can use them in the code behind - 错误。不要在 WPF 的过程代码中操作 UI 元素。这就是 XAML 的用途。

标签: c# wpf xaml


【解决方案1】:

好的,我给你举个例子,涉及Custom Controls(与UserControls相反)

第 1 步:

创建一个派生自 ContentControl 的新类(仅代码,无 XAML)(或具有与您需要的行为相似的任何 UI 元素)

    public class ReusableContainer : ContentControl
    {
        public static readonly DependencyProperty ButtonProperty = DependencyProperty.Register("Button", typeof(Button), typeof(ReusableContainer), new PropertyMetadata(default(Button)));

        public Button Button
        {
            get { return (Button)GetValue(ButtonProperty); }
            set { SetValue(ButtonProperty, value); }
        }
    }

在此处查看我如何将 Button 属性定义为 DependencyProperty。您可以为自定义控件中所需的任何“内容占位符”添加更多 DP。

第 2 步:

将容器内 UI 元素的预定义样式放在单独的 ResourceDictionary 中:

CustomStyles.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="Button">
        <Setter Property="Background" Value="Green"/>
    </Style>
</ResourceDictionary>

第 3 步:app.xaml 中,为 ReusableContainer 定义一个应用程序范围的样式,它定义了它的模板:

<Application x:Class="WpfApplication14.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication14"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <Style TargetType="{x:Type local:ReusableContainer}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:ReusableContainer}">
                        <ControlTemplate.Resources>
                            <ResourceDictionary Source="CustomStyles.xaml"/>
                        </ControlTemplate.Resources>

                        <ContentPresenter Content="{TemplateBinding Button}"/>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>
</Application>

看看我如何使用TemplateBinding 表达式来定义ContentPresenter 的内容将由ReusableContainer 中的Button 属性定义。

还要注意我如何将CustomStyles.xaml 中的资源添加到ControlTemplate.Resources 集合中。这使得模板内的所有 UI 元素都可以使用这些资源。

第 4 步:

将你的 ReusableContainer 放在一个窗口中:

<Window x:Class="WpfApplication14.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication14"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <local:ReusableContainer>
            <local:ReusableContainer.Button>
                <Button x:Name="Button1" Content="Hello! Button 1"/>
            </local:ReusableContainer.Button>
        </local:ReusableContainer>

        <local:ReusableContainer>
            <local:ReusableContainer.Button>
                <Button x:Name="Button2" Content="Hello! Button 2"/>
            </local:ReusableContainer.Button>
        </local:ReusableContainer>

        <local:ReusableContainer>
            <local:ReusableContainer.Button>
                <Button x:Name="Button3" Content="Hello! Button 3"/>
            </local:ReusableContainer.Button>
        </local:ReusableContainer>
    </StackPanel>
</Window>

【讨论】:

  • 感谢您的回答。这确实解决了我的问题(对于我控制范围内的模板)。但在接受答案之前我会等待,因为我仍然不确定这是否是创建自定义控件的最佳方式...... :)
  • @marty 我在这里仍然看到一个 XY 问题。为什么要仅在此容器内为 UI 元素定义特定样式?对我来说没有多大意义。
  • 我认为这是有道理的,菜单栏中的按钮在整个应用程序中看起来都是一样的,而不是让应用程序上的所有部分自行决定应该使用哪种样式。我不确定这在概念上是否正确......
  • @marty 你说的这个菜单栏是什么?只有按钮吗?如果是这样,我强烈建议使用 ViewModel-first IEnumerable&lt;ICommand&gt; 方法,而不是在 XAML 中定义每个 MenuBar 的 View-First 方法。
  • 菜单栏的内容将不仅仅是按钮(组合框...),但重点是,整个应用程序的内容并不相同 - 就像 ms Outlook 中的功能区一样。
猜你喜欢
  • 2021-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-11
  • 2014-02-14
  • 1970-01-01
  • 2014-12-14
  • 1970-01-01
相关资源
最近更新 更多