Eto.FormsでTreeGridView

さて、Eto.Formsの紹介もかなり進んできました。あとは表とツリーあたりでしょうか。

しかし、表(GridView)については公式サンプルで十分かと思います。 なので、表は割愛してツリーについて紹介します。

Eto.Formsには、ツリー型のコントロールが2つあります。

両者は使い方も比較的似ているので、今回はTreeGridViewを使ってみます。 ツリーと表(グリッド)をまとめたコントロールとなります。

f:id:mokake:20170131184120p:plain

TreeGridView Class
http://api.etoforms.picoe.ca/html/T_Eto_Forms_TreeGridView.htm

要注意事項

既にGitHubのプルリクエストにおいて、TreeGridViewの仕様追加が行われています。 このため、次バージョン(おそらく2.4)では、TreeGridViewは現状よりも使いやすくなるはずです。


TreeGridViewで表示するデータ

TreeGridViewは、DataStoreプロパティに設定した値を表示します。 DataStoreには通常、TreeGridItemCollection型のインスタンスを設定します。

TreeGridItemCollectionは、APIを見れば分かるとおり、基本的にはObservableCollectionを継承しています。 コレクションの中身は、ITreeGridItemインタフェースですが、通常は基本実体型であるTreeGridItemを使います。

TreeGridItemは、木構造を作るための再帰的なChildrenプロパティや、関連するParent,Countプロパティの他に、ツリーノードの状態を示すExpandedプロパティなどがあります。

TreeGridItemを作るサンプルコードを示します。

var item = new TreeGridItem(
    new object[] { "foo", true, 42 }
);

これは1個のデータの中身を作っていますが、加えて、木構造を別途作る必要があります。

木構造を含んだデータを作成するメソッドのサンプルを示します。 単一アイテムの中身を作るメソッド(MakeItem)と、木構造を加えてアイテム群を返すメソッド(PrepareItems)の組み合わせです。

protected TreeGridItem MakeItem(string name, bool isFolder, int size)
{
    return new TreeGridItem(
        new object[] { name, isFolder, size });
}

protected IEnumerable<TreeGridItem> PrepareItems()
{
    var drivec = MakeItem("C:", true, 32);
    yield return drivec;

    var windows = MakeItem("windows", true, 32);
    drivec.Children.Add(windows);

    var coredll = MakeItem("core.dll", false, 98451206);
    windows.Children.Add(coredll);

    var miscdll = MakeItem("misc.dll", false, 45461876);
    windows.Children.Add(miscdll);

    var drivez = MakeItem("Z:", true, 32);
    yield return drivez;

    var folderBin = MakeItem("bin", true, 32);
    drivez.Children.Add(folderBin);

    var exe = MakeItem("MyApp.exe", false, 1683204);
    folderBin.Children.Add(exe);

    var dll = MakeItem("MyLib.dll", false, 489560);
    folderBin.Children.Add(dll);

    var folderDat = MakeItem("data", true, 32);
    drivez.Children.Add(folderDat);

    var dat1 = MakeItem("config.dat", false, 4096);
    folderDat.Children.Add(dat1);

    var folderPatch = MakeItem("patches", true, 32);
    folderDat.Children.Add(folderPatch);

    var patch1 = MakeItem("patch1.dat", false, 464795);
    folderPatch.Children.Add(patch1);

    var patch2 = MakeItem("patch2.dat", false, 50129);
    folderPatch.Children.Add(patch2);

    var patch3 = MakeItem("patch3.dat", false, 2204568);
    folderPatch.Children.Add(patch3);

    var dat2 = MakeItem("characters.dat", false, 8715625);
    folderDat.Children.Add(dat2);
}

これで、次のような木構造のデータができます。

  • C:
  • Z:
    • bin
      • MyApp.exe
      • MyLib.dll
    • data
      • config.dat
      • patches
        • patch1.dat
        • patch2.dat
        • patch3.dat
      • characters.dat

TreeGridViewの設定

次に、TreeGridViewのインスタンスを生成、設定するサンプルを示します。

var MyList = new TreeGridItemCollection();

// MyListに中身を追加

var view = new TreeGridView { DataStore = MyList };

view.Columns.Add(new GridColumn {
    HeaderText = "Name",
    DataCell = new TextBoxCell(0)
});
view.Columns.Add(new GridColumn {
    HeaderText = "Folder",
    DataCell = new CheckBoxCell(1)
});
view.Columns.Add(new GridColumn {
    HeaderText = "Size",
    DataCell = new TextBoxCell(2)
});

TreeGridViewはDataStoreプロパティにインスタンスを設定し、そのインスタンスによって表示を定めます。 直接表示データを追加・変更するメソッドはありません。

続きは表(グリッド)としての列定義です。 GridColumn.DataCellには、表示形式に応じた設定を行います。 TextBoxCellCheckBoxCellのコンストラクタには、通常はintかstringを設定します。 intならDataStoreの各インスタンス(配列)のインデックスを示し、stringならインスタンス(オブジェクト)のプロパティ名を示します。

これでDataStoreの内容がDataGridViewで表示可能になりました。

サンプルコード全体

実際に動作するサンプルコード全体を示します。

using System.Collections.Generic;
using Eto.Forms;
using Eto.Drawing;

namespace EtoSample_TreeGridView
{
    public class MainForm : Form
    {
        TreeGridItemCollection MyList = new TreeGridItemCollection();
        TreeGridView view;

        public MainForm()
        {
            Title = "My Eto Form";
            ClientSize = new Size(300, 300);

            MyList.AddRange(PrepareItems());

            view = new TreeGridView { DataStore = MyList };
            view.Columns.Add(new GridColumn {
                HeaderText = "Name",
                DataCell = new TextBoxCell(0)
            });
            view.Columns.Add(new GridColumn {
                HeaderText = "Folder",
                DataCell = new CheckBoxCell(1)
            });
            view.Columns.Add(new GridColumn {
                HeaderText = "Size",
                DataCell = new TextBoxCell(2)
            });

            var name = new TextBox { };
            var add = new Button { Text = "Add" };
            add.Click += (s, e) => {
                var node = view.SelectedItem as TreeGridItem;
                if (node == null) return;
                node.Children.Add(MakeItem(name.Text, false, 12345));
            };

            Content = new StackLayout
            {
                Padding = 10,
                Items =
                {
                    new StackLayoutItem
                    {
                        Control = view,
                        Expand = true,
                        HorizontalAlignment = HorizontalAlignment.Stretch,
                    },
                    new StackLayoutItem
                    {
                        Control = name,
                        HorizontalAlignment = HorizontalAlignment.Stretch,
                    },
                    add,
                }
            };

            var quitCommand = new Command { MenuText = "Quit" };
            quitCommand.Executed += (sender, e) => Application.Instance.Quit();

            Menu = new MenuBar
            {
                Items =
                {
                    new ButtonMenuItem { Text = "&File", Items = { } },
                },
                QuitItem = quitCommand,
            };
        }

       #region TreeGridItem

        protected TreeGridItem MakeItem(string name, bool isFolder, int size)
        {
            return new TreeGridItem(
                new object[] { name, isFolder, size });
        }

        protected IEnumerable<TreeGridItem> PrepareItems()
        {
            var drivec = MakeItem("C:", true, 32);
            yield return drivec;

            var windows = MakeItem("windows", true, 32);
            drivec.Children.Add(windows);

            var coredll = MakeItem("core.dll", false, 98451206);
            windows.Children.Add(coredll);

            var miscdll = MakeItem("misc.dll", false, 45461876);
            windows.Children.Add(miscdll);

            var drivez = MakeItem("Z:", true, 32);
            yield return drivez;

            var folderBin = MakeItem("bin", true, 32);
            drivez.Children.Add(folderBin);

            var exe = MakeItem("MyApp.exe", false, 1683204);
            folderBin.Children.Add(exe);

            var dll = MakeItem("MyLib.dll", false, 489560);
            folderBin.Children.Add(dll);

            var folderDat = MakeItem("data", true, 32);
            drivez.Children.Add(folderDat);

            var dat1 = MakeItem("config.dat", false, 4096);
            folderDat.Children.Add(dat1);

            var folderPatch = MakeItem("patches", true, 32);
            folderDat.Children.Add(folderPatch);

            var patch1 = MakeItem("patch1.dat", false, 464795);
            folderPatch.Children.Add(patch1);

            var patch2 = MakeItem("patch2.dat", false, 50129);
            folderPatch.Children.Add(patch2);

            var patch3 = MakeItem("patch3.dat", false, 2204568);
            folderPatch.Children.Add(patch3);

            var dat2 = MakeItem("characters.dat", false, 8715625);
            folderDat.Children.Add(dat2);
        }

       #endregion
    }
}

起動すると、トップレベルの項目だけが表示されます。

f:id:mokake:20170131184102p:plain

閉じているツリーを開くこともできます。

f:id:mokake:20170131184120p:plain

TreeGridViewのフォルダ項目を選び、下のTextBoxに名前を入力して、下のAddボタンを押せば、TreeGridViewの選んだフォルダの下位に項目を追加できます。 ただし、Addボタンを押した直後は、表示は更新されません。

f:id:mokake:20170131184135p:plain

いったん親ノードを閉じて、再度開くと、追加した項目が表示されます。

f:id:mokake:20170131184149p:plain

追加直後にコードでExpandedプロパティにfalse, trueと続けて設定してみましたが、自動表示更新はできませんでした。

まとめ

TreeGridViewについて、データの準備からコントロールの設定まで含めてサンプルを示しました。

TreeViewやTreeGridViewは使いやすいとはいえませんが、いざとなればこういった要素も使えますということで。