KURZUS: Számítási módszerek

MODUL: Excel programozás

4. lecke: Összetett adattípusok, objektumok

Cél: Ebben a leckében az összetett adattípusokról, az objektumokról és az objektum típusokról lesz szó. Ezeknek a használata bizonyos feladatoknál elengedhetetlen, nélkülük nem lehetne azokat megoldani, bizonyos feladatoknál pedig egyszerűsítik a megoldást.

A lecke végén ismét lesznek programozási feladatok, amelyek megoldásához az eddig megszerzett tudást (1-3. leckék) és az új ismeretanyagot (4. lecke) egyaránt fel kell majd használni.

Követelmények: Ön akkor sajátította el megfelelően a tananyagot, ha képes

  • tömböket deklarálni és megfelelően használni,
  • rekordtípust deklarálni és megfelelően felhasználni,
  • formok vizuális tervezésére és a megfelelő eseménykezelő szubrutinok elkészítésére.

Időszükséglet: A tananyag elsajátításához (az önálló programozási feladatok megoldása nélkül) hozzávetőlegesen 8 órára lesz szüksége.

Kulcsfogalmak

  • Tömb
    • Dinamikus
    • Statikus
  • Dimenzió
  • Rekord
  • Objektum
  • Osztály
  • Metódus
  • Tulajdonság
  • Eseménykezelő
  • Vizuális formtervezés
Összetett adattípusok

Az eddig megismert adattípusokkal ellentétben, ahol egy változóban csak egy adatot tárolhatunk egy időben, az összetett adattípusok segítségével több adat együttes kezelésére is lehetőségünk nyílik.

Tömbök

A tömb egy általánosan és gyakran használt eszköz a szoftverfejlesztők, programozók körében, mivel a tömbökkel kényelmesen kezelhetünk több, azonos típusú adatot. Az adatok elérésére, vagyis a tömbök elemeinek kiválasztására sorszámokat, más néven indexeket használunk.

Ha az elemeket egy sorszámmal azonosítjuk, akkor egydimenziós tömbről, ha két sorszámmal, akkor kétdimenziós tömbről, ha n db sorszámmal azonosítjuk, akkor n dimenziós tömbről beszélünk. Egy tömb egy elemének kiválasztásához tehát pontosan annyi index szükséges, ahány dimenziós az adott tömb.

Ahogy az egyszerű adattípusok esetén is megkülönböztettük az adatot (pl. 3) az őt tároló változótól (pl. i) és annak típusától (pl. Integer), itt is kitérünk ezek különbözőségére. A tömb elnevezés ugyanis a rövidsége miatt mind a három esetben használatos, így a tömb lehet:

  • Tömbadat: amely egydimenziós tömb esetén egy adatsornak, vagy matematikai fogalommal egy vektornak, kétdimenziós tömb esetén egy adattáblázatnak, vagy mátrixnak felel meg.
  • Tömb adattípus: amely definiálja a tömb dimenzióit és a tömb elemeinek típusát.
  • Tömbváltozó: amelyben az adatok tárolódnak. Egy elem kiválasztásával, azaz a tömbváltozó indexelésével egy, a tömb elemtípusával megegyező típusú változót hivatkozunk.

Egy tömböt a használata előtt deklarálni kell, azaz definiálnunk kell a tömb elemeinek típusát és a tömb dimenzióit.

A deklarálás (egyszerűsített) szintaktikája:

Dim varname[([subscripts])] [As type] [,...]

Az indexek (subscripts) megadásának szintaktikája:

[lower To] upper [,[lower To] upper] ,...

Pl.

Dim a(10) As Integer              'Egydimenziós tömb
Dim b(1 To 5, -3 To 2) As Double  'Kétdimenziós tömb

Vegyük észre, hogy az egyes dimenziók alsó indexhatára elhagyható. Az explicit módon nem definiált alsó indexhatárok értékeit az Option Base (modulszintű) utasítás határozza meg.

Az utasítás szintaktikája:

Option Base {0|1}

Értelemszerűen a megadott érték (0 vagy 1) lesz a tömbök alsó indexhatára. Alapértelmezésben az Option Base 0 van érvényben, azaz a tömbelemek alsó indexhatára 0.

Megjegyzés: Noha a VB-ben egy tömb dimenzióinak maximális száma 60 lehet, azaz használhatunk három, négy, ..., 60 dimenziós tömböket, a gyakorlatban az egy- és kétdimenziós tömbök a leggyakoribbak.

Az előzőekben ismertetett deklarálás ún. statikus tömböket deklarál, amelyek mérete fordítási időben meghatározódik és futási időben már nem változtatható. A VB azonban megengedi az ún. dinamikus tömbök használatát is, amelyek mérete a program futása során megváltoztatható. Ezeket a tömböket is a Dim utasítással deklaráljuk, de az első deklarálásnál csak a tömbelemek típusát adjuk meg, a dimenziókat nem. A tömb átméretezése ezután a ReDim utasítással történik, amelyben előírhatjuk, hogy a már definiált értékű tömbelemek értékeit megőrizzük-e (Preserve) vagy sem.

Ha egy tömb méretét kisebbre állítjuk, akkor az "eltűnt" tömbelemek értékeit elveszítjük, míg egy tömb méretének növelésekor "keletkező" új elemek (a változókhoz hasonlóan) numerikus elemtípusú tömb esetén 0, a logikai elemek esetén False, míg String és Variant típus esetén az üres sztring kezdőértéket kapják. Ilyen értékeket kapnak az új tömb elemei akkor is, ha a Preserve kulcsszót elhagyjuk.

Szintaktika:

ReDim [Preserve] varname(subscripts) [As type] [,...]

Készítsen egy Teszt szubrutint, amelyben próbálja ki a következő programrészletet! A ReDim utasítás után írassa ki (pl. MsgBox, Debug.Print) a tömb elemeit (mind a 10-et)!

Dim a() As Integer    'Dinamikus tömb egészekből
ReDim a(5)            'A tömb legyen 5 elemű
For i = 1 To 5        'A tömb használata
  a(i) = i
Next
ReDim Preserve a(10)  'A tömb legyen 10 elemű
                      'Az első 5 elem értéke megmarad, a többi 0 lesz

Megjegyzés

  • A példában feltettük, hogy a modulban az Option Base 1 utasítás van érvényben.
  • Egy dinamikus tömb többször is átméretezhető, de az elemek típusa nem változtatható meg.
  • A dinamikus tömbök a ReDim utasítással közvetlenül (a Dim utasítás nélkül) is deklarálhatók, ekkor az elemek típusát is itt adjuk meg (pl. ReDim a(5) As Integer).

Az, hogy statikus vagy dinamikus tömböket használunk-e egy feladat megoldása során, az attól függ, hogy ismerjük-e előre az adatok maximális számát vagy sem. Ha ismerjük, akkor statikus (az adott maximumra deklarált) tömböt használunk, és az adatok aktuális számát egy külön változóban tároljuk. Ebben az esetben csak akkor lesz helypazarló a megoldásunk, ha az adatok maximális száma (azaz a tömb által lefoglalt memória mérete) jóval nagyobb, mint az adatok aktuális száma.

Ha az adatok maximális számát nem tudjuk előre meghatározni, akkor a megoldást dinamikus tömbökkel végezzük. Ebben az esetben a tömböt mindig akkorára deklaráljuk, hogy az adataink éppen elférjenek benne, így a megoldás sose foglal le több memóriát, mint amennyi szükséges.

A VB az UBound (felső határ) és LBound (alsó határ) függvényekkel biztosítja a tömbök deklarált indexhatárainak lekérdezhetőségét.

Szintaktika:

UBound(arrayname[,dimension])
LBound(arrayname[,dimension])
arraynameA tömb azonosítója.
dimensionA lekérdezendő dimenzió sorszáma (alapértelmezett értéke 1).

Készítsen egy Teszt szubrutint, amelyben próbálja ki a következő programrészletet! A kifejezések értékeit írassa ki (pl. MsgBox, Debug.Print)!

Dim a(10) As Integer
Dim b(1 To 5, -3 To 2) As Double
 
UBound(a) 10
UBound(b) 5
UBound(b,2) 2
LBound(a) 0
LBound(b) 1
LBound(b,2) -3

Megjegyzés: A példában feltettük, hogy a modulban az Option Base 0 utasítás érvényes.

Rekordok

A tömbök (legyenek azok egy-, vagy többdimenziósak) azonos típusú adatok egy összességének használatát biztosították. Ha azonban különböző típusú adatok egy összességét szeretnénk együtt kezelni, akkor egy újabb típust, a rekord adattípust kell használnunk. A rekord adattípussal ugyanis több, tetszőlegeses számú és típusú adatot foglalhatunk egy logikai egységbe.

Ezt az adattípust külön kell definiálnunk, ugyanis meg kell adnunk a rekord felépítését, azaz a rekordot alkotó mezők azonosítóját és típusát.

A rekordtípus megadásának (egyszerűsített) szintaktikája:

Type name
  elementname[([subscripts])] As type
  elementname[([subscripts])] As type
  ...
End Type
nameA rekordtípus azonosítója.
elementnameAz adott mező azonosítója.
subscriptsHa a mező tömb, akkor annak indexhatárai.
typeAz adott mező típusa.

Pl.

Type Adat
  Nev As String * 20      'Fix (20) hosszú sztring
  Jegyek(1 To 2) As Byte
End Type

A példa egy olyan rekordtípust (Adat) definiál, amelynek két mezője van. Az első mező (Nev) egy fix hosszú sztring, a második mező (Jegyek) egy 2 elemű, egydimenziós, Byte elemtípusú tömb.

Megjegyzés

  • Az egyes mezők beljebb tagolása nem kötelező, csak az áttekinthetőséget segíti.
  • A példában fix hosszú sztringtípust (String * 20) használtunk, de a változó hosszú String típust is használhattuk volna.
  • A Type utasítás modulszintű utasítás.

Egy definiált rekordtípus ugyanúgy használható változók deklarálására, mint a VB többi adattípusa.

Pl.

Dim h As Adat, a(1 To 3) As Adat

A példa két változót deklarál. A h változóban egy rekord adatai tárolhatók, míg az a változó három rekord tárolására alkalmas.

A rekord mezőire való hivatkozás:

rekordváltozó.mezőnév

A rekordok egyes mezőire úgy hivatkozhatunk, hogy a rekordváltozó és a mezőnév közé egy pontot teszünk. Ezzel a hivatkozással egy, a mező típusával megegyező típusú változót hivatkozunk, amit ennek megfelelően használhatunk. A rekordokra nem definiáltak műveletek, de az azonos típusú rekordváltozók között megengedett az értékadás.

Pl.

h.Nev = "Halász Helga"
a(1).Jegyek(1) = 5

A mezőkre való hivatkozás a With utasítás segítségével rövidíthető. Ekkor a rekordváltozót elegendő egyszer megadni (a With kulcsszó után), a mezőnevek előtt elhagyhatjuk, de a pontot ki kell tennünk.

Pl.

With a(2)
  .Nev = "Vadász Viktória": .Jegyek(1) = 3: .Jegyek(2) = 4
End With

A példa a korábban deklarált a tömb második elemében tárolt rekord egyes mezőinek ad értéket.

Készítsen egy Teszt szubrutint az előző példa kipróbálására!

Megjegyzés

  • A With utasítás objektumokra is használható.
  • A With blokkban szereplő utasítások beljebb tagolása nem kötelező, csak az áttekinthetőséget segíti.
  • A With utasítások egymásba ágyazhatók.
  • A rekord szó a szakirodalomban általánosan használt (ezért mi is ezt használtuk), de a VB súgója a rekord adattípusra a felhasználó által definiált adattípus (user-defined data type) elnevezést használja.
  • Az összetett adattípusú (tömb, rekord) deklarált változók is kapnak kezdőértéket, amit a tömbelemek, illetve az egyes mezők típusa határoz meg.
Mintafeladat

A tömbök és rekordok használatát az alábbi feladat segítségével szemléltetjük.

Oldjuk meg a következő feladatot!

Feladat: Egy hallgatói nyilvántartásban ismerjük a hallgatók nevét és adott számú érdemjegyét. Minden hallgatónak ugyanannyi érdemjegye van. Az adatokat egy rekordokból álló egydimenziós tömbben tároljuk. Készítsünk névsor szerint rendezett listát, amelyen a hallgatók érdemjegyei is szerepelnek!

Az alábbi megoldásban először egy konstanst deklarálunk, amely definiálja a hallgatók érdemjegyeinek számát. Az olyan értékeket, amelyek a programban több helyen is szerepelnek, célszerű névvel ellátni, azaz külön deklarálni, mert akkor az érték esetleges módosításához elegendő egy helyen (a deklarációban) módosítanunk.

A konstansok deklarálásának szintaktikája:

[Public|Private] Const constname [As type] = expression
PublicA konstans hivatkozható lesz minden modulból.
PrivateA konstans csak abban a modulban hivatkozható, ahol deklarálták (ez az alapértelmezés).
constnameA konstans azonosítója.
typeA konstans típusa (opcionális).
expressionA konstans értékét megadó kifejezés.

Pl.

Const db = 2

Megjegyzés

  • Az utasítás modul szinten és szubrutinon belül is használható. Az utóbbi esetben a konstans csak az adott szubrutinon belül hivatkozható és ezt nem lehet a Public, illetve Private kulcsszavakkal módosítani.
  • A konstans értékét megadó kifejezésben konstans értékek, deklarált (névvel azonosított) konstansok, valamint műveletek állhatnak.
  • A típust (type) általában elhagyjuk (a példában is így tettünk), ekkor a konstans típusa a kifejezés értékének legmegfelelőbb típus lesz (esetünkben Byte).

A konstans definiálása modul szinten történt, hiszen az egész modulban szeretnénk használni. A rekordtípus (amely leírja egy hallgató adatait) deklarációja viszont csak modul szinten (azaz a modul elején) történhet. A nevekhez fix hosszú sztring típust használtunk, amely nem engedi meg, hogy a nevek a megadott hossznál (a példában 20) hosszabbak legyenek.

Az adatok kiírását egy szubrutin (Kiiras) végzi, amely egy Adat rekordokból álló tömböt kap paraméterként (a), amit a paraméter után szereplő (üres) zárójelpár jelez. A kapott tömböt egydimenziós tömbként kezeljük és feltesszük, hogy a tömb minden eleme definiált, amit a Kiiras szubrutin hívója (RendezesTeszt) biztosít, ezért a tömbelemeken végiglépdelő ciklus szervezését a tömb lekérdezett (LBound, UBound) indexhatárai alapján végezhetjük. Az adatkiírások után akkor emelünk sort (egy paraméter nélküli Debug.Print hívással), ha az adott hallgató összes adatát (név, érdemjegyek) kiírtuk, így minden hallgató külön sorba kerül. Az eredménylista az 1. ábrán látható. Vegyük észre a nevek jobbról szóközökkel való feltöltöttségét (ez a fix hosszú sztringek értékadásakor automatikusan megtörténik).

A mintafeladat eredménylistája
1. ábra

Az adatok rendezését a Rendezes szubrutin végzi, amely (a Kiiras szubrutinhoz hasonlóan) szintén megkapja az adatok tömbjét. Az adatkiírás nem változtatott a paraméterként kapott adatokon, de a rendező szubrutinnak éppen ez a dolga, nevezetesen hogy névsor szerinti sorrendbe tegye a tömbben lévő adatokat. Mivel a tömb átadására deklarált (a) paraméter cím szerinti átadású (ByRef az alapértelmezés, és tömböket nem is adhatunk át érték szerint), ezért a tömb elemein végzett változtatásokat a hívó is "érzékelni fogja".

A rendezést egy, a minimum kiválasztás elvén alapuló rendező algoritmus segítségével valósítottuk meg. A külső ciklus minden egyes lépésében megkeressük (a belső ciklussal) a még rendezetlen tömbrész legkisebb elemét (annak indexét tárolja a k változó), és ha ez az elem nincs a helyén (az i-edik helyen), akkor odatesszük. A rekordok között értelmezett az értékadás, ezért két elem (i-edik és k-adik) felcserélése elvégezhető három értékadással (nevezetesen megjegyezzük az egyiket, oda áttesszük a másikat, majd a másikba betesszük a megjegyzett egyiket). Vegyük észre, hogy a cseréhez használt változó (cs) a tömbelemekkel megegyező (Adat) típusú.

A minimumkeresés úgy történik, hogy megjegyezzük az adott tömbrész első elemének indexét, és megvizsgáljuk a többi elemet. Ha kisebbet találunk (a rekordok Nev mezője szerinti összehasonlítás szerint, hiszen névsor szerinti sorrendet szeretnénk előállítani), akkor annak indexét jegyezzük meg (hiszen most már ő az aktuálisan legkisebb). A rendezetlen tömbrész kezdetben (i=1 esetén) az egész tömb, utána (i=2 esetén) csak a második elemtől az utolsó elemig (hiszen az első, vagyis a legkisebb elem már a helyére, a tömb elejére került), és így tovább, legvégül (i=UBound(a)-1 esetén) már csak a tömb két utolsó elemét tartalmazza.

A Rendezes szubrutint a RendezesTeszt szubrutin hívja meg azután, hogy kezdőértékekkel látta el az adattárolásra használt tömbváltozót. A tömb azonosítására mindkét helyen ugyanazt az azonosítót (a) használtuk, de használhattunk volna különböző azonosítókat is. A harmadik személy (akinek adatait a tömb 2-es indexű elemében tároljuk, hiszen a modulban alapértelmezésben az Option Base 0 érvényes) adatainak megadásakor a rekordmezőkre a With utasítás segítségével hivatkoztunk, hogy ezt is bemutassuk.

Megjegyzés

  • A megoldás csak a VB nyelv lehetőségeit használta, így szükség volt egy rendező algoritmus programozására. Ha azonban kihasználtuk volna az Excel VBA lehetőségeit, akkor a hallgatók adatait egy Excel munkalapon tárolva egyetlen metódushívással "elintézhető" lett volna az adatok rendezése.
  • Igen sokféle rendező algoritmus ismert, amelyekből ez egyik legegyszerűbbet választottuk ki. A témakör iránt érdeklődőknek a szakirodalmat - pl. (Pusztai 2008) vagy (Cormen et al. 2003) - ajánljuk.
'Egy hallgató érdemjegyeinek száma
Const db = 2
 
'Egy hallgató adatait leíró rekordtípus
Type Adat
  Nev As String * 20 'Fix (20) hosszú sztring
  Jegyek(1 To db) As Byte
End Type
 
'Adat típusú elemekből álló tömb kiírása
Sub Kiiras(a() As Adat)
  Dim i As Integer, j As Integer
  For i = LBound(a) To UBound(a)
    Debug.Print a(i).Nev;
    For j = 1 To db
      Debug.Print a(i).Jegyek(j);
    Next
    Debug.Print
  Next
End Sub
 
'Adat típusú elemekből álló tömb rendezése név szerint
Sub Rendezes(a() As Adat)
  Dim cs As Adat, i As Integer, j As Integer, k As Integer
  For i = LBound(a) To UBound(a) - 1
    k = i
    For j = i + 1 To UBound(a)
      If a(j).Nev < a(k).Nev Then
        k = j
      End If
    Next
    If k > i Then
      cs = a(i): a(i) = a(k): a(k) = cs
    End If
  Next
End Sub
 
'A rendezés tesztjéhez ez indítandó
Sub RendezesTeszt()
  Dim a(2) As Adat
  a(0).Nev = "Madarász Mónika": a(0).Jegyek(1) = 1: a(0).Jegyek(2) = 2
  a(1).Nev = "Vadász Viktória": a(1).Jegyek(1) = 2: a(1).Jegyek(2) = 3
  With a(2)
    .Nev = "Halász Helga": .Jegyek(1) = 3: .Jegyek(2) = 4
  End With
  Call Rendezes(a)
  Call Kiiras(a)
End Sub
Objektumok, objektumtípusok

Az objektum szót már használtuk (pl. a Debug objektum kapcsán), de még nem beszéltünk arról, hogy pontosan mit jelent, mit takar az objektum elnevezés az informatikában.

Objektumon adatok és a rajtuk értelmezett tevékenységek egy logikailag összefüggő egységét értjük. Az objektum (hasonlóan, mint a tömb) egy gyakran és általánosan használt szó, ami jelenthet adatot (egy konkrét objektumpéldányt), változót (ami tárolja és elérhetővé teszi az objektum adatait és tevékenységeit), valamint típust is (az objektum típusát, amely definiálja az objektum adatainak típusát és az objektum tevékenységeit).

Az objektumok tevékenységeit definiáló szubrutinokat metódusoknak (methods), az objektumok típusát osztályoknak (classes) nevezzük. Az objektumok adataihoz általában tulajdonságok (properties) segítségével férhetünk hozzá, amelyek védik az adatok helyességét.

Az objektumok azon metódusait, amelyek valamilyen esemény bekövetkezésekor (pl. egy nyomógomb esetén a nyomógomb feletti kattintás) aktivizálódnak, eseménykezelőknek nevezzük. Az események általában valamilyen felhasználói interakció eredményeként következnek be, de más futó folyamatok is kiválthatják az egyes események bekövetkezését (pl. meghívunk egy eseménykezelőt).

Megjegyzés: A VBA megengedi az osztályok definiálását, de mi csak a "készen kapott" osztályokat fogjuk használni.

Vizuális formtervezés

A Windows alkalmazásokban a felhasználóval történő kommunikáció egyik alapvető eszköze az ablak (más néven form, űrlap). Természetesen az alkalmazás helyes működése a legfontosabb, de a küllem, az áttekinthetőség, a könnyű kezelés, vagyis a felhasználóbarát viselkedés is lényeges szempont. Éppen ezért a Windows platformú szoftverfejlesztő rendszerek (így az MS Excel VBA is) támogatják a formok vizuális, interaktív tervezését.

A tervezés (manapság általánosan elterjedt) jellemzője a WYSIWYG (What You See Is What You Get) tervezési mód, ami azt jelenti, hogy a tervezett ablak pontosan úgy fog megjelenni futáskor, mint ahogyan azt a tervezéskor látjuk (lásd 2. ábra).

Egy új form létrehozása a Visual Basic Editor Insert menüjének UserForm funkciójával (vagy a Project ablak helyi menüjének segítségével) hozható létre. A form (pl. a jobb szélén és alján lévő fehér keretező négyzetek segítségével) tetszőlegesen átméretezhető.

Egy form tervezéskor és futáskor
2. ábra

A formra tehető, használható vezérlőket (Controls) a Toolbox ablak jeleníti meg (lásd 3. ábra). Egy form tervezésekor (azaz ha a UserForm objektum ablaka aktív), a Toolbox ablak automatikusan megjelenik (és ha az ablakot esetleg bezárnánk, akkor a View menü Toolbox funkciójával, vagy az eszköztársor megfelelő ikonjával újra megnyithatjuk).

A Toolbox ablak
3. ábra

A Toolbox ablak a fontosabb Windows vezérlőket tartalmazza, mint pl. címke (Label), beviteli mező (TextBox), legördülő lista (ComboBox), lista (ListBox), jelölőnégyzet (CheckBox), választógomb (OptionButton), nyomógomb (CommandButton).

Egy vezérlő formra helyezése az egérrel történhet. A kívánt vezérlő a Toolbox ablakból a formra vihető a "fogd és vidd" egérművelettel, de a kívánt vezérlőn, majd a formon való kattintás is ugyanezt teszi, azaz elhelyezi a formon az adott vezérlő egy példányát (alapértelmezett méretben). Ha a formon kattintás helyett egy téglalap alakú területet jelölünk ki, akkor a vezérlő a megadott méretben kerül a formra.

A form, illetve a formon lévő vezérlők egérkattintással kijelölhetők, de a Windows-ban szokásos kijelölési technikákkal (lenyomott Ctrl vagy a Shift billentyű és egérkattintás, vagy jelölőkeret rajzolásával) csoportos elemkijelölés is végezhető. Csoportos kijelölést akkor használunk, ha a vezérlők egy csoportjára szeretnénk valamilyen műveletet (pl. mozgatás, törlés, adatmegadás) elvégezni.

A kijelölt vezérlők az egér "fogd és vidd" művelettel szabadon mozgathatók (áthelyezhetők) és méretezhetők (ehhez valamelyik keretező négyzetet kell mozgatnunk). Ha több vezérlő van kijelölve, akkor a Properties ablakban (lásd 1. lecke) csak a közös tulajdonságok jelennek meg.

A kijelölt vezérlő (vagy vezérlők) kezelése az Edit menü (vagy a helyi menü) segítségével is történhet. A törlés a Delete billentyűvel (illetve a Delete funkcióval) végezhető, de a vágóasztal többi művelete (pl. vágólapra másolás, beillesztés) is használható.

Futáskor alapesetben az egyes vezérlők a formra való felkerülésük sorrendjében aktivizálhatók. Az első vezérlő lesz fókuszban, és a felhasználó a Tab (illetve Shift+Tab) billentyűkkel tud ezen sorrend szerint "lépkedni" az egyes vezérlőkön. Ez azonban nem feltétlenül igazodik a vezérlők formon való elrendezéséhez. A form helyi menüjének Tab Order funkciójával módosítható a vezérlők futáskori sorrendje (lásd 4. ábra). Az aktuálisan kiválasztott vezérlő felfelé, illetve lefelé mozgatható a megfelelő nyomógombokkal (Move Up, Move Down), így a megfelelő sorrend kialakítható.

A Tab Order ablak
4. ábra

A formra tett vezérlők a vezérlő típusából és egy sorszámból álló azonosítót (Name) kapnak (pl. CommandButton1). A sorszám egytől kezdődően kerül kiosztásra. A kifejezőbb, olvashatóbb programkód érdekében a vezérlőket át is nevezhetjük, ezt célszerű közvetlenül a formra való elhelyezés után megtenni, még mielőtt eseménykezelőket definiálnánk hozzá (ugyanis a megváltozott neveket az eseménykezelők nevei nem követik).

Egy vezérlő egy eseménykezelőjének létrehozása (illetve már meglévő eseménykezelő esetén az arra való rápozícionálás) általánosan a kódszerkesztő ablak tetején lévő legördülő listákból való választással történhet. Azonban a vezérlők alapértelmezett eseménykezelője (ami pl. nyomógomb esetén a kattintásnak megfelelő Click, beviteli mező esetén a tartalom megváltozásának megfelelő Change) a formr tervezésekor az adott vezérlőn való dupla kattintással is megnyitható.

Az eseménykezelők azonosítója az adott objektum azonosítójából (Name), az alulvonás karakterből és az esemény nevéből áll, a paraméterezésük pedig rögzített (pl. CommandButton1_Click(), ListBox2_DblClick(ByVal Cancel As MSForms.ReturnBoolean)).

Megjegyzés

  • Az eseménykezelő szubrutinok Private elérhetőséget kapnak (azaz csak a form moduljában hivatkozhatók), de ez megváltoztatható (pl. Public eléréssel az eseménykezelőt egy másik modulból is meghívhatjuk).
  • Egy vezérlő a Properties ablak legördülő listájával is kijelölhető.
  • Az objektumoknak lehetnek olyan tulajdonságai is, amelyek csak futási időben érhetők el, ezek meg sem jelennek a Properties ablakban (pl. ListBox.ListIndex).
  • Néhány fontosabb tulajdonság: név (Name), felirat (Caption), elhelyezkedés (Top, Left), méret (Width, Height), választhatóság (Enabled), láthatóság (Visible).

Készítse el vizuális formtervezés témakörhöz kapcsolódó, a jegyzetben (Kallós-Pusztai 2016) található mintafeladatot! Először a formot, majd a megfelelő eseménykezelőket készítse el, végül futtassa a formot és tesztelje a működését!

Önálló programozási feladatok

Készítsen VB szubrutinokat az alábbi feladatokra! A megoldáshoz készítsen egy megoldó és egy tesztelő szubrutint!

A megoldó szubrutin egy teljes körűen paraméterezett szubrutin legyen, amely csak a feladat megoldását tartalmazza (a felhasználóval való kommunikációt ne), a működéséhez szükséges adatokat a paraméterein keresztül vegye át, az eredményeket pedig azokon keresztül (függvény esetén a függvény értékeként) adja vissza.

A tesztelő szubrutin egy paraméterek nélküli (azaz közvetlenül futtatható) szubrutin legyen, amely kérje be a szükséges adatokat (InputBox), hívja meg a megoldó szubrutint, majd írja ki (MsgBox, Debug.Print) az eredményül kapott adatokat!

Pl. Kérjünk be három valós számot és döntsük el, hogy lehetnek-e egy háromszög oldalai vagy sem!

Function Haromszog(a As Single, b As Single, c As Single) As Boolean
  Haromszog = a + b > c And b + c > a And a + c > b
End Function
 
Sub Haromszog_Teszt()
  Dim a As Single, b As Single, c As Single
  a = InputBox("Kérem az első számot!")
  b = InputBox("Kérem a második számot!")
  c = InputBox("Kérem a harmadik számot!")
  If Haromszog(a, b, c) Then
    MsgBox "A három szám lehet egy háromszög három oldala!"
  Else
    MsgBox "A három szám nem lehet egy háromszög három oldala!"
  End If
End Sub

Oldja meg a feladatokat formok segítségével is (lásd 5. ábra)! Célszerű feladatonként egy-egy formot készíteni! A szükséges bemenő adatokat beviteli mezők (TextBox) segítségével kérje be, a megoldást elvégző forráskódot tegye egy nyomógomb (CommandButton) kattintás eseménykezelőjébe (pl. CommandButton1_Click)! Ha már elkészített egy megfelelően paraméterezett megoldó szubrutint (a példában ez a Haromszog függvény) akkor a megoldáshoz ezt használja! Ez utóbbit szemlélteti az alábbi példa. Ha megoldó szubrutinokat használ, akkor ügyeljen azok elérhetőségére (pl. legyen mindegyik a megfelelő form moduljában, vagy tegye őket (az alapértelmezett publikus eléréssel) egy külön modulba)!

Feladatmegoldás form segítségével
5. ábra
Private Sub CommandButton1_Click()
  'A típuskonverzió (szöveget számmá) automatikusan végrehajtódik
  If Haromszog(TextBox1.Text, TextBox2.Text, TextBox3.Text) Then
    MsgBox "A három szám lehet egy háromszög három oldala!"
  Else
    MsgBox "A három szám nem lehet egy háromszög három oldala!"
  End If
End Sub
 
Private Sub CommandButton2_Click()
  End
End Sub

Feladatok:

1. Darabszám: Határozzuk meg egy adott [a, b] intervallumba (a < b pozitív egészek) eső, adott c számmal osztható számok darabszámát! (Tipp: Egy darabszám változót állítsunk 0-ra, majd egy ciklussal vizsgáljuk meg az [a, b] intervallumba eső egész számokat! Ha a vizsgált szám osztható c-vel, akkor növeljük meg eggyel a darabszám változó értékét!)

2. Összeg: Oldjuk meg az előző feladatot úgy, hogy a feltételt teljesítő számok összegét is meghatározzuk! (Tipp: Az összeget tartalmazó változót is állítsuk 0-ra, majd amikor a darabszámot eggyel növeljük, akkor az összeget gyűjtő változót növeljük meg a vizsgált számmal!)

3. Minimum: Adott egy nem üres sztring. Határozzuk meg a sztring legkisebb ASCII kódú karakterét! (Tipp: Jegyezzük meg a sztring első karakterét mint eredményt, majd vizsgáljuk meg a sztring többi karakterét! Ha valamelyik vizsgált karakter ASCII kódja kisebb, mint az addigi eredmény ASCII kódja, akkor módosítsuk az eredményt, azaz jegyezzük meg a vizsgált karaktert eredményként!)

4. Maximum: Adott egy nem üres sztring. Határozzuk meg a sztring legnagyobb ASCII kódú karakterét! (Tipp: Az előző minimumkereséshez hasonlóan végezzük, csak most akkor módosítsuk az eredményt, ha nagyobbat (nagyobb ASCII kódú karaktert) találunk!)

5. Átlag: Adott N db szám, mint input adat. Kérjük be őket és írjuk ki az átlagnál nagyobb adatokat! Az adatok száma is input adat, értéke legfeljebb 10. (Tipp: Az adatok tárolására használjunk tömböt! Előbb határozzuk meg az adatok összegét (lásd Összeg), azután egy N-nel való osztással az átlagot, majd egy ciklussal írjuk ki az átlagnál nagyobb elemeket!)

6. Döntsük el egy pozitív egész n számról, hogy prímszám-e vagy sem! Egy szám prím, ha 1-en és önmagán kívül nincs más osztója. (Tipp: Az n<=2 esetek külön kezelendők, (1 nem prím, 2 prím) egyébként meg, ha a számnak nincs osztója a [2, Int(Sqr(n))] intervallumban, akkor a szám "prím", egyébként "nem"! A feladat megoldását a 4. lecke tartalmazza.)

7. Az előző feladat segítségével írjuk ki egy adott [a, b] intervallumba (a < b pozitív egészek) eső összes prímszámot! (Tipp: Egy For ciklus segítségével lépkedjünk végig az [a, b] intervallumba eső egész számokon, az előző algoritmussal döntsük el azt, hogy az adott szám prímszám-e vagy sem, és ha az, akkor írjuk ki az adott prímszámot!)

8. Adott egy sztring, amelyben egy legalább kétjegyű, pozitív egész szám van. A szám nagy is lehet, akár 100 számjegyből álló is (ezért is van sztringben megadva). Mondjuk meg, hogy a szám osztható-e 2-vel, 3-mal, 4-gyel, 5-tel! (Tipp: A szám (a nagysága miatt) számként nem használható (azaz nem végezhetünk vele numerikus műveletet), így a megoldáshoz a szám megfelelő számjegyeit kell megvizsgálni! A 2-vel való oszthatósághoz az kell, hogy az utolsó számjegye páros legyen, a 3-mal való oszthatósághoz az, hogy a szám számjegyeinek összege osztható legyen 3-mal, a 4-gyel való oszthatósághoz az, hogy a két utolsó számjegyből álló szám osztható legyen 4-gyel, az 5-tel való oszthatósághoz az utolsó számjegy 0 vagy 5 volta szükséges. Egy c számjegykarakter számértékét az Asc(c)-48 kifejezés adja.)

9. Generáljunk egy szabályos 5-ös lottószelvényt, azaz 5 db különböző egész számot az [1, 90] intervallumból! (Tipp: A lottószámokat egy (5 elemű) tömbbe tegyük! Annak eldöntésére, hogy egy számot ismételten generáltuk-e vagy sem, használjunk keresőciklust! Az alábbi kódrészlet egy egydimenziós (a) tömb 1-től db-ig indexű elemei között keres egy adott (x) elemet. A keresés csak sikertelen keresésnél vizsgálja meg az összes (db darab) elemet, egyébként az első találatnál befejeződik a keresés (lásd a logikai típusú (van) változó szerepét).)

van = False: i = 1
While (i <= db) And Not van
  If x = a(i) Then van = True Else i = i + 1
Wend

10. Adott egy pozitív egész szám, mint egy euróban kifizetendő pénzösszeg. Fizessük ki a legkevesebb címlet felhasználásával! A felhasználható címletek: 500, 200, 100, 50, 20, 10, 5, 2, 1.

Pl. 1243 500: 2db, 200: 1db, 20: 2 db, 2: 1 db, 1: 1 db

(Tipp: Az egyes címleteket tegyük egy (9 elemű) tömbbe nagyság szerint csökkenő sorrendben, és az összeg kifizetésekor ilyen sorrendben vegyük az egyes címleteket, azaz először a legnagyobb címlettel (500) kezdjünk, és a legkisebbel (1) fejezzük be! Egy adott címlettel való kifizetésnél egész osztást végezzünk, a hányados az adott címlet darabszámát adja, míg a maradék a további címletekkel kifizetendő összeget!)

11. Egy kártyajátékot 52 lapos kártyával játszanak. Készítsünk véletlen leosztást N (N<=4, input adat) játékosnak úgy, hogy minden játékos M (M<=13, input adat) db lapot kap! A lapokat a sorszámukkal azonosítsuk, egy lapot csak egyszer osszunk ki, az eredmény egy N×M-es mátrix legyen! (Tipp: Használjunk egy 52 elemű logikai tömböt az egyes lapok kiosztottságának adminisztrálására! Kezdetben minden elem legyen hamis (False), hiszen még nem osztottunk ki egyetlen lapot sem! A véletlenszerű osztást játékosonként, azon belül laponként haladva végezzük két, egymásba ágyazott For ciklussal! Egy adott játékos adott lapjának generálását egy hátultesztelő ciklussal végezzük, mert csak olyan lap generálható, amelyik még nincs kiosztva! Ha egy lapot kiosztunk (betesszük a lap sorszámát az eredménymátrixba), akkor a lap kiosztottságát állítsuk igazra (True)!)

Önellenőrző kérdések
1. Az alábbi állítások közül melyek igazak az összetett adattípusokkal kapcsolatosan?
A tömbök indexeinek alsó határa 0 vagy 1.
Egy tömb egy elemének kiválasztásához pontosan annyi darab index szükséges, mint ahány dimenziós az adott tömb.
A dinamikus tömbök elemtípusa a ReDim utasítással megváltoztatható.
Egy rekord mezői lehetnek tömbök is és rekordok is.
Egy rekordváltozó deklarálása a Type kulcsszóval kezdődik.
Azonos típusú rekordváltozók között megengedett az értékadás.
A With utasítással a rekordváltozóra való hivatkozás rövidíthető.
2. Az alábbi állítások közül melyek igazak az objektumokkal kapcsolatosan?
Objektumon adatok egy halmazát értjük.
A szubrutinok egyben metódusok is.
A metódusok egyben szubrutinok is.
Az eseménykezelők egyben metódusok is.
A metódusok egyben eseménykezelők is.
Az objektumpéldányokat osztályoknak nevezzük.
Az osztály egyben típus is.
3. Értékelje ki az alább kifejezéseket, majd adja meg az eredmény értékét!

FONTOS: A megoldást a fejlesztőkörnyezet használata nélkül adja meg!

Option Base 0
Dim a(10) As Integer
Dim b(1 To 5, -3 To 2) As Double

UBound(a) + UBound(b)

Az eredmény értéke:

LBound(a) + LBound(b)

Az eredmény értéke:

UBound(b,1) + LBound(b,2)

Az eredmény értéke: