Voyager
Voyager

Reputation: 539

Better design for Qt tree view with tree items requiring different context menus

I have a QTreeView object and the items of this tree view can hold one of three types of data, each require different handling. Therefore, for each of these types, I need a different context menu upon right clicking by the user. My tree object looks like this:

MyTreeView::MyTreeView(QWidget* parent): QTreeView(parent) {

    // some code

    m_init_item_model();

    // some code

    connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(make_context_menu(const QPoint&)));

}

void MyTreeView::m_init_item_model() {

    m_itemModel = new QStandardItemModel(this);
    m_itemModel->setHorizontalHeaderLabels(QStringList()
        << "Item Name" << "Item Type");

    this->setModel(m_itemModel);

    // some code

}

How I currently deal with different context menus is, every time an item is right clicked, I look at the name of the QStandardItem that falls under "Item Type" for that tree item, and I pass it through a filter of if()s and call different functions that create different context menus for different type of items. My approach is given below:

void MyTreeView::make_context_menu(const QPoint& pos) {

    m_currentPos = pos;
    QModelIndex firstIndex= this->indexAt(m_currentPos);

    if(firstIndex.isValid()) {
        if(firstIndex.parent() == QModelIndex()) { // if it's a top level item
            int row = firstIndex.row();
            m_currentIndex = firstIndex.sibling(row, 0);
            m_currentItem = m_itemModel->itemFromIndex(m_currentIndex);

            QModelIndex flTypeIndex = firstIndex.sibling(row, 1);
            QStandardItem* fileTypeItem = 
                m_itemModel->itemFromIndex(flTypeIndex);
            QString fileType = fileTypeItem->text();

            if(fileType == "Type A") m_make_typeA_context_menu();
            if(fileType == "Type B") m_make_typeB_context_menu();
        }
    }

}

This works, but I'm not sure if this is a rather good way of doing it. Now the thing is, as I add support for more types in my program, I'll have to get back to this and add more if()s. Well, I'll need different context menus for those new types, so maybe there's no other way; but I'm not an experienced programmer so I would like to see different approaches at this problem.

Upvotes: 1

Views: 355

Answers (1)

Voyager
Voyager

Reputation: 539

I came up with a not-so-terrible solution to this which is I think better than the approach I've mentioned in my original post. I wanted to share my solution as I thought it could help someone.

Firstly, I've created a base class for all tree items to inherit. The base class uses an enum to enumerate possible/registered tree item types.

// base_tree_item.h

#include <QStandardItem>

enum TREE_ITEM{

    TYPE1,
    TYPE2,
    TYPE3,
    // etc
    TYPE_COUNT

};

class BaseTreeItem: public QStandardItem {

public:
    BaseTreeItem() = delete;
    BaseTreeItem(TREE_ITEM itemType): m_itemType(itemType) {}

    const TREE_ITEM itemType() const { return m_itemType; }

    virtual ~BaseTreeItem() {}

private:
    TREE_ITEM m_itemType;

};

Now, every tree item will inherit this base class and have to construct the base with a type enum. For example,

// some_type1_item.h

#include "base_tree_item.h"

class SomeType1Item: public BaseTreeItem {

public:
    SomeType1Item(): BaseTreeItem(TREE_ITEM::TYPE1) { //stuff }

};

And now, to construct the custom context menu exclusive to the item type:

void TreeView::make_context_menu(const QPoint& pos) {

    m_currentPos = pos;
    QModelIndex firstIndex= this->indexAt(m_currentPos);

    if(firstIndex.isValid()) {
        int row = firstIndex.row();
        m_currentIndex = firstIndex.sibling(row, 0);
        m_currentItem = m_itemModel->itemFromIndex(m_currentIndex);

        BaseTreeItem* currentTreeItem = 
            static_cast<BaseTreeItem*>(m_currentItem);

        TREE_ITEM itemType = currentTreeItem->itemType();

        switch(itemType) {
            case TYPE1:
                make_type1_menu(pos);
                break;
            case TYPE2:
                make_type2_menu(pos);
                break;
            // etc
            default:
                break;
        }
    }

}

To me, this seemed like a tidier solution. To make this better, classes deriving from QMenu could be constructed to make menu objects for each type like MenuType1, MenuType2 etc and then they could be used as below to further tidy things up:

void TreeView::make_type1_menu(pos) {

// stuff

MenuType1* newMenu = new MenuType1(//args);
newMenu->exec(mapToGlobal(pos));

}

Upvotes: 1

Related Questions