PG日誌

読者です 読者をやめる 読者になる 読者になる

PG日誌

主にc#の事を書いています

WPF で TreeViewItem をノードを展開したときに動的に取得する


よく WindowsForm で TreeView の Items にダミーデータ突っ込んであたかも展開可能のように見せることをしてたけどそれを 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

のように取得結果が変わります。やろうとしていることは簡単ですが、WFP のプロパティシステムと Style の考え方、MVVM などかなり色々知らないとできない感じですね。