Published on

Un glisser/déposer en QML sans déplacement (copie, icône fantôme)

Authors
  • Name
    Anthony Rabine

On rencontre très souvent les mêmes exemples quant il s'agit de se renseigner sur l'implémentation d'un glisser-déposer en Qt/QML. Les exemples montrent surtout un déplacement d'objet graphique. Dans cet article, nous allons apprendre comment effectuer un glisser-déposer sans déplacement, avec une copie de l'icône qui suit le curseur pendant le drag.

Usage

On trouve souvent ce type de comportement de glisser-déposer au sein de logiciels professionnels. Il s'agit généralement d'un objet que l'on souhaite ajouter à son projet ; par exemple, dans un outil de modélisation UML, on va glisser-déposer un nouveau bloc de type "Classe" dans la zone de travail.

Voyons comment effectuer cela avec le QML, ce n'est pas si trivial, il y a beaucoup de petits détails à régler pour que cela fonctionne convenablement.

Voici ce que vous obtiendrez à la fin de cet article :

image

La barre d'outil générique

Notre barre d'outils prendra la forme d'un rectangle avec un layout horizontal contenant un élément Repeater.

La généricité vient de cet élément qui va instancier N composants à partir d'un modèle. Notre modèle, côté QML, contiendra le nom et l'emplacement de chaque icône. De cette manière, l'icône déposée sera convenablement identifiée par son nom.

javascript
Rectangle {

    ListModel {
        id: idToolBarModel

        ListElement {
            icon: "qrc:/step_screwdriver.png"
            name: "tightening"
        }
        ListElement {
            icon: "qrc:/step_pick_to_light.png"
            name: "picktolight"
        }
        ListElement {
            icon: "qrc:/step_input.png"
            name: "input"
        }
        ListElement {
            icon: "qrc:/step_user_input.png"
            name: "userInput"
        }
        ListElement {
            icon: "qrc:/step_switch.png"
            name: "switch"
        }
    }

    RowLayout {
        id: idToolBarLayout

        anchors.fill: parent
        anchors.leftMargin: 10
        anchors.verticalCenter: parent.verticalCenter
        spacing: 5
        property var dragedComp;

        Repeater {
            model: idToolBarModel
            delegate: Item {
                width: 64
                height: 64

                TileBody {
                    anchors.fill: parent
                    title: name // titre de notre icône
                    imageName: icon // emplacement de l'icône (dans un QRC)
                }
            }
        }
    }
}

Rien de sorcier pour le moment, c'est du classique. Mais il faut aller prudemment et comme toujours, bien tester les étapes intermédiaires.

L'astuce

Un élément est important ici : c'est notre propriété de type "var" (le type fourre-tout du QML et Javascript) appelé draggedComp. Cette variable va contenir la copie de l'icône que l'on veut déplacer.

Car l'astuce est là : pour obtenir notre effet, il va falloir dupliquer l'icône source.

En QML, on va préparer les choses en déclarant l'existance d'un composant instanciable :

javascript
Component {
    id: itemComponent
    TileBody {

    }
}

Le coeur de notre système est donc de créer une copie de notre icône qui se baladera tant que le drag n'est pas terminé (par un succès, le drop, ou un abandon).

Pour créer le drag, entourons notre élément d'un objet MouseArea. Dans tous les cas, lorsque le bouton est relâché, succès ou non, on détruit la copie.

Lorsque le bouton est pressé, on crée notre copie à l'aide de la fonciton createObject() à partir du composant, et on paramètre tout : sa position, son titre, son icône et les paramètres de drag.

javascript
MouseArea {
  id: mouseArea
  anchors.fill: parent

  onPressed: (mouse)=> {
      console.log("Pressed");
      addItem(mouse.x, mouse.y);
  }

  onReleased: {
      Drag.active = false;
      if (idToolBarLayout.dragedComp !== null) {
          idToolBarLayout.dragedComp.Drag.drop();
          idToolBarLayout.dragedComp.destroy();
      }
  }

  function addItem(x, y)
  {
      var positionInWindow = mapToItem(idStepsToolBar, x, y)
      idToolBarLayout.dragedComp = itemComponent.createObject(idToolBarLayout, { title: name, imageName: icon, x: positionInWindow.x - 25, y: positionInWindow.y - 25 });
      if (idToolBarLayout.dragedComp !== null) {
          idToolBarLayout.dragedComp.parent = idStepsToolBar;
          idToolBarLayout.dragedComp.z = 100;

          idToolBarLayout.dragedComp.Drag.keys = [ "step" ]
          idToolBarLayout.dragedComp.Drag.active = true;
          idToolBarLayout.dragedComp.Drag.hotSpot.x = 32;
          idToolBarLayout.dragedComp.Drag.hotSpot.y = 32;

          mouseArea.drag.target = idToolBarLayout.dragedComp;
          Drag.active = true;
      }
  }
}

Le drop

Le drop est simple, nous acceptons uniquement le type désiré grâce à la propriété "keys" (on l'avait initialisé à la bonne valeur lors de la création de la copie).

Insérez ensuite votre code dans l'événement "onDropped". Le code présenté dans l'exemple ajoute un petit effet graphique sur l'élément de destination afin de rendre le drop plus visuel.

javascript
DropArea {
    anchors.fill: parent
    keys: ["step"]
    onEntered: {
        console.log("ENTERED");
        idDropOverlay.visible = true;
    }
    onExited: {
        console.log("EXIT");
        idDropOverlay.visible = false;
    }

    onDropped: {
        console.log("DROP");
        idDropOverlay.visible = false;
        console.log(drag.source.title)
    }

    Text {
        anchors.centerIn: parent
        text: "Target item!"
    }
}

Conclusion

Nous obtenons notre effet désiré ! Le code source complet est ici :

Code source sur Github

image