ルートフォルダーを取得してTreeViewコントロールに表示する
それでは、ファイルマネージャーの骨格を作っていきましょう。まず、起動時にはすべてのルートフォルダーを表示したいですね。ルートフォルダーをStorageFolderオブジェクトとして取得し、それをTreeViewコントロールに追加します。
TreeViewコントロールを配置する
TreeViewコントロールは標準のものですから、次のコードのようにXAMLを記述するだけです。
<TreeView x:Name="TreeView1" SelectionMode="Single"
/>
選択できるノード(ここではフォルダーやファイル)は1つだけに制限したいので、SelectionModeプロパティをSingleにしています。この後で、イベントハンドラーやスタイルを追加していきます。
ルートフォルダーを取得してTreeViewコントロールに追加する
前述したようにDirectory.GetLogicalDrivesメソッドで全ドライブレターが得られるので、StorageFolder.GetFolderFromPathAsyncメソッドを使って各ルートフォルダーのStorageFolderオブジェクトを取得します。そして、取得したStorageFolderオブジェクトを持つTreeViewNodeオブジェクトを作り、TreeViewのルートノードに追加します(次のコード)。
private async Task InitializeTreeViewAsync()
{
// すべてのドライブレターを取得する
string[] drives = System.IO.Directory.GetLogicalDrives();
foreach (var drive in drives)
{
// 各ドライブのルートフォルダーを取得する
StorageFolder folder
= await StorageFolder.GetFolderFromPathAsync(drive);
// TreeViewのルートノードに追加する
this.TreeView1.RootNodes.Add(new TreeViewNode()
{
IsExpanded = false,
HasUnrealizedChildren = true,
Content = folder,
});
}
}
ここでTreeViewNodeオブジェクトのIsExpandedプロパティは、ノードが展開された状態かどうかを示します。HasUnrealizedChildrenプロパティは、trueにすると、実際に子ノードがあるかどうかに関係なくノードを展開するUIを表示します。
上のメソッドを画面表示時に呼び出すようにして実行してみると、次の画像のようになります。
まだデータテンプレートを与えていないので、オブジェクトの型名が表示されています。2つあるうちの下側を選択した状態です。TreeViewコントロールのSelectionModeプロパティをSingleにしたので、複数を選択することはできません。それぞれの左側に表示されている「>」記号は、そのノードに子ノードがあることを示しています。ルートフォルダー配下のフォルダー/ファイルを実際にはまだ追加していませんが、HasUnrealizedChildrenプロパティをtrueにしたので、あたかも子ノードがあるかのような表示になっています。
TreeViewコントロールの表示をカスタマイズする
TreeViewのノードにデータテンプレートを適用して、フォルダー名/ファイル名とフォルダー/ファイルのアイコンを表示しましょう。とはいうものの、Windows 10 1803のTreeViewコントロールには、その子要素であるノード(TreeViewNodeオブジェクト)に適用するデータテンプレートを指定するためのプロパティが用意されていません(本稿執筆時点ではそのためのItemTemplateプロパティはプレビュー段階)。そのため、TreeViewに与えるスタイル定義の中でデータテンプレートを指定するという、少々煩雑な手順を踏みます。
フォルダー用/ファイル用のデータテンプレート
TreeViewのノードには、StorageFolderオブジェクトだけでなく、このあとStorageFileオブジェクトも追加します。見た目で区別できるように、アイコンが異なる2種類のデータテンプレートを用意します(次のコード)。
<!-- TreeViewのノードに適用するデータテンプレート -->
<!-- フォルダー用 -->
<DataTemplate x:Key="FolderTemplate">
<TreeViewItem IsHitTestVisible="False">
<StackPanel Orientation="Horizontal">
<Image Width="20" Source="Assets/folder.png"/>
<TextBlock Text="{Binding Content.Name}" FontWeight="Bold" />
</StackPanel>
</TreeViewItem>
</DataTemplate>
<!-- ファイル用 -->
<DataTemplate x:Key="FileTemplate">
<TreeViewItem IsHitTestVisible="False">
<StackPanel Orientation="Horizontal">
<Image Width="20" Source="Assets/file.png"/>
<TextBlock Text="{Binding Content.Name}" />
</StackPanel>
</TreeViewItem>
</DataTemplate>
この2つのテンプレートは、ほぼ同じです。違いは、アイコンと、フォルダーの文字列を太字にしていることくらいです。
データコンテキストはTreeViewNodeオブジェクトになるので、そのContentプロパティ(=IStorageItemオブジェクト)のNameプロパティ(=フォルダー名/ファイル名)をTextBlcokコントロールにデータバインディングしています。
データテンプレートセレクター
上の2つのテンプレートを切り替える仕掛けを用意します。
まず、DataTemplateSelectorクラスを継承して、テンプレートを切り替えるためのクラスを作ります(次のコード)。
public class StorageItemTemplateSelector : DataTemplateSelector
{
// 選択対象のデータテンプレート
// その実体はXAML側からセットされる
public DataTemplate FolderTemplate { get; set; }
public DataTemplate FileTemplate { get; set; }
// 渡されたTreeViewNodeのContentがフォルダーだったらFolderTemplateを返す。
// それ以外のときはFileTemplateを返す。
protected override DataTemplate SelectTemplateCore(object item)
=> ((item as TreeViewNode)?.Content is StorageFolder)
? FolderTemplate : FileTemplate;
}
このStorageItemTemplateSelectorクラスのインスタンス化は、XAML側で行います。ページのリソースに次のコードを追加します。
<local:StorageItemTemplateSelector
x:Key="StorageItemTemplateSelector"
FolderTemplate="{StaticResource FolderTemplate}"
FileTemplate="{StaticResource FileTemplate}" />
XAMLで、StorageItemTemplateSelectorオブジェクトを生成し、そのFolderTemplateプロパティ/FileTemplateプロパティにデータテンプレートのオブジェクトをセットしています。
TreeViewのスタイル定義
まず、generic.xamlからTreeView用のスタイルをコピーして、ページのリソースに貼り付けます。generic.xamlを開くには、MainPage.xamlの冒頭部分にあるBackgroundプロパティのApplicationPageBackgroundThemeBrush部分にカーソルを置いて[F12]キー(定義へ移動)を押すと速いです。generic.xamlの中で、「TreeView」を単語単位で検索してスタイル定義を見つけます。
そうしたら、次のコードのようにTreeView用のスタイルを変更します(変更部分を太字にしてあります)。
<Style TargetType="TreeView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<TreeViewList x:Name="ListControl"
ItemTemplateSelector="{StaticResource StorageItemTemplateSelector}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
CanDragItems="False" AllowDrop="False" CanReorderItems="False">
<TreeViewList.ItemContainerTransitions>
<TransitionCollection>
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</TreeViewList.ItemContainerTransitions>
</TreeViewList>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
これでStorageItemTemplateSelectorオブジェクトがTreeViewにセットされました。StorageItemTemplateSelectorオブジェクトは、前述したようにノードの内容に合わせてフォルダー用またはファイル用のデータテンプレートを提供してくれます。実行してみると、次の画像のようにルートフォルダーが表示されます。なお、画像中のPドライブというのは、この記事のために用意したネットワークドライブです。
