Как использовать ContentPresenter внутри UserControl

Я хотел бы создать 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>

Now, as you may expect it, if I use this Control inside my MainView everthing works just fine until I define some Content.

С использованием:

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

В этом случае «ТЕСТ» переопределит все содержимое пользовательского элемента управления (весь шаблон кнопки). Я предполагаю, что это происходит потому, что кнопка внутри UserControl определяется как «Контент», и она будет переопределена при определении нового контента.

Итак, последний вопрос: возможно ли достичь того, что я ищу? если да: как? Как я могу «перенаправить» контент, который я определяю в своем MainView, в самоопределяемый ContentPresenter внутри моего шаблона кнопок вместо ContentPresenter UserControls?

Если возможно, я не хочу создавать новое свойство dp, в котором размещается мой контент, например:

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

person Th1sD0t    schedule 06.04.2016    source источник
comment
Вы имеете в виду, что не хотите создавать новый dp для этого?   -  person Gopichandar    schedule 06.04.2016
comment
Правильно - если можно, конечно.   -  person Th1sD0t    schedule 06.04.2016
comment
@Chill-X Смотрите мой ответ ниже. Дайте мне знать, если у вас возникнут какие-либо проблемы.   -  person Gopichandar    schedule 06.04.2016


Ответы (5)


Вот так.

<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>
person Gopichandar    schedule 06.04.2016
comment
Сначала это казалось очень хорошим, но при передаче некоторого содержимого (TEST или ‹TextBlock Text=Test/›) ничего не появляется - элемент управления остается пустым (кроме собственного цвета) - person Th1sD0t; 06.04.2016
comment
Вот и все — не могли бы вы объяснить, почему TargetType кнопки оказал такое большое влияние на ContentPresenter? И если вы занимаетесь этим, может быть, вы могли бы объяснить, почему привязка шаблона кнопки не позволяет WPF очищать весь контент при передаче некоторого контента в элемент управления? Заранее спасибо :) - person Th1sD0t; 06.04.2016
comment
Это будет большая тема для обсуждения. Может быть, это отвечает на ваш первый вопрос. - person Gopichandar; 06.04.2016
comment
Чтобы ответить вам на второй вопрос. Это не привязка шаблона Button, которая помогает нам прекратить очистку всего содержимого. Привязка шаблона Usercontrol сделала свое дело. - person Gopichandar; 06.04.2016

Используйте 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>
person Sriram Sakthivel    schedule 06.04.2016
comment
Тоже работает. Но я хотел знать, как все работает в XAML — мне очень не нравится украшать код атрибутами. - person Th1sD0t; 06.04.2016
comment
Отличный совет! поскольку это не переопределение шаблона, вам разрешено легко определять имена и ссылки с помощью кода - person Thiago Romam; 01.12.2016
comment
Откуда взялся _modernButton? - person Glaucus; 21.12.2018
comment
@Glaucus Вот как OP назвал свой UserControl. - person Sriram Sakthivel; 27.12.2018
comment
После попытки создать собственный озаглавленный фрейм это действительно кажется лучшим решением. Однако я узнал много других вещей, которые делают размещение различных форм контента более динамичным. Спасибо. - person Krythic; 31.08.2019

Предположим, что ваш 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> 

Я настоятельно рекомендую вам прочитать это перед реализацией чего-либо

person Noam M    schedule 06.04.2016
comment
Я прочитал это, но перечитал часть, в которой говорится: Без TargetType проект будет успешно скомпилирован, но ContentPresenter не будет работать. Вы заслуживаете моего одобрения, потому что CodeProject объясняет, почему не следует использовать мое решение :) - person Th1sD0t; 06.04.2016

Простой; Просто обойдите и замените шаблон 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 внутри. На самом деле ContentPresenter — единственная важная часть.

Так что все, что вам нужно сделать, это выпотрошить его шаблон и передать свойство Content, которое есть у UserControl, во что-то немного другое; в данном случае, ваша кнопка.

В этом разница между созданием пользовательского элемента управления из других элементов управления и помещением некоторых элементов управления в пользовательский элемент управления. Создание пользовательского элемента управления из других элементов управления дает вам гораздо больше возможностей.

person Logan    schedule 06.04.2016
comment
Думаю, это могло бы работать и в том случае, если тип второго ControlTemplate был бы Button. - person Th1sD0t; 06.04.2016
comment
О да, как я это пропустил? - Отредактировал. - person Logan; 07.04.2016

Мой пример для диалогового окна

<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>
person Pomian    schedule 01.10.2020