Umíme to s Delphi: 102. díl – operace drag&drop prakticky

Dnešní článek je zaměřen na ryze praktické použití operace drag and drop (táhni a pusť). Vytvoříme společně jednoduchý textový editor umožňující zobrazovat obsah textových souborů jejich přetažením do komponenty Memo. Poznáme také několik úskalí, které při realizaci drag and drop můžeme potkat.
Před týdnem jsme si vysvětlili všechny nejdůležitější teoretické aspekty, které se týkají operace drag and drop (tj. táhni a pusť) a její realizace při programování v Delphi. Jen připomeňe, že implementací operace drag and drop můžeme značně usnadnit uživateli ovládání naší aplikace a zpříjemnit mu tak významným způsobem jeho práci. Operace drag and drop spočívá v „chycení“ objektu myší a v přesunutí tohoto objektu (za držení tlačítka myši) do jiného umístění. Operace drag and drop (která je k dispozici i při práci v samotném systému Windows) je často používána k běžným operacím typu kopírování souborů z jedné složky do druhé, otvírání souborů jejich přetažením do cílové aplikace apod.

Připomeňme také, že rozhodneme-li se přidat podporu operace drag and drop do naší aplikace, kterou píšeme v Delphi, měli bychom mít obecně povědomost o následujících šesti základních krocích:

  • Zahájení operace „drag“, tedy zahájení tažení.
  • Akceptace (tedy přijetí) tažené položky do cílového umístění.
  • Ošetření operace „drop“, tedy puštění tažené položky.
  • Ukončení operace „drag, tedy ukončení tažení položky.
  • Přizpůsobení celé operace „drag and drop“ objektu, který je předmětem tažení.
  • Změna kurzoru myši v okamžiku probíhajícího tažení (operace „drag“).

Teoretický popis výše uvedených kroků byl uveden v rámci předchozího, 101. dílu seriálu.

Tolik k zopakování základních informací o drag and drop. Nyní se můžeme směle pustit do Delphi a naprogramovat společně jednoduchou aplikaci umožňující díky metodě „táhni apusť“ komfortní ovládání. V rámci aplikace si také ukážeme několik častých úskalí, na něž bychom si měli při programování drag and drop aplikací dávat pozor.

Programujeme aplikaci s drag and drop

Vytvoříme společně aplikaci, která bude umožňovat vypisování textových souborů pomocí operace drag and drop (táhni a pusť). Uživateli bude zobrazen seznam souborů v komponentě FileListBox; uživatel může vybraný soubor v této komponentě „chytit“ pomocí myši a přetáhnout jej do komponenty Memo, ve které se soubor ihned zobrazí.

Při vytváření aplikace použijeme tedy komponenty DirectoryListBox a FileListBox pro zobrazení adresářové cesty a seznamu souborů v dané složce (komponenty jsou postaršího data a nacházejí se na záložce Win 3.1, pro naše účely však bohatě postačují) a dále komponentu Memo.

Budeme ošetřovat následující události:

  • OnCreate hlavního formuláře,
  • OnDragOver a OnDragDrop komponenty Memo1,
  • OnEndDrag komponenty FileListBox1.

Abychom se drželi teoretického schématu naznačeného před týdnem, zrealizujeme postupně každý ze šesti kroků, které byli v předchozím článku uvedeny (a které jsou shrnuty i v úvodu dnešního dílu).

Krok 1 – Zahájení operace drag (zahájení tažení)

Minule jsme si uvedli, že krok „zahájení tažení“ se obecně skládá z několika fragmentů, které musíme (a nebo nemusíme) vzít v úvahu. Jedná se především o:

  • vlastnost DragMode komponenty, v níž má tažení započít,
  • metodu BeginDrag komponenty, v níž má tažení započít,
  • událost, nejčastěji OnMouseDown komponenty, v níž má tažení započít.

Pomocí vlastnosti DragMode říkáme, má-li tažení začít samo při charakteristické práci s myší a nebo máme-li jej „zapnout“ ručně – obvykle pomocí události OnMouseDown a metody BeginDrag.

V našem případě bude tažení zahajováno výhradně z komponenty FileListBox, proto se nyní zaměříme právě na ní. Použijeme jednodušší variantu – zvolíme automatický start tažení, takže nastavíme hodnotu vlastnost DragMode komponenty FileListBox na dmAutomatic. Pokud vás zajímá, jak ručně zajistit začátek tažení, doporučuji vám předchozí díl seriálu, v němž bylo ruční zahájení (pomocí BeginDrag) ukázáno v programovém výpisu.

Jinak řečeno: začátek operace drag and drop probíhá v okamžiku, kdy uživatel stiskne levé tlačítko myši nad komponentou FileListBox. Abychom nemuseli ošetřovat událost OnMouseDown této komponenty ručně (s využitím funkce BeginDrag), nastavíme vlastnost DragMode komponenty FileListBox na dmAutomatic už v obsluze OnCreate formuláře:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ...
  FileListBox1.DragMode := dmAutomatic;
  ...
end;

Tím je v našem případě první krok hotov; můžeme se posunout dále – k přijetí tažené položky v cílovém umístění.

Krok 2 – přijetí tažené položky v cílovém umístění

Dejme tomu, že tažení bylo zahájeno. Situace je nyní taková, že uživatel kamsi táhne nějakou položku (konkrétně soubor z komponenty FileListBox). Jediná komponenta, která má v naší aplikaci přijímat tažené objekty, je Memo. Nyní se tedy budeme zabývat právě s ní.

V okamžiku, kdy uživatel něco táhne nad jakoukoliv vizuální komponentou, obdrží tato komponenta událost OnDragOver. Totéž samozřejmě platí i pro naše Memo, proto musíme ošetřit tuto událost. V její obsluze musíme jasně říci, zda je komponenta schopna a ochotna taženou položku přijmout v případě, že ji svéhlavý a nevypočitatelný uživatel zrovna upustí.

Ošetříme tedy událost OnDragOver komponenty Memo. Minule jsme si řekli, že základní činností, kterou bychom měli provést v obsluze OnDragOver, je nastavení (výstupního) parametru Accept: hodnota True znamená, že objekt přijímáme. V následujícím programovém výpisu si prosím pouze povšimněte přiřazení Accept := Source is TFileListBox; (ríkáme, že hodnota Accept bude nastavena na True v případě, že platí podmínky Source is TFileListBox, tedy v případě, že tažený objekt pochází z komponenty FileListBox, tedy vlastně v případě, že tažený objekt je souborem):

procedure TForm1.Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  // jakmile je objekt z FileListBoxu tazen nad Memo, musime oznamit, ze jej prijimame
  Accept := Source is TFileListBox;
end;

Toť vše k druhému kroku. Posouváme se dál, přesněji řečeno k puštění taženého objektu.

Krok 3 – puštění taženého objektu

Zopakujme, že uživatel táhne nějaký objekt, a je-li zrovna nad komponentou Memo, tato komponenta prohlašuje: „Ano, já jsem ochotna ten objekt přijmout.“ Dalším krokem je tedy logicky ošetření této situace – tedy stanovení, co se stane poté, co uživatel (nad Memo) objekt upustí.

Jak jsme si uvedli posledně, k tomuto účelu slouží událost OnDragDrop komponenty Memo. Událost OnDragDrop bude generována v okamžiku, kdy uživatel „upustí“ nad Memo tažený objekt.

Co se následně má stát? Musíme nejprve pro jistotu zkontrolovat, že taženým objektem je opravdu soubor. Je-li tomu tak, načteme soubor z disku (můžeme použít třeba LoadFromFile) a zobrazíme jej v komponentě Memo.

Ošetříme tedy událost OnDragDrop komponenty Memo:

procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  // pokud byl nad Memo upusten objekt z FileListBox, nacteme a zobrazime prislusny soubor
  if Source is TFileListBox then
    Memo1.Lines.LoadFromFile(FileListBox1.FileName);

end;

Tato část zdrojového kódu si zřejmě zaslouží stručný komentář. Obsluha události je nesmírně krátká, přesto funguje správně. Obsahuje dvě zdánlivě nesourodé a nijak nesouvisející věci: kontrolu, zda je tažený objekt z FileListBox a následně načtení souboru, jehož cesta a jméno se skrývá ve vlastnosti FileListBox.Filename. Vlastnost FileName totiž obsahuje cestu a jméno označeného (vybraného) souboru v komponentě FileListBox. Využíváme zde toho, že aby mohl být soubor tažen, musí být nejprve označen; dále využíváme toho, že v průběhu tažení nemohl být označen jiný soubor. Proto můžeme při „puštění“ objektu jednoduše načíst ten soubor, který je zrovna v komponentě FileListBox označený.

Dostáváme se pomalu k závěru celých manévrů – totiž k ukončení tažení.

Krok 4 – ukončení tažení (ukončení operace drag)

Opět vyjdeme z toho, co jsme si o ukončení tažení prozradili minule, totiž že operace tažení může skončit ve dvou případech:

  • tažená položka je úspěšně puštěna nad komponentou, která je schopna ji přijmout;
  • tažená položka je uvolněna nad komponentou, která ji nepřijme.

V obou případech je generována událost OnEndDrag, která je zaslána „startovní“ komponentě, tedy komponentě, v níž tažení započalo. V našem případě se bude tedy jednat o komponentu FileListBox. Ošetříme tedy událost OnEndDrag komponenty FileListBox:

procedure TForm1.FileListBox1EndDrag(Sender, Target: TObject; X,
  Y: Integer);
begin
  // pro demonstraci vypiseme hlasku o pripadnem neuspesnem dokonceni operace
  if Mouse.IsDragging then          // pokud vubec zacala operace drag&drop
    if (Target <> nil) then
      ShowMessage(`Soubor byl úspìšnì pøetažen do komponenty.`)
    else
      ShowMessage(`Soubor se nepodaøilo pøetáhnout. `);

end;

V uvedeném zdrojovém kódu vás možná trochu překvapuje testování vlastnosti IsDragging globálního objektu Mouse. Zde právě leží jeden z nejčastějších problémů při programování drag and drop. Vše si vysvětlíme.

Standardní chování operace drag and drop je totiž takové, že tato operace je inicializována vždy po stisknutí levého tlačítka myši. Už tento okamžik totiž může být „zahájením“ tažení – stačí, aby pak uživatel nechal levé tlačítko stisknuté a začal myší pohybovat. Proto už v okamžiku stisku tlačítka musí dojít k inicializaci operace drag and drop (a kromě jiného se také generuje událost OnStartDrag). Inicializace této operace je však něco jiného než její spuštění. K tomu totiž nemusí dojít ihned, ale např. až po posunutí kurzoru myši o předem definovaný počet pixelů. V okamžiku vyvolání události OnEndDrag se tedy může stát, že žádná operace drag&drop nebyla vůbec spuštěna (byla pouze inicializována stiskem levého tlačítka myši). To, jestli operace byla spuštěna (přesněji jestli běží), můžeme zjistit z vlastnosti globálního objektu Mouse.IsDragging, která bude mít v tom případě hodnotu True. A přesně toto testování provádíme v obsluze události OnEndDrag – vyhneme se tak vypisování hlášek o tažení, které nikdy nenastalo.

Na tomto místě bych pouze rád poznamenal, že vlastnost IsDragging je k dispozici až v Delphi od verze 6.

Zopakujme raději ještě jednou, že událost OnEndDrag je generována při ukončení operace drag and drop (ať již úspěšném – tažený objekt byl přijat – a nebo neúspěšném – tažený objekt nebyl přijat). Důležitý je zde parametr Target, který v případě úspěchu obsahuje cílovou komponentu, v případě neúspěchu nil.

Hotovo!

Jsme hotovi! Operace drag and drop funguje, zkuste si spustit aplikaci a přetahovat textové soubory do komponenty Memo. Funguje?

Ještě předtím, než si ukážeme kompletní zdrojový kód, bych rád věnoval jednu poznámku obsluze události OnCreate hlavního formuláře. V ní totiž provádíme několik zajímavých činností, které stojí za pozornost:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := `Textový prohlížeè souborù`;
  Memo1.Clear;
  // pri zmene adresare v DirectoryListBox se automaticky zmeni obsah FileListBox
  DirectoryListBox1.FileList := FileListBox1;
  FileListBox1.DragMode := dmAutomatic;
  FileListBox1.Mask := `*.txt`;
  Mouse.DragImmediate := False;
end;

Ponecháme nyní stranou nastavení titulku formuláře a vymazání obsahu komponenty Memo. Zaměříme se na další příkazy. Příkazem

  DirectoryListBox1.FileList := FileListBox1;

říkáme, že obsah adresáře aktuálně vybraného v DirectoryListBox se má automaticky zobrazovat v komponentě FileListBox. Provedli jsme tedy jakési primitivní „navázání spojení“ mezi komponentami DirectoryListBox a FileListBox.

Nastavením vlastnosti DragMode jsme se již zabývali; nastavením vlastnosti Mask zajistíme, že ve FileListBox se budou zobrazovat pouze soubory s příponou TXT.

A k čemu slouží nastavení vlastnosti globálního objektu Mouse.DragImmediate na False? Toto nastavení zajistí, že se operace drag&drop nespustí ihned po stisku tlačítka myši, ale až poté, co kurzor myši urazí vzdálenost udanou ve vlastnosti Mouse.DragThreshold v pixelech (standardně nastaveno na 5).

Klepněte pro větší obrázek

Zdrojový kód

Tolik k popisu dnešní jednoduché aplikace. Na úplný závěr si uvedeme kompletní zdrojový kód, protože v předchozích odstavcích je uveden s přílišnou fragmentací :-)

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, FileCtrl;

type
  TForm1 = class(TForm)
    FileListBox1: TFileListBox;
    DirectoryListBox1: TDirectoryListBox;
    Memo1: TMemo;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure FileListBox1EndDrag(Sender, Target: TObject; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := `Textový prohlížeč souborů`;
  Memo1.Clear;
  // pri zmene adresare v DirectoryListBox se automaticky zmeni obsah FileListBox
  DirectoryListBox1.FileList := FileListBox1;
  FileListBox1.DragMode := dmAutomatic;
  FileListBox1.Mask := `*.txt`;
  Mouse.DragImmediate := False;
end;

procedure TForm1.Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  // jakmile je objekt z FileListBoxu tazen nad Memo, musime oznamit, ze jej prijimame
  Accept := Source is TFileListBox;
end;

procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  // pokud byl nad Memo upusten objekt z FileListBox, nacteme a zobrazime prislusny soubor
  if Source is TFileListBox then
    Memo1.Lines.LoadFromFile(FileListBox1.FileName);

end;

procedure TForm1.FileListBox1EndDrag(Sender, Target: TObject; X,
  Y: Integer);
begin
  // pro demonstraci vypiseme hlasku o pripadnem neuspesnem dokonceni operace
  if Mouse.IsDragging then          // pokud vubec zacala operace drag&drop
    if (Target <> nil) then
      ShowMessage(`Soubor byl úspěšně přetažen do komponenty.`)
    else
      ShowMessage(`Soubor se nepodařilo přetáhnout. `);

end;

end.

Na závěr

Dnešní článek byl zaměřen na vytvoření jednoduché aplikace umožňující pomocí operace drag and drop zobrazovat obsah textového souboru. Protože operace srag and drop skýtá ještě několik dalších zajímavých oblastí, jejichž prozkoumání by stálo za úvahu, s největší pravděpodobností se za týden k tomuto tématu ještě vrátíme.

Diskuze (3) Další článek: Přehled užitečných programů uplynulého týdne – 39. týden

Témata článku: Software, Programování, Drop, Předchozí odstavec, Komponenta, Častá operace, Source, Vybraný soubor, Díl, Drag, Uvedený krok, Jednoduchá cesta, Fragment, Celý manévr, Filename, Krok, TDI

Určitě si přečtěte


Aktuální číslo časopisu Computer

Test 6 odolných telefonů a 22 powerbank

Srovnání technologií QLED a OLED

Měřte své sportovní výkony

Sady pro chytrou domácnost