スポンサーリンク

wxListBoxで自作ツリー構造を表示する

tree.hpp

#ifndef TREE_H
#define TREE_H

#endif //TREE_H

#include <algorithm>
#include <memory>

using namespace std::literals::string_literals;

// ノードを継承したクラスに asString() メンバ関数があるかを判定するコンセプト
template<typename T>
concept HasAsString = requires(T a) {
    { a.asString() } -> std::same_as<std::string>;
};

// ノードの基底クラス
class NodeBase {
protected:
public:
    std::shared_ptr<NodeBase> parent;

    virtual ~NodeBase() = default;
    virtual std::string asString() const = 0;  // 必ず実装させる仮想関数
    virtual std::shared_ptr<NodeBase> getParent() const { return parent; }
    virtual bool isFolder() const = 0;
};

// フォルダクラス
template <HasAsString T>
class Folder : public NodeBase, public std::enable_shared_from_this < Folder<T> > {
    std::vector<std::shared_ptr<NodeBase>> children;  // 子ノードは NodeBase 型を保持

    T value;
    bool isOpen;
public:
    Folder(const T& data) : value(data), isOpen(false) {}

    // 子ノードを追加するメソッド
    void add(const std::shared_ptr<NodeBase>& node) {
        children.push_back(node);
        node->parent = std::dynamic_pointer_cast<NodeBase>(this->shared_from_this());
    }

    const T& getName() const {
        return value;
    }

    std::string asString() const override {
        return value.asString();
    }

    const std::vector<std::shared_ptr<NodeBase>>& getChildren() const {
        return children;
    }
    std::vector<std::shared_ptr<NodeBase>>& getChildren() {
        return children;
    }

    bool getIsOpen() const {
        return isOpen;
    }

    void setIsOpen(bool open) {
        isOpen = open;
    }

    bool isFolder() const override {
		return true;
	}


};

// アイテムクラス
// フォルダに入れるデータを保持するクラス
template <HasAsString T>
class Item : public NodeBase, public std::enable_shared_from_this < Item<T> > {
    T value;
public:
    Item(const T& data) : value(data) {}

    std::string asString() const override {
        return value.asString();
    }

    bool isFolder() const override {
        return false;
    }
};

/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////

// 全てのノードの数をカウントする関数
template <HasAsString FT>
int getAllNodeCount(const std::shared_ptr<NodeBase>& node) {
    int count = 1;  // 自分自身をカウント

    // フォルダの場合は、子ノードも再帰的にカウント
    if (auto folder = std::dynamic_pointer_cast<Folder<FT>>(node)) {
        for (const auto& child : folder->getChildren()) {
            count += getAllNodeCount<FT>(child);
        }
    }

    return count;
}



// 開いているノードの数をカウントする関数
template <HasAsString FT>
int getOpenNodeCount(const std::shared_ptr<NodeBase>& node) {
    int count = 1;  // 自分自身をカウント

    // フォルダの場合、開いているかどうかを確認
    if (auto folder = std::dynamic_pointer_cast<Folder<FT>>(node)) {
        if (folder->getIsOpen()) {
            // 開いている場合、子ノードも再帰的にカウント
            for (const auto& child : folder->getChildren()) {
                count += getOpenNodeCount<FT>(child);
            }
        }
    }

    return count;
}

////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////

// 指定した index のノードを取得する関数の補助関数
template <HasAsString FT>
std::shared_ptr<NodeBase> getNodeHelper(const std::shared_ptr<NodeBase>& node, int& currentIndex, int targetIndex, bool open = false) {
    // 現在のノードをカウント
    if (currentIndex == targetIndex) {
        return node;
    }
    currentIndex++;

    // フォルダの場合は、開閉状態を確認し、再帰的に子ノードを探索
    if (auto folder = std::dynamic_pointer_cast<Folder<FT>>(node)) {
        if (open || folder->getIsOpen()) {
            for (const auto& child : folder->getChildren()) {
                auto result = getNodeHelper<FT>(child, currentIndex, targetIndex, open);
                if (result) {
                    return result;
                }
            }
        }
    }

    return nullptr; // 見つからなかった場合
}

// 指定した index のノードを取得する関数
// ここでの index は、開いているノードのみでカウントする
template <HasAsString FT>
std::shared_ptr<NodeBase> getNodeOnlyOpen(const std::shared_ptr<NodeBase>& root, int targetIndex) {
    int currentIndex = 0; // ノードのカウント用
    return getNodeHelper<FT>(root, currentIndex, targetIndex, false);
}


////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////




// ノードを削除する関数
template <HasAsString FT>
void deleteNode(std::shared_ptr<NodeBase> node) {
    if (auto parent = node->getParent()) {
        if (auto parentFolder = std::dynamic_pointer_cast<Folder<FT>>(parent)) {
            auto& siblings = parentFolder->getChildren();
            siblings.erase(std::remove(siblings.begin(), siblings.end(), node), siblings.end());
        }
    }
}

// 指定したノードの管理情報を取得する関数
// 戻り値 1 親ノード
// 戻り値 2 posで指定したノードを持つ親ノードの、子ノードリストのインデクス
template <HasAsString FT>
std::pair< std::shared_ptr<NodeBase>, int> GetNodeListPos(std::shared_ptr<NodeBase> root, int pos) {

    // posの位置の現在のノードを取得
    auto now_node = getNodeOnlyOpen<FT>(root, pos);

    // now_nodeの親ノードを取得
    auto parent_node = now_node->getParent();

    // now_nodeの親ノードの子ノードリストを取得
    auto parent_folder = std::dynamic_pointer_cast<Folder<FT>>(parent_node);
    auto& siblings = parent_folder->getChildren();

    // now_nodeの親ノードの子ノードリストのインデクスを取得
    auto it = std::find(siblings.begin(), siblings.end(), now_node);
    int index = std::distance(siblings.begin(), it);


    return { parent_node,index };

}

template <HasAsString FT, HasAsString IT>
void addNodeUnder(std::shared_ptr<NodeBase> root, std::shared_ptr<NodeBase> node) {
    // nodeをrootの最後に追加
    auto folder = std::dynamic_pointer_cast<Folder<FT>>(root);
    folder->getChildren().push_back(node);
    node->parent = root;

}

template <HasAsString FT, HasAsString IT>
void addNodeHere(std::shared_ptr<NodeBase> root, std::shared_ptr<NodeBase> node, int pos) {
    // ノードを追加する関数
    // 挿入位置は番号posで指定する
    // 新しく追加したアイテムが必ずpos番目になる
    // 必ず、追加前のposのノードと同じ階層に追加する

    // 番号posは開いているノードのみでカウントする
    // 例えばfolder21がノードを持っていても、folder21は開いていないのでその中のノードはカウントしない
    // 1 - folder1
    // 2   - folder2
    // 3      item21
    // 4      item22
    // 5      + folder21
    // 6   - folder3
    // 7      item31
    // 8      item32
    // 9      item33

    // getNode(folder1,3) == item21
    // この時、
    // 例1 addNode(NewFolder,3) で
    // 2   - folder2
    // 3      + NewFolder
    // 4      item21
    // 5      item22


    auto childinfo = GetNodeListPos<FT>(root, pos);
    std::shared_ptr<NodeBase> childlist = childinfo.first;
    auto index = childinfo.second;

    // nodeをchildlistのindex番に追加
    auto folder = std::dynamic_pointer_cast<Folder<FT>>(childlist);
    folder->getChildren().insert(folder->getChildren().begin() + index, node);
    node->parent = childlist;

}

////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////



////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////

struct TreeSliceItem {
    int pos;
    std::shared_ptr<NodeBase> node;
    int hierarchy;
};
template <HasAsString FT>
void GetTreeSliceTraverse(const std::shared_ptr<NodeBase>& node, int hierarchy, int& currentIndex, int from, int to, std::vector<TreeSliceItem>& slice) {
    if (currentIndex >= from && currentIndex <= to) {
        slice.push_back({ currentIndex, node, hierarchy });
    }
    currentIndex++;

    if (auto folder = std::dynamic_pointer_cast<Folder<FT>>(node)) {
        if (folder->getIsOpen()) {
            for (const auto& child : folder->getChildren()) {
                GetTreeSliceTraverse<FT>(child, hierarchy + 1, currentIndex, from, to, slice);
            }
        }
    }
}

// ツリーから一部を取り出す関数
template <HasAsString FT>
std::vector< TreeSliceItem> GetTreeSlice(std::shared_ptr<NodeBase> root, int from, int to) {
    // 例えば、以下の状態の時、
    // 1 - folder1
    // 2   - folder2
    // 3      item21
    // 4      item22
    // 5      + folder21
    // 6   - folder3
    // 7      item31
    // 8      item32
    // 9      item33

    // GetTreeSlice(folder1,3,7) で
    // 3      item21
    // 4      item22
    // 5      + folder21
    // 6   - folder3
    // 7      item31
    // を取得

    std::vector<TreeSliceItem> slice;
    int currentIndex = 0;
    int currentHierarchy = 0;

    GetTreeSliceTraverse<FT>(root, currentHierarchy, currentIndex, from, to, slice);

    return slice;

}

TreeSlice.hpp

ツリーの一部を取得するクラスを取得するクラス。

#include "tree.hpp"
 
// ツリーの一部を取り出すクラス
template
<HasAsString FT, HasAsString IT> class TreeSlice { std::shared_ptr<Folder<FT>> _root; std::vector< TreeSliceItem> _items; int _size; int _pos; public: void SetTree(const std::shared_ptr<Folder<FT>>& r) { _root = r; } void SetSliceInfo(int size,int pos) { _size = size; _pos = pos; } void GetSlice() { _items = GetTreeSlice<FT>(_root, _pos, _pos + _size - 1); } const std::vector< TreeSliceItem>& GetItems() const { return _items; } };
////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////
 
struct FolderData {
    std::string name;
    std::string asString() const {
        return name;
    }

    // コンストラクタ
    FolderData(const std::string& n) : name(n) {}

};
      
struct ItemData {
    std::string name;
    std::string asString() const {
        return name;
    }

    // コンストラクタ
    ItemData(const std::string& n) : name(n) {}

};

main.cpp

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include <wx/gdicmn.h> // wxPointに必要
#include <wx/frame.h>  // wxFrameに必要


// wxBufferedPaintDC のinclude
#include <wx/dcbuffer.h>

/////////////////////////////////////
/////////////////////////////////////
/////////////////////////////////////

#include <string>
#include <memory>
#include <unordered_map>

#include "tree_data.hpp"

// データを作成
std::shared_ptr<Folder<FolderData>> create_data() {
    // ツリー構造を作成
    auto root = std::make_shared<Folder<FolderData>>(FolderData{ "folder1" });

    for (size_t i = 0; i < 10; i++) {
        auto folder = std::make_shared<Folder<FolderData>>(FolderData{ "folder" + std::to_string(i) });
        root->add(folder);
        for (size_t j = 0; j < 10; j++) {
            auto item = std::make_shared<Item<ItemData>>(ItemData{ "item" + std::to_string(i) + std::to_string(j) });
            folder->add(item);
        }
    }
    root->setIsOpen(true);

    return root;
}
      
// リストボックスが一度に表示可能なアイテム数を取得
int GetVisibleItemCount(wxListBox * listBox)
{
    // リストボックスのDCを使って一行の高さを取得し
    // リストボックスのクライアントサイズから表示可能なアイテム数を計算する


    // リストボックスのクライアントサイズを取得
    wxSize clientSize = listBox->GetClientSize();

    // wxClientDCを使用してデフォルトのアイテム高さを取得
    wxClientDC dc(listBox);
    wxCoord textWidth, textHeight;
    // 指定した文字列の幅と高さを取得
    dc.GetTextExtent("TEXT", &textWidth, &textHeight);

    // 表示可能なアイテム数を計算
    int visibleItemCount = clientSize.GetHeight() / textHeight;

    return visibleItemCount;
}

// スクロールバー付きリスト
class ExList : public wxPanel {

    int itemCount;// アイテム数
    int visibleItemCount;// 一度に表示できるアイテム数

    wxBoxSizer* mainSizer;
    wxScrollBar* scrollBar;

    // リストコントロール
    wxListBox* listBox;

    TreeSlice<FolderData, ItemData> _treeSlice;// リストの一部を取得
    std::shared_ptr<Folder<FolderData>> _root;// ツリーのルート

    std::vector< TreeSliceItem> _now_valid_items;// 表示する範囲のツリーのアイテム

    void PostCreate() {


        _root = create_data();// アイテム作成
        _treeSlice.SetTree(_root);// ツリーの一部を切り出す準備

        visibleItemCount = GetVisibleItemCount(listBox);// 表示可能なアイテム個数を更新

        disp();

    }

public:
    // コンストラクタ
    ExList(wxWindow* parent) : wxPanel(parent, wxID_ANY) {
        // メインSizer
        mainSizer = new wxBoxSizer(wxHORIZONTAL);
        this->SetSizer(mainSizer);

        // リストコントロール
        listBox = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE);
        mainSizer->Add(listBox, 1, wxEXPAND | wxALL, 5);

        // スクロールバー
        scrollBar = new wxScrollBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL);
        mainSizer->Add(scrollBar, 0, wxEXPAND | wxALL, 5);


        this->Layout();
        CallAfter(&ExList::PostCreate);

        // スクロールバーのイベント
        scrollBar->Bind(wxEVT_SCROLL_PAGEUP, &ExList::OnScrollPageUp, this);
        scrollBar->Bind(wxEVT_SCROLL_PAGEDOWN, &ExList::OnScrollPageDown, this);
        scrollBar->Bind(wxEVT_SCROLL_THUMBTRACK, &ExList::OnScrollThumbTrack, this);
        scrollBar->Bind(wxEVT_SCROLL_LINEUP, &ExList::OnScrollUp, this);
        scrollBar->Bind(wxEVT_SCROLL_LINEDOWN, &ExList::OnScrollDown, this);

        // リストボックスのアイテムをクリックした時のイベント
        listBox->Bind(wxEVT_LISTBOX, &ExList::OnListBox, this);

        // サイズイベント
        this->Bind(wxEVT_SIZE, &ExList::OnSize, this);

    }

    void disp() {
        listBox->Clear();

        int offset = scrollBar->GetThumbPosition();

        itemCount = getOpenNodeCount<FolderData>(_root);
        // スクロールバーの範囲とサムサイズを設定
        scrollBar->SetScrollbar(offset, visibleItemCount, itemCount, 5, true);




        _treeSlice.SetSliceInfo(visibleItemCount, offset);
        _treeSlice.GetSlice();
        _now_valid_items = _treeSlice.GetItems();


        // デバッグにposを表示
        std::string debugMessage = "pos: " + std::to_string(offset) + "\n";
        OutputDebugStringA(debugMessage.c_str());
        
        /////////////////////////////////////////////
        // 現在の表示範囲のアイテムを追加
        for (int i = 0; i < _now_valid_items.size(); i++) {


            std::string itemstr;
            // itemstr にアイテムの文字列を設定
            // hierarchy個のスペースを追加
            for (int j = 0; j < _now_valid_items[i].hierarchy; j++) {
                itemstr += "   ";
            }
            auto node = _now_valid_items[i].node;
            if (node->isFolder()) {
                std::dynamic_pointer_cast<Folder<FolderData>>(node)->getIsOpen() ? itemstr += "- " : itemstr += "+ ";
            }
            else {
                itemstr += "  ";
            }

            itemstr += node->asString();
            listBox->Append(itemstr);// アイテムをリストに追加

        }
    }
    
    // スクロールバーのつまみ以外の部分をクリックしたとき
    void OnScrollPageUp(wxScrollEvent& event) {

        disp();
    }

    void OnScrollPageDown(wxScrollEvent& event) {

        disp();
    }

    void OnScrollThumbTrack(wxScrollEvent& event) {
        
        disp();
    }

    // 下方向クリック
    void OnScrollDown(wxScrollEvent& event) {

        disp();
    }
    // 上方向クリック
    void OnScrollUp(wxScrollEvent& event) {

        disp();
    }

    // リストボックスのアイテムをクリックした時のイベント
    void OnListBox(wxCommandEvent& event) {
        // 選択されたアイテムのインデックスを取得
        int index = listBox->GetSelection();
        if (index == wxNOT_FOUND) {
            return;
        }

        auto node = _now_valid_items[index].node;
        
        std::string debugMessage = "Selected: " + node->asString() + "\n";
        OutputDebugStringA(debugMessage.c_str());

        auto folder = std::dynamic_pointer_cast<Folder<FolderData>>(node);

        if (folder) {
            folder->setIsOpen(!folder->getIsOpen());// クリックしたフォルダをオープン
            disp();
        }

    }



    void OnSize(wxSizeEvent& event) {       

        visibleItemCount = GetVisibleItemCount(listBox);// 表示可能なアイテム個数を更新

        disp();

        // イベントを処理済みにする
        event.Skip();
    }


};
// ウィンドウ作成
class MyFrame : public wxFrame
{
    wxBoxSizer* mainSizer;

    ExList* exList; // スクロールバー付きリスト

public:

    void PostCreate() {


        this->Layout(); // レイアウトの更新
    }


    MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
        : wxFrame(NULL, wxID_ANY, title, pos, size)
    
    {

        // メインSizer
        mainSizer = new wxBoxSizer(wxHORIZONTAL);
        this->SetSizer(mainSizer);


        // リストコントロール
        exList = new ExList(this);
        mainSizer->Add(exList, 1, wxEXPAND | wxALL, 5);


        // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行
        // コンストラクタはウィンドウ生成イベント扱い
        CallAfter(&MyFrame::PostCreate);


    }

private:
};

/////////////////////////////////////
/////////////////////////////////////
/////////////////////////////////////

// wxWidgetsのアプリケーション作成
class MyApp : public wxApp {
public:

    virtual bool OnInit() {
        MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340));
        frame->Show(true);

        return true;
    }

};

/////////////////////////////////////
/////////////////////////////////////
/////////////////////////////////////

// WinMainをマクロで定義
wxIMPLEMENT_APP(MyApp);

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: