2017-04-26 58 views
3

我正在写一个WPF控件,意思是以同样的方式将一个容器BorderScrollViewer作为容器。它被称为EllipsisButtonControl,它应该在其内容的右侧放置一个省略号按钮。下面是我打算如何为它使用的例子:在充当容器的WPF控件中,如何放置内容?

<local:EllipsisButtonControl> 
    <TextBlock Text="Testing" /> 
</local:EllipsisButtonControl> 

这里是EllipsisButtonControl的XAML:

<ContentControl 
    x:Class="WpfApplication1.EllipsisButtonControl" 
    x:Name="ContentControl" 
    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="30" d:DesignWidth="300"> 

    <Grid> 

     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*" /> 
      <ColumnDefinition Width="Auto" /> 
     </Grid.ColumnDefinitions> 

     <ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" /> 

     <Button Grid.Column="1" Command="{Binding ElementName=ContentControl, Path=Command}" Margin="3,0" Width="30" Height="24" MaxHeight="24" VerticalAlignment="Stretch" Content="..." /> 

    </Grid> 

</ContentControl> 

这里是后面的代码:

using System.Windows; 
using System.Windows.Input; 

namespace WpfApplication1 
{ 
    public partial class EllipsisButtonControl 
    { 
     public EllipsisButtonControl() 
     { 
      InitializeComponent(); 
     } 

     public static string GetCommand(DependencyObject obj) 
     { 
      return (string)obj.GetValue(CommandProperty); 
     } 

     public static void SetCommand(DependencyObject obj, string value) 
     { 
      obj.SetValue(CommandProperty, value); 
     } 

     public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
       name: "Command", 
       propertyType: typeof(ICommand), 
       ownerType: typeof(EllipsisButtonControl), 
       defaultMetadata: new UIPropertyMetadata()); 
    } 
} 

这并未没有工作。它以System.Runtime.Remoting.RemotingException使设计者崩溃。

我相信X30L的ContentPresenterContentPresenter上的绑定是错误的,但我不知道如何使它正确。使该行参考控件内容的适当语法是什么? (例如,在使用示例中定义的TextBlock

编辑:

捅提供以下(包括工作代码)一个全面的答案,但对于其他的谁可能分享我最初误解的好处,让我总结这里的关键概念是:容器控制本身不能“放置内容”。它通过定义一个模板来达到预期的效果,该模板修改了调用XAML呈现内容的方式。解决方案的其余部分来自该前提。

+0

为什么命令是附属属性,而不是常规的依赖属性? – Clemens

+0

嗯......因为我正在复制的例子就是这样......提示你的问题,我研究了附加属性,并了解到我应该在第23行使用DependencyProperty.Register(),而不是DependencyProperty.RegisterAttached() 。 谢谢。 – Lork

+0

*“容器控件本身不能”放置内容“。”* - 它可以,但只有一个内容,因此设置外部内容会替换内部内容。你想要的是内容放置在内的“框架”(=模板)。 – poke

回答

1
<local:EllipsisButtonControl> 
    <TextBlock Text="Testing" /> 
</local:EllipsisButtonControl> 

这并设置你的用户控件的Content。但这样做在用户控件的XAML如下:

<ContentControl …> 
    <Grid> 
     … 
    </Grid> 
</ContentControl> 

主叫XAML有precendence在这里,所以无论你是用户控制的XAML里面做实际上被忽略。

这里的解决方案是设置用户控件的模板。模板(在这种情况下是控件的控件模板)决定了控件本身的渲染方式。用户控件最简单的模板(也是它的默认模板)就是在那里使用ContentPresenter,但是当然,你想要添加一些东西,所以我们必须覆盖模板。这通常看起来像这样:

<ContentControl …> 
    <!-- We are setting the `Template` property --> 
    <ContentControl.Template> 
     <!-- The template value is of type `ControlTemplate` and we should 
      also set the target type properly so binding paths can be resolved --> 
     <ControlTemplate> 

      <!-- This is where your control code actually goes --> 

     </ControlTemplate> 
    </ContentControl.Template> 
</ContentControl> 

现在这是您需要使这项工作的框架。但是,一旦进入控件模板,您需要使用正确的绑定类型。由于我们正在编写一个模板并希望绑定到父控件的属性,因此我们需要将父控件指定为绑定中的相对源。但最简单的方法就是使用TemplateBinding标记扩展。利用这一点,一个ContentPresenter可以放置这样上面的ControlTemplate内:

<ContentPresenter Content="{TemplateBinding Content}" /> 

这应该是所有你所需要的,以获得内容演示工作。

但是,现在您使用了控件模板,当然您还需要调整其他绑定。特别是绑定到您的自定义依赖项属性Command。这通常看起来一样的作为模板结合Content但由于我们的控制模板靶向类型ContentControlContentControl没有你的自定义属性,我们需要在这里明确地引用您的自定义依赖项属性:

<Button Command="{TemplateBinding local:EllipsisButtonControl.Command}" … /> 

一旦我们有了,所有的绑定应该可以正常工作。所以


,总结这一切,你的自定义内容的控制应该是这个样子:(是的,永远的约束性指标在类的静态依赖属性如果你现在想):

<ContentControl 
     x:Class="WpfApplication1.EllipsisButtonControl" 
     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:local="clr-namespace:WpfApplication1" 
     d:DesignHeight="30" d:DesignWidth="300" mc:Ignorable="d"> 
    <ContentControl.Template> 
     <ControlTemplate TargetType="ContentControl"> 

      <Grid> 
       <ContentPresenter Grid.Column="0" 
         Content="{TemplateBinding Content}" /> 

       <Button Grid.Column="1" Content="…" 
         Command="{TemplateBinding local:EllipsisButtonControl.Command}" /> 
      </Grid> 

     </ControlTemplate> 
    </ContentControl.Template> 
</ContentControl> 
+0

这种方法需要不同的思考方式,但我开始理解它。但它引发了两个问题:1)为了使代码正常工作,我必须将ContentControl更改为UserControl;为什么不ContentControl工作? 2)我可以使用模板绑定来支持按钮的Command,而不是将其编码为依赖属性? – Lork

+0

随着我的理解的增长,我意识到你已经回答了问题2:不,我不能放弃Command的依赖属性代码,因为ContentControl没有Command属性(并且在切换到UserControl之后,它不会)。 – Lork

+0

WPF一般要求您重新思考组件如何工作。一般来说,控件只是附带一些模板的代码(另请参阅“不见的组件”);在很多情况下,您想要覆盖默认模板。回答你的问题:1)这也适用于一个'ContentControl';你可以看到[这个Gist](https://gist.github.com/poke/bd9bcb6d1f1bf50b988ea8909edb99af)作为一个工作示例。 2)你可以绑定到控件模板中的其他东西,但是在这里你想暴露你的'EllipsisButtonControl'属性,所以你实际上需要一个依赖属性。 – poke

1

尝试更换这行:

<ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" /> 

有了这个

<ContentPresenter Grid.Column="0" Content={Binding Content} /> 

在现有的代码,你使这种ContentPresenter显示EllipsesButtonControl生成的内容,其中包括ContentPresenter必须使产生内容ElipsesButtonControl其中包括ContentPresenter .....无限递归。

+0

安德鲁,解决了设计者崩溃问题,但仍有一些问题。当我尝试使用示例时,我只看到TextBlock,就好像它没有包含在EllipsisButtonControl中一样。 – Lork

1

您的EllipsisButtonControl的XAML已经将其内容设置为顶层网格。您可能需要的是创建一个ControlTemplate,例如像这样:

<ContentControl x:Class="WpfApplication1.EllipsisButtonControl" 
       x:Name="ContentControl" 
       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="30" d:DesignWidth="300"> 
    <ContentControl.Template> 
     <ControlTemplate> 
      <Grid> 
       <Grid.ColumnDefinitions> 
        <ColumnDefinition Width="*" /> 
        <ColumnDefinition Width="Auto" /> 
       </Grid.ColumnDefinitions> 

       <ContentPresenter Grid.Column="0" 
        Content="{Binding ElementName=ContentControl, Path=Content}"/> 

       <Button Grid.Column="1" 
        Command="{Binding ElementName=ContentControl, Path=Command}" 
        Margin="3,0" Width="30" Height="24" MaxHeight="24" 
        VerticalAlignment="Stretch" Content="..." /> 
      </Grid> 
     </ControlTemplate> 
    </ContentControl.Template> 
</ContentControl>