-
Každá aplikace může být implementována jako vícedokumentové rozhraní (MDI)
nebo jednodokumentové rozhraní (SDI). V aplikaci MDI může být otevřeno
více dokumentů nebo podřízených oken v jednom nadřízeném okně. V aplikaci
SDI může být pouze jedno okno dokumentu. Začneme novou aplikací. Pro formulář
vytvoříme nabídku:
a nastavíme tyto vlastnosti formuláře: Caption na MDI rám,
FormStyle
na fsMDIForm a WindowMenu na Okno1. K aplikaci přidáme
další formulář, kde nastavíme: Caption na Podřízené okno,
FormStyle
na fsMDIChild a Position na poDefaut. Obsluha volby
Nové
bude tvořena příkazy (deklaraci instance formuláře Form2 můžeme
v jednotce druhého formuláře vynechat a vyřadíme
Form2 z automaticky
vytvářených formulářů; je také zapotřebí hlavičkový soubor druhého
formuláře vložit do jednotky prvního formuláře):
static int Citac;
TForm2* Form2 = new
TForm2(this);
Citac++;
Form2->Caption =
Form2->Caption + ' ' + IntToStr(Citac);
Form2->Show();
Po spuštění aplikace můžeme otevřít několik podřízených oken. Povšimněte
si, že se jejich seznam objevuje v nabídce Okno (je to určeno vlastností
WindowMenu).
Zjistěte, co se stane, otevřeme-li jich více než 9. Jestliže některé z
podřízených oken zavřeme, pak v seznamu oken stále existuje (není zrušeno).
Toto vyřešíme v další verzi aplikace.
-
Většina MDI aplikací obsahuje v nabídce Okno volby pro kaskádovité
a dlaždicovité uspořádání otevřených podřízených oken. Pro obsluhu těchto
voleb můžeme použít některé vlastnosti a metody, které jsou u formuláře
s vlastností FormStyle nastavenou na fsMDIForm. Jsou to:
metoda Cascade (kaskádovité řazení), metoda Tile (dlaždicovité
řazení), vlastnost TileMode (určuje jak bude pracovat Tile)
a metoda ArrangeIcons (uspořádání ikon minimalizovaných oken). Toto
jsou metody a vlastnosti odvozené od Windows. Builder definuje některé
další: vlastnost ActiveMDIChild (určuje aktivní okno; tato vlastnost
je určena pouze pro čtení), metoda Next (aktivuje následující okno),
metoda Previous (aktivuje předchozí okno), vlastnost MDIChildCount
(současný počet podřízených oken) a vlastnost MDIChildren (pole
podřízených oken; pomocí cyklu můžeme procházet všemi podřízenými okny).
Na závěr předchozího zadání jsme se zmínili o nedostatku (nezrušení
uzavřeného podřízeného okna). Tuto závadu odstraníme vytvořením obsluhy
události OnClose podřízeného formuláře s příkazem:
Action = caFree;
Toto je jediná metoda podřízeného okna. Rám MDI aplikace nyní vybavíme
následující nabídkou:
Soubor |
Okno |
Nové |
Kaskáda |
---- |
Dlaždice |
Konec |
Uspořádat ikony |
Obsluhy voleb v nabídce již vytvořte sami.
-
Nyní se pokusíme vytvořit další MDI aplikaci. Začneme vývoj nové aplikace.
U formuláře změníme tyto vlastnosti: Name nastavíme na MainForm,
Caption
na Prohlížeč obrázků, Height na 450,
Width
na 575 a FormStyle na fsMDIForm. K formuláři přidáme
také nabídku. Na formulář vložíme komponentu MainMenu, změníme její
vlastnost Name na MainMenu a dvojitým kliknutím na
této komponentě zobrazíme Návrhář nabídky. V místní nabídce Návrháře zvolíme
Insert
from Template a ze seznamu nabídek vybereme MDI Frame Menu.
Naše nabídka bude v angličtině. Okno Návrháře nabídky již můžeme uzavřít.
Na formulář přidáme dále komponentu OpenPictureDialog (změníme
Name
na OpenDialog a Title na Otevření obrázku) a komponentu
SavePictureDialog
(změníme Name na SaveDialog a Title na Uložení
obrázku.
Dále se budeme zabývat obsluhou voleb File | Open a File
| Save As. Obsluha volby File | Open je tvořena příkazy:
if (OpenDialog->Execute())
{
TChild* child
= new TChild(this);
child->Image->Picture->LoadFromFile(OpenDialog->FileName);
child->ClientWidth
= child->Image->Picture->Width;
child->ClientHeight
= child->Image->Picture->Height;
child->Caption
= ExtractFileName(OpenDialog->FileName);
child->Show();
}
Nejprve je provedeno dialogové okno otevření souboru. Pokud jej uzavřeme
stiskem tlačítka OK, pak je vytvořen nový objekt TChild (třída,
kterou vytvoříme později). Soubor obrázku je zaveden do komponenty Image
na podřízeném formuláři, klientská oblast je změněna na rozměry obrázku
a vlastnost Caption je nastavena na jméno zobrazovaného souboru.
Funkce ExtractFileName je použita k extrakci samotného jména souboru
obsaženého ve vlastnosti FileName komponenty OpenDialog.
K získání jiných částí specifikace souboru existují další funkce: ExtractFilePath,
ExtractFileDir,
ExtractFileDrive
a ExtractFileExt.
Nesmíme zapomenout na naši předchozí diskusi o volání delete
pro všechny objekty vytvořené pomocí new. Je předchozí kód výjimkou
z tohoto pravidla? Ve skutečnosti není, protože VCL přebírá zodpovědnost
za uvolňování paměti alokované pro podřízené okno MDI. Konstruktor TChild
přebírá parametr this, který říká VCL, že vlastníkem podřízeného
okna MDI je okno hlavního formuláře MDI. Při uzavření aplikace je také
zajištěno zrušení všech podřízených objektů MDI.
Obsluha volby File | Save As bude vypadat takto:
TChild* child = dynamic_cast<TChild*>(ActiveMDIChild);
if (!child) return;
if (SaveDialog->Execute())
{
child->Image->Picture->SaveToFile(SaveDialog->FileName);
}
První dva řádky tohoto kódu zjistí, zda podřízené okno MDI je aktivní.
Pokud ano, pak je zobrazeno dialogové okno uložení souboru a obrázek je
uložen. V předchozím kódu je použit speciální operátor C++ dynamic_cast.
Tento operátor se používá k přetypování ukazatele třídy předka na ukazatel
na odvozenou třídu. Vlastnost ActiveMDIChild vrací ukazatel na objekt
TForm.
V našem případě, pokud se jedná o ukazatel na TChild (typ uvedený
ve špičatých závorkách; naše třída podřízeného okna MDI je odvozená od
TForm),
pak zpřístupníme vlastnost Image podřízeného formuláře. Pokud není
možno provést přetypování, je vráceno NULL. Přetypování pomocí
dynamic_cast
je lepší (bezpečnější) než starý způsob přetypování.
Obsluhy voleb Tile, Cascade a Arrange All vytvořte
sami.
Nyní již můžeme vytvořit podřízený formulář MDI. K aplikaci přidáme
nový formulář a změníme u něj vlastnosti Name na Child, Caption
necháme beze změny (je nastavován za běhu) a FormStyle na fsMDIChild.
Na tento formulář vložíme komponentu Image, její vlastnost Name
změníme na Image, vlastnost Stretch na true, Align
na alClient.
Do jednotky hlavního formuláře je také nutno přidat hlavičkový soubor
podřízeného okna. Aplikace je hotova, ale má zatím několik nedostatků.
Některé volby v nabídce nejsou obslouženy, tím se již ale nebudeme zabývat.
Další nedostatek je automatické vytvoření podřízeného formuláře při spuštění
aplikace. Podřízený formulář je nutno vyřadit ze seznamu podřízených formulářů
(tím přebíráme odpovědnost za jeho vytvoření). Problémy také vznikají,
když otevřeme soubor, který není obrázkem. Těmito problémy se již nebudeme
zabývat.
-
Pro MDI aplikace (např. textový editor) potřebuje hlavní nabídka získávat
prvky nabídky z jiných (podřízených formulářů). Tomuto procesu říkáme slučování
nabídek. Při práci s nabídkami používáme vlastnost Menu formuláře
a vlastnosti GroupIndex prvků nabídky. Vlastnost Menu určuje
aktivní nabídku formuláře (můžeme ji měnit i za běhu aplikace). Vlastnost
GroupIndex
určuje pořadí, ve kterém slučujeme prvky nabídky. Slučované prvky mohou
nahradit prvky na hlavním řádku nabídky (stejná hodnota GroupIndex)
nebo mohou být vloženy.
Slučování nabídek si ukážeme na jednoduché aplikaci (nejedná se o MDI
aplikaci). Hlavní formulář bude obsahovat nabídku (v závorkách jsou uvedeny
hodnoty vlastností GroupIndex):
Soubor (0) |
E-Mail (1) |
Nápověda (2) |
Nový |
Zobraz modálně |
O aplikaci |
Otevřít |
Zobraz nemodálně |
|
Uložit |
Zavři nemodální |
|
----- |
|
|
Konec |
|
|
Volbu Zavři nemodální zakážeme. K aplikaci přidáme další formulář,
který bude mít nabídku:
E-Mail Soubor (1) |
Otevřít |
Uložit |
------ |
Zavřít |
Abychom si ukázali, že tento přístup funguje s modálním i nemodálním formulářem,
jsou v nabídce E-Mail volby pro zobrazení okna oběma způsoby. Obsluha
volby Zobraz modálně je tvořena příkazem:
Form2->ShowModal();
Formuláře tohoto programu jsou vytvořeny při spuštění aplikace. Když
program zobrazí druhý formulář jako modální, pak není možno používat nabídku
hlavního formuláře. Pokud formulář zobrazíme nemodálně, měli bychom zakázat
ručně volby Zobraz modálně a Zobraz nemodálně (v tomto případě
není vhodné umožnit vícenásobné zobrazení formuláře). Obsluha volby Zobraz
nemodálně tedy bude tvořena příkazy:
Zobrazmodln1->Enabled
= false;
Zobraznemodln1->Enabled
= false;
Zavinemodln1->Enabled
= true;
Form2->Show();
Poslední volba v nabídce E-Mail provádí zavření nemodálního
formuláře. Obsluha bude tvořena příkazem:
Form2->Close();
Po uzavření nemodálního formuláře, bychom ale měli vrátit nabídku do
původního stavu. Formulář lze ale uzavřít mnoha způsoby. Místo opakování
kódu pro povolení voleb nabídky na mnoha místech je můžeme napsat pouze
do obsluhy události OnClose pro druhý formulář:
Form1->Zobrazmodln1->Enabled
= true;
Form1->Zobraznemodln1->Enabled
= true;
Form1->Zavinemodln1->Enabled
= false;
Nyní se ale programové jednotky obou formulářů odkazují na sebe navzájem.
Do jednotky druhého formuláře musíme tedy přidat vložení hlavičkového souboru
prvního formuláře. Pro druhý formulář vytvoříme ještě obsluhu volby Zavřít
s příkazem Close();.
Nyní již aplikaci můžeme vyzkoušet (zatím se ještě nejedná o slučování
nabídek). Na tomto příkladě vidíme, že obsluha modálních formulářů je jednodušší
než nemodálních formulářů.
Aby naše aplikace měla nějaký význam, umístěte na hlavní formulář komponentu
ListBox
s telefonními čísly svých známých a na druhý formulář také
ListBox,
tentokrát s E-Mail adresami. Vytvořte také ostatní obsluhy voleb v nabídce
(ukládání seznamů do souborů, otevírání souboru atd.).
-
V předchozí aplikaci měly oba formuláře vlastní nabídky. C++ Builder ale
podporuje slučování nabídek. Je to prováděno takto. Hlavní formulář má
stále nabídku. Ostatní formuláře mají nabídky s nastavenou vlastností AutoMerge
na true (jejich nabídka potom nebude zobrazována ve formuláři, ale
bude spojena s hlavní nabídkou). Slučování proběhne podle hodnot vlastností
GroupIndex.
V našem příkladě nabídka E-Mail Soubor nahradí nabídku E-Mail
(obě mají stejnou hodnotu GroupIndex). Slučování nabídek má význam
pouze pro nemodální formuláře (u modálních formulářů se k nabídce nedostaneme,
takže C++ Builder slučování neprovádí). Po uzavření nemodálního formuláře
se ale neobnoví původní hlavní nabídka (formulář není zrušen, pouze se
skryl). Problém vyřešíme zásadní změnou přístupu k obsluze druhého formuláře.
Místo vytváření objektu formuláře při spouštění aplikace jej musíme vytvořit
a zrušit pokaždé, když má být zobrazen nebo skryt. V dialogovém okně Project
Options na stránce Forms zrušíme automatické vytváření druhého
formuláře. Abychom mohli formulář zobrazit jako modální, musíme jej vytvořit,
nastavit jeho vlastnosti a později jej zrušit. Obsluha volby Zobraz
modálně bude tedy tvořena příkazy:
Form2 = new TForm2(this);
Form2->MainMenu1->AutoMerge
= false;
Form2->ShowModal();
Form2->Free();
Pro Zobraz nemodálně to bude:
Form2 = new TForm2(this);
Form2->MainMenu1->AutoMerge
= true;
Form2->Show();
Při této volbě formulář zrušen není (není zde již nutné zakazovat volby
v nabídce). Aby při uzavření formuláře byl formulář opravdu zrušen, musíme
ještě u události OnClose druhého formuláře nastavit hodnotu parametru
Action
na caFree. Nyní je již aplikace hotova.
-
Zatím vytvořené MDI aplikace neprováděly nic rozumného. Obdrželi jsme program
s typickým chováním MDI, ale s žádným praktickým užitkem. S podřízeným
formulářem můžeme ale provádět cokoliv. Můžeme přidat různé komponenty,
vytvořit editory apod. Kterýkoli z programů, které jsme zatím vytvořili
můžeme převést na MDI aplikaci (u některých to ale nedává smysl). Naší
skutečnou první MDI aplikací bude jednoduchá verze grafického programu.
Tento program na místě kliknutí myší zobrazí kruh. Podřízené okno obsahuje
tuto nabídku (v aplikaci již použijeme slučování nabídek):
Kruh |
Barva výplně ... |
Barva okraje ... |
Šíře okraje ... |
--------------- |
Získej pozici |
Důležité je to, že při programování podřízeného formuláře nemusíme brát
v úvahu existenci jiných formulářů (včetně formuláře rámu). Jediný speciální
případ je slučování nabídek. Vezmeme rám ze druhého zadání této kapitoly
s jedinou změnou, kterou v něm provedeme je nastavení vlastností GroupIndex
pro nabídku
Soubor na 1 a pro nabídku Okno na 3.
To je vše, co na tomto formuláři změníme. GroupIndex u nabídky Kruh
v podřízeném formuláři nastavíme na 2. U podřízeného formuláře nastavíme
tyto vlastnosti: Caption na Podřízené okno, Color
na clTeal,
FormStyle na fsMDIChild, Menu na
MainMenu1
a Position na poDefault. Do soukromé části podřízeného formuláře
přidáme deklarace:
int XStred, YStred;
int SireOkraje;
TColor BarvaOkraje,
BarvaVyplne;
Nyní již zbývá vytvořit několik obsluh událostí. Obsluha OnCreate
formuláře je tvořena příkazy:
XStred = -200;
YStred = -200;
SireOkraje = 1;
BarvaOkraje = clBlack;
BarvaVyplne = clYellow;
Obsluhu OnPaint vytvoříme příkazy:
Canvas->Pen->Width
= SireOkraje;
Canvas->Pen->Color
= BarvaOkraje;
Canvas->Brush->Color
= BarvaVyplne;
Canvas->Ellipse(XStred-30,
YStred-30, XStred+30, YStred+30);
Obsluhu OnMouseDown tvoří příkazy:
XStred = X;
YStred = Y;
Repaint();
Obsluha OnClose je stejná jako v předcházející MDI aplikaci.
Zbývá ještě vytvořit několik obsluh voleb v nabídce. Např. obsluhu volby
Šířeokraje
tvoří příkazy:
AnsiString VstupniRetezec
= IntToStr(SireOkraje);
if (InputQuery("Okraj",
"Zadej šířku", VstupniRetezec)){
SireOkraje
= VstupniRetezec.ToIntDef(SireOkraje);
Repaint();
}
Ostatní obsluhy voleb vytvořte sami. Výpis informací volbou Získej
pozici provádějte funkcí ShowMessage. Aplikaci můžeme vyzkoušet.
Každý podřízený formulář může obsahovat pouze jeden kruh. Podřízených formulářů
můžeme otevřít více.
-
Obecně mohou MDI aplikace obsahovat více druhů podřízených formulářů. Ukážeme
si to na rozšíření předchozí aplikace. Budeme muset vytvořit nový podřízený
formulář. Tentokrát vytvoříme formulář s pohybujícím se čtvercem, který
se bude odrážet od okrajů. K předchozí aplikaci přidáme další formulář,
přidáme k němu následující nabídku:
Čtverec |
Pohyb |
Barva výplně ... |
Start |
--------------- |
Stop |
Získej pozici |
|
U obou těchto nabídek nastavíme GroupIndex na 2 a volbu Start
zakážeme. U formuláře nastavíme tyto vlastnosti: AutoScroll na False,
Caption
na Pohybující se čtverec, Color na clAqua,
FormStyle
na fsMDIChild, Menu na MainMenu1,
Position
na poDefault a Visible na True. Na formulář vložíme
dále komponentu Shape a nastavíme pro ní vlastnosti:
Left
na 40, Top na 48, Width a Height na
30,
Brush.Pen
na clFuchsia, Pen.Color na
clBlue,
Pen.Width
na 2 a Shape na stSquare. Na formulář přidáme ještě
komponentu Timer a nastavíme jeho vlastnost
Interval na 200.
Nyní se již můžeme zabývat pohybem čtverce. Nejprve nadefinujeme výčtový
typ Smery s možnými směry pohybu čtverce. Deklaraci tohoto typu
umístíme do hlavičkového souboru před deklaraci typu formuláře:
enum Smery {nahoru_vpravo,
dolu_vpravo, dolu_vlevo, nahoru_vlevo};
Jako soukromou položku třídy formuláře přidáme:
Smery Smer;
Obsluhu OnCreate bude tvořit příkaz:
Smer = dolu_vlevo;
a obsluha OnClose je tvořena obvyklým příkazem. Obsluhu volby
nabídky Start tvoří příkazy:
Timer1->Enabled =
true;
Start1->Enabled =
false;
Stop1->Enabled =
true;
Obsluhy ostatních voleb v nabídce vytvořte sami. Zbývá ještě vytvořit
obsluhu události OnTimer časovače. V této obsluze budeme měnit souřadnice
čtverce. Začátek obsluhy je tvořen příkazy:
switch (Smer){
case nahoru_vpravo:
Shape1->Left += 3;
Shape1->Top -= 3;
if (Shape1->Top <= 0) Smer = dolu_vpravo;
if (Shape1->Left+Shape1->Width>=ClientWidth) Smer = nahoru_vlevo;
break;
case nahoru_vlevo:
...
}
Zbytek obsluhy vytvořte sami. Tím je hotov druhý podřízený formulář.
Musíme ještě změnit hlavní formulář. Změníme zde nabídku:
Soubor |
Okno |
Nový kruh |
Kaskáda |
Nový čtverec |
Dlaždice vodorovně |
Uzavřít vše |
Dlaždice svisle |
--------------- |
Uspořádat ikony |
Konec |
Počet |
V nabídce Soubor si ukážeme obsluhu volby Uzavřít vše (ostatní
vytvořte sami):
for (int I = MDIChildCount
- 1; I >= 0; I--) MDIChildren[I]->Close();
Volba Dlaždice vodorovně bude obsloužena příkazy:
TileMode = tbHorizontal;
Tile();
Obdobně vytvořte obsluhu volby Dlaždice svisle (tbVertical).
Obsluhu volby Počet tvoří příkazy:
int NCtverec = 0,
NKruh = 0;
for (int I = 0; I
< MDIChildCount; I++)
if (MDIChildren[I]->ClassNameIs("TForm3"))
NCtverec++;
else NKruh++;
ShowMessage("Jsou
zde "+IntToStr(MDIChildCount)+" podřízené formuláře.\n"
+IntToStr(NKruh)+" je podřízených oken kruhů a \n" +
IntToStr(NCtverec) + " podřízených oken čtverců.");
Ostatní obsluhy vytvořte sami a aplikaci vyzkoušejte.