0

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 不是从 DataGrid 派生的。事实上,它是从 ItemsControl 派生的。

正如我提到的,它有列和行,或者更准确地说,行需要了解列。那就是我想在一行中插入列实例的地方。我如何在 WPF 中做到这一点?

这是我的问题的一个简单的简单示例:

假设 CustomControl 的 VisualTree 如下所示:

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

如您所见,行远离标题,我想在不找到标题的情况下获取/插入标题的实例到行。

Rows 和 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。事实上,我尝试这样做,并且在使用性能分析器工具监视了我的 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() 反复地在树上或下旅行。尽管就像我在问题中解释的那样,我不想使用递归方式。我想插入实例..

4

2 回答 2

1

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

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

在此处输入图像描述

该控件有 5 个依赖项才能正常工作;4Sliders和 1FrameworkElement用于显示最终结果。

这意味着ControlTemplate我希望看到 4 个滑块和 1FrameworkElement名为. 例如,控制红色值的滑块可以称为“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 的指导是,如果缺少命名部分,则不应抛出异常(即,即使您的控件无法按预期工作,您的应用程序仍然可以运行)。

例如

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(为了简单起见,我将假设一个RowHeader)。

首先CustomControlTemplatePartAttribute这样的方式记录你的:

[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()方法:

public override void OnApplyTemplate()
{
    myHeader = GetTemplateChild("PART_Header") as Header;
    myRow = GetTemplateChild("PART_Row") as Row;

    if ((myHeader != null) && (myRow != null))
    {
        myRow.Headers = myHeader.Cols;
    }
}
于 2013-02-02T13:19:12.740 回答
0

如果您的自定义控件对其可视化树有特定要求,您可以使用TemplatePartAttributes 声明这些并在OnApplyTemplate.

于 2013-01-27T10:10:10.897 回答