トップ回答者
DataGridで、DataTemplate内のコントロールの取得

質問
-
お世話になります
WindowForms の DataGridView で作成したプログラムを、DataGrid に置き換える作業を行っています
もともと DataGridView で RowsAdded イベントで行が追加された際に、
バインドされたデータの値によってセルのスタイルなどを変更を行っていたので、
DataGrid でも同じような動作とするために LoadingRow イベントで行おうとしていますさらに、DataGridView では行うのが難しかった列方向のセルの結合を、
DataTemplate を用いてセルの中に複数のコントロールを詰め込んで、
擬似的にセルの結合っぽいことをさせようとしています<DataGridTemplateColumn Header="sample">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="Col1" Text="{Binding Path=data1}"/>
<TextBlock Name="Col2" Text="{Binding Path=data2}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>ここで問題となっているのが、LoadingRow イベントハンドラ内でコードによって DataTemplate 内のコントロールに対して
アクセスするにはどうしたらよいのかということです
(Col1 と Col2 にアクセスしたい)MSDNから、DataTemplate によって生成された要素を検索するというサンプルを見てアクセスできるかと思ったのですが、
DataGrid(?).ItemContainerGenerator.ContainerFromItem に設定すべき値はなにかもよくわかっていませんどのようにすれば、コントロールにアクセスできるのでしょうか?
よろしくお願いします
回答
-
少なくともLoadingRowイベントでは難しい気がします。なぜならまだVisual Treeが形成されていないからです。したがって、VisualTreeHelperを使って見つけることもできないでしょう。Windows FormにおけるDataGridViewのRowsAddedイベントに相当するLoadedRowみたいなイベントは無いようです。あったとしてもこの段階でもVisual Treeが形成されていないので無理でしょう。たぶん、Windowが全て表示された後でないと無理じゃないかと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク Nymphaea 2010年5月20日 4:48
-
DataGridだとItemContainerStyleあたりでStyleを設定して
そこのTriggersあたりでDataTriggerやTriggerを駆使して色々
出来ます。簡単なところだと、奇数行だけ背景色を赤色にしたりとか、データが
指定した値以上だと赤色にするとかそういったことが出来ます。
後者は、コンバータかバインドしてるクラス側に指定した値以上かどうかを
boolで返すようなものを準備しておいたほうが実装は楽かも。
かずき Blog:http://blogs.wankuma.com/kazuki/- 回答としてマーク Nymphaea 2010年5月20日 4:51
-
MSDNの DataGrid.LoadingRow イベントの説明では、
「DataGridRow がインスタンス化された後で発生します。使用する前にカスタマイズできます。」
「このイベントでは、使用する前に新しい行に必要な変更を加えることができます。」
とあったため、このイベントでいけるかなと思ったのですが・・・
WPFには論理ツリーとビジュアルツリーの2つのツリーがあります。実際にDataGridの行が作成され、それに対してテンプレートなどが適用されて実際の見た目、つまりビジュアルツリーが作成されます。これはWindows Formとは大きく異なる点です。
したがって、DataGridの行が作成されただけでは不十分で、ビジュアルツリーが動的に生成された後に、それを読み取らなくてはなりません。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク Nymphaea 2010年5月20日 4:48
-
>自身のコントロールのバインディングデータをもとに、
>データを数学的な処理を行った後に自身(Cell)のコントロールのプロパティの変更を行い、
>さらに親(Row)のプロパティの変更と、兄弟(Cell)に関連付けしたコントロールのプロパティを変更するということを行いたいです
一つの考え方として、1行分のデータに、非表示項目を追加して「コントロールのバインディングデータをもとにデータを数学的な処理を行った後コントロールのプロパティの変更」する型に変換したもの(色を変えるんであればBrushとか)を格納した値を、変更したいプロパティにBindしておけば達成できるのではないでしょうか?DataTrigger よりも柔軟でプログラムで制御できる方法を考えてみました。
注意としては非表示項目も NotifyPropertyChanged が必要です。
えムナウ@わんくま同盟 Microsoft MVP Visual Studio C# Since 2005/01-2010/12- 回答としてマーク Nymphaea 2010年5月20日 4:48
すべての返信
-
Hongliang様、えムナウ様、返信ありがとうございます。
> WPF では一般的に、動的なスタイルの変更を DataTrigger などを使って表現できます。
> ほとんどの場合、わざわざ DataTemplate の中を外部から操作する必要はないでしょう。WPF を初めてまだ間もなく、いろいろなサンプルを見ながら多くの場合はロジックコードを書くことなくできるのだなとは感じているのですが、
XAML だけでどの程度の範囲のことまでできるのかは、いまだよく理解できていませんその中で、とりわけよくわかっていないことが、XAML では自身のコントロールのプロパティの変化をトリガに、
親、兄弟、子関係のコントロールに対して影響を与えることができるのか? というところのサンプルを見かけません
(なんとなく出来なさそう? と思っています・・・)今回の質問の意図としましては、DataGridView から DataGrid への置き換えで、自身のコントロールのバインディングデータをもとに、
データを数学的な処理を行った後に自身(Cell)のコントロールのプロパティの変更を行い、
さらに親(Row)のプロパティの変更と、兄弟(Cell)に関連付けしたコントロールのプロパティを変更するということを行いたいです
(DataGridView では、RowsAdded イベント内でこれらすべての処理を行っていました)なんとなく、XAML だけだと自分以外のコントロールに対しては操作ができなさそうという思い込みで、
とりあえずコードで書けばいけるかなと思い込んで突っ走ってしまったのですが、
このようなことも含めて、XAML or コードで実現できそうでしょうか?申し訳ありませんが、もうしばらくお知恵を拝借できればと思います
-
少なくともLoadingRowイベントでは難しい気がします。なぜならまだVisual Treeが形成されていないからです。したがって、VisualTreeHelperを使って見つけることもできないでしょう。Windows FormにおけるDataGridViewのRowsAddedイベントに相当するLoadedRowみたいなイベントは無いようです。あったとしてもこの段階でもVisual Treeが形成されていないので無理でしょう。たぶん、Windowが全て表示された後でないと無理じゃないかと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク Nymphaea 2010年5月20日 4:48
-
trapemiya様、返信ありがとうございます
> あったとしてもこの段階でもVisual Treeが形成されていないので無理でしょう。
> たぶん、Windowが全て表示された後でないと無理じゃないかと思います。この結果はかなり予想外でした・・・
MSDNの DataGrid.LoadingRow イベントの説明では、
「DataGridRow がインスタンス化された後で発生します。使用する前にカスタマイズできます。」
「このイベントでは、使用する前に新しい行に必要な変更を加えることができます。」
とあったため、このイベントでいけるかなと思ったのですが・・・
変更可能なのは行(Row)のみで、そこに含まれる子(Cell)は未初期化ということでしょうか?
(名称が進行形だったのは気になってはいましたが・・・)DataGrid.LoadingRow イベントのテストでは、確かに行に対してはプロパティの変更は可能でしたが、
そこからセルの内容に対しては、デバッガを見てもたどることができなかったのはそれが原因だったのでしょうか?少なくとも、DataGrid では各行はスクロールとともにリサイクルされるため、
やろうとすると Window が全て表示された後で、スクロールなどのイベントを拾って各行をすべて書き換えるとかになってしまうのでしょうか・・・? -
DataGridだとItemContainerStyleあたりでStyleを設定して
そこのTriggersあたりでDataTriggerやTriggerを駆使して色々
出来ます。簡単なところだと、奇数行だけ背景色を赤色にしたりとか、データが
指定した値以上だと赤色にするとかそういったことが出来ます。
後者は、コンバータかバインドしてるクラス側に指定した値以上かどうかを
boolで返すようなものを準備しておいたほうが実装は楽かも。
かずき Blog:http://blogs.wankuma.com/kazuki/- 回答としてマーク Nymphaea 2010年5月20日 4:51
-
MSDNの DataGrid.LoadingRow イベントの説明では、
「DataGridRow がインスタンス化された後で発生します。使用する前にカスタマイズできます。」
「このイベントでは、使用する前に新しい行に必要な変更を加えることができます。」
とあったため、このイベントでいけるかなと思ったのですが・・・
WPFには論理ツリーとビジュアルツリーの2つのツリーがあります。実際にDataGridの行が作成され、それに対してテンプレートなどが適用されて実際の見た目、つまりビジュアルツリーが作成されます。これはWindows Formとは大きく異なる点です。
したがって、DataGridの行が作成されただけでは不十分で、ビジュアルツリーが動的に生成された後に、それを読み取らなくてはなりません。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク Nymphaea 2010年5月20日 4:48
-
>自身のコントロールのバインディングデータをもとに、
>データを数学的な処理を行った後に自身(Cell)のコントロールのプロパティの変更を行い、
>さらに親(Row)のプロパティの変更と、兄弟(Cell)に関連付けしたコントロールのプロパティを変更するということを行いたいです
一つの考え方として、1行分のデータに、非表示項目を追加して「コントロールのバインディングデータをもとにデータを数学的な処理を行った後コントロールのプロパティの変更」する型に変換したもの(色を変えるんであればBrushとか)を格納した値を、変更したいプロパティにBindしておけば達成できるのではないでしょうか?DataTrigger よりも柔軟でプログラムで制御できる方法を考えてみました。
注意としては非表示項目も NotifyPropertyChanged が必要です。
えムナウ@わんくま同盟 Microsoft MVP Visual Studio C# Since 2005/01-2010/12- 回答としてマーク Nymphaea 2010年5月20日 4:48
-
かずき様、trapemiya様、えムナウ様、返信ありがとうございます
> 簡単なところだと、奇数行だけ背景色を赤色にしたりとか、データが
> 指定した値以上だと赤色にするとかそういったことが出来ます。個々のコントロールに対しては、バインドしたデータからさまざまな変更を加えられることは、
MSDN のサンプルで実際に試してみたりはしていました> WPFには論理ツリーとビジュアルツリーの2つのツリーがあります。
> 実際にDataGridの行が作成され、それに対してテンプレートなどが適用されて実際の見た目、つまりビジュアルツリーが作成されます。
> これはWindows Formとは大きく異なる点です。
> したがって、DataGridの行が作成されただけでは不十分で、ビジュアルツリーが動的に生成された後に、それを読み取らなくてはなりません。とても参考になります
少しずつ MSDN などの情報源を探して目を通しているのですが、WindowForms とのギャップに戸惑ってばかりです・・・> 一つの考え方として、1行分のデータに、非表示項目を追加して
> 「コントロールのバインディングデータをもとにデータを数学的な処理を行った後コントロールのプロパティの変更」する型に
> 変換したもの(色を変えるんであればBrushとか)を格納した値を、変更したいプロパティにBindしておけば達成できるのではないでしょうか?この発想でなら確かにいけそうと思い実際に試してみたところ、10分ほどでイベントなどを使わずにデータ変換の組み込みだけで出来ました!
最初はバインド用のDataTableを用意した際に、あらかじめすべての行に演算を加えた結果を格納する列を追加して、
その列を XAML 側でスタイル設定してみたのですが、スタイル設定用の列を追加しなくてもデータ変換を利用することでも可能でした<Window.Resources> <src:DataConverter x:Key="dataConverter"/> </Window.Resources> <Grid> <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" HorizontalAlignment="Left" VerticalAlignment="Top"> <DataGrid.Columns> <DataGridTemplateColumn Header="sample"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Name="Col1" Text="{Binding Path=data1}"/> <TextBlock Name="Col2" Text="{Binding Path=data2}"/> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> <DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="Foreground" Value="{Binding Path=data1, Converter={StaticResource dataConverter}}"/> </Style> </DataGrid.RowStyle> </DataGrid> </Grid>
[ValueConversion( typeof( string ), typeof( SolidColorBrush ) )] public class DataConverter : IValueConverter { public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) { string str = value as string; // ここでバインドデータをもとに処理を行う if( str == "r" ) return new SolidColorBrush( Color.FromRgb( 0xff, 0x00, 0x00 ) ); else if( str == "b" ) return new SolidColorBrush( Color.FromRgb( 0x00, 0x00, 0xff ) ); else return new SolidColorBrush( Color.FromRgb( 0xff, 0xff, 0xff ) ); } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture ) { return null; } }
いま思えば確かにと思うのですが、WindowForms に慣れすぎてしまい、
WPF のようにプロパティごとにデータをバインドできるということからコントロール同士が互いに干渉する必要はないということに発想できませんでした・・・
(皆さん、再三にわたって Style や Binding で出来ますよと言っていただいていたのですが・・・)上記のコードではセルに対するスタイルは省略していますが(というか、全体的にほぼ省略していますが)、
行やセル、セル内のコントロールまですべてのスタイルを目的通りに設定することができましたお知恵をお貸しくださった皆様、ありがとうございました