WPFでTeeViewを展開可能なように見せかける

よくWindowsFormでTreeViewの子要素にダミーデータを突っ込んであたかも展開可能のように見せかけるために三角形を表示する奴をWPFでもやってみようと思います。

コードビハインドではなく、MVVM形式で実現しようと思います。

完成イメージ


ノードの左側に展開可能なことを示す三角形アイコンを、本当にデータがあるかどうかに係わらず表示します。


f:id:Takachan:20150604193121p:plain


XAML定義

XAML 上では以下2つを定義します

  • IsExpandedプロパティをViewModelにバインドする定義
  • TreeViewItemをどうやって画面上に表示するかの定義

今回はTreeViewItemは、テキストのみです。(本当は画像 + テキストにしてみたかったり…

<Window x:Class="TreeViewSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewSample"
        Title="MainWindow" Height="350" Width="525" Initialized="Window_Initialized">
    <StackPanel>
        <TreeView Name="TreeView" HorizontalAlignment="Stretch" Height="201">
            
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
                </Style>
            </TreeView.ItemContainerStyle>
            
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="local:Persion" ItemsSource="{Binding Childs}">
                    <TextBlock Text="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </StackPanel>
</Window>

ViewModelのPersonクラス

Personクラスは、初回表示のChilds取得時は、ダミーデータを作成して応答します。で、バインドされたIsExpandedのプロパティが変更されたときに実際のデータを取得しています。

public class Persion : INotifyPropertyChanged {

    // 適当なデータ作成用
    private Random r = new Random();

    // INotifyPropertyChanged のメンバー
    public event PropertyChangedEventHandler PropertyChanged;

    private string _Name;
    public string Name {
        get { return this._Name; }
        set {
            this._Name = value;
            this.PropertyChanged.Notice(this);
        }
    }

    private ObservableCollection<Persion> _childs = new ObservableCollection<Persion>();
    public ObservableCollection<Persion> Childs {
        get {
            // ダミーデータを突っ込んで▲ボタンを表示させる
            this._childs.Add(new Persion() { Name = "Dummy" });
            return this._childs;
        }
    }

    private bool _isExpanded;
    public bool IsExpanded {
        get {
            return this._isExpanded;
        }
        set {
            if (this._isExpanded == value) {
                return;
            }

            // モデルに展開するノード情報を問い合わせに行く
            this.changeItems(value);

            this._isExpanded = value;
            this.PropertyChanged.Notice(this);
        }
    }

    private void changeItems(bool isExpanded) {
        if (!isExpanded) {
            return;
        }

        // 動的にノードを問い合わせ → 構築
        this._childs.Clear();
        for (int i = 0; i < r.Next(1, 5); i++) {
            this._childs.Add(new Persion() { Name = r.Next().ToString() });
        }
    }
}

以下の記事で書いた拡張メソッド使ってます。
WPF の PropertyChanged で使用するプロパティ名の文字列を動的に取得する - PG日誌

MainWindowクラス

MainWindowクラスは単純に初回表示用のTreeView.ItemSourceを設定するだけです。(というかこれもXAMLに書けばいいんでしょうがやり方がよくわかんないんでこっちに書いています)

/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window {
    public MainWindow() {
        this.InitializeComponent();
    }

    private void Window_Initialized(object sender, EventArgs e) {
        this.TreeView.ItemsSource = new List<Persion>() {
            new Persion() { Name = "ROOT1" },
            new Persion() { Name = "ROOT2" },
        };
    }
}

と、すると展開するたびにモデルに問い合わせ(たつもり)を行って

1回目
f:id:Takachan:20150604193223p:plain

2回目
f:id:Takachan:20150604193433p:plain

のように取得結果が変わります。やろうとしていることは簡単ですが、そこそこめんどくさいですね。