将子项添加到 UserControl

发布于 2025-01-02 03:59:06 字数 3752 浏览 1 评论 0原文

我的任务

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

我的问题

我无法让子项在我的容器中正确显示,我尝试了几种方法,但没有找到在设计器中有效的方法。我还尝试使用 ContentControl 但没有显示任何内容。

我的方法

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

之后,我尝试通过将其 Content 属性绑定到 UIElementCollection 类型的可绑定属性来使用 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));
        }
    }
}

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>

Generic-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>

My task

Create a UserControl which 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.

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 ContentControl but nothing gets displayed.

My approaches

First i found this link 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.

After this i tried to use ContentControl by 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.

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.

I'm sure there must be a easy way to archive this.

Current situation

Thanks to Andrei Gavrila and jberger i 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

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>

Generic-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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

二货你真萌 2025-01-09 03:59:06

通常,您无法绑定 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 特性设置将由该类型的父元素所包含的任何元素设置的属性。因此, 中的任何元素都将添加到 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

请注意标签如何成为 元素的直接后代。您还可以将它们包含在 元素中。不过,我们添加到 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>

You cannot bind dependency properties of type UIElementCollection, generally. Try something like this:

MultiChildDemo.xaml

Nothing much to see here. The StackPanel will hold our child elements. You could obviously do quite a bit more.

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

Important to note:

  • The ContentPropertyAttribute attribute 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 the Children property.
  • 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 the Children property: DependencyProperty takes 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.

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

Note how the labels are direct descendants of the <demo:MultiChildDemo> element. You could also enclose them in a <demo:MultiChildDemo.Children> element. The ContentPropertyAttribute attribute that we added to the MultiChild class allows us to omit this step, though.

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>
白鸥掠海 2025-01-09 03:59:06

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

Just remove the UserControl tag and replace with Grid

恬淡成诗 2025-01-09 03:59:06

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

为了简单起见:

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

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

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

Adam Nathan - WPF Unleashed 4

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

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

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

上述步骤修改为:

Derive Items控件

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" >

First of all try to understand the difference between an User Control and 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."

Adam Nathan - WPF Unleashed 4

Now if all you want is a ContentControl:

  1. Make a new CustomControl that derives ContentControl
  2. 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
  3. Use the control as a regular ContentControl.

For multiple items as Content take a look at 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

<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

EDIT:

The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin

You should set the Border properties on your control itself:

<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文