2014年5月22日木曜日

wpf : ListViewItemのDataTemplateで作った子コントロールを探す

検索してもそのものズバリが出てこなかったので一応書いてみました。 特定の行のListViewItemを探すには次のコードを使います。

でもこれだけじゃダメです。 ListViewはデフォルトでVirtualizingStackPanelを使っています。 VirtualizingStackPanel上では画面に必要なListViewItemしか作られません。 そのおかげで大量に行を登録しても軽快に動くのですが、当然作られていないListViewItemにはアクセスできません。 ContainerFromIndexを呼んでもnullが返ってきます。

「どうすりゃいいの?」と少し試したところ、ListView.ScrollIntoViewメソッドを使えば、副作用として作られていないListViewItemがすぐに用意されることが分かりました。 しかし、そのListViewItemはそのまま使える状態ではありません。 デバッガでそれをよく見るとIsLoadedがfalseになってました。 ってことでLoadedイベントで使える状態になるのを待つことに。 こんなコードで目的のListViewItemにアクセスすることができました。

ListViewItemから子コントロールを探すのはmsdnに載ってるとおり...

ではダメでした。 これはListBox用のコードで、ListViewに対して使うとDataTemplate.FindNameが上手くいきません。 デバッグ画面からWPFツリービジュアライザーを見てみると、

どうやらデフォルトテンプレートだとGridViewRowPresenterの子として列の数だけContentPresenterが作られるようですね。 msdnの通りにやると1つ目のContentPresenterしか得られません。 それで別の列のGridViewColumn.CellTemplateから1列目のContentPresenterに対してFindNameしてしまい失敗したのでしょう。 これは対象の列のContentPresenterを探せばすぐに解決できます。 ということで、ListViewItemの子から目的のコントロールを探すコードはこんな感じ(デフォルトテンプレートの場合)。

これで目的のコードが実行できます。

ちなみに、FindVisualChildメソッドの中身は参考にしたmsdnのページの通りです。


動作確認用の簡単なサンプルプログラムを作りました。 サンプルの動作は、行指定のTextBoxに行数を入れてその右のスクロールボタンをクリックするとListViewのその行までスクロールして「文章」列のTextBoxにフォーカスを当てます。

以下、コードです。 まずはMainWindow.xamlから。

MainWindow.xaml.csです。

データ用のクラスは簡単なものです。


一応、ツリービジュアライザーの説明へのリンクも貼っておきます。