如何将多个UI子项添加到CustomControl的DependencyProperty?

时间:2016-06-20 18:46:36

标签: c# wpf xaml dependency-properties itemscontrol

我创建了一个代表图块的CustomControl。它包含一个带标题,工具栏和主要内容的标题,并且工作正常。但是,我想将<StackPanel Orientation="Horizontal">移动到控件的模板,因为我总是希望工具栏像这样呈现。

由于我想将孩子添加到DependencyProperty而不是直接添加到控件,我无法从ItemsControl派生。我可以在不向控件中添加大量样板代码的情况下解决这个问题吗?

我认为我必须用ContentPresenter替换第二个ItemsPresenter,但是:

  • ToolbarContent DependencyProperty需要什么类型? <{1}}和UIElementCollection都不能实现。
  • 如何告诉ItemsPresenter以横向ItemsCollection显示其内容?

当前用法:

StackPanel

请求用法:

<controls:Tile Title="Projects" Margin="6,6,0,6">
    <controls:Tile.ToolbarContent>
        <StackPanel Orientation="Horizontal">
            <controls:ToolbarButton Text="Add" ImageSource="pack://application:,,,/MyAssembly;Component/Resources/plus-16.png" />
            <controls:ToolbarButton Text="Remove" ImageSource="pack://application:,,,/MyAssembly;Component/Resources/delete-16.png" />
            <controls:ToolbarButton ... />
            <controls:ToolbarButton ... />
        </StackPanel>
    </controls:Tile.ToolbarContent>
    <ListView x:Name="MainContent" BorderThickness="0" ItemsSource="{Binding Projects}" />
</controls:Tile>

样式:

<controls:Tile Title="Projects" Margin="6,6,0,6">
    <controls:Tile.ToolbarContent>
        <controls:ToolbarButton Text="Add" ImageSource="pack://application:,,,/MyAssembly;Component/Resources/plus-16.png" />
        <controls:ToolbarButton Text="Remove" ImageSource="pack://application:,,,/MyAssembly;Component/Resources/delete-16.png" />
        <controls:ToolbarButton ... />
        <controls:ToolbarButton ... />
    </controls:Tile.ToolbarContent>
    <ListView x:Name="MainContent" BorderThickness="0" ItemsSource="{Binding Projects}" />
</controls:Tile>

代码:

<Style TargetType="controls:Tile">
    <Setter Property="UseLayoutRounding" Value="True" />
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="BorderBrush" Value="{DynamicResource PrimaryColor_100}" />
    <Setter Property="Background" Value="White" />
    <Setter Property="FontSize" Value="12" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:Tile">
                <Border BorderBrush="{DynamicResource PrimaryColor_40}" BorderThickness="1" Background="{TemplateBinding Background}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Border x:Name="Header" Background="{TemplateBinding BorderBrush}">
                            <TextBlock VerticalAlignment="Center" FontSize="{TemplateBinding FontSize}" Margin="3" Text="{TemplateBinding Title}" Foreground="White" />
                        </Border>
                        <Border x:Name="Toolbar" Grid.Row="1" Background="{DynamicResource PrimaryColor_40}" Padding="1">
                            <ContentPresenter x:Name="ToolbarContent" Content="{TemplateBinding ToolbarContent}" />
                        </Border>
                        <ContentPresenter x:Name="MainContent" Grid.Row="2" />
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

1 个答案:

答案 0 :(得分:1)

我不知道一个好的方法来按字面意思做你要求的东西。您以一种恕我直言,笨拙并且不符合特定控件的预期设计的方式重载ContentControl类型。也就是说,有可能沿着这些方向发挥作用。

要理解的重要一点是,您无法创建随后分配给StackPanel子项的集合。这不是Panel对象的工作方式。相反,您需要一些其他控件,您可以为其分配控件集合并使其工作。用于此目的的通常控制是ItemsControl对象。

您可以将ToolbarContent属性设为ObservableCollection<UIElement>,然后将其值绑定到ItemsControl.ItemsSource属性。在ItemsControl中,您可以设置ItemsPanel属性,让ItemsControl使用具有所需布局的StackPanel(例如Orientation="Horizontal")。

例如:

      <ControlTemplate TargetType="controls:Tile">
        <Border BorderBrush="{DynamicResource PrimaryColor_40}" BorderThickness="1" Background="{TemplateBinding Background}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
          <Grid>
            <Grid.RowDefinitions>
              <RowDefinition Height="Auto" />
              <RowDefinition Height="Auto" />
              <RowDefinition />
            </Grid.RowDefinitions>
            <Border x:Name="Header" Background="{TemplateBinding BorderBrush}">
              <TextBlock VerticalAlignment="Center" FontSize="{TemplateBinding FontSize}" Margin="3" Text="{TemplateBinding Title}" Foreground="White" />
            </Border>
            <Border x:Name="Toolbar" Grid.Row="1" Background="{DynamicResource PrimaryColor_40}" Padding="1">
              <ItemsControl ItemsSource="{TemplateBinding ToolbarContent}">
                <ItemsControl.ItemsPanel>
                  <ItemsPanelTemplate>
                    <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
                  </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
              </ItemsControl>
            </Border>
            <ContentPresenter x:Name="MainContent" Grid.Row="2" />
          </Grid>
        </Border>
      </ControlTemplate>

class Tile : ContentControl
{
    private static readonly DependencyPropertyKey ToolbarContentKey =
         DependencyProperty.RegisterReadOnly("ToolbarContent", typeof(ObservableCollection<UIElement>), typeof(Tile), new PropertyMetadata());

    public static readonly DependencyProperty ToolbarContentProperty = ToolbarContentKey.DependencyProperty;

    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(Tile), new PropertyMetadata());

    public ObservableCollection<UIElement> ToolbarContent
    {
        get { return (ObservableCollection<UIElement>)GetValue(ToolbarContentProperty); }
    }

    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    public Tile()
    {
        SetValue(ToolbarContentKey, new ObservableCollection<UIElement>());
    }
}

然后,当您使用所需的语法时,元素将添加到集合中,并且该集合绑定到ItemsSource属性,以便可以使用StackPanel作为主机来显示它们。 / p>


现在,虽然这会起作用,但对我来说,你定义的模板确实有更多的复合控制&#34;感受到它。这通常意味着实施UserControl。使用UserControl,您只是声明一个控件,它本身由一组其他控件组成,其中XAML中定义的布局和其他特征类似于Window的方式。< / p>

在这种方法中,您可以将内部元素集合作为UserControl的公共接口的一部分公开,而不是创建外部集合并绑定到内部元素的属性,并添加内容直接。这也允许您以更直接的方式定义自定义控件的布局/组合(例如,具有实际的StackPanel元素,而不必将其包装在ItemsControl中。)

这可能看起来像这样:

<UserControl x:Class="TestSO37929673CollectionContent.Tile2"
             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" 
             x:Name="root"
             d:DesignHeight="300" d:DesignWidth="300">

  <Border BorderBrush="Black" BorderThickness="1"
          Background="{Binding Background, ElementName=root}"
          UseLayoutRounding="{Binding UseLayoutRounding, ElementName=root}"
          SnapsToDevicePixels="{Binding SnapsToDevicePixels, ElementName=root}">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition />
      </Grid.RowDefinitions>
      <Border x:Name="Header" Background="{Binding BorderBrush, ElementName=root}">
        <TextBlock VerticalAlignment="Center"
                   FontSize="{Binding FontSize, ElementName=root}"
                   Margin="3"
                   Text="{Binding Title, ElementName=root}" Foreground="White" />
      </Border>
      <Border x:Name="Toolbar" Grid.Row="1" Background="White" Padding="1">
        <StackPanel x:Name="stackPanel1" Orientation="Horizontal"/>
      </Border>
      <ContentPresenter Content="{Binding MainContent, ElementName=root}" Grid.Row="2" />
    </Grid>
  </Border>
</UserControl>

[ContentPropertyAttribute("MainContent")]
public partial class Tile2 : UserControl
{
    private static readonly DependencyPropertyKey ToolbarContentKey =
         DependencyProperty.RegisterReadOnly("ToolbarContent", typeof(UIElementCollection), typeof(Tile2), new PropertyMetadata());

    public static readonly DependencyProperty ToolbarContentProperty = ToolbarContentKey.DependencyProperty;

    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(Tile2), new PropertyMetadata());

    public static readonly DependencyProperty MainContentProperty =
        DependencyProperty.Register("MainContent", typeof(object), typeof(Tile2));

    public UIElementCollection ToolbarContent
    {
        get { return (UIElementCollection)GetValue(ToolbarContentProperty); }
    }

    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    public object MainContent
    {
        get { return GetValue(MainContentProperty); }
        set { SetValue(MainContentProperty, value); }
    }

    public Tile2()
    {
        InitializeComponent();
        SetValue(ToolbarContentKey, stackPanel1.Children);
    }
}

使用语法是相同的(即您在帖子中要求的完全相同),您也可以以相同的方式设置控件的样式。但是它在UserControl定义本身中封装了控件的布局,而不是依赖于样式中指定的ControlTemplate来实现这一点。

请注意,UserControl本身是ContentControl子类。因此,如果您觉得使用ContentControl的功能很重要,您仍然可以利用它们。只是UserControl范例恕我直言使客户端和自定义控件的关系更清晰,更易于维护。