F#でWPF --- 階層構造表示

今回はTreeViewを利用して階層構造のデータを表示してみます。

HierarchicalDataTemplate

基本的にはDataTemplateにItemsSourceが加わったのものです。

ItemsSourceに子供の要素を保持しているプロパティをバインドします。

DataTemplateに関しては下記記事を参照してください。
F#でWPF --- 可変個のコントロール --- 型で生成するコントロールを変更 - 何でもプログラミング

<TreeView ItemsSource="{Binding Items}" Margin="10,10,10,35">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type MyClass}" ItemsSource="{Binding Children}">
            <!-- Control -->
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>


作成するアプリケーション

フォルダダイアログで選択したフォルダ内のファイル構造を表示するアプリケーションです。

f:id:any-programming:20170219002627p:plain

File、Folder

DataTemplateで型で分岐させるため、ファイル及びフォルダの情報を持つクラスを定義します。

また階層構造を作成する関数も定義します。

namespace FileTree

[<AbstractClass>]
type FileOrFolder(name : string) =
    member this.Name = name

type File(name : string) =
    inherit FileOrFolder(name)

type Folder(name : string, children : FileOrFolder list) =
    inherit FileOrFolder(name)
    member this.Children = children

[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Folder =
    open System.IO
    let rec create path =
        let name = Path.GetFileName(path)
        let files = 
            Directory.EnumerateFiles(path) 
            |> Seq.map (Path.GetFileName >> File)
            |> Seq.cast<FileOrFolder>
        let folders = 
            Directory.EnumerateDirectories(path) 
            |> Seq.map create
            |> Seq.cast<FileOrFolder>
        Folder(name, files |> Seq.append folders |> Seq.toList)


アプリケーションコード

DataTemplateの時と同様に、Resourcesの中にHierarchicalDataTemplateを定義します。

ファイルとフォルダのアイコンは、Material Design Iconsのものを利用しています。

XAML内のFolderDialogActionは下記記事を参照してください。
F#でWPF --- フォルダダイアログCommand - 何でもプログラミング

F#内のDataContextクラスは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング


XAML

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="clr-namespace:FileTree;assembly=FileTree"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="200">
    <Grid>
        <TreeView ItemsSource="{Binding FileOrFolders}" Margin="10,10,10,35">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:Folder}" ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Viewbox Width="16" Height="16">
                            <Path Data="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" Fill="Black" />
                        </Viewbox>
                        <TextBlock Text="{Binding Name}" Margin="5,0,0,0" />
                    </StackPanel>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type local:File}">
                    <StackPanel Orientation="Horizontal">
                        <Viewbox Width="16" Height="16">
                            <Path Data="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z" Fill="Black" />
                        </Viewbox>
                        <TextBlock Text="{Binding Name}" Margin="5,0,0,0" />
                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
        <Button Content="フォルダを開く" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="75">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <local:FolderDialogAction Command="{Binding OpenFolder}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </Grid>
</Window>

F#

open System
open System.Windows
open FileTree

type Msg = OpenFolder of string

type Model = { FileOrFolders : FileOrFolder list }

let initialModel = { FileOrFolders = [] }

let updateModel model msg =
    match msg with
    | OpenFolder x ->
        { model with FileOrFolders = [ Folder.create x ] }

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- DataContext(initialModel, updateModel, id)
    Application().Run(window) |> ignore       
    0