VB 6 praktika: a jolly joker „object” objektum lehetőségei

A VB objektumai, objektumgyűjteményei hatalmas lehetőségeket rejtenek, melyek azonban sokszor rejtve maradnak a tapasztalatlanabb felhasználók előtt. A cikk a legáltalánosabb objektumot, az objectet mutatja be. A téma tárgyalásán keresztül a cikk arra az alap VB-ben nem megoldott problémára is megoldást nyújt, mint a form és a rajta lévő vezérlők automatikus átméretezése, áthelyezése.

Kiknek, miért?

A Visual Basic (VB) az egyik legnépszerűbb programozási nyelv mind a hivatásos, mind pedig a hobbiprogramozók között. Úgy sejtem, meglehetősen nagy az utóbbiak száma, sőt a VB egyszerűségénél fogva a hobbiprogramozók nagy része előbb-utóbb itt köt ki. Részben ez lehet az oka annak, hogy a magyar és külföldi fórumokon néha elképesztően naiv kérdésekkel találkozom.  A kérdezőnek a kérdésből ítélve fogalma sincs a VB objektumairól, azok egymásból történő leszármaztatásáról, különböző objektumok közös tulajdonságairól – általában olyan programozási egyszeregyről, amivel egy C++ vagy Java programozó az első naptól kezdve tisztában kell, hogy legyen. Mert ilyen a VB, ezért ilyen népszerű! Csodálatos programokat készíthetünk mindenféle különösebb előképzettség nélkül, szinte azonnali sikerélménnyé változtatva a tanulás nehéz, feszültséggel teli folyamatát. A VB egyik nagy hátránya azonban éppen az egyszerűségében rejlik: sok "nagy, komoly" programozási nyelvet beszélő fejlesztő lenézően, jobb esetben sajnálkozva tekint a VB-s kollégáikra, mondván, igazán nagy dolgokra a VB nem alkalmas. Való igaz, szinte a VB az egyetlen nyelv, melynek a forráskódját megnézve, a programot betöltve és böngészve a teljesen kezdőnek is halvány fogalma támad, mi miért van, hiszen minden magától értetődik, a VB elrejti előlünk a problémásabb részeket, de az is igaz, hogy a VB az a nyelv, ahol el lehet kontárkodni. Sajnos a VB igen könnyen el tudja rejteni előlünk lehetőségeit, és ebben a súgója is ludas. Ami azonnal szembeötlik egy C++ súgó oldalán az adott objektum öröklődési ábrája: ezt igen gyakran hiába keressük a klasszikus VB súgójában, pedig itt is van, persze jóval egyszerűbb, mint például a Microsoft C++ MFC könyvtára. Igaz az is, hogy a Microsoft Office VBA súgójában igen jól nyomon követhetjük az objektumok származtatását. Ezzel azonban egy a baj: sok VB programozó olyan szinten nem tanulta meg az objektumokban gondolkodást, hogy az öröklődési ábrát tökéletesen figyelmen kívül hagyja, esetleg egy futó pillantással letudja. Cikkem az ilyen programozók számára készült.

A VB objektumai, objektumgyűjteményei hatalmas lehetőségeket rejtenek, más nyelvekhez hasonló öröklődési sémát követnek. Lehetővé teszik például, hogy olyan látszólag teljesen különböző dolgokat, mint a form és a rajta elhelyezett vezérlőelemek, együtt kezeljük (tudom, tudom egy C++ programozónak ez teljesen természetes, de ez most VB), gyűjteménybe helyezzük, a gyűjtemény elemein, pl. a form vezérlőelemein végiglépkedjünk és a típusától függően műveletet végezzünk rajta stb. Tapasztalatom szerint éppen ezek az alkalmazási módok a leggyakrabban használtak a gyakorlatban. Cikkemben mindhárom esetre nyújtok példát, hiszen egy példa többet ér ezer szónál.

 A cikk keretein belül az általános object-ről szóló tudnivalókat egy olyan általános, minden komolyabb programban elengedhetetlen, az alap VB-ben mégsem megoldott probléma megoldásán keresztül mutatom be, mint a form és a rajta lévő vezérlők automatikus átméretezése, áthelyezése. A probléma megoldásához éppen a VB általános objektuma ad kulcsot, mellyel mind a form, mind a rajta lévő vezérlők azonos módon kezelhetők - ideális példa hát a VB általános objektumának bemutatásához.

Miért a VB 6

Pályafutásom során több kitérőt tettem más programnyelvek felé, végül mégis visszatértem a VB-hez. Elismerem más nyelvek felsőbbrendűségét több kérdésben is, de megkapó az az egyszerűség, lényegretörőség, mellyel a VB a feladatokat kezeli. Iszonyatosan gyorsan lehet vele (nemcsak asztali) alkalmazásokat készíteni, nem hiszem, hogy más nyelvek a VB-t akár csak megközelítő fejlesztési időt tudnának produkálni. Egy jól elkészített eljáráskönyvtár segítségével szinte órákon belül lehet SOS projekteket készíteni, ahol a rendszerterv párhuzamosan halad a projektfejlesztéssel, ahol emiatt a kezdeti terv és a végállapot alig-alig van köszönőviszonyban egymással. A VB jól elboldogul ilyen körülmények között. Ha lesz rá lehetőségem, egy későbbi cikkben körvonalazom egy ilyen könyvtár főbb paramétereit, komponenseit. A Debug rendszere pedig utolérhetetlen, hiszen futás közben szerkeszthető a forráskód!

Szívemhez nőtt a VB az évek alatt. Hagy sirassam el ezért most egy kicsit a feltörekvő utódnak kikiáltott mindentudó VB.NET fényében! Mert valljuk meg, a VB.NET igencsak kevéssé hasonlít az elődjeihez, talán nem is VB többé. Tiszteletem és elismerésem a VB.NET felé, valóban szinte határtalanok a lehetőségei, jól megírt és elég jól átgondolt fejlesztői környezetnek tűnik az eddigi tapasztalataim alapján, de hosszú az a lista mely azt taglalja, miben más, és főleg miben nem támogatja az elődjeit. Az alábbi weboldalon található ilyen lista 116. bejegyzésből állt a cikk írásának idején:

http://www.mvps.org/vb/index2.html?rants/vfred.htm

De ami nekem személy szerint, mint volt hobbiprogramozónak leginkább hiányzik, az a klasszikus VB tovatűnt egyszerűsége, barátságos felülete. Attól tartok, hogy a VB.NET-tel a VB túllépett azon a határon, ahol egy lelkes amatőr egy-két könyvvel felfegyverkezve izgalmas programokat tudott megírni. De éppen emiatt hiszek abban is, hogy a klasszikus VB még sokáig fennmarad a programozók egy részének nagy örömére, és titokban abban is bízom, hogy valamelyik cég felkarolja a .NET-tel elejtett egyszerű fejlesztési filozófiát.

A probléma

No, de térjünk vissza a problémához. A programozók az első programjuk elkészítése után tisztában vannak azzal, hogy a vezérlőelemek pozícióját az őket befogadó nagyobb egység, általában a form, vagy egy másik vezérlőelem, pl. egy keret bal felső sarkából kell számítani. Méretüket általában a tervezési fázis során megadjuk, és bár kódból is változtathatjuk, többnyire a program futása során a méret és a pozíció fix marad. Sajnos, így a vezérlőelemek a form átméretezése során is megtartják mind a pozíciójukat, mind pedig a méretüket, ami igen nagy hátrány.

A Java igen jó megoldást nyújt a problémára oly módon, hogy a formot szekciókra osztja (közép, felső, alsó, jobb és bal), ahová a vezérlőelemek elhelyezhetők. A form átméretezésekor a vezérlőelemek mérete és helyzete együtt változik a form szekcióival, így igen tetszetős kinézetet biztosít a formnak. A Microsoft Access ezzel szemben megtartja a vezérlőelemek méretét és pozícióját, de egy függőleges és egy vízszintes gördítősávval bejárható a teljes form. Mindez azon az áron, hogy a teljes form nem látható egyszerre. Az új VB.NET egy másfajta, de a Javához hasonlóan elegáns megoldást ad a problémára: a vezérlőelemeket a form, vagy az adott vezérlőelemet befogadó nagyobb egység egy vagy több oldalához köti, horgonyozza (Anchor to).  Ezáltal átméretezéskor a vezérlőelem követheti az őt befogadó elem jobb oldalát (Anchor to Right), az alját (Anchor to Bottom), vagy a szélessége nő (Anchor to Left And to Right). Nos, ez utóbbi megoldást a VB korábbi verzióival is könnyen megtehetjük, és általánosan alkalmazhatjuk a VB-s formjainkra. Erre mutat megoldást a cikk keretében bemutatásra kerülő példaprogram.

A példaprogram bemutatása

A cikkhez egy a fentebb leírt általános átméretezést végző demo programot mellékeltem a forráskóddal együtt. A példaprogram egy példaformot és az általános átméretezési műveletet végző modult tartalmaz. A formra egy általános adatbázis egy adatlapjának megfelelő vezérlőelemeket tettem fel, parancsgombot, gridet, keretet, szövegablakot, címkét. A programot futtatva a form átméretezésekor látható az eredmény, mint azt az alábbi két ábra mutatja: a keret szélessége követte a form szélességét, a Frissítés gomb pozíciója változott és a keret jobb oldalán maradt, a gridnek pedig mind a szélessége, mind a magassága követte a form méretének változását.

A vezérlőelemek teljes ablakméret esetén

A vezérlőelemek normál ablakméret esetén

A megoldás körvonalazása

Mielőtt a kódokba ugranánk, röviden nézzük át a megoldást, hogy aztán a részletekkel tudjunk törődni!

A célunk az, hogy a form átméretezésekor az általunk kiválasztott vezérlőelemek pozíciója és/vagy mérete igazodjon a form új méretéhez. Például ha a form szélességét 100 egységgel megnöveljük, akkor a jobbra igazítandó vezérlőelemeket is szeretnénk ugyanannyival jobbra mozgatni. Ha a form magasságát 200 egységgel csökkentjük, akkor a form aljához és tetejéhez igazított vezérlőelem, pl. grid, magasságát szeretnénk 200 egységgel csökkenteni.

A megoldást nehezítik azok a vezérlőelemek, melyek maguk is más vezérlőelemeket tartalmaznak, akár több szinten egymásba ágyazva. Az egyes vezérlőelemek pozíciója, mint azt említettük korábban az őket befogadó elem bal felső sarkából számítandó, így az adott vezérlőelemnek meg kell határozni az őt befogadó másik vezérlőelemét, és annak méretbeli változását kell követni.

A megoldás sémája ezek után a következő:

A form betöltésekor elmentjük a form méretét. Átméretezéskor az új és a régi méret különbsége adja azt az értéket, mellyel a vezérlőelemek pozícióját és/vagy méretét változtatni akarjuk. Átméretezés után egyenként végiglépkedünk a form vezérlőelemein, és beállítjuk az új méretüket és pozíciójukat. Azt az információt, hogy az adott vezérlő mely oldalra igazított, a jelenlegi példában az egyes vezérlőelemek Tag tulajdonságában tároljuk. Amennyiben a vezérlő önmaga is tartalmaz más vezérlőket, akkor az előbbi műveletsort a tartalmazott vezérlőkre is végigfuttatjuk és így tovább. A műveletek végén ismét elmentjük a form jelenlegi méretét, felkészülve a következő átméretezésre. Ennyi! Egyszerű, igaz? Egy kissé több munkával egy tömbbe, vagy gyűjteménybe is menthetjük az igazítási információt, ekkor a Tag tulajdonságot egyéb műveletekre fenntarthatjuk.

A kódolás

Nos, akkor nézzük hát a kódot, ahol egyben a szükséges tudnivalókról is szó esik!

A kód a következő kódrészletekben bemutatott technikákra épül:

1.

Minden VB-beli objektum közös őse az általános object objektum. A címben így írtam: jolly joker. A legegyszerűbb, ha így is tekintünk rá. Ha két valamely specifikus objektum közös tulajdonságát egy művelettel kívánjuk kezelni, akkor ezt az object objektummal biztosan megtehetjük. Ezért egy olyan függvényfejléc, mely bemeneti paraméterként egy object elemet tartalmaz, mind a formot, mind a vezérlőelemeket elfogadja bemeneti paraméternek.

Private Sub DoWriteObjectName(ByRef InputObject As Object)

  'Az objektum típusának kiíratása

  Debug.Print TypeName(InputObject)

  'Az objektum nevének kiíratása

  Debug.Print InputObject.Name

End Sub

Ha ezt a függvényt egy létező parancsgomb vezérlőelem paraméterrel hívjuk meg:

Private Sub Command1_Click()

  DoWriteObjectName Command1

End Sub

akkor a következő eredményt kapjuk a debug ablakban:

CommandButton

Command1

A form (a formból hívva a Me) paraméterrel meghívva ugyanazt a függvényt:

Private Sub Command1_Click()

  DoWriteObjectName Me

End Sub

az eredmény a következő

Form1

Form1

A következő, a példaprogramban is szereplő DoResizeChildControls függvény a ParentObject paraméterével mind form, mind pedig az adott form bármely vezérlőelemét átveheti:

Private Sub DoResizeChildControls(_

  ByRef ContainerForm As Form, _

  ByRef ParentObject As Object, _

  ByVal OldWidth As Long, _

  ByVal OldHeight As Long)

Ebben a függvényben például a ParentObject.Width tulajdonság a paramétertől függően bármely vezérlőelem, vagy magának a formnak a Width tulajdonságát hordozhatja. Így tudjuk elérni és változtatni egyetlen függvénnyel egy megadott form minden komponensének pozícióját és méreteit. 

2.

Az egyes vezérlőelemeket a form egy Controls gyűjteményben tárolja. Egy ForEach ciklussal végiglépkedhetünk az adott form összes vezérlőelemén, amennyiben egy általános Control objektumot definiálunk:

Public Sub DoWriteControlsName(ByRef ContainerForm As Form)

  Dim ctr As Control

  For Each ctr In ContainerForm

    'A vezérl típusának kiíratása

    Debug.Print TypeName(ctr)

    'A vezérl nevének kiíratása

    Debug.Print ctr.Name

  Next

End Sub

A példaprogrambeli form esetén az eredmény a debug ablakban a következő eredményt hozza:

DataGrid

xgrdDataGrid

TextBox

xtxtRogzito

TextBox

xtxtRowver

Frame

xfmeFilter

CommandButton

xcmdFrissit

TextBox

xtxtSorszam

Label

xlblSorszam

Label

xlblRogzito

Label

xlblRowver

Label

FormBorder

Ezt a technikát többször is használjuk a példaprogramban, pl. annak megállapítására, hogy van-e az adott vezérlőelemnek tartalmazott vezérlőeleme (IsControlHasChildControl funkció).

Az eddigiekből már az is világos, hogy a ContainerForm As Form bemeneti paraméter helyett ContainerForm As Object paramétert is használhattunk volna, a kód ugyanúgy működött volna, hiszen minden form egyben object is! Sőt, ha az átméretező modult egy külön dinamikus könyvtárba (dll) tesszük, akkor csak ez lehet a megoldás, mivel oda nem adhatjuk át a formunkat form paraméterként.

További felhasznált technikák

Rekurzív függvényhívással oldható meg a tartalmazott vezérlőelemek átméretezésének és áthelyezésének kérdése. Röviden arról van szó, hogy egy függvény meghívja önmagát. A rekurzív hívás technikájával most nem foglalkozom, általában minden VB könyvben megtalálható a könyv első harmadában.

Bármely vezérlőelem befogadója, legyen az a form, vagy egyéb más vezérlőelem meghatározható a Container tulajdonság lekérdezésével. A Parent tulajdonság ellenben a befogadó form nevét adja vissza.

A funkciónak egyértelmű okokból csak akkor szabad lefutnia, ha normál, vagy maximalizált a form nézet. Ezt könnyen eldönthetjük a form WindowState tulajdonságával. Ezen kívül a Form_Resize esemény a form betöltésekor automatikusan meghívódik, de az lenne célszerű, hogy csak akkor fusson le az átméretezési funkció, amikor a form már inicializálva van, és a felhasználó maga változtatja a méretét. Annak eldöntésére, hogy egy objektum üres-e, vagyis Nothing az értéke, (ez a VB.NET-nél az <object> Is Nothing hívással egyenértékű), egy kis funkciót kellett megírni (IsObjectInitialized).

Ezek után úgy gondolom, hogy a teljes kód megértése nem okozhat problémát.

Az átméretezési funkció meghívása

Az átméretezési funkciót az adott form Form_Resize eseményéhez kötjük, ezáltal érjük el, hogy a funkció automatikusan lefusson a form átméretezésekor. A funkció meghívásakor azonban figyelemmel kell lenni néhány dologra.

Egy bizonyos méretnél kisebbre nem engedhetjük összehúzni a formot, hiszen a vezérlőelemek egymásra csúsznának. Praktikus megoldásként egy a tervezési időben a jobb alsó sarokban lévő kis piros négyzettel állíthatjuk be a form minimális méretét, majd a form betöltésekor ehhez igazítjuk a form méreteit.

 

Ugyancsak a form betöltésekor rendeljük a vezérlőelemek Tag tulajdonságához az irányultság (Anchor) értékeket. Ezek az értékek a modulban Enum-ként lettek definiálva.

Ezek után nézzük a form minimális kódját a függvény meghívásához:

'Példaform a resizing modul meghívásához

'Filenév: frmResizeDemo.frm

'Készítette: Balogh Tamás

'2002. októberében

'szabadon felhasználható

Option Explicit

'Itt tároljuk a form átméretezés eltti méretét

Public FormOldWidth As Long

Public FormOldHeight As Long

Private Sub Form_Load()

  'A form méretének beállítása

  'Tervezési idben a jobb alsó sarokban lév kis piros négyzettel

  'állítható be a minimális méret

  Me.Width = Me.FormBorder.Left + (Me.Width - Me.ScaleWidth)

  Me.Height = Me.FormBorder.Top + (Me.Height - Me.ScaleHeight)

  Me.Top = 0

  Me.Left = 0

  Me.FormOldHeight = Me.Height

  Me.FormOldWidth = Me.Width

  'Anchor beállítása az egyes vezérlelemet Tag tulajdonságába

  Me.xfmeFilter.Tag = enmAnchorTo.eAnchorTo_TopLeftRight

  Me.xcmdFrissit.Tag = enmAnchorTo.eAnchorTo_TopRight

  Me.xgrdDataGrid.Tag = enmAnchorTo.eAnchorTo_All

  Me.xlblRogzito.Tag = enmAnchorTo.eAnchorTo_BottomLeft

  Me.xtxtRogzito.Tag = enmAnchorTo.eAnchorTo_BottomLeft

  Me.xlblRowver.Tag = enmAnchorTo.eAnchorTo_BottomRight

  Me.xtxtRowver.Tag = enmAnchorTo.eAnchorTo_BottomRight

End Sub

'Ez az esemény indítja el az automatikus átméretezést

Private Sub Form_Resize()

  'Ha normál a form nézet,

  'egy bizonyos méretnél kisebbre nem engedi az ablakot

  'Tervezési idben a jobb alsó sarokban lévő kis piros négyzettel

  'állítható be a minimális méret

  If Not Me.WindowState = vbMinimized Then

    If Me.Height < FormBorder.Top + (Me.Height - Me.ScaleHeight) Then

      Me.Height = FormBorder.Top + (Me.Height - Me.ScaleHeight)

    End If

    If Me.Width < Me.FormBorder.Left + (Me.Width - Me.ScaleWidth) Then

      Me.Width = Me.FormBorder.Left + (Me.Width - Me.ScaleWidth)

    End If

  End If

  'A funkció meghívása

  DoResizeControls Me

End Sub

Lám ennyi az egész. Pár sor a formon, egy modul és a programunk vezérlőelemei szabadon méretezhetők! Remélem kis cikkem sokaknak hasznos információkat nyújtott. Hozzáfűzéseket, esetleges helyesbítéseket, egyáltalán bármilyen javító szándékú hozzászólást szívesen fogadok.

Balogh Tamás