类依赖性WPF控件

时间:2013-01-27 09:13:23

标签: wpf

WPF真的很棒,原因很多,其中之一就是它允许我们更改控件内的控件。例如,如果我们采用ListBox。我们可以将面板从StackPanel更改为WrapPanel,它仍然可以工作(我猜它显然ListBox和WrapPanel不共享任何类依赖,因此它可以工作)。

以下是我对类依赖关系的一个例子。

public class Test1
{
 public Test2 t;

 public Test1(Test2 t)
 {
   this.t = t;
 }
}

public class Test2
{
  public string someStr;
}

现在可以像这样注入/插入实例:

Test2 test2 = new Test2();
test2.someStr = "Hello";

Test1 test1 = new Test1();
test1.t = test2;

或者可以像这样插入实例:

Test2 test2 = new Test2();
test2.someStr = "Hello";
Test1 test1 = new Test1(test2);

还有其他几种方法可以做到这一点,但我希望你们现在明白这一点。

所以现在我们知道如何注入实例后,让我们尝试在WPF中使用以下示例:

我有一个CustomControl。我的CustomControl有点复杂,因为它有行和列。此外,CustomControl不是从DataGrid派生的。实际上它来自ItemsControl。

正如我所提到的,它有列和行,或者更准确地说,行需要知道列。多数民众赞成我想在一行中插入列的实例。我如何在WPF中做到这一点?

以下是我的问题的简单例子:

让我们说CustomControl的VisualTree看起来像这样:

CustomControl
+ Grid
 + Border
  + ContentPresenter
   + Headers
 + DockPanel
  + Border
   + ContentPresenter
    + Rows

正如您所看到的,Rows远离Headers,我希望在没有找到Headers的情况下将Headers实例插入到行中。

行和标题类看起来像这样:

class Row : ContentControl
{
  List<Column> Headers;
  ...
}

class Headers : ContentControl
{
  List<Column> Cols = new List<Column>()
  public Headers()
  {
    this.Cols.Add(...);
    this.Cols.Add(...);
  }
 ...
}

问题是如何做这样的事情:

this.rows.Headers = columns.Cols;

我在网上搜索过,很多人建议我让这行使用VisualTreeHelper,然后沿着树向上走,找到Cols。事实上,我尝试这样做,在使用Performance Profiler工具监视我的CustomControl之后,我认为这正是每一行都会绊倒树以找到占用大部分时间的标头的步骤。因此,不要使用VisualTreeHelper,并允许使用上面的Test1和Test2示例中描述的注入。

有什么建议吗?可能有这种模式吗?

编辑:@Benjamin。在OnApplyTemplate中使用RedSlider = GetTemplateChild(“RedSlider”)是一个很好的解决方案,但它对我不起作用,因为我的情况更复杂。在我的情况下,我真的需要以某种方式插入实例。这是我的一个例子,我不能在OnAppyTemplate方法中使用GetTemplateChild。

这是滑块的控制。

class CustomSliders : ContentControl
{
}

CustomSlider的样式如下所示:

 <Style x:Key="mySliders" TargetType="{x:Type local:CustomSliders}">
    <Style.Setter Property="Template">
    <ControlTemplate TargetType="{x:Type local:CustomSliders}">
    <Grid>
        <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Slider x:Name="PART_RedSLider" />
     <Slider x:Name="PART_GreenSlider" Grid.Row="1"/>
     <Slider x:Name="PART_BlueSlider" Grid.Row="2"/>
     <Slider x:Name="PART_AlphaSlider" Grid.Row="3"/>
    </Grid>
    </ContentTemplate>
   </Style>

这是我的CustomControl的ControlTemplate,名为ColorPicker。

<ContentTemplate TargetType="Picker">
    <DockPanel>
     <ContentControl DockPanel.Dock="Left" Style="{StaticResource mySliders}"/>
    <Rectangle x:Name="PART_ColorPresenter" 
                   DockPanel.Dock="Right"
                   Margin="5"/>
    </Grid> 
</ControlTemplate>

在此示例中,将在ColorPicker的OnApplyTemplate内执行的GetTemplatedChild方法无法工作,因为它无法找到CustomSliders中的PART_RedSLider。

GetTemplateChild无法在VisualTree中找到所有内容。

因为GetTemplatedChild无法解决这个复杂的模板结构,所以很多人建议我使用VisualTreeHelper.Find()在树上向上或向下移动。虽然我在问题中解释过,但我不想使用这种方式。我想插入实例..

2 个答案:

答案 0 :(得分:1)

正如我已经说过的,Kent Boogaarts的答案在技术上是正确的,但我认为您可能需要更多信息来帮助您理解。

想象一下你正在编写的控件是一个颜色选择器。该控件有4 Sliders用于更改红色,绿色,蓝色和Alpha值,最终结果显示在Rectangle中的滑块旁边。模拟如下所示:

enter image description here

此控件有5个依赖项,以使其正常工作; 4 Sliders和1 FrameworkElement用于显示最终结果。

这意味着我的ControlTemplate我希望看到4个滑块和1个FrameworkElement 名为。例如,控制红色值的滑块可以称为“PART_RedSlider”(名称可以是您想要的任何名称,但推荐的方法是在名称前加上“PART _”)。

示例模板可能如下所示:

<!-- Typically this would be defined in a style -->
<ControlTemplate TargetType="ColorPicker">
    <Grid>
        <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Slider x:Name="PART_RedSLider" />
    <Slider x:Name="PART_GreenSlider" Grid.Row="1"/>
    <Slider x:Name="PART_BlueSlider" Grid.Row="2"/>
    <Slider x:Name="PART_AlphaSlider" Grid.Row="3"/>

    <Rectangle x:Name="PART_ColorPresenter" 
                   Grid.Column="1" 
                   Grid.RowSpan="4" 
                   Margin="5"/>
    </Grid> 
</ControlTemplate>

现在在您的控件代码中,您将覆盖OnApplyTemplate()方法,如下所示:

public override void OnApplyTemplate()
{
    RedSlider = GetTemplateChild("PART_RedSlider") as Slider;
    GreenSlider = GetTemplateChild("PART_GreenSlider") as Slider;
    BlueSlider = GetTemplateChild("PART_BlueSlider") as Slider;
    AplhaSlider = GetTemplateChild("PART_AlphaSlider") as Slider;

    ColorPresenter = GetTemplateChild("PART_ColorPresenter") as FrameworkElement;
}

您现在可以访问ControlTemplate的命名部分。然后,您可以在代码中对这些控件执行任何操作(设置默认属性值,连接事件处理程序等)。

现在你可能想知道TemplatePartAttribute在这一切中的位置。问题是,这个属性纯粹是出于文档目的,因此工具(例如Expression Blend)可以帮助其他人创建自定义模板。

该属性应用于您正在编写的类:

[TemplatePart(Name="PART_RedSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_GreenSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_BlueSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_AlphaSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_ColorPresenter", Type=typeof(FrameworkElement)]
public class ColorPicker

一条建议是永远不会假设模板将包含一个命名部分。如果没有指定的部分存在,您的控件仍应起作用(尽其所能)。 Microsoft的指导是,如果缺少命名部分,则不应抛出异常(即使您的控件无法按预期运行,您的应用程序仍然可以运行)。

E.G。

public override void OnApplyTemplate()
{
    RedSlider = GetTemplateChild("RedSlider") as Slider;

    if (RedSlider != null)
    {
        // Only do anything if the named part is present.
    }
}

您还应尝试使用命名部件可能的最低基类。例如。在我的示例中,Sliders的命名部分可以是RangeBase而不是Slider,因此可以使用范围控件的类型。

最后,您的类应该包含对您的命名部分的引用的变量,这些变量在调用OnApplyTemplate期间检索。每次要使用它们时都不要试图找到控件。

所以现在你需要将这个应用到你的控件上,这取决于RowHeader控件,所以这些控件需要出现在ControlTemplate中(我假设一个{为简单起见,{1}}和Row

首先,将您的HeaderCustomControl一起记录,如下所示:

TemplatePartAttribute

现在确保将这些控件作为[TemplatePart(Name="PART_Header", Type=typeof(Header)] [TemplatePart(Name="PART_Row", Type=typeof(Row)] public class CustomControl : Control { // etc... 中的命名部分,如下所示:

ControlTemplate

然后你使用这样的<ControlTemplate TargetType="CustomControl"> <Grid> <!-- other elements omitted --> <Header x:Name="PART_Header" /> <Row x:Name="PART_Row" /> </Grid> </ControlTemplate> 方法:

OnApplyTemplate()

答案 1 :(得分:0)

如果您的自定义控件对其可视树有特定要求,您可以使用TemplatePartAttribute声明这些要求,并在OnApplyTemplate期间使用解决方法。