【C++/MFC】ツリービューの基本的な使い方

 ツリービュー(CTreeCtrlクラス)の基本的な使い方について説明します。

開発環境Visual Studio 2022
言語C++
フレームワークMFC
動作確認Windows 11
Windows 10
Windows 7
ツリービュー

前提条件

  • ツールボックスより、「Tree Control」をダイアログに貼ります。
  • ダイアログクラスにメンバー変数を追加します。
  • 
        CTreeCtrl m_tree;
    
    
  • DoDataExchangeに、IDとメンバー変数の結びつきを設定します。
  • 
        DDX_Control(pDX, IDC_TREE1, m_tree);
    
    

プロパティ

  • アイテムの親子関係を線で結ぶには、「行あり」「ルートの行」をTrueに設定します。(「行」は「線」の誤訳と思われます。原語はlines)
  • 線を表示
  • 親アイテムの横にプラス/マイナスボタンを表示するには、「ボタンあり」「ルートの行」をTrueに設定します。
  • ボタンを表示
  • 線とボタンの両方を表示するには、「行あり」「ボタンあり」「ルートの行」をTrueに設定します。
  • 線とボタンを表示

アイテムの追加

 各アイテムは、一意のハンドルにて識別されます。また、テキストの文字コードは、UTF-16です。


    // ルート
    HTREEITEM ハンドル = m_tree.InsertItem(テキスト);

    // ルート以外
    HTREEITEM ハンドル = m_tree.InsertItem(テキスト, 親アイテムのハンドル);

    // 例
    HTREEITEM hItem_1 = m_tree.InsertItem(L"東京都");
    HTREEITEM hItem_2 = m_tree.InsertItem(L"新宿区", hItem_1);
    HTREEITEM hItem_3 = m_tree.InsertItem(L"西新宿", hItem_2);

 上記の方法では、親アイテムに対して複数の子アイテムを追加する際、末尾に追加されます。先頭に挿入するには、3番目の引数にTVI_FIRSTを設定します。また、ソートしながら追加したい場合は、3番目の引数にTVI_SORTを設定します。ただし、ソートされるのはTVI_SORTを設定したアイテムのみです。既にTVI_LAST(デフォルト値)/TVI_FIRSTにて追加されたアイテムは、ソートされません。


    // 「追加方法」は、TVI_LAST、TVI_FIRST、TVI_SORTのいずれか

    // ルート
    HTREEITEM ハンドル = m_tree.InsertItem(テキスト, TVI_ROOT, 追加方法);

    // ルート以外
    HTREEITEM ハンドル = m_tree.InsertItem(テキスト, 親アイテムのハンドル, 追加方法);

 追加後に子アイテムをまとめてソートするには、SortChildrenを使用します。この場合、TVI_LAST/TVI_FIRST/TVI_SORTの指定は関係ありません。ただし、ソート対象は、親アイテム直下の子アイテムのみです(孫アイテムは対象外)。


    // ソート対象はルート
    m_tree.SortChildren(TVI_ROOT);

    // ソート対象はルート以外
    m_tree.SortChildren(親アイテムのハンドル);

選択したアイテムの取得

(1) ダイアログクラスにメンバー関数を追加します。(関数名は任意)


    void OnTreeSelChanged(NMHDR *, LRESULT *);

(2) メッセージマップに、以下を追加します。


    ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, OnTreeSelChanged)

(3) メンバー関数を記述します。ツリービューにメッセージが飛ぶ(マウスや矢印キーでアイテム選択)度に、この関数が呼ばれます。


void CXxxxDlg::OnTreeSelChanged(NMHDR *, LRESULT *)
{
    // ハンドルの取得
    HTREEITEM hItem = m_tree.GetSelectedItem();

    if (hItem != nullptr)
    {
        // テキストの取得
        std::wstring name = m_tree.GetItemText(hItem);

        // 階層の取得
        int level = 0;
        HTREEITEM hTemp = hItem;

        do
        {
            level++;

            // 親アイテムのハンドルを取得。引数がルートのハンドルの場合、nullptrが返る
            hTemp = m_tree.GetParentItem(hTemp);
        }
        while (hTemp != nullptr);

        // hItem    ハンドル
        // name     テキスト
        // level    階層(1開始)
    }
}

アイテムの削除


    // ハンドルに該当するアイテムを削除。子アイテムが存在する場合は、同時に削除される
    m_tree.DeleteItem(ハンドル);

    // 全削除
    m_tree.DeleteAllItems();

サンプルソース

 指定したフォルダーの配下を再帰的に検索して、全フォルダー/ファイル名をツリービューに追加します。<filesystem>を使用するので、プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++17標準(/std:c++17)」以降を選択します。


#include <filesystem>


    // 呼び出し側

    std::filesystem::path p = L"フォルダーのフルパス、あるいはカレントフォルダーからの相対パス";

    // ルートに設定
    HTREEITEM hItem = m_tree.InsertItem(p.filename().c_str());

    recursive(hItem, p);


// ダイアログクラスに再帰検索用のメンバー関数を追加(関数名は任意)

void CXxxxDlg::recursive(const HTREEITEM &hItem, const std::filesystem::path &p)
{
    for (const std::filesystem::directory_entry &i : std::filesystem::directory_iterator(p))
    {
        if (i.is_directory())
        {
            // フォルダー
            HTREEITEM hNewItem = m_tree.InsertItem(i.path().filename().c_str(), hItem);

            // 再帰呼び出し
            recursive(hNewItem, i.path());
        }
        else if (i.is_regular_file())
        {
            // ファイル
            m_tree.InsertItem(i.path().filename().c_str(), hItem);
        }
    }
}

リファレンス

CTreeCtrl Class
https://learn.microsoft.com/en-us/cpp/mfc/reference/ctreectrl-class