Adding children to UserControl

前端 未结 3 1617
青春惊慌失措
青春惊慌失措 2020-12-07 15:07

My task

Create a UserControl which should be able to contain any visual child which is available in WPF, the children are displayed in a container whi

相关标签:
3条回答
  • 2020-12-07 15:31

    First of all try to understand the difference between an User Control and a Custom Control (Control/Content Control)

    To keep it simple:

    "The standard WPF controls provide a great deal of built-in functionality. If the functionality of one of the preset controls, such as a progress bar or a slider, matches the functionality that you want to incorporate, then you should create a new template for that preexisting control to achieve the appearance you want. Creating a new template is the simplest solution to creating a custom element, so you should consider that option first.

    If the functionality you want to incorporate into your application can be achieved through a combination of preexisting controls and code, consider creating a user control. User controls enable you to bind together multiple preexisting controls in a single interface and add code that determines how they behave.

    If no preexisting control or combination of controls can achieve the functionality you want, create a custom control. Custom controls enable you to create a completely new template that defines the visual representation of the control and to add custom code that determines the control’s functionality."

    Adam Nathan - WPF Unleashed 4

    Now if all you want is a ContentControl:

    1. Make a new CustomControl that derives ContentControl
    2. Locate the generic.xaml in themes and add a Content Presenter to your control template. As said above, the custom control logic is separated from it's visual presentation
    3. Use the control as a regular ContentControl.

    For multiple items as Content take a look at ItemsControl

    The steps above are modified as:

    Derive Items Control

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

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

    Use the control

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

    As said above, for this simple case it would not be necessary to derive ItemsControl but to simply use the ItemsControl and define a Template for it. Derive ItemsControl when planning to extend by by adding functionality

    EDIT:

    The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin

    You should set the Border properties on your control itself:

    <ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
    
    0 讨论(0)
  • 2020-12-07 15:43

    Just remove the UserControl tag and replace with Grid

    0 讨论(0)
  • 2020-12-07 15:44

    You cannot bind dependency properties of type UIElementCollection, generally. Try something like this:

    MultiChildDemo.xaml

    Nothing much to see here. The StackPanel will hold our child elements. You could obviously do quite a bit more.

    Code:

    <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

    Important to note:

    • The ContentPropertyAttribute attribute sets the property that will be set by any elements enclosed by the parent element of this type. Thus, any elements within <MultiChildDemo></MultiChildDemo> will be added to the Children property.
    • We are extending a UserControl. This does not necessitate a completely custom control.
    • It is good practice in WPF to make properties using DependencyProperty.Register() and variants. You will notice that there is no backing variable for the Children property: DependencyProperty takes care of storing the data for us. Were we not creating a read-only property, this would enable the use of bindings and other cool WPF features. Thus, it is important to get into the habit of using dependency properties, rather than plain properties as you often see in examples around the Internet.
    • This is a relatively simple dependency property example. All we do is copy the reference to a child's dependency property, thereby forwarding calls to UIElementCollection.Add. Much more complex examples are out there, especially on MSDN.

    Code:

    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

    Note how the labels are direct descendants of the <demo:MultiChildDemo> element. You could also enclose them in a <demo:MultiChildDemo.Children> element. The ContentPropertyAttribute attribute that we added to the MultiChild class allows us to omit this step, though.

    Code:

    <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>
    
    0 讨论(0)
提交回复
热议问题