Implémentation Avancée du Glisser-Déposer de Widgets Personnalisés avec Qt

Architecture du Glisser-Déposer dans Qt

Le mécanisme de glisser-déposer (drag and drop) dans Qt repose sur une séquence précise d'événements. Pour permettre le déplacement libre d'un composant personnalisé au sein d'un conteneur, il est nécessaire de surcharger quatre méthodes clés : la détection du clic, l'entrée dans la zone de dépôt, le déplacement au-dessus de la zone, et enfin le relâchement.

Initialisation du Glisser (mousePressEvent)

Lorsqu'un clic est détecté, le système doit identifier le compoasnt cible, calculer le point de saisie pour aligner correctement l'image lors du déplacement, et sérialiser les données via QMimeData. Une session de glisser est ensuite créée et exécutée.

void MagnetBoard::mousePressEvent(QMouseEvent *clickEvent) {
    MagnetLabel *clickedLabel = qobject_cast<MagnetLabel*>(childAt(clickEvent->pos()));
    if (!clickedLabel) {
        return;
    }

    QPoint grabOffset = clickEvent->pos() - clickedLabel->pos();

    QByteArray serializedData;
    QDataStream stream(&serializedData, QIODevice::WriteOnly);
    stream << clickedLabel->contentText() << grabOffset;

    QMimeData *mimePayload = new QMimeData();
    mimePayload->setData(MagnetBoard::customMimeType(), serializedData);
    mimePayload->setText(clickedLabel->contentText());

    QDrag *dragSession = new QDrag(this);
    dragSession->setMimeData(mimePayload);
    dragSession->setPixmap(clickedLabel->renderPixmap());
    dragSession->setHotSpot(grabOffset);

    clickedLabel->setVisible(false);

    Qt::DropAction finalAction = dragSession->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction);

    if (finalAction == Qt::MoveAction) {
        clickedLabel->deleteLater();
    } else {
        clickedLabel->setVisible(true);
    }
}

Gestion du Survol (dragEnterEvent et dragMoveEvent)

Pour optimiser la structure et éviter la duplication de code, les événements d'entrée et de déplacement sont centralisés dans une méthode commune. Cette logique vérifie le format MIME et détermine si l'opération doit être acceptée, déplacée ou ignorée.

void MagnetBoard::dragEnterEvent(QDragEnterEvent *enterEvent) {
    processDragEvent(enterEvent);
}

void MagnetBoard::dragMoveEvent(QDragMoveEvent *moveEvent) {
    processDragEvent(moveEvent);
}

void MagnetBoard::processDragEvent(QDragEvent *dragEvent) {
    if (dragEvent->mimeData()->hasFormat(MagnetBoard::customMimeType())) {
        if (children().contains(dragEvent->source())) {
            dragEvent->setDropAction(Qt::MoveAction);
            dragEvent->accept();
        } else {
            dragEvent->acceptProposedAction();
        }
    } else if (dragEvent->mimeData()->hasText()) {
        dragEvent->acceptProposedAction();
    } else {
        dragEvent->ignore();
    }
}

Finalisation du Dépôt (dropEvent)

Lors du relâchement, les données sérialisées sont extraites pour instancier un nouveau composant à la position exacte du curseur, en tenant compte du décalage initial. Si le dépôt contient du texte brut, celui-ci est découpé pour générer plusieurs composants alignés horizontalement.

void MagnetBoard::dropEvent(QDropEvent *releaseEvent) {
    const QMimeData *incomingMime = releaseEvent->mimeData();

    if (incomingMime->hasFormat(MagnetBoard::customMimeType())) {
        QByteArray rawPayload = incomingMime->data(MagnetBoard::customMimeType());
        QDataStream stream(&rawPayload, QIODevice::ReadOnly);

        QString labelText;
        QPoint storedOffset;
        stream >> labelText >> storedOffset;

        MagnetLabel *spawnedLabel = new MagnetLabel(labelText, this);
        spawnedLabel->move(releaseEvent->pos() - storedOffset);
        spawnedLabel->show();
        spawnedLabel->setAttribute(Qt::WA_DeleteOnClose);

        if (releaseEvent->source() == this) {
            releaseEvent->setDropAction(Qt::MoveAction);
            releaseEvent->accept();
        } else {
            releaseEvent->acceptProposedAction();
        }
    } else if (incomingMime->hasText()) {
        QStringList textTokens = incomingMime->text().split(
            QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts);
        QPoint currentPos = releaseEvent->pos();

        for (const QString &token : textTokens) {
            MagnetLabel *spawnedLabel = new MagnetLabel(token, this);
            spawnedLabel->move(currentPos);
            spawnedLabel->show();
            spawnedLabel->setAttribute(Qt::WA_DeleteOnClose);

            currentPos += QPoint(spawnedLabel->width(), 0);
        }
        releaseEvent->acceptProposedAction();
    } else {
        releaseEvent->ignore();
    }
}

Étiquettes: Qt C++ DragAndDrop QWidget QMimeData

Publié le 18 août à 01h33