LCL Drag Drop/fr

From Free Pascal wiki
Jump to navigationJump to search

English (en) français (fr) русский (ru)

Guide de DoDi pour glisser(drag), déplacer(drop), déposer(Dock)

Les contrôles dans un GUI peut être attrapée avec la souris, déplacés vers d'autres endroits, et déposés par dessus d'autres contrôles ou sur le bureau. Lorsque le contrôle glissé est déplacé vers un emplacement différent, l'opération est appelée "glisser déposer". Par ailleurs, le contrôle source et le contrôle cible peuvent échanger des informations ou interagir de toute autre manière, ce qu'on appelle une opération de "glisser-déposer".

Mais tout cela ne fonctionne pas de lui-même, une application doit configurer les opérations permises de sorte que la bibliothèque LCL puisse savoir ce qu'il faut faire avec chaque contrôle simple dans un GUI. Les opérations sophistiquées exigent davantage de code pour la gestion du retour visuel à l'utilisateur de l'application, et ce qui va se passer quand le contrôle glissé sera finalement déposé.

Glisser Déplacer

C'est seulement une action de glisser simple et originale. Les acteurs sont:

  • Un contrôle d'origine que l'on peut glisser dans le GUI
  • Un objet qui glisse, dérivés de TDragObject
  • des fonctions d'aide
  • des événements

Maintenant, jetons un œil à la manière dont ils travaillent ensemble, basé sur l'implémentation initiale en Delphi pour plus de clarté. L'implémentation de la bibliothèque LCL est quelque peu différente dans l'utilisation des objets d'aide, les détails seront discutés plus loin.

La source de glissement

Un contrôle peut être rendu déplaçable en fixant sa propriété DragMode à dmAutomatic. Lorsque l'utilisateur appuie avec le bouton gauche de la souris sur un tel contrôle, le pointeur de la souris change en un indicateur de glissement jusqu'à ce que l'utilisateur relâche le bouton, ou annule l'opération en appuyant sur la touche Escape. L'indicateur suit désormais les mouvements de la souris et change sa forme, conformément à ce qui se passera lorsque le contrôle sera déplacé sur l'emplacement du moment.

Le glissement peut également être lancé dans le code, en appelant la source.BeginDrag. Dans les deux cas, la procédure d'aide DragInitControl est appelé pour démarrer le glissement. Le contrôle initial lève un évènement StartDrag, là où l'application peut fournir un objet de glissement usuel. En cas d'absence d'un tel objet, un objet par défaut est créé. Des structures de données internes pour la rétroaction visuelle et plus sont initialisées. Alors DragTo et appelée pour montrer que le glissement a commencé.

L'objet qui glisse

Un objet de glissement reçoit toutes les entrées utilisateur pendant le glissement. Dans l'implémentation par défaut, il réagit en appelant soit DragTo soit finalement DragDone.

L'objet de glissement est également responsable du retour visuel :

  • GetDragCursor renvoie la forme du pointeur de la souris.
  • GetDragImages renvoie une liste d'images de glissement, à attacher au pointeur de la souris.
  • ShowDragImage et HideDragImage implémentent le retour visuel.

La méthode Finished notifie le contrôle source lorsque le glissement s'arrête. En cas d'annulation de l'opération, il invoque source.DragCanceled, suivi par source.DoEndDrag dans les deux cas, ce qui déclenche un événement EndDrag.

LCL Modifications

L'implémentation actuelle de LCL a supprimé le traitement des entrées de l'objet de glissement, de sorte que l'application ne peut plus personnaliser l'interface utilisateur. Et l'implémentation LCL est loin d'être satisfaisante :-(

De plus, l'objet de glissement est contourné dans la présentation du retour visuel.

Helper Functions

Les fonctions d'aide permettent principalement d'accéder aux propriétés et méthodes protégées des contrôles et autres classes, depuis l'extérieur de l'unité Controls.

Elles peuvent également effectuer des tâches courantes, et encapsuler des détails d'implémentation spécifiques à la plateforme.

General Helpers

  • DragTo détermine la cible actuelle du glissement, envoie des notifications aux contrôles impliqués, et gère le retour visuel.
    Elle gère également le démarrage différé d'une opération de glissement, c'est-à-dire qu'elle peut attendre que la souris s'éloigne suffisamment du point de départ.
  • DragDone termine le glissement, soit par un dépôt, soit par une annulation de l'opération.
  • CancelDrag est protégé contre les appels erronés, sinon il reporte d'autres actions à DragDone.


Special Helpers

Il existe quelques procédures d'aide supplémentaires, qui sont non documentées dans Delphi et ne sont pas nécessaires pour les opérations de glissement personnalisées.

  • DragFindWindow recherche une fenêtre cible de glissement (spécifique à la plateforme).
  • DragInit initialise la gestion interne, comme la capture de la souris, et le retour visuel.
  • DragInitControl crée l'objet de glissement, puis appelle DragInit.
  • SetCaptureControl gère la capture de la souris.


Événements

OnStartDrag/Dock

Cet événement se produit lorsque l'opération commence. Le gestionnaire peut fournir un DockObject personnalisé pour l'opération.

OnDrag/DockOver

Cet événement notifie une cible potentielle lorsqu'un objet est glissé au-dessus. Le gestionnaire peut signaler pour accepter ou rejeter un dépôt.

L'événement signale non seulement les mouvements de la souris, mais aussi lorsque la souris entre ou quitte un contrôle cible.

Un événement OnDockOver se produit sur le DockSite, non sur le contrôle cible sous la souris. Le gestionnaire peut signaler pour refuser un dépôt, et peut ajuster le retour visuel. L'événement se produit lorsque la souris entre ou quitte un contrôle, et à chaque mouvement de la souris.

OnDrag/DockDrop

Cet événement notifie le contrôle cible de la fin d'une opération de glissement.

OnEndDrag/Dock

Cet événement notifie le contrôle source de la fin d'une opération de glissement.


Événements spécifiques au Docking

(cette section devrait être déplacée dans le sujet docking)

OnGetSiteInfo

Cet événement se produit pendant la recherche d'un site de docking qualifié. Plusieurs sites de docking peuvent se chevaucher, résider dans des fenêtres superposées, ou être invisibles. Chaque candidat doit fournir un rectangle d'influence (zone de capture), et peut signaler pour refuser un dépôt. Le gestionnaire de glissement sélectionne le site de docking le plus approprié comme cible pour les événements OnDockOver suivants.

OnUnDock

Cet événement se produit lorsque le contrôle glissé doit être retiré de son site de docking hôte.

Il est incertain de ce qui doit se passer lorsque le gestionnaire refuse de dédocking le contrôle.


Implémentation LCL

L'implémentation de Delphi du glissement a de nombreux défauts, en détail en ce qui concerne le docking.

Jusqu'à présent, personne ne semble comprendre les détails du modèle de glissement de Delphi, la plupart du code a été copié de la VCL et implémente donc exactement le même comportement erroné.

Cette section, et plus important la page spécifique au docking, doivent éclairer le modèle de glissement en général et les défauts de conception et d'implémentation sévères dans l'implémentation de Delphi.

L'implémentation actuelle de LCL utilise un objet DragManager et des Drag Performers/docking au lieu de fonctions d'aide. La raison de ce changement radical est incertaine - qui peut expliquer?

Alors que le modèle de Delphi doit être fourni pour des raisons de compatibilité, l'introduction d'une classe et d'un objet DragManager (singleton) permet l'implémentation d'un gestionnaire de glissement alternatif, avec un comportement amélioré, et permet à une application de sélectionner le gestionnaire le plus approprié.

DragManager

Cet objet gère les entrées capturées, fournit des méthodes d'aide, et stocke les paramètres de glissement et de docking.

Il est discutable que l'objet de glissement ne soit pas autorisé à gérer les entrées utilisateur. Cela rend le système plus sûr, mais empêche les applications d'étendre l'interface utilisateur pour des opérations de glissement spécifiques.

Les méthodes d'aide nécessitent des appels différents, mais cela est inoffensif tant qu'elles ne sont appelées que par les composants LCL.

Plus critique est le stockage des paramètres de glissement dans l'objet DragManager, qui disparaissent lorsqu'un autre gestionnaire de glissement est installé. Cela est fatal en cas de liste des sites de docking enregistrés :-(

Drag Performers

L'utilisation interne de différentes classes pour le glissement et le docking est acceptable dans la mesure où les procédures diffèrent de manière significative entre les opérations de glissement-déplacement et de glissement-docking. Néanmoins, les actions communes devraient utiliser du code commun, par exemple hérité de la classe de base commune.


Message Sequence Chart or UML Sequence Diagram

Fourni par Tom Verhoeff

Quelque chose dans ce sens (c'est pour Delphi ; à visualiser avec une police à espacement fixe) :

                  Main            Drag          (Optional)        Drag
  User            Event          Source           Drag           Target
 (Mouse)          Loop           Control          Object         Control
    =               =               =                               =
    |               |               |                               |
   1|--MouseDown--->|               |                               |
    |              2#--OnMouseDown->|                               |
    |               |              3#                               |
    |              4#<-BeginDrag()--#                               |
    |               #               #                               |
    |               |               |                               |
    |              5#--OnStartDrag->|                               |
    |               #              6#----Create---->=               |
    |               |               #               |               |
   7|--MouseMove--->|               |               |               |
    |              8#-----------------------------------OnDragOver->|
    |               #               |               |              9#
    |               #               |               |               #
    |               |               |               |               |
  10|---MouseUp---->|               |               |               |
    |             11#-----------------------------------OnDragDrop->|
    |               #               |               |             12#
    |               #               |               |               #
    |               #---OnEndDrag-->|               |               |
    |               |             13#----Destroy--->=               |
    |               |               |                               |


L'objet DragObject est facultatif. Il pourrait y avoir plusieurs cibles candidates. La boucle principale des événements détermine quel événement envoyer à quel objet, en fonction de l'action de l'utilisateur, de la position du curseur et de l'état actuel de la GUI.

4 déclenche en quelque sorte 5 (il y a un délai en fonction du paramètre booléen Immediate à BeginDrag).

6 détermine l'emplacement du MouseDown et peut utiliser cela pour décider de ce qui est glissé (via GetCursorPos, ScreenToClient, MouseToCell). Il peut appeler SetDragImage pour visualiser l'élément en cours de glissement.

La chaîne 7, 8, 9 peut se répéter ; 9 indique l'acceptabilité d'un dépôt à cette cible via le paramètre booléen var Accepted.

12 est appelé uniquement si l'événement OnDragOver immédiatement précédent de cette cible a renvoyé Accepted = True ; c'est-à-dire qu'Accepted est une précondition de 12.

13 reçoit le paramètre Target pour savoir où le dépôt a été accepté ; si Target = nil, alors le dépôt a été annulé par l'utilisateur (MouseUp à un emplacement inacceptable), et OnDragDrop n'a pas été exécuté ; si Target <> nil, alors OnDragDrop a déjà été exécuté sur la cible. Le travail associé à un dépôt réel est donc divisé entre OnDragDrop (pour les actions sur la cible) et OnEndDrag (pour les actions sur la source). Comme un objet de glissement doit être détruit, indépendamment du succès ou de l'annulation du dépôt, il est préférable d'appeler Free (ou Destroy) uniquement dans le gestionnaire OnEndDrag.

Un seul glissement peut être actif à tout moment ; il se termine soit par un dépôt, soit par une annulation.

Il y a diverses autres choses à inclure, mais ceci est la partie fondamentale. (Peut-être voir Delphi-Central ou "Implementing drag-and-drop in controls".)

J'espère que cela est utile. Tom

Autres ressources