4

データグリッドをカスタマイズする必要があるため、WPF データグリッドを拡張する独自のデータグリッドを作成します。以下に投稿された小さな関連コード-

public class ExtendedDataGrid : DataGrid
{
    public ExtendedDataGrid()
    {
        this.SelectionMode = DataGridSelectionMode.Extended;
    }
}

ウィンドウでそのインスタンスを作成し、完全に正常に機能するように設定SelectionModeし、dataGrid のSingleプロパティを設定します。Singleこれまでのところ、すべて良いです。

しかし、DataGrid を ControlTemplate に配置した場合、SelectionModeに設定されることはありませんSingle。たとえば、SelectionMode は、DataGrid のコンストラクターでその値を明示的に設定した場合、XAML 経由で DP が設定されません。

問題を再現する小さなサンプルはこちらです -

 <Grid>
    <Grid.Resources>
        <ControlTemplate x:Key="MyTemplate">
            <local:ExtendedDataGrid ItemsSource="{Binding Collection,
                                                RelativeSource={RelativeSource   
                                                  Mode=FindAncestor, 
                                                  AncestorType=Window}}"
                                    SelectionMode="Single">
                <local:ExtendedDataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding}"/>
                </local:ExtendedDataGrid.Columns>
            </local:ExtendedDataGrid>
        </ControlTemplate>
    </Grid.Resources>
    <ContentControl Template="{StaticResource MyTemplate}"/>
    <local:ExtendedDataGrid ItemsSource="{Binding Collection,
                                          RelativeSource={RelativeSource 
                                          Mode=FindAncestor,
                                          AncestorType=Window}}"
                            Grid.Row="1" SelectionMode="Single">
        <local:ExtendedDataGrid.Columns>
            <DataGridTextColumn Binding="{Binding}"/>
        </local:ExtendedDataGrid.Columns>
    </local:ExtendedDataGrid>
</Grid>

2 番目の DataGrid では正常に動作しますが、ControlTemplate 内に配置された DataGrid では動作しません。なぜこの奇妙な行動?DataGrid コードのバグですか?

- SelectionMode を Extended に明示的に設定している DataGrid コンストラクターの行にコメントを付けると、正常に動作します。私はそれがデフォルト値であることを知っており、それを削除した後、両方のケースで正常に動作することを知っています (また、デフォルト値を設定する方法はたくさんあります) が、あるケースでは機能し、他のケースでは機能しない理由を知りたいです。

4

3 に答える 3

2

これは良い質問であり、答えるには、WPF エンジンがこれら 2 つのDataGrid.

の最初のインスタンスDataGridが の直接の子でWindowある場合、そのインスタンスはInitializeComponents()のコンストラクターからWindowが呼び出されたときに作成されます。どのように動作するかについては深く掘り下げませんがInitializeComponents、簡単に言うと、メソッドを呼び出してSystem.Windows.Application.LoadComponent()、渡された にあるファイルをLoadComponent()ロードし、ファイルのルート要素で指定されたオブジェクトのインスタンスに変換します。 . そうしている間、最初に作成する要素のデフォルトのコンストラクターを呼び出してから、属性に記載されているものを再度設定します。XAMLURIXAMLDependancyProperties

次に、 内に配置した 2 番目のインスタンスですControlTemplate。そのインスタンスはControlTemplate、要素に適用されるときに作成されます。を適用していない場合Template、インスタンスは作成されません。の適用中にTemplateControlTemplate.LoadContent()が呼び出されて のルート要素が作成されますControlTemplate。で定義された LoadContent()を作成するために、異なるコースを使用するようになりました。各要素に対してデフォルトのコンストラクターを呼び出しますが、 の設定に関しては、プロパティ値を決定するためにいくつかのチェックを実行します。簡単に言えば、要素インスタンスの特定の値がすでに設定されているかどうかを確認します (つまり、値はデフォルト値ではなく、インスタンスの ValueDictionary 内のローカル値エントリです)。UIElementscontrolTemplateDependancyPropertiesDependancyPropertyDependancyObject) xaml で指定された値は考慮されません。したがって、この場合LoadComponents、DataGrid のデフォルト コンストラクターを呼び出すときに、SelectionModeProperty値を設定します。コンテンツの読み込み中に、ControlTemplateそれをチェックして同じ値を返し、xaml で指定された値を無視します。

これは だけでなく、すべてのコントロールに当てはまりますDataGrid

于 2013-10-25T03:18:55.310 に答える
1

最後に、PresentationFramework assemblyを使用してコードを少し掘り下げた後reflector、この問題の正確な RCA を見つけることができました。nitで述べたように、この動作は、DataGrid だけでなく、すべての DP およびすべてのコントロールに対して有効です。

これはすべて、DP の設定中にどの値が他の値よりも優先される依存プロパティ値の優先順位と関係があります。(Enum は BaseValueSourceInternal であり、DP のこの優先順位を WindowsBase.dll アセンブリに格納します)

DependencyObject classDataGrid インスタンスのメソッドを UpdateEffectiveValue呼び出して、DP の最終的な実際の値を設定するメソッドが含まれています。メソッドには、実際に DP でメソッドを呼び出す前に多くのロジックが含まれています。SetValueUpdateEffectiveValueSetValue

ControlTemplate を介して設定を停止している興味深いチェックはこれです (新しい値の優先順位が古い値の優先順位よりも高いかどうかをチェックするのは、その場合にのみ値が DP に設定されます。それ以外の場合は、DP を設定せずに戻ります) -

if ((newEntry.BaseValueSourceInternal != BaseValueSourceInternal.Unknown)
    && (newEntry.BaseValueSourceInternal < oldEntry.BaseValueSourceInternal))
{
    return (UpdateResult) 0;
}

dataGrid がウィンドウの直接の子である最初のケースでは、DP プロパティは次の手順で設定されます -

  1. WPF エンジンは BAML (コンパイル済み XAML) を上から下に読み取り、DataGrid に遭遇すると、そのインスタンスを作成します。
  2. 選択モード DP を設定すると、コンストラクターから、クラスSetValueCommonのメソッドが呼び出されます。DependencyObject古い値と新しい値を method に渡しますUpdateEffectiveValue
  3. 今、old value's BaseValueSourceInternal is Unkownそしてnew value's BaseValueSourceInternal is set as LocalSetValueCommon メソッドによって。したがって、上記の if チェックから渡され、DP が設定されます。
  4. ここで、DataGrid インスタンスを作成した後、DataGrid に関連付けられたすべての属性が BAML から 1 つずつ読み取られ、検出されたすべての DP で SetValueCommon メソッドが呼び出されます。
  5. 以来、SetValueCommon メソッドの setnew value with BaseValueSourceInternal.Localold value is already BaseValueSourceInternal.Local. したがって、優先順位は同じです。そのため、Single 値が DP に設定されます。

DataGrid が ControlTemplate 内に配置される 2 番目のケースでは、

  1. DataGrid は、ContentControl 内に含まれているため、WPF エンジンが BAML を読み取るときに作成されません。ContentControl が GUI でレンダリングされる場合にのみ作成されます。Framework's ApplyTemplate gets called which calls LoadContent method to load the template.
  2. LoadContent は、最終的に DataGrid のインスタンスを作成し、BaseValueSourceInternal.Local で設定された現在の値を設定する前のケースのように DP を設定する、より多くのメソッドを内部的に呼び出します。
  3. ここで、dataGrid インスタンスが作成さApplyTemplatedParentValueれると、 method が呼び出され、 method を呼び出して、見つかったすべての DP を設定しようとしUpdateEffectiveValueます。
  4. Current value set on DP is set with BaseValueSourceInternal.Localしかしnew value、設定しようとするものは として設定されBaseValueSourceInternal.ParentTemplateます。
  5. そのため、最終的に methodUpdateEffectiveValueに移動すると、上記の if 条件は から失敗しますParentTemplate precedence order is lower than Local。したがって、古い値の BaseValueSourceInternal が不明であり、新しい値の BaseValueSourceInternal が ParentTemplate であるため、コンストラクターからの行にコメントを付ける場合よりも、SetValue が新しい値で DP で呼び出されることはありません。

DP の優先順位で述べたように、アニメーションを介して設定されたプロパティは、ローカル値よりも優先されます。したがって、理想的には、アニメーションを介して ControlTemplate でプロパティを設定すると、正常に動作するはずです。私はそれを試してみましたが、完全に正常に動作します -

<ControlTemplate x:Key="MyTemplate">
    <local:ExtendedDataGrid ItemsSource="{Binding Collection,
                                         RelativeSource={RelativeSource
                                       Mode=FindAncestor, AncestorType=Window}}">
         <local:ExtendedDataGrid.Columns>
             <DataGridTextColumn Binding="{Binding}"/>
         </local:ExtendedDataGrid.Columns>
         <local:ExtendedDataGrid.Triggers>
             <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                 <BeginStoryboard>
                     <Storyboard>
                        <ObjectAnimationUsingKeyFrames 
                             Storyboard.TargetProperty="SelectionMode">
                            <DiscreteObjectKeyFrame KeyTime="00:00:00"
                              Value="{x:Static DataGridSelectionMode.Single}"/>
                        </ObjectAnimationUsingKeyFrames>
                     </Storyboard>
                 </BeginStoryboard>
             </EventTrigger>
          </local:ExtendedDataGrid.Triggers>
    </local:ExtendedDataGrid>
</ControlTemplate>
于 2013-10-25T18:21:49.960 に答える
1

申し訳ありませんが、 を使用したときに機能しない理由についての質問にはお答えできませんがControlTemplate、拡張クラスで継承されたプロパティのデフォルト値を設定するためのより良い方法を提供できます。問題。

DependencyPropertyメソッドを使用して、継承のデフォルト値を持つ新しいメタデータを提供することができますDependencyProperty.OverrideMetadata。次のようにコンストラクターSelectionModeを使用して、プロパティに独自のデフォルト値を設定できます。static

static ExtendedDataGrid()
{
    SelectionModeProperty.OverrideMetadata(typeof(ExtendedDataGrid), 
        new FrameworkPropertyMetadata(2));
}

更新 >>>

SelectionMode列挙を必要な値を表す整数に置き換えると、コードはコンパイルされます。値を使用しただけSelectionMode.Extendedです(現在は整数値-2に置き換えられています)。これは例で使用したものだからです。

SelectionModeプロパティのデフォルト値をSelectionMode.Extended. これを使用してその行を置き換えると、問題が解決する可能性があると思いました。

于 2013-10-24T20:03:41.517 に答える