0

WPFは多くの理由で本当に素晴らしいですが、その1つは、コントロール内のコントロールを変更できることです。例として、リストボックスを取り上げます。パネルを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

ご覧のとおり、行はヘッダーから遠く離れているので、ヘッダーを見つけずに行へのヘッダーのインスタンスを取得/挿入したいと思います。

行とヘッダーのクラスは次のようになります。

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を使用させてから、ツリーを上に移動してColを見つけるように提案しました。実際、私はそうしようとしましたが、パフォーマンスプロファイラーツールで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>

そして、これはColorPickerと呼ばれる私のCustomControlのControlTemplateです。

<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 の回答は技術的には正しいですが、理解を助けるためにさらに情報が必要になると思います。

作成しているコントロールがカラー ピッカーであると想像してください。コントロールにはSliders、赤、緑、青、およびアルファ値を変更するための 4 つがあり、最終結果は のスライダーの横に表示されますRectangle。モックアップを以下に示します。

ここに画像の説明を入力

このコントロールが正しく機能するために、5 つの依存関係があります。最終結果を表示するための4Slidersと 1 。FrameworkElement

これが意味することは、私の中でControlTemplate4 つのスライダーと 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

1 つのアドバイスとして、テンプレートに名前付きのパーツが含まれていると想定しないでください。名前付き部分が存在しなくても、コントロールは (可能な限り) 機能するはずです。Microsoft からのガイダンスでは、名前付き部分が欠落している場合は例外をスローしないでください (つまり、コントロールが期待どおりに機能しなくても、アプリケーションは実行できます)。

例えば

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

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

また、名前付きパーツには可能な限り低い基底クラスを使用するようにしてください。たとえば、私の例では、範囲コントロールのタイプではなくSliders、タイプの名前付きパーツを使用できます。RangeBaseSlider

最後に、クラスには、 の呼び出し中に取得される名前付きパーツへの参照を保持する変数が必要OnApplyTemplateです。使用するたびにコントロールを見つけようとしないでください。

Rowしたがって、これをandコントロールに依存するコントロールに適用する必要があるHeaderため、これらはあなたの中に存在する必要があります(簡単にするために、1 つのandをControlTemplate想定します)。RowHeader

まず、次のように文書化しCustomControlます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()ような方法を使用します。

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

カスタム コントロールにビジュアル ツリーに関する特定の要件がある場合は、 を使用してこれらを宣言し、TemplatePartAttribute中に解決を使用できますOnApplyTemplate

于 2013-01-27T10:10:10.897 に答える