Delphi - Multiple Document Interface használata Delphi-ben

forráskód letöltése
A Multiple Document Interface, rövidítve MDI ablakkezelési technika annyit jelent a Windows-ban, hogy adott egy keret ablak, melynek vannak gyerek ablakai. Ezt úgy kell elképzelni, mint mondjuk a Word szövegszerkesztőt, ahol szintén van egy keret program és ahol minden egyes dokumentum egy-egy gyerek ablaknak felel meg.

Minden gyerek ablak lehet teljesen egyforma, mint a Word-ben, de lehetnek akár különbözőek is. Minden ablakból tetszőleges számút nyithatunk meg és ezzel sok munkát takaríthatunk meg, gondoljuk csak ismét a Word-re, számtalan dokumentumot kezelhetünk vele egy időben, pedig ezt az ablakot és a hozzátartozó funkciókat csupán egyszer kellett elkészíteni.

Ezt a technikát és annak rejtelmeit, valamint a 4.0-s Delphi újdonságait bemutató példaprogramot találhat a mellékelt cikkben. A Multiple Document Interface, rövidítve MDI ablakkezelési technika annyit jelent a Windows-ban, hogy adott egy keret ablak, melynek vannak gyerek ablakai. Ezt úgy kell elképzelni, mint mondjuk a Word szövegszerkesztőt, ahol szintén van egy keret program és ahol minden egyes dokumentum egy-egy gyerek ablaknak felel meg.

Minden gyerek ablak lehet teljesen egyforma, mint a Word-ben, de lehetnek akár különbözőek is. Minden ablakból tetszőleges számút nyithatunk meg és ezzel sok munkát takaríthatunk meg, gondoljuk csak ismét a Word-re, számtalan dokumentumot kezelhetünk vele egy időben, pedig ezt az ablakot és a hozzátartozó funkciókat csupán egyszer kellett elkészíteni.

Ezt a technikát és annak rejtelmeit, valamint a 4.0-s Delphi újdonságait bemutató példaprogramot találhat a mellékelt cikkben.


Nézzük sorra milyen lépések is szükségesek ahhoz, hogy ilyen típusú alkalmazást hozzunk létre.

Kezdjük először is a keret ablakkal. Miután létrehoztunk hagyományos módon egy Form-ot, állítsuk át a FormStyle property-ét fsMDIForm értékre. Ettől kezdve csak olyan komponenseket helyezzünk el ezen a Form-on, mely valamely oldalához igazítva van (Align property alLeft, alRight, alBottom, vagy alTop). Ha másképp teszünk az elég nagy zavart fog okozni programunk kinézetében.

Mint a mellékelt példában is látható, elhelyezünk egy menürendszert, egy nyomógomb sort (TToolBar) és alul egy TStatusBar komponenst.

A középen fennmaradó hely áll majd rendelkezésre a gyerek ablakok számára.

A menürendszerben a Window menüpont legördülő elemeinek a létrehozásánál egy TActionList komponenst fogunk felhasználni. Erről a komponensről már volt szó újságunk régebbi számaiban, így erre most külön nem térnénk ki.

Kattintsunk duplán a TActionList komponensen, majd a megjelenő ablakban nyomjuk le a Ctrl + Ins billentyűkombinációt. A megjelenő újabb ablakban találunk már előre definiált cselekményeket. Ezen belül is a TWindow kezdetűek lesznek most számunkra érdekesek, mivel ezek pont a MDI gyerek ablakok kezelését hivatottak elvégezni, ezért adjuk is hozzá az összes ilyen elemet.

Ezen kívül még létrehozunk néhány saját elemet is, de erről majd később.

Ha ez kész, akkor kattintsunk duplán a TMainMenu komponensen. Itt vegyünk fel egy Window nevű menüpontot, majd a legördülő menüpontjainál ahelyett, hogy kézzel töltenénk fel őket, minden egyes új, üres menüpontnál válasszunk ki egy cselekményt az adott menüpont Action property-én keresztül.

Az így létrehozott menüpontoknak automatikusan be lesz állítva a kezdeti értékük. Igaz, hogy csak angol nyelven, de ha ezen változtatni szeretnénk, akkor térjünk vissza a TActionList komponens szerkesztő ablakába és sorra kiválasztva az egyes cselekmények Caption property-ét tetszés szerint átírhatjuk. Amint ez megtörténik, a menüpontok Caption property-e is megkapja az új értéket.

A TMainMenü-be így felvett elemekkel még csak annyi dolgunk sincs, hogy az OnClick eseményüket lekezeljük, ez automatikusan megtörténik és a megfelelő funkció fog végrehajtódni a kiválasztásukkor.

Kicsit kanyarodjuk el most a keret Form-tól és nézzük meg a gyerek ablakok létrehozását.

Ehhez nyissunk egy új Form-ot, majd a FormStyle property-nél válasszuk az fsMDIChild értéket. Ezek után helyezzünk el tetszés szerinti komponenseket, ezekhez rendelhetünk tetszés szerinti funkcióikat is.

A mellékelt példában létrehoztunk még egy gyerek ablakot, eltérő kinézettel, így lesz majd egy keret ablakunk és azon belül két különböző gyerek ablakot kezelhetünk.

Mind a két gyerek ablakon elhelyeztünk különböző tartalmú TMainMenu komponenst. Az MDI technikát használó alkalmazásoknál lehetőség van arra, hogy a gyerek ablakok menürendszerei a keret ablak menürendszerében jelenjenek meg. Ezt úgy kell elképzelni, hogy amikor egy gyerek ablak aktív, akkor az azon lévő TMainMenu komponens tartalma átkerül a keret Form menürendszerébe. Ha egy másik gyerek ablak lesz az aktív, akkor annak a menüje fog átkerülni.

Ennek az elérésére mind a két gyerek ablak TMainMenu komponensénél az AutoMerge property-t igazra kell állítanunk.

Továbbá azt is szabályozhatjuk, hogy a gyerek ablak menüje hol jelenjen meg a keret ablak menürendszerében. Ehhez a menük legfelső szintjén lévő menüpontjainak a GroupIndex property-ére lesz szükségünk. Ez egy egész számot tároló property. Itt tulajdonképpen, mint egy index számot adhatunk meg és a programunk majd úgy "keveri össze" a keret Form és a gyerek Form menüpontjait, hogy ezek az index számok növekvő sorrendben legyenek balról jobbra haladva. Abban az esetben, ha azonos GroupIndex-et talál a keret és a gyerek ablak menüjében, akkor a keret ablak menüjét felcseréli a gyerek ablakon található menüvel.

A mellékelt példában a keret ablakon három menüpont található a legfelső szinten:

File - GroupIndex-e: 0
Menu - GroupIndex-e: 1
Window - GroupIndex-e: 2

A gyerek ablakokon csupán egyetlen menüpont van, melynek vannak legördülő menüpontjai. Itt a GroupIndex értékét 1-re állítottuk.

Ennek következtében mikor láthatóvá válik egy-egy gyerek ablak, akkor a keret ablak Menu nevű menüpontja eltűnik, mivel GroupIndex-e azonos a gyerek ablakéval és mivel a gyerek ablak menüpontjának a GroupIndex-e 1 ezért a File és Window menü között fog megjelenni a menürendszer, így alakul ki a növekvő sorrend.


A gyerek ablakoknál még egy fontos teendőnk van. Az MDIChild típusú Form-okról fontos tudnivaló, hogy mikor a felhasználó a jobb felső sarokban lévő bezárás gombra kattint, akkor az ablak csak ikon állapotba kerül, de valójában nem lesz bezárva. Ha ez esetleg nem felelne meg igényeinknek és ténylegesen szeretnénk ilyen esetben az ablakot bezárni, akkor hozzuk létre a Form OnClose eseményét és itt a paraméterként kapott Action változónak adjunk caFree értéket.


Nézzük tovább, hogy milyen teendőink is vannak még a keret Form-on, vagyis térjünk vissza az Unit1.pas-ban található ablakhoz.

Most, hogy már van keret Form-unk és gyerek Form-unk is, legfontosabb tennivaló az, hogy a felhasználó ezeket meg is tudja nyitni.

Mielőtt ezt elkészítenénk van egy lényeges tudnivaló az MDIChild ablakokról. Mint minden ablak, így ez a típusú Form is lehet olyan, mely a program indulásakor automatikusan létrejön, illetve olyan, amelyet nekünk kell létrehozni, amikor szükségünk van rá. Viszont az MDIChild, ha automatikusan létrejövő típusú, akkor ráadásul automatikusan meg is jelenik a program indulásakor.

Ettől kezdve már az adott feladat dönti el, hogy melyik változatra van éppen szükségünk.

A mellékelt példában mi azt valósítjuk meg, hogy mind a két gyerek ablak csak rendelkezésre álló és ne automatikusan létrejövő Form legyen.

Ehhez a Project/Options menüpont kiválasztása után az Auto-create forms ablakból át kell tennünk a Form2-t és Form3-at az Avaliable forms-ba.

Ezzel elértük, hogy a gyerek ablakok nem jönnek létre induláskor és nem is jelennek meg, amíg mindezt nem akarjuk.

Tegyünk most fel két nyomógombot a TToolBar komponensre, a Caption property-ük legyen 1 és 2. Ezen gombok lenyomására hozzuk létre a két gyerek ablakot.

Létrehozáskor csak meghívjuk a Create constructor-ukat és a létrejött objektumot nem tároljuk el változóba. Egyrészt azért, mert annyiszor létrejön egy ablak, ahányszor megnyomjuk a gombot és így már nem lenne elég egy változó a tárolásra, másrészt azért, mert amint azt látni fogjuk, a programunk a háttérben mindezt a tárolást automatikusan elvégzi.

Minden létrejövő gyerek ablaknak a fejlécét beállítjuk az MDIChild szövegre és hozzáadunk egy sorszámot, mely az adott ablak egyedi sorszáma lesz egytől kezdve.

Ehhez a Screen TScreen típusú változó FormCount property-ét használjuk fel. Ez a property tárolja az alkalmazásunkban létrejött összes Form számát. Itt most le kell vonnunk egyet a helyes eredményért, mivel itt a keret Form is számít.

A feltett TStatusBar komponensen szeretnénk megjeleníteni az aktuális gyerek ablak fejlécének szövegét. Ehhez szükségünk lenne egy olyan eseményre, ami akkor keletkezik, ha a felhasználó átvált az egyik ablakról a másikra. Erre e segítséget szintén a Screen változóban találjuk meg. Létezik egy OnActiveFormChange esemény, amely pont ekkor jön létre.

Ehhez készítünk egy eseménykezelőt. Mikor létrejön az esemény, akkor csak azt tudjuk, hogy változott az aktív ablak, de azt, hogy most melyik is lett az, azt még nem tudjuk. Ennek eldöntésére a keret Form ActiveMDIChild property-t használjuk, mely egy TForm típusban szolgáltatja az aktív gyerek ablakot.

Itt egy aprócska hibával is találkozhatunk, melyet mi vétettünk. Amikor a felhasználó lenyomja valamelyik TToolButton-t, hogy létrehozzon egy gyerek ablakot, akkor a TForm2.Create(Application) sor után máris létrejön a fenti esemény, mivel az új ablak máris aktívvá válik, ekkor viszont még nem került megváltoztatásra az újonnan létrehozott ablak Caption property-e, így viszont a TStatusBar sem kaphatja meg a helyes eredményt, hanem csak az új Form alapértelmezett Caption property-ének értékét.


Tegyük fel, hogy most egy olyan feladatunk van, hogy egyetlen választással az összes gyerek ablak betűtípusát megadhassuk.

Ehhez létrehoztunk egy Font nevű nyomógombot a TToolBar-on. Itt egy TFontDialog segítségével fogjuk kiválasztani az adott betűtípust. Mielőtt e dialóg ablakot megnyitnánk, alapértékként, ha már van létrehozott gyerek ablak, akkor annak a Font-ját kell beállítanunk a TFontDialog-hoz. Erre azért van szükség, mert a TFontDialog mindig egy alapértelmezett betűtípussal jelenik meg és ha már többször is használjuk a funkciót a programunkban, akkor zavaró lesz, hogy nem a legutoljára beállított értékeket fogjuk viszontlátni, hanem mindig csak az alapértelmezetteket.
Ezzel az apró kezdeti lépéssel azonban elkerülhetjük ezt.


A TFontDialog bezárása után szükségünk lesz arra, hogy az összes megnyitott gyerek ablakot elérjük. Ehhez a keret Form MDIChildren tömb típusú property-t fogjuk használni. Ennek a tömbnek minden eleme TForm típusú és az adott gyerek ablakot szolgáltatja. Az MDIChildCount property-ből pedig megtudhatjuk, hogy hány gyerek ablak lett megnyitva. Ettől kezdve már egy egyszerű ciklussal végigmehetünk az összes ablakon és beállíthatjuk a választott betűtípust.


A menürendszer Window menüpontjában létrehoztunk a kezdetekkor néhány menüt, mellyel a megjelenő MDIChild ablakokat szabályozhatjuk. Ezek használata eléggé egyértelmű, így itt nem is sorolnánk fel őket.

Viszont ezen menüpontok közül hiányzik néhány alapvető funkció, így ezeket egészítsük ki most.

Lehetőségünk van arra, hogy a gyerek ablakok között lépkedjünk sorban előre és hátrafelé is. Ehhez hozzunk létre a TActionList-ben egy-egy elemet WindowNext1 és WindowPrevious1 néven. Állítsuk be itt a Caption property-t, valamint az OnExecute eseményhez hozzunk létre kezelőt. Ez az esemény következik be akkor, amikor az adott cselekmény aktiválva lesz, magyarul, amikor a felhasználó kiválasztja majd a menüpontot. Ezekhez az eseményekhez csupán annyit kell beírnunk, hogy Next, illetve Previous. A keret Form e két eljárása ugyanis pont ezeket a funkciókat látja el.

Ha ez kész, akkor lépjünk át a TMainMenu szerkesztőjébe és a Window menübe vegyük fel két újat oly módon, hogy egy üres menüpontnál válasszuk ki a menüpont Action property-nél az újonnan létrehozott cselekményeket.

A fentihez hasonló módon hozzunk létre egy olyan menüpontot is, melynek segítségével egy lépésben bezárhatjuk az összes megnyitott gyerek ablakot.

Ezt CloseAll-nak neveztük el és a hozzá tartozó kódrészletben van egy apró trükk is elrejtve.
Ahhoz, hogy minden ablakot bezárjunk, nyilvánvaló, hogy az összest el is kell érnünk. Erre már láthattunk példát az előbbiek során az MDIChildren és az MDIChildCount property-k kapcsán. Az MDIChildren tartalmazza az összes gyerek ablakot egy tömbben. De ha egy sima ciklussal mennénk végig mindegyiken, akkor elég csúnya hibaüzenetet kapnánk. Gondoljuk csak végig, ha a tömb első elemében található Form-ot bezárjuk, akkor az megszűnik és ebből a tömbből is eltűnik, így a MDIChildCount értéke is csökken. Ezért hát fordítsuk meg a ciklust és a tömb utolsó elemétől az elsőig visszafelé haladva zárjuk be az ablakokat, így elkerülhetjük a kellemetlen hibákat.


Ha megfigyeljük a Window legördülő menüpontjai automatikusan kiegészítésre kerülnek a létrehozott gyerek ablakok neveivel, illetve egy-egy ilyen menüpont kiválasztásával az adott ablakra léphetünk. Ennek a funkciónak ne is keressük a forráskódját a saját alkalmazásunkban, mivel ezt szintén egy ilyen automatikus funkció. Létrehozásához csupán a keret Form WindowMenu property-ben kell kiválasztanunk a TMainMenu Window menüpontját és a többi "megy magától".