Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QTableView and custom model: Question about dropMimeData()
QtWS25 Last Chance

QTableView and custom model: Question about dropMimeData()

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 2 Posters 1.3k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • R Offline
    R Offline
    Robert Hairgrove
    wrote on last edited by Robert Hairgrove
    #1

    My custom model inherits QAbstractTableModel; the view is a QTableView. I have specified a custom MIME type which returns the value of the cell (an integer) concatenated with the row (separated by ';') from which the drag originated. The reason being that I want to only accept drags from one column to another IF they are in the same row.

    Everything but dropping seems to work, namely because my overwritten dropMimeData() function always receives row and column as -1 instead of the row and column of the item on which is being dropped.

    Do I need to derive a special class from QTableView as well to prevent this from happening? I have set the following properties for the table view in QtDesigner:

    editTriggers: NoEditTriggers
    tabKeyNavigation: yes
    showDropIndicator: yes
    dragEnabled: yes
    dragDropOverwriteMode: yes
    dragDropMode: InternalMove
    defaultDropAction: MoveAction
    ...
    selectionMode: singleSelection
    selectionBehavior: selectItems
    ... etc.
    

    Flags returned from my model are: Qt::ItemIsEnabled | Qt::ItemIsSelectable and additionally Qt::ItemIsDragEnabled or Qt::ItemIsDropEnabled depending on the column.

    I might also add that I did not re-implement canDropMimeData() because of the same problem (i.e., row and column), but it seems that perhaps it is not needed here?

    It seems difficult to put together even a very simple test program to illustrate this, but I am working on this, too. I might add that I am working with Qt version 5.11.1 which was a fairly recent stable version; would an update help here?

    1 Reply Last reply
    0
    • R Offline
      R Offline
      Robert Hairgrove
      wrote on last edited by
      #2

      It's a little long, but here is the test case. It shows a table view with five columns and four rows. Drag and drop is enabled for the first row only, and from column 5 to column 4. However, every time the function dropMimeData() is called, the arguments row and column are set to -1 which makes it impossible to know which item is meant as drop target.

      Dragging is initiated properly, and the focus rectangle around the item in column 4 is shown as well as the drag pixmap. But when I release the mouse button, I do not receive the proper coordinates.

      Should I file a bug report? I am using Qt version 5.11.1 as noted in a previous post in this thread; OS is Linux Ubuntu 18.04.2 LTS.

      File: TestDragNDrop.pro:

      QT       += core gui widgets
      TARGET = TestDragNDrop
      TEMPLATE = app
      SOURCES += main.cpp TestModel.cpp
      HEADERS += TestModel.hpp
      

      File: TestModel.hpp:

      #ifndef TESTMODEL_HPP
      #define TESTMODEL_HPP
      
      #include <QtCore>
      #include <QAbstractTableModel>
      #include <tuple>
      
      typedef std::tuple<int,QString,int,int,int>
          ROWTYPE;
      typedef QVector<ROWTYPE>
          MODELRECS;
      
      #define MODEL_COLUMNS  5
      
      #define MODEL_ID_POS   0
      #define MODEL_TEXT_POS 1
      #define MODEL_B1_POS   2
      #define MODEL_B2_POS   3
      #define MODEL_B3_POS   4
      
      #define TEST_MIME_TYPE "application/x-mytype"
      
      class TestModel : public QAbstractTableModel
      {
        Q_OBJECT
      
      public:
        explicit TestModel(QObject *parent = nullptr);
        QVariant headerData(int section,
                            Qt::Orientation orientation,
                            int role = Qt::DisplayRole) const override;
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        int columnCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex &index,
                      int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
        Qt::ItemFlags flags(const QModelIndex& index) const override;
        QMimeData *mimeData(const QModelIndexList &indexes) const override;
        bool dropMimeData(const QMimeData *mimedata,
                          Qt::DropAction action,
                          int row,
                          int column,
                          const QModelIndex & /*parent*/) override;
        QStringList mimeTypes() const override;
        Qt::DropActions supportedDragActions() const override
        { return Qt::MoveAction;  }
        Qt::DropActions supportedDropActions() const override
        { return Qt::MoveAction; }
      
      private:
        MODELRECS recs_;
      };
      
      #endif // TESTMODEL_HPP
      

      File: TestModel.cpp:

      #include "TestModel.hpp"
      
      TestModel::TestModel(QObject *parent)
        : QAbstractTableModel(parent)
      {
        recs_ = MODELRECS{
                   {1,"John Doe",1,2,100},
                   {2,"Jane Doe",3,4,200},
                   {3,"Bill Smith",5,6,300},
                   {4,"Janet Kelly",7,8,400}
                };
      }
      
      QVariant TestModel::headerData(int section,
                                     Qt::Orientation orientation,
                                     int role) const
      {
        QVariant retval;
        // show only horizontal header:
        if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) {
          switch (section) {
          case MODEL_ID_POS:
            retval = "ID:";
            break;
          case MODEL_TEXT_POS:
            retval = "Name:";
            break;
          case MODEL_B1_POS:
            retval = "1st choice:";
            break;
          case MODEL_B2_POS:
            retval = "2nd choice:";
            break;
          case MODEL_B3_POS:
            retval = "3rd choice:";
            break;
          }
        }
        return retval;
      }
      
      int TestModel::rowCount(const QModelIndex &parent) const
      {
        if (!parent.isValid()) {
          return recs_.size();
        }
        return 0;
      }
      
      int TestModel::columnCount(const QModelIndex &parent) const
      {
        if (!parent.isValid()) {
          if (!recs_.isEmpty())
            return MODEL_COLUMNS;
        }
        return 0;
      }
      
      QVariant TestModel::data(const QModelIndex &index, int role) const
      {
        QVariant retval;
        if (index.isValid()) {
          int row = index.row();
          int col = index.column();
          if (role == Qt::DisplayRole) {
            // Normally I would use QSqlRecord as ROWTYPE, but unfortunately
            // I chose std::tuple to avoid pulling in all the SQL headers
            // and cannot use "col" here directly because it was
            // initialized with a non-const expression:
            switch(col) {
            case MODEL_ID_POS:
              retval = std::get<MODEL_ID_POS>(recs_.at(row));
              break;
            case MODEL_TEXT_POS:
              retval = std::get<MODEL_TEXT_POS>(recs_.at(row));
              break;
            case MODEL_B1_POS:
              retval = std::get<MODEL_B1_POS>(recs_.at(row));
              break;
            case MODEL_B2_POS:
              retval = std::get<MODEL_B2_POS>(recs_.at(row));
              break;
            case MODEL_B3_POS:
              retval = std::get<MODEL_B3_POS>(recs_.at(row));
              break;
            default:
              break;
            }
          }
        }
        return retval;
      }
      
      bool TestModel::setData(const QModelIndex &index,
                              const QVariant &value,
                              int role)
      {
        if (index.isValid() && (role == Qt::EditRole)) {
          int row = index.row();
          int col = index.column();
          ROWTYPE &r = recs_[row];
          bool ok = false;
          // Normally I would use QSqlRecord as ROWTYPE, but unfortunately
          // I chose std::tuple to avoid pulling in all the SQL headers
          // and cannot use "col" here directly because it was
          // initialized with a non-const expression:
          switch(col) {
          case MODEL_ID_POS:
            std::get<MODEL_ID_POS>(r) = value.toInt(&ok);
            break;
          case MODEL_TEXT_POS:
            std::get<MODEL_TEXT_POS>(r) = value.toString();
            ok = true;
            break;
          case MODEL_B1_POS:
            std::get<MODEL_B1_POS>(r) = value.toInt(&ok);
            break;
          case MODEL_B2_POS:
            std::get<MODEL_B2_POS>(r) = value.toInt(&ok);
            break;
          case MODEL_B3_POS:
            std::get<MODEL_B3_POS>(r) = value.toInt(&ok);
            break;
          default:
            break;
          }
          if (ok) {
            emit dataChanged(index, index, QVector<int>() << role);
            return true;
          }
        }
        return false;
      }
      
      Qt::ItemFlags TestModel::flags(const QModelIndex &index) const
      {
        if (!index.isValid())
          return Qt::NoItemFlags;
      
        Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
      
        int row = index.row();
        int col = index.column();
      
        // Only allow DnD in the first row:
        if ((row == 0) && (col == MODEL_B3_POS))
          flags |= Qt::ItemIsDragEnabled;
        else if ((row == 0) && (col == MODEL_B2_POS))
          flags |= Qt::ItemIsDropEnabled;
      
        return flags;
      }
      
      QMimeData *TestModel::mimeData(const QModelIndexList &indexes) const
      {
        QMimeData *pMimeData = nullptr;
      
        if (!indexes.isEmpty()) {
          QModelIndex idx = indexes.at(0);
          // Only allow DnD in the first row:
          if (idx.row() == 0) {
            QVariant value = data(idx);
            if (value.isValid()) {
              QString mimeText = value.toString() + ";0";
              pMimeData = new QMimeData;
              if (pMimeData) pMimeData->setData(TEST_MIME_TYPE, mimeText.toUtf8());
            }
          }
        }
        return pMimeData;
      }
      
      QStringList TestModel::mimeTypes() const
      {
        return QStringList() << TEST_MIME_TYPE;
      }
      
      bool TestModel::dropMimeData(const QMimeData *mimedata,
                                   Qt::DropAction action,
                                   int row,
                                   int column,
                                   const QModelIndex & /* parent is not used */)
      {
        if (!mimedata) return false;
        if (action != Qt::MoveAction) return false;
        if ((row < 0) || (column < 0)) return false;
        if (!mimedata->hasFormat(TEST_MIME_TYPE)) return false;
      
        QString dta = QString::fromUtf8(mimedata->data(TEST_MIME_TYPE));
        QStringList sl = dta.split(';');
        Q_ASSERT(sl.size() == 2);
        bool ok = false;
        QString sval, srow;
        sval = sl.at(0);
        srow = sl.at(1);
        int r = -1;
        int v = sval.toInt(&ok);
        if (ok) {
          r = srow.toInt(&ok);
        }
        if (!ok) return false;
        if (r == row) {
          // accept the drop:
          return setData(index(row,column), v);
        }
        return false;
      }
      

      File: main.cpp:

      #include <QApplication>
      #include <QTableView>
      #include "TestModel.hpp"
      
      int main(int argc, char *argv[])
      {
        QApplication a(argc, argv);
        QTableView view;
        TestModel model;
      
        view.setDefaultDropAction(Qt::MoveAction);
        view.setDragDropMode(QAbstractItemView::InternalMove);
        view.setDragEnabled(true);
        view.setEditTriggers(QAbstractItemView::NoEditTriggers);
        view.setSelectionBehavior(QAbstractItemView::SelectItems);
        view.setSelectionMode(QAbstractItemView::SingleSelection);
        view.showDropIndicator();
        view.setModel(&model);
        view.setMinimumWidth(600);
        view.setMinimumHeight(100);
        view.show();
      
        return a.exec();
      }
      
      1 Reply Last reply
      1
      • R Offline
        R Offline
        Robert Hairgrove
        wrote on last edited by
        #3

        Even after simplifying the logic to allow drops from column 5 into column 4 in ANY row of the table, none of the drops work ... the dropMimeData() function still receives row == -1 and column == -1 although the drag symbol changes to the drop symbol as it should when the mouse is over any cell in the 4th column.

        1 Reply Last reply
        0
        • Christian EhrlicherC Online
          Christian EhrlicherC Online
          Christian Ehrlicher
          Lifetime Qt Champion
          wrote on last edited by
          #4

          You're ignoring the parent, see documentation: "When a drop occurs, the model index corresponding to the parent item will either be valid, indicating that the drop occurred on an item, or it will be invalid, indicating that the drop occurred somewhere in the view that corresponds to top level of the model."

          Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
          Visit the Qt Academy at https://academy.qt.io/catalog

          R 1 Reply Last reply
          2
          • Christian EhrlicherC Christian Ehrlicher

            You're ignoring the parent, see documentation: "When a drop occurs, the model index corresponding to the parent item will either be valid, indicating that the drop occurred on an item, or it will be invalid, indicating that the drop occurred somewhere in the view that corresponds to top level of the model."

            R Offline
            R Offline
            Robert Hairgrove
            wrote on last edited by Robert Hairgrove
            #5

            @christian-ehrlicher : Does this mean that I should get the row and column number from the index and not from the arguments passed to the function? In a table model, the parent is almost always an invalid QModelIndex in rowCount() and similar functions.

            (EDIT: I just tested this, and now it seems to work... Thank you very much!)

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved