Umíme to s Delphi, 43. díl – další možnosti komunikace s uživatelem

V dosavadních dílech seriálu jsme již probrali mnoho funkcí a komponent sloužících pro komunikaci s uživatelem – tedy pro zadávání a výpis informací. Na několik důležitých možností jsme však zapomněli – a napravíme to právě dnes. V příštím dílu pak otevřeme rozsáhlé téma, o které si často píšete.
Každá aplikace musí nějak komunikovat s uživateli. Přestože si to řada vývojářů odmítá připustit, velmi důležitý je nejen obsah této komunikace, ale také její forma. Program musí být k uživateli „přívětivý“ ve svém chování a příjemný na pohled. To ovšem neznamená, že je vhodné vytvářet extrémně pestrobarevné, „upovídané“ či jinak „úchylné“ aplikace, neboť to zase vyvolává dojem neprofesionality. Navržení optimálního uživatelského prostředí není nikterak triviální úkol, a je stejně důležité jako vlastní algoritmizace! V dnešním díle seriálu vám přináším přehled několika dosud nezmíněných (avšak velmi dobře použitelných) funkcí a komponent, jejichž primárním účelem je zobrazit uživateli data, nebo je od něho naopak získat.

Funkce InputBox - varianta standardního boxu

Pokud je naším cílem získat od uživatele jednořádkový vstup, máme samozřejmě mnoho možností, jak toho dosáhnout. Na prvním místě vývojáře asi napadne použití vhodné komponenty, například Edit. Někdy (například v případech, kdy nepředpokládáme extrémně častý vstup) ovšem nechceme zabírat na formuláři místo další komponentou. V takovém případě by se hodila nějaká funkce, která by například otevřela okénko, vyzvala uživatele k zadání údaje a následně tento údaj vrátila jako svou funkční hodnotu. A přesně takovou funkci Delphi také přinášejí a jde o funkci InputBox. Vzpomínáte-li si na popis standardních boxů v jednom z úvodních dílů seriálu (konkrétně v dílu 6), můžete si funkci InputBox přirovnat k jinému standardnímu boxu, např. ShowMessage (ShowMessage však slouží k výpisu údaje, InputBox k zadání). Znovu zdůrazněme, že InputBox není komponentou, nehledejte ji proto v paletě komponent. Funkce vrací hodnotu string. Používá se velmi jednoduše:

function InputBox(const ACaption, APrompt, ADefault: string): string;

Význam jednotlivých parametrů:

  • ACaption – titulek dialogu,
  • APrompt – text, který se objeví v záhlaví dialogu,
  • ADefault – text, který se při otevření dialogu objeví v editačním okénku (jakýsi přednastavený text).
Následující metoda ZjistiJmeno otevře InputBox, zeptá se uživatele na jeho uživatelské jméno a toto jméno vrátí jako svou výstupní hodnotu. Pokud uživatel opustí InputBox stiskem tlačítka Cancel nebo klávesou Esc, InputBox vrátí hodnotu specifikovanou v parametru ADefault, v tomto případě tedy jméno vkadlec. Tato hodnota se v průběhu práce nemění, tedy zadáním jiné vstupní hodnoty neovlivníte hodnotu ADefault pro příští otevření dialogu. Hlavní formulář aplikace je frmInput. Výsledek je na následujícím obrázku.

function TfrmInput.ZjistiJmeno: string;
begin
  Result := InputBox(`Zadejte jméno`,
                    `Zadejte prosím své uživatelské jméno:`,
                    `vkadlec`);
end;

Důležitá poznámka: ukažme si pro jistotu ještě použití vytvořené funkce ZjistiJmeno v ošetření události OnClick tlačítka btnStart formuláře frmInput:

procedure TfrmInput.btnStartClick(Sender: TObject);
var ZjistenyUdaj: string;

begin
  ZjistenyUdaj := ZjistiJmeno;
  ShowMessage(ZjistenyUdaj);
end;

Abyste však vytvořenou funkci ZjistiJmeno mohli takto ve své aplikaci používat, musíte její hlavičku ještě přidat do deklarace třídy TfrmInput v horní části modulu (viz tučně zvýrazněná řádka programového výpisu):

unit frmHlavni;

interface

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

type
  TfrmInput = class(TForm)
    btnStart: TButton;
    procedure btnStartClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function ZjistiJmeno: string;
  end;

var
  frmInput: TfrmInput;
...

Funkce InputQuery – jiná varianta jednořádkového vstupu

Funkce InputBox, kterou jsme detailně popsali v předchozí podkapitole, může za určitých okolností mít jednu nevýhodu: nikdy se totiž nedozvíte, jestli uživatel opustil dialog stiskem tlačítka OK nebo tlačítka Cancel (případně analogicky klávesami Enter a Esc). Funkce InputBox vrátí v každém případě řetězec, a to buď nový a nebo ten, který jsme uvedli v parametru ADefault.

Co s tím? Delphi, jak jinak, přinášejí velmi elegantní řešení. Potřebujete-li zjistit, jakým tlačítkem uživatel dialog opustil, použijte funkci InputQuery:

function InputQuery(const ACaption, APrompt: string;
var Value: string): Boolean;

Návratovou hodnotou této funkce je logický údaj typu Boolean, který nabývá buď hodnoty „ano“ (v případě opuštění dialogu stiskem tlačítka OK nebo klávesy Enter) nebo ne (stisk tlačítka Cancel nebo klávesy Esc). Můžete tedy snadno otestovat, jakým způsobem uživatel dialog uzavřel. Význam parametrů je stejný jako u výše popsané funkce InputBox, včetně třetího parametru (který se však místo ADefault jmenuje Value). V něm předáváme řetězec, který se má zobrazit jako přednastavený (default). Všimněte si však, že na rozdíl od parametru ADefault je parametr Value předávaný odkazem (tj. s uvedeným klíčovým slovem var), takže se dá předpokládat, že pomocí tohoto parametru nám bude funkce InputQuery také něco vracet. A překvapení se nekoná – po uzavření dialogu opravdu parametr Value obsahuje buď nový, uživatelem zadaný řetězec (to v případě OK či Enteru) nebo původní, default text (v případě Cancel či Esc).

Následující příklad ukazuje možné použití funkce InputQuery. Zpráva ShowMessage se zobrazí pouze v případě, že uživatel opustil dialog stiskem OK nebo Enteru:

procedure TfrmInput.btnInputQueryClick(Sender: TObject);
var NovyText: string;

begin
  NovyText := `Pøednastavený text`;
  if InputQuery(`Ukázka funkce InputQuery`,
    `Zadejte nějaký inteligentní text:`, NovyText) = True
  then
    ShowMessage(NovyText);
end;

Funkce CreateMessageDialog – potřebujete-li opakovaně tentýž dialog

Zůstaňme ještě krátkou chvíli u funkcí pro zobrazení standardního dialogového boxu. Předchozí dvě funkce vytvořily a zobrazily dialog pro zadání jednořádkové hodnoty. Už víme, že existuje celá řada funkcí, které zobrazují dialogy pro výpis krátkých údajů (viz 6. díl tohoto seriálu). Pomocí těchto funkcí (např. ShowMessage, MessageDlg, MessageDlgPos) si můžete s dialogy relativně dost „vyhrát“. Často se ovšem stává, že v aplikaci potřebujete opakovaně použít stejně vypadající dialog. V takovém případě je jistě nemilé vypisovat vždy celý příkaz pro jeho zobrazení, včetně všech parametrů. V Delphi ovšem existuje funkce, která umožní vytvořit si dialog podle svých potřeb a ten pak za běhu programu volat jediným, velmi jednoduchým příkazem. Tato funkce se jmenuje CreateMessageDialog a má následující hlavičku:

function CreateMessageDialog(const Msg: string; DlgType: TMsgDlgType;
  Buttons: TMsgDlgButtons): TForm;

Parametry jsou stejné jako u funkcí MessageDlg, tedy Msg obsahuje vypisovanou zprávu, DlgType označuje styl dialogu a Buttons vyjadřuje, která tlačítka se na dialogu mají objevit. Důležité je, že funkce vrací objekt třídy TForm. Tento objekt je samozřejmě nutné nadefinovat před voláním funkce CreateMessageDialog. V následujícím příkladu můžete vidět typické použití funkce. Nejprve je definován ukazatel NasDialog, následně se v rámci obsluhy události OnCreate hlavního formuláře vytvoří dialog a nakonec se v obsluze události OnClick tlačítka btnTest zobrazuje (i s příslušným testem návratové hodnoty). Aby bylo jasné, jak nadefinovat proměnnou NasDialog, prohlédněte si celý zdrojový kód:

unit frmHlavni;

interface

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

type
  TfrmInput = class(TForm)
    btnCreateDlg: TButton;
    procedure btnCreateDlgClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmInput: TfrmInput;
  NasDialog: TForm;

implementation

{$R *.DFM}

procedure TfrmInput.btnCreateDlgClick(Sender: TObject);
begin
  if NasDialog.ShowModal = mrAll then
    ShowMessage(`Stiskl jste tlačítko All`);
end;

procedure TfrmInput.FormCreate(Sender: TObject);
begin
  NasDialog := CreateMessageDialog(`Toto je ukázka vytvořeného dialogu`,
    mtInformation, [mbYes, mbAll, mbRetry]);
end;

end.

Komponenta MaskEdit – možnost omezit formát vstupu

Tolik tedy k funkcím. Nyní je čas podívat se také na několik komponent pro komunikaci s uživatelem. O komponentě MaskEdit z palety Additional jsme se již zmínili (bylo to v 7. dílu seriálu), nicméně bylo to velmi zběžně (a letmo:-)) a obávám se, že na základě této zmínky asi nikdo této komponentě na chuť nepřišel, proto se na komponentu MaskEdit podíváme nyní podrobněji. Komponenta MaskEdit, stejně jako komponenta Edit, slouží k jednořádkovému vstupu. Má také s komponentou Edit zcela shodnou drtivou většinu vlastností a metod, ovšem navíc 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, ukázka na následujícím obrázku), který spustíte klepnutím na tlačítko se třemi malými tečkami v Object Inspectoru vedle vlastnosti EditMask.

Vlastní text je uložen ve vlastnosti Text (stejně jako u komponenty Edit). EditText obsahuje text formátovaný aplikací masky (tedy tak, jak jej vidí uživatel). Vlastnost IsMasked umožňuje testovat, byla-li maska definována.

Podívejte se nyní na použitelné symboly pro tvorbu masek:

Symbol Význam
! Odstraní počáteční mezery
> Převede všechna následující písmena (až do symbolu <) na velká
< Převede všechna následující písmena (až do symbolu >) na malá
<> Po nalezení tohoto symbolu se neprovádí žádná kontrola a modifikace velikosti písmen
\ Následuje literál
L Povinný alfabetický znak (A..Z, a..z)
l Nepovinný alfabetický znak (A..Z, a..z)
A Povinný alfanumerický znak (A..Z, a..z, 0..9)
a Nepovinný alfanumerický znak (A..Z, a..z, 0..9)
C Povinný (libovolný) znak
c Nepovinný (libovolný) znak
0 Povinná číslice (0..9)
9 Nepovinná číslice (0..9)
# Nepovinná číslice nebo znaky + či -
: Oddělovač hodin, minut, sekund (místo dvojtečky se může objevit jiný oddělovač, specifikovaný v Ovládacích panelech ve Windows)
/ Oddělovač dnů, měsíců, roků (místo lomítka se může objevit jiný oddělovač, specifikovaný v Ovládacích panelech ve Windows)
; Oddělovač částí masky
_ Vloží mezeru do textu
Maska se skládá ze tří částí oddělených znakem ; (středník). Nejzajímavější je první část, což je vlastní maska. Této části jsme věnovali předchozí tabulku.

Druhá část určuje, zda ukládat znaky masky společně s daty. Toto nastavení se týká pouze vlastnosti Text komponenty MaskEdit, neboť do vlastnosti EditText jsou znaky masky ukládány v každém případě. Pokud toto ukládání povolíte (hodnota 1), budou ve vlastnosti Text obsaženy jednak znaky zadané uživatelem a jednak všechny znaky masky, např. oddělovací tečky, lomítka apod. Datum může tedy vypadat např. takto: „12.10.01“. Bude-li ukládání zakázáno (hodnota 0), vlastnost Text bude obsahovat pouze uživatelem zadané znaky, tedy totéž bude vypadat takto: „121001“.

Třetí část masky umožňuje změnit znak _ (podtržítko) reprezentující mezeru. V editačním poli se vždy zobrazuje příslušný počet podtržítek, který říká, kolik znaků (a na jakých pozicích) je nutné do pole zadat. Místo podtržítka se ovšem může vyskytovat jiný znak – specifikovaný třetí částí masky.

Podívejte se na příklad masky (komponenta se jmenuje MaskEdit, formulář frmHlavni), která potlačí úvodní mezery a umožní zadat znak + nebo -, pak jedno- až dvouciferné číslo a do závorky opět znak + či – a jedno- až dvouciferné číslo. Znaky masky se nebudou ukládat spolu s daty a pozice, na které se bude zapisovat, budou označeny křížkem (#).

procedure TfrmHlavni.FormCreate(Sender: TObject);
begin
  MaskEdit.EditMask := `!#09\(#09\);0;#`;
  MaskEdit.Text := ``;
  MaskEdit.AutoSelect := False;
end;

Pokud uživatel opustí editační pole v okamžiku, kdy zadání neodpovídá masce, je generována výjimka EDBEditError. Standardním ošetřením této výjimky je dialog s (anglickým) popisem chyby a s hlášením, že stiskem klávesy Esc je možné zrušit provedené změny (smazat nesprávné zadání).

Komponenta UpDown – šipky nahoru a dolů

Další komponentou, na kterou se podíváme v souvislosti s komunikací s uživateli, je UpDown. Nachází se v paletě Win32 a slouží k číselnému vstupu pomocí myši. Aktuální hodnota komponenty UpDown je k dispozici ve vlastnosti Position. Komponentu je možné (vizuálně) asociovat s jiným objektem pomocí vlastnosti Associate. O vlastnostech této komponenty podrobněji pojednává následující tabulka:

Vlastnost Význam
AllignButton Určuje umístění komponenty UpDown vzhledem k asociované komponentě (viz vlastnost Associate). Možné hodnoty: udLeft (UpDown je umístěn v levém rohu asociované komponenty) a udRight (v pravém rohu).
ArrowKeys Říká, je-li možné měnit hodnotu také klávesami šipka nahoru a šipka dolů, tedy zda komponenta dostává vstup od těchto kláves.
Associate Udává komponentu, s níž je UpDown asociován. Tato asociace probíhá jednak vizuálně (UpDown se k asociované komponentě přimkne a automaticky upraví svou velikost) a jednak může i upravit funkčnost asociované komponenty: zkuste si třeba asociovat UpDown s komponentou Edit – získáte tak při minimálním úsilí hezkou komponentu pro elegantní zadávání číselných údajů, viz příklad pod tabulkou.
Increment Určuje, o kolik se zvýší/sníží hodnota vlastnosti Position po jednom klepnutí na některou ze šipek UpDown.
Min, Max Udávají nejmenší a největší možnou hodnotu vlastnosti Position komponenty UpDown.
Orientation Stanovuje orientaci UpDown: možné hodnoty jsou udHorizontal (horizontální), udVertical (vertikální).
Position Nejdůležitější vlastnost komponenty, obsahuje aktuální hodnotu numerického vstupu.
Thousands Říká, má-li být řád tisíců vizuálně oddělen od ostatních číslic.
Wrap Udává, co se stane při pokusu zadat větší hodnotu než Max nebo menší hodnotu než je Min. V případě Wrap = true hodnota přeteče (vytvoří se nekonečná smyčka), v opačném případě bude oříznuta (nastavena na Min, resp. Max).
Komponenta UpDown má několik „standardních“ událostí, zaměříme se jen na jednu, která není úplně běžná: událost OnChanging je generována při pokusu o změnu hodnoty UpDown, ale ještě před fyzickou změnou této hodnoty. V obsluze této události je ještě možné změně zabránit pomocí parametru AllowChange, viz příklad.

Předvedeme si, jak se s touto komponentou pracuje.

Vytvořte novou aplikaci , hlavní formulář nazvěte frmHlavni. Umístěte na něj následující komponenty:

  • UpDown (Name = udVelikost);
  • Edit (Name = edtVelikost);
  • Button (Name = btnKonec);
  • CheckBox (Name = cbPovolit).
Pak ošetřete událost OnChanging komponenty udVelikost:

procedure TfrmHlavni.udVelikostChanging(Sender: TObject;
  var AllowChange: Boolean);
begin
        // povolení/zakázání zmìny podle aktuální hodnoty vlastnosti
// Checked komponenty CheckBox
  AllowChange := cbPovolit.Checked;
end;

Ošetřete také událost OnClick tlačítka btnKonec:

procedure TfrmHlavni.btnKonecClick(Sender: TObject);
begin
  Application.Terminate;
end;

Ošetřete událost OnCreate formuláře frmHlavni. V ošetření této události se provedou nastavení titulků a především nastavení asociace komponenty UpDown s komponentou Edit (tato nastavení je samozřejmě možné provést i v Object Incpectoru).

procedure TfrmHlavni.FormCreate(Sender: TObject);
begin
  frmHlavni.Caption := `Ukázka komponenty UpDown`;
  btnKonec.Caption := `Konec`;
  cbPovolit.Caption := `Povolit`;
  udVelikost.Associate := edtVelikost;
end;

Aplikaci spusťte a zkuste si měnit hodnotu komponenty UpDown a povolovat/zakazovat změnu.

Všimněte si především toho, že hodnota nastavená komponentou UpDown se automaticky zobrazuje v editačním poli, aniž je nutné ošetřovat jakoukoliv událost (událost OnChanging ošetřujeme jen kvůli povolení/zakázání změny)! Všimněte si také toho, že komponenta UpDown se automaticky zobrazí vedle komponenty Edit, i když předtím měla na formuláři jinou (libovolnou) pozici. To vše je způsobeno právě asociací UpDown s editačním polem. Možnost asociovat komponentu UpDown s jinou komponentou je často využívána právě k pohodlnému vytváření takovýchto ovládacích prvků.

Následující obrázek ukazuje běžící aplikaci:

Komponenta CheckListBox – položko, zaškrtni se

Poslední komponentou, na kterou se v rámci dnešního dílu seriálu podrobně podíváme, je komponenta CheckListBox, která se nalézá v paletě Additional. Tento seznam je zcela identický se seznamem ListBox (který jsme popisovali v 8. dílu našeho seriálu), i práce s ním je zcela analogická. S jedním jediným rozdílem: u každé položky je zaškrtávací pole. Tento seznam tedy přináší navíc (oproti klasickému seznam ListBox) možnost označovat (zaškrtávat) své položky.

Vlastnosti komponenty CheckListBox jsou tedy jakýmsi „kompilátem“ vlastností komponenty CheckBox a seznamu ListBox. Kromě standardních vlastností seznamu, se kterými jsme se setkali při popisu komponenty ListBox, lze udávat, zda může být stav políček i cbGrayed (vlastnost AllowGrayed). Pomocí pole Checked je dále možné testovat či nastavovat zaškrtnutí položek. Navíc je k dispozici vlastnost Flat, která určuje vzhled zaškrtávacích polí. Dle předpokladů funguje také vlastnost State: je to run-time pole obsahující stav jednotlivých položek; možné hodnoty jsou cbUnchecked (nezaškrtnuto), cbChecked (zaškrtnuto) a cbGrayed (šedé).

Navíc přibývá také událost OnClickCheck. Ta je generována v případě, že došlo ke změně zaškrtnutí některého políčka. Událost OnClick nastává vždycky po klepnutí myší v seznamu, událost OnClickCheck pouze po klepnutí na zatrhávacím poli.

Ukázka seznamu se zaškrtávacími poli je na následujícím obrázku:

Ukažme si ještě rozdíl mezi událostmi OnClick a OnClickCheck. Následující programový výpis obsahuje obsluhy obou těchto událostí. Zkuste si vytvořit program, takto obě události ošetřit a dívejte se, kdy je vypsána která zpráva. Klepnete-li na zatrhávací políčko, je generována událost OnLickCcheck a už nikoliv událost OnClick.

procedure TfrmHlavni.CheckListBoxClickCheck(Sender: TObject);
begin
  ShowMessage(`Došlo ke klepnutí na zatrhávací políčko`);
end;

procedure TfrmHlavni.CheckListBoxClick(Sender: TObject);
begin
  ShowMessage(`Došlo ke klepnutí položku v seznamu`);
end;

Na závěr

Tolik tedy k dnešnímu popisu dalších možných fragmentů uživatelského prostředí aplikace. Doufám, že podobné informace shledáváte užitečnými a nikoliv zbytečnými, jak to (zcela mylně) řada vývojářů činí. Znovu bych chtěl podotknout, že uživatelské prostředí je zcela fundamentální součástí každé vytvářené aplikace a jeho podceňování není vůbec na místě (pokud tedy není cílem vašeho snažení pouze vlastní algoritmus, který za vás bude prodávat někdo jiný).

V příštím díle seriálu však na uživatelské prostředí na chvíli zapomeneme a budeme se věnovat tématu, který si ve svých mailech často přejete a který já již delší dobu slibuji:-) Vrhneme se totiž na tvorbu vlastních komponent. V předchozích dílech jsme si popsali valnou většinu důležitých témat, které k vytváření komponent budeme potřebovat a těch několik, které nám dosud zbývají, budeme zařazovat a popisovat průběžně. Celou problematiku se opět pokusíme vysvětlit srozumitelně a jasně, tak, aby každý zájemce dokázal bez větších problémů projít celým procesem tvorby nové komponenty – od úvodní rozvahy přes vlastní tvorbu až k instalaci a používání v aplikacích. Protože téma vytváření vlastních komponent je relativně rozsáhlé, bude jeho popis opět rozdělen do více dílů seriálu. Příští týden tedy začínáme a já se těším nashledanou.

Váš názor Další článek: Malý velký kompakt DiMAGE S304

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,