将子级添加到 UserControl

Posted

技术标签:

【中文标题】将子级添加到 UserControl【英文标题】:Adding children to UserControl 【发布时间】:2012-02-24 01:18:06 【问题描述】:

我的任务

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

我的问题

我无法让孩子在我的容器中正确显示,我尝试了多种方式,但没有找到适合设计器的方式。我也尝试使用ContentControl,但没有显示任何内容。

我的方法

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

在此之后,我尝试通过将 Content-property 绑定到 UIElementCollection-type 的可绑定属性来使用 ContentControl。这种方法不会在设计器中引发任何错误,但我必须承认我在容器中从未看到任何控件(例如 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));
        
    

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

【问题讨论】:

或者,也许,也许,您可以尝试使用 ControlTemplate 来转换面板,而不是创建新的控件? @deerchao 我真的需要一个自己的用户控件。不过谢谢你的建议。 在“我的方法”下,第 2 段似乎自相矛盾:“它保持空白,但添加了孩子。”..? @jberger 哦,这仅仅意味着没有孩子可见。 :-) 您定义了什么模板/主题来显示自定义ContentControl 【参考方案1】:

一般情况下,您不能绑定 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 属性设置将由该类型的父元素所包含的任何元素设置的属性。因此,&lt;MultiChildDemo&gt;&lt;/MultiChildDemo&gt; 中的任何元素都将添加到 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

注意标签是&lt;demo:MultiChildDemo&gt; 元素的直接后代。您也可以将它们包含在 &lt;demo:MultiChildDemo.Children&gt; 元素中。不过,我们添加到 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>

【讨论】:

太棒了!!我用网格替换了你的StackPanel。将MultiChildDemo-item 放在主窗口中后,我在其中放置了一个网格。最后,设计视图完美运行,我可以使用设计器。非常感谢。 这行得通,但是当在设计器中你得到“属性'内容'设置不止一次”。它仍然可以正常构建和运行。 完美答案。仅出于完整性考虑,如果使用 C# 6.0,则应使用 nameof(Children),而不是 "Children" 感谢出色的解决方案。我知道我已经进行了几年的讨论,但是......如果我采用您的解决方案并更改 1 件事,绑定它不起作用的标签内容(即 )。我已经尝试了所有绑定属性的组合,但都没有成功。将内容硬编码为“Hello”,就像一个魅力。有什么想法吗? 我怎样才能只允许一个孩子?我尝试了 UIElement,但它不起作用【参考方案2】:

只需删除 UserControl 标记并替换为 Grid

【讨论】:

是的,我用儿童标签对其进行了测试 令人惊讶的是,这确实有效,即使在 WinRT 中也是如此。 VS/Blend 设计器将不会显示包含在原始 Grid XAML 中的任何子项,一旦您将任何其他子项添加到它,但除此之外这工作得很好,并且是迄今为止最直接的解决方案。 我以前做过这个,作为用户控件的快速和肮脏的替代品。这有点骇人听闻,但如果你没有做任何太花哨的事情,它通常可以正常工作。已经有一段时间了,但如果我没记错的话,你基本上只是在扩展 Grid 而不是 UserControl。【参考方案3】:

首先尝试了解User Control 和自定义控件(Control/Content Control)之间的区别

为了简单起见:

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

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

如果没有预先存在的控件或控件组合可以实现 您想要的功能,创建一个自定义控件。自定义控件 使您能够创建一个全新的模板来定义视觉 控件的表示并添加确定的自定义代码 控件的功能。”

Adam Nathan - WPF Unleashed 4

现在,如果你想要的只是一个 ContentControl:

    创建一个派生 ContentControl 的新 CustomControl 在主题中找到 generic.xaml 并将 Content Presenter 添加到您的控件模板中。如上所述,自定义控制逻辑与其视觉呈现是分开的 将该控件用作常规 ContentControl。

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

以上步骤修改为:

派生项目控制

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 并为其定义一个模板。计划通过添加功能进行扩展时派生 ItemsControl

编辑:

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

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

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

【讨论】:

你测试你的代码了吗?它对我没有任何表现。无论如何,您的 MyCtrl 似乎只是其他控件的容器。但这对我没有帮助。我想创建一个预先存在的控件的组合,一个UserControl,它有一个可以包含任何类型的 UIElements 的子容器(可以是网格或其他东西)。所以我不确定这对我有什么帮助。不过还是谢谢。 我确实做到了。这是与来源skydrive.live.com/… 的链接。它应该会有所帮助。您的控件也可以包含其他控件。只需将它们添加到您的类中,将它们的内部属性作为 DP 在父控件本身上公开,并改进模板以显示它们 好的,谢谢,但我会等待其他答案,因为我希望能够在不使用模板的情况下设计节点。 @FelixK.:不幸的是,WPF 的工作方式,Andrei 是正确的。我将使用从 Control、ContentControl 或 ItemsControl 派生的自定义控件,或者使用模板、DataTemplates 等。鉴于您没有提供太多上下文来说明为什么需要这个确切的解决方案,因此很难将您引导到哪个可能实际上效果最好。 @jberger + Andrei Gavrila:现在可以使用,但不能使用设计器将内容放置在节点中。

以上是关于将子级添加到 UserControl的主要内容,如果未能解决你的问题,请参考以下文章

JS/w2ui:如何使用 w2ui JavaScript UI 库将子级添加到网格?

核心数据父子关系

将子级扩展到父级可滚动内容的 100% 高度

如何将子级和大子级json数据附加到树视图结构中

使用图像的 SRC 将子级传递给父级时出现问题。反应 JS

将子级用作函数时组件不呈现