Search code examples
c++qtdraggableqtableviewqtablewidget

Qt C++ Drag QHeaderView between tables


I want to copy the selected column of a QTableWidget to another one.

So I tried to make selected columns draggable by adding this code:

void makeDraggable(QTableWidget *table)
{
    table->setDragEnabled(true);
    table->setAcceptDrops(true);
    table->setSelectionBehavior(QAbstractItemView::SelectColumns);
}

Result I got:

image

But I want to drag a whole column (horizontal and vertical headers) by clicking on headers only, not on cells, and copy its data to another table including the header text.


Solution

  • Dragging between different tables inside one application can be done with reimplementing custom QHeaderView and QTableWidget. In my example I generate text with indecies of table and column for drag event. Custom header:

    #include <QHeaderView>
    
    class ITableManager;
    
    class DraggableHeaderView : public QHeaderView
    {
        Q_OBJECT
    public:
        explicit DraggableHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
    
        int tag() const;
        void setTag(const int tag);
        void setTableManager(ITableManager* manager);
    
    protected:
        void mouseMoveEvent(QMouseEvent *e);
    
        void dragEnterEvent(QDragEnterEvent *event);
        void dragMoveEvent(QDragMoveEvent *event);
        void dropEvent(QDropEvent *event);
    
    signals:
    
    public slots:
    
    private:
        int m_tag;  //internal index of table
        ITableManager *m_tableManager;  //manager will convert table index into pointer
    };
    

    Custom header cpp

    #include <QMouseEvent>
    #include <QDrag>
    #include <QMimeData>
    #include <QDebug>
    #include <QTableWidget>
    
    #include <ITableManager.h>
    
    DraggableHeaderView::DraggableHeaderView(Qt::Orientation orientation, QWidget *parent) :
        QHeaderView(orientation, parent)
    {
        m_tag = 0;
        m_tableManager = 0;
        setAcceptDrops(true);
    }
    
    void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
    {
        if (e->buttons() & Qt::LeftButton)
        {
            int index = logicalIndexAt(e->pos());
            QDrag *drag = new QDrag(this);
            QMimeData *mimeData = new QMimeData;
            //custom drag text with indecies inside
            QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
                    ";Index:" + QString::number(index);
            mimeData->setText(mimeTxt);
            drag->setMimeData(mimeData);
            Qt::DropAction dropAction = drag->exec();
        }
    }
    
    int DraggableHeaderView::tag() const
    {
        return m_tag;
    }
    
    void DraggableHeaderView::setTag(const int tag)
    {
        m_tag = tag;
    }
    
    void DraggableHeaderView::dragEnterEvent(QDragEnterEvent *event)
    {
        if (!m_tableManager)
        {
            event->ignore();
            return;
        }
    
        QString dragText = event->mimeData()->text();
        int index = dragText.indexOf("MoveHeader;");
        if (index == 0)
        {
            event->accept();
        }
        else
        {
            event->ignore();
        }
    }
    
    void DraggableHeaderView::dropEvent(QDropEvent *event)
    {
        if (!m_tableManager)
        {
            event->ignore();
            return;
        }
    
        QStringList dragText = event->mimeData()->text().split(';');
        if (dragText.count() < 3 || dragText.at(0) != "MoveHeader")
        {
            event->ignore();
            return;
        }
    
        int tableIndex = dragText.at(1).mid(6).toInt();//6 - length 'Table:'
        QTableWidget* tableSrc = m_tableManager->getTableFromIndex(tableIndex);
        if (!tableSrc)
        {
            event->ignore();
            return;
        }
    
        //dst table as parent for header view
        QTableWidget *tableDst = qobject_cast<QTableWidget*> (this->parentWidget());
        if (!tableDst)
        {
            event->ignore();
            return;
        }
    
        //move column: modify for your needs
        //now moves only items text
        int columnIndex = logicalIndexAt(event->pos());
        int srcColumnIndex = dragText.at(2).mid(6).toInt(); //6 - length of 'Index:'
        tableDst->insertColumn(columnIndex);
        for (int iRow = 0; iRow < tableDst->rowCount() && iRow < tableSrc->rowCount(); ++iRow)
        {
            if (tableSrc->item(iRow, srcColumnIndex))
            {
                tableDst->setItem(iRow, columnIndex,
                                  new QTableWidgetItem(tableSrc->item(iRow, srcColumnIndex)->text()));
            }
            else
            {
                tableDst->setItem(iRow, columnIndex, new QTableWidgetItem());
            }
        }
        tableSrc->removeColumn(srcColumnIndex);
    }
    
    void DraggableHeaderView::setTableManager(ITableManager *manager)
    {
        m_tableManager = manager;
    }
    

    Now create custom QTableWidget with DraggableHeaderView inside

    class CustomTableWidget : public QTableWidget
    {
        Q_OBJECT
    public:
        explicit CustomTableWidget(QWidget *parent = 0);
    
        void setTag(const int tag);
        void setTableManager(ITableManager* manager);
    };
    
    CustomTableWidget::CustomTableWidget(QWidget *parent) :
        QTableWidget(parent)
    {
        DraggableHeaderView *headerView = new DraggableHeaderView(Qt::Horizontal, this);
    
        setHorizontalHeader(headerView);
    
        setAcceptDrops(true);
    }
    
    void CustomTableWidget::setTag(const int tag)
    {
        DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
        if (header)
        {
            header->setTag(tag);
        }
    }
    
    void CustomTableWidget::setTableManager(ITableManager *manager)
    {
        DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
        if (header)
        {
            header->setTableManager(manager);
        }
    }
    

    For converting table index to pointer I use ITableManager

    class ITableManager
    {
    public:
        virtual QTableWidget* getTableFromIndex(const int index) = 0;
    };
    

    And implement it in QMainWindow

    class MainWindow : public QMainWindow, ITableManager
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
        QTableWidget* getTableFromIndex(const int index);
    }
    
    QTableWidget * MainWindow::getTableFromIndex(const int index)
    {
        switch (index)
        {
        case 1:
            return ui->tableWidget;
        case 2:
            return ui->tableWidget_2;
        default:
            return nullptr;
        }
    }
    

    Dont forget setup tags (indecies) and table manager for tables (in main window constructor)

    ui->tableWidget->setTag(1);
    ui->tableWidget_2->setTag(2);
    ui->tableWidget->setTableManager(this);
    ui->tableWidget_2->setTableManager(this);
    

    EDIT: If you want change custom pixmap for dragging just set QDrag::setPixmap

    void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
    {
        if (e->buttons() & Qt::LeftButton)
        {
            int index = logicalIndexAt(e->pos());
            QDrag *drag = new QDrag(this);
            QMimeData *mimeData = new QMimeData;
            QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
                    ";Index:" + QString::number(index);
            mimeData->setText(mimeTxt);
            drag->setMimeData(mimeData);
            drag->setPixmap(pixmapForDrag(index));
            Qt::DropAction dropAction = drag->exec();
        }
    }
    

    And method for taking pixmap of column can be like this

    QPixmap DraggableHeaderView::pixmapForDrag(const int columnIndex) const
    {
        QTableWidget *table = qobject_cast<QTableWidget*> (this->parentWidget());
        if (!table)
        {
            return QPixmap();
        }
    
        //image for first 5 row
        int height = table->horizontalHeader()->height();
        for (int iRow = 0; iRow < 5 && iRow < table->rowCount(); ++iRow)
        {
            height += table->rowHeight(iRow);
        }
    
        //clip maximum size
        if (height > 200)
        {
            height = 200;
        }
    
        QRect rect(table->columnViewportPosition(columnIndex) + table->verticalHeader()->width(),
                     table->rowViewportPosition(0),
                     table->columnWidth(columnIndex),
                     height);
        QPixmap pixmap(rect.size());
        table->render(&pixmap, QPoint(), QRegion(rect));
        return pixmap;
    }