2010年5月31日月曜日

番外編 IsSynchronizedWithCurrentItemのメモ

前にどこかにちょっと書いたような気がするけど、メモ。

リストのIsSynchronizedWithCurrentItemは便利でわりと重要だと思うんだけど、
MSDNもエッセンシャルWPFも、現在性管理とか難しい言葉で説明してあるので、
ここではかみ砕いた表現での説明に挑戦します。

個人的には、このプロパティを使うときは2つのパターンのどちらかです。

  1. リストで現在選択中のアイテムを『即時に』取得したい
  2. リストの選択項目と、他の表示コントロールを同期させたい

1は、IsSynchronizedWithCurrentItem=falseの時は
リストからフォーカスが移動したときしかSelectedItemが更新されませんが、
trueにしておくことで、リストの選択項目を変更した瞬間に更新されます。

多くの場合、OKボタンなどを押したときに選択されている項目を取得できればいいのですが、
ショートカットキーとかを駆使したりして、
リストにフォーカスがあるままで細かく選択項目を制御/取得したいときに
trueにすると便利になります。

2はエッセンシャルWPFでも紹介されている、
リストを表示して、リストの選択項目が変わると、
別の領域に選択項目に応じてその項目の詳細を表示するものです。
Master-Detailパターンと呼ばれるものです。

たとえばこんな感じです。
リストボックスにタイムラインの一覧を選択肢としていれておいて、
選択中のタイムラインの説明を下の領域に表示しています。

正直いくつかのサンプルを見ても、どうしてそうなるのかがわからなかったのですが、
たぶん以下のような原理で動いています。
「たぶん」というのが情けないですが。

まず、Window(またはUserControl)のDataContextを
そのままリストのデータとしてバインドしている場合は簡単です。
以下、実際にためしていないのですが、動くはずです。


<UserControl x:Class="Cassador.Twitter.View.Parts.EditDataSource"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 <DockPanel>
  <ComboBox ItemsSource="{Binding}" DisplayMemberPath="DisplayName"
     IsSynchronizedWithCurrentItem="True" DockPanel.Dock="Top"/>
  <GroupBox Header="説明">
   <TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap"/>
  </GroupBox>
 </DockPanel>
</UserControl>

選択中のアイテムのDescriptionプロパティをTextBlockに表示します。

ですが、MVVMで開発しているとWindowのDataContextはViewModelで
ViewModelのプロパティにリストを持っていて、それにバインドする場合が多いと思います。
ですが、以下のようにすると、動きません(これもためしていませんが、きっと。)


<UserControl x:Class="Cassador.Twitter.View.Parts.EditDataSource"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 <DockPanel>
  <ComboBox ItemsSource="{Binding Path=DataSourceTemplates}" DisplayMemberPath="DisplayName"
     IsSynchronizedWithCurrentItem="True" DockPanel.Dock="Top"/>
  <GroupBox Header="説明">
   <TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap"/>
  </GroupBox>
 </DockPanel>
</UserControl>

理由は、TextBlockが見ているのはWindowのDataContext(のしたのDescription)であり
リストが操作しているのは前の例と違ってWindowのDataContextではないからです。

これを解決する方法はいくつかあると思いますが、
1つはTextBlockのDataContextをComboBoxとあわせてやることです。
以下実際に使っているコード。



<UserControl x:Class="Cassador.Twitter.View.Parts.EditDataSource"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" >
 <DockPanel>
  <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
   <Button Command="{Binding Path=CloseCommand}">_Cancel</Button>
   <Button>_OK</Button>
  </StackPanel>
  <DockPanel DataContext="{Binding Path=DataSourceTemplates}">
   <ComboBox ItemsSource="{Binding}" DisplayMemberPath="DisplayName"
       IsSynchronizedWithCurrentItem="True" DockPanel.Dock="Top" Name="listbox"/>
   <GroupBox Header="説明">
    <TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap"/>
   </GroupBox>
  </DockPanel>
 </DockPanel>
</UserControl>




DataContextをあわせるために、DockPanelを1枚かませています
(DataContextを持たせられれば他のコントロールでもいいと思います)
上記の例の場合、ボタンのCommandのはリストの選択項目とは無関係の
DataContextのCloseCommandにバインディングしていることに注目してください。
ボタンを内側のDockPanelの中に持ってくると、
それはリストの選択項目のインスタンスのCloseCommandにバインドされるということになります。
(そして選択項目にCloseCommandがなければもちろん動きません)

1 件のコメント:

  1. 書き忘れましたがDataContextはXAMLのツリー構造で
    上の階層のDataContextを引き継ぎます。
    つまり、DataContextを指定しない場合は、
    ツリー構造を上がっていって、
    一番最初にDataContextを指定してあるコントロールの
    DataContextを引き継ぐことになります。

    返信削除