如何在 UserControl 中使用 ContentPresenter

Posted

技术标签:

【中文标题】如何在 UserControl 中使用 ContentPresenter【英文标题】:How to use a ContentPresenter inside a UserControl 【发布时间】:2016-07-26 13:54:12 【问题描述】:

我想创建一个 UserControl(在这种情况下是一个带有已定义背景颜色的方形按钮),它可以托管它自己的内容。

用户控制:

<UserControl x:Class="SGDB.UI.Controls.ModernButton"
         xmlns:local="clr-namespace:SGDB.UI.Controls"
         xmlns:converter="clr-namespace:SGDB.UI.Converter"
         x:Name="_modernButton">
<Button>
    <Button.Resources>
        <converter:EnumToColorConverter x:Key="ColorConverter"/>
    </Button.Resources>
    <Button.Template>
        <ControlTemplate>
            <Border Width="Binding Size, ElementName=_modernButton" Height="Binding Size, ElementName=_modernButton" BorderBrush="Black" BorderThickness="0.8,0.8,3,3">
                <Grid Background="Binding BackgroundColor, ElementName=_modernButton, Converter=StaticResource ColorConverter">
                    <ContentPresenter/>
                </Grid>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

现在,正如您所料,如果我在 MainView 中使用此控件,一切正常,直到我定义一些内容。

使用:

<control:ModernButton Size="200" BackgroundColor="Light">
    TEST
</control:ModernButton>

在这种情况下,“TEST”将覆盖 UserControl 的整个内容(整个按钮模板)。我猜这是因为 UserControl 中的按钮本身被定义为“内容”,并且在定义新内容时会被覆盖。

所以最后一个问题是:是否有可能实现我想要的?如果是:如何?如何将我在 MainView 中定义的内容“重定向”到按钮模板内的自定义 ContentPresenter 而不是 UserControls 的 ContentPresenter?

如果可能,我不想创建一个新的 dp-propery 来托管我的内容,例如:

<controls:MordernButton Size="200" BackgroundColor="Light">
    <controls:ModernButton.Content>
        I don't want this, if possible
    </controls:ModernButton.Content>
</controls:ModernButton>

【问题讨论】:

你的意思是你不想为此创建新的dp? 正确 - 如果可能的话,当然。 @Chill-X 请参阅下面的答案。如果您遇到任何问题,请告诉我。 【参考方案1】:

使用ContentPropertyAttribute 指示xaml 设置此属性而不是实际的Content 属性。

[ContentProperty("InnerContent")]
public partial class ModernButton : UserControl

    public ModernButton()
    
        InitializeComponent();
    

    public static readonly DependencyProperty InnerContentProperty =
        DependencyProperty.Register("InnerContent", typeof(object), typeof(ModernButton));

    public object InnerContent
    
        get  return (object)GetValue(InnerContentProperty); 
        set  SetValue(InnerContentProperty, value); 
    

然后在您的 xaml 中,将 Content Presenter 绑定为使用 InnerContent 属性。

<ContentPresenter Content="Binding InnerContent, ElementName=_modernButton"/>

这样您就可以在不替换实际内容的情况下执行以下操作。

<control:ModernButton Size="200" BackgroundColor="Light">
    TEST
</control:ModernButton>

【讨论】:

也可以。但我想知道 XAML 中的工作原理——我真的不喜欢用属性装饰代码。 很棒的提示!由于它不是模板覆盖,因此您可以轻松地使用代码定义名称和链接 _modernButton 从何而来? @Glaucus 这就是 OP 将他的 UserControl 命名为。 在尝试制作自定义标题框架后,这似乎确实是最好的解决方案。不过,我确实学到了很多其他东西,这使得托管各种形式的内容更加动态。谢谢。【参考方案2】:

我们开始吧。

<UserControl x:Class="SGDB.UI.Controls.ModernButton"
     xmlns:local="clr-namespace:SGDB.UI.Controls"
     xmlns:converter="clr-namespace:SGDB.UI.Converter"
     x:Name="_modernButton">

    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <Button Content="TemplateBinding Content">
                 <Button.Resources>
                    <converter:EnumToColorConverter x:Key="ColorConverter"/>
                  </Button.Resources>
            <Button.Template >
                <ControlTemplate TargetType="Button">
                    <Border Width="Binding Size,
                                    ElementName=_modernButton"
                    Height="Binding Size,
                                     ElementName=_modernButton"
                    BorderBrush="Black"
                    BorderThickness="0.8,0.8,3,3">
                        <Grid Background="Binding BackgroundColor, ElementName=_modernButton, Converter=StaticResource ColorConverter">
                            <ContentPresenter />
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Button.Template>
            </Button>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

【讨论】:

起初它看起来非常好 - 但是当传递一些内容(“TEST”或)时什么都没有出现 - 控件保持为空(除了它自己的颜色) 就是这样 - 你能解释为什么 Button 的 TargetType 对 ContentPresenter 有如此大的影响吗?如果你在上面,也许你可以解释为什么在将一些内容传递给控件时,按钮的模板绑定会阻止 WPF 清除整个内容?在此先感谢:) 这将是一个很大的话题。可能是this 回答了您的第一个问题。 回答你第二个问题。它不是 Button 的模板绑定,可以帮助我们停止清除整个内容。它的用户控件的模板绑定完成了它的工作。【参考方案3】:

假设你的 UserControl 是:

<UserControl x:Class="QuickAndDirtyAttempt.Decorator" ....
      <UserControl.Template>
        <ControlTemplate TargetType="x:Type local:Decorator">
          <StackPanel Orientation="Vertical">
            <Label>Foo</Label>
            <ContentPresenter/>
            <Label>Bar</Label>
          </StackPanel>
        </ControlTemplate>
      </UserControl.Template>
</UserControl>

注意模板上的 TargetType 属性:没有它,项目将顺利编译,但 ContentPresenter 将无法工作。 然后:

<Window ... >
    <StackPanel Orientation="Vertical">
        <local:Decorator>
            <Label Background="Wheat">User supplied content here</Label>
        </local:Decorator>
    </StackPanel>
</Window> 

我强烈建议你在实施任何事情之前read this

【讨论】:

我读了这篇文章,但仔细阅读了上面写着的部分:“没有 TargetType,项目将愉快地编译,但 ContentPresenter 将无法工作”。你应该得到我的支持,因为 CodeProject 解释了为什么不应该使用我的解决方案 :)【参考方案4】:

简单;只需绕过并替换 UserControl 的模板即可。

  <UserControl.Template>
        <ControlTemplate TargetType="x:Type UserControl">
            <Button Content="Binding RelativeSource=RelativeSource TemplatedParent, Path=Content">
                <Button.Resources>
                   <converter:EnumToColorConverter x:Key="ColorConverter"/>
                </Button.Resources>
                <Button.Template>
                    <ControlTemplate TargetType="x:Type Button">
                        <Border Width="Binding Size,
                                        ElementName=_modernButton"
                        Height="Binding Size,
                                         ElementName=_modernButton"
                        BorderBrush="Black"
                        BorderThickness="0.8,0.8,3,3">
                            <Grid Background="Binding BackgroundColor, ElementName=_modernButton, Converter=StaticResource ColorConverter">
                                <ContentPresenter />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </ControlTemplate>
    </UserControl.Template>

所有用户控件都是(至少它是 XAML 及其模板的术语),是一个带有 ContentPresenter 的 Border。 ContentPresenter 是唯一重要的部分,真的。

所以你要做的就是去掉它的 Template 并将 UserControl 的 Content 属性提供给一些不同的东西;在这种情况下,您的按钮。

这是使用户控件脱离其他控件和将某些控件放入用户控件之间的区别。 使用户控件脱离其他控件可以为您提供更多功能。

【讨论】:

认为如果第二个 ControlTemplate 的类型是“按钮”,这也可以工作。 哦,是的,我怎么错过了? - 编辑它。【参考方案5】:

我的对话框示例

<UserControl
x:Class="CyberpunkModManager.Controls.DialogBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CyberpunkModManager.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
Foreground="StaticResource ThemeForeground"
mc:Ignorable="d">
<UserControl.Template>
    <ControlTemplate TargetType="UserControl">
        <Grid Background="StaticResource ThemeTransparentColor">
            <Border
                MinWidth="400"
                Padding="12"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Background="StaticResource ThemeElement"
                CornerRadius="4">
                <ContentPresenter />
            </Border>
        </Grid>
    </ControlTemplate>
</UserControl.Template>

【讨论】:

以上是关于如何在 UserControl 中使用 ContentPresenter的主要内容,如果未能解决你的问题,请参考以下文章

如何设置控件属性(在DataTemplate和UserControl中)的绑定以使用ItemSource的给定属性?

WPF的usercontrol中如何使用消息循环,就是DefWndProc

如何在 UserControl 中扩展模型?

如何在 Blend 中动态更改 UserControl 的外观?

使用该控件时的 MVVM UserControl 和绑定

如何使用自定义内容定义 UserControl?