QTableWidget Copy and Move single selection

In work I needed a function which will be able to copy or move the data around QTableWidget, unfortunetty for me QTableWidget doesn't have an event which will tell you which cell was dragged, and where it was dropped. After couple hours of Googling I still wasn't able to find what I was looking for, so I begun to think how can I do it my way.


First of All, my approach might not be the correct one, so if you use it, be careful and don't hold me responsible if the application crashes.

 

In the begining I tried overriding dropMimeData() event, since it gets the row and collumn where the item was dropped, it works fine until you want to do the MOVE, the event is only generated when a COPY action is called. However, any of the drop actions will call an even dropEvent(), after testing it, I can confirm that for COPY and MOVE action it is being called.

 

So first of all you want to override the dropEvent() function, I hope everyone know how to do it. If not, make a class which extends QTableWidget, and in header file add

protected:    
    void dropEvent(QDropEvent *event);

Now it's time to alter the dropEvent() function in your CPP file. Remember that in these kinds of overrides you most likely will want to accept the event or should I say pass it to the inherited class.

QTableWidget::dropEvent(event);

But before we accept the event, we have to save the coordinates from where the item was moved, we do that just by saving the current items row and column. If you will do it afterwards, there won't be any QTableWidget item after accepting the event and your application will crash. While writing this statement, I just realized that you can use functions currentRow() and currentColumn() and it will work after accepting the event as well. But that's not the case, since I did it this way, I will continue describing it.

So how can we detected what type of ACTION(copy, move) was done by user? Well QDropEvent has return type dropAction() for MOVE the value will be 2 and for copying it will be 1. Note: I noticed an interesting behaviour, when you accept the event, the dropAction() return value changes and for me it always pointed to copy action. To overcome this, just save the dropAction() value in somekind of variable.

If you understood all I said, your function now should look like this:

void XTableWidget::dropEvent(QDropEvent *event)
{
    QPoint old_coordinates = QPoint(-1,-1);
    int dropAction = event->dropAction(); 
    if(currentItem() != NULL)
    {
        old_coordinates = QPoint( currentItem()->row(), currentItem()->column() );
    }
 
    QTableWidget::dropEvent(event);
}

By now you have the old coordinates and the ACTION. So next step would be to get the drop coordinates. I do it after accepting the event, because we need the item to be there, which I'll explain later. Anyway, it's pretty easy, just like in QGraphicsScene, the QTableWidget has itemAt()function, which will return QTableWidgetItem, which are under those coordinates, in our case those will be the drop coordinates. Lucky for us, the QDropEvent holds those kind of coordinates.

Basically, to get the "Destinations" row and column, you just have to do the follwing:

this->itemAt( event->pos().x(), event->pos().y() )->row();
this->itemAt( event->pos().x(), event->pos().y()->column();

Now you should understand why we had to get the destination coordinates after accepting the event, if not, it's because we need to access the item, if it's not going to be there and we ask for a row or column, the application will simply crash.

That's basically it, now you just have to emit a SIGNAL with your data and interrpret it there or somwhere else, in my case I just emitted a SIGNAL containing the source and destination coordinates + the action.

In case you don't know how to emit and make signal, in your header file add:

signals:
    void itemDropped(int original_row, int original_column, int new_row, int new_column, int dropAction);

And the end function will look something like this:
 

void XTableWidget::dropEvent(QDropEvent *event)
{
    QPoint old_coordinates = QPoint(-1,-1);
    int dropAction = event->dropAction();
    if(currentItem() != NULL) //Check if user is not accessing empty cell
    {
        old_coordinates = QPoint( currentItem()->row(), currentItem()->column() );
    }
 
    QTableWidget::dropEvent(event);
    qDebug() << "Detected drop event...";
    if( this->itemAt( event->pos().x(), event->pos().y() ) != NULL && old_coordinates != QPoint(-1, -1))
    {
        qDebug() << "Drop Event Accepted.";
        qDebug() << "Source: " << old_coordinates.x() << old_coordinates.y()
                 << "Destinition: " << this->itemAt( event->pos().x(), event->pos().y() )->row() 
                 << this->itemAt( event->pos().x(), event->pos().y() )->column()
                 << "Type: " << dropAction;
 
        emit itemDropped( old_coordinates.x(), old_coordinates.y(),
                          this->itemAt( event->pos().x(), event->pos().y() )->row(),
                          this->itemAt( event->pos().x(), event->pos().y() )->column(),
                          dropAction);
 
    }
}

 




ADVERTISEMENT