将子项添加到UserControl

时间:2012-02-01 10:45:47

标签: c# wpf user-controls wpf-controls

我的任务

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

我的问题

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

我的方法

首先我找到this链接,我尝试了一些变化。我设法在正确的容器中显示内容,但它在设计器中不起作用,因为content-property是set-private而设计者想要覆盖它。将所有内容放在XAML中都有效,但在与设计师合作时这并不好。这可能是最喜欢的方式。

在此之后,我试图通过将ContentControl - 属性绑定到Content - 类型的可绑定属性来使用UIElementCollection。这种方法不会给设计师带来任何错误,但我不得不承认我的容器中从未见过任何控制(例如Button)。它保持空白但是已经添加了孩子。

结论

在寻找简单快捷的解决方案后,我决定在这里寻求解决方案。我有点失望。如果微软可以将样本带入MSDN,那将非常有用。

我确信必须有一种简单的方法来存档。

现状

感谢 Andrei Gavrila jberger 我已归档创建一个显示内容的节点(请参阅下面的代码),但仍有两个问题: - 没有设计师支持 - 边框(参见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>

3 个答案:

答案 0 :(得分:42)

通常,您无法绑定类型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>

答案 1 :(得分:4)

首先尝试了解User Control和自定义控件(Control / Content Control

之间的区别

为了简单起见:

  

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

     

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

     

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

Adam Nathan - WPF释放4

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

  1. 制作一个新的CustomControl,用于派生ContentControl
  2. 在主题中找到generic.xaml,并将Content Presenter添加到控件模板中。如上所述,自定义控制逻辑与其可视化表示分离
  3. 将控件用作常规ContentControl。
  4. 对于多个项目,内容请查看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" >
    

答案 2 :(得分:4)

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

即可