如何创建包含占位符以供以后使用的 WPF 用户控件

Posted

技术标签:

【中文标题】如何创建包含占位符以供以后使用的 WPF 用户控件【英文标题】:How to create WPF usercontrol which contains placeholders for later usage 【发布时间】:2011-08-11 03:05:52 【问题描述】:

我最好通过例子来提问。假设我有使用此控件的 UserControl 和 Window。

我想以这种方式设计这个控件(名为 MyControl)(这是科幻语法!):

<Grid>
  <Button>Just a button</Button>
  <PlaceHolder Name="place_holder/>
</Grid> 

并在设计我的窗口时以这种方式使用:

<MyControl/>

<MyControl>
  <place_holder>
    <Button>Button 1</Button>
  </place_holder>
</MyControl> 

<MyControl>
  <place_holder>
    <Button>Button 1</Button>
    <Button>Button 2</Button>
  </place_holder>
</MyControl> 

当然,我希望能够在 Window 中向 MyControl 添加更多元素。因此,在某种程度上,它应该作为容器工作(如 Grid、StackPanel 等)。放置将在 UserControl 中定义(在此示例中,在按钮“只是一个按钮”之后),但要添加的内容(哪些元素)将在 Window 中定义(使用 UserControl -- MyControl --)。

我希望这很清楚我想要实现的目标。重点是在设计Window的时候使用XAML,所以我的类应该不会比其他控件差。

现在,最大的问题是——怎么做?

备注:样式超出范围。我要做的就是在设计 Window 时(而不是在设计 MyControl 时)将我想要的任何控件添加到 MyControl。

【问题讨论】:

【参考方案1】:

ContentControls 和 ItemsControls 对此很有用,您可以将它们绑定到 UserControl 的属性或公开它们。

使用 ContentControl(用于多个断开位置的占位符):

<UserControl x:Class="Test.UserControls.MyUserControl2"
             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" 
             Name="control">
    <Grid>
        <Button>Just a button</Button>
        <ContentControl Content="Binding PlaceHolder1, ElementName=control"/>
    </Grid>
</UserControl>
public partial class MyUserControl2 : UserControl

    public static readonly DependencyProperty PlaceHolder1Property =
        DependencyProperty.Register("PlaceHolder1", typeof(object), typeof(MyUserControl2), new UIPropertyMetadata(null));
    public object PlaceHolder1
    
        get  return (object)GetValue(PlaceHolder1Property); 
        set  SetValue(PlaceHolder1Property, value); 
    

    public MyUserControl2()
    
        InitializeComponent();
    

<uc:MyUserControl2>
    <uc:MyUserControl2.PlaceHolder1>
        <TextBlock Text="Test"/>
    </uc:MyUserControl2.PlaceHolder1>
</uc:MyUserControl2>

ItemsControl-Version(用于一个地方的集合)

<UserControl x:Class="Test.UserControls.MyUserControl2"
             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"
             Name="control">
    <Grid>
        <Button>Just a button</Button>
        <ItemsControl Name="_itemsControl" ItemsSource="Binding ItemsSource, ElementName=control"/>
    </Grid>
</UserControl>
[ContentProperty("Items")]
public partial class MyUserControl2 : UserControl

    public static readonly DependencyProperty ItemsSourceProperty = 
        ItemsControl.ItemsSourceProperty.AddOwner(typeof(MyUserControl2));
    public IEnumerable ItemsSource
    
        get  return (IEnumerable)GetValue(ItemsSourceProperty); 
        set  SetValue(ItemsSourceProperty, value); 
    

    public ItemCollection Items
    
        get  return _itemsControl.Items; 
    

    public MyUserControl2()
    
        InitializeComponent();
    

<uc:MyUserControl2>
    <TextBlock Text="Test"/>
    <TextBlock Text="Test"/>
</uc:MyUserControl2>

使用 UserControls,您可以决定公开内部控件的某些属性;除了ItemsSource 之外,可能还想公开ItemsControl.ItemTemplate 之类的属性,但这完全取决于您想如何使用它,如果您只是设置Items,那么您不一定需要任何这些。

【讨论】:

DataContext="Binding RelativeSource=RelativeSource Self,因为它会阻止绑定到实际数据。 UserControl 将无法从其父级继承 DataContext,如果您在使用控件时显式定义它,则 PlaceHolder1 上的绑定将不起作用。您需要改用 RelativeSource 或 ElementName 绑定。 非常感谢。使用 DataContext 修复后,它就像一个魅力。 我只是倾向于这样设置 DataContext,因为在 Windows 中这并不重要,因为它们没有父级。 @H.B.,我也是(在 Windows 中),但在这里确实最好不要。顺便提一句。奇怪的结果,使用这种方法,如果没有特殊的代码圈,您无法命名嵌套控件(示例中为 TextBlock)。见:***.com/questions/751325/… 当然没有抱怨,只是说。也许将来有人会从这里收集的所有智慧中受益:-) 哦,是的,我知道,这确实可能有点问题。【参考方案2】:

我认为您希望使用位于内部的 ContentPresenter 来设置 UserControl 的 ControlTemplate(这样您就可以定义内容的呈现位置)。

您的自定义用户控件:

<UserControl x:Class="TestApp11.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Template>
        <ControlTemplate>
            <StackPanel>
                <TextBlock Text="Custom Control Text Area 1" />
                <ContentPresenter Content="Binding RelativeSource=RelativeSource TemplatedParent, Path=Content" />
                <TextBlock Text="Custom Control Text Area 2" />
            </StackPanel>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

用法:

<Window x:Class="TestApp11.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:l="clr-namespace:TestApp11"
    Title="Window1" Height="250" Width="200">
    <StackPanel>
        <l:UserControl1>
            <Button Content="My Control's Content" />
        </l:UserControl1>
    </StackPanel>
</Window>

如果您的内容部分需要多个项目,只需将它们放在网格或堆栈面板等容器中:

<l:UserControl1>
    <StackPanel>
        <Button Content="Button 1" />
        <Button Content="Button 2" />
    </StackPanel>
</l:UserControl1>

【讨论】:

嗯,感谢您的帮助和链接,但这篇文章正在做一些我已经知道的事情——它定义了一些用户控件,然后使用它们。就是这样——用户控件不充当容器,请注意它们的内容是在控件级别定义的,而不是窗口——这就是我的问题的重点。 @macias,该链接仅包含我所了解的几个概念......但并没有按照我的想法将它们放在一起。我附上了我的意思的示例。 感谢您的澄清,我对 WPF 不太熟悉通过样式“雾”来查看容器 ;-) 我正在标记 H.B.回复为答案,因为尽管您的解决方案是无代码的,但我已经设计了用户控件,并且 H.B.方法需要对现有代码进行较少的更改。不过还是非常感谢! @macias...不用担心...只要您找到了适合您需求的解决方案,这才是最重要的! 有一个问题,无论您在用户控件中放置什么内容,例如 x 不能有命名内容。如何添加带有命名元素的内容?编译错误是无法在元素'x 上设置名称属性值 x。 x 在元素 y 的范围内,当它在另一个范围内定义时,它已经注册了一个名称。

以上是关于如何创建包含占位符以供以后使用的 WPF 用户控件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 WPF 用户控件关闭父窗口

jQuery UI Sortable 将拖动对象的类添加到占位符以确定大小

ruby 为app添加占位符以使用Angular

如何在设计模式下调试 WPF 用户控件?

如何在画布内拖动用户控件

一种用户将选择存储为输入以供以后使用的方法