Formás ablakok

A multimédia világát éljük. Furcsa alakú, díszes ablakok, szép vonalú grafikák, animációk teszik látványossá a programjainkat. Néha elengedhetetlen, hogy megszabaduljunk a kötött formáktól, hogy valami újat hozzunk be a piacra, hisz talán ettől (és pont ettől) lesz sikeres a programunk. Emiatt talán kénytelenek vagyunk más, szokatlan formákhoz nyúlni, mint például, kitörünk a Windows által nyújtott standard téglalap alakú ablakokból, és merészen kör alakú ablakra épített felhasználói kezelőfelületet alakítunk ki, vagy színes képekkel gazdagítjuk programjainkat.

Ez a cikk ebbe a világba fogja elkalauzolni önöket, bemutatja hogyan lehetséges szakítani a megszokott téglalap alakú világgal, és miként lehet létrehozni egy teljesen szabad formájú ablakot, mindenféle megszorításoktól mentesen. A programozási nyelv amit használni fogunk az a Delphi, mivel tapasztalataim alapján ebben lehet a legjobb idő/energiabefektetéssel a legjobb minőségű multimédiás programot előállítani.

Régiók.

A Windows-beli régiókat, a Win32SDK alapos és hosszas böngészése után a következőképpen lehet meghatározni: egy régió nem más, mint egy téglalap, egy ellipszis, egy általános sokszög, vagy ezeknek bizonyos fokú kombinációja, amelyet meg lehet tölteni egy színnel, be lehet keretezni, bele lehet rajzolni, valamint még sok más tulajdonsággal bír, amelyeket helyszűke miatt nem sorolok fel.

Amint kitűnik a Win32SDK-ból, négyfajta régiót különböztetünk meg, és mindegyiknek megvan a saját függvénye, amivel létre lehet őt hozni.

ˇ         Ha egy téglalap alakú régiót akarunk létrehozni, akkor használjuk a következő függvények valamelyikét:

HRGN CreateRectRgn(int Left, int Top, int Right, 
                   int Bottom);
HRGN CreateRectRgnIndirect(CONST RECT *lpRect);
BOOL SetRectRgn(HRGN rgn, int Left, int Top, int Right, 
                int Bottom);

A HRGN típus, amely egyes függvények visszatérési értéke, nem más, mint a régió handle változója. Én feltételezem, hogy nem kell elmagyarázni, mi is az a handle, hisz ez a cikk elméletileg nem annak készült, aki most írja meg az első Windows programját.

Az első függvénnyel létrehozunk egy régiót, amelynek koordinátáit mi adjuk meg, először a bal felső sarkát, aztán meg a jobb alsót. Az MSDN figyelmeztet, hogy a legalsó sor illetve a legjobboldalibb oszlop nem fognak szerepelni az újonnan létrehozott régióban. A második függvény is valami hasonló célt szolgál, csak a paraméter az egy téglalap koordinátáit fogja tartalmazni.

A SetRectRgn másképp működik, mert egy létező régiót átformáz úgy, hogy a paraméterként megadott értékeket fogja tartalmazni, és természetesen téglalap alakja van.

ˇ         A következő régiótípus, amely nagyon hasonló a téglalap alakúakhoz, az a lekerekített sarkú téglalap alakú régió. Ilyesmit csak egy függvény kezel:

HRGN CreateRoundRectRgn(int Left, int Top, int Height, int Bottom, 
                        int WidthEllipse, int HeightEllipse );

Gondolom, sokat itt sem kell magyaráznom a paraméterek mibenlétét, a megadott téglalap alakú zónába olyan lekerekített sarkú téglalapot szerkeszt be, amelynek a sarkaiba berakja azt az ellipszist, amelynek hosszúságát illetve szélességét az utolsó két paraméter adja meg.

ˇ         Ha ellipszis alakú régiókat szerkesztünk, akkor a következő függvényeket használjuk:

HRGN CreateEllipticRgn(int Left, int Top, int Right, int Bottom);
HRGN CreateEllipticRgnIndirect(CONST RECT* lpRect);

Ezek úgy működnek, hogy a téglalapba, amelyet megadtunk paraméterként, berajzol egy ellipszist, melynek tengelyei párhuzamosak a téglalap tengelyeivel.

ˇ         Végül a sokszög alakú régiók a következő függvényekkel kezelhetők:

HRGN CreatePolygonRgn(CONST POINT* lpPoints, int nrPoints,
                      int PolyFillMode);
HRGN CreatePolyPolygonRgn(CONST POINT* lpPoints, 
                          CONST INT* lpPolyCounts,
                          int nCount, int PolyFillMode);

Az első függvény az lpPoints tömbben található pontokból felhasznál nrPoints számút, és létrehozza a kívánt régiót. A PolyFillMode változót a következőképpen értelmezi: ha PolyFillMode ALTERNATE, akkor csak minden páratlan oldal után található zónát tekinti a régió belsejének, ha meg WINDING, akkor mindent berak a régióba.

A következő rajz szemlélteti a két mód közti különbséget:


Tehát mint látjuk, elindul a poligon külsejéről (tegyük fel, hogy fentről), és amint elért a második vonalig (vagyis a képen a vízszintes vonalig) addig tart a régió belseje. Azután megint továbbmegy, és amint elért a következő vonalig, ott tudja, hogy már megint a régió belseje következik. A második (
WINDING) egyszerűen mindent egy kalap alá vesz, és berakja az egészet a régióba.

A másik függvény viszont már bonyolultabb, mint az összes eddigi egy helyen. Elsősorban ez több poligont fest ki, nem csak egyet. Az első paraméter egy vektor, melyben pontokat tartunk. Minden pont egyszer kell megadni és alapértelmezett módon, egymás után veszi őket a függvény. Az azt is jelenti, hogy ha szükségünk van arra, hogy egy pont több poligonnak a csúcsa legyen, akkor többször kell azt definiálnunk. A második paraméter megadja az egyes poligonokban található pontok számát. És végül a harmadik paraméter, az nCount megadja, hogy hány eleme van az lpPolyCounts tömbnek. A PolyFillMode paramétert nem részletezem, lásd fentebb.

A régiók egyesítése

Régiókat lehet kombinálni a

int CombineRgn(HRGN hrgnDest, HRGN hrgnSrc1, 
               HRGN hrgnSrc2, int fnCombineMode); 

függvénnyel. Ezzel a függvénnyel egyesíthetjük a hrgnSrc1 illetve hrgnSrc2 régiókat a hrgnDest régióba, amelynek léteznie kell, a fnCombineMode pedig a következő értékeket veheti fel:

RGN_AND - amikor két régiót metszünk

RGN_COPY - amikor csak az első régiót adja vissza

RGN_DIFF - amikor azokat a részeket veszi, amelyek benne vannak az elsőben, de nincsenek benne a másodikban

RGN_OR - amikor egyesíti a két régiót.

RGN_XOR - amikor egyesíti a két régiót, és kiszedi az így keletkezett alakzatból a kettő metszetét.

Magyarázatképpen egy rajz:

(Nézzünk meg egy érdekes függvényt, mielőtt még továbblépnénk a következő gondolatra. A PtInRegion függvény megmondja, hogy a paraméterként átadott pont benne van-e a szintén paraméterként átadott régióban.)

Kapcsolat az ablakok és a régiók közt

Minden ablakhoz a Windows hozzárendel egy téglalap alakú régiót. És azért téglalap alakút, mert az alapértelmezett ablak téglalap alakú. Ezt a régiót le lehet kérdezni a

int GetWindowRgn( HWND hWnd, HRGN hRgn );

függvénnyel. A hWnd paraméter az annak az ablaknak a handle-je, amelynek a régióját le akarjuk kérdezni, és a hRgn változóban tárolni. Figyelem! A régió az ablak bal felső sarkába kezdődik, és nem a ClientArea bal felső sarkába.

Egyértelmű, hogy ha van olyan függvény, amivel le lehet kérdezni egy ablak régióját, akkor olyannak is kell létezni, amivel be tudjuk állítani, az pedig a következő:

int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL redraw );

Gondolom, sok magyarázatra nincs szükség, az egyedüli dolog, amire kell figyelni, hogy ne töröljük a hRgn változót, mert a rendszer nem őriz másolatot a régiókról. És valójában ezzel elértünk a célunkhoz, mert ezt a függvényt kell használnunk, hogy ilyen meg olyan alakú ablakokat létre tudjunk hozni.

Amit tenni kell, nem más, mint létrehozni egy új projektet, és belerakni a következő kódot:

procedure TForm1.FormCreate(Sender: TObject);
var regCircleSmall, regCircleBig:HRGN;
    regFinal:HRGN;
begin
  regCircleBig := CreateEllipticRgn(10,10,200,200);
  regCircleSmall := CreateEllipticRgn(100,100,200,200);
  regFinal := CreateRectRgn(0,0,400,300);
  if(
CombineRgn(regFinal, regCircleBig, regCircleSmall, RGN_DIFF) = ERROR)
  then
  begin
    ShowMessage('Error.');
  end;
  SetWindowRgn(Handle,regFinal,false);

end;

És ez a pár sor létrehoz egy érdekes félhold alakú user interface-t, ahova be lehet rakni a saját kezelőszerveket, képeket, mindenféléket. Csak egy a gond. Ezáltal eltűnik az ablaknak a címsora, és nem tudjuk többet mozgatni. Ennek a kiküszöbölésére írtam a következő kódot:

procedure TForm1.Image1MouseDown(Sender: TObject;
                    Button: TMouseButton;
                    Shift: TShiftState; X, Y: Integer);
begin
 if button in [mbLeft] then
 begin
  mdown:=true;
  mdx:=x;
  mdy:=y;
 end;
end;
 
procedure TForm1.Image1MouseMove(Sender: TObject; 
                             Shift: TShiftState; X,  Y: Integer);
begin
 if not mdown then exit;
 form1.Left:=form1.left-mdx+x;
 form1.top:=form1.top-mdy+y;
end;
 
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
 if button in [mbLeft] then mdown:=false;

end;

amely lehetővé teszi, az ablak mozgatását, persze ne feledjék el definiálni az mdown, mdx, illetve mdy osztályváltozókat, mert ezek nélkül nem lehet a kódot működésre bírni.

Viszlát a következő számban.
Deák Ferenc