そこで誰かがWPFTreeView
の使用を提案し、私は「そうだ、それは正しいアプローチのようだ」と思った。何時間も経った今、私はこのコントロールを使用することがどれほど困難であったかを信じられません。たくさんの調査を通じて、TreeView`コントロールを機能させることができましたが、選択したアイテムをビューモデルに取り込むための「適切な」方法を見つけることができません。コードから選択した項目を設定する必要はありません。ユーザーがどのアイテムを選択したかを知るためにビューモデルが必要です。
これまでのところ、私はこのXAMLを持っていますが、それ自体はあまり直感的ではありません。これはすべてUserControl.Resourcesタグ内にあります。
<CollectionViewSource x:Key="cvs" Source="{Binding ApplicationServers}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="DeploymentEnvironment"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<!-- Our leaf nodes (server names) -->
<DataTemplate x:Key="serverTemplate">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<!-- Note: The Items path refers to the items in the CollectionViewSource group (our servers).
The Name path refers to the group name. -->
<HierarchicalDataTemplate x:Key="categoryTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource serverTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
</HierarchicalDataTemplate>
そして、これがツリービューです。
<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Source={StaticResource cvs}, Path=Groups}"
ItemTemplate="{StaticResource categoryTemplate}">
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/>
</Style>
</TreeView>
これにより、サーバーが環境(dev、QA、prod)ごとに正しく表示されます。ただし、SOで選択したアイテムを取得する方法はさまざまで、多くは複雑で難しいものです。選択したアイテムを取得する方法は簡単ですか?私のビューモデル?
注:TreeView`にはSelectedItem
プロパティがありますが、これは読み取り専用です。私にとってイライラするのは、読み取り専用で問題がないことです。コードで変更したくありません。しかし、コンパイラが読み取り専用であると文句を言うので、私はそれを使用できません。
このようなことをするための一見エレガントな提案もありました:
<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" />
そして、私はこの質問をしました:「ビューモデルはどのようにしてこの情報を取得できますか?ContentPresenter
が選択されたアイテムを保持していることがわかりますが、それをビューモデルにどのように取得しますか?」しかし、まだ答えはありません。
したがって、私の全体的な質問は、「選択したアイテムをビューモデルに取得するための単純方法はありますか?」です。
やりたいことをするために、ItemContainerStyle
のTreeView
を変更できます。
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
次に、ビューモデル(ツリー内の各アイテムのビューモデル)は、ブール値のIsSelected
プロパティを公開する必要があります。
特定のTreeViewItem
が展開されるかどうかを制御できるようにしたい場合は、そのプロパティにもセッターを使用できます。
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
次に、ビューモデルはブール値のIsExpanded
プロパティを公開する必要があります。
これらのプロパティは双方向で機能するため、ユーザーがツリー内のノードを選択すると、ビューモデルのIsSelected
プロパティがtrueに設定されることに注意してください。一方、ビューモデルでIsSelected
をtrueに設定すると、そのビューモデルのツリー内のノードが選択されます。そして同様に拡張されました。
ツリー内の各アイテムのビューモデルがない場合は、それを取得する必要があります。ビューモデルがないということは、モデルオブジェクトをビューモデルとして使用していることを意味しますが、これを機能させるには、これらのオブジェクトにIsSelected
プロパティが必要です。
親ビューモデル(SelectedItem
にバインドし、子ビューモデルのコレクションを持つプロパティ)でTreeView
プロパティを公開するには、次のように実装できます。
public ChildViewModel SelectedItem {
get { return Items.FirstOrDefault(i => i.IsSelected); }
}
ツリー上の個々のアイテムの選択を追跡したくない場合でも、SelectedItem
のTreeView
プロパティを使用できます。ただし、「MVVMスタイル」で実行できるようにするには、ブレンド動作を使用する必要があります(さまざまなNuGetパッケージとして利用可能-「ブレンドインタラクティブ機能」を検索してください)。
ここに、選択したアイテムがツリー内で変更されるたびにコマンドを呼び出すEventTrigger
を追加しました。
<TreeView x:Name="treeView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction
Command="{Binding SetSelectedItemCommand}"
CommandParameter="{Binding SelectedItem, ElementName=treeView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
SetSelectedItemCommand
のDataContext
にプロパティTreeView
を追加して、ICommand
を返す必要があります。ツリービューの選択されたアイテムが変更されると、コマンドのExecute
メソッドが、選択されたアイテムをパラメーターとして呼び出されます。コマンドを作成する最も簡単な方法は、おそらくDelegateCommand
を使用することです(WPFの一部ではないため、Googleで実装を取得します)。
不格好なコマンドなしで双方向バインディングを可能にするおそらくより良い代替手段は、ここStackOverflowでSteveGreatrexによって提供された BindableSelectedItemBehavior を使用することです。
私はおそらく SelectedItemChanged
イベントを使用して、VMにそれぞれのプロパティを設定します。
マーティンの答えに基づいて、提案されたソリューションを適用する方法を示す簡単なアプリケーションを作成しました。
サンプルコードはCinchV2フレームワークを使用してMVVMをサポートしていますが、好みのフレームワークを使用するように簡単に変更できます。
興味のある方は、GitHubで コードはこちら
それが役に立てば幸い。
パーティーに少し遅れましたが、今これに出くわしている人にとって、私の解決策は次のとおりでした。
<i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>
これにより、標準のMVVM ICommandバインディングを使用して、コードビハインドや長い回避策を使用せずにSelectedItemにアクセスできます。
また、パーティーに遅れますが、MVVMLightユーザーの代替手段として:
全体の実装は非常に高速で、正常に機能します。
ここで、IsSelectedプロパティ(SourceItemは選択されたViewModelアイテムのModel部分です):
Public Property IsSelected As Boolean
Get
Return _isSelected
End Get
Set(value As Boolean)
If Me.HasImages Then
_isSelected = value
OnPropertyChanged("IsSelected")
Messenger.Default.Send(Of SelectedImageFolderChangedMessage)(New SelectedImageFolderChangedMessage(Me, SourceItem, "SelectedImageFolder"))
Else
Me.IsExpanded = Not Me.IsExpanded
End If
End Set
End Property
そしてここでVMホストコード:
Messenger.Default.Register(Of SelectedImageFolderChangedMessage)(Me, AddressOf NewSelectedImageFolder)
Private Sub NewSelectedImageFolder(msg As SelectedImageFolderChangedMessage)
If msg.PropertyName = "SelectedImageFolder" Then
Me.SelectedFolderItem = msg.NewValue
End If
End Sub