Procedury a funkce

Všechny projekty v Delphi i ostatních programovacích jazycích se skládají z jednotlivých částí - celý program je rozdělený na menší problémy, které jsou vyřešeny v podprogramech - procedurách nebo funkcích a z hlavního programu jsou potom v potřebný okamžik "zavolány". V našich projektech zatím procedury vždy obsluhovaly nějakou událost (např. OnClick), je ale samozřejmě možné vytvořit proceduru nebo funkci pro více událostí nebo i pro jiný účel.

Rozdíl mezi procedurou a funkcí je ten, že procedura prostě jen vykoná svoje příkazy, zatímco funkce vykoná příkazy a navíc je jejím výsledkem nějaká hodnota (u funkce musí být jako u proměnné daný typ - to je právě typ její výsledné hodnoty). Pro srovnání si prohlédněte příklad jednoduché procedury a funkce (zatím nevalného významu :-)):
procedure ukazZpravu;
begin
showmessage('Důležitá informace'); beep;
end;
function dvakrat(cislo: integer): integer;
begin
dvakrat:=cislo*2;
{result:=cislo*2;}
end;
Procedura obsahuje výpis hlášky vylepšený o pípnutí, funkce vrátí dvojnásobek parametru, který je uvedený v závorce za názvem funkce. Pro určení výsledné hodnoty funkce můžete použít buď název funkce (dvakrat:=cislo*2) nebo proměnnou Result, která je pro každou funkci automaticky definovaná (viz příkaz funkce v komentářové složené závorce). Konkrétní příkazy pro použití uvedených příkladů by mohly vypadat takto:
...
ukazZpravu;
x:=dvakrat(6);
...


Procedury i funkce musí být v projektu definovány před tou částí kódu, ve které budou použity!

Podobně jako funkce mohou i procedury pracovat s parametry, uvedený příklad ukazZpravu vypisuje stále stejný text, pokud použijeme parametr, můžeme vypisovat i jiné texty:
procedure ukazZpravu(text: string);
begin
showmessage(text); beep;
end;
Volání této procedury by potom obsahovalo v závorce text (či proměnnou typu string), který by se vypisoval, například:
ukazZpravu('Dobrý den');

Hodnoty parametrů se během procedur nebo funkcí mohou měnit - důležité ale bude, jaká bude jejich hodnota po ukončení procedury nebo funkce. Pokud budeme chtít, aby hodnota použitého parametru byla po skončení stejná jako před spuštěním, uvedeme parametr tak, jako v ukázce (tzv. volání hodnotou - předá se jen hodnota). Pokud budeme chtít, aby si parametr svoje změny uchoval i po skončení procedury nebo funkce, využijeme tzv. volání odkazem (na adresu proměnné), do hlavičky k parametru přidáme klíčové slovo var, například:
procedure ukazZpravu(var text: string);

Další poznámky jsou spíše už jen pro experty:
Procedury a funkce musí být definovány předtím než budou použity, někdy se ale může stát, že z jedné procedury chceme volat druhou a z té druhé zase první (kterou definovat dřív - viz problém vejce a slepice), pak využijeme tzv. dopřednou deklaraci pomocí klíčového slova forward, například:
procedure druha (r: real); forward;
Ačkoliv bude mít procedura jen tuto hlavičku, můžeme už její název použít v jiné proceduře a celou ji potom definovat až za procedurou, která ji využila.

Zvláštním typem procedur jsou takzvané přetížené procedury, což určuje klíčové slovo overload v hlavičce. Takové procedury jsou stejné až na parametry, při volání můžeme použít kterýkoliv z uvedených typů parametrů a Delphi už automaticky zavolá správnou proceduru s příslušným typem parametru:
procedure ukazZpravu(text: string); overload;
begin
showmessage(text); beep;
end;

procedure ukazZpravu(numero: integer); overload;
begin
showmessage(inttostr(numero)); beep;
end;
Po definování těchto procedur můžeme volat ukazZpravu('Ahoj') nebo ukazZpravu(3) a nedojde k chybě.


Procedur už jsme vytvořili mnoho, v minulé lekci jsme narazili na první předdefinovanou funkci MessageDlg, jejíž hodnotou (výsledkem) je vybrané tlačítko. Další funkce využijeme k doplnění našeho rozpracovaného textového editoru.

Další úpravy textového editoru

Typickou ukázkou pro využití funkce je ukládání souboru, ta se totiž může využít při různých situacích - u nabídek Uložit, Uložit jako, Soubor - Nový, při ukončení aplikace atd. Použijeme funkci a nikoli proceduru, to proto, abychom jako výsledek mohli získat informaci, zda došlo k uložení souboru.
Zavedeme nové dvě proměnné JmenoSouboru a zmeneno (typ boolean může mít jen dvě hodnoty - True nebo False - viz 3. lekce).
V ukázce níže je uvedena i část hlavičky programu (nekopírujte celé! :-) ), aby bylo jasné, kam co umístit. Za prvé si všimněte názvu nové funkce - TForm2.Uloz - TForm2 je název formuláře, ve kterém je funkce využívána, za druhé musí být deklarace funkce (definice pomocí hlavičky) uvedena ještě mezi deklaracemi ostatních procedur - tam už není v názvu TForm2, protože jde vlastně o definici objektů třídy TForm2. Vlastní definice funkce s jejími příkazy je umístěna před definici všech procedur (hlavně před těmi, které ji budou využívat) - stejně se postupuje i u dalších funkcí.
type
TForm2 = class(TForm)
RichEdit1: TRichEdit;
MainMenu1: TMainMenu;
Soubor1: TMenuItem;
.....
procedure KonecClick(Sender: TObject);
function Uloz(soubor: string): boolean;
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form2: TForm2;
JmenoSouboru: string;
zmeneno: boolean;

implementation

{$R *.dfm}

function TForm2.Uloz(soubor: string): boolean;
begin
RichEdit1.Lines.SaveToFile(Soubor); //uložení souboru
zmeneno:=false;//soubor je již uložen
result:=true;
end;
Tato funkce má jako parametr proměnnou soubor - řetězec, ve které bude uložen v průběhu aplikace název souboru, hodnota této funkce je typu boolean (True, False). Funkce využívá předdefinovanou metodu (proceduru) SaveToFile komponenty RichEdit, jejímž parametrem je název souboru. Dále je hodnota proměnné zmeneno změněna na false (po uložení nejsou změny) a do proměnné result (předdefinované pro každou funkci - viz předešlý text) je uloženo true - uložení proběhlo.

Druhá funkce, kterou přidáme, bude sloužit pro uložení souboru pod jiným názvem (Uložit jako...), využijeme v ní také předešlou funkci Uloz (v jakém pořadí musí být tedy jejich definice?). Na rozdíl od předešlé funkce jde o funkci s parametrem volaným odkazem, protože se v průběhu této funkce bude měnit proměnná JmenoSouboru a my potřebujeme, aby tato změna zůstala i po skončení funkce (všimněte si rozdílu v hlavičce oproti funkci Uloz).
function TForm2.UlozJako(var Soubor:String):boolean;
begin
SaveDialog1.FileName:=Soubor;
if SaveDialog1.Execute then
Soubor:=SaveDialog1.FileName //výběr jména souboru
else
begin
Result:=false;
exit; //stisknuto tlačítko zrušit -> konec
end;
Result:=Uloz(Soubor);
Form2.Caption:='Editor - '+Soubor; //úprava titulku
end;
Tentokrát je funkce delší, vezměme ji řádek po řádku, nebo příkaz po příkazu:
- nejprve se v dialogovém okně pro uložení do řádku pro jméno souboru uloží hodnota parametru soubor, což je název našeho souboru
- jestliže byl dialog ukončen tlačítkem OK (dává výsledek True), potom se do parametru soubor uloží nově zapsané jméno souboru
- jinak (jestliže bylo kliknuto na Storno nebo Cancel) je uložena do result hodnota False a nám zatím neznámým příkazem exit je procedura ukončena
- dále se spustí funkce Uloz(Soubor) a do proměnné result se uloží její výsledná hodnota
- nakonec se upraví titulek formuláře - připíše se případný název souboru

Vytvořené funkce můžeme nyní použít pro příslušná tlačítka (nabídky) Save (upravíme nabídku z minulé lekce) nebo Save as...(přidáme do menu):
procedure TForm2.saveClick(Sender: TObject);
begin
if JmenoSouboru<>'' then Uloz(JmenoSouboru)
else UlozJako(JmenoSouboru);
end;

procedure TForm2.saveasClick(Sender: TObject); begin
UlozJako(JmenoSouboru);
end;

Dál musíme upravit ještě procedury pro otevření souboru a pro vytvoření nového souboru - zaprvé proto, abychom získali jméno souboru a zadruhé, abychom je trochu ošetřili:
procedure TForm2.openClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
JmenoSouboru:=OpenDialog1.FileName;
RichEdit1.Lines.LoadFromFile(JmenoSouboru);
zmeneno:=false;
Form2.Caption:='Editor - '+ JmenoSouboru;
end;
end;


Case - vícenásobné větvení programu

Pro větvení programů jsme doposud využívali příkaz if (jestliže byla splněna podmínka vykonal se příkaz, jinak ..). Často bude potřeba větvit program do více větví, například podle hodnoty obsažené v nějaké proměnné - takové větvení řešit pomocí příkazu if by bylo komplikované, pro takové případy je ve všech programovacích jazycích příkaz case.

Syntaxe příkazu case je následující:
case promenna of
hodnota1: prikaz1;
hodnota2: prikaz2;
hodnota3: prikaz3;
.
.
else
prikaz
end;
Podle toho, jaká je hodnota v proměnné promenna, se vykoná odpovídající příkaz (případně příkazy uzavřené mezi begin a end - složený příkaz). Za else se mohou uvést (a také nemusí) příkazy, které by se měly vykonat, pokud by v proměnné promenna nebyla ani jedna z uvedených hodnot. Zvláštností tohoto příkazu je, že končí příkazem end, ačkoliv nemá begin!

Jako příklad využití příkazu case si uvedeme už pečlivěji ošetřenou proceduru pro tlačítko Nový soubor v našem textovém editoru. Po volbě nabídky Nový soubor je potřeba zjistit, jestli došlo ke změnám dosavadního souboru a zeptat se uživatele, zda chce změny uložit. Měli bychom mu nabídnout tři možnosti a také na ně regovat:
- Ano - nabídnout uložení souboru, po uložení vyprázdnit RichEdit, pokud ale uživatel soubor neuloží (dá Cancel v dialogu pro ukládání), nebudeme otvírat nový soubor
- Ne - čili nechce změny uložit, vyprázdníme RichEdit
- Cancel - zrušení nabídky - nic se neukládá ani neotvírá, návrat bez akce do předešlého stavu

Tuto situaci by mohla řešit následující procedura pro tlačítko Soubor nový (i když hloubavější uživatelé úplně spokojeni nebudou :-) ):
procedure TForm2.NovyClick(Sender: TObject);
begin
if (Zmeneno) then //pokud došlo ke změně, musí se starý soubor uložit.
case MessageDlg('Uložit změny?',mtConfirmation,[mbYes,mbNo,mbCancel],0) of
mrYes: if not UlozJako(JmenoSouboru) then exit; //uložit soubor, pokud se neuložil, konec procedury
mrCancel: exit; // zrušit akci.
end; //tento end patří ke case
RichEdit1.Lines.Clear; //smazání souboru
Zmeneno:=false;
Jmenosouboru:='';
Formular.Caption:='Editor';
end;

Během tvorby funkcí a procedur v této lekci jsme několikrát pracovali s proměnnou zmeneno, ve které si "poznamenáváme", jestli došlo ke změně souboru v našem editoru. Ještě je potřeba do této proměnné při změně RichEditu uložit hodnotu True, doplníme událost OnChange komponenty RichEdit:
procedure TForm2.RichEdit1Change(Sender: TObject);
begin
zmeneno:=true;
end;
Udělali jsme v této lekci spoustu práce, která ale navenek není moc vidět (a tak je to s programováním (a vlastně každou pořádnou prací) velice často), v příští lekci provedeme změny a vylepšení i pouhým okem neznalého laika viditelné :-) .


Aplikaci ukládej do složky s názvem lekce16 .

Úkoly:

Základní úloha:

Vytvořte aplikaci podle textu lekce (viz ukázka ) - vylepšete nabídky Uložit, Uložit jako .., Otevřít a Nový.


Úloha na plus:

Ošetřete nabídku Otevři obdobně jako Soubor nový.. .


Úlohy na jedničku:

Komponenta RichEdit má také vlastnost Modified (RichEdit.Modified) s hodnotami True nebo False podle toho, zda došlo ke změně v RichEditu. Vyměňte někde proměnnou zmeneno za tuto vlastnost.