如何创建一个接受在 XAML 中添加子元素的 UserControl

Posted

技术标签:

【中文标题】如何创建一个接受在 XAML 中添加子元素的 UserControl【英文标题】:How to create a UserControl which accepts adding child elements in XAML 【发布时间】:2021-07-25 18:19:13 【问题描述】:

目标:

我想实现类似于GroupBox 控件的功能。

我想要围绕我在 XAML 中指定的子元素进行一些设计和控件:

当前代码:

<GroupBox Header="Filter">
    <local:Filterview></local:Filterview>
</GroupBox>

目标:

<objectViews:ContractableGroupBox Header="TEST">
    <local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>

现状/问题:

我的自定义“组框”可以将其添加到表单并设置标题,但在添加子元素 Filterview 时无法正常工作。

工作(但没有内容):

<objectViews:ContractableGroupBox Header="TEST">
</objectViews:ContractableGroupBox>

Bugs out(内容存在但没有包装):

<objectViews:ContractableGroupBox Header="TEST">
    <local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>

代码隐藏:

这是ContractableGroupBox 的 XAML:

<UserControl x:Class="SoundStudio.Views.ObjectViews.ContractableGroupBox"
             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:local="clr-namespace:SoundStudio.Views.ObjectViews"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Border BorderBrush="#FF303030" Background="#FF646464" CornerRadius="8,8,3,3" >
            <Expander x:Name="ExpanderContent" Header="Binding Header" IsExpanded="True">
                
            </Expander>
        </Border>  
    </Grid>
</UserControl>

注意,我想在父UserControl中指定子元素,但是应该像在Expander节点中一样显示如:

<Expander x:Name="ExpanderContent" Header="Binding Header" IsExpanded="True">
    <local:Filterview></local:Filterview>    
</Expander>

这是当前的ContractableGroupBox.cs

using System.Windows.Controls;

namespace SoundStudio.Views.ObjectViews

    /// <summary>
    /// Interaction logic for ContractableGroupBox.xaml
    /// </summary>
    public partial class ContractableGroupBox : UserControl
    
        public ContractableGroupBox()
        
            InitializeComponent();
            this.DataContext = this;
        
        public string Header  get; set;
    

【问题讨论】:

【参考方案1】:

您看到的是,以下 XAML 覆盖您的 UserControl 的内容,其中包括 GridExpander 本身,这就是标题似乎丢失的原因。

<objectViews:ContractableGroupBox Header="TEST">
    <local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>

作为一般建议,不要UserControlDataContext 设置为自身,这会破坏数据上下文继承并且是不好的做法。关于您的问题,您应该将 Header 设为依赖属性以启用数据绑定并为扩展器的内容添加另一个依赖属性,例如ExpanderContentContent 已存在于 UserControl)。

[ContentProperty(nameof(ExpanderContent))]
public partial class ContractableGroupBox : UserControl

   public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
      nameof(Header), typeof(string), typeof(ContractableGroupBox));


   public static readonly DependencyProperty ExpanderContentProperty = DependencyProperty.Register(
      nameof(ExpanderContent), typeof(object), typeof(ContractableGroupBox));


   public ContractableGroupBox()
   
      InitializeComponent();
   

   public string Header
   
      get => (string)GetValue(HeaderProperty);
      set => SetValue(HeaderProperty, value);
   

   public object ExpanderContent
   
      get => GetValue(ExpanderContentProperty);
      set => SetValue(ExpanderContentProperty, value);
   

顶部的ContentProperty 属性将确保您在XAML 中放入此用户控件中的任何内容(如下所示)将分配给ExpanderContent 属性,而不是Content 类型已经提供的UserControl 属性.如果您不这样做,则必须手动将您的内容分配给ExpanderContent,否则UserControl 本身的实际内容(您的GridExpander 等将被覆盖。

<objectViews:ContractableGroupBox Header="TEST">
    <local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>

您必须使用RelativeSourceAncestorType 更改您的用户控件XAML 绑定,以便它们解析您控件上的依赖属性HeaderExpanderContent。请注意,我将Expander 重命名为Expander,以避免与依赖属性ExpanderContent 发生命名冲突。现在绑定使用依赖属性,甚至不需要设置DataContext

<UserControl x:Class="SoundStudio.Views.ObjectViews.ContractableGroupBox"
             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:local="clr-namespace:SoundStudio.Views.ObjectViews"
             mc:Ignorable="d" >
   <Grid>
      <Border BorderBrush="#FF303030" Background="#FF646464" CornerRadius="8,8,3,3" >
         <Expander x:Name="Expander"
                   IsExpanded="True"
                   Header="Binding Header, RelativeSource=RelativeSource AncestorType=x:Type UserControl"
                   Content="Binding ExpanderContent, RelativeSource=RelativeSource AncestorType=x:Type UserControl"/>
      </Border>  
   </Grid>
</UserControl>

但是,如果您想要添加到Expander 的唯一内容是围绕它的Border,那么您不必创建单独的UserControl。您可以通过复制默认模板并在其中添加Border 来为Expander 创建一个自定义控件模板。

【讨论】:

感谢您的广泛解释。这是我收到的最好的答案之一!还要感谢您在 datacontext 上设置 self 的建议。我对来自 winforms 的 wpf 数据绑定很陌生,在这种情况下,我主要依靠阅读其他地方的问题和教程。

以上是关于如何创建一个接受在 XAML 中添加子元素的 UserControl的主要内容,如果未能解决你的问题,请参考以下文章

如何在wpf应用程序中使gridview成为树视图的子元素

向 XAML 中的元素添加自定义属性?

C#如何在没有 XAML 的情况下创建到父元素的(两种方式)数据绑定

如何在列表视图索引中切换子元素的可见性?

如何在 UWP 后台任务中使用 XAML UI 元素?

如何在silverlight中将子元素的宽度绑定到父元素的宽度