Dodawanie dzieci do UserControl

Moje zadanie

Utwórz UserControl, który powinien zawierać wszystkie wizualne potomkinie dostępne w WPF, dzieci są wyświetlane w kontenerze, który jest potomkiem UserControl.

Mój Problem

Nie udało mi się poprawnie wyświetlić dzieci w moim kontenerze, wypróbowałem sposoby Serwal i nie znalazłem sposobu, który działa w Projektancie. Próbowałem też użyć ContentControl, ale nic się nie wyświetla.

Moje podejście

Najpierw znalazłem ten link i próbowałem go z pewnymi wariantami. Udało mi się wyświetlić zawartość w odpowiednim kontenerze, ale nie działa ona w Projektancie, ponieważ właściwość content-jest ustawiona-private i projektant chce ją nadpisać. Umieszczenie wszystkiego w XAML działa, ale nie jest to dobre podczas pracy z projektantami. To jest ulubiony sposób maja.

Po tym próbowałem użyć ContentControl wiążąc właściwość Content-do bindowalnej właściwości UIElementCollection-type. Ten aproach nie rzuca żadnych błędy w projektorze, ale muszę przyznać, że nigdy nie widzę żadnej kontroli (np. Button) w moim kontenerze. Pozostaje pusta, ale dodaje dzieci.

Podsumowanie

Po wielogodzinnych poszukiwaniach łatwego i szybkiego rozwiązania postanowiłem poprosić o rozwiązania tutaj. Jestem trochę rozczarowany. Byłoby bardzo pomocne, gdyby Microsoft mógł pobrać próbkę do MSDN.

Jestem pewien, że musi być łatwy sposób, aby to zarchiwizować.

Obecna sytuacja

Dzięki do Andrei Gavrila I jberger zarchiwizowałem, aby utworzyć węzeł, który wyświetla zawartość (patrz kod poniżej), ale są jeszcze dwa problemy: - Brak wsparcia projektanta - Ramka (patrz xaml) nie jest pokazywana w designerze i nie jest pokazywana, gdy aplikacja jest uruchomiona nie ma nawet marginesu

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>
Author: Dave Clemmer, 2012-02-01

3 answers

Ogólnie nie można powiązać właściwości zależności typu UIElementCollection. Spróbuj czegoś takiego:

MultiChildDemo.xaml

Nie ma tu nic ciekawego. StackPanel pomieści nasze dziecięce elementy. Oczywiście mógłbyś zrobić trochę więcej.

Kod:

<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

Ważne do zapamiętania:

  • atrybut ContentPropertyAttribute ustawia właściwość, która będzie ustawiona przez dowolne elementy zamknięte przez element nadrzędny tego typu. Tak więc każdy elementy wewnątrz <MultiChildDemo></MultiChildDemo> zostaną dodane do właściwości Children.
  • rozszerzamy UserControl. Nie wymaga to całkowicie niestandardowej kontroli.
  • dobrą praktyką w WPF jest tworzenie właściwości za pomocą DependencyProperty.Register() i wariantów. Zauważ, że nie ma zmiennej wspierającej dla właściwości Children: DependencyProperty zajmuje się przechowywaniem danych dla nas. Gdybyśmy nie tworzyli właściwości tylko do odczytu, umożliwiłoby to Korzystanie z wiązań i innych fajnych funkcji WPF. Dlatego ważne jest, aby uzyskać do nawyku używania właściwości zależności, a nie zwykłych właściwości, jak często widać w przykładach w Internecie.
  • jest to stosunkowo prosty przykład właściwości zależności. Wystarczy skopiować odwołanie do właściwości zależności dziecka, przekierowując wywołania do UIElementCollection.Add. Istnieją znacznie bardziej złożone przykłady, szczególnie na MSDN.

Kod:

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

Zwróć uwagę, że etykiety są bezpośrednimi potomkami <demo:MultiChildDemo> element. Można je również zamknąć w elemencie <demo:MultiChildDemo.Children>. Atrybut ContentPropertyAttribute dodany do klasy MultiChild pozwala nam jednak pominąć ten krok.

Kod:

<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>
 40
Author: Zenexer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-31 12:50:53

Po prostu usuń znacznik UserControl i zamień go na Grid

 4
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-20 13:01:47

Przede wszystkim spróbuj zrozumieć różnicę między kontrolką użytkownika A kontrolką niestandardową (kontrolką/Content Control )

To keep it simple:

" standardowe elementy sterujące WPF zapewniają wiele wbudowanych funkcjonalność. Jeśli funkcjonalność jednego z wstępnie ustawionych kontrolek, takich jak pasek postępu lub suwak, pasuje do funkcjonalności, które chcesz włączyć, powinieneś utworzyć nowy szablon do tego preexisting Kontrola, aby osiągnąć pożądany wygląd. Tworzenie nowego szablon jest najprostszym rozwiązaniem do stworzenia niestandardowego elementu, dzięki czemu powinniśmy najpierw rozważyć tę opcję.

Jeśli funkcjonalność, którą chcesz włączyć do swojej aplikacji, może być osiągnięte poprzez połączenie wcześniej istniejących kontroli i kodu, rozważ utworzenie kontroli użytkownika. Kontrolki użytkownika umożliwiają Wiązanie razem wiele wcześniej istniejących kontrolek w jednym interfejsie i dodaj kod, który określa sposób ich zachowuj się.

Jeśli żadna istniejąca kontrola lub kombinacja kontroli nie może osiągnąć funkcjonalność, którą chcesz, Utwórz niestandardową kontrolę. Sterowanie niestandardowe umożliwia utworzenie zupełnie nowego szablonu, który definiuje wizualizację reprezentacja kontrolki i dodanie kodu niestandardowego, który określa funkcjonalność sterowania."

Adam Nathan-WPF Unleashed 4

Teraz jeśli chcesz tylko ContentControl:

  1. Stwórz nowy CustomControl, który wywodzi ContentControl
  2. Zlokalizuj generyk.xaml w tematach i dodaj prezenter treści do szablonu sterującego. Jak wspomniano powyżej, niestandardowa logika sterowania jest oddzielona od wizualnej prezentacji
  3. użyj kontrolki jako zwykłej kontrolki ContentControl.

Dla wielu elementów jako treści spójrz na ItemsControl

Powyższe kroki są modyfikowane jako:

Derive Items Control

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

Modyfikuj Generic.XAML to include 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>

Użyj kontrolki

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

Jak wspomniano powyżej, w tym prostym przypadku nie byłoby konieczne wyprowadzenie ItemsControl, ale po prostu użycie ItemsControl i zdefiniowanie szablonu dla niego. Uzyskaj ItemsControl podczas planowania rozszerzenia poprzez dodanie funkcjonalności

EDIT:

Ramka (patrz xaml) nie jest wyświetlana w designerze i nie jest wyświetlana, gdy aplikacja jest uruchomiona, nie ma nawet marginesu

Należy ustawić właściwości obramowania na twoja kontrola:

<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
 4
Author: Andrei Gavrila,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 15:59:50