如何在UserControl中使用ContentPresenter

18

我希望创建一个用户控件(在本例中为具有定义的背景颜色的正方形按钮),该控件可以托管自己的内容。

用户控件:

<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内部的按钮被定义为“Content”本身,当定义新内容时它将被覆盖。
所以最终的问题是:我正在寻找的是否可能实现?如果是:如何实现?我如何将我在MainView中定义的“Content”“重定向”到我按钮模板内部的自定义ContentPresenter,而不是UserControls的ContentPresenter?
如果可能,我不想创建一个新的dp属性来托管我的内容,例如:
<controls:MordernButton Size="200" BackgroundColor="Light">
    <controls:ModernButton.Content>
        I don't want this, if possible
    </controls:ModernButton.Content>
</controls:ModernButton>

你的意思是你不想为此创建新的 dp 吗? - Gopichandar
正确 - 如果可能的话,当然。 - Th1sD0t
@Chill-X 请看下面我的回答。如果你遇到任何问题,请告诉我。 - Gopichandar
5个回答

27

使用ContentPropertyAttribute指示XAML设置此属性,而不是实际的内容属性。

[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 中,将 ContentPresenter 绑定到使用 InnerContent 属性。

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

使用这种方式,您可以在不替换实际内容的情况下执行以下操作。

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

4
可以。但我想了解XAML的工作原理——我真的不喜欢用属性来装饰代码。 - Th1sD0t
1
太棒了!由于它不是模板覆盖,您可以轻松地使用代码定义名称和链接。 - Thiago Romam
1
现代按钮(_modernButton)来自哪里? - Glaucus
@Glaucus,这就是OP给他的UserControl命名的方式。 - Sriram Sakthivel
1
尝试制作自定义标题框架后,这似乎真的是最好的解决方案。不过我确实学到了很多其他的东西,这使得托管各种形式的内容更加动态化。谢谢。 - Krythic

16

我们开始吧。

<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”或<TextBlock Text="Test"/>)时,什么也没有显示出来 - 控件保持空白(除了它自己的颜色)。 - Th1sD0t
就是这样 - 你能解释一下为什么Button的TargetType对ContentPresenter有如此大的影响吗?如果你在这方面有经验,也许你可以解释一下为什么Button的Templatebinding会阻止WPF在将一些内容传递到控件时清除整个Content?提前感谢 :) - Th1sD0t
1
这将是一个需要涵盖的大课题。也许这个链接可以回答你的第一个问题。 - Gopichandar
2
回答你的第二个问题。不是按钮的模板绑定帮助我们停止清除整个内容。是用户控件的模板绑定完成了它的工作。 - Gopichandar

7
假设您的用户控件是:
<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> 

我强烈建议在实施任何事情之前阅读这篇文章

我读了这个,但是过度阅读了那部分说:“如果没有 TargetType,则项目将愉快地编译,但 ContentPresenter 将无法工作”。你应该得到我的点赞,因为 CodeProject 解释了为什么不应该使用我的解决方案 :) - Th1sD0t

3

简单的方法是绕过并替换用户控件的模板。

  <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的边框。实际上,ContentPresenter才是最重要的部分。

所以你要做的就是清空它的模板并将用户控件的Content属性提供给一些稍微不同的东西;在这种情况下,就是你的按钮。

这就是使用其他控件制作用户控件和将一些控件放入用户控件之间的区别。使用其他控件制作用户控件会给你更大的能力。


1
如果第二个ControlTemplate的Type是“Button”,那么这也可能起作用。 - Th1sD0t
哦,是啊,我怎么会错过那个?- 我已经编辑了它。 - Logan

0

我的对话框示例

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接