Добавление детей в UserControl - PullRequest
22 голосов
/ 01 февраля 2012

Моя задача

Создайте UserControl, который должен содержать любого визуального дочернего элемента, доступного в WPF, дочерние элементы отображаются в контейнере, который является дочерним элементом UserControl.

Моя проблема

Мне не удается правильно отобразить детей в моем контейнере, я пробовал сервальные способы и не нашел способа, который работает в конструкторе. Я также пытался использовать ContentControl, но ничего не отображается.

Мои подходы

Сначала я нашел эту ссылку, и я попробовал ее с некоторыми вариациями. Мне удалось отобразить контент в нужном контейнере, но он не работает в конструкторе, потому что свойство content установлено в private и дизайнер хочет переопределить его. Размещение всего в XAML работает, но это не очень хорошо при работе с дизайнерами. Это может быть любимый способ.

После этого я попытался использовать ContentControl, привязав его Content -свойство к привязываемому свойству UIElementCollection -типа. Этот подход не вызывает каких-либо ошибок в конструкторе, но я должен признать, что никогда не вижу никакого элемента управления (например, Button) в своем контейнере. Он остается пустым, но к нему добавляются дети.

Заключение

После нескольких часов поисков простого и быстрого решения я решил спросить здесь. Я немного разочарован. Было бы очень полезно, если бы Microsoft смогла получить образец в MSDN.

Я уверен, что должен быть простой способ заархивировать это.

Текущая ситуация

Благодаря Андрею Гавриле и jberger я заархивировал, чтобы создать узел, который отображает контент (см. Код ниже), но есть еще две проблемы: - Нет дизайнерской поддержки - Граница (см. Xaml) не отображается в конструкторе и не отображается, когда приложение работает, даже поля нет

public class NodeContent : ContentControl
{
    static NodeContent()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NodeContent), new FrameworkPropertyMetadata(typeof(NodeContent)));
    }
}

public partial class Node : UserControl, INotifyPropertyChanged
{
    UIElementCollection _Elements;

    public event PropertyChangedEventHandler PropertyChanged;

    public UIElementCollection NodeContent
    {
        get { return _Elements; }
        set
        {
            _Elements = value;
            OnPropertyChanged("NodeContent");
        }
    }

    public Node()
    {
        InitializeComponent();
        NodeContent = new UIElementCollection(NodeContentContainer, this);
    }



    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

Node-Xaml:

<UserControl x:Class="Pipedream.Nodes.Node"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="216" d:DesignWidth="174" Background="Transparent" Name="NodeControl" xmlns:my="clr-namespace:Pipedream.Nodes">

    <Border BorderThickness="1" CornerRadius="20" BorderBrush="Black" Background="White">
        <Grid>
            <my:NodeContent x:Name="NodeContentContainer" Margin="20" Content="{Binding Source=NodeControl, Path=NodeContent}" />
        </Grid>
    </Border>
</UserControl>

Generic-Xaml:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Pipedream.Nodes">


    <Style TargetType="{x:Type local:NodeContent}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:Node}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <ContentPresenter />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Ответы [ 3 ]

42 голосов
/ 08 февраля 2012

Вы не можете связать свойства зависимостей типа UIElementCollection, как правило.Попробуйте что-то вроде этого:

MultiChildDemo.xaml

Ничего особенного, чтобы увидеть здесь.StackPanel будет содержать наши дочерние элементы.Очевидно, вы могли бы сделать немного больше.

Код:

<UserControl x:Class="Demo.MultiChildDemo"
             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:demo="clr-namespace:Demo"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel x:Name="PART_Host" />
</UserControl>

MultiChildDemo.xaml.cs

Важно отметить:

  • Атрибут ContentPropertyAttribute устанавливает свойство, которое будет установлено любыми элементами, заключенными в родительский элемент этого типа.Таким образом, любые элементы в пределах <MultiChildDemo></MultiChildDemo> будут добавлены к свойству Children.
  • Мы расширяем UserControl.Для этого не требуется полностью настраиваемый элемент управления.
  • В WPF рекомендуется создавать свойства, используя DependencyProperty.Register() и варианты.Вы заметите, что для свойства Children нет резервной переменной: DependencyProperty позаботится о сохранении данных для нас.Если бы мы не создавали свойство только для чтения, это позволило бы использовать привязки и другие интересные функции WPF.Таким образом, важно привыкнуть использовать свойства зависимостей, а не простые свойства, как вы часто видите в примерах в Интернете.
  • Это сравнительно простой пример свойства зависимостей.Все, что мы делаем, это копируем ссылку на дочернее свойство зависимостей, тем самым перенаправляя вызовы на UIElementCollection.Add.Существуют гораздо более сложные примеры, особенно в MSDN.

Код:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace Demo
{
    [ContentProperty(nameof(Children))]  // Prior to C# 6.0, replace nameof(Children) with "Children"
    public partial class MultiChildDemo : UserControl
    {
        public static readonly DependencyPropertyKey ChildrenProperty = DependencyProperty.RegisterReadOnly(
            nameof(Children),  // Prior to C# 6.0, replace nameof(Children) with "Children"
            typeof(UIElementCollection),
            typeof(MultiChildDemo),
            new PropertyMetadata());

        public UIElementCollection Children
        {
            get { return (UIElementCollection)GetValue(ChildrenProperty.DependencyProperty); }
            private set { SetValue(ChildrenProperty, value); }
        }

        public MultiChildDemo()
        {
            InitializeComponent();
            Children = PART_Host.Children;
        }
    }
}

MultiChildDemoWindow.xaml

Обратите внимание, что метки являются прямыми потомками<demo:MultiChildDemo> элемент.Вы также можете заключить их в элемент <demo:MultiChildDemo.Children>.Атрибут ContentPropertyAttribute, который мы добавили в класс MultiChild, позволяет нам пропустить этот шаг.

Код:

<Window x:Class="Demo.MultiChildDemoWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:demo="clr-namespace:Demo"
        Title="MultiChildDemoWindow" Height="300" Width="300">
    <demo:MultiChildDemo>
        <Label>Test 1</Label>
        <Label>Test 2</Label>
        <Label>Test 3</Label>
    </demo:MultiChildDemo>
</Window>
4 голосов
/ 20 сентября 2013

Просто удалите тег UserControl и замените его на Grid

4 голосов
/ 03 февраля 2012

Прежде всего попытайтесь понять разницу между пользовательским элементом управления и пользовательским элементом управления ( элемент управления / контроль содержимого )

Для простоты:

"Стандартные элементы управления WPF предоставляют множество встроенных функциональность. Если функциональность одного из предустановленных элементов управления, такие как индикатор выполнения или ползунок, соответствует функциональности, которую вы хотите включить, то вы должны создать новый шаблон для этого существующий контроль для достижения желаемого внешнего вида. Создание нового Шаблон является самым простым решением для создания пользовательского элемента, поэтому вы следует сначала рассмотреть этот вариант.

Если функциональность, которую вы хотите включить в свое приложение, может быть достигнутым с помощью комбинации ранее существовавших элементов управления и кода, рассмотреть возможность создания пользовательского элемента управления. Пользовательские элементы управления позволяют вам связывать вместе несколько существующих элементов управления в одном интерфейсе и добавить код, который определяет, как они себя ведут.

Если никакой существующий элемент управления или комбинация элементов управления не могут достичь функциональность, которую вы хотите, создать пользовательский элемент управления. Пользовательские элементы управления позволит вам создать совершенно новый шаблон, который определяет визуальный представление элемента управления и добавить пользовательский код, который определяет функциональность элемента управления. "

Адам Натан - WPF Unleashed 4

Теперь, если вам нужен только ContentControl:

  1. Создайте новый CustomControl, который получает ContentControl
  2. Найдите файл generic.xaml в темах и добавьте Content Presenter в свой шаблон управления. Как сказано выше, логика пользовательского управления отделена от ее визуального представления
  3. Используйте элемент управления как обычный ContentControl.

Для нескольких элементов в качестве Контента смотрите ItemsControl

Шаги, описанные выше, модифицируются как:

Контроль извлечения элементов

public class MyCtrl : ItemsControl
{
    static MyCtrl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCtrl), new FrameworkPropertyMetadata(typeof(MyCtrl)));
    }
}

Измените Generic.xaml для включения ItemsPresenter

<Style TargetType="{x:Type local:MyCtrl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCtrl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Используйте элемент управления

<StackPanel>
    <ctrl:MyCtrl>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
    </ctrl:MyCtrl>
</StackPanel>

Как уже было сказано выше, для этого простого случая не нужно будет извлекать ItemsControl, а просто использовать ItemsControl и определить для него шаблон. Получите ItemsControl при планировании расширения, добавив функциональность

EDIT:

Граница (см. Xaml) не отображается в конструкторе и не отображается, когда приложение работает, даже поля нет

Вы должны установить свойства Border для самого элемента управления:

<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
...