C# 将子项添加到 UserControl
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9094486/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Adding children to UserControl
提问by Felix K.
My task
我的任务
Create a UserControlwhich should be able to contain any visual child which is available in WPF, the children are displayed in a container which is a child of the UserControl.
创建一个UserControl应该能够包含 WPF 中可用的任何视觉子项的 ,子项显示在作为UserControl.
My Problem
我的问题
I can't manage to get the children displayed correctly in my container, i tried serval ways and did not find a way which works in the designer. I also tried to use ContentControlbut nothing gets displayed.
我无法让孩子们在我的容器中正确显示,我尝试了 serval 方法,但没有找到在设计器中有效的方法。我也尝试使用ContentControl但没有显示任何内容。
My approaches
我的方法
First i found thislink and i tried it with some variations. I managed to display the content in the right container but it does not work in the designer because the content-property is set-private and the designer want to override it. Placing everything in XAML works but this is not good when working with designers. This is may favorite way.
首先,我找到了这个链接,并尝试了一些变化。我设法在正确的容器中显示内容,但它在设计器中不起作用,因为内容属性设置为私有并且设计器想要覆盖它。将所有内容都放在 XAML 中是可行的,但这在与设计人员合作时并不好。这是可能最喜欢的方式。
After this i tried to use ContentControlby binding it's Content-property to a bindable property of the UIElementCollection-type. This aproach is not throwing any errors in the designer, but i have to admit that i never see any control ( e.g. a Button) in my container. It stays empty but has the children added.
在此之后,我尝试ContentControl通过将它的Content-property 绑定到UIElementCollection-type的可绑定属性来使用。这种方法不会在设计器中引发任何错误,但我必须承认我从未Button在我的容器中看到任何控件(例如 a )。它保持为空,但添加了孩子。
Conclusion
结论
After serval hours of searching for a easy and quick solution i decided to ask for solutions here. I'm a little disappointed. It would be really helpful if Microsoft could get a sample into MSDN.
在搜索了几个小时的简单快捷的解决方案后,我决定在这里寻求解决方案。我有点失望。如果 Microsoft 可以将示例放入 MSDN,那将非常有帮助。
I'm sure there must be a easy way to archive this.
我相信一定有一个简单的方法来存档这个。
Current situation
现在的情况
Thanks to Andrei Gavrilaand jbergeri archived to create a node which displays the content ( see code below ) but there are still two issues: - No designer support - The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin
感谢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));
}
}
}
Node-Xaml:
节点 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>
Generic-Xaml:
通用 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>
采纳答案by Zenexer
You cannot bind dependency properties of type UIElementCollection, generally. Try something like this:
UIElementCollection通常,您不能绑定 type 的依赖项属性。尝试这样的事情:
MultiChildDemo.xaml
MultiChildDemo.xaml
Nothing much to see here. The StackPanel will hold our child elements. You could obviously do quite a bit more.
这里没什么可看的。StackPanel 将保存我们的子元素。你显然可以做得更多。
Code:
代码:
<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
MultiChildDemo.xaml.cs
Important to note:
需要注意的重要事项:
- The
ContentPropertyAttributeattribute sets the property that will be set by any elements enclosed by the parent element of this type. Thus, any elements within<MultiChildDemo></MultiChildDemo>will be added to theChildrenproperty. - We are extending a
UserControl. This does not necessitate a completely custom control. - It is good practice in WPF to make properties using
DependencyProperty.Register()and variants. You will notice that there is no backing variable for theChildrenproperty:DependencyPropertytakes care of storing the data for us. Were we not creating a read-only property, this would enable the use of bindings and other cool WPF features. Thus, it is important to get into the habit of using dependency properties, rather than plain properties as you often see in examples around the Internet. - This is a relatively simple dependency property example. All we do is copy the reference to a child's dependency property, thereby forwarding calls to
UIElementCollection.Add. Much more complex examples are out there, especially on MSDN.
- 该
ContentPropertyAttribute属性设置将由此类型的父元素包含的任何元素设置的属性。因此,其中的任何元素都<MultiChildDemo></MultiChildDemo>将被添加到该Children属性中。 - 我们正在扩展一个
UserControl. 这不需要完全自定义的控件。 - 在 WPF 中使用
DependencyProperty.Register()和变体创建属性是一种很好的做法。您会注意到该Children属性没有支持变量:DependencyProperty负责为我们存储数据。如果我们不创建只读属性,这将允许使用绑定和其他很酷的 WPF 功能。因此,养成使用依赖属性的习惯非常重要,而不是像您在 Internet 上的示例中经常看到的那样简单的属性。 - 这是一个相对简单的依赖属性示例。我们所做的就是复制对孩子的依赖属性的引用,从而将调用转发到
UIElementCollection.Add。那里有更复杂的例子,尤其是在 MSDN 上。
Code:
代码:
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
MultiChildDemoWindow.xaml
Note how the labels are direct descendants of the <demo:MultiChildDemo>element. You could also enclose them in a <demo:MultiChildDemo.Children>element. The ContentPropertyAttributeattribute that we added to the MultiChild class allows us to omit this step, though.
请注意标签如何是<demo:MultiChildDemo>元素的直接后代。您也可以将它们包含在一个<demo:MultiChildDemo.Children>元素中。不过ContentPropertyAttribute,我们添加到 MultiChild 类的属性允许我们省略这一步。
Code:
代码:
<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>
回答by Andrei Gavrila
First of all try to understand the difference between an User Controland a Custom Control (Control/Content Control)
首先尝试了解用户控件和自定义控件(控件/内容控件)之间的区别
To keep it simple:
为简单起见:
"The standard WPF controls provide a great deal of built-in functionality. If the functionality of one of the preset controls, such as a progress bar or a slider, matches the functionality that you want to incorporate, then you should create a new template for that preexisting control to achieve the appearance you want. Creating a new template is the simplest solution to creating a custom element, so you should consider that option first.
If the functionality you want to incorporate into your application can be achieved through a combination of preexisting controls and code, consider creating a user control. User controls enable you to bind together multiple preexisting controls in a single interface and add code that determines how they behave.
If no preexisting control or combination of controls can achieve the functionality you want, create a custom control. Custom controls enable you to create a completely new template that defines the visual representation of the control and to add custom code that determines the control's functionality."
“标准 WPF 控件提供了大量内置功能。如果其中一个预设控件(例如进度条或滑块)的功能与您要合并的功能相匹配,那么您应该创建一个新模板为该预先存在的控件实现您想要的外观。创建新模板是创建自定义元素的最简单的解决方案,因此您应该首先考虑该选项。
如果可以通过组合预先存在的控件和代码来实现您想要合并到应用程序中的功能,请考虑创建一个用户控件。用户控件使您能够在单个界面中将多个预先存在的控件绑定在一起,并添加确定它们行为方式的代码。
如果没有预先存在的控件或控件组合可以实现您想要的功能,请创建自定义控件。自定义控件使您能够创建一个全新的模板来定义控件的可视化表示,并添加确定控件功能的自定义代码。”
Adam Nathan - WPF Unleashed 4
Adam Nathan - WPF 释放 4
Now if all you want is a ContentControl:
现在,如果您只想要一个 ContentControl:
- Make a new CustomControl that derives ContentControl
- Locate the generic.xaml in themes and add a Content Presenter to your control template. As said above, the custom control logic is separated from it's visual presentation
- Use the control as a regular ContentControl.
- 创建一个派生 ContentControl 的新 CustomControl
- 在主题中找到 generic.xaml 并将内容展示器添加到您的控件模板。如上所述,自定义控件逻辑与其视觉呈现分离
- 将该控件用作常规 ContentControl。
For multiple items as Content take a look at ItemsControl
对于作为内容的多个项目,请查看ItemsControl
The steps above are modified as:
以上步骤修改为:
Derive Items Control
派生物品控制
public class MyCtrl : ItemsControl
{
static MyCtrl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCtrl), new FrameworkPropertyMetadata(typeof(MyCtrl)));
}
}
Modify Generic.xaml to include ItemsPresenter
修改 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>
Use the control
使用控件
<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>
As said above, for this simple case it would not be necessary to derive ItemsControl but to simply use the ItemsControl and define a Template for it. Derive ItemsControl when planning to extend by by adding functionality
如上所述,对于这个简单的情况,不需要派生 ItemsControl 而是简单地使用 ItemsControl 并为其定义模板。在计划通过添加功能进行扩展时派生 ItemsControl
EDIT:
编辑:
The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin
边框(请参阅 xaml)未在设计器中显示,在应用程序运行时也未显示,甚至没有边距
You should set the Border properties on your control itself:
您应该在控件本身上设置 Border 属性:
<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
回答by Andreas
Just remove the UserControl tag and replace with Grid
只需删除 UserControl 标记并替换为 Grid

