CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
Delphi je vývojovým nástrojem, jehož první verze byla vypuštěna firmou Borland v roce 1994. Dnes – o sedm let později – patří Delphi k nejpoužívanějším programovacím prostředkům. Zajímá-li i vás, čím Delphi tak magicky přitahuje vývojáře na celém světě, setrvejte u tohoto seriálu, který vám o Delphi prozradí vše podstatné.
Delphi je vývojovým nástrojem, jehož první verze byla vypuštěna firmou Borland v roce 1994. Dnes – o sedm let později – patří Delphi k nejpoužívanějším programovacím prostředkům. Přestože je založeno na jazyce Pascal, jenž je sám již poněkud za zenitem, jeho popularita snad stále roste. Zajímá-li i vás, čím Delphi tak magicky přitahuje vývojáře na celém světě, setrvejte u tohoto seriálu, který vám o Delphi prozradí vše podstatné.
Pro koho je tento seriál
Seriál není určen pro úplné začátečníky. Za minimální považuji tuto vaši „konfiguraci“:
Co je Delphi
Tento odstavec mohou směle přeskočit všichni, kteří již Delphi někdy viděli. Ostatním prozradím, že Delphi je komplexní vizuální programovací prostředí pro operační systém Windows. Jeho hlavní síla spočívá především ve významu slova vizuální. Vzhled, rozhraní, prostředí vaší aplikace, které byste v Turbo Pascalu nebo v podobném programovacím jazyce tvořili týden, zvládnete vytvořit v Delphi za hodinu.
Vysvětlení tohoto zdánlivého zázraku je dvojí:
Co si představit pod kouzelným slůvkem „vizuální“, kterým se neustále oháním? Nejlepší představu si zřejmě uděláte z obrázku. Největší šedá plocha se nazývá formulář. Na něj myší umístíte tlačítka, nápisy, dialogy, textová pole a veškeré další prvky. Nazýváme je komponenty. Tím vytvoříte vzhled aplikace.
Na zvolenou komponentu budeme nahlížet za dvou pohledů.
Proč Delphi
Již v době, kdy bylo vytvořeno první Delphi, si programátor mohl vybírat z mnoha vývojových nástrojů. Zdůrazňuji to proto, že totéž platí i dnes. Rozdíl je však v tom, že slovo „mnoho“ dnes označuje mnohonásobně větší počet vývojových nástrojů a pomůcek. Programátoři tedy musejí mít mnoho pádných důvodů, proč preferovat právě Delphi. Vyjmenovat valnou většinu je obtížné, vyjmenovat všechny je nemožné. Přesto se lze alespoň pokusit dobrat se podstatných bodů:
firma Borland je známým výrobcem spolehlivých, osvědčených kompilátorů pro Pascal a C;
jednoduchý návrh vizuálního prostředí aplikace s využitím vlastností operačního systému;
objektově orientovaný přístup;
podpora databází, v posledních verzích velmi podstatná a v dnešní době velmi ceněná (nezbytná);
neobsáhnutelné množství integrovaných nástrojů (namátkou TeamSource pro správu činností);
významná podpora Internetu (např. integrovaná komponenta WebBrowser);
možnost vytvářet vlastní komponenty;
a mnoho dalších důvodů
Co je nového
Již jsem zmínil, že poslední vydanou verzí Delphi (informace ze dne 2.3.2001) je verze 5. Verze 6 se podle Richarda Kubáta, ředitele Borlandu.cz, připravuje pro druhé až čtvrté čtvrtletí roku 2001. Pátá verze existuje ve třech edicích - Standard, Professional a Enterprise (řazeno od nejořezanější). Abyste si mohli vybrat edici, která vám bude nejvíce vyhovovat, podívejme se na hlavní rozdíly:
Pozn.: ceny jsou uvedeny např. na https://shop.inprise.cz/shop/template_category.php
Podrobnější seznam dostupných komponent jednotlivých verzí viz následující tabulka:
Součást |
Standard |
Professional |
Enterprise |
Rychlý vývoj aplikací (RAD) |
částečně |
úplně |
úplně |
Standardní komponenty pro RAD | |||
Objektově orientovaná architektura |
částečně |
úplně |
úplně |
Integrované vývojové prostředí |
částečně |
úplně |
úplně |
Podpora vývoje vícejazyčných aplikací |
částečně |
úplně |
úplně |
Nástroje Translation Suite |
ne |
ne |
úplně |
Nástroje CodeInsight |
částečně |
úplně |
úplně |
ActiveInsight |
částečně |
úplně |
úplně |
Ladicí nástroje |
částečně |
částečně |
úplně |
Visual Component Library |
částečně |
úplně |
úplně |
Corba |
ne |
ne |
úplně |
Podpora Oracle8 |
ne |
ne |
úplně |
ActiveX |
ne |
úplně |
úplně |
Vývoj databázových aplikací |
ne |
částečně |
úplně |
Na závěr této kapitoly poznámku ke kompatibilitě verze 5 s předchozími verzemi: platí, že aplikaci napsanou ve starší verzi Delphi v „pětce“ otevřete, ale je zde právě to jedno významné ale. Pátá verze obsahuje poměrně velké množství novinek a některé mohou (negativně) změnit chování vaší aplikace. Nejde o základní věci – budete-li mít „obyčejnou“ aplikaci, která bude obsahovat „obyčejné“ komponenty, nemusíte se ničeho bát. Jde-li ovšem o „vypečenější“ projekt, můžete se dostat do problémů. Většina jich je popsána v nápovědě v páté verzi, takže se nemusíte bát, že byste se dostali do slepé uličky, považuji ovšem za slušné na toto úskalí upozornit. Problémy mohou způsobit změny v definici datových typů (např. HRESULT byl definován jako 32bitový integer bez znaménka, nyní je to 32bitový integer se znaménkem), změny parametrů funkcí (do funkce CustomDrawItem přibyl parametr) apod.
Konec začátku
Tolik na úvodní stručné seznámení s vývojovým prostředím Delphi. V příštích dílech seriálu se už podíváme na konkrétní ukázku programu, na základní prvky aplikací (tj. komponenty), vysvětlíme si, jak vytvořit aplikaci a kterak reagovat na události.
Proč zdravit svět?
Pokud jste někdy otevřeli libovolnou učebnici programování, s velkou pravděpodobností jste kdesi na začátku spatřili vytvoření „programu Ahoj, světe!“. Je to tradice, ukázat na tomto triviálním příkladu možnosti, způsob práce a vůbec styl programování daného jazyka. My se tohoto zvyku přidržíme.
Volba komponent
Vytvoření první aplikace rozložíme do posloupnosti jednoduchých kroků.
Nastavení vlastností komponent
Nastavení reakcí na události
Vlastní programování
Uložit, přeložit, spustit
Gratuluji!
Vytvoření první aplikace máme úspěšně za sebou. Podíváte-li se do adresáře, do kterého jste ukládali zdrojové soubory (Okno1.pas a Ahoj.dpr), uvidíte tam mimo jiné soubor Ahoj.exe. Ten můžete kdykoliv (ve Windows) spustit a kochat se pohledem na svou krásnou, funkční první aplikaci.
Palety komponent
Komponenty, které chceme použít, vybíráme z tzv. palety komponent.
Palet je více a každá z nich shromažďuje komponenty určitého typu. Spustíte-li Delphi, budou palety komponent standardně zobrazeny v pravé horní části obrazovky (viz obrázek). Seznam dostupných komponent můžete získat (a žádanou komponentu zvolit) také v hlavním menu (View – Component List).
Paleta Standard
Nyní se podíváme na jednotlivé komponenty. Nelze popsat všechny, vybereme jen ty nejpodstatnější. Paleta Standard obsahuje nejzákladnější, nejpoužívanější, nejjednodušší komponenty:
Další palety
Z dalších palet vybereme opravdu jen reprezentativní vzorky.
Můžete namítnout, že jsem opomenul množství důležitých a zajímavých komponent. Vzhledem k rozsahu článku a k jeho zaměření na začátečníky ale nelze jinak. Slibuji, že ke spoustě dalších komponent se dostaneme v dalších kapitolách.
Vyzkoušejte sami
Zkusíte-li si vytvořit novou aplikaci a umístit na její formulář několik komponent, ať už výše popsaných nebo úplně jiných, zjistíte zajímavý fakt. Některé komponenty se na formuláři zobrazí ve své konečné podobě (Label, Button, Edit…), některé ale jsou reprezentovány malým čtverečkem. Může jít například o komponenty, které budou za běhu aplikace vypadat pokaždé jinak, nebudou vidět vůbec, případně vytvoří vlastní nové okno. Příkladem neviditelné komponenty je třeba časovač (Timer), který se navenek nijak neprojevuje, ale v aplikaci jej máte a můžete využívat všech jeho funkcí (přesněji metod).
Některé komponenty mají svůj „Designer“, který usnadní nastavení vzhledu a vlastností. Příkladem je již zmíněné MainMenu, ale také PopupMenu nebo StatusBar. Pro ilustraci práce s touto pomůckou si ukážeme, jak navrhnout stavový řádek rozdělený do čtyř částí.
Vytvoření stavového řádku
Umístěte kamkoliv na formulář komponentu StatusBar. Sama se „přichytí“ ke spodnímu okraji formuláře; to je způsobeno nastavením vlastnosti Align (zarovnání), o které si povíme v příštím díle. Nyní na stavový řádek dvakrát klikněte a otevře se vám okno, jehož vzhled závisí na používané verzi Delphi. Na obrázku vidíte okno verze 5.
Nyní čtyřikrát klikněte na jedinou aktivní ikonku (ve verzi 3 na tlačítko Add). Všimněte si, že stavový pruh se okamžitě rozdělil na čtyři panely. Představte si, že programujete grafický editor a že byste chtěli mít ve stavovém pruhu panely s těmito informacemi:
Aktuální obsahy jednotlivých panelů budete muset samozřejmě nastavovat za běhu programu, při návrhu je však dobré nastavit hodnoty, které předpokládáte při prvním spuštění aplikace (tedy jakési „default“ hodnoty). Řekněme, že budete mít standardně předvolen soubor Nový.bmp, plnou čáru, černou barvu a font Times New Roman.
V okně Editing StatusBar1.Panels postupně klikejte na názvy jednotlivých panelů (TstatusPanel). V Object Inspectoru se budou zobrazovat vlastnosti jednotlivých panelů, nikoliv celého stavového řádku. Nastavením vlastnosti Text docílíte vložení hodnot (viz obrázek).
Aby náš stavový pruh vypadal opravdu profesionálně, je třeba změnit šířky jednotlivých panelů. To provedete změnou vlastnosti Width (šířka) jednotlivých panelů. Rozměry se zadávají v pixelech. Název souboru může být poměrně dlouhý, proto nastavte minimálně 300 pixelů (ale i to bude pravděpodobně málo). Pro styl a barvu snad postačí 80 pixelů. Šířku posledního panelu nastavovat nemusíte; panel bude vždy široký přesně do konce okna (nezměníte-li nastavení zmíněné vlastnosti Align celého StatusBaru). Výsledný vzhled stavového pruhu viz obrázek.
Všimněte si (ale to už je trochu „vyšší matematika“), že k jednotlivým panelům budete přistupovat pomocí indexů. Indexace začíná od nuly, takže změníte-li v programu barvu na zelenou, musíte také provést následující přiřazení:
StatusBar1.Panels[2].Text := ‘Zelená’;
Co jsou to vlastnosti
Obecně řečeno, vlastnosti jsou atributy tříd, které určují stav objektu (v našem případě komponenty) a jeho chování. Neznáte-li pojmy jako atribut, třída či objekt, nezoufejte, při práci v Delphi se bez nich zpočátku hravě obejdete. Během tvorby aplikace můžeme zjistit (a modifikovat) hodnotu vlastnosti pomocí Object Inspectoru (viz obrázek). Za běhu aplikace je možné k vlastnostem přistupovat pomocí čtení a zápisu zprostředkovanými jednoduchým programovým kódem.
V Object Inspectoru nalezneme pouze ty vlastnosti, které jsou přístupné v době návrhu aplikace. Kromě nich ovšem existují tzv. run-time vlastnosti (vlastnosti běžící komponenty). Ty jsou přístupné až při běhu aplikace, nikoliv v době návrhu. Aby nebylo teorie málo, rozlišujeme dále read-only a write-only vlastnosti (pouze ke čtení, resp. k zápisu). Dva posledně zmíněné typy jsou obyčejně přístupné jen za běhu aplikace. Jakého typu je vlastnost, kterou právě chcete použít, zjistíte vždy v nápovědě. V dalším textu této kapitoly se budeme zpravidla (avšak nejen) zabývat vlastnostmi přístupnými v době návrhu.
Poznámky pro pokročilé uživatele
Společné vlastnosti
Již jsem zmínil, že mnohé vlastnosti jsou společné pro většinu komponent. Nyní se na ty nejdůležitější podíváme.
Jméno a titulek komponenty
Každá komponenta v Delphi má své jméno (vlastnost Name). Pokud nepřidělíte jméno ručně, Delphi pojmenuje komponentu automaticky. Názvy jsou tvořeny složením názvu komponenty (např. Button) a pořadového čísla, např. Button5. Jméno komponenty musí být jedinečné v rámci vlastníka. Zjednodušeně řečeno: můžete mít v aplikaci dva formuláře, z nichž každý obsahuje komponentu stejného jména.
Naproti tomu titulek komponenty (vlastnost Caption) může být zcela libovolný, může obsahovat mezery a může být stejný jako titulky jiných komponent, bez ohledu na vlastníka. Titulek se objeví např. v záhlaví okna (u komponenty Form) nebo přímo na těle tlačítka (Button). Titulkem nelze opatřit zdaleka všechny komponenty, ale jen ty, u kterých to má smysl (dovedete si představit titulek u scrollbaru?).
Velikost a poloha komponenty
Polohu komponenty udávají vlastnosti Left a Top. Souřadnice se nevztahují k celé obrazovce, ale ke klientské oblasti komponenty předka. Znamená to, že umístíte-li na formulář tlačítko, budou se souřadnice tlačítka vztahovat k levému hornímu rohu formuláře. Dáte-li ale na formulář nejprve panel a na něj tlačítko, vztažným bodem pro tlačítko bude levý horní roh panelu.
Velikost komponenty je určována vlastnostmi Height a Width (výška, šířka). Stejně jako v případě polohových vlastností je velikost udávána v pixelech; formulář veliký 1024x768 bude (při stejném rozlišení monitoru) zabírat celou plochu obrazovky.
U některých komponent můžete nastavit, jakým způsobem budou „přichyceny“ k formuláři nebo panelu. Tato vlastnost se jmenuje Align. Většina jejích možných hodnot vás nepřekvapí (alBottom, alTop, alLeft, alRight, alNone), zajímavá je hodnota alClient. Znamená, že komponenta se bude „rozprostírat“ v celé klientské oblasti.
Dostupnost komponenty
Pomocí dvou základních vlastností lze řídit působení uživatele na komponentu. Nastavíte-li hodnotu vlastnosti Enabled na false, komponentu zakážete. V době návrhu nebude mít toto nastavení žádný vizuální efekt, v době běhu aplikace bude komponenta zpravidla šedá, což uživateli naznačí, že je nedostupná (viz obrázek).
Chcete-li být radikálnější, můžete komponentu úplně skrýt, buď použitím příslušné metody nebo nastavením vlastnosti Visible na false.
Poznámka pro pokročilé uživatele
Přečtením vlastnosti Visible ještě nezjistíte, zda je komponenta opravdu viditelná. Je-li totiž skrytý vlastník této komponenty, nic jí nebude platná kladná hodnota její vlastnosti Visible. Z toho důvodu existuje vlastnost Showing, která je typu run-time a read-only. Přečtením její hodnoty zjistíte, zda je prvek pro uživatele v daném okamžiku skutečně viditelný.
Vlastnost Tag
Tag (v překladu přívěsek) je velmi zvláštní vlastnost, protože její nastavení nemá vůbec žádný efekt. Jde pouze o dodatečné paměťové místo, kam lze ukládat různé, libovolné uživatelské hodnoty. Standardně lze do přívěsku uložit hodnotu LongInt (což je celé číslo v rozsahu –2147483648 až 2147483647), ale pomocí přetypování do něj můžete vložit ukazatel nebo cokoliv jiného o velikosti 4 bajty.
Barva a typ písma komponenty
Vlastnosti Color a Font se užívají k úpravě vzhledu komponenty. Samotná vlastnost Color obvykle udává barvu pozadí komponenty. Atribut Color existuje také pro typ písma a mnohých dalších grafických prvků. Název barvy je možné specifikovat pomocí konstant clXXX, kde za XXX dosadíte buďto název barvy (anglicky) nebo název barvy, kterou Windows používají pro systémové prvky. Příklady viz tabulka.
Označení |
Význam |
clGreen |
zelená barva |
clBtnFace |
barva povrchu tlačítka |
clCaptionText |
barva textu v titulním pruhu aktivního okna |
clHighlight |
barva pozadí vybraného textu |
clWindow |
barva pozadí oken |
Další možností je specifikovat barvu číslem.
Poznámka pro pokročilé uživatele
Většina komponent obsahuje také vlastnosti ParentColor a ParentFont udávající, zda má daný prvek používat stávající typ písma a barvu svého vlastníka (kterým je často formulář). Pomocí těchto vlastností můžete změnit písmo každého řídicího prvku formuláře pouhým nastavením vlastnosti Font vlastního formuláře.
Plovoucí nápověda
Velkou výhodou každé aplikace je, když se uživateli zobrazí plovoucí nápověda, jestliže nechá kurzor chvíli bez pohybu nad některým prvkem (např. dozví-li se, co se přesně stane po stisku daného tlačítka). K tomu slouží vlastnost Hint, do které napíšete text nápovědy, a vlastnost ShowHint, která říká, má-li tato nápověda být zobrazena (např. pokročilý uživatel může zobrazování této nápovědy potlačit volbou v menu a vy jen nastavíte ShowHint, příp. ParentShowHint.
Tabulátor
Pokud má vaše aplikace více ovládacích prvků (což má zpravidla vždy), je dobré, aby „inteligentně“ fungovala klávesa Tab. Vytvoříte-li okénko s dotazem typu „opravdu chcete skončit?“, měl by stačit stisk Enteru ke kladné odpovědi. Stisk Tab a Enteru by naopak měl vyvolat zápornou odpověď. Toho lze docílit nastavením vlastností TabOrder a TabStop. TabStop říká, zda se vůbec na danou komponentu lze dostat tabulátorem, a TabOrder říká, kolikátá v pořadí bude. Počítá se od nuly.
Co jsou události
Když uživatel provede nějakou činnost s komponentou, například na ni klikne, generuje tato komponenta odpovídající událost. Někdy jsou události také generovány operačním systémem, bez zjevného uživatelova přispění.
Poznámka pro pokročilé uživatele:
Z technického hlediska jsou události Delphi vyvolávány poté, co dojde k obdržení zprávy operačního systému. Počet těchto událostí nemusí vždy nutně odpovídat počtu zpráv.
Podobně, jako existuje skupina vlastností, která je společná více (případně všem) komponentám, nalézáme v Delphi události, jež jsou dostupné více (všem) komponentám. Krátký popis nejpoužívanějších z nich (a případné tipy) viz následující tabulka:
Událost |
Kdy k ní dojde |
Poznámky |
OnChange |
při změně objektu (nebo jeho obsahu) |
Používána často u komponent typu Edit, Memo. Souvisí s read-only run-time vlastností Modified, která říká, byl-li obsah změněn. |
OnClick |
při kliknutí levým tlačítkem myši na komponentu |
Jedna z nejpoužívanějších komponent vůbec. |
OnDblClick |
při dvojkliknutí levým tlačítkem myši na komponentu | |
OnEnter |
v okamžiku, kdy je komponenta aktivována |
Nejde o aktivaci formuláře (okna), například při přepnutí z jiné běžící aplikace, ale o aktivaci dané komponenty; tedy například kliknete-li do Editu, apod. |
OnExit |
V okamžiku, kdy je komponenta deaktivována |
Opak předchozí události; je např. vyvolána, skončíte-li zadávání do Editu, a kliknete jinam, apod. |
OnKeyDown |
v okamžiku, kdy je daná komponenta aktivní a uživatel stiskne klávesu |
Lze použít parametru Key, který říká, která klávesa byla stisknuta; lze rozlišovat současný stisk kláves Shift, Alt, Ctrl. |
OnKeyPress |
v okamžiku, kdy je daná komponenta aktivní a uživatel stiskne klávesu |
Rozdíl oproti OnKeyDown spočívá v tom, že parametr Key je typu char, událost je vyvolána jen při stisku kláves odpovídajících ASCII znakům (tedy ne Shift, F1, apod.). |
OnKeyUp |
v okamžiku, kdy daná komponenta je aktivní a uživatel uvolní klávesu |
Klíčovým momentem je uvolnění klávesy (pohyb nahoru); parametr Key je stejný jako u OnKeyDown. |
OnMouseDown |
v okamžiku, kdy uživatel stiskne některé tlačítko myši |
Zpravidla je poslána komponentě, která leží pod kurzorem myši. |
OnMouseMove |
v okamžiku, kdy uživatel pohne myší nad komponentou | |
OnMouseUp |
v okamžiku, kdy uživatel uvolní některé tlačítko myši |
Je-li současně stisknutých více tlačítek, OnMouseUp je generována vícekrát při uvolnění každého z nich. |
Události formuláře (okna)
Výše uvedené události jsou často používané a patří asi k nejdůležitějším, rád bych ale ještě zmínil některé unikátní události formuláře. Když budete mít v programu dvacet tlačítek, budete pravděpodobně ošetřovat dvacet událostí OnClick. Formulář budete mít jeden, přesto je ošetření jediné vlastnosti OnActivate nebo OnCreate možná důležitější než dvacet „OnClicků“.
Událost |
Kdy k ní dojde |
Poznámky |
OnActivate |
stane-li se okno aktivním |
Je generována, přepne-li se uživatel do okna z jiného okna téže aplikace, nikoliv z jiné aplikace. |
OnDeactivate |
přestane-li být okno aktivním |
Je generována, přepne-li se uživatel do jiného okna téže aplikace, nikoliv do jiné aplikace. |
OnClose, OnCloseQuery |
je-li formulář zavřen (Alt-F4, křížek v rohu, systémové menu…) |
Při zavírání formuláře je nejprve generována OnCloseQuery, poté OnClose. První se používá např. pro potvrzení („Chcete opravdu skončit?“) nebo pro výzvu k uložení dat. Nicméně i v OnClose je ještě šance zavření zabránit a pomocí parametrů lze okno třeba jen skrýt či minimalizovat. |
OnCreate, OnDestroy |
při vytvoření, resp. zrušení formuláře | |
OnShow, OnHide |
při zobrazení, resp. skrytí formuláře |
Tato událost úzce souvisí s vlastností formuláře Visible. |
Formulář má nepochybně další důležité vlastnosti, nemůžeme však popisovat všechny.
Poznámka pro pokročilé uživatele:
Při vytváření viditelného formuláře (např. při startu aplikace, vlastnost Visible = true) , je sekvence generovaných událostí následující:
V události OnDestroy by měly být zrušeny (Dispose) všechny objekty dynamicky vytvořené v OnCreate (New).
Jak pracovat s událostmi
Vlastní vložení reakcí na události je velmi jednoduché, problémy zpravidla nastávají až v okamžiku, kdy je třeba naprogramovat konkrétní akce. Vše si vysvětlíme na jednoduchém příkladu, který využijeme i v dalším textu při hledání chyb.
Vytvoříme jednoduchou aplikaci, která po svém otevření nastaví jako svůj titulek slovo „Události“. Bude obsahovat dvě tlačítka, na jednom se zobrazí nápis „Konec“ jen tehdy, když nad ním bude kurzor myši (to není zrovna chytré řešení a v reálných aplikacích jej příliš často nevyužívejte J.). Druhé tlačítko bude mít titulek „Vypočti“ a po jeho stisknutí proběhne jednoduchá smyčka (for cyklus).
procedure TwndHlavni.btnVypoctiClick(Sender: TObject); var i, j: integer; begin j := 0; for i := 1 to 10 do j := j + 10; end;
Hledáme chyby
Pokud vytvořenou aplikaci přeložíte a spustíte (doporučuji též uložit), bude pracovat správně. Takový ideální stav ale bohužel není dosažen vždy. Proto v Delphi nacházíme integrovaný debugger s množstvím ladicích nástrojů a nástrojů pro detekci chyb v kódu. Je jich opravdu dost, my se podíváme na několik nejpoužívanějších, které patří k těm jednodušším, přesto jsou vcelku výkonné.
Poznámka pro pokročilé uživatele:
Kdykoliv spustíte program v prostředí Delphi, běží vlastně v integrovaném debuggeru. Tím je zajištěna použitelnost breakpointů a všech dalších ladicích nástrojů. Trochu jiná je také reakce na výjimky v debuggeru a ve vlastním operačním systému.
Co si vlastně představit pod pojmem „chyby v kódu“? Chyby můžeme (velmi hrubě) rozdělit do dvou částí:
Chybami, na které upozorní již překladač, se nebudeme zabývat. V takovém případě kurzor spočívá na řádce ve zdrojovém souboru, kde se chyba vyskytuje a je zobrazena chybová hláška. Kliknete-li na tuto hlášku a stisknete-li F1, vypíše se nápověda s podrobným popisem příslušné chyby.
Horší jsou zpravidla chyby, které překladač nedetekuje. V takovém případě program spustíte a žijete v přesvědčení, že běží správně. Pokud je chyba skutečně „vypečená“, program většinou běhá dobře a jen někdy, zřídkakdy jsou třeba výsledky výpočtu špatné. Pak nastává fáze ladění a známého „odvšivování“ (debugg).
Ladíme a odvšivujeme
Podotýkám, že následující text není kompletním referenčním popisem příslušných nástrojů. Klade si za cíl jen seznámit začínající uživatele s nejběžnějšími možnostmi integrovaného debuggeru. Všechny níže uvedené nástroje se nacházejí v hlavním menu v položce Run.
Krokování Trace Into
Trace Into je klasické krokování programu po jednotlivých řádkách. Nejsnáze jej vyvoláte klávesou F7 a jejím každým dalším stiskem se posune provádění programu o další řádku. Narazí-li se přitom na volání podprogramu, skočí se do něj a pokračuje se stejným způsobem řádku po řádce.
Krokování Step Over
Step Over je podobné krokování jako Trace Into, ale s tím rozdílem, že při nalezení volání podprogramu je tento vykonán jako jednolitý blok a není tedy prováděn po řádkách. Vyvolá se klávesou F8.
Run to Cursor
Nastavíte-li kurzor na kteroukoliv řádku v kódu a stisknete-li F4, program se normálně spustí a poběží až do řádky, na které byl kurzor. Pak se provádění zastaví.
Breakpoints
Breakpoint (bod přerušení) funguje tak, že řeknete Delphi, aby běh programu zastavil v určeném bodě. Běžným použitím je nastavení breakpointu na některou řádku v kódu. Pak program spustíte (F9) a jakmile se vykonávání programu dostane na danou řádku, je zastaveno a vy můžete používat další nástroje, např. prohlížení obsahu proměnných, apod. – viz dále. Breakpointů můžete mít ve zdrojovém kódu nastaveno více najednou. Dostanete je do svého programu např. tak, že najedete na příslušnou řádku kurzorem a v menu najdete Run – Add Breakpoint… (ve verzi 3) nebo Run – Add Breakpoint – Source Breakpoint (ve verzi 5). Breakpointů existuje více typů, ale o tom se prozatím netřeba šířit.
Watch
Jedním z mnoha významů slova Watch je „číhat“. Myslím, že celkem vystihuje to, co se s nástrojem Watch často dělá. Zvolíte si některou (i nejednu) z proměnných (nebo libovolný výraz), breakpointem (nebo F4) se dostanete na začátek „problematického“ bloku a třeba krokováním se posouváte řádku po řádce a stále sledujete obsah dané proměnné (eventuálně hodnotu zadaného výrazu). Číháte na okamžik, kdy hodnota začne být podezřelá nebo vyloženě špatná, a tak se doberete původu chyby. Watches naleznete v menu Run – Add Watch… (nebo pomocí zkratky Ctrl-F5).
Evaluate/Modify
Tímto nástrojem můžete za běhu prohlížet, ale především měnit hodnoty výrazů, proměnných či vlastností. Je to výborný nástroj třeba v okamžiku, kdy chcete zjistit, jak by se program choval, kdyby v tom íčku nebyla sedmička, ale dva tisíce. Evaluate/Modify se skrývá v menu Run – Evaluate/Modify… (nebo Ctrl-F7).
Poznámka pro pokročilé uživatele:
Jediné, v čem Evaliate/Modify odmítne spolupracovat, je použití výrazu obsahující lokální nebo statické proměnné, které nejsou přístupné z daného místa; a dále funkční volání.
Co když zamrznete?
V průběhu ladění dojde občas k zamrznutí programu, nebo se prostě dostanete do situace, kdy chcete ladění přerušit a začít znovu. V takovém případě použijte příkaz Run – Program Reset
(nebo Ctrl-F2).
Vyzkoušejte a vymyslete
Vyzkoušejte si použití zmíněných nástrojů na programu, který jsme vytvořili výše. Nebude to snadné, protože v něm není příliš co zkoumat (a nač číhat J), ale pro seznámení se zmíněnými nástroji postačuje.
Budete-li chtít sami sobě, případně někomu z vašich blízkých dokázat, že se z vás skutečně stává Delphi – expert, zamyslete se nad následující otázkou: umístíte-li breakpoint třeba na řádku j := j + 10;
breakpoint nezabere a program se na dané řádce nezastaví (přestože by měl, a to nejednou). A co je ještě tajuplnější – když třeba tělo for cyklu rozšíříte o podmínku if j = 40 then wndHlavni.color := clRed;
Breakpoint ponechaný na původním místě najednou zabere! Víte, proč tomu tak je? Důvod prozradím v dalším díle seriálu (pokud jej tedy některý aktivnější Delphař nenapíše do příspěvku pod článek :).
Řešení domácího úkolu
V minulém díle (5. kapitola) se na konci článku vyskytoval jakýsi námět k zamyšlení pro vás. Ptal jsem se, proč „nezabere“ breakpoint umístěný dovnitř jednoduchého for cyklu v našem jednoduchém příkladě. Nuže, důvodem je to, že Delphi provádí při překladu (nenastavíte-li jinak) optimalizaci. Překladač zjistil, že proměnnou, do které jsme uvnitř cyklu přičítali číslo 10, nikde za cyklem v programu nevyužijeme (výslednou hodnotu z jí nečteme), proto celý cyklus vyřadil a ten se v přeloženém programu neobjevil. Breakpoint umístěný do jeho těla tudíž nemohl „zabrat“. Když jsme potom do cyklu přidali test na hodnotu proměnné j, cyklus se ve výsledku objevil a breakpoint „zabral“.
Struktura programu v Delphi
A teď už k dnešnímu tématu. Ze všeho nejdříve se podívejme na to, jak vypadá struktura souborů projektu, tedy těch souborů, které použije Delphi k vytvoření spustitelného programu.
Program v Delphi je „zkonstruován“ ze zdrojových souborů (lidově zdrojáků), které se nazývají units (programové jednotky, soubory *.PAS). Každá jednotka je uložena v separátním souboru a je kompilována samostatně. Když jsme v předchozích kapitolách psali libovolný programový kód, psali jsme jej právě do jednotky *.PAS. Zkompilované jednotky (soubory *.DCU) jsou již přímo spojeny s vlastním vytvářením aplikace (tzv. linkování).
Výhody jednotek jsou zřejmé. Jednotky umožňují (kromě jiného):
V tradičním Pascalském programu je veškerý zdrojový kód (včetně „hlavního“ programu) uložen v souboru *.PAS. Delphi využívá k uložení „hlavního“ programu souboru *.DPR, přičemž valná většina ostatního zdrojového textu se nachází v jednotce (jednotkách) - *.PAS. Kód, který je v souboru projektu <.DPR), je vytvářen automaticky a v drtivé většině případů jej sami nijak needitujeme. Tento soubor především organizuje soubory jednotek v aplikaci. Vše, co sami po aplikaci chceme, uvádíme zpravidla do jednotek <.PAS).
Poznámka pro pokročilé uživatele:
Soubor *.DPR z technického hlediska hlavně vytváří instance tříd formulářů (form) a po jejich úspěšném vytvoření spouští aplikaci.
Každá aplikace (nebo projekt) sestává z jednoho souboru projektu <.DPR) a jedné nebo více jednotek <.PAS). K vytvoření spustitelného programu potřebuje Delphi buď zdrojové kódy nebo již kompilované soubory všech jednotek <.PAS nebo *.DCU). Jak asi víte, seznam používaných jednotek se uvádí za klausuli Uses.Většinou jej ovšem Delphi spáchají automaticky.
Klausule uses poskytuje kompilátoru informaci o vztazích mezi moduly. Protože tato informace je uložena přímo v jednotkách (modulech) samotných, Object Pascal nevyžaduje makefiles, hlavičkové soubory (headers) nebo instrukci preprocesoru „include“, jak to známe třeba z jazyka C.
Poznámky pro pokročilé uživatele:
Soubory projektu
Abych shrnul výklad z předchozí kapitoly, uvedu nyní seznam všech souborů používaných Delphi při překladu a vytváření projektu:
„Pascalské“ zdrojové soubory
Přípona |
Typ |
Obsah a význam |
*.PAS |
soubory jednotek (units) |
většina zdrojového kódu aplikace. Jedna aplikace jich zpravidla obsahuje větší počet. |
*.DPR |
soubor projektu |
aplikace obsahuje právě jeden. Organizuje soubory jednotek. |
*.DPK |
soubory balíků (packages) |
tyto soubory jsou podobné souborům projektu, ale jsou používány ke konstrukci speciálních dynamicky linkovaných knihoven zvaných packages. V jednoduchých aplikacích zpravidla nejsou použity. |
Automaticky generované, „nepascalské“ soubory
Přípona |
Typ |
Obsah a význam |
*.DFM |
soubory formulářů |
zpravidla textové soubory obsahující informace o formulářích, vč. řetězců, bitmap, atd. |
*.RES |
resource soubory |
standardní Windows-resource soubor, zde je kvůli ikoně aplikace |
*.DOF |
soubor s volbami projektu |
obsahuje nastavení kompilátoru a linkeru, adresáře, informace o verzi apod. |
Poznámka pro pokročilé uživatele:
Kromě těchto nejdůležitějších souborů se v adresáři s aplikací nalézá mnoho dalších souborů. Mnohé jsou sice vcelku důležité, ale málokdy je uživatel ručně edituje. Stručně si je vyjmenujme:
Správa většího projektu
V této kapitole si povíme několik základních zásad správy projektu a štábní kultury.
Pojmenování komponent
Není vhodné ponechávat komponentám jejich implicitní jména, která jim přiděluje Delphi. Pokud vytváříte jednoduchou aplikaci, jejímž jediným úkolem je spustit výpočet po stisku jediného přítomného tlačítka, je to celkem jedno; takové aplikace ovšem nejsou příliš časté. Mnohem lepší je přejmenovávat komponenty podle nějakého transparentního klíče, tedy např. tlačítka na btnXXX, kde XXX popisuje funkci tlačítka, např. btnKonec, btnVypocti, apod. Formuláře je dobré pojmenovávat frmXXX (a možná ještě lépe wndXXX), editační okna třeba edtXXX, apod. Jde o to, aby byl program čitelný s odstupem času pro vás samotné (a ještě spíše pro někoho, kdo bude číst „zdrojáky“ po vás).
Více formulářů
Není problémem mít v aplikaci více oken. Je to zcela běžné a většina aplikací disponuje více okny (formuláři). Chcete-li přidat do návrhu aplikace nový formulář, vyberte jednoduše z menu File – New Form. Delphi automaticky vytvoří nové soubory související s přidávaným formulářem. Pokud např. do jednoho modulu ze svého programu doplníte volání funkce (metody) z jiného vašeho modulu a zapomenete v sekci uses uvést jméno modulu, ve kterém se funkce (metoda) nachází, Delphi se zeptají (při kompilaci), zda to mají provést, a vám stačí jednou kladně odpovědět.
Poznámka pro pokročilé uživatele:
Přesněji řečeno, dojde k tomu, že Delphi přidají sekci uses do části implementation (nikoliv interface), a pokud tam již existuje, přidají do ní nový modul.
Není ovšem dobré zbytečně formuláři „hýřit“. Na spoustu věcí vůbec nemusíte navrhovat nová okna, stačí použít některý standardní dialog nebo box. Uživatelské prostředí vaší aplikace by mělo být vytvářeno v souladu s principem prvořadosti uživatele. To znamená, že primárním cílem vývojáře není vyřádit se při návrhu designu aplikace, ale využít uživatelových návyků při určitých činnostech a umožnit mu aplikovat je i v novém produktu. Pokud je uživatel zvyklý na standardní způsob, jakým mu pět aplikací něco sděluje, je nanejvýš vhodné v šesté aplikaci využít zcela stejného způsobu.
Štábní kultura kódu
O štábní kultuře by spíše měl pojednávat seriál vyučující Pascal, proto se omezím skutečně jen na naprosté minimum. Následující doporučení by vám měla umožnit psát čitelné a přehledné zdrojové texty. Jde skutečně pouze o doporučení a nemusíte je dodržovat.
Nyní se podívejte na příklad jedné metody. Nejprve bude napsána „ošklivě“:
procedure TForm3.FormActivate(Sender: TObject); var i,x,y:integer; begin i:=-1; repeat inc(i); until(Form2.ListBox1.Selected[i]); Edit1.Text:=IntToStr(PreparujX(Form2.ListBox1.Items[i])); Edit2.Text:=IntToStr(PreparujY(Form2.ListBox1.Items[i])); end;
Nyní „hezká“ verze:
procedure TwndSeparuj.FormActivate(Sender: TObject); var i : integer; begin i := -1; repeat inc (i); until (wndData.lstKomplex.Selected[i]); edtSourX.Text := IntToStr(PreparujX(wndData.lstKomplex.Items[i])); edtSourY.Text := IntToStr(PreparujY(wndData.lstKomplex.Items[i])); end;
Úplně nejlepší je podívat se, jakým způsobem řeší „štábní kulturu“ samotné Delphi. Budete-li pokračovat v jeho stylu (pokud jde např. o velikost písmen), bude zdroják vypadat konzistentně a čitelně.
Hierarchie tříd
Následující text může začátečníkovi připadat poněkud složitý. Stane-li se vám při čtení něco podobného, neházejte flintu do žita. Hierarchie tříd není věc, kterou byste při programování jednoduchých aplikací museli nezbytně znát. Přesto jsem její popis zařadil, protože jednoho dne nastane okamžik (a ještě ke všemu u každého programátora jindy), kdy se může hodit, že o ní něco víte.
Systémová knihovna Delphi se nazývá Knihovna vizuálních komponent (Visual Component Library, VCL). Srdcem Delphi je hierarchie tříd. Každá třída v systému je potomkem datového typu TObject, takže celá hierarchie má jediný kořen. Tím je umožněno používat TObject jako náhradu za jakýkoliv datový typ v systému. K detailnímu vysvětlení tohoto jevu bychom museli zabrousit do hloubi objektově orientovaného přístupu, na což bohužel nemáme prostor. Stačí, když budete vědět, že při používání komponent vlastně vytváříme instance (tj. cosi jako konkrétní výskyty) koncových tříd hierarchie - listů ze stromu hierarchie. Dvojice Object Inspector a paleta komponent umožňuje umisťovat komponenty VCL na formulář, stejně jako měnit jejich vlastnosti, bez nutnosti psát kód.
Poznámka pro pokročilé uživatele:
Například metody reagující na událost obvykle obsahují parametr Sender typu TObject. Z výše uvedeného důvodu tedy můžeme jako odesílatele uvést prvek libovolné třídy VCL, protože je určitě odvozen od třídy TObject. TObject je abstraktní třídou, jejíž metody zapouzdřují základní chování, jako jsou např. vytvoření, zrušení a manipulace se zprávami.
Abyste nebyli z chaosu popsaného v předchozím odstavci úplně znechuceni, pokusím se jemně osvětlit rozdíl mezi instancí a třídou na krátkém příkladu:
var Neco : Tneco // TNeco - třída begin Neco := TNeco.Create; // Neco - nově vytvořená instance práce s instancí ..// nyní můžu pracovat s Neco Neco.Free; // zrušení instance end;
Tímto způsobem je také možné přidávat komponenty do programu za jeho běhu, tedy nikoliv ve fázi návrhu, ale run-time. V jakékoliv metodě formuláře můžeme například vytvořit nové tlačítko následujícím způsobem:
var btnNovy : TButton begin btnNovy := TButton.Create(self); btnNovy.Parent := self; btnNovy.Left := 100; btnNovy.Top := 200; btnNovy.Visible := True; end;
V okamžiku, kdy vytváříme novou komponentu, v zásadě knihovnu VCL rozšiřujeme, a proto používáme množství nástrojů Object Pascal, které uživatelé komponent zřídka potřebují. Problematice tvorby vlastních komponent bude věnována samostatná kapitola tohoto seriálu. Velkou výhodou knihovny VCL (a Delphi vůbec) je, že k používání komponent nepotřebujeme znát detailně strukturu hierarchie tříd. Potřebujeme pouze rozumět listům stromu, tj. komponentám.
Standardní boxy aneb „Ano, Ne, Storno“
V aplikacích velmi často potřebujeme získat od uživatele nějaký vstup. Komplexně se na zadávání informací a uživatelský vstup podíváme v příštím díle, ale už dnes začneme jednodušší variantou problému. Jedním z nejčastějších uživatelských vstupů je „parlamentní hlasovací“ odpověď - potvrzení, odmítnutí, nebo zrušení (zdržení se rozhodnutí).
Kromě toho (ne úplně zřídka) nastává situace, kdy je třeba uživatele informovat o aktuálním stavu výpočtu, upozornit jej na chybu nebo mu vynadat za špatný vstup. Pro oba zmíněné případy disponuje Delphi velmi elegantním řešením - standardními dialogovými boxy. Jak uvidíme za chvíli, použití těchto boxů je naprosto triviální, přesto poskytují širokou škálu voleb a možností zobrazení.
Nejjednodušší je ShowMessage
Při popisu boxů začneme od toho nejjednoduššího. Potřebujeme-li vypsat jednu krátkou informaci (zpravidla větu, alespoň se to tak doporučuje), nejsnáze použijeme proceduru ShowMessage. Člověk nemusí být zrovna expertem na angličtinu, aby si název funkce přeložil a aby jej pochopil. Syntaxe je následující:
procedure ShowMessage(const Msg: string);
Příklad:
ShowMessage(’Toto je krátká informace, milý uživateli.’);
Více umí MessageDlg
Vše, co umí ShowMessage, ale ještě mnohem víc, nabízí funkce MessageDlg. Umožňuje poměrně velkou variabilitu při návrhu vzhledu boxu. Syntaxe:
function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint): Word;
Popis parametrů:
MessageDlg vrací hodnotu tlačítka, jehož stisknutím uživatel dialog opustil. Možné hodnoty: mrNone, mrAbort, mrYes, mrOk, mrRetry, mrNo, mrCancel, mrIgnore, mrAll.
Titulek okénka bude stejný jako název spustitelného souboru s aplikací (tedy název projektu v Delphi). To platí i o boxíku vytvořeném procedurou ShowMessage.
Příklad:
if MessageDlg(`Soubor byl změněn. Chcete jej uložit?`, mtConfirmation, [mbYes, mbNo, mbCancel], 0) = mrYes then uloz_soubor(aktualni);
Poslední vylepšení: MessageDlgPos
Jedinou větší nevýhodou funkce MessageDlg je, že okénko se otevře uprostřed obrazovky. To je někdy velmi nevhodné, naštěstí v Delphi existuje rozšířená funkce MessageDlgPos, která má úplně stejné parametry jako MessageDlg, ale navíc disponuje možností zadat souřadnice, na kterých se má boxík objevit:
function MessageDlgPos (const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer): Word;
Funkcí je více
Funkcí pro výpis krátké informace a pro získání „jednobitové“ informace o dalším postupu je samozřejmě více. Pokud byste chtěli experimentovat s dalšími podobnými funkcemi, doporučuji především funkci MessageBox, což je zapouzdřená funkce Windows API MessageBox.
Zadávání logických hodnot
Začneme od toho nejjednoduššího. Potřebujeme-li získat od uživatele pouze logickou hodnotu (tj, „ano“ nebo „ne“, příp. „1“ nebo „0“, apod.), je situace poměrně jednoduchá. Často stačí třeba použít obyčejný „MessageBox“ (nebo jiný standardní nástroj popsaný v 6. kapitole našeho seriálu).
Někdy to ovšem není příliš elegantní řešení. Naproti tomu elegancí oplývá typický představitel logické hodnoty – zatrhávací pole (CheckBox). Jeho výhody jsou nesporné: je-li zobrazen stále na formuláři nebo v menu, může uživatel kdykoliv změnit hodnotu jediným kliknutím. Je přehledný, na uživatele stále nevyskakují další a další okénka (jako by tomu bylo u soustavného používání MessageBoxu, apod.).
Jak CheckBox na formulář dostat? Je to snadné:
Velmi jednoduché je také čtení hodnot ze zatrhávacích boxů. Chcete-li např. zjistit, zda si uživatel přeje automaticky ukládat soubor, provedete to takto:
if chbAutoSave.Checked = True then …
Poznámka: podmínku lze použít z také „zkráceně“:
if chbAutoSave.Checked then …
Tento tvar už sám o sobě dostatečně vyjadřuje, oč v podmínce jde, a není nijak matoucí. Velmi často se proto používá.
Kromě vstupu lze samozřejmě s výhodou použít komponentu CheckBox také k zobrazení „jednobitové“ informace (uvědomuji si, že pojem „jednobitové“ není přesný, zvláště povolíte-li třetí stav (Grayed)).
Přepínací boxy
Komponenta RadioButton pro nás také není žádnou novinkou. Tato přepínací tlačítka se ale zpravidla vyskytují ve skupinách, protože lze zároveň vybrat jen jednu možnost. Je možné, aby nejprve (na startu aplikace) nebylo vybráno žádné tlačítko, nicméně tato možnost nebývá příliš obvyklá.
Jedinou důležitou vlastností je Checked, která signalizuje, že tlačítko bylo vybráno.
Komponenty RadioButton se (skoro) vždy sdružují na některou kontejnerovou komponentu, nejčastěji na GroupBox Panel, příp. samotný formulář. Místo použití několika RadioButtonů ovšem raději využijte jednu komponentu RadioGroup
Skupina přepínacích tlačítek:
Vložíte-li na formulář komponentu RadioGroup, nastavte vlastnost Items. Ta je stejného typu (TStrings) jako vlastnost Lines komponenty Memo. Podrobný popis naleznete níže v této kapitole. Do editoru napište pouze popisky jednotlivých RadioButtonů, kolečka budou doplněna automaticky. Editací vlastnosti Columns volíte počet sloupců, ve kterých budou tlačítka zobrazena.
Zjišťování, které tlačítko je vybráno, se provádí testováním hodnoty vlastnosti ItemIndex. Mnohým programátorům ale tento způsob nevyhovuje, protože si musí pamatovat, jakou číselnou hodnotu má které „přepínátko“, navíc tato hodnota se může (teoreticky) za běhu programu měnit, budete-li seznam editovat (což není, pravda, příliš šikovné).
Dávají proto přednost testování textu (popisku) tlačítka na základě tohoto indexu (rdgpPozdrav je komponenta RadioGroup):
if rdgpPozdrav.Items.Strings[rdgpPozdrav.ItemIndex] = `Nazdar` then …
Poznámka pro pokročilé uživatele:
Je třeba podotknout, že tento způsob je poněkud „drsný“. Řetězce u jednotlivych tlačítek jsou zpravidla delší než krátké pozdravy v příkladu. Proto lze tuto metodu „vylepšit“, a to například nadefinováním konstant místo čísel ItemIndex (NAZDAR, AHOJ, ) a následné testování podmínek typu
if rdgpPozdrav.ItemIndex = NAZDAR then
Jednořádkový vstup
Ne vždy si ale vystačíme se zatrhávacími boxy, představte si je třeba v situaci, kdy uživatel má zadat číslo (byť celé) z intervalu <0, 1000>. Nestačí ani přepínací tlačítka.
Nejjednodušší metodou je použití dalšího typu „MessageBoxu“ (podrobný popis podstatných boxů viz 6. díl seriálu). Konkrétně jde o funkci InputBox. Používá se velmi snadno:
function InputBox(const ACaption, APrompt, ADefault: string): string;
Význam jednotlivých parametrů:
Mocnějším nástrojem pro vložení jednoho řádku textu je ovšem komponenta Edit. Má mnoho specifických vlastností, jimiž můžeme tento jednořádkový vstup omezit či formátovat (např. lze místo zadávaných znaků vypisovat hvězdičky; použití si jistě dovedete živě představit).
Vizte tabulku s nejdůležitějšími vlastnostmi komponenty Edit.
Vlastnost |
Význam |
Text |
Nejdůležitější vlastnost; v návrhové fázi je výhodné vložit do ní počáteční text. |
MaxLength |
Maximální počet znaků, které lze do Editu zadat. Doporučuji vyhradit na formuláři dostatek místa, aby Edit nemusel scrollovat (uživatele mate, když mu část textu kamsi ujíždí). |
Modified |
Slouží ke zjištění, zda došlo ke změně. |
AutoSelect |
Podle ní je/není při vstupu do pole stávající text automaticky vybrán. Nastavte podle toho, předpokládáte-li spíše zadání zcela nové hodnoty nebo editaci stávající. |
ReadOnly |
Určuje, může-li uživatel hodnotu v Editu měnit. |
PasswordChar |
Hodnotou této vlastnosti je znak, který se bude vypisovat do Editu místo skutečně zadávaných znaků. |
Komponenta Edit oplývá také několika skvělými metodami. Jen pro ukázku – jedna z metod se jmenuje CopyToClipboard. Hádejte, co dělá!
Potřebujete-li získat řetězec (tedy vstup typu String), je situace nejjednodušší. Na formulář umístěte komponentu Edit (paleta Standard). Doporučuji k ní rovnou umístit štítek (komponenta Label), do něhož napíšete, co vlastně má uživatel do Editu zadat. Toť vše, pak už jen v kterékoliv metodě (např. v metodě OnClick tlačítka na tomtéž formuláři) vstupní řetězec zkontrolujete (je-li to nutné) a použijete (kamsi přiřadíte apod.).
Situace je zajímavější v případě, že potřebujete od uživatele získat číselný vstup. Možností je několik, například:
použít komponentu Edit, standardně získat řetězec a ten následně konvertovat na číslo dostupnými funkcemi. Tento postup je možný, ale má několik nevýhod. Nedisciplinovaný (nebo prostě hloupý) uživatel může zadat (takže stoprocentně zadá) místo čísel písmena. V takovém případě samozřejmě nastává chyba, kterou je nutno testovat a ošetřovat (pozn.: použití funkce val je tak trochu přežitek ze starého dobrého Pascalu. V Delphi existuje efektivnější a rychlejší funkce StrToInt. Jejím problémem ovšem je, že k ošetření chybového stavu musíme pracovat s výjimkami. Pokud na ně nemáte náladu, použijte klidně Val, která vrací výsledek konverze v posledním parametru. Popis funkce StrToInt, včetně reakce na chybu, naleznete v závěru tohoto dílu).
Příklad:
val (edtPlat.Text, plat, vysledek); if vysledek <> 0 then begin edtPlat.SetFocus; MessageDlg(`Výše platu musí být číselný údaj!`, mtError, [mbOk], 0); end;
použít komponentu Edit, ale ze vstupního řetězce zrušit všechny nečíselné hodnoty. Toto „rušení“ však musíme provést velmi důsledně, jinak uživatel - hnidopich může dostat text do pole přes schránku. Navíc celková koncepce tohoto řešení je taková poněkud no, nepříliš „hezká“. Nicméně obecně funguje dobře a snadno se implementuje.
Příklad:
if not (key in [`0`..`9`, #8]) then begin key := #0; messagebeep($FFFFFFFF); end;
použít komponentu MaskEdit. Ta oproti komponentě Edit oplývá vlastností EditMask, která představuje omezení aplikovaná na vstupní data. Řada formátů je již předdefinována v Delphi a je samozřejmě možné vytvořit libovolný jiný formát. V Delphi k tomu existuje nástroj (Input Mask Editor), který spustíte kliknutím na tlačítko se třemi malými tečkami v Object Inspectoru při kliknutí na vlastnost EditMask.
Podrobný popis tvorby masek naleznete k nápovědě.
Vlastní text je uložen ve vlastnosti Text (jako u komponenty Edit). EditText obsahuje text formátovaný aplikací masky (tedy tak, jak jej vidí uživatel). Vlastnost IsMasked umožňuje testovat, zda byla maska definována.
Poznámka pro pokročilé uživatele:
Funkce MessageBeep zasluhuje krátké vysvětlení. Jde totiž o funkci Windows API, a proto ji v nápovědě Delphi příliš popsanou nenaleznete (záleží ovšem na konkrétní instalaci, někdy bývá v menu Help k nalezení položka Windows SDK). Tato funkce se používá pro přehrání zvuku definovaného ve Windows pro určitou událost. Možné hodnoty jsou 0xFFFFFFFF, MB_ICONASTERISK, MB_ICONEXCLAMATION, MB_ICONHAND, MB_ICONQUESTION, MB_OK.
Víceřádkový vstup
Komponenta Edit (včetně její rozšířené odrůdy – MaskEdit) může obsloužit omezené množství textu, a to pouze na jednom řádku. Potřebujeme-li něco podobného jako Edit (tedy ne moc lepšího), použijeme komponentu Memo. Ta se liší především možností obsáhnout několik řádků (a v důsledku toho např. přítomností posunovacích lišt pro pohyb v textu atd.).
Přehled nejdůležitějších vlastností opět shrneme do tabulky:
Vlastnost |
Význam |
Alignment |
Komponentu Memo můžeme výhodně využít i pro zadávání jednořádkového vstupu, pokud tento řádek chceme zarovnávat doprava. K tomu právě slouží Alignment. |
ScrollBars |
Umožňuje nastavit nezávisle na sobě oba posuvníky (tedy vodorovné i svislé). |
WordWrap |
Kladná hodnota této vlastnosti (True) znamená, že dojde k zalamování řádků. Pak byste neměli „zapnout“ vodorovné posuvnítko; už z logiky věci vyplývá, že se tyto dvě vlastnosti vzájemně vylučují. |
WantTabs, WantReturns |
Klávesy Tab a Enter mají u většiny komponent jinou funkci. Stisk tabulátoru zpravidla způsobí přechod na další komponentu (její „aktivaci“). Chceme-li ale uživateli umožnit, aby do komponenty Memo mohl zadávat tabelátory a Entery, umožníme mu to těmito vlastnostmi. Podotýkám, že v každém případě může uživatel zmíněné znaky do textu dostat současným stiskem Ctrl (Ctrl+Tab, Ctrl+Enter). |
Text |
Podobná vlastnost jako u Editu; zde znamená práci s celým textem. |
Lines |
Zcela stěžejní vlastnost! Podrobnější popis následuje pod tabulkou. |
Nejpodstatnější vlastností komponenty Memo je Lines. Ta umožňuje pracovat s jednotlivými řádky textu. Lines je typu TStrings a jako taková má mnoho užitečných vlastností a metod. Ukážeme si dvě z nejpůvabnějších – LoadFromFile, resp. SaveToFile (tedy načtení textového souboru z disku, resp. uložení textového souboru na disk). Jejich velmi elegantní použití ilustruje následující příklad:
procedure wndHlavni.btnNactiClick(Sender: TObject); begin memoHlavni.Lines.LoadFromFile(`C:AUTOEXEC.BAT`); ShowMessage(`6. řádka souboru AUTOEXEC.BAT je: ` + memoHlavni.Lines[5]); end;
Číselný vstup – posuvné lišty
Dalším často řešeným problémem je zadávání číselných hodnot z daného intervalu. Lze k tomu použít komponentu Memo, ale velmi pěkným řešením je užití komponent ScrollBar (posuvných lišt).
Nejdůležitější vlastnosti komponenty ScrollBar:
Vlastnost |
Popis |
Kind |
Určuje orientaci (vodorovná (sbHorizontal) nebo svislá (sbVertical)). Nově umístěná komponenta je na formuláři vždy vodorovná. |
Min, Max |
Určují mezní hodnoty. |
Position |
Stanovuje aktuální pozici jezdce. |
SmallChange |
Jemnost posuvu, tato vlastnost platí pro posuv pomocí koncových šipek ScrollBaru nebo kurzorových kláves. |
LargeChange |
Jemnost posuvu, vlastnost platí pro posuv pomocí kliknutí kamkoliv na pruh ScrollBaru nebo pomocí kláves PageUp, PageDn. |
Chcete-li přečíst hodnotu, kterou uživatel nastavil na ScrollBaru, provedete to nejlépe ošetřením události OnChange.
Pokud chcete mít posuvné lišty skutečně elegantní, je vhodné doplnit je popiskou (štítkem, komponenta Label), která bude vždy popisovat aktuální pozici jezdce (aktuální hodnotu).
Poznámka: v tomto příkladu bylo použito volání Windows API funkce RGB, jejíž syntaxe je velmi jednoduchá:
RGB(red, green, blue);
kde red, green a blue jsou barevné složky klasického RGB modelu v rozsahu 0 – 255 (tedy hodnota typu byte). Tyto hodnoty tedy (0 a 255) byly nastaveny jako minimální a maximální ve vlastnostech Min a Max všech posuvných lišt. Události OnChange všech tří lišt proto mají zhruba tento tvar:
procedure TwndBarvy.scrCervenaChange(Sender: TObject); begin memoUkazka.Color := RGB(scrCervena.Position, scrZelena.Position, scrModra.Position); lblCervenaHodnota.Caption := IntToStr(scrCervena.Position); end;
Poznámka:
V předchozím textu byly uvedeny dvě funkce, o nichž možná zatím nic nevíme: StrToInt a IntToStr. Jde o konverzní funkce; první z nich převádí řetězec na číslo a druhá naopak (samozřejmě lze na číslo převést pouze řetězec obsahující číslo, řetězec „Nazdar“ bude převeden jen těžko). Syntaxe je následující:
function IntToStr(Value: Integer): string; function StrToInt(const S: string): Integer;
Pokud řetězec S neobsahuje platný číselný údaj, vyvolá StrToInt výjimku EconvertError. O výjimkách budeme (psát) mluvit později, proto napíšu jen způsob použití:
try Hodnota := StrToInt(Retezec); except on EConvertError do . end;
Číselné vstupy – kombinace editačního pole a myši
Na závěr si ukážeme komponentu, která se také používá k zadávání číselných údajů. Jde o komponentu SpinEdit z palety Samples.
Tato komponenta v sobě částečně integruje vlastnosti normálního editačního pole a posuvných lišt. Nebudu v tabulce popisovat všechny její důležité vlastnosti, omezím se na konstatování, že místo vlastnosti Text (kterou má Edit) disponuje vlastností Value a místo vlastností Min, Max, SmallChange (které má ScrollBar) má vlastnosti MinValue, MaxValue a Increment.
Do SpinEditu je možné zadávat údaje jak pomocí klávesnice (jako do Editu), tak klikáním na šipky vpravo.
Zadávat lze i seznamem
Vstup údajů je možný také pomocí seznamů (ListBox, ComboBox, atd.). Protože se ale tyto seznamy často používají také k výpisům, necháme si jejich popis na příští díl seriálu. Kromě nich se podíváme i na další komponenty umožňující výstup a výpis informací, tedy druhý směr komunikace uživatel « aplikace.
Vstup = výstup, alespoň občas
Na úvod si neodpustím jemnou poznámku, která zdánlivě zhatí naše úsilí rozdělit si komunikaci s uživatelem na dva směry (aplikace -> uživatel, uživatel -> aplikace). Pravdou je, že velmi často (skoro vždy) můžeme použít tutéž komponentu pro vstup i výstup informací; pro zadávání i výstup hodnot. Příkladem budiž v minulé kapitole zmíněné zatrhávací políčko (CheckBox). Buď jej můžeme použít (tak, jak bylo posledně popsáno) ke vstupu informace, nebo naopak kupříkladu načteme konfiguraci z nějakého souboru a pomocí CheckBoxů ji přehledně, efektně a úsporně zobrazíme (předáme) uživateli.
Proto prosím neberte dělení komponent striktně. Není možné v jednom dílu popsat všechny komponenty pro vstup/výstup, tak jsem je rozdělil do minulé kapitoly a do této.
Sezname, otevři se (komponenta ListBox)
V odstavci, jehož nadpis hyzdí poněkud laciná hrátka se slovy (přiznávám), se podíváme na jednu z nejtypičtějších výstupních komponent - na klasický seznam. Tento seznam se v terminologii komponent Delphi nazývá ListBox. Slůvko „klasický“ jsem použil z toho důvodu, že jsou i jiné seznamy, na které se podíváme níže, a které mají různá vylepšení.
Nejprve se podívejme na několik vlastností seznamů a níže si je vysvětlíme a předvedeme.
Vlastnost |
Význam |
Columns |
Počet sloupců, ve kterých budou údaje (položky) zobrazeny |
Items |
Klíčová vlastnost seznamu – jeho položky |
ItemIndex |
Číslo položky, která je právě vybrána |
Multiselect |
Povoluje / zakazuje současné vybrání (označení) více hodnot (položek) |
SelCount |
Udává počet vybraných položek (je-li Multiselect = True) |
Sorted |
Udává, mají-li hodnoty (řádky) v seznamu být (abecedně) seřazeny |
Číslování položek (vlastnost ItemIndex) začíná od 0 (0 je tedy číslo první položky). Vlastnost ItemIndex je logicky run-time vlastností (je tedy přístupná jen za běhu programu), a má hodnotu –1, není-li vybrána žádná položka.
Zajímavá vlastnost je Sorted. Uvědomme si, že potřebujeme-li setřídit nějaké údaje, nemusíme vytvářet své algoritmy třídění, stačí ony položky „nastrkat“ do ListBoxu, který má Sorted = True (a klidně může mít Visible = False, a uživatel aplikace vůbec neuvidí, že třídění provedl právě ListBox). Budete-li přidávat položku do seznamu s takto nastavenou vlastností Sorted, bude položka automaticky zatříděna. Jde tedy o velmi mocný nástroj, přičemž jeho použití je velmi jednoduché.
Nejpodstatnější vlastností seznamu jsou ovšem Items (položky). Vzpomene-li si ještě někdo ze čtenářů na předchozí díl seriálu, jednak mě nesmírně potěší a jednak si možná uvědomí souvislost s komponentou Memo a její vlastností Lines. Ano, Items je opět typu TStrings a jako taková má mnoho šikovných metod. Protože ListBox (a seznam vůbec) je jednou z nejpoužívanějších komponent, uvedeme si v tabulce několik nejpůvabnějších metod jeho vlastnosti Items:
Metoda |
Význam |
Add |
Přidá položku na konec seznamu |
Clear |
Zajistí vymazání všech údajů z ListBoxu |
Delete |
Vymaže položku ze seznamu |
Equals |
Testuje, jsou-li dva seznamy totožné. Vrací False, liší-li se dva seznamy v délce, obsahují-li různé řetězce nebo nesouhlasí-li pořadí položek |
Insert |
Vloží položku do seznamu na zadanou pozici |
LoadFromFile |
Načte položky seznamu z textového souboru a zobrazí je v seznamu. Nepodaří-li se načtení, řeší se to tzv. výjimkami. Problematice výjimek budeme věnovat některou z příštích kapitol. |
Move |
Přesune položku ležící na zadané pozici na jinou (také zadanou J ) pozici |
SaveToFile |
Uloží položky seznamu do textového souboru. Každý údaj z ListBoxu bude na svém řádku v cílovém souboru. Nepodaří-li se uložení, je generována tzv. výjimka. Problematice výjimek budeme věnovat některou z příštích kapitol. |
Zřejmě uzrála doba k uvedení příkladů jednotlivých metod, protože z popisu v tabulce těžko získáte představu o vlastní implementaci.
Nejprve vložíme na formulář komponentu ListBox, nazveme ji dle našich konvencí lbxStamgasti a vložíme do její vlastnosti Items několik jmen. Na formulář přidáme pár tlačítek (Seřaď, Přidej, Zruš, Vymaž vše, Načti, Ulož, Konec).
Nyní budeme ošetřovat události OnClick jednotlivých tlačítek:
Přidání štamgasta se jménem Vopička, František na konec seznamu (to samozřejmě není příliš univerzální řešení, neboť vždy přidá právě štamgasta Vopičku; s využitím poznatků z minulého dílu ale jistě bez problémů najdete řešení, kterak přidat právě to jméno, které uživatel někam zadá):
procedure wndStamgasti.btnPridejClick(Sender: TObject); begin lbxStamgasti.Items.Add(`Vopička, František`); end;
Seřazení seznamu podle abecedy:
procedure wndStamgasti.btnSeradClick(Sender: TObject); begin lbxStamgasti.Sorted := True; end;
Zrušení (vymazání ze seznamu) označeného štamgasta:
procedure wndStamgasti.btnZrusClick(Sender: TObject); begin lbxStamgasti.Items.Delete(lbxStamgasti.ItemIndex); end;
Vymazání všech štamgastů (vyprázdnění seznamu):
procedure wndStamgasti.btnVymazVseClick(Sender: TObject); begin lbxStamgasti.Clear; end;
Poznámka pro pokročilé uživatele:
Vymazání celého seznamu by bylo možné také takto: lbxStamgasti.Items.Clear; tedy zavolat nikoliv metodu Clear seznamu ListBox, ale tutéž metodu položek Items (typu TStrings). V tomto případě se ale doporučuje volat spíše metodu celého seznamu, neboť to umožňuje provést kromě výmazu položek i další nezbytné “čistící” akce seznamu. Prakticky to má význam třeba u ComboBoxu: použijete-li ComboBox.Clear, seznam se vymaže; ale napíšete-li ComboBox.Items.Clear, seznam se vymaže, ale poslední vybraná položka zůstane zobrazena v editačním okně!
Uložení seznamu do souboru stamgasti.txt:
procedure wndStamgasti.btnUlozClick(Sender: TObject); begin lbxStamgasti.Items.SaveToFile(`stamgasti.txt`); end;
Načtení seznamu ze souboru stamgasti.txt:
procedure wndStamgasti.btnNactiClick(Sender: TObject); begin lbxStamgasti.Items.LoadFromFile(`stamgasti.txt`); end;
Vidíte, že poslední dvě zmíněné metody se zcela samy postarají o režii v souvislosti s otvíráním a zavíráním (případně i zakládáním) souborů.
Předvedeme si (už jen v krátkosti) příklady volání dalších metod:
Vložení štamgasta Adolfa Medvěda na 3. pozici v seznamu:
lbxStamgasti.Items.Insert(2, `Medvěd, Adolf`);
Přesunutí prvního štamgasta na třetí pozici v seznamu:
lbxStamgasti.Items.Move(0, 2);
Tolik tedy k základní práci se seznamem ListBox. Je vidět, že tento seznam má mnoho možností využití, ale občas se nám přesto nelíbí, protože má jednu nevýhodu: na formuláři (okně) zabírá standardně (a nepřetržitě) docela dost místa. Druhou potencionální nevýhodou by mohl být fakt, že používáme-li seznam jako vstupní komponentu, může uživatel vybírat pouze z hodnot, které již v seznamu jsou. Někdy je to samozřejmě dobře, ale čas od času chceme dát uživateli více volnosti. Co s tím?
Sezname, rozbal se (komponenta ComboBox)
Oba zmíněné problémy můžeme řešit komponentou ComboBox. Tento rozbalovací box je na obrazovce podobný editačnímu oknu (komponenta Edit) a uživatel do něj může často vložit nějaký (vlastní) text. Úzce ovšem také souvisí se seznamem (komponenta ListBox), neboť po kliknutí na šipku vpravo na ComboBoxu (nebo stisknete-li Alt+šipka dolů, příp. Alt+šipka nahoru) se seznam zobrazí (rozbalí).
Protože vlastnosti, metody i používání ComboBoxu se v mnohém zcela shodují (nebo velmi přibližují) k téže činnosti v ListBoxu, nebudu znova vše popisovat; zaměříme se jen na změny a doplňky.
Na nastavení vlastnosti Style komponenty ComboBox závisí nejen vizuální vzhled, ale také chování komponenty a možnosti uživatelského vstupu:
Poslední dva styly slouží například pro seznamy bez řetězců a místo toho s grafikou.
Nestačí seznamy? Zkusíme mřížku (komponenta StringGrid)
Tato komponenta se nachází v paletě Additional a není zpravidla tak používaná, jako seznamy. Její „zobrazovací“ význam spočívá v prezentování textových dat v tabulkovém formátu. Divíte-li se, proč to zdůrazňuji, jen podotýkám, že existuje podobná komponenta, DrawGrid, která zobrazuje právě ta „druhá“ data – tedy netextová.
Mřížka (budeme teď mluvit o řetězcové mřížce, tedy o komponentě StringGrid) je poněkud komplikovanější komponentou. Kromě vypisování informací slouží také ke „svázání“ objektu s buňkou. Lze asociovat objekt(y) s každou buňkou (lépe – s každým řetězcem v mřížce). Tyto objekty mohou zapouzdřovat nějakou informaci nebo chování reprezentované řetězcem, který předkládáme uživateli.
Skutečná síla spočívá spíše v odvozené komponentě – DBGrid, která umožňuje zobrazovat údaje z tabulky databáze. To ovšem nebude cílem tohoto dílu seriálu. My se podíváme na jednoduchý příklad, který nám ukáže, kterak vypisovat do obyčejné textové mřížky (StringGrid) jednoduché údaje.
Stačí umístit na formulář komponentu StringGrid a jedno tlačítko. Do události OnClick tohoto tlačítka umístíme následující kód (pro větší názornost je komponenta StringGrid pojmenovaná StringGrid1, ač víme, že je to nepěkné a odsouzeníhodné):
procedure wndHlavni.btnZobrazClick(Sender: TObject); var i, j, k : integer; begin k := 0; with StringGrid1 do for i := 1 to ColCount - 1 do for j:= 1 to RowCount - 1 do begin k := k + 1; Cells[i,j] := IntToStr(k); end; end;
Tento příklad ukazuje hned několik vlastností mřížky:
V příkladu se nevyskytuje několik dalších hezkých vlastností, především:
Na závěr si řekněme malou 'perličku': metoda MouseToCell převádí souřadnice X, Y na korespondující číslo sloupce a řádky v tabulce, metoda CellRect naopak vrátí obrazovkové souřadnice (v pixelech) zadané buňky.
Cimrmanovský úkrok stranou: komponenta Timer
O podkapitolku níže se podíváme na dvě poslední komponenty sloužící k dobré informovanosti uživatele. Obě slouží k zobrazení průběhu (déletrvajícího) procesu. K tomu ale potřebujeme komponentu, která umí (alespoň nějak) pracovat s časem. V Delphi taková samozřejmě je a jmenuje se Timer.
Timer je systémový časovač a nachází se tedy v paletě System. K čemu je vlastně časovač dobrý? Často potřebujeme po určitých časových intervalech přerušit normální běh programu, vykonat nějakou specifickou (leč krátkodobou!) činnost a poté se vrátit zpět do normálního chodu. Časovač potřebujeme také například k vytváření posuvných textů.
Timer nemá příliš vlastností ani událostí (k čemu taky?), vlastně jedinou speciální vlastností je Interval, který říká, kolik času (v milisekundách) uplyne, než časovač opět „tikne“. „Tiknutí“ je vlastně generování události OnTimer, jediné to události, kterou Timer oplývá. Do obsluhy této události napíšeme veškerý kód, který se má periodicky zpracovávat.
Systémový časovač je velmi zajímavou komponentou, ale z hlediska začátečníka stačí znát informace uvedené dosud. Proto další popis označím jako poznámku pro pokročilé; neznamená to, že pro začátečníky bude nesrozumitelný, spíše, že informace z něho zatím nepotřebují.
Poznámky pro pokročilé uživatele:
Zobrazení průběhu procesu (komponenty Gauge, ProgressBar)
Zobrazit průběh běžícího procesu (se znalostí komponenty Timer) je hračka. Nejprve si popíšeme komponentu Gauge, v závěru se krátce podíváme na modernější ProgressBar.
Komponenta Gauge se nachází v paletě Samples. Jde o jakýsi „procentuální pruh“, o stupnici. Její důležitými vlastnostmi jsou MinValue a MaxValue (je tedy vidět, že nejvyšší hodnota nemusí být vždycky nutně 100) a Progress, což znamená (zjednodušeně), jaká je aktuální hodnota pruhu.
Příklad:
Bude-li MinValue = 0 a MaxValue = 200, hodnota Progress = 5 bude odpovídat 2%, které se také zobrazí na komponentě. Je vidět, že dochází k zaokrouhlování dolů.
Další pěknou vlastností je ForeColor, která znamená, jakou barvu bude mít „vytvářený“ (posouvající se) pruh. Poslední vlastností, která stojí za zmínku, je Kind, která specifikuje vzhled komponenty. Možné hodnoty:
Teď už máme všechny potřebné informace, které potřebujeme k vytvoření procentuálního pruhu. Pro jednoduchost opět nespácháme nic smysluplného, vytvoříme Gauge (raději se již přidržím originálního výrazu), který jen během 10 sekund doběhne do konce.
Na formulář umístíme komponenty Gauge a Timer. Do vlastnosti Interval komponenty Timer zadáme 100 milisekund (tedy desetkrát za sekundu hodiny tiknou“ a událost OnTimer téže komponenty bude vypadat takto (názvy komponent jsem nechal standardní, ač víme, že je to nepěkné a odsouzeníhodné):
procedure wndUkazkaGauge.Timer1Timer(Sender: TObject); begin Gauge1.Progress := Gauge1.Progress + 1; end;
Můžete si sami vyzkoušet různé tvary, vzhledy a barvy komponenty, naprosto šílenou barvu pruhu v Gauge demonstruje následující obrázek:
Komponenta ProgressBar je podobná, jen má trochu jiné názvy vlastností (místo MinValue, MaxValue a Progress jde o Min, Max a Position). Navíc přináší vlastnost Step, která určuje, o kolik se bude vlastnost Position vždy inkrementovat.
3 domácí úkoly
A na úplný závěr dva domácí úkoly pro pilné Delphaře:
Řešení domácích úkolů:
Nejprve si řekneme, jaké je řešení domácích úkolů:
Stačí v návrhu nastavit komponentě Timer (časovači) vlastnost Enabled = False a do události OnClick přidaného tlačítka napsat
Timer1.Enabled := True;
Časovač se pak spustí po stisku tlačítka a vzápětí se „rozjedou“ i Gauge a ProgressBar.
V Delphi 3 byla zkompilována aplikace, kterou ukazuje první obrázek (ListBox). Poznáte to podle odlišné ikonky v rohu :).
Důvodem toho, že oba for-cykly běží od jedničky, je fakt, že nechceme čísla vypisovat do „fixed“ buněk, které je lépe použít třeba k nadpisu apod.
Zásady tvorby hlavního menu
V aplikaci můžeme samozřejmě mít více typů menu. Nejdůležitější bývá hlavní menu, které je umístěno zpravidla úplně nahoře v okně. Zdálo by se, že k hlavním uživatelským menu není obecně co dodat. Opak je však pravdou, neboť jeho vytvoření si komentář rozhodně zaslouží. V následujícím textu shrnu některá fakta, která je vhodné respektovat (nebo se jim naopak vyhnout) při vytváření menu. Pokud patříte k vývojářům, kteří už nějaké ty aplikace vytvořili, asi vás bude následující text trochu nudit, neboť buďto už to všechno víte, nebo se tím řídit stejně nehodláteJ.
Druhy položek menu
Nejprve se podíváme, jaké druhy položek může hlavní menu obsahovat:
Obecné poznámky a konvence související s návrhem menu:
Položku menu je možné vložit přímo do pruhu s menu (viz obrázek, položka Zavři vše!). Tomuto způsobu je ale vhodné se vyhnout. Uživatelé totiž vybírají součásti pruhu z menu z důvodu prozkoumání struktury. Málokdo předpokládá, že by se mohl rovnou spustit příkaz. V případě, že se přesto rozhodnete vložit příkaz přímo do pruhu, vložte za něho alespoň vykřičník. Stále ale tvrdím, že je lepší vytvořit pro příkaz jedno rozvinutelné menu s jedinou položkou.
Umístění rozvinutelného menu do jiného rozvinutelného menu (rozvinutí ve dvou úrovních) je velmi běžné; ve Windows se v takovém případě objeví vizuální nápověda – malý trojúhelníček na pravé straně. Tento typ položek menu je ale vhodný jen pro zřídka používané příkazy. Častější výběr takové položky je totiž zdlouhavý. Někdy bývá lepší místo vytváření vnořené úrovně umístit skupinu přímo do hlavní úrovně menu a její položky oddělit čarami.
Už vůbec není vhodné používat větší množství zanoření. Nainstalujete-li si někdy aplikaci s více než dvěma úrovněmi zanoření, velmi brzy otestujete kvalitu odinstalačního programu této aplikace.
Větší opatrnost je třeba také při změnách popisů (položek) za běhu programu. Pokud například máte v menu položku Zobraz tabulku, je vhodné poté, co uživatel tuto položku zvolí, změnit její text na Skryj tabulku. Není ale příliš chytré měnit položky „brutálním“ způsobem, tedy nedržet se jejich logického významu a změnit třeba Zobraz tabulku na Zobraz seznam. Pro uživatele neexistuje příliš více matoucích akcí.
Jednou z nich ovšem je skrývání položek menu. Pokud se rozhodnete některou z položek zakázat (což je běžné), udělejte to nastavením Enabled na false (položka bude šedá, nebude ji možno vybrat, ale fyzicky bude na tomtéž místě v menu). Pokud položku zcela skryjete (Visible = false) a pokud něco takového občas periodicky zopakujete, stane se menu zcela nepoužitelným. Uživatelé totiž často hledají položky „popaměti“, jen podle jejich pozice.
Položky v menu by měly být logicky členěny do sekcí (vodorovnými čarami). Maximální počet položek v jedné sekci by rozhodně neměl přesáhnout šest.
Důležité je také dodržování standardní struktury menu. Určitě víte, že ve většině windowsových aplikací je menu jakoby „podle stejného mustru“. V souladu s principem prvořadosti uživatele je nutné uživateli vyjít vstříc používáním takových ovládacích prvků, které dobře zná. Pruh menu by měl začínat položkou Soubor, následovat by měly Úpravy, Zobrazit apod. Menu by mělo končit položkami Možnosti, Nástroje, Okno. Úplně poslední by měla být Nápověda. Navíc každé z těchto podmenu má specifické řazení položek (např. menu Soubor vypadá takto: Nový, Otevřít, Uložit, Uložit jako…, Nastavení tisku, Tisk, Konec (samozřejmě se tato struktura může lišit aplikace od aplikace, ale je dobré dodržovat alespoň jakousi základní linii).
Dalším bodem je používání klávesových zkratek. Jedním typem jsou „standarní“ zkratky typu Ctrl+C, Ctrl+V. Platí zcela totéž, co v předchozím případě (tedy neměnit zaběhnuté scénáře). Pokud v textovém editoru nastavíte pod zkratku Ctrl+C zavření dokumentu, příliš uživatelů nepotěšíte. Druhým typem klávesových zkratek jsou Alt+písmeno, která způsobí vybrání položky menu začínající tímto písmenem.
S tím souvisí další otázka – jazyková. Moje doporučení zní: děláte-li aplikaci pro využití v Česku, udělejte menu české. Jsme Češi, pišme česky a nestyďme se za to. Chcete-li svůj produkt šířit do zahraničí, vytvořte jazykové mutace. Kromě nesporného přínosu pro uživatele to vašemu programu dodá nádech profesionality. Naopak nedoporučuji „překládat“ klávesové zkratky. Uživatelé zpravidla znají ono Ctrl+C, a změníte-li to na Ctrl+K, bude to víc ke škodě než k užitku.
Poznámka pro pokročilé uživatele:
Ještě dodatek ke zmíněným jazykovým mutacím: řešit to lze buď tak, že se text položek menu načte z nějakého souboru (např. textového), nebo je menu v samostatném modulu aplikace, a nebo se vytvářejí spustitelné soubory s menu pro každý jazyk zvlášť, přičemž každý způsob má své výhody a nevýhody.
Konečně Delphi
Dostáváme se k tomu, jak vytvořit menu v naší aplikaci. Je to jednoduché a v Delphi za tím účelem existuje výkonný Menu Designer (viz obrázek, omlouvám se za prohřešek proti štábní kultuře – při vytváření tohoto příkladu jsem zapomněl přejmenovat formulář).
Umístěte na formulář komponentu MainMenu z palety Standard. Umístit ji můžete kamkoliv, protože se stejně zobrazí jen malý čtvereček (který bude samozřejmě za běhu skryt) a menu bude na správném místě. Když na tento čtvereček dvakrát kliknete, otevře se Menu Designer. V něm navrhujete vše podstatné. Kliknete na místo nové položky a napíšete její popis. Tím nastavíte její vlastnost Caption (každá položka menu má své vlastnosti viditelné v Object Inspectoru). Vlastnost Name se také nastaví automaticky, je ovšem na vás, budete-li zachovávat štábní kulturu a položky „správně“ přejmenovávat (v hodně rozsáhlém menu je to samozřejmě poměrně brutální úkol).
Menu vytvářené v Menu Designeru vypadá stejně, jako bude vypadat výsledná (run-time) podoba, s tím rozdílem, že samozřejmě vidíte všechny položky, tedy i položky s vlastností Visible = False, a navíc nevidíte význam vlastnosti Break (viz dále).
Všechny položky menu mají jedinou událost: OnClick. Do jejího těla napíšete kód, který se má provést při vybrání položky.
Chcete-li některou položku použít k rozbalení dalšího podmenu, klikněte na ni pravým tlačítkem a zvolte „Create Submenu“ (je to jen jedna z možností).
Chcete-li položky v menu opticky rozdělit vodorovnou čárou (což vypadá skvěle, pokud to nepřeženete a nebudete oddělovat každou druhou), jednoduše uveďte jako Caption položky pomlčku (-), ve výsledku bude tato položka vypadat právě jako ona čára.
Je dobré vědět, jak nastavíte „aktivní“ písmeno položky (tedy jak např. zařídit, že položka „Otevři“ se vybere stiskem Alt+O). Je to také snadné – jednoduše napište před zvolené písmeno znak & (např. &Otevřít, nebo Uložit j&jako).
Domácí úkol: zkuste vymyslet, co dělat, chceme-li mít znak & přímo v textu položky menu (např. „Vašek & Eva“).
Můžete používat také tzv. šablony menu. Dostanete se k nim také pravým tlačítkem a vybráním „Insert From Template“. Tyto šablony obsahují poměrně dost „standardizovaných“ menu (např. menu Files apod.), takže se nemusíte trápit s vypisováním všech položek. Takto vytvořené menu samozřejmě můžete dále upravovat a následně opět uložit jako šablonu pro příští použití. Jediným problémem tak zůstává, že standardní šablony obsahují samozřejmě pouze anglická menu.
Podívejme se na vlastnosti položky menu:
Vlastnost |
Význam |
Break |
Na tuto vlastnost raději zapomeňte (viz dále). |
Checked |
Je-li vedle položky zobrazeno zatrhávací „véčko“. Je to velmi elegantní metoda, jak do menu zakomponovat „povolovací“ položky. |
Default |
Málo používaná, přitom vcelku praktická vlastnost. Nastavení položky v podmenu jako Default znamená, že pokud uživatel poklepe na položku, která toto podmenu rozbalí, bude to bráno jako vybrání této „Default“ položky. |
Enabled |
Již zmíněná vlastnost „povolující“ položku. Zakážete-li položku, bude šedá a nebude ji možno vybrat. |
GroupIndex |
Slouží k logickému „rozčlenění“ položek menu. |
RadioItem |
Slouží k nastavení položky jako „výlučně vybratelné“. Příklad viz níže. |
ShortCut |
Specifikuje klávesovou zkratku této položky, příklad viz níže. |
Visible |
Slouží k nastavení položky jako neviditelné. Má mnohdy praktický význam, ne však za účelem opakovaného skrývání a odkrývání položky před uživatelovýma očima. |
V tabulce jsem několikrát slíbil, že níže něco blíže vysvětlím či předvedu. Ten okamžik právě nastal:
Vlastnost Break
Ne, prosím, tuto vlastnost raději nepoužívejte… Nastavíte-li u některé položky vlastnost Break na mbBreak, příp. mbBarBreak, zobrazí se tato položka v následujícím sloupci / na následujícím řádku. Je to odporné a používejte to jen k demonstraci toho, jak by menu vypadat nemělo (viz obrázek).
RadioItem, GroupIndex
Nastavíte-li některým položkám stejnou hodnotu GroupIndex a RadioItem = True, bude možno vybrat (zatrhnout, Checked) pouze jednu z nich. V takovém případě budete ale sami muset nastavovat vlastnost Checked, i když ta se u „normálních“ položek nastavuje sama podle toho, kam uživatel klikne.
Klávesové zkratky
V době návrhu nejsnáze vložíte klávesovou zkratku tak, že ji vyberete v Object Inspectoru, kde je k dispozici nepřeberné množství zkratek. Při běhu programu je to trochu obtížnější. Příkladem hodnoty je Ctrl+C. Zadáte ji takto:
MnItmOtevri.ShortCut := ShortCut(Word(`O`), [ssCtrl]);
Zkratka se vedle položky v každém případě objeví (vypíše) automaticky.
Již jsem se zmínil o tom, že není dobré manipulovat s položkami menu za běhu programu (přeskupovat, skrývat, přejmenovávat apod.). Přesto občas nastává situace, kdy potřebujeme použít jiné položky v menu nebo celé úplně jiné menu. Typicky jde o přítomnost dvou typů menu (pro „začátečníky“ a „pokročilé uživatele“). V takovém případě si prostě vytvořte dvě rozdílná menu (na formulář umístěte dvě komponenty MainMenu), jedno z nich nastavte jako implicitní (vlastnost Menu formuláře) a někam (klidně i do menu) přidejte příkaz zobrazující druhé menu.
Položky menu mají poměrně dost slušné množství metod. Jednou z nich je např. Add, která přidá položku na konec menu. Příklad:
procedure Form1.Button1Click(Sender: TSender); var MnItmNovaPolozka: TMenuItem; begin mnItmNovaPolozka := TMenuItem.Create(Self); mnItmNovaPolozka.Caption := `Nová položka`; MnItmFile.Add(mnItmNovaPolozka); end;
V souvislosti s metodou Add možná stojí za úvahu, co dělat, potřebujeme-li za běhu přidat do menu nějakou položku. Pokud už to potřebujete, máte (hrubě rozděleno) tři možnosti:
Každopádně se při změnách položek menu příliš nerozšoupněte.
Lokální (pop-up) menu
V poslední době se snad nevyskytuje nová aplikace, která by neobsahovala pop-up menu (lokální menu, kontextové menu, dokonce jsem zaznamenal vpravdě příšerný výraz vyskakovací menu). Otevře se zpravidla po stisku pravé klávesy myši. Jeho kouzlo spočívá hlavně v tom, že obsahuje právě takové položky, které jsou v daném okamžiku aktuální (vztahují se k vybranému elementu).
Delphi poskytuje komponentu PopupMenu. Je dosti podobná komponentě MainMenu, a to jak vizuálně (čtvereček), tak způsobem práce (stejný Menu Designer).
Proto nebudeme zabíhat do podrobností. Jediné, co vysvětlíme, bude zajištění toho, aby se v každém okamžiku otevřelo vždy to „správné“ kontextové menu. Zařídí se to tak, že dané komponentě (např. formuláři, editačnímu oknu, memu…) nastavíme jako hodnotu jeho vlastnosti PopupMenu název odpovídajícího kontextového menu (musíme tedy v aplikaci samozřejmě mít více komponent PopupMenu).
Celá komponenta PopupMenu má navíc (od MainMenu) jednu vlastnost a jednu metodu. Vlastnost se jmenuje AutoPopup a její hodnota True (implicitní) znamená, že se menu objeví po stisku pravého tlačítka myši, tedy tak, jak jsme zvyklí. Zakážeme-li „vynořování“ menu (tedy AutoPopup = False), menu se nezobrazí a vyvolat ho lze jen programově jeho metodou Popup. Přidaná metoda komponenty PopupMenu se jmenuje OnPopup a do jejího těla napíšeme kód, který se má vykonat, „vynoří-li se“ (otevře) kontextové menu.
Poznámka pro pokročilé uživatele:
Událost OnPopup je generována těsně předtím, než se menu rozbalí. Můžete tedy například otestovat nějaké konkrétní nastavení a podle něho upravit položky v menu.
O položkách kontextového menu platí beze zbytku to, co bylo řečeno k položkám hlavního menu.
Řešení domácího úkolu z minulého dílu
Chcete-li mít znak „&“ přímo v textu položky menu, v návrhu musíte ampersand zdvojit, tedy napsat např. „Vašek && Eva“. Chcete-li navíc podtrhnout (učinit aktivním) písmeno k, napište „Vaše&k && Eva“.
Objektově orientovaná architektura
Důvod, proč jsem zařadil popis objektově orientované architektury, spočívá v tom, že si nejsem jist, zda je vhodné toto téma ignorovat a opájet se představou, že jej buď všichni znají, nebo nepotřebují.
Co to je vlastně objektově orientované programování? Často vypadá spíše jako náboženství než jako přístup k programování. Tento programovací styl používá oddělené objekty, které obsahují (zapouzdřují) svá data i kód. Tyto objekty jsou stavebními prvky aplikace. Obvyklým důvodem používání objektů je možnost jednodušších zásahů do programu. Fakt, že data i kód jsou jaksi pohromadě (a že si tedy každý objekt plně za svá data zodpovídá), znamená, že proces odstraňování chyb (a také modifikace vlastností objektu) má minimální efekt na okolní objekty.
Jazyk objektově orientovaného programování obvykle zahrnuje implementaci alespoň tří principů:
Pokud bychom se měli pokusit stanovit základní dva termíny objektově orientovaného programování, šlo by zřejmě o třídu a objekt. Třída je datový typ, který si můžete představit jako jakousi šablonu určitých objektů (třeba aut), která popisuje chování konkrétních objektů (aut). Konkrétní auta vytváříme podle této „šablony“. Třída obsahuje nějaká svá (interní) data (tzv. atributy) a své metody (tj. procedury a funkce). Třída by měla charakterizovat chování a vlastnosti několika podobných objektů (např. aut více značek). Objekt je instancí třídy, jakýmsi konkrétním výskytem (konkrétním, fyzicky existujícím exemplářem auta). Jinak řečeno, je to proměnná datového typu, který představuje třída. Objekty při běhu programu zabírají paměť pro svou reprezentaci.
Vztah mezi objektem a třídou si lze představit třeba jako vztah mezi proměnnou a datovým typem.
Abychom si vše názorně předvedli (a také abychom si ukázali, jak to prakticky udělat v Object Pascalu - a tedy také v Delphi), vytvoříme třídu automobil, která bude mít následující atributy:
Třída bude mít tyto metody:
Abychom hovořili všichni o tomtéž, provedeme to tak: v Delphi vytvoříme novou aplikaci s jedním formulářem, na kterém bude jedno tlačítko.Veškerý níže uvedený kód budeme psát do modulu tohoto formuláře (standardně Unit1.pas).
type TAuto = class Znacka: String; RokVyroby, Benzin, Kapacita: Integer; procedure VypisInfo; function Natankuj(Kolik: Integer): Boolean; end;
Podotýkám, že tento kód se bude vyskytovat v sekci interface příslušného modulu (souboru). Abychom s touto třídou mohli pracovat, je ještě nutné říci, jak budou vypadat těla zmíněných metod. Tato těla budou zapsána v sekci implementation (rozdíl mezi oběma sekcemi vysvětlíme níže), a aby kompilátor věděl, ke které třídě budou těla patřit (lze totiž mít víc různých tříd a v každé např. metodu s názvem Natankuj), používá se v Object Pascalu tzv. tečková notace:
procedure TAuto.VypisInfo; begin ShowMessage( Format(`%s, %d: %d (%d).`, [Znacka, RokVyroby, Benzin, Kapacita]) ); end; function TAuto.Natankuj(Kolik: Integer): Boolean; begin Result := (Benzin + Kolik) <= Kapacita; Benzin := Max(Kapacita, (Benzin + Kolik)); end;
Poznámky:
Nyní ještě provedeme deklaraci proměnné typu třída a ukážeme si, jak metody volat a jak s proměnnou pracovat. Následující kód bude umístěn v sekci implementation, v jakékoliv proceduře či funkci (např. v metodě ošetřující událost OnClick nějakého tlačítka umístěného na formulář).
procedure TwndHlavni.btnStartClick(Sender: TObject); var MujBlesk: TAuto; begin // (A) MujBlesk.Znacka := `Skoda 1000MB`; // (B) MujBlesk.RokVyroby := 1950; MujBlesk.Benzin := 0; MujBlesk.Kapacita := 5; MujBlesk.VypisInfo; if not MujBlesk.Natankuj(2) then ShowMessage(`Nepřehánějte to s tím benzínem!`); MujBlesk.VypisInfo; end;
Nyní si zkuste program zkompilovat a spustit. Vše bude v pořádku, ale jen do okamžiku, než kliknete na „startovací“ tlačítko. Pak se program zboří. (Tedy – nezboří se úplně, ale fungovat nebude a bude generována tzv. výjimka – viz dále).
Proč tomu tak je? Vysvětlení příčiny je složitější a souvisí se základní myšlenkou objektově orientovaného modelu. Musíme si říci několik informací o vytváření instancí. Následující řádky jsou klíčové pro pochopení OOP.
Základní myšlenka objektově orientovaného modelu spočívá v tom, že proměnná datového typu třída (nemluvíme o instanci objektu, jen o proměnné), jako je např. MujBlesk z předchozího příkladu, neobsahuje 'hodnotu' objektu. Neobsahuje ani objekt auto ani atributy auta. Obsahuje pouze odkaz (ukazatel) na místo v paměti, kde je vlastní objekt fyzicky uložen.Vytvoříme-li proměnnou tak, jak jsme to předvedli o pár řádků výše (pomocí klíčového slova var), nevytvoříme zmíněnou fyzickou reprezentaci objektu (místo pro uložení objektu v paměti), ale jen odkaz na objekt (místo pro uložení tohoto odkazu v paměti)! Vlastní instanci musíme vytvořit ručně zavoláním jeho metody Create, což je tzv. konstruktor (procedura určená k alokování paměti a k inicializaci objektu).
Řešení je tedy prosté: mezi řádky označené (A) a (B) v předchozí proceduře vsuneme volání konstruktoru:
begin // (A) MujBlesk := TAuto.Create; MujBlesk.Znacka := `Skoda 1000MB`; // (B)
Kde se vzal konstruktor Create? Je to konstruktor třídy TObject, od něhož všechny ostatní třídy (a tedy i tato) dědí (viz dále).
Když jsme objekt vytvořili, je třeba jej nakonec také zrušit. To provedeme zavoláním metody Free:
MujBlesk.VypisInfo; MujBlesk.Free; end;
Konstruktor
Metodu Create jsme volali kvůli přidělení paměti objektu. Často ale objekt potřebujeme také inicializovat. Za tím účelem přidáváme do třídy konstruktor. Lze použít upravenou verzi metody Create nebo definovat konstruktor úplně nový. Není však příliš vhodné pojmenovat konstruktor jinak než Create.
Konstruktor je velmi specifická procedura, protože Delphi samy alokují paměť pro objekt, nad kterým jej spustíte. Použití konstruktoru tedy za vás vyřeší problémy s alokací paměti. Konstruktor se deklaruje užitím klíčového slova constructor. Přidáme tedy do třídy TAuto konstruktor:
type TAuto = class Znacka: String; RokVyroby, Benzin, Kapacita: Integer; constructor Create(ZZnacka: String; RRokVyroby, BBarva, BBenzin, KKapacita: Integer); procedure VypisInfo; function Natankuj(Kolik: integer): Boolean; end;
Musíme také zapsat tělo konstruktoru. To se zapisuje kamkoliv do modulu, klidně mezi další procedury a funkce, ale konvencí je zapisovat jej jako první podprogram modulu, hned na počátku sekce implementation. Správně bychom měli v konstruktoru každé nově vytvořené (odděděné – viz dále) třídy nejdříve vyvolat konstruktor předka a následně uvést své vlastní, specializující příkazy. Pro třídu odděděnou od TObject to jistě není třeba, ale přesto je to vhodné a formálně správné.
constructor TAuto.Create(ZZnacka: String; RRokVyroby, BBarva, BBenzin, KKapacita: Integer); begin inherited Create; Znacka := ZZnacka; RokVyroby := RRokVyroby; Benzin := BBenzin; Kapacita := KKapacita; end;
Takhle teď bude vypadat tělo procedury btnStartClick, ve které pracujeme s objektem MujBlesk třídy TAuto:
procedure wndHlavni.btnStartClick(Sender: TObject); var MujBlesk: TAuto; begin MujBlesk := TAuto.Create(`Skoda 1000MB`, 1950, 0, 5); MujBlesk.VypisInfo; if not MujBlesk.Natankuj(2) then ShowMessage(`Nepřehánějte to s tím benzínem!`); MujBlesk.VypisInfo; MujBlesk.Free; end;
Destruktor
Destruktor je jednoduše řečeno opakem konstruktoru. V předchozím příkladu jsme se s ním již setkali (v řádce MujBlesk.Free). Implicitní název je tedy Free. Jeho funkcí je „zničit“ objekt (uvolnit jej z paměti). Platí, že dynamickou paměť, kterou jsme alokovali v konstruktoru, bychom měli v destruktoru uvolnit.
Typy přístupu k datům
Třída může obsahovat teoreticky jakékoliv množství atributů a metod. Správně by měla být vlastní data třídy skrytá (neboli zapouzdřená) uvnitř třídy. K těmto skrytým datům by měly přistupovat pouze metody téže třídy. Není vhodné nechat kohokoliv manipulovat s daty naší třídy. Jedním z principů objektově orientovaného programování je „každý je zodpovědný za svá data“.
Optimální přístup (z hlediska zásad OOP) je tedy takový: nelze „zvenku“ přistupovat k datům, ale jsou k dispozici veškeré potřebné metody, které tento přístup (a to jak čtení, tak zápis) zajišťují. Tím je zajištěn tzv. autorizovaný přístup k datům.
V Object Pascalu toho dosáhneme používáním specifikátorů přístupu:
Ukážeme si, jak zajistit autorizovaný přístup k datům v naší třídě TAuto (protected atributy využijeme ve zděděné třídě TNakladak – viz dále).
type TAuto = class protected Znacka: String; RokVyroby, Benzin, Kapacita: Integer; public constructor TAuto.Create(ZZnacka: String; RRokVyroby, BBarva, BBenzin, KKapacita: Integer); procedure VypisInfo; function Natankuj(Kolik: integer): Boolean; end;
Poznámka pro pokročilé uživatele:
V rámci pravdomluvnosti je nutné podotknout, že v Delphi, na rozdíl třeba od C++, je trochu jiný význam specifikátoru private. V C++ není sekce private přístupná nikomu jinému než vlastní třídě, zatímco zde můžeme k private položkám přistupovat z celého modulu (zdrojového souboru), ve kterém je daná třída deklarována.
Sekce modulu interface a implementation
Do sekce interface zapisujeme vše, co má být viditelné i v ostatních modulech – datové typy, hlavičky procedur, funkcí apod. V sekci implementation se jednak vyskytují těla funkcí a procedur, jejichž hlavičky jsou v interface, a jednak všechny „soukromé“ prvky (tedy klidně i další funkce potřebné jen v rámci modulu).
Dědičnost
Dědičnost je vlastnost objektově orientovaného programování, kterou mohu využít, pokud potřebuji vytvořit novou třídu podle vzoru konkrétní (již hotové) třídy, ale s dalšími specifickými vlastnostmi (tedy poněkud 'více konkrétní' třídu).
Jako příklad uvedeme vytvoření nákladního auta pomocí existující třídy auto. Třída nákladní auto bude mít navíc atribut nosnost a bude mít vlastní konstruktor. Metoda VypisInfo bude navíc vypisovat i nosnost vozu.
type TNakladak = class (TAuto) // dědíme od třídy TAuto private Nosnost: integer; public constructor Create(ZZnacka: String; RRokVyroby, BBarva, BBenzin, KKapacita, NNosnost: Integer); procedure VypisInfo; end;
Těla konstruktoru a pozměněné metody:
constructor TNakladak.Create(ZZnacka: String; RRokVyroby, BBarva, BBenzin, KKapacita, NNosnost: Integer); begin inherited Create(ZZnacka, RRokVyroby, BBarva, BBenzin, KKapacita); Nosnost := NNosnost; end; procedure TNakladak.VypisInfo; begin ShowMessage(Format(`%s, %d: %d (%d). Nosnost = %d`, [Znacka, RokVyroby, Benzin, Kapacita, Nosnost])); end;
Nyní si vyzkoušíme práci s novou třídou:
var Vejtraska: TNakladak; begin Vejtraska := TNakladak.Create(`Avia`, 1980, 20, 200, 10); Vejtraska.VypisInfo; Vejtraska.Free; end;
Poznámka k typové kompatibilitě v případě dědičnosti: objekt třídy potomka můžeme kdykoliv použít na místě objektu třídy předchůdce. Opačný postup však není možný. Příklad:
MujBlesk := Vejtraska; // lze Vejtraska := MujBlesk; // NELZE, chyba!!!
Polymorfismus, virtuální a abstraktní metody
Bohužel, článek je již teď mnohem delší, než je únosné, proto nezbývá, než další koncepty zmínit jen velmi stručně. Pascalovské funkce a procedury jsou obvykle založeny na tzv. statické vazbě. To znamená, že volání metody je „vyřešeno“ již překladačem (a linkerem). Všechny metody z našich dosavadních příkladů mají statické vazby. Objektově orientované jazyky však umožňují použití jiného druhu vazby, známého jako pozdní vazba (někdy též dynamická).
Výhoda tohoto přístupu je známa jako polymorfismus. Předpokládejme, že naše dvě třídy TAuto a TNakladak definují metodu s dynamickou vazbou. Potom lze tuto metodu aplikovat na všeobecnou proměnnou (jako např. MujBlesk), která může za běhu odkazovat na objekty obou tříd. Určení metody, která bude volána, bude provedeno za běhu, podle konkrétní situace. K definici se používá klíčových slov virtual a override:
type TAuto = class procedure VypisInfo; virtual; end; TNakladak = class(TAuto) procedure VypisInfo; override; end;
Klíčovým slovem abstract deklarujeme metody, které budou definovány až v potomcích současné třídy. Prakticky z toho plyne, že ve třídě nemusíme zapisovat (definovat) tělo metody deklarované jako abstraktní.
type TAuto = class procedure Zmen_rok; virtual; abstract; end;
Běhové chyby mohou vzniknout v důsledku „čehosi prohnilého“ při provádění funkcí, procedur a metod pracujících v rámci knihovny vizuálních komponent (Visual Component Library, VCL), běhové knihovny (Runtime Library, RTL) či v rámci operačního systému. Jakmile tušíme, že by se v daném úseku programového kódu mohla vyskytnout chyba, musíme všechny možnosti správně ošetřit (a to i v případě, že pravděpodobnost vzniku chyby v daném místě je takřka zanedbatelná, neboť Murphyho (i jiné) zákony nás učí, že může-li se něco pokazit, zaručeně se to pokazí).
Vytvoříme proceduru, na které budeme demonstrovat různé přístupy k ošetření chyb. Vytvořte formulář, přidejte na něj tlačítko (Button) a ošetřete jeho kliknutí (OnClick) takto:
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; C := A div B; ShowMessage(IntToStr(C)); btnTest.Caption := IntToStr(C); end;
Vypisujeme výsledek celočíselného podílu dvou čísel (operátor div).
V takto zapsané proceduře neošetřujeme vůbec žádné chyby. Vzhledem k tomu, co jsme si napřed přiřadili do obou proměnných, je zřejmé, že nastane chyba. Tato chyba způsobí výjimku, kterou generuje operátor div. Když si náš program spustíte a kliknete na tlačítko, uvidíte zprávu o výjimce. I když sami nic neošetřujeme, výjimka je zjevně ošetřena. Proč tomu tak je, si povíme níže.
Tradiční způsob ošetření chyb
Tradiční styl spočívá zpravidla v podmínkách a testech, většinou kontrolujeme návratové kódy a „výstupy“ procedur a funkcí a dále operandy u operací apod. Problémy (nevýhody) tohoto postupu jsou (zjednodušeně řečeno) následující:
Podívejme se, jak bychom tradičně ošetřili chybu u procedury z příkladu:
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; if B <> 0 then begin C := A div B; ShowMessage(IntToStr(C)); btnTest.Caption := IntToStr(C); end else begin ShowMessage(`Chystáte se dělit nulou!`); btnTest.Caption := `Chyba`; end; end;
Ošetření chyby s užitím výjimek
Nyní tutéž proceduru přepíšeme s užitím výjimky. Nevadí, že zatím neznáme přesnou syntaxi práce s výjimkami ani definici vlastní výjimky. K tomu se dostaneme vzápětí, nyní jde jen o demonstraci toho, jak bude procedura vypadat.
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; try C := A div B; ShowMessage(IntToStr(C)); btnTest.Caption := IntToStr(C); except on EDivByZero do begin ShowMessage(`Chystáte se dělit nulou!`); btnTest.Caption := `Chyba`; end; end; end;
Takto zapsaná procedura vypíše chybovou hlášku a do titulku tlačítka napíše slovo „Chyba“ jako indikaci chyby. Pokud změníte hodnotu proměnné B na nenulovou, vypíše se místo toho výsledek podílu (tedy nula) a ten bude také přiřazen do titulku tlačítka.
Tento triviální příklad demonstruje v nejhrubších rysech práci s výjimkami. Celý mechanismus výjimek je postaven na čtyřech klíčových slovech:
Než si ukážeme konkrétní práci s výjimkami, řekneme si něco o výjimkách a stavu programu. Dojde-li k výjimce, hledá se „ošetřující procedura“ (tzv. handler výjimky), tedy procedura, která výjimku ošetří. Není-li nalezena v dané části programu, je výjimka „přenesena“ (tzv. propagována) výše, a to až do okamžiku, kdy se o ni někdo postará. V extrémním případě jde tento postup až k tzv. implicitnímu handleru výjimek v Delphi (proto je nakonec ošetřena každá výjimka). Důležité je, že po ošetření výjimky program pokračuje kódem následujícím po kódu handleru a nikoliv kódem následujícím po kódu, který způsobil výjimku.
Podívejme se nyní blíže na sekci finally. Ta se používá k provedení nějaké činnosti v případě výjimky i v případě normálního provedení (typicky vyčištění paměti, apod.). Kód po finally bude vykonán vždy po opuštění bloku try, ať již k výjimce došlo nebo nikoliv.
Podívejme se na příklad ošetření výjimky bez použití bloku finally (místo čištění paměti budeme měnit titulek formuláře) :
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; try C := A div B; wndHlavni.Caption := `Nazdar`; except on EDivByZero do begin ShowMessage(`Chystáte se dělit nulou!`); btnTest.Caption := `Chyba`; end; end; end;
V případě dělení nulou nebude nikdy provedeno nastavení titulku formuláře na „Nazdar“. Řešením je užití bloku finally:
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; try C := A div B; finally wndHlavni.caption := `Nazdar`; end; end;
Nyní máme jistotu, že nastavení titulku se vždy provede. Bohužel ale zase nemáme ošetřenou vlastní výjimku, což není úplně šikovné, protože kvůli tomu to všechno děláme. Navíc blok finally toto ošetření neumožňuje (lépe řečeno – ošetření sice umožňuje, ale výjimka se bude šířit (propagovat) dál, protože z hlediska Delphi stále ošetřena nebude). Takže nám nezbývá, než zkombinovat finally a except blok (jde vlastně o zanoření dvou bloků):
procedure wndHlavni.btnTestClick(Sender: TObject); var A, B, C: Integer; begin A := 0; B := 0; try try C := A div B; except on EDivByZero do begin showmessage(` Chystate se delit nulou!`); btnTest.Caption := `Chyba`; end; end; finally wndHlavni.Caption := `Nazdar`; end; end;
Poznámka: pokud chcete tyto příklady přesně opisovat a zkoušet, mějte na paměti, že kompilátor vůbec nezařadí do výsledného programu příkaz c := a div b, pokud hodnotu c někde dále nepotřebujete! A nekamenujte mě prosím za nesmyslnost uvedených příkladů, jsem si jí vědom; chápejte uvedené příklady jen jako ukázku.
Syntaxe bloku except
Blok except umožňuje více možností použití:
try except on do on do else end;
Vidíme, že v sekci else můžeme ošetřit jakoukoliv výjimku, i tu, kterou jsme neočekávali (a tedy nezařadili do výčtu on .. do). V případě ošetřování neznámých podmínek ale buďte maximálně opatrní. Zpravidla je nejlepší nechat ošetření neznámé výjimky na implicitním handleru Delphi. Není dobrý ani nápad výjimku ošetřit (např. indikovat MessageBoxem) a následně znovu vyvolat, protože pak o ní bude uživatel informován dvakrát: vaším MessageBoxem a MessageBoxem Delphi. Takže dostáváme zlaté pravidlo:
Buď výjimku ošetříme, nebo ji necháme bez povšimnutí ošetřit standardně!
Pokud už chcete výjimku sami ošetřit, je možné například použít vyvolání nové výjimky se zadaným chybovým textem:
raise EConvertError.Create(`Nelze zkonvertovat!`);
Poznámky pro pokročilé uživatele:
Souborová podpora v Object Pascalu
Práci se soubory v Object Pascalu si pouze připomeneme, a to na příkladu. Vytvořte aplikaci, na formulář umístěte jedno tlačítko a jednu komponentu Memo. Stiskem tlačítka se do Memo načte obsah textového souboru DATA.TXT z aktuálního adresáře (ošetříme tedy událost OnClick zmíněného tlačítka):
type TTextSoub = TextFile; procedure TwndHlavni.btnOPClick(Sender: TObject); var Soub: TTextSoub; Radka: String; begin AssignFile(Soub, `data.txt`); Reset(Soub); while not Eof(Soub) do begin ReadLn(Soub, Radka); memoSoubor.Lines.Add(Radka); end; CloseFile(Soub); end;
Poznámky
Možná jste z Pascalu zvyklí na výrazy Text (pro označení textového souboru), Assign (pro asociování fyzického souboru s proměnnou) či Close (pro uzavření souboru). Ty jsou v Delphi nahrazeny výrazy TextFile, AssignFile, resp. CloseFile. Důvodem je, že původní znění je v Delphi použito k jinému účelu (např. Text je vlastnost některých komponent, např. Memo či Edit). „Staré“ názvy v Delphi sice přesto zůstaly zachovány i s původním významem, je ale nutné uvozovat je kvalifikátorem modulu System. Například místo Assign(F) použijte System.Assign(F).
Využijete-li poznatků předchozí kapitoly, jistě sami snadno přijdete na to, jak provést ošetření chyb. V Delphi je nejefektivnějším způsobem použití mechanismu výjimek (podrobněji viz níže). V „původním“ Pascalu bylo ošetření chyb samozřejmě řešeno trochu jinak (všichni „pascaláci“ si jistě pamatují nezapomenutelné IOResult apod…
Chcete-li pracovat s jiným než textovým souborem, použijte konstrukci file of <typ>, např. file of Integer (soubor s celými čísly).
Souborová podpora v Delphi
Někdy se ovšem nechceme zdržovat vymýšlením konstrukcí vycházejících ze standardního Pascalu. Pak máme k dispozici množství výkonných podpůrných nástrojů pro práci se soubory přímo v Delphi. Asi nejvýznamnějšími prvky jsou metody LoadFromFile, resp. SaveToFile, které dokáží načíst (a zobrazit) obsah souboru zápisem jediné řádky kódu. Nejde přitom zdaleka jen o textové soubory (jak jsme poznali např. v rámci popisu komponenty Memo v kapitole 7).
Metody LoadFromFile, resp. SaveToFile jsou dostupné např. pro třídy TStrings, TPicture, TBitmap, pro některé datové komponenty (TBlobField, TMemoField, TGraphicField), pro grafické formáty (TGraphic, TIcon, TMetaFile) nebo pro OLE zásobníky. Podobné metody jsou k dispozici také u objektů TMediaPlayer.
Poznámka pro pokročilé uživatele:
Jinou třídou týkající se souborů je TIniFile. Ta popisuje speciálně inicializační soubory Windows 3.1. Ve vyšších Windows aplikace zpravidla nahrazují INI soubory systémovými registry. Delphi obsahují i třídy určené pro takovou situaci – TRegistry, resp. TRegistryIniFile. Třídy TIniFile a TRegistryIniFile mají společného předka (TCustoIniFile), proto umožňují kromě jiného změnu přístupu (registry vs. INI soubory) s minimálními změnami kódu.
Modifikujeme nyní příklad uvedený výše, jen použijeme zmíněnou metodu LoadFromFile.
procedure TwndHlavni.btnOPClick(Sender: TObject); begin memoSoubor.Lines.LoadFromFile(`data.txt`); end;
Myslím, že úspora je více než zřejmá:-). Nemusíme deklarovat žádné typy, definovat žádné proměnné, otvírat žádné soubory, asociovat žádné názvy.
Poznámka pro pokročilé uživatele:
Další zajímavou formou v Delphi je podpora souborových streamů, která vychází z abstraktní třídy TStream. Tato třída má tři následníky: TFileStream, THandleStream a TMemoryStream. Většina jejich metod se týká komponent a používají je zpravidla jejich tvůrci, ale některé jsou použitelné pro kohokoliv (např. ReadBuffer).
Chyby při práci se soubory
Než se vrhneme na standardní dialogy, podíváme se, jak ošetřovat chyby při práci se soubory. Delphi při jakékoliv I/O chybě (tedy chybě vstupu a výstupu) generují výjimku EInOutError. Přesnější specifikace chyby se objevuje (je vracena) v lokální proměnné ErrorCode, která může nabývat následujících hodnot:
Error Code |
Význam |
File not found |
|
Invalid file name |
|
Too many open files |
|
Access denied |
|
Disk read error – hlášeno, chcete-li číst za koncem souboru - EOF (End of File) |
|
Disk write error – hlášeno, chcete-li zapisovat na zaplněný disk |
|
File not assigned – hlášeno, chcete-li použít proměnnou typu Text nebo TextFile bez předchozího volání Assign() |
|
File not open – hlášeno, chcete-li pracovat se souborem, jenž nebyl otevřen některou z funkcí Reset(), Rewrite(), Append() |
|
File not open for input – hlášeno, chcete-li číst ze souboru otevřeného pro zápis |
|
File not open for output – hlášeno, chcete-li zapisovat do souboru otevřeného pro čtení |
|
Invalid numeric format – hlášeno, chcete-li číst nečíselnou hodnotu z textového souboru do číselné proměnné |
Standardní obsluha výjimek (implicitní handler Delphi) dokáže samozřejmě výjimku ošetřit, i když ji sami neodchytnete (viz díl 10), a vypíše MessageBox s odpovídající hláškou podle konkrétní hodnoty ErrorCode. „Nezná“ ovšem všechny hodnoty ErrorCode, a tak se může stát, že uživateli vypíše hlášku typu „I/O Error 103“. Pokud byste tedy chtěli například hlášku počeštit (což u českých programů výrazně doporučuji!), nebo ošetřit opravdu všechny chyby, odchytněte a ošetřete výjimku sami. Podívejte se na následující příklad, který demonstruje ošetření chybových stavů v souvislosti s načítáním obsahu souboru do Memo.
procedure TwndHlavni.btnOPClick(Sender: TObject); var Soub: TTextSoub; Radka: String; begin AssignFile(Soub, `data.txt`); try Reset(Soub); try while not Eof(Soub) do begin ReadLn(Soub, Radka); memoSoubor.Lines.Add(Radka); end; // while finally CloseFile(Soub); end; // finally except on E:EInOutError do case E.ErrorCode of 2: ShowMessage(`Soubor nenalezen!`); 103: ShowMessage(`Soubor neotevřen!`); else ShowMessage(`Chyba při práci se souborem: ` + E.Message); end; // case end; // except end; // procedure
Asi byste sami dokázali vysvětlit, že ve vnějším bloku try se pokusíme soubor otevřít, a nepodaří-li se to, ošetříme výjimku (oznámíme chybu) a končíme. Ve druhém vnořeném bloku try (vnitřnějším) se pokusíme soubor načíst do komponenty Memo. Je jisté, že v tuto chvíli je soubor správně otevřen, takže v každém případě soubor posléze zavřeme (sekce finally).
V tomto příkladu tedy máme ošetřeny chyby při otvírání souboru i při vlastním čtení. Pokud by ovšem např. ReadLn generovala při chybě výjimku EReadError, chyby čtení by ošetřeny nebyly (neboť odchytáváme jen EInOutError). To ona ovšem negeneruje).
Poznámky pro pokročilé uživatele:
S problematikou I/O chyb souvisí také výjimky EReadError, resp. EWriteError. První je generována, pokusí-li se aplikace načíst ze streamu větší počet bytů, než je možno. Může být také generována, nedokáže-li Delphi načíst vlastnost při vytváření formuláře (komponenta načítá zdroje – resource – nekorektně, příp. zdroje jsou chybné). Analogicky funguje EWriteError.
Představte si, že byste měli v aplikaci mnoho procedur a funkcí, které budou pracovat se souborem. Opisovat vždy tentýž kód spojený s obsluhou výjimek je jistě nepříjemné a nudné. Naštěstí je to také zbytečné, neboť lze použít událost OnException objektu TApplication, ve které napíšeme ošetřující kód „jednou provždy“. Jednoduchá ukázka, ve které se aplikace po vzniku každé neošetřené výjimky ukončí:
procedure TwndHlavni.FormCreate(Sender: TObject); begin Application.OnException := AppException; end; procedure TwndHlavni.AppException(Sender: TObject; E: Exception); begin Application.ShowException(E); // ukaž chybovou zprávu Application.Terminate; // ukonči aplikaci end;
Další příkazy pro práci se soubory
Pouze telegraficky si popíšeme několik dalších metod Delphi, které se soubory (a adresáři) úzce souvisí.
Funkce |
Význam |
FileExist(jmeno) |
Vrací True, existuje-li zadaný soubor |
DeleteFile(jmeno) |
Smaže zadaný soubor a vrací True, podařilo-li se |
RenameFile(stare, nove) |
Přejmenuje soubor a vrací True, podařilo-li se |
ChangeFileExt(jmeno, pripona) |
Změní příponu souboru na zadanou a vrací nový název souboru |
ExtractFileName(uplne_jmeno) |
Extrahuje název souboru (tj. odstraní případnou cestu). Vrací získaný název. |
ExtractFileExt(jmeno) |
Vrací příponu daného souboru |
Poznámka: nekamenujte mě prosím za nesprávně (stručně) uváděnou syntaxi. Použil jsem ji pro úsporu místa, jistě si sami dokážete odvodit (příp. najít v helpu), že např. „úplný funkční prototyp“ funkce FileExists() vypadá takto:
function FileExists(const FileName: string): Boolean;
Poznámka pro pokročilé uživatele:
Dále existuje celá řada funkcí pracujících s adresáři a se jmény souborů v jednotce SysUtils.
K souborům lze v Delphi přistupovat též pomocí FileCreate(), FileOpen(), FileSeek(), FileWrite(), FileClose(). Tyto funkce tvoří 'samostatnou' množinu funkcí pro práci se soubory za pomoci handlerů.
Úvodem této části se podívejme na další komponenty Delphi, které zajišťují práci se soubory, adresáři a disky. Nejde už o přímou práci, spíše o zobrazení názvů, výběr, apod. Tím se nenápadně dostaneme ke standardním dialogům, které jsou v podstatě jakousi standardní kombinací níže uvedených komponent.
Komponenty jsou sice poněkud zastaralé, přesto je lze použít. Nacházejí se v paletě Win 3.1, jedna také na paletě Samples.
Komponenta |
Význam |
FileListBox |
Jde o specializovaný ListBox (seznam) určený k výpisu všech souborů v aktuálním adresáři. |
DirectoryListBox |
Jde opět o specializovaný seznam určený k zobrazení adresářové struktury na daném disku. |
DriveComboBox |
Specializovaný rozbalovací seznam, zobrazuje název aktuálního disku a „sbalený“ seznam dalších dostupných disků. |
FilterComboBox |
Specializovaný rozbalovací seznam, zobrazuje aktuální souborovou masku (neboli filtr, typicky All Files - *.> a „sbalený“ seznam dalších dostupných masek |
DirectoryOutline |
Ukazuje hierarchický pohled na adresářovou strukturu, lze zvolit počet úrovní zobrazených adresářů. |
Úvodem ke standardním dialogům
Mnoho aplikací má společný požadavek: potřebují otvírat/ukládat soubory, vyhledávat, tisknout, volit barvu (písma, pozadí…), apod. Proto pro tyto činnosti existují tzv. common dialogs, tedy předdefinované dialogové boxy.
Výhody:
Nevýhody:
V Delphi 5 nacházíme tyto dialogy:
Název dialogu |
Modální |
Význam |
OpenDialog |
ano |
pro (ruční) nalezení, vybrání, otevření souboru(ů) |
SaveDialog |
ano |
pro (ruční) nalezení, vybrání, uložení souboru(ů) |
OpenPictureDialog |
ano |
pro (ruční) nalezení, vybrání, otevření souboru(ů) s obrázkem. Navíc od předchozích dvou obsahuje náhled vybraného obrázku. |
SavePictureDialog |
ano |
viz OpenPictureDialog, pro ukládání |
FontDialo |
ano |
pro výběr fontu, velikosti, stylu písma |
ColorDialog |
ano |
pro výběr barvy |
PrintDialog |
ano |
pro odeslání tiskového úkolu na tiskárnu |
PrinterSetupDialog |
ano |
pro konfiguraci tiskárny (nastavení tisku) |
FindDialog |
ne |
pro vyhledávání výrazu v textu |
ReplaceDialog |
ne |
pro vyhledávání a nahrazování výrazu v textu |
Poznámka: modální okno znamená (zjednodušeně), že se z něj nelze přepnou do jiného okna téže aplikace před jeho uzavřením.
Dialogy se v Delphi nacházejí v paletě Dialogs. V době návrhu se na formuláři zobrazují jako malé ikonky, jejich plný tvar je vidět až za běhu. Otevřou se zavoláním jejich nejdůležitější metody Execute. Tato metoda vrací True, uzavřel-li uživatel dialog stiskem OK (a chce-li tedy výběr použít) a False, pokud dialog ukončil stiskem Cancel nebo stiskem křížku v rohu, apod. Až na PrinterSetupDialog mají vlastnost Options, která se skládá z několika true/false „podvlastností“.
Nyní se na jednotlivé dialogy podrobněji podíváme. Provedeme to tak, že shrneme vždy jejich nejdůležitější vlastnosti, události a metody, má-li to smysl, jinak si jenom uvedeme nejzajímavější rysy.
OpenDialog, SaveDialog
Tyto dva dialogy se tolik podobají, že je probereme najednou. Ještě jednou připomínám, že Delphi tyto (ani jiné) dialogy neimplementují, pouze je zapouzdřují již hotové jako komponenty. Vzhled dialogů bude proto silně závislý na používané verzi operačního systému Windows, a také na systémovém nastavení!
Vlastnosti:
Vlastnost |
Význam |
DefaultExt |
Přípona, která je přidána za zvolený název souboru, pokud uživatel nezadá žádnou (při zápisu názvu souboru z klávesnice). |
FileName |
Vrací kompletní název (tj. včetně cesty) zvoleného souboru nebo ji lze použít jako vstupní hodnota (jinak se zobrazí naposledy vybraný soubor). |
Files |
Read-only run-time vlastnost obsahující kompletní (tj. včetně cest) název (názvy) vybraného souboru (vybraných souborů). |
Filter |
K zadávání filtrů (masek) souborů. K návrhu filtru (lze zadat i více filtrů) použijte Filter Editor (spustí se kliknutím na tři tečky v rohu vlastnosti Filter v Object Inspectoru). |
FilterIndex |
Který index je zvolen jako „default“ při otevření dialogu. |
InitialDir |
Počáteční adresář |
Options |
Možnosti, blíže viz níže |
Title |
Titulek, místo Caption |
Podívejme se na vlastnost Option. Ta obsahuje 20 logických hodnot, kterými lze poměrně značně modifikovat vzhled a vlastnosti dialogu. Příklady:
Události:
Nejdůležitější jsou OnShow, resp. OnClose, které se objeví, je-li dialog otevřen, resp. uzavře-li jej uživatel. Lze též ošetřit OnCanClose, ve které můžeme zavření dialogu zakázat (např. po provedení nějakého dodatečného testu, apod.). Vyhrát si můžete s událostmi OnFolderChange, OnSelectionChange, resp. OnTypeChange, které se objeví, změní-li uživatel v dialogu adresář, výběr souborů, resp. masku (filtr).
Metody:
Jak již bylo řečeno, nejdůležitější metodou je Execute, kterou se dialog spouští (otvírá).
Obrázek ukáže „nový styl dialogu“ (ofOldStyleDialog = False) OpenDialog ve Windows 2000, se zapnutým zobrazováním položek jako www-odkazů (znovu připomínám, že toto nastavení se provádí ve Windows (Možnosti složky), nikoliv v Delphi!!).
Příklad: chceme do titulku formuláře wndHlavni zapsat jméno otevřeného souboru a zjistit, jestli byl otevřen jen pro čtení. Použitý OpenDialog se jmenuje dlgOpen.
if dlgOpen.Execute then begin wndHlavni.Caption := dlgOpen.FileName; if ofReadOnly in dlgOpen.Options then ShowMessage(`Soubor otevřen pro čtení!`);
Poznámka: vlastnost Options je typu množina, proto se testování jejích prvků provádí s výhodou pomocí operátoru in (prvek je v množině).
OpenPictureDialog, SavePictureDialog
Tyto dva dialogy se velmi podobají předchozím (pokud jde o vlastnosti, události, metody). Podíváme se tedy jen na některé zvláštnosti.
Už jsme zmínili, že součástí dialogu je plocha pro zobrazení náhledu obrázku. Tento náhled je ovšem zobrazen pouze v případě (a to je také největším omezením těchto dialogů), že zvolený obrázek je rozpoznán třídou TPicture. Jde ovšem pouze o bitmapy <.BMP), ikony <.ICO), Windows metasoubory <.WMF) a rozšířené Windows metasoubory <.EMF). Pokud zvolený obrázek nemůže být zobrazen v náhledu, objeví se v oblasti pro náhled nápis „(None)“.
Pokud uživatel zvolí (chce otevřít) soubor nerozpoznaného formátu (tedy jiný než výše uvedené), TPicture vyvolá výjimku EInvalidGraphic. (To ovšem samozřejmě neznamená, že takový soubor pomocí daného dialogu otevřít nejde, tedy že Dialog jeho název nevrátí!)
FontDialog
Tento dialogový box určitě znáte z textových editorů.
Vlastnosti:
Vlastnost |
Význam |
Device |
Určuje, pro které zařízení je font určen (fdScree, fdPrinter, fdBoth) |
Font |
Pro vstup a výstup informace o fontu (vstup – např. aktuální font, apod.) |
MinFontSize, MaxFontSize |
Lze definovat rozsah, z něhož může uživatel vybrat velikost písma. Nutno povolit ještě fdLimitSize flag v Options. Ponechte hodnotu 0, nechcete-li výběr omezovat. |
Opět se podívejme na některé zajímavé Options:
Události:
Oproti předchozím dialogům má navíc událost OnApply, která nastane, když uživatel stiskne tlačítko Použít (Apply).
Metody:
Nejdůležitější metodou je opět Execute.
Podívejte se na příklad konkrétního použití FontDialogu. Chceme změnit písmo v komponentě Memo (nazvané memoText) FontDialog se jmenuje dlgFont.
procedure wndHlavni.btnPismoClick(Sender: TObject); begin if dlgFont.Execute then memoText.Font := dlgFont.Font; end;
Poznámka pro pokročilé uživatele:
Někdy se spíše než přiřazení používá metoda Assign:
memoText.Font.Assign(dlgFont.Font);
Metoda Assign také nastaví vlastnosti fontu, ale s výjimkou vlastnosti PixelsPerInch. Může tedy být použita např. ke kopírování obrazovkového fontu na font tiskárny.
ColorDialog
K tomuto dialogu také není mnoho co dodat, takže jen telegraficky: klíčovou vlastností je Color, ve které se předává a vrací konkrétní barva.Vlastnost CustomColor může obsahovat definici až 16 uživatelských barev. K definici se používá klasický String List Editor, formát je následující:
ColorX=AABBCC,
kde za X dosaďte písmeno A..P a AA, BB, CC jsou hexadecimálně vyjádřené jednotlivé barevné složky.
Vlastnost Options má tentokráte jen 5 položek. cdFullOpen zajistí otevření celého dialogu (tedy i části umožňující definovat uživatelské barvy), naopak cdPreventFullOpen zcela potlačí možnost otevřít sekci uživatelských barev.
Obrázek ukáže kompletní ColorDialog (včetně sekce uživatelských barev):
PrinterSetupDialog, PrintDialog
PrinterSetupDialog zobrazí standardní dialog „Nastavení tisku“. Nemá žádné zvláštní události ani vlastnosti. Vše, co je zpravidla nutné udělat v souvislosti s tímto dialogem, je zareagovat na nějakou událost OnClick a zavolat metodu Execute. Vzhled tohoto dialogu úzce souvisí s nainstalovanou tiskárnou.
PrintDialog již má několik parametrů, které stojí za zmínku. Můžete nastavovat vstupní hodnoty (default) a číst výstupní, jako například počet kopií (vlastnost Copies), způsob kompletace (vlastnost Collage). Lze také omezit počet stran, které bude možné tisknout (MinPage, MaxPage). Zaškrtnete-li volbu PrintToFile, musíte sami od uživatele získat jméno souboru a provést sami příslušnou operaci se souborem!
Některé zajímavé volby lze provádět také pomocí vlastnosti Options, např. poSelection zobrazí RadioButton umožňující uživateli vybrat ručně oblast k tisku (zvýrazněný text se vytiskne). Vždy byste měli nechat zatrženou vlastnost poWarning, která znamená, že uživateli bude zobrazeno varování, bude-li se pokoušet tisknout na nenainstalovanou tiskárnu.
FindDialog, ReplaceDialog
Základním kamenem úrazu komponent Find- a ReplaceDialog je fakt, že jde jen o dialogy! Slouží pouze k zadání toho, co se má hledat (nahrazovat). Vlastní hledání (nahrazování) je třeba naprogramovat ručně. Bohužel.
Text, který se má vyhledat, je ve vlastnosti FindText. Doporučuji při prvním hledání nechat tento řetězec prázdný (případně by mohl obsahovat slovo, na kterém je kurzor), při každém další se tam ponechává naposledy hledaný výraz. ReplaceDialog má pochopitelně ještě vlastnost ReplaceText.
Volby jsou opět ve vlastnosti Options:
Pokud některou z těchto voleb nechcete uživateli zobrazit vůbec, použijte odpovídající možnost začínající slovem Hide (např. frHideMatchCase, atd.). Můžete také uživateli tyto možnosti zobrazit, ale zakázat mu je nastavit, pak použijte odpovídající možnost začínající slovem Disable (např. DisableMatchCase).
Události:
FindDialog oplývá především událostí OnFind, která nastane, stiskl-li uživatel tlačítko Find Next (Najít další). ReplaceDialog má navíc analogickou událost OnReplace, která následuje po stisku tlačítka Replace nebo Replace All (Nahradit nebo Nahradit vše).
Metody:
Tyto dva dialogy jsou nemodální, mohou tedy zůstat na obrazovce po několik operací hledání / nahrazování. Kromě metody Execute je tedy k dispozici metoda CloseDialog, která dialog uzavře.
Veškerý kód zajišťující vlastní vyhledávání a nahrazení musíte vytvořit ručně! Pokud si na to sami netroufáte, nevadí, v příštím díle seriálu jej vytvoříme společně.
Komponenta RichEdit
Komponenta RichEdit se nachází v paletě Win32. Slouží k poměrně sofistikované práci s textem. Podíváme se na ni blíže a následně ji použijeme v reálné aplikaci – postavíme na ní textový editor.
Komponenta RichEdit obsahuje velké množství vlastností formátu textu RTF (Rich Text Format). Podrobnější informace o formátu RTF můžete nalézt třeba na www.wotsit.org.
„Odlehčenou“ verzí ovládacího prvku RichEdit je komponenta Memo, která je popsána v sedmém dílu našeho seriálu.
Vlastnosti textu (TTextAttributes)
Pomocí komponenty RichEdit můžeme mnoha způsoby pracovat s textem. Můžeme upravovat typ fontu, barvu, velikost, styl apod. Výhodou RichEditu je, že všechny tyto atributy lze nastavovat buď celému textu, nebo jen vybrané části.
Než se podíváme na konkrétní vlastnosti a metody pro práci s textem, musíme si něco říci k třídě TTextAttributes. Tato třída slouží právě k uchování informací o atributech textu a je použita v několika atributech vlastního RichEditu, ve kterých uchováváme informace o textu. Podívejme se na vlastnosti třídy TTextAttributes:
Charset - specifikuje znakovou sadu fontu. Typem jde o celé číslo v rozmezí 0…255. K dispozici je také celá řada předdefinovaných konstant:
Color – barva písma. Přípusné hodnoty jsou buď přímo barvy (např. clGreen, clRed, apod.) nebo barvy definované systémem. V takovém případě záleží na nastavení barevného schématu ve Windows (např. clMenuText = barva písma v menu, clHightlightText = barva vybraného textu, apod.). Pokud máte ve svých Windows nastavenu barvu vybraného (tj. označeného) textu např. na zelenou, způsobí přiřazení barvy na clHighlightText zezelenání textu.
ConsistentAttributes – read-only vlastnost říkající, které vlastnosti TTextAttributes jsou konzistentní v celém označeném textu současného výběru v RichEditu. Jistě si dovedete představit využití – pokud bude celý označený text mít stejnou velikost písma, můžete tuto velikost zobrazit. Pokud ne, nezobrazíte nic, apod.
Height – výška (velikost) písma. Je udávána v pixelech.
Name – jméno fontu
Pitch – udává, jakou šířku mají jednotlivé znaky, lépe řečeno, mají-li všechny stejnou šířku nebo nikoliv. Přípustné hodnoty: fpDefault (hodnota záleží na fontu specifikovaném ve vlastnosti Name), fpFixed (všechny znaky mají stejnou šířku), fpVariable (znaky mají různou šířku). Poznámka: nastavení šířky písma nepřinutí Windows roztáhnout nebo smrštit znaky aktuálního fontu:-), i když by to byl jistě zajímavý pohled, ale vyberou font, který danou vlastnost splňuje. Pokud máte např. vybrán font MS Serif a nastavíte fpFixed, zobrazí se Courier.
Protected – logická hodnota indikující, je-li text reprezentovaný v TTextAttributes „protected“. Protected text nemůže být uživatelem změněn. Pokusí-li se uživatel editovat výběr, který obsahuje protected text, RichEdit generuje událost OnProtectChange. V obsluze této události můžeme povolit nebo zakázat editaci textu. Pokud jsme nevytvořili obsluhu události OnProtectChange, protected text je read-only.
Size – velikost písma. Vztah mezi hodnotou v pixelech (vlastnost Height) a hodnotou Size je následující: Size = Height * 72 / ScreenPixelsPerInch. Hodnotu ScreenPixelsPerInch lze získat z proměnné Screen.PixelsPerInch.
Style – styl textu, přípustné hodnoty: fsBold (tučné), fsItalic (kurzíva), fsUnderline (podtržené), fsStrikeout (horizontálně škrtnuté). Typ vlastnosti Style je množina, proto může obsahovat více hodnot (písmo může být zároveň podtržené a tučné, tedy [fsUnderline, fsBold]).
Tolik k vlastnostem třídy TTextAttributes. Tato třída je v RichEditu využívána pro vybraný (označený) text (tj. vlastnost SelAtributes) a ostatní text (tj. vlastnost DefAtributes). K těmto vlastnostem se ještě vrátíme v další podkapitole.
Na závěr se podívejme, jak v programu zmíněné vlastnosti nastavit. Předpokládejme, že máme na formuláři (TwndHlavni) umístěn RichEdit (reEditor). Podívejme se, jak například zjistit, je-li celý označený text tučný (nebo celý netučný):
with TwndHlavni.reEditor do if caBold in SelAttributes.ConsistentAttributes then ShowMessage(`Celý text je tučný nebo netučný`) else ShowMessage(`Část textu je tučná, část ne`);
Vlastnosti RichEditu
K práci s textem slouží především vlastnosti, které shrnuje následující tabulka. Upozorňuji, že většina jich je runtime, proto je nehledejte v Object Inspectoru.
Vlastnost |
Význam |
DefAttributes |
je typu TTextAttributes (viz předchozí podkapitola) a popisuje „normální“, tedy neoznačený text. V podstatě říká, jak bude vypadat nově přidávaný text. |
HideScrollBars |
říká, mají-li se skrýt posuvníky, nejsou-li potřeba (celý text se na stránku vejde). Nastavení této vlastnosti nemá žádný význam, pokud je vlastnost ScrollBars (celého RichEditu) nastavena na ssNone. |
HideSelection |
říká, bude-li vizuální indikace označení textu viditelná i po ztrátě zaměření RichEditu (jednoduše: bude-li text stále vidět jako označený, když se přepnete z RichEditu jinam v rámci aplikace). |
Lines |
klíčová vlastnost, obsahuje vlastní text. Je typu TStrings. |
Paragraph |
specifikuje formát odstavce textu, podrobněji viz další podkapitola |
PlainText |
velmi důležitá vlastnost, kladná hodnota (True) říká, že soubor se bude ukládat jako čistý text <.TXT), kdežto záporná hodnota (false) znamená ukládání ve formátu RTF |
SelAttributes |
je typu TTextAttributes (viz předchozí podkapitola) a popisuje označený text. |
SelLength |
délka (tj. počet znaků) označeného textu |
SelStart |
pozice začátku označeného textu (ve znacích od začátku dokumentu, 0 znamená první znak) |
SelText |
označený text. Tato vlastnost je typu string (nikoliv TStrings) |
WordWrap |
říká, budou-li zalamovány řádky, které přesahují viditelnou oblast obrazovky |
Než se podíváme na vlastnosti odstavce, povíme si ještě o několika metodách komponenty RichEdit, které jsou velmi praktické:
Metoda |
Význam |
FindText |
Mocná vlastnost, vyhledává text v RichEditu. Parametry: FindText (co: string; odkud, délka: Integer, možnosti: TSearchTypes). Tato metoda je použita v příkladu níže. |
|
Vytiskne obsah RichEditu |
ClearSelection |
Vymaže označený text |
CopyToClipboard |
Zkopíruje označený text do schránky |
CutToClipboard |
Vystřihne označený text do schránky, tj. text z dokumentu vyjme |
PasteFromClipboard |
Vloží na pozici kurzoru text ze schránky. Je-li označen jiný text, nahradí se. |
SelectAll |
Označí celý dokument |
Odstavec (Paragraph)
Jednou z vlastností komponenty RichEdit je odstavec (Paragraph). Paragraph je runtime vlastností, která specifikuje formátovací informaci aktuálního odstavce. Formátovací informací rozumíme zarovnávání, odrážky, číslování a tabulátory. Aktuálním odstavcem je myšlen odstavec obsahující označený text. Pokud není označen žádný text, je aktuálním odstavcem ten, na (ve) kterém je kurzor. Paragraph je typu TParaAttributes.
Poznámka pro pokročilé uživatele:
Paragraph je read-only vlastnost, protože objekt TCustomRichEdit má pouze jeden objekt TParaAttributes, který nemůže být měněn. Atributy aktuálního odstavce ovšem mohou být měněny, a to nastavováním vlastností objektu TParaAttributes. Mohou být nastavovány jedna po druhé nebo najednou podle existujícího objektu TParaAttributes zavoláním Paragraph.Assign.
Podívejme se tedy na vlastnosti odstavce.
Vlastnost |
Význam |
Alignment |
Specifikuje, jak má být text odstavce zarovnán. Přípustné hodnoty: taLeftJustify (vlevo), taCenter (na střed), taRightJustify (vpravo). |
FirstIndent |
Říká, jak moc bude první řádka odstavce „odražená“ od levého okraje, udává se v pixelech. |
LeftIndent |
Říká, jak moc budou ostatní řádky odstavce „odraženy“ od levého okraje. Tato vlastnost je použitelná např. k „vypíchnutí“ některého důležitého odstavce, který má jiné postavení v textu, apod. LeftIndent, stejně jako předchozí FirstIndent, nesouvisí s odrážkami (tedy puntíky). |
Numbering |
Specifikuje odrážku aktuálního odstavce (tedy onen puntík). Je-li nastavena vlastnost LeftIndent, je odrážka posunuta o uvedenou vzdálenost. Přípustné hodnoty: nsNone, nsBullet. |
RightIndent |
Analogie LeftIndent. Je efektní zvláště při nastavení Alignment = taRightJustify. |
Tab |
Určuje počet pixelů, po kterých se bude „poskakovat“ tabulátorem. Tab je indexovaná vlastnost. Pozice prvního „tab stopu“ je tedy Tab[0], dalšího Tab[1], atd. |
TabCount |
Počet definovaných zastávek tabulátoru („tab stopů“) v tomto odstavci. |
Podívejme se na příklad procedury, která definuje pozici zarážek tabulátoru:
procedure NastavTabStops (RichEdit: TRichEdit; TabStops: array of Integer); var i: Integer; // indexy oznacujeme zpravidla malymi pismeny begin with RichEdit.Paragraph do begin TabCount := High(TabStops) + 1; // nejvyssi index + 1 for i := 0 to TabCount – 1 do Tab[i] := TabStops[i]; end; end;
Nyní nadefinujeme zarážky tabulátoru:
NastavTabStops(reEditor, [25, 50, 80, 100]);
Vyhledávání v textu
Tolik tedy obecně k RichEditu. Nyní již jsme schopni vytvořit onen kýžený textový editor, ovšem nejprve vyřešíme restík, který nám zbyl z předchozího dílu. Tam jsme si ukázali dialogy pro vyhledávání a nahrazování v textu – FindDialog, resp. ReplaceDialog. Řekli jsme si, že je ovšem nutné ručně zpracovat vlastní vyhledávání. Na to se tedy nyní vrhneme.
Uvedu možnou obsluhu události pro vyvolání (otevření) FindDialogu (např. po stisku tlačítka, vybrání příkazu v menu apod.). Její součástí bude automatické vyplnění políčka s vyhledávaným textem v případě, že uživatel nějaký text označil.
procedure TwndHlavni.btnNajdiClick(Sender: TObject); begin dlgFind.FindText := reEditor.SelText; dlgFind.Execute; end;
Následuje kód ošetřující událost OnFind dialogu FindDialog:
procedure TwndHlavni.dlgFindFind(Sender: TObject); var Pozice: Longint; begin with reEditor do begin Pozice := Pos(dlgFind.FindText, Lines.Text); if Pozice > 0 then begin SelStart := Pozice - 1; SelLength := Length(dlgFind.FindText); end else MessageDlg(`Nenalezeno!`, mtError, [mbOk], 0); SetFocus; // jako aktivni nastav reEditor dlgFind.CloseDialog; end; end;
Poznámka: tento kód je použitelný k hledání obecně. Pokud ale vyhledáváme konkrétně v RichEditu (což se právě chystáme provádět), je lepší použít vlastnost RichEditu FindText a implementovat vyhledávání takto:
procedure TwndHlavni.dlgFindFind(Sender: TObject); var Pozice: Longint; begin with reEditor do begin Pozice := reEditor.FindText(dlgFind.FindText, 0, Length(text), []); if Pozice > 0 then begin SelStart := Pozice; SelLength := Length(dlgFind.FindText); end else MessageDlg(`Nenalezeno!`, mtError, [mbOk], 0); dlgFind.CloseDialog; SetFocus; end; end;
Vytváříme textový editor
Nyní již nám nechybí vůbec nic k tomu, abychom naprogramovali textový editor. Protože to určitě zvládnete sami, budu psát jen body a podrobněji se zastavím jen u „zapeklitějších“ situací.
Vytvoříme nový projekt. Na formulář umístíme následující komponenty (v závorce uvedu, jak je nazveme): RichEdit (reEditor), MainMenu (mnHlavni), PopupMenu (mnPopup). Dále na formulář umístíme dialogy: OpenDialog (dlgOpen), SaveDialog (dlgSave), FontDialog (dlgFont), ColorDialog (dlgColor), PrintDialog (dlgPrint), PrinterSetupDialog (dlgSetup), FindDialog (dlgFind).
Otevřeme si Menu Designer u mnHlavni a vytvoříme hlavní nabídku programu. V té budou následující položky:
Poznámka: mějte na paměti, že správně vytvořené menu (viz 9. díl našeho seriálu) by mělo obsahovat možnost zkrácené volby (tj. používání ampersandu & před příslušným písmenem textu položky) a možnost používat klávesové zkratky (např. Ctrl+C by mělo zkopírovat označený text apod.).
V návrhu aplikace je ještě vhodné nastavit „Default“ hodnotu titulku okna. Aby bylo možno kód plně opět využít:-), použijeme konstantu JM_PRG, která bude obsahovat název programu, tedy v našem případě „Textový editor“. Další konstanta (NO_NAME) označuje název souboru „Bez názvu.txt“. Konstanty budou definovány „nahoře“ v modulu, tedy v sekci definic, buď v části interface nebo implementation.
const JM_PRG = `dfsd`; NO_NAME = `Bez názvu.txt`; wndHlOkno.Caption := NO_NAME + JM_PRG;
Nyní přijde důležitý okamžik. Budeme postupně ošetřovat jednotlivé položky menu.
Soubor – Nový: program by se měl zeptat, zda uložit změněný soubor (pokud byl změněný), a pak vymazat celý obsah RichEditu. K tomu účelu je možné např. vytvořit funkci UlozZmeny, která zjistí, zda byl soubor změněn, a podle uživatelova přání jej uloží. Použitelná je vlastnost RichEditu Modified a událost RichEditu OnChange. Použitá procedura UlozSoubor bude definována níže. Budeme udržovat privátní atribut JmenoSouboru, ve kterém bude uloženo jméno právě zpracovávaného souboru. V souladu s poznatky o objektově orientované architektuře víme, že je nutné definovat metodu, která bude toto jméno souboru nastavovat. Ukázka kódu funkce pro nastavení jména:
procedure TwndHlavni.NastavJmenoSouboru(const Jmeno: String); begin JmenoSouboru := Jmeno; wndHlavni.Caption := ExtractFileName(JmenoSouboru) + ` - ` + JM_PRG; end;
Poznámka: při každém vkládání nového jména souboru budeme muset volat funkci NastavJmenoSouboru. To je sice poměrně běžný postup, ale není úplně hifi:-), to jaksi všichni cítíme. Ideálním řešením by bylo použití tzv. property. Tato problematika bude popsána v druhém díle zaměřeném na objektové koncepty.
Kód funkcí pro test změny textu uložení změn:
function TwndHlavni.UlozZmeny: TModalResult; begin if reEditor.Modified then begin Result:= MessageDlg(`Soubor byl změněn, chcete jej uložit?`, mtConfirmation, mbYesNoCancel, 0); if Result = mrYes then UlozSoubor; end else Result := mrOK; end; function TwndHlavni.UlozSoubor; begin reEditor.Lines.SaveToFile(JmenoSouboru); reEditor.Modified := False; end;
Kód vlastní položky menu:
procedure TwndHlavni.NovyClick(Sender: TObject); begin if (UlozZmeny <> mrCancel) then begin NastavJmenoSouboru(NO_NAME); reEditor.Lines.Clear; reEditor.Modified := False; end; end;
Soubor – Otevři: program by měl nejprve otestovat (stejně jako v předchozím případě), zda je nutné soubor uložit. Pak by měl otevřít dialog OpenDialog. Protože RichEdit jako takový umožňuje práci se soubory TXT a RTF, měl by tento dialog být nastaven pro práci právě se zmíněnými soubory. Ukázka kódu:
procedure TwndHlavni.OtevriClick(Sender: TObject); begin if (UlozZmeny <> mrCancel) then if dlgOpen.Execute then begin NastavJmenoSouboru(dlgOpen.FileName); reEditor.Lines.LoadFromFile(JmenoSouboru); reEditor.Modified := False; reEditor.ReadOnly := ofReadOnly in dlgOpen.Options; end; end;
Soubor – Ulož: soubor bude (bez ptaní) uložen pod svým aktuálním jménem. Jedinou výjimkou bude situace, kdy se soubor bude jmenovat „Bez názvu.txt“. V takovém případě se otevře dialog Uložit jako.
procedure TwndHlavni.UlozClick(Sender: TObject); begin if JmenoSouboru = NO_NAME then UlozJakoClick(Sender) else UlozSoubor; end;
Soubor – Ulož jako: bude otevřen dialog SaveFile, ve kterém bude uživatel moci vybrat jméno, pod kterým se bude soubor ukládat.
procedure TwndHlavni.UlozJakoClick(Sender: TObject); begin if dlgSave.Execute then begin NastavJmenoSouboru(dlgSave.FileName); UlozSoubor; end; end;
Další příkazy musíme z důvodu rozsahu článku probrat již jen povrchně.
Soubor - Nastavení tisku: vyvoláme dialog PrintSetupDialog
Soubor - Tisk: vyvoláme dialog PrintDialog. Ukážeme si kód této procedury:
procedure TwndHlavni.TiskClick(Sender: TObject); begin if dlgPrint.Execute then reEditor.Print(JmenoSouboru); end;
Soubor - Konec: nejprve bychom měli opět otestovat, zda je soubor potřeba uložit. Pak ukončíme aplikaci. Není zrovna vhodné provádět další explicitní dotazy typu „Chcete opravdu skončit?“, protože uživatel bude často potřebovat editor opakovaně vypínat a znovu spouštět. Tato situace může nastat třeba při editaci textu, jehož originál je umístěn na vzdáleném stroji. Pak je totiž potřeba editor zavřít, aby byla lokální kopie souboru přenesena zpět (typicky Windows Commander).
Úpravy - Kopíruj: na zkopírování označeného textu do schránky existuje metoda komponenty RichEdit (lépe řečeno zděděná metoda z TCustomEdit).
procedure TwndHlavni.CopyClick(Sender: TObject); begin wndHlavni.reEditor.CopyToClipboard; end;
Úpravy – Vyřízni, Vlož: k těmto dvěma činnostem existují analogické metody komponenty RichEdit jako pro kopírování do schránky. Popsány jsou v tabulce v předchozí podkapitole.
Úpravy – Vybrat vše: opět existuje metoda, konkrétně SelectAll.
Úpravy – Najít: kód, který uvedeme do obsluhy. Události vybrání této položky v menu jsou uvedeny v předchozí podkapitole, stejně jako kód, který je nutno napsat do obsluhy události OnFind dialogu FindDialog.
Formát – Písmo: nejprve zjistíme, zda je označena nějaká část textu (a zda se tedy bude nastavování týkat atributu SelAttributes nebo DefAttributes). Pak zavoláme dialog FontDialog:
procedure TwndHlavni.PismoClick(Sender: TObject); begin with reEditor do begin if SelLength > 0 then begin dlgFont.Font.Assign(SelAttributes); if dlgFont.Execute then SelAttributes.Assign(dlgFont.Font); end else begin dlgFont.Font.Assign(DefAttributes); if dlgFont.Execute then DefAttributes.Assign(dlgFont.Font); end; end; end;
Formát – Barva: provedeme analogicky jako font
Formát – Zalamování řádků: použjeme vlastnosti RichEditu WordWrap. Praktické je u této položky v menu zobrazovat zatržení říkající, zda je tato volba právě aktivována či nikoliv.
Nápověda – O programu: vytvoříme volbou v menu File – New Form nový formulář. Na jeho designu se můžeme klidně vyřádit; pokud nechceme zůstat u „střízlivého“ vzhledu, zkusíme si třeba zařadit na formulář nějaký posuvný text (námět na domácí úkol – víte, jak na to?).
Nápověda – Nápověda: buďto vytvoříme klasickou nápovědu (soubor *.HLP, což je vcelku „pakárna“ a kromě toho to bude obsahem samostatného dílu našeho seriálu), nebo budeme líní (a u takovéto aplikace by to asi až tolik nevadilo) a nápovědu vytvoříme jako nový formulář s komponentou Memo (nebo RichEdit:-)), do které napíšeme text nápovědy, tedy stručné shrnutí voleb a možností programu.
Pryč s Poznámkovým blokem
Jednoduchý editor máme hotov. Můžete ze svého systému vymazat Poznámkový blok a používat místo něho váš vlastní výtvor – určitě vás to při každém použití potěší. Samozřejmě, nabízelo by se mnoho vylepšení. Iniciativě se meze nekladou. Můžete vylepšit editor o stavový řádek (na kterém by se třeba zobrazovaly údaje o aktuálním souboru, číslo řádky apod). Dále by se daly implementovat statistické údaje, např. počet řádků, znaků; dále aktuální doba psaní apod. Vzhled by mohly vylepšit ikonky (pro otevření a uzavření souboru) – na vylepšování formulářů se podíváme právě v příštím díle. Na závěr dodávám, že podobný příklad je k nalezení v podadresáři DEMOSRICHEDIT adresáře, ve kterém máte nainstalováno Delphi. Je ovšem samozřejmě v angličtině.
A ještě jedna poznámka na závěr: kdo mi jako první pošle vytvořený vlastní zdroják příkladu z tohoto dílu seriálu (s dobrou štábní kulturou:-)), získá čestný titul „Živě’s Best Delphi Programmer“ a nehynoucí slávu plynoucí ze zveřejnění svého jména v příštím díle seriálu…:-)
Řešení domácího úkolu
V minulém díle jsem se vás ptal, jak byste vyřešili vytvoření posuvného textu. Řešením je použití komponenty Timer. Na její událost OnTimer pak posunete text o malý kousek daným směrem. Nezapomeňte testovat, zda je text už na konečném místě :-).
Formuláře nebo okna?
Až dosud jsme používali pojmy „formulář“ a „okno“ v podstatě jako synonyma. Z hlediska uživatele budiž, ale obecně to není úplně přesné.
Pojďme se podívat, co to je vlastně okno z hlediska Windows. Definici rozdělíme podle úhlu pohledu – z pohledu uživatele a z pohledu systému:
Co to je tedy formulář? Formulář reprezentuje okno z hlediska uživatele. Může být použit k vytvoření hlavních oken, dialogových boxů a MDI oken (viz dále). Na oknech je založeno mnoho dalších komponent, ale jen formuláře definují okna z pohledu uživatele. Další komponenty nebo ovládací prvky mohou být definovány jako okna jen podle definice z technického hlediska.
Když vytvoříte aplikaci, která bude mít jeden formulář, jedno tlačítko a jeden RichEdit, bude samozřejmě z hlediska uživatele existovat jediné okno – onen formulář. Z technického hlediska jsou však vytvořena okna hned čtyři:
Overlapped, child, pop-up
V předchozím příkladu jsem použil názvů overlapped, child a pop-up. Každé z těchto tří označení znamená tzv. styl okna. Podívejme se, oč vlastně u jednotlivých stylů jde:
Můžeme si tedy vysvětlit čtyři okna vytvořená zmíněnou aplikací. Hlavní okno je asi jasné, jde o overlapped okno související s formulářem. Obě child okna jsou vlastně použitými komponentami (tlačítkem, RichEditem). Možná trochu zapeklitější je situace s oknem pop-up. Oč vlastně jde?
Je důležité si uvědomit, že vlastní aplikace je také oknem. Nejen hlavní formulář této aplikace (tedy nejen to, co vidíme na ploše Windows a co my, jako uživatelé, považujeme za aplikaci) je oknem. Oknem je také vlastní aplikace. Abyste na něj nezapomínali, můžete si jej (nepřesně) představit třeba jako ikonu na hlavním panelu.
Okno, vztahující se k objektu aplikace, slouží ke společnému uložení všech oken. Všechny formuláře mají toto (skryté) okno, které je ovládá, proto např. při kliknutí na jedno okno aplikace přenese do popředí všechny dané aplikace.
Z hlediska Windows je okno aplikace typu pop-up. Za domácí úkol zkuste vymyslet, jaké okno je vlastníkem tohoto okna.
V Delphi jsou všechny formuláře okny typu overlapped. Dialogové boxy a další ovládací prvky, které do formuláře umístíte, jsou formulářem vlastněny. Jejich předchůdcem může být buď formulář nebo jedna ze speciálních, tzv. kontejnerových komponent, např. Panel.
Rozdílné pojmy jsou vlastník a rodič (rodičovské okno). Vlastníkem je okno, které má se „svým oknem“ plynulou výměnu zpráv. Rodičovské okno je zpravidla také vlastníkem, ale nutí svá dceřiná okna k pobytu uvnitř vyhrazené (jeho) plochy. Dceřiná okna pak nepoužívají absolutní obrazovkové souřadnice, ale relativní souřadnice z plochy okna rodiče.
Velice šikovným nástrojem na sledování všech oken ve Windows je program WinSight. Dodává se společně s Delphi (např. u Delphi 5 ale jen s verzemi Professional a Enterprise) a naleznete jej v adresáři ..binws32.exe. (Bohužel není součástí Trial verze Delphi, a to ani Enterprise). Po spuštění se objeví seznam všech aktuálně existující oken (bude pěkně dlouhý). Procházíte jej standardně – kliknutím na kosočtvereček, ve kterém je „+“, zobrazíte okna, která jsou daným oknem vlastněna. Dvojité kliknutí na některé okno zobrazí podrobné informace, včetně vlastníka, handle a dalších údajů. Můžete také sledovat zprávy, které si jednotlivá okna posílají, a to pro všechna okna, nebo pro vybraná (hlavní menu, volba Messages). Pokud ovšem chcete něco sledovat či vyhledávat, doporučuji volbu Stop!, která dočasně pozastaví aktivitu programu.
MDI, SDI, cože?
V předchozím textu jste mohli jednou narazit na výraz MDI. Kromě něj se často vyskytuje zkratka SDI. Význam těchto tajuplných zkratek je nakonec vcelku snadno pochopitelný:
Poznámka pro pokročilé uživatele:
Pojem „dokument“ je momentálně již poněkud zastaralý. Aplikace jsou mnohem více orientovány na objekty než na dokumenty. Znamená to, že do SDI aplikace nemusíme nutně vkládat jen dokumenty, ale třeba libovolné objekty OLE (Object Linking and Embedding).
Styly formulářů
V předchozí podkapitole jsme si ukázali styly oken. Jak v Delphi nastavit u daného okna jeho styl?
Formuláře mají vlastnost FormStyle, která je přesně tím, co hledáme:
Poznámka: není úplně nejvhodnější měnit tuto vlastnost (FormStyle) za běhu aplikace!
Vytvoření MDI aplikace – prohlížeč obrázků
Aplikaci SDI jsme vlastně vytvářeli v minulém díle seriálu, proto se dnes podíváme pouze na aplikaci MDI. Nejprve velmi jednoduchou aplikaci (prohlížeč obrázků) vytvoříme, pak si povíme o několika důležitých vlastnostech.
Jednou z možností vytvoření MDI aplikace je kliknutí na New… a vybrání položky MDI Application z karty Projects. Pak se vám vytvoří kompletní, funkční MDI aplikace. Touto cestou se ovšem nevydáme a popíšeme si spíše „ruční“ řešení, krok za krokem.
Při vytvářen aplikace nebudu vypisovat zřejmé a triviální kroky, jako např. nastavení titulku komponenty, apod.
Nyní nadefinujeme dceřiný (child) formulář:
Máme hotové vytváření uživatelského rozhraní. Nyní projekt uložíme (File – Save Project As). Jednotku Unit1 uložíme jako Rodic, jednotku Unit2 jako Potomek a projekt jako prjMDIApp.
Zbývá napsat programový kód:
Nejprve ošetříme událost OnClick tlačítka btnNacti:
procedure TwndRodic.btnNactiClick(Sender: TObject); begin if dlgOpenPicture.Execute then with TwndPotomek.Create(Application) do begin Caption := dlgOpenPicture.Filename; imgObrazek.Picture.LoadFromFile(dlgOpenPicture.Filename); end; end;
Nyní musíme ještě do sekce uses modulu tohoto formuláře připsat odkaz na potomka, tedy
uses Potomek;
Projekt nyní můžeme směle přeložit a spustit. Zkuste si otevřít více potomků – více oken s obrázky. Jediným „problémem“ zůstává, že při uzavření některého z potomků nedojde k jeho úplnému uzavření (a vymazání z paměti), ale jen k minimalizaci. Kontrolu nad uzavíráním formuláře získáme, pokud ošetříme událost OnClose formuláře wndPotomek:
procedure TwndPotomek.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end;
Tím změníme implicitní hodnotu proměnné Action na caFree – formulář se uzavře a uvolní z paměti.
Vzhled aplikace:
Všimněte si, že takto vytvořená MDI aplikace se chová trochu jinak než aplikace, které jsme dosud vytvářeli. Formuláře potomka nemůžeme posouvat mimo formulář rodiče, při minimalizaci se minimalizují jen dolů do oblasti potomka. Všimněte si také chování titulku aplikace.
Formuláře potomků jsou zajímavé ještě z jednoho důvodu. Když si zkusíte některý z nich maximalizovat, zjistíte, že je maximalizován skutečně přes celou plochu svého předka a že jej už nemůžete (jedním kliknutím) ani minimalizovat ani zmenšit na původní velikost. Nemáte zkrátka jeho minimalizační, maximalizační (a vůbec systémová) tlačítka. Víte, jak docílit zpřístupnění těchto tlačítek pro potomky? Námět na domácí úkol!
Automatické vytváření formuláře:
Standardně se oba formuláře (wndRodic, wndPotomek) vytváří automaticky při startu aplikace. Pokud nechcete, aby se automaticky vytvářel i potomek (ostatně to není příliš hezké, je-li vidět prázdné okno bez obrázku), postup je následující:
Obecně lze k vytváření formulářů říci toto: MDI aplikace zpravidla potřebují zobrazit více instancí téže třídy formuláře. Každý formulář je objektem, jeho instance se musí vytvořit a po skončení práce opět uvolnit z paměti. Delphi to může zajistit automaticky (viz předchozí příklad), nebo se o to postaráme sami.
Nejprve se podíváme, jak tuto činnost provádějí Delphi automaticky. Důležité jsou tři body:
Definice typu v souboru s jednotkou formuláře: type TwndRodic = class(TForm) private public end;
Deklarace proměnné typu objekt, která ukazuje na instanci objektu typu TwndPotomek:
var wndRodic: TwndRodic;
Vlastní vytvoření instance třídy formuláře TwndRodic a přiřazení reference do proměnné wndRodic.
Application.CreateForm(TwndRodic, wndRodic);
Odstranění objektu se provádí až při ukončení aplikace. Formulář vytvořený metodou CreateForm je vlastněn objektem Application. Odstraněním objektu z paměti se zároveň odstraní i všechny objekty, které tento objekt vlastní.
Dynamické vytváření formuláře:
Automatické vytváření formulářů se ovšem uplatní především v aplikacích SDI. V MDI není příliš využitelný, protože tyto aplikace zpravidla obsahují více instancí téže třídy formuláře – viz předchozí příklad s více okny obsahujícími obrázek.
Formulář vytvoříme dynamicky zavoláním jeho konstruktoru Create, jak jsme si řekli v 10. díle seriálu. Tím vytvoříme novou instanci (jméno formuláře ponechávám pro názornost implicitní, i když vím, že je to odsouzeníhodné):
Form1 := TForm1.Create(Application);
Jako parametr konstruktoru předáváme potomka třídy TComponent. Tento prvek se následně chová jako vlastník formuláře. Z konvence obvykle předáváme objekt Application. Jednou z výhod tohoto postupu je, že se instance formuláře automaticky odstraní při ukončení aplikace. Jistě bychom mohli jako parametr předat např. Nil, ale nelze to příliš doporučovat (objekt by neměl vlastníka, odstranění musíme zajistit manuálně, při chybě neošetřené příslušným ovladačem by zůstal formulář v paměti atd).
Poznámka: proměnná Form1 bude ukazovat pouze na poslední vytvořenou instanci (proto v našem příkladu v předchozím textu vůbec žádnou proměnnou nepřiřazujeme a použijeme přímo
with TwndPotomek.Create(Application) do
O odstraňování formuláře z paměti platí totéž co v případě automatického vytváření formulářů. Pokud je formulář vlastněn objektem Application, je při ukončení aplikace automaticky uvolněn. V každém případě jej však můžeme uvolnit ručně. K tomu můžeme použít buď událost OnClose nebo přímo metodu Free.
Událost OnClose má parametr Action, předaný odkazem. Tomuto parametru můžeme přiřadit následující hodnoty:
Čistší možností je ovšem zavolání metody objektu formuláře Free. Tuto metodu bychom měli volat ze stejného modulu, z jakého jsme volali metodu pro vytvoření formuláře, tedy Create. Nejlepší je zavolat ji hned za zavoláním metody Close: Ne vždy ovšem můžeme Free volat (ne vždy dopředu víme o tom, že okno bude uzavřeno). Jméno formuláře ponechávám pro názornost implicitní, ač vím, že je to odsouzeníhodné:
Form1.Close; Form1.Free; end;
Vlastnosti, události, metody MDI formuláře
V souvislosti s MDI aplikacemi se podíváme na několik zajímavých vlastností formuláře:
Vlastnost |
Význam |
ActiveMDIChild |
Vrací dceřiný formulář MDI, který má právě zaměření (Focus; jinak řečeno, který je právě aktivní). Pokud nejsou otevřeny žádné dceřiné formuláře, vrací Nil. |
MDIChildren |
Pole objektů typu formulář. Umožňuje přístup ke všem aktuálně vytvořeným potomkům MDI. První potomek má index 0. |
MDIChildCount |
Počet dceřiných formulářů (tedy počet prvků v poli MDIChildren). |
TileMode |
Definuje chování MDI potomků v případě zavolání metody Tile rodičovského formuláře. Při nastavení hodnoty TileMode = tbHorizontal budou uspořádány vodorovně, při TileMode = Vertical svisle. |
WinowMenu |
Typ této vlastnosti je TmenuItem. Delphi na jejím základě sestaví seznam dostupných dceřiných MDI formulářů, který je vhodné zobrazit v položce hlavního menu Okno. |
Podívejme se též na událost OnActivate: ta se vyvolává při přechodu z jednoho dceřiného okna do jiného. Pokud se přepínáte z formuláře, který není MDI, na dceřiný formulář MDI, je vyvolána OnActivate odpovídajícího rodiče MDI.
Na závěr MDI formulářů jsem si nechal několik zajímavých metod:
Metoda |
Význam |
ArrangeIcons |
Uspořádá ikony minimalizovaných dceřiných formulářů podél spodní hranice rodičovského formuláře. |
Cascade |
Dceřiné formuláře uspořádá do známé kaskády (budou viditelná jejich záhlaví). |
Next |
Přepne se do dalšího dceřiného formuláře (podle pořadí přepínání klávesou Tab). |
Previous |
Přepne se do předchozího dceřiného formuláře (podle pořadí přepínání klávesou Tab). |
Tile |
Uspořádá potomky do „dlaždic“ (mají všechny stejnou velikost, nepřekrývají se). Souvisí s vlastností TileMode – viz výše. |
Vytvoření konzolové aplikace
Pryč s grafickým uživatelským rozhraním, s ikonkami, animacemi a dalšími zbytečnostmi, které jen zdržují! Všichni ortodoxní programátoři touží po efektivních programech, které jsou ovládány pouze z příkazové řádky.
Ale vážně: konzolové aplikace (tedy aplikace běžící v módu příkazové řádky) sice momentálně nejsou zrovna nejčastěji vyvíjenými, přesto však (z mnoha důvodů) svůj smysl stále mají. Dříve ovšem existoval velký problém související s vývojem konzolových aplikací: musely běžet v prostředí MS-DOS, a nemohly mít tedy přístup k funkcím Windows API. Delphi ovšem momentálně konzolové aplikace podporují, takže lze vytvořit program běžící v konzolovém módu, ale fungující jako 32bitová aplikace s využitím funkcí Windows API.
Konzolovou aplikaci můžete vytvořit přímo pomocí položky v menu Delphi: New…, Console Application. Bohužel ale ne ve všech verzích (v trojce například tato ikonka chybí; podpora konzolových aplikací v Delphi3 není příliš mohutná). Každopádně nyní vytvoříme konzolovou aplikaci krok za krokem, tak, jak to půjde realizovat i ve starších verzích Delphi.
Bohužel to není vše, co je nutné učinit za účelem vytvoření konzolové aplikace. Zatím máme pouze aplikaci, která nemá žádný formulář. Velikost spustitelného souboru je však např. pro verzi Delphi 3 stále cca 150 kB.
Nyní provedeme editaci zdrojového souboru projektu. Otevřete si buď v některém editoru nebo volbou View – Project Source soubor .DPR. Uvidíme následující kód:
program prjKonzole; uses Forms; begin Application.Initialize; Application.Run; end.
S tímto kódem si nyní trochu pohrajeme
Výsledný zdrojový kód tedy bude vypadat takto:
program prjKonzole; begin WriteLn(`Ahoj, svete! Zdravi Te konzole!`); end.
Pokud chceme použít i jiný příkaz než WriteLn uvedený v příkladu, doporučuji zavést sekci Uses a použít jednotku SysUtils.
Aplikaci uložíme a přeložíme (Ctrl+F9 nebo Project – Compile). Spuštění provedeme z příkazového řádku (ve verzi Delphi 5 lze ovšem i přímo): ve Windows kliknete na Start – Spustit, napíšete Command, v okně najdete vytvořený spustitelný soubor naší aplikace a spustíte jej zapsáním jeho názvu. Všimněte si, že výsledný soubor má velikost pod 20 kB.
Poznámka pro pokročilé uživatele:
V konzolové aplikaci se dostává ke slovu tzv. standardní vstup a výstup, tedy v Pascalu input a output (a v C stdin, stdout, stderr). Použitelné jsou tedy Pascalské příkazy Read, Write, ReadLn, WriteLn.
Řešení domácího úkolu
V minulé kapitole jsem nastínil dvě otázky. První z nich byla, jaké okno je vlastníkem okna aplikace. Pravda je taková, že jde o okno Desktop. Nakonec je to logické, že :-)?
Druhá otázka byla, jak v MDI aplikaci docílit toho, aby okna potomků měla svá minimalizační a maximalizační tlačítka a aby bylo možno takové maximalizované okno uzavřít jedním kliknutím. Kdo z vás na to přišel, může si směle pogratulovat: řešením je vložit na rodičovský (hlavní) formulář komponentu MainMenu, tedy vytvořit v programu hlavní menu. Zmíněná tlačítka potomků se pak vytvářejí jakoby „na onom“ menu.
Co jsou to pouťáky?
A teď už se směle vrhneme do dnešního tématu. Poněkud tajuplný výraz „pouťáky“ neskrývá nic jiného, než pouťové efekty aplikace. Takovým efektem rozumíme třeba barevné tlačítko, obrázek na ploše, změnu kurzoru myši nebo papírek letící do koše při pokusu o výmaz souboru. Tedy vše, čemu se programátoři zpravidla brání, ale čeho si „běžný“ uživatel (a především jeho šéf, který mu bude program kupovat), hned všimne.
Poznámka: Poněkud sporná je otázka, zdali je nápis typu „Systém je zaneprázdněn“ také pouťovým efektem. V tomto ohledu bych vám, drazí čtenáři, dal rád prostor v diskusi pod článkem :-)
Proč se programátoři zuby nehty brání komponování pouťáků do svých aplikací? Nevýhody pouťáků jsou zřejmé:
Nevýhod lze najít ještě mnohem víc, ale ať se nám to líbí nebo ne, pouťáky zkrátka aplikaci prodávají. Šéf běžné (ve smyslu nepočítačové) firmy se nebude pídit po tom, kolik geniálních nápadů využil programátor ke zrychlení výpočtu. Když se mu program na první pohled nezalíbí, koupí jej od konkurence, a bude mu jedno, že běží dvakrát pomaleji než ten váš.
Tenhle scénář se netýká jen pouťáků, ale „viditelných“ částí aplikace vůbec, tedy ovládání, struktuře menu, nápovědě, dokumentaci, apod.
Taková je realita. Proto se vší vážností doporučuji v maximální míře dbát na přítomnost pouťových efektů, stejně jako na kvalitu vizuálního rozhraní programu, nápovědy, dokumentace a podobného „balastu“.
Pouťáky v Delphi
Podívejme se, jak snadno a rychle dostat pár pouťáků do aplikace v Delphi a jak obecně vylepšit její vzhled.
Oddělovací čáry – komponenta Bevel
Nejjednodušším, přesto poměrně elegantním způsobem, jak vylepšit vzhled aplikace, je používání oddělovacích čar a rámečků. Pomocí nich vizuálně rozdělíme komponenty na formuláři do logických skupin a celý formulář vypadá mnohem přehledněji.
Komponentu Bevel nalezneme v paletě Additional. Pomocí ní můžeme vytvořit jak pouhou oddělovací čáru, tak rámeček, který navíc může být zapuštěný či vyzdvihnutý. Důležité je, že Bevel (na rozdíl např. od komponenty Panel) se nestává vlastníkem komponent, které na něho umístíme, funguje jen čistě vizuálně.
Důležité jsou dvě jeho vlastnosti: Shape a Style. Vlastnost Shape říká, jak bude Bevel vypadat:
Vlastnost Style říká, zda tvar bude zapuštěný (bsLowered) nebo vyzdvihnutý (bsRaised) u těch nastavení Shape, kde to má smysl.
Tato komponenta nemá žádné události.
Tlačítko s obrázkem – komponenta BitBtn
Dalším typickým pouťákem jsou tlačítka s bitmapou – komponenty BitBtn. Nalezneme je v paletě Additional. Tato tlačítka jsou pokud jde o funkčnost velmi podobná „klasickým“ tlačítkům (komponentám Button). Liší se od něho pouze svým vzhledem, protože obsahuje bitmapu, která by měla na první pohled charakterizovat účel tlačítka.
V Delphi existuje deset předdefinovaných tlačítek, jak je ukazuje obrázek. Kromě nich je ovšem možné vytvořit libovolný jiný vzhled tlačítka – stačí použít bitmapu vhodné velikosti. Pokud chcete, aby velikost vašich vlastních obrázků korespondovala s velikostí těch standardních, používejte bitmapy o velikosti 18 x 18 bodů.
Podívejme se na důležité vlastnosti tlačítek BitBtn:
Vlastnost |
Význam |
Glyph |
Přiřazuje obrázek (bitmapu) k tlačítku. |
Kind |
Určuje druh tlačítka, tedy jeho vzhled. Umožňuje vybrat zmíněná předdefinovaná tlačítka, při zadání hodnoty bkCustom můžete použít libovolný (svůj) obrázek. |
Layout |
Určuje, kde se na tlačítku bitmapa objeví (vlevo, vpravo, nahoře, dole). |
Margin |
Slouží ke stanovení počtu pixelů mezi levým okrajem tlačítka a obrázkem. Hodnota –1 (která je default) říká, že obrázek i s textem (titulkem) bude centrovaný. |
ModalResult |
Velmi použitelná vlastnost: říká, zda (a jak) bude uzavřen modální rodičovský (parent) formulář tlačítka. Nevyžadujeme-li jinou činnost, není vůbec nutné ošetřovat události OnClick tlačítek, stačí jim takto nastavit „výsledek“. |
NumGlyphs |
Říká, kolik obrázků bitmapa obsahuje – viz poznámka pod tabulkou. |
Spacing |
Určuje počet pixelů mezi obrázkem a titulkem (Caption). |
Vlastnost NumGlyphs říká, kolik obrázků obsahuje bitmapa specifikovaná vlastností Glyph. Bitmapa musí splňovat následující podmínky: všechny obrázky musí mít stejnou velikost a musí být umístěny v řadě za sebou. BitButton pak zobrazí jednu z ikon v závislosti na svém stavu:
Jednu z možností, jak získat bitmapu s více obrázky, popíšeme níže v podkapitole věnované seznamu obrázků (StringList).
Nástrojová lišta poprvé – komponenty SpeedButton
Nástrojové lišty jsou v současných aplikacích velmi časté. Umožňují uživateli možnost rychlé volby bez nutnosti proklikávat se strukturou menu.
V Delphi můžeme nástrojovou lištu vytvořit dvěma způsoby. Komplexnější a flexibilnější je použití komponent SpeedButton z palety Additional. Tyto komponenty sdružíme na komponentu Panel (ta je v paletě Standard).
Komponenta SpeedButton je velmi podobná komponentě BitBtn, popisované v předchozí podkapitole. Zobrazuje bitmapu, ale nemůže zobrazit titulek. Stejně jako u BitButtonu může mít více obrázků pro více svých stavů. Zde se také dostává ke slovu „čtvrtý“ stav – Down (stále vybráno).
Důležitá je vlastnost GroupIndex, pomocí níž můžeme sdružovat tlačítka do skupin. Hodnota 0 (která je default) říká, že jde o nezávislé tlačítko. Pokud chceme vytvořit skupinu, nastavíme všem jejím tlačítkům stejnou (nenulovou) hodnotu GroupIndex. Vlastností Down pak můžeme nastavit, které tlačítko bude zpočátku stisknuto.
Pomocí těchto vlastností můžeme zajistit, aby se tlačítka chovala jako CheckBoxy nebo RadioButtony (tedy aby mohlo být stisknuto právě jedno tlačítko, aby mohla být vybrána libovolná kombinace, nebo aby třeba nemuselo být vybráno žádné tlačítko).
Vytvoření nástrojové lišty:
Shrňme tedy, že pokud chceme vytvořit nástrojovou lištu, vložíme nejprve na formulář komponentu Panel z palety Standard. Pak na ní rozmístíme vlastní tlačítka (komponenty SpeedButton z palety Additional) a vhodně si „pohrajeme“ s jejich vlastnostmi GrouIndex, Down, AllowAllUp.
Cimrmanovský úkrok stranou – komponenta ImageList
Abychom se mohli podívat na další možnost, jak vytvořit nástrojovou lištu, musíme si něco říci o komponentě ImageList. Nalezneme ji v paletě Win32. Jejím účelem je shromažďovat kolekci (seznam) obrázků stejné velikosti. Tyto obrázky (bitmapy, ikony…) jsou následně přístupné pomocí indexu.
ImageList můžeme použít k účinné správě velkých, rozsáhlých množin obrázků a ikon. Navíc všechny obrázky ImageListu jsou reprezentovány jako jedna (široká) bitmapa. Pokud vás napadá souvislost s předchozími podkapitolami (tedy používání bitmap obsahující více obrázků – např. komponenta BitBtn), jste na správné adrese. Kromě tlačítek BitBtn a SpeedButton je seznam obrázků využíván i dalšími komponentami, jako např. TreeView, ListView, CoolBar a ToolBar (viz další podkapitola).
Všechny obrázky jsou následně přístupny pomocí indexu v rozsahu 0 až N-1. V návrhové fázi je nejlepší používat nástroj nazvaný Image List Editor. Ten se vyvolá po stisku pravého tlačítka myši na komponentě ImageList, kterou jsme vložili na formulář (viz obrázek).
Nástrojová lišta podruhé – komponenta ToolBar
Pomocí komponenty ToolBar můžeme také vytvořit nástrojovou lištu. ToolBar se nalézá v paletě Win32. Oproti popisovanému nástrojovému pruhu (vytvořeného z Panelu a ze SpeedButtons) umožňuje navíc některé operace a vyznačuje se poněkud jiným způsobem návrhu. Úzce spolupracujeme s komponentou ImageList popsanou v předchozí podkapitole.
Vše si předvedeme pro změnu na příkladu:
Vidíme, že vytvoření nástrojového pruhu tímto způsobem je velmi flexibilní. Navíc ToolBar umožňuje některé další „bonbónky“, o kterých se ovšem zmíním již jen telegraficky:
Záložky – komponenty TabControl a PageControl
V Delphi můžeme se záložkami pracovat dvěma různými způsoby. Buď použijeme komponentu TabControl nebo PageControl. Přestože na první pohled rozdíl nepoznáte, funkčnost je zcela odlišná.
TabControl slouží pouze k definici vlastních záložek. Ve své horní části zobrazuje seznam záložek, ale sám (trochu paradoxně) žádné karty nemá: pokud umístíte na jednu „kartu“ (použil jsem uvozovky, protože fyzicky tam žádná karta vlastně není) jakýkoliv ovládací prvek (komponentu, např. editační pole), bude zobrazeno bez rozdílu na všech „kartách“. Z toho vyplývá, že je nutné naprogramovat reakci na výběr (změnu) záložky.
Naproti tomu PageControl obsahuje vlastní (rozlišné) listy záložek. Ty mohou obsahovat své (vlastní) komponenty a ovládací prvky, jako např. editační okna, tlačítka, apod. Pokud vložíte některou ikonu na jednu kartu, bude skutečně jen na příslušné kartě, nikoliv (zdánlivě) na všech.
Z těchto rozdílů vyplývá také rozdíl v práci a v návrhu těchto dvou komponent.
TabControl U komponenty TabControl se záložky spravují ve vlastnosti Tabs. Tato vlastnost je (nám již dobře známého) typu TStrings, takže v době návrhu je také k dispozici String List Editor. Pro Tabs tedy také fungují všechny metody, které jsme si popisovali v souvislosti s typem TStrings (viz Umíme to s Delphi, 8. díl). Vlastnost TabIndex definuje aktuálně vybranou záložku.
U komponenty TabControl stojí za zmínku také událost OnChanging, v níž je možné zabránit přesunu na jinou záložku (např. můžete otestovat platnost údajů, které uživatel vyplnil do jednotlivých editačních polí, apod.). Používá se parametru AllowChange. V následujícím příkladu např. zakážeme přechod na jinou záložku, pokud je v editačním poli edtUserName slovo „Administrator“, protože nechceme povolit vytvoření uživatele s tímto uživatelským jménem. Komponentě TabControl jsem ponechal původní jméno, ač vím, že je to odsouzeníhodné:
procedure TwndHlavni.TabControl1Changing(Sender: TObject; var AllowChange: Boolean); begin AllowChange := (edtUserName <> `Administrator`); end;
PageControl Všimněte si, že jednotlivé listy vytvářejí fyzicky nové komponenty (jmenují se TabSheet a jsou typu TTabSheet). To sice poněkud komplikuje správu, ale zase to umožňuje nastavovat (a navrhovat) individuálně každou z nich.
U komponenty PageControl není k dispozici žádný editor, který by umožňoval editovat seznam záložek. V době návrhu přidáme list se záložkou tak, že na PageControl klikneme pravým tlačítkem a vybereme New Page. S každou vytvořenou stránkou následně můžeme zacházet jako se zcela samostatným, autonomním objektem.
Domácí úkol: zkuste vymyslet, jak přidat novou stránku za běhu programu.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 4460
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved