Umíme to s Delphi: 101. díl – operace drag&drop (táhni a pusť)

Velké množství dnešních aplikací (včetně samotného systému Windows) podporuje operaci drag&drop, tedy táhni a pusť. Tato operace umožňuje komfortní přesun objektů (například kopírování souborů) z jednoho umístění do druhého pomocí myši. V článku se však kromě popisu drag&drop také dozvíte, jak to vypadá s offline verzi tohoto seriálu.
Ještě předtím, než se dostaneme k samotné náplni dnešního dílu, dovolím si stručnou odbočku týkající se offline verze tohoto seriálu.

Offline verze seriálu

Vzhledem k tomu, že vaše žádosti o offline verzi jsou čím dále četnější, jsem moc rád, že vám mohu po delší době sdělit příznivou zprávu: offline verze je na světě. Vytvořil ji pan Ladislav Toral a o jejím umístění informoval v diskusi pod předchozím (stým) dílem seriálu. Dovolím si však její existenci připomenout i na tomto místě, abych zvýšil pravděpodobnost, že se dostane ke každému, kdo o ni má zájem. Stáhnout si ji můžete již nyní, ale prosím vás, abyste ještě pár dní počkali – jednak proto, že v té době bude offline verze již kompletně dokončena (a odladěna) a pak také proto, že si ji budete moci stáhnout přímo ze serveru Živě ze sekce Soubory a nezahltíte tak web pana Torala. Za vaši trpělivost předem děkuji.

Za vytvoření offline verze patří panu Ladislavu Toralovi velký dík.

Zpět k dnešnímu článku

Nyní se můžeme vrátit k obsahu dnešního dílu. Článek se bude zabývat tím, co v mnoha dnešních aplikacích považujeme skutečně za standardní výbavu: operací drag and drop a jejím zařazením do naší vlastní aplikace. Populární operace drag and drop (táhni a pusť) umožňuje prostřednictvím myši provádět celou řadu činností, například kopírovat soubory z jedné složky do druhé, otvírat soubory jejich přetažením do cílové aplikace apod. I naše aplikace by si proto měly s touto operací poradit – přesněji řečeno, měly by jí umožňovat a podporovat.

Celou operaci si dnes vysvětlíme a rozebereme. V příštím dílu seriálu se budeme zabývat její praktickou realizací: napíšeme společně aplikaci umožňující drag and drop.

Drag and drop – k čemu to je?

Jak jsme uvedli výše, pomocí operace drag and drop usnadňujeme uživateli ovládání aplikací a zpříjemňujeme mu jeho práci. Tato operace 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 může v zásadě nabývat jedné ze dvou forem:

  • Pomocí drag and drop umožníme uživateli přesun celého ovládacího prvku. Uživatel si tedy může třeba přizpůsobit vzhled aplikace, například vzhled nejrůznějších posuvných lišt a tlačítek.
  • Pomocí drag and drop umožníme uživateli přesun položek z jednoho ovládacího prvku do jiného, například ze seznamu souborů do komponenty Memo, jak si později ukážeme prakticky.

Rozhodneme-li se implementovat operaci drag and drop v naší Delphi aplikaci, musíme se vypořádat se šesti základními tématickými okruhy:

  • 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í. V rámci operace budeme provádět různé činnosti podle toho, co vlastně táhneme.
  • Změna kurzoru myši v okamžiku probíhajícího tažení (operace „drag“). Uživatel je o probíhajícím tažení informován změněným kurzorem myši (zkuste si pomocí této operace přesunout třeba soubor z jedné složky do druhé v Průzkumníku a všimněte si změny kurzoru).

Těmito „tématickými okruhy“ se budeme nyní postupně zabývat v následujících podkapitolách.

Zahájení operace tažení (drag)

Prohlédnete-li si v Object Inspectoru kteroukoliv komponentu, snadno zjistíte, že je vybavena vlastností DragMode. (Hovoříme nyní samozřejmě pouze a výhradně o vizuálních komponentách, protože u komponent bez vizuální reprezentace nemá smysl hovořit o operaci drag and drop). Vlastnost DragMode, která je přítomna u všech vizuálních komponent, určuje, jakým způsobem může být iniciována (tedy zahájena) operace tažení. Tato vlastnost má dvě možné hodnoty: dmAutomatic a dmManual. Hodnota dmAutomatic znamená, že tažení začne automaticky v okamžiku, kdy uživatel stiskne tlačítko myši a kurzor je nad příslušnou komponentou. Hodnota dmManual naproti tomu znamená, že tažení začne až ve chvíli, kdy ošetříme příslušnou událost mouse-down.

Pokud bychom nastavili tuto vlastnost na dmAutomatic, může se stát, že by se „křížilo“ provádění operace tažení s běžným chováním, které by se mělo dít při stisknutí myši. Z toho důvodu je přednastavenou hodnotou dmManual.

Potřebujeme-li zahájit operaci tažení ručně, použijeme s výhodou metodu BeginDrag. Tato metoda je opět přítomna u všech vizuálních komponent majících vlastnost DragMode. Metoda BeginDrag má parametr Immediate typu Boolean. Jeho hodnota True znamená, že tažení začne okamžitě, naproti tomu hodnota False znamená, že chceme zahájit tažení až poté, co uživatel pohne myší o tolik pixelů, kolik je hodnota předaná ve druhém parametru – Treshold.

Je samozřejmé, že můžeme stanovit další podmínky povolující/zakazující započetí operace tažení. Takovými podmínkami může být například testování, které tlačítko myši bylo stisknuto. Tyto podmínky vložíme do příslušné obsluhy události OnMouseDown. Ukážeme si jednoduchý příklad: ošetříme událost OnMouseDown komponenty FileListBox. V této obsluze zahájíme operaci tažení pouze v případě, že byl stisknuto levé tlačítko myši.

procedure TForm1.FileListBox1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then  { otestujeme, bylo-li stisknuto levé tlačítko }
    with Sender as TFileListBox do  { Sender bude vystupovat jako TFileListBox }
      begin
        if ItemAtPos(Point(X, Y), True) >= 0 then  { došlo ke kliknutí tam, kde je nějaká položka? }
          BeginDrag(False);  { pokud ano, zahájíme její operace drag }
    end;

end;

Tolik k prvnímu kroku – k zahájení operace tažení. Vidíme tedy, že pokud nenastavíme vlastnost komponenty DragMode na dmAutomatic (tedy pokud ponecháme přednastavenou hodnotu dmManual), zahájíme tažení pomocí metody BeginDrag, a to typicky v obsluze nějaké události OnMouseDown. Metoda MeginDrag má parametr Immediate říkající, má-li se tažení zahájit hned nebo až po pohybu o stanověný počet pixelů; pokud tento parametr nastavíme na False, je druhým parametrem počet pixelů, o který je nutné posunout myš.

Tím se dostáváme k dalšímu výše uvedenému kroku: k akceptace (k přijetí) tažené položky do cílového umístění.

Přijetí tažené položky v cílovém umístění

Nyní jsme v situaci, v níž uživatel kamsi táhne nějakou položku (nebo ovládací prvek). V okamžiku, kdy uživatel něco táhne nad ovládacím prvkem X, tento ovládací prvek obdrží událost OnDragOver. V obsluze této události musí ovládací prvek X jasně říci, jestli je schopen a ochoten taženou položku „přijmout“ v případě, že by uživatele napadlo položku zrovna tady upustit. Kurzor myši (indikující v té době tažení) se změní a indikuje, zda ovládací prvek X je schopen přijmout tažený objekt.

Událost OnDragOver má parametr pojmenovaný Accept. Ten právě musíme nastavit na True (jsme-li připraveni tažený objekt přijmout) nebo na False (v opačném případě). Nastavení hodnoty do této vlastnosti způsobí (automatickou) změnu vzhledu kurzoru myši, takže se o ni nemusíme vůbec starat.

Událost OnDragOver má ještě další parametry, například parametr Source obsahující tažený objekt nebo parametry X a Y indikující aktuální umístění kurzoru myši. Na základě těchto parametrů je možné rozhodnout, můžeme-li tažený objekt přijmout. Podle toho pak nastavíme parametr Accept.

Ukážeme si opět jednoduchý příklad programového kódu, tentokráte samozřejmě obsluhu události OnDragOver. Komponenty DirectoryOutline je schopna přijmout tažené objekty jen v případě, že pocházejí z komponenty FileListBox (jinak řečeno – do adresáře je možné zkopírovat NĚCO pouze v případě, že to NĚCO je souborem).

procedure TForm1.DirectoryOutline1DragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source is TFileListBox then  {pochází-li tažený objekt z FileListBox}
    Accept := True                {můžeme je přijmout}
  else                            {jinak}
    Accept := False;              {jej přijmout nemůžeme}
end;

V tomto okamžiku jsme se prokousali druhým krokem, tedy přijetí tažené položky v cílovém umístění. Už víme, že každá komponenta schopná přijmout nějaké tažené objekty musí obsahovat obsluhu události OnDragOver. Tato událost vznikne v okamžiku, kdy je „přes“ komponentu tažen nějaký objekt. Na základě informací předaných v parametrech události OnDragOver rozhodneme, přijmeme-li případný puštěný objekt. Pokud ano, nastavíme v obsluze události OnDragOver parametr Accept na True, v opačném případě jej nastavíme na False. Kurzor myši se změní automaticky v závislosti na hodnotě vlastnosti Accept.

Dostáváme se ke třetímu kroku, s nímž si musíme poradit. Tímto krokem je ošetření operace drop, tedy stanovení toho, co se má stát po puštění taženého objektu.

Operace drop – puštění taženého objektu

Pokud ovládací prvek X indikuje, že je schopen přijmout právě tažený objekt (viz minulá podkapitola), je zřejmé, že uživatel může objekt nad ovládacím prvkem X pustit. Je tedy logické, že musíme ošetřit, co se po puštění objektu stát.

K tomuto účelu slouží událost OnDragDrop, kterou jsou opět vybaveny všechny komponenty, o nichž jsme hovořili v předchozích podkapitolách. Událost OnDragDrop je generována v okamžiku, kdy uživatel „upustí“ nad příslušným ovládacím prvkem tažený objekt. Stejně jako v případě události OnDragOver, i v případě události OnDragDrop máme k dispozici několik parametrů, z nichž lze vyčíst podrobnější informace o taženém objektu a o pozici kurzoru myši v okamžiku upuštění.

Opět si uvedeme jednoduchou ukázku zdrojového kódu, z něhož bude patrný způsob práce s událostí OnDragDrop. V příkladu je ošetřena událost OnDragDrop komponenty DirectoryOutline, v níž otestujeme, pochází-li puštěný objekt z komponenty FileListBox; pokud ano, provedeme přesun taženého souboru do cílového adresáře (nad kterým byl tažený soubor puštěn).

procedure TFMForm.DirectoryOutline1DragDrop(Sender, Source: TObject; X,
  Y: Integer);
begin
  if Source is TFileListBox then  {pochazi-li tazeny objekt z FileListBox}
    with DirectoryOutline1 do    {presuneme jej do adresare, nad nimz byl pusten}
      ConfirmChange(`Move`, FileListBox1.FileName, Items[GetItem(X, Y)].FullPath);
end;

V tomto okamžiku jsme dokončili další krok – ošetření puštění taženého objektu. Už tedy víme, že je-li komponenta schopna přijmout tažený objekt, ošetříme její událost OnDragDrop. V obsluze této události se přesvědčíme, že objekt skutečně chceme (a umíme) přijmout; v kladném případě zjistíme potřebné informace z parametrů události a objekt přijmeme.

Dostáváme se ke čtvrtému kroku, kterým je ukončení operace drag.

Ukončení operace drag (ukončení tažení objektu)

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. Tato zdrojové komponenta tedy může v závislosti na informacích z události OnEndDrag provést nějakou činnost (například vypsat hlášení, aktualizovat svůj obsah apod.). Pokud tedy chceme, aby se komponenta A, v níž započalo tažení, mohla dozvědět o jeho ukončení (a také výsledku), ošetříme událost OnEndDrag komponenty A.

Událost OnEndDrag má čtyři parametry, přičemž zřejmě nejdůležitější je parametr Target. Ten totiž indikuje komponentu, v níž tažení úspěšně skončilo (jinak řečeno určuje komponentu, která přijala tažený objekt). Pokud má parametr Target hodnotu nil, znamená to, že tažený objekt nebyl přijat žádnou komponentou (že byl tedy puštěn kdesi „cestou“ nad komponentou, která jej nepřijala). Událost OnEndDrag kromě toho obsahuje souřadnice, na nichž došlo k puštění (resp. přijetí) taženého objektu.

Ukážeme si opět jednoduchý a krátký příkládek: komponenta FileListBox se z události OnEndDrag dozví, byl-li tažený soubor úspěšně „doručen“ do nějaké cílové komponenty. Pokud ano, obnoví svůj obsah pro případ, že soubor byl přesunut.

procedure TFMForm.FileListBox1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  if Target <> nil then FileListBox1.Update; {byl-li soubor přetažen, obnovíme svůj obsah}
end;

V tomto okamžiku jsme dokončili čtvrtý krok, kterým bylo dokončení operace tažení. Dozvěděli jsme se, že zdrojová komponenta (tj. komponenta, v níž tažení započalo) obdrží události OnEndDrag v okamžiku, kdy byl tažený objekt upuštěn – ať již úspěšně (byl přijat cílovou komponentou) nebo neúspěšně (nebyl přijat nikým). Úspěch operace drag se zdrojová komponenta dozví z parametru Target, jehož hodnota nil znamená, že tažení neskončilo úspěšně.

Nyní nás čeká pátý krok – přizpůsobení operace drag and drop objektu, který je tažen.

Přizpůsobení operace drag and drop taženým objektům

Potřebujeme-li z nějakého specifického ovlivnit a modifikovat chování operace drag and drop, můžeme použít potomky třídy TDragObject. Standardní události OnDragOver a OnDragDrop indikují zdroj tažení (tedy komponentu, v níž operace započala)a souřadnice myši nad komponentou v okamžiku tažení. Pokud bychom například potřebovali nějaké další informace, můžeme odvodit potomka třídy TDragObject a přetížit její virtuální metody. To můžeme provést například v obsluze události OnStartDrag.

Poslední poznámka, kterou proneseme v souvislosti s operací drag and drop, spočívá ve změnách kurzoru myši při probíhajícím tažení.

Změna kurzoru myši při probíhajícím tažení

Pokud chceme modifikovat kurzor myši při probíhajícím tažení, můžeme použít vlastnost DragCursor. Touto vlastností jsou opět vybaveny všechny komponenty schopné tažení. Vlastnost DragCursor nemusíme samozřejmě nastavovat u všech komponent, přes které by tažení mohlo probíhat: stačí se zaměřit na zdrojovou komponentu, tedy na komponentu, v níž se tažení zahajuje. Nastavením vlastnosti DragCursor způsobíme změnu kurzoru myši v průběhu tažení.

Na závěr

V dnešním článku jsme podrobně rozebrali operaci drag and drop a popsali jsme všechny kroky, které budeme muset učinit při realizaci této operace v Delphi. Je asi docela dobře vidět, že díky Delphi není zajištění funkčního drag&drop příliš obtížné. Stačí ošetřit několik málo událostí a nastavit několik vlastností – a funkční drag and drop je na světě. Jsem si jist, že po přečtení dnešního článku už jste schopni implementovat svou vlastní aplikaci, která tuto operace bude umožňovat a podporovat.

Přesto se za týden budeme věnovat praktickým aspektům operace drag and drop a její implementaci. Ukážeme si ještě několik dalších úskalí, na které můžeme narazit připráci s drag and drop a vysvětlíme si, jak tyto potencionální problémy řešit.

Kromě toho společně vytvoříme jednoduchou aplikaci, která bude umožňovat práci prostřednictvím operace drag and drop.

    Diskuze (12) Další článek: Taiwanský Computex Živě...

    Témata článku: Software, Windows, Programování, Drag, Díl, Položka, Druhý případ, Opačný případ, Ovládací aplikace, Krok, Komponenta, Drop, Tito, Dokončené dílo, Offline, Toto

    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