将子项添加到用户控件

本文关键字:用户 控件 添加 | 更新日期: 2023-09-27 18:22:19

我的任务

创建一个应该能够包含 WPF 中可用的任何可视子项的UserControl,这些子项显示在作为UserControl子项的容器中。

我的问题

无法设法在我的容器中正确显示孩子,我尝试了薮猫方式,但没有找到在设计器中工作的方法。我也尝试使用ContentControl但没有任何显示。

我的方法

首先,我找到了这个链接,并尝试了一些变化。我设法在正确的容器中显示内容,但它在设计器中不起作用,因为内容属性是设置私有的,并且设计器想要覆盖它。将所有内容放在 XAML 中是有效的,但在与设计器合作时,这并不好。这可能是最喜欢的方式。

在此之后,我尝试通过将Content属性绑定到UIElementCollection类型的可绑定属性来使用ContentControl。这个 aproach 不会在设计器中抛出任何错误,但我不得不承认我从未在我的容器中看到任何控件(例如Button(。它保持为空,但添加了子项。

结论

经过几个小时的搜索,一个简单快捷的解决方案,我决定在这里寻求解决方案。我有点失望。如果Microsoft可以将示例放入 MSDN,那将非常有帮助。

我相信一定有一种简单的方法来存档它。

现状

感谢Andrei Gavrilajberger,我存档以创建一个显示内容的节点(请参阅下面的代码(,但仍有两个问题:- 无设计器支持- 边框(参见xaml(在设计器中不显示,并且在应用程序运行时不显示,甚至没有边距

public class NodeContent : ContentControl
{
    static NodeContent()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NodeContent), new FrameworkPropertyMetadata(typeof(NodeContent)));
    }
}
public partial class Node : UserControl, INotifyPropertyChanged
{
    UIElementCollection _Elements;
    public event PropertyChangedEventHandler PropertyChanged;
    public UIElementCollection NodeContent
    {
        get { return _Elements; }
        set
        {
            _Elements = value;
            OnPropertyChanged("NodeContent");
        }
    }
    public Node()
    {
        InitializeComponent();
        NodeContent = new UIElementCollection(NodeContentContainer, this);
    }

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

Node-Xaml:

<UserControl x:Class="Pipedream.Nodes.Node"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="216" d:DesignWidth="174" Background="Transparent" Name="NodeControl" xmlns:my="clr-namespace:Pipedream.Nodes">
    <Border BorderThickness="1" CornerRadius="20" BorderBrush="Black" Background="White">
        <Grid>
            <my:NodeContent x:Name="NodeContentContainer" Margin="20" Content="{Binding Source=NodeControl, Path=NodeContent}" />
        </Grid>
    </Border>
</UserControl>

泛型Xaml:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Pipedream.Nodes">

    <Style TargetType="{x:Type local:NodeContent}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:Node}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <ContentPresenter />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

将子项添加到用户控件

通常不能绑定类型 UIElementCollection 的依赖属性。 尝试这样的事情:

MultiChildDemo.xaml

这里没什么可看的。 StackPanel 将容纳我们的子元素。 你显然可以做得更多。

法典:

<UserControl x:Class="Demo.MultiChildDemo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:demo="clr-namespace:Demo"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel x:Name="PART_Host" />
</UserControl>

MultiChildDemo.xaml.cs

重要提示:

  • ContentPropertyAttribute 属性设置将由此类型的父元素包含的任何元素设置的属性。 因此,<MultiChildDemo></MultiChildDemo>中的任何元素都将添加到 Children 属性中。
  • 我们正在扩展UserControl. 这不需要完全自定义的控件。
  • 在 WPF 中,最好使用 DependencyProperty.Register() 和变体创建属性。 您会注意到 Children 属性没有支持变量:DependencyProperty负责为我们存储数据。 如果我们没有创建只读属性,这将允许使用绑定和其他很酷的 WPF 功能。 因此,养成使用依赖属性的习惯非常重要,而不是像您在 Internet 上的示例中经常看到的那样使用普通属性。
  • 这是一个相对简单的依赖项属性示例。 我们要做的就是复制对子依赖属性的引用,从而将调用转发到 UIElementCollection.Add . 那里有更复杂的例子,特别是在MSDN上。

法典:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Demo
{
    [ContentProperty(nameof(Children))]  // Prior to C# 6.0, replace nameof(Children) with "Children"
    public partial class MultiChildDemo : UserControl
    {
        public static readonly DependencyPropertyKey ChildrenProperty = DependencyProperty.RegisterReadOnly(
            nameof(Children),  // Prior to C# 6.0, replace nameof(Children) with "Children"
            typeof(UIElementCollection),
            typeof(MultiChildDemo),
            new PropertyMetadata());
        public UIElementCollection Children
        {
            get { return (UIElementCollection)GetValue(ChildrenProperty.DependencyProperty); }
            private set { SetValue(ChildrenProperty, value); }
        }
        public MultiChildDemo()
        {
            InitializeComponent();
            Children = PART_Host.Children;
        }
    }
}

MultiChildDemoWindow.xaml

请注意标签是如何<demo:MultiChildDemo>元素的直接后代的。 您也可以将它们包含在<demo:MultiChildDemo.Children>元素中。 但是,我们添加到 MultiChild 类的 ContentPropertyAttribute 属性允许我们省略此步骤。

法典:

<Window x:Class="Demo.MultiChildDemoWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:demo="clr-namespace:Demo"
        Title="MultiChildDemoWindow" Height="300" Width="300">
    <demo:MultiChildDemo>
        <Label>Test 1</Label>
        <Label>Test 2</Label>
        <Label>Test 3</Label>
    </demo:MultiChildDemo>
</Window>

只需删除 UserControl 标记并替换为网格

首先尝试了解用户控件和自定义控件(控件/内容控件(之间的区别

为简单起见:

"标准 WPF 控件提供了大量内置控件 功能性。如果其中一个预设控件的功能, 例如进度条或滑块,与您匹配的功能 想要合并,那么您应该为此创建一个新模板 预先存在的控件以实现所需的外观。创建一个新的 模板是创建自定义元素的最简单解决方案,因此 应该首先考虑该选项。

如果要合并到应用程序中的功能可以 通过预先存在的控件和代码的组合来实现, 请考虑创建用户控件。用户控件使您能够绑定 在单个界面中将多个预先存在的控件放在一起并添加 确定其行为方式的代码。

如果没有预先存在的控件或控件组合可以实现 所需的功能,创建自定义控件。自定义控件 使您能够创建定义视觉对象的全新模板 控件的表示形式,并添加确定的自定义代码 控件的功能。

亚当·内森 - WPF 释放 4

现在,如果您只需要一个 ContentControl:

  1. 创建一个派生 ContentControl 的新自定义控件
  2. 在主题中找到 generic.xaml,并将内容演示器添加到控件模板。如上所述,自定义控件逻辑与其视觉呈现是分开的
  3. 将该控件用作常规内容控件。

对于作为内容的多个项目,请查看项目控件

上述步骤修改为:

派生项控件

public class MyCtrl : ItemsControl
{
    static MyCtrl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCtrl), new FrameworkPropertyMetadata(typeof(MyCtrl)));
    }
}

修改 Generic.xaml 以包含 ItemsPresenter

<Style TargetType="{x:Type local:MyCtrl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCtrl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

使用控件

<StackPanel>
    <ctrl:MyCtrl>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
    </ctrl:MyCtrl>
</StackPanel>

如上所述,对于这个简单的情况,没有必要派生 ItemsControl,而只需使用 ItemsControl 并为其定义一个模板。派生项控制,当计划通过添加功能进行扩展时

编辑:

边框(参见xaml(在设计器中不显示,并且在应用程序运行时不显示,甚至没有边距

应在控件本身上设置 Border 属性:

<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >