Bienvenue sur le forum !

Si vous souhaitez rejoindre la communauté, cliquez sur l'un de ces boutons !

Qt 5 : 5.9.1 - Qt Creator : 4.4.0 - Qt Installer : 2.0.3 - JOM : 1.1.2 - Qt Build suite : 1.7.0 - VS Qt 5 : 2.0.0

QTableWidget Drag and Drop simplifié.

December 2016 modifié dans Qt / Langage C++
Je dispose d'une application contenant parmi d'autres, un composant QTableWidget conçu dans Designer avec les options Drag and Drop activées.
Le Drag and Drop fonctionne sans ajout de code.

Mais ... Je voulais capturer l'évenement Qt::Drop dans un eventFilter attribué à ce QTableWiget pour simplement avoir un résultat du drop final bien justifié par un resizeColumnsToContent.

J'ai cherché sur le net comment récupérer les données dans un MimeData mais sans résultat. Je n'ai rien compris puisque tous ces exemples partaient du fait que le Drag and Drop s'opérait sur des Widgets sous-classés avec réimplémentation des dragEnter, dragMove, dragDrop.

J'ai donc réfléchis et me suis dit que si les données déposées au moment du drop sont inévitablement sélectionnées, on peut les récupérer en tant que sélection et les placer aux coordonnées du moment déposer.
Et bien ça marche. Seulement peut-être que cette méthode présente un risque ?

Voiçi la capture du drop :



T1 = la QTableWidget
g_clip = QVector<QTableWigetItem*> en tant que variable globale à la classe MainWindow.

bool MainWindow::eventFilter(QObject * obj , QEvent * event )
{
if (obj == ui->T1->viewport() )
{

if (event->type() == QEvent::Drop )
{
QDropEvent * ev = dynamic_cast< QDropEvent *>(event);


if( ! ev )
return true;

QPoint pos = ev->pos();
int row = pos.y();
int col = pos.x();

QRect R = ui->T1->geometry();
if( ! R.contains(pos) )
{
qDebug( "NOT IN GEOMETRY ");
return true ;
}


QTableWidgetItem * z = 0 ;
z = ui->T1->itemAt(pos);

if( !z )
{
qDebug( "NO ITEM AT THIS POS ");
return true ;
}

row = z->row();
col = z->column();

if( row < 0 || col < 0 )
{
qDebug( "ITEM POS OUT OF RANGE ");
return true ;
}

g_clip = ui->T1->selectedItems();

int nb = g_clip.count();

if( nb <= 0 )
return true ;

QTableWidgetItem * z1 = g_clip.at(0);
int i1 = z1->row();
int j1 = z1->column();


QTableWidgetItem * z2 = g_clip.at(nb-1);
int i2 = z2->row();
int j2 = z2->column();


int nbl = (i2-i1) + 1 ;
int nbc = (j2-j1) + 1 ;

// Sauvegarde de la sélection
QList<QTableWidgetItem*> T ;
for( int i = 0 ; i < g_clip.count(); i++ )
{
QTableWidgetItem * z = g_clip.at(i)->clone();
T.push_back( z );
}

// Efface la sélection
for( int i = 0 ; i < g_clip.count(); i++ )
{
g_clip.at(i)->setText("");
g_clip.at(i)->setSelected(false);
}

// Rend la sélection
for( int i = 0 ; i < nbl; i++ )
{
for( int j = 0 ; j < nbc ; j++ )
{
QTableWidgetItem * z = T.at( ( j * nbl ) + i );
ui->T1->setItem( row + i , col + j , z );
}
}


T.clear();
g_clip.clear();

ui->T1->resizeColumnsToContents();
// true , l'évènement est traité.
return true;
}
}
return false ;
}

Cette méthode ne vaut que si les cellules du tableau ne sont que des chiffres ou des lettres.

Réponses

  • Bonsoir,

    J'ai cherché sur le net comment récupérer les données dans un MimeData mais sans résultat. Je n'ai rien compris puisque tous ces exemples partaient du fait que le Drag and Drop s'opérait sur des Widgets sous-classés avec réimplémentation des dragEnter, dragMove, dragDrop.
    Qu'est-ce que tu n'as pas compris dans les exemples.
    Qu'est-ce qui t'empêche de te créer une classe héritant de QTableWidget ?
  • December 2016 modifié
    Bonjour,

    C'est à dire que ce n'est pas pratique de sous-classer un QTableWidget dans le Designer. Ensuite une fois sous-classée comment capturer les évènements drag and drop dans la classe principale MainWindow ? Ce serait automatique en redéfinissant les dragEnter, dragMove etc ?

    Enfin la méthode que je propose reste assez simple si les données sont textes ou chiffres et que le QTableView est conçu dans le Designer.

    Perso je trouve que la méthode officielle est beaucoup trop lourde lorsque dans un QTableWidget on ne déplace que des cellules textes à l'intérieur de ce même QTableWidget.
    Après les choses sont plus complexes lorsque les cellules sont déplacées vers un autre Widget externe.
  • Re,
    j'avais en tête un déplacement de données externes au widget.
    Je testerai un bout de code demain en utilisant une classe perso, le dropevent et le designer bien sûr.
    Que doit faire exactement le comportement du drag&drop ?
  • voici un exemple tout simple d'utilisation de dropMimeData
    J'ai mis en commentaire l'utilisation de dropEvent/dragEnterEvent/dragMoveEvent qui fait la même chose
    L'avantage de ce code, c'est que c'est la tableWidget qui fait le boulot et pas la mainwindow
    Donc j'ai fait une classe perso héritant de QTableWidget, et dans le designer, j'ai fait un promote.
    J'ai joint le projet zippé,
    le code de la classe :
    .h

    #ifndef DRAGDROPTABLEWIDGET_H
    #define DRAGDROPTABLEWIDGET_H

    #include <QTableWidget>

    class DragDropTableWidget : public QTableWidget
    {
    Q_OBJECT

    public:
    explicit DragDropTableWidget(QWidget *parent = Q_NULLPTR);
    DragDropTableWidget(int rows, int columns, QWidget *parent = Q_NULLPTR);

    protected:
    virtual bool dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action);

    private:
    void init();
    void populate();
    };

    #endif // DRAGDROPTABLEWIDGET_H
    .cpp

    #include "dragdroptablewidget.h"

    #include <QDebug>
    #include <QMimeData>

    DragDropTableWidget::DragDropTableWidget(QWidget *parent)
    : QTableWidget(3, 3, parent)
    {
    init();
    populate();
    }

    DragDropTableWidget::DragDropTableWidget(int rows, int columns, QWidget *parent)
    : QTableWidget(rows, columns, parent)
    {
    init();
    populate();
    }

    bool DragDropTableWidget::dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action)
    {
    qDebug() << "dropMimeData";
    if (data->hasFormat("application/x-qabstractitemmodeldatalist"))
    {
    QList<QTableWidgetItem *> selectedItemsList = selectedItems();
    qDebug() << "Destination cell, row : " << row << ", column : " << column;
    foreach(QTableWidgetItem *selectedItem, selectedItemsList)
    {
    qDebug() << "Source cell, row : " << selectedItem->row() << ", column : " << selectedItem->column() << ", text : " << selectedItem->text();
    QTableWidgetItem* targetItem = itemAt(selectedItem->row() + row, selectedItem->column() + column);
    if (targetItem)
    qDebug() << "Target cell, row : " << selectedItem->row() + row << ", column : " << selectedItem->column() + column << ", text : " << targetItem->text();
    }
    }
    return QTableWidget::dropMimeData(row, column, data, action);
    }

    void DragDropTableWidget::init()
    {
    setDragEnabled(true);
    }

    void DragDropTableWidget::populate()
    {
    for(auto i = 0; i < rowCount(); i++) {
    for(auto j = 0; j < columnCount(); j++) {
    QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(qrand()));
    setItem(i, j, item);
    }
    }
    }

    zip
    zip
    dad.zip
    3K
  • Bonjour,

    Je vous remercie pour votre attention. Je vais tester votre code. Ma méthode échoue si les sélections ne sont pas contigües. Dans le cas où dans le Designer on choisit MultiSelection au lieu de ContiguousSelection dans SelectionMode.
  • Bonjour,

    J'ai testé votre code. Effectivement la solution du sous-classement est efficace. Je pensais cela plus compliqué.
    Merçi.
  • December 2016 modifié
    Normalement le widget doit gérer la sélection non contigüe de base. A tester quand même.
  • December 2016 modifié
    Bonjour,

    La sélection non contigüe fonctionne aussi. J'ai aussi corrigé au cas où dans ma solution eventFilter.
    Par contre avec la solution sous-classement je ne parviens pas à effacer la sélection après le drop.
    Les qDebug() dans votre exemple plantent, mais ce n'est pas l'essentiel. Le principe du sous-classement fonctionne.
  • December 2016 modifié
    Bonjour,

    La solution trouvée pour désactiver la sélection de la plage source mène à une réimplémentation de dropEvent dans la sous-classe.


    void xtab::dropEvent(QDropEvent *event)
    {
    qDebug() << "dropEvent";

    if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
    {
    QPoint pos = event->pos();
    int row = pos.y();
    int col = pos.x();

    QTableWidgetItem * z = 0 ;
    z = itemAt(pos);

    if( !z )
    {
    qDebug( "NO ITEM AT THIS POS ");
    return ;
    }

    row = z->row();
    col = z->column();

    QList<QTableWidgetItem*> g_clip ;

    g_clip = selectedItems();

    int nb = g_clip.count();

    if( nb <= 0 )
    return ;

    QTableWidgetItem * z1 = g_clip.at(0);
    int i1 = z1->row();
    int j1 = z1->column();

    int gap_line = row - i1 ;
    int gap_col = col - j1 ;

    typedef QPair<int,int> TPOS ;
    QList<QTableWidgetItem*> T ;
    QList<TPOS> P ;

    for( int i = 0 ; i < g_clip.count(); i++ )
    {
    QTableWidgetItem * z = g_clip.at(i)->clone();
    TPOS p ;
    // sauvegarde et calcul les coordonnés
    // car z->row() ou z->col() renvoient toujours -1
    p.first = g_clip.at(i)->row() + gap_line ;
    p.second = g_clip.at(i)->column() + gap_col ;
    P.push_back( p );
    T.push_back( z );
    }

    // Effacer la sélection de la plage source
    for( int i = 0 ; i < g_clip.count(); i++ )
    {
    g_clip.at(i)->setText("");
    g_clip.at(i)->setSelected(false);
    }


    for( int i = 0 ; i < T.size() ; i++ )
    {
    setItem( P.at(i).first , P.at(i).second , T.at(i) );
    }

    resizeColumnsToContents();

    T.clear();
    P.clear();
    g_clip.clear();
    }
    }



  • Salut,
    je comprends pas bien ce que tu veux faire exactement.
    Si tu veux seulement avoir un move, pas besoin de réimplémenter les events ni même le dropMimeData.
    Tu écris juste :

    Qt::DropActions DragDropTableWidget::supportedDropActions() const
    {
    return Qt::MoveAction;
    }
    et tu as le comportement de déplacement avec le bouton principale de la souris.
  • Bonjour,

    Ce que j'ai fait c'est annuler la sélection des cellules sources et retailler la largeur des cellules après l'événement dropEvent.
    Je suis obligé de programmer le dropEvent car je n'ai pas trouvé moyen de désélectionner les cellules sources .
    Par contre le resizeColumnsToContents fonctionne après :

    QTableWidget::dropEvent(event);

    Mais pas la désélection de la plage de départ.
  • December 2016 modifié
    Re,
    c'est facile ça ;)
    pour résumer tu as besoin que de ça :

    DragDropTableWidget::DragDropTableWidget(QWidget *parent)
    : QTableWidget(3, 3, parent)
    {
    setDragEnabled(true);
    }

    DragDropTableWidget::DragDropTableWidget(int rows, int columns, QWidget *parent)
    : QTableWidget(rows, columns, parent)
    {
    setDragEnabled(true);
    }

    Qt::DropActions DragDropTableWidget::supportedDropActions() const
    {
    return Qt::MoveAction;
    }

    void DragDropTableWidget::dropEvent(QDropEvent *event)
    {
    QTableWidget::dropEvent(event);
    QItemSelectionModel *selection = selectionModel();
    selection ->clear();
    resizeColumnsToContents();
    }

  • December 2016 modifié
    Bonjour,

    Ah bon c'est parfait. Cela m'évite de coder le dropEvent. Je cherchais dans selectedItems au lieu de selectionModel.

    Merçi
  • Bonjour,

    Dernière question d'où peut-on trouver une chaîne de format comme :

    if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
    D'où sort "application/x-qabstractitemmodeldatalist" ?
  • December 2016 modifié
    Salut,
    j'ai pris ça dans un exemple, donc pas trop investigué la question
    après tu peux tester la méthode
    virtual QStringList QMimeData::formats() const
    pour obtenir la liste de event->mimeData().
    qDebug() << event->mimeData().formats()
Connectez-vous ou Inscrivez-vous pour répondre.