TreeView が配置された UI で Model-View-ViewModel パターンを使う方法(VB.NET)


TreeView にどう再帰的にデータを設定するか調べていたら、id:griefworker さんの以下の記事が見つかりました。


TreeView が配置された UI で Model-View-ViewModel パターンを使う方法


なるほど!HierarchicalDataTemplate を使えばいいわけですね!*1


以下学習のため、Livet のテンプレートで生成した VB.NET プロジェクトに実装してみました。*2


ViewModel


まず ViewModel です。Livet を使う関係上、id:griefworker さんのコードをかなり改造させていただいてます。再帰構造にするため、自身のコレクションをメンバに持たせることが肝ですね。

Option Explicit On
Option Strict On

Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.IO

Public Class DirectoryViewModel
	Inherits ViewModel

	Private _children As ReadOnlyCollection(Of DirectoryViewModel)
	Private Shared _dummy As DirectoryViewModel = New DirectoryViewModel()
	Private _model As DirectoryInfo
	Private _parent As DirectoryViewModel

#Region "プロパティ"

	Public ReadOnly Property Children As ReadOnlyCollection(Of DirectoryViewModel)
		Get
			If (Me.HasDummy) Then
				Dim list = New List(Of DirectoryViewModel)()
				Try
					For Each info In _model.GetDirectories()
						Dim dic = New DirectoryViewModel() With {.Path = info.FullName}
						list.Add(dic)
					Next
				Catch ex As UnauthorizedAccessException
				Catch ex As Exception
					Throw
				End Try
				_children = New ReadOnlyCollection(Of DirectoryViewModel)(list)
			End If
			Return _children
		End Get
	End Property

	Public ReadOnly Property HasDummy() As Boolean
		Get
			Return (_children.Count = 1) AndAlso (Object.ReferenceEquals(_children(0), _dummy))
		End Get
	End Property

	Public ReadOnly Property Name As String
		Get
			Return _model.Name
		End Get
	End Property

	Public ReadOnly Property Parent() As DirectoryViewModel
		Get
			Return _parent
		End Get
	End Property

#Region "IsSelected変更通知プロパティ"
	Private _IsSelected As Boolean

	Public Property IsSelected() As Boolean
		Get
			Return _IsSelected
		End Get
		Set(ByVal value As Boolean)
			If (_IsSelected = value) Then Return
			_IsSelected = value
			RaisePropertyChanged("IsSelected")
		End Set
	End Property
#End Region

#Region "Path変更通知プロパティ"
	Private _Path As String

	Public Property Path() As String
		Get
			Return _Path
		End Get
		Set(ByVal value As String)
			If (_Path = value) Then Return
			_Path = value
			RaisePropertyChanged("Path")

			Me.SetDirectory(Me, _Path)
		End Set
	End Property
#End Region

#End Region

#Region "メソッド"

	Public Sub SetDirectory(parent As DirectoryViewModel, path As String)
		If (Directory.Exists(path) = False) Then
			Throw New ArgumentException(path + " は存在しません。", "path")
		End If
		_model = New DirectoryInfo(path)
		_parent = parent
		_children = New ReadOnlyCollection(Of DirectoryViewModel)(New DirectoryViewModel() {_dummy})
	End Sub

#End Region

End Class

View


お次は View です。コードビハインドをなくすため、DirectoryViewModel.Path プロパティの最上位ディレクトリの設定を XAML に移動しました。

<Window x:Class="DirectoryWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
	xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
	xmlns:local="clr-namespace:DirectoryApplication"
	Title="DirectoryWindow" Height="350" Width="525">

	<Window.DataContext>
		<local:DirectoryViewModel Path="C:\" />
	</Window.DataContext>
	
	<Grid>
		<!--DirectoryViewModel の Children を TreeView にバインド-->
		<TreeView ItemsSource="{Binding Path=Children}">

			<!--TreeViewItem の状態を DirectoryViewModel に保存するためのスタイル-->
			<TreeView.ItemContainerStyle>
				<Style TargetType="{x:Type TreeViewItem}">
					<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
					<Setter Property="FontWeight" Value="Normal"/>
					<Style.Triggers>
						<Trigger Property="IsSelected" Value="True">
							<Setter Property="FontWeight" Value="Bold"/>
						</Trigger>
					</Style.Triggers>
				</Style>
			</TreeView.ItemContainerStyle>

			<!--DirectoryViewModel のデータを表示するときに使うテンプレート-->
			<TreeView.ItemTemplate>
				<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
					<TextBlock Text="{Binding Path=Name}"/>
				</HierarchicalDataTemplate>
			</TreeView.ItemTemplate>
		</TreeView>
	</Grid>
</Window>


なんかのお役に立てば幸いです。<(_ _)>


*1:エッセンシャル WPF に 324頁に書いてあるのだが、完全飛ばし読みしてた・・・

*2:VBユーザーに優しい仕様なのが当ブログの売りですw