Az MDI (többdokumentumos felület) egy hasznos és elterjedt
technika a Windows alkalmazások szerkezetének felépítésére.
Az MDI alkalmazások egy főformból és az azon belül megjelenő formokból
állnak. A főform a szülő, míg a többit gyermek ablaknak nevezzük.
MDI alkalmazások általában a következők: a szövegszerkesztők egy
része, fejlesztő
programok, internetes böngészők (az MS Word már nem ilyen egy ideje -
az SDI).
Ahol több dokumentum megnyitására van lehetőség vagy szükség,
ott érdemes lehet ezt a módszert használni.
MDI működése a Windowsban
Az MDI szerkezet több előnyt is nyújthat a programozónak. Ha Delphi MDI
sablont használunk, akkor több funkció
van, ami már eleve működik és nem kell leprogramozni. Például a gyermekablakok listája megtalálható a főform egy lenyíló menüjében, illetve
az ablakok mozaikszerűen elrendezhetőek egy gombnyomás segítségével. A
fenti pillanatkép egy olyan MDI alkalmazás futása közben készült, amelybe
egy sor kódot sem írtam, alapban ezt adja a Delphi, ha elindítjuk egy új
MDI alkalmazás fejlesztését (File/New/Other/MDI application).
Az MDI alkalmazások felépítése a Windowsban a következő:
- Az alkalmazás fő ablaka keretként vagy tárolóként viselkedik
- Van egy MDI ügyfélként ismert ablak, amely az MDI keretablak teljes
területét befedi (sötétszürke rész a képen)
- Az MDI alkalmazásoknak több azonos vagy különböző típusú
gyermekablaka van. Ezek nem közvetlenül a keretablakba helyeződnek,
hanem az MDI ügyfél ablak gyermekei (az pedig a kereté)
Keret- és gyermekablakok
A Delphi megkönnyíti az MDI alkalmazások készítését, még a Delphi által
előre elkészített MDI alkalmazássablon nélkül is (File/New/Other/MDI
application).
Elég csupán két formot létrehoznunk (minimum) az MDI alkalmazásunkhoz:
- az egyiknek a FormStyle tulajdonságát fsMDIForm-ra kell állítani
- a másikét pedig fsMDIChild-ra
Rögtön indíthatjuk is a programot, és a két ablak a várt módon jelenik
meg:
Általában azonban a gyermekablakok nem jönnek létre a program
indításakor, hanem futás közben dinamikusan hozzuk létre őket.
Ezt úgy oldhatjuk meg, hogy menüt készítünk, és a menüben csinálunk pl.
egy
New Element menüt, amelyhez a következő kódot rendeljük hozzá:
procedure
TMainForm.NewElement1Click(Sender: TObject);
var
ChildForm : TChildForm;
begin
ChildForm := TChildForm.Create(Application);
ChildForm.Show;
end;
Hogy a program helyesen működjön, a következőképpen kell
kiegészíteni a fenti programkódot:
procedure
TMainForm.NewElement1Click(Sender: TObject);
var
ChildForm : TChildForm;
begin
WindowMenu:=Window1;
Inc(counter);
ChildForm := TChildForm.Create(Self);
ChildForm.Caption:=ChildForm.Caption+ ' ' + IntToStr(counter);
ChildForm.Show;
end;
Fontos még, hogy a kezdetkor csak a főform jelenjen meg, gyerek pedig
ne. Ezt a Project / Options párbeszédablaka Forms oldalán állíthatjuk
be.
megj: Előfordulhat, hogy a Window menüben nem jelenik meg a gyerekformok listája, amíg egy másik almenüt (Pl: itt Cascade) be nem teszük.
A gyermekablkakokat megnyithatjuk, kis méretre, ill. teljes méretre
állíthatjuk, viszont a bezárás nem úgy működik, ahogy várnánk.
Bezárás helyett lekicsinyíti az ablakokat. Ez a Delphi alapvető
működésével függ össze - a Delphi, ha lehet, nem zárja be az ablakokat,
hanem elrejti, de MDI
esetén ez nem működik.
Ahhoz, hogy valóban eltűnjön egy ablak, az OnClose esemény Action
paraméterét caFree-re kell állítani, megsemmisítve ezzel az ablakot.
procedure TChildForm.FormClose(Sender:
TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
Ablak menü felépítése TForm tagfüggvényekkel és egyéb hasznos MDI
tagfüggvények
Hogy szabadon készíthessünk tetszőleges MDI alkalmazást és menüt hozzá,
illetve hogy jobban átlássuk a működését, érdemes megnézni, hogyan
készíthetünk saját Window menüt.
(Emlékeztetőül jegyzem meg, hogy a Delphinek van előre elkészített MDI
sablonja File/New/Other/MDI application menü alatt, tehát nem
muszáj nekünk megcsinálni mindig, csak ha testre akarjuk szabni.)
A Window menünek általában három eleme van: Cascade (lépcsőzetes
elrendezés), Tile (mozaikszerű elrendezés), Arrange Icons (Ikonok
elrendezése).
Erre a TFormnak beépített tagfüggvényei vannak a fenti menüknek
megfelelően, amelyeket meghívhatunk : Cascade(), Tile(), ArrangeIcons().
Ezeket csak MDI keretek esetén lehet használni.
Van egy másik, jobb megoldás is.
Helyezzünk a formra egy ActionList komponenst, és rendeljük hozzá az
előre meghatározott, kész MDI műveleteket. (Klikk ActionList-re, majd
New Standard Action és a tree-be a Window alatt)
Majd hozzuk létre a Window menüben a megfelelő almenüket és azok action
tulajdonságát állítsuk be a megfelelő action-ökre. Ezután már működnek
is a parancsok.
További hasznos tagfüggvények és
tulajdonságok, amelyek Delphiben
kizárólag az MDI-hez kapcsolódnak:
-
ActiveMDIChild tulajdonság -
Az MDI keretform futási idejű csak olvasható tulajdonsága. Visszaadja az
aktív gyerekablakot. Ez megváltozik, ha a felhasználó egy másik
gyerekablakra kattint, illetve ha programban meghívjuk a
Next, vagy a
Previous eljárásokat, amelyek a
következő ill. az előző gyermekablakra váltanak át.
-
ClientHandle tulajdonság -
az MDI ügyfélablak (lásd fent) Windows leíróját tartalmazza
-
MDIChildren tulajdonság - a
gyermekablakok tömbje, illetve ehhez tartozik a következő
-
MDIChildCount -
a gyermekablakok száma
A gyermekablakok belső sorrendje a működésbe hozatal sorrendjének
fordítottja. Ez sorrend határozza meg az ablakok elhelyezkedését a
képernyőn. A lista elején lévő ablak lesz legfelül.
Képzeljünk el egy geometriai tengelyt (a z tengely), amely a képernyőből
kifelé mutat. Amelyik ablak magasabb koordinátával bír, az van
feljebb (előrébb),
így az takarja a többit (ez az ún. z-sorrend).
MDI alkalmazások készítése különböző gyermekablakokkal
Összetettebb MDI alkalmazások általában különböző fajtájú (vagyis
eltérő gyermekformú) gyermekablakokból állnak.
Ezzel kapcsolatban felmerül néhány probléma, amire most kitérünk.
Hozzunk létre egy másik gyermek formot (ChildOther). Ez abban
különbözzön az előzőtöl, hogy ennek lesz saját menüje, és ebbe teszünk
egy AddCircle és egy Clear metódust.
Az AddCircle menü segítségével véletlenszerűen tudunk kirajzolni
köröket, a
Clear segítségével pedig töröljük az ablak tartalmát (a köröket).
procedure
TChildOther.AddCircle1Click(Sender: TObject);
var x,y:integer;
begin
randomize;
x:=random(300);
y:=random(300);
Canvas.Ellipse(x-20,y-20,x+20,y+20);
end;
procedure
TChildOther.Clear1Click(Sender: TObject);
begin
Refresh;
end;
Állítsuk be ezt is gyermekformnak (FormStyle tulajdonságát
fsMDIChild-ra állítsuk).
Mivel MDI alkalmazásnál egy gyermekablaknak nem lehet saját menüje,
ezért ez zavart okoz. Ilyenkor azt láthatjuk, hogy a gyermekablak
menüje bekerül a főablak menüjének helyére, ami gond.
A gyermekablak és a keretablak menüjét egyesíthejük, ha beállítjuk a
menükhöz tartozó GroupIndex tulajdonságokat, mondjuk így:
- File menü a MainFormon : 1
- Circle menü a ChildOther formon: 2
- Window menü a MainFormon : 3
Ekkor, ha egy TChildOther típusú gyermekablak aktív, akkor a menüben
megjelenik a hozzá tartozó menü pluszban:
Míg ha a másik típus ablakra váltunk, akkor a menü eltűnik:
Az MDI ügyfélablakra mozaikszerű háttér rajzolása
A mozaikszerű háttérképek beállítását támogatja Delphi. A rendszer a
Windows wm_EraseBKgnd üzenetkezelője segítségével festi a formra. Ehhez
kellene írni egy kezelőt.
(Az üzenetek kezelésére egy külön utasítás van: a message kulcsszóval
egyetlen var paraméterrel rendelkező eljárásokat írhatunk objektum
tagfüggvényként.)
Ez a módszer itt nem működne azonban, mert a főform ablakát eltakarja a
rajta lévő ügyfélablak (lásd fent).
Ezért egy alacsony szintű Windows programozási módszert használunk, az
ún. alosztályozást (subclassing). Ennek az a lényege, hogy az ablak
összes érkező üzenetét fogadó ablakeljárást egy új, általunk megírttal
helyettesítjük. Erre a SetWindowLong API függvényt használjuk. Minden
ablakhoz tartozik egy ilyen ún. ablakeljárás, amely fogadja az
üzeneteket. A Delphi ablakok esetében ezek továbbítódnak a form
megfelelő üzenetkezelő tagfüggvényeihez. A Delphi formok is
rendelkeznek ablakeljárással, csak ez rejtve marad - a WndProc
virtuális függvényt hívják meg. Csak akkor kell a Windows
ablakeljárásokat nekünk kezelni, ha nem Delphi ablakról van szó - mint
pl. most, az MDI ügyfélablak esetén.
Itt azért nem részletezem az üzenetkezelés módszerét, mert elég nagy
témakör, és ahhoz, hogy itt felhasználhassunk, most elegendő ennyit
tudnunk róla.
A MainFormhoz vegyük hozzá a következőket:
private
OldwinProc,NewWinProc : Pointer;
// az eredeti és az általunk kiegészített új ablakeljárásra mutató pointerek
procedure NewWinProcedure(var Msg: TMessage); // az új ablakeljárásunk
Ahhoz, hogy a tagfüggvényünket ablakeljárásként lehessen használni,
MakeObjectInstance függvényt meg kell rá hívni, hogy a rendszer
függvényként kezelni tudja.
procedure TMainForm.FormCreate(Sender: TObject);
begin
counter2:=0;
NewWinProc:=MakeObjectInstance(NewWinProcedure); // tagfüggvényünk a rendszer által kezelhető alakra hozza
OldWinProc:=Pointer (SetWindowLong(ClientHandle, gwl_WndProc,Cardinal(NewWinProc))); // eredeti ablakeljárás
OutCanvas:=TCanvas.Create;
// egy OutCanvas nevű tagváltozót írjunk be a MainForm tagváltozói közé, amely segíti a gyorsabb kirajzolást
end;
Az általunk kiegészített ablakkezelő eljárás pedig ez
procedure
TMainForm.NewWinProcedure(var Msg: TMessage);
var
BmpWidth, BmpHeight,i,j: Integer;
begin
// az alapértelmezett feldolgozás elõször Msg.Result:=CallWindowProc(OldwinProc,
//ClientHandle,Msg.Msg,MSG.WParam,Msg.LParam);
// kiegészítés - háttér újrafestés
if Msg.Msg= wm_EraseBkgnd then // - ha wm_EraseBkgnd üzenet jött, akkor fesse újra a hátteret
begin
BmpWidth:= MainForm.Image1.Width;
BmpHeight:= MainForm.Image1.Height;
if (BmpWidth<>0) and (BmpHeight<>0) then
begin
OutCanvas.Handle:=Msg.WParam;
for I:=0 to MainForm.ClientWidth div BmpWidth do
for J:=0 to MainForm.ClientHeight div BmpHeight do
begin
OutCanvas.Draw( I*BmpWidth, J*BmpHeight, MainForm.Image1.Picture.Graphic);
// kirajzolás, egy tetszőleges Image objektumból vehetjük a képet
end;
end;
end;
end;
Íme a végeredmény:
Példa program
Az fenti leírás alapján elkészült példa program letöltehető verziója
megtalálható itt:
Programs/MDI_example_application.zip
A program Borland Delphi Enterprise 6.0-ban készült.
Szerző neve: Kovács István
Készítés éve: 2008