Подтвердить что ты не робот

Добавление детей в UserControl

Моя задача

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

Моя проблема

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

Мои подходы

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

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

Заключение

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

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

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

Благодаря Андрею Гаврила и jberger я архивируются, чтобы создать node, который отображает содержимое (см. код ниже), но есть еще две проблемы: - Нет поддержки дизайнеров - Граница (см. 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>
4b9b3361

Ответ 1

Вы не можете связывать свойства зависимостей типа 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.
  • Мы расширяем a 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>

Ответ 2

Прежде всего, попробуйте понять разницу между User Control и пользовательским контролем (Control/Content Control)

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

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

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

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

Теперь, если все, что вы хотите, это ContentControl:

  • Создайте новый CustomControl, который выводит ContentControl
  • Найдите шаблон generic.xaml в темах и добавьте Content Presenter в свой шаблон управления. Как сказано выше, пользовательская логика управления отделена от нее визуальным представлением
  • Используйте элемент управления как обычный 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 и определять шаблон для него. Derive ItemsControl при планировании расширения путем добавления функциональности

EDIT:

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

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

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

Ответ 3

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