...formulář je okno ve kterém se nacházejí ovládací prvky a slouží k interakci s uživatelem...
Na formulář můžeme nahlížet buď jako na objekt nebo jako na třídu.
Objektem je myšlen formulář uložený v souboru SCX (příklad example1a.txt).
Třídou je myšlena třída formuláře uložená v souboru VCX - interaktivní návrh nebo PRG - programový návrh (příklad example1b.txt).
Rozdíl mezi třídou a objektem je, že třída automaticky nezahrnuje do své definice objekt
DataEnvironment.
Formulář má dvoje základní chování: modální a nemodální.
modální (modal)
Jeli zobrazen (ne spuštěn) modální formulář, pak se VFP automaticky zastaví a nepokračuje dále dokud takovýto formnulář není ukončen či skryt.
Tento typ formulářů je vhodný na různá dialogová okna jako jsou nastavení možností programu, zadávání parametrů atd.
nemodální (modeless)
Jeli zobrazen nemodální formulář VFP dále pokračuje ve zpracování programu dokud nenarazí na konec programového kódu nebo dokud nenarazí na příkaz READ EVENTS.
Tento příkaz způsobí zastavení VFP a čeká zde tak dlouho, dokud tento příkaz není zrušen pomocí příkazu CLEAR EVENTS.
Formulář jako takový je zobrazen vždy v hlavním okně VFP.
Nicméně pomocí vlastnosti
ShowWindow lze toto chování změnit.
0
Zobrazí vždy formulář v hlavním okně VFP.
1
Formulář se zobrazí v aktivním formuláři který je "As Top Level".
Pokud takový formulář není aktivní, zobrazí se v hlavním okně VFP.
2
Zobrazí formulář mimo hlavní okno VFP, rodičem je tedy samotná plocha Windows.
Soubor SCX nebo třída ve VCX nemusí obsahovat jen jeden formulář, ale celou sadu formulářů združených do objektu zvaného
formset (příklad example1c.txt).
Taktéž může být součástí takovéto definice i jeden či více objektů třídy
toolbar (příklad example1d.txt).
Formset se vytvoří tak, že se během návrhu:
z nabídky "Form" vybere volba "Create FormSet"
se z panelu "Form controls" vybere třída jejiž základní třída je toolbar a vloží se přímo na formulář - VFP se potom zeptá, zda se má automaticky vytořit formset
Spuštění formulář je závislé na tom, zda je to objekt nebo třída.
* vytvoření formuláře založeného na základní třídě
CREATE FORM test.scx
* vytvoření formuláře založeného na třídě _form
* uloženého v knihovně example.vcx
CREATE FORM test.scx AS _form FROM example.vcx
CREATE CLASS _frmtest OF test.vcx AS form
Formulář se spouští pomocí příkazu DO FORM (příklad example2a.prg).
LOCAL loForm
DO FORM anyform.scx [LINKED] [NAME loForm] [NOSHOW]
bez NAME
VFP automaticky vytvoří proměnnou stejného jména jako je název formuláře.
NAME
Proměnná bude obsahovat odkaz na spuštěný formulář, pokud neexistuje VFP ji sama vytvoří.
LINKED
Formulář bude pevně svázán s proměnnou uvedenou za NAME, tj. při uvolnění proměnné bude uvolněn i formulář pokud neexistuje jiná reference formuláře nebo jeho části.
NOSHOW
Zajistí, že formulář je po spuštění skrytý (hodí se modální formuláře).
Pro spuštění formuláře coby třídy se používají funkce
CREATEOBJECT() nebo
NEWOBJECT() (rozdíl je popsaný v helpu, nicméně
NEWOBJECT() je asi 500x pomalejší než
CREATEOBJECT()), ve formsetu lze využít metod
AddObject() a
NewObject() (příklad example2b.prg).
LOCAL loObject
loObject=CREATEOBJECT("_frmMy")
*loObject=NEWOBJECT("_frmMy")
Pokud dojde přirovnání
CREATEOBJECT() k
DO FORM pak tomu odpovídá toto:
DO FORM anyform.scx LINKED NAME loForm NOSHOW
VFP má možnost procházet kolekci spuštěných formulářů.
Ta to kolekce je na systémovém objektu
_Screen.
Počet prvků v kolekci je definuje vlastnost
FormCount a samotná kolekce má název
Forms[].
Tato kolekce také obsahuje i reference na spuštěné panelové nabídky (toolbary).
DO FORM example1c
FOR lii=1 TO _Screen.FormCount
?_Screen.Forms(lii).BaseClass,_Screen.Forms(lii).Name
NEXT
Tato kolekce je dynamická a obsah se mění i pouhou aktivací formuláře.
Obsah může být následovný: Jako první jsou v kolekci panely nabídek od poslední po první spuštěný.
Pak následují formuláře od posledně aktivovaného po první.
Neznalost pořadí prováděných událostí vede k dalšímu matení a nechuti a na vinně je jen vždy programátor...
Základní seznam pořadí událostí je v nápovědě v kapitole "Event Sequence in Visual FoxPro" pro formset s jedním formuláře s dvěma objekty a
DataEnvironmentem.
Od startu až po ukončení.
Objekt | Událost |
Data environment | BeforeOpenTables |
Form set | Load |
Form | Load |
Data environment cursor(s) | Init |
Data environment | Init |
Objects (1) | Init |
Form | Init |
Form set | Init |
Form set | Activate |
Form | Activate |
Object1 (2) | When |
Form | GotFocus |
Object1 | GotFocus |
Object1 | Message |
Object1 | Valid (3) |
Object1 | LostFocus |
Object2 (3) | When |
Object2 | GotFocus |
Object2 | Message |
Object2 | Valid (4) |
Object2 | LostFocus |
Form | QueryUnload |
Form Set | Destroy |
Form | Destroy |
Object (5) | Destroy |
Form | Unload |
Form set | Unload |
Data environment | AfterCloseTables |
Data environment | Destroy |
Data environment cursor(s) | Destroy |
(1) Pro každý objekt, od prvního po posledního (vytvořeného) v konteineru
(2) První objekt dle pořadí uvedeného ve vlastnosti TabOrder
(3) Další objekt získá fokus
(4) Object ztratí fokus
(5) Pro každý objekt, od posledního po prvního (vytvořeného) v konteineru
Pokud chcete vědět jaké události se spouštějí za běhu vaší aplikace, podívejte se do nápovědy na příkaz
SET EVENTTRACKING.
Jak z výše uvedené tabulky vyplývá, některé události dokonce probíhají ještě dříve než by se na první pohled zdálo.
Proto je nutné rozlišit události mezi DE a ostatními objekty.
Pokud se tak stane, je vidět že události DE a jeho objektů se řídí stejnými pravidly jako události ostatních objektů (formset, formulář...) akorát jsou vmíšeny mezi ně.
Dalším problém způsobuje že některé události mají opačný průběh než ostatní...
Události jako
Load(),
Activate(),
Destroy() probíhají od nejvýše postaveného objektu ve stromu až po ty nejníže postavené objekty: Formset->Form->Objekt.
Kdežto událost
Init() probíhá opačným směrem: Objekt->Form->Formset.
(si myslím)...
A to kdy se provádí přiřazení hodnot vlastnostem.
Tato činnost se provádí mezi vytvořením instance objektu a voláním jeho události
Init().
Jak ukazuje příklad example2d.prg, nejdříve se provádí přiřazení hodnot vlastnostem ve třídě a pak objektu.
Teprve potom následují události jako
Load() a
Init().
Nezapomeň te, že vlastnosti třídy se inicializují pouze jednou a to při prvním zavedení do paměti.
U formulářů se parametry předávají obdobným způsobem jako u volání procedur a u tříd stejně jako při vytváření objektů.
Samotné hodnoty parametrů se přijímají až v události
Init() formuláře.
Proměnné jsou viditelené pouze v události
Init() formuláře/třídy v případě použítí
LPARAMETERS.
Je-li použito
PARAMETERS, jsou proměnné viditelné i ve volaných P/F/M z události
Init().
Po opuštění Initu jsou proměnné
uvolněny.
Proto je vhodné proměnné v události
Init() ihned pro přijmutí uložit do příslušných vlastností formuláře/třídy.
Objekt (příklad example3a.prg)
LOCAL loObject
DO FORM anyform.scx WITH 'Hodnota1',2
* form::Init()
LPARAM lcParam1,lnParam2
?lcParam1,lnParam2
This.Param1=lcParam1
This.Param2=lnParam2
Třída (příklad example3b.prg)
LOCAL loObject
loObject=CREATEOBJECT("_frmMy",'Hodnota1',2)
* form::Init()
LPARAM lcParam1,lnParam2
?lcParam1,lnParam2
This.Param1=lcParam1
This.Param2=lnParam2
Různé verze VFP mají různá omezení pro maximální počet parametrů P/F/M/E.
Vzhledem k tomu, že VFP interně předává parametry přes registry je tato operace rychlá ale je zároveň omezena maximální počtem registrů které lze využít.
Proto, a nejen proto, se doporučuje místo sady parametrů poslat jen jeden a to objekt.
Mezi další výhody patří:
Pokud dojde ke změně specifikace volání formuláře (změna parametrů - počet, pořadí), pak se dělá minální počet změn
Objekt lze mnohem jednodušeji využít pro návrat hodnot, viz. Vrácení hodnoty
LOCAL loObject,loPar
loPar=CREATEOBJECT("_Param")
loPar.Par1='Hodnota1'
loPar.Par2=2
loObject=CREATEOBJECT("_frmMy",loPar)
* form::Init()
LPARAM loPar
?loPar.Par1,loPar.Par2
This.Par=loPar
* definice třídy parametru
DEFINE CLASS _Param AS CUSTOM
Par1=""
Par2=0
ENDDEFINE
Někdy je však potřeba znát parametry již v události
Load().
Je-li volán formulář z formuláře lze využít vlastnosti, že původní formulář je v této chvíli stále aktivní.
Krátká ukázka vyšla v
Softwarovém Quasu č. 2002/40 (přepis) (příklad example3d.prg).
Není li však tato podmínka splněna, lze si vytvořit jednoduchý
zásobník na parametry (příklad example3e.prg).
Formulář, ne však třída umožnuje vrátit
jednu hodnotu.
Tato vlastnost se týká pouze modálních formulářů.
Vrácení hodnoty je realizováno příkazem
RETURN v události
Unload().
Vzhledem k tomu, že v době provádění této události, jsou veškeré objekty formuláře odstraněné, je nutné nejdříve hodnotu zapsat do nějaké vlastnosti formuláře a teprve v události
Unload() hodnotu oné vlastnosti vrátit.
Pro vrácení více hodnot je možné využít objekt.
Domnívám se, že poslat objekt jako parametr při spuštění formuláře je výhodnější než ho vytvořit až v cílovém formuláři a pak vrátit v události Unload().
Hlavně proto, že tento způsob nelze použít pro třídy formuláře.
LOCAL loObject,luReturn
DO FORM anyform.scx WITH 'Hodnota1',2 TO luReturn
* form::Release()
This.Value=This.textbox1.Value
* form::QueryUnload()
This.Value=This.textbox1.Value
* form::Unload()
RETURN This.Value
Pro vrácení více hodnot lze využít objekt.
Je to vhodné jak pro formuláře tak třídy formulářů, modální nebo modeless.
Objekt se pošle jako parametr formuláři s naplněnýma vstupníma hodnotama a zároveň jeho další vlastnosti slouží pro zpětný přenos nových hodnot.
Při ukončování formuláře platí vždy jedno pravidlo: Formulář je ukončen jen tehdy, jeli uvolněná poslední reference formuláře a jeho části.
Formulář se dá ukončit několika způsoby:
1) RELEASE WINDOW/ CLEAR WINDOWS/ QUIT
Název okna je dán obsahem vlastnosti Name formuláře (příklad example5a.prg).
2) RELEASE oVariable
Pouze je-li formulář svázán s danou proměnnou (příklad example5b.prg).
3) loForm.Release
Explicitní volaní metody Release() dojde k ukončení formuláře (příklad example5c.prg).
4) KEYB "{CTRL+F4}"/ KEYB "{CTRL+W}"; Kliknutím na ikonu vpravo nahoře; Z nabídky okna, kliknutím na volbu "Close"
Tyto tři akce (klávesnice, tlačítko, nabídka) jsou ekvivaletní (příklad example5d.prg).
Způsob ukončení | Hodnota vlastnosti ReleaseType | Proběhlé metody a události |
1) | 1, 2 pro QUIT | QueryUnload Destroy |
2) | 0 | Destroy |
3) | 0 | Release Destroy |
4) | 1 | QueryUnload Destroy |
Ne vždy je přijatelné ukončit formulář aniž by na to měl uživatel vliv.
V případě kdy je nutné se uživatele zeptat, zda souhlasí např. se změnami a to i tehdy když uživatel využije nabídky okna pro uzavření formuláře.
Odpovíli uživatel že ne, je nutné zastavit ukončování formuláře.
Toto je možné pouze v události
QueryUnload() kde použití klíčového slova
NODEFAULT způsobí nevykonání výchozího kódu, tj. přeruší se ukončování formuláře.
Z výše uvedené tabulky je vidět, že toto chování je možné v případech 1) a 4).
Případy 2) a 3) tedy slouží k ukončení formuláře bez uživatelského zásahu.
* událost QueryUnload()
liEnd=MESSAGEBOX("Chcete uložit změny?",3+32,"Example 5E")
DO CASE
CASE liEnd=6 && Yes
DEBUGOUT "Save: Ano"
CASE liEnd=7 && No
DEBUGOUT "Save: Ne"
CASE liEnd=2 && Cancel
DEBUGOUT "Save: Storno"
NODEFAULT && Tento příkaz zabrání vykonání původní činnosti - VFP nativního kódu
ENDCASE
Datové prostředí, dále jen DE (DataEnvironment), umožňuje soustředit potřebné datové zdroje přímo do formuláře.
Je to tedy jen definice jaké tabulky či pohledy se mají otevřít a nemůže být součástí definice třídy formuláře.
Samotný objekt
DataEnvironment (výchozí název) není v objektovém stromu vidět a musí aktivovat přes nabídku: "View"->"Data Environment..."
Teprve až s příchodem VFP 8.0 přibyla nejen možnost definovat datové prostředí jako třídu (vcx/prg), ale i pro třídu formuláře nastavit pomocí vlastností
DEClass a
DEClassLibrary tuto definici jako vychozí.
Což je výhodné, pokud potřebujeme mít data po ruce i při vytvoření objektu ze třídy formuláře nebo pokud chceme toto prostředí sdílet mezi více formuláři.
Datová oblast, dále jen DO (Session), je ekvivaletní k DE s tím, že nepodporuje visualní definici tj. ve VCX.
Tato třída (Session) byla přidána do SP 3 pro VFP 6.0.
Příklad example6a.prg ukazuje vložení dvou tabulek do DE, z toho jedna je volaná tabulka a jsou provázány relací.
Obdobnou variantou je vložení pohledu z databázového konteineru do DE - příklad example6b.prg.
Přesměrování složek souborů se musí zajistit dříve než VFP vytvoří první ovládací prvek.
Protože pokud je nějaký ovladač svázán s tabulkou či polem tabulky a v době jeho vytvoření zdroj neexistuje, pak VFP vyvolá chybu.
Projde se kolekce členů DE a pro všechny objekty základní třídy
Cursor se provede oprava složky buď ve vlastnosti
ControlSource - volná tabulka nebo
Database - tabulky či pohled v databázovém konteineru.
Viz. příklad example6c.prg
DE má dva módy - veřejný (public) a privátní (private).
Přičemž každá (DE) má vlastní identifikátor, ale veřejná (DE) má vždy 1.
Tento identifikátor je uložen ve vlastnosti
DataSasseionID a příznak zda je DE veřejná či privátní určuje vlastnost
DataSession.
Obě dvě vlastnosti jsou součástí formuláře (formsetu, toolbaru, atd.).
Veřejný
Veřejný DE zajišťuje zpětnou kompatibilitu s předchozími verzemi FP DOS/WIN a je to vlastně hlavní DE, který nelze nikdy uvolnit.
Privátní
Privátní DE slouží k oddělení datových oblastí jednotlivých formulářů, tak aby nebylo nutno otevírat tabulky a pohledy s dynamickými aliasy.
Použití privátních DE má však jednu nevýhodu: VFP nemá žádný jednoduchý mechanismus pro přístup dat v rozdílných privátních DE.
Nelze si tedy otevřít v jedné privátní DE jednu tabulku, druhou tabulku v dalším privátním DE pak obě použít v jednom SQL SELECTu.
Ovšem při běžném procházení záznamů v tabulce pomocí SCAN...ENDSCAN, DO WHILE...SKIP...ENDO atd. je možné dočasně přepínat datové oblasti.
Dalším možným problémem je, že některá systémová nastavení (SETy) nejsou globální a jsou specifická pro danou datovou oblast viz. nápověda pro příkaz SET DATASESSION TO.
Další drobnou výhodou privátní DE je možnost emulovat víceuživatelské prostředí v jedné instanci VFP.
Příklad example6d.prg ukazuje využití tříd
Session a privátního módu.
V nižších verzi VFP se třída
Session dá emulovat pomocí skrytého formuláře s privátní DE.
Nabídka ve formuláři je možná pouze pro formulář kde vlastnost
ShowWindow=2 (As Top Level).
Pro ostatní varianty
ShowWindow {0,1} se využívá buď vlastní nabídky nebo modifikace systémové nabídky.
Zde platí jedno pravidlo aby byla nabídka vidět - musí se vytvořit v události
Init().
Návrhář nabídky umožňuje v nastavení obecném nastavení zatrhnout generování pro AS Top Level formulář.
Součástí vygenerovaného MPR je i ukázka zavolání nabídky (příklad example7a.prg).
*form::Init()
* Vytvoření nabídky
DO mymenu.mpr WITH THIS,.T.
*form::Destroy()
* Uvolnění nabídky
RELEASE MENU (THIS.Name) EXTENDED
Nikomu nic nebrání si v události
Init() vytvořit celou vlastní nabídku bez použití návrháře nabídek (příklad example7b.prg).
Při užití nabídky ve formuláři je nutné pamatovat na jednu věc: užitná výška formuláře je zmenšena o výšku nabídky.
Při používání MPR je nutné pamatovat na:
Pokud je druhý parametr .T. bude vždy přepsána vlastnost Name formuláře.
Pokud je druhý parametr řetězec a vyplněný, pak název nabídky bude mít hodnotu druhého parametru
I v tomto případě lze využít jak návrháře nabídky, tak i programové definice nabídky.
Jako nejčastější varianty programové definice jsou
Nabídka obsahuje všechny možné volby a dle aktivního formuláře se jednotlivé volby zakazují a povolují
Nabídka obsahuje volby shodné pro všechny formuláře které se zakazují a povolují dle potřeby - form::Activate() a zase uvolňují - form::Deactivate() (příklad example7c.prg).
Nejhorší varianta je:
Generovat celou nabídku pro každý formulář.
Kontextová nabídká formuláře se aktivuje na kliknutí pravého tlačítko myši (událost
RightClick()) a je možné ji zobrazit bud uvnitř formuláře nebo s přesahem mimo formulář.
Pozor se musí dát na:
Aby tato nabídka "nepřekryla" další kontextové nabídky ostatních objektů na formuláři mají li být zobrazeny.
Jeli na formuláři povícero lokálních kontextových nabídek, je nutné zajistit aby se předchozí aktivovaná nabídka deaktivovala.
Dojdeli k opětovné aktivaci té samé nabídky, je nutno ji posunout na nové místo.
Hluchá místa by měla spustit kontextovou nabídku nadřazeného objektu, pokud ji nemá tak pokračovat po objektovém stromu až do formuláře.
Pro zajištění zobrazení kontextové nabídky v hluchých místech formuláře, tj. záložky, konteinery atd. se dá využít tří cest:
Ačkoliv mnozí programátoři tento příkaz považují za anachronismus z DOS verze, protože "nepodporuje" objektový model chápání VFP i když to samé ignorují u příkazů
ON SHUTDOWN,
ON ERROR,
ON ESCAPE, umožňuje nám tento příkaz se zahákovat na událost stisknutí pravého tlačítka myši.
Tímto jednoduchým způsobem s minimem programového kódu lze přesměrovat veškerá volání události
RightClick() do jedno centrálního místa a má vždy přednost před nativními OOP událostmi.
Vzhledem k tomu, že
ON KEY LABEL RIGHTMOUSE automaticky vyvolává jak události
RightClick() na formuláři tak na objektu pod myší, tak stačí, když se v události
RightClick() provede otestovaní, zda objekt pod myší má vlastní zpracování události
RightClick().
Pokud ano, z
RightClick() formu se vyskočí a VFP sama zajistí zbytek.
Celé to ukazuje příklad example8a.prg.
Navíc je pro VFP 3.0-7.0 toto jeden ze dvou způsobů jak se zahákovat na událost
RightClick() hlavního okna VFP (
_Screen), druhý je popsán v článku
Přidání metod k _Screen.
VFP 3.0-9.0
Pokud daný objekt nezpracovává danou dálost, může ji přesměrovat do stejné události nadřazeného objektu a tak dále až po formulář.
To se dá zařídit pomocí vlastní knihovny odvozených základních třídy, kde se provede přesměrování (příklad example8b.scx).
VFP 3.0 - 7.0
Pouze u některých konteinerových tříd jako
PageFrame lze narazit na problém (pouze VFP 3-7), jak provést přesměrování u záložek když ještě neexistují.
Lze to provést např. až na formuláři/třídy formuláře, kde se každé záložce natvrdo předefinuje událost
RightClick() (příklad example8c.scx).
A problém přetrvává u záložek generovaných dynamicky - lze jej vyřešit pomocí
ON KEY LABEL RIGHTMOUSE.
VFP 8.0-9.0
Použitím vlastností
MemberClass a
MemberClasslibrary u
PageFrame lze definovat jaká třída se má použít pro vytvoření objektu základní třídy
Page a právě v defici třídy udané těmito vlastnosti lze mít přesměrování.
BINDEVENT() je mocná funkce, umožňující zahákovat se téměř na libovolnou událost či metodu objektu (příklad example8d.scx), ale pozor na její použití:
Mít v události Init() seznam co vše se má nabindovat; velmi špatně se udržuje a navíc v této době ještě nemusí být všechny objekty vytvořeny - viz Optimalizace.
Mít metodu která rekurzivně projede celý objektový strom; velmi pomalé.
Objektový strom je hiearchické uspořádání objektu a jeho podřazených objektů do stromové struktury.
Každý objekt může mít podřazené objekty a musí mít, vyjma nejvýše postaveného objektu, nadřazený objekt.
Nezapomenout provést odbindování.
Soubory s definicemi konstant lze přiřadit buď pro celý formulář nebo pro jednotlivé metody.
Systémová proměnná
_INCLUDE neslouží ke globálnímu přiřazení souboru konstant během kompilace, ale automaticky se přiřazuje při vytvoření nové třídy nebo nového formuláře.
Dále není možno použít konstanty při přizazení coby hodnoty vlastnosti v "Object Inspectoru", protože samotné přiřazení hodnot vlastonostem není součástí p-kódu (příklad example9a.scx) a je nutné provést přiřazení v události
Init() (příklad example9b.scx).
Jedna z nečastějších chyb je volání nemodálního formuláře z modálního.
Samotné zavolání však není nebezpečné.
Nebezpečí je pečlivě skryto a ukáže se až bude nemodální formulář na pozadí:
dojde z nějakého důvodu (třeba při ladění) k aktivaci předchozího modálního formuláře (příklad example10a.prg).
V tomto okamžiku jsou totiž veškeré události směrovány do nemodálního formuláře, který je nemůže přijmou a modální formulář je naopak odmítá přijmout.
A tento stav bude trvat tak dlouho, dokud nebudou všechny modální formuláře uzavřeny nebo skryty.
modální formulář má nastaveno AlwaysOnTop=.T. a zároveň nemodální formulář je tak malý že jej modální formulář celý zakrývá (příklad example10b.prg).
V tomto okamžiku jsou totiž veškeré události směrovány do nemodálního formuláře, který je nemůže přijmou a modální formulář je naopak odmítá přijmout.
V tomto případě toho moc nepomůže, zlášť u klienta...
Při aktivaci modálního formuláře dojde k tomu, že všechny volby nabídky, vyjma systémových, jsou zakázané.
To se dá jednoduše obejít - stačí příslušné volby vygenerovat v události
GotFocus() formuláře jak ukazuje příklad example10c.scx.
Aby mohla událost GotFocus() formuláře nastat musí formulář obsahovat nejméně jeden ovladač který focus může dostat.
Aby se formulář kompletně uvolnil z paměti je nutné zajistit aby neexistovaly žádné další reference částí jeho objektového stromu.
Příklad example10d.prg tento problém lehce demonstruje.
Zacyklovaný formulář byl běžným jevem pro verze VFP 3.0-6.0 a podrobně to popisuje článek
Zavěšený (hung) formulář a co s tím.
Celé se to projevovalo tak, že při pokusu o ukončení formuláře proběhla událost
QueryUnload() nebo
Release(), dle způsobu ukončení, a formulář byl dále zobrazen.
Nejčastější chybou bylo prolinkování v objektovém stromu, kdy se na vlastnost nějakého objektu nalinkoval jiný objekt z jiné části stromu či použitím funkce
PEMSTATUS().
O verze VFP 7.0 jsou tyto chyby opraveny, nicméně nelze vyloučit, že při složitějším objektovém stromu dojde k při uvolňování objektu z paměti k zacyklování.
Jedna z nejčastějších otázek začátečníků je, proč program (spuštění formuláře) v IDE funguje dobře v pod Runtimem ne.
Vcelku jsou možné dvě varianty:
Nemodální formulář a chybějící READ EVENTS (ukončení aplikace)
Je spuštěn nemodální formulář a chybí příkaz READ EVENTS.
VFP tedy pokračuje dál, ukončí program a tím pádem i sebe (příklad example10f.exe).
Rozsah platnosti proměnné na kterou je formulář nalinkován a svázán
Je spuštěn nemodální formulář nalinkovaný na proměnnou deklarovou jako lokální, či která vůbec nebyla deklarovaná.
VFP po suštění formuláře pokračuje ve vykonání procedury, při jejím opuštění uvolní všechny lokální a privátní proměnné (příklad example10g.prg).
Toto chování,nikoliv chyba, také vyvolává údiv na tvářích programátorů.
Proč k tomu dochází?
Protože při kliknutí na tlačítko v panelu nabídky nebo aktivaci baru v nabídce nedochází ke ztrátě fokusu aktivního objektu.
A právě změna fokusu, vyvolá interně přepis hodnoty z vlastnosti
Value do zdroje dat určeného vlastností
ControlSource (příklad example10h.scx).
Toto chování se dá vynutit zavoláním metodu
SetFocus() daného objektu (příklad example10i.scx nebo example10i1.scx).
Bohužel za podmínky kdy je panel nabídky součástí kolekce
Forms[] formsetu toto nefunguje a dochází přesně k opačnému jevu, kdy se zavoláním metody
SetFocus() obnoví hodnota dle zdroje dat uvedeného v
ControlSource (příklad example10i2.scx a example10i3.scx).
Vysvětlení je jednoduché - protože je panel nabídky součástí kolekce
Forms[] formsetu (příklad example10i2.scx a example10i3.scx).
Tím pádem probíhá jiný sled událostí než u příkladů example10i.scx a example10i1.scx
Panel nabídky NENÍ součástí kolekce Forms[]
=== PROCEDURE _TOOLBAR.CMDSAVE.CLICK E:\NEW\CLANKY\FORMS\EXAMPLES\EXAMPLE.VCT
Value: 20060804
Value of CS: 20060803
38494,647, form1.text1.When()
38494,647, form1.text1.RangeLow()
38494,647, form1.text1.RangeHigh()
38494,647, form1.text1.Valid()
38494,647, form1.text1.GotFocus()
Value: 20060804
Value of CS: 20060804
Panel nabídky JE součástí kolekce Forms[]
=== PROCEDURE FORMSET._TOOLBAR1._CMDSAVE1.CLICK E:\NEW\CLANKY\FORMS\EXAMPLES\EXAMPLE.VCT
Value: 20060803
Value of CS: 20060802
37529,866, formset.form1.text1.When()
37529,866, formset._toolbar1._cmdsave1.LostFocus()
37529,866, formset.form1.text1.LostFocus()
37529,866, formset.form1.GotFocus()
37529,866, formset.form1.text1.GotFocus()
Value: 20060802
Value of CS: 20060802
37529,866, formset._toolbar1._cmdsave1.Valid()
37529,882, formset._toolbar1._cmdsave1.When()
Jak je vidět, v prvním případě proběhne sled událostí na textboxu který má fokus a tlačítko v panelu nabídky fokus nemůže získat.
Kdežto v druhém případě tlačítko v panelu nabídky fokus může získat stejně jako když dochází ke změne fokusu mezi dvěma formuláři.
Tím pádem nedojde k vyvolání interního zápisu z
Value do zdroje dat uvedeného v
ControlSource ale k opětovnému načtení hodnoty.
Projevuje se to pouze u objektů, které mají vyplněnou vlastnost
ControlSource.
Je to chyba č. 2005 a její vzor je: Error with "name" - "property": "error".
Tedy, nic moc...
Problém spočívá v tom, že datový typ zdroje dat neodpovídá datovému typu hodnoty vlastnosti
Value daného objektu.
Například: Objekt očekává ve
Value hodnotu datového typu "number" ale zdroj dat je typu "boolean" nebo "string".
Výhoda RAD nástrojů spočívá v jednoduchém návrhu uživatelského rozhraní (GUI a TUI) což velmi lehce svádí nejen naházet objekty na formuláře ale i využívat kontainerové třídy jako
Container,
Controls a
PageFrame a navíc vnořeně (příklad example11a.scx).
Takový objekt se potom zavádí do paměti velmi pomalu ať už se jedná o spuštění nebo modifikaci v návrháři.
Proto se doporučuje mít jednotlivé části rozdělné a uložené jako třídy zabalené v objektu dle základní třídy
container.
A ve vhodný čas jej vytvořit.
Tento způsob je sice náročný na programování, ale takto lze dosáhnout rychlejšího startu formuláře neboť uživateli se zobrazí jen to, co v danou chvíli může vidět.
Nejčastěji se toho využívá při aktivaci stránky listovacího rámečku, kdy se vytvoří například mřížky, OLE objekty (stromy, seznamy, atd.), nebo celé oblasti s ovladači (příklad example11b.prg).
Od VFP 8.0 lze pomocí vlastnosti
BindControls řídit, zda se mají být ovladače napojeny na zdroje uvedených ve vlastnostech
ControlSource a
RecordSource.
Výchozí hodnota je
.T. aby byla zachována zpětná kompatibilita s předchozími verzemi VFP.
Při nastavení hodnoty
.F. lze zdroje uzavřít a otevřít nové aniž by VFP hlásila chybu.
Po nastavení hodnoty
.T. dojde k opětovnému napojení ovladačů na zdroje (příklad example11c.scx).
Ovlivňuje obsah objektů tříd combobox a listbox - seznamy nezobrazují hodnoty pokud je vlastnost RowsourceType<>1.
Opět jsme další pseudovýhody RAD nástrojů... plácání výkoného kódu přímo do GUI (příklady example11d.scx,example11e.scx ).
Jistě je to jednoduché, pro začátečníky přívětivé, ale co se týče pozdější správy (tj. oprav a údržby) je i peklo vůči tomu vzorné pracoviště mající nejnižší normu ISO.
Ve formuláři by měl být jen takový kód, který přímo manipuluje s GUI.
Vše ostatní by mělo bý mimo objekty GUI (příklad example11f.prg).
Jak vidno z helpu, příkaz
DO FORM nemá klíčové slovo pro identifikaci EXE/APP modulu ve kterém se nachází formulář.
Spuštění formuláře se tedy zajišťuje pomocí proxy programu.
Proxy program je buď program nebo procedura zahrnutá do výsledného EXE/APP zajištující potřebnou činnost: spuštění formuláře, vykopírování reportu, otevření zahrnutých tabulek atd.
Čím více je proxy program obecnější, tím větší je to bezpečnostní riziko.
Proxy program může být označen jako hlavní ale taky nemusí.
Proxy program je označen jako hlavní
DO any.app WITH "myForm"
* proxy.prg
LPARAM lcName
DO FORM (lcName)
Proxy program není označen jako hlavní
DO proxy IN any.app WITH "myForm"
* proxy.prg
LPARAM lcName
DO FORM (lcName)
Většina podstatných věcí je řečena v tomto
seriálu.
Jedna z dalších "nepříjemných" vlastností VFP je, že při změně hodnoty vlastnosti
RecordSource mřížky dojde k automatickému vymazání hodnoty ve vlastnosti
ControlSource ve všech sloupcích.
Řešení je několik:
Použít nějakou utilitu pro přímý přístup do SCX/VCX (RAS Hack SCX/VCX)
Upravit hodnotu RecordSource přímo buď ručně (je nutné znát způsob uložení metadat), nebo pomocí vlastní utility (example12a.prg).
Datová oblast se určuje pomocí vlastnosti
DataSessionID.
Čili jednoduchou změnou hodnoty vnutíme formuláři i jinou datovou oblast.