如何正确创建自定义控件?

3

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

我想创建自己的控件,其中特殊元素(按钮等)具有预定义的样式和位置,但总体而言,任何内容都应该能够放置在我的自定义控件中。在我的情况下,自定义控件只是一个“菜单栏”,应该能够在“GUI代码”中的任何地方使用 - 但没有必要它必须在那里。但是,它被使用时,应该在任何地方都具有相同的样式和行为。仅有样式是不够的,因为此菜单栏中还有预定义的元素(例如,“帮助”已经在菜单栏中)

编辑结束。

我想在WPF中构建一个自定义控件(只是一个特殊的StackPanel),具有以下要求: 可以像其他控件一样在xaml中使用 对于自定义控件内的控件具有定义好的样式

首先,我尝试创建一个包含具有定义好样式(在xaml中)的容器元素(例如Button)的UserControl中的StackPanel。这个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);

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

我可以在ResourceDictionary中为自定义控件定义样式。但我必须将ResourceDictionary添加到全局(App.xaml)资源中。然后,我只能为我的自定义控件定义样式,而不能为包含的元素定义样式? - 但不管怎样...用这种方式做感觉就是错的!

因此,主要问题是:创建一个可像任何其他控件一样在xaml中使用的自定义控件的“正确”方法是什么?如果第二种方式是正确的方式-如何设置样式,就像在xaml中一样(例如,在该元素中的每个按钮都有一个特殊的样式),并且必须是一个“全局”的ResourceDictionary吗?

第三方库是如何实现的呢?


你为什么想要一开始就给东西“命名”? - Federico Berasategui
1
当您无法使用Name属性时,可以使用x:Name属性,但就像@HighCore所说,为什么要这样做呢? 回答您的问题,创建CustomControl的“正确”方法可以在MSDN上的控件编写概述页面中找到。 - Sheridan
元素必须被命名,这样我才能在后端代码中使用它们——例如,动态创建一个 TabView。 - marty bourque
1
@marty “元素必须被命名,这样我才能在代码后台中使用它们” - 这是错误的。不要在 WPF 的过程式代码中操作 UI 元素。这就是 XAML 的作用。 - Federico Berasategui
Sheridan x:Name也不起作用... @HighCore - 如果没有MyStackPanel-xaml,那么在哪里放置MyStackPanel.Resource? - marty bourque
显示剩余9条评论
1个回答

3

我为您准备了一个示例,涉及到自定义控件(与用户控件相对)

步骤1:

创建一个新类(只有代码,没有XAML),继承自ContentControl(或其他具有类似所需功能的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。
第二步:
在单独的ResourceDictionary中拥有UI元素内部容器的预定义样式: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>

步骤三: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 bourque
@marty,我仍然认为这是一个XY问题。为什么你想要仅在此容器内定义UI元素的特定样式?对我来说没有太多意义。 - Federico Berasategui
我认为,在应用程序中,菜单栏中的按钮应该看起来都是一样的,而不是让应用程序的所有部分自行决定使用哪种样式。我不确定这在概念上是否正确... - marty bourque
@marty,你说的这个MenuBar是什么?它只是按钮吗?如果是这样,我强烈建议使用ViewModel-first的IEnumerable<ICommand>方法,而不是View-First方法,在其中你需要在XAML中定义每个MenuBar。 - Federico Berasategui
菜单栏的内容将不仅仅是按钮(组合框等),但主要的一点是,它不是应用程序中所有地方都相同的内容 - 就像 MS Outlook 中的功能区。 - marty bourque
您可能想要为子控件属性添加属性更改处理程序,以便将子元素从父逻辑树中添加/删除。它们将通过包含在模板中而被加载到可视树中。至于将自定义样式应用于某些子控件,请查看WPF ToolBar的实现方式。具体来说,请查看它如何使用自定义样式键来定义上下文样式,然后在控件内应用这些样式(例如,当ButtonComboBox放置在ToolBar中时,它们的外观与其他地方不同)。 - Mike Strobel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接