L'intégration de la fonctionnalité de glisser-déposer (drag-and-drop) externe est une exigence courante pour les applications de bureau modernes, permettant aux utilisateurs d'interagir intuitivement avec le programme. Cet article explore comment implémenter la réception de fichiers et de texte par glisser-déposer dans des composants d'édition de texte sous Delphi, en s'appuyant sur les mécanismes du système d'exploitation Windows.
Deux approches principales sont nécessaires pour gérer les différents types de données que l'utilisateur pourrait déposer :
- Pour les fichiers : L'interception du message Windows
WM_DROPFILESest la méthode standard pour recevoir des chemins de fichiers. - Pour le texte ou d'autres formats de données complexes : L'implémentation de l'interface COM
IDropTargetest indispensable pour une gestion plus sophistiquée des données transférées.
Implémentation personnalisée pour un composant TMemo
Pour démontrer cette fonctionnalité, nous allons créer un composant TMemo amélioré, capable d'accepter à la fois des fichiers et du texte glissés depuis l'extérieur de l'application. Ce nouveau composant, que nous nommerons TMemoGlisserDeposer, héritera de TMemo et implémentera les interfaces COM IUnknown et IDropTarget.
type
TMemoGlisserDeposer = class(TMemo, IUnknown, IDropTarget)
private
FSupporteDepot: Boolean; // Indique si la fonctionnalité de glisser-déposer est active
FValidationFormat: HResult; // Stocke le résultat de la validation de format pour le texte
FFormatDonneesCible: TFormatEtc; // Décrit le format de données de texte attendu
FCompteurReferences: Integer; // Compteur de références pour l'interface IUnknown
protected
procedure GererFichiersDeposes(var Msg: TWMDropFiles); message WM_DROPFILES;
procedure ActiverSupportDepot(const P_Valeur: Boolean);
// Méthodes de l'interface IUnknown
function QueryInterface(const IID: TGUID; out P_Obj: Pointer): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
// Méthodes de l'interface IDropTarget
function DragEnter(const P_DataObject: IDataObject; P_CleEtat: Longint;
P_Position: TPoint; var P_EffetDepot: Longint): HResult; stdcall;
function DragOver(P_CleEtat: Longint; P_Position: TPoint;
var P_EffetDepot: Longint): HResult; stdcall;
function DragLeave: HResult; stdcall;
function Drop(const P_DataObject: IDataObject; P_CleEtat: Longint; P_Position: TPoint;
var P_EffetDepot: Longint): HResult; stdcall;
public
property SupporteDepot: Boolean read FSupporteDepot write ActiverSupportDepot;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
{ TMemoGlisserDeposer implémentation }
constructor TMemoGlisserDeposer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCompteurReferences := 0; // Initialiser le compteur de références COM
FSupporteDepot := False; // Le glisser-déposer est désactivé par défaut
end;
destructor TMemoGlisserDeposer.Destroy;
begin
// S'assurer de révoquer le support de glisser-déposer si l'objet est toujours enregistré
if FSupporteDepot then
RevokeDragDrop(Handle);
inherited;
end;
procedure TMemoGlisserDeposer.ActiverSupportDepot(const P_Valeur: Boolean);
begin
if FSupporteDepot = P_Valeur then Exit; // Aucun changement d'état
FSupporteDepot := P_Valeur;
// Enregistrer ou désenregistrer le support du dépôt de fichiers (WM_DROPFILES)
DragAcceptFiles(Handle, FSupporteDepot);
// Enregistrer ou désenregistrer le support du dépôt de texte (IDropTarget)
if FSupporteDepot then
RegisterDragDrop(Handle, Self) // Enregistre cet objet comme cible de dépôt
else
RevokeDragDrop(Handle); // Révoque l'enregistrement
end;
procedure TMemoGlisserDeposer.GererFichiersDeposes(var Msg: TWMDropFiles);
var
LNomFichierBuffer: array[0..MAX_PATH] of Char; // Buffer pour le chemin du fichier
LCompteurFichiers: Cardinal;
LCheminComplet: string;
begin
with Msg do
begin
// Récupérer le nombre de fichiers déposés (le dernier paramètre doit être nil pour le décompte)
LCompteurFichiers := DragQueryFile(Drop, $FFFFFFFF, nil, 0);
if LCompteurFichiers = 0 then
Exit; // Aucun fichier détecté
// Dans cet exemple, nous traitons uniquement le premier fichier déposé
DragQueryFile(Drop, 0, LNomFichierBuffer, SizeOf(LNomFichierBuffer));
LCheminComplet := LNomFichierBuffer;
DragFinish(Drop); // Libérer les ressources allouées pour l'opération de dépôt
// Charger le contenu du fichier dans le composant Memo
Lines.LoadFromFile(LCheminComplet);
end;
end;
function TMemoGlisserDeposer.DragEnter(const P_DataObject: IDataObject; P_CleEtat: Integer;
P_Position: TPoint; var P_EffetDepot: Integer): HResult;
begin
Result := E_FAIL;
FValidationFormat := E_FAIL; // Réinitialiser l'état de validation
if not FSupporteDepot then
Exit; // Le glisser-déposer n'est pas actif
if not Assigned(P_DataObject) then
Exit; // Aucun objet de données fourni
// Configurer le format de données attendu : texte
with FFormatDonneesCible do
begin
{$IFDEF UNICODE}
cfFormat := CF_UNICODETEXT; // Préférer le texte Unicode pour les systèmes modernes
{$ELSE}
cfFormat := CF_TEXT; // Revenir au texte ANSI si nécessaire
{$ENDIF}
ptd := nil; // Pas de données de périphérique cibles
dwAspect := DVASPECT_CONTENT; // Affichage normal du contenu
lindex := -1; // Toutes les données sont concernées
tymed := TYMED_HGLOBAL; // Les données sont transférées via un handle mémoire global
end;
// Vérifier si l'objet de données peut fournir les données dans le format spécifié
FValidationFormat := P_DataObject.QueryGetData(FFormatDonneesCible);
Result := FValidationFormat;
if Succeeded(Result) then // Si le format est supporté
P_EffetDepot := DROPEFFECT_COPY // Indiquer une action de copie
else
P_EffetDepot := DROPEFFECT_NONE; // Aucune action possible
end;
function TMemoGlisserDeposer.DragOver(P_CleEtat: Integer; P_Position: TPoint; var P_EffetDepot: Integer): HResult;
begin
// Le traitement ici est généralement léger, l'effet de dépôt est souvent déterminé par DragEnter
Result := S_OK;
end;
function TMemoGlisserDeposer.DragLeave: HResult;
begin
FValidationFormat := E_FAIL; // Réinitialiser l'état lors du départ du curseur
Result := S_OK;
end;
function TMemoGlisserDeposer.Drop(const P_DataObject: IDataObject; P_CleEtat: Integer; P_Position: TPoint;
var P_EffetDepot: Integer): HResult;
var
LMediumDonnees: TStgMedium;
LHandleDonnees: HGLOBAL;
LTexteRecu: PChar;
begin
Result := E_FAIL;
if Failed(FValidationFormat) then
Exit; // Le format de données n'a pas été validé précédemment
// Obtenir les données dans le format spécifié par FFormatDonneesCible
Result := P_DataObject.GetData(FFormatDonneesCible, LMediumDonnees);
if Failed(Result) then
begin
ReleaseStgMedium(LMediumDonnees); // S'assurer de libérer le medium en cas d'échec
Exit;
end;
// Verrouiller le handle global pour accéder aux données
LHandleDonnees := HGLOBAL(GlobalLock(LMediumDonnees.hGlobal));
if LHandleDonnees = 0 then
begin
ReleaseStgMedium(LMediumDonnees); // Libérer le medium si le verrouillage échoue
Exit;
end;
try
LTexteRecu := PChar(LHandleDonnees);
// Assigner le texte reçu au composant Memo
Lines.Text := LTexteRecu;
P_EffetDepot := DROPEFFECT_COPY; // Confirmer que l'opération de copie a eu lieu
finally
GlobalUnlock(LMediumDonnees.hGlobal); // Déverrouiller le handle global
ReleaseStgMedium(LMediumDonnees); // Libérer les ressources du medium de stockage
end;
end;
function TMemoGlisserDeposer.QueryInterface(const IID: TGUID; out P_Obj): HResult;
begin
// Méthode standard pour l'implémentation de QueryInterface pour les interfaces COM
if GetInterface(IID, P_Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TMemoGlisserDeposer._AddRef: Integer;
begin
// Incrémente le compteur de références et retourne la nouvelle valeur
Result := InterlockedIncrement(FCompteurReferences);
end;
function TMemoGlisserDeposer._Release: Integer;
begin
// Décrémente le compteur de références et retourne la nouvelle valeur
Result := InterlockedDecrement(FCompteurReferences);
if Result = 0 then
// Si le compteur atteint zéro, l'objet est libéré
Destroy;
end;
L'activation ou la désactivation du support de glisser-déposer pour ce composant se fait via sa propriété SupporteDepot.
Solution générique pour les contrôles d'édition de texte
L'approche précédente est spécifique à un TMemo personnalisé. Cependant, il est souvent utile d'activer le glisser-déposer de texte sur n'importe quel contrôle Windows capable d'afficher ou d'éditer du texte, tel qu'un TEdit, un TRichEdit ou un TMemo standard. Pour cela, nous pouvons créer une classe utilitaire indépendante qui implémente IDropTarget et s'enregistre auprès du handle de fenêtre (THandle) du contrôle cible.
type
TGestionnaireGlisserTexte = class(TObject, IUnknown, IDropTarget)
private
FHandleCible: THandle; // Le handle du contrôle Windows qui recevra le texte
FResultatValidation: HResult; // L'état de la validation du format de données
FFormatTexteDesire: TFormatEtc; // Le format de données de texte attendu
FCompteurRefExterne: Integer; // Compteur de références COM
protected
// Méthodes de l'interface IUnknown
function QueryInterface(const IID: TGUID; out P_Obj: Pointer): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
// Méthodes de l'interface IDropTarget
function DragEnter(const P_DataObject: IDataObject; P_CleEtat: Longint;
P_Position: TPoint; var P_EffetDepot: Longint): HResult; stdcall;
function DragOver(P_CleEtat: Longint; P_Position: TPoint;
var P_EffetDepot: Longint): HResult; stdcall;
function DragLeave: HResult; stdcall;
function Drop(const P_DataObject: IDataObject; P_CleEtat: Longint; P_Position: TPoint;
var P_EffetDepot: Longint): HResult; stdcall;
public
constructor Create(P_HandleCible: THandle);
destructor Destroy; override;
end;
{ TGestionnaireGlisserTexte implémentation }
constructor TGestionnaireGlisserTexte.Create(P_HandleCible: THandle);
begin
inherited Create;
FCompteurRefExterne := 0; // Initialiser le compteur de références
FHandleCible := P_HandleCible;
// Enregistrer cette instance comme cible de dépôt pour le handle spécifié
RegisterDragDrop(FHandleCible, Self);
end;
destructor TGestionnaireGlisserTexte.Destroy;
begin
// Révoquer l'enregistrement du glisser-déposer lors de la destruction de l'objet
RevokeDragDrop(FHandleCible);
inherited;
end;
function TGestionnaireGlisserTexte.DragEnter(const P_DataObject: IDataObject;
P_CleEtat: Integer; P_Position: TPoint; var P_EffetDepot: Integer): HResult;
begin
Result := E_FAIL;
FResultatValidation := E_FAIL;
if not Assigned(P_DataObject) then
Exit;
// Définir le format de texte désiré pour le transfert
with FFormatTexteDesire do
begin
{$IFDEF UNICODE}
cfFormat := CF_UNICODETEXT;
{$ELSE}
cfFormat := CF_TEXT;
{$ENDIF}
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
// Vérifier la capacité de l'objet de données à fournir le format texte
FResultatValidation := P_DataObject.QueryGetData(FFormatTexteDesire);
Result := FResultatValidation;
if Succeeded(Result) then
P_EffetDepot := DROPEFFECT_COPY
else
P_EffetDepot := DROPEFFECT_NONE;
end;
function TGestionnaireGlisserTexte.DragLeave: HResult;
begin
FResultatValidation := E_FAIL; // Réinitialiser l'état
Result := S_OK;
end;
function TGestionnaireGlisserTexte.DragOver(P_CleEtat: Integer; P_Position: TPoint;
var P_EffetDepot: Integer): HResult;
begin
// L'effet de dépôt est généralement déjà établi dans DragEnter, peu de logique ici
Result := S_OK;
end;
function TGestionnaireGlisserTexte.Drop(const P_DataObject: IDataObject; P_CleEtat: Integer;
P_Position: TPoint; var P_EffetDepot: Integer): HResult;
var
LMedium: TStgMedium;
LHandleGlobal: HGLOBAL;
begin
Result := E_FAIL;
if Failed(FResultatValidation) then
Exit; // Aucun format valide précédemment identifié
// Tenter d'obtenir les données dans le format texte
Result := P_DataObject.GetData(FFormatTexteDesire, LMedium);
if Failed(Result) then
begin
ReleaseStgMedium(LMedium);
Exit;
end;
// Verrouiller le handle global pour un accès mémoire
LHandleGlobal := HGLOBAL(GlobalLock(LMedium.hGlobal));
if LHandleGlobal = 0 then
begin
ReleaseStgMedium(LMedium);
Exit;
end;
try
// Envoyer le message WM_SETTEXT au contrôle cible pour y coller le texte
SendMessage(FHandleCible, WM_SETTEXT, 0, LParam(LHandleGlobal));
P_EffetDepot := DROPEFFECT_COPY;
finally
GlobalUnlock(LMedium.hGlobal); // Déverrouiller la mémoire
ReleaseStgMedium(LMedium); // Libérer les ressources du medium de stockage
end;
end;
function TGestionnaireGlisserTexte.QueryInterface(const IID: TGUID; out P_Obj): HResult;
begin
// Implémentation standard de QueryInterface
if GetInterface(IID, P_Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TGestionnaireGlisserTexte._AddRef: Integer;
begin
Result := InterlockedIncrement(FCompteurRefExterne);
end;
function TGestionnaireGlisserTexte._Release: Integer;
begin
Result := InterlockedDecrement(FCompteurRefExterne);
if Result = 0 then
Destroy; // L'objet est libéré lorsque plus aucune référence ne le maintient
end;
L'utilisation de cette classe est simple : il suffit de créer une instance en lui passant le handle du contrôle désiré.
var
LGestionnaire: TGestionnaireGlisserTexte;
begin
// Exemple d'activation du glisser-déposer de texte pour un Memo nommé Memo1
LGestionnaire := TGestionnaireGlisserTexte.Create(Memo1.Handle);
// Le gestionnaire restera actif tant que son compteur de références est > 0 ou qu'il est libéré manuellement.
// Pensez à gérer la durée de vie de LGestionnaire, par exemple en l'associant à la forme qui possède Memo1.
end;
Cette approche permet de doter n'importe quel contrôle Delphi doté d'un handle de fenêtre et capable de recevoir WM_SETTEXT de la capacité d'accepter du texte glissé, sans nécessiter de modification de sa classe d'oriigne.