You are on page 1of 959

A C# 2008 és a . NET 3.

Második kötet

Andrew Troelsen
A C# 2008
és a . N ET 3.5
Második kötet (20-33. fejezet)

Andrew Troelsen

200 9
A C# 2008 és a .NET 3.5 - Második kötet (20-33. fejezet és két függelék)
A negyedik kiadás magyarul, két kötetben
Pro C#2008 and the .NET 3.5 Platform, Fourth Edition
Andrew Troelsen

Copyright 2008 by A press.


Original English language edition published by Apress Co. Sound View Court 3B,
Greenwich CT 06830 USA. Copyright© 2008 by Apress Co. Hungarian-language
edition copyright©2009 by SZAK Kiadó. Ali rights reserved.

Magyar fordítás (Hungarian translation)©SZAK Kiadó 2009.


Fordította a SZAK Kiadó fordítócsopor�a: Domoszlai László, Endrédi Gabriella,
Erős Szilvia, Iváncsy Renáta, Szalay Márton, Tiber Melinda, Varga Ágnes,
Varga Péter
Lektor: Dávid Zoltán, MVP és Gincsai Gábor, MVP
Magyar nyelvi gondozás: Laczkó Krisztina és Trepák Mónika

ISBN 978-963-9863-10-1

A könyv fordítása a Kilgray Kft. MemoQ (http://www.memoqtm.com) prog­


ramjával készült, a szöveg helyességét és az elválasztásokat a MorphoLogic He­
lyesek nevű programjával ellenőriztük

Minden jog fenntartva. Jelen könyvet, illetve annak részeit a kiadó engedélye
nélkül tilos reprodukálni, adatrögzítő rendszerben tárolni, bármilyen formában
vagy eszközzel elektronikus úton vagy más módon közölni.

SZAK Kiadó Kft. • Az 1795 ben alapított Magyar Könyvkiadók és


-

Könyvterjesztők Egyesülésének a tagja • 2060 Bicske, Diófa u. 3. • Tel.:


36-22-350-209 • Fax: 36-22-565-311 • www .szak.hu • e-mail:
info@szak.hu • Kiadóvezető: Kis Ádám, e-mail: adam.kis@szak.hu •
Főszerkesztő: Kis Balázs MCSE, MCT, e-mail: balazs.kis@szak.hu
Ezt a kiadást Mikkónak, a csodamacskának, a 412-es szám alatt eltöltött
éveknek és csodálatos feleségemnek, Amandának ajánlom, aki türelmesen
várta, hogy elkészüljek újabb könyvem megírásávaL
Tartalomjegyzék

Köszönetnyi Ivánitás ............................................... xxix

Bevezetés ........................................................... xxxi

Mi egy csapat vagyunk, az Olvasó és én ................................................ xxxii


A könyv áttekintése.................................................................................. xxxiii
Első kötet ....................................... . . ...... ........................ ................ . . . . . . . xxxiii
1. rész: Bevezetés a C#-ba és a .NET platformba ............................ xxxiii
1. fejezet: A .NET filozófiája ......................................................... xxxiii

2. fejezet: C#-alkalmazások készítése ......................................... xxxiv


2. rész: A C# alapvető építőelemei ................................................... xxxiv
3. fejezet: A C# alapvető építőelemei, I. rész ............................. xxxiv
4. fejezet: A C# alapvető építőelemei, II. rész ............................ xxxiv

5. fejezet: Egységbe zárt osztálytípusok definiálása .................. xxxv


6. fejezet: A származtatás és a polimorfizmus............................ xxxv
7. fejezet: Strukturált hibakezelés ................................................. xxxv
8. fejezet: Az objektumok életciklusa ........................................... xxxv
3. rész: Haladó programozási szerkezetek a C#-ban ..................... xxxvi
9. fejezet: Interfészek használata ................................................. xxxvi
10. fejezet: Gyűjtemények és generikus típusok ....................... xxxvi
11. fejezet Metódusreferenciák, események és lambdák.......... xxxvi
12. fejezet: Indexerek, operátorok és mutatók .......................... xxxvii
13. fejezet: C# 2008 nyelvi újdonságai ....................................... xxxvii
14. fejezet: Bevezetés a nyelvbe ágyazott
lekérdezésekbe (LINQ) ................................................................ xxxvii
4. rész: Programozás .NET-szerelvényekkel .................................. xxxvii

15. fejezet: A .NET-szerelvények ............................................... xxxviii


16. fejezet: Típusreflexió, késői kötés és
attribútumalapú programozás .................................................. xxxviii
17. fejezet: Folyamatok, alkalmazástartományok és
objektumkörnyezetek ................................................................. xxxviii
18. fejezet: Többszálú alkalmazások készítése ........................xxxviii
19. fejezet: A köztes nyelv (ClL) és
a dinamikus szerelvény ek. ........................................................... xxxix
Tartalomjegyzék

Második kötet ...................................................................................... xxxix


5. rész: Bevezetés a .NET alaposztály könyvtáraiba ....................... xxxix
20. fejezet: Fájlműveletek és elkülönített tárolás ....................... xxxix
21. fejezet: Bevezetés az objektumsorosítás világába ..................... xl
22. fejezet: ADO.NET 1. rész: Az élő kapcsolat.. ............................. xl
23. fejezet: ADO.NET 2. rész: A bontott kapcsolat ......................... xl
24. fejezet: A LINQ API programozása ............................................ xl
25. fejezet: A WCF .............................................................................. xli
26. fejezet: A WF ................................................................................. xli
6. rész: Felhasználói felületek ................................................................. xli
27. fejezet: Windows Forms-programozás...................................... xli
28. fejezet: A WPF és az XAML ....................................................... xlii
29. fejezet: Programozás WPF-vezérlőelemmekkel.. .................... xlii
30. fejezet: WPF 2D grafikus renderelés,
erőforrások és témák......................................................................... xlii
7. rész: Webes alkalmazások fejlesztése ASP.NET segítségével...... xliii
31. fejezet: ASP.NET weboldalak készítése .................................. xliii
32. fejezet: ASP.NET-vezérlőelemek,
témák és mesteroldalak ................................................................... xliii
33. fejezet: ASP.NET állapotkezelési technikák............................ xliii
8. rész: Függelék ..................................................................................... xliv
A függelék: A COM és a .NET együttműködése ......................... xliv
B függelék: Platformfüggetlen .NET-fejlesztés a Monóval......... xliv
Öt szabadon letölthető fejezet - még több információ .......................... xlv
A könyv forráskódjának igénylése............................................................. xlv
A lehetséges javítások ................................................................................. xlvi
Elérhetőségern .............................................................................................. xlvi

5. rész: Bevezetés a .NET alaposztálykönyvtáraiba

20. Fáj lm űveletek és elszigetelt tárolás . . . . . . . . . . . . . . . .. . .. . . . . .3

A System.! O névtér ...................................................................... . .... . ...... . ...... 3


A Directory(Info) és File(Info) típusok .... ........................ . .............. . . .. ...... . ... 5
Az absztrakt FileSysteminfo ősosztály ........................... . ........ . ... . .......... 6
A Directoryinfo típus használata .................. . ...... . .
.. .................. . .................. 7
Fájlok listázása a Directoryinfo típus segítségéve!... ........... . ...... . .......... 9
Alkönyvtárak létrehozása a Directoryinfo segítségéve!... ............. .. . .. 10

viii
Tartalomjegyzék

A Directory típus használata ...................................................... . ................ 11


A Driveinfo osztálytípus használata ......... . ............. . ..... .. ... ..... .................... 12
A Fileinfo osztály használata . ....... .... ....... . ... .. .................. . ... .......... . .
.. .......... 14
A Filelnfo.Create() metódus ......... . .
.. ......... . ..... . .............................. . . ...... 15
A Fileinfo.Open() metódus ..................................................................... 16
A Filelnfo.OpenRead() és a Filelnfo.OpenWrite() metódusok .......... 17
A Fileinfo.OpenText() metódus . . . .......... . ........ ...
. . .......... .. ....... . ... ........... 18
A Filelnfo.CreateText() és a Filelnfo.AppendText() metódusok ....... 18
A File típus használata 19
:
.................................................................................

További fájlközpontú tagok. ................................ . .......... .. . ...... . .... . ...... . . 20


.

Az absztrakt Stream osztály ............. .. ........... . ............ . ........... . .......... . .......... 22


A FileStream típusok használata ...... . ............. ...
. . .......... .. .......... .. .......... 23
A StreamW riter és StreamReader típusok használata .............................. 25
Szövegfájl írása ......................................................................................... 26
Olvasás szövegfájlból .......... . .................................... .. . .............. , ............. 27
A StreamWriter/StreamReader típusok közvetlen létrehozása ... . .... 29
A StringWriter és StringReader típusok használata ...... . ....... . ....... . . .. ....... 29
A BinaryWriter és BinaryReader osztályok használata ........................... 31
Fájlok programozott "figyelése" . .......................................... ...................... 34
Aszinkron fájlolvasás és -írás .. ... .. .
........... .......... ......... ......... ........... . ... ......... 36
Az elszigetelt tároló szerepe . ....... .. ............ .. . ..... .. . ... .. . ... . ... . .
.. ........ . ............. 38
Bizalom kérdése ............................. . .......... . .............................................. 38
Az elszigetelt tárolóhoz tartozó API egyéb
felhasználási módjai ...... .. ........... ... ........ .... ........ .... .......... . . . ........ .. . .......... 39
Bevezetés a kóderedet-alapú biztonságba ...... . ....... . .... . ......... . ................... 40
A bizonyítékok szerepe ............. . ....... . ........ . .......... . . .. .............................. 41
A kódcsoportok szerepe ............................. . ............................................ 45
Az engedélykészletek szerepe ................. . .. .
... . ... .......... . ..
. ...... . ..... . .... . . 49
.. .

A CAS működése .............. . ......... . ........ .. . ...


. .. ... . . ....... .. ....... . ..................... 50
Full Trust jogosultság visszaállítása
a My Computer_Zone kódcsoportra .............................................. 52
_

Az elszigetelt tároló ....................................................................................... 52


Az elszigetelt tároló hatóköre................................................................. 53
Az elszigetelt tároló helye . ..... . ....
. ..... . .... . ...... .. . ............ . ..... . ..
.. ........ . ....... 55
Az elszigetelt tároló kezelése a storeadm.exe segítségéve!................ 57
A System.IO.IsolatedStorage típusa ................................................ 57
Tároló létrehozása az IsolatedStorageFile objektummal... ........ . .
... ........ . 58
.

Adat írása a tárolóba .................. . .......................... . ................... . .


.. ...... . .. 60
. .

ix
Tartalomjegyzék

Adat olvasása a tárolóból... ............... ................. ................ ............. ........ 61


Felhasználói adat törlése a tárolóból ............. . . . . . ............................ ....... 62
Egyedi könyvtárstruktúra létrehozása . . . . . . . . . . . . . ..... ...... . . . . . . . .... . . . . . . . . . . . . . . . 62
Az elszigetelt tároló működés közben: ClickOnce-telepítés .................... 64
Az IsolatedStorageFilePermission attribútum ..................................... 65
A biztonsági zóna korlátozása ............................................................... 65
Az alkalmazás közzététele webkiszolgálón ......................................... 67
Az eredmény megtekintése .................................................................... 67
Összefoglalás .................................................................................................. 68

21. Bevezetés az objektumsorositás világába................. 71

Az objektumsorosítás .... . . . .... . . . . . ......... ........ . . . . ................................ . . . . . . . . . . . . . . . 71


Az objektumgráfok szerepe .................................................................... 73
Objektumok konfigurálása sorosításhoz . . . ......................................... ........ 75
Sorosítható típusok meghatározása ...................................................... 75
Nyilvános mezők, privát mezők és nyilvános tulajdonságok ........... 76
A sorosító formázó kiválasztása .................................................................. 77
Az IFormatter és az IRemotingFormatter interfészek ......................... 78
A formázák közötti típuspontosság ...................................................... 80
Objektumok sorosítása a BinaryFormatterrel.. .......................................... 81
Objektumok visszaállítása a BinaryFormatterrel ................................ 83
Objektumok sorosítása a SoapFormatterrel.. ............................................. 83
Objektumok sorosítása az XmlSerializerrel . . ........... . . . ....................... . . . . .... 85
Generált XML-adatok szabályozása ...................................................... 86
Objektumgyűjtemények sorosítása ............................................................. 88
A sorosítási folyamat testreszabása ............................................................ 90
Az objektumsorosítás háttérrészletei .................................................... 91
Sorosítások testreszabása az ISerializable használatával ................... 92
Sorosítások testreszabása attribútumokkal ..................... . . ................... 96
Összefoglalás .................................................................................................. 97

22. ADO.NET, 1. rész: Az élő kapcsolat ........................ 99

Az ADO.NET magas szintű meghatározása .............................................. 99


Az ADO.NET két arca ........................ ................... ...... ..................... . . . . . 101
Az ADO.NET-adatszolgáltatók működése ..................... . . . . . .. . . . . . . . . . . . . . . . . . . 102
A Microsoft által szállított ADO.NET-adatszolgáltatók ................... 104
Harmadik féltől származó ADO.NET-adatszolgáltató
beszerzése . . ............... ........... ............... . . . . .............. . . . . . . . . . ............. ............ 106

x
Tartalomjegyzék

További ADO.NET-névterek ....................................... ......................... ..... 107


A SystemData névtér típusai . ................................................ ................... 108
Az lObConnection interfész szerepe . . . . ............................................... 109
Az lObTransaction interfész szerepe ............. . . . . . . . . ...... ........ ............... 109
Az lObCommand interfész szerepe .... ........ ........................................ 110
Az lObDataParameter és az lDataParameter
interfészek szerepe ..... .......................... ................................. ................. 110
Az lObDataAdapter és az lDataAdapter interfészek szerepe ......... 111
Az lDataReader és az lDataRecord interfészek szerepe ................... 112
Adatszolgáltatók absztrahálása interfészekkel ............. .......................... 113
Rugalmasság növelése az alkalmazáskonfigurációs
fájlokkal .................. ....................................... ..................................... . . . . . 115
Az AutoLot adatbázis létrehozása ..................... ......................... . . . ........... 117
Az Inventory tábla létrehozása ......... . . . . .... ........................................... 118
A GetPetName() tárolt eljárás létrehozása . . ....................................... 120
A Customers és az Orders táblák létrehozása ...... ............... . .............. 121
Táblakapcsolatok vizuális bemutatása ............. . . . . . . . ..... ...................... 123
Az ADO.NET data provider factory modell ........ . . . .... ............................. 124
Regisztrált data provider factoryk.. ..................................................... 125
Egy teljes data provider factory példa ................ ................. ...... ......... 126
A data provider factory modell lehetséges hátránya ...... .................. 130
A <connectionStrings> elem ........... . . . . . . . ......... ..................................... 130
Az ADO.NET kapcsolatalapú modellje . . . . . . . . . . . . . . . . . ................................... 131
A kapcsolatobjektumok használata .......................................... ........... 133
A ConnectionStringBuilder objektumok .......... ........................ .......... 136
A parancsobjektumok ............... . . . . . .. . . . . . . . . . . . ........ ..................... . . . .......... 137
Az adatolvasók .... ................................... . . . ...... ............ ................................ 139
Több eredményhalmaz kinyerése adatolvasóval ........ ........... ........... 141
Újrafelhasználható adatelérési könyvtár készítése . . . ...... . ....................... 142
A kapcsolatlogika létrehozása ...................................... ....... ................. 144
A beszúrást végző logika létrehozása ......................... . . . . . . . . . ............... 145
A törlést végrehajtó logika létrehozása ................... . . . ................ . . . . ..... 145
A módosítást végző logika létrehozása ................................ .............. 146
A lekérdezést végrehajtó logika létrehozása ........... . . . . . ...................... 147
A paraméterezett parancsobjektumok .............................. .................. 148
Paraméterek megadása a DbParameter típus segitségével ........ 148
Tárolt eljárások végrehajtása ......................... ....................................... 150
Parancssoros front end létrehozása .................... .................. . . . .................. 152

xi
Tartalomjegyzék

A Main() metódus implementálása ............................. . ................ . ...... 153


A Showlnstructions() metódus implementálása ............................... 155
A Listlnventory() metódus implementálása ...................................... 155
A DeleteCarO metódus implementálása ............................................. 156
Az InsertNewCarO metódus implementálása ................................... 157
Az UpdateCarPetName() metódus implementálása ........................ 157
A tárolt eljárásunk meghívása .................................................. . ........... 158
Aszinkron adatelérés az SqlCommand használatával ........................... 159
Az adatbázis-tranzakciók .......... . .............................................................. . . 161
Az ADO.NET -tranzakcióobjektum kulcsfontosságú tagjai ............. 162
Tranzakciómetódus hozzáadása az InventoryDAL
osztályhoz ............................................................................................... 163
Az adatbázis-tranzakciónk tesztelése ................................................. 166
Összefoglalás ................................................................................................ 167

23. ADO.NET, 2. rész: A bontott kapcsolat .................. 169

Az ADO.NET kapcsolat nélküli modellje ................................................ 170


A DataSet szerepe ..................... ......................... .......................................... 171
A DataSet alapvető tulajdonságai.. ...................................................... 172
A DataSet kulcsfontosságú metódusai ....... . ........................................ 173
DataSet létrehozása .... . .............. . ............................................................ 174
DataColumn típusok használata .................... .. ............................... . ......... 174
A DataColumn létrehozása .................................................................. 176
A mezők automatikus növelésének engedélyezése .......................... 177
DataColumn objektumok hozzáadása egy
DataTable típushoz ............................. ................................................... 178
DataRow típusok használata .................................. . ............................... . .. 178
A RowState tulajdonság .................................................................... .... 180
A DataRowVersion tulajdonság .......................................................... 182
DataTable típusok használata ..... ............................................................... 183
DataTable típusok beszúrása DataSet objektumokba ....................... 185
A DataTable adatainak feldolgozása DataTableReader
objektumokkal ..... ................................................................................... 186
A DataTable/DataSet objektumok sorosítása XML-ként ....... .......... 188
A DataTable/DataSet objektumok sorosítása bináris
formátumban .................. . ............... . ....................................................... 189
DataTable objektumok kötése felhasználói felületekhez ....................... 190
DataTable feltöltése egy generikus List<T> használatával .............. 192

xii
Tartalomjegyzék

Sorok programozott törlése .................................................................. 194


Sorok kiválasztása szűrési feltételek alapján ...................................... 196
Sorok módosítása ................................................................................... 199
A DataView típus használata ................................................. .............. 200
Egy utolsó felhasználói felületbővítmény: sorok
számának megjelemtése ........................................................................ 202
DataSetjDataTable objektumok feltöltése adatillesztőkkel . . . ............... 203
Egy egyszerű adatillesztő ..................................................................... 205
Adatbázisnevek leképezése barátságos nevekre ............................... 206
Az AutoLotDAL.dll ismételt vizsgálata ................................................... 207
A kiinduló osztálytípus definiálása ........................ ............................. 207
Az adatillesztő konfigurálása az SqlCommandBuilder
használatával .............................................................. .......... . . . ............. . . 208
A GetAlllnventory() implementálása ................... ............................... 210
Az Updatelnventory() implementálása .............................................. 210
Windows Forrns front end létrehozása ............................................... 211
Navigálás a többtáblázatos DataSet objektumokban ............................. 212
Az adatillesztők előkészítése ................................................................ 213
A táblázatok közötti kapcsolatok kiépítése ........................................ 215
Az adatbázistáblák módosítása .................................................... ........ 215
Navigálás a kapcsolódó táblázatok között ......................................... 216
A Visual Studio 2008 adatelérési eszközei ............................. .................. 220
A DataGridView vizuális megtervezése .............. ............... . . .............. 220
Az App.config fájl és a Settings.Settings fájl ......... . . . . ......................... 224
A generált DataSet vizsgálata .............................................................. 226
A generált DataTable és DataRow típusok vizsgálata ...................... 228
A generált adatillesztő ........................................................................... 230
Generált típusok használata a kódban ................................................ 231
Az automatikusan generált kód elválasztása
a felhasználóifelület-rétegéről. ................................................................... 233
Egy felhasználói felület front end: újra
a MultitabledDataSetApp ..................................................................... 236
Összefoglalás ................................................................................................ 237

24. A LINQ API programozása ................................... 239

A LINQ to ADO.NET szerepe ................................................................... 239


Programozás a LINQ to DataSettel ........................................................... 240
A DataSet bővítmények szerepe .......................................................... 242

xiii
Tartalomjegyzék

DataTable UNQ-kompatibilis használata . . . . . . . . . . .............. ...... . . . .. . . . . . . . 243


A DataRowExtensions.Field<T>() bővítő metódus szerepe ............ 245
Új DataTable objektumok feltöltése
a LINQ-lekérdezésekből ........... . . . . ....... . . . . . ............................................ 246
Programozás a LINQ to SQL használatával ............... . . . . . . . . . . . . . . . . . . . . . . . ...... 247
Az entitásosztályok szerepe ....................................... ............ . . . . . . . . . . . . . . 248
A DataContext típus szerepe . .. . . .. . . . . . . . .......... . . . . .. . . . . . ............................. 248
Egy egyszerű LINQ to SQL példa . . . . . .......... ........................................ 249
Erősen típusos DataContext létrehozása .............................. . . . .. . . . . . . . . . 251
A [Table] és [Column] attribútumok: további részletek ..... . . . . . . . . . . . . . . 253
Entitásosztályok generálása az SqlMetal.exe használatával... ............... 254
A generált entitásosztályok .......................... . . . . . . ........ . . ........................ 257
Kapcsolatok definiálása entitásosztályok használatával .................. 258
Az erősen típusos DataContext ............................................................ 259
A generált típusok használata . . . . .......................................................... 260
Entitásosztályok létrehozása a Visual Studio 2008
használatával .............................. . . . . . . . . . . . . ...................................................... 262
Új elemek beszúrása ............ . . . ....... . . . . . . .................................................. 264
Létező elemek módosítása ................................ . . . . . . . . . . . . . ............... . . . . . . . . 265
Létező elemek törlése ..................... . . . . . . . . . .............. . . ........ . . . . ........ . . . . . ..... 266
XML-dokumentumok kezelése a LINQ to XML
használatával. ........................... . . . . . . ...... . . . . . . . . . . . . . . . ......................................... 267
LINQ to XML: egy jobb DOM ............................. . . . . . . . . . .... . . . ...... . . . . . . .. . . . 267
A System.Xml.XLinq névtér ..... ......................... . . . . . . . . ............ . . . . . . . . . . . . . . . 268
XML-dokumentumok létrehozása programozottan ........ ................. 269
Dokumentumok létrehozása LINQ-lekérdezésekből ....................... 271
XML-tartalom betöltése és elemzése ... ........ . .................. ............... . . . . . . 272
Navigálás egy memóriában lévő dokumentumban . . . . . .. . . . . . . . ........... . . . . . . 272
Adatok módosítása egy XML-dokumentumban . . . . . .............. . . . . . . . . . . . . 275
Összefoglalás . . . . . . . . .. . .................................. . . .............................. . . . . .......... . . . . . 276

25. A WCF ........................................................... 277

Néhány elosztott API ............. . . . . . . . . . . ........ ................................................... 277


A DCOM szerepe .................. . . . . .................. . . ............. . . . . . . . . . . . ....... .......... 278
A COM+/Enterprise Services szerepe ................................................ 279
Az MSMQ szerepe ......................... . . . . . . . . . . . . . . .. . . ................. . . . . . . . ..... . . . . . . . . . 280
A .NET-remoting szerepe .................... . . . . . . . . . ....... ............ . . ................... 281
Az XML-webszolgáltatás szerepe . . . . . . . . . . ...... . . . . . . ...... . . . ......................... 282

xiv
Tartalomjegyzék

Példa .NET-webszolgáltatásra ........................................................ 282


Webszolgáltatási szabványok......................................................... 285
Named pipe-ok, socketek és P2P ......................................................... 286
A WCF szerepe ............................................................................................ 286
A WCF-funkciók áttekintése ................................................................ 287
A szolgáltatásorientált architektúra áttekintése ................................ 288
1. alapelv: A határok explicitek ...................................................... 289
2. alapelv: A szolgáltatások autonómok........................................ 289
3. alapelv: A szolgáltatások szerződésen keresztül és
nem implementáción keresztül kommunikálnak ........................ 289
4. alapelv: A szolgáltatás kompatibilitása
házirenden alapul............................................................................. 289
WCF: A lényeg ....................................................................................... 290
Az alapvető WCF-szerelvények ................................................................ 290
A Visual Studio WCF projektsablonok. .................................................... 292
A WCF Service Website projektsablon ............................................... 293
A WCF-alkalmazás alapösszeállítása ....................................................... 294
A WCF ABC-je ......................................... .................................. ........... ....... 296
A WCF-szerződések .............................................................................. 297
A WCF-kötések ...................................................................................... 298
HTTP-alapú kötések ...............................................................'......... 299
TCP-alapú kötések ........................................................................... 300
MSMQ-alapú kötések ...................................................................... 301
A WCF-címek ......................................................................................... 302
WCF-szolgáltatás készítése ........................................................................ 304
A [ServiceContract] attribútum............................................................ 306
Az [OperationContract] attribútum .................................................... 307
Szolgáltatástípusok mint működési szerződések .............................. 308
A WCF-szolgáltatás hasztolása ................................................................. 308
ABC-k létrehozása az App.config fájlban ........................................... 309
A ServiceHost típus használata ........................................................... 310
Hosztfejlesztési lehetőségek ................................................................. 311
A ServiceHost típus ............................................................................... 313
A <system.serviceModel> elem jellemzői .......................................... 315
Metaadatcsere (Metadata Exchange) engedélyezése ........................ 317
WCF-ügyfélalkalmazás készítése .............................................................. 320
Proxykód generálása az svcutil.exe segítségéve!... ............................ 320
Proxykód generálása Visual Studio 2008-ban .................................... 321
TCP-alapú kötés konfigurálása ............................................................ 323

XV
Tartalomjegyzék

A WCF Service Library projektsablon használata .................................. 325


Egyszerű Matek-szolgáltatás készítése ............................................... 325
A WCF-szolgáltatás tesztelése a WciTestClient.exe-vel.. ................. 326
A konfigurációs fájl módosítása az SvcConfigEditor.exe
programmal ............................................................................................ 327
WCF-szolgáltatás hasztolása Windows-szolgáltatásként ............. ......... 329
Az ABC-k megadása a forráskódban .................................................. 330
A MEX engedélyezése .............................................. ............................. 332
Windows-szolgáltatástelepítő létrehozása . . . ...................................... 332
A Windows-szolgáltatás telepítése ...................................................... 333
Szolgáltatás aszinkron hívása .................................................................... 334
WCF-adatszerződések tervezése ............................................................... 337
Webközpontú WCF Service projektsablon használata ..................... 338
Szolgáltatásszerződés implementálása ............................................... 340
A *.svc fájl szerepe ................................................................................. 341
A Web.config fájl módosítása ............................................................... 342
A szolgáltatás tesztelése ................................................ ........................ 342
Összefoglalás ................................................................................................ 343

26. A Windows Workflow Foundation - Bevezetés . . . . . . . . 345

Egy üzleti folyamat definiálása . . . . ..................................... ............... . . . . . .... 345


A WF szerepe .......................... ........... ..................................................... 347
A WF építőkockái ......................................................... ............................... 347
A WF futtatókörnyezete ........................................................................ 348
A WF alapvető szolgáltatásai ............................................................... 349
A WF-tevékenységek első megközelítésben ...................................... 350
Szekvenciális és állapotgép-munkafolyamatok ................................. 352
WF-szerelvények, -névterek és -projektek ............................................... 355
A .NET 3.5 WF-támogatása ............ . . .................................................... 356
Visual Studio munkafolyamat-projektsablonok ........ ........................ 356
A munkafolyamat menete .................................................................... 357
Egyszerű munkafolyamat-alkalmazás létrehozása ................................. 358
A kezdeti munkafolyamathoz tartozó kód vizsgálata ...................... 358
A Code tevékenység hozzáadása ......................................................... 359
While tevékenység hozzáadása ........................................................... 361
A WF-motor hosztolási kódja .............. ...................................................... 364
Egyedi indítási paraméterek hozzáadása ........................................... 365
Webszolgáltatások hívása a munkafolyamatunkban ............ ................. 368

xvi
Tartalomjegyzék

A MathWeb szolgáltatás létrehozása .................................................. 368


A WF-webszolgáltatás-fogyasztó létrehozása .............. ..... . . . . . . ........ . . 370
Az IfElse tevékenység konfigurálása ......... . . . . ...................... ............... 372
Az InvokeWebService tevékenységek konfigurálása ....................... 374
Kommunikáció WCF-szolgáltatással a SendActivity
segítségéve! ........................... . ............. . . . . . . .................... . . . . . . . .......... ....... . . 377
Újrafelhasználható WF-kódkönytár létrehozása ....... . . . . ..................... . . . . . 382
Hitelellenőrzés végrehajtása .................................. ............................... 384
Windows Forms-kliensalkalmazás létrehozása .......... . . . . . .................. 387
Az egyedi tevékenységek ............................................... ....................... . . . . . 390
Összefoglalás ................................................................................................ 391

6. rész: Felhasználói felületek

27. Windows Forms-programozás .............................. 395

A Windows Forms-névterek ...................................................................... 396


Egyszerű Windows Forms-alkalmazás (IDE-mentes)
létrehozása ................ . ................ ............................. . . . ..................... . . . . . . .. . ..... 397
A vezérlőelemek gyűjteményének feltöltése ........ . . . . .......................... 399
A System.EventArgs és a System.EventHandler szerepe ................ 402
A Visual Studio Windows Forms-projektsablona ................................... 404
A vizuális tervezőfelület .................. . . . . ........ . . . . .................. . . . . . . ......... .... 404
A kezdeti űrlap .......................................................... ............................. 406
A Program osztály ............... . . . . . ......... . . ......... . . . . ..................................... 408
Menürendszerek vizuális építése ........... ........... . . . ........ . . .......... . . . ........ 409
Az űrlapok anatómiája . . . . . . ........... .......... ........... . . . ....................................... 412
A Control osztály funkcionalitása ............. . . . . . ..................................... 414
A Form osztály funkcionalitása .................................................... ....... 417
A Form típus életciklusa .................... . . . ...... . . . ............................ ....... . . . . 419
Reagálás az egér eseményeire . . ............... ............ . . ....................... .............. 422
Az egérgombkattintás meghatározása ....... ......................................... 424
Reagálás a billentyűzet eseményeire ..... ................................................... 425
Párbeszédablakok tervezése ................................... . . . . .......... . . . ......... . . . ...... 427
A DialogResult tulajdonság .................................................................. 429
A tabulátorsorrend konfigurálása ....................................................... 430
A tabulátorsorrend-varázsló ................................................................ 430
Az űrlap alapértelmezett beviteli gombjának a beállítása . . .......... . . . 431
Párbeszédabla_!<ok megjelemtése ......................................................... 431

xvii
Tartalomjegyzék

Az űrlapok származtatása .................................................................... 433


GDI+-alapú grafikus adatok renderelése ................................................. 436
A System.Drawing névtér ..................................................................... 437
A Graphics típus szerepe ...................................................................... 438
Graphics objektumok megszerzése a Paint
eseményen keresztül ............................................................................. 439
Az űrlap felületének érvénytelenítése ................................................. 442
Teljes Windows Forms-alkalmazás létrehozása ...................................... 443
A főmenürendszer készítése ................................................................ 443
A ShapeData típus meghatározása ...................................................... 444
A ShapePickerDialog típus meghatározása ....................................... 445
Infrastruktúra hozzáadása a MainWindow típushoz ....................... 446
A Tools menü funkcionalitásának implementálása .......................... 447
A grafikus kimenet rögzítése és renderelése ...................................... 449
A sorosítási logika implementálása ..................................................... 450
Összefoglalás ................................................................................................ 452

28. A WPF és az XAML .••..••.•••.•••••...••••••••••••••••.•••••.• 453

A WPF mozgatórugója ................................................................................ 453


A különböző API-k egységesítése ....................................................... 454
Kapcsolatok elkülönítése a XAML segítségéve! ................................ 455
Optimalizált renderelési modell biztosítása ....................................... 456
További WPF-központú hasznos tulajdonságok ............................... 457
A WPF-alkalmazások különböző típusai ................................................. 458
Hagyományos asztali alkalmazások ................................................... 458
Navigációalapú WPF-alkalmazások ................................................... 458
XBAP-alkalmazások .............................................................................. 459
Silverlight-alkalmazások. ...................................................................... 461
A WPF-szerelvények vizsgálata ................................................................ 461
Az Application osztály szerepe ........................................................... 463
A Window osztály szerepe ................................................................... 465
A System. Windows. Controls.ContentControl
alaposztály szerepe .......................................................................... 465
A System.Windows.Controls. Control alaposztály
szerepe ............................................................................................... 468
A System.Windows.FrameworkElement alaposztály
szerepe ............................................................................................... 468
A System.Windows.UIElement alaposztály szerepe ................... 469

xviii
Tartalomjegyzék

A System.Windows.Media.Visual szerepe ................................... 470


A System.Windows.DependencyObject osztály szerepe ............ 470
A System.Windows.Threading.DispatcherObject szerepe ......... 471
(XAML-mentes) WPF-alkalmazás készítése ............................................ 471
A Window osztálytípus kibővítése ..................................................... 474
Egyszerű felhasználói felület létrehozása ........................................... 475
Az Application típus további jellemzői ................................... ................. 477
Az alkalmazás adatai és a parancssori argumentumok
feldolgozása ............................................................................................ 477
Az Application típus Windows gyűjteményének
feldolgozása ......................... . .
...... ........................................................... 479
Az Application típus további eseményei ................ . ........................... 479
A Window típus további jellemzői ................. .
......................................... 480
A Window objektum élettartama ........................................................ 480
A Window objektum Closing eseményének kezelése ...................... 482
Ablakszintű egéresemények kezelése ................................................. 484
Ablakszintű billentyűzetesemények kezelése .................................... 485
(XAML-központű) WPF-alkalmazás készítése ........................................ 486
A MainWindow definiálása XAML-ben ............................................. 487
Alkalmazásobjektum definiálása XAML-ben .................. .................. 488
XAML-fájlok feldolgozása az msbuild.exe segítségéve!................... 489
Markup átalakítása .NET-szerelvénnyé ................................................... 492
XAML leképezése C#-kódra ................................................................ 492
A BAML szerepe .................................... ................................................ 494
XAML-ből szerelvény: a folyamat összefoglalása ............................. 496
A kapcsolatok elkülönítése mögöttes kódfájlokkal ................................ 497
A XAML-szintaxis ....................................................................................... 499
XAML-kísérletek a XamlPad segítségéve!... ....................................... 499
XAML-névterek és -kulcsszavak ......................................................... 501
XAML-elemek és -attribútumok .............................................. ............ 505
A XAML tulajdonságelem szintaxisa .................................................. 506
A XAML csatolt tulajdonságai ............................................................. 509
XAML-típusátalakítók ........................................................................... 510
A XAML markupbővítményei ..................................... ........................ 512
Az erőforrások és adatkötések előzetes áttekintése .......... .
............... 514
WPF-alkalmazások készítése a Visual Studio 2008 segítségével .......... 517
WPF-projektsablonok ............................................................................ 518
A kezdeti ablak nevének módosítása ................................................ . 519
.

A WPF-tervező .......................................................... . ...................... . ..... 520

xix
Tartalomjegyzék

XAML feldolgozása futásidőben: a SimpleXamlPad.exe . . ..................... 523


A Loaded esemény megvalósítása ...................................................... 525
A Button kattintási eseményének megvalósítása ....... .... .
. . . . . . . . . . ......... 526
A Closed esemény megvalósítása .............................................. . . . . . . . . . . 527
Az alkalmazás tesztelése ................................................. . ..................... 527
A Microsoft Expression Bl end szerepe ............................... . . . . . . . . . . . . . . . . . . .... 528
Az Expression Blend előnyei . . . . . . . . . . . . .......... . . ................... ............... . . . . . . 529
Összefoglalás . . . . . . ......... ..
. .......................................................... .................... 530

2 9. Programozás WPF -vezérlöelemekkel . . . . . . . . . . . . . . . . . . . . 5 31

A WPF vezérlőelem-könyvtár vizsgálata ......................... . . . . . . . . ................ 531


A WPF-vezérlőelemek és a Visual Studio 2008 ................................. 533
A részletek a dokumentációban találhatók ........................................ 534
Vezérlőelemek deklarálása a XAML-ben ................................................. 535
Együttműködés a vezérlőelemekkel a forráskódfájlokban .............. 536
A függőségi tulajdonságok szerepe .......................................................... 539
Létező függőségi tulajdonság vizsgálata ............................................ 540
A függőségi tulajdonságok regisztrálása ............................................ 542
Wrapper tulajdonság definiálása a DependencyProperty
mezőhöz ................................ ............................................. ..................... 543
Továbbított események ....................................... . ....................................... 544
A továbbított buborékesemények szerepe . . . . . . ................................... 546
A buborékesemények folytatása és leállítása ..................................... 547
A továbbított lefutó események szerepe ............................................. 548
A Button típusok használata ......... ......... . . . ...... ..... . . . ................................... 551
A ButtonBase típus ................................................................................ 551
A Button típus ...... . ................................................................................. 552
AToggleButton típus . . . . . ............................ ............................... . . . . ........ 553
A RepeatButton típus ................................................... .................... . . . . . 554
A jelölőnégyzetek és a rádiógombok használata .................................... 556
Logikai csoportosítások létrehozása......., ............................................ 558
A kapcsolódó elemek összeállítása GroupBox típusokba . . . . . . .......... 558
Kapcsolódó elemek bővítő típusokba csoportosítása . ...................... 559
A ListBox és a ComboBox típusok használata ............................ .
............ 560
Lista-vezérlőelemek feltöltése programozott módon ........... .
..... . ..... 562
Tetszőleges tartalom hozzáadása ................................. . .... . . . . .... .
..... .. 563
..

Az aktuális kiválasztás meghatározása ..................... . . . . ..... .. ..


. .. . ........ 564
Az aktuális kiválasztás meghatározása a beágyazott
tartalom esetében ................................................................................... 566

XX
Tartalomjegyzék

A többsoros szövegbeviteli mezők használata ................................. . . . .... 568


A TextBox típus használata ..... . . . . ......................... . . ...... . . . . . . .................. 568
A PasswordBox típus használata ......................................................... 570
A tartalomelrendezés kezelése panelek használatával... .............. . . ........ 572
A WPF alapvető paneltípusai ........... .................. ............ . . . . . . . . ...... . . . . .... 573
Tartalom elhelyezése a Canvas paneleken belül .... ........................... 574
Tartalom elhelyezése a WrapPanel paneleken belül... ... ......... .......... 577
Tartalom elhelyezése a StackPanel paneleken belül ......................... 579
Tartalom elhelyezése a Grid paneleken belül ....................... ......... . . . . 580
Rácsok GridSplitter típusokkal ............. ......................................... 582
Tartalom elhelyezése a DockPanel paneleken belül ............:............ 583
A lapozás engedélyezése a paneltípusoknál ...................................... 584
Ablak kereteinek készítése beágyazott panelek használatávaL ........... 585
A menürendszer készítése ............................................... ....... . . . .. . ........ 587
A ToolBar típus készítése ...................................................................... 588
A StatusBar típus készítése ......... . . . . ........... ...... . . . ............... . . . . ............... 589
A felhasználói felület véglegesítése ..................................................... 590
A megvalósítás véglegesítése .................... . . . .. . . . . . . . . . . . . . . . ........ ....... . . ...... 591
A WPF vezérlőutasításai.. ........................... . . . . . ........................................... 592
A belső vezérlőelem parancsobjektumok . . ..... . . . . . . . . . ................... ........ 593
Utasítások kapcsolása a Command tulajdonsághoz ............... ........ . . 595
Utasítások kapcsolása a felhasználói felület tetszőleges
elemeihez ............. . . . . . . ...... . . . .. . . . . . ......... . . . . ......... . . .......... . . . . . ............ . . . . . ..... 596
A WPF adatkötési modell............... ; ........................................................... 598
Ismerkedés az adatkötéssel ...... ....... . . . . ....................... . . . ................ ....... 599
A DataContext tulajdonság ......... ...................................... . . ................. 600
A Mode tulajdonság ......................... . . ........ . . . ....... . . . . . . . . . . . ........... . . . . . ...... 601
Adatátalakítás az IValueConverter segítségéveL ...... .......... ........... ....... 602
Konvertálás különböző adattípusok között ................. . . . ...... . ............ 605
Kötés egyedi objektumokhoz ......................... . . . . . .............. . . . ....... ....... . . ...... 606
Az ObservableCollection<T> típus használata .. .
........ . . .
........ ........... 608
Egyedi adatsablon készítése .............................. . . . . . .............. . . . . . . . . . ....... 610
A felhasználói felület elemeinek kötése
XML-dokumentumokhoz .......... . . . . ............... .................................. . . . ...... . . . 611
Egyedi párbeszédablak készítése .... ..................................................... 612
A DialogResult érték hozzárendelése ................................................. 614
Az aktuális kiválasztás megszerzése ............................... .................... 615
. Egyedi párbeszédablak megjelenítése .............. ................ . .................. 616
Összefoglalás ..... . . . . . . . .......................................... ..................... .............. . . . . . . . 617

xxi
Tartalomjegyzék

30. WPF 20 grafikus renderelés, erőforrások


és témák . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619

A WPF grafikus renderelési szolgáltatásának filozófiája ....................... 620


A WPF grafikus renderelési lehetőségei ............................................. 621
A Shape leszármazott típusainak használata ..................................... 622
A Drawing leszármazott típusainak használata .......................... 623
A Visual leszármazott típusainak használata ............................... 624
Egyedi vizuális renderelési program készítése ............................ 627
A megfelelő megoldás kiválasztása ................................. . . ................. 629
A Shape leszármazott típusainak felfedezése .......................................... 630
A Shape ősosztály funkcionalitása ...................................................... 631
A Rectangle, az Ellipse és a Line típusok használata .................. 631
A Polyline, a Polygon és a Path típusok használata .................... 632
A WPF-ecsettípusok használata ................................................................ 633
Egyszínű ecsettípusok készítése .......................................................... 634
Átmenetes ecsetek használata ............ . ................................................. 635
Az ImageBrush típus ............................................................................. 636
A WPF-tollak használata .......................... . ................................................. 637
A Drawing leszármazott típusainak vizsgálata ...................................... 638
A Geometry típusok szerepe ............ .................................................... 639
Egyszerű rajzoló geometria szétdarabolása ....................................... 641
Drawing típusok a Drawingimage típusban ..................................... 641
Drawing típusok a DrawingBrush típusban ............... . ...................... 642
Összetett rajzoló geometria .................................................................. 643
A felhasználóifelület-transzformációk szerepe ......... .......................... ....
.
645
A Transform leszármazott típusok ...................................................... 645
Transzformációk alkalmazása .
...... ....................................................... 646
A WPF animációs szolgáltatásai ............ . ................................................... 647
Az Animation utótaggal rendelkező típusok szerepe ...................... 648
A Timeline ősosztály szerepe ............................................................... 649
Animáció készítése C#-forráskódból ................... . ....... ....................... 650
Az animáció ütemezésének szabályozása .... . ............ . ........................ 652
Animáció lejátszása visszafelé és folyamatos ismétlése .............. . .... 653
Animáció készítése XAML-leírással... ..... ..
.... .... . .............. . . . . .. . .. .. 654
..... . . .

A Storyboard típus szerepe ........................................................... . 654


.

Az <EventTrigger> használata . . .................................................. . . . 655


A kulcsképkocka-animáció szerepe ....... ............. . ............................... 655
Animáció diszkrét kulcsképkockákkal... . . . . . .... . .
... ......................... 656

xxii
Tartalomjegyzék

Animáció lineáris kulcsképkockákkal ............................... ........ .... 658


A WPF erőforrásrendszere ......................................................................... 660
A bináris erőforrások használata ......................................................... 660
A Resource Build Action ................................... .............................. 661
A Content Build Action ................................................................... 662
Az objektum (vagy másnéven logikai) erőforrások szerepe ............ 663
Stílusok készítése és alkalmazása WPF-vezérlőelemeken ..................... 663
Az inline stílusok használata ............... . . . . . . . . . . ............ .......... . ................ 663
A megnevezett stílusok használata ..................................................... 665
Stílusbeállítások felülbírálása ............................................................... 667
Létező stílusok leszármaztatása ..................................... ...................... 667
A stílusok kiterjesztése ...................................... .................... . . . ............. 668
A stílusok korlátozása ..................................... . . . . . . ................. ............... 669
Stílusok hozzárendelése implicit módon ............................................ 669
Stílusok definiálása triggerekkel. ......................................................... 670
Stílusok hozzárendelése programozott módon ............................ , .... 672
Vezérlőelem felhasználói felületének módosítása sablonok
segítségéve!. .................................................................................................. 675
Egyedi sablon készítése ......................... ....................... ...... . . . . . .............. 676
Triggerek hozzáadása a sablonokhoz ................................................. 677
Sablonok használata a stílusokban ..................... . ................................ 679
Összefoglalás ........................................ ........................................................ 681

7. rész: Webes alkalmazások fejlesztése ASP.NET


segitségével

31. ASP. NET-weboldalak készitése ........... . .... . .... ... ... . 685

A HTTP szerepe ........................................................................................... 685


A HTIP kérés/válasz-ciklus ................................... . . . .............. ............ 686
A HTIP állapotiDentes protokoll... ...................................................... 686
W ebes alkalmazások és webkiszolgálók ............................... . . . . . .............. 687
Az liS virtuális könyvtárainak szerepe .................................. . . . .......... 688
Az ASP.NET fejlesztőkiszolgálója ....................................................... 690
A HTML szerepe ............... .......................................................................... 691
HTML-dokumentumstruktúrák .......................................................... 692
HTML-űrlapok fejlesztése .............. ....................................... ............... 693
HTML-alapú felhasználói felület készítése . . . . ................ .................... 695

xxiii
Tartalomjegyzék

Az ügyféloldali szkriptírás szerepe . .. ............... . ........................................ 698


Példa az ügyféloldali szkriptírásra ......... . .... . . . . .... . .
. . .. . . . ...... ..
. .... .. .. .. .... 699
A default.htm űrlapadatainak ellenőrzése .................... . .... . . .. ............ 700
Az űrlapadatok továbbítása (a GET és a POST) . . .... . ...... . . . .. .. . . .
. . . . . . .. ...... 701
Klasszikus ASP-oldal készítése .................... . . .. ...... .. . .... .. . ................... . . . 702
. . .

A klasszikus ASP problémái . . .......................... . ...... . ... . . .. ................ . .... . .... . 705
Az ASP.NET 1.x főbb előnyei . .. .. .. .. .. . .. ... . .... .. . .................. .. ... . .... . ......... 705
Az ASP.NET legfőbb újdonságai ......................................................... 706
A .NET 3.5 legfőbb webes újdonságai . .. .... . ...... . .... . .... . ... . ....... . .... . ....... 707
Az ASP.NET-névterek ................................................................................ 707
Az ASP.NET weboldal-kódolási modellje . . ................... . ............. .. .
. .. ...... 709
Adatközpontú egyfájlas tesztoldal készítése .......... . .... ..
. .... . ... ........... 710
Az AutoLotDAL.dll fájl manuális hivatkozása .. .. .............. . .... . . 710
.. .

A felhasználói felület tervezése ........... . ...... .. . . . . ................ . . .. .


. .. . .... . 711
Adatelérési logika hozzáadása .......... . ..
.. . ........... . .... . .... . ...... . . .. .... . .. 712
Az ASP.NET-direktívák szerepe . ... ... ..
. . .. . ...... . ..................... . ......... 715
A szkriptblokk elemzése .................. .. . .... . ........... ... . . .... . . .. .... . .... . ..... 716
Az ASP.NET vezérlőelem-deklarációinak áttekintése . . .. . ...... .. . .. 717
A mögötteskód-modell használata ... . .............................. ..
. ................. 718
Hivatkozás az AutoLotDAL.dll szerelvényre . .. .... . ...................... 720
A kódfájl módosítása .......... .. . ...... .. . .... .. ..
. .. . ......... . ...... .. . ............ . ..... 720
Az ASP.NET-oldalak hibakeresése és nyomkövetése ................. 721
ASP.NET-webhely könyvtárszerkezetének részletei . . . . . . ......... .. . .... . . . 723
. .. .

Hivatkozás szerelvényekre .... . . ..


. . ..... . ......................... . ................ . . . . 724
.. . ..

Az App_Code mappa szerepe .... ... .


. . .. ... .. . . .................... .. .
. .. ................ 725
Az ASP.NET-oldal fordítási ciklusa .................... . .... . ............. . .................. 726
Egyfájlos oldalak fordítási ciklusa ...................... .. . ..
. .. .. . . . ...... . .... ..
. ..... . 726
Többfájlos oldalak fordítási ciklusa . . . . . . .. . . .................. . ....................... . 727
A Page típus származtatási lánca . . . . ...... . ........................................... . .... . .. 729
Együttműködés a bejövő HTTP-kérésekkel.. ............. . ....... . ..... . .... . .... . .... . 730
Böngészőstatisztikák . . .. . ........ . ........... ............... ........ ... . . .... . .... . . . . . . 732
. .. .. . .. .

Hozzáférés a bemeneti űrlapadatokhoz ............ .. . ..................... ..... . ... 733


Az IsPostBack tulajdonság . . . . . . . .. .... . .. ................................. . ............ . .... . 734
Együttműködés a kimenő HTTP-válaszokkal.. ....................................... 735
HTML-tartalom kibocsátása ... . ... ...
. . .... . ... . .... . .... ... . . .... .. . .... . .... . ............. 736
Felhasználók átirányítása ......................... . ...... .. . . . .
. . .. . ... ........ . ... . . ..
.. .. . ... 737
Az ASP.NET-weboldalak életciklusa .......... .. . ...... . ...... .. ... .. . .... .. ..
. .. . .... .. . ... 738
Az AutoEventWireup attribútum szerepe ..... . ........ ................... ..... ... 740

xxiv
Tartalomjegyzék

Az Error esemény . .... . . . .. ... ..


. . . . . . . ... . . . .. . . .......
. . . . . . ...... . .......... . ... . . . .. .
.. .. . . .... 741
A Web.config fájl szerepe ...... .. ..... ...... ..... . ... . ..... .
. . . . . . . . . ... ........ . . . .... .. . .. .. . 742
. . . . .

Az ASP.NET Website Administration segédprogramja . .. .... ..... ... 745 . . . .

Összefoglalás ................................................................................................ 746

32. ASP . N ET-vezérlőelemek, - témák és - mesteroldalak .. 747

A webes vezérlőelemek viselkedésének megértése . .. . .... . . . . ... .. . . .. . .


. . .. . .. . . 747
Kiszolgálóoldali események kezelése ...... . ..... . . . . .. ... .
. . . . . . ... .. .... . . ..
. . .... .. 748
Az AutoPostBack tulajdonság ........ ... ....... ....... ... ...... ... .. .... .... .. .. 749 . . . . . . . . . . .

A System.Web.UI.Control típus ........ . ..... .. . .......... . . .. ... ... . . .... ... . . .. .


. . .. .. .. . .. . . 751
Vezérlőelemek felsorolása .................... . ........... . . ... . . . . . . .
. . . . . .. . . . . .... .. . ..... . 752
Vezérlőelemek dinamikus hozzáadása (és törlése) .... . ... . .. . .
.. . .. . . .... . .. 754
A System.Web.UI.WebControls.WebControl típus .. ..... ...... .. . ... . .. . . . . . . . . . . 755
Az ASP.NET webes vezérlőelemeinek főbb kategóriái .... . ..... . .......... 756 . . . . .

Néhány szó a System.Web.UI.HtmlControls típusokról... ............... 758


Sokoldalú ASP.NET-webhely készítése .............................................. . . . . . 759
Mesteroldalak használata ...... . .............................................................. 760
A Menu vezérlőelem és a *.sitemap fájlok használata .. .. .. .. .. 763 . . . . . .

Kenyérmorzsa-vezérlőelem létrehozása a SiteMapPath


típus segítségéve! .............................. ..
. ............. . ... ... . ... ... . . . . . . .. 767
. .. . .. . ..

Az AciRotator használata ...... ....... ...... .. ....... .. . . .... . .. . . ... . ... 767 . . . . . . . . . . . . . . . .

A Default.aspx tartalomlap definiálása ......... ....... . . ...... . . ..... .... . 768 . . . . . . . . . .

Az Inventory tartalomlap tervezése ... ... . . . . . . . .. ... . .... ...... .. .. .. 770 . . . . . . . . . . . . . . . . .

Rendezés és lapozás engedélyezése . .. ........ ............... .................... 774


Helyben történő szerkesztés engedélyezése . . ........ . . . . . .. .... .. . .. . 774
. . . . . .

A Build-a-Car tartalomlap tervezése . ..... . . ... . . . . . . . . . ........ . . .... .... .. .. .. 775 . . . . . . .

Az ellenőrző vezérlőelemek szerepe . . .. . . ... .. . . . . . ... . . ... . . . ....... . . ... . .. 778


.. .. . . . . . . . . . . .

A RequiredFieldValidator vezérlőelem ........................ .................... . . 780


A RegularExpressionValidator vezérlőelem .. ... . .. . .. . .... . .. .. . .. 781 . . . . . . . . . . . . . .

A RangeValidator vezérlőelem . . ..... ...... . . . . . . .. ... ... . ....... .. .... . . . . . . . . . . . . . . . . . . . 782


A CompareValidator vezérlőelem ...... . ... . ..... .. .. . .... . . . . . . .... . .. .... . 782
. . . . .. . . . . ..

Ellenőrzés összegzésének létrehozása ........... .. . ... . . . .. .


. . . . .... .. .. . . .. . .. ..... . 783
Ellenőrzési csoportok definiálása . . ... . ...... . ... . .............. . ................ . . .. .... 785
Témák használata ... . .. . . ... . ...... ... . . .. .. ....
.. . . .. .. ...... .. ...... ......
.. .. ...... ... . . .. .. . . 787
. . . . . . . .

A *.skin fájlok ...... . .... ... .. . ...... ....... . ...... .......


. . . . . . . . . . . .... ... ... ... .. .. . .. . .. .. 788
. . . . . . . . .

Témák alkalmazása a teljes webhelyre ............................................... 790


Témák alkalmazása oldalanként ..................... . .................................... 791
A SkiniD tulajdonság .. ..... ... .... . . .. ... .
. . . .. .. . ... . ... . . . . . . . . ... . ... . .. .. .. 791
. . . . . . . . .. . .. . . . .

XXV
Tartalomjegyzék

Témák beállítása kódból ........ ............................................................... 793


Vezérlőelemek elhelyezése HTML-táblákkal ...................... .................... 795
Összefoglalás ......................... ....................................................................... 797

33. ASP.NET állapotkezelési technikák . .. . .. . . . . ... 799 . . . . . . . . . .

Az állapot. ......................................................................... ............................ 799


Állapotkezelési módszerek az ASP.NET-ben .......................................... 802
Az ASP.NET-nézetállapot szerepe ................................ . ........................... 803
A nézetállapot bemutatása . . . . ............................................................... 804
Egyedi nézetállapot-adat hozzáadása ................................................. 806
A Global.asax fájl szerepe ................. ....................................... ................... 808
A véső, globális kivételkezelő .............................................................. 810
A HttpApplication ősosztály ............ ................................................. . . . 811
Az alkalmazás és a munkamenet közötti különbség ..... ......................... 812
Alkalmazásszintű állapotadatok karbantartása ................................ 813
Alkalmazásadatok módosítása ............................................................ 816
A webalkalmazás leállításának kezelése ..... ....................................... 818
Az alkalmazás-gyorsítótár használata ...................................................... 818
Adatok gyorsítótárazása ......................................... .............................. 819
Az *.aspx fájl módosítása ............ . . . . . . . . . ........................................ . . . . . . . . . 822
Munkamenetadatok kezelése ......... ........................................ . . ...... . . . . . . ...... 824
A HttpSessionState további tagjai .................. . .......... . . ........................ 828
A sütikről ................. ..................................................................................... 829
Sütik létrehozása .... . . . . . . . . . . ...................................................................... 830
Bemeneti sütik adatainak olvasása ...................................................... 831
A <sessionState> elem szerepe ........ . . . . . ........................................... . . .. . . . . . . 832
Munkamenetadatok tárolása ASP.NET munkamenet-
állapotkiszolgálón .................................................................................. 833
Munkamenetadatok tárolása dedikált adatbázisban .................... .... 835
Az ASP.NET profil-API-ja ................... ........................ . . . ............................ 836
Az ASPNETDB.mdf adatbázis ............................................................. 836
Felhasználói profil meghatározása a Web.config fájlban ................. 838
Profiladatok hozzáférése programozottan ......................................... 839
Profiladatok csoportosítása és egyedi objektumok tárolása ............ 843
Összefoglalás ........................................................... ..................................... 845

xxvi
Tartalomjegyzék

8. rész: Függelékek

A A COM és a . N ET együttműködése ........................ 849

A .NET együttműködési képesség hatóköre ...... . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......... 849


A .NET és a COM együttműködésének egyszerű példája . . . . . . . . . . . . . . . .. . . . . 851
A C#-ügyfélalkalmazás elkészítése . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 852
Egy .NET együttműködési szerelvény vizsgálata . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . 855
A futási időben hívható burkoló . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . 857
RCW: a COM-típusok mint .NET-típusok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . 858
RCW: coelassok referenciaszámlálójának kezelése .................... . . . . . . . 860
RCW: alacsony szintű COM-interfészek elrejtése . . . . . . . . . . . . . . . . . . . . . . . .. . . . . 860
A COM lDL szerepe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . 861
A VB COM-kiszolgálónkhoz generált lDL ...................................... . . . 863
Az IDL-attribútumok ........................................ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 864
Az lDL-könyvtár-utasítás .................................................... :................ 865
A [default] interfész szerepe ................................. . . ............. . . ..... . . . . . .. . . . 865
Az IDispatch szerepe ............ . . ...... . . .. . . . ........ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 866
IDL-paraméterattribútumok ................................................................ 866
Típuskönyvtár használata együttműködési szerelvény
készítéséhez . . . . . . . . .. . . ........ ................................................ . . . . ........ . . . .. . . . . . . . . .. . . . 867
Késői kötés a CoCalc coelasshoz .......................................................... 868
Bonyolultabb COM-kiszolgáló készítése.................................................. 870
Még egy COM-interfész támogatása ................................................... 871
Belső objektumok feltárása ................................................................... 872
Az együttműködési szerelvény ................................ . . . . . . . . . . ...... . . . . . . .. . . . . . . . . . 873
Saját C#-kliensalkalmazás készítése ............................... . . . . . . . . . .......... . . 874
Együttműködés a CoCar típussal .. ...................................................... 875
COM-események elfogása .................................................................... 877
A COM és a .NET együttműködési képessége . . . ......... . . . . ..... . . . . . . . . . . . . . . . . . . . 879
A System.Runtime.InteropServices attribútumai ............ . . ..... . . . . . . . . . . . 880
A CCW szerepe ........................................ .................................................... 881
A .NET-osztályinterfész szerepe ............................................................... 882
Osztályinterfész definiálása.................................................................. 883
Saját .NET-típusok készítése ...................................................................... 884
Erős név definiálása ............................................. .................................. 886
A típuskönyvtár létrehozása és a .NET-típusok regisztrálása . . . . . . . .. . . . . . 886
Az exportált típus adatainak a vizsgálata ................... . ............................ 887
Visual Basic 6.0 tesztkliens készítése ........................................................ 888
Összefoglalás ................................................................................................ 890

xxvii
Tartalomjegyzék

B Platformfüggetlen .NET-fejlesztés a Monóval.......... 891

A .NET platformfüggetlen természete ...................................................... 891


A CLl szerepe ........................... .............................................................. 892
A népszerű CIL-disztribúciók .............................................................. 894
A Mono hatóköre ............. ...... . .......... . .................. . ...... . .......................... 895
A Mono beszerzése és telepítése ................................................. .............. 896
A Mono könyvtárszerkezetének vizsgálata ....................................... 898
A Mana-fejlesztőeszközök ................ ......................................................... 899
A C#-fordítók használata ...................................................................... 900
Microsoft-kompatibilis Mono-fejlesztőeszközök. .................. .......... . 900
.

Mana-specifikus fejlesztőeszközök ................... . ...... . ....... .......... .. .


. .. ... 901
A monop(2) használata .................................................................... 902
.NET-alkalmazások készítése Monóval... . . . .......................... . . . . . . . ............. 903
Mana-kódkönyvtár készítése .... .
... . .................. . .... . .............................. 903
Erős név hozzárendelése a CoreLibDumper.dll
szerelvényhez ............................................ . . . . . . . . . . .............. . . . . ........... 904
A módosított manitesztum megtekintése a manadis
használatával ... ....
. . . .... ............. . ..................... . ... . .............................. 905
Szerelvények telepítése a Mono GAC-ba ...................................... 906
Konzolalkalmazás készítése Manóban ................. . ................. .. .. .. ...... 907
Ügyfélalkalmazásunk betöltése a Mana-
futtatókörnyezetbe ........................................... ................... � ............
· 908
Windows Forms ügyfélprogram készítése ......... ... . . ...................... . .... 909
Windows Forms alkalmazásunk futtatása Linux alatt ........ .. . .... . . . ... 911
Javaslatok további tanuláshoz . . . ................ ................................................ 912
Összefoglalás ................................................................................................ 913

Tárgymutató ........................ ............................... 915 .

A szakmai lektorról ............................... ............... 92 7 .

A szerzőről.......................................................... 928

xxviii
Köszönetnyi Ivánitás

Jóllehet egyedül az én nevem jelenik meg a könyv borítóján, de a munka soha


nem készülhetett volna el jó néhány tehetséges ember segítsége nélkül. A követ­
kezőkben azokhoz szólok, akik lehetővé tették, hogy ezt a könyvet megírjam.
Mindenekelőtt köszönet illeti az Apress összes munkatársát, akikkel már
több éve van szerenesém együtt dolgozni. Rendkívüli emberek, akik képesek
voltak az én eredeti Word-dokumentumaimat tökéletes prózává varázsolni.
Hálás köszönet érte. Már most nagyon várom, hogy mikor dolgozhatunk is­
mét együtt egy következő könyvön (de azért előtte szabadságolom magam
egy időre).
Külön köszönöm technikai szerkesztőm, Gavin munkáját, aki annyi bölcs
tanáccsal segített, hogy úgy érzem, ez a kiadás sokkal színvonalasabb lett,
mint az előzőek. Mint mindig, az esetleges gépelési hibákért vagy technikai
tévedésekért egyedül én vagyok a felelős.
Végül, de nem utolsósorban, köszönöm családomnak, barátaimnak és
munkatársaimnak, hogy elviselték mogorva viselkedésemet, amely a kézirat
elkészítésének utolsó szakaszaiban jó néhányszor jellemzett engem.
Bevezetés

Ez a könyv nagyjából 2001 nyara óta létezik, ha nem is ebben a formában: a C#


és a .NET platform első kiadása ugyanis akkor jelent meg, a .NET 1.0 Beta 2
verziójával karöltve. Nagy örömmel és hálával tölt el, hogy munkám kedvező
fogadtatásra lelt a sajtóban, és ami még fontosabb, az olvasók körében is. Az
évek során jelölték a Jolt Award döntőjére (vesztettem... kellemetlen) és a
2003-as Referenceware Excellence A wardon a programozási könyvek kategó­
riájában (nyertem? ... de jó!)
Azóta azon dolgoztam, hogy a könyvet frissítsem és aktualizáljam a .NET
platform újabb verzióinak megfelelően. Eközben limitált darabszámmal meg­
jelentettem egy speciálls kiadványt, amely bemutatta a .NET 3.0 verzióját
(Windows Presentation Foundation, Windows Coomunication Foundation,
és a Windows Workflow Foundation), valamint áttekintést nyújtott olyan kü­
lönböző technológiákról, amelyek még megjelenésre vártak, és amelyeket
most LINQ néven ismerünk.
Jelenleg a könyv negyedik kiadását tar�a a kezében az Olvasó. Ez a kiadás
egyrészt tömören megismétli a korábbi ismereteket, azaz a .NET 3.5-ös verzió­
jában található legfontosabb változások magyarázatát, másrészt számos telje­
sen új fejezetet is tartalmaz, de ezen túlmenően több eddigi rész is jelentősen
kibővült.
Ahogy a korábbiakban megszokhattuk, ez a kiadás is egyszerű és érthető
nyelven ismerteti a C# nyelvet és a .NET -alaposztálykönyvtárakat. Soha nem
értettem azokat a szakírókat, akik úgy adják elő mondandójukat, hogy az
jobban hasonlít egy GRE-szászedetről írt tanulmányra, mint egy szakkönyv­
re. Munkám továbbra is arra fekteti a hangsúlyt, hogy a benne található in­
formációk segítségével jól használható szoftverrendszereket lehessen készí­
teni, így nem töltök túl sok időt az ezoterikus részletek tanulmányozásával,
hiszen ezekkel csak nagyon kevés olvasóm fog valaha is foglalkozni.
Bevezetés

Mi egy csapat vagyunk, az Olvasó és én

A szakírók az emberek igényes csopor�ának írnak (nekem már csak tudnom


kell- egy vagyok közülük). Tisztában vagyunk azzal, hogy egy szaftver el­
készítése - bármelyik platformmal történjen is (.NET, J2EE, COM stb.) -
rendkívül aprólékos munka, ugyanakkor nagyon specifikus a részleg, a cég,
az ügyfél vagy a tárgykör szerint. Hiszen lehet, hogy épp az elektronikus ki­
adói iparágban dolgozunk, vagy éppenséggel rendszert fejlesztünk az állam­
nak, esetleg a helyi önkormányzatnak, a NASA-nak vagy egy katonai rész­
legnek. Én a magam részéről gyermekeket oktató szaftvert fejlesztettem,
számos n-rétegű rendszert, valamint projekteket a gyógyászati és a pénzügyi
ágazatokban. Majdnem nulla az esélye, hogy annak a kódnak, amelyet egyi­
künk ír a munkájához, köze lesz ahhoz a kódhoz, amelyet a másikunk ír a sa­
ját munkájához (kivéve, ha véletlen épp együtt dolgoztunk korábban!).
Ezért szándékosan kerülöm az olyan mintapéldákat, amelyeket konkrét
iparághoz vagy programozási feladathoz köthetnénk: a C#, az objektumori­
entált programozás és a .NET 3.5 alaposztály könyvtárait ipari alkalmazások­
tól független példákan keresztül mutatom be. Ahelyett, hogy minden példám
adatok tömkelegét szolgáltatná, bérlistát számolna, vagy ehhez hasonlókkal
szolgálna, az én példáimhoz mindenkinek van valami köze: autókról szálnak
(ráadásként néhány geometriai struktúrával és alkalmazottakkal). És ez az a
pont, ahol összetalálkozunk.
Az én feladatom, hogy legjobb tudásarn szerint elmagyarázzam a C# prog­
ramozási nyelvet és a .NET platform alapvető jellegzetességeit. Ugyanígy
mindent megteszek azért, hogy ellássarn a kedves Olvasót különféle eszkö­
zökkel és stratégiákkal, amelyek lehetövé teszik, hogy a könyv alapján foly­
tatni lehessen a téma tanulmányozását.
Az olvasó feladata pedig, hogy a megszerezett tudást képes legyen alkal­
mazni saját programozási feladatában. Persze egyértelmű, hogy a projektek
nem állatnevekkel ellátott automobilok körül forognak, de hát pont erről
szólna az alkalmazott ismeret. Biztos lehet benne az Olvasó, hogy ha egyszer
megértette a könyvben bemutatott elveket, ismereteinek birtokában olyan
.NET -alkalmazásokat írhat, amelyek használhaták saját programozási kör­
nyezetükben.

xxxii
A könyv áttekintése

A könyv áttekintése

A Pro C# 2008 and the .NET 3.5 Platform (A C# és a Mícrosoft .NET 3.5.) negyedik
kiadása logikailag nyolc különálló részből áll, s ezek mindegyike további feje­
zeteket tartalmaz. Jó néhány témakör, például az alapvető C#-szerkezetek, az

objektumorientált programozás és a platformfüggetlen .NET fejlesztése kibő­


vült a korábbiakhoz képest, saját fejezetet kapott. Az új kiadás a .NET 3.0-3.5
programozás tulajdonságait (LINQ , WCF , WPF , WF stb.) összefoglaló fejeze­
teket is tartalmaz.

Megjegyzés A magyar változat kiadásának előkészítése során a SZAK Kiadó úgy döntött, hogy
az eredeti könyv tartalmát két kötetben jelenteti meg. Ezt a viszonylag nagy terjedelem indo·
kolja. Az alábbiakban mind a két kötet tartalmát ismertetjük, így biztosítva, hogy az is képet
kapjon a mű egészéről, akinek csak az egyik kötet kerül a kezébe. (Ezt a Bevezetést változat­
lan formában mind a két kötetben szerepeltetjük.)

Első kötet

1. rész: Bevezetés a C#-ba és a .NET platformba

Az első rész bemutatja a .NET platform alapvető természetét és számos olyan


fejlesztőeszközt (köztük több nyílt forráskodút), amelyeket .NET-alkalmazá­
sok fejlesztéséhez használhatunk. Mindeközben néhány alap C# programo­
zási nyelvi részlettel és a .NET-típusrendszerrel is megismerkedünk.

1. fejezet: A .NET filozófiája

Az első fejezet voltaképpen a könyv további részeinek a gerincét képezi. A ha­


gyományos Windows-fejlesztések világának vizsgálatával kezdjük, és feltár­
juk a korábbi hátrányokat is. A fejezet elsődleges célja azonban az, hogy meg­
ismerjünk jó néhány .NET -központú építőkövet, például az egységes nyelvi
futtatót (Common Language Runtime - CLR), a közös típusrendszert (Com­
mon Type System - CTS), az egységes nyelvi specifikációt (Common Langu­
age Specification - CLS) és az alaposztálykönyvtárakat. Egyszerű betekintést
nyerhetünk a C# programozási nyelvbe és a .NET-szerelvényformátumába,
valamint áttekintést kapunk a .NET platform platformfüggetlen természeté­
ről (a B függelék részletesen tárgyalja ezt a témakört).

xxxiii
Bevezetés

2. fejezet: (#-alkalmazások készítése


A fejezet célja, hogy különféle eszközök és technikák ismertetésével bevezes­
sen minket a C#-forráskódfájl fordításának folyamatába. Először a parancs­
soros fordító ( esc. ex e) és a C#-eredményfájlok használatát ismerhetjük meg.
A fejezet további részében pedig számos forráskódszerkesztőt és integrált fej­

lesztési környezetet (integrated development environments- IDEs) tanulmá­


nyozhatunk, többek között a következőket: TextPad, Notepad++, Sharp­
Develop, Visual C# 2008 Express és a Visual Studio 2008. Ezenkívül számos
olyan további programozási eszközt ismertetünk, amelyet bármely .NET-fej­
lesztőnek azonnal elő kell tudnia húzni a farzsebébőL

2. rész: A C# alapvető építőelemei

A 2. részben tárgyalt témakörök kiemelten fontosak, hiszen az itt megjelenő

tudnivalókat folyamatosan használjuk függetlenül attól, hogy milyen jellegű


.NET-szoftvert szándékozunk fejleszteni (webes alkalmazás, asztali grafikus
alkalmazás, forráskódkönyvtárak, Windows-szolgáltatások stb.). Itt derül ki,
hogy hogyan működnek a C# nyelv alapvető építőkövei, beleértve az objek­
tumorientált programozás (OOP) részleteit is. Ez a rész ismerteti továbbá,
hogy hogyan kell kezelni a futásidejű kivételeket, és bevezet minket a .NET
szemétgyűjtő szolgáltatásának részleteibe is.

3. fejezet: A C# alapvető építőelemei, l. rész


Ebben a fejezetben kezdjük el részletesen tanulmányozni a C# programozási
nyelvet. Megismerkedünk a Mai n O metódus szerepével és számos részlettel a
.NET platform belső adattípusaival kapcsolatban, beleértve a szövegesadat­
kezelést a system. String és a System. Text. stri ngBui l der névterek alkalmazá­
sával. Megvizsgáljuk továbbá az iterációs és a döntési szerkezeteket, a szűkítő
és a bővítő műveleteket, valamint az unchecked kulcsszó használatát

4. fejezet: A C# alapvető építőelemei, ll. rész


A 4. fejezet befejezi az alapvető C#-szemléletek meghatározását, a típustúl­
terhelt metódusok létrehozásától az out, ref és params kulcsszavakon keresz­
tüli paraméterek definiálásáig. Vizsgáljuk, hogyan kell adathalmazt létre­
hozni és kezelni, hogyan kell olyan adattípust készíteni, amelynek null is le­
het az értéke (a 7 és a?? operátorokkal), és megmutatjuk az értéktípus és a re­
Jerenciatípus közötti különbséget is.

xxxiv
A könyv áttekintése

5. fejezet: Egységbe zárt osztálytipusok definiálása

Ebben a fejezetben az objektumorientált programozással (OOP) ismerkedhe­


tünk meg a C# programozási nyelv használatán keresztül. Először meghatá­
rozzuk az OOP pilléreit (beágyazás, származtatás és polimorfizmus), majd a
fejezet további részében azt nézzük meg, hogyan kell konstruktorok, tulaj­
donságok, statikus tagváltozók, kanstansok és csak olvasható fájlok haszná­
latával robusztusosztály-típusokat létrehozni. Végezetül ismerte�ük a részle­
ges típus definícióját és a C# XML-kódjának dokumentációs szintaktikáját.

6. fejezet: A származtatás és a polimorfizmus

Ebben a részben az OOP maradék pilléreit (származtatás és polimorfizmus)


vizsgáljuk meg; ezek a segítségével összetartozó osztálytípusok családját
hozha�uk létre. Megnézzük a virtuális metódusok és absztrakt metódusok
(és az absztrakt alaposztályok) szerepét, valamint a polimorf interfész termé­
szetét Végül, de nem utolsósorban, ez a fejezet ismerteti a .NET platform leg­
fontosabb alaposztályának, a system. obj ect osztálynak a szerepét is.

7. fejezet: Strukturált hibakezelés

Ez a fejezet elsősorban azt mutatja be, mit kell tenni a strukturális kivételke­
zelés segítségével a futási időben keletkezett anomáliákkal. Nemcsak az ilyen
jellegű problémák megoldására szolgáló C#-kulcsszavakat ismerjük itt meg
(try, catch, throw és fi nal l y) , hanem az alkalmazásszintű és a rendszerszintű
kivételek közötti különbséget is. Megismerhetünk továbbá többféle, a Visual
Studio 2008-ban található eszközt, amelyek segítségével hibakeresést végez­
hetünk a figyelmen kívül hagyott kivételek között.

8. fejezet: Az objektumok életciklusa

Ennek a résznek az utolsó fejezete azt tárgyalja, hogy hogyan kezeli a CLR a
memóriát a .NET szemétgyűjtőjének a segítségéveL Megérthe�ük az alkal­
mazásgyökerek, az objektumnemzedékek és a system. GCType szerepét. Az
alapok megismerése után, a fejezet további része az eldobható objektumokkal
(az ro i sposab l e intedészen keresztül) és a véglegesítő folyamattal (a sys­
tem. obj ect. Fi nal i ze O metóduson keresztül) foglalkozik.

XXXV
Bevezetés

3. rész: Haladó programozási szerkezetek a C#-ban

A könyv 3. része ismerteti a C# nyelv haladóbb jellegű (de ugyancsak na­


gyon fontos) elveit. Az interfészek és metódusreferenciák vizsgálatával befe­
jezzük a .NET típusrendszerével való ismerkedést. A továbbiakban betekin­
tést nyerhetünk a generikus típusok és a számos új C# 2008 nyelvi elem szere­
pébe, valamint a LINQ-ba is.

9. fejezet: Interfészek használata


Ennek a fejezetnek az anyagát az interfészalapú programozást is magába fog­
laló objektumalapú fejlesztés alkotja. Megismerhetjük belőle, hogy hogyan
kell olyan típusokat definiálni, amelyek többszörös viselkedést támogatnak,
hogyan lehet ezeket a viselkedéseket futásidőben feltérképezni, és hogyan
lehet szelektíven elrejteni bizonyos viselkedéseket az explicit interfészmegvaló­
sítás segítségéveL Továbbá a számos előre definiált .NET-interfésztípus meg­
ismeréséhez el kell sajátitanunk azoknak az egyedi interfészeknek a haszná­
latát, amelyek ad hoc jellegű eseményarchitektúra készítéséhez szükségesek.

1 O. fejezet: Gyűjtemények és generikus tipusok


Ez a fejezet a system.collections névtérgyűjtemény típusainak tanulmányozá­
sával kezdődik; ezek már a .NET platform kezdeti kiadásának is részei voltak.
Noha a .NET 2.0 megjelenése óta a C# programozási nyelv lehetővé teszi generi­
kus típusok használatát, ahogy a későbbiekben kiderül, a generikus programozás
lényegesen megnöveli az alkalmazás teljesítményét és a típusbiztonságot Nem­
csak azt vizsgáljuk meg, hogy milyen különböző generikus típusok léteznek a
system.collections.Generic névtérben, de azt is, hogyan készíthetjük el saját

generikus metódusainkat és típusainkat (megszorításokkal vagy anélkül).

11. fejezet Metódusreferenciák, események és lambdák


A 11. fejezetnek az a célja, hogy érthetővé tegye a metódusreferencia-típusokat.
Leegyszerűsítve: a .NET-metódusreferencia olyan objektum, amely az alkal­
mazás más metódusaira mutat. Ez a minta olyan rendszerek készítését teszi le­
hetővé, amelyek több objektum összekapcsalódását engedik meg kétirányú
együttműködés segítségéveL A .NET-metódusreferenciák vizsgálata után meg­
ismerkedünk a .NET event kulcsszavával, amelyet arra használunk, hogy egy­
szerűsítsük a nyers metódusreferencia programozásának kezelését. Végezetül
megvizsgáljuk a C# 2008 lambda-operátorát (=>), és feltárjuk a kapcsolatot a
metódusreferenciák, a névtelen metódusok és a lambda-kifejezések között.

xxxvi
A könyv áttekintése

12. fejezet: lndexerek, operátorok és mutatók

Ez a fejezet a C# programozási ismereteinket mélyíti el számos haladó prog­


ramozási technika segítségéveL Megismerhe�ük, hogyan kell operátorokat
túlterhelni, és hogyan kell a típusaink számára egyedi konverziós rutinokat
írni (akár implicit, akár explicit formában). Megtanulha�uk azt is, hogy ho­
gyan kell létrehozni és használni a típusindexeló'ket, és hogyan kell kezelni a
C-stílusú mutatókat a "nem biztonságos" kódkörnyezetben.

13. fejezet: C# 2008 nyelvi újdonságai

A .NET 3.5 kiadásában a C# nyelv számos új programozási eszközzel bővült,


amelyek közül jó néhány arra szolgál, hogy lehetövé tegye a LINQ API hasz­
nálatát (erről a 14. fejezetben többet is megtudhatunk). Megismerjük a lokális
változók implicit típussal történő ellátásának a szerepét, a részleges metódus,
az automatikus tulajdonságok, a bővítő metódusok, a névtelen típusok és az
objektuminicializáló szintaxis használatát.

14. fejezet: Bevezetés a nyelvbe ágyazott lekérdezésekbe (LINQ)

Ez a fejezet ismerteti a LINQ-et (Language Integrated Query - nyelvi integ­


rált lekérdezés), amelyet nyugodtan nevezhetünk a .NET 3.5 legérdekesebb
részének. Ahogy a fejezet későbbi részéből kiderül, a LINQ segítségével lehe­
tőségünk nyílik erősen típusos lekérdezéseket írni, amelyeket számos LINO­
"
célpontra alkalmazhatunk, hogy a szó legtágabb értelmében "adatot kezel­
hessünk. Megtanuljuk a LINQ objektumokra történő alkalmazását, ennek se­
gitségével a LINO-kifejezéseket adattárolókra (tömbök, kollekciók, egyedi ti­
pusok) fogjuk tudni alkalmazni. Ez a tudás nélkülözhetetlen lesz akkor, ami­
kor azt vizsgáljuk meg, hogyan kell LINO-kifejezéseket relációs adatbázi­
sokon (a LINQ az ADO-n keresztül) és XML-dokumentumokon (a LINQ az
XML-en keresztül) használni.

4. rész: Programozás .NET-szerelvényekkel

A negyedik rész a .NET-szerelvényformátum részleteit taglalja. Nemcsak azt


tanulha�uk meg, hogyan kell .NET-kódkönyvtárakat telepíteni és konfigurálni,
de azt is, hogy mi a .NET bináris kép belső összeállítása. Ebben a részben lesz
szó a .NET-attribútumok szerepéről és a többszálú alkalmazások létrehozásá­
nak módjáról is. A későbbi fejezetek néhány haladó témát érintenek, például az
objektumkörnyezetet, a köztes nyelvi kódot és a dinamikus szerelvényeket.

xxxvii
Bevezetés

15. fejezet: A .NET-szerelvények

Első megközelítésben a szerelvény nem más, mint egy felügyelt *.dll vagy
* . ex e bináris fájl leírására szolgáló kifejezés. Ám a .NET -szerelvények törté­
nete sokkal több ennél. Ebben a fejezetben megtudjuk, mi az egyfájlos és a
többfájlos szerelvények közötti különbség, és, hogy ezeket hogyan kell készí­
teni és telepíteni. Megvizsgáljuk, hogyan kell privát és megosztott szerelvé­
nyeket konfigurálni XML-alapú *. confi g fájlokkal és kibocsátó házirenddel
rendelkező szerelvényekkel. Mindeközben megtudhatjuk, hogy milyen a
GAC (Global Assembly Cache - globális szerelvénytár) belső struktúrája, és
mi a .NET keretrendszer konfigurációs segédprogramjainak a szerepe.

16. fejezet: Tipusreflexió, késői kötés és attribútumalapú


programozás

A 16. fejezetben folytatjuk az isrnerkedést a .NET-szerelvényekkel. A sys­


tem. Re flee ti on névtér használatával kapcsolatban azt vizsgáljuk, a futási
időben hogyan valósul meg a típusmeghatározás folyamata. Az ilyen jellegű
típusok használatával lehetőségünk van olyan alkalmazások létrehozására,
amelyek futás közben képesek a szerelvények rnetaadatait olvasni. Kiderül,
hogyan lehet a késői kötés segítségével futási időben dinamikusan betölteni és
létrehozni típusokat. A fejezet utolsó témaköre - mind a szabványos, mind az
egyedi - .NET-attribúturnok szerepének ismertetése. Az egyes térnakörök
hasznosságának szernléltetésére a fejezet konklúziójaként bemutatunk egy
kiterjeszthető Windows Forms-alkalrnazást.

17. fejezet: Folyamatok, alkalmazástartományok és


objektumkörnyezetek

A szerelvényekről szóló tudnivalók után ez a fejezet a betöltött .NET végre­


hajtható fájlok felépítését ismerteti. Az elsődleges cél a folyamatok, az alkal­
mazástartományok és a kontextusbeli határok közötti kapcsolat illusztrálása.
Ezek alapozzák meg a következő fejezet témáját: a többszálú alkalmazások
készítésének módját.

18. fejezet: Többszálú alkalmazások készítése

Ez a fejezet azt mutatja be, hogy hogyan kell többszálú alkalmazásokat készí­
teni, és szárnos olyan technikát ismertet, amelyeket a szálbiztos kód készítésé­
hez alkalmazhatunk. A fejezet elején átismételjük a .NET rnetódusreferencia­
típusait, hogy megértsük, valójában hogyan támogatják a metódusreferenciák

xxxviii
A könyv áttekintése

az aszinkronfüggvény-hívásokat. Majd megvizsgáljuk a system. Thread névtér­


ben található típusokat. Ezek között számos olyan van (Thread, Threadstart) ,
amelynek segítségével könnyen lehet újabb végrehajtási szálakat készíteni. Vé­
gül megismerkedünk a Backgroundworker típussal, amely nagyban megköny­
nyíti a felhasználói felületről történő szálkészítést.

19. fejezet: A köztes nyelv (Cll) és a dinamikus szerelvények


A 4. rész utolsó fejezetének szerepe kettős. Először a korábbinál részletesebben
megvizsgáljuk ClL szintaktikáját és szemantikáját, majd a system. Re fl ee ti on.
Emi t névtér szerepét muta�uk be. Ezeknek a típusoknak a használatával lehe­
tőségünk nyílik olyan szaftver készítésére, amely képes .NET-szerelvényeket
futásidőben generálni a memóriába. Az olyan szerelvényeket, amelyeket a
memóriában definiálunk és futtatunk, dinamikus szerelvényeknek nevezzük.

Második kötet

5. rész: Bevezetés a . N ET alaposztálykönyvtáraiba

Ha elérkeztünk ehhez a részhez, akkor már biztos tudásunk van a C# nyelvről


és a .NET- szerelvényformátumok részleteiről. Az ötödik rész továbbfejleszti
ezeket az ismereteket azáltal, hogy számos általánosan használt alaposztálybeli
szolgáltatást mutat be, többek között a fájl IjO-t és az adatbáziselérést az
ADO.NET segítségéveL Ez a rész tárgyalja továbbá az elosztott alkalmazások
készítésének témakörét a Windows Communication Foundation (WCF) segít­
ségéve!, valarnint azokat a munkafolyamat-engedélyezett alkalmazások készí­
tését, amelyek a Windows Workflow Foundation (WF) API-t használják.

20. fejezet: Fájlműveletek és elkülönitett tárolás


A system. ro névtér segítségével lehetőségünk van a számítógép fájl- és
könyvtárszerkezetének elérésére. Ebben a fejezetben megismerjük, hogyan
kell programozottan könyvtárrendszert létrehozni (és törölni), valamint, ada­
tokat különböző folyamokba (fájlalapú, sztringalapú, memóriaalapú stb.) be-,
illetve kimozgatni. A fejezet utolsó része megvizsgálja az elkülönített tárolást,
amelynek segítségével lehetőségünk van biztonságos helyen külön tárolni az
adatokat függetlenül attól, hogy a célgép biztonsági beállításaitóL Hogy
megértsük a system. ro. r so l a ted API bizonyos elveit, áttekintést kell kap­
nunk a kódhozzáférés-szabályozásról (Code Access Security- CAS).

xxxix
Bevezetés

21. fejezet: Bevezetés az objektumsorosttás világába

Ez a fejezet a .NET-platform objektumsorosító szolgáltatásába nyújt betekin­


tést. Egyszerűen szólva a sorosítás segítségével lehetőségünk nyílik arra, hogy
egy objektum (vagy a kapcsolódó objektumok egy halmazának) állapotát egy
folyamban tároljuk a későbbi használat céljából. A deszerializálás (a sorosítás
ellentétes művelete) az a folyamat, amelynek segítségével kivehetjük az ob­
jektumot a folyamból, és a memóriában az alkalmazás számára használható
formába hozhatjuk Az alapok elsajátítása után megtanulhatjuk, hogyan kell
egyedivé tenni a sorosítási folyamatot az rserializableinterface és a .NET­
attribútumok halmazának segítségéveL

22. fejezet: ADO.NET 1. rész: Az élő kapcsolat

Az adatbázisokról szóló két fejezet közül az elsőben az ADO.NET programo­


zási API-ról kaptunk ismereteket. Ez a rész bevezeti a .NET adatszolgáltató
szerepét, valamint azt, hogy hogyan kell a relációs adatbázissal kommuni­
kálni az ADO.NET élő kapcsolatán keresztül. Ez a kapcsolati objektumokat, a
parancsobjektumokat és az adatolvasó objektumokat jelenti Ezenkívül ez a
fejezet mutatja be az egyedi adatbázis létrehozásának módját, és ismerteti
azokat az adatelérési könyvtárakat, amelyeket majd a könyv további részé­
ben használunk.

23. fejezet: ADO.NET 2. rész: A bontott kapcsolat

Ez a fejezet az adatbázis-kezelés különböző módokon történő manipulálási


lehetőségeit tárgyalja tovább, jelen esetben az ADO.NET bontott kapcsolaton
keresztül. Megismerkedhetünk a Dataset típus és az adatfeldolgozó objek­
tumok szerepével, valamint számos Visual Studio 2008 eszközzel, amelyek
nagyban megkönnyítik az adatvezérelt alkalmazások létrehozását. Mind­
eközben rávilágítunk arra, hogyan kell a DataTab l e-objektumokat felhaszná­
lói felületi elemekhez kapcsolni, olyanokhoz, mint például a Windows Forms
API G ridvi ew típusa.

24. fejezet: A LINQ API programozása

A 24. fejezet a LINQ programozási modellt mutatja be, pontosabban a LINQ


"
"az objektum irányába részét. Megvizsgáljuk, hogyan kell LINQ-lekérdezé­
seket alkalmazni relációs adatbázisokon, Dataset-objektumokon és XML-do­
kumentumokon. Mindeközben megtanuljuk az adatkontextus-objektum, va­
lamint az sq lmetal . exe segédprogram szerepét, és megvizsgáljuk a Visual
Studio 2008 különböző LINQ-támogató funkcióit.

x l
A könyv áttekintése

25. fejezet: A WCF


A .NET 3.0 bevezetett egy teljesen új API-t, a WCF-et, amely lehetőséget ad
arra, hogy szimmetrikus módon, függetlenül az alatta található rétegek fel­
építésétől elosztott alkalmazást fejlesszünk Ez a fejezet feltárja a WCF-szol­
gáltatások, -gazdagépek és -kliensek készítésének lehetőségeit. A WCF-szol­
gáltatások rendkívül flexibilisek, a gazdagépek és a kliensek XML-alapú kon­
figurációs fájlokat használhatnak, hogy deklaratív módon adjanak meg cí­
meket, kötéseket és szerződéseket.

26. fejezet: A Windows Workflow Foundation- Bevezetés


A .NET 3.0 a WCF mellett bevezette a WF API-t is, amely lehetőséget ad arra,
hogy munkafolyamatokat definiáljunk, futtassunk és monitorozzunk. Ennek
segítségével komplex üzleti folyamatok modellezhetők. Megtanuljuk a WF
általános célját, ahogy azt is, hogy mi a szerepe az aktivitásoknak, a munka­
folyamat tervezőnek, a munkafolyamat futtatási motomak, és a munkafo­
lyamat-engedélyezett forráskód könyvtáraknak

6. rész: Felhasználói felületek

Általánosan hibás feltételezés a .NET platformmal kapcsolatban az, hogy ez a


keretrendszer kizárólag a webalapú felhasználói felületekkel foglalkozik (ez
feltehetően a ".NET" kifejezés miatt van, hiszen felidézi az "Internet" szót,
azaz a "webprogramokat"). A .NET kiemelkedő módon támoga�a a webal­
kalmazások készítését, a könyv ezen része azonban a hagyományos felhasz­
nálói felületekre koncentrál két grafikus felület keretrendszerén, a Windows
Forms és a WPF használatán keresztül.

27. fejezet: Windows Forms-programozás


Az eredeti asztali grafikus felhasználói felület, amely a .NET platformmal
együtt kapható, a Windows Forrns. Ez a fejezet ismerteti a felhasználói felület
keretrendszerének szerepét, és bemuta�a, hogyan kell főablakokat, párbe­
szédablakokat és menürendszereket készíteni. Feltárja továbbá az űrlap­
származtatás szerepét, és azt, hogy hogyan kell kétdimenziós adatot rajzolni
a system. Drawin g névtér segítségéveL A témakör illusztrálására a fejezet vé­
gén készítünk egy (korlátozott funkcionalitással bíró) rajzolóalkalmazást.

x li
Bevezetés

28. fejezet: A WPF és az XAML

A .NET 3.0 teljesen új grafikus felhasználói felületet vezetett be, amelyet WPF­
nek nevezett el. Röviden, a WPF kimagaslóan interaktív és médiában gazdag
front endek készítését teszi lehetővé asztali alkalmazásokhoz (és indirekt mó­
don webalkalmazásokhoz). A Windows Forrnsszal ellentétben, ez a túltelített
felhasználóifelület-keretrendszer számos kulcsfontosságú szolgáltatást (2D-s és
3D-s grafika, animációk, gazdag dokumentumok stb.) integrál egyetlen egysé­
gesített objektummodellbe. Ebben a fejezetben megkezdjük az ismerkedést a
WPF-fel és a kiterjeszthető alkalmazásjelölő nyelvvel (Extendable Application
Markup Language- XAML). Megtanuljuk, hogyan kell XAML-mentes, csak
XAML-t használó és a kettő kombinációjából felépülő WPF-programokat létre­
hozni. A fejezet végén készítünk egy egyedi XAML-nézegetőt, amelyet a to­
vábbi WPF-fel foglalkozó fejezetekben is használni fogunk.

29. fejezet: Programozás WPF-vezérlöelemekkel

Ebben a fejezetben megtudjuk, hogy hogyan kell dolgozni a WPF-vezérlő­


elem tartalommodelljével, és érintünk számos vezérlőelemekkel kapcsolatos
témát is, például a függőségi tulajdonságokat és irányított eseményeket. Jó
néhány WPF-vezérlőelem használatát bemuta�uk, s szintén ebben a fejezet­
ben magyarázzuk el az elrendezéskezelő, -vezérlő utasítások és a WPF-adat­
kötés modelljének a használatát.

30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

Az utolsó fejezet a WPF tárgyalását három látszólag független téma vizsgála­


tával zárja. A WPF grafikus renderelő szolgáltatásának általában szüksége
van arra, hogy egyedi erőforrásokat definiáljunk hozzá. Ezeknek az erőfor­
rásoknak a használatával lehetőségünk van egyedi WPF-animációk készíté­
sére, és grafikát, erőforrásokat és animációkat alkalmazva egyedi témák ké­
szítését fogjuk tudni megoldani WPF-alkalmazások számára. A különböző
témák összegzéseképpen a fejezet utolsó részében azt illusztráljuk, hogyan
kell futásidőben egyedi grafikus témákat alkalmazni.

xlii
A könyv áttekintése

7. rész: Webes alkalmazások fejlesztése ASP.NET


segitségével

A 7. rész az ASP.NET programozási API használatával készített webes al­


kalmazásokat vizsgálja. Az ASP.NET szándékosan úgy lett kialakítva, hogy
az asztali felhasználói felület létrehozását modellezze, úgy, hogy szabványos
HTIP kérésekre/válaszokra helyez egy eseményvezérelt, objektumorientált
keretrendszert.

31. fejezet: ASP.NET weboldalak készítése

Ez a fejezet vezet be bennünket azASP.NET használatával történő webes al­


kalmazásfejlesztésbe. A kiszolgálóoldali szkripteket felvál�ák az igazi objek­
tumorientált nyelvek (mint a C#, VB.NET és hasonlók). Bemuta�uk egy
ASP.NET weboldal készítését, valamint az alatta található programozási mo­
dellt és az ASP.NET egyéb kulcsfontosságú elveit, például azt, hogy hogyan
kell webkiszolgálót választani, valamint a web. con fi g fájlok használatát

32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

Ez a fejezet azokkal a vezérlőelemekkel foglalkozik, amelyek a belső vezérlő­


fát töltik fel adattal. Megismerjük az alapvető ASP.NET webes vezérlőeleme­
ket, többek között a ellenőrző vezérlőelemet, a belső oldali navigációs vezér­
lőelemeket és a különböző adatkötő műveleteket Ugyancsak ennek a fejezet­
nek a feladata, hogy bemutassa a mesteroldalak és az ASP.NET térnamatorjá­
nak a szerepét, amely a hagyományos stíluslapok szerveroldali megfelelője.

33. fejezet: ASP.NET állapotkezelési technikák

Ez a fejezet kiterjeszti az eddigi ASP.NET-ismereteinket, ugyanis megvizs­


gálja, milyen különböző módokon lehet állapotot kezelni a .NET alatt. Ahogy
a klasszikus ASP, az ASP.NET is egyszerűen teszi lehetővé sütik létrehozását,
ahogy alkalmazásszintű és munkafolyamat-szintű változók használatát is. Az
ASP.NET azonban bevezet egy új állapotkezelő technikát: az alkalmazás­
gyorsítótárat Ha már tudjuk, milyen módokon lehet állapotot kezelni az
ASP.NET alatt, megismerkedünk a syst:em. Ht:t:pApp l i cat: i on alaposztály sze­
repével (a G l oba l asax fájlon belül), valamint azzal, hogy hogyan kell dina­
.

mikusan megváltoztatni a webalkalmazásunk futásidejű viselkedését a


web. con fi g fájl segítségéveL

xliii
Bevezetés

8. rész: Függelékek

A könyv utolsó része két fontos témát vizsgál meg, amelyek valójában nem il­
leszkedtek szervesen a könyv felépítésébe, ezért a függelékbe kerültek. A füg­
gelék kiteljesíti a C#-pal és .NET platformmal kapcsolatos ismereteinket,
megvizsgáljuk ugyanis, hogyan kell régebbi kódokat integráini a .NET-alkal­
mazásainkba, valamint hogyan kell a .NET-fejlesztést a Windows operációs
rendszer családján kívül használni.

A függelék: A COM és a .NET együttműködése


Azok, akik már programoztak Windows operációs rendszer alatt a .NET plat­
form használata előtt, nagy valószínűséggel ismerik a Component Object
Modelt (COM). Noha a COM-nak és a .NET-nek semmi köze egymáshoz
(azon túl, hogy mindkettő a Microsoft szerzeménye), a .NET platform teljes
névteret biztosít ahhoz (system. Ru nti me. Interopservi c es) , hogy .NET-szoft­
verek COM-komponenseket használjanak, és fordítva. Ez a függelék az együtt­
működési réteget muta�a be egészen részletesen; ez a téma azért nagyon fon­
tos, hogy a korábban megírt programjainkat át tudjuk menteni az újonnan
készített .NET-alkalmazásainkba.

B függelék: Platformfüggetlen .NET-fejlesztés a Monóval


Végül, de nem utolsó sorban a B függelék a .NET nyílt forráskódú implemen­
tációjával, a Manóval foglalkozik. A Mono segítségével lehetőségünk van
gazdag tulajdonságokkal rendelkező .NET-alkalmazás létrehozására, telepí­
tésére és futtatására különböző operációs rendszereken, mint például a Max
OS X, Solaris, AIX és számos Linux-disztribúció. Mivel a Mono nagyjából
kompatibilis a Microsoft .NET platformjával, így már tisztában vagyunk az­
zal, hogy rnit tud nyújtani számunkra. Ezért a függelék a Mono installálását
tárgyalja, valamint azt, hogy, rnilyen fejlesztői eszközök állnak a rendelkezé­
sünkre, és hogyan működik a Mono futtatómotorja.

xliv
Öt szabadon Letölthető fejezet - még több információ

Öt szabadon letölthető fejezet


még több információ
Azoknak, akiknek a 33 fejezet és a két függelék nem lenne elég, szabadon le­
tölthetnek további öt fejezetet. A könyv korábbi verziói három olyan fejezetet
tartalmaztak, amelyeket a Windows Forms fejlesztésének szenteltünk (egyedi
vezérlők vizsgálatával egyetemben), egy másik fejezetet a .NET remoting ré­
tegének ( system. Run ti me. Remoti ng és társai), és a végül egy fejezet a hagyo­
mányos XML-webszolgáltatások készítésével foglalkozott az ASP.NET Web
Service projekt sablonjainak a segítségével .
A könyv jelen kiadása nem tartalmazza ezt az öt fejezetet. Ennek az a leg­
főbb oka, hogy a .NET 3.0-ban a WCF és a WFP API-k rendre a .NET-remo­
ting/XML-webszolgáltatások és a Windows Forms API-k örökösei. Ha mé­
lyebben szeretnénk elmerülni a Windows Forms világában (azon túl, amit a
27. fejezet nyújtani tud), vagy meg akarjuk nézni, hogyan kell használni a
(örökségül hagyott) .NET-remotingot és az XML-webszolgáltatás-API-t, egy­
szerűen csak meg kell nézni az Apress honlapján a megfelelő részt:

http:j/apress.com/book/view/1590598849.

Az itt található linkre kattintva digitális formában letölthe�ük a könyv to­


vábbi fejezeteit, ha sikerült válaszolni a könyv szövegéből véletlenszerűen
feltett kérdésekre. (A letölthető fejezetek csak angol nyelven érhetők el - a
SZAK Kiadó megjegyzése).

A könyv forráskódjának igénylése

A könyvben szereplő összes forráskód-mintapélda (az öt szabadon letölthető


további fejezetben levők is) szabadon és azonnal elérhető az Apress honlap­
jának Source Code/Download részlegérőL Egyszerűen gépeljük be a
http:/ jwww.apresss.com címet, válasszuk ki a Source Code/Download lin­
ket, és keressük meg a megfelelő címet, és, töltsük le az önmagát kicsomagoló
*.zi p fájlt. A forráskódokat fejezetenként osztottuk fel.
Figyelem: a fejezetekben a Forráskód megjelölés, mint amilyen az alábbi
is, arra utal, hogy a szóban forgó példákat be lehet tölteni a Visual Studio
2008-ba további vizsgálódások és módosítások céljából:

Forráskód Ez a forráskód-megjelölés egy kiválasztott könyvtárra hivatkozik.

xlv
Bevezetés

Egyszerűen nyissuk meg a *. sl n fájlt a megfelelő alkönyvtárból. Ha nem


használjuk a Visual Studio 2008-at (lásd a 2. fejezetet további integrált fejlesz­
tői környezetekért), akkor manuálisan töltsük be a felkínált forráskódfájlokat
a választott fejlesztői eszközbe.

Megjegyzés A magyar nyelvü változatban a forráskódok megjegyzéseit lefordítottuk. A letölthe­


tő forráskódok fordítása nem állt módunkban, így azok teljesen angol nyelvüek. Ilyen módon in­
kább megfelelnek a letölthető kódok használati céljának- a SZAK Kiadó megjegyzése.

A lehetséges javftások

Elképzelhető, hogy a könyv végigolvasása során nyelvtani vagy forráskódbe­


li hibákat vél felfedezni a kedves Olvasó, bár remélem, hogy erre nem kerül
sor. Ha mégis, elnézést kérek. Az aktuális hibalistát az Apress weboldalról
lehet igényelni (amely szintén a könyv honlapján található meg), és ugyan­
csak itt található az elérhetőségem, amelyen értesíteni tudnak.

Elérhetőségern
Ha bármilyen kérdés merül fel a könyvhöz tartozó forráskódokkal kapcso­
latban, vagy szükség van valamelyik példa tisztázására, vagy egyszerűen
csak véleményt szeretne nyilvánítani a .NET platformról, nyugodtan küldjön
levelet a következő e-mail címre (hogy biztosak legyünk benne, hogy a levele
nem kerül a kéretlen levelek közé, kérem, tegye a tárgymezőbe a "C#FE"
szöveget): atroelsen@Intertech.com.
Annak ellenére, hogy megpróbálak minden levélre lehetőség szerint vála­
szolni, sajnos, ahogy mindnyájan, időnként nagyon elfoglalt vagyok. Ha nem
válaszolak egy-két héten belül, akkor ez nem jelenti azt, hogy nem akarok
foglalkozni a problémával, csak épp nagyon nem érek rá (vagy ha szeren­
esém van, valahol épp nyaralok).
Nos, akkor, köszönöm, hogy megvásáralta a könyvemet (vagy legalábbis,
hogy belenéz a könyvesboltban, miközben azon gondolkodik, hogy megve­
gye-e). Remélem, élvezni fogja a kedves Olvasó a tanulmányozását, és hasz­
nosítani tudja az újonnan szerzett ismereteket.

Minden jót!
Andrew Troelsen

xlvi
5. rész

Bevezetés
a .NET
alap osztály­
könyvtáraiba
HUSZADIK FEJEZET

Fájlműveletek és elszigetelt
tárolás

Teljes értékű asztali alkalmazás készítésekor fontos, hogy a program képes


legyen eltárolni a felhasználói munkamenetek információit. Ez a fejezet a
.NET keretrendszer szemszögéből mutat be számos, 1/0-val kapcsolatos té­
makört. Elsőként megismerkedünk a syst:em. IO névtérben definiált alapvető
típusokkal, és megvizsgáljuk, hogy hogyan módosíthatjuk programozottan a
számítógép mappa- és fájlstruktrúráját, majd a különböző karakter-, bináris,
sztring- és memóriaalapú adattárolók olvasásával és írásával foglalkozunk.
Miután megismertük a fájlok és könyvtárak kezelését az alapvető 1/0 típu­
sok segitségével, bemutatjuk az elszigetelt tároló használatát (a syst:em. IO. Isol a­
t:edst:orage névtéren keresztül). Ennek segítségével a szigorúbb biztonsági kör­

nyezetben futó alkalmazások a felhasználói és az alkalmazásadatokat bizton­


ságosan, korlátozott fájlműveletekkel hozhatják létre. Ennek az API-nak a be­
mutatásához először a .NET platform egyik biztonsági megoldását, a CAS-t
(Code Access Security, kóderedet-alapú biztonság) kell megvizsgálnunk
A CAS-t gyakran az elszigetelt tárolóval együtt használjuk.

A System.IO névtér

A .NET keretrendszerben a fájlalapú (és a memóriaalapú) be- és kiviteli (1/0)


szolgáltatásokat biztosító alapvető osztálykönyvtárak helye a syst:em. IO név­
tér. Mint minden névtér, a syst:em. IO is számos osztályt, interfészt, felsorolt
típust, struktúrát és metódusreferenciát határoz meg, amelyek túlnyomó
többsége az rnscorl i b. dll fájlban található. A Syst:em. IO névtér további tagjait
a syst:em. dll szerelvény definiálja (a Visual Studio 2008 projektek az alapér­
telmezés szerint rnindkét szerelvényre hivatkoznak, így az itt említett típuso­
kat további teendők nélkül, azonnal használhatjuk).
20. fejezet: Fájlműveletek és elszigetelt tárolás

A system. ro névtér több típusa a fizikai mappák és fájlok programozott kezelé­

sére összpontosít. Vannak azonban további tipusok, amelyek a sztring-és me­


móriamanipulációkat támoga�ák. A 20.1. táblázat bemuta�a a system. ro alap­
vető (nem absztrakt) osztályait.

. .
. .. . .- . . .' . . '�:� ';' . � .. ���
��
. - ' . �
. . . ' '. . �-.

BinaryReader, Primitív adattípusok (egész számok, logikai értékek,


Binarywriter sztringek stb.) bináris értékként történő tárolását és
visszaolvasását teszik lehetövé.

BufferedStream Byte-folyamok ideiglenes tárolójaként szolgál, ezeket a


későbbiek során más tárolókra menthetjük.

Directory, Ezekkel a számítógép könyvtárstruktúráját módosíthat­


Directoryinfo juk. ADi rectory típus a funkcionalitást statikus tagok
segítségével teszi elérhetővé. ADi rectoryinfo típus
hasonló működést eredményez az érvényes objektumre­
ferencíából.

Driveinfo A számítógép meghajtóiról szalgáltat részletes infor­

mációt.

File, Fil e Info Ezekkel a számítógépen található fájlokat módosíthatjuk.


A File típus a funkcionalitást statikus tagok segítségével

teszi elérhetővé. A File Info típus hasonló működést


eredményez az érvényes objektumreferencíából.

Filestream Véletlen fájlhozzáférést (pl. keresési képességeket) tesz


lehetövé, és az adatokat byte-folyamként jeleníti meg.

Filesystemwatcher Egy megadott könyvtárban található külső fájlok meg­


változásának figyelését teszi lehetövé.

Memorystream Véletlen hozzáférést biztosít a memóriában tárolt adat­


folyamhoz (nem pedig egy fizikai fájlhoz).

Path Olyan system. String típusú objektumokon hajt végre


műveleteket, amelyek platformfüggetlen fájl-, illetve
könyvtárelérési utakról tartalmaznak információkat

streamwriter, Ezekkel fájlokban tárolhatunk (illetve azokból visszaol­


StreamReader vashatunk) szöveges információkat. A véletlen fájlhoz­
záférést nem támogatják.

4
A Directory(lnfo) és File(lnfo) típusok

r:�y��:,_: �-.

StringWriter,
;·.-. . ·-·� ' . .. . -- - .· -, ... ..

A StreamReader és Streamwriter típusokhoz hasonló­


.� ,· ·,· '

StringReader an ezek az osztályok is szöveges információkat kezel­


nek, háttértárolóik azonban nem fizikai fájlok, hanem
sztringpufferek.

20. 1. táblázat: A System.IO névtér kulcsfontosságú tagjai

Az imént felsorolt konkrét osztálytípusok mellett a system. IO több felsorolt


típust és olyan absztrakt osztályt (stream, TextReader, Textwriter stb.) is defi­
niál, amelyek egy megosztott polimorf interfészt definiálnak az összes le­
származott számára. A későbbiekben számos ilyen típust bemutatunk.

A Directory(lnfo) és File(lnfo) tipusok

A system. IO névtér négy típus segítségével teszi lehetővé a számítógép


könyvtárstruktúrájának és különálló fájljainak a kezelését. Az első két típus, a
Directory és a File, különböző statikus tagokon keresztül biztosítja az objek­
tumok létrehozását, törlését, másolását és mozgatását. Az ezekhez szorosan
kapcsolódó Fileinfo és Directoryinfo típusok hasonló működést tesznek el­
érhetővé példányszintű metódusokkal (így ezeket a new kulcsszóval le kell
foglalni). A 20.1. ábrán látható, hogy a Directory és Fi l e típusok közvetlenül
a system.object osztályt bővítik ki, míg a Directoryinfo és Fileinfo osztá­
lyok az absztrakt Fi l esysteminfo típusból származnak.

·lobj<!ct &J

�355

® ®
·-;;�-s;;;;;,;,;--·· ®'1 Fle
Abstroct Class i' Class

·��,.....------�--·-!'·1\·-·-=-·-····�--·'

flelnfo &J Directoryinfo ®


Class Class
+ FileSysteminfo .. FileSysteminfo

20. 1. ábra: A File- és Directory-központú típusok

5
20. fejezet: Fájlműveletek és elszigetelt tárolás

Általánosságban elmondhatjuk, hogy a Fil ernfo és a Di rectoryrnfo osztá­


lyokat célszerűbb használni, ha egy fájlról vagy könyvtárról részletes infor­
mációkra (pl. létrehozás ideje, írhatóság/ olvashatóság stb.) van szükségünk,
ugyanis ezek tagjai erősen típusos objektumokat adnak vissza. Ezzel szem­
ben a Directory és a Fil e osztályok tagjai erősen típusos objektumok helyett
inkább egyszerű sztringértékekkel térnek vissza.

Az absztrakt FHeSystemlnfo ősosztály

ADi rectoryrnfo és a Fil ernfo típusok az absztrakt Fi l esystemrnfo ősosztálytól


öröklik számos képességüket. Legnagyobbrészt arra használjuk Fil esystem­

Info osztály tagjait, hogy lekérdezzük egy fájl vagy egy könyvtár általános tu­
lajdonságait (pl. a létrehozás idejét, s különböző attribútumokat stb). A 20.2.
táblázat felsorolja az érdekesebb alaptulajdonságokat

Attributes Visszaadja vagy beállítja az aktuális fájlnak azokat az att­


ribútumait, amelyeket a Fil eAtt rib u tes felsorolás ír le.

CreationTime Visszaadja vagy beállítja az aktuális fájl vagy könyvtár


létrehozásának az idejét.

Exists Segítségével megállapíthatjuk, hogy egy adott fájl vagy


könyvtár létezik-e.

Extension Visszaadja a fájl kiterjesztését.

FullName Visszaadja a fájl vagy könyvtár teljes elérési útvonalát.

LastAccessTime Visszaadja vagy beállítja az aktuális fájl vagy könyvtár


legutolsó elérésének az időpontját.

L astwri teT i me Visszaadja vagy beállítja az aktuális fájl vagy könyvtár


utolsó módosításának az idejét.

Name Az aktuális fájl vagy könyvtár nevét adja vissza.

20. 2. táblázat: A FileSysteminfo tulajdonságai

A Fil e systeminfo osztály definiálja a Del ete() metódust is. Ezt a származta­
tott típusok valósítják meg azért, hogy egy adott fájlt vagy könyvtárat törölni
lehessen a merevlemezről. Az attribútumok lekérdezése előtt továbbá meg­
hívhatjuk a Refresh O metódust, amely biztosítja, hogy az aktuális fájira
(vagy könyvtárra) vonatkozó statisztikák ne legyenek elavultak.

6
A Directoryinfo típus használata

A Directoryinfo tipus használata

Az első, 1/0 műveletekkel kapcsolatos típus, amelyet közelebbről megvizs­


gálunk, a Directoryrnfo osztály. Tagjainak a segítségével létrehozhatunk, át­
helyezhetünk, törölhetünk és kilistázhatunk könyvtárakat és alkönyvtárakat
Az ősosztályától (Filesystemrnfo) örökölt funkcionalitásorr túl a Directory­
rnfo a 20.3. táblázatban feltüntetett kulcsfontosságú tagokkal rendelkezik.

'�}i::l>���Új�0·���:i����t����;rr:���:i>t.·'. ·.:ó�:,·,::;:·"�.:��f�{�ii����:;��
Create(), Egy könyvtár (vagy több alkönyvtár) létrehozása a
Createsubdirectory() megadott útvonalon.

Delete O Egy adott könyvtárnak és teljes tartalmának a törlése.

Get Directori es() Visszaad egy, az aktuális könyvtár összes alkönyvtárá­


nak nevét tartalmazó sztringtömböt.

GetFil es() Filernfo típusok tömbjével tér vissza, amely az adott


könyvtár fájljait tartalmazza.

MoveTo() A könyvtárat és a tartalmát új elérési út alá mozgatja.

Parent Az adott elérési út szülőkönyvtárát adja vissza.

Root Megkapja az elérési útvonal gyökérrészét

20. 3. táblázat: A Directoryinfo típus kulcsfontosságú tagjai

A Directoryrnfo típus használatához az elérési útvonalat kell megadnunk


konstruktorparaméterként. Ha az aktuális könyvtárhoz (pl. a futó alkalmazás
könyvtárához) szeretnénk hozzáférni, használjuk a 11." jelölést. Az alábbiak­
ban bemutatunk néhány példát:

ll Az aktuális könyvtárhoz kötés.


Directoryinfo dirl = new Directoryinfo(".");

/l A C:\Windows könyvtárhoz kötés,


ll szó szerinti sztring használatával.
Directoryinfo dir2 = new Directoryrnfo(@"C:\Windows");

A második példában azt feltételezzük, hogy a konstruktornak átadott útvo­


nal (C:\ Windows) már létezik a számítógépen. Ha nem létező könyvtáron
próbálunk műveleteket végrehajtani, a rendszer egy system. ro. Directory­
NotFoundExcepti on kivételt dob. Így, ha még nem létező könyvtárat adunk
meg, először meg kell hívnunk a create() metódust:

7
20. fejezet: Fájlműveletek és elszigetelt tárolás

ll Kössük hozzá egy nem létező könyvtárhoz, majd hozzuk létre.


Directoryinfo dir3 = new Directoryinfo(@"C:\MyCode\Testing");
dir 3.Create();

Miután létrehoztuk a Directoryinfo objektumot, megvizsgálhatjuk a könyv­


tár tartalmát bármely, a Filesysteminfo típusból örökölt tulajdonsággaL Pél­
daként hozzunk létre egy új, DirectoryApp nevű konzolalkalmazást A Prog­
ram osztályt egészítsük ki egy új statikus metódussal, amely létrehoz egy
olyan új Directoryinfo objektumot a C:\ Windows könyvtárra leképezve
(szükség esetén módosítsuk az útvonalat), amely számos érdekes statisztikát
jelenít meg (a kimenet a 20.2. ábrán látható):

class Program
{
static void Main(string[] args)
{
console.writeL ine("*''**'' Fun with Directory(Info) ****''\n");
ShowwindowsDirectoryinfo();
console.ReadLine();
}

static void ShowwindowsDirectoryinfo()


{
ll A könyvtárinformációk kiírása.
Directoryinfo dir = new Directoryinfo(@"C:\windows");
console. writeLine("***''* Directory Info **''**");
Console.writeLine("FullName: {O}", dir.FullName);
console.writeLine("Name: {O}", dir.Name);
Console.writeLine("Parent: {O}", dir.Parent);
console.writeLine("creation: {O}", dir.CreationTime);
Console.writeLine("Attributes: {O}", dir.Attributes);
Console.writeLine("Root: {O}", dir.Root);
console.writeLine("**************************\n");
}
}

20. 2. ábra: Információk megjelenítése a Windows-könyvtárról

8
A Directoryinfo típus használata

Fájlok listázása a Directoryinfo tipus segitségével

A fenti példát kiegészíthe�ük azzal, hogy az alapvető információk lekérdezé­


sén túl a Directoryrnfo típus néhány metódusát is használjuk. Először kér­
dezzünk le adatokat a C:\ Windows\ Web\ Walipaper könyvtárban található
''.jpg-fájlokról a Get:Fi les() metódussal.

Megjegyzés Ha a számítógépen nem található C:\Windows\Web\Wallpaper könyvtár, alakítsuk


át megfelelően a kódot, például úgy, hogy a C:\Windows könyvtár*. bmpfájljait olvassa be.

Ez a metódus olyan Filernfo tömbbel tér vissza, amelyek illindegyike egy


adott fájlról tartalmaz információkat (a Filernfo típus használatáról részlete­
sen később lesz szó). Vegyük a Program osztály következő, a Main() függ­
vényből meghívott, statikus metódusát:

static void DisplayrmageFiles()


{
Directoryinfo dir =
new Directoryrnfo(@"C:\Windows\Web\Wallpaper");
ll Az összes *.jpg kiterjesztésű fájl lekérdezése.
Filernfo[] imageFiles = dir.GetFiles("*.jpg");

ll M ennyit találtunk?
Console.writeLine("Found {O} ''.jpg files\n", imageFiles.Length);

ll Minden fájlról kiírjuk az információkat.


foreach (Filernfo f in imageFiles)
{
Console.writeLi ne ("''**'"''***1'**'"'****;«'***''**1"'");
console.writeLine("File n ame: {0}", f.Name);
console.writeLine("File size: {O}", f.Length);
Console.writeLine("creation: {O}", f.creationTime);
console.writeLine("Attributes: {O}", f.Attributes);
Console.writeLi ne ("''<1'**'' ;""""'' ***;'***''******''*1'\n ");
}
}

Ha futta�uk az alkalmazást, akkor a 20.3. ábrához hasonló listát kell látnunk.


(A képek nevei különbözhetnek.)

9
20. fejezet: Fájlműveletek és elszigetelt tárolás

20.3. ábra: Információk képfájlokról

Alkönyvtárak létrehozása a Directoryinfo


segitségével

Egy könyvtár szerkezetét a Directoryrnfo.createsubdirectory() metódussal


programozottan bővíthe�ük. Ezzel a metódussal akár egy, akár több egy­
másba ágyazott alkönyvtárat is létrehozhatunk egyszeres függvényhívássaL
A következő metódus például kibővíti az alkalmazás telepítési útvonalának
könyvtárszerkezetét néhány alkönyvtárral:

static void ModifyAppDirectory()


{
Directoryinfo dir = new Directoryrnfo(".");

ll A \MyFolder létrehozása az alkalmazás könyvtárában.


dir.Createsubdirectory("MyFolder");

ll A \MyFolder2\Data létrehozása az alkalmazás könyvtárában.


dir.Createsubdirectory(@"MyFolder2\Data");
}

Ha meghívjuk ezt a metódust a Main O függvényből, és Windows Intézővel


megnézzük a Windows-könyvtárat, látható, hogy megjelentek az új alkönyv­
tárak (lásd a 20.4. ábrát).
A createsubdirectory() metódus visszatérési értékéről annyit kell tudni,
hogy sikeres végrehajtás esetén a metódus visszaad egy Directoryrnfo objek­
tumot, amely az újonnan létrehozott könyvtárat jelképezi. Nézzük meg az
alábbi példát:

10
A Directory típus használata

static void ModifyAppDirectory()


{
Directoryinfo dir = new Directoryinfo(".");

ll A \MyFolder létrehozása a kezdőkönyvtárban.


dir.Createsubdirectory("MyFolder");

ll A visszaadott Directoryinfo objektum tárolása.


Directoryinfo myDataFolder =
dir.Createsubdirectory(@"MyFolder2\Data");

ll A . .\MyFolder2\Data elérési útjának kiírása.


console.writeLine("New Folder is: {O}", myDataFolder);
}

• •y N r_s,�"'" Pl

F.:::crite lin!f� Nam� Date modified Type Slze

� MyFolder 9/13/2007 9,06 PM File Folder


Mcre »
,;. MyFolder2 9/13/2007 9,06 PM File Folder
Folders
\!JOirectoryApp.6e 9/13/2007 g,Q6 PM Application
DirectoryApp • · � o;rectoryApp.pdb 9/13/2007 9'06 PM Program Debug D...
A J. bin 0: � DirectoryApp.vshost.exe 9/13/2007 8'58 PM Application
,. � Debug L.] DirectoryApp.vshost.exe.manifest 211112007 651 PM MANIFEST F;le
MyFolder
MyFolder2
.... t4L � m ___ --,

20.4. ábra: Alkönyvtárak létrehozása

A Directory tipus használata

A következőkben ismerkedjünk meg a Directory típussaL A legtöbb esetben

a Directory osztály statikus tagjainak működése megfelel annak a funkciona­


litásnak, amelyet a Directoryinfo példányszintű tagjai nyújtanak Ne feled­
jük azonban, hogy a Directory tagjai általában sztringtípusokat adnak vissza,
nem pedig erősen típusos Fi leinfo/ Directoryinfo típusokat.
A Directory típus működésének szemléltetésére ez a végső segédfügg­

vény megjeleníti a számítógépen található összes meghajtó nevét (a Directo­


ry. GetLog icalDri ves() metódussal), és a Directory. Delete() statikus metó­

dussal letörli a korábban létrehozott \MyFolder és \MyFolder2\Data al­


könyvtárakat:

11
20. fejezet: Fájlműveletek és elszigetelt tárolás

static void FunwithDirectoryType()


{
ll A számítógép meghajtóinak kilistázása.
string[] drives Directory.GetLogicalDrives();
=

console.writeLine("Here are your drives:");


foreach (string s in drives)
Console.writeLine("--> {O} ", s);

ll Letöröljük, amit létrehoztunk.


console.WriteLine("Press Enter to delete directories");
console.ReadLine();
try
{
Directory.Delete(string.Format(@"{O}\MyFolder",
Environment.currentDirectory));

ll A második paraméterben adhatjuk meg, hogy


ll az alkönyvtárakat is törölni szeretnénk-e.
Directory.Delete(string.Format(@"{O}\MyFolder2",
Environment.currentDirectory), true);
}
catch (IOException e)
{
Console.writeLine(e.Message);
}
}

Forráskód A DirectoryApp projektet a forráskódkönyvtárban a 20. fejezet alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A Driveinfo osztálytipus használata

A system.ro névtérben található a Drivernfo nevű osztály. A Directory. Get­


LogicalDrives() metódushoz hasonlóan a statikus Drivernfo.GetDrives()

metódus is a szánútógép meghajtóinak a nevét adja vissza, de a Directory.


GetLogicalDrives() metódussal ellentétben a Drivernfo számos egyéb részle­

tet is megad a meghajtókról (pl. a meghajtó típusát, a szabad területet, a kö­


tetcímkét stb.). Nézzük a következő, DrivernfoApp nevű új konzolalkalma­
zásban definiált Program osztályt:

class Program
{
static void Main(string[] args)
{
Console.writeLine("***** Fun with Driveinfo *****\n");

12
A Driveinfo osztálytípus ha sználata

ll Információk lekérése minden meghajtóról.


Drivernfo[] myorives = Drive rnf o. G e tDrives();

ll A meghajtók statisztikáinak kiírása.


for each(Drive rnf o d in myorives)
{
console.writeLine("Name: {O}", d.Name);
Console. Write Line ("Type : {O}", d. Dri veTy pe ) ;

ll Ellenőrizzük, hogy a meghajtó csatlakoztatva van-e.


if (d.IsReady)
{
console.writeLine("Free space: {O}", d.TotalFreespace);
console.writeLine("Format: {0}", d.DriveFormat);
Console.writeLine("Label: {O}", d.VolumeLabel);
console.writeLine();
}
}
Console.ReadLine();
}
}

A 20.5. ábra egy lehetséges kimenetet mutat.

20.5. ábra: Meghajtók adatainak lekérdezése a Driveinfo használatával

A oirectory, a oirect oryr nf o és a orivernfo osztályok alapvető működésének át­

tekintése után a következőkben megvizsgáljuk, hogyan hozhatunk létre, nyitha­


tunk meg, zárhatunk be és törölhetünk adott könyvtárban lévő fájlokat.

Forráskód A DrivelnfoApp proje ktet a forrás k ódkönyvtárban a 20. fejezet a lkönyvtára tar·
talmazz a . A forráskódkönyvtárró l lásd a Be vezetés xlv. oldalát.

13
20. fejezet: Fájlmüveletek és elszigetelt tárolás

A Fileinfo osztály használata

Ahogy a oirecto ryApp-példában is láttuk, a Fi l eInfo osztály segítségével a


számítógépen található fájlokról kérdezhetünk le adatokat (létrehozás ideje,
méret, attribútumok stb.), továbbá az osztály segítségével létrehozhatunk,
másolhatunk, áthelyezhetünk és törölhetünk fájlokat. A Fi l esystemrnfo tí­
pusból örökölt funkcionalitáson túl a Fi lernfo osztály néhány egyedi alap­
tagját a 20.4. táblázatban foglaltuk össze.

App endText() Egy Streamwri ter típust hoz létre (lásd később), amelynek se­
gítségéve! szöveget fűzhetünk a fájlhoz.

copyTo O A meglévő fájlt átrnásolja egy új fájlba.

c reat e () Új fájlt hoz létre, és egy Fi l eStream típussal (lásd később) tér
vissza, amellyel az újonnan létrehozott fájlt kezelhetjük

C re at eT ext( ) Egy Streamwriter típust hoz létre, amely új szövegfájlt ír.

Delete O Törli a Fi l e Info példány által reprezentált állományt.

Directory A szülőkönyvtár egy példányát adja vissza.

DirectoryName Visszaadja a szülőkönyvtár teljes elérési útját.

Length Visszaadja az aktuális fájl vagy könyvtár méretét.

M o veTo () A fájlt áthelyezi, és lehetőséget ad arra, hogy új nevet adjunk


neki.

Name Visszaadja a fájl nevét.

Open() Megnyitja a fájlt különböző olvasási/ írási és megosztási jogo­


sultságokkaL

Open Read() Csak olvasható Fi l estream típust hoz létre.

openText O Egy StreamReader típust hoz létre (lásd később), amellyel egy
létező szövegfájlból olvashatunk.

ope nwrite() Csak írható Fi l eStream típust hoz létre.

20.4. táblázat: Fileinfo alapvető tagjai

14
A Fileinfo osztály használata

A Fileinfo osztály metódusainak többsége tehát egy specifikus I/0-központú


objektumot (Fi l estream, streamwriter stb.) ad vissza. Ezek segítségével a fájl­
ból többféleképpen olvashatunk, illetve írhatunk adatokat. A példák tanulmá­
nyozása előtt azonban vizsgáljuk meg azokat a különböző módszereket, ame­
lyekkel a Fi l e Info osztállyai megszerezhe�ük a fájlazonosítóját

A Filelnfo.Create() metódus

A fájlazonosító létrehozásának első módja a File Info.create() metódus hasz­


nálata:

static void Main(string[] args)


{
ll Létrehozunk egy új fájlt a c meghajtón.
Fileinfo f = new Fileinfo(@"C:\Test.dat");
Filestream fs = f.create();

ll Használjuk a Filestream objektumot...

ll zárjuk le a fájlfolyamot.
fs.Close();
}

A Fileinfo.Create() metódus Filestream típussal tér vissza, amely a fájlon


mind szinkron, rnind aszinkron írási/ olvasási műveleteket tesz lehetővé (a
részleteket lásd később). A Fileinfo.create() által visszaadott Filestream ob­
jektum rninden felhasználó számára teljes olvasási/írási hozzáférést biztosít.
Miután befejeztük a Fi l estream objektummal a munkát, zárjuk le a fájl­
azonosítót, hiszen így a rendszer felszabadí�a az adatfolyam nem felügyelt
erőforrásait. Minthogy a Filestream megvalósí�a az IDisposable interfészt, a
C# us ing blokkjának a segítségével a fordítóra bízha�uk a "takarító"-logika
létrehozását (a részleteket lásd az első kötet 8. fejezetében):

static void Main(string[] args)


{
ll Egy 'using' hatókör definiálása
ll ideális a fájl IlO típusokhoz.
Fileinfo f = new Fileinfo(@"C:\Test.dat");
using (Filestream fs = f.create())
{
ll Használjuk a Filestream objektumot...
}
}

15
20. fejezet: Fájlműveletek és elszigetelt tárolás

A Filelnfo.Open() metódus

A Filernfo.open() metódust a Filernfo.create() által nyújtott lehetőségek­


nél precízebben használhatjuk a fájlok megnyitására és létrehozására, ugyanis
az open O jellemzően több paramétert vesz fel a fájl kezelésének a leírására.
Az Open() meghívása után egy Filestream objektumot kapunk vissza. Néz­
zük meg a következő logikát:

static void Main(string[] args)


{
ll Létrehozunk egy új fájlt a Filernfo.open() metódussal.
Fileinfo f2 = new Filernfo(@"C:\Test2.dat");
using(Filestream fs2 = f2.open(FileMode.Openorcreate,
FileAccess.Readwrite, Fileshare.None))
{
ll Használjuk a Filestream objektumot...
}
}

A túlterhelt Open() metódus ezen verziójának három paraméterre van szük­


sége. Az első paraméter az IjO-kérés típusát határozza meg (pl. új fájl létre­
hozása, létező fájl megnyitása, hozzáfűzés fájlhoz stb.), amelyet a FileMode
felsorolt típussal határozhatunk meg (lásd 20.5. táblázat):

public enum FileMode


{
createNew,
Create,
Open,
Openorcreate,
Truncate,
Append
}

createNew Az operációs rendszert új fájl létrehozására utasítja. Ha a fájl


már létezik, IOException kivételt dob.

create Az operációs rendszert új fájl létrehozására utasítja. Ha a fájl


már létezik, akkor felülírja azt.

Open Megnyit egy már létező fájlt. Ha a fájl nem létezik,


FileNotFoundException kivételt dob.

openorcreate Megnyitja a fájlt, ha az már létezik; ellenkező esetben új fájlt


hoz létre.

16
A Fileinfo osztály használata

: 0i�t.·:.��>i:;��,-:�:,I·:�:;�--��:�;;;:�$..4;�i
,,.,

_., ,_ : .
Truncate Megnyitja és O byte méretűre vágja le a fájlt

Append Megnyitja a fájlt, a végére pozicionál, és írási műveletet kezd


meg (ez a kapcsaló kizárólag csak írható folyamatokkal hasz­
nálható). Ha a fájl nem létezik, új fájlt hoz létre.

20. 5. táblázat: A FileMade felsorolt típus tagjai

A második paraméter a FileAccess felsorolt típus egy olyan értéke, amellyel


a folyam olvasási/írási viselkedését határozhatjuk meg:

public enum FileAccess


{
Read,
write,
Readwrite
}

Végül a harmadik paraméter ( Fileshare) azt határozza meg, hogy milyen


módon osztjuk meg a fájlt más fájlkezelőkkeL Az alapnevek a következők:

public enum Fileshare


{
None,
Read,
write,
Readwrite
}

A Filelnfo.OpenRead() és a Filelnfo.OpenWrite()
metódusok

Habár a Fi le Info. OpenO metódussal rendkívül rugalmasan kezelhetők a


fájlazonosítók, a Fi lern fo osztály további, OpenRead O és OpenwriteO nevű
metódusokat is tartalmaz. Ezek megfelelően konfigurált írásvédett, illetve
csak írható Filestream típusokat adnak vissza anélkül, hogy különféle felso­
rolásértékeket kellene ehhez megadnunk. A Filernfo.createO és a File­
Info.OpenO metódusokhoz hasonlóan az OpenReadO és az OpenwriteO is File­
Stream objektummal tér vissza (a következő kódban feltételezzük, hogy a C
meghajtón létezik a Test3.dat és Test4.dat nevű fájl):

17
20. fejezet: Fájlműveletek és elszigetelt tárolás

static void Main(string[] args)


{
ll írásvédett Filestream objektum megszerzése.
Fileinfo f3 = new Fileinfo(@"C:\Test3.dat");
using(Filestream readonlystream = f3.0penRead())
{
ll Használjuk a Filestream objektumot...
}

ll Most pedig a csak írható Filestream objektumot kapjuk vissza.


Fileinfo f4 = new Fileinfo(@"C:\Test4.dat");
using(FileStream writeonlyStream = f4.openwrite())
{
ll Használjuk a Filestream objektumot ...
}
}

A Filelnfo.OpenText() metódus

Az OpenText ( ) a FileInfo típus egy másik, a fájlmegnyitással kapcsolatos tagja.


A Create(), az open(), az openRead() és az Openwrite() metódusokkal szemben

az OpenText O metódus a streamReader típus egy példányát adja vissza a File­


Stream típus helyett. Feltételezve, hogy a C meghajtón található egy boot. ini

nevű fájl, annak tartalmát a következőképpen olvasha�uk ki:

static void Main(string[] args)


{
ll visszakapunk egy streamReader objektumot.
Fileinfo fS = new Fileinfo(@"C:\boot.ini");
using(StreamReader sreader = fS.openText())
{
ll Használjuk a StreamReader objektumot...
}
}

A streamReader típus segítségével karakteradatokat olvashatunk a mögöttes


fájlból.

A Filelnfo.CreateText() és a Filelnfo.AppendText()
metódusok

A két utolsó érdekes metódus a createText() és az AppendText(), amelyek


streamwriter referenciát adnak vissza:

18
A File típus használata

static void Main(string[] args)


{
Fileinfo f6 = new Fileinfo(@"C:\TestS.txt");
using(Streamwriter swriter = f6.CreateText())
{
ll Használjuk a Streamwriter objektumot...
}

Fileinfo f7 = new Fileinfo(@"C:\FinalTest.txt");


using(Streamwriter swriterAppend = f7.AppendText())
{
ll Használjuk a Streamwriter objektumot ...
}
}

A Streamwriter típussal karakteradatokat írhatunk a mögöttes fájlba.

A File tipus használata

A File típus a Filein fo osztályhoz hasonló funkcionalitást biztosít a statikus


tagokon keresztül. Ugyanúgy, mint a FileI nfo típusban, a File típusban is
megtalálhatók az AppendText(), a create(), a createText(), az Open(), az open­

Read O, az Openwrite O és az openText () metódusok. Sok esetben a File és a

Fileinfo típusok egymással felcserélhetők. Az előző, a Filestream használatát

bemutató példákat a File típus alkalmazásával például egyszerűbbé tehe�ük:

static void Main(string[] args)


{
ll Filestream objektum megszerzése a File.create() metódus révén.
using(Filestream fs = File.create(@"C:\Test.dat"))
{
}

ll FileStream objektum megszerzése a File.Open() metódus révén.


using(Filestream fs2 = File.open(@"C:\Test2.dat",
FileMode.Openorcreate,
FileAccess.Readwrite, Fileshare.None))
{
}

ll Írásvédett FileStream objektum megszerzése.


using(FileStream readonlystream = File.OpenRead(@"Test3.dat"))
{
}

19
20. fejezet: Fájlműveletek és elszigetelt tárolás

ll csak írható Filestream objektum megszerzése.


using(Filestream writeonlystream = File.Openwrite(@"Test4.dat"))
{
}

ll visszakapunk egy StreamReader objektumot.


using(StreamReader sreader = File.openText(@"C:\boot.ini"))
{
}

ll Néhány Streamwriter megszerzése.


using(Streamwriter swriter = File.createText(@"C:\Test3.txt"))
{
}

using(Streamwriter swriterAppend =

File.AppendText(@"C:\FinalTest.txt"))
{
}
}

További fájlközpontú tagok

A File típus rendelkezik néhány egyedi taggal is (lásd 20.6. táblázat), ame­

lyek jelentősen megkönnyítik a szöveges adatok olvasását és írását.

ReadAll Bytes() Megnyitja a megadott fájlt, visszaadja a bináris adatokat byte­


tömbként, majd lezárja a fájlt.

ReadAll L ines() Megnyitja a megadott fájlt, visszaadja a karakteradatokat


sztringtömbként, majd lezárja a fájlt.

ReadAllText() Megnyitja a megadott fájlt, visszaadja a karakteradatokat


system. String típusként, majd lezárja a fájlt.

writeAll Bytes() Megnyitja a megadott fájlt, kiírja a byte-tömböt, majd lezárja a


fájlt.

WriteAll L ines() Megnyitja a megadott fájlt, kiírja a sztringtömböt, majd lezárja


a fájlt.

writeAllText() Megnyitja a megadott fájlt, kiírja a karakteradatokat, majd le­


zárja a fájlt.

20. 6. táblázat: A File típus metódusai

20
A File típus használata

A Fi le típus új metódusaival, néhány sornyi kóddal olvashatunk vagy írha­


tunk adatcsomagokat Sőt mi több, ezek a tagok a művelet végén automati­
kusan le is zárják a mögöttes fájlazonosítót A következő konzolalkalmazás
(simpleFilero) például a lehető legkevesebb munkával menti a sztringadato­
kat egy új fájlba a C meghajtón (majd onnan beolvassa memóriába):

using System;
using system.IO;

class Program
{
static void Main(string[] args)
{
console.writeLine("***** simple IO with the File Type *****\n");
string[] myTasks {=

"Fix bathroom sink", "call Dave",


"call Mom and Dad", "Play Xbox 360"};

ll Az összes adatot kiírjuk egy fájlba a c meghajtón.


File.writeAllLines(@"c:\tasks.txt", myTasks);

ll visszaolvassuk az egészet, és kiírjuk a képernyőre.


foreach (string task in File.ReadAllLines(@"C:\tasks.txt"))
{
console.writeLine("TODO: {O}", task);
}
console.ReadLine();
}
}

Ha gyorsan szeretnénk fájlazonosítóhoz jutni, a File típus egyértelműen


megkönnyíti a dolgunkat. Ha pedig előbb létrehozunk egy FileInfo objek­
tumot, ennek megvan az az előnye, hogy megvizsgálha�uk a fájlt az abszt­
rakt Filesysteminfo alaposztály tagjainak a segítségéveL

Forráskód A SimpleFilelO proje kt megtalálható a 20. feje zet alkönyvtár ában. A forrásk ó d­
könyvtárr óllásd a Be v ezetés xlv. oldalát.

21
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Az absztrakt Stream osztály

Eddig a Filestream, a streamReader és a streamwriter objektumokat még nem


használtuk fájlok adatainak írására vagy olvasására. Hogy megértsük ennek
menetét, meg kell ismernünk az adatfolyam koncepcióját. Az 1/0 kezelésé­
ben az adatfolyam olyan adattöredéket jelképez, amely a forrás és a cél között
folyik. Az adatfolyamokkal egyformán kezelhetünk egy byte-sorozatot attól
függetlenül, hogy milyen eszköz (fájl, hálózati kapcsolat, nyomtató stb.) tá­
rolja vagy jeleníti meg azt.
Az absztrakt system. ro. stream osztály több tagot definiál, amelyek szink­
ron és aszinkron adatkezelést tesznek lehetővé a tárolóeszközön (pl. mögöt­
tes fájl- vagy memóriahelyen). A 20.6. ábra a Stream típus különböző leszár­
mazottait mutatja a Visual Studio 2008 objektumböngészőjében.

11----������-�-, �- 1 � .. ! �l 1511
.." ®{:J Extension Members

.
��---���--- ..:;;-;,;,J ' BeginRead(byte( ], in� int System.AsyncCallbaclc. objec-'-.
� ·� � � BegínWrite(byte( ], int, int System.Async(allbaclc. objec 1 E:
·

P CloseO
·· '·

�·, �::::� T�pes


C reateObjRef(System.Ty pe)
L
éJ...'\: AuthenticatedStream
·

CreateWaitHandleO
::iJ...'i$ SulferedStream DisposeO
$··'-1: CryptoStream
·.$ Dispose(bool}
$···'1$ DeflateStream
� EndRead(System.!AsyncResu�)
$··� FileStream
·� EndWrite(SystemlAsyncResu�)
· ·•

�l ·'-1: GZipStream
'" Equals(object)
·

ffiAt MemoryStream ·

$-·'i$ NetworkStream "'


ffi..� OracleBFile public abstract class Stream :
rB··1t Oraclelob Svstem.MarshaiByRefObject
tB···'is PipeStream Member of System.!O

m At PrintQueueStream
rB ·'\: UnmanagedMemoryStream Summary:
C:. Provictes a generic view of a sequence of b)1es.

20.6. ábra: A Streamből származó típusok

Megjegyzés Az adatfolyamok segítségével nem csak fájlokat kezelhetünk. A .NIT-könyvtá­


rak adatfolyam-alapú hozzáférést biztosítanak a hálózatokhoz, a memóriaterületekhez és az
egyéb adatfolyam-alapú absztrakciókhoz.

A stream osztály leszármazottai tehát az adatokat nyers byte-folyamként je­

lenítik meg; így gyakran elég nehézkes a nyers adatfolyamokkal dolgozni.


Bizonyos stream-leszármazottak támogatják a keresést, és ez azt jelenti, hogy
lekérdezhetjük vagy beállíthatjuk az adatfolyamban lévő pozíciót.

22
Az absztrakt Stream osztály

A stream osztály által nyújtott funkcionalitás megértéséhez nézzük meg

annak alapvető tagjait a 20.7. táblázatban.

��-­
��.
CanRead, Megadja, hogy az aktuális adatfolyarn támoga�a-e az olva-
Canwrite, canseek sást, a keresést és/vagy az írást.

cl ose() Lezárja az aktuális adatfolyamot, és felszabadít minden, az


adatfolyammal kapcsolatos erőforrást (pl. csatlakozóponto­
kat és fájlazonosítókat). Belsőleg ez tulajdonképpen a
Di spose() metódus álneve; vagyis az "adatfolyam lezárása"
funkcionálisan megegyezik az "adatfolyam eldobásával".

Flush() Frissíti a mögöttes adatforrást vagy adattárat a puffer jelen­


legi állapotával, majd törli a puffert. Ha egy adatfolyamhoz
nem tartozik puffer, ez a metódus nem csinál semmit.

Length Byte-ban visszaadja az adatfolyam hosszát.

Position Meghatározza a pozíciót az aktuális adatfolyamban.

Read(), Kiolvas egy byte-sorozatot (vagy egyetlen byte-ot) az adatfo­


ReadByte() lyamból, és az olvasott byte-ok számával előrébb lépteti az
aktuális pozíciót az adatfolyamban.

seek() Pozicionál az aktuális adatfolyamban.

SetLength() Beállí�a az aktuális adatfolyam hosszát.

.write(), Kiír egy byte-sorozatot (vagy egyetlen byte-ot) az adatfo­


writeByte() lyamba, és a kiirt byte-ok számával előrébb lépteti az aktuá­
lis pozíciót az adatfolyarnban.

20.7. táblázat: A Stream absztrakt tagjai

A FileStream tipusok használata

A Fil est re a m osztály úgy valósí�a meg az absztrakt stre am típus tagjait,

ahogy az a fájlalapú folyamok számára a legmegfelelőbb. Ez egy elég egysze­


rű adatfolyam; csak egy byte-ot vagy byte-ok tömbjét tudja olvasni. A való­
ságban közvetlenül nem túlságosan gyakran használjuk a Fil estream típus
tagjait. Ehelyett inkább különböző adatfolyam-burkolókat alkalmazunk, ame­
lyek megkönnyítik a szöveges adatokkal vagy a .NET-típusokkal végzett
munkát. Ennek ellenére példaként nézzük meg a Fil estream típus szinkron
olvasási/ írási lehetőségeit.

23
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Tételezzük fel, hogy van egy új, Filest:reamApp nevű konzolalkalmazásunk


A célunk az, hogy egy egyszerű szöveges üzenetet írjuk a myMessage.dat: nevű új
fájlba. Mivel a Filest:ream csak nyers byte-okkal tud dolgozni, a syst:em.St:ring
típusú adatot előbb egy megfelelő byte-tömbbé kell alakítani. A syst:em.Text: név­
tér egy Eneading nevű típust definiál, amelynek tagjai sztringeket tudnak byte­
tömbbe kódoini vagy abból dekódolill (az Eneading típus részletes leírását a .NET
Framework 3.5 SDK dokumentációja tartalmazza).
A kódolás végeztével a byte-tömböt a Fil est:ream. wri t: e() metódussal ír­
haljuk ki a fájlba. Ha a byte-okat vissza szeretnénk olvasni a memóriába, elő­
ször alaphelyzetbe kell állítanunk az adatfolyam belső pozícióját (a Posit:i on
tulajdonság segítségéve!), majd meg kell hívnunk a ReadByt:e() metódust.
Végül megjeleníljük a konzolon a nyers byte-tömböt és a dekódolt sztringet.
A teljes Main() metódus a következő:

ll Ne felejt:sük el import:álni a syst:em.Text: és


ll a Syst:em.IO névt:ereket:.
st:at:ic void Main(st:ring[] args)
{
Console.writ:eLine("***** Fun wit:h Filest:reams *****\n");

ll A Filestream objekt:um megszerzése.


using(Filest:ream fst:ream = File.Open(@"c:\myMessage.dat:",
FileMode.Creat:e))
{
ll A szt:ring byt:e-t:ömbbe t:ört:énő kódolása.
st:ring msg = "Hello!";
byt:e[] msgAsByt:eArray = Encoding.Default:.Get:Byt:es(msg);

ll A byt:e[] fájlba írása.


fst:ream.writ:e(msgAsByt:eArray, O, msgAsByt:eArray.Lengt:h);

ll Az adatfolyam belső pozíciójának alaphelyzetbe állít:ása.


fst:ream.Posit:ion =O;
'
'

ll A t:ípusok kiolvasása a fájlból és megjelenít:ésük a konzolon.


console.writ:e("Your message as an array of byt:es: ");
byt:e[] bytesFromFile = new byt:e[msgAsByt:eArray.Lengt:h];
for (int: i = O; i < msgAsByt:eArray.Lengt:h; i++)
{
byt:esFromFile[i] (byt:e)fSt:ream.ReadByt:e();
=

Console.writ:e(byt:esFromFile[i]);
}

ll Dekódolt: üzenet:ek megjelenít:ése.


Console.writ:e("\nDecoded Message: ");
Console.writ:eLine(Encoding.Default:.Get:St:ring(byt:esFromFile));
}
Console.ReadLine();
}

24
A StreamWriter és StreamReader típusok használata

Bár a fenti példa valóban feltölti a fájlt adatokkal, kiderül a Fil estream típus
közvetlen használatának legnagyobb hátránya is: nyers byte-okkal kell dol­
goznunk. Más stream-leszármazott típusok is hasonlóképpen működnek. Ha
például egy byte-sorozatot szeretnénk egy adott memóriaterületre kiimi, lefog­
lalhatunk egy Memorystream objektumot. Hasonlóképp, ha egy byte-tömböt egy
hálózati kapcsolaton keresztül kell továbbítanunk, a Networkstream típus lesz a
segítségünkre.
A system. ro névtér tartalmaz számos "olvasó" és "író" típust, amelyek
magukba foglalják a streamből származó típusok működésének részleteit.

Forráskód A FileStreamApp projekt megtalálható a 20. fejezet alkönyvtárában. A forráskód­


könyvtárról lásd a Bevezetés xlv. oldalát.

A StreamWriter és StreamReader
tipusok használata

A streamwriter és streamReader osztályok segítségével karakteralapú adato­


kat (pl. sztringeket) írhatunk vagy olvashatunk. Az alapértelmezés szerint
mindkét típus Unicode-karakterekkel dolgozik, de ezt megváltoztathatjuk,
ha megadunk egy megfelelően konfigurált system. Text. En cod in g objektum­
referenciát. Az egyszerűség kedvéért tegyük fel, hogy az alapértelmezett
Unicode-kódolás megfelelő.
A streamReader az absztrakt TextReader típusból származik ugyanúgy, mint
a kapcsolódó stri ngReader típus (erről a későbbiekben még lesz szó). A Text­
Reader alaposztály nagyon korlátozott funkcionalitást tesz lehetővé a leszár­
mazottainak, különösen a karakterfolyam olvasásában és megtekintésében.
A streamwriter típus (a stringwriterhez hasonlóan, ezt lásd később) az
absztrakt Textwri ter ősosztályból származik. Ez az osztály olyan tagokat de­
finiál, amelyekkel a származtatott típusok szöveges adatot írhatnak egy meg­
adott karakterfolyamba.
A streamwri ter és a Stringwriter osztályok jobb megértésének érdekében
a 20.8. táblázat összefoglalja a Textwri ter ősosztály legfontosabb tagjait.

25
20. fejezet: Fájlműveletek és elszigetelt tárolás

close() A metódus lezárja az írót, és felszabadít minden kapcsolódó


erőforrást. A folyamat során a puffer automatikusan kiürül (ez
a tag funkcionálisan megegyezik a Dispose() metódus meg­
hívásával).

Flush() Ez a metódus kiüríti a puffereket az aktuális íróból, és minden


pufferelt adatot kiír a mögöttes eszközre, de nem zárja le az írót.

NewLine Ez a tulajdonság jelöli a származtatott író osztály újsor-kons­


tansát. A Windows operációs rendszer alapértelmezett sorvég­
jelzője a "kocsivissza", amelyet soremelés követ (\r\n).

write() Ez a túlterhelt metódus adatokat ír ki a szövegfolyamba újsor­


konstans nélkül.

writeLine() Ez a túlterhelt metódus adatokat ír ki a szövegfolyamba újsor­


konstanssaL

20.8. táblázat: A TextWriter alapvető tagjai

Megjegyzés A Textwriter osztály utolsó két tagja ismerős lehet. A system. cons o l e típusnak
is van write O és writeLi ne O tagja, amelyek az alapértelmezett kimeneti eszközre írnak szö­
veges adatot. Valójában a Console.In tulajdonság egy Textwriter, a Console.out tulajdon­
ság pedig egy TextReader objektum.

A származtatott streamwriter osztály megfelelő write(), close() és Flush()

metódusokat valósít meg, emellett egy AutoFlush tulajdonsággal is rendelke­


zik. Ha ezt a tulajdonságot igaz értékre állítjuk, akkor arra kényszeríti a
streamwriter típust, hogy kiürítsen minden adatot az írási műveletek után.
Jobb teljesítményt érhetünk el, ha az AutoFlush tulajdonságot nem kapcsoljuk
be, ugyanis ez esetben meghívjuk a close() metódust, miután befejeztük az
írást a streamwriter típussal.

Szövegfájl irása

A Streamwriter típus működésének tanulmányozásához készítsünk egy új,

streamwriterReaderApp nevű konzolalkalmazást A következő Main() metó­


dus létrehoz egy remi nders. txt nevű fájlt a File. createText () metódus hasz­
nálatával. A visszakapott streamwriter objektummal szöveges adatot írunk
ki az új fájlba:

26
A StreamWriter és StreamReader típusok használata

static void Main(string[] args)


{
Console.writeLine("***** Fun with streamwriter l
StreamReader *****\n");

'
·ll Megkapjuk a streamwritert, majd kiírjuk a sztringadatot.
using(Streamwriter writer = File.CreateText("reminders.txt"))
{
writer.writeLine("Don't forget Mother's oay this year...");
writer.writeLine("Don't forget Father's oay this year...");
writer.WriteLine("Don't forget these numbers:");
for(int i = O; i < 10; i++)
writer.write(i + " ");

ll új sort szúrunk b e.
writer.write(writer.NewLine);
}
con sole.wri teL ine("created file and wrot e some thoughts.... ");
console.ReadLine();
}

Ha lefuttatjuk a programot, megvizsgálhatjuk az új fájl tartalmát (lásd 20.7.


ábra). A fájl az aktuális alkalmazás bin\Debug mappájában található, feltéve,
ha nem abszolút elérési útvonalat adtunk meg a createText O függvénynek

·] reminders.txt - Notepad l= l @] iMJiil


File Edit Forrnat View Help

pon't forget Mother's Day this year... A

Don't forget Father's Day this year...


Don't forget these numbers;
o l 2 3 4 5 6 7 8 9

< '

Lnl,Coll ..

20. 7. ábra: A *.txtfájl tartalma

Olvasás szövegfáj l ból

A következőkben megnézzük, hogyan olvashatunk programozottan adatot


fájlból a megfelelő streamReader típus segítségéveL Ez az osztály az absztrakt
TextReader osztályból származik, amely a 20.9. táblázatban összegzett funk­

cionalitást nyújtja.

27
20. fejezet: Fájlműveletek és elszigetelt tárolás

- '
.o
1. '

: '
' ·

Peek O Visszaadja a következő elérhető karaktert anélkül, hogy az ol­


vasó pozícióját módosítaná. Ha -l értékkel tér vissza, az azt je­
lenti, hogy az adatfolyam végére értünk.

Read O Adatot olvas a bemeneti adatfolyambóL

ReadBlockO Legfeljebb annyi karaktert olvas be az aktuális adatfolyamból,


amennyit a count paraméterben megadunk, az adatokat pedig
kiírja a pufferbe a megadott indextől kezdve.

ReadLineO Beolvas egysornyi karaktert az aktuális adatfolyamból, és


sztringként adja vissza az adatot (null sztringgel tér vissza, ha
a fájl végén vagyunk).

ReadToEndO Beolvassa az adatfolyamban található valamennyi karaktert az


aktuális pozíciótól kezdve, és egyetlen sztringként adja vissza
őket.

20.9. táblázat: A TexfReader alapvető tagjai

Ha úgy módosí�uk a MyStreamwriterReader osztályt, hogy az a StreamReadert


használja, akkor az alábbiak szerint olvasha�uk be a reminders.txt fájl szö­
veges tartalrnát:

static void Main(string[] args)


{
Console.WriteLine("***** Fun with Streamwriter l
StreamReader *****\n");

ll Most adatokat olvasunk a fájlból.


console.writeLine("Here are your thoughts:\n");
using(StreamReader sr File.openText("reminders.txt"))
=

{
string input = null;
while ((input = sr.ReadLine()) != null)
{
console.writeLine (input);
}
}
Console.ReadLine();
}

Miután lefutta�uk a programot, a reminders.txt fájl tartalmát lá�uk a konzolon.

28
A StringWriter és StringReader típusok használata

A StreamWriter/StreamReader tipusok közvetlen


létrehozása

A system. ro névtér típusait használva zavaró lehet, hogy ugyanazt az ered­


ményt gyakran többféle megközelítéssel is elérhe�ük. Megszerezhetünk pél­
dául egy Streamwriter objektumot a File vagy a Fileinfo révén, a create­
Text() metódussal. Van azonban még egy módszer, amelyet a streamwriter
és a streamReader típusokkal használhatunk: ha közvetlenül hozzuk létre
őket. Az aktuális alkalmazást például a következőképpen is módosítha�uk:

static void Main(string[] args)


{
console.writeLine("***** Fun with Streamwriter l
StreamReader *****\n");

ll Megkapunk egy Streamwritert, és kiírjuk a sztringadatot.


using(Streamwriter writer new Streamwriter("reminders.txt"))
=

ll Most adatokat olvasunk a fájlból.


using(StreamReader sr new StreamReader("reminders.txt"))
=

}
}

Bár zavaró lehet, hogy a fájl 1/0-műveleteit ennyiféleképpen végrehajthat­

juk, ám ezek mind a nagyobb rugalmasságot biztosí�ák. A következőkben a


stringwriter és a stringReader osztályok szerepét vizsgáljuk meg.

Forráskód A StreamWriterReaderApp projektet a forráskódkönyvtárban a 20. fejezet al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A StringWriter és StringReader tipusok


használata

A stringwriter és a StringReader típusok segítségével a szöveges információt


memóriában tárolt karakterek folyamaként kezelhe�ük. Ez akkor lehet hasz­
nos, ha egy mögöttes pufferhez karakteralapú információt kell hozzáfűz-

29
20. fejezet: Fájlmüveletek és elszigetelt tárolás

nünk. Vegyük példaként azt, amikor a stringReaderwriterApp nevű konzol­


alkalmazás a helyi rnerevlernezen lévő fájl helyett egy stringwriter objek­
turnba ír sztringadatblokkot.

static void Main(string[] args)


{
console.WriteLine("***** Fun with Stringwriter l
StringReader *****\n");
ll Létrehozzuk a stringwritert, és memóriába írjuk
ll a karakteradatot.
using(Stringwriter strwriter = new Stringwriter())
{
strwriter.writeLine("Don1t forget Mother1S Day this year . . . ");
ll Megkapjuk a tartalom másolatát (sztringben tárolva),
ll és kiírjuk a konzolra.
console.writeline("contents of stringwriter:\n{0}11, strwriter);
}
console.ReadLine();
}

Mivel a stringwriter és a Streamwriter ugyanabból az ősosztályból (Text­


writer) származik, az írás logikája többé-kevésbé megegyezik. A string­

writer osztályból azonban - természeténél fogva - kinyerhető egy System.

Text.St ringBuil der objektum, amelyet a GetstringBui l der() rnetódussal kér­

hetünk le:

using (Stringwriter strwriter = new Stringwriter())


{
strwriter.writeli neC"Don t forget Mother s Day this year... ");
1 1

console.writeLine("contents of Stringwriter:\n{O}", strwriter);

ll Megkapjuk a belső StringBuildert.


StringBuilder sb = strWriter.GetstringBuilder();
sb.Insert(O, "Hey!! ");
Console.WriteLine("-> {O}", sb.ToString());
sb.Remove(O, "Hey!! ".Length);
Console.writeLine("-> {O}", sb.ToString());
}

Ha egy szöveges adatfolyamból szeretnénk olvasni, használjuk a megfelelő


stringReader típust, amely (ahogy az várható) a kapcsolódó StreamReader

osztályhoz hasonlóan rnűködik. A StringReader osztály tulajdonképpen nem


tesz mást, rnint felüldefiniálja az örökölt tagokat, hogy azok ne fájlból, hanern
szöveges adatból olvassanak:

30
A BinaryWriter és BinaryReader osztályok használata

using (Stringwriter strwriter = n ew StringWriter())


{
strWriter.writeLine("Don't forget Mother's Day this year . .. ");
console.writeLine("contents of strin gwriter : \n { O } " , strwriter);

ll Kiolvassuk az adatot a Stringwriter-ből.


using ( Strin gR eader s trRe a der =

new Strin gReader(strwriter.T oSt ring()))


{
string input = null;
whi l e ( (inp u t = st r R eade r . R ea d Lin e ()) != null)
{
c on s ol e.writ e Lin e ( inp ut);
}
}
}

Forráskód A StringReaderWriterApp p rojektet a forráskódkönyvtárban a 20. fejezet alkönyv·


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A BinaryWriter és BinaryReader
osztályok használata

Végezetül megvizsgálunk még két írással/ olvasással kapcsolatos osztályt, a


BinaryReadert és a Binarywritert. Mindkettő közvetlenül a system.object osz­

tályból származik. Ezekkel a típusokkal különálló adattípusokat írhatunk és


olvashatunk tömör bináris formátumban a mögöttes adatfolyambóL A Binary­
writer osztály egy többszörösen túlterhelt write() metódust határoz meg,

amellyel egy adattípust írhatunk a mögöttes adatfolyamba. A write O metódus


mellett a B inarywriter további tagokkal is rendelkezik, ezek segítségével le­
kérdezheljük vagy beállíthaljuk a streamből származó típust, továbbá a Binary­
writer véletlen elérést biztosít az adathoz (lásd 20.10. táblázat).

::;:�·�-: ;. :;� -. ' -.. '�/[___ .."· .. . .. :.'7�


B as e stream Ez az írásvédett tulajdonság hozzáférést biztosít a Binary­
writer objektummal használt mögöttes adatfolyamhoz.

close() Ez a metódus lezárja a bináris adatfolyamot

Flush() Ez a metódus kiüríti a bináris adatfolyam pufferét.

31
20. fejezet: Fájlműveletek és elszigetelt tárolás

seek() Ez a metódus beállítja a pozíciót az aktuális adatfolyamban.

write() Ez a metódus kiír egy értéket az aktuális adatfolyamba.

20.1 O. táblázat: A Binan;Writer legfontosabb tagjai

A sinaryReader osztály a sinarywriter által nyújtott működést a 20.11. táblá­


zatban szereplő tagokkal egészíti ki.

Basestream Ez az írásvédett tulajdonság hozzáférést biztosít a B inary­


Reader objektummal használt mögöttes adatfolyamhoz.

Close() Ez a metódus lezárja a bináris olvasót

Pee kchar() Ez a metódus visszaadja a következő elérhető karaktert anél­


kül, hogy módosítaná a pozíciót az adatfolyamban.

Read () Ez a metódus kiolvas egy adott byte- vagy karaktersorozatot,


és eltárolja őket a bemeneti tömbben.

Readxxxx() A sinaryReader osztály számos olyan olvasási metódust defi­


niál, amelyek kiolvassák a következő tipust az adatfolyamból
( Readsoolean(), Readsyte(), Readint320 stb.).

20.11. táblázat: A Binan;Reader alapvető tagjai

A következő példa ( sinarywriterReader nevű konzolalkalmazás) többféle


adattípust ír ki egy új 1' . dat fájlba:

static void Main(string[] args)


{
Console.writeLine("***** Fun with Binary writers l
Readers *****\n");

ll Megnyitunk egy bináris írót egy fájl szamara.


Fi le info f = new Fileinfo("BinFile.dat");
using(Binarywriter bw = new Binarywriter(f.openwrite()))
{
ll Kiírjuk a Basestream tulajdonság típusát.
ll (Jelen esetben System.IO.Filestream.)
console.writeLine("Base stream is: {O}", bw.Basestream);

ll Létrehozunk néhány adatot, amelyet elmenthetünk a fájlba.


double aDouble = 1234.67;
int anint 34567;
=

string aString = "A, B, c";

32
A BinaryWriter és BinaryReader osztályok használata

ll Az adatok írása.
bw.write(aDouble);
bw.write(anrnt);
bw.write(astring);
}
console.ReadLine();
}

Figyeljük meg, hogy a Filernfo.openwrite() által visszaadott Filestream ob­


jektumot hogyan adjuk át a Binarywriter típus konstruktorának. A módszer
segítségével könnyen közbeiktathatunk egy másik "adatfolyarnréteget" az
adatok kiírása előtt. A Binarywriter konstruktora bármilyen, stream-leszár­
mazott típust (pl. Fil estream, Memorystream vagy Bufferedstream) kezelhet
Vagyis, ha a memóriába bináris adatot szeretnénk írni, egyszerűen adjunk
meg egy érvényes Memorystream objektumot.
A bináris adatok kiolvasására a Bin File.dat fájlból a BinaryReader típus

több lehetőséget is ad. Meghívunk több olvasásközpontú tagot, hogy kiolvas­


sanak minden adattöredéket a fájlfolyamból:

static void Main(string[] args)


{

Fileinfo f = new Filernfo("BinFile.dat");

ll Kiolvassuk a bináris adatokat az adatfolyamból.


using(BinaryReader br = new BinaryReader(f.openRead()))
{
Console.writeLine(br.ReadDouble());
console.writeLine(br.Readint32());
Console.writeLine(br.ReadString());
}
console.ReadLine();
}

Forráskód A BinaryWriterReader projektet a forráskódkönyvtárban a 20. fejezet alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

33
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Fájlok programozott "figyelése"

A következőkben nézzük meg a Filesystemwatcher osztály szerepét. Ez a típus


meglehetősen hatékonyarr segít a rendszerben található fájlok programozott
felügyeletében (vagy megfigyelésében). Ez azt jelenti, hogy a Filesystem­
watcher típust utasítha�uk a fájlok felügyeletére a system. 10. NotifyFilters ál­

tal felsorolt műveletek tekintetében (bár a tagok többsége egyértelmű, célszerű


elolvasni a .NET Framework 3.5 SDK dokumentációjának idevágó részeit):

public enum NotifyFilters


{
Attributes, CreationTime,
DirectoryName, FileName,
LastAccess, Lastwrite,
security, Size,
}

A Filesystemwatcher használatának első lépéseként be kell állítanunk a Path


tulajdonságot a megfigyelendő fájlokat tartalmazó mappa elérési útvonalára,
valamint a Filter tulajdonságban meg kell adnunk a felügyelendő fájlok ki­
terjesztéseit.
Itt eldönthe�ük, hogy kezeljük-e a changed, created és oeleted eseménye­
ket, amelyek mindegyike a FilesystemEventHandler metódusreferenciával
dolgozik együtt. Ez a metódusreferencia bármely, a következő mintát követő
metódust meghívha�a:

ll A FilesystemEventHandler metódusreferenciának egy,


ll a következő szignatúrának megfelelő metódust kell meghívnia.
void MyNotificationHandler(object source, FilesystemEventArgs e)

A Renamed eseményt is kezelheljük a RenamedEventHandler metódusreferencia­


típussal, amely a következő szignatúrának megfelelő metódusokat hívha�a meg:

ll A RenamedEventHandler metódusreferenciának egy,


ll a következő szignatúrának megfelelő metódust kell meghívnia.
void MyNotificationHandler(object source, RenamedEventArgs e)

A fájlok megfigyelésének folyamatát a következő példa szemlélteti; ehhez azt


feltételezzük, hogy a C meghajtón létrehoztunk egy MyFolder nevű új könyvtá­
rat, amelyben különböző . txt fájlok találhatók (tetszés szerint elnevezve). Az
alábbi (MyDirectorywatcher nevű) konzolalkalmazás a MyFolder könyvtárban
van. A txtfájlokat fogja felügyelni, és üzenetet ír ki a konzolra, ha új fájlt ho­
* .

zunk létre, illetőleg meglévő fájlt törlünk, módosítunk vagy átnevezünk:

34
Fájlok programozott "figyelése"

static void Main(string[] args)


{
console.writeLine("***** The Amazing File watcher App *****\n");

ll Megadjuk a megfigyelendő mappa elérési útját.


Filesystemwatcher watcher = new Filesystemwatcher();
try
{
watcher.Path = @"c:\MyFolder";
}
catch(ArgumentException ex)
{
console.writeLine(ex.Message);
return;
}

ll Beállítjuk a manitorozandó eseményeket.


watcher.NotifyFilter = NotifyFilters.LastAccess
NotifyFilters.LastWrite
NotifyFilters.FileName
NotifyFilters.DirectoryName;

ll csak szövegfájlokat figyelünk.


watcher.Filter = "*.txt";

ll Hozzáadjuk az eseménykezelőket.
watcher.changed += new FilesystemEventHandler(onchanged);
watcher.created += new FilesystemEventHandler(onchanged);
watcher.Deleted += new FilesystemEventHandler(onchanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);

ll Megfigyeljük a könyvtárat.
watcher,EnableRaisingEvents = true;

ll várunk, amíg a felhasználó ki nem lép.


console.Writeline(@"Press 'q' to quit app.");
while(console.Read()!='q');
}

A két eseménykezelő egyszerűen kiírja az aktuális fájl módosításait:

static void onchanged(object source, FileSystemEventArgs e)


{
ll Mi történjen, ha a fájl megváltozik, létrehozzák vagy letörlik?
console.writeLine("File: {O} {1}!", e.FullPath, e.changeType);
}

static void OnRenamed(object source, RenamedEventArgs e)


{
ll Mi történjen, ha a fájlt átnevezik?
console.Writeline("File: {O} renamed to\n{l}", e.oldFullPath,
e.FullPath);
}

35
20. fejezet: Fájlműveletek és elszigetelt tárolás

A program kipróbálásához indítsuk el az alkalmazást, és nyissuk meg a


Windows Intézőt. Próbáljuk meg átnevezni a fájlokat, hozzunk létre és töröl­
jünk egy *.txt fájlt stb. A MyFolder könyvtárban lévő szövegfájlok állapotá­
val kapcsolatos különböző információk láthatóvá válnak (lásd 20.8. ábrát).

20.8. áb ra : Szövegfájlok megfigtjelése

Forráskód A MyDirectoryWatcher alkalmazást a forráskódkönyvtárban a 20. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Aszinkron fájlolvasás és -irás

A System. ro névtér áttekintésének befejezéseként nézzük meg, miként hajtha­


tunk végre aszinkron műveleteket a Filestream típussaL Az első kötetben, a
többszálú feldolgozás elemzésekor már láthattuk, hogyan támoga�a a .NET
keretrendszer az aszinkron műveleteket (lásd a 18. fejezetet). Mivel a nagy
fájlok írása és olvasása időigényes művelet lehet, a system. ro. stream osztály­
ból származó típusok több, az aszinkron adatfeldolgozást lehetővé tevő me­
tódussal rendelkeznek. Ezek a metódusok az IAsyncResul t típust használják:

public abstract class stream :


MarshalByRefobject, IDisposable
{

public virtual IAsyncResult BeginRead(byte[] buffer, int offset,


int count, Asynccallback callback, object state);
public virtual IAsyncResult Beginwrite(byte[] buffer, int offset,
int count, Asynccallback callback, object state);
public virtual int EndRead(IAsyncResult asyncResult);
public virtual void Endwrite(IAsyncResult asyncResult);
}

36
Aszinkron fájlolvasás és -írás

A stream osztályból származtatott típusok aszinkron viselkedésével végzett


munka hasonló ahhoz, mint amikor aszinkron metódusreferenciákkal és
aszinkron távoli metódushívásokkal dolgozunk. Bár nem valószínű, hogy az
aszinkron viselkedés nagyban meggyorsítja a fájlok elérését, más adatfolyamok
(pl. a csatlakozópont-alapúak) aszinkron kezelése során jelentősebb teljesít­
ménynövekedést tapasztalhatunk. A következő (AsyncFilestream nevű) kon­
zolalkalmazás bemutatja a Filestream típus aszinkron kezelésének egyik mód­
ját (mindenképpen importáljuk a system. Threading és a system. ro névtereket):

class Program
{
static void Main(string[] args)
{
console.writeLine("***** Fun with Async File IlO *****\n");

Console.writeLine("Main thread started. ThreadiD ={O}'',


Thread.currentThread.GetHashcode());

ll Egy Filestream aszinkron olvasási vagy írási hozzáféréséhez


ll ezt a konstruktort kell használnunk.
Filestream fs = new Filestream("logfile.txt", FileMode.Append,
FileAccess.write,
Fileshare.None, 4096, true);

string msg = "this is a test";


byte[] buffer = Encoding.ASCII.GetBytes(msg);

ll Az aszinkron írás elkezdése. A program a writeoone-t hívja


ll meg, ha végzett. Figyeljük meg, hogy a visszahívási metódus
ll a Filestream objektumot kapja meg állapotinformációként.
fs.Beginwrite(buffer, O, buffer.Length,
new Asynccallback(writeDone), fs);
}

private static void writeDone(IAsyncResult ar)


{
console.writeLine("Asynccallback method on ThreadiD ={O} ",
Thread.currentThread.GetHashcode());

stream s = (Stream)ar.Asyncstate;
s.Endwrite(ar);
s.close();
}
}

A fenti példa egyetlen érdekessége az, hogy a Filestream típus aszinkron visel­

kedésének engedélyezéséhez csak a példában szereplő konstruktort használhat­


juk. A legutolsó, system.Boolean típusú paraméter (ha igaz értéket kap) arra uta­
sítja a Filestream objektumot, hogy a munkát egy másik szálon végezze.

37
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Forráskód A AsyncFileStream alkalmazást a forráskódkönyvtárban a 20. fejezet alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az elszigetelt tároló szerepe

Minden előző, fájl If O-val kapcsolatos példánkban az alkalmazás végrehajtá­


sakor azt feltételeztük, hogy a program a CLR-től mindent megengedő (full
trust) biztonsági privilégiumot kap. Egy szárrútógép merevlemezéről olvasni
vagy a lemezre írni potenciális biztonsági fenyegetést jelenthet az alkalmazás
eredetétől függően. A .NET platform támogatja, hogy különböző helyekről
szerelvényeket töltsünk le, többek között külső webhelyekről, az intranetről,
de akár a System. Refl ee ti on. Em i t névtér típusainak segítségével dinamiku­
san, a memóriában létrehozott új szerelvénnyel is megtehetjük ezt (lásd az
előző kötet 19. fejezetét).
Az első kötet 15. fejezetében szó volt arról, hogy az ügyfél *.confi g fájljá­
nak <codeBase> eleme lehetővé teszi, hogy deklaratív módon tetszőleges el­
érési utat határozzunk meg, ahonnan a CLR külső szerelvényeket tölthet be,
míg az Assembly.LoadFrom() metódussal pedig (lásd a 16. fejezetet) progra­
mozottan tölthetünk be külső URI-ról szerelvényt

Bizalom kérdése

Figyelembe véve, hogy .NET-szerelvényeket a számítógép helyi merevleme­


zén kívül számos különböző helyről tölthetünk be, a bizalom kérdése igen
fontossá válik. Tegyük fel például, hogy van egy olyan alkalmazásunk,
amely távoli helyről letöltött kódot futtat. Ha ez a kódkönyvtár a helyi számí­
tógépről fájlokat próbál olvasni, vajon hogyan bizonyosodhatunk meg arról,
hogy nem akar érzékeny adatokat olvasni? Hasonlóképpen, ha távoli számí­
tógépről töltünk le végrehajtható fájlt, mi garantálja, hogy a szerelvény nem
próbál érzékeny adatokat kiolvasni a rendszerleíró adatbázisból, nem intéz
(rosszindulatú) hívásokat az operációs rendszer mögöttes API-jához, vagy
nem jelent egyéb potenciális biztonsági kockázatot?
Erre a .NET platform válasza a kóderedet-alapú biztonság (CAS) elnevezésű,
.NET-központú biztonsági mechanizmus. A CAS segítségével a CLR biztonsági
kiváltságokat adhat a végrehajtó szerelvénynek; többek között az alábbiakat:

38
Az elszigetelt tároló szerepe

• a számítógép könyvtár- és fájlrendszerének módosítása,

• hálózati, internet- és adatbáziskapcsolatok kezelése,

• új alkalmazástartományok és dinamikus szerelvények létrehozása,

• a .NET reflexiós szolgáltatás használata,

• nem felügyelt kód meghívása a Pinvoke segítségéveL

Az 1/O-specifikus biztonsági kérdésekre koncentrálva tételezzük fel, hogy


olyan alkalmazást fejlesztünk, amelyet egy távoli, webalapú URL-en keresz­
tül telepítünk külső felhasználóknak Attól függően, hogy a telepítőszkriptet
hogyan állítjuk be (és hogy a felhasználó számítágépén milyen biztonsági
házirend van érvényben), lehetséges, hogy a program a helyi fájlrendszer el­
érését tiltó szigorú biztonsági környezetben fut. Ha az alkalmazás felhaszná­
lói vagy alkalmazásbeállításokat próbál eltárolni a system. ro névtérrel, a CLR
futásidejű biztonsági kivételeket fog dobni.
A system. ro. r so l atedstarage névtér típusai használhaták olyan alkalmazás
létrehozására, amely a .NET-et futtató számítógép speciális, elszigetelt tárolónak
nevezett területén tárolja az alkalmazás adatait. Ez valójában egy biztonságos
futtatókörnyezet (angolul: "sandbox"), amelyben a CLR lehetövé teszi az írási/
olvasási műveleteket még akkor is, ha az alkalmazást külső URL-ről töltötték le,
vagy a rendszergazda más okból helyezte ebbe a virtuális környezetbe.

Az elszigetelt tárolóhoz tartozó API egyéb


felhasználási módjai

Az elszigetelt tárolóhoz tartozó API-t nem kizárólag a távoli számítógépekről


letöltött kódok olvasási/írási műveleteinek szabályozására használhatjuk Az
API-val azt is könnyen megvalósíthatjuk, hogy a felhasználói adatokat más
alkalmazás közvetetten (vagy közvetlenül) ne módosíthassa. Írhatunk például
olyan egyszerű alkalmazást, amely az adott munkaállomás összes felhaszná­
lójának adatát elkülönített mappákban tárolja.
Az elszigetelt tárolóhoz tartozó API használatának másik előnye az, hogy
a kódnak nincs szüksége bedrótozott elérési utakra vagy könyvtárnevekre a
programban. Ehelyett az elszigetelt tároló használatakor az alkalmazás indi­
rekt módon menti a kód azonosítójával (pl. URL, erős név vagy X509 digitális
aláírás) valamilyen módon összefüggésbe hozható egyedi adattároló területre
az adatokat (a kódazonosítók ismertetését lásd később).

39
20. fejezet: Fájlműveletek és elszigetelt tárolás

Szerencsére az elszigetelt tároló kezelése igen egyszerű azoknak, akik ér­


tik az alapvető fájlműveleteket. Az elszigetelt tároló működésének tanulmá­
nyozása előtt nézzük át a .NET kódhozzáférés-szabályozási modelljét.

Megjegyzés A CAS teljes bemutatása legalább egy (vagy két) teljes fejezetet igényeine,
ezért a CAS működését kizárólag olyan szinten tekintjük át, hogy megérthessük az elszigetelt
tároló-API szerepének alapjait. A CAS működésének további részleteit a .NET Framework 3.5
SDK tartalmazza.

Bevezetés a kóderedet-alapú
biztonságba

A távoli .NET-szerelvények letöltése és futtatása által jelentett biztonsági


problémák megelőzésének érdekében a CLR automatikusan azonosítja a sze­
relvényt, és besorolja az előre meghatározott kódcsoportok egyikébe. Leegy­
szerűsítve a kódcsoport egy olyan szerelvénygyűjtemény, amelynek tagjai
ugyanazon feltételeknek felelnek meg (pl. ugyanaz az eredetük).
Azt a feltételt, amely alapján a CLR eldönti egy szerelvényről, hogy az
melyik kódcsoportba tartozik, bizonyítéknak nevezzük. A szerelvényeket ere­
detükön kívül más bizonyíték alapján is besorolhatjuk egy kódcsoportba.
Ilyen lehet a szerelvény erős neve, egy beágyazott X509 digitális tanúsítvány
vagy bármely, általunk programozottan elkészített egyedi feltétel.

Megjegyzés Szigorúan véve kétféle bizonyítékról beszélhetünk: ezek a szerelvényalapú és a


hosztalapú bizonyítékok. Míg a szerelvényalapú bizonyítékat lefordítjuk a szerelvénybe, a
hosztalapú bizonyítékat programozottan, az AppDomai n típus segítségével határozhatjuk meg.

Miután a CLR kiértékelte a szerelvény bizonyítékait, és eldöntötte, hogy me­


lyik kódcsoportba kerüljön, annak érdekében, hogy meghatározhassa, a sze­
relvény mit tehet, és főként mit nem tehet, lekérdezi a kódcsoporthoz társított
engedélykész/etet (ez egyszerűen a különálló engedélyek nevesített készlete).
A kódcsoportok és a hozzájuk tartozó engedélyek együtt alkotják a bizton­
sági házirendet, amelyet három nagyobb szintre oszthatunk (vállalati, gép-, il­
letve felhasználószintű). Ennek a réteges megközelítésnek a használatával a
rendszergazdának lehetősége van arra, hogy egyedi házirendet alakítson ki a
vállalat, illetve az egyes számítógépek/ felhasználók szintjén.

40
Bevezetés a kóderedet-alapú biztonságba

Miután rnind a három (vállalati, számítógép- és felhasználói) biztonsági


házirend alkalmazása megtörtént, a szerelvény lefut a .NET futásidejű kör­
nyezetében. Ha a szerelvény a jogosultsági készletébe nem tartozó kódot
próbál futtatni, a CLR futásidejű biztonsági kivételt fog dobni. A 20.9. ábra
muta�a be, hogy a CAS ezen építőelemei (bizonyíték, kódcsoportokj enge­
délykészletek és házirendek) hogyan fonódnak össze.

Alkalmazástartomány
Házirend beállítások
(Enterprise, Machine, User)
MyAsm.exe
rBizonyilék.,l Kód-
csoport
� Biztosflott
engedélyek

r-?
20. 9. ábra: A CAS építőelemei

A bizonyíték kiértékelésének, a szerelvény kódcsoportba sorolásának és a jo­


gosultsági készletek hozzárendelésének folyamata rninden alkalommal, egy
.NET-alkalmazás futtatásakor a háttérben, észrevehetetlenül zajlik le. Leg­
többször az alapértelmezett CAS biztonsági beállítás, valarnint a CLR és a
CAS közötti interakció a háttérben történik, felhasználói beavatkozás nélkül.
Érdemes azonban kicsit részletesebben szemügyre venni a CAS építőelemeit,
kezdve a szerelvénybizonyíték fogalmával.

A bizonyftékok szerepe

Annak érdekében, hogy a CLR meghatározza, hogy egy szerelvény melyik


kódcsoportba kerüljön, először kiolvassa a megadott bizonyítékot. A bizonyí­
ték tehát olyan adat, amelyet a szerelvényből (vagy az azt hasztoló alkalma­
zástartományból) szerzünk meg, arnikor az betöltődik a memóriába. A 20.12.
táblázat írja le azokat a főbb bizonyítéktípusokat, amelyet a szerelvény be­
mutathat a CLR-nek.

41
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Alkalmazáskönyvtár A szerelvény telepítési könyvtára.

Szerelvényhashkód A szerelvény tartalmának hashértéke.

Kibocsátó tanúsítványa A szerelvényhez rendelt Authenticode X509-es digitális


tanúsítvány (ha van).

Hely Az a forráswebhely, amelyről a szerelvényt betöltötték


(nem érvényes a helyi gépről betöltött szerelvények
esetében).

A szerelvény erős neve A szerelvény erős neve (ha van ilyen).

URL Az az URL, ahonnan a szerelvény betöltődött (HTIP,


FTP, fájl stb.).

Zóna Annak a zónának a neve, ahonnan a szerelvény betöl­


tődött.

20.12. táblázat: Különböző típusú szerelvénybizonyítékok

Bár a bizonyítékok beolvasása automatikusan törtémk, arra is van lehetősé­

günk, hogy a bizonyítékokat programozottan olvassuk be a reflexiós API és a


system.seeuri ty. Poliey névtér Evidence típusának a segítségéveL A bizonyíté­
kok megértéséhez hozzunk létre egy új, MyEvidenceviewer nevű konzolalkal­
mazást Ne felejtsük el importálill a system.Reflection, a System.Collections
és a System. seeurity. Poli ey névtereket
Ezután egy olyan egyszerű alkalmazást írunk, amely megkéri a felhaszná­
lót, hogy adja meg a betöltendő szerelvény nevét. Számba vesszük valameny­
nyi szerelvénybizonyítékot, majd az eredményt kiírjuk a konzolablakba. Elő­
ször is, a Program osztály Main() metódusa lehetővé teszi, hogy a felhasználó
megadja a kiértékelendő szerelvény teljes elérési útját. Ha az L lehetőséget
választja, meghívunk egy segédmetódust, amely megpróbálja betöltem a
megadott szerelvényt a memóriába. Ha sikerült, átadjuk az Assembly referen­
ciát egy másik, DisplayAsmEvidence() nevű segédmetódusnak. A megvalósí­
tás a következő:

class Program
{
static void Main(string[] args)
{
bool isuserDone = false;
st ring userOption = '"' ;
Assembly asm = null;

42
Bevezetés a kóderedet-alapú biztonságba

console_writeLine("**;"'* Evidence viewer *****\n");


do
{
console.write("L (load assembly) or Q (quit): ");
userOption = Console.ReadLine();
switch (useroption.ToLower())
{
case "l":
asm = LoadAsm();
if (asm != null)
{
DisplayAsmEvidence(asm);
}
break;

case "q":
isuserDone = true;
break;

default:
Console.writeLine("I have no idea what you want!");
break;
}
} while (!isuserDone);
}
}

A LoadAsm() metódus egyszerűen meghívja az Assembly. Load Form() metó­


dust, hogy beállítsa a privát Assembly tagváltozót:

private static Assembly LoadAsm()


{
console.write("Enter path to assembly: ");
try
{
return Assembly.LoadFrom(console.ReadLine());
}
catch
{
console.writeLine("Load error... ");
return null;
}
}

Végül pedig a DisplayAsmEvidence() metódus olvassa ki a betöltött szerel­


vényből a bizonyítékat az Assembly típus Evidence tulajdonságának révén_
Ezután egy felsoroló segítségével (az Evidence típus GetHostEvidence() me­
tódusával) kiírjuk az összes jelenlévő bizonyítéktípust:

43
20. fejezet: Fájlmüveletek és elszigetelt tárolás

private static void DisplayAsmEvidence(Assembly asm)


{
ll A bizonyítékgyűjtemény és a mögöttes felsoroló lekérése.
Evidence e = asm.Evidence;
IEnumerator itfEnum = e.GetHostEnumerator();

ll A bizonyíték kiírása.
while (itfEnum.MoveNext())
{
console.WriteLine(" '"''** Press Enter to continue ****");
console.ReadLine();
console.writeLine(itfEnum.current);
}
}

Az alkalmazás teszteléséhez érdemes a C meghajtó gyökerében létrehozni


egy MyAsms mappát, majd ebbe a mappába belemásolni az erős névvel ren­
delkező carLibrary.dll szerelvényt (lásd az első kötet 15. fejezetét), végül el­
indítani a programot. Feltételezve, hogy az L parancs mellett döntünk, adjuk
meg a szerelvény teljes elérési útvonalát, majd nyomjuk meg az Enter billen­
tyűt. Az alkalmazás kiírja a konzolra az összes bizonyítékot, ahogy az a 20.10.
ábrán is látható (fontos, hogy az alkalmazás konstrukciója szerint minden
egyes bizonyítéktípus megjelemtése után Entert kell ütni).

20.10. ábra: A CarLibrary.dll bizonyítékainak megtekintése

44
Bevezetés a kóderedet-alapú biztonságba

Látható, hogy a carLibrary.dll a MyComputer zónába került a C:\MyAsms\


CarLibrary.dll URL-ről, és rendelkezik erős névvel. Ha a szerelvényt más
helyről töl�ük be (mondjuk távoli webhelyről), akkor teljesen más kimenetet
fogunk kapni. A lényeg az, hogy a szerelvény memóriába való betöltésekor a
CLR megvizsgálja a szerelvény által szaigáitatott bizonyítékokat.

Forráskód A MyEvidenceViewer projektet a forráskódkönyvtárban a 20. fejezet alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A kódcsoportok szerepe

A bizonyítékok használatával a CLR a szerelvényeket kódcsoportokba sorolhat­


ja. Minden kódcsoportot egy biztonsági zónához rendeltek, amelyek gyárilag
meghatározott biztonsági beállításokkal rendelkeznek. A CLR ezek segítségével
határozza meg, hogy a szerelvény mit tehet meg, és mit nem. A 20.13. táblázat a
leggyakoribb kódcsoportokat muta�a be az alapértelmezés szerint hozzájuk
rendelt engedélykészlettel együtt (ezekről a következő részben lesz szó ).

Jelentés

My_Computer_Zone Full Trust Közvetlenül a helyi merevlemezről be­


töltött szerelvényt jelképez.

Locallntranet_Zone Localintranet A helyi intranet egy megosztási pon�á­


ról letöltött szerelvényt jelez.

Internet_Zone Internet A webről letöltött szerelvényt jelez.

20.13. táblázat: Néhány gJJakoribb kódcsoport

A .NET keretrendszer 3.5 SDK egy olyan grafikus felügyeleti eszközt biztosít,
amellyel a rendszergazdák megnézhetik és módosítha�ák a meglévő kód­
csoportokat, vagy szükség szerint újat hozhatnak létre. Az eszköz segítségé­
vel beállítha�uk például, hogy a CLR minden, adott címről (pl. http:/ j www .
intertech.com) letöltött külső szerelvényt egy testre szabott biztonsági kör­
nyezetben futtasson.

Megjegyzés A .NET Framework 3.5 SDK egy c aspol.exe nevü parancssori programot is tar­
talmaz, amellyel ugyanezeket a beállításokat érhetjük el.

45
20. fejezet: Fájlmüveletek és elszigetelt tárolás

Ezt az eszközt a Control Panel Administrative Tools mappájában találjuk Mic­


rosoft .NET Framework Configuration néven. Az eszköz elindítása után a
eAS-beállításokat három szinten módosítha�uk: vállalati szinten (pl. minden
hálózatba kapcsolt gép), a helyi gép szin�én, illetve felhasználónként (A CAS-t
alkalmazástartományi szinten is beállítha�uk, de ezt csak programozottan te­
he�ük meg.) A 20.11. ábra a CAS alapértelmezett "számítógépszintű házirend"
beállításainak lenyitott csomópon�át muta�a aktuálisan.
Az All_Code kódcsoport (amely az összes .NET-szerelvényt jelenti) több
olyan zónát is meghatároz, amelybe a szerelvény tartozhat (pl. My_Com­
puter_Zone stb.). Ha jobb gombbal kattintunk az All_Code gyökér alatt talál­
ható bármelyik csomópontra, megnyitha�uk az adott objektum tulajdonság­
lapját, amely további részletekkel szolgál. A 20.12. ábra például a My_Com­
puter_Zone kódcsoport (amely a közvetlenül a helyi merevlemezről betöltött
szerelvényeket tartalmazza) tulajdonságait muta�a.

(@ Configur�d Assemblies
8: Remeting S�rvices
� � Runtime Security Policy
r> � Enterprise
• l!!!! Mach·i,;-e
" .ctJ Code Groups
• � AII_Code
� � My_Computer_Zone
t> � Locallntranet_Zone
� � Jnternet_Zone
� Restricted_Zone
� � Trusted_Zone
� � Permission Sets
®J FuiiTrust
ú!J SkipVerification
� Execution
®J Nothing
� localintranet
@] Internet
®J Everything
4i Policy Assemblies
p fl User
� Applications
20. 11. ábra: A CAS-központú számítógépházirend

A Membership Condition fülre kattintva látható, hogy a CLR mi alapján dönti


el, hogy egy adott .NET-szerelvénynek ebbe a zónába kell-e tartoznia. A 20.13.
ábra muta�a, hogy a tagsági feltétel a nem túl beszédes Zone-érték, amely azt a
helyet jelöli, ahonnan a szerelvényt betöltöttük

46
Bevezetés a kóderedet-alapú biztonságba

--
My_Computer_Zone Properties
r��. !jrjf3i
Gene!al LMent>ersllp Úlndlion l.Pesnission Setl
Code group name:

M)._a.;;;,;;;_�------------------.
' -
Code group description:
Code g:oup grants fuitrust to al code O!iginating on the local """'")!Íer

J the merrbership condition ls mel:


This policy le'/el will only have the permissíons from the
O permission set associated with this code group

O Policy levels below this levelv.;u not be evalualed 'l

l
l OK
ll Cancel
IG71
20.12. ábra: A My_Computer_Zone kódcsoport részletei

My_Computer_Zone Properties �
,l Geu•ral )[:�-�§:]\�P���
-�
- iSIIOil Set? ..J.j
enti _____ ---,
The membership condition specifies the condition that an assembly
must meet in order to be a member ol a code group,

Choose the condition type for this code group:

[Zone ;l
The Zone .-nbership condition is true for ali assemblies that originate
from the zone listed below. Assembliesthat meet this membership
condition will be granted the permissioos associalt!d wilh this code
group,

�one:
[My Ú>n1><Je< ·l
This zone containsaU applications installed on ycur C<XJ1>U!er.

QK ll �ancel l �-bPPiQ
20.13. ábra: A My_Computer_Zone kódcsoport tagsági feltételei

Ha a My_Computer_Zone kódcsoport Pennission Set fülére kattintunk, azt


lá�uk, hogy minden, a helyi merevlemezről betöltött szerelvény a Full Trust
elnevezésű biztonsági engedélykészletet kapja meg (lásd a 20.14. ábrát).

47
20. fejezet: Fájlműveletek és elszigetelt tárolás

My_Computer_Zone Properties

1 G:<"""' 1 � Conckion ���:L ___ _

Asserr' .blies that meet this code group's mernbership condition -�.
�-··
;--!
receive the permissions in the per�ssion set spedfied bela�t·. !
Pennission set:
I
l lnATrust �l
l
The selected perrnission set has the folla.�aing permissions:

ll! l
l

'l
ljl
l

ll___
.
-- _____

-·-· --'-·--
jl
, 1]e.•.· �ermission
· - ·

: ll

20.14. ábra: Az alapértelmezés szerint a helyi merevlemezről betöltött szerelvények


Full Trust engedélyt kapnak

��.

Internet_Zone Properties ·, �
l Generel l � Conckion j Pe<mission Set l
l
l
Assembliesthat meet this code groop·s membership condition Yoill
receive the pemVssions in the pennission set specified below.

Pennission set:

!�-temet �J l

ll
l ��
The selected permission set has the folloYoing permissions:
Rle Dialog
Isolated Storage Fde
� Sectrty
� Userl-teriace l
� Prtnting

r --�------,

c-2..r:.v Perrnf� l

l OK
ll Cancel
l! ,\p;Jfy l
20.15. ábra: A külső URL-ről betöltött szerelvények az alapértelmezés szerint nem kapják meg
a Full Trust engedélykész/etet

48
Bevezetés a kóderedet-alapú biztonságba

Ezzel szemben a külső URL-ről (a céges intraneten kívülről) betöltött szerelvé­


nyek az Intemet_Zone kódcsoportba kerülnek, amely jóval szigorúbb jogosult­
ságcsoportba tartozik. Ahogy a 20.15. ábrán is látható, az Intemet_Zone cso­
portba sorolt szerelvények nem a Full Trust engedélyt kapják meg, hanern az

Intemet nevű engedélykészlettel fognak rendelkezni.

Az engedélykészletek szerepe

A CLR különböző feltételek alapján sorolja a szerelvényeket kódcsoportokba (a


betöltés eredete, erős név stb. alapján). A kódcsoporthoz pedig egy barátságos
névvel rendelkező (Full Trust, Intemet stb.) engedélykészlet tartozik. Mind­
egyik engedélykészlet (rnint azt a neve is rnuta�a) olyan egyedileg beállított
engedélyek gyűjteménye, amelyek különböző biztonsági beállításokat vezérel­
nek (nyorntatóhoz vagy fájlrendszerhez való hozzáférés, reflexiós API haszná­
lata stb.). A Microsoft .NET Framework Configuration segédprogramban a
View Permissions hivatkozásra kattintva nézhe�ük meg az alapértelmezett en­
gedélykészletek beállításait. A 20.16. ábra az Intemet-engedélykészlet beállítá­
sait rnuta�a a View Perrnissions hivatkozásra való kattintás után.

"' � My Computer A 11 Permission


� Assembly Cache � File Dialo;
� Configured Assemblies � Isolllled Starage File
6] Remoting Servic<S
� Security
"' 4ll Runtime Se:curity Policy
t> � Enterprise @) User Interface
A g Machine
� Printing
t> ctl Code Groups
A � Permission Sets
[@J Fui!Trust
®J SkipVerification
®J Execution
[@J Nothing
®J locaUntranet
- ·
®l lnt�,-�- et
[@J EverYthing
� Policy Ass.mbli<S
> f} Um
� Applications

20. 16. ábra: Internet engedélykészlet

Az itt látható ikonok (File Dialog, Isolated Storage File, Security stb.) rnind a
készlet egy-egy specifikus engedélyét képviselik, amelyeket még részleteseb­
ben beállíthatunk, ha duplán kattintunk rájuk. Ha például kétszer kattintunk
a Security engedélyre (amely az általános biztonsági beállítások gyűjtőenge­
délye), azt látha�uk, hogy az Intemet_Zone alatt futó szerelvény (amelyet

49
20. fejezet: Fájlműveletek és elszigetelt tárolás

ezért az Internet engedélykészlet korlátoz) elindítható, de az alapértelmezés


szerint nem hajthat végre számos egyéb alapvető műveletet, nem használha�a
például a platformhívási szolgáltatást arra, hogy meghívja az operációs rend­
szer API-ját (lásd a 20.17. ábrát).

Perrnission Viewer (Read-Only) �


Security Permission:

Pemlission j Granted J

Enable Code Execution Yes


Allow Calls to Unmanaged Code No
Assert any permissioo that has been granted No
Skip Verification No
Enable thread control No
Allow Policy Control No
Ali<1N Domain Policy Control No
AllaH Prir.cipal Cootrol No
Create and Control Application Domaíns No
Serializalion FormaHer No
Alla" Evidence Control No
Extend Infrastructure No
Enable Remoting Configuration No

l Clooe
l
20.17. ábra: Az engedélykészlet egtjes engedélyeinek megtekintése

A CAS müködése

A CAS-műveletek bemutatása érdekében megváltozta�uk a My_Computer


kódcsoport néhány alapértelmezett beállítását, majd megnézzük az ered­
ményt. A kapcsolódó tulajdonságlap megnyitásához kattintsunk jobb gomb­
bal a Microsoft .NET Framework Configuration alkalmazásban a számítógép­
házirendhez tartozó My_Computer_Zone kódcsoportra. Ezután kattintsunk a
Permission Set fülre, majd módosítsuk az engedélykészletet Full Trustról In­
ternetre (lásd a 20.18. ábrát).
Az OK gombra kattintva a program módosí�a a helyi számítógép bizton­
sági házirendjét Ez ebben az esetben azt jelenti, hogy a merevlemezről betöl­
tött .NET végrehajtható fájlok többé nem fogják tudni elérni a helyi merev­
lemezt a system. ro névtér típusainak felhasználásával. Ellenőrzésképpen in­
dítsuk el valamelyik korábban készített !O-intenzív alkalmazásunkat Hasz­
náljuk a parancssort arra, hogy a korábban készített ori vernfoApp. e xe alkal­
mazást tartalmazó könyvtárra váltsunk, és próbáljuk meg elindítani a prog­
ramot. Ha megjelenik a futásidejű hibát jelző párbeszédpanel, válasszuk a
Close the Program lehetőséget, majd figyeljük meg a parancssori kimenetet.

50
Bevezetés a kóderedet-alapú biztonságba

A 20.19. ábrán látszik, hog y az alkalmazás nem tudott hozzáférni a helyi


merevlemezhez, és a CLR biztonsági kivételt dobott.

My_Computer_Zone Properties f!.Q-i


l Gene<� �Condition l Penni s sion Set l
Assembliesthat meet this code group"s membership condition -..ill
receive the permissions in the pennission set spe<:ified befow.

Pennission set:
lirtemet ·l
The selected pennission set has the folla...ing permissioos:
'@j FJ!e Dialog l
@l Isolated Starage Fle
® Secuity
® User Irtefface
@) Pmting

--------
t V��N_Permi:�!�;

[ OK
ll Cancel
ll Apply l
20.18. ábra: A My_Computer_Zone kódcsoport engedélykészletének módosítása

20.19. ábra: Biztonsági veszély. A My_Computer_Zone kódcsoport nem engedélyezi


I0-míiveletek végrehajtását

51
20. fejezet: Fájlműveletek és elszigetelt tárolás

Full Trust jogosultság visszaállitása a My_Computer_Zone


kódcsoportra

A Microsoft .NET Framework Configuration alkalmazás segítségével állítsuk


vissza a My_Computer_Zone kódcsoportra a Full Trust engedélykészletet.
Ezután a Dr i vernfoApp. ex e programnak hiba nélkül le kell futnia.

Megjegyzés Ha a számítógépre vonatkozó eAS-beállításokat módosítjuk, előfordulhat, hogy


egy véletlen helytelen beállítás következtében nem futnak megfelelöen az alkalmazások. Ha
vissza kell állítani az alapértelmezett számítógépházirend-beállításokat, kattintsunk jobb
gombbal a Runtime Security Policy mappában található Machine ikonra, majd a helyi menüből
válasszuk a Reset parancsot.

A fenti rövid leírás nem adhat teljes áttekintést erről a rendkívül sokoldalú
API-ról, de ez alapján jobban megérthetjük az elszigetelt tároló szerepének a
fontosságát.

Az elszigetelt tároló

Az elszigetelt tárolóhoz tartozó API legfontosabb szerepe, hogy egy olyan


biztonságos virtuális környezetet hoz létre, amelybe az alkalmazások attól
függetlenül írhatnak vagy abból olvashatnak adatokat, hogy melyik kódcso­
portban találhatók, anélkül, hogy az alapértelmezett biztonsági házirendet
módosítani kellene. Ezenkívül ennek a technológiának a használata az alábbi
körülmények között is célszerű lehet.

• El kell menteni az alkalmazás felhasználói beállításait vagy egyéb fel­


használóhoz kapcsolódó adatokat (DataSet-ek, XML-fájlok stb.).

• Egy ClickOnce alkalmazást telepítünk, amely biztonságos futtatókör­


nyezetben működik, és nincs korlátlan hozzáférése a helyi fájlrend­
szerhez.

• Olyan Windows Forms-vezérlőelemet töltöttünk le az internetről,


amely egy webalkalmazásba integrálódik, és az ügyfélgépen kell beál­
lításokat tárolnia.

• XAML böngészőalkalmazást (XBAP, XAML Browser Application -


lásd a 28. fejezetet) telepítünk, amely a felhasználó számítágépén tárol
adatokat.

52
Az elszigetelt tároló

Az elszigetelt tároló használata nem jelenti azt, hogy ne tárolhatnánk adatot a


. con fi g fájlban (például kapcsolatsztringeket), vagy helyezhetnénk el külön­
böző alkalmazásbeállításokat a rendszerleíró adatbázisban. Mint minden
technológiának, az elszigetelt tárolónak is vannak hátrányai. Első és legfon­
tosabb az, hogy az elszigetelt tárolába helyezett adat nincs automatikusan
titkosítva. Ezért csakúgy, mint a hagyományos IO-műveletek esetén, az érzé­
keny adatokat (pl. hitelkártya-információkat) manuálisan kell titkosítanunk
(és visszafejtenünk).

Megjegyzés A .NET titkosító API-jának használata nem témája ennek a könyvnek. Erről rész­
letes információval szolgál a .NET Framework 3.5 SDK dokumentációjának Cryptographic Servi­
ces című része.

Csakúgy, mint a fájlrendszer más részein tárolt adatokat, a végfelhasználó az


elszigetelt tárolába helyezett fájlokat is átmásolha�a, áthelyezheti vagy letö­
rölheti. A system.IO.Isolatedstorage névtér olyan, dinamikusan generált
könyvtárstruktúrában tárolja az adatokat, amely el van rejtve a merevlemez
egy adott helyén. Így a legtöbb felhasználó soha sem találkozik közvetlenül a
számítógép elszigetelt tárolójávaL
A rendszergazda korlátozha�a az elszigetelt tároló által elfoglalható le­
mezterület nagyságát. Ha az alkalmazásunk mellett más alkalmazások is
nagy mennyiségű adatot tárolnak, lehetséges, hogy a tároló mérete eléri a
megengedett felső korlátot. Tanácsos a strukturált kivételkezelés segítségével
felkészülni erre az esetre.

Az elszigetelt tároló hatóköre

Természeténél fogva az elszigetelt tároló (legalább) felhasználónként elszige­


teli egymástól az adatokat. Ezért, ha a programunk az elszigetelt tárolába
ment adatokat, azokat a .NET az aktuálisan bejelentkezett felhasználó szint­
jén tárolja. Ha ugyanarra a munkaállomásra egy másik felhasználó is beje­
lentkezik, és ugyanabból az alkalmazásból adatokat ment el, az eltárolt ada­
tokat egy, az adott felhasználó számára egyedi helyen rögzíti.
A felhasználószintű elszigetelésen kívül az elszigetelt tároló szerelvény
és/vagy alkalmazástartomány-azonosító szin�én is képes az adatokat izolál­
ni. Ha az elszigetelést felhasználónként és szerelvényenként is engedélyez­
zük, az alkalmazás ugyanazt a tárolót fogja használni függetlenül attól, hogy
a programot melyik alkalmazástartomány hosztolja.

53
20. fejezet: Fájlműveletek és elszigetelt tárolás

Ebben az esetben az elérhető legjellegzetesebb bizonyíték alapján (pl. az


erős név) jön létre a tároló neve. Így elérhetjük, hogy ugyanazon a gépen egy
időben a program több példánya fusson, amelyek mind ugyanazt a tárolót
használják (lásd a 20.20. ábrát).

Elszigetelés felhasználó és szerelvény alapján

Felhasználó: Horner

A alkalmaz6startomány B allcalmaaástart

MyAsm.exe MyAsm.exe

Elszigetelt tároló
1. tár

20.20. ábra: Felhasználó- és szerelvényszintű izoláció

Másrészről, ha felhasználó + szerelvény + alkalmazástartomány elkülönítési


szintet állítunk be, akkor a .NET az alkalmazástartományt is figyelembe ve­
szi. A legjellegzetesebb szerelvénybizonyítékokon kívül a legjellegzetesebb
alkalmazástartomány-bizonyítékot Gellemzően a Site) is felhasználja a rend­
szer a tároló létrehozásához. Ekkor, még ha ugyanazt a szerelvényt is hasz­
náljuk több egyedi alkalmazástartományból, az alkalmazás adatai külön táro­
lókba kerülnek (lásd a 20.21. ábrát).
Az elszigetelt tároló használatával vándorló (roarning) profilok is kiala­
kíthatók. A vándorló hatókör lehetővé teszi, hogy bár a felhasználó különbö­
ző számítógépekre jelentkezik be, mégis ugyanazt az alkalmazásadatot kapja
meg. Ilyen esetben az adattárolás hálózati helyen történik, és igény szerint le­
töltődik, ha a felhasználó bejelentkezik egy adott munkaállomásra. Ennek
részleteiért olvassuk el a .NET Framework 3.5 SDK dokumentációjának ide
vonatkozó részét.

54
Az elszigetelt tároló

Elszigetelés felhasználó, szerelvény és alkalmazástartomány alapján

Felhasználó: Horner

A alkalmadstartom Baladi.-Astao....,

l MyAsm.exe
F'
l l MyAsm.exe
l

Elszigetelt tároló Elszigetelt tároló


1. tár 2. tár

20. 21. ábra: Felhasználó-, szerelvény- és alkalmazástartomány-szintű izoláció

Az elszigetelt tároló helye

Az elszigetelt tároló nem más, mint a .NET-et futtató számítógép fájlrendsze­

rének erre a célra szánt része, vagyis nem különbözik a C:\Windows, C:\Prog­
ram Files vagy bármely más, a merevlemezen lévő könyvtártóL Mindazonáltal
az elszigetelt tároló pontos helye az operációs rendszertől is függ. Egy adott tá­
roló gyökerének a helye egy Vistát futtató számítógépen:

C:\ Users\ <felhasználó>\App Data\Local\IsolatedStorage

Egy Windows XP számítógépen viszont:

C:\Documents and Settings\<felhasználó>\Local Settings\Application


Data\IsolatedStorage

Ez alatt található számtalan (teljesen kimondhatatlan nevű) alkönyvtár, arne­


lyeket az elszigetelt tárolóhoz tartozó API hoz létre és tart karban. A 20.22.
ábra a tároló helyét rnuta�a egy Vistát futtató számítógépen.
A könyvtárak neveivel vagy azzal, hogy ezeket programozottan megad­
juk a tárolók létrehozása során, nem kell foglalkoznunk. Ezek a nevek auto­
matikusan jönnek létre a felhasználó azonossága és a szükséges szerelvény
és/vagy alkalrnazástartomány-bizonyítékok alapján.

55
20. fejezet: Fájlmüveletek és elszigetelt tárolás

A bizonyítékok gyűjtése során a CLR az alábbi listát ellenőrzi (a legspeci­


fikusabb bizonyítékkal kezdve), ebben a sorrendben:

• Kibocsátó tanúsítványa

• Erős név

• URL

• Hely

• Zóna

.. • b'plore IS E-m�11l S: Share � Bum


·
Organr:e .., �� Vte.o.":, • .,
--�-- - - --- ------- - --- - ------- -----
Name Date modified Ty

hjphpkzn.lp2 6/25/200710:58 AM Fil


More )>

Folders vc

Users
Andre--v Troelsen
atrcelsen
.netbeans
AppData
" . local
Adobe
Apple Computer
:-- j Apps
assembly

�eployment
-
lsolatedStorage
hjphpkzn.lp2
Ocxcyjqm.vej
StrongName.vynmtjls31cezx2wwudolgtjg40jp530
• . Ur12tv.<>4ubjvbndio5xymfhzxpyjowojfrg
AssemFiles
Url221c2tyliqli0yoyzaa5gjr1it0jmqlx5
Url.lc2udjuowrvo40dz5ghjnw4oc0o0hw0ac
Url.u0gpakojhfxvdn4g4os10pklltpqgf5s

hjphpkzn.lp2 Date mcdif.ed: 6/25/200710:58 AM


File Folder

20.22. ábra: Elszigetelt tároló eg�; Vistát futtató számítógépen

56
Az elszigetelt tároló

Az elszigetelt tároló kezelése a storeadm.exe


segitségével

A .NET Framework 3.5 SDK tartalmaz egy storeadm. exe nevű parancssori
eszközt is, amellyel a számítógép aktuális tárolórendszerét kezelhe�ük. Az
eszköz segítségével megtekinthe�ük az éppen bejelentkezett felhasználó tá­
rolóit (a /l i st paraméterrel), és törölhe�ük az aktuális felhasználó tárolóit (a
/remove argumentummal). A 20.23. ábrán a /l i st kapcsaló kimenete látható.
Mint azt az előző ábra is muta�a, ez az eszköz megjeleníti az izoláció
szin�ét (szerelvény, alkalmazástartomány) és a tároló létrehozásához hasz­
nált bizonyítékokat is. De nem muta�a meg az egyes tárolókban található fáj­
lok tartalmát. Ehhez meg kell keresnünk a létrehozott tárolóhelyeket a Win­
dows Intézővel, vagy programozottan kell beolvasnunk az adatokat.

20.23. ábra: Az aktuális felhasználó tárolóinak megjelenítése a storeadm.exe programmal

A System.IO.IsolatedStorage tipusa

Mielőtt megnéznénk, hogy írhatunk (és olvashatunk) adatokat az elszigetelt


tárolóba, tekintsük át a 20.14. táblázatot, amely az elszigetelt tároló alapvető
típusait ismerteti. Ez a névtér meglehetősen kicsi, mivel típusait a system. ro
alapvető típusaival együtt használjuk.

57
20. fejezet: Fájlműveletek és elszigetelt tárolás

Isolatedstorage Ez a típus az az absztrakt ősosztály, amelyből va­


lamennyi elszigetelt tárolónak származnia kell.

IsolatedstorageScope Ennek a felsorolt típusnak a segítségével állíthat­


juk be a használandó izoláció szintjét (szerelvény,
alkalmazástartomány vagy vándorló).

IsolatedstorageException Ez a típus határozza meg, hogy rnilyen kivételt


dobjon a program, ha egy művelet nem sikerül az
elszigetelt tárolóban.

IsolatedstorageFil e Ez a tag jelöli azt a területet, ahol az elszigetelt tá­


roló a fájlokat és könyvtárakat tartja.

IsolatedStorageFilestream Ez a típus az elszigetelt tárolóban található fájlok


olvasását, írását, létrehozását teszi lehetővé.

20.14. táblázat: A System.IO.IsolatedStorage típusai

Tároló létrehozása az
lsolatedStorageFi le objektummal

Ha az elszigetelt tárolóban szeretnénk alkalmazásadatot tárolni, első lépés­


ként el kell döntenünk, hogy milyen szintű izoládát szeretnénk használni.
A .NET a tárolókat legalább felhasználónként mindenképp elkülöníti, de be­
állíthatunk szerelvényenkénti vagy alkalmazástartományonkénti izoládát is
(valamint vándorló profilt is). A megfelelő elkülönítési szint beállításakor
egyik lehetőségünk, hogy az Isolatedstoragescope felsorolás egy tagjával be­
állí�uk az értékeket, majd meghívjuk az IsolatedstorageFile. Getstore O me­
tódust. Az IsolatedstorageFile típus statikus GetuserstoreForoomain() vagy
GetuserstoreForAssembly() metódusai is használhaták Vizsgáljuk meg a kö­
vetkező példákat:

static void GetAppDomainstorageForuser()


{
ll Elszigetelt tároló megnyitása szerelvény- és
ll alkalmazástartomány-azonosító alapján (rövid).
IsolatedstorageFile store =

IsolatedstorageFile.GetuserStoreForoomain();

58
Tároló létrehozása az lsolatedStorageFile objektummal

ll vagy egyesítjük a jelzőket, és meghívjuk a Getstore() metódust.


rsolatedstorageFile store2 =

rsolatedstorageFile.Getstore(Isolatedstoragescope.user
Isolatedstoragescope.Domain, null, null);
}

·static void GetAssemblystorageForuser()


{
ll Megnyitunk egy tárolót
ll szerelvényazonosító alapján (rövid).
IsolatedstorageFile store2 =

IsolatedstorageFile.GetuserstoreForAssembly();

ll vagy egyesítjük a jelzőket, és meghívjuk a Getstore() metódust.


IsolatedstorageFile store2 =

IsolatedStorageFile.Getstore(Isolatedstoragescope.user
Isolatedstoragescope.Assembly, null, null);
}

Bármelyik módszert is választjuk, az eredményként egy r solatedstorageFile


objektumot kapunk. Ennek a típusnak a segítségével a tárolóha adatokat ír­
hatunk, abból olvashatunk, vagy létrehozhatunk tetszőleges egyedi könyv­
társzerkezetet az aktuális felhasználó tárolóján belül. A 20.15. táblázat az
IsolatedstorageFi le néhány fontosabb tagját mutatja be.

-,JI';�
�-

�� ; , ·�...
currentsize, Ezek az írásvédett tulajdonságok visszaadják az el­
Maximumsize szigetelt tároló méretével kapcsolatos adatokat.

Scope Megadja az izoláció hatókörét (felhasználó, szerel­


vény vagy alkalmazástartomány).

CreateDirectory() Létrehoz egy új könyvtárat a tárolóban.

DeleteDirectory() Töröl egy könyvtárat a tárolóból.

DeleteFile() Töröl egy fájlt a megadott könyvtárból.

GetDirectoryNames() Ez a metódus lehetővé teszi, hogy végigiteráljunk a


nevesített könyvtárakon.

GetEnumerator() Egy hatókör-specifikus IEnumerator típussal tér


vissza.

GetFiles() Visszaadja a tárolóban található fájlok listáját.

Getstore O Ez a túlterhelt metódus a megadott alkalmazástarto­


mány- és szerelvénybizonyítékoknak és az izolációs
hatókörnek megfelelő elszigetelt tárolót adja vissza.

59
20. fejezet: Fájlmüveletek és elszigetelt tárolás

GetuserstoreForAssembl y() Ez a metódus megszerzi a hívó szerelvényazonosító­


jának megfelelő elszigetelt tárolót

GetuserstoreForDomain() Ez a metódus megszerzi az alkalmazástartomány- és


szerelvényazonosítónak megfelelő elszigetelt tárolót.

Remove() Ez a metódus eltávolítja a tárolókat

20. 15. táblázat: Az IsolatedS torageFile tagjai

Adat frása a tárotóba

Miután létrehoztuk a tárolót, következő lépésként létre kell hoznunk az Iso­


latedstorageFilestream típus egy példányát, amely az adattároláshoz hasz­

nált fájlt jelenti a tárolóban. A többi 10-adatfolyarnhoz hasonlóan ez a típus is


beállítható a korábban ismertetett system. IO. FileMode felsorolássaL Ennek
bemutatásához hozzunk létre egy konzolalkalmazást simp leisostorage né­
ven, amelybe importáljuk a system.IO és a system.IO.Iso l atedstorage névte­
reket Ezután módosítsuk úgy a Main() metódust, hogy a Program osztály
alábbi segédmetódusát hívja meg:

static void writeTextToisostorage()


{
ll Elszigetelt tároló megnyitása szerelvény- és
ll felhasználói bizonyítékok alapján.
using (IsolatedStorageFile store =

IsolatedstorageFile.GetuserstoreForAssembly())
{
ll Létrehozzuk az IsolatedstorageFilestream típust.
using (IsolatedstorageFilestream isstream =

new IsolatedStorageFilestream("MyData.txt",
FileMode.openorcreate, store))
{
ll Az adatfolyamot "becsomagoljuk" egy streamwriter
ll objektumba, és kiírunk valamilyen szöveget.
using (Streamwriter sw = new Streamwriter(isstream))
{
sw.writeLine("This is my data.");
sw.writeLine("cool, huh?");
}
}
}
}

60
Tároló létrehozása az lsolatedStorageFile objektummal

Először megszerzünk egy tárolót a felhasználó számára a végrehajtó szerel­


vény (simpleisostorage. exe) azonossága alapján az Iso l atedstorageFile. Get­
userstoreForAssembly() metódussal. Ezután létrehozunk egy új Isolatedsto­
rageFileStream objektumot, és megadjuk, hogy az újonnan létrehozandó fájl
neve Myoa.t a. t.xt legyen a most megszerzett tárolóban (ha a fájl már létezik,
akkor megnyitjuk). Végül az Isol atedstorageFilestream objektumot egy sys­
tem. ro. Streamwri ter íróba csomagol juk, és kiírunk néhány sornyi szöveget.
Ha lefuttatjuk az alkalmazást, belenézhetünk a szárrútógépen lévő elszige­
telt tárolóba, és (némi kutakodás után) megtalálhatjuk a MyData. txt fájlt (lásd
a 20.24. ábrát). A MyData. txt fájlban, ha jegyzettömbbel megnyitjuk, az abba
kiírt két sort látjuk.
Természetesen az rsolatedstorageFilestream objektumot más, a stream
osztályból származó típusba is becsomagolhatjuk Ha például inkább bináris
formában szeretnénk kiírni az adatokat, használhatjuk a Streamwriter helyett
a Binarywriter osztályt.

l l @l lii40j
o

@\._-"')o;-r�_<< Url2�?�-�b�v.-y�f2attoe_:;Sr�2.sllMJg ._ As�emFil6 ·!!!J [Secd P1


m..���� -...iL�·

Namé" Date modified Type


FJ.'crite tink:;
r-··--·--··"--1
More >>

Folders i l
_. Ocxcyjqm.vej
StrongName.vynmtjls3lcezx2,.'f\•Judolgtjg40jp530
. Ur12p40Sz3bq'lwzyqf2altoe55rq2slu>Qg
. AssemFiles
L_�lx�•cl>t_j
Ur1.2tv-.04ubjvbndio5xymfhzxpyjewojfrg c
Ud22k2tyl;qli0yoyzaa5gjrl;!Ojmqbó
Url.k2udjuowrv�Odz5ghjny,4oc0o0h\\Oac
Url.u0gpakejhfxvdn4g4osl0pklltpqgf5s
t..t:..-... -...4., ··�-=��=·····�
MyData.txt Date me difi d 9/3/2007 9,13 PM DatE: crt:ated: 9/3/2007 9� PM
Ted Documl!:nt Si.:e: 30 bytes

20.24. ábra: Az elszigetelt tárolába helyezett szövegfájl

Adat olvasása a tárolóból

A felhasználó tárolójában elhelyezett adat olvasása is hasonlóan egyszerű. Néz­

zük meg a következő metódust (amelyet célszerű szintén a Main() függvényből


meghívni a writeTextToisostorage() metódus után), amely a Myoata. txt fájlban
található információt kiolvassa, és megjeleníti a konzolon:

61
20. fejezet: Fájlműveletek és elszigetelt tárolás

private static void ReadTextFromisoStorage()


{
using (IsolatedStorageFile store =

IsolatedstorageFile.GetuserstoreForAssembly())
{
using (IsolatedstorageFilestream isstream =

new IsolatedstorageFilestream("MyData.txt",
FileMode.open, FileAccess.Read, store))
{
ll "Becsomagoljuk" streamReaderbe.
using (StreamReader sr = new StreamReader(isstream))
{
string allTheoata = sr.ReadToEnd();
Console.writeLine(allTheoata);
}
}
}
}

Felhasználói adat törtése a tárolóból

Az Iso latedstorageFile típus két módszert biztosít a tárolók törlésére. A pél­


dányszintű Remove() azt a tárolót törli, amelyik meghívja. A statikus Isolated­
storageFile.Remove() metódus az Isolatedstoragescope.user értéket veszi

fel, és törli a kódot futtató felhasználó összes tárolóját. A következő kód pél­
dául törli az aktív tárolót, és megsemmisíti annak tartalmát:

ll Töröljük az aktuális felhasználó aktuális tárolójának adatait.


rsolatedstorageFile store =

IsolatedStorageFile.GetuserstoreForAssembly();
store.Remove();

Az alábbi kód pedig az aktuálisfelhasználó összes tárolóját törli (ugyanúgy, rnint


arnikor a storeadm.exe segédprogramot a lremove paraméterrel indítjuk):

ll Töröljük a felhasználó ÖSSZES tárolóját.


rsolatedstorageFile.Remove(rsolatedstoragescope.user);

Egyedi könyvtárstruktúra létrehozása

Az előző példákban nem hoztunk létre egyedi hierarchiát a különböző adat­


fájlok tárolására. Ehelyett a MyData.txtfájlt közvetlenül a tároló gyökerében
helyeztük el (a legtöbb esetben pontosan erre is van szükség). Egyedi könyv­
tárakat a példányszintű createDirectory() metódussal hozhatunk létre. Az

62
Tároló létrehozása az lsolatedStorageFile objektummal

elszigetelt tárolóban az alkönyvtárakat nem képezhetjük le objektumokra,


hanem olyan sztringeket kell átadnunk, amelyek a létrehozni kívánt könyv­
tárakat jelzik. Ha ez megvan, a createDi rectory() metódus egy rsolatedsto­
rageFil e típussal tér vissza, amely a legbelső könyvtárhoz nyújt hozzáférését.

Nézzük meg a következő kódot:

private static void CreateStorageDirectories()


{
ll Hagyományos és fordított perjelet is használhatunk.
using (IsolatedstorageFile store =

rsolatedstorageFile.GetuserstoreForAssembly())
{
store.CreateDirectory(@"MyDir\XmlData");
store.createDirectory("MyDir\\BinaryData");
store.CreateDirectory("MyDiriTextData");
}
}

Ha a fenti metódust meghívjuk a Main() függvényből, megtalálhatjuk a létre­


hozott könyvtárstruktúrát az elszigetelt tároló területén (lásd a 20.25. ábrát).

� Gl [S<a:ch

Name Date modif... Type


F�k:-rite lmk::;

Mcr� >>

Folders:

lsolatedStorage

hjphpkzn.lpl

4 � Ocxcyjqm.vej
MyDir MyData.txt
StrongName.vynmtjls31cezxlwwudolgtjg40jp530

_ UrL2p405z3bq0wzyqf2altoe55rq2sllMlg
f.J
� . AssemFiles

MyDir
BinaryData
TextData
'. XmlData

MyDir D<te modified: 9/3/2007 9:38 PM


File Folder

20.25. ábra: Könyvtárstruktúra létrehozása a megadott tárolóban

Forráskód A SimplelsoStorage projektet a forráskódkönyvtárban a 20. fejezet alkönyvtára


A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.
tartalmazza.

63
20. fejezet: Fájlműveletek és elszigetelt tárolás

Az elszigetelt tároló müködés közben:


ClickOnce-telepités

Meglehet, hogy az elszigetelt tároló ez idáig pusztán a felhasználószintű


adattárolás egyedi módjának tűnt (ez önmagában is hasznos dolog). Ám az
egyik probléma, amit ez az API orvosol, az az, hogy a nem Full Trust jogo­
sultsággal futó alkalmazások is biztonságosan tárolhassanak adatokat.
Tegyük fel, hogy van egy Windows Forms-alkalmazásunk (neve: Fileor­
rsostoragewinApp) , amely egy két gombot tartalmazó űrlapból áll. (A Windows
Forms API-ról részletesen a 27. fejezetben lesz szó, de az egyes Button típu­
sok kattintási eseményét már itt is le fogjuk kezelni.)
Az egyik gomb a helyi merevlemezre próbál adatot menteni hagyomá­
nyos IO-megoldásokkal (ne felejtsük el importálill a system.ro és a system.
ro.rso latedstorage névtereket a kódfájlunkba):

private void btnFilero_click(object sender, EventArgs e)


{
using (Streamwriter sw = new Streamwriter(@"C:\MyData.txt"))
{
sw.w riteLine("This is my data.");
sw.writeLine("Cool, huh?");
}
}

A második gomb ugyanazt az adatot egy elszigetelt tárolóban lévő fájlba írja
ki. A cli ck eseménykezelő megvalósítása a korábbi projektben már megírt
writeTextTorsostorage() metódushoz hasonlít:

private void btnrsostorage_Click(object sender, EventArgs e)


{
ll Elszigetelt tároló megnyitása szerelvény- és
ll felhasználói bizonyítékok alapján.
using (IsolatedstorageFile store =

rsolatedStorageFile.GetuserStoreForAssembly())
{
ll Létrehozzuk az rsolatedstorageFilestream típust.
using (IsolatedStorageFileStream isstream =

new rsolatedStorageFilestream("MyData.txt",
FileMode.openorcreate, store))
{
ll Az adatfolyamot "becsomagoljuk" egy Streamwriter
ll objektumba, és kiírunk valamilyen szöveget.

64
Az elszigetelt tároló működés közben: ClickOnce-telepítés

using (Streamwriter sw = new Streamwriter(isstream))


{
sw.writeLine("This is my data.");
sw.writeLine("cool, huh?");
}
}
}
}

Az lsolatedStorageFilePermission attribútum

Mielőtt kipróbálnánk az alkalmazást, adjuk hozzá az alábbi using direktívát a


kezdeti Form-leszármazott osztályt definiáló C#-fájlhoz:

using System.Security.Permissions;

Ebben a névtérben számos olyan biztonsággal kapcsolatos, az alkalmazáson


használható attribútum található, amelyek segítségével értesíthe�ük a bizton­
sági alrendszert arról, hogy az adott szerelvény megfelelő működéséhez mi­
lyen biztonsági beállításokra van szükség (az egyéb részletek mellett). A pél­
dában arról értesí�ük a CLR-t, hogy az alkalmazásunknak legalább szerel­
vényszintű, elszigetelt tárolási izolációs jogosultságra van szüksége. Ezt úgy
érhe�ük el, ha a következő szerelvényszintű attribútumot hozzáadjuk a Form­
leszármazott típusunk kódfájljához:

[assembly:
IsolatedstorageFilePermission(SecurityAc tion.RequestMinimum,
usageAllowed = rsolatedstoragecontainment.AssemblyrsolationByuser)]

Ha a programot közvetlenül Visual Studio 2008-ban (a Ctrl + FS billentyű­


kombinációval) fordí�uk le, és futta�uk, azt tapasztaljuk, hogy bármelyik
gombra is kattintunk, létrejön a megfelelő szöveges adatot tartalmazó új fájl.
Ennek oka az, hogy az alkalmazást a My_Computer_Zone kódcsoportból töl­
töttük be, amely a Full Trust jogosultságot biztosí�a a szerelvény számára.

A biztonsági zóna korlátozása

Telepítsük úgy az alkalmazást, hogy az a jóval korlátozóbb Intemet_Zone en­


gedélykészlettel fusson. Ennek érdekében az alkalmazásunkat a GickOnce­
szal telepí�ük. A ClickOnce segítségével telepíthetünk távoli webkiszolgálóról
futtatható alkalmazást a végfelhasználó számítógépére.

65
20. fejezet: Fájlműveletek és elszigetelt tárolás

A távoli alkalmazás egy IlS virtuális könyvtárban található, ahonnan egy­


szerűen az URL-böngészőben történő megadásával letölthe�ük, és telepíthet­
jük a helyi számítógépre.

Megjegyzés A ClickOnce teljes ismertetése túlmutat ennek a fejezetnek a keretein. Ha va­


laki még nem telepített ily módon alkalmazást, csak kövesse az alábbi utasításokat (és igény
szerint olvassa el a .NET Framework 3.5 SDK dokumentációját).

Először nyissuk meg a projekt Properties lapját úgy, hogy a Solution Explo­
rerben duplán kattintunk a Properties ikoma. Majd kattintsunk a Security
fülre. Az alapértelmezés szerint a ClickOnce alkalmazások Full Trust enge­
déllyel települnek, ezért ugyanolyan biztonsági jogosultságokkal rendelkez­
nek, mint a hagyományosan telepített prograrnak
Olyan telepítőszkriptet kell írnunk, amely arra kényszeríti a programot,
hogy az Internet zónában fusson, amely nem enged hozzáférést a merevle­
mezhez hagyományos IO-műveletek felhasználásával.

A ppl ication

Bu ild

Speci'fy the code access s�urity permissicns th at you r Cl ic kOnce appi


l cation requir�in crder to ru n.
Buid
l Eve nt s
Learn mcre about code access security...

Debu g !lJ Enabl e Clid:.Once Se curit y Se ttings il


Resource:s · �> Thisis a full trurt application l!
Sj Thisis a parti al trurt application
Service s
(lickOnce SecurityPermiss ions ll
Settings Zoneyour application will be install edfrom:
i
jint ernd:

i
Refere nce P�ths

Signing Pe rmíssions requiredby th e appl ication:


s

li�
Permís�i on e tt i n..:
g ncluded
_:_
l
-;--r
Environme.ntPermission : e_f.-u-
(Z_0 n_ e 0 l t)
[���;�_-·· r
_ _ _ _ ___ 1
·­

Fi·t; ;;-I- - �
r o -----------· ·-· ----- - l[•IZ- - _ _ f- au- lt)- + • -i - c; :
;
---:-� :-i:
Pubi
l sh D ogPer i ssi n ( one De
-
Fi e
l lOPerrns
i i
s on t(Zone: Default) •
l
l
_m_�.o ��BI_I_I_II_I_I•
����!��=":�=�-�=�:z-isso _ _•__I_I_••••,t:
_
_
: �
l

RegistryPermission _ ___ j (Z one De f ault) • _ ! _

[ Ca!culate Permissions J JProperties... j[ Rese t

j Advanced... j

20.26. ábra: Az alkalmazás biztonsági zónájának korlátozása

66
Az elszigetelt tároló müködés közben: ClickOnce-telepítés

Ehhez kattintsunk az Enable ClickOnce Security Settings jelölőnégyzetre, vá­


lasszuk a This is a partial trust application rádiógombot, majd válasszuk az
Internet lehetőséget a zónákat felsoroló legördülő listamezőből. Végül, de
nem utolsósorban kattintsunk a Calculate Permissions gombra a biztonsági
beállításokat tartalmazó oldal alján. Ez meghatározza az alkalmazás által
igényelt végső engedélykészletet (lásd a 20.26. ábrát).

Az alkalmazás közzététele webkiszolgálón

Kattintsunk a tulajdonságszerkesztőben a Publish fülre. Majd kattintsunk az


oldal alsó részében található Publish Wizard gombra, és a varázsló első lap­
ján írjuk be az alábbi URL-t, hogy létrehozzuk az alkalmazást tároló új liS
virtuális könyvtárat a helyi gépen:

http://localhost/MyrsolatedstorageApp/

Az eszköz további beállításait alapértelmezett értéken hagyhatjuk. Mikor a


Finish gombra kattintunk, a webböngésző betölti az automatikusan generált
pub l i sh. htm fájlt. Ezt fogják látni a végfelhasználók is, amikor a letöltendő és
a helyileg telepítendő alkalmazást tároló távoli webkiszolgálóra csatlakoznak
(a weboldalt kedvünk szerint testre szabhatjuk; a fájlt és a kapcsolódó tartal­
makat a projekt bin\Debug mappájában találjuk). Kattintsunk az Install
gombra, majd futtassuk a setup. exe alkalmazást (és fogadjunk el minden biz­
tonsági kérdést). Kis idő múlva az alkalmazás feltelepül a számítógépre, a
ClickOnce gyorsítótárnak nevezett területre, és a program elindul.

Az eredmény megtekintése

Mivel az alkalmazást úgy állítottuk be, hogy korlátozott környezetben fusson,


ha arra a gombra kattintunk, amely a system. ro típusok használatával próbál
adatot menteni, akkor a 20.27. ábrán lévő biztonsági kivétel fog megjelenni.
Ellenben, ha arra a gombra kattintunk, amely az elszigetelt tároló segítsé­
gével ment adatot, az alkalmazás sikeresen lefut.

67
20. fejezet: Fájlműveletek és elszigetelt tárolás

File 10 lIsoStorage

/Wiicatioo att�ed to pelform an operOO<>n not alawed by the

o sectrty policy. To gnrt tm �ation the reqo.nd pemission. contact


you-sy.tem a<ininístr.ltor. or use the Microsoft .NET FrameWOik
(;Qd�tool.
f ycu cick Cormue. the� wil iglore this error and att� to
c:ortrue. f ycu cick <nt. the applicatiowil
n dose immediotely.
Request forthe pernission ci type
'System.Secuiy.Pemissions.fleKJPenníssion. msconb .
VefSion•20.0.0. C..�n"'1el.(ral, POOicKeyToken-b77a5c561934e089'

Cormue
l !c__.;:.:.a..
·__ _J

See the end ci tm message for detaíls on invoking


just1n-tine {JIT) debugging inslead ci this cialog box.
o
--- Exception Text---
System.Secuiy.SecuiyException: Request forthe pernission ci type 'System.Sect
ot System.Secuiy.CodeAccesSecuiys Engine.OleckPbject demand. StackCrav
ot System.Secuiy.CodeAccesP s enníssion.DemandO
ot System.Kl.fleStteamJnit(String poth. AeMode mode . Ae!\ccess access. rt32
ot System.Kl.AeS!Jeam..ctor(String poth. fleMode mode. Ae!\ccess access . Ae
ot System.KJ.StreamWriter.Oeatefie(String poth, Boolean append)

20.27. ábra: Biztonsági veszély. Nem lehet hozzáférni a helyi fájlrendszerhez az Internet zónából

Forráskód A FileOrlsoStorageWinApp projektet a forráskódkönyvtárban a 20. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Összefoglalás

A fejezet elején megismertük a Di rectory(Info) és File(Info) típusok hasz­

nálatát. Megtudtuk, hogy ezek az osztályok lehetővé teszi a merevlemezen


található fizikai fájlok és könyvtárak kezelését. Ezután megismerkedtünk
több, az absztrakt stream osztályból származó típussal, amelyek közül a
Fi l estream típussal bővebben foglalkoztunk. Mivel a Stream-leszármazott ti­

pusok nyers byte-folyamokkal végeznek műveleteket, a System. IO névtér


számos olyan olvasó/író típust (str e a mwri te r, stringwriter, Binarywriter
stb.) rendelkezésünkre bocsát, amelyek megkönnyítik ezt a folyamatot. Ezek
során a DriveType által nyújtott funkcionalitást is megnéztük, és megvizsgál­
tuk, hogyan figyelhetünk meg fájlokat a Fi lesystemwatcher segitségével, és
hogyan kezelhetjük az adatfolyamokat aszinkron módon.
A fejezet második része az elszigetelt tárolót mutatta be. Ez az API lehe­

tővé teszi a prograrnak számára, hogy egy biztonságos virtuális környezet­


ben végezzenek olvasási és írási műveleteket még akkor is, ha az alkalmazást
korlátozott biztonsági környezetben töltötték be. Bár a programozási modell

68
Összefoglalás

meglehetősen egyértelmű (ha megértjük az alapvető fájlműveleteket), a kap­


csolódó témakörök valamelyest megbonyolítják Ennek fényében röviden át­
tekintettük a kódhozzáférés-szabályzást is.
Ebben megtudtuk, hogy a szerelvények alkalmazástartományba való be­
töltésük során a CLR részére bizonyítékokat szolgáltatnak. Ez alapján kerül­
nek bele az alapértelmezett engedélykészletekkel rendelkező kódcsoportokba.
A CAS fájlműveletek szempontjából fontos tulajdonsága, hogy ha az alkal­
mazás nem kap Full Trust jogosultságot, a hagyományos 10-műveletek biz­
tonsági kivételt eredményeznek. Ezzel szemben az elszigetelt tároló segítsé­
gével a prograrnak felhasználónként elszigetelten és biztonságosan tárolhat­
nak adatokat.

69
HUSZONEGYEDIK FEJEZET

Bevezetés az objektum­
sorositás világába

Az előző fejezetben megismerkedtünk a system. ro névtér funkcionalitásával


és az elszigetelt tároló (system.ro. Isol atedstorage) szerepével.
Ezek a névterek lehetővé teszik, hogy az adatokat számos olvasó és író
használatával egy adott helyre (és egy adott formátumban) mentsük. A kö­
vetkezőkben az ehhez a témához kapcsolódó objektumsorosítás kérdéskörét
tárgyaljuk Az objektumsorosítás segítségével elmenthetjük és visszaállíthat­
juk az objektum állapotát bármely system.ro.streamből származó típusból
vagy típusba (beleértve az rsol atedstorageFi l estream típus t is).
A típusok sorosítása akkor rendkívül fontos, amikor egy objektumot kü­
lönböző remoting technológiákkal, például a .NET remoting réteggel, az
XML-webszolgáltatással vagy a Windows Communication Foundation segít­
ségével távoli gépre szeretnénk másolni. A sorosítás önmagában is nagyon
hasznos, és minden bizonnyal nagy szerepet fog játszani jó néhány .NET-al­
kalmazásunkban (akár elosztott, akár nem). A továbbiakban megismerke­
dünk a .NET sorosítási séma számos aspektusával, köztük olyan attribútu­
mokkal (és interfészekkel), amelyek lehetővé teszik a folyamat testreszabását.

Az objektumsorositás

A sorosítás kifejezés az objektumok folyamba (fájlfoiyamba, memóriafolyam­


ba stb.) történő mentését (és lehetőség szerinti átvitelét) jelenti. Az elmentett
adatsorozatok minden szükséges információt tartalmaznak az objektumok
állapotának visszaállításához (vagy deszerializációjához) a későbbi használatra.
Egyértelmű választás, ha nagy mennyiségű (különböző formátumú) adatot
szeretnénk elmenteni a lehető legkisebb ráfordítássaL Ha a sorosítószol­
gáltatást használjuk, akkor az alkalmazásadatok mentése sok esetben keve­
sebb kódot eredményez, mint ha a system.ro névtér olvasóinak/íróinak
használatával tennénk.
21. fejezet: Bevezetés az objektumsorosítás világába

Tételezzük fel például, hogy egy grafikus asztali alkalmazást hoztunk létre,
és lehetővé szeretnénk tenni, hogy a felhasználók elmenthessék a beállításaikat
(ablak színe, betűméret stb.). Ehhez definiálhatunk egy userPrefs nevű osz­
tályt, amely körülbelül 20 adatmezőt foglal magába. Ha a system. ro. B i nary­
writer típust használnánk, a userPrefs objektum núnden egyes mezőjét manu­

álisan kellene elmenteni. Ugyanígy, ha az adatokat a fájlokból vissza akar­


nánk tölteni a memóriába, szükség lenne a system. ro. B inaryReaderre, és az
új userPrefs objektum újrakonfigurálásához (ismét csak) manuálisan kellene
beolvasni núnden egyes értéket.
Bár a fenti megoldás teljesen működőképes, rengeteg időt spórolhatunk
meg saját magunknak, ha a userPrefs osztályt egyszerűen a [Serializable]
attribútummal jelöljük meg:

[seri alizable]
public class userPrefs
{
ll Különböző adatok...
}

Így az objektum teljes állapotát csupán néhány soros forráskóddal is elment­


he�ük. Nézzük meg az alábbi Main() metódust:

static void Main(string[] args)


{
ll Tételezzük fel, hogy a userPrefs az alábbi tulajdonságokat
ll határozza meg.
userPrefs userData= new userPrefs();
userData.windowcolor = "Yellow";
user D ata F ontsize
. = "50";

ll A BinaryFormatter az állapotadatokat bináris formátumban menti.


BinaryFormatter binFormat = new BinaryFormatter();

ll Az objektumot egy helyi fájlban tárolja.


using(Stream fstream = new Filestream("user.dat",
FileMode.Create, FileAccess.write, Fileshare.None))
{
binFormat.Serialize(fstream, userData);
}
Console.ReadLine();
}

A .NET-objektumsorosítással az objektum állapotának mentése meglehető­


sen egyszerűnek mondható, ám a háttérben zajló folyamatok eléggé összetet­
tek Ha egy objektumot például egy folyamba mentünk, minden társított adat
(ősosztályadatok, tárolt objektumok stb.) sorosítása is automatikusan megtör-

72
Az objektumsorosítás

ténik. Ezért ha származtatott osztályt szeretnénk elmentem, minden, a szár­


mazási láncban lévő adat tárolódik. Az egymással kapcsolatban levő objek­
tumok egy objektumgráffal ábrázolhaták
A .NET-sorosítószolgáltatás az objektumgráfok különböző formátumú
mentését is lehetővé teszi. Az előző példakód a BinaryFormatter típust al­
kalmazta, tehát a userPrefs objektum állapotát tömörített bináris formátum­
ban mentette. Az objektumgráfokat más típusokat használó SOAP- vagy
XML-formátumban is elmenthetjük Ezek a formáturnak meglehetősen hasz­
nosnak bizonyulnak, ha biztosítani akarjuk, hogy az elmentett objektumokat
zavartalanul lehessen használni a különböző operációs rendszerekben, nyel­
vekben és architektúrákban.
Az objektumgráfokat bánnilyen system.IO.Streamból származó típusba
elmenthetjük Az előző példában a userPrefs objektumot a Filestream típus
segítségével mentettük a helyi fájlba. Ha azonban az objektumokat inkább
egy meghatározott memóriaterületen szeretnénk tárolni, használhatjuk a Me­
morystream típust is. A lényeg az, hogy az adatsorozat megfelelőerr ábrázolja

a gráfon található objektumok állapotát.

Az objektumgráfok szerepe

Ha egy objektumot sorosítunk, a CLR valamennyi kapcsolódó objektumot szá­


mításba veszi annak biztosítására, hogy az adatok elmentése megfelelőerr történ­
jen meg. A kapcsolódó objektumoknak ezt a halmazát objektumgráfnak nevezzük.
Az objektumgráfok egyszerűen azt dokumentálják, hogy az objektumok miként
kapcsolódnak egymáshoz. Nem feltétlenül képezik le a klasszikus 00 kapcsola­
tokat (mint például az "az egy" vagy a "van neki egy" kapcsolatokat) annak el­
lenére, hogy ezt a paradigmát is meglehetősen jól modellezik.
Az objektumgráfokban található objektumok egyéni numerikus értékkel
rendelkeznek. Az objektumgráf tagjaihoz rendelt számok tetszőlegesek, és
semmilyen kapcsolatban nem állnak a külvilággal. Ha minden egyes objek­
tumhoz hozzárendelődik egy numerikus érték, az objektumgráf minden egyes
objektum függőséghalmazát képes elmentem.
Ennek egyszerű szemléltetésére tételezzük fel, hogy olyan osztálykészle­
tet hozunk létre, amely néhány gépkocsit modellez. Van tehát egy car nevű
ősosztály, amelynek "van egy" Radio osztálya. Egy másik, JamesBondcar nevű
osztály kibővíti a car alaptípust. A 21.1. ábra ezeket a lehetséges kapcsolato­
kat modellező objektumgráfot ábrázolja.

73
21. fejezet: Bevezetés az objektumsorosítás világába

Car Radio

21. 1 ábra:
. Egtjszerű objektumgráf

Az objektumgráfok olvasásakor a nyilak összekapcsolásában használha�uk a


" "
"függőség vagy a "hivatkozás kifejezéseket. Tehát a 21.1 ábrán azt lá�uk, hogy
a car osztály hivatkozik a Radio osztályra (tekintettel a "van neki egy" kap­
csolatra). A JamesBondcar hivatkozik a car osztályra (mivel adott az "az egy"
kapcsolat), valamint a Radio osztályra (mivel az örökli ezt a védett tagváltozót).
A CLR természetesen nem rajzol képeket a memóriába a kapcsolódó objek­
tumgráfok ábrázolásához. Az előző diagramban dokumentált kapcsolatot in­
kább egy, az alábbihoz hasonló matematikai formula segítségével ábrázolja:

[car 3, ref 2], [Radio 2], [JamesBondcar l, ref 3, ref 2]

A formula elemzésekor ismét látha�uk, hogy a 3-as objektum (a car) függő­


ségi kapcsolatban áll a 2-es objektummal (a Radio ) . A 2-es objektum, a Radi o,
"
"magányos farkas , nincs szüksége senkire. Végül pedig a l-es objektum (a
JamesBondcar) függőségi viszonyban áll rnind a 3-as, rnind a 2-es objektum­

mal. Mindenesetre ha sorosí�uk vagy visszaállítjuk a JamesBondcar osztály


egy példányát, az objektumgráf biztosítja, hogy a Radio és a car típusok is
részt vegyenek a folyamatban.
A sorosítási folyamatban az objektumok közötti kapcsolatokat ábrázoló
gráf automatikusan, a háttérben jön létre. A későbbiekben is látni fogjuk,
hogy lehetőség van arra, hogy irányítsuk az objektumgráf létrejöttét a sorosí­
tási folyamat attribútumain és interfészeken keresztüli testreszabássaL

Megjegyzés Az. xmlserializer típus (lásd később) nem az objektumgráfokkal menti el az ál­
lapotot, hanem továbbra is prediktív módon sorosítja és állítja vissza a kapcsolódó objektumokat.

74
Objektumok konfigurálása sorosításhoz

Objektumok konfigurálása sorositáshoz

Annak érdekében, hogy egy adott objektum a .NET-sorosítószolgáltatások


számára elérhető legyen, nem kell mást tennünk, rnint rninden egyes kapcso­
lódó osztályt (vagy struktúrát) fel kell ruháznunk a [serializable] attribú­
tummal. Ha egy adott típus olyan adattagokkal rendelkezik, amelyeknek
nem kellene szerepelnie (vagy nem szerepelhetnek) a sorosítási sérnában, je­
löljük meg ezeket a rnezőket a [Nonserialized] attribútummaL Ez akkor le­
het hasznos, ha olyan tagváltozók vannak egy sorosítható osztályban, arne­
lyekre nem kell "emlékezni" (pl. fix értékek, véletlenszerű értékek, átmeneti
adatok stb.), és szeretnénk a mentett adatok rnéretét csökkenteni.

Sorositható tipusok meghatározása

Kiindulásként hozzunk létre egy új konzolalkalmazást SimpleSerialize név­


vel. Szúrjunk be egy új osztályt Radio névvel, amelyet a [serializable] attri­
bútummal jelöltünk, kivéve egy tagváltozót (radi om), amelyet pedig a [Non­
serialized] attribútummal jelöltünk, ezért ez nem rnentődik el a meghatáro­
zott adatfolyamban:

[Serializable]
public class Radio
{
public bool hasTweeters;
public bool hassubwoofers;
public double[] stationPresets;

[Nonserialized]
public string radiolD = "XF-552RR6";
}

Ezt követően adjunk hozzá két osztályt (J amesBondcar és a car), lássuk el őket
a [Seriali zab le] attribútummal, és definiáljuk az alábbi tagokat:

[Serializable]
public class Car
{
public Radio theRadio = new Radio();
public bool isHatchBack;
}

75
21. fejezet: Bevezetés az objektumsorosítás világába

[Serializable]
public class JamesBondcar car
{
public b ool canFly;
public bool cansubmerge;
}

A [serializable] attribútum nem öröklődik a szülőosztályból. Ha tehát egy


osztályt egy [Serializable] attribútummal jelölt típusból származtatunk, a
gyermekosztályt is a [Serializable] attribútummal kell megjelölnünk, kü­
lönben nem tudjuk elmenteni. Valójában az objektumgráf minden tagját a
[Serializable] attribútummal kell jelölnünk. Ha a BinaryFormatter vagy a

soapFormatter segítségével nem sorosítható objektumot próbálunk sorosítani,

futásidőben a serializationException jelenik meg.

Megjegyzés Mivel a xmlse ri al i ze r típus nem használja az objektumgráfokat, technikailag


nem szükséges a típusokat a [se ri alizab l e] attribútummal megjelölni annak érdekében,
hogy az objektum állapotát XML-ként mentsük el. Ha azonban azt szeretnénk, hogy a típusok
minden lehetséges formátumban menthetők legyenek, érdemes minden sorosítandó típust a
[se ri ali zable] attribútumokkal megjelölni.

Nyilvános mezők, privát mezők és nyilvános


tulajdonságok

Az egyszerűség kedvéért ezekben az osztályokban az adatmezőket nyilvá­


nosként határoztuk meg. A nyilvános tulajdonságokkal felruházott privát
adatok természetesen jobbak lennének az 00 szempon�ából. Az egyszerűség
kedvéért azonban ezekre a típusokra nem határoztam meg semmilyen egyedi
konstruktort, ezért azok az adatmezők, amelyeknek nem adtunk értéket, az
ilyenkor várható alapértelmezett értékeket kapják.
Eltekintve az 00 tervezési elvektőt kérdés lehet, hogy a különböző for­
mázók hogyan várják a típusok adatmezőjére vonatkozó hozzáférés­
módosítókat annak érdekében, hogy azok sorosíthatók legyenek. A válasz:
attól függ. Ha a BinaryFormatterrel vagy a soapFormatterrel men�ük el az
objektumok állapotát, abszolút nem számít, milyen hozzáférés-módosítót
használunk. Ez a két típus képes sorositani minden sorositható mezőt attól
függetlenüt hogy ezek nyilvános mezők, privát mezők vagy nyilvános tulaj­
donságokat használó privát mezők. Vannak azonban olyan adatok, amelye­
ket nem szeretnénk az objektumgráilia menteni, ekkor a nyilvános és a privát
mezőket szelektíven a [Nonserialized] attribútummal jelölhe�ük, ahogy azt
a Radio típus sztringmezőjével is tettük.

76
A sorosító formázó kiválasztása

A helyzet azonban más, ha az xmlserializer típust használjuk. Ez a típus


csak a nyilvános adatmezőket vagy a nyilvános tulajdonságok által kiajánlott
privát adatokat sorosítja, rníg a tulajdonságon keresztül nem elérhető privát
adatok figyelmen kívül maradnak. Példaként vegyük az alábbi sorosítható
Person típust:

[Seriali zable]
public class Person
{
ll Nyilvános mező.
public bool isAlive = true;

ll Privát mező.
private int personAge = 21;

ll Nyilvános tulajdonság/privát adat.


private string fName = string.Empty;
public string FirstName
{
get { return fName; }
set { fName value; }
=

}
}

Ha a BinaryFormatter vagy a SoapFormatter típust használtuk, megfigyelhet­


jük, hogy az isAlive, a personAge és a fName mezők mindegyikét elmentettük
a kijelölt folyamba. Az xmlserial izer azonban nem menti el a personAge érté­
két, ugyanis ezt a privát adatelemet nem ajánlottuk ki nyilvános típustulaj­
donsággaL Ha a személy életkorát az xmlserializerrel szeretnénk elmenteni,
a mezőt nyilvánosként kell meghatároznunk, vagy be kell ágyaznunk a pri­
vát tagot egy nyilvános tulajdonságba.

A sorositó formázó kiválasztása

Ha a szükséges attribútumokkal kanfiguráltuk a .NET sorosítási sémájában


szereplő típusokat, a következő lépésben ki kell választanunk, melyik formá­
tumot (bináris, SOAP vagy XML) használjuk az objektumok állapotának el­
mentésére. Az egyes lehetőségeket az alábbi osztályok mutatják:

• BinaryFormatter

• SoapFormatter

• XmlSerializer

77
21. fejezet: Bevezetés az objektumsorosítás világába

A sinaryFormatter típus az objektumok állapotát tömörített bináris formá­


tum segítségével sorosítja. A típust az mscorl ib. dll részét képező system.
Runtime. Seri alization. Formatters. Bi nary névtér definiálja. Ha tehát ehhez a

típushoz szeretnénk hozzáférni, egyszerűen csak az alábbi, C# using direktí­


vát kell megadnunk:

ll Hozzáférés a mscorlib.dll szerelvényben lévő


ll BinaryFormatter típushoz.
using System.Runtime. serialization.Formatters. Binary;

A soapFormatter típus az objektumok állapotát SOAP-üzenetként menti el. Ezt a


típust a különálló szerelvényben definiált System.Runtime.Serialization. Format­
ters. soap névtérben határozhatjuk meg. Ha tehát az objektumgráfot SOAP-üze­

netbe szeretnénk sorosítani először a Visual Studio 2008 Add Reference párbe­
szédpaneljével hivatkozzunk a system. Runtime. Se rialization. Formatters.

soap. dll szerelvényre, majd adjuk meg az alábbi C# u sing direktívát:

ll Kötelező referencia
ll a system.Runtime.serialization.Formatters.soap.dll szerelvényre.
using System.Runtime. serialization. Formatters. soap;

Végül pedig, ha egy objektumfát XML-dokumentumként kívánunk elmente­


ni, használjuk a xmlserializer típust. Ehhez csak határozzuk meg a névteret,
majd állítsuk be a system. xml. dll szerelvényre vonatkozó referenciát. Sze­
rencsére minden Visual Studio 2008 projektsablon automatikus referenciával
rendelkezik a system.xml dll szerelvényre, ezért nekünk csak az alábbi név­
.

teret kell használnunk:

ll Definiálva a System.xml.dll szerelvényben.


using System.xml. serialization;

Az IFormatter és az IRemotingFormatter
interfészek

Függetlenül attól, hogy melyik formázót választjuk, ne feledjük, hogy azok


mindegyike közvetlenül a system. obj ect névtérből származik, tehát egy so­
rosítás központú alaposztállyai nincsen közös tagkészletük A sinaryFormat­
ter és a soapFormatter azonban az IFormatter és az IRemoti ngFormatter inter­

fészek implementációján keresztül támogatja a gyakori tagokat (bármilyen


furcsának is tűnik, a xmlse ri alizer azonban egyáltalán nem).

78
A sorosító formázó kiválasztása

A system.Runtime.serialization.IFormatter határozza meg azokat a leg­

fontosabb Serialize() és Deserialize() rnetódusokat amelyek az objektum­


gráfokat egy adott folyarnba helyezik, vagy onnan kiveszik. Ezeken a tago­
kon kívül az IFormatter meghatározza még azt a pár tulajdonságot is, arne­
lyeket az implementáló típus a háttérben használ:

public interface IFormatter


{
SerializationBinder Binder { get; set; }
Streamingcontext Context { get; set; }
rsurrogateselector surrogateselector { get; set; }
object Deserialize(System.IO.Stream serializationstream);
void serialize(System.IO.Stream serializationstream,
object graph);
}

A system. Runtime. Remoting. Messagin g. IRemotingFormatter interfész (amelyet a

.NET-rernoting a háttérben használ) a Serialize() és a Deserialize() tagokat


az elosztott sorosítások szernpon�ából sokkal hasznosabban terheli túl. Az
IRemotingFormatter a sokkal általánosabb IFormatter interfészből származik:

public interface IRemotingFormatter : IFormatter


{
object Deserialize(stream serializationstream,
HeaderHandler handler);
void serialize(Stream serializationstream, object graph,
Header[] headers);
}

Bár a legtöbb sorosítási feladatban valószínűleg nem lesz közvetlenül dol­


gunk ezekkel az interfészekkel, érdemes tudni, hogy az interfészalapú poli­
morfizmus lehetővé teszi, hogy egy IFormatter referencia segítségével meg­
tartsuk a BinaryFormatter vagy a soapFormatter egy példányát. Tehát ha
olyan rnetódust kívánunk építeni, amely ezen osztályok valamelyikének fel­
használásával sorosítja az objekturngráfot, a következőket írjuk:

static void SerializeobjectGraph(IFormatter itfFormat,


Stream deststream, object graph)
{
itfFormat.Serialize(deststream, graph);
}

79
21. fejezet: Bevezetés az objektumsorosítás világába

A formázák közötti tipuspontosság

A három formázó közötti legnyilvánvalóbb különbség az objektumgráf fo­


lyamba mentésének módjában keresendő (bináris, SOAP vagy XML). Létezik
még néhány apróbb pont is, amelyben különböznek egymástól, ezek közül ki­
emelkedik a formázák típuspontosság szempon�a. A BinaryFormatter típus
nemcsak az objektumgráfban található objektumok adatmezőit menti el, ha­
nem minden egyes típus teljesen meghatározott nevét, valamint a meghatározó
szerelvény teljes nevét is (név, verzió, nyilvános kulcstoken és kultúra). Ezek
miatt az extra adatpontok miatt a B inaryFormatter ideális választásnak tűnik,
ha az objektumokat érték szerint (pl. teljes másolatként), a gépek közötti korlá­
tok átlépésével kívánjuk áthelyezni a .NET-központú alkalmazásokba.
A soapFormatter az eredeti szerelvény nyomait egy XML-névtér segítsé­
gével tárolja el. Gondoljunk a korábban említett Person típusra. Ha a típust
SOAP-üzenetként mentettük volna el, a Person kezdő elemét a generált xmlns
minősítené. Nézzük meg az alábbi definíciórészletet, s közben figyeljünk az
XML-névtérre:

<al:Person id="ref-1" xmlns:al=


"http://schemas.microsoft.com/clr/nsassem/simpleserialize/
MyApp%2C%20version%3Dl.0.0.0%2C%20Culture%3Dneutral%2C%20
PublicKeyToken%3Dnull">
<isAlive>true</isAlive>
<personAge>21</personAge>
<fName id="ref-3"></fName>
</al:Person>

Az xmlserializer azonban nem törekszik a teljes típuspontosság megtartására,


ezért nem tárolja el a típusok teljesen meghatározott nevét, sem az eredeti
szerelvényt Bár ez első ránézésre korlátozásnak tűnhet, az ok az XML-ada­
tok megjelenítésének nyílt természetében keresendő. Nézzük meg a Person
típus egy lehetséges XML-ábrázolását:

<7xml version="l.O"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<isAlive>true</isAlive>
<PersonAge>21</PersonAge>
<FirstName />
</Person>

80
Objektumok sorosítása a BinaryFormatterrel

Ha az objektumok állapotát úgy szeretnénk elmenteni, hogy bármely operá­


ciós rendszer (Windows XP, Mac OS X és a különböző Linux-disztribúciók),
alkalmazási keretrendszer (.NET, J2EE, COM stb.) vagy programozási nyelv
használhassa, ne ragaszkodjunk a teljes típuspontossághoz, hiszen nem téte­
lezhe�ük fel, hogy minden lehetséges befogadó i�meri a .NET -specifikus
adattípusokat Ennek alapján a soapFormatter és az xmlserializer a legmeg­
felelőbb választás, ha az elmentett objektumfának a lehető legszélesebb elér­
hetőséget szeretnénk biztosítani.

Objektumok sorositása
a BinaryFormatterrel

Annak szemléltetésére, hogy milyen egyszerűen menthe�ük el a JamesBondcar


egy példányát fizikai fájlokba, először használjuk a Bi naryFormatter típust. Is­
mét a BinaryFormatter típus két legfontosabb metódusára, a serialize() és a
Deserialize() metódusra kell figyelnünk:

• seri a lize(): Az objektumgráfokat byte-ok sorozataként egy adott fo­


lyamba menti.

• Deseri a lize O: Az elmentett byte-sorozatot objektumgráffá konvertálja.

Tételezzük fel, hogy létrehoztuk a JamesBondcar egy példányát, módosítot­


tunk néhány állapotadatot, és egy ''.dat fájlba szeretnénk elmenteni. Először
magát a *.dat fájlt kell létrehozn unk. Ezt a System. ro. Filestream típus pél­
dányosításával érhe�ük el (lásd a 20. fejezetet). Ezután egyszerűen hozzuk
létre a B inaryFormatter egy példányát, és adjuk át a Filestremet és az objek­
tumgráfot a sorosításhoz. Vizsgáljuk meg a következő Main() metódust:

ll Feltétlenül importáljuk
ll a system.Runtime.serialization.Formatters.Binary
ll és a system.ro névtereket.
static void Main(string[] args)
{
Console.writeLine("***** Fun with object serialization *****\n");

ll Hozzunk létre egy JamesBondCar osztályt, és határozzuk meg


ll az állapotát.
JamesBondcar jbc = new JamesBondcar();
jbc.canFly = tr ue;

81
21. fejezet: Bevezetés az objektumsorosítás világába

jbc.cansubmerge = false;
jbc.theRadio.stationPresets = new doubl e [] { 89. 3, 105.1, 97.1};
jbc.theRadio .hasTweeters = true;

ll Most mentsük el az autót egy adott, bináris formátumú fájlba.


SaveAsBinaryFormat(jbc, "caroata.dat");
Console.ReadLine();
}

A saveAsBinaryFormat() metódust az alábbiak szerint valósí�uk meg:

static void saveAsBinaryFormat(object objGraph, string fileName)


{
ll Mentsük az objektumot egy caroata.dat nevű bináris fájlba.
BinaryFormatter binFormat = new BinaryFormatter();

using(Stream fstream = new Filestream(fileName,


FileMode.create, FileAccess.write, Fileshare.None))
{
b i nForm a t . seria l iz e( fstre am , o bjGraph);
}
Console.writeLine("=> saved car in b in a ry fo rm a t ! ");
}

Látha�uk, hogy a BinaryFormatter.serial ize() metódus felelős az objektum­


gráf összeállításáért és a byte-sorozat valamilyen Streamből származtatott tí­
pusba helyezéséért Ebben az esetben a folyam egy fizikai fájl. Az objektumtí­
pusokat bármilyen Streamből származó típusba, például memóriaterületre, há­
lózati folyamba stb. sorosítha�uk. A program futtatása után a projekt \ bin\
Debug mappájához taliázva megnézhe�ük annak a caroata.dat fájlnak a tar­
talmát, amely a JamesBondcarnak ezt a példányát jeleníti meg. A 21.2. ábra ezt a
Visual Studio 2008-ban megnyitott fájlt ábrázolja.

. ..... FSi�pleSer
ialize. Version=
1.0.0.0, Culture
=neutral, Public

OB 63
52 61
6B 00
72 69
00 Ol
03 00 00 00 .. Si
6C 69 7A 65 mpleSerialize.Ra
61 73 54 77 65 dio ..... hasTweet
75 62 57 6F 6F 66 ers.hasSubYoofer
6E 50 72 65 73 65 s.stationPresets
00 00 Ol 00 09 04
00 00 06 33 33 33 . . .... 33333
46 SA 40 66 66 66 SV@fffffFZ@f ffff
FX@

21.2. ábra: A BinaryFormatter használatával sorosított JamesBondCar

82
Objektumok sorosítása a SoapFormatterrel

Objektumok visszaállitása a BinaryFormatterrel

Tételezzük fel, hogy a bináris fájlba sorosított JamesBondcar típust szeretnénk


objektumváltozókba beolvasni. Miután programozottan megnyitottuk a car­
Data.dat fájlt (a File.openRead() metóduson keresztül), egyszerűen csak

használjuk a BinaryFormatterDeserialize() metódusát. A Deserialize() egy


generikus system. obj ect típust ad vissza, így meg kell adnunk egy explicit
kasztot az alábbiak szerint:

static void LoadFromBinaryFile(string fileName)


{
BinaryFormatter binFormat = new BinaryFormatter();

ll olvassuk be a JamesBondcar típust a bináris fájlból.


using(Stream fstream = File.openRead(fileName))
{
JamesBondcar carFromDisk =

(JamesBondcar)binFormat.Deserialize(fstream);
console.writeLine("can this car fly? : {O}",
carFromDisk.canFly);
}
}

A Deserial ize() hívásakor az elmentett objektumgráf helyét jelző Streamből


származtatott típust adunk meg.. Miután az objektumot visszakasztoillik a
megfelelő típusba, látható, hogy az állapotadatok az utolsó mentési állapotot
tükrözik.

Objektumok sorositása
a SoapFormatterrel

A következő választható formázó a soapFormatter típus. A soapFormatter az


objektumgráfokat SOAP-üzenetként menti el, és ez jó választásnak tűnik ab­
ban az esetben, ha az objektumokat távoli helyeken, különböző környezete­
ken keresztül szeretnénk megosztani. Ha nem ismerjük a Simple Object Access
Protocol (SOAP) specifikációját, röviden annyit azért érdemes tudni róla,
hogy a SOAP egy olyan standard folyamatot határoz meg, amelyben a metó­
dusokat platform- és operációsrendszer-független módon hívhatjuk meg.

83
21. fejezet: Bevezetés az objektumsorosítás világába

Ha beállítottunk egy referenciát a System.Runtime.serialization.Formatters.


Soap.dll szerelvényre (és importáltuk a system.Runtime.serialization.Format­

ters.soap névteret), SOAP-üzenetként sorosíthatunk és állíthatunk vissza egy

JamesBondcar objektumot azáltal, hogy a sinaryFormatter núnden egyes előfor­

dulását a soapFormatter típussal helyettesítjük. Nézzük meg a Program osztály


alábbi, új metódusát, amely az objektumokat helyi fájlba sorosítja:

ll Feltétlenül importáljuk
ll a System.Runtime.serialization.Formatters.Soap névteret,
ll és állítsunk fel referenciát a
ll System.Runtime.serialization.Formatters.Soap.dll szerelvényre.
static void saveAsSoapFormat (object objGraph, string fileName)
{
ll Mentsük az objektumot SOAP-formátumban egy Caroata.soap
ll nevű fájlba.
SoapFormatter soapFormat new soapFormatter();

using(Stream fstream = new Filestream(fileName,


FileMode.create, FileAccess.write, Fileshare.None))
{
soapFormat.serialize(fstream, objGraph);
}
console.WriteLine("=> saved car in SOAP format!");
}

Ahogy azt az előbb is tettük, használjuk a serial ize() és a Deserialize() me­


tódusokat az objektumgráf folyamba helyezésére és az onnan történő kivételre.
Ha a metódust a Main() metódusból hívjuk meg, és futtatjuk az alkalmazást,
megnyithatjuk az eredmenyként létrejött *. soap fájlt.

/(ArO�ta..soap* cais.Cs'\ -� �p;ö-iarTi:-01 "Start()e ... X


( ::El <SOAP -ENV : Envel op e: xrr.. l:-.�: x�i:a "http: 1/WT..""N. W3, orq/2001/XMLSchern.d-in:q;ance" P
Xl!'J.r:.� :x3d "
2$ <SOAP-ENV:8ody> ��
3, <al:J�esBondCa= id�"re:f-1" xr.���:a:*"http://sche�as.microsott.com/clr/nsassem/Si

<canfly>true</canfly>
��
,:�: <canS'..l.bmerqe>false</canSutrr.erqe>
<theRadio href-"Jret-3" l>
<isHatct;.Back>false</i3HatchBack>

</al: Jarr.e!IBondCa:=>
9:- <al: Radio id'"'""ref-3" xmln.s: al="http: //;�cPema.s .rnicrosoft.com/clr/n;�as:sem/SiJl'nleSer

<hasTweeter�>true</hasTweeter�>
<ha�Sub�Voofer�>fal�e</ha�Su:t;Wooter�>
<.!St.ationPre!fets hr ef'""" fref - 4 "l>
</al: Radio>
<SOAP-E11C :A.!:ray id="ref-4" SOAP-ENC: arrayType="xsd: double [3]">

<iterr.>S9. 3</i tem>

.16� <iten:>lOS.l</item>
17� <item>97 .1</ite�r;>

.!.Si </SCAP-ENC:Array>

l9l </SOAP-ENV: Body>

</SOAP-EWY:E�velope>

21.3. ábra: A SoapFormatter használatával sorosított JamesBondCar

84
Objektumok sorosítása az XmlSerializerrel

Itt találha�uk azokat az XML-elemeket, amelyek az aktuális JamesBondcar ál­


lapotára vonatkozó értékeket, valamint a gráfban lévő objektumokat a #ref
tokeneken keresztül jelölik (lásd 21.3. ábra).

Objektumok sorositása
az XmlSerializerrel

A SOAP- és a bináris formázék mellett a system.xml.dll szerelvény egy har­


madik formázót is biztosít: a system.xml.Serialization.xmlserializer egy
adott objektum nyilvános állapotát SOAP-üzenetbe ágyazott XML-adatok he­
lyett tiszta XML-ként tárolja. Ennek a típusnak a használata némiképp kü­
lönbözik a SoapFormatterétől vagy a BinaryFormatterétől. Nézzük meg az
alábbi forráskódot, amely feltételezi, hogy a system.xml.serialization névte­
ret már importál tuk:

static void saveAsXmlFormat(object objGraph, string fileName)


{
ll Mentsük az objektumot XML-formátumban egy caroata.xml
ll nevű fájlba.
xmlserializer xmlForrnat = new xmlserializer(typeof(JamesBondcar),
new Type[] { typeof(Radio), typeof(Car) });

using(Stream fStream = new Filestream(fileName,


FileMode.create, FileAccess.write, Fileshare.None))
{
xmlFormat.Serialize(fstream, objGraph);
}
console.writeLine("=> Saved car in XML format!");
}

A xmlseria lizer típus alkalmazásakor felmerülő leglényegesebb különbség,


hogy ehhez meg kell határoznunk minden egyes, a gyökérbe ágyazott alelem
típusára vonatkozó információt. Az xmlserializer első konstruktorargumen­
tuma meghatározza az XML-fájl gyökérelemét, míg a második argumentum
a system.Type típusok tömbje, amely az alelemekre vonatkozó metaadatokat
tartalmazza. Ha belenézünk az újonnan generált XML-fájlba (feltételezve,
hogy ezt az új metódust valóban a Main() metódusban hívtuk meg), a 21.4.
ábrán látható XML-adatokat találjuk

85
21. fejezet: Bevezetés az objektumsorosítás világába

�x
l� <?xml version="l.O"?>

2 j <Jar.r esBondCar xm.l!'ls :x�i="http: lbr,;w.w3. org/2001/XHLSchema-ínst.ance" ..:.1
3� xrr.J.ns: xsd="http: II'N"W'II'I. w3. orq/2001/Xl-!LSchei'!'.a">

�-.� <t
��=:��:= te�s>true</hasTweete�s>

€� l <hasSubNoofers>false</hasSubWoofers>

<stationPresets>

8; <double>89.3</double>

9[ <double>l05 .l</double>

: o' <double>97.1</double>

l:!.�
::.2i
</statio�Presets>

<radioiD>XF-552RR6</:::adio!D>
l

l
�3' </theRadio>
:�i <isHatchBack>fal.se</isHatchBack>

:5 <canFly>true</canFly>

:6 <canSubrr.erge>false</ canSt.:l:::rr.erc;e>

</Jarr.es3ondCar>
-l
l
21.4. ábra: A XmlSerializer l1asználatával sorosított JamesBondCar

Megjegyzés Az xmlseriali zer típus egyik feltétele, hogy minden, az objektumgráfban ta­
lálható sorosított típus támogasson egy alapértelmezett konstruktort (tehát mindenképpen ad­
juk hozzá az egyedi konstruktorok meghatározásakor). Ha ez nem történik meg, futásidőben
InvalidoperationException jelenik meg.

Generált XML-adatok szabályozása

Az XML-technológíákra vonatkozó háttérismeretekből tudjuk, hogy gyakran


létfontosságú annak biztosítása, hogy az XML-dokumentum adatai megfelel­
jenek az adatok éJ7Jényességét meghatározó szabályoknak Tudjuk, hogy az
"
"érvényes XML-dokumentumoknak semmi köze az XML-elemek szintakti­
kai jólétéhez (pl. minden nyitóelemhez tartoznia kell egy záróelemnek is). Az
érvényes dokumentumoknak inkább olyan - megegyezésen alapuló - formá­
zási szabályoknak kell megfelelniük (pl. az X mezőt attribútumként, nem pe­
dig alelemként kell kifejezni), amelyeket jellemzően egy XML-sémában vagy
egy DTD- (dokumentum-típusdefiníció) fájlban határozunk meg.
Az alapértelmezés szerint egy [seria l izab le] típus minden nyilvános
adatát alelemként és nem XML-attribútumként formázzuk meg. Ha szeret­
nénk meghatározni, hogy az xmlserializer hogyan gerterálja az eredmény­
ként létrejövő XML-dokumentumot, a [seria li zabl e] típusokat tetszőleges
számú további, a system.xml.serialization névtérből származó attribútum­
mal ruházha�uk fel. A 21.1. táblázat néhány (de nem minden) olyan aUribú­
turnot tartalmaz, amelyek befolyásolják az XML-adatok folyamba történő
kódolásának a módját.

86
Objektumok sorosítása az XmlSerializerrel

· . .·�:r;;,�f.:�: ..
· :· ··
·. : . �- -...

xmlAttributeAttribute A tagot XML-attribútumként sorosítja.

xmlElementAttribute A mezőt vagy tulajdonságot XML-elemként sorosítja.

xmlEnumAttribute Meghatározza, hogy egy felsorolt típus egy tagjához


rnilyen nevű elem tartozzon a sorosítás során nyert
XML-ben.

xmlRootAttribute Ez az attribútum határozza meg, hogy a gyökérelem


rnilyen szerkezetű legyen (névtér és elemnév).

xmlTextAttribute A mező vagy tulajdonság sorosításának XML-szöveg­


ként kell történnie.

xmlTypeAttribute Az XML-típus neve és névtere.

21.1. táblázat: A System.Xml.Serialization névtér néhány attribútuma

Először egy egyszerű példán figyeljük meg, hogy a JamesBondcar adatmezői­


nek elmentése milyen XML-t eredményez:

<?xml version="l.O" encoding="utf-8"7>


<JamesBondcar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<canFly>true</canFly>
<cansubmerge>false</cansubmerge>
</JamesBondcar>

Ha olyan egyedi XML-névteret szeretnénk megadni, amely meghatározza a


JamesBondcar osztályt, valamint a canFly és a cansubmerge értékeket XML­

attribútumként kódolja, módosítsuk a JamesBondcar C#-definícióját az alábbiak


szerint:

[Serializable,
xmlRoot(Namespace = "http://www.intertech.com")]
public class JamesBondcar : Car
{
[xmlAttri bute]
p ub lic bool canFly;
[XmlAttri bute]
public bool cansubmerge;
}

Ennek eredménye a következő XML-dokumentum lesz (figyeljük meg a kezdő


<JamesBondcar> elemet):

87
21. fejezet: Bevezetés az objektumsorosítás világába

<?xml version="l.O"""?>
<JamesBondcar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
canFly="true" cansubmerge="false"
xmlns="http://www.intertechtraining.com">

</JamesBondcar>

Természetesen számos attribútumot használhatunk annak vezérlésére, hogy


az xmlserializer hogyan generálja a létrejövő XML-dokumentumokat. To­
vábbi részletekért tekintsük át a .NET Framework 3.5 SDK dokumentációjá­
ban a system.xml.serial ization névtérre vonatkozó információkat.

Objektumgyűjtemények sorositása

A továbbiakban azt vizsgáljuk meg, hogyan menthe�ük el az objektumok


egy halmazát. Az IFormatter interfész serialize() metódusa nem teszi lehe­
tővé, hogy inputként tetszőleges számú objektumot adjunk meg (csak egy
önálló system.object objektumot). Ennek megfelelően a oeserialize() visz­
szatérési értéke szintén csak egy önálló system.Object (az xmlserializer tí­
pusra ugyanezek az alapvető korlátozások érvényesek):

public interface IFormatter


{

object Deserialize(system.ro.stream serializationstream);


void serialize(System.IO.Stream serializationstream,
object graph);
}

A System.object valójában az objektum teljes fáját képviseli. Ez alapján, ha


átadunk egy [Serializable] attribútummal megjelölt objektumot, amely to­
vábbi [Serializable] objektumokat tartalmaz, akkor az objektumok teljes
halmaza egyetlen metódushívással rögzül. A system.collections és a sys­
tem.collections.Generic névterekben található típusok legnagyobb része

már rendelkezik [seriali zable] jelöléssel. Ezért ha sorosítani szeretnénk az


objektumok egy halmazát, egyszerűen adjuk hozzá a halmazt egy tárolóhoz
(pl. egy ArrayListhez vagy egy Li st <T>-hez), majd sorosítsuk az objektumot a
kívánt folyamhoz.

88
Objektumgyűjtemények sorositása

Tételezzük fel, hogy néhány állapotadat meghatározásához kétargumen­


tumú konstruktorral frissítettük a JamesBondcar osztályt (látha�uk, hogy az
xmlseriali zer követelményeinek megfelelőerr az alapértelmezett konstruk­
tort is hozzáadjuk):

[Serial izable,
XmlRoot(Namespace "http:llwww.intertech.com")]
=

public class JamesBondcar : car


{
public JamesBondcar(bool skyworthy, bool seaworthy)
{
canFly = skyworthy;
cansubmerge = seaworthy;
}
ll Az xmlserializer megkövetel egy alapértelmezett konstruktort!
public JamesBondcar(){}

Így most már az alábbiak szerint akárhány JamesBondcar osztályt menthetünk el:

static void SaveListOfCars()


{
ll Most sorosítsuk a JamesBondcar List<T> objektumot.
List<JamesBondcar> mycars new List<JamesBondcar>();
=

mycars.Add(new JamesBondcar(true, true));


mycars.Add(new JamesBondcar(true, false));
mycars.Add(new JamesBondcar(false, true));
mycars.Add(new JamesBondcar(false, false));

using(stream fstream = new Filestream("carcollection.xml",


FileMode.create, FileAccess.write, Fileshare.None))
{
xmlserializer xmlForrnat =

new xmlserializer(typeof(List<JamesBondcar>),
new Type[] { typeof(JamesBondcar), typeof(car),
typeof(Radio) });
xmlFormat.serialize(fstream, mycars);
}
console.writeLine("=> Saved list of cars!");
}

Minthogy az xmlserializer típust használtuk, meg kell határoznunk a típusin­


formációt minden egyes, a gyökérobjektumban található alobjektumra vonat­
kozóan (a gyökérobjektum jelen esetben a List<JamesBondcar>) . Ha a Binary­
Formatter vagy a soapFormatter típust használtuk volna, még egyértelműbb
logikai úton haladnánk, például:

89
21. fejezet: Bevezetés az objektumsorosítás világába

static void saveListofcarsAsBinary()


{
ll Mentsük el az ArrayList objektumot (mycars) bináris
ll objektumként.
List<JamesBondcar> mycars new List<JamesBondCar>();

BinaryFormatter binFormat new BinaryFormatter();


using(Str e am fstream = new Filestream("AllMyCars.dat",
FileMode.Create, FileAccess.Write, Fileshare.None))
{
binFormat.serialize(fstream, mycars);
}
console.WriteLine("=> saved list of cars in binary!");
}

Forráskód A SimpleSerialize projektet a forráskódkönyvtárban a 21. fejezet alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A sorositási folyamat testreszabása

A legtöbb esetben az alapértelmezett .NET platform sorosítási séma pontosan


olyan lesz, amilyet szeretnénk. Egyszerűen használjuk a [Serializable] attri­
bútumot a kapcsolódó típusokhoz, és adjuk át a feldolgozandó objektumfát a
kívánt formázónak Néhányszor előfordulhat, hogy nagyobb beleszólást sze­
retnénk a fa szerkesztésekor és kezelésekor a sorosítási folyamatba. Ha például
olyan üzleti szabállyal rendelkezünk, amely szerint minden adatmezőt adott
formátumban kell elmenteni, vagy további olyan adatelemeket szeretnénk a fo­
lyamhoz hozzáadni, amelyek nem képeződnek le közvetlenül a rögzítendő ob­
jektumban található mezőkbe (időbélyegek, egyedi azonosítók stb.).
Ha jobban be szeretnénk kapcsolódni az objektumsorosítás folyamatába, a
System.Runtime.serialization névtér számos olyan típust bocsát a rendelke­
zésünkre, amelyek mindezt lehetővé teszik. A 21.2. táblázat a legfontosabb
típusok közül ismertet néhányat.

I Seria l izabl e Ennek az interfésznek a [Se ri a l izab l e] típusra törté­


nő implementálásával vezérelhetjük annak sorosítását
és visszaállítását.

objectiDGenerator Ez a típus generálja az objektumgráf tagjainak azonosí­


tóját.

90
A sorosítási folyamat testreszabása

��-
;-"·j,·
���-:.
.w:�:.��
;.';;;-�;�,.,::_�:'.,..,... ..;..
:t'��l;·_.�;"��, ....,
...." h..,;�i..;,r.-' �-.-;-''<�..;
�-e-".�-��")�.r.-�....
. ���":01>��-�f'·
-,.; __;.'
;__ ; ..;:.w ,-·i:•·;.� 1!,-
..�.,.., __ .. en...

·;
"c<'W•,'
··"""'!.
'·;,-;',·�'c:;
�·�-::- ��.lll
[OnDeserialized] Ez az attribútum teszi lehetövé, hogy specifikáljuk azt a
metódust, amelyet közvetlenül az objektum visszaállí­
tása után hívunk.

[OnDeserializing J Ez az attribútum teszi lehetövé, hogy specifikáljuk azt a


metódust, amelyet az objektum visszaállítása előtt hívunk

[on serialized] Ez az attribútum teszi lehetővé, hogy specifikáljuk azt a


metódust, amelyet közvetlenül az objektum sorosítása
után hívunk.

[ons erializing J Ez az attribútum teszi lehetövé, hogy specifikáljuk azt a


metódust, amelyet a sorosítási folyamat előtt hívunk

[Opti on al Fi e l d] Ez az attribútum teszi lehetövé, hogy meghatározzuk a


típusoknak azokat a mezőit, amelyek kimaradhatnak az
adott folyambóL

se ri al iz a tion info Ez az osztály egy ún. "tulajdonságkosár", amely a


sorosítási folyamat során az objektumok állapotát jelző
névj érték párokat képviseli.

21.2. táblázat: A System.Runtime.Serialization névtér alapvető típusai

Az objektumsorositás háttérrészletei

Mielőtt megvizsgálnánk a sorosítási folyamat testreszabásának különböző


módjait, érdemes a háttérrel is foglalkozni. Amikor a Bi naryFormatter soro­
sí�a az objektumgráfot, az alábbi információkat továbbí�a az adott folyamba:

• a gráfban található objektum teljesen meghatározott nevét (pl. MyApp.


JamesBondca r),

• az objektumgráfot meghatározó szerelvény nevét (pl. MyApp. ex e) ,

• az serializationinfo osztály egy példányát, amely minden, az objek­


tumgráf tagjai által fenntartott állapotadatot tartalmaz.

A Bi naryFormatter a visszaállítási folyamat során az objektum egy azonos


másolatának létrehozásához ugyanazt az információt használja, amelyet az
alatta lévő folyamból nyer ki. A SoapFormatter által alkalmazott folyamat
meglehetősen hasonló.

91
21. fejezet: Bevezetés az objektumsorosítás világába

Megjegyzés Az XmlSerializer, annak érdekében, hogy a lehető legnagyobb mértékben meg­


tartsa az objektum állapotának hordozhatóságát, nem menti el a típusok teljesen meghatáro­
zott nevét, sem a meghatározó szerelvény nevét. Ez a típus csak a megjelenített nyilvános
adatok mentésével foglalkozik.

Azon túl, hogy a szükséges adatokat a folyamba helyezik, illetve onnan kive­
szik, a formázák az alábbi infrastrukturális adatok szempon�ából elemzik is
az objektumgráfban található tagokat:

• Ellenőrzik, hogy az objektumok meg vannak-e jelölve a [serializable]


attribútummaL Ha az objektum nincs megjelölve, serializationExcep­
tion jelenik meg.

• Ha az objektum meg van jelölve a [Serializable] attribútummal, ellen­


őrzik és meghatározzák, hogy az objektum implementálja-e az r seria ­

lizable interfészt. Ebben az esetben a Getobjectoata() metódust hív­


ják az objektumra.

• Ha az objektum nem implementálja az r serial i zab le interfészt, az


alapértelmezett sorosítási folyamat sorosí�a az összes, [Nonserialized]
attribútummal meg nem jelölt mezőt.

Azon kívül, hogy meghatározzák, hogy a típus támoga�a-e az r serializab le


interfészt, a formázák azért is felelősek, hogy felfedjék, vajon a kérdéses típu­
sok támoga�ák-e azokat a tagokat, amelyeket az [onserializing], az [onse­
ria lized], az [onoeserializin g] vagy az [onoeserialized] attribútumban ha­

tároztunk meg. Mielőtt megvizsgálnánk ezeknek az attribútumoknak a sze­


repét, nézzük meg közelebbről az r serializab le funkcióját.

Sorositások testreszabása az ISerializable


használatával

A [serializable] attribútummal megjelölt objektumoknak lehetősége van


implementálill az rserializable interfészt. Ezzel "részévé válhatunk" a soro­
sítási folyamatnak, és az adatokat előzetesen vagy utólagosan formázhatjuk

Megjegyzés A .NET 2.0 megjelenése óta a sorosítási folyamat testreszabásának preferált


módja a sorosítási attribútumok használata (ennek leírását lásd alább). A meglévő rendszerek
fenntartása érdekében azonban fontos, hogy ismerjük az r seri alizable interfészt is.

92
A sorosítási folyamat testreszabása

Az rserializable interfész meglehetősen egyszerű, hiszen csak egy metódust


határoz meg, ez pedig a Getobjectoata():

ll Ha a sorosítási folyamat finomhangolására törekszünk,


ll implementáljuk az rserializable interfészt.
public interface rserializable
{
void Getobjectoata(Serializationrnfo info,
Streamingcontext context);
}

A Getobjectoata() metódust az adott formázó a sorosítási folyamat során au­


tomatikusan hívja. Az implementációja feltölti a bemeneti serializationrnfo
paramétert név j érték párok sorozatával, amelyek Gellemzően) az elmenten­
dő objektum adatmezőire képződnek le. A serializationrnfo számos variá­
ciót határoz meg a felüldefiniált Addvalue() metódusra, valarnint a tulajdon­
ságok egy kis gyűjteményére, ezek lehetővé teszik, hogy az adott típus lekér­
dezze és módosítsa a típus nevét, a meghatározó szerelvényt és a tagok szá­
mát. Nézzünk meg egy kódrészletet:

public sealed class Serializationrnfo : object


{
public serializationrnfo(Type type,
IFormatterconverter converter);
public string AssemblyName { get; set; }
public string FullTypeName { get; set; }
public int Membercount { get; }
public void Addvalue(string name, short value);
public void Addvalue(string name, urntl6 value);
public void Addvalue(string name, int value);

Az r serial izable interfészt implementáló típusoknak egy, az alábbi szigna­


túrával rendelkező, speciális konstruktort is meg kell határozni:

ll Az egyedi konstruktort el kell látni ezzel az aláírással


ll annak érdekében, hogy a futtatómotor meg tudja határozni
ll az objektum állapotát.
[serializable]
class Someclass : ISerializable
{
protected Someclass (Serializationrnfo si,
Streamingcontext ctx) { ... }

93
21. fejezet: Bevezetés az objektumsorosítás világába

Ennek a konstruktornak a láthatósága védettre van beállítva. Ez azért lehetsé­


ges, mert a formázó a láthatóságtól függetlenül hozzáfér ehhez a taghoz.
Ezek a speciális konstruktorok általában védett (vagy privát) jelölést kapnak,
hogy megakadályozzák, hogy az alkalmi objektumfelhasználók ilyen módon
hozzanak létre objektumokat. A konstruktor első paramétere a serialization­
Info típus egy példánya (ahogy azt korábban is láttuk).

A speciális konstruktor második paramétere egy streamingcontext típus,


amely a bitek forrásával vagy rendeltetési helyével kapcsolatban tartalmaz in­
formációt. Ennek a típusnak a leginformatívabb tagja a state tulajdonság,
amely egy, a streamingcontextstates felsorolt típusból származó értéket kép­
visel. A felsorolt típus értékei az aktuális folyam alapösszeállítását ábrázolják.
Ha csak nem implementálunk néhány alacsony szintű, egyedi remoting­
szolgáltatást, aligha kell közvetlenül foglalkoznunk ezzel a típussaL Ennek
ellenére megadjuk a streamingcontextstates felsorolt típus lehetséges értékeit
(a részletek a .NET Framework 3.5 SDK dokumentációjában találhatók):

public enum StreamingcontextStates


{
crossProcess,
crossMachine,
Fi le,
Persistence,
Remoting,
Other,
clone,
crossAppDomain,
Al l
}

A rserializable interfészt használó sorosítási folyamat testreszabásának il­


lusztrálásához tételezzük fel, hogy olyan új konzolalkalmazást hozunk létre
(customserialization névvel), amely két sztringadatot tartalmazó osztálytípust
határoz meg. Továbbá tegyük fel, hogy a sztringobjektumokat csupa nagybe­
tűvel kell sorosítani, és csupa kisbetűvel kell visszaállítani. Ezeknek a szabá­
lyoknak az érdekében a következőképpen implementáljuk az rserializable in­
terfészt (ne felejtsük el importálni a system. Ru ntime. serial ization névteret):

[serializable]
class StringData : ISerializable
{
public string datartemone = "First data black";
public string datartemTwo= "More data";

94
A sorosítási folyamat testreszabása

public StringData(){}
protected StringData(serializationinfo si, Streamingcontext ctx)
{
ll Töltsük fel újra a tagváltozókat a folyamból.
datartemone = si.GetString("First_Item").ToLower();
dataitemTwo = si. GetString("dataitemTwo").ToLower();
}

void rserializable.GetobjectData(serializationrnfo info,


Streamingcontext ctx)
{
ll Töltsük fel a Serializationrnfo objektumot
ll a formázott adatokkal.
info.Addvalue("First_Item", dataitemone.ToUpper());
info.Addvalue("dataitemTwo", dataitemTwo. ToUpper());
}
}

Amikor a GetobjectData() metódusból töl�ük fel a serializationrnfo típust,


az adatoknak nem kell ugyanazt a nevet adnunk, mint a típus belső tagválto­
zóinak Ez akkor hasznos, ha a típust és a perszisztálási formátumot még
jobban függetleníteni szeretnénk.
A testreszabás teszteléséhez tételezzük fel, hogy elmentettük a MyString­
Data egy példányát egy soapFormatter használatával:

static void Main(string[] args)


{
console.writeLine("***''* Fun with custom Serialization *****");

ll Emlékszünk, hogy ez a típus implementálja az rserializable


ll interfészt.
StringData myoata = new StringData();

ll Mentsük el SOAP-formátumban egy helyi fájlba.


soapFormatter soapFormat = new SoapFormatter();
using(stream fstream = new Filestream("MyData.soap",
FileMode.create, FileAccess.write, Fileshare.None))
{
soapFormat.Serialize(fStream, myData);
}
Console.ReadLine();
}

Ha megnézzük az így létrejövő *.soap fájlt, lá�uk, hogy a sztringmezőket


nagybetűsen tároljuk (lásd a 21.5.ábrát).

95
21. fejezet: Bevezetés az objektumsorosítás világába

/ MYI5ata:soipl}��Dita.� 1 ·Pro �
� ... x
r 1'EJ <SOAP-ENv: Envelope xmln s : xs i="htto: //�.7-:--:;;-3.--z;ga·ooi/"XMr:sct;:�;;;-:1
i 2f;:l <SOAP-ENV:Body> ,--o

l! _=· fiJ-: <al:Stringr::ata id="ref-1" xrn l ns:al="http://schemas .microsol _ ::

4Sof ll <First_Item id="ref-3">FIRST DATA BLOCK</First_Item> i" :


���[ <dataitemTHo id="ref-4">MORE DATA</dataitemTvlO>
o •

o ; /al:Str �ngData> -�j


o , </ �o;._P-ENV. Body>
i e: -</SOAP-ENV : Envelope> l
! q_ �!
���:�-----"·-·--) ---�---- �--- - -- ' ·

21. 5. ábra: A sorosítás testreszabása az ISerializable használatával

Sorositások testreszabása attribútumokkal

Bár az rserializable interfész implementációja a sorosítási folyamat testre­


szabásának egy lehetséges módja, a .NET 2.0 megjelenése óta a sorosítási fo­
lyamat testreszabása inkább az új sorosítás-központú attribútumok segítsé­
géve!: [OnSerializing], [OnSerialized], [OnDeserializing] vagy [OnDeseria-
1 ized] megadott metódusokkal történik. Ezeknek az attribútumoknak a
használata kevésbé nehézkes, mint az r serializabl e implementálása, hiszen
nem kell manuálisan közreműködnünk a bemeneti serializationrnfo para­
méterekkel. Ehelyett, miközben a formázó elvégzi a típussal kapcsolatos
munkát, közvetlenül módosítha�uk az állapotadatokat

Megjegyzés Ezek a sorosítási attribútumok a System. Runtime. se rialization névtéren


belül vannak definiálva.

Ezeknek az attibútumoknak a használatához egy olyan metódust kell defini­


álni, amely megkapja a Streamingcontext paramétert, és nem ad vissza sem­
mit (különben futásidejű kivételt kapunk). Nem kellminden egyes sorosítás­
központú attribútumot számba vennünk, elég csak a sorosítás számunkra ér­
dekes állapotaival foglalkoznunk. Ennek illusztrálására nézzünk meg egy új
[serializable] típust, amely ugyanazokkal a követelményekkel rendelke­
zik, mint a stringData, és ez alkalommal az [onserializing] és az [OnDese­
rialized] attribútumokat használja:

[Serializable]
class MoreData
{
public string datartemone = "First data block";
public string dataitemTwo= "More data";

96
Összefoglalás

[onserializi ng]
private void onserializing(Streamingcontext context)
{
ll Hívás a sorosítási folyamat alatt.
dataiternOne = dataitemOne.ToUpper();
dataitemTwo = dataitemTwo.ToUpper();
}

[onoeserialized]
private void OnDeserialized(Streamingcontext context)
{'
ll Hívás a visszaállítási folyamat befejezését követően.
datartemone = dataitemone.ToLower();
dataitemTwo = dataitemTwo.ToLower();
}
}

Ha az új típust sorosítani szeretnénk, ismét azt tapasztalnánk, hogy az ada­


tok sorosítása nagybetűs, míg a visszaállítása kisbetűs.

Forráskód A CustomSerialization projektet a forráskódkönyvtárban a 21. fejezet alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az előző példa segítségével megismertük az objektumsorosítás legfőbb szol­


gáltatásait, köztük a folyamat testreszabásának különböző módjait. A sorosí­
tási és a visszaállítási folyamat révén nagy mennyiségű adatot is egyszerűen
elmenthetünk, ez sokkal kevesebb munkát igényel, mintha a system.ro név­
tér különböző olvasó/író osztályaival dolgoznánk. Ha közelebbről is meg
szeretnénk ismerkedni a .NET platformnak ezzel az aspektusával, olvassuk
el a BinaryFormatter, a SoapFormatter és az xmlserializer típusokról szóló in­
formációkat a .NET Ftamework 3.5 SDK dokumentációjában.

Összefoglalás

Ebben a fejezetben az objektumsorosító szolgáltatások témáját tárgyaltuk


A .NET platform az objektumgráfokat használja azon kapcsolódó objektu­
mok teljes halmazának figyelembevételére, amelyeket egy folyamba kell el­
mentem. Mindaddig, amíg minden objektumgráfot a [serializable] attribú­
tummal jelölünk, megválaszthatjuk az adatok sorosításának a formátumát
(bináris, SOAP vagy XML). A nem egyedi sorosítási folyamatoknak kétféle
testreszabási módja lehetséges.

97
21. fejezet: Bevezetés az objektumsorosítás világába

Először megnéztük az r se rial izab l e interfész implementálásának (és egy


speciális privát konstruktor támogatásának) a mádját annak érdekében, hogy
nagyobb befolyássailehessünk arra, hogy a megadott adatokat hogyan ment­
sék el a formázák Ezt követően megismertünk néhány olyan .NET-attri­
bútumot, amelyekkel leegyszerűsíthető a sorosítás testreszabása. A streami ng­
context paraméterrel rendelkező tagoknál az [onserial i zing], az [onserial i­
zed], az [OnDeserializing] vagy az [OnDeserialized] attribútumot alkalmaz­
zuk, a formázák pedig ennek megfelelőerr fogják őket meghívni.

98
HUSZONKETTEDIK FEJEZET

ADO.NET, 1. rész:
Az élő kapcsolat

A .NET platform számos névteret definiál, amelyek segítségével a gépen ta­


lálható lokális vagy távoli relációs adatbázisokat kezelni tudjuk. Ezeket a
névtereknek a gyűjtőneve ADO.NET. Először körvonalazzuk az ADO.NET
általános szerepét, majd az adatszolgáltatók témakörét tárgyaljuk A .NET
platform számos adatszolgáltatót támogat, ezek mindegyikét adott adatbá­
zis-kezelő rendszerrel (Microsoft SQL Server, Oracle, MySQL stb.) történő
kommunikációra optimalizálták.
A különböző adatszolgáltatók közös funkcióit megvizsgálva megnézzük a
data provider factory minta működését. A system. Data. common névtér hasz­
nálatával (és a hozzátartozó App. confi g fájl segítségéve!) elkészíthetünk egy
olyan egyszerű kódot, amely dinamikusarr képes választani és használni a
mögöttes adatszolgáltatók közül anélkül, hogy az alkalmazás kódját újra kel­
lene fordítani vagy telepíteni.
Olyan egyedi adathozzáférési könyvtárt fogunk tudni készíteni (AutoLot­
DAL. dll), amely különböző, egy egyedi adatbázison, az AutoLaton végre­

hajtható adatbázis-műveleteket valósít meg. A könyvtárat a következő feje­


zetben továbbfejlesztjük, és a továbbiakban többször használni is fogjuk. A
fejezet végén megvizsgáljuk, hogyan lehet a Microsoft SQL Serverrel aszink­
ron módon kommunikálni a system. Data. Sql cl i e nt névtér típusainak fel­
használásával, valamint bevezetjük az adatbázis-tranzakciók fogalmát

Az ADO.NET magas szintü


meghatározása

Ha ismerjük a Microsoft korábbi, COM-alapú adatelérési modelljét (Active


Data Objects vagy ADO), tudjuk, hogy az ADO.NET-nek meglehetősen ke­
vés köze van az ADO-hoz. Bár van kapcsolat a két rendszer között (pl. mind­
kettő tartalmazza a kapcsolat- és utasításobjektumok fogalmát), néhány
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

ADO-típus (rnint pl. a Recordset) már nem létezik. Továbbá számos olyan
ADO.NET-típus van, amelyhez nem találunk a klasszikus ADO-ban megfe­
leltethető párt (ilyen pl. az adatillesztő).
Ellentétben a klasszikus ADO-val, amelyet a szorosan kapcsolódó kli­
ens/ szerver rendszerekre hoztak létre, az ADO.NET a kapcsolat nélküli vilá­
got szem előtt tartva készült el a Datasetek használatávaL A típus tetszőleges
számú kapcsolódó adatbázistáblák helyi másolatát reprezentálja, amelyeknek
rnindegyike sorok és oszlopok gyűjteménye. A Dataset segítségével a hívó­
szerelvény (pl. egy weboldal vagy egy asztali végrehajtható fájl) kezelheti és
módosíthatja a Dataset tartalmát, mialatt a kapcsolata az adatforrással bontva
van, és a megfelelő adatillesztőn keresztül bármilyen módosított adatot visz­
szaküldhet további feldolgozásra.
További lényeges különbség a klasszikus ADO és az ADO.NET között az,
hogy az ADO.NET messzemenően támogatja az XML-adatok megjelenítését.
Tulajdonképpen az adattárolóból kinyert adatot (az alapértelmezés szerint)
XML-formátumban sorosítja a rendszer. Mivel az XML-adatokat gyakran
szabványos HTTP protokollon keresztül továbbítjuk a rétegek között, így az
ADO.NET nem ütközik semmilyen tűzfal által okozott korlátozásba.
Talán a legalapvetőbb különbség a klasszikus ADO és az ADO.NET között
az, hogy az ADO.NET nem más, rnint egy felügyelt kódkönyvtár, így pontosan
a felügyelt könyvtárak szabályai szerint működik. Az ADO.NET-et alkotó ti­
pusok a CLR memóriakezelési protokollját használják, ugyanazokat a típus­
rendszereket alkalmazzák (osztályokat, interfészeket, felsorolásokat, struktú­
rákat és metódusreferenciákat), és bármely .NET-nyelv által elérhetők.
Programozási szemszögből nézve az ADO.NET lényegét egy alapvető sze­
relvény képviseli, amelynek System. Data. dll a neve. A bináris fájlban jó né­
hány névtér megtalálható (lásd 22.1. ábra), ezek közül sok az elérhető
ADO.NET-adatszolgáltató (ennek definícióját lásd alább) típusait reprezentálja.

éJ....o kffi@•li
: $··· (} Microsoft.Sq!Server.Server 0
�··· (} System.Data
�... {} fJ
lJ
System.Data.Common
$ .. (} System.Data.Odbc
@ .. {} System.Data.OieDb
@ .. {} System.Data.Sql
f éJ.. (} System.Data.Sq!Ciient
! $.. (} System.Data.SqfTypes
! IÍI· (} System.Xml
G
;c "'
.l
22. 1. ábra: A System.Data.dll a legalapvetőbb ADO.NET szerelvény

100
Az ADO.NET magas szintű meghatározása

A legtöbb Visual Studio 2008 projektsablon automatikusan hivatkozik erre a


kulcsfontosságú adatelérési könyvárra. Ennek ellenére módosítani kell a for­
ráskódunkat, hogy azokat a névtereket is importáljuk, amelyeket használni
szeretnénk. Például:

ll vezessünk be néhány ADO.NET-névteret!


using system.Data;
using System.Data.Sqlclient;

namespace MyApp
{
class Program
{
static void Main(string[] args)
{
}
}
}

Az említett system.Data.dll szerelvényen kívül léteznek más ADO.NET­


centrikus szerelvények (rnint pl. a system.Data.oracleclient.dll), amelyekre
manuálisan kell hivatkoznunk az aktuális projektünkben az Add Reference
párbeszédpanel segítségéveL

Megjegyzés A .NET 3.5 megjelenésével az ADO.NET számos további szerelvénnyel és név­


térrel bővült, amelyek az ADO.NET/LINQ integrációt segítik. A 24. fejezetben részletesen meg­
vizsgáljuk az ADO.NET LINQ-központú jellemzőit.

Az ADO. N ET két arca

Az ADO.NET könyvtárakat két koncepcionálisan egyedi módon lehet hasz­


nálni: élőkapcsolat-alapú és kapcsolat nélküli módon. Ha a kapcsolatalapú mo­
dellt használjuk, akkor a forráskódunk expliciterr csatlakozik az alatta található
adattárolóhoz, és szintén expliciterr bontja is a kapcsolatot. Ha ilyen megkö­
zelítésben használjuk az ADO.NET-et, akkor tipikusan a kapcsolatobjektu­
mokon, parancsobjektumokon és adatolvasó objektumokon keresztül kom­
munikálunk az adattárolóvaL
A kapcsolat nélküli modell (lásd a 23. fejezetben részletesen) lehetőséget
biztosít arra, hogy DataTable objektumoknak azt a halmazát kezeljük (ezeket
a Dataset tárolja), amelyek a külső adatok kliensoldali másolatának felelnek
meg. Ha lekérünk egy Dataset objektumot a megfelelő adatillesztő objek-

101
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

tummal, a kapcsolat automatikusan megnyílik, majd lezáródik. Ez a megkö­


zelítés a kapcsolatok gyors felszabadítását segíti más hívák számára, és
nagyban hozzájárul a rendszer skálázhatóságának tökéletesítéséhez.
Amikor a hívó megkap egy Datasetet, képes bejárni és kezelni a tartal­
mát, közben pedig nem generál hálózati forgalmat. Hasonlóképpen, amikor a
hívó szeretné visszaküldeni a módosításait az adattárolónak, a rendszer az
adatillesztő segítségével (SQL-utasítások egy halmazával együttműködés­
ben) módosí�a az adatforrást, ezután a kapcsolat azonnal bomlik.

Az ADO.NET-adatszolgáltatók müködése

A klasszikus ADO-val ellentétben az ADO.NET nem egyetlen típushalmazt


kínál fel a különböző adatbáziskezelő rendszerekkel (Database Management
System - DBMS) folytatott kommunikációhoz, helyette több adatszolgáltatót
támogat, amelyek mindegyike egy meghatározott adatbáziskezelőre van op­
timalizálva. A megközelítés egyik előnye, hogy ilyen módon az meghatáro­
zott adatszolgáltató hozzáférhet az adott adatbáziskezelő egyedi tulajdonsá­
gaihoz. A másik előnye az, hogy a specifikus adatszolgáltató közvetlenül
csatlakozhat a mögöttes adatbázismotorhoz anélkül, hogy egy köztes leképe­
ző réteget kellene igénybe vennie.
Röviden, az adatszolgáltató egy adott névtérben definiált típusok egy hal­
maza, amely tudja, hogyan kell kommunikálnia egy konkrét adatforrássaL
Függetlenül attól, hogy melyik adatszolgáltatót használjuk, mindegyik egy
olyan osztálytípushalmazt definiál, amely az alapvető funkcionalitást bizto­
sí�a. A 22.1. táblázat néhány alapvető objektumot mutat be, valamint a hoz­
zájuk tartozó ősosztályt (amelyek mindegyikét a system.Data.common névtér
definiálja), és az általuk megvalósított adatközpontú interfészeket (amelyek
mindegyikét a System. Data névtér definiálja).

connection DbConnection robconnection Lehetőséget biztosít az adattá­


rolóhoz való csatlakozásra és a
kapcsolat bontására. A kapcso­
latobjektumok lehetőséget biz­
tosítanak a kapcsolódó tranzak­
ciós objektumok eléréséhez.

102
Az ADO. NIT-adatszolgáltatók müködése

�--
i.>:.
··'
.
&!>.�:�'.'·. �-.•'.
'

·'
,.

Command D beammand robcommand Egy SQL-lekérdezést vagy egy


tárolt eljárást képvisel. A kap-
csalatobjektumok hozzáférést
biztosítanak a szolgáltató
adatolvasó objekturnaihoz.

D at a Rea der D bDa t a Re a d er IDa t a Reader , Szerveroldali kurzor használa-


IDa t a Record tával az adatok egyirányú, írás-
védett hozzáférését biztosílja.

D at aAd apter DbD a taAd a pter IDa t aAdapter , Dataseteket szállít a hívó és
lObD ataAd a pter az adattároló között. Az adat-
illesztő egy kapcsolatobjek-
turnot és négy belső parancs-
objektumot tartalmaz a lekér-
dezés, beszúrás, rnódosítás és
törlés rnűveleteihez.

Para met er Db P a ra meter D ata P arameter , Egy névvel ellátott paramétert


IDbData- képvisel egy pararnéterezett
Parameter lekérdezésben.

Transaction DbTransacti on IDbTransacti on Beágyaz egy adatbázis-tran­


zakciót.

22. 1. táblázat: Egy ADO.NET adatszolgáltató alapvető objektumai

Noha az alapvető objektumok konkrét neve eltérhet az egyes adatszolgálta­


tók esetén (pl. az sqlconnecti on, az oracl econneti on, az od bcconnection és a
MySqlconnection viszonylatában), minden objektum ugyanabból az ősosz­

tályból származik (obconnecti on a kapcsolatobjektumok esetén), ez pedig


azonos interfészeket valósít meg. Ennek megfelelőerr biztosak leherunk ben­
ne, hogy ha egy adatszolgáltató kezelését ismerjük, már nem lesz nehéz más
adatszolgáltatókat használni.

Megjegyzés Az ADO.NET·ben, ha "kapcsolatobjektumról" beszélünk, akkor valóban egy konk·


rét DBConnectionből származtatott típusra hivatkozunk, és nincs olyan osztály, amelynek a ne·
" "
ve szó szerint "Connection lenne. Ugyanez érvényes a "parancsobjektumra , az "adatillesztő
"
objektumra és így tovább. A névadási konvencióknak megfelelően egy adott adatszolgáltató ob­
jektumainak a neve a kapcsolódó adatbáziskezelő nevével kezdődik (pl. sqlconnection,
oracleconnection, SqloataReader stb.).

103
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A 22.2. ábra bemutatja az ADO.NET-adatszolgáltatók hátterét. A képen a


"
"Kliens szerelvény bármilyen .NET-alkalmazás lehet: konzolalkalmazás,
Windows Forms-alkalmazás, ASP.NET-weboldal, WCF-szolgáltatás, .NET­
kódkönyvtár és így tovább.

Kapcsolat objektum Adatillesztő objektum

l Tranzakció l Lekérdező utasítás

Beszúró utasítás
Kapcsolat objektum

l Paraméter gyűjtemény
Módosító utasítás

l Adatolvasó objektum Törlő utasítás

22. 2. ábra: Az ADO.NET-adatszolgáltatók adott adatbáziskezelő hozzáJérését biztosítják

Természetesen egy adatszolgáltató az ábrán látható objektumokon kívül más


típusokat is tartalmaz. Ám ezek az objektumok egységes alapot képeznek az
összes adatszolgáltatóban.

A Microsoft által szállitott ADO.NET­


adatszolgáltatók

A Microsoft .NET-disztribúciója számos adatszolgáltatóval kerül a piacra,


amelyek között találunk szolgáltatót az Oracle felé, az SQL Server felé és az
OLE DB/ODBC összekapcsolhatóság felé is. A 22.2. táblázat a Microsoft
ADO.NET-adatszolgáltatóihoz tartozó névtereket és a befoglaló szerelvénye­
ket sorolja fel.

104
Az ADO.NET-adatszolgáltatók müködése

�.�'f'�::'.:
·�\'.,

OLE DB System.Data.OleDb System.Data.dll

Microsoft SQL system.Data.sqlclient system.Data.dll

Server

Microsoft SQL System.Data.Sqlserverce system.Data.Sqlserverce.dll


Server Mobile

ODBC system.Data.odbc system.Data.dll

Oracle system.Data.oracleclient system.Data.oracleclient.dll

22. 2. táblázat: A Microsoft ADO.NET-adatszolgáltatói

Megjegyzés Nincsen olyan adatszolgáltató, amelyet kifejezetten a Jet motorhoz lehetne le­
képezni (és ilyenformán a Microsoft Accesshez). Ha egy Access adatbázisfájt szeretnénk kezel­
ni, akkor ezt az OLE DB vagy az ODBC adatszolgáltatóin keresztül tudjuk megtenni.

Az OLE DB adatszolgáltatóján keresztül, amelyet a system.Data.oleDB névtér


típusai alkotnak, bármilyen adathoz hozzáférhetünk, amely a klasszikus
COM-alapú OLE DB protokollt támogató adattárban található. Ennek a szal­
gáltatának a használatával bármely OLE DB- vel kompatibilis adatbázissal
kommunikálhatunk, ha a kapcsolatsztring Provider szegmensét beállí�uk.
Az OLE DB szolgáltató számos COM-objektummal működik együtt a
színfalak mögött, és ez befolyásolha�a az alkalmazás teljesítményét. Általá­
ban elmondható, hogy csak akkor érdemes OLE DB adatszolgáltatót hasz­
nálni, ha olyan adatbázis-kezelővel szeretnénk dolgozni, amelyhez nincsen
specifikus .NET-adatszolgáltató. Minthogy napjainkban minden valamire va­
ló relációsadatbázis-kezelőnek kell rendelkeznie saját letölthető ADO.NET­
adatszolgáltatóval, így a system.Data.oleDB olyan korábbi névtér, amelyet
igen ritkán használunk a .NET 3.5 világában (és ez egyre inkább így van a
data provider factory modell megjelenésével, amely a .NET 2.0 megjelenése
óta áll a rendelkezésünkre).

Megjegyzés Egy olyan eset van, amikor a system.Data. oleD b névtér típusainak a használa­
tára van szükség: ha a Microsoft SQL Server 6.5 vagy valamelyik régebbi verziójával kell dol­
goznunk. A system.Data.Sqlclient névtér ugyanis csak a Microsoft SQL Server 7.0 vagy en­
nél magasabb verziójával tud kommunikálni.

105
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A Microsoft SQL Server adatszolgáltató közvetlen elérést biztosít a Microsoft


SQL Server adattárakhoz, és csak az SQL Server adattárakhoz (7.0 vagy ké­
sőbbi verziókhoz). A system.Data.Sqlclient névtér az SQL Server adatszal­
gáitató által használt típusokat tartalmazza, és ugyanazt az alapfunkcionali­
tást biztosítja, mint az OLE DB szolgáltató. A legfontosabb különbség az,
hogy az SQL Server szolgáltató kikerüli az OLE DB réteget, ilyenformán
számos teljesítménybeli előnyre tesz szert. A Microsoft SQL Server adatszal­
gáitató természetesen lehetőséget biztosít olyan egyedi tulajdonságok eléré­
séhez, amelyeket ez az adatbázis-kezelő nyújt.
A többi Microsoft által szállított szolgáltató (system.Data.oracleclient,
system.Data.odbc és system.Data.sqlclientce) elérési lehetőséget nyújt az
Oracle-adatbázisokhoz, biztosítják az ODBC-kapcsolatokkal történő kom­
munikációt, és elérhetővé teszik a SQL Server Mobile DBMS kiadását (általá­
ban kézieszközöknél használatos, mint pl. a Pocket PC). A system. Data.odbc
névtéren belül definiált ODBC-típusok jellemzően csak olyan esetben hasz­
nosak, ha olyan relációs adatbázissal kell kommunikálnunk, amelyhez nem
definiáltak egyedi .NET-adatszolgáltatót (hiszen az ODBC egy általánosan el­
terjedt modell, amely számos adattárolóhoz szalgáltat hozzáférést).

Harmadik féltől származó ADO.NET-adatszolgáltató


beszerzése

A Microsoft által kínált adatszolgáltatókon kívül számos harmadik féltől


származó adatszolgáltató létezik számos nyílt forráskódú és piaci adatbázis­
kezelőhöz. Noha valószínűleg közvetlenül az adatbáziskezelőt szállító cégtől
fogjuk beszerezni az ADO.NET-adatszolgáltatót, érdemes megnézni a követ­
kező oldalt is (az URL változtatásának jogát a tulajdonos fenntartja): http:/ j
www.sqlsummit.com/ DataProv.htm.
Ez a webhely egy azok közül a webhelyek közül, amelyek minden ismert
ADO.NET-adatszolgáltatót dokumentálnak, valamint hivatkozásokat biztosí­
tanak további információkhoz és letöltésekhez. Számos ADO.NET-adatszol­
gáltató megtalálható itt, többek között az SQLLite, a DB2, a MySQL, a Post­
greSEL és a Sybase.
Mivel nagyon sok ADO.NET-adatszolgáltató létezik, a fejezetben található
mintapéldák a Microsoft SQL Server adatszolgáltatót (system. Data.sq 1-
client.dll ) használják.

106
További ADO.NET-névterek

Ez az adatszolgáltató a Microsoft SQL Server 7.0-val és a későbbi verziók­


kal - az SQL Server 2005 Express Editiont is beleértve - folytatott komrnuni­
kációt biztosítja. Ha az ADO.NET-et másmilyen adatbázis-kezelővel szeret­
nénk használni, akkor sem lesz ezzel problémánk a következő tudnivalók
ismeretében.

További ADO.NET-névterek

A .NET-névtereken kívül, amelyek az egyes adatszolgáltatók típusait defini­


álják a .NET alaposztálykönyvtárai számos további ADO.NET-tel foglalkozó
névteret kínálnak, amelyekből néhányat a 22.3. táblázatban láthatunk.

!f;>�.�. -.�·;-"•"-; ·'-·"''(''j,;JAJtf.i.."r::'#.d-.,.',;.;/J:'A';;_j:,l$_·;,, .;;· ··:�B.i c;R�.;,: ., ;..'-


Microsoft.Sqlserver. Ez a névtér olyan típusokat szolgáltat, amelyek segítik
Server a CLR és az SQL Server 2005 integrációs szolgáltatásait.

System.Data Ez a névtér definiálja az ADO.NET-nek azokat az alap­


vető típusait, amelyeket minden adatszolgáltató hasz­
nál, beleértve a közös interfészeket és számos típust,
amelyek a kapcsolat nélküli madelit képviselik (Data­
Set, DataTab l e stb.).

system. Data. common Ez a névtér azokat a típusokat tartalmazza, amelyeket


minden ADO.NET-adatszolgáltató használ, beleértve a
közös absztrakt ősosztályokat.

System.Data.sql Ez a névtér lehetövé teszi, hogy feltérképezzük az ak­


tuális helyi hálózaton megtalálható Microsoft SQL Ser­
ver példányokat.

System.Data.SqlTypes Ez a névtér a Microsoft SQL Server által használt natív


adattípusokat tartalmazza. Noha bármikor nyugodtan
használhatjuk a megfelelő CLR-adattípusokat, az
SqlTypes típusai kimondottan az SQL Serverrel mű­
ködnek optimálisan.

22. 3. táblázat: További ADO.NET-tel foglalkozó névterek

107
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A System.Data névtér tipusai

Az ADO.NET-névterek között a system. Data a legkisebb közös nevező. Nem is


tudunk ADONET-alkalmazást készíteni anélkül, hogy ne hivatkoznánk erre a
névtérre. Ez a névtér olyan típusokat tartalmaz, amelyet minden ADO.NET­
adatszolgáltató használ, függetlenül a mögöttes adattárolótóL A számos adatbá­
zissal kapcsolatos kivételen kívül (NoNull AllowedException, RowNotinTab l eExcep­
tion, MissingPrimaryKeyException és hasonlók) a system. Data olyan típusokat is

tartalmaz, amelyek különböző adatbázis-primitíveket képviselnek (táblák, sorok,


oszlopok, megszorítások stb.), valamint ugyancsak tartalmaz az adatszolgáltató
objektumok által megvalósított közös interfészeket A 22.4. táblázat néhány
olyan alapvető típust sorol fel, amelyeket ismernünk kell.

������..��;�"":�-,��j-:f';�.� '!i��-:.,..��1t""'·�•'\·�-if;.i.·��)-,; .Y����"l.�tt'. 1 ,-;�t'""..��.•r�.r:.<".,t�\;;.*'


1�' ..i::-.�-:��� :·.../i��- "'J,....: .�::·· � ..:
'�· ... ·:.:�· --·� .1',� ':-""i" .··•.
,. .

Constraint Az adott Dataco l um n objektum megszorítását képviseli.

Datacol umn A DataTab l e objektumon belül egy oszlopot képvisel.

DataRel ation Két DataTab l e objektum közötti szülő j gyermek viszonyt


képviseli.

DataRow A DataTab l e objektumon belül egy sort képvisel.

DataSet Az adatok memóriabeli gyorsítótárát képviseli, amelyben bár­


mennyi egymással összefüggő DataTable objektum található.

DataTab l e A memóriában található adatok táblázatos formáját képviseli.

DataTab l eReader Lehetővé teszi, hogy a DataTab le objektumot egy tömlőkur­


zorként kezeljük (egyirányú, írásvédett adathozzáférés).

Dataview A DataTab l e objektum egy testre szabott nézetét képviseli


rendezés, szűrés, keresés, módosítás és navigálás céljára.

IDataAdapter Az adatillesztő objektum alapviselkedését definiálja.

IDataParameter A paraméterobjektum alapviselkedését definiálja.

IDataReader Az adatolvasó objektum alapműködését definiálja.

robcommand A parancsobjektum alapműködését definiálja.

robDataAdapter Az IDataAdapter interfészt bővíti ki, hogy az adatillesztő


objektumot további funkcionalitásokkal lássa el.

robTransaction A tranzakcióobjektum alapműködését definiálja.

22.4. táblázat: A System. Data névtér alapvető tagjai

108
A System. Data névtér típusai

A system. Data névtéren belül akkor használjuk a legtöbb osztályt, amikor az


ADO.NET kapcsolat nélküli modelljével dolgozunk. A következő fejezetben
megismerkedünk a Datasetnek és társainak (DataTable, DataRelation, DataRow
stb.) részleteivel, valamint azzal, hogy segítségükkel (és a kapcsolódó adatil­
lesztőkkel) hogyan lehet megjeleníteni és kezelni a távoli adatok kliensoldali
másolatát.
Most azonban magasabb szintről megvizsgáljuk a system. Data névtér
alapvető interfészeit, és azt, hogy milyen közös funkcionalitást nyújtanak ál­
talában az adatszolgáltatók.

Az lObConnection interfész szerepe

Az robconnection típust az adatszolgáltató kapcsolatobjektuma valósí�a meg. Az

interfész egy megadott adattároló kapcsolatának konfigurálására szolgáló tagok


halmazát definiálja, és lehetövé teszi, hogy hozzáférjünk az adatszolgáltató
tranzakcióobjektumához. Az IDbeanneteion formális definíciója a következő:

public interface robconnection : IDisposable


{
string connectionstring { get; set; }
int connectionTimeout { get; }
string Database { get; }
connectionState State { get; }
robTransaction BeginTransaction();
robTransaction BeginTransaction(IsolationLevel il);
void changeDatabase(string databaseName);
void close();
robcommand createcommand();
void Open();
}

Megjegyzés Ahogy a .NET alaposztálykönyvtáraiban szereplő számos más típus, a close()


metódus meghívása megegyezik funkcionálisan a Dis p o se() metódus direkt vagy indirekt hí­
vásával a C# u s i ng blokkjában . (lásd az előző kötet 8. fejezetét).

Az lObTransaction interfész szerepe

Az IDbConnection által definiált, túlterhelt BeginTransaction() metódus hoz­


záférést biztosít a szolgáltató tranzakcióobjektumához. Az IDbTransaction tagjai­
nak használatával lehetőségünk van programozottan kezelni a tranzakciós
munkamenetet és a mögöttes adattárolót

109
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

public interface robTransaction : IDisposable


{
robconnection connection { get; }
IsolatienLevel IsolatienLevel { get; }
void commit();
void Ro ll back();
}

Az lObCommand interfész szerepe

Az IDbeammand interfészt az adatszolgáltató parancsobjektuma implementálja.


Más adatelérési objektummodellhez hasonlóan a parancsobjektumok SQL­
utasítások, tárolt eljárások és paraméterezett lekérdezések programozott ke­
zelését teszik lehetővé. Továbbá a parancsobjektumok a túlterhelt Execute­
Reader() metódus segítségével az adatszolgáltató adatolvasó típusához biz­

tosítanak hozzáférést.

public interface robcommand : roisposable


{
string CommandText { get; set; }
int commandTimeout { get; set; }
commandType commandType { get; set; }
robconnection connection { get; set; }
IDataParametercollection Parameters { get; }
robTransaction Transaction { get; set; }
UpdateRowsource updatedRowsource { get; set; }
void Cancel();
robDataParameter CreateParameter();
int ExecuteNonQuery();
IDataReader ExecuteReader();
IDataReader ExecuteReader(commandBehavior behavior);
object Executescalar();
voi d Prepare();
}

Az lObDataParameter és az IDataParameter
interfészek szerepe

Az lObCommand Parameter tulajdonsága egy olyan erősen típusos gyűjteményt


ad vissza, amely az IDataParametercollection interfészt valósítja meg. Ez az
interfész hozzáférést biztosít lObD ataParameter kompatibilis osztálytípusok
-

halmazához (pl. paraméterobjektumokhoz):

110
A System. Data névtér típusai

public interface robDataParameter : IDataParameter


{
byte Precision { get; set; }
byte Scale { get; set; }
int Size { get; set; }
}

Az robDataParameter interfész az IDataParameter interfészt bővíti ki, és a to­


vábbi viselkedéseket biztosítja:

public interface IDataParameter


{
DbType DbType { get; set; }
ParameterDirection Direction {get; set; }
bool IsNullable { get; }
string ParameterName { get; set; }
string sourcecolumn { get; set; }
DataRowversion sourceversion { get; set; }
object value { get; set; }
}

Az robDataParameter és az roataParameter interfészek lehetövé teszik, hogy


az SQL-parancsokon belül (ide értve a tárolt eljárásokat is) paramétereket ál­
lítsunk be a specifikus ADO.NET- paraméterobjektumokon keresztül, ahe­
lyett, hogy kódolt sztringliterálokat használnánk

Az lObDataAdapter és az IDataAdapter
interfészek szerepe

Az adatilleszták segítségével Dataseteket vehetünk ki egy adattárolóból, vagy


oda vissza is írhatjuk őket. Az robDataAdapter interfész olyan tulajdonságok
halmazát definiálja, amelyek SQL-utasítások kezelésére használatosak a hoz­
zájuk kapcsolódó lekérdezési, beszúrási, módosítási és törlési müveletekhez:

public interface robDataAdapter : IDataAdapter


{
robcommand Deletecommand { get; } set;
robcommand Insertcommand {get; } set;
robcommand selectcommand {get; set; }
robcommand updatecommand { get; set; }
}

111
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

E mellé a négy tulajdonság mellé egy ADO.NET-adatillesztő az IDataAdapter


alapinterfész által definiált viselkedéseket is megvalósítja. Ez az interfész de­
finiálja egy adatillesztő típus kulcsfontosságú funkcióit: a Fill O és az upda­
te() metódúsokkal Dataset objektumokat továbbít a hívó és a mögöttes adat­
tároló között. Továbbá az IDataAdapter interfész lehetővé teszi, hogy a
TableMappings tulajdonságon keresztül adatbázisoszlopok neveihez sokkal
inkább felhasználóbarát megjelenítési nevet rendeljünk:

public interface IDataAdapter


{
MissingMappingAction MissingMappingAction { get; set; }
MissingSchemaAction MissingschemaAction { get; set; }
ITableMappingcollection TableMappings { get; }
int Fill(system.Data.DataSet dataSet);
DataTable[] Fillschema(Dataset dataset, schernaType schemaType);
IDataParameter[] GetFillParameters();
int update(DataSet dataset);
}

Az IDataReader és az IDataRecord
interfészek szerepe

A következő fontos interfész, az IDataReader egy adott adatolvasó objektum


által támogatott általános működést képvisel. Ha egy ADO.NET-adatszolgál­
tatótól lekérünk egy IDataReader-kompatibilis típust, az eredményhalmazon
egyirányúan és írásvédetten iterálhatunk végig.

public interface IDataReader : IDisposable, IDataRecord


{
int Depth { get; }
bool IsClosed { get; }
int RecordsAffected { get; }
void close();
DataTable GetschemaTable();
bool NextResult();
bool Read();
}

Végül: az roataReader kibővíti az roataRecord interfészt, amely számos olyan


tagot definiál, amely lehetővé teszi egy folyamból az erősen típusos értékek ki­
emelését, ahelyett, hogy a generikus system.obj ect típust kasztolná, ez utóbbit
az adatolvasó túlterhelt indexelő metódusából kapná meg. A különböző
Getxxx() metódusok részleges listája, amelyet az roataRecord definiál, a követ­
kező (a teljes listát a .NET Framework 3.5 SDK dokumentáció tartalmazza):

112
Adatszolgáltatók absztrahálása interfészekkel

public interface IDataRecord


{
int Fieldcount { get; }
object this[ string name J { get; }
object this[ int i J { get; }
bool GetBoolean(int i);
byte GetByte(int i);
char Getchar(int i);
DateTime GetDateTime(int i);
Decimal GetDecimal(int i);
float GetFloat(int i);
short Getint16(int i);
int Getrnt32(int i);
long Getrnt64(int i);

bool IsDBNull(int i);


}

Megjegyzés Az IDataReader. IsDBNull () metódus lehetőséget biztosít arra, hogy progra·


mozottan feltérképezzük, hogy egy adott mező null értékkel rendelkezik-e anélkül, hogy az
értékét kiolvasnánk az adatolvasóból (a futásidejű kivételek elkerüléséért). A C# támogatja a
nullázható adattípusokat (lásd az előző kötet 4. fejezetét), és ezekkel a típusokkal olyan adat­
oszlopokkal lehet ideálisan dolgozni, amelyek üresek is lehetnek.

Adatszolgáltatók absztrahálása
interfészekkel

A fentiekben láttuk a .NET-adatszolgáltatók közös funkcióit. Noha a külön­


böző adatszolgáltatóknál az implementált típusok pontos neve eltérő lesz,
ezeket a típusokat mégis hasonlóképpen kell programozill - ez az interfész­
alapú polimorfizmus szépsége. Ha például definiálunk egy olyan metódust,
amely egy robconnection paramétert kap, bármilyen ADO.NET-kapcsolat­
objektumot átacihatunk

public static void Openconnection(IDbConnection cn)


{
ll A bejövő kapcsolat megnyitása a hívó számára.
cn.open();
}

Megjegyzés Az interfészek nem szigorúan kötelezőek, ugyanez a végeredmény elérhető abszt­


rakt ősosztályok paraméterként vagy visszatérési értékként történő használatával is (pl. a obton­
necti on ne l).

113
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Ugyanez igaz a tagok visszatérési értékeire is. Nézzük meg például a követ­
kező egyszerű Cit-konzolalkalmazást (MyconnectionFactory névvel), amely
egyedi felsorolt típus alapján lehetővé teszi egy adott kapcsolatobjektum
megszerzését. Diagnosztikai célokból egyszerűen csak kiírjuk az alatta talál­
ható kapcsolatobjektumot a reflexiós szolgáltatás segítségéve!:

ll Erre szükségünk van, hogy a közös interfészek definícióit


ll és néhány kapcsolatobjektumot megkapjunk a tesztünkhöz.
using System;
using System.Data;
using System.Data.Sqlclient;
using system.Data.odbc;
using System.Data.Oleob;

ll szükség van a system.Data.oracleclient.dll-re történő


ll referenciára ennek a névtérnek a használatához.
using system.Data.oracleclient;

namespace MyConnectionFactory
{
ll A lehetséges szolgáltatók listája.
enum DataProvider
{ sqlserver, oleDb, odbc, oracle, None }

class Program
{
static void Main(string[] args)
{
console.writeLine("*** very simple Connection Factory ***\n");

ll Egy adott kapcsolat beolvasása.


robconnection mycn = Getconnection(DataProvider.sqlserver);
console.writeLine("Your connection is a {O}",
mycn.GetType().Name);

ll A kapcsolat megnyitása, használata és bezárása ...


Console.ReadLine();
}

ll Ez a metódus tér vissza egy adott kapcsolatobjektummal


ll a DataProvider értéke alapján.
static robconnection Getconnection(DataProvider dp)
{
robconnection conn = null;
switch (dp)
{
case DataProvider.SqlServer:
conn = new Sqlconnection();
break;

114
Adatszolgáltatók absztrahálása interfészekkel

case DataProvider.OleDb:
conn = new oleDbConnection();
break;
case DataProvider.odbc:
conn = new odbcconnection();
break;
case DataProvider.oracle:
conn = new oracleconnection();
break;
}
return conn;
}
}
}

A system.Data általános interfészeivel (vagy akár ugyarúgy a system. Data.common


absztrakt ősosztályaival) nagyobb esélyünk van arra, hogy olyan rugalmas kó­
dot írjunk, amely idővel továbbfejleszthető. Lehet, hogy például most egy olyan
alkalmazást írunk, amely a Microsoft SQL Serverrel dolgozik, de mi van akkor,
ha pár hónap múlva át kell térni Oracle-re? Ha olyan alkalmazást írtunk, amely
bedrótozta az MS SQL Serv er specifikus system.Data. sqlclient típusokat, nyil­
vánvalóan a szerelvény módosítására, újrafordítására és újratelepítésére van
szükség, ha a háttérben futó adatbáziskezelő rendszer változik.

Rugalmasság növelése az alkalmazáskonfigurációs


fájlokkal

Hogy tovább növelhessük az ADO.NET-alkalmazásaink rugalmasságát, fel­


használhatunk egy kliensoldali *.config fájlt, amely az <appsettings> elem
kulcs/ érték párjait használja fel. A *.config fájlban tárolt egyedi adatok prog­
ramozottan kinyerhetőek a system.configuration névtér típusainak a segítsé­
gével (lásd az előző kötet 15. fejezetét). Tételezzük fel például, hogy egy konfi­
gurációs fájlban a következőképpen adtuk meg az adatszolgáltató értékét:

<configuration>
<appSettings>
<!-- Ez a kulcsérték a felsorolt típusunk valamely értékével
egyezik meg -->

<add key="provider" value="SqlServer"/>


</appSettings>
</configuration>

115
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Ezek alapján úgy módosíthatjuk a Main O függvényt, hogy programozottan sze­


rezze meg a mögöttes adatszolgáltatót. Ekkor lényegében egy kapcsolatobjektum­
factoryt készítettünk, amely lehetövé teszi, hogy úgy változtassuk meg a szolgál­
tatót, hogy ne kelljen hozzá újrafordítani a kódot (egyszerűen a *.config fájlt kell
módosítani). Az alábbiakban a Main O releváns módosításait látjuk:

static void Main(string[] args)


{
console.writeLine("**** very Simple Connection Factory *****\n");

ll olvassuk be az adatszolgáltató kulcsot.


string dataProvstring =

ConfigurationManager.Appsettings["provider"];

ll Alakítsuk át a sztringértéket felsorolássá.


DataProvider dp = oataProvider.None;
if(Enum.IsDefined(typeof(DataProvider), dataProvstring))
dp (DataProvider)Enum.Parse(typeof(DataProvider),
dataProvString);
else
console.writeLine("sorry, no provider exists!");

ll Egy adott kapcsolat beolvasása.


robconnection mycn Getconnection(dp);
=

if(mycn != null)
console.writeLine("Your connection is a {O}",
mycn.GetType().Name);

ll Nyissuk meg, használjuk és zárjuk a kapcsolatot...

console.ReadLine();
}

Megjegyzes Ahhoz, hogy a Configurati onManager típust használjuk, állítsunk referenciát a


system.configurati on. dll szerelvényre, és importáljuk a system.configurati on névteret.

Az eddig létrehozott ADO.NET-kód lehetövé teszi, hogy dinamikusarr hatá­


rozzuk meg a mögöttes kapcsolatot. Az egyetlen nyilvánvaló probléma, hogy
ezt az absztrakciót csak a MyConnectionFactory.exe alkalmazásban használ­
hatjuk Ha ezt a példaprogramot egy .NET-kódkönyvtárban készítettük vol­
na el (pl. a MyconnectionFactory.dll-ben) , akkor lehetőségünk lenne tetszőle­
ges számú klienst létrehozni, amely különböző kapcsolatobjektumokat hasz­
nálhatna az absztrakciós rétegek segítségéveL

116
AzAutoLot adatbázis létrehozása

A kapcsolatobjektum megszerzése az ADO.NET használatának csak egyik


lényeges szempontja. Ahhoz, hogy valamirevaló data provider factory kód­
könyvtárat hozzunk létre, a parancsobjektumokat, az adatolvasókat, az adat­
illesztőket, a tranzakcióobjektumokat és az egyéb adatközpontú típusokat
hasonlóan kell kezelni. Noha egy ilyen kódkönyvárat nem feltétlenül nehéz
elkészíteni, de nagy mennyiségű kódot és sok időt igényeine.
A .NET 2.0 verziójától kezdve ez a funkcionalitás közvetlenül beépült a
.NET-alaposztálykönyvtáraiba. Mielőtt megvizsgálnánk ezt a formális API-t,
létre kell hoznunk egy olyan egyedi adatbázist, amelyet a későbbiekben is
használni fogunk.

Forráskód A MyConnectionFactory kódfájlokat a forráskódkönyvtárban a 22. fejezet alkönyv·


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Az Autalot adatbázis létrehozása

Ebben a fejezetben mindig egy egyszerű, AutaLot nevű SQL Server teszt­
adatbázison hajtunk végre lekérdezéseket Kapcsolódva az eddigi autós té­
mához, ez az adatbázis három egymással kapcsolatban álló táblát tartalmaz
(lnventory - raktár, Orders - megrendelések és Customers - vásárlók). Ezek
a táblák különböző jellegű adatokat tartalmaznak, amelyekben egy kitalált
autókereskedés megrendelési információi találhatók.
Tételezzük fel, hogy van egy Microsoft SQL Server (7.0 vagy magasabb
verziójú) vagy egy Microsoft SQL Server 2005 Express Edition adatbáziske­
zelő szaftverünk (http://msdn.microsoft.comjvstudiojexpress/sql). Ez az
adatbázisszerVer tökéletesen megfelel a céljainkra, hiszen egyrészt ingyenes,
másrészt olyan grafikus felületet biztosít (az SQL Server Management Tool
eszközt), amelynek segitségével létrehozhatjuk és felügyelhetjük az adatbázi­
sainkat, és harmadrészt a Visual Studio 2008/Visual C# Express Edition vál­
tozattal integrálható.
Ez utóbbi előnyének illusztrálására megnézzük, hogy hogyan kell létre­
hozni az AutaLot adatbázist a Visual Studio 2008 segítségéveL Ha Visual C#
Express verziónk van, hasonló műveleteket hajthatunk rajta végre, mint ame­
lyeket itt részletezünk a Database Explorer ablak segítségével (amelyet a
View> Other Windows menüpont használatával lehet betöltetni).

MegjegyzésAzAutoLot adatbázist a könyv további részeiben is használni fogjuk.

117
ZZ. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Az lnventory tábla létrehozása

Hogy létrehozhassuk a tesztadatbázisunkat, indítsuk el a Visual Studio 2008-


at, és nyissuk meg a Server Explorer nézetet az integrált fejlesztői környezet
View menüpon�ából. Kattintsunk a jobb egérgombbal a Data Connections
csomópontra, és válasszuk ki a Create New SQL Server Database menüpon­
tot. Az eredményként megjelenő párbeszédablakban kapcsolódjunk a helyi
gépünkön található SQL Server példányhoz, és adjuk meg az AutoLot kifeje­
zést az adatbázis neveként (a Windows Authentication megfelelő lesz- lásd
a 22.3 ábrát).

Ent<r information to connect to a SQL Servor, then specify the


name of a database to create.

Setver name::

JNTERUBER\SQLEXPRESS

log on to the server··

@ Use Windows Authentication


<O Use SQL Setver Authentication

User name:

- P:aH'tf/Ord:
O Save my password

New database name:

Autolot

OK lL Cancel

22. 3. ábra: Egy új SQL ServerExpress adatbázis létrehozása Visual Studio 2008 használatával

Megjegyzés Ahelyett, hogy konkrétan megadnánk a számítógépünk nevét (mint az INTERUBER


a 22. 3. ábrán), a Server name szövegdobozba egyszerűen beírhatjuk, hogy (local)\SQLEXPRESS.

Ekkor az AutoLot adatbázis egyetlen adatbázisobjektumot (táblát, tárolt eljá­


rásokat stb.) sem tartalmaz. Az Inventory tábla beszúrásához egyszerűen kat­
tintsunk jobb egérgombbal a Tables csomópontra, és válasszuk ki az Add
New Table parancsot (lásd a 22.4. ábrát).
A táblázatszerkesztővel hozzunk létre négy oszlopot (Carill, Make, Color,
PetName). Bizonyosodjunk meg róla, hogy a Carill oszlopot elsődleges kulcs­
ként állítottuk be. Ehhez jobb gombbal kattintsunk a Carill sorra, és válasszuk
a Set Ptimary Key opciót. A 22.5. ábra muta�a a végső táblabeállításokat

118
Az Autalot adatbázis létrehozása

0�it\11
l�l- (J] Data Connectíons
8- _iJ, ínteruber\sqlexpress.Autolot.dbo
ffi Ciii Database Díagrams
ffi-
Ciii!![
-

th-- Ciii Ví Add Ne. Table

NewQuery

Relresh

�ÍJ-- Cil Typ.j G


!±J···

!
Properties
j
' ttl-- Cil Assembhes
B �Servers
lil- 8 InterUber

4!1 Server Explorer j�Toolbox

22.4. ábra: Az Inventory tábla hozzáadása

/dbo.lnventory: TL.Jexpress.Autolot)t1iiijijiijjil � X

r.�r�·--Column Name •.- . .-•-· .. �--uData Type ··--- ti-


l Allow Nulls

Make nchar(lO) ILl


Color nchar(lO) ILl
PetName nchar(lO) �
El

Column Properties l
�U lS
IGenenD
-
- -

8
(Name) CarlO o
Allow Nulls No
DataType int
Default Value or Sinding

l
T

GenenD
I
l
22. 5. ábra: Az Inventory tábla teroezése

Mentsük el (majd zárjuk be) az új táblánkat, és bizonyosodjunk meg róla,


hogy az új adatbázis-objektumunknak az Inventory nevet adtuk. Ekkor lát­
nunk kell az Inventory táblát a Tables csomópont alatt a Server Explorerben.
Kattintsunk jobb gombbal az Inventory tábla ikonjára, és válasszuk ki a Show
Table Data opciót. Vigyünk be vagy egytucatnyi tetszőleges autó adatait (hogy
érdekesebb legyen a dolog, legyenek azonos színű és gyártmányú autóink).
A 22.6. ábra egy lehetséges raktárkészletlistát mutat.

119
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

/lnventory: Que<y(.Jexpress.Autolot) ;--1 ... x

CarlO Make Color PetName

i .. �j 1 BMW Gn!en Sidd l


vw Red Zippy

Ford Black Mel

BMW Silver Henry

Yugo Pink Sa ily

6 Saab Blue Sven

7 BMW Black Bimmer

8 vw Tan Sal
... . .

* NULL NULL NULL NULL

·� � ll of8 l � �l �;o l® l
22. 6. ábra: Az Invrntory tábla feltöltése

A GetPetName() tárolt eljárás létrehozása

A későbbiekben megvizsgáljuk, hogyan használható az ADO.NET tárolt eljá­


rások hívására. A tárolt eljárások olyan rutinok, amelyeket egy adott adatbá­
zis tárol, és legtöbbször táblákon hajtanak végre műveleteket azért, hogy va­
lamilyen visszatérési értéket állítsanak elő. Egy egyszerű tárolt eljárást ho­
zunk létre, amely a megadott CariD érték alapján egy autó nevével fog visz­
szatérni. Kattintsunk jobb egérgombbal az AutaLot adatbázis Stared Proce­
dures csomópon�ára a Server Explorer ablakban, és válasszuk ki az Add
New Stared Procedure opciót. A megjelenő szerkesztőablakba írjuk be a kö­
vetkező kódot:

CREATE PROCEDURE GetPetName


@carm i n t ,

@petName char(lO) output


AS
SELECT @petName = PetName from Inventory where CariD = @cariD

Amikor men�ük az eljárásunkat, automatikusan a GetPetName nevet kapja a


CREATE PROCEDURE utasítás alapjárt. Ha készen vagyunk, az új tárolt eljárást a
Server Explorer ablakban lá�uk (lásd a 22.7. ábrát).

Megjegyzés A tárolt eljárásoknak nem kell kimeneti paraméterrel visszatérniük (mint ahogy
itt látható). A fejezet "Tárolt eljárások futtatása" című részében lesz stó az Sql Parameter
Di recti on tulajdonságának vizsgálatáróL

120
Az AutoLot adatbázis létrehozása

02:11�·
;J-� [j) Data Connections
á- ll interuber\sql.,.press.Autolot.dbo
ffi··· � Database Diagrams
8 �Tables
tB·· ff!l lnventory
IH· �Views
8-·�
MM•++
. 8 CJ GetPetName
·

! @il @carlO
··

L � @petName
..

i±l·· Cil Functions


ffi Cil
· Synonyms
0·� Types
� Assemblies

l:
..

8-- "!!!Servers
. ffi
tB· SInterUber

� S.rver Explorer�Toolbox
22.7. ábra: A GetPetName tárolt eljárás

A Customers és az Orders táblák létrehozása

A tesztadatbázisunk két további táblával gyarapodik. A Customers tábla


(ahogy azt a neve is muta�a) a vásárlók listáját fogja tartalmazni, amelyet há­
rom oszlop képvisel (CustiD [amelyet elsődleges kulcsként kell majd beállí­
tani], FirstName és LastName). Az Inventory tábla létrehozásakor végrehaj­
tott lépésekkel a következő séma alapján hozzuk létre a Customers táblát
(lásd a 22.8. ábrát).

/dbo.CustCJ�J�en: Tólo...lexpress.Autolot)l ; X

[..
�c...uD-
Column Name DataType Allow Nulis
· - - ----- -- - --- - ----- - - - - - - -- - - --- ------- - ---

int ro
FirstName nchar(lO) fO
LastName nchar(lO) fO
El

Column Properties
'------,

•f·Jame1 Cust!D
AllowNulls No

[(Nome)
22.8. ábra: A Customers tábla tervezése

Miután elmentettünk, töltsük fel a táblát vásárlói rekordokkal (lásd a 22.9.


ábrát).

121
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

/Wstomers: Query(..Jexpresls.Auto ot) ... X

C ustiO FirsiNanle . �Name.

i lloBil o- lln!nner
l
Matt Wa�on

Pat Wa�on

Jim White

* NULL NULL NULL

22. 9. ábra: A Customers táblafeltöltése

Utolsó táblánk, az OrdE1�' ai.6l(�taz autókat tartalmazza, amelyeket az adott


vásárlók szeremének �egvennÍ: Ehhez az OrdedD értékeket Car IDj CustiD
értékekre kell leképezni. A 22.10. �bra>muta�a be a tábla végső struktúráját
::
(az OrdedD lesz tehát az elsődleg���lts)
: . · ·
·
. . -
.

/dbo.Onlers: T�ess.AutolotlYjijifijjl ... X

Column Name Data Type Allow Nulls


f---"1------------------------------------------

� OrdedD int EJ
Cust!D int
: Z?�� :
· -- -.
.
·IEJ
CarlO
-

int .
:\;">·· • ·lLl
El

Column Properties
'-------,

OrderiD
No

22.10. ábra: Az Orders tábla tervezése

Töltsük fel adatokkal az Orders táblát. Tegyük fel, hogy az OrdedD értéke
1000-rel kezdődik. Válasszunk egy egyedi CadD értéket minden egyes
CustiD értékhez (lásd a 22.11. ábrát).

/Onlers: Quety{my..Jexpress.Autolot)j :;:x

()rdedD.. Cust!D ···--


CarlO

IfillliDO 'l 2
l
1001

1003 7

1010 8

* NULL NULL NULL

·� � ll of4 l � �·�"l® l
22.11. ábra: Az Orders tábla feltöltése

122
Az AutaLot adatbázis létrehozása

Ha a könyvbeli adatokat vittük be, akkor láthatjuk, hogy Dave Banner (Cust­
ID = l) a piros Volkswagent szeretné megvenni (CariD = 2) , míg Pat Walton
(CustiD =3) a rozsdabarna Volkswagenre (CariD = 8) vetett szemet.

Táblakapcsolatok vizuális bemutatása

Az utolsó feladatunk az, hogy a szülő/ gyermek kapcsolatokat beállítsuk a


Customers, Orders és az Inventory táblák között. Ez a Visual Studio 2008 se­
gítségéve! egyszerű, hiszen ha a Server Explorerben jobb egérgombbal kattin­
tunk az AutaLot adatbázis Database Diagrams csomópontjára, egy új adat­
bázis-diagram létrehozását választhatjuk ki. Mielőtt az Add gombot meg­
nyomnánk, nézzük meg, hogy minden táblát kiválasztottunk-e a megjelenő
párbeszédablakban.
Ahhoz, hogy a kapcsolatokat felállítsuk a táblák között, először kattint­
sunk az Inventory tábla CariD kulcsára (mialatt lenyomva tartjuk az egér
gombját), majd húzzuk oda az Orders tábla CariD mezőjéhez. Amikor elen­
gedjük az egér gombját, fogadjunk el minden alapbeállítást az eredményként
megjelenő párbeszédablakokban.
Ugyanennek a műveletsomak a végrehajtásával a Customers tábla CustiD
kulcsát rendeljük az Orders tábla CustiD mezőjéhez.

/dbo.Autolot oa,..Jexpreu.Autolot)rjijiijjjjjirjl � x

:::J
Orders Customers
'll OrderiD _'t CustlD
FK Orders_Custorners
CustiD FirstName

CarlO LastName

FK_Orders_Inventory

Inventorv
.! Car!D

Make

Color

PetName

.iJ �
22. 12. ábra: Az összekapcsolt Orders, Inventory és Customers táblák

123
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Ha végeztünk, akkor a 22.12. ábrán látható osztály-párbeszédablakot kapjuk (a


táblák közti kapcsolatok megjelenítésének engedélyezéséhez a tervezőben kat­
tintsunk jobb egérgombbal, és válasszuk a Show Relationship Labels opciót).
Ezzel elkészítettük az AutoLot adatbázist. Noha ez elég messze áll egy valós
életben is használatos adatbázistól, számunkra a továbbiakban megfelelő lesz.

Az ADO.NET data provider factory


modell

A .NET data provider factory minta egyszerű kódbázis létrehozását teszi le­
hetövé általánosított adatelérési típusok alkalmazásával. Továbbá alkalma­
záskonfigurációs fájlok (és a <connect ienstrings> elem) használatával dekla­
ratív módon kaphatunk szolgáltatókat és kapcsolatsztringeket anélkül, hogy
újra kellene fordítani és telepíteni a szerelvényt.
A data provider factory implementációjának megértéséhez emlékezzünk
vissza a 22.1. táblában foglaltakra: egy adatszolgáltatóban az objektumok
ugyanabból az ősosztályból származnak, amelyet a system. Data. common név­
tér definiál:

• DbCommand: absztrakt ősosztály minden parancsobjektum számára.

• Dbconnection : absztrakt ősosztály minden kapcsolatobjektum számára.

• DbDataAdapter: absztrakt ősosztály minden adatillesztő objektum szá­

mára.

• DbDataReader: absztrakt ősosztály minden adatolvasó objektum szá­

mára.

• DbParameter: absztrakt ősosztály minden paraméterobjektum számára.

• DbTransact i on: absztrakt ősosztály minden tranzakcióobjektum szá­

mára.

Ezenkívül minden egyes Microsoft által szállított adatszolgáltató tartalmaz


egy olyan osztálytípust, amely a system. Data. common. DbProviderFactory osz­
tályból származik. Az ősosztály számos metódust definiál, s ezek szolgál­
tatóspecifikus objektumokkal térnek vissza. Nézzük meg a DbProviderFac­
tory fontosabb tagjait:

124
Az ADO.NET data provider factory modell

public abstract class DbProviderFactory


{

public virtual
DbCommand CreateCommand();
public virtual
obcommandBuilder createCommandBuilder();
public virtual
obconnection Createconnection();
public virtual
obconnectionstringBuilder
createconnectionstringBuilder();
public virtual DbDataAdapter createDataAdapter();
public virtual DbDatasourceEnumerator
CreateDataSourceEnumerator();
public virtual DbParameter createParameter();
}

Megszerezhetjük a DbProviderFactory leszármazott típusát az adatszolgálta­


tónk számára: ehhez a system.Data.Common névtér a DbProviderFactories osz­
tálytípust biztosítja (figyeljük meg a többes számot a típus nevében). A stati­
kus GetFactory() metódus használatával megkaphatjuk a megadott adat­
szaigáitató specifikus DbProviderFactory objektumát, például:

static void Main(string[] args)


{
ll Kérjük az SQL adatszolgáltatóhoz tartozó factoryt.
DbProviderFactory sqlFactory �

DbProviderFactories.GetFactory("System.Data.SqlClient");

ll Kérjük az. oracle adatszolgáltatóhoz tartozó factoryt.


DbProviderFactory oracleFactory �

DbProviderFactories.GetFactory('' system.Data.oracleclient");

Ahelyett, hogy bedrótozott sztringliterálokkal kérnénk le a factoryt, ezt az in­


formációt a kliensoldali *.con fig fájlból is beolvashatjuk (az előző MyConnec­
tionsFactory példához hasonlóan). Miután a factory rendelkezésre áll az
adatszolgáltatónk számára, megszerezhetjük a hozzátartozó szolgáltatóspeci­
fikus adatobjektumokat (kapcsolatokat, parancsokat, adatolvasókat stb.).

Regisztrált data provider factoryk

A DbProvi derFactories típus csak a lehetséges adatszolgáltatók egy részhal­


mazára képes factorykat lekérdezni. Az érvényes provider factoryk listáját a
.NET 3.5 telepítés machine.config fájljának <DbProviderFactories> eleme tá­
rolja (az ínvaríant attribútum, értéke megegyezik a DbProviderFactories.Get­
Factory() metódusnak átadott értékkel):

125
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

<system.data>
<DbProviderFactories>
<add name="Odbc Data Provider" invariant="System.Data.odbc"
description=".Net Framework Data Provider for odbc"
type="System.Data.odbc.odbcFactory,
system.Data, version=2.0.0.0, culture=neutral,
PublicKeyToken=b77a5c561934e089" />
<add name="OleDb Data Provider" invariant="System.oata.oleDb"
description=".Net Framework Data Provider for oleDb"
type="System.Data.OleDb.OleDbFactory,
System.Data, version=2.0.0.0, culture=neutral,
PublicKeyToken=b77a5c561934e089" />
<add name="Oracleclient Data Provider"
invariant="System.Data.oracleclient"
description=".Net Framework Data Provider for oracle"
type="System.Data.oracleclient.oracleclientFactory,
System.Data.Oracleclient,
version=2.0.0.0, culture=neutral,
PublicKeyToken=b77a5c561934e089" />
<add name="Sqlclient Data Provider"
invariant="System.Data.Sqlclient"
description=".Net Framework Data Provider for sqlServer"
type="System.Data.sqlclient.sqlclientFactory, system.Data,
version=2.0.0.0, culture=neutral,
PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>

Megjegyzés Ha olyan data provider factory mintát szeretnénk használni, amelyhez a kapcso­
lódó adatbáziskezelőt a machine.con fi g fájl nem tartalmazza, akkor technikailag lehetőség
van arra, hogy olyan új invariáns értékeket adjunk hozzá, amelyek a globális szerelvénytár
megosztott szerelvényeire mutatnak. Meg kell bizonyosadnunk arról, hogy az adatszolgáltató
ADO. NIT 2.0 kompatibilis, és együtt tud működni a data provider factory modellel.

Egy teljes data provider factory példa

A teljes példához hozzunk létre egy C#-konzolalkalmazást (az alkalmazás


neve legyen DataProviderFactory), amely listázza az AutoLot adatbázis autó­
raktárkészletét Az első példában az adathozzáférési logikát közvetlenül a
DataProviderFactory.exe szerelvénybe drótozzuk be (hogy az elején minél

egyszerűbb dolgunk legyen). Ahogy egyre jobban megismerjük az ADO.NET


programozási modellt, elkülömljük az adatlogikánkat egy külön .NET-kód­
könyvtárba, és ezt a továbbiakban is használni fogjuk.

126
Az ADO.NET data provider factory modell

Először is adjunk referenciát a System. configu rati on. dll szerelvényre, és


importáljuk a system.Configuration névteret. Ezután szúrjunk be egy App.
con fig fájlt az aktuális projektünkbe, és definiáljunk egy üres <appsetti ngs>
elemet. Adjunk hozzá egy új, provider nevű kulcsot, amely ahhoz az adat­
szolgáltató névtér nevéhez tartozik, amelyet meg szeretnénk kapni (system.
Data.Sqlcli ent) . Ezen kívül definiáljunk egy kapcsolatsztringet, amely az

AutoLot adatbázishoz állítja be a kapcsolatot:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<appsettings>
<!-- Melyik szolgáltató? -->
<add key="provider" value="System.Data.sqlclient" />
<!-- Melyik kapcsolatsztring? -->
<add key="cnstr" value=
"Data s ou rce=( lo ca l) \SQLEXPRESS;Initial
C a ta log=AutoLot;In tegr a t ed security=True"
/>
</appSettings>
</configuration>

Megjegyzés Később részletesebben megvizsgáljuk a kapcsolatsztringeket. Ha az Server Exp­


loreren belül kiválasztjuk az Autalot adatbázis ikonját, a Visual Studio 2008 Properties ablaká­
nak Connection String tulajdonságából egyszerűen kimásolható és beilleszthető a megfelelő
kapcsolatsztring.

Miután van egy megfelelő *.config fájlunk, beolvashatjuk a szolgáltatót és a


cnstr értékeket a configurationManager.AppSettings() metódussal. A szalgálta­

tót a DbProviderFactories.GetFactory() metódusnak adjuk tovább, hogy meg­


kapjuk az adatszolgáltató specifikus factorytípusát. A cnstr érték segítségével
beállítjuk a Dbconnection leszármazott típusának connectionstring tulajdonsá­
gát. Tételezzük fel, hogy importáltuk a system.Data és a system.Data.common név­
tereket Ezek után módosítsuk a Main O metódust a következők szerint:

static void Main(string[] args)


{
Console.WriteLine(''***** Fun with Data Provider Factaries
*****\n");

ll Beolvassuk a kapcsolatsztringet és a szolgáltatót


ll a *.config fájlból.
string dp =
ConfigurationManager.AppSettings["provider"];
string cnstr =
configurationManager.AppSettings["cnStr"];

127
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

ll Beolvassuk a factoryszolgáltatót.
DbProviderFactory df = DbProviderFactories.GetFactory(dp);

ll Hozzunk létre egy kapcsolatobjektumot.


Dbconnection cn = df.createconnection();
console.writeLine("Your connection object is a: {O}",
cn.GetType().FullName);
cn.connectionstring = cnstr;
cn .Open();

ll Hozzunk létre egy parancsobjektumot.


Dbeammand cmd = df.createcommand();
console.writeLine("Your command object is a: {O}",
cmd.GetType().FullName);
cmd.connection = cn;
cmd.CommandText = "select * From Inventory";

ll írassuk ki az adatokat az adatolvasó segítségével.


ll Mivel a commandBehavior.Closeconnectiont választottuk, nem kell
ll explicit módon meghívni a close() metódust a kapcsolatra.
DbDataReader dr =

cmd.ExecuteReader(CommandBehavior.CloseConnection);
console.writeLine("Your data reader object is a: {O}",
dr.GetType().FullName);
Console.WriteLine("\n***** current Inventory *****");
while (dr.Read())
console.writeLine("-> Car #{O} is a {1}.",
dr ["cariD"], dr ["Make"] . ToString(). Trim());
dr.close();
Console.ReadLine();
}

Megjegyzés Ha a commandBehavior.cl oseconnection értéket választottuk az Execute­


Reader() paramétereként, akkor a kapcsolat automatikusan lezárul, ahogy az adatolvasó ob­
jektumot bezárjuk.

Diagnosztikai célokból a reflexiós szolgáltatás segítségével a mögöttes kap­


csolat-, parancs- és adatolvasó teljesen meghatározott nevét íratjuk ki. Ha fut­
tatjuk az alkalmazást, láthatjuk, hogy a Microsoft SQL Server szolgáltató se­
gítségéve! olvasta ki a program az adatokat az AutaLot adatbázis Inventory
táblájából (lásd a 22.13. ábrát).
Ha a következők szerint módosítjuk a *.config fájlt, hogy a system.Data.
oleDb legyen az adatszolgáltató (és bővítjük a kapcsolatsztringet egy Pr ovider
szegmenssel):

<configuration>
<appSettings>
<!-- Melyik szolgáltató? -->

<add k ey="provider" value="System.Data.oleDb" />

128
Az ADO.NET data provider factory modell

<!-- Melyik kapcsolatsztring? -- >

<add key="cnstr" value=


"Provider=SQLOLEDB;Data Source=(local)\SQLEXPRESS;
Integrated Security=SSPI;Initial catalog=AutoLot"/>
</appSettings>
</configuration>

akkor látha�uk, hogy a system.Data.oleDb típusokat használja a program a


háttérben (lásd a 22.14. ábrát).

22.13. ábra: Az SQL Server data providerfacton; megszerzése

22.14. ábra: Az OLE DB data provider factory megszerzése

Az eddigi ADO.NET-es tapasztalataink alapján nem biztos, hogy tudjuk,


pontosan mire is szalgálnak a kapcsolat-, a parancs- és az adatolvasó objek­
tumok. Egyelőre elég annyit megértenünk, hogy az ADO.NET data provider
factory modellel lehetőségünk van olyan egyszerű kódbázist írni, amely dek­
laratív módon használha�a a különböző adatszolgáltatókat.

129
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A data provider factory modell lehetséges hátránya

Noha a data provider factory modell meglehetősen hatékony, biztosnak kell


lennünk abban, hogy a kódbázis valóban csak azokat a típusokat és metódu­
sokat használja, amelyek az absztrakt ősosztályok révén minden szolgáltató­
ban megtalálhatók Ezért a program létrehozásakor csak olyan tagokat hasz­
nálhatunk, amelyek a DbConnetion, a Dbeammand és a system. Data. common név­
tér más típusaiban szerepeinek
Mindezek alapján úgy érezhetjük, hogy ez az "általánosított" megközelí­
tés nem teszi lehetövé, hogy az adott adatbáziskezelők egyedi lehetőségeit
közvetlenül elérjük. Ha a mögöttes szolgáltató speciális tagjait kell meghív­
nunk (pl. az Sqlconnection tagot), akkor erre az explicit kasztolás lehet a
megoldás. Ekkor azonban a kódunk sokkal nehezebben lesz karbantartható
(és kevésbé rugalmas), mivel számos futásidejű ellenőrzést kell beleépíteni.

A <connectionStrings> elem

A kapcsolatsztring-adatunk jelenleg a *.config fájlunk <appsetti ngs> elemé­


ben szerepel. Az alkalmazáskonfigurációs fájl definiálhat egy <connection­
stri ngs> elemet is. Az elemerr belül tetszőleges számú név/ érték párt defini­
álhatunk, amelyeket programozottan a memóriába olvashatunk a configura­
tionManager.connectionstrin gs indexelő segítségéveL Ennek a megközelí­
tésnek az egyik előnye (az <appSettings> elem és a ConfigurationManager.
Appsettings indexelő használata helyett) az, hogy konzisztenserr adhatunk
meg egyetlen alkalmazás számára több kapcsolatsztringet.
Módosítsuk a következők szerint az App.confi g fájlunkat (figyeljük meg,
hogy minden egyes kapcsolatsztringet a name és a connectionstring attribú­
tumokkal adunk meg, nem pedig a key és a value attribútumokkal, mint az
<appsetti ngs> esetén ):

<configuration>
<appsettings>
<!-- Melyik szolgáltató? -->
<add key="provider" value="System.Data.sqlclient" />
</appSettings>

<!-- Itt következnek a kapcsolatsztringek -->


<connectionstrings>
<add name ="AutoLotSqlProvider" connectionstring
"Data Source=(local)\SQLEXPRESS;

130
Az ADO. NET kapcsolatalapú modellje

Integrated Security=SSPI;Initial catalog=AutoLot"/>


<add name ="AutoLotoleDbProvider" connectionstring =
"Provider=SQLOLEDB;Data Source=(local)\SQLEXPRESS;
Integrated Security=SSPI;Initial catalog=AutoLot"/>
</connectionstrings>
</configuration>

Ezek alapján a következőképpen módosíthatjuk a Main O metódusunkat:

static void Main(string[] args)


{
console.writeLine("***** Fun with Data Provider Factaries
*****\n");
string dp =
configurationManager.AppSettings["provider"];
string cnstr =
ConfigurationManager.connectionstrings["AutoLotsqlProvider"].
connectionstring;

A jelenlegi alkalmazásunk semleges kóddal jeleníti meg az AutaLot adatbá­


zis Inventory táblájának tartalmát Ha a szolgáltató nevét és a kapcsolat­
sztringet kiírjuk egy külső *. config fájlba, a data provider factory modell a
háttérben dinamikusarr betölti a megfelelő szolgáltatót.

Megjegyzés A könyv további példái expliciten használják a system. D a ta.Sq l cl i ent névtér
típusait, hogy a továbbiakban a lényegre tudjunk összpontosítani. Ha más adatbáziskezelő rend­
szert szeretnénk használni (pl. Oracle-t), akkor ennek megfelelően módosítani kell a kódot.

Forráskód A DataProviderFactory kódfájlokat a forráskódkönyvtárban a 22. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Az ADO.NET kapcsolatalapú modellje

Az ADO.NET kapcsolatalapú modellje lehetőséget nyújt arra, hogy az adatbá­


zissal az adatszolgáltató kapcsolat-, parancs-, és adatolvasó objektumain ke­
resztül kornrnunikáljunk. Bár az előző DataProvi derFactory alkalmazásunk­
ban már használtuk ezeket az objektumokat, nézzük végig a folyamatot még
egyszer egy kibővített példával. Ha egy adatbázishoz szeretnénk csatlakozni
és egy adatolvasó objektum segítségével kiolvasni a rekordokat, a következő
lépéseket kell végrehajtanunk:

131
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

l. A kapcsolatobjektum allokálása, konfigurálása és megnyitása.

2. Egy parancsobjektum allokálása és konfigurálása, a kapcsolatobjektum


meghatározása a konstruktor argumentumaként vagy a connection tu­
lajdonságon keresztül.

3. ExecuteReader() metódus meghívása a beállított parancsobjektumon.

4. Az adatolvasó Read() metódusával rninden egyes rekord feldolgozása.

Kiindulásként hozzunk létre az új AutoLotDataReader konzolalkalrnazást, és


importáljuk a System.Data és a System.Data.Sqlclient névtereket A cél az,
hogy megnyissunk egy kapcsolatot (az Sqlconnection objektumorr keresztül),
és elküldjünk egy SQL-lekérdezést (az sqlcommand objektumorr keresztül),
hogy megkapjuk az Inventory tábla összes rekordját. Ekkor az sqlDataReader
objektumot fogjuk használni, hogy kiírassuk az eredményt a típusindexelő­
veL A teljes kód a Main() metóduson belül a következő:

class Program
{
static void Main(string[] args)
{
Console.writeLine("***** Fun with Data Readers *****\n");

ll Egy nyitott kapcsolat létrehozása.


sqlconnection cn = new sqlconnection();
cn.Connectionstring =
@"Data source=(local)\SQLEXPRESS;Integrated security=SSPI;" +

"Initial catalog=AutoLot";
cn.Open();

ll Egy SQL-parancsobjektum létrehozása.


string strSQL = "Select * From Inventory";
sqlcommand mycommand = new sqlcommand(strSQL, cn);

ll Adatolvasó beolvasása az ExecuteReader() metódussal.


Sql.DataReader myDataReader;
myDataReader =
mycommand.ExecuteReader(commandBehavior.closeconnection);

ll Lépkedjünk végig az eredményen egy ciklus segítségével.


while (myDataReader.Read())
{
console.writeLine("-> Make: {O}, PetName: {1}, color: {2}.",
myDataReader["Make"].ToString().Trim(),
myDataReader["PetName"].ToString().Tr im(),
myDataReader["Color"]. ToString().Trim());
}

132
Az ADO.NET kapcsolatalapú modellje

ll Mivel a commandBehavior.closeconnection típust specifikáltuk,


ll nem szükséges explicit módon meghívni a close() metódust
ll a kapcsolatra.
myDat a Re a der. Close() ;
Co n s ole . Read L ine ( ) ;
}
}

A kapcsolatobjektumok használata

Az első lépés, ha egy adatszolgáltatóval dolgozunk, az, hogy létrehozunk egy


munkamenetet az adatforrással a kapcsolatobjektumot felhasználva (amely a
Dbconnecti on típusból származik). A .NET-kapcsolatobjektumok formázott

kapcsolatsztringgel rendelkeznek, amely pontosvesszővel elválasztott névj ér­


ték párokat tartalmaz. Ezt az információt használjuk a gép nevének meghatá­
rozására, amelyikhez csatlakozni szeretnénk, a kívánt biztonsági beállítások
meghatározására, a gépen található adatbázis nevének definiálására és más
adatszolgáltató-specifikus információ meghatározására.
Az Initial Catalog név utal arra az adatbázisra, amelyhez a munkamenetet
próbáljuk meg létrehozni. A Data Source név határozza meg a gép nevét,
ahol az adatbázis található. Itt a (l oc al) kulcsszó segítségével definiálhatunk
egy tokent, amely meghatározza az aktuális helyi gépet (függetlenül attól,
hogy az adott gépnek mi a valódi neve), míg az \SQLEXPRESS token tájékoztat­
ja az SQL Server szolgáltatót, hogy az alapértelmezett SQL Server Express te­
lepítéshez csatlakozunk (ha az AutaLot adatbázist egy SQL Server 2005 teljes
vagy korábbi verzión hoztuk létre, egyszerűen adjuk meg a D a ta source =

Cl oc a l) adatforrást).

Ezek kívül megadhatunk további olyan tokeneket, amelyek meghatároz­


zák a biztonsági azonosítóadatokat Ebben a példában az Integrated Security
értékét SSPI-re állítjuk (ez az igaz értékkel egyenlő), amely az aktuális Win­
dows felhasználói bizonyítványokat használja a felhasználói hitelesítés során.

Megjegyzés Keressük meg a .NET Framework 3.5 SOK dokumentációjában az adatszolgálta·


tónk kapcsolatobjektumának connecti enstri ng tulajdonságát, hogy megismerjük az adott
adatbáziskezelő rendszerhez tartozó összes név/érték párt. Egy szegmens (mint pl. az Integra­
ted Security) több, redundáns értékre is állítható (pl. az SSPI, a true és a yes ugyanúgy mű­
ködik az Integrated Security értékre). Továbbá több kifejezést is találunk majd ugyanahhoz a
feladathoz (pl. az lnitial Catalog és a Database kifejezések felcserélhetők).

133
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Ha létrehoztuk a felépített sztringet, az open O metódus hívásával felépítjük a


relációs adatbáziskezelő rendszerünkkel a kapcsolatot. A connectionstring,
az open() és a close() tagokon kívül a kapcsolatobjektum számos olyan tagot
tartalmaz még, amelyekkel további beállításokat végezhetünk a kapcsolatun­
kon, ilyen például az időtúllépés és a tranzakciós információk. A 22.5. táblá­
zat néhány (de nem az összes) DbConnection alaposztálytagot tartalmaz.

BeginTransaction() Ez a metódus egy adatbázis-tranzakció indítására szolgál.

changeDatabase() Ez a metódus adatbázist vált egy megnyitott kapcsolat


alatt.

connectionTimeout Ez az írásvédett tulajdonság azzal az értékkel tér vissza,


hogy mennyi időt várhatunk a kapcsolat felépítésére,
mielőtt félbeszakadna a próbálkozás, és hiba generá­
lócina (az alapértelmezett érték 15 másodperc). Ha sze­
retnénk megváltoztatill az alapértelmezett értékét, ak­
kor meg kell adni a kapcsolatsztringben egy "Connect
Timeout" szegmens t (pl. connect Timeout 30).
=

Database Ez a tulajdonság adja meg, hogy a kapcsolatobjektum


melyik adatbázissal dolgozik.

Datasource Ez a tulajdonság adja meg, hogy a kapcsolatobjektum


által használt adatbázis hol található.

Getscherna O Ez a metódus olyan Da tasette l tér vissza, amely az

adatforrásra vonatkozó sémainformációkat tartalmazza.

state Ez a tulajdonság állítja be a kapcsolat aktuális állapotát,


amelyet a connecti onstate felsorolás képvisel.

22. 5. táblázat: A DbConnection típus tagjai

A DbConnection típus tulajdonságai tipikusan írásvédettek, és arra szolgál­

nak, hogy futásidőben megkapjuk a kapcsolat tulajdonságait. Ha szeretnénk


felülírni az alapértelmezett beállításokat, akkor magát a felépített sztringet
kell módosítani. Például a kapcsolatsztring a kapcsolat időtúllépését 15 má­
sodpercről30 másodpercre állítja:

static void Main(string[] args)


{
console.writeLine("***** Fun with Data Readers *****\n");

134
Az ADO.NET kapcsolatalapú modellje

Sqlconnection cn = new Sqlconnection();


cn.connectionstring =
@"Data source=(local)\SQLEXPRESS;" +

"Integrated Security=SSPI;Initial Catalog=AUtoLot;


connect Timeout=30";
cn .open();

ll új segédfüggvény (lásd lent).


showconnectionstatus(cn);

Az előző kódban látható, hogy a kapcsolatobjektumunkat a Program osztály­


ban paraméterként adtuk át egy új statikus segédfüggvénynek, amelynek
showconnetionstatus() a neve, és a következőképpen implementáljuk (bizo­
nyosodjunk meg róla, hogy importáltuk a system. Data. common névteret, hogy
megkapjuk a obconnecti on defuúcióját):

static void showconnectionStatus(obconnection cn)


{
ll Az aktuális kapcsolatobjektum különböző információinak
ll megjelenítése.
Console.writeLine("***** Info about your connection *****");
console.writeLine("Database location: {O}", cn.DataSource);
Console.writeLine("oatabase name: {O}", cn.Database);
Console.writeLine("Timeout: {0}", cn.connectionTimeout);
Console.WriteLine ("connection state: {O}\n", cn.State.ToString());
}

Míg a legtöbb tulajdonság a nevében hordozza a jelentését is, a state tulaj­


donságot érdemes külön megemlíteni. Noha ez a tulajdonság a connection­
State felsorolt típus bármely értékét megkapha�a:

public enum connectionstate


{
Broken, closed,
connecting, Executing,
Fetching, open
}

az érvényes értékek a connectionstate érték, a Connectionstate.open és a


connectionstate. closed (a felsorolt típus további tagjait későbbi használatra
tar�uk fenn). Mindig biztonságos egy olyan kapcsolatot bezárni, amelynek
aktuális állapota connectionstate.cl osed.

135
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A ConnectionStringBuilder objektumok

A kapcsolatsztringekkel programozottan dolgozni eléggé nehézkes, hiszen


gyakran sztringliterálként jelennek meg, ezeket pedig nehéz kezelni, és sok a
hibázási lehetőség. A Microsoft által kínált ADO.NET-adatszolgáltatók tá­
moga�ák azokat a kapcsolatszfring-építő objektumokat, amelyek erősen típusos
tulajdonságokkal lehetövé teszik a névl érték párok használatát. Nézzük meg
a Main O metódus következő módosítását:

static void Main(string[] args)


{
Console.writeLine("***** Fun with Data Readers *****\n");

ll Hozzunk létre egy kapcsolatsztringet az építő objektum


ll segitségével.
SqlconnectionstringBuilder cnstrBuilder =

new SqlconnectionstringBuilder();
cnStrBuilder.InitialCatalog = "AutoLot";
cnstrBuilder.DataSource = @"(local)\SQLEXPRESS";
cnstrBuilder.connectTimeout = 30;
cnStrBuilder.Integratedsecurity = true;
sqlconnection cn = new sqlconnection();
cn.connectionstring = cnstrBuilder.connectionstring;
cn.Open();
showconnectionstatus(cn);

Ebben a lépésben létrehozunk egy Sql connectienstringBui lder példányt,


megfelelően beállí�uk a tulajdonságokat, és lekérjük a belső sztringet a con­
nectienstring tulajdonság segítségéveL Figyeljük meg, hogy a típus alapér­
telmezett konstruktorát használtuk. Ha ezt a megoldást válasz�uk, akkor lét­
rehozha�uk az adatszolgáltatónk kapcsolatsztring-építő objektumának egy
példányát, ha kiindulási pontként átadunk egy létező kapcsolatsztringet (ez
hasznos lehet, ha az értékeket dinamikusan egy Ap p.con fig fájlból olvassuk
be). Ha feltöltöttük az objektumunkat a kezdeti sztringadatokkal, akkor a kü­
lönböző névl érték párokat a kapcsolódó tulajdonságok segítségével módo­
sítha�uk, például:

static void Main(string[] args)


{
console.writeLine("***** Fun with Data Readers *****\n");

136
Az ADO.NET kapcsolatalapú modellje

ll Tételezzük fel, hogy a constr valóban egy *.config fájlból


ll érkezett.
string cnstr = @"Data source=(local)\SQLEXPRESS;" +

"Integrated Security=SSPI;Initial catalog=AutoLot";


SqlConnectionstringBuilder cnstrBuilder =
new sqlconnectionstringBuilder(cnStr);

ll Módosítsuk az időtúllépés értékét.


cnStrBuilder.ConnectTimeout = 5;

A parancsobjektumok

A kapcsolatobjektum szerepének megismerése után, a következő feladatunk


azt megvizsgáhú, hogyan tudunk SQL-lekérdezéseket küldeni az adatbázis­
nak. Az sqlcommand típus (amely a Dbeammand típusból származik) egy SQL-le­
kérdezés, táblanév vagy tárolt eljárás objektumorientált megjelenitése. A pa­
rancs típusát a commandType tulajdonsággal állíthatjuk be, amely a commandType
felsorolás bármely értékét felveheti:

public enum commandType


{
StoredProcedure,
TableDirect,
Text ll Alapértelmezett érték.
}

Amikor létrehozunk egy parancsobjektumot, az SQL-lekérdezést vagy konst­


ruktorparaméterként adjuk át, vagy közvetlenül a commandText tulajdonságon
keresztül. A parancsobjektum létrehozásakor meg kell adni, hogy melyik
kapcsolatot akarjuk használni. Ezt vagy szintén konstruktorparaméterként
adhatjuk meg, vagy a connection tulajdonságon keresztül:

static void Main(string[] args)


{

Sqlconnection cn = new sqlconnection();

ll Parancsobjektum létrehozása a konstruktorargumentumok


ll segitségével.
string strSQL = "select * From Inventory";
Sqlcommand mycommand = new sqlcommand(strSQL, cn);

137
22. fejezet: ADO.NET, 1. rész: Az élő kapcsotat

ll Egy másik parancsobjektum létrehozása tulajdonságok


ll segítségével.
SqlCommand testcommand = new sqlcommand();
testcommand.Connection = cn;
testCommand.commandText = strSQL;

Itt nem küldtük el az SQL-lekérdezést az AutaLot adatbázisnak, hanem elő­


készítettük a parancsobjektum állapotát a későbbi felhasználásra. A 22.6. táb­
lázat a obcommand típus további tagjait emeli ki.

CommandTimeout Lekérdezi vagy beállítja azt az időértéket, amennyit a


rendszer a parancs futtatása közben vár, mielőtt meg­
szakítaná a próbálkozást, és hibát generálna. Az alapér­
telmezett érték 30 másodperc.

connection Lekérdezi vagy beállítja a obcommand példánya által


használt obconnection típust.

Parameters Beolvassa a ObParameter típusok gyűjteményét, ame­


lyeket a paraméterezett lekérdezéshez használunk.

cancel() Felfüggeszti egy parancs futását.

ExecuteReader() Az adatszolgáltató oboataReader objektumával tér


vissza, amely egyirányú, írásvédett hozzáférést biztosít
a mögöttes adatokhoz.

ExecuteNonQuery() Lefuttatja a parancsot az adattárolón, és nem vár vissza


semmilyen eredményt.

Executescalar() Az ExecuteNonQuery() metódus könnyített verziója,


kifejezetten az egyelemű lekérdezések számára (példá­
ul a rekordok számának meghatározására).

ExecutexmlReader() A Microsoft SQL Server (2000 vagy magasabb verziói)


képesek az eredményhalmazt XML-formátumban visz­
szaadni. Ez a metódus visszaad egy System.xml.xml­
Reader típust, amely lehetővé teszi számunkra a bejövő
XML-folyam feldolgozását.

138
Az adatol vasók

Prepare() A parancs egy előkészített (vagy lefordított) verzióját

hozza létre az adatforráson. Egy előkészített lekérdezés


valamelyest gyorsabban fut le, és olyankor hasznos, ha
ugyanazt a lekérdezést többször szeretnénk futtatni.

22. 6. táblázat: A DbCommand típus tagjai

Megjegyzés Ahogy azt maj d a fejezet későbbi részében l áthatjuk, az Sql command objektum
további tagokat tartal maz, amelyek az adatbázis-kezelés aszinkron módj át teszik lehetövé.

Az adatolvasók

Miután létrehoztunk egy aktív kapcsolatot és egy SQL-parancsot, a követke­


ző lépés az, hogy elküldjük a lekérdezést az adatforráshoz. Többféleképpen
megtehetjük ezt. A oboataReader típus (amely az IDataReadert implementál­
ja) a legegyszerűbb és a leggyorsabb módja annak, hogy információt nyer­
jünk ki az adattárolóbóL Az adatolvasó írásvédett, egyirányú adatfolyamot
képvisel, és egyszerre egy rekorddal tér vissza. Ezek alapján nyilvánvaló,
hogy az adatolvasók akkor hasznosak, ha egy lekérdező SQL-utasítást kül­
dünk el a mögöttes adattárolóba.
Az adatolvasók akkor hasznosak tehát, ha nagy adathalmazon szeretnénk
végiglépkedni, és közben nincs szükség arra, hogy az adatok memóriában tá­
rolt megjelemtése a rendelkezésünkre álljon. Ha például 20 OOO rekordot sze­
retnénk lekérdezni egy táblából, hogy szöveges fájlban tároljuk, nagyon sok
memóriát használna fel, ha ezt az információt egy Dataset típusban tárol­
nánk. Sokkal jobb megoldás, ha adatolvasót használunk, ez ugyanis nagyon
gyorsan lépked végig a rekordokon. Figyeljünk azonban arra, hogy az adat­
olvasó objektumok (ellentétben az adatillesztővet ezeket lásd később) az
adatforrással élő kapcsolatot tartanak fenn mindaddig, amíg expliciten be
nem zárjuk a munkamenetet.
Az adatolvasó objektum a parancsobjektumból nyerhető az ExecuteRea­
der() metódus hívásával. Amikor meghívjuk ezt a metódust, az olvasóhoz

tartozó kapcsolatobjektum commandBehavi o r. cl oseconnecti on tulajdonságá­


nak beállításával megadhatjuk az olvasának, hogy automatikusan zárja le a
kapcsolatot.

139
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A következő példában az olvasó a Read O metódust használja annak meg­


határozására, hogy Inikor értük el az utolsó rekordot (a false visszatérési ér­
ték segitségével). Minden egyes bemeneti rekordnál használjuk a típusinde­
xelőt, hogy listázzuk minden egyes autó gyártmányát, nevét és színét. Figyel­
jük meg, hogy amint befejeztük a rekordok feldolgozását, meghívjuk a
close() metódust, hogy felszabadítsuk a kapcsolatobjektumot:

static void Main(string[] args)


{
ll Adatolvasó beolvasása az ExecuteReader() segítségével .

SqlDataReader myoataReader;
myDataReader =

mycommand.ExecuteReader(CommandBehavior.Closeconnection);
ll Lépkedjünk végig az eredményen egy ciklus segítségével.
while (myoataReader.Read())
{
console.writeLine("-> Make: {O}, PetName: {1}, color: {2}.",
myDataReader["Make"]. ToString().Trim(),
myDataReader["PetName"] .ToString().Trim(),
myDataReader ["color"].ToString().Trim());
}
myDataReader.Close();
console.ReadLine();
}

Megjegyzés Asztringadatok csonkítását csak azért hajtjuk végre, hogy leszedjük a szóközöket az
adatbázis·bejegyzésekből; ezek nem kapcsolódnak közvetlenül az ADO.NET-hez. Az, hogy szükség
van-e erre a lépésre, attól függ, hogy hogyan definiáltuk az oszlopot, és milyen adatot szúrtunk be a
táblába. Így erre a müveletre természetesen nem minden egyes esetben van szükség.

Az olvasó objektumindexelője túlterhelt: vagy egy sztringet (amely az oszlop


nevét képviseli), vagy egy egész számot (amely az oszlop sorszámát jelzi)
kaphat paraméterként. Így elegánsabbá tehe�ük az aktuális olvasólogikánkat
(és elkerülhe�ük a bedrótozott sztringneveket), ha a következőképpen mó­
dosí�uk az eddigieket (figyeljük meg a Fiel deount tulajdonság használatát):

while (myDataReader.Read())
{
console.WriteLine("***** Record *****");
for (int i O; i < myDataReader.Fieldcount; i++)
=

{
console.writeLine("{O} {l} ",
=

myDataReader.GetName(i),
myDataReader.Getvalue(i).ToString().Trim());
}
console.writeLine();
}

140
Az adatolvasók

Ha lefordítjuk és futtatjuk a projektünket, az AutoLot adatbázis Inventory


táblájának tartalomlistáját láthatjuk (lásd a 22.15. ábrát).

22.15. ábra: Az adatolvasó objektumok

Több eredményhalmaz kinyerése adatolvasóval

Az adatolvasó objektumok egyetlen parancsobjektummal több eredmény­


halmazt kezelnek. Ha például egyszerre szeretnénk megkapni az lnventory
és a Customers tábla sorait, megadhatjuk mindkét SELECT utasítást pontos­
vesszővel elválasztva:

string strSQL = "select * From Inventory;Select *from customers";

Amikor megkapjuk az adatolvasó objektumot, a különböző eredményhalma­


zok között a NextResult() metódussal iterálhatunk. Figyeljünk arra, hogy au­
tomatikusan mindig az első eredményhalmazt kapjuk vissza. Ezért, ha a táb­
lák minden során szeretnénk végiglépkedni, a következő iterációkonstruktort
kell elkészítenünk:

do
{
while (myDataReader.Read())
{
Consol e.writeL ine("***** Record '""'***");

141
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

for (int i =O; i < myDataReader.Fieldcount; i++)


{
console.writeLine("{O} = {1}",
myDataReader.GetName(i),
myoataReader.Getvalue(i).ToString().Trim());
}
console.writeLine();
}
} while (myDataReader.NextResult());

Egy adatolvasó csak egy SQL select utasítást tud feldolgozni, és nem hasz­
nálható létező adatbázistábla adatainak a módosítására beszúrással, törléssei
vagy frissítéssel. Egy létező adatbázis módosításának megértéséhez a pa­
rancsobjektum további vizsgálatára van szükség.

Forráskód Az AutolotDataReader kódfájlokat a forráskódkönyvtárban a 22. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Újrafelhasználható adatelérési
könyvtár készítése

Az ExecuteReader O metódus egy olyan adatolvasó objektumot biztosít, amely


lehetövé teszi, hogy egy SQL-utasítás eredményét végignézzük egy egyirányú,
írásvédett információfolyamban. Ha olyan SQL-utasítást szeretnénk végrehaj­
tani, amely az adott táblát módosítja, akkor a parancsobjektum ExecuteNon­

Query() metódusát kell meghívni. Ez a metódus beszúrásokat, módosításokat


és törléseket hajt végre a parancsszöveg alapján.

Megjegyzés Technikai szempontból a nonquery egy olyan SQL-utasítás, amely nem tér vissza
eredményhalmazzaL Tehát a select utasítások lekérdezések, míg az Insert, az Update és a
Delete utasítások nem. Ezért az ExecuteNonQuery() metódus egy integertípussal tér vissza,
amely az érintett sarok számát adja vissza, nem pedig a rekordok egy új halmazát.

A következőkben megvizsgáljuk, hogyan kell módosítani egy létező adatbá­


zist úgy, hogy kizárólag az ExecuteNonQuery() utasítást alkalmazzuk. A kö­
vetkező feladatunk pedig az, hogy olyan egyedi adatelérési könyvtárat hoz­
zunk létre, amely magába foglalja az AutoLot adatbázison végrehajtott mű­
veletek folyamatát. Egy termékszintű környezetben az ADO.NET-logikánk
szinte mindig elkülönített .NET *.dll szerelvény, nagyon egyszerű okból: a

142
Újrafelhasználható adatelérési könyvtár készítése

kód-újrafelhasználás miatt. A fejezet első példái nem így működtek, ennek


oka az volt, hogy szem előtt tarthassuk az éppen tárgyalt problémaköröket
Ám időpocsékolás lenne minden egyes alkalmazáshoz, amely az AutoLot
adatbázissal dolgozik, ugyanazt a kapcsolódási logikát, ugyanazt az adatolva­
sási logikát és ugyanazt a parancslogikát létrehozni.
Ha az adatelérési logikát elkülöní�ük egy .NET-kódkönyvtárban, több al­
kalmazás, amely tetszőleges front endet használ (parancssoros, asztali, webes
stb.) nyelvfüggetlenül hivatkozhat a könyvtárra. Ha tehát az adatelérési
könyvtárunkat C#-ban hozzuk létre, más .NET-fejlesztők is elkészíthetik a
felhasználói felületet a nekik tetsző nyelven (VB, C++ /CLl stb.).
Ebben a fejezetben az adatkönyvtárunk (az AutoLotDAL.dll) egyetlen név­
teret tartalmaz (az AutoLotConnectedLayert) , amely az AutoLot adatbázissal
működik együtt az ADO.NET kapcsolatalapú modellje alapján . A következő
fejezet ugyanehhez a *.dll-hez új névteret ad (AutoLotoisconnectedLayer) ,
amelynek típusai biztosí�ák az AutoLot adatbázissal folytatott kommuniká­
ciót a kapcsolat nélküli modell segítségéveL A könyv további részében ezt a
könyvárat számos alkalmazás fogja használja.
Először is hozzunk létre egy új C#-osztálykönyvtárprojektet AutoLotoal
névvel (ez az AutoLot adathozzáférési réteg rövidítése- AutoLot Data Access
Layer), és a kezdeti C#-kódfájlt nevezzük át AutoLotConnDAL.cs-re. Követ­
kező lépésként a névteret átnevez:zük AutoLotConnectedLayerre, és megvál­
tozta�uk a kezdeti osztályunk nevét rnventoryDAL-ra, ugyanis ez az osztály
definiálja majd az AutoLot adatbázis Inventory tábláját kezelő különböző ta­
gokat. Végül importáljuk a következő .NET-névtereket:

using System;
using System.collections.Generic;
using system.Text;

ll Az SQL-kiszolgáló adatszolgáltatóját használjuk;


ll noha megengedett az ADO.NET factoryminták alkalmazása
ll a nagyobb rugalmasság érdekében.
using system.oata;
using system.oata.sqlclient;

namespace AutoLotConnectedLayer
{
public class InventoryDAL
{
}
}

143
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Megjegyzés Ha olyan típusokat használunk, amelyek natív erőforrásokat kezelnek (pl. az adat­
bázis-kapcsolatot), érdemes implementálni az IDisposabl e interfészt és elkészíteni a megfelelő
véglegesítőt (vö. az előző kötet 8. fejezetét). Termékszintű környezetben az olyan osztályokkal,
mint az rnventoryDAL, ezt a gyakorlatot követnénk; ezt azonban most elhagyjuk, hogy az
ADO.NET lényegére koncentrálj unk.

A kapcsolatlogika létrehozása

Először definiálnunk kell néhány metódust, amely lehetövé teszi a hívó szá­
mára, hogy egy érvényes kapcsolatsztring segítségével kapcsolódjori, és
bontsa a kapcsolatot az adatforrássaL Mivel az AutaLotDAL _dll szerelvényünk
bedrótozottan használja a sy stem. Data. sql cl i e nt típusait, definiáljunk egy
sqlconnection privát tagváltozót, amelyet a rendszer akkor foglal le, arnikor
az rnventoryDAL objektumot létrehozzuk Készítsünk egy openconnection() és
egy closeconnection() metódust, amelyek ezzel a tagváltozóval működnek
együtt a következőképpen:

public class InventoryDAL


{
ll Ezt a tagot használja minden metódus.
private sqlconnection sqlcn = new sqlconnection();

public v o i d Ope nconnection(stri ng co nnectionstring)


{
sqlcn.connectionstring = connectionstring ;
sqlcn.open();
}

public void Closeconnection()


{
sqlcn close() ;
.

}
}

A tömörség kedvéért az rnventoryDAL típus nem vizsgálja a lehetséges kivéte­


leket, és nem vált ki egyedi kivételeket sem különböző körülmények között
(pl. hibás kapcsolatsztring megadásakor). Ha egy ipari környezetben futó
adatelérési könyvtárat kellene készítenünk, nagy valószínűséggel használ­
nánk a strukturált kivételkezelési módszereket, hogy minden futásidejű
anomáliát kezeljünk.

144
Újrafelhasználható adatelérési könyvtár készítése

A beszúrást végző logika létrehozása

Az Inventory táblába olyan egyszerű új rekordot beszúrni, mint anúlyen egy­


szerű megírni egy SQL r n s e rt utasítást (a felhasználói bemenetek alapján) és
meghívni az Ex ecut e No nQue r y() utasítást a parancsobjektum segítségéveL En­

nek bemutatására adjuk az InsertAuto() nyilvános metódust az InventoryDAL


típusunkhoz. A metódus négy paraméterrel rendelkezik, amelyek az Inventory
tábla négy oszlopának felelnek meg (CariD, Color, Make és PetName). A be­
meneti paraméterekkel hozzunk létre egy sztringet, amely beszúrja az új re­
kordot. Végül az sq l co n n e ctio n objektumunk használatával futtassuk le az

SQL-utasítást:

public void InsertAuto(int id, string color, string make,


str ing p e tName)
{
ll Az SQL-utasítás létrehozása és futtatása.
string sql = string.Format("Ins ert In to I n v e ntor y " +
"(CariD, Make, color, P e tName ) va l ue s " +
"('{O}', '{l}', '{2}', '{3}' )", id, make, color, petName);

ll Futtatás a kapcsolatunk segitségével.


using(Sqlcommand cmd = n ew sqlcommand( sq l , this.sqlcn))
{
cm d .Ex ecu t e No n Quer y() ;
}
}

Megjegyzés SQL·utasításokat sztringösszefűzéssel létrehozni biztonsági okból elég veszélyes


(gondoljunk az SQL inj ection jellegű támadásokra). Előnyben részesítjük ezért az olyan megoldáso­
kat, ahol paraméterezett lekérdezéseket használunk. Rövidesen megismerkedünk a módszerrel.

A törlést végrehajtó logika létrehozása

Törölni egy létező rekordot éppen ugyanolyan egyszerű, mint beszúrni. Az


r n ser tAu to( ) kódjától eltérőerr megnézünk egy fontos try/catch blokkot,

amely azt az esetet kezeli, amikor megrendelés alatt álló autót próbálunk tö­
rölni a Customers táblából. Adjuk a következő metódust az InventoryDAL
osztálytípushoz:

public void Deletecar(int id)


{
ll olvassuk be a törölni kívánt kocsi azonosítóját, majd töröljük.
string sql = string.Format(''Delete fr om In v e n tor y wh er e c a r i D =

'{0}'", id);

145
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

using(sqlCommand cmd = new SqlCommand(sql, this.sqlcn))


{
try
{
cmd.ExecuteNonQuery();
}
catch(sqlException ex)
{
Exception error = new Exception("sorry! That car is on
order!", ex);
throw error;
}
}
}

A módosftást végző logika létrehozása

Amikor az Inventory táblának egy rekordját kell módosítani, az első nyilván­


való kérdésünk az lesz, hogy pontosan melyik adatok módosítását tesszük
lehetővé a hívó számára. Az autó színét? A nevét vagy a márkáját? Vagy az
összes fent említett tulajdonságot? A hívó fél számára úgy tudnánk a teljes
rugalmasságot biztosítani, ha olyan metódust hoznánk létre, amely egy
sztringtípust kap bemenetként, és ez a sztring egy tetszőleges SQL-utasítást
tartalmaz. Ám ez a legkockázatosabb megoldás. Ideális esetben metódusok
olyan halmazát kellene segítségül hívni, amelyek különböző lehetőségeket
biztosítanak a hívó számára a rekordok módosításához. A mi egyszerű adat­
elérési programkönyvtárunkban azonban egyetlen metódust fogunk létre­
hozni, ez pedig a hívó számára meghatározott autó nevét engedi módosítani:

public void updatecarPetName(int id, string newPetName)


{
ll Beolvassuk a módosítani kívánt autó azonosítóját és
ll az új nevét.
string sql =
string.Format("Update Inventory Set PetName = '{O}'
Where CariD = '{1}'", newPetName, id);
using(Sqlcommand cmd = new Sqlcommand(sql, this.sqlCn))
{
cmd.ExecuteNonQuery();
}
}

146
Újrafelhasználható adatelérési könyvtár készítése

A lekérdezést végrehajtó logika létrehozása

A következő metódus, amelyet a könyvtárunkhoz hozzá kell adni, a lekérdező


metódus. Ahogyan azt a fejezet korábbi részében már láthattuk, az adatszal­
gáitató adatolvasó objektuma egy írásvédett, egyirányú szerveroldali kurzor
segítségével engedi a rekordok lekérdezését. Amikor meghívjuk a Read O me­
tódust, minden egyes rekordot megfelelően tudunk feldolgozni. Ám nekünk
azt kell megoldanunk, hogy valamilyen módon visszaadjuk a kívánt rekor­
dokat az alkalmazás hívórétegének
Az egyik lehetőség az lenne, ha feltöltenénk egy többdimenziós tömböt (vagy
bármilyen hasonló objektumot, pl. egy generikus L i st<T> objektumot) a Read()
metódus által visszaadott adatokkal, és azzal térnénk vissza. A másik lehetőség
(amelyet a jelenlegi példánkban választunk), hogy egy System.oata.DataTable
objektumot adunk vissza, amely valójában része az ADO.NET kapcsolat nélküli
rétegének
A DataTable típus teljes leírása a következő fejezetben található, egyelőre
elég annyit tudni róla, hogy a oataTable olyan osztálytípus, amely az adatok
táblázatos formáját képviseli (mint a rács egy táblázatkezelő programban).
Ennek érdekében a oataTable típus sorok és oszlopok gyűjteményét kezeli.
Míg ezek a gyűjtemények programozottan feltölthetők, a oataTable típus fel­
kínálja a Load O metódust, amely automatikusan feltölti ezeket a gyűjtemé­
nyeket egy adatolvasó objektummal. Nézzük meg a következőt:

public DataTable GetAllrnventory()


{
ll Ez tárolja a rekordokat.
DataTable inv = new DataTable();

ll Készítsük elő a parancsobjektumot.


string sql = "select * From Inventory";
us ing(sqlcommand cmd = new sqlcommand(sql, this.sqlcn) )
{
sql DataRead er dr = cmd. ExecuteReaderO;
ll Töltsük fel a DataTable objektumot adatokkal,
ll amelyeket az olvasó szolgáltat, és takarítsunk.
inv.Load(dr);
dr.closeO;
}
return i nv;
}

147
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A paraméterezett parancsobjektumok

Az InventoryDAL típus beszúrás-, módosítás- és törléslogikái jelenleg bedró­


tozott sztringliterálokat tartalmaznak minden egyes SQL-lekérdezéshez. A
paraméterezett lekérdezés SQL-paraméterek objektumként történő alkalmazását
teszi lehetővé ahelyett, hogy egyszerűen szövegként használnánk őket. Az
SQL-lekérdezések objektumorientált használata nemcsak csökkenti a gépelési
hibák lehetőségét (az erősen típusos tulajdonságok révén), hanem a paramé­
terezett lekérdezések végrehajtása tipikusan gyorsabb, mint egy literál-SQL­
sztring, hiszen a rendszer pontosan egyszer elemzi őket (ahelyett, hogy min­
den alkalommal a commandText tulajdonsághoz rendelné az SQL-sztringet).
Továbbá a paraméterezett lekérdezések segítenek elkerülni az SQL injection
jellegű támadásokat (ez egy jól ismert adatelérési biztonsági probléma).
A paraméterezett lekérdezések támogatása érdekében az ADO.NET pa­
rancsobjektumai egyedi paraméterobjektumok gyűjteményét kezelik. Alapér­
telmezésben ez a gyűjtemény üres, de lehetőségünk van szabadon feltölteni
tetszőleges számú paraméterobjektummal, amelyeket sorra hozzárendelhe­
tünk az SQL-lekérdezésben a "helyőrző paraméterekhez". Ha egy paramétert
szeretnénk az SQL-lekérdezésben a parancsobjektum paramétergyűjtemé­
nyének egy tagjához kapcsolni, akkor lássuk el az SQL-szövegben szereplő
paramétert a @ prefixummal (legalábbis Microsoft SQL Server esetén; nem
minden relációs adatbáziskezelő támoga* ezt a jelölésrendszert).

Paraméterek megadása a DbParameter tipus segitségével

Mielőtt elkészítenénk egy paraméterezett lekérdezést, ismerkedjünk meg a


DbParameter típussal (amely a szolgáltatóspecifikus paraméterobjektum ős­
osztálya). Az osztály számos tulajdonságot tart karban, ezek lehetővé teszik,
hogy beállítsuk a paraméter nevét, méretét és adattípusát Más jellemzőket is
kezel, például a paraméter irányát (kimenő, bemenő stb.). A 22.7. táblázat
bemuta�a a Ob Par ameter típus néhány kulcsfontosságú tulajdonságát.

DbType Lekérdezi vagy beállítja az adatforrásból azt a natív adattí­


pust, amelyet a CLR-adattípus képvisel.

Direction Lekérdezi vagy beállítja, hogy a paraméter csak bemenő,


csak kimenő, kétirányú vagy egy visszatérési értékkel ren­
delkező paraméter lesz-e.

148
Újrafelhasználható adatelérési könyvtár készítése

IsNullable Lekérdezi vagy beállítja, hogy a paraméter elfogad-e null


értékeket.

ParameterName Lekérdezi vagy beállítja a ObParameter nevét.

siz e Lekérdezi vagy beállítja az adatok maximális paraméter­


IDéretét (valójában csak szöveges adatoknál hasznos).

value Lekérdezi vagy beállítja a paraméter értékét

22. 7. táblázat: A DbParameter típus kulcsfontosságú tagjai

Hogy bemutassuk, hogyan kell egy parancsobjektum ObParamete r-kompatibi­


lis objektumgyűjteményét feltölteni, módosítsuk a korábbi rnsertAuto() me­
tódusunkat úgy, hogy használjunk benne paraméterobjektumokat (hasonló
módosítást lehetne végezni a többi SQL-központú metódusainkon, de erre
most nincs szükség).

public void rnsertAuto(int id, string color, string make,


string petName)
{
ll Figyeljük meg a "helyőrzőket" az SQL-lekérdezésben.
string sql = string.Format("Insert Into Inventory" +

"(CariD, Make, Color, PetName) values" +

"(@CariD, @Make, @Color, @PetName)");

ll Ez a parancs belső paraméterekkel rendelkezik.


using(Sqlcommand cmd = new Sqlcommand(sql, this.sqlcn))
{
ll Töltsük fel a paraméterek gyűjteményét.
SqlParameter pararn = new SqlParameter();
param.ParameterName = "@carro";
param.value = id;
param.SqlobType = SqlobType.Int;
cmd.Parameters.Add(param);

pararn = new SqlParameter();


param.ParameterName = "@Make";
param.value = make;
param.sqlDbType = SqlDbType.char;
param.size = 10;
cmd.Parameters.Add(param);

pararn = new SqlParameter();


param.ParameterName = "@Color";
param.value = color;
param.SqlDbType = SqlDbType.Char;
param.size = 10;
cmd.Parameters.Add(param);

149
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

pararn = new SqlParameter();


param.ParameterName = "@PetName";
param.value = petName;
param.SqlDbType = sqlDbType.Char;
param.size = 10;
cmd.Parameters.Add(param);

cmd.ExecuteNonQuery();
}
}

Az SQL-lekérdezésünk. négy beágyazott helyőrző szimbólumot tartalmaz,


amelyek mindegyike a@ tokennel kezdődik. Az sqlParameter típus használa­
tával lehetőségünk van minden helyőrzőt leképezni a ParameterName tulaj­
donsággal és ,különböző részleteket erősen típusosan meghatározni (az érté­
két, típusát, méretét stb.). Miután minden egyes paraméterobjektumot beállí­
tottunk, az Add() metódus segítségével hozzáadjuk őket a parancsobjektum
gyűjteményéhez.

Megjegyzés Különféle tulajdonságokat használtunk a paraméterobjektum Létrehozásához.


Tudni kell azonban, hogy a paraméterobjektum több túlterhelt konstruktort támogat, amelyek
Lehetövé teszik, hogy különböző tulajdonságok értékeit beállítsuk (ez egy sokkal tömörebb kó­
dot eredményez). A Visual Studio 2008 számos grafikus tervezőt biztosít, amelyek a paramé·
ter·központú forráskód nagy részét elkészítik számunkra (lásd a 23. fejezetet).

A paraméterezett lekérdezések létrehozásakor gyakran nagyobb mennyiségű


kódot kell megírni, s ennek az eredménye, hogy sokkal egyszerűbben lehet
az SQL-utasításokat programozottan beállítani, valamint jobb teljesítményt
érhetünk el. Bár ezt a módszert bármikor alkalmazha�uk, ha valamilyen
SQL-utasítással dolgozunk, a paraméterezett lekérdezések leginkább tárolt
eljárások hívásakor a leghasznosabbak.

Tárolt eljárások végrehajtása

A tárolt eljárások tulajdonképpen adatbázisban tárolt, névvel ellátott SQL­


utasításokblokkok. Sorok halmazát adják vissza, vagy visszatérhetnek skalá­
ris adattípusokkal is, és tetszőleges számú opcionális paraméterrel rendel­
kezhetnek. A végeredmény egy olyan feldolgozási egység, amely függvény­
hez hasonlóan működik, azzal a nyilvánvaló különbséggel, hogy az eljárás az
adattárolón helyezkedik el, nem pedig a bináris üzleti objektumon. Pillanat­
nyilag az AutaLot adatbázisunk egyetlen tárolt eljárást tartalmaz, GetPetName
névvel, ezt pedig a következőképpen hoztuk létre:

150
Újrafelhasználható adatelérési könyvtár készítése

CREATE PROCEDURE GetPetName


@cariD int,
@petName char(lO) output
AS
SELECT @petName = PetName from Inventory where cariD = @cariD

A továbbiakban nézzük meg az rnventoryDAL típusunk utolsó metódusát,


amely meghívja a tárolt eljárásunkat

public string LookUpPetName(int cariD)


{
.string carPetName = string.Empty;

ll A tárolt eljárás nevének beállítása.


using (Sqlcommand cmd = new Sqlcommand("GetPetName", this.sqlcn))
{
cmd.CommandType = commandType.StoredProcedure;

ll Bemeneti paraméter.
SqlParameter pararn = new SqlParameter();
param.ParameterName = "@cariD";
param.sqlDbType = SqlDbType.Int;
param.value = cariD;
param.Direction = ParameterDirection.Input;
cmd.Parameters.Add(param);

ll Kimeneti paraméter.
pararn = new SqlParameter();
param.ParameterName = "@petName";
param.sqlDbType = SqlDbType.Char;
param.size = 10;
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);

ll A tárolt eljárás futtatása.


cmd.ExecuteNonQuery();

ll visszatérés a kimeneti paraméterrel.


carPetName = ((string)cmd.Parameters["@petName''].value).Trim();
}
return carPetName;
}

Az első fontos tudnivaló a tárolt eljárások meghívásáról az, hogy a parancs­


objektum SQL-utasítást (ez az alapértelmezett) vagy a tárolt eljárás nevét
képviselheti.
Ha szeretnénk tájékoztatni a parancsobjektumot arról, hogy tárolt eljárást
kell meghívnia, meg kell adnunk az eljárás nevét (a konstruktor argumentu­
mában vagy a commandText tulajdonságon keresztül), és be kell állítanunk a

151
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

commandType tulajdonságot a commandType.storedProcedure értékre (ha ezt el­


felejtjük beállítani, futásidejű kivételt kapunk, hiszen a parancsobjektum
alapértelmezés szerint SQL-utasítást vár):

Sqlcommand cmd = new Sqlcommand("GetPetName", this.sqlcn);


cmd.commandType = commandType.storedProcedure;

A paraméterobjektum Direction tulajdonságával lehet megadni, hogy a tá­


rolt eljárásnak megadott különböző paraméterek milyen irányban közleked­
nek (pl. bemeneti paraméter és kimeneti paraméter). A korábbiakhoz hason-
. lóan minden egyes paramétert hozzáadunk a parancsobjektum paraméter­
. gyÜjteményéhez:

ll Bemeneti paraméter.
sqlParameter param = new SqlParameter();
·param.ParameterName = "@cariD";
pa.ram.sqlDbType = sqlDbType.rnt;
param.value = cariD;
param.Direction = ParameterDirection.Input;
cmd.Parameters.Add(param);

Végül, ha a tárolt eljárást az ExecuteNonQuery() metódus hívásával hajtjuk


végre, akkor a kimeneti paraméter értékét a parancsobjektum paramétergyűj­
teményének vizsgálatával és a megfelelő kasztolással kapjuk meg:

ll visszaadjuk a kimeneti paramétert.


carPetName = (string)cmd.Parameters["@petName"].value;

Forráskód Az AutaLotDAL kódfájokat a forráskódkönyvtárban a 22. fejezet alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Parancssoros front end létrehozása

Az AutoLotDAL.dll adatelérési könyvtár első iterációjával elkészültünk A sze­


relvény használatával bármilyen front endet létrehozhatunk adataink megjele­
nítésére és módosítására (konzolalapú, Windows Forms-alapú, WCF-alkalma­
zásokat vagy egy HML-alapú webalkalmazást). Mivel még nem vizsgáltuk
meg, hogyan kell grafikus felhasználói felületet készíteni, az adatkönyvtárun­
kat az új AutoLotcurclient nevű konzolalkalmazásból teszteljük Amikor létre­
hozzuk a projektet, mindenképpen hivatkozzunk az AutaLotDAL. dll szerel­
vényre, valamint a system.confi guration.dll szerelvényre, és módosítsuk a
using utasításainkat a következők szerint:

152
Parancssoros front end létrehozása

using AutoLotConnectedLayer;
using system.configuration;
using System.Data;

Szúrjunk be új App. config fájlt a projektbe, amely <connectionstring> elemet


tartalmaz. Az elem segítségével csatlakozzunk az AutoLot adatbázispéldá­
nyukhoz, például:

<configuration>
<connectionstrings>
<add name ="AutoLotSqlProvider" connectionString =
"Data source=(local)\SQLEXPRESS;" +

"Integrated security=SSPI;Initial catalog=AutoLot"/>


</connectionstrings>
</configuration>

A Main() metódus implementálása

A Main() metódus feladata, hogy a felhasználótól bekérje a kívánt műveletet,


és a kérést a switch utasítás segítségével végrehajtsa. A program a következő
parancsok meghatározását teszi lehetövé a felhasználó számára:

• I : Új rekord beszúrása az Inventory táblába.

• u: Létező rekord módosítása az Inventory táblában.

• D: Létező rekord törlése az Inventory táblából.

• L: Az aktuális raktárkészlet megjelemtése adatolvasó segítségéveL

• s: Megjeleníti ezeket a lehetőségeket a felhasználó számára.

• P: Azonosító alapján kikeresi az autó nevét.

• Q: Kilép a programból.

Az összes lehetőséget a Program osztály egyik statikus metódusa kezeli. Itt ta­
lálható a Main() teljes implementációja. Figyeljük meg, hogy a do/whil e cik­
lusból meghívott metódusok (a sh owinstruction() metódus kivételével) egy
InventoryDAL objektumot kapnak egyetlen paraméterként:

static void Main(string[] args)


{
console.writeLine("***** The AutaLot console UI *****\n");

153
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

ll A kapcsolatsztring lekérdezése az App.config fájlból.


string cnstr =
configurationManager.ConnectionStrings["AutoLotSqlProvider"].
connectionstring;
bool userDone = false;
string usercommand= "";

ll Az InventoryDAL <:lbjektumunk
,
létrehozása.
InventoryDAL invDAL = new InventoryDAL();
invDAL.OpenConnection(cnStr);

ll Bemenetet kérünk a felbasználótól,


��Yamí g nem nyomj a meg' ,�.'J�.:;petűt.
{
showinstructions();
do
{
console.write("P.le!1Se enter your command: ");
usercommand= Console.ReadLine();
console.writeLine();
switch (usercommand.ToUpper())
{
case .. I":
InsertNewcar(invDAL);
break;
case "u":
UpdatecarPetName(invDAL);
break;
case "o":
Deletecar(invDAL);
break;
case "L":
Listinventory(invDAL);
break;
case " s ":

showinstructions();
break;
case "P":
LookUpPetName(invDAL);
break;
case "Q":
userDone true;
break;
default:
Console.writeLine("Bad data! Try again");
break;
}
} while (!userDone);
}

154
Parancssoros front end létrehozása

catch (Exception ex)


{
console.writeLine(ex.Message);
}
final ly
{
invDAL.Closeconnection();
}
}

A Showlnstructions() metódus implementálása

A showinstructions() metódus pontosan azt teszi, amire a nevéből következ­


tethetünk:

private static void showinstructions()


{
console.writeLine("I: Inserts a new car.");
Console.writeLine("U: Updated an existing car.");
console.writeLine("D: Deletes an existing car.");
Console.writeLine("L: List current inventory.");
console.writeLine("s: Show these instructions.");
console.writeLine("P: Look up pet name.");
console.writeLine("Q: Quits program.");
}

A Listlnventory() metódus implementálása

A Listinventory() metódus DataTable objektumot olvas be az InventoryDAL


objektum GetAllinventory() metódusától. Ezt követően meghívunk egy (még
megvalásításra váró) függvényt, a DisplayTable()-t:

private static void Listinventory(InventoryDAL invDAL)


{
ll A raktárlista lekérdezése.
DataTable dt = invDAL.GetAllinventory();
DisplayTable(dt);
}

A DisplayTableO segédmetódus megjeleníti a táblaadatokat a bemenetként


megkapott DataTab le Rows és columns tulajdonságainak a felhasználásával (a
DataTab le objektum részleteit lásd a következő fejezetben).

155
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

private static void DisplayTable(DataTable dt)


{
ll Az oszlopnevek kiírása.
for (int curcol = O; curcol < dt.Columns.count; curcol++)
{
console.write(dt.columns[curcol].ColumnName.Trim() + "\t");
}
console.Writeline("\n----------------------------------");

ll A DataTable kiírása.
for (int curRow O; curRow
= < dt.Rows.count; curRow++)
{
for (int curcol= O; curcol < dt.Columns.count; curcol++)
{
Console.write(dt.Rows[curRow][curcol].ToString().Trim() +
"\t");
}
console.writeLine();
}
}

A DeleteCarO metódus implementálása

Létező autó törlésekor meg kell kérdezni a felhasználótól a törölni kívánt au­
tó azonosítóját és az értéket átadni az rnventoryDAL típus DeletecarO metó­
dusának:

private static void DeleteCar(InventoryDAL invDAL)


{
ll A törölni kívánt autó azonosítójának a beolvasása.
console.Write("Enter ID of car to delete: ");
int id= int.Parse(Console.ReadLine());

ll Arra az esetre, ha megsértenénk


ll az elsődleges kulcsot.
try
{
invDAL.Deletecar(id);
}
catch(Exception ex)
{
console.writeLine(ex.Message);
}
}

156
Parancssoros front end létrehozása

Az InsertNewCarO metódus implementálása

Új rekord beszúrásához be kell kérni a felhasználótól a szükséges adatokat


(Console.ReadLineO hívásokkal), és átadni az InventoryDAL InsertAu toO me­
tódusának:

private static void InsertNewcar(InventoryDAL invDAL)


{
ll Először kérjük be a felhasználói adatokat.
int newcariD;
string newcarcolor, newcarMake, newcarPetName;

console.write("Enter car ID: ");


newcariD = int.Parse(console.ReadLine());
Console.write("Enter car color: ");
newcarcolor = console.ReadLine();
console.write("Enter car Make: ");
newcarMake = Console.ReadLine();
console.write("Enter Pet Name: ");
newcarPetName = console.ReadLine();

ll Adjuk át az adatelérési könyvtárnak.


invDAL.InsertAuto(newcariD, newcarcolor, newcarMake,
newcarPetName);
}

Az UpdateCarPetName() metódus implementálása

Az updatecarPetNameO metódus implementálása nagyon hasonlít az előző


metódushoz:

private static void updateCarPetName(InventoryDAL invDAL)


{
ll Először kérjük be a felhasználói adatokat.
int cariD;
string newcarPetName;

console.write("Enter car ID: ");


cariD = int.Parse(console.ReadLine());
console.write("Enter New Pet Name: ");
newcarPetName = console.ReadLine();

ll Adjuk át az adatelérési könyvtárnak.


invDAL.UpdatecarPetName(cariD, newcarPetName);
}

157
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A tárolt eljárásunk meghivása

Az autó nevének lekérdezése ugyancsak nagyon hasonlít az eddi gi metódu­


sok működéséhez, hiszen az adatelérési programkönyvtár minden alsóbb
szintű ADO.NET-hívást beágyazott:

private static void LookUpPetName(InventoryDAL invDAL)


{
ll A keresett autó azonosítójának beolvasása.
console.write("Enter ID of car to look up: ");
int id= int.Parse(console.ReadLine());
Con sol e.writeLine(" Petname of {O} is {l}.",
id, invDAL.LookUpPetName(id));
}

22. 16. ábra: Rekordok beszúrása, módosítása és törlése parancsobjektumok segítségével

158
Aszinkron adatelérés az SqlCommand használatával

Ezzel a parancssati front end elkészült. A 22.16. ábra egy tesztfuttatást mutat be.

Forráskód Az AutoLotCUIClient kódfájlokat a forráskódkönyvtárban a 22. fejezet alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Aszinkron adatelérés az SqlCommand


használatával

Minden jelenlegi adatelérési logikánk egyetlen szálon fut. A .NET 2.0 megje­
lenése óta az SQL-adatszolgáltató fejlődött, és támogatja az aszinkron adatbá­
ziskezelést az sqlcommand újabb tagjainak a segitségével:

• BeginExecuteReader()/EndExecuteReader()

• BeginExecuteNonQuery()/EndExecuteNonQuery()

• BeginExecutexmlReader()/EndExecuteXmlReader()

Az előző kötet fejezetében megvizsgáltuk a metóduspárok névadási kon­


18.
vencióját A .NET aszinkron metódusreferencia mintája a "begin" metódussal
futtat másodiagos szálon egy feladatot, az "end" metódus pedig arra szolgál,
hogy az aszinkron módon meghívott művelet eredményét beolvassa az
IAsyncResult interfész tagjai és az opcionális Asynccallback metódusreferen­
cia segítségéveL Mivel az aszinkron parancsokkal történő munka folyamata a
standard metódusreferencia-minták alapján működik, ezt egy egyszerű pél­
dával mutatjuk be (a 18. fejezet részletesen tárgyalja az aszinkron metódusre­
ferenciákat).
Tegyük fel, hogy adatolvasó objektum segítségével szeretnénk lekérdezni
az Inventory tábla rekordjait egy másodiagos végrehajtási szálon. A teljesen
új Asynccmdobject:App parancssori alkalmazás (amely nem használja az Inven­

toryDAL. dll szerelvényünket) a következő:

Megjegyzés Ha aszinkron módon szeretnénk engedélyezni az adatelérést, módosítani kell a


kapcsolatsztringet és beállítani az Async h rono u s Prosessi n g = true szegmenst (az alapér·
telmezett érték a false).

159
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

static void Main(string[] args)


{
console.writeLine("***** Fun with ASNYC Data Readers *****\n");

ll Hozzunk létre és nyissunk meg egy aszinkron működésre


ll képes kapcsolatot.
sqlConnection cn = new Sqlconnection();
cn.Connectionstring =
@''Data source=(local)\SQLEXPRESS;Integrated security=SSPI;" +

"rnitial catalog=AutoLot;Asynchronous Processing=true";


cn .open();

ll Hozzunk létre egy SQL-parancsobjektumot, amely körülbelül


ll 2 másodpercig várakozik.
string strSQL = "WaitFor Delay '00:00:02';
Select * From Inventory";
sqlcommand mycommand = new Sqlcommand(strSQL, cn);

ll Futtassuk az olvasót a második szálon.


IAsyncResult itfAsynch;
itfAsynch =
mycommand.BeginExecuteReader(commandBehavior.Closeconnection);

ll csináljunk valamit, amíg a másik szál dolgozik.


while (!itfAsynch.rscompleted)
{
console.WriteLine("Working on main thread...");
Thread.sleep(lOOO);
}
console.WriteLine();

ll Kész. Kérjük le az olvasót, és menjünk végig az eredményeken.


sqlDataReader myDataReader =
myCommand.EndExecuteReader(itfAsynch);
while (myDataReader.Read())
{
Console.writeLine("-> Make: {O}, PetName: {1}, color: {2}.",
myDataReader["Make"] .ToString(). Trim(),
myDataReader ["PetName"] .ToString().Trim(),
myDataReader["color"] .ToString().Trim());
}
myDataReader.close();
}

Az első érdekes pont az, hogy a kapcsolatsztring Asynchronous Processing


szegmensén keresztül engedélyezni kell az aszinkron működést. Az sqlcom­
mand objektum parancsszövegébe beágyaztunk egy waitFor Delay szegmenst,

hogy egy hosszan futó adatbázis-roűveletet szimuláljunk.

160
Az adatbázis-tranzakciók

A Begi nExecuteDataReader() hívása a kívánt IAsyncResul t-kompatibilis


típussal tért vissza, amelyet arra használunk, hogy a hívószállal szinkronizál­
junk (az rscompleted tulajdonságorr keresztül), illetve megkapjuk az SqlData­
Readert, ha a lekérdezés befejeződött_

Forráskód Az AsyncCmdObjectApp kódfájokat a forráskódkönyvtárban a 22. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv.oldalát.

Az adatbázis-tranzakciók

A tranzakció adatbázis-műveletek halmaza, amelyek mindegyike vagy sikere­


sen végrehajtódik, vagy mindegyike sikertelen. A tranzakciók meglehetősen
fontosak, mert biztosítják a táblaadatok biztonságát, érvényességét és kon­
zisztenciáját.
A tranzakciók nagyon lényegesek, ha az adatbázis-művelet több táblát
érint, vagy több tárolt eljárás kezelését foglalja magában (vagy adatbázisato­
mok kombinációját). A klasszikus tranzakció mintapéldája két bankszámla
közötti átutalások folyamatát mutatja be. Tételezzük fel, hogy 500 dollárt uta­
lunk át a takarékszámlánkról a folyószámlánkra. A rendszer a következő
tranzakciós lépéseket hajtja végre:

• A bank 500 dollárt levon a takarékszámlánkróL

• A bank 500 dollárt könyvel a folyószámlánkra.

Kellemetlen lenne, ha a takarékszámláról már levették volna a pénzt, de a folyó­


számlára még nem utalták volna át (valamilyen belső banki hiba miatt), és így
elveszítenénk 500 dollárt. Ha ezeket a lépéseket adatbázis-tranzakcióban fogjuk
össze, akkor az adatbáziskezelő rendszer biztosílja, hogy minden kapcsolódó lé­
pést egyetlen egységként kezeljen. Ha a tranzakció bármely része meghiúsul, a
rendszer az egész műveletet " visszagörgeti" az eredeti állapotba. Másrészről, ha
minden lépés sikeres volt, a rendszer "jóváhagyja" a tranzakciót.

Megjegyzés Az ACID betűszó egy tranzakció négy kulcsfontosságú tulajdonságára utal: az


atomicitásra (mindent vagy semmit), a konzisztenciára (az adat állandó marad a tranzakció so­
rán), az izotációra (a tranzakciók nem lépnek egymás "lábára") és a tartásságra (a tranzakció­
kat a rendszer menti és naplózza).

161
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

A .NET platform különbözőképpen támogatja a tranzakciókezelést Számunk­


ra rnost ezek közül a legfontosabb az ADO.NET-adatszolgáltató tranzak­
cióobjektuma (a system.Data.sqlclient esetén az SqlTransaction). A .NET­

alaposztálykönyvtárak több API segítségével biztosítanak tranzakciós támoga­


tást, ezek például a következők:

• system. Enter priseservices: A névtér olyan típusokat szolgáltat, arne­

lyekkel az alkalmazást a COM+ futtató réteggel integrálhatjuk, és ez


magában foglalja a COM+ elosztott tranzakciók támogatását is.

• system. Transacti ons: A névtér osztályai lehetővé teszik, hogy saját

tranzakciós alkalmazásokat és erőforrás-kezelő programokat írjunk


különböző szolgáltatások számára (MSMQ ADO.NET, COM+ stb.).

• Windows Communication Foundation: A WCF API tranzakciókezelést


biztosító szolgáltatásokat nyújt.

• Windows Workflow Foundations: A WF API workflow-tevékenységek


számára nyújt tranzakciós támogatást.

A .NET-alaposztálykönyvtárba beépített tranzakciós támogatás rnellett az


adatbáziskezelő rendszer SQL-nyelvét is alkalmazhatjuk ilyen célokra. Írha­
tunk például egy olyan tárolt eljárást, amely használja a BEGIN TRANSACTION, a
ROLLBACK és a COMMIT utasításokat.

Az ADO.NET-tranzakcióobjektum
kulcsfontosságú tagjai

Noha tranzakciókezelő típusok léteznek az alaposztálykönyvtárban, az


ADO.NET-adatszolgáltatóban található tranzakcióobjektumok rnindegyike a
DBTransaction osztályból származik, és az IDbTransacti on interfészt imple­

mentálja. Az IDbTransaction több tagot definiál:

public interface lObTransaction : IDisposable


{
robconnection connection { get; }
Isolationlevel IsolationLevel { get; }
void commit();
void Rollback();
}

162
Az adatbázis-tranzakciók

Mindenekelőtt vizsgáljuk meg a connecti on tulajdonságot. A tulajdonság a


kapcsolatobjektum referenciáját adja vissza, amely az aktuális tranzakciót in­
dította (ahogyan majd látni fogjuk, az adott kapcsolatobjektumtól kapjuk a
tranzakcióobjektumot). A commit() metódust akkor hívjuk, ha minden adat­
bázisműveletünk sikeresen lefutott. Így minden, eddig függőben levő módo­
sítást a rendszer rögzít az adattárolóban. A Rollback() metódust ellenben
akkor hívjuk, ha futásidejű kivétel lép fel, amely tájékoztatja az adatbáziske­
zelő rendszert, hogy minden függőben levő módosítást hagyjon figyelmen
kívüt és az eredeti adatokat hagyja érintetlenül.

Megjegyzés A tranzakcióobjektum lsolationLevel tulajdonságával beállíthatjuk, hogy milyen


agresszíven k ell védeni a tranzakciónkat a párhuzamosan futó tranzakciók műveleteivel szem­
ben. Az alapértelmezés szerint a tranzakciók a véglegesítésig teljesen izoláltak. Az Isol ati on­
Level felsorolt típus értékeivel kapcsolatban bővebb információkat a .NET Framework 3.5 SDK
dokumentációjában találhatunk.

Az IDbTransacti on interfész által definiált tagokon kívül az sqlTransacti on

típus még a save() tagot definiálja, amely mentési pontok elhelyezését teszi le­
hetővé a tranzakción belül. Ez a megközelítés megoldja, hogy hiba esetén
csak a megnevezett pontig kelljen visszagörgetni a tranzakciót ahelyett, hogy
a rendszer az egész tranzakciót visszagörgetné. Amikor az sqlTransacti on

objektum segítségével meghívjuk a save() metódust, egy barátságos sztring­


monikert definiálhatunk. A Roll b ack() hívásakor argumentumként megad­
hatjuk ugyanezt a monikert, amelynek segítségével "részleges visszagörge­
tést" hajtunk végre. Ha argumentum nélkül hívjuk meg a Rollback() metó­
dust, akkor a rendszer visszagörget minden függőben levő módosítást.

Tranzakciómetódus hozzáadása az lnventoryDAL


osztályhoz

Az ADO.NET-tranzakciók bemutatására indítsunk el a Server Explorert a Vi­


sual Studio 2008-ban, és adjuk a CreditRisks táblát az AutoLot adatbázisunk­
hoz. A tábla ugyanazokkal az oszlopokkal rendelkezik, mint a fejezet korábbi
részében elkészített Customer tábla (CustiD [ez az elsődleges kulcs], First­
Name és LastNamer). Ahogyan a neve sugallja, a táblába száműzzük azokat
a nemkívánatos vásárlókat, akik valamilyen okból nem feleltek meg a hitel­
képességi vizsgálatnak

163
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Megjegyzés Ezt az új tranzakciós funkcionalitást használjuk majd a 26. fejezetben, ahol a


Windows Workflow Foundation API·t vizsgáljuk, ezért a CreditRisks táblát az itt leírak szerint
adjuk az Autalot adatbázishoz.

A korábban látott átutalási példához hasonlóan a nem megbízható ügyfelek


átmozgatását a Customer táblából a CreditRisks táblába szintén tranzakciós
hatókör felügyelete alatt kell végrehajtani (hiszen azoknak az azonosítóját és
a nevét szeretnénk megjegyezni, akik nem hitelképesek). Biztosítanunk kell,
hogy vagy sikeresen töröljük a megbízhatatlannak minősített vásárlókat a
Customers táblából, és beszúrjuk őket a CreditRisks táblába, vagy egyik adat­
bázis-roűveletet sem hajtuk végre.
Megmutatjuk, hogy hogyan lehet programozottan használni az ADO.NET­
tranzakciókat. Nyissuk meg a fejezet korábbi részében létrehozott AutaLotDAL
kódkönyvtárprojektünket. Adjuk hozzá az rnventoryDAL osztályhoz az új Pra­
eessereditRisk() metódust, amely a következőképpen kezeli a nem hitelképes

ügyfeleket:

ll AZ rnventoryDAL osztály új tagja.


public void ProcesscreditRisk(bool throwEx, int custi D)
{
ll Az ügyfél azonosítója alapján megkeressük a nevet.
string fName string.Empty;
=

string lName string.Empty;


=

SqlCommand cmdselect new SqlCommand(


=

stri n g. Format (" Sel e ct * from customers


where cu stiD {O}", custiD), sqlcn);
using (SqlDataReader dr = cmdselect.ExecuteReader())
{
while (dr.Read())
{
fName (st ri n g) dr["Fi rstName"] ;
lName (strin g )dr[" La stName" ] ;
}
}

ll Hozzunk létre parancsobjektumokat, amelyek az egyes műveleti


ll lépéseket képviselik.
Sqlcomma nd cmdRemove new SqlCommand(
=

string.Format("Delete from customers where custiD =


{O}", custiD),
sqlcn);
sqlcommand cmdrnsert = new Sqlcommand(string.Format("rnsert rnto
CreditRisks" +

"(CustiD, FirstName, LastName) values" +

"({O}, '{1}', '{2}')", custiD, fName,


lName), sqlcn);

164
Az adatbázis-tranzakciók

ll Ezt a kapcsolatobjektumtól kapjuk meg.


SqlTransaction tx = null;
try
{
tx = sqlcn.BeginTransaction();

ll Igénybe vesszük a parancsokat a tranzakcióban.


cmdinsert.Transaction = tx;
cmdRemove.Transaction = tx;

ll A parancsok futtatása.
cmdinsert.ExecuteNonQuery();
cmdRemove.ExecuteNonQuery();

ll Hiba szimulálása.
if (throwEx)
{
throw new ApplicationException("Sorry! Database error!
Tx failed ...");
}

ll véglegesítsünk.
tx.CommitO;
}
catch (Exception ex)
{
console.writeLine(ex.Message);
ll Bármilyen hiba visszagörgeti a tranzakciót.
tx. RollbackO;
}
}

Ebben az esetben bool típusú bemeneti paraméterben jelöljük meg azt, hogy
szeretnénk tetszőleges kivételt kiváltani, ha megpróbáljuk a megbízhatatlan
vásárlót kezelni. Így könnyedén szimulálhatunk egy olyan előre nem látható
körülményt, amely miatt az adatbázis-tranzakció végrehajtása sikertelen lesz.
Természetesen ezt most csak a bemutató kedvéért tesszük, egy valós adatbá­
zis-tranzakciós metódus nem engedi a hívónak, hogy tetszés szerint meghiú­
sítsorr egy logikát.
Miután megkerestük a vásárló vezeték- és keresztnevét a bemeneti custiD
paraméter alapján, két sqlcommand objektumot használunk, amelyek a tranz­
akció lépéseit képviselik, és a kapcsolatobjektumtól a BeginTransactionO me­
tódus segítségével érvényes SqlTransacti on objektumot kapunk. Következő
fontos lépés, hogy minden parancsobjektumnak állítsuk be a Transaction tulaj­
donságát: mégpedig a BeginTransacti onO eredményeként kapott tranzakció­
objektumra. Ha ezt nem tesszük meg, a beszúrás/törlés logika nem lesz ben­
ne a tranzakciós kontextusban.

165
22. fejezet: ADO.NET, 1. rész: Az élő kapcsolat

Miután minden parancsan meghívtuk az ExecuteNonQuery() metódust,

kivételt jelzünk akkor (és csak akkor), ha a bool paraméter értéke igaz. Ebben
az esetben a rendszer minden függő adatbázis-roűveletet visszagörget. Ha
nem jelzünk kivételt, a rendszer mindkét lépést jóváhagyja az adatbázistáb­
lákban a commit() metódus meghívásakor. Fordítsuk le a módosított Auto­
LotDAL projektünket, hogy elkerüljük a gépelési hibákat.

Az adatbázis-tranzakciónk tesztelése

Noha olyan új opcióval módosíthatnánk a korábban megírt AutoLotCUICL ient


alkalmazásunkat, amely meghívja a PraeessereditRi sk() metódust, hozzuk
létre inkább az új AdoNetTransaction parancssori alkalmazást. Állítsunk be re­
ferenciát az AutaLotFAL.dll szerelvényünkre, és importáljuk az AutoLotcon­
nectedLayer névteret

Nyissuk meg a Customers táblánkat, hogy új adatokat tudjunk bevinni.


Ehhez a Server Explorerben jobb egérgombbal kattintsunk a táblaikonra, és
válasszuk a Show Table Data menüpontot. Adjunk hozzá egy új vásárlót, aki
alacsony hitelképességi mutatóval rendelkezik, például:

• CustiD: 333

• FirstName: Horner

• LastName: Simpson

Most módosítsuk a következőképpen a MainO metódusunkat.

static void Main(string[] args)


{
Console.WriteLine("***** Simple Transaction Example *****\n");

ll Egyszerű módja annak, hogy megengedjük a tranzakciónak,


ll hogy sikeres legyen vagy sem.
bool throwEx = true;
string userAnswer = string.Empty;
Console.write("Do you want to throw an exception (Y or N): ");
userAnswer = Console.ReadLine();
if (userAnswer.ToLower() == "n")
{
throwEx = false;
}
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data Source=(local)\SQLEXPRESS;Integrated
security=SSPI;" + "rnitial catalog=AutoLot");

166
Összefoglalás

ll Dolgozzuk fel a 333-as vásárló adatait.


dal.ProcesscreditRisk(throwEx, 333);
Console.ReadLine();
}

Ha futta�uk a programunkat, és a kivételjezés opcióját válasz�uk, akkor lát­


ha�uk, hogy Homert a rendszer nem törölte a Customers táblából, hiszen az
egész tranzakciót visszagörgette. Ha azonban nem jelzünk kivételt, akkor a
333-as azonosítójú vásárló nincs a Customers táblában, hanem átkerült a
credi tRi sks O táblába (lásd a 22.17. ábrát).

treditRisks: Quer.Jexpress.Autolot) :;:x

CustiD FirstName lastName

ill Homer Simpson

* !NULL NULL NULL

l� � ll ofll� �Ih!@!
22.17. ábra: Az adatbázis-tranzakciónk eredménye

Forráskód Az AdoNetTransaction kódfájlokat a forráskódkönyvtárban a 22. fejezet alkönyv­


tára tartalmazza. A forráskódkönyvtárról Lásd a Bevezetés xlv.oldalát.

Összefogla lás

Az ADO.NET a .NET platform egy olyan natív adatelérési technológiája,


amelyet két különböző megközelítésben használhatunk: élő kapcsolatalapú
és kapcsolat nélküli módon. Ebben a fejezetben a kapcsolatalapú modellt
vizsgáltuk meg, és tanulmányoztuk az adatszolgáltatók szerepét, amelyek
valójában absztrakt ősosztályok (a system . Data. common névtérben) és inter­
fésztípusok (a system. Data névtérben) tényleges implementációi. Ahogyan
láthattuk, az ADO.NET data provider factory modell segítségével lehetőség
van szolgáltatófüggetlen kód megírására.
A kapcsolatalapú modell kapcsolat-, tranzakció-, parancs- és adatolvasó
objektumainak segítségével lehetőségünk nyílik rekordokat lekérdezni, mó­
dosítani, beszúrni és törölni. A parancsobjektumok belső paramétergyűjte­
ményt támogatnak, amelynek a segítségével gondoskodhatunk SQL-lekérde­
zéseink típusbiztonságáról, és meglehetősen hasznosnak bizonyul a tárolt el­
járások hívásakor.

167
HUSZONHARMADIK FEJEZET

ADO.NET, 2. rész:
A bontott kapcsolat

Ebben a fejezetben az előző folytatásaként az ADO.NET kapcsolat nélküli madell­


jét ismerhe�ük meg. Ennek az aspektusnak a használatával a memóriában a
hívórétegen belül modellezhe�ük az adatbázisadatokat a system. oata névtér
tagjai (főként a Dataset, a DataTab le, a DataRow, a Datacol umn, a Dataview és a
DataRelation ) segítségéveL Így azt az illúziót kelthe�ük, hogy a hívóréteg fo­
lyamatosan kapcsolatot tart fenn az adatforrással, ám a valóságban egyszerűen
a relációs adatok helyi másolatát használja.
Noha az ADO.NET-nek ezt a "kapcsolat nélküli" aspektusát úgy is lehet
használni, hogy egyáltalán nem hozunk létre valódi kapcsolatot egy relációs
adatbázissal, ám leggyakrabban az adatszolgáltatónk adatillesztő objektumá­
val fogunk feltöltött Dataset objektumokat megszerezni. Az adatillesztő objek­
tumok hídként működnek a kliensréteg és egy relációs adatbázis között. Ezek­
kel az objektumokkal megszerezhetünk Dataset objektumokat, kezelhe�ük a
tartalmukat, és a módosított sorokat feldolgozásra visszaküldhe�ük. A vég­
eredmény egy pontosan skálázható, adatcentrikus .NET-alkalmazás lesz.
A kapcsolat nélküli modell előnyeinek bemutatásához új névterekkel egé­
szí�ük ki a 22. fejezetben létrehozott AutaLotDAL. dll adatkönyvtára t, amely a
kapcsolat nélküli modell nyújtotta típusokat használja. Megvizsgáljuk az
adatkötés és a Windows Forms API-n belül lévő felhasználóifelület-elemek
szerepét: ezek teszik lehetővé a kliensoldali lokális adatok megjelenítését és
módosítását. Végül megvizsgáljuk az erősen típusos Dataset objektumok
funkcióját, és megnézzük, hogyan lehet egy objektumorientáltabb modell se­
gítségéve! felhasználni őket adatok kinyeréséhez.
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Az ADO.NET kapcsolat nélküli modellje

A kapcsolatalapú modell egy adatbázissal a munkát az elsődleges kapcsolat-, a


parancs- és az adatolvasó objektumok révén teszi lehetővé. Ezekkel a típusok­
kal tetszés szerint kiválaszthatunk, beszúrhatunk, módosíthatunk és törölhe­
tünk rekordokat (valamint hívhatunk tárolt eljárásokat). Ezzel az ADO.NET­
nek csak az egyik oldalát ismertük meg. Az ADO.NET-objektummodellt
ugyanis kapcsolat nélkül is használha�uk.
A kapcsolatot nem igénylő típusokkal a memóriában elhelyezett objek­
tummodell révén is modellezhe�ük a relációs adatokat. A sarok és az oszlo­
pok táblázatos blokkjának egyszerű modellezése mellett a system. Data típu­
sai lehetővé teszik, hogy táblázatkapcsolatokat, oszlopmegszorításokat, el­
sődleges kulcsokat, nézeteket és egyéb adatbázis-primitíveket is ábrázoljunk
Az adatok modellezése után alkalmazhatunk szűrőket, futtathatunk memó­
riabeli lekérdezéseket, és menthetünk (vagy betölthetünk) adatokat XML- és
bináris formátumban is. Mindezekhez nem kell valódi kapcsolatot létrehoz­
nunk egy DBMS-hez sem (innen jön a kapcsolat nélküli modell kifejezés).
Jóllehet valóban használha�uk a kapcsolat nélküli típusokat anélkül, hogy
csatlakoznánk egy adatbázishoz, többnyire mégis használjuk a kapcsolat- és a
parancsobjektumokat. Emellett egy bizonyos objektum, az adatillesztő (amely
kibővíti az absztrakt DbDataAdapter osztályt) segítségével kérhetünk le és mó­
dosíthatunk adatokat. A kapcsolatalapú modellel ellentétben az adatillesztőn
keresztül érkezett adatokat a rendszer nem adatolvasó objektumokkal dolgoz­
za fel, hanem ehelyett az adatillesztő objektumok a Dataset objektumokat
használják adatok mozgatására a hívó és az adatforrás között. A Dataset típus
olyan tetszőleges számú DataTable objektum számára szaigál tárolóként, ame­
lyek mindegyike DataRow és Datacolumn objektumok gyűjteménye.
Az adatszolgáltatónk adatillesztő objektuma automatikusan kezeli az
adatbázis-kapcsolatot. A skálázhatóság tökéletesítése érdekében az adatil­
lesztök a lehető legkevesebb ideig tar�ák nyitva ezt a kapcsolatot. Amint a
hívó megkapja a Dataset objektumot, a hívóréteg bon�a a kapcsolatot az
adatbázissal, és mindössze a távoli adatok helyi másolatát őrzi meg. A hívó
egy adott DataTable objektumban nyugodtan beszúrhat, törölhet vagy módo­
síthat sorokat, de maga az adatbázis nem frissül addig, amíg a hívó explicit
módon át nem adja frissítésre a Dataset típust az adatillesztőnek Röviden, a
Dataset típusok azt a látszatot keltik a kliens számára, hogy azok folyamato­
san kapcsolatban állnak az adatbázissal, ám valójában egy memóriában tárolt
adatbázissal dolgoznak (lásd a 23.1. ábrát).

170
A DataSet szerepe

23.1. ábra: Az adatillesztő objektumok mozgatják a DataSet típusokat az kliensrétegben

Mivel a kapcsolat nélküli modell legfontosabb eleme a Dataset típus, ezért


ebben a fejezetben először azt vizsgáljuk meg, hogy hogyan kezelhetjük ma­
nuálisan a típust. Ezután nem okoz majd problémát az adatillesztő objektum­
tól lekért Dataset tartalmának a kezelése.

A DataSet szerepe

A Dataset a relációs adatok memóriabeli megjelenítése. Pontosabban, a


Dataset olyan osztálytípus, amely három belső, erősen típusos gyűjteményt
(lásd a 23.2. ábrát) tart karban.

DataRelationCollection

23.2. ábra: A DataSet anatómiája

A Dataset típus tulajdonsága teszi lehetövé, hogy hozzáférjünk az


Tabl es
egyes DataTable objektumokat tartalmazó DataTablecollection gyűjtemény­
hez. A Dataset által használt másik fontos gyűjtemény a DataRelationcol lee­
tion. Mivel a Dataset egy adatbázisséma leválasztott változata, segítségével
programozottan megjeleníthetjük a szülőj gyermek kapcsolatot az adatbázis
táblái között. A DataRel ation típus használatával például létre lehet hozni
olyan kapcsolatot két táblázat között, amely idegenkulcs-korlátozást model­
lez. Ezt az objektumot aztán a Relations tulajdonság segítségével hozzáad­
hatjuk a DataRelationcollection gyűjteményhez. Ezután már tudunk navi­
gálni a két összekapcsolt tábla között, adatkereséskor a későbbiekben meg­
nézzük ennek mikéntjét is.

171
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Az ExtendedProperties tulajdonság hozzáférést biztosít a Propertycoll ee­


tion objektumhoz, amely név j érték párként lehetővé teszi bármilyen további
információ társítását a Datasethez. Ez az információ szó szerint bármi lehet,
még akkor is, ha nem kapcsolódik szorosan az adatokhoz. Társítha�uk pél­
dául a cégünk nevét a Datasethez, amely így memóriabeli metaadatként mű­
ködhet. A bővítő tulajdonságok további példái az időbélyegek, egy olyan tit­
kosított jelszó, amelyet meg kell adni, ha szeretnénk hozzáférni a Dataset tar­
talmához, egy szám, amely az adatfrissítési sebességet jelképezi stb.

Megjegyzés A DataTab l e osztály is támogatja a bővítő tulajdonságokat az ExtendedPro­


perti es tulajdonság révén.

A DataSet alapvető tulajdonságai

Először nézzük meg a Dataset néhány fő tagját. A Tab l es, a Re l ati ons és az
ExtendedProperties tulajdonságok mellett a 23.1. táblázat ismertet néhány
további érdekes tulajdonságot.

casesensitive Azt jelzi, hogy a sztring-összehasonlítások a DataTab l e ob­


jek tumokban kis- és nagybetű érzékenyek-e (vagy sem).

DataSetName A DataSet barátságos nevét képviseli. Ezt az értéket álta­


lában konstruktorparaméterként vezetjük be.

Enforceconstraints Olyan értéket ad meg vagy vesz fel, amely jelzi, hogy va­
lamilyen frissítési művelet végrehajtása során követni kell
a korlátozási szabályokat.

Has Erra r s Felvesz egy értéket, amely jelzi, hogy vannak-e hibák a
DataSet bármelyik DataTab l es objektumának bármelyik

sorában.

Remeting Forrnat Segitségével megadhatjuk, hogy a DataSet hogyan soro­


sítsa tartalmát (bináris vagy XML-formában).

23.1. táblázat: A DataSet tulajdonságai

172
A DataSet szerepe

A DataSet kulcsfontosságú metódusai

A Dataset rnetódusai a fent említett tulajdonságokkal rnűködnek együtt.


Amellett, hogy képes XML-folyarnokkal dolgozni, a Dataset olyan rnetódu­
sokat biztosít, amelyekkel átmásolhatjuk a Dataset tartalrnát, navigálhatunk
a belső táblák között, és megállapíthatjuk egy frissítési köteg kezdő- és vég­
pontjait. A 23.2. táblázat bernutat néhányat az alapvető rnetódusok közül.

AcceptChanges () Jóváhagyja a DataSet betöltése vagy az Acceptchanges ()


legutóbbi meghívása óta végrehajtott összes módosítást.

clear() Teljesen törli a Dataset adatait, eltávolít minden sort


minden egyes DataTa bl e objektumbóL

clone() Klónozza a Dataset struktúráját, beleértve minden Data­


Tabl e objektumot, valamint az összes kapcsolatot és kor­

látozást.

copy() Lemásolja a DataSet struktúráját és adatait.

Getchanges() Visszaadja a Dataset egy olyan másolatát, amely tartal­


maz minden módosítást annak legutóbbi betöltése vagy
az Acceptchanges () meghívása óta.

GetchildRelations() Visszaadja azoknak a gyermekkapcsolatoknak a gyűjte­


ményét, amelyek az adott táblához tartoznak.

GetParentRela ti ons() Visszaadja azoknak a szülökapcsolatoknak a gyűjtemé-


nyét, amelyek az adott táblához tartoznak.

Haschanges() Visszaadja, hogy a Dataset változott-e, beleértve az új, a


törölt és a módosított sorokat is.

Merge() Egyesíti ezt a Dataset típust egy megadott DataSet tí­


pussal.

Readxml () Lehetövé teszi XML-adatok beolvasását egy folyamból


ReadxmlScherna O (fájlalapú, memóriaalapú vagy hálózatalapú) a Datasetbe.

Rej ectchanges() Visszagörgeti a Dataset típuson a létrehozása vagy az


AcceptChanges () legutóbbi meghívása óta végrehajtott

összes módosítást.

writexml() Lehetövé teszi, hogy kiírjuk az Dataset tartalmát egy


writexml Scherna O XML-folyamba.

23. 2. táblázat: A DataSet metódusai

173
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

DataSet létrehozása

Készítsünk egy új parancssori alkalmazást simpleDataset névvel. A Main()


metóduson belül hozzunk létre egy új Dataset objektumot, amely három bő­
vítő tulajdonságot tartalmaz: a cégünk nevét, egy egyedi azonosítót (system.
Guid típus formájában) és egy időbélyeget (ne felejtsük el importálni a sys­

tem.Data névteret):

static void Main(string[] args)


{
console.WriteLine("***** Fun with Datasets *****\n");

ll Hozzuk létre a Dataset objektumot, és adjunk hozzá néhány


ll tulajdonságot.
Dataset carsrnventoryDS = new DataSet("car Inventory");

carsrnventoryDS.ExtendedProperties["TimeStamp"] = DateTime.Now;
carsrnventoryDS.ExtendedProperties["DataSetiD"] = Guid.NewGuid();
carsrnventoryDS.ExtendedProperties["company"]
"rntertech Training";
console.ReadLine();
}

Ha nem vagyunk járatosak a globálisan egyedi azonosítók (GUID) világában,


akkor elég most annyi, hogy a GUID egy egyedi 128 bites szám. Jóllehet eze­
ket széles körben használják a COM keretrendszerben számos COM-atom
(osztályok, interfészek, alkalmazások stb.) azonosítására, a System.GUID tí­
pus továbbra is nagyon hasznos lehet a .NET alatt akkor, ha gyorsan kell
egyedi azonosítót generálnunk.
A következő feladatunk az lesz, hogy megvizsgáljuk a DataTable belső
felépítését, és megismerkedjünk a Datacol umn típussaL

DataColumn tipusok használata

A Datacolumn típus egyetlen oszlopot képvisel egy DataTab le objektumon belül.


Az adott DataTab le objektumhoz tartozó összes Datacol umn jelképezi egy tábla
sémainformációinak az alapját. Ha például modelleznénk az AutaLot adatbázis
Inventory tábláját (lásd a 22. fejezetet), akkor négy Datacolumn típust hoznánk
létre, egyet-egyet minden oszlophoz (CariD, Make, Color és PetName). Amint
létrehoztuk a Datacolumn objektumainkat, tipikusan hozzá szoktuk őket adni a
DataTab le típus oszlopgyűjteményéhez (a col umns tulajdonság révén).

174
DataColumn típusok használata

Egy adatbázis-tábla adott oszlopához kényszereket rendelhetünk (pl. be­


állítás elsődleges kulcsként, alapértelmezett érték hozzárendelése, írásvéde­
lem beállítása stb.). Emellett egy tábla minden oszlopa leképezhető egy
alapadattípusra. Az Inventory táblázat sémája például megköveteli, hogy a
CariD oszlop egy egész szám legyen, rníg a Make, a Color és a PetName ka­
rakterek sorozata. A Datacolumn osztály számos tulajdonsággal rendelkezik,
amelyekkel beállíthatjuk ezeket a jellemzőket. A 23.3. táblázat néhány alap­
vető tulajdonság leírását tartalmazza.

AllowDBNull Ez a tulajdonság azt jelzi, hogy egy sorban az oszlop tar­


talmazhat-e null értéket, vagy sem. Az alapértelmezett
érték: true.

Autorncrement Ezekkel a tulajdonságokkal lehet beállítani, hogy egy


Autorncrementseed adott oszlop értéke automatikusan növekedjen. Ez akkor
Autorncrementstep lehet hasznos, ha biztosítani szeretnénk az egyedi értéke­
ket egy adottDatacol umn típusban (pl. az elsődleges
kulcsnál). Az alapértelmezés szerint a Datacolumn nem
támogatja az automatikus növelést.

Caption Ez a tulajdonság az oszlop címét kérdezi le vagy állítja be.


Lehetövé teszi, hogy meghatározzuk egy adatbázisoszlop
felhasználóbarát nevét.

columnMapping Ez a tulajdonság határozza meg, hogy a rendszer hogyan


ábrázoljon egy D a tacolumn objektumot, ha a Dataset tí­
pust XML-dokumenturnként a Dataset. writexml () me­
tódus segítségével mentettük.

ColumnName Ez a tulajdonság kérdezi le vagy állítja be a Column s gyűj­


teményben lévő oszlop nevét (azt mutatja meg, hogyan
jeleníti meg belsőleg a DataTable) . Ha expliciten nem ál­
lítjuk be a columnName tulajdonságot, akkor az alapértel­
mezett érték a Column (n + 1) numerikus utótaggal
(vagyis Columnl, Column2, Column3 stb.).

DataType Ez a tulajdonság határozza meg az oszlopban tárolt adat­


típust ( boa lean, sztri ng, float stb.).

Defaultvalue Ez a tulajdonság új sarok beszúrásakor az oszlophoz ren­


delt alapértelmezett értéket kérdezi le vagy állítja be.
A rendszer ezt az értéket használja, ha csak nem rendel­
kezünk másként.

175
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Expression Ez a tulajdonság kérdezi le vagy állítja be a sorok szűrésé­


re, egy oszlop értékének kiszárrútására vagy egy összegző
oszlop létrehozására használt kifejezést.

Ordinal Ez a tulajdonság veszi fel az oszlop numerikus pozícióját


a DataTab l e által fenntartott Co lumns gyűjteményben.

Readonly Ez a tulajdonság dönti el, hogy az oszlop módosítható-e,


ha egy új sort adtunk a táblához. Az alapértelmezett ér­
ték: fal se.

Table Ez a tulajdonság kérdezi le a oataco lumn típust tartalma­


zó DataTab le objektumot.

unique Ez a tulajdonság kérdezi le vagy állítja be, hogy az oszlop


egyes soraiban lévő értékeknek egyedinek kell lenniük,
vagy az értékek ismétlődése megengedett. Ha egy oszlop
rendelkezik egy elsődlegeskulcs-korlátozással, akkor a
Unique tulajdonságot true értékre ajánlott állítani.

23. 3. táblázat: A DataColumn tulajdonságai

A DataColumn létrehozása

A SimpleDataSet projekt folytatásához (és a Datacolumn használatának bemu­

tatásához) tételezzük fel, hogy szeretnénk modellezni az Inventory tábla osz­


lopait. Mivel a CarlO oszlop lesz a tábla elsődleges kulcsa, a oatacol umn ob­
jektumon írásvédett, egyedi és nem nullázható jellemzőket kell beállítanunk
(a Readonly, a unique és az AllowDBNull tulajdonságokkal). Módosítsuk a Main()
metódust úgy, hogy létrehozzunk benne négy Datacolumn objektumot:

static void Main(string[] args)


{

ll Hozzunk létre adatoszlopokat, amelyek leképezik


ll az "igazi" oszlopokat az AutoLot adatbázis
ll Inventory táblájában.
Datacolumn carlOColumn = new Datacolumn("carro", typeof(int));
cariDColumn.Caption = "car ID";
cariDColumn.Readonly = true;
cariDColumn.AllowDBNull = false;
cariDColumn.unique = true;

176
DataColumn típusok használata

Datacolumn carMakecolumn = new Datacolumn("Make", typeof(string));


Datacolumn carcolorcolumn =
new Datacolumn("color", typeof(string));
Datacolumn carPetNamecolumn =
new Datacolumn("PetName", typeof(string));
carPetNameColumn.Caption = "Pet Name";
Console.ReadLine();
}

A carmcolumn objektum konfigurálásakor hozzárendeltünk egy értéket a


captiontulajdonsághoz. Ez a tulajdonság azért hasznos, mert lehetővé teszi,
hogy a megjelenítéshez más sztringértéket használjunk, rnint az oszlop neve.
(Az adatbázistáblákban szereplő oszlopnevek általában jobban megfelelnek a
programozási céloknak [pl. au_fname], rnint a megjelenítési céloknak [pl.
Author First Name]).

A mezők automatikus növelésének engedélyezése

A Datacolumnegyik olyan jellernzője, amelyet konfigurálhatunk, az automati­


kus növelésképessége. Az automatikusan növekvő oszlopot annak biztosítá­
sára használjuk, hogy arnikor egy új sort hozzáadunk egy adott táblához, az
oszlop értéke automatikusan beállítódjon a sorban következő értékre. Ez
hasznos lehet akkor, ha azt akarjuk, hogy az oszlopban ne legyenek ismétlő­
dő értékek (pl. egy elsődleges kulcs esetén).
Ezt a viselkedést az Autorncrement, az Autorncrementseed és az Autorncre­
mentstep tulajdonságokkal irányíthatjuk A seed értékkel jelöljük az oszlop

kezdőértékét, rníg a step érték határozza meg azt a számot, amelyet hozzá
kell adni a növeléskor. Nézzük meg a következő módosítást a carmcolumn
Datacolumn szerkezetében:

static void Main(string[] args)


{

Datacolumn carmcolumn = new Datacolumn("carm", typeof(int));


cariDColumn.Readonly = true;
cariDColumn.caption = "car ID";
cariDColumn.AllowDBNull = false;
cariDColumn.Unique = true;
cariDColumn.Autorncrement = true;
cariDColumn.Autorncrementseed = O;
cariDColumn.Autorncrementstep = l;

177
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A carmcolumn objektum beállításai biztosítják, hogy ha sorokat adunk a meg­


felelő táblához, akkor a CariD oszlopban az érték 1-gyel növekedjen. Mivel a
kezdőérték O, ezért ennek az oszlopnak a számozása O, l, 2, 3 stb. lesz.

DataColumn objektumok hozzáadása egy


DataTable tipushoz

A Datacolumn típus általában nem önálló entitás, hanem egy kapcsolódó Data­
Table objektumba illeszkedik. Ennek bemutatásához hozzunk létre új Data­
Table típust, majd szúrjuk be az oszlopgyűjteményben található összes Data­
column objektumot a columns tulajdonság alkalmazásával:

static void Main(string[] args)


{

ll Datacolumn objektumok hozzáadása egy oataTable típushoz.


DataTable inventoryTable = new DataTable("Inventory");
inventoryTable.Columns.AddRange(new Datacolumn[]
{ cariDColumn, carMakecolumn, carcolorcolumn,
carPetNamecolumn });
Console.ReadLine();
}

A DataTable objektum jelenleg négy Datacolumn objektumot tartalmaz, ame­


lyek a memóriabeli Inventory tábla sémáját jelképezik. A tábla azonban most
nem tartalmaz adatokat, és jelenleg a Dataset által fenntartott táblázatgyűj­
teményerr kívül található. A továbbiakat a táblázat feltöltésével kezdjük a
DataRow objektumok révén.

DataRow tipusok használata

A Datacolumn objektumok gyűjteménye egy DataTable sémáját jelképezi. Ez­


zel szemben a DataRow típusok gyűjteménye a táblázat tényleges adatait kép­
viseli. Ezért, ha 20 tétel szerepel az AutaLot adatbázis Inventory táblájában,
akkor ezeket a rekordokat 20 DataRow típussal reprezentálhatjuk A DataRow
osztály tagjainak használatával beilleszthetjük, eltávolíthatjuk, kiértékelhet­
jük és manipulálhatjuk a tábla értékeit. A 23.4. táblázat összefoglalja a Data­
Row típus néhány (de nem az összes) tagját.

178
DataRow típusok használata

HasErrars A H asErra rs tulajdonság egy olyan logikai értéket ad


GetColumnsinError() vissza, amely azt jelzi, hogy vannak-e hibák. Ha igen, ak­
GetcolumnError C) kor a GetcolumnsinError() metódussal megkaphatjuk a
ClearErrors () hibás tagokat, a Get co l umnError() metódussal a hibale­
RowError írást, rrúg a clearErrors() metódus törli a sor hibalistá­
ját A RowError tulajdonsággal beállíthatunk szöveges le­
írást egy adott sor hibájáróL

HemArray Ez a tulajdonság lekérdezi vagy beállítja a sorhoz tartozó


összes értéket objektumtömbök használatávaL

Rowstate Ezzel a tulajdonsággal állapíthatjuk meg a DataRow aktu­


ális "állapotát" a Rowstate felsorolt típus értékeinek az
alkalmazásával.

Table Ezzel a tulajdonsággal kaphatjuk meg az a DataRow típust


tartalmazó DataTabl e objektum referenciáját.

AcceptChanges () Ezekkel a metódusokkal jóváhagyhatunk vagy elvethe­


Rej ectchanges () tünk az Acceptchanges () legutóbbi meghivása óta a so­
ron végrehajtott minden módosítást.

BeginEdit() Ezekkel a metódusokkal megkezdhetünk, befejezhetünk


EndEdit() vagy megszakíthatunk szerkesztési műveleteket egy
cane elEdi t() DataRow objektumon.

Delete O Ez a metódus megjelöli a sort, hogy a következő


AcceptChanges () metódushíváskor törölni kell.

IsNull() Ez a metódus lekérdezi, hogy az adott oszlop tartalmaz-e


n ull értéket.

23.4. táblázat: A DataRow típus kulcsfontosságú tagjai

A D ataRow típusokkal egy kicsit másképp kell dolgozni, mint a Datac ol umn tí­

pusokkal, ugyanis nem hozhatjuk létre a típus közvetlen példányát, mivel


nincsen nyilvános konstruktor:

ll Hiba! Nincs nyilvános konstruktor.


DataRow r = new DataRow();

Ehelyett be kell szereznünk egy DataRow referenciát egy adott DataTabl e ob­
jektumból. Tegyük fel, hogy szeretnénk beszúrni két sort az Inventory táblába.
A DataTab l e. NewRow() metódus lehetövé teszi a táblázat következő "rekeszé-

179
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

nek" megszerzését, és ekkor a típusindexelővel feltölthetjük az oszlopokat az

új adatokkal. Ilyenkor megadhatjuk akár a Datacolumnhoz rendelt sztringne­


vet, akár annak a sorszámot:

st:at:ic void Main(st:ring[] args)


{

ll Most: adjunk hozzá néhány sort: az Invent:ory t:áblához.


Dat:aRow carRow = invent:oryTable. NewRow();
carRow["Make"] = "BMW";
carRow["color"] = "Black";
carRow["Pet:Name"] = "H am l e t:" ;
i nven t:o ry Tabl e . Ro ws Add(ca rRo w);
.

carRow = invent:oryTable.NewRow();

ll A O. oszlop az aut:omat:ikusan megnövelt: azonosít:ómező,


ll úgyhogy kezdjünk 1-gyel.
carRow[l] "saab";
carRow[2] "Red";
=

carRow[3] = "sea Breeze";


invent:oryTable.Rows.Add(carRow);
console.ReadLine();
}

Megjegyzés Ha érvénytelen oszlopnevet vagy sorszámot adunk át a Dat:aRow indexelöjének,


akkor futásidejű kivételt kapunk.

Jelenleg egy két sort tartalmazó Dat:aTable objektummal rendelkezünk. Ezt az


általános eljárást (több Dat:aTable objektum létrehozásához) természetesen
megismételhetjük azért, hogy meghatározzuk a sémát és az adattartalmat
Mielőtt beillesztenénk az invent:oryTabl e objektumunkat a Dat:aset: objek­
tumba, vizsgáljuk meg a Rowst:at:e tulajdonságot.

A RowState tulajdonság

A Rowst:at:e tulajdonság akkor hasznos, ha programozottan kell azonosíta­

nunk egy táblában az összes olyan sort, amelynek az eredeti értéke megvál­
tozott, amely újonnan lett beszúrva, és így tovább. Ehhez a tulajdonsághoz
hozzárendelhetünk bármilyen értéket a Dat:aRowst:at:e felsorolásból, amely a
23.5. táblázatban látható.

180
DataRow típusok használata

Added A sort hozzáadtuk egy DataRowcollection gyűjteményhez, az


Acceptchanges() metódust pedig még nem hívtuk meg.

Deleted A sor törlésre ki lett jelölve a DataRow Dele te() metódusa révén.

Detached A sort ugyan létrehoztuk, de ez nem része egy DataRowco 11 ection


gyűjteménynek sem. Egy DataRow objektum közvetlenül azután talál­
ható ebben az állapotban, hogy létrehoztuk, de még nem adtuk hozzá
egyik gyűjteményhez sem, illetve ha eltávolítottuk egy gyűjteménybőL

Modified A sor módosult, de az Acceptchanges() metódust még nem hívtuk meg.

unchanged A sor nem változott az Acceptchanges () legutóbbi meghívása óta.

23.5. táblázat: A DataRowState felsorolt típus értékei

Míg egy adott DataTab l e sorait programozottan kezelhetjük, a Rowstate tulaj­


donság automatikusan beállítódik. Ennek példájaként adjunk hozzá egy
olyan új metódust a Program osztályunkhoz, amely egy helyi DataRow objek­
tummal dolgozik, és menetközben kiírja a sor állapotát:

private static void ManipulateDataRowstate()


{
ll Hozzunk létre egy DataTable objektumot tesztelési célból.
DataTable temp = new DataTable("Temp");
temp.Columns.Add(new Datacolumn("Tempcolumn", typeof(int)));

ll Rowstate = Detached.
DataRow row = temp.NewRow();
Console.writeLine("After calling NewRow(): {O}", row.Rowstate);

ll Rowstate = Added.
temp.Rows.Add(row);
console.writeLine(''After calling Rows.Add(): {O}", row.Rowstate);

ll Rowstate = Added.
row["Tempcolumn"] = 10;
console.writeLine("After first assignment: {O}", row.Rowstate);

ll Rowstate = unchanged.
temp.Acceptchanges();
console.writeLine("After calling Acceptchanges: {O}",
row.Rowstate);

ll Rowstate = Modified.
row["Tempcolumn"] = ll;
console.writeLine("After first assignment: {O}", row.Rowstate);

181
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

ll Rowstate = oeleted.
temp.Rows[O].Delete();
Console.writeLine("After calling Delete: {O}", row.Rowstate);
}

Az ADO.NET DataRow típusa elég intelligens ahhoz, hogy megjegyezze az ak­


tuális állapotát. Ennek köszönhetően a tulajdonos DataTabl e képes beazono­
sítani, hogy mely sorok módosultak. Ez a Dataset egyik kulcsfontosságú tu­
lajdonsága, ugyanis arnikor eljön az ideje, hogy a frissített információkat el­
küldjük az adattárba, akkor csak a módosított adatokkal kell foglalkoznunk.

A DataRowVersion tulajdonság

Amellett, hogy a Rowstate tulajdonsággal számon tar�a egy sor aktuális álla­
potát, a DataRow objektum a DataRowversi on tulajdonság révén megőrzi az ál­
tala tartalmazott adatok három lehetséges verzióját. Egy DataRow objektum
létrehozásakor csak az adatok egyetlen másolatát tartalmazza, amely "aktuá­
lis változatként" van jelen. Ahogy azonban programozottan dolgozunk a
DataRow objektummal (a különböző metódushívások segítségéve!), további

adatváltozatok jönnek létre. A DataRowversion tulajdonságot beállítha�uk a


DataRowversion felsorolt típus bármelyik értékére (lásd a 23.6. táblázatot).

current Egy sor aktuális értékét jelzi még a módosítások után is.

Default A DataRowstate alapértelmezett verziója. Az Added, aModified


vagy a Deleted értékű DataRowstate esetén az alapértelmezett vál­
tozat a current. A Detached értékű DataRowstate esetében ez a
verzió a Proposed.

original A DataRow tulajdonságba először beírt vagy az AcceptChanges()


legutóbbi meghívásakor fennálló értéket képviseli.

Proposed A sor értéke jelenleg szerkesztés alatt áll a B egin Edit() meghívá­
sának köszönhetően.

23.6. táblázat: A DataRow Version felsorolás érte'kei

182
DataTable típusok használata

Ahogy a 23.6. táblázat mutatja, a DataRowversi on tulajdonság értéke számos


esetben függ a DataRowstate tulajdonság értékétőL A DataRowversion tulaj­
donság a háttérben változik, amikor különböző metódusokat hívunk meg a
oataRow (vagy bizonyos esetekben a DataTable} objektumon. A következők­

ben megnézzük azoknak a metódusoknak a kifejtését, amelyek befolyásolhat­


ják egy sor DataRowversi on tulajdonságának az értékét:

• Ha meghívjuk a DataRow.BeginEditO metódust, és megváltoztatjuk a


sor értékét, akkor elérhetővé válnak a current és a Proposed értékek.
• Ha meghívjuk a DataRow.cancelEditO metódust, akkor a Proposed ér­
ték törlődik.
• A DataRow.EndEditO meghívása után a Proposed érték lesz a current érték.
• A DataRow.AcceptchangesO metódus meghívása után az original érték
ugyanaz lesz, mint a eurrent érték. Ez történik akkor is, ha meghívjuk
a DataTable.Acceptchanges O metódust.
• A DataRow. Rej ectchangesO meghívása után a Proposed érték törlődik,
és a változat current lesz.

Ez így egy kissé bonyolult- különösen amiatt, hogy egy DataRow rendelkez­
het is meg nem is az összes változattal egy adott pillanatban (futásidejű kivé­
teleket kapunk, ha olyan sorváltozatot próbálunk megszerezni, amelyet jelen­
leg nem követünk nyomon). Az összetettségétől függetlenül, mivel a DataRow
három adatmásolatot tart karban, nagyon egyszerű lesz olyan front endet lét­
rehozni, amely lehetövé teszi a végfelhasználó számára az értékek módosítá­
sát, illetve azt, hogy meggondolhassa magát, és visszaállítsa az értékeket,
vagy végérvényesen jóváhagyjon értékeket. A továbbiakban különböző pél­
dákat fogunk látni ezeknek a metódusoknak a kezelésére.

DataTable tipusok használata

A DataTable típusnak számos tagja van, amelyek közül soknak a neve és a


funkcionalitása azonos a Dataset típusban lévőkkeL A 23. 7. táblázat bemutatja
a DataTabl e típus néhány alapvető tagját a Rows és col umns tagokon túlmenően.

183
casesensitive Azt jelzi, hogy a sztring-összehasonlítások a táblákban
kis- vagy nagybetűérzékenyek-e (vagy sem). Az alapér­
telmezett érték: fa l se.

childRelations Visszaadja a DataTab le gyermekkapcsolatainak gyűjte­


ményét (ha van).

Constraints Lekérdezi a táblázat által fenntartott korláHozások gyűj­


teményét.

Copy() A metódus átmásolja egy adott DataTab l e sémáját és


adatait egy új példányba.

Dataset Lekérdezi a táblázatot tartalmazó Datasetet (ha van).

Defa u l tview Lekérdezi a táblázat testre szabott nézetét, amely állhat


szűrt nézetből vagy valamilyen kurzorpozícióból.

Minimumcapacity Lekérdezi vagy beállítja a táblázat sorainak kezdeti szá­


mát (az alapértelmezett érték: 25).

P arentRel ations Lekérdezi a DataTable szülőkapcsolatainak gyűjteményét.

PrimaryKey Lekérdezi vagy beállítja azon oszlopok tömbjét, amelyek


elsődleges kulcsként szalgálnak a táblában.

RemotingFormat Segítségével megadhatjuk, hogy a Dataset hogyan soro­


sítsa tartalmát (bináris vagy XML-formában) a .NET­
remotingréteg számára.

Tab leN a me Lekérdezi vagy beállítja a táblázat nevét. Ezt a tulajdon­


ságot megadhatjuk konstruktorparaméterként is.

23. 7. táblázat: A DataTable típus kulcsfontosságú tagjai

Állítsuk be a példánkban a DataTable PrimaryKey tulajdonságát a cari Deolumn ob­


jekturnra. A PrimaryKey tulajdonsághoz Datacolumn objektumok gyűjteményét
kell rendelnünk, hogy gondoskodjunk a többoszlopos kulcsról. Esetünkben
azonban csak a Carill oszlopot kell megadni (amely az első oszlop a táblában):

static void Main(string[] args)


{

ll A táblázat elsődleges kulcsának megjelölése.


i nventoryTable . Primary Key =
new Datacolumn[] { inventoryTable.columns [ O ) };

184
DataTable típusok használata

DataTable tipusok beszúrása DataSet objektumokba

Utolsó lépésként a DataTable objektumot illesszük be a carsrnventoryDS Data­


set objektum ba a Tables gyűjtemény használa tával. Tételezzük fel, hogy ehhez

módosítottuk a Main() metódust, és átadtuk a Dataset objektumot a PrintData­


set() új segédmetódusnak (amelyet· még meg kell írni):

static void Main(string[] args)


{

ll végül adjuk hozzá táblánkat a Datasethez.


carsrnventoryDS.Tables.Add(inventoryTable);

ll A Dataset kiíratása.
PrintDataset(carsrnventoryDS);
console.ReadLine();
}

A PrintDataset() metódus egyszerűen végiglépked a Dataset metaadatain


(az ExtendedProperti es gyűjtemény segitségével) és a Dataset núnden Data­
Table objektumán, kiírja az oszlopneveket, valamint a sorok értékeit a típus­

indexelőkkel:

static void PrintDataset(Dataset ds)


{
ll Kiír minden nevet és bővített tulajdonságot.
console.writeLine(''DataSet is named: {O}", ds.DataSetName);
foreach (System.Collections.DictionaryEntry de in
ds.ExtendedProperties)
{
console.writeLine("Key ={O}, value = {1}'', de.Key, de.value);
}
Console.writeLine();

foreach (DataTable dt in ds.Tables)


{
Console.writeLine("=> {O} Table:", dt.TableName);

ll Kiírja az oszlopok neveit.


for (int curcol = O; curcol < dt.columns.count; curcol++)
{
console.write(dt.Columns[curcol].columnName + "\t");
}
console.writeLine("\n-------------------------�--------");

ll Kiírja a DataTable-t.
for (int curRow = O; curRow < dt.Rows.count; curRow++)
{

185
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

for (int curcol = O; curcol < dt.columns.count; curcol++)


{
Console.Write(dt.Rows[curRow] [curcol] .ToString() + "\t");
}
Console.writeLine();
}
}
}

23.3. ábra: A példa DataSet objektumának tartalma

A DataTable adatainak feldolgozása


DataTableReader objektumokkal

Az előző fejezetben végzett munkánk azt bizonyította, hogy máshogy dol­


gozzuk fel az adatokat a kapcsolatalapú modell (pl. adatolvasó objektumok),
illetve a kapcsolat nélküli modell (pl. Dataset objektumok) esetében. Az adat­
olvasó használata általában magában foglalja egy whí le ciklus létrehozását, a
Read() metódus hívását és egy indexelő alkalmazását a névj érték párok ki­
szedéséhez. Másrészről a Dataset feldolgozása általában magában foglal egy
iterációszerkezet-sorozatot, amely beleássa magát a táblákban, sorokban és
oszlopokban lévő adatokba.
A .NET 2.0 megjelenése óta a DataTable típusok rendelkeznek a create­
DataReader() metódussal. Ez a metódus lehetövé teszi egy DataTable típusból
az adatok kinyerését adatolvasó-szerű navigációs séma használatával (csak
előre léptethető és írásvédett). Ennek a megközelítésnek a legfőbb előnye az,
hogy egyetlen modellt használunk az adatok feldolgozásához, függetlenül at­
tól, hogy az ADO.NET melyik "modelljét" használjuk azok megszerzéséhez.
Tételezzük fel, hogy megírtuk a következő PrintTable() segédfüggvényt, és
az alábbiak szerint implementáltuk

186
DataTable típusok használata

static void PrintTable(DataTable dt)


{
ll Megkapjuk a DataTableReader típust.
DataTableReader dtReader = dt.CreateDataReader();

ll A DataTableReader úgy működik, mint a DataReader.


while (dtReader.Read())
{
for (int i =O; i < dtReader.Fieldcount; i++)
{
Console.write("{O}\t",
dtReader.Getvalue(i).ToString().Trim());
}
console.writeLine();
}
dtReader.close();
}

A oataTableReader ugyanúgy működik, mint az adatszolgáltatónk adatolva­


só objektuma. Ideális választás lehet akkor, ha gyorsan szeretnénk kinyerni
az adatokat egy DataTab le típusból anélkül, hogy be kéne járnunk a belső sor­
és oszlopgyűjteményeket. Tételezzük fel, hogy úgy módosítottuk az előző
PrintDataset() metódust, hogy az meghívja a PrintTableO metódust, ahe­

lyett, hogy beleásná magát a sor- és oszlopgyűjteményekbe:

static void PrintDataset(Dataset ds)


{
ll Kiír minden nevet és bővített tulajdonságot.
console.WriteLine("DataSet is named: {O}", ds.DataSetName);
foreach (System.Collections.DictionaryEntry de in
ds.ExtendedProperties)
{
Console.writeLine("Key = {O}, value {1}", de.Key, de.value);
}
Console.WriteLine();

foreach (DataTable dt in ds.Tables)


{
Console.writeLine("=> {O} Table:", dt.TableName);

ll Kiírja az oszlopok neveit.


for (int curcol = O; curcol < dt.Columns.count; curcol++)
{
console.write(dt.columns[curcol].ColumnName.Trim() + ''\t");
}
Console.writeLine('' \n----------------------- -----------");

ll Hívjuk meg az új segédmetódusunkat.


PrintTable(dt);
}
}

187
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Ha futta�uk az alkalmazást, a kimenet hasonló lesz, mint amelyet a 23.3. áb­


rán láthatunk. Az egyetlen különbség az, hogy hogyan fértünk hozzá belső­
leg a DataTable tartalmához.

A DataTable/DataSet objektumok
sorositása XML-ként

Mind a Dataset, mind a DataTable objektumok támoga�ák a writexml O és


Readxml O metódusokat. A writexml O lehetővé teszi, hogy egy helyi fájlba (va­

lamint bármilyen system.ro.stream leszármazott típusba) elmentsük az objek­


tum tartalmát XML-dokumentumként. A Readxml O lehetővé teszi, hogy fel­

töltsük egy Dataset (vagy DataTable) állapotát adott XML-dokumentumból.


Emellett, mind a Dataset, mind a DataTable támoga�a a writexmlschema() és
Readxmlschema metódusokat * . xsd fájlok mentéséhez vagy betöltéséhez.
Ehhez módosítsuk a Main() metódust, hogy meghívja a következő és egy­
ben utolsó segédfüggvényt (a Datasetet adjuk át egyedüli paraméterként):

static void DatasetAsXml(Dataset carsrnventoryDS)


{
ll A Dataset mentése XML-ként.
carsrnventoryDS.WriteXml("carsDataSet.xml");
carsrnventoryDS.WriteXmlSchema("carsDataset.xsd");

ll A Dataset kiürítése.
carsrnventoryDs.clear();

ll A Dataset betöltése XML-fájlból.


carsrnventoryDS.Readxml("carsDataset.xml");
}

Ha megnyi�uk a carsDataset.xml fájlt (amely a projektünk \bin\Debug


könyvtárában található), látható, hogy a táblázat minden egyes sora XML­
elemként van kódolva:

<7xml version="l.O" standalone="yes"?>


<Car_x0020_rnventory>
<Inventory>
<CariD>O</CariD>
<Make>BMW</Make>
<Color>Black<IColor>
<PetName>Hamlet</PetName>
</Inventory>

188
DataTable típusok használata

<Inventory>
<CariD>l<ICariD>
<Make>Saab<IMake>
<Color>Red<IColor>
<PetName>Sea Breeze<IPetName>
<IInventory>
<1Car_x0020_Inventory>

A DataTable/DataSet objektumok sorositása


bináris formátumban

A Dataset (vagy önálló DataTable) tartalmát tömör bináris formában is el­


menthe�ük. Ez akkor lehet különösen hasznos, ha egy Dataset objektumot
számítógéphatáron keresztül kell átadni (megosztott alkalmazás esetén); az
XML-adatok reprezentációjának egyik hátránya pedig, hogy leíró természe­
tének köszönhetően jókora többletköltséget eredményezhet.
Ahhoz, hogy bináris formátumban rögzítsük a DataTable vagy a Dataset
objektumokat, egyszerűen állítsuk át a Remoti ngForrnat tulajdonságot seria­
lization Forrnat. Binary értékre. Mostantól úgy használha�uk a BinaryFormat­

ter típust (lásd a 21. fejezetet), ahogy azt elvárha�uk. Vizsgáljuk meg a

simpleDataset projekt alábbi, utolsó metódusát (amelyhez importálill kell a

System. IO és a system.Runtime.serialization.Formatters.Binary névtereket):

static void DataSetAsBinary(Dataset carsinventoryDS)


{
ll Allítsuk be a bináris sorosítás kapcsolóját.
carsrnventoryDS.RemotingFormat = serializationFormat.Binary;

ll A Dataset mentése binárisként.


FileStream fs = new Filestream('' Binarycars.bin", FileMode.create);
BinaryFormatter bFormat = new BinaryFormatter();
bFormat.serialize(fs, carsrnventoryDS);
fs.Close();

ll A Dataset kiürítése.
carsinventoryDS.Clear();

ll A Dataset betöltése bináris fájlból.


fs = new Filestream("Binarycars.bin", FileMode.open);
Dataset data = (DataSet)bFormat.Deserialize(fs);
}

A 23.4. ábra muta�a a Binarycars.bin fájl tartalmát

189
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

•X
O 00 00 FF FF FF FF Ol 00 00 00 00 00 00
2 00 00 00 4E 53 79 73 74 65 60 2E 44 61 ...... NSyste�.Oa [J
C 20 56 65 72 73 69 6F 6E 3032 2E 30 2E ta. Version=2.0.
O 2C 20 43 75 6C 74 75 72 65 30 6E 65 75 0.0. Culture=neu
l 6C 2C 20 50 75 62 6C 69 63 4B 65 79 54 tra!. PublicKeyT
5 6E 3062 37 37 61 35 63 35 36 31 39 33 oken=b77a5c56193
O 38 39 05 Ol 00 00 00 13 53 79 73 74 65 4e089. .... Syste
4 61 74 61 2E 44 61 74 61 53 65 74 18 00 a.Oata.DataSet.
7 44 61 74 61 53 65 74 2E 52 65 606F 74 ...OataSet.Re�ot
7 56 65 72 73 69 6F 6E 16 44 61 74 61 53 ingVersion.OataS
E 52 65 606F 74 69 6E 67 46 6F 72 60 61 et.ReaotingFor�a
4 61 74 61 53 65 74 2E 44 61 74 61 53 65 t.DataSet.DataSe
l 6065 ll 44 61 74 61 53 65 74 2E 4E 61 tNaae.DataSet.Na
3 70 61 63 65 OE 44 61 74 61 53 65 74 2E •espace.OataSet.
5 66 69 78 15 44 61 74 61 53 65 74 2E 43 Prefix.DataSet.C
5 53 65 6E 73 69 74 69 76 65 12 44 61 74 aseSensitive.Oat
� 74 2E 4C 6F 3 61 6C 6 43 49 a t . ocale
�= ��_
t.. ::
_;_
:_� �- ..:
'�4
'::.-�
� - 5e
___ l ___ LC
_.-J
I� f > �

23 .4. ábra : DataSet sorosítása bináris formátumba

Forráskód A SimpleDataSet kódfájlokat a forráskódkönyvtár 23. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárróllásd a Bevezetés xlv. oldalát.

DataTable objektumok kötése


felhasználói felületekhez

Megvizsgáltuk, hogyan hozhatjuk létre és tölthetjük fel manuálisan egy


Dataset objektum tartalmát, illetve hogyan iterálhatunk rajta végig az
ADO.NET objektummodelljének használatávaL Noha ennek a mikéntje meg­
lehetősen fontos, a .NET platform számos olyan API-t biztosít, amelyek au­
tomatikusan "hozzákötik" az adatokat a felhasználói felület elemeihez.
A .NET eredeti GUI-eszközrendszere, a Windows Forms például rendel­
kezik a DataGridview nevű vezérlőelemmel, amelynek megvan az a beépített
képessége, hogy megjeleníti egy Dataset vagy egy DataTable objektum tar­
talmát mindössze néhány kódsor használatával. Az ASP.NET (a .NET web­
fejlesztő API-ja) és a Windows Presentation Foundation API-ja (a .NET 3.0-
ban bemutatott új, hatékony grafikusfelület-API) szintén támogatja valami­
lyen formában az adatkötés fogalmát.
Az ADO.NET kapcsolat nélküli modelljének vizsgálatában a következő
feladatunk olyan Windows Forms-alkalmazás készítése, amely a felhasználói
felületén megjeleníti egy DataTable objektum tartalmát. Közben azt is meg­
vizsgáljuk, hogyan lehet szűrni és módosítani a táblázat adatait, valamint
megismerkedünk a Dataview objektum szerepével. Először hozzunk létre egy
teljesen új Windows Forms-projektet WindowsFormsDataTableViewer néven
(lásd a 23.5. ábrát).

190
DataTable objektumok kötése felhasználói felületekhez

Megjegyzés Ez a példa azt feltételezi, hogy van némi tapasztalatunk a Windows Forms gra­
fikus felhasználói felületek Létrehozásában_ Ellenkező esetben egyszerűen nyissuk meg a kész
példát, és kövessük azt figyelemmel, vagy térjünk vissza ehhez a részhez a 27_ fejezet elolva­
sása után, amelyben formálisan megvizsgáljuk a Windows Forms API-L

--- _,.-
--
x
New Project

�_:· :��lL���----
froj<ct types: Iomplates: [.NET fr.,.,._,.x 3.5 �]m�
VtSuaiBasic Visual Studio installed tempiates
Vi5űaiC# � Reports Application �ASP.NET Wob Application
Windows
.. ASP.NET W� �rvice Application i'!ASP.NET AJAX S.rvor Control
Wob
DASP.NET AJAX Servor Control Extondor �Class Library
Office
Smart Device
� Console Application i'!Wob Control Library
lelWindows FormsApplic� i!� Windows Forms Controllibrary
Database
Tost i)iWPF AppiiOltion �WPFBrowser Application
WCF illWPF Custom Controllibrary �WPF User Controllibraty
Workflow MyTemplat� ------ ------------

Othot Project Typos


G'� Search Online T�plates...
Test Projeru

A proj� for creating an application with a Windows Fonns user interface (.NET Framework 35)

Name: WindowsFormsOataTableViewer

l.ocation: . C:\My Books\C# and tho .NET Platform 4th od\Codo\Chaptor 23


• l J!rowse.-. l
Solution Name: Windo...vsFormsOataTable:Viewer [j Create .Qirectory for solution

Ol( l[ Cancol

23. 5. ábra: Windows Forms-alkalmazás létrehozása

•X

ír�
��:.,- .-;��-x-''
The Ctmrt Uo! r:J '"-tory

23.6. ábra: A kezdeti felhasználói felület

191
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Nevezzük át a kezdeti FormLes fájlt a sokkal megfelelőbb MainForm.cs névre.


Ezután a Visual Studio 2008 Toolbox használatával húzzunk át egy DataGri d­
view vezérlőelemet (a Properties ablak Name tulajdonságának révén a carrnven­

toryGridview nevet kapja) a tervezői felületre. Ekkor előugrik egy helyi menü,

amely egy fizikai adatforráshoz teszi lehetővé a csatlakozást. Hagyjuk figyel­


men kívül a tervezőnek ezt a tulajdonságát, ugyanis programozottan kö�ük az
adatokat a DataTable objektumhoz. Végül, információs célból, adjunk leíró
Label típust a tervezőhöz. A 23.6. ábra egy lehetséges kivitelezést mutat be.

DataTable feltöltése egy generikus List<T>


használatával

Az előző SimpleDataSet példához hasonlóan a windowsFormsDataTabl evi ewer


alkalmazás olyan DataTab le típust hoz létre, amely Datacolumn típusok hal­
mazát tartalmazza, adatok különböző oszlopait és sorait képviselve. Ezúttal
azonban egy generikus List<T> típusú tagváltozó segítségével töl�ük fel a so­
rokat. Először szú:rjunk be új C#-osztályt a projektbe (car néven), a követke­
zők definícióval:

class car
{
ll A C# automatikus tulajdonságok használata.
public string carPetName { get; set; }
public string carMake { get; set; }
public string carcolor { get; set; }

public Car(string petName, string make, string color)


{
carPetName = petName;
carcolor = color;
carMake = make;
}
}

Az alapértelmezett konstruktoron belül töltsünk fel a List<T> típusú tagvál­


tozót (neve: listcars) új car objektumokkal:

public partial class MainForm : Form


{
ll car objektumok gyűjteménye.
List<Car> listcars = new List<Car>();
public MainForm()
{
Initializecomponent();

192
DataTable objektumok kötése felhasználói felületekhez

ll Töltsük fel a listát néhány autóval.


listcars .Add(new car("chucky", "BMW", "Green"));
listcars.Add(new car("Tiny", "vugo", "white"));
listcars.Add(new Car("Ami", "Jeep", "Tan"));
listcars.Add(new Car("Pain Inducer", "caravan", "Pink"));
listcars.Add(new car("Fred", "BMW", "Pea soup Green"));
listcars.Add(new car("Sidd", "BMW", "Black"));
listcars.Add(new car("Mel", "Firebird", "Red"));
listcars.Add(new car("sarah", "colt", "Black"));
}
}

Ezután adjunk hozzá egy új, DataTab le típusú tagváltozót inventoryTable


néven a MainForm osztálytípusunkhoz. Adjunk új segédfüggvényt az osztály­
hoz CreateDataTable() névvel, és hívjuk meg ezt a metódust a MainForm osz­
tály alapértelmezett konstruktorán belül:

void createDataTable()
{
ll A táblázatséma létrehozása.
Datacolumn carMakecolumn = new Datacolumn("Make", typeof(string));
Datacolumn carcolorcolumn =
new Datacolumn("color", typeof(string));
Datacolumn carPetNamecolumn =
new DataColumn("PetName", typeof(string));
carPetNamecolumn.caption = "Pet Name";·
inventoryTable.columns.AddRange(new Datacolumn[] { carMakecolumn,
carcolorcolumn, carPetNamecolumn });

ll Iteráljunk végig a tömblistán sorok készítéséhez.


foreach (Car c in listcars)
{
DataRow newRow = inventoryTable.NewRow();
newRow["Make"] = c.carMake;
newRow["color"] = c.carcolor;
newRow["PetName"] =c.carPetName;
inventoryTable.Rows.Add(newRow);
}

ll Kössük hozzá a DataTable-t a carinventoryGridview


ll vezérlőelemhez.
carinventoryGridview.Datasource = inventoryTable;
}

A metódusimplementáció elején létrehozzuk a DataTable sémáját három


Datacolumn objektum megvalósításával (az egyszerűség kedvéért most nem

tettük hozzá az automatikusan növekvő CariD mezőt), és ezt követően az ob­


jektumokat hozzáadjuk a DataTable tagváltozóhoz. A soradatok leképzése a
List<T>-ből a DataTable-be egy foreach ciklusban a natív ADO.NET-objek­
tummodell segítségével történik.

193
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A createDataTable() metódus utolsó utasítása hozzárendeli az inventory­


Table táblát a Datasource tulajdonsághoz. Ez az egyetlen tulajdonság, amelyet

be kell állítanunk, hogy a DataTable típust a DataGri dview objektumhoz kössük.


Ez a CUI-vezérlőelem a háttérben belsőleg olvassa be a sor- és az oszlopgyűj­
teményeket, nagyjából úgy, mint azt a simpleDataset példa PrintDataSet() me­
tódusa esetében. Ezt követően futtathaljuk az alkalmazásunkat, és megnézhet­
jük a DataTablet a DataGridview vezérlőelemben (lásd a 23. 7. ábrát).

iil, DataTable Viewer

23. 7. ábra: Egy DataTablekötése egy Windows Fonns DataGridView vezérlőelemhez

Sorok programozott törtése

Tételezzük fel, hogy úgy szeretnénk módosítani a grafikus felületünket, hogy


a felhasználó sorokat töröiliessen a DataGridview vezérlőelemhez kötött Data­
Table objektumbóL Ennek egyik módja az, ha meghívjuk annak a DataRow ob­

jektumnak a Delete() metódusát, amely az eltávolítani kívánt sort képviseli.


Egyszerűen határozzuk meg a törölni kívánt sort jelképező indexet (vagy
DataRow objektumot). Ahhoz, hogy lehetővé tegyük a felhasználó számára a

törölni kívánt sor meghatározását, adjunk hozzá egy TextBox (txtRowToRemove


névvel) és egy Button (btnRemoveRow névvel) vezérlőelemet a tervezőhöz a
23.8. ábrán látható módon.
Az új Button vezérlőelem click eseménykezelőjében található logika törli
a felhasználó által meghatározott sort a memóriában lévő DataTable objek­
tumbóL A törlési logika egy try blokkban van, hiszen számításba vesszük
annak lehetőségét, hogy a felhasználó olyan sor számát írta be, amely jelen­
leg nincs a DataGri dview vezérlőelemben:

194
DataTable objektumok kötése felhasználói felületekhez

ll A sor eltávolítása a DataRowcollection gyűjteményből.


private void btnRemoveRow_click (object sender, EventArgs e)
{
try
{
inventoryTable.Rows[(int.Parse(txtRowToRemove.Text))].Delete();
inventoryTable.Acceptchanges();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}

/'CM.Cs YMainForm.c.<Y �x
�. �

�-- ---�--·- -�
• ,::;�:.- --
;:-·-_ ___ _ _ _:.,:- • r

ile:.._ -----·· " -"'"= ..... ,... r�ll


23.8. ábra: A felhasználói felület módosítása sorok törlésének az engedélyezésenez
az alapul szolgáló DataTable objektumból

A De l eteO metódust találóbb lenne a MarkedAsDe letableO névvel illetni,


ugyanis a sort a rendszer valójában addig nem távolí�a el, amíg nem hívjuk a
DataTable. Acceptchanges O metódust. A DeleteO metódus voltaképpen beál­
lít egy kapcsolót, amely azt jelzi: "Készen állok a megsemmisülésre, amikor a
tábla erre utasítást ad." Ha egy sort törlésre jelöltünk, a DataTable elvetheti a
törlési műveletet a RejectchangesO metódus révén. Ezt most ebben a példá­
ban nem kell megtennünk, de módosíthatnánk a kódot a következők szerint:

ll sor megjelölése töröltként, de a módosítások visszautasítása.


private void btnRemoveRow_click (object sender, EventArgs e)
{

inventoryTable.Rows[(int.Parse(txtRemove.Text))].Delete();

195
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

ll csináljunk még valamit?

ll Az előző Rowstate érték visszaállítása.


inventoryTable.RejectChanges();

Most már futtathajtuk az alkalmazást, és meghatározott sort törölhetünk a Data­


Table objektumból a DataGridview adott sora alapján (a belső sorgyűjtemény
indexere nulla alapú). Ha DataRow objektumokat távolítunk el a DataTable objek­
tumból, a táblázat azonnal frissül, hiszen az objektum állapotához kötöttük. Azt
azonban ne feledjük, hogy a sor valójában még mindig a DataTable objektumban
van, csak a táblázat nem jeleníti meg a Rowstate értéke miatt.

Sorok kiválasztása szürési feltételek alapján

Számos adatközpontú alkalmazás esetén igény van arra, hogy a DataTable


objektumok adatainak csak kis részhalmazát lássuk, amelyet valamilyen szű­
rési feltételek alapján határozhatunk meg. Mi történne például akkor, ha csak
bizonyos márkájú autókat (pl. csak BMW-ket) szeretnénk látni a memóriában
lévő DataTable objektumból? A DataTable osztály select() metódusa ponto­
san ezt az alapvető funkcionalitást biztosítja. A select() metódus használa­
tával megadhatunk olyan keresési feltételt, amely támogatja a kifejezetten a
szabványos SQL-lekérdezések modellezésére tervezett szintaxist. A metódus
visszaadja a DataRow objektumok azon tömbjét, amelyben benne van a feltéte­
leknek megfelelő összes bejegyzés. Ennek bemutatásához ismét módosítsuk a
felhasználói felületet - ezúttal lehetövé téve a felhasználók számára, hogy
megadják a látni kívánt autómárkák nevét (lásd a 23.9. ábrát) -egy új Textbox
vezérlőelem (txtMakeTaview) és egy új Button vezérlőelem (btnDisplayMakes)
hozzáadásával.
A select() metódus többszörös túlterhelése különböző szűrési szemanti­
kákat biztosít. Alapjában véve a select() metódusnak átadott paraméter egy
olyan sztring, amely tartalmaz néhány feltételes műveletet. Figyeljük meg az
új gomb cl ick eseménykezelőjének következő logikáját:

private void btnDisplayMakes_click(object sender, EventArgs e)


{
ll szűrő létrehozása a felhasználói bevitel alapján.
string filterstr =
string.Format("Make= '{O } '", txtMakeToView.Text);

196
DataTable objektumok kötése felhasználói felületekhez

ll A szűrőnek megfelelő összes sor keresése.


DataRow[] makes = inventoryTable.select(filterStr);

ll Lássuk, mink van!


if (makes.Length ==O)
MessageBox.Show("Sorry, no cars...", "selection error!");
else
{
string strMake = null;
for (int i O; i < makes.Length; i++)
=

{
DataRow temp makes[i];
=

strMake += temp["PetName"] + "\n";


}

ll Mutassunk meg minden egyezést egy üzenetdobozban.


MessageBox.show(strMake,
string.Format("{O} type(s):", txtMakeToView.Text));
}
}

tar.csl"MainForm.cs�!�� • x

l
l
lL=----��
Erter row rUIDer to delele Erter make To view

Rernove
·�-=��-'��;��]

23.9. ábra: A felhasználói felület =;:;


módosítása a sarok szűrésének engedélyezéséhez

Először egy egyszerű szűrőt hoztunk létre, amely a hozzákapcsolódó TextBox


értékén alapul. Ha BMW-t írunk bele, akkor a szűrő Make = 'BMW' lesz. Ha el­

küldjük ezt a szűrőt a select() metódusnak, akkor visszakapjuk a oataRow


típusok egy tömbjét, amely minden olyan sort megjelenít, amely megfelel a
szűrőnek (lásd a 23.10. ábrát).

197
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

� DataTable Voewer

Chucky
Frod
Sidd

OK

Erterrow runber to delele

BMW

23.10. ábra: A szűrt adatok megjelenítése

A szűrési logika a szabványos SQL-szintaxison alapul. Tételezzük fel, hogy


szeretnénk megkapni az előző select O hívásnevek szerint ábécésorrendbe
rendezett eredményeit. Az SQL szempontjából ez olyan rendezés, amelynek
a PetName oszlop az alapja. A select() metódustnak van egy olyan túlter­
helt változata, amelynek rendezési feltételt is átadhatunk, ahogyan az alábbi­
akban látható:

ll Rendezés PetName szerint.


makes = inventoryTable.select(filterstr, "PetName");

Ha csökkenő sorrendben szeretnénk látni az eredményeket, akkor így hívjuk


meg a select() metódust:

ll AZ eredmények megjelenitése csökkenő sorrendben.


makes inventoryTable.select(filterStr, "PetName DESC");
=

A rendezősztring az oszlop nevét tartalmazza, amelyet az ASC (ascending = nö­


vekvő, ez az alapértelmezett) vagy a DESC (descending = csökkenő) kulcsszó
követ. Ha szükséges, akkor vesszővel elválasztva több oszlopot is megadha­
tunk. A szűrősztring bármennyi kapcsolódó operátorból állhat. Mi történik
például akkor, ha minden olyan autóra kíváncsiak vagyunk, amelynek az azo­
nosítója 5-nél nagyobb? Az alábbi segédfüggvény pontosan ezt biztosítja:

private void showcarswithidGreaterThanFive()


{
ll Most megmutatjuk az összes olyan autó nevét,
ll amelynek az azonosítója nagyobb, mint 5.
DataRow[] properiDs;

198
DataTable objektumok kötése fel használói felületekhez

string newFilterstr = "ID > 5";


properros = inventoryTable.Select(newFilterstr);
string strros = null;
for(int i = O; i < properros.Length; i++)
{
oataRow temp = properiDs[i];
strros += temp["PetName"]
+ " is ID " + temp["ID"] + "\n";
}
MessageBox.Sh ow(striDs, "Pet names of cars w here ID > 5");
}

Sorok módositása

Végül vizsgáljuk meg a már meglévő sarok értékeinek a módosítását. Ennek


egyik módja az, ha először megszerezzük ·az összes olyan sort, amely megfe­
lel egy adott szűrési feltételnek - természetesen a sel ect O metódus haszná­
latávaL Amint megvan(nak) a kérdéses oataRow objektum( ok), módosíthatjuk
őket. Tételezzük fel például, hogy van egy új gomb az űrlapon btnch ange­
BeemersToYugos névvel, amely (ha rákattintunk) a táblában megkeres minden
olyan sort, amelyben a márka BMW. Amikor azonosítottuk ezeket az eleme­
ket, változtassuk meg a márkát BMW-ről Yugóra:

ll szűrő segítségével keressük meg a szerkeszteni kívánt sorokat.


private void btnChangeBeemersToYugos_Click(object sender,
EventArgs e)
{
ll Bizonyosodjunk meg arról, hogy a felhasználó nem őrült meg.
if (DialogResult.Yes ==
MessageBox.show("Are you sure??
BMWs are much nicer t ha n Yugos!",
"Please confirm!", MessageBoxButtons.vesNo))
{
ll Hozzunk létre egy szűrőt.
string filterstr = "Make='BMW'";
string strMake = string.Empty;

ll Keressünk meg minden sort, amely megfelel a szűrőnek.


DataRow[] makes = inventoryTable.Select(filterStr);

ll Az összes BMW megváltoztatása Yugóra!


for (i nt i = O; i < makes.Leng t h ; i++)
{
makes[i]["Make"] = "vugo";
}
}
}

199
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A DataRow osztály biztosítja a BeginEdit O, az EndE�it O és a cane elEdit O


metódusokat, amelyek lehetövé teszik, hogy megszerkesszük egy sor tartal­
mát, Iniközben ideiglenesen felfüggesztünk minden érvényesítési szabályt.
Az előző kódban nem függesztettük fel ezeket a szabályokat, így minden ér­
tékadás ellenőrizve lett. (Emellett, ha a DataRow bármilyen eseményét kezel­
jük, azok minden módosításkor elsünek.) Ha meghívjuk a BeginEdit O metó­
dust egy adott DataRow objektumon, akkor a sor szerkesztésmódba kerül.
Ilyenkor elvégezhetjük a kívánt módosításokat, és meghívhatjuk az End­
Edit O metódust ezek véglegesítéséhez, vagy a canee lEdit O metódust, hogy

visszatérjünk az eredeti változathoz, például:

private void UpdateSomeRow()


{
ll Tegyük fel, hogy megkaptunk egy sort szerkesztésre.
ll Most tegyük ezt a sort szerkesztésmódba.
rowToUpdate.BeginEdit();

ll Küldjük el a sort egy segédfüggvénynek,


ll amely egy logikai értéket ad vissza.
if( changevaluesForThisRow( rowToUpdate) )
rowToUpdate.EndEdit(); ll OK!
else
rowToUpdate.CancelEdit(); ll Felejtsük el!
}

Noha manuálisan is meghívhatjuk ezeket a metódusokat egy adott DataRow


objektumra, a tagok automatikusan meghívódnak, amikor egy DataTab l e tí­
pushoz kötött DataGridvi ew céleszközt szerkesztünk. Amikor például a Data­
Gridview vezérlőelemből választunk ki egy sort szerkesztésre, a sor auto­

matikusan szerkesztésmódba kerül. Amikor áttérünk egy új sorra, az End­

Edit O metódus automatikusan meghívódik.

A DataView típus használata

Az adatbázisok szaknyelvében a nézet egy tábla (vagy táblák halmazának) al­


ternatív megjelenítését jelenti. A Microsoft SQL Server segítségével például
létrehozhatunk olyan nézetet az lnventory táblázathoz, amely csak adott szí­
nű autókat tartalmazó táblát ad vissza. Az ADO.NET-ben a Dataview típus
lehetövé teszi adatok részhalmazának programozott kinyerését a oataTable
típusból egy önálló objektumba.

200
DataTable objektumok kötése felhasználói felületekhez

Egyik nagy előnye, ha ugyanannak a táblázatnak többféle nézete van,


hogy a nézeteket hozzáköthetjük különböző GUI-célelemekhez (pl. a oata­
Gridview vezérlőelemhez). Az egyik DataGridview nézetet hozzá lehet kötni
olyan Dataview objektumhoz, amely az Inventory összes autóját mutatja, míg
egy másikat be lehet állítani úgy, hogy csak a zöld autókat jelenítse meg.
Ennek bemutatásához egészítsük ki a jelenlegi felhasználói felületet egy
további oataGridvi ew�val, amelynek a neve dataGrideoltsview, és egy leíró
Labellel. Ezután definiáljunk egy Dataview típusú, coltsonlyview nevű tag­
változót:

public partial class MainForm : Form


{
ll A DataTable nézete.
Dataview coltsonlyview;

Hozzuk létre a createoatavi ew() segédfüggvényt, és hívjuk meg ezt a metó­


dust az űrlap alapértelmezett konstruktorán belül, közvetlenül a oataTable
sikeres létrehozása után, az alábbiak szerint:

public MainForm()
{

ll Adattáblázat létrehozása.
createDataTable();

ll Nézet létrehozása.
CreateDataview();
}

Alább látható az új segécifüggvény implementációja. A oataview konstrukto­


rának átadtuk a oataTablet, amelyet az adatsorok egyéni készletének létre­
hozására fogunk használni.

private void createDataView()


{
ll Allítsuk be a táblát, amelyet a nézet létrehozására használunk.
coltsonlyview = new Dataview(inventoryTable);

ll Most állítsuk be a nézeteket egy szűrő segítségével.


coltsonlyview.RowFilter = "Make = 'colt'";

ll Kössük hozzá az új táblázathoz.


dataGridcoltsview.Datasource = coltsonlyview;
}

201
23. fejezet: ADO.NET, 2. rész: A bo n tott kapcsolat

A Dataview osztály támogatja a RowFí l te r nevű tulajdonságot, amely a megfe­


lelő sorok kinyerésére használt szűrési feltételeket jelképező sztringet tartal­
mazza. Miután létrehoztuk ezt a nézetet, ennek megfelelően állítsuk be a táb­
lázat Datasource tulajdonságát. A 23.11. ábra mutatja a kész alkalmazást mű­
ködés közben.

í!lil DataTableV�ewer

The Curert Ust d lnvertOI)"

Enerrow renber to del<te Enermake To view

Here ore the Cols:

23.11. ábra: Adataink egyéni nézete

Egy utolsó felhasználói felületbővitmény:


sorok számának megjelenitése

Adjunk hozzá még egy apró kiegészítést az aktuális alkalmazásunkhoz. Az ab­


lakban lévő táblázatok jelenleg nem adnak támpontot a végfelhasználónak ah­
hoz, hogy hányas számú sort szerkeszti (vagy törli) éppen. Ha szeretnénk meg­
jeleníteni a sorok számait a DataG rí dví ew vezérlőelemen, először a RowPost­
Paí nt eseményt kell kezelnünk magán a táblázaton. Ezt az eseményt akkor

váltja ki a rendszer, ha a táblázat celláiban lévő összes adatot megjelenítettük a


felhasználói felületen. Az esemény lehetőséget ad arra, hogy véglegesítsük a
sorok grafikai megjelenését, mielőtt megmutatnánk őket a felhasználónak.

202
DataSet/DataTable objektumok feltöltése adatillesztökkel

Miután kezeltük ezt az eseményt a bejövő DataGridviewRowPostPaintEvent­


Args paraméterben, megkapunk egy Graphics objektumot. A Graphics típus a
GDI+ API része, amely a Windows Forms 2D saját renderelő eszközrendszere
(lásd 27. fejezet). A Graphics objektum segítségével meghívhatunk különböző
metódusokat (pl.az ehhez a példához megfelelő Drawstring O) a tartalom meg­
jelenítéséhez. (A GDI+ eszközrendszert lásd a 27. fejezetben.) A RowPostPaint
esemény implementációja minden sor számát megjeleníti a carinventoryGrid­
view objektumon (ugyanezt az eseményt természetesen a dataGri deoltsview

objektumon is kezelhe�ük, a hatás hasonló lesz):

void carinventoryGridview_RowPostPaint(object sender,


DataGridviewRowPostPaintEventArgs e)
{
ll "Fessük fel" a sorok számát egyszínű ecset használatával,
ll az aktuális sor stílusának megfelelő saját betűtípussal.
using (SolidBrush b = new solidBrush(Color.Black))
{
e.Graphics.Drawstring((e.Rowindex).ToString(),
e .Inh erited Rowstyl e .F ont, b,
e.RowBounds.Location.x 15, +

e.RowBounds.Location.Y + 4);
}
}

Forráskód A WindowsFormsDataTableViewer kódfájlokat a forráskódkönyvtár 23. fejezeté­


nek alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

DataSet/DataTable objektumok
feltöltése adatillesztökkel

A következőkben vizsgáljuk meg az adatillesztő objektumokat. Ezeket arra


használjuk, hogy feltöltsünk egy Dataset típust DataTab l e objektumokkaL
Továbbá képesek módosított DataTable objektumokat visszaküldeni az adat­
bázisnak feldolgozásra. A 23.8. táblázat összefoglalja a DbDataAdapter ősosz­
tálynak, minden adatillesztő objektum közös szülőjének alapvető tagjait.

Fill() Feltölti egy Dataset adott tábláját a parancsobjektum­


specifikus sel ectcommand alapján.

203
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Selectcommand Olyan SQL-parancsokat hoz létre, amelyek az adattárolóban


InsertCommand futnak a Fi ll() és az Update() metódusok meghívásakor.
updatecommand
Deletecommand

update() Az Insertcommand, az updatecommand vagy a Deletecom­


mand tulajdonságban megadott parancsobjektumok haszná­
latával frissíti a DataTable t A végrehajtott pontos parancs
- .

(egy adott Dataset) adott DataTable típusának adott Data­


Row objektumához tartozó Rowstate értéken alapul.

23.8. táblázat: A DbDataAdapter alapvető tagjai

Egy adatillesztő négy tulajdonságot határoz meg (a selectcommand, az Insert­


command, az updatecommand és a Deletecommand), amelyek mindegyike külön
parancsobjektumokon működik. Arnikor létrehozzuk az adatillesztő objek­
tumot az adott adatszolgáltató (pl. sqlDataAdapter) számára, átadhatunk egy
sztringtípust, amely a selectcommand parancsobjektuma által használt pa­
rancs szövegét képviseli. A fennmaradó három parancsobjektumot (amelye­
ket az Insertcommand, Updatecommand és Deletecommand tulajdonságok használ­
nak) manuálisan kell beállítanunk.
Ha mind a négy parancsobjektumot megfelelően konfiguráltuk, meghív­
hatjuk a Fi ll O metódust, hogy beolvassan egy Dataset (vagy igény szerint
egyetlen DataTabl e) típust. Ehhez az adatillesztő azt a parancsobjektumot
fogja használni, amelyiket megtalálja a selectcommand tulajdonság révén. Ha­
sonló módon, arnikor vissza akarunk adni egy módosított Dataset (vagy
DataTable) objektumot az adatbázisnak feldolgozásra, meghívhatjuk az upda­
te() metódust, amely a fennmaradó parancsobjektumok valamelyikét hasz­
nálja annak alapján, hogy rni az egyes sorok állapota a DataTab l e objektum­
ban (ezt részletesebben lásd később).
Az adatillesztő objektumok egyik legkülönösebb aspektusa az a tény,
hogy velük dolgozva soha nem kell csatlakoznunk egy adatbázishoz vagy le­
zárni az adatbázis-kapcsolatot. Ezek az objektumok helyettünk kezelik az
alapul szolgáló adatbázis-kapcsolatot. Meg kell adnunk az adatillesztőnek
egy érvényes kapcsolatobjektumot vagy kapcsolatsztringet (ezzel belsőleg
épül fel egy kapcsolatobjektum), amely tájékoztatja az adatillesztőt, hogy
pontosan melyik adatbázishoz szeretnénk csatlakozni.

204
DataSet/DataTable objektumok feltöltése adatillesztökkel

Egy egyszerű adatillesztő

Mielőtt új funkcionalitással bővítenénk a 22. fejezetben létrehozott AutaLot­


DAL. dll szerelvényt, kezdjük egy nagyon egyszerű példával, amelyben egy

Dataset objektumot töltünk fel egy táblával, az ADO.NET adatillesztő objek­


tumának segítségéveL Hozzunk létre egy új parancssori alkalmazást Fill -
DatasetwithsqlDataAdapter néven, majd importáljuk a system.Data és a sys­

tem.Data.Sqlcli e nt névtereket a kezdeti a C#-kódfájlunkba.

Ezután módosítsuk a Main() metódust a következők szerint (az egyszerű­


ség kedvéért nyugodtan használjunk bedrótozott kapcsolatsztringet):

static void Main(string[] args)


{
Console.writeLine("***** Fun with Data Adapters *****\n");
ll Bedrótozott kapcsolatsztring.
string cnstr = "Integrated S e c u rity =
SSPI;Initial catalog=AutoLot;" +

@"Data source=(local)\SQLEXPRESS";

ll A hívó létrehozza a Dataset objektumot.


Dataset ds = new DataSet("AutoLot");

ll Az illesztő tájékoztatása a Select parancsszövegről és


ll .a kapcsolatról.
SqlDataAdapter dAdapt =
new sqlDataAdapter("select * From Inventory", cnstr);

ll A Dataset feltöltése az rnventory nevű új táblával.


dAdapt.Fill(ds, "rnventory");

ll A Dataset tartalmának megjelenítése.


PrintDataSet(ds);
console.ReadLine();
}

Az adatillesztőt egy olyan sztringliterál megadásával hoztuk létre, amely az


SQL Select utasítására fog leképződni. Ennek az értéknek az alapján fog létre­
jönni a belső parancsobjektum, amelyet később a selectcommand tulajdonság
révén érhetünk el.
A hívó dolga a Dataset típus egy példányának létrehozása, amelyet aztán
átadunk a Fill O metódusnak. A Fill O metódusnak opcionálisan átadható
egy második sztringargumentum is, amellyel az új DataTable TableName tu­
lajdonságát lehet beállítani (ha nem adunk meg táblázatnevet, akkor az adat­
illesztő egyszerűen a Tabl e nevet fogja használni). Bár a legtöbb esetben a
DataTable neve ugyanaz lesz, mint a fizikai tábla neve a relációs adatbázis­
ban, de ez nem kötelező, hogy így legyen.

205
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Megjegyzés A Fi ll() metódus olyan egész számot ad vissza, amely az SQL-lekérdezés által
visszaadott sorok számát jelképezi.

A Main() metóduson belül explicit módon sehol nem nyitunk meg vagy zá­
runk be adatbázis-kapcsolatot. Egy adott adatillesztő Fill O metódusa auto­
matikusan megnyitja, majd bezárja a kapcsolatot, mielőtt visszatérne a metó­
dusból. Ezért, amikor átadjuk a Dataset objektumot a PrintDataset() metó­
dusnak (a fejezet korábbi részében megvalósítottuk), akkor az adatok helyi
másolatán dolgozunk, és az adatokat nem kérjük le feleslegesen.

Adatbázisnevek leképezése barátságos nevekre

Az adatbázis-rendszergazdák hajlamosak olyan tábla- és oszlopneveket kita­


lálni, amelyek nem érthetők a végfelhasználók számára (pl. au_id, au_fname,
au_lname stb.). Ám az adatillesztő objektumok karbantartanak egy erősen tí­
pusos system.Data.common. DataTableMapping belső gyűjteményt (DataTable­
Mappingcoll ection néven). A gyűjteményhez az adatillesztő objektum Table­
Mappi ngs tulajdonságának révén lehet hozzáférni.
Ezt a gyűjteményt módosíthatjuk is, ezzel tájékoztathatjuk a DataTab le tí­
pust, hogy mely "megjelenitendő neveket" használja, amikor ki kell írnia a
tartalmát. Tételezzük fel például, hogy szeretnénk leképezni az Inventory
táblanevet Current Inventory névre a megjelenítéshez. Továbbá, a CariD osz­
lop nevét mondjuk Car ID-ként (egy plusz szóközzel), a PetName oszlop ne­
vét pedig Name of Carként szeretnénk megjeleníteni. Ehhez adjuk hozzá a
következő kódot, mielőtt meghívnánk az adatillesztő Fill O metódusát (és
ne felejtsük el importálill a System. Data.common névteret, hogy elérjük a Data­
TableMappi n g típust):

static void Main(string[] args)


{

ll Most képezzük le az adatbázis oszlopneveit


ll felhasználóbarát nevekre.
DataTableMapping custMap =

dAdapt.TableMappings.Add("rnventory", "current rnventory");


custMap.ColumnMappings.Add("Carro", "car ID");
custMap. columnMappin gs.Add ("PetName", "Name of car");
dAdapt.Fill(myDS, "rnventory");

206
Az AutolotDAL.dll ismételt vizsgálata

Ha megint futtatjuk a programot, látható, hogy a PrintDataset () metódus


most a DataTab l e és a DataRow objektumok "barátságos nevét" jeleníti meg,
nem pedig az adatbázissémában megadott neveket. A 23.12. ábra mutatja a
példa kimenetét.

23.12. ábra: DataTable objektumok egyedi leképezésekkel

Forráskód A FillDataSetWithSqlDataAdapter kódfájlokat a forráskódkönyvtár 23. fejezetének


alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az AutolotDAL.dll ismételt vizsgálata

Annak a folyamatnak a bemutatásához, amelyben az adatillesztő visszaküldi


a DataTabl e módosításait az adatbázisnak feldolgozásra, módosítsuk az Auto­
LotDAL. dll szerelvényt - amelyet a 22. fejezetben hoztunk létre -, hogy egy új

névteret tartalmazzon (AutoLotDi sconnectedLayer névvel). A névtérben lévő


új osztály, az InventoryDALDi sLayer egy adatillesztőt használ arra, hogy a
DataTab l e-lel kommunikáljon.

A kiinduló osztálytipus definiálása

Nyissuk meg az AutaLotDAL projektet a Visual Studio 2008-ban, szúrjunk


be egy új osztálytípust InventoryDALDi sLayer névvel a Project >Add New
Item menüpont segitségével, és győződjünk meg róla, hogy az új kódfájlban
van egy nyilvános osztálytípus. Az InventoryDAL típus kapcsolat-központú

207
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

modelljével ellentétben ennek az új osztálynak nem kell megadni egyedi


megnyitás/bezárás metódusokat, mivel az adatillesztő automatikusan kezeli
ezeket a részleteket.
Először adjunk hozzá egy egyedi konstruktort, amely beállítja a kapcsolat­
sztringet jelképező sztringváltozót. Ezután definiáljunk egy privát sqlData­
Adapter tagváltozót, amelyet egy (még létrehozandó) configureAdapter() nevű

segédmetódus meghívásával konfigurálunk, amely egy SqlDataAdapter ki­


meneti paramétert kap:

public class InventoryDALDisLayer


{
ll Adatmező.
private string cnString = string.Empty;
private SqlDataAdapter dAdapt = null;

public InventoryDALDisLayer(string connectionstring)


{
cnstring = connectionString;

ll AZ SqlDataAdapter konfigurálása.
configureAdapter(out dAdapt);
}
}

Az adatillesztő konfigurálása az SqlCommandBuilder


használatával

Amikor az adatillesztővel módosítunk táblákat egy Datasetben, az első dol­


gunk hozzárendelni az updatecommand, a Deletecommand és az Insertcommand tu­
lajdonságokhoz érvényes parancsobjektumokat (amíg ezt nem tesszük meg, a
tulajdonságok nullreferenciát adnak vissza). Az "érvényes" parancsobjektu­
mok alatt azt a parancsobjektum-készletet kell érteni, amelyet a módosítani kí­
vánt táblával Gelen esetben az Inventory táblával) együtt használunk.
Az illesztő feltöltése a szükséges adatokkal jókora mennyiségű kódot igé­
nyel, különösen akkor, ha paraméterezett lekérdezéseket használunk. A pa­
raméterezett lekérdezés lehetővé teszi SQL-utasítások létrehozását paramé­
terobjektumok segítségével (lásd 22. fejezet). Ezért, ha az időigényesebb
megoldást választjuk, implementálhatjuk úgy a configureAdapter() metó­
dust, hogy manuálisan létrehozunk három új sqlcommand objektumot, ame­
lyek mindegyike tartalmazza a szükséges sql Parametereket. Ezután mind­
egyik objektumot beállíthatjuk az illesztő updatecommand, Deletecommand

Insertcommand tulajdonságához.

208
Az AutoLotDAL. dll ismételt vizsgálata

Szerencsére a Visual Studio 2008 több tervezőeszközt biztosít, amelyek


kiváltják ezt a kódolási feladatot. Néhány ilyen egyszerűsítést megvizsgá­
lunk a fejezet összefoglalójában. Ahelyett, hogy a rengeteg kódutasítással baj­
lódnánk az adatillesztő teljes konfigurálásához, egyszerűsítsük a feladatot a
configu reAdapter() metódus következő megvalósításával:

private void configureAdapter(out sqlDataAdapter dAdapt)


{
ll Az illesztő létrehozása és a selectcommand beállítása.
dAdapt = new SqlDataAdapter("Select * From rnventory", cnString);

ll A fennmaradó parancsobjektumok dinamikus megszerzése


ll futásidőben, az sqlcommandsuilder használatával.
SqlcommandBuilder builder = new SqlcommandBuilder(dAdapt);
}

Az adatillesztő objektumok létrehozásának egyszerűsítéséhez, az AOO.NET­


adatszolgáltatók mindegyike biztosít egy parancsépítő (Command Builder) típust.
Az sqlcommandsuilder automatikusan generálja az sqlDataAdapter, az Insert­
command, az UpdateCommand és a Deletecommand tulajdonságaiban lévő értékeket a
kezdeti selectcommand alapján. Ennek előnye nyilvánvalóan az, hogy nem kell
manuálisan létrehoznunk az összes sqlcommand és SqlParameter típust.
A kérdés magától értetődően felmerül: a parancsépítő hogyan tudja létrehoz­
ni ezeket az SQL-parancsobjektumokat menet közben? Röviden a válasz a
metaadat. Futásidőben, amikor meghívjuk az adatillesztő update() metódusát, a
parancsépítő kiolvassa az adatbázis séffiájának adatait, hogy automatikusan ge­
nerálja az alapul szolgáló beszúrási, törlési és módosítási parancsobjektumokat
Ehhez nyilvánvalóan üzenetváltásra van szükség a távoli adatbázissal,
ezért biztosan kisebb lesz a teljesítmény, ha egy alkalmazáson belül többször
is használjuk az sqlcommandBuildert. Úgy csökkentjük ezt a negatív hatást,
hogy akkor hívjuk meg a configureAdapter() metódust, arnikor létrehozzuk
az rnventoryDALDi sLayer objektumot, és megtartjuk a konfigurált SqlData­
Adaptert az objektum teljes életciklusának az idejére.·
Az előző kódban megfigyelhettük, hogy nem használtuk a parancsépítő
objektumot Gelen esetben az sqlcommandBuilder objektumot), csak átadtuk az
adatillesztő objektumot konstruktorparaméterként. Ennél többet nem is kell
tennünk (tehetünk, ha szeretnénk, de nem szükséges). A felszín alatt ez a tí­
pus beállítja az adatillesztő többi parancsobjektumát.
A parancsépítők azonban több komoly korlátozással bírnak. Egészen pon­
tosan, a parancsépítő csak akkor képes automatikusan SQL-parancsokat ge­
nerálni az adatillesztőhöz, ha az alábbi feltételek mindegyike megvalósul:

209
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

• Az SQL select parancsa csak egyetlen táblával lép kapcsolatba (azaz


nincsenek összekapcsolások).

• Ez a tábla rendelkezik elsődleges kulccsal.

• Az SQL select utasításnak tartalmaznia kell az(oka)t az oszlopo(ka)t,


amely(ek) a tábla elsődleges kulcsa(i).

Az AutoLot adatbázis létrehozása alapján ezek a korlátozások nem jelente­


nek problémát. Egy "iparibb" adatbázis esetén azonban meg kell fontolnunk,
hogy hasznos-e ez a típus (ha nem, akkor a Visual Studio 2008 automatiku­
san generálja a szükséges kód jó részét).

A GetAlllnventory() implementálása

Az új osztálytípusunk első metódusa az sqlDataAdapter objektum Fill O me­


tódusát fogja használni arra, hogy beolvassa az AutoLot adatbázis Inventory
táblájának összes rekordját jelképező DataTable objektumot:

public DataTable GetAllrnventory()


{
DataTable inv = new DataTable("Inventory");
dAdapt.Fill(inv);
return inv;
}

Az Updatelnventory() implementálása

Az Updaternventory() metódus nagyon egyszerű:

public void Updateinventory(DataTable modifiedTable)


{
dAdapt.Update(modifiedTable);
}

Az adatillesztő objektum itt megvizsgálja a bejövő DataTab l e minden sorának


Rowstate értékét Ennek az értéknek (Rowstate.Added, Rowstate.Deleted vagy

Rowstate. Modified) az alapján a megfelelő parancsobjektumot használja fel a

háttérben.

Forráskód Az AutalotDAL (Part 2) kódfáj lokat a forráskódkönyvtár 23. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

210
Az AutolotDAL dll ismételt vizsgálata
.

Windows Forms front end létrehozása

Most már létrehozhatunk egy front endet, hogy teszteljük az új rnventory­


DALDisLayer objektumunkat, amely egy új Windows Forms-alkalmazás lesz

WindowsFormslnventoryUI néven. Ha létrehoztuk a projektet, állítsunk be


egy referenciát a módosított Auto LotDAL. dll szerel vényre, és importáljuk a
következő névteret:

using AutoLotDisconnectedLayer;

Az űrlap egy Label, egy DataGridview (neve: inventoryGrid) és egy Button


(neve: btnupdaternventory) típusból áll, ez utóbbi fogja kezelni a Click ese­
ményt. Az űrlap definíciója (amely az egyszerűség kedvéért nem tartalmazza a
hibakezelési logikát; ám nyugodtan hozzáadhatunk egy try j catch blokkot,
ha szeretnénk) a következő:

public partial class MainForm : Form


{
rnventoryDALDisLayer dal= null;

public MainForm()
{
rnitializecomponent();
ll Tételezzük fel, hogy van egy App.config fájlunk, amely
ll a kapcsolatsztringet tárolja.
string cnstr =

configurationManager.connectionstrings[
"AutoLotsqlProvider"].connectionstring;

ll H ozzuk létre az adathozzáférési objektumunkat.


dal = new InventoryDALDisLayer(cnStr);

ll Töltsük fel a táblázatot.


inventoryGrid.Datasource= dal.GetAllinventory();
}

private void btnUpdateinventory_click(object sender, EventArgs e)


{
ll szerezzük meg a módosított adatokat a táblázatból.
DataTable changedDT = (DataTable)inventoryGrid.Datasource;

ll Hagyjuk jóvá a módosításokat.


dal.updaternventory(changedDT);
}
}

211
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Ez a példa azt feltételezi, hogy létezik egy App. con fig fájl, amely tárolja a
kapcsolatsztringadatokat a <connect:ionst:rin gs> részben. A confi gurat:ionMa­
nager típus connect:i on st:rin gs indexelőjének használatához győződjünk meg,

hogy referenciát adtunk a syst:em. configu rat: ion. dll szerelvényre. Miután
létrehoztuk az rnvent:oryDALDisLayer objektumot, kössük hozzá a Get:Allrn­
vent:ory() metódusból visszaadott Dat:aTabl e típust a Dat:aGri dview objek­
tumhoz. Amikor a felhasználó az updat:e gombra kattint, kiszedjük a módosí­
tott Dat:aTable típust a táblázatból (a Dat:asource tulajdonsággal), és átadjuk
az updat:ernvent:ory() metódusunknak.
Ha futtatjuk az alkalmazást, adjunk hozzá új sorokat a táblázathoz, és
módosítsunk/ töröljünk néhány másikat. Ha a gombra kattintunk, látni fog­
juk, hogy a módosításokat az AutaLot adatbázis rögzítette.

Forráskód A módosított WindowsFormslnventoryUI kódfájlokat a forráskódkönyvtár 23. feje·


zetének alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Navigálás a többtáblázatos DataSet


objektumokban

Eddig a fejezet összes példájában egyetlen oat:aTable objektummal dolgoz­


tunk. A kapcsolat nélküli modell valódi erejére akkor derül igazán fény, ha
egy Dat:aset: objektum több, egymással összefüggő Dat:aTabl e objektumot tar­
talmaz. Ilyenkor tetszőleges számú Dat:aRelat:ion objektumot szúrhatunk be a
Dat:aset: Dat:aRelat:ion gyűjteményébe, hogy gondoskodjunk a táblázatok ösz­

szefüggéseirőL Ezekkel az objektumokkal a kliensréteg navigálhat a táblázat


adatai között anélkül, hogy hálózati forgalma t generálna.

Megjegyzés Ahelyett, hogy ismét módosítanánk az Aut:oLotDAL. dll szerelvényt, hogy hasz·
nálja a Customers és Orders táblákat, ez a példa új Windows Forms-projektben különít el minden
adatelérési logikát. Mindazonáltal kifejezetten ellenjavallt összekeverni a felhasználóifelület· és
az adatlogikát egy termékszintü alkalmazásban. A fejezet utolsó példái kihasználják a különbözö
adatbázistervező eszközöket, hogy elkülönítsék a felhasználóifelület-és adatlogikakódokat.

Kezdjük ezt a példát egy új Windows Forms-alkalmazás létrehozásával


MultitabledDataSetApp néven. A grafikus felület meglehetősen egyszerű.
A 23.13. ábrán láthatunk három DataGridViewt, amelyek az AutaLot adatbá-

212
Navigálás a többtáblázatos DataSet objektumokban

zis Inventory, Orders és Customers táblázataiból lekért adatokat tartalmaz­


zák. Emellett a kezdeti Button (btnupdateDatabase) típus az adatillesztő objek­
tumok révén az adatbázisnak visszaküldi feldolgozásra az összes módosítást,
amelyet beírtunk a táblázatba.

ainForm:C:SV'........,.,.� •X

.11
lR Cars Database M.tnipulator t_gj(JID@

l
�l

23.13. ábra: A kezdeti felhasználói felület megjeleníti az A utoLot adatbázis tábláinak adatait
y�j
Az adatillesztök előkészitése.

Ahhoz, hogy az adat-hozzáférési kód a lehető legegyszerűbb legyen, a Mai n­


Form parancsépítő objektumokat használ az SQL-parancsok automatikus ge­

nerálásához mindhárom Sql DataAdapter esetében (egy darabot mindegyik


táblázathoz). A Form-leszármazott típus első módosítása a következő:

public partial class MainForm : Form


{
ll Az egész űrlapra vonatkozó Dataset.
private DataSet autoLotDS = new DataSet("AutoLot");

ll Parancsépítők használata az adatillesztő konfigurálásának


ll egyszerűsítéséhez.
private SqlcommandBuilder sqlCBinventory;
private SqlcommandBuilder sqlCBCustomers;
private SqlCommandBuilder sqlCBOrders;

213
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

ll Az adatillesztők (minden táblázathoz).


private sqloataAdapter invTableAdapter;
private SqloataAdapter custTableAdapter;
private SqloataAdapter ordersTableAdapter;

ll Az egész űrlapra vonatkozó kapcsolatsztring.


private string cnstr = string.Empty;

A konstruktor végzi el az adatközpontú tagváltozók létrehozását és a Dataset


feltöltését. A példa most azt feltételezi, hogy létrehoztunk egy App.config fájlt,
amely tartalmazza a helyes kapcsolatsztringadatokat (és van referenciánk a
system.Configuration.dll szerelvényre, valamint importáltuk a System.con­

figuration névteret). Meghívunk egy privát segédfüggvényt, a BuildTable­

Relationship() t, a következőképpen:
-

public MainForm()
{
Initializecomponent();

ll Kapcsolatsztring megszerzése a *.config fájlból.


cnstr =
ConfigurationManager.ConnectionStrings[
"AutoLotSqlProvider"].ConnectionString;

ll rllesztők létrehozása.
invTableAdapter = new sqloataAdapter("Select *from Inventory",
cnstr);
custTableAdapter = new sqlDataAdapter("select *from customers",
cnstr);
ordersTableAdapter = new SqlDataAdapter("select *from orders",
cnStr);

ll Parancsok automatikus generálása.


sqlCBinventory = new SqlCommandBuilder(invTableAdapter);
sqlCBOrders = new sqlcommandBuilder(ordersTableAdapter);
sqlCBCustomers = new SqlCommandBuilder(custTableAdapter);

ll Táblák hozzáadása a oatasethez.


invTableAdapter.Fill(autoLotDS, "rnventory");
custTableAdapter.Fill(autoLotDS, "customers");
ordersTableAdapter.Fill(autoLotos, "orders");

ll Kapcsolatok kiépítése a táblázatok között.


BuildTableRelationship();

214
Navigálás a többtáblázatos DataSet objektumokban

ll Hozzákötés a táblázatokhoz.
dataGridviewinventory.Datasource = autoLotDS.Tables["Inventory"];
dataGridviewcustomers.DataSource = autoLotDS.Tables["customers"];
dataGridvieworders.Datasource = autoLotDS.Tables["orders"];
}

A táblázatok közötti kapcsolatok kiépitése

A BuildTableRelationship() segédfüggvény végzi el a két DataRelation ob­


jektum hozzáadását az autoLatos objektumhoz. Az AutoLot adatbázis több
olyan szülő /gyermek kapcsolatot kifejez, amelyeket a következő kóddal ve­
szünk figyelembe (lásd a 22. fejezetet):

private void BuildTableRelationship()


{
ll customerorder adatkapcsolati objektum létrehozása.
DataRelation dr = new DataRelation("customerorder",
autoLotDS.Tables["customers"].Columns["custiD"],
autoLotDS.Tables["orders"].Columns["CustiD"]);
autoLotDS.Relations.Add(dr);

ll Inventoryorder adatkapcsolati objektum létrehozása.


dr = new DataRelation("InventoryOrder",
autoLotDS.Tables["Inventory"].columns["cariD"],
autoLotDS.Tables["orders"].columns["cariD"]);
autoLotDS.Relations.Add(dr);
}

Amikor létrehozunk egy DataRelation objektumot, akkor egy barátságos


sztringmonikert adunk meg az első paraméterrel (ennek hasznát lásd ké­
sőbb), ezután magának a kapcsolatnak a kiépítéséhez használt kulcsok kö­
vetnek. A szülőtáblázatot (a második konstruktorparaméter) a gyermektáb­
lázat (harmadik konstruktorparaméter) előtt adjuk meg.

Az adatbázistáblák módositása

Miután a Datasetet feltöltöttük, és leválasztottuk az adatforrásról, helyben


kezelhetünk minden DataTable objektumot. Ehhez egyszerűen illesszünk be,
módosítsunk vagy töröljünk értékeket a három DataGridview bármelyikébőL
Amikor vissza szeretnénk küldeni az adatokat feldolgozásra, kattintsunk az
update gombra. A Click esemény mögötti kód ekkor már egyértelmű:

215
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

private void btnupdateDatabase_Click(object sender, EventArgs e)


{
try
{
invTableAdapter.update(carsDS, "rnventory");
ci.JstlableAdapter.Update(carsDS, "customers");
ordersTableAdapter.Update(carsDS, "orders");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Futtassuk az alkalmazást, és hajtsunk végre különböző módosításokat. Ami­


kor újra futtatjuk az alkalmazást, a táblázatokban megjelennek az korábbi
változtatások.

Navigálás a kapcsolódó táblázatok között

Annak illusztrálására, hogy a DataRelation programozottan hogyan teszi le­


hetővé a mozgást a kapcsolódó táblák között, bővítsük a felhasználói felüle­
tet új gombbal (btnGetorderrnfo), a hozzá kapcsolódó szövegdobozzal
(txtcusuo) és leíró címkével (a tetszetősség kedvéért ezeket egy GroupBox tí­
puson belül csoportosítjuk). A 23.14. ábra egy lehetséges elrendezést mutat.

• x

Cuotomer ID:
9

6=-�==��--o==�-�
l Get Qdor Del* l l

23. 14. ábra: A módosított felhasználói felület lehetővé teszi a felhasználó számára,
hogy utánanézzen az ügyfelek megrendeléseinek

216
Navigálás a többtáblázatos DataSet objektumokban

A módosított felület segítségével a felhasználó beírhatja egy ügyfél azonosító­


ját, és visszakeresheti az ügyfél rendelésével kapcsolatos információkat (név,
megrendelés azonosítója, autórendelés stb.), amelyeket a rendszer sztringtípussá
formáz, és végül egy üzenetdobozban megjelenít. Gondoljuk végig az új gomb
cl ick eseménykezelője mögötti kódot:

private void btnGetorderinfo_click(object sender,


System.EventArgs e)
{
string strorderinfo = string.Empty;
DataRow[] drscust = null;
DataRow[] drsorder = null;

ll Megkapjuk az ügyfél azonosítóját a szövegdobozban.


int custiD = int.Parse(this.txtCustiD.Text);

ll Most a custiD alapján lekérdezzük a customers táblázat


ll megfelelő sorát.
drscust = autoLotDS.Tables["customers"].select(
string.Format("custiD ={O}", custiD));
strOrderinfo += string.Format("Customer {O}: {l} {2}\n",
drscust[O]["custiD"].Tostring(),
drscust[O]["FirstName"].ToString().Trim(),
drscust[O]["LastName"].ToString().Trim());

ll A customers táblázatból navigáljunk az orders táblázatba.


drsorder =
drscust[O] .GetchildRows(autoLotDS.Relations["customerorder'']);

ll Lekérdezzük a rendelés számát.


foreach (DataRow r in drsorder)
strorderinfo += string.Format("order Number: {0}\n",
r["orderiD"]);

ll Most navigáljunk az orders táblázatból az Inventory táblázatba.


DataRow[] drsinv =
drsorder[O].GetParentRows(
autoLotDS.Relations["Inventoryorder"]);

ll Lekérdezzük az autó adatait.


foreach (DataRow r in drsinv)
{
strOrderinfo += string.Format("Make: {0}\n", r["Make"]);
strorderinfo += string.Format("color: {0}\n", r["color"]);
strOrderinfo += string.Format("Pet Name: {0}\n", r["PetName"]);
}
MessageBox.Show(strOrderinfo, "order Details");
}

217
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Elemezzük a kódot lépésről lépésre. Először megszerezzük az ügyfél azono­


sítóját a szövegdobozból, és felhasználjuk arra, hogy kiválasszuk a megfelelő
sort a Customers táblázatból a select() metódus segítségéveL Mivel a se­
lect() metódus a oataRow objektumok tömbjét adja vissza, kétszeres indexe­
lést kell használnunk annak biztosítására, hogy a tömb első (és egyetlen) tag­
jának adatait kapjuk meg:

ll Megkapjuk az ügyfél azonosítóját a szövegdobozban.


int custiD = int.Parse(this.txtcustiD.Text);

ll Most a custiD alapján lekérdezzük a customers táblázat


ll megfelelő sorát.
drscust autoLotDS.Tables["customers"].Select(
=

string.Format("custiD ={O}", custiD));


strorderinfo += string.Format("customer {O}: {l} {2}\n",
drscust[O]["custiD"].ToStri ng(),
drscust[0]["FirstName"].ToStri ng(). Tri m(),
drscust[O]["LastName"].ToStrin g(). Trim());

Ezután a Customers táblázatból az Orders táblázatba navigálunk a customer­


order adatkapcsolat segítségéveL A DataRow.GetchildRows() metódus lehető­
vé teszi sorok kiragadását a gyermektáblázatbóL Amint ez kész, kiolvashat­
juk belőle az adatokat:

ll A customers táblázatból navigáljunk az Orders táblázatba.


drsorder =
drscust[O].GetChildRows(autoLotDS.Relations["customerorder"]);

ll Lekérdezzük a rendelés számát.


foreach (DataRow r in drsorder)
strorderinfo += string. Format("Order Number: {0}\n",
r["orderiD"]);

Az utolsó lépés az, hogy az Orders táblából annak szülőtáblázatába (Inven­


tory) navigálunk a GetParentRows O metódus használatávaL Ekkor kiolvas­
ha�uk az adatokat az Inventory táblából a Make, PetName és Color oszlopok
használatával, a következőképpen:

ll Most navigáljunk az orders táblázatból az Inventory táblázatba.


DataRow[] drsinv =
drsorder[O].GetParentRows(
autoLotDS.Relations["Inventoryorder"]);

218
Navigálás a többtáblázatos DataSet objektumokban

ll Lekérdezzük az autó adatait.


foreach (DataRow r in drsinv)
{
strorderinfo += string.Format("Make: {0}\n", r["Make"]);
s_tror.derinfo += string.Format("color: {0}\n", r["color"]);
strorderinfo += string.Format("Pet Name : {0}\n", r["PetName"]);
}

A 23.15. ábra egy lehetséges kimenetet mutat, ha a 2-es ügyfélazonosítót ad­

juk meg (ami AutoLot adatbázisunkban Matt Walton).

_ig Cars Database Manipulator

Custom er 2: Matt Walton


Order Number.lOOl
Make: BMW
Color. Silver
Pet Name: Henry

� CustomerOrder

Customer ID: 2 OK

l Get Order Detaíls l

23.15. ábra: Navigálás az adatkapcsolatok között

Mivel a Dataset egyáltalán nem kapcsolódik az alapjául szolgáló adatforrás­


hoz, dolgozhatunk az adatok memóriában lévő másolatával, és körbemehe­
tünk minden táblázaton, hogy elvégezzük a szükséges módosításokat, törlé­
seket és beszúrásokat. Ha végeztünk, elküldhe�ük feldolgozásra a módosítá­
sokat az adattárolónak A végeredmény egy rendkívül skálázható és stabil
alkalmazás lesz.

Forráskód A MultitabledDataSetApp kódfájlokat a forráskódkönyvtár 23. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

219
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A Visual Studio 2008 adatelérési


eszközei

Az eddigi ADO.NET-példák mindegyike megterhelő annyiban, hogy minden


adatelérési logikát manuálisan hoztunk létre. Bár az előbb említett kód jó ré­
szét áthelyeztük egy .NET-kódkönyvtárba ( Au t: o Lot:DAL . dll) a későbbi újra­
felhasználás érdekében, mégis manuálisan kell létrehoznunk az adatszolgál­
tatónk kölönböző objektumait, mielőtt a relációs adatbázissal dolgozhatnánk
A következőkben vizsgáljuk meg a Visual Studio 2008 által biztosított né­
hány eszközt, amelyek segítenek az adatelérési logika létrehozásában. Ez az
integrált fejlesztői környezet jó pár vizuális tervező és kódgeneráló eszközt
(vagy varázslót) támogat, amelyek a kiinduló kód jó részét megalkotják.

Megjegyzés Nyilvánvalóan nem hihetjük azt, hogy soha nem kell manuálisan létrehoznunk
ADO.NET·logikát, vagy a varázsló által generált kód mindig tökéletesen megfelelő lesz az ak­
tuális projektünk számára. Noha ezek az eszközök rengeteg időt megtakaríthatnak, mégis ér­
demes minél többet tudunk az ADO.NET programozási modelljéről, ugyanis ezáltal egyedi igé·
nyeinkhez alakíthatjuk a létrehozott kódot.

A DataGridView vizuális megtervezése

Az első egyszerűsítést a DataGridView tervező jelenti. Bár előző példákban már


használtuk ezt az eszközt megjelenítési és szerkesztési célokra, még nem foglal­
koztunk a hozzá kapcsolódó varázslóval, amely helyettünk generál adatelérési
kódot. Először hozzunk létre egy teljesen új Windows Forms-alkalmazás­
projektet VisualDataGridViewApp néven. Adjunk hozzá egy leíró Labelt:, vala­
mint a oat:aGri dvi ew vezérlőelem egy példányát. Ekkor megnyílik egy inline
szerkesztő a felhasználói felülettől jobbra. A Choose Data Soure legördülő listá­
ból válasszuk az Add Project Data Source lehetőséget (lásd a 23.16. ábrát).
Ezzel elindul a Data Source Configuration Wizard. Ez az eszköz néhány
lépésben segít kiválasztani és beállítani egy adatforrást, amelyet aztán egyedi
adatillesztő típussal hozzákötünk a Dat: aG ri dvi ew típushoz. Az első lépésben
a varázsló megkér minket, hogy adjuk meg azt az adatforrástípust, amellyel
dolgozni szeretnénk. Válasszuk a Database lehetőséget (lásd a 23.17. ábrát),
majd kattintsunk a Next gombra.

220
A Visual Studio 2008 adatelérési eszközei

r" 1öC1iíLCS�� ObjectBr�er l •X

l

l
(EJ[§[)(}[] l
The Autalot lnventoryTable
' l
l'
l
ll
l
i
l

il!
!
ll
i
'link to l l
23. 1 6. ábra: A DataGridView szerkesztő

Choose a Data Source Type

�.. wil the .applaotian get cilta hom?

liml!B!I Service Object

lm you connect to a database and choose th� database objK!s for your application. This option crmes a dataset.

< Previous� � l [ Finish J L, �..

23.17. ábra: Az adatforrás típusának kiválasztása

Megjegyzés Ebben a lépésben arra is lehetőségünk nyílik, hogy egy külső XML-webszolgálta­
tásból vagy külön .NET-szerelvényben lévő egyedi üzleti objektumból származó adatokat csat­
lakoztassunk.

221
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A második lépés (amely az első lépés választása alapján némileg eltérő lehet)
lehetővé teszi az adatbázis-kapcsolat konfigurálását. Ha van adatbázisunk,
amelyet hozzáadtunk a Server Explorerhez, az automatikusan megjelenik a
legördülő listában. Ha nincsen (vagy ha bármikor szükségünk van arra, hogy
olyan adatbázishoz csatlakozzunk, amelyet korábban nem adtunk hozzá a
Server Explorerhez), kattintsunk a New Connection gombra. A 23.18. ábra
muta�a az AutaLot helyi példányának kiválasztását.

Which �tit <onnection sbould your �lion use to connect to the �titböe?

Ll
int
_ eru
_ _:_,_
ber\sq ..:___ol
lexpr5s.Aut _ dbo
ot._______________ • ll
_, � ConnK!ion... J
Thi� connectlen !tri n g appe�rs to contain sf.nsitive data (for exsmple, a password}. wh!ch Í5" required tc ccnned to th�
d:3taba>;e. Hc-.ve-.•er, �tc ring �n�itive data m the connection string can be a security risk. Do you w;; nt to !ndude thi�
stmitiv� data in the connectien string?

_..,' No, e.;.;d ude !:l!:n:;ítive data from the ccnnecticn string. i will se:t this inform:ltietn in my application cc é e.

_1 Yes, include s-ensitive data in the connectien !:tring.

G Connoction string -------'--

Data Source=(locaQ\SQLEXPRESS;lnitial Catalog=Autolot;lntO<Jrated Security= True

< Previous ll Next> ll Fini'h ll Cancel

23.18. ábra: Az AutaLot adatbázis kiválasztása

A harmadik lépésben meg kell erősítenünk, hogy menteni akarjuk-e a


kapcsolatsztringet az App. confi g fájlba, és ha igen, akkor a <connecti onstri ngs>
elemben használt nevet ugyanígy. Hagyjuk meg az alapértelmezett beállításokat
ennél a pontnál, majd kattintsunk a Next gombra.
A varázsló utolsó lépésében választha�uk ki azokat az adatbázis-objektu­
mokat, amelyekről gondoskodni szeretnénk az automatikusan generált Data­
set és a kapcsolódó adatillesztők segítségéveL Noha az AutaLot adatbázis
adatobjektumainak mindegyikét kiválasztha�uk, most csak az Inventory táblá­
zattal foglalkozunk. Ennek alapján változtassuk meg a javasolt DataSet nevet
InventoryDataSetre (lásd a 23.19. ábrát), jelöljük ki az Inventory táblát, majd
kattintsunk a Finish gombra.

222
A Visual Studio 2008 adatelérési eszközei

Choose Your Database Objects

Which database objects do you want ín your dataset?

8 - [j] f.OI Tables


' ffiO Lm CredítRísks
1±1 Cl t;'l Customers
f:3·0 r;) lnventory
' L [{] l]] CarlO
;..rtJ[I Make
'··0!]] Color
·

0 1]] PetName
·--

. ffi OD'J Orders


'Ü�Víews
á·O@!I> Stored Procedures
• ffiOI:J GetPetName
'-{]ll, Functíons

DataSet name:
lnventoryDataSet
_ __ j

< Previcus If Next> ll Fínish ll Cancel

23.19. ábra: Az Inventory tábla kiválasztása

/Maínform.c.>1iiiíifona.a (Desigoo) VStart Page rObject Browser l �x

!1
a--·--·--·-··-·----·---·------·---·..1 .

� ínventoryDataSet W inventoryBindingSource l_-�-��-�����!��-����-:�:_.J

23.20. ábra: A Windows Forms-projektünk a Data Source Configuration Wizard futtatása után

223
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Ekkor láthatjuk, hogy a vizuális tervező több szempontból módosult. A leg­


feltűnőbb az a tény, hogy a DataGridView az Inventory táblázat sémáját mu­
tatja úgy, ahogy azt az oszlopok fejlécei illusztrálják. Emellett az űrlaptervező
alján (a komponenstálca területén) három komponenst látunk: egy Dataset, egy
Bindingsource és egy Tab leAdapter komponenst (lásd a 23.20. ábrát).
Ekkor futtatható az alkalmazás, a táblázatot a rendszer feltöltötte az
Inventory tábla rekordjaival (lásd a 23.21. ábrát).

riJ lnvertory Ví"_r Redux

The Autalot lnventOI)' Table

CarlO Make Color PetName A

jvw
BMW Pri< Sidd
-· í
t
2 Red ZwY
3
iFonJ Black j Me!
4 j BMW Siver Hervy
5
jYugo JPri< Saly
6 jSaab J Blue Sven
l l

23.21. ábra: Feltöltött DataGridView-nincs szükség manuális kódolásra

Az App.config fájl és a Settings.Settings fájl


A Solution Explorert megvizsgálva láthatjuk, hogy a projektünk tartalmaz egy
App. config fájlt. Ha megnyitjuk, megfigyelhetjük az előző példában használt
<connectionstrings> elem name attribútumát:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<configSections>
</configSections>
<Connectionstrings>
<add name="VisualoataGridViewApp.Properties.Settings.
AutoLotconnectionstring"
connectionstring=
"Data Source=(local)\SQLEXPRESS;
rnitial catalog=AutoLot;Integrated Security=True"
providerName="System.Data.sqlClient" />
</connectionstrings>
</configuration>

224
A Visual Studio 2008 adatelérési eszközei

Egészen pontosan a meglehetősen hosszú visualoataGridviewApp.Properties.


seuings.AutoLotconnectionstring értéket látjuk a kapcsolatsztring neveként
Még furcsább az a tény, hogy ha átfutjuk a generált kódot, nem találunk
referenciát a configurationManager típusra, hogy kiolvashassuk az értéket a
<connectionstrings> elemből. Azt azonban láthatjuk, hogy az automatikusan
generált adatillesztő objektumot (lásd részletesen később) részben a követke­
ző privát segédfüggvény meghívásával hoztuk létre:

private void rnitConnection()


{
this._connection =

new global::System.Data.sqlclient.Sqlconnection();
this._connection.connectionstring =global::
VisualoataGridviewApp.Properties.settings.Default.
AutoLotconnectionString;
}

A Connectionstring tulajdonságot a seui ngs.Default meghívásával állítjuk


be. Minden Visual Studio 2008 projekttípus fenntart az egész alkalmazásra
kiterjedő beállításokat, amelyek metaadatként égnek bele a szerelvénybe,
amikor lefordítjuk az alkalmazást. Ha megnyitunk egy lefordított alkalma­
zást a re fl ector.exe használatával (lásd az előző kötet 2. fejezetetét), akkor
megnézhetjük ezt a belső típust (lásd a 23.22. ábrát).

:,.�- -
'fltutz Roede."s .NET R..fledw ��.;__,__ �T.;;,j@iif3il
; filo Y- Tools Help
� ó OT� i.if�1i _p-�:T�nOnn-
nn ���-�
111 .O Pr6enbtionCOt'e •ll�
ffi .Q Pr�ntationFramework
{CompilerG�e.rated. Gener;,tedCoderMicrosoft.VisuaiStudio.Editors.SettingsDesigner.SettingsSing


8 .Q AutalotDAl
internal sf!aled class Sdtings: ApplicationSettingsBase
8 1\ AutolotDAL.dll {
m � Rderences ll Field.s:
0{). private static Settings defiiUftlnsUnc�
00 {} AutolotConnectedlayer
ll ��1ethod!>
1iJ O AutolotDAL static Sett:in9sű;
(il {} AutolotOAL.AutolotDataSetTa : public Settings();
8 {} AutolotDAL.Properties
ID�I!m B!!m!
[DefauftSgtingValue(@�Data Source:::(locai)\SQLEXPRESS;Initial Catalog:::Autolot;lntegrated Sec
ffi {) AutolotOisconnectedlayer
1-, L: 1
public string AutolotConnectionString ( get; }
public static Srltings Oef.ault:{ get; }
� J
internal sealed class Settings : Applicatiorc"l Expond ethods
M
Nii1lme: AutolotOAL.Propert.i�ings •

·-
-�

23.22. ábra: A Settings objektum tartalmaz egy beágyazott kapcsolatsztringértéket

A fentiek ismeretében lehetséges lenne úgy telepíteni az alkalmazást,, hogy


nem adjuk oda a * . config fájlt, mivel az alapértelmezés szerint a beágyazott
értéket használjuk, ha nincsen jelen kliensoldali *.config fájl.

225
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Megjegyzés A Visual Studio 2008 beállításaiban a programozási modellek teljes ismertetése


túlmutat a fejezet hatáskörén. Ha szeretnék többet megtudni ezekről, olvassuk el a .NET Fra­
mework 3.5 SDK dokumentációjának "Managing Application Settings" című témakörét.

A generált DataSet vizsgálata

Nézzük meg a generált kód néhány alapvető aspektusát. Először szúrjunk be


egy új osztálydiagramot a projektbe, amelyhez válasszuk ki a projekt ikonját a
Solution Explorerben, majd kattintsunk a View Class Diagram gombra. A be­
menet alapján a varázsló létrehoz egy új Dataset típust, amelynek ebben az
esetben InventoryDataset a neve. Az osztály számos hasznos tagot definiál,
amelyek közül az Inventory tulajdonság a legfontosabb (lásd a 23.23. ábrát).

lnventoryOataSet ®
Class
.. Datas.!

13 Fields
:;P _schemaSeríali;:ationMcde : Schen;aSeri.1li ...

:fl tablelnventcry: InventcryDataTab le

8 Properties
fff inventory {get: } : lnventoryDataTab! e
!!il Relaticns {get; } : DataRelationCollet:tion
� SchemaSeriali:::ationt'-.,1ode { get; set; } : Sch. . .
"ffi' Tables (get; i: DataTableCcllecticn

13 Melhods

.;� Clone() DataSet


:

,J'f GetSchemoSerializable() : XmiSchema


'Of GetTypedDataSetSchema(XmiSchemaSet ...

,§,,_ !nitCiassO :void


1,� InitializeDerivedDataS!':t(): \loid
r/;� !nitVarsO :void
�-. InitVars(bool initTable): void
·''- lnventoryDataSet(J
fQ InventcryDataSet(Serializ�1icnlnfo info, St...
t� ReadXmiSerialioable(XmiReader reader) : ...

Jj� SchemaChanged(object sender, Cclle::tio.. .


S,. Shou!dSeria!i::clnventory{} : bool
-J'- ShculdSeriali<eRelations() : boci

f'- ShouldSeriali<eTablesQ · l;col

iti Nested Types

23.23. ábra: A Data Source Configuration Wizard létrehozott egtj erősen típusos DataSet típust

Ha a Solution Explorerben kétszer kattintunk az InventoryDataset. x sd fájlra,


akkor betöl�ük a Visual Studio 2008 Dataset Designer eszközt (erről részlete­
sebben később lesz szó). Ha ezen a tervezőn belül bárhova kattintunk a jobb
egérgombbal, és kiválasz�uk a View Code lehetőséget, meglehetősen üres
részleges osztálydefiníciót láthatunk:

226
A Visual Studio 2008 adatelérési eszközei

public partial class rnventoryDataSet {


partial class rnventoryDataTable
{
}
}

Az események a tervező által karbantartott fájlon (rnventoryDataset.Desig­


ner.cs) belül zajlanak. Ha a Soluton Explorerrel megnyitjuk ezt a fájlt, látható,

hogy az rnventoryDataset valójában kibővíti a Dataset osztálytípust. Amikor


mi (vagy a varázsló) létrehozunk egy olyan osztályt, amely kibővíti a Dataset

típust, akkor erősen típusos Datasetet valósítunk meg. Az erősen típusos


Dataset objektumok használatának egyik előnye az, hogy több olyan tulaj­

donságot tartalmaznak, amelyek közvetlenül az adatbázistáblák neveire ké­


pezhetők le. Így ahelyett, hogy bele kéne ásni magunkat a táblák gyűjtemé­
nyébe a Tables tulajdonság segítségéve!, egyszerűen használhatjuk az lnven­
tory tulajdonságot. Vizsgáljuk meg az alábbi kódrészletet, amely az érthető­
ség kedvéért megjegyzéseket is tartalmaz:

ll Ez mind a tervező által létrehozott kód.


public partial class rnventoryDataset : global::system.Data.Dataset
{
ll rnventoryDataTable típusú tagváltozó.
private InventoryDataTable tablernventory;

ll Minden konstruktor meghív egy rnitclass() segédmetódust.


public rnventoryDataset()
{

this.rnitclass();
}

ll Az Initclass() előkészíti a Datasetet, és hozzáadja az


ll rnventoryDataTable osztályt a Tables gyűjteményhez.
private void rnitClass()
{
this.DataSetName = "rnventoryDataSet";
this.Prefix = "";
this.Namespace = "http:lltempuri.orglrnventoryDataset.xsd";
this.Enforceconstraints = true;
this.SchemaserializationMode =
global::System.Data.SchemaserializationMode.rncludeschema;
this.tablernventory = new rnventoryDataTable();
base.Tables.Add(this.tablernventory);
}

227
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

ll Az írásvédett Inventory tulajdonság visszaadja


ll az InventoryDataTable tagváltozót.
public InventoryDataTable Inventory
{
get { return this.tableinventory; }
}
}

Amellett, hogy egyesíti a DataTab l e objektum fenntartásának részleteit, a ter­


vező által generált erősen típusos Dataset tartalmazhat hasonló logikát, hogy
feltárjon olyan DataRe l ati on objektumokat (ezekkel jelenleg nem rendelke­
zünk), amelyek a táblák közötti kapcsolatot képviselik.

A generált DataTable és DataRow tipusok vizsgálata

A fentiekhez hasonlóan, a varázsló létrehozott egy erősen típusos DataTab l e és


egy erősen típusos DataRow osztályt, amelyek mindegyikét beágyazta az Inven ­

toryDataset osztályba. Az InventoryDataTable osztály (amely ugyanolyan tí­

pusú, mint a most megvizsgált erősen típusos Dataset tagváltozója) egy


olyan tulajdonságkészletet definiál, amely a fizikai Inventory tábla oszlopneve­
in alapul (CariDColumn, ColorColumn, MakeColumn és PetNameColumn),
valamint egy egyedi indexelőt és egy count tulajdonságot, hogy beolvassa a
rekordok aktuális számát.
Sokkal érdekesebb, hogy ez az erősen típusos DataTab le osztály egy olyan
metóduskészletet definiál (lásd a 23.24. ábrát), amely sarok beszúrását, hely­
meghatározását és törlését teszi lehetővé a táblában erősen típusos tagok
használatával (vonzó alternatíva a Row és col umn indexelők manuális navigá­
lása mellett).

Megjegyzés Az erősen típusos DataTab l e számos olyan eseményt is definiál, amelyekkel


megfigyelhetjük a táblázat adatainak módosításait.

Az egyedi DataRow típus sokkal kevésbé egzotikus, mint a generált Dataset


vagy DataTabl e. Ez az osztály kibővíti a DataRow osztályt (lásd 23.25. ábra), és
olyan tulajdonságokat nyújt, amelyek közvetlenül az Inventory tábla sémájára
képezhetők le (természetesen a tulajdonságok típusa az oszlopok típusának
megfelelő).

228
A Visual Studio 2008 adatelérési eszközei

(lnvent�Table ®l
Class

l
l -+ TypedTableBase<lnvento�

lii Fields

lit) Properties
!
1:! Methods
-=\il AdólnventoryP.c-.�,;(ínt CariD, string Make_, string Color, string PetName) : Inventor;Row
:(; AddfnventcryRow(InventoryRow rov.·) ,;oíd
:

-� Clone(): DataTab/e
j'l Createlnstance(}: D:1taTable
._, FindByCarlD{int <2 riO) lnve:ntcryRc\''
:

,j� GetRcv"Type(): Type


�Y GetTypedTableSchema(XmiSchemaSet xs) : XmiSc.hemaCcmp!e..xTyj::e

JJY fnitCiassO : void


�t:l InitVzrsO :void
=� lnventor;DataTableO
.jjt'.i InventcryD3taTab!e(0.3taTable tab!e)
-.j� Inve:ntoryDataTable{Seria!izationinfo info, StreamingContext ccntext)
,y Né\·-;hwentcryRcwO : InventcryRow
��" Ne\·.•RowFrornBui!der(OataRo•·;Builder builder): DataRcw
-�� CnRc•/;(hanged(DataRo•.-\Changt-EventArgs e): void
�� OnRm-vCho.mging(DataRcwChangeEventArgs e); void
,J'I OnRc<••Deleted(DataRcwChangeEventArgs e): void
}� OnRcwDeleting(DataRowChangeEventArgs e): void
�� Ren;ovelnventcr;Rcw(lnventoryRow row); void
Í ltJ Event>

23.24. ábra: Az egyedi DataTable típus

l lnventoryRow ®
Class
-+OataRow

8 Fields

# �ableinventol)': !nventcryOataiable

�Properties

� CeriD {ge�� set:}: int


'3 Cc!or {get.: <;et.:} ;;tnng
:

'fff ;,ilak� �g..:-:: set:} string


:

fff P·::tNz.1n:: { g:'!t.: 5et:} string


:

8 Methods

3\i ínventcr/Rc,c,•(DataHo·;vBuilder rb)


� isD�:;-.Iarrtr·Jull() :beo!
:"' Se:D,;:NameNuiiO: void

23.25. ábra: Az egyedi DataRow típus

229
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

A generált adatillesztő

Az erős típusok megléte a kapcsolat nélküli típusok számára a Data Source


Configuration Wizard használatának komoly előnye, ugyanis az erősen típu­
sos osztályok manuális hozzáadása meglehetősen hosszadalmas (bár kivite­
lezhető) lenne. A varázsló szerencsére egyedi adatillesztő objektumot gene­
rál, amely képes feltölteni és módosítani az rnventoryDataSet és az rnven­
toryDataTabl e osztálytípusokat (lásd a 23.26. ábrát).

lnventc><ylableAdapter ®
Class
.. comporoert

8 Fields

ji _adapter: SqiDataAdapter

:J' _dearBeforeFHI : bool


# _commandCollection: SqiCommand!]

d" _connection: SqiConne<:tion


� _transaction: Sq!Transaction

9 Properti<S
ifj} Adapter { get; } : SqiDataAdapter
tfJ ClearBeforeFill { get; set; i : boci
�· CommandCollection {get: } : SqlCommandO
\;'g Connection {ge� set: } : SqiConnection
� Transaction {get; set;}: SqiTrans.action
13 Methods

-=• Del�te(int Original_CarlO_, string Originai_Make, string...


Fill(lnventoryDataTable dataTablel : mt
GetDataQ: InventoryDataTable
.1 InitAdapter(l :void
tJ• !nitCommandCollectionO: void
},• !nitConnec�onO : vcid
Inse�(int CariD, string Make, string Color, string PetNa...
,. Invento')'TableAdapterO
,. Update(DataRow dataRC1•1) : int
Update(DataRowO dataRo·,,·s) : int

Update(int CarlO, string Make, string Celer, string PetN...


�· Update(lnventoryDataSet dataSet) : int
Update(lnventO')'DataTable dataTablel: int

23.26. ábra: Egt;edi adatillesztő, amely erősen típusos típusokkal mííködik

Az automatikusan generált rnventoryTabl eAdapter típus karbantartja az

sqlCommand objektumok gyűjteményét, amelyek mindegyike rendelkezik az


sql Parameter objektumok teljesen feltöltött készletével (ez jelentős időmegtakarí­

tást eredményez). Emellett ez az egyedi adatillesztő tulajdonságkészletet biztosít


az alapul szolgáló kapcsolat-, tranzakció- és adatillesztő objektumok kinyerésére,

valamint egy tulajdonságot az egyes parancstípusokat képviselő tömb megszer­


zésére. Ennek nyilvánvaló előnye az, hogy nem nekünk kell megírni a kódot.

230
A Visual Studio 2008 adatelérési eszközei

Generált tipusok használata a kódban

Ha megvizsgáljuk az űrlapból származtatott típus Load eseménykezelőjét,


látható, hogy az egyedi adatillesztő Fill O metódusa indításkor meglúvódik,
és átadja az egyedi Dataset által fenntartott egyedi DataTable típust:

private void MainForm_Load(object sender, EventArgs e)


{
this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);
}

Ugyanezt az egyedi adatillesztő objektumot használha�uk arra, hogy frissítsük


a táblázat módosításait. Adjunk az űrlap felhasználói felületéhez egy Button
vezérlőelemet (btnUpdatelnventory), készítsünk eseménykezelőt a Click ese­
ményhez, és írjuk bele a következő kódot:

private void btnupdaternventory_click(object sender, EventArgs e)


{
ll Ez visszaadja az Inventory táblában végrehajtott összes
ll módosítást feldolgozásra az adatbázisnak.
this.inventoryTableAdapter.update(
this.inventoryDataset.Inventory);

ll Megkapjuk a táblázat friss másolatát.


this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);
}

Futtassuk megint az alkalmazást; adjunk hozzá, töröljünk vagy módosítsunk


rekordot a táblázatban; végül kattintsunk az Update gombra. Ha megint fut­
ta�uk a programot, látha�uk, hogy a változások megjelentek.
Az összes erősen típusos osztályt közvetlenül felhasználha�uk a kódunk­
ban (nagyjából) ugyanúgy, ahogy azt már megtettük a korábbiakban. Téte­
lezzük fel például, hogy olyan felhasználóifelület-tartománnyal frissítettük
az űrlapunkat (lásd a 23.27. ábrát), amely lehetövé teszi a felhasználó számá­
ra új rekord beírását szövegmezők segítségével (ez voltaképpen felesleges
ebben a példában, ugyanis ezt a DataGridview megteszi helyettünk).
Az új Button vezérlőelem Click eseménykezelőjébe beírha�uk az alábbi
kódot:

private void btnAddRow_click(object sender, EventArgs e)


{
ll Lekérdezzük az adatokat a szövegdobozokból
int id= int.Parse(txtcariD.Text);
string make= txtMake.Text;
string color= txtcolor.Text;
string petName = txtPetName.Text;

231
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

ll Egyedi illesztő használatával beszúrunk egy sort.


inventoryTableAdapter.Insert(id, make, color, petName);

ll újra feltöltjük a táblázatot.


this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);
}

;x

r����-p,;·;;; --0------------------------y

i
CarlO

Make
� l

o
o 6

l
L_: :--_���=····=·--·=··=--�-=---=-=·-�-��-- -�-0:-_---�---_-_-·-=�����-··-··-· - _Ó_' ��--��[J,
____ _
_
__

l < lr:::::;��==�c:===::;:::�:::=::J
l � inventoryDataSet rrJSl inventoryBindingSource 'm inventoryTableAdapter

l
l 'L
23.27. ábra: Egtjszerű frissítés az !Írlap típusához

Manuálisan is hozzáadhatunk egy új sort:

private void btnAddRow_click(object sender, EventArgs e)


{
ll új sor beolvasása.
Inventoryoataset.InventoryRow newRow =

inventoryDataSet.Inventory.NewinventoryRow();
newRow.cariD = int.Parse(txtcariD.Text);
newRow.Make = txtMake.Text;
newRow.color = txtcolor.Text;
newRow.PetName = txtPetName.Text;
inventoryDataset.Inventory.AddinventoryRow(newRow);

ll Egyedi illesztő használatával beszúrunk egy sort.


inventoryTableAdapter.Update(inventoryDataset.Inventory);

ll újra feltöltjük a táblázatot.


this.inventoryTableAdapter.Fill(this.inventoryDataset.Inventory);
}

Forráskód A VisualDataGridViewApp kódfájlokat a forráskódkönyvtár 23. fejezetének al·


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

232
A:z. automatikusan generált kód elválasztása a felhasználóifelület-rétegéről

Az automatikusan generált kód


elválasztása a felhasználóifelület­
rétegéről

Végül mutassunk rá, hogy míg a DataGridView által elindított Data Source
Configuration Wizard elvégezte a munkát helyettünk rengeteg kód létrehozá­
sával, az előző példa az adatelérési logikát közvetlenül a felhasználóifelület-ré­
tegbe illesztette be - ez pedig súlyos tervezési hiba. Ideális esetben ez a kód az
AutaLotDAL. dll szerelvényünkhöz (vagy más egyéb adatelérési könyvtárhoz)
tartozik. Elgondolkozhatunk azon, hogy hogyan hasznosíthatjuk a DataGrid­
View varázslójában generált kódot egy osztálykönyvtár-projektben, hiszen az
alapértelmezés szerint nem áll rendelkezésünkre űrlaptervező.
A Visual Studio 2008 adattervezői eszközeit bármilyen típusú projektből
elérhetjük (legyen az felhasználóifelület-alapú vagy másmilyen) anélkül,
hogy óriási mennyiségű kódot kellene átmásolnunk egyik projektből a má­
sikba. A lehetőségek bemutatásához még egyszer nyissuk meg az AutaLot­
DAL projektet, és illesszünk be új Dataset típust (AutoLotDataset) a Project >

Add New ltem menüelem használatával (lásd a 23.28. ábrát).

Add New Jt�m - AutalotDAL


:...;. 'fr����-�. . -· - :l.-:·� ;.,..
Categories: Templat5: []EJ
Visual C# ltems Visual Studio instatl�d tempiates

� � �
lü- l

ADO.NET Web About Box Application Application Bitmap File
Entity Da... Configurat... Configurat... Manifest File


Class
�Class

CodeFile

Component

CursorFile
JE'
Custom
u
Diagram Class Control


DataSet

Oebugger HTMLPage
[il .

IconFile

lnstaller Class

Interface
Vlsualizer

..
� . .....
lJ l

.....� .. -

" _. _
u �
· · ·-· ·- - - - - ---
A OataSg for using data in your application
--------
Name AutolotDataSt.t.xsd

Add JI Cancel l

23.28. ábra: Új DataSet beszúrása

233
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Ekkor megnyílik egy üres Dataset Designer felület. A Server Explorer segít­
ségével kapcsolódjunk egy adott adatbázishoz (elvileg már kapcsolódunk az
AutoLothoz), és húzzunk át minden adatbázis-objektumot (most ne törőd­
jünk a CreditRisk táblával), amelyet a felületen szeretnénk generálni. A 23.29.
ábrán láthatjuk az AutoLot minden olyan egyedi aspektusát, amelyről most
gondoskodunk.

r,.. lnventory
@
f CarlO
Make
Color

l
!l
l l

�==�====�=====J·�
23.29. ábra: Az egyedi erősen típusos típusaink, ezúttal egy osztálykönyvtár-projekten belül
'l

Ha rápillantunk a létrehozott kódra, erősen típusos Dat:aset:, DataTab le és


Dat:aRow típusok egy új kötegét láthatjuk, valamint minden egyes táblázathoz
egy egyedi adatillesztő objektumot. Mivel az Aut:oLot:Dat:aSet: típus tartalmaz
olyan kódot, amely feltölti és frissíti az A utoLot adatbázis minden tábláját, az
automatikusan generált kód mennyisége több mint 3000 sor. Ennek nagy ré­
sze azonban csak infrastruktúra, amelyet figyelmen kívül hagyhatunk.
Ahogy a 23.30. ábrán látható, az Aut:oLot:Dat:aSet: típus az előző Invent:ory­
Dataset típus létrehozásához nagyon hasonlóan készült.
Emellett minden egyes adatbázis-objektumhoz találhatunk egyedi adatil­
lesztő objektumot, amelyet áthúztunk a Dataset Designer felületére, valamint
egy TableAdapt:erManager nevű hasznos típust, amely belépési pontot biztosít
minden egyes objektumba (lásd a 23.31. ábrát).

234
Az automatikusan generált kód elválasztása a felhasználóifelület-rétegéről

AutotottlataSet ®
Class
+Datas.t

8 Fields

.JI _sc:hernaSerializationMode

:JI relationFK_Orders_Customers.
:.fl relatlonFK_OrdersJnventcry
.'!J� tab!eCustomers.
# table!nvento1y

;fi tableürders

8 Properties

� Cus.tcmers
� Inventcry
'fft Order;
f8 Relations
1ft SchemaSericlizationMcde
1ft Tables
lil Methods

1±1 Nosted Types

23.30. ábra: Az AutoLotDataSet

JnventorylableAdapter @J TableAdapterManager ®
aass Class
,.. componert :+ Componert

-
8 Fields
QuerleslableAdapter ®
Class t/' _ba.:kupOataSetSefcreUpda�e
+ Componert :;{1 _ccnnection
- - - -·
/
# _custcmersTzobleA.dapter
CustomerslableAdapter ® # _inventoryTableAdapter
Clan .# _ordersTableAdapter

[
+ Componert
ir J' _updateOrder
'-.- - - -- 8 Properties
OrderslableAdapter @} � BackupDataSetBefcreUpdate
Class
'ff! Conne:::tíon
+ Componert
l fff Custom�rsTableAdapt�r
-......- �- --
- --
� InventoryTableAdapter

:f!i' Order>TableAdapter
� TableAdapterinstance(cunt
� UpdateOrder

8 Methods

},� GetReaiUpdatedRO>.'.'S
.fQI MatchTableAdapt�r(onnecti ..

,�tV SortSelfReferenceRo'ss
,._ UpdateAli
;;,• UpdateDeletedRows
�'W UpdateinsertedRows
},._ UpdateUpdatedRows

Iti Nosted Types

23.31. ábra: Az automatikusan generált adatillesztő objektumok

Forráskód Az AutalotDAL (Part 3) kódfájlokat a forráskódkönyvtár 23. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

235
23. fejezet: ADO.NET, 2. rész: A bontott kapcsolat

Egy felhasználói felület front end:


újra a MultitabledDataSetApp

Az automatikusan generált típusok használata meglehetősen egyszerű, ha


rutinosan dolgozunk a kapcsolat nélküli modellel. A szöveghez letölthető
forráskód tartalmaz egy MultitabledDataSetApp-Redux projektet, amely
- ahogy a neve is muta�a - a fejezet során korábban létrehozott Multitabled­
DataSetApp projekt egyik verziója.
Az eredeti példa gyengén típusos Dataset és SqlDataAdapter típusokat
használt, hogy adatokat mozgasson a táblázatok között. Ez a verzió az Auto­
LotDAL.dll harmadik iterációját és a varázsló által generált típusokat használja.

Bár most nem foglalkozunk azzal, hogy megnézzük a teljes kódot (mivel töb­
bé-kevésbé ugyanaz, mint a projekt első iterációja), néhány fontos megjegy­
zést azonban érdemes tenni:

• Többé nem kell manuálisan létrehoznunk App.confi g fájlt vagy a con­


figurationManagert használnunk, hogy megszerezzük a kapcsolatsztrin­

get, ugyanis ezt a settin gs objektum kezeli.

• Most már erősen típusos osztályokat használunk az AutaLotDAL és az


AutaLotDAL. AutoLotDatasetTab leAdapters névtereken belül.

• Többé nem kell manuálisan létrehoznunk vagy kanfigurálnunk a táb­


láink közötti kapcsolatot, ezt ugyanis automatikusan elvégzi a Dataset
Designer.

Az utolsó ponttal kapcsolatban figyelni kell arra, hogy a Dataset Designer ál­
tal a táblakapcsolatoknak adott nevek különböznek azoktól, amelyeket mi ad­
tunk a projekt első iterációjában. Ezért a btnGetOrderrnfo_cli ck() metódust
módosítani kell, hogy a helyes kapcsolatneveket (amelyeket a Dataset Desig­
ner tervezői felületén láthatunk) használja, például:

private void btnGetorderrnfo_click(object sender,


System.EventArgs e)
{

ll Frissíteni kell a kapcsolat nevét.


drsorder =

drscust[O].GetchildRows(
autoLotDS.Relations["FK_Orders_customers"));

236
Összefoglalás

ll Frissíteni kell a kapcsolat nevét.


DataRow[] drsrnv =

drsorder[O].GetParentRows(
autoLotDS.Relations["FK_Orders_Inventory"]);

Forráskód A MultitabledDataSetApp·Redux kódfájlokat a forráskódkönyvtár 23. fejezetének


alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Összefoglalás

Ebben a fejezetben az ADO.NET kapcsolat nélküli modelljének részleteivel


foglalkoztunk, amelynek központi eleme a Dataset. Ez a típus tetszőleges
számú tábla, valamint tetszőleges számú opcionális kapcsolat, korlátozás és
kifejezés memóriabeli megjelenítésére szolgál. A helyi táblák közötti kapcso­
latok kiépítésének előnye az, hogy programozottan navigálhatunk közöttük,
Iniközben azok nem kapcsolódnak a távoli adattárolóhoz.
A fejezetben megvizsgáltuk az adatillesztő típus szerepét. Ennek a típus­
nak (és a kapcsolódó se l ectcommand, Insertcommand, updatecommand és Del ete­
Command tulajdonságoknak) a használatával az illesztő végrehajthatja a Data­
Set módosításait az eredeti adattárban is. Emellett megtanultuk, hogyan na­
vigáljuk egy Dataset objektummodelljében manuálisan, valamint erősen tí­
pusos objektumok révén, amelyeket főként a Visual Studio 2008 Dataset De­
signer eszközeivel generáltunk

237
HUSZONNEGYEDIK FEJEZET

A LINQ API programozása

Ebben a fejezetben elkezdjük a LINQ to ADO.NET vizsgálatát. Ezt a kifejezést


LINQ programozási modellhez kapcsolódó két jellemző leírására használjuk,
a LINQ to DataSet és a LINQ to SQL lehetőségekhez. Ezek az API-k teszik
lehetövé a LINQ-lekérdezések végrehajtását relációs adatbázisokban, illetve
ADO.NET Dataset objektumokon. Ezután a LINQ to XML szerepét tekin�ük
át. A LINQ ezen aspektusa nemcsak azt teszi lehetövé, hogy a lekérdezésope­
rátorok készletének használatával adatokat nyerjünk ki XML-dokumentum­
ból, hanem azt is, hogy lényegre törően (sokkal hatékonyabban, mintha a
system. xml . dll szerelvénybe csomagolt típusokat használnák) töltsünk be,

mentsünk és hozzunk létre XML-dokumentumokat.

Megjegyzés Ez a fejezet az első kötet14. fejezetében leírt LINQ programozási modellben való
jártasságat feltételezi.

A LINQ to ADO.NET szerepe

A LINQ programozási modell (lásd az előző kötet 14. fejezetét is) lehetővé teszi a
programozók számára, hogy olyan erősen típusos lekérdezéskifejezéseket hoz­
zanak létre, amelyeket az adattárolók széles körére lehet a1kahnazni (tömbök,
gyűjtemények, adatbázisok, XML-dokumentumok). Függetlenül attól, hogy mi­
lyen objektumon haj�uk végre a LINQ-lekérdezést, mindig lehet ugyanazokat a
lekérdezésoperátorokat használni, és a LINQ to ADO.NET API néhány további
típust és infrastruktúrát is biztosít a UNQ-adatbázis integrációjához.
A LINQ to ADO.NET olyan gyűjtőfogalom, amely a LINQ két adatbázis­
központú szempon�át írja le. Az egyik a LINQ to DataSet. Ez az API lényegé­
ben bővítménykészlet a szabványos ADO.NET DataSet programozási modell­
hez, amely lehetővé teszi, hogy a Dataset, a DataTab l e és a DataRow típusokon is
lehessen UNQ-lekérdezéskifejezéseket végrehajtani. A system. cor e. dll típusa­
inak használatán túl a LINQ to DataSet megköveteli, hogy a projekt használja a
system. Data. DataSetExtensi ons. dll szerelvény kiegészítőtípusai t.
24. fejezet: A UNQ API programozása

A LINQ to ADO.NET másik komponense a LINQ to SQL. Ez az API lehetövé


teszi, hogy relációs adatbázisokkal működjünk együtt, mert elvonatkoztat az

alapul szolgáló ADO.NET-adattípusoktól (kapcsolatok, parancsok, adatillesztök


stb.) az entitásosztályok használata révén. Ezeken az entitásosztályokon keresztül
ábrázolhatunk relációs adatokat egy intuitív objektummodellel, valamint mani­
pulálha�uk az adatokat a LINQ-lekérdezések segítségéveL A LINQ to SQL funk­
cionalitás a System.Data.L i nq. dll szerelvényben található.

Megjegyzés A .NET 3.5 óta a LINQ to SQL nem támogatja az data provider factory modellt
(lásd a 22. fejezetet). Ezért amikor ezt az API-t használjuk, az adatoknak a Microsoft SQL Ser­
veren kell lenniük. Ezzel szemben a LINQ to DataSet API természete "szabadosabb", ugyanis a
kezelt Dataset bármilyen relációs adatbázisból származhat.

Programozás a LINQ to DataSettel

Az előző fejezetben láthattuk, hogy a Dataset típus a kapcsolat nélküli mo­


dell központi eleme, és arra használható, hogy jelképezze a kapcsolódó Data­
Tab le objektumok gyorsHátárazott másolatát, valamint ( esetlegesen) a köztük

lévő kapcsolatot. Ehhez kapcsolódó megjegyzés, hogy egy Dataset adatait


három különböző módon lehet manipulálni:

• indexelőkkel,

• adattáblázat-olvasókkal,

• erősen típusos adattagokkaL

Amikor a Dataset és DataTable típusok különböző indexelőit használjuk, ak­


kor meglehetősen egyértelműen, de nagyon gyengén tipizáltan dolgozha­
tunk a tárolt adatokkal. Ez a megközelítés azt követeli meg, hogy az adatokat
cellák táblázatos blokkjaként kezeljük. Például:

static void PrintDatawithindxers(DataTable dt)


{
ll Kiírja a DataTable-t.
for (int curRow = O; curRow < dt.Rows.count; curRow++)
{
for (int curcol = O; curcol < dt.c o lumn s . co unt ; curcol++)
{
Console.write(dt.Rows [curRow] [curcol].ToString() + "\t");
}

240
Programozás a LINQ to DataSettel

console.writeline();
}
}

A oataTable típus createDataReader() metódusa egy olyan másik megközelí­

tést kínál, amely szerínt a Dataset adatait úgy kezelhe�ük, mínt sorok lineáris
készletének szekvenciális feldolgozását:

static void PrintDataWithDataTableReader(DataTable dt)


{
ll Beolvassuk a DataTableReader típust.
DataTableReader dtReader dt.CreateDataReader();
=

while (dtReader.Read())
{
for (int i = O; i < dtReader.Fieldcount; i++)
{
console.write("{O}\t'', dtReader.Getvalue(i));
}
Console.writeLine();
}
dtReader.close();
}

Végül az erősen típusos Dataset lehetövé teszi, hogy az objektumban lévő


adatokat olyan tulajdonságok használatával kezeljük, amelyek leképezhetők
a relációs adatbázis tényleges oszlopneveire.
Erősen típusos objektumok használatával írhatunk olyan kódokat, mínt
például a következő (lásd a 23. fejezet):

static void AddRoWWithTypedoataSet()


{
InventoryTableAdapter invDA = new InventoryTableAdapter();
AutoLotDataset.InventoryDataTable inv = invDA.Getoata();
inv.AddinventoryRow(999, "Ford", "Yellow", "Sal");
invDA.Update(inv);
}

Ezen túlmenően a LINQ to DataSet még egy lehetőséget biztosít a tárolt ada­
tok kezelésére a LINQ-lekérdezéskifejezések használatávaL Az ADO.NET
DataSetbe (és az olyan kapcsolódó típusokba, mínt a DataTable és Dataview)
alapvetően nincsen integrálva a szükséges infrastruktúra ahhoz, hogy köz­
vetlenül végrehajtsunk rajta egy LINQ-lekérdezést. A következő metódus
például fordítási idejű hibát eredményezne:

241
24. fejezet: A LINQ API programozása

static void LinqOverDataTable()


{
ll Adatok DataTable típusának beolvasása.
InventoryDALDisLayer dal = new InventoryDALDisLayer(
@"Data Source=(local)\SQLEXPRESS;" +

"rnitial catalog=AutoLot;rntegrated security=True");


DataTable data = dal .GetAllinventory();

ll Az 5-nél nagyobb cariD-vel rendelkező típusok beolvasása?


�ar moreData = from c in data where (int)c["cariD"] > 5 select c;
}

Ha lefordítanánk a LinqoverDataTable() metódust, akkor a fordító arról tájé­


koztatna, hogy a DataTable típus biztosít egy "lekérdezésirninta-implementá­
ciót". Hasonlóan az IEnumerable<T> típust (pl. az ArrayL istet) nem imple­
mentáló objektumokra vonatkozó LINQ-lekérdezések alkalmazásához, az
ADO.NET-objektumokat pedig kompatibilis típussá kell alakítani. Hogy
megértsük ennek mikén�ét, meg kell vizsgálnunk a system. Data.DataSetEx­
tensions. dll típusait.

A DataSet bővitmények szerepe

A System.Data.DataSetExtensions.dll több új taggal bővíti a System.Data


névteret (lásd a 24.1. ábrát).

1 �s-
·�-
- �
y sol ion
._
M _ _ m_ _ ____________ 8:� ·
··��1_



• 1�
�� , =�·----�----------�--���
� [@� l

j
<Search>
Ja -?;
...
l
• . • . . l�'

J
B·· O System.Data
[!J
<i: DataRowComparer
8 '1$ DataRowExtenSions

J i'
l±J � DataTableExt�nsions
ffi '\$ EnumerableRowColl«tion
ffi- <1$ EnumerableRowColleet•on<TRow> i!
LiJ <1:$: EnumerableRowCollect1onExtens1ons :======================JI
8- <1$ OrderedEnumerableRowCollectton< TR Assembly System.Data.DataSetExtensions
.
�··� TypedTableBase<T> C:\Program Flles\Reference Assembhes\Mlcrosoft

0 ·'1: TypedTableBaseExtensions · \Framework\V3.5\System.Data.DataSetExtens•ons.dll


G
lll

24. 1. ábra: A System.Data.DataSetExtensions.dll szerelvény

A két leghasznosabb tag vitathatatlanul a DataTableExtensions és a DataRow­


Extensions. Ahogy a nevük is muta�a, ezek a típusok a DataTable és a DataRow

típusok működését bővítő metódusok készletével egészítik ki. Egy másik


kulcsfontosságú típus a TypedTableBaseExtensions, amely olyan bővítő metó-

242
Programozás a LINQ to DataSettel

dusokat definiál, amelyeket erősen típusos Dataset objektumokra lehet alkal­


mazni azért, hogy a belső DataTable objektumok LINQ-kompatibilisek legye­
nek. A system. Data.DatasetExtensions.dll szerelvény összes többi tagja pusz­
tán az infrastruktúrát szolgálja ki, és nem közvetlen használatra szánják őket.

DataTable LINQ-kompatibilis használata

A Dataset bővítmények használatának bemutatásához tételezzük fel, hogy


van egy új C# parancssori konzolalkalmazásunk LinqoverDataset névvel.
Fontos megjegyezni, hogy amikor a .NET 3.5-öt célzó projekteket készítünk,
akkor automatikusan referenciát kapunk a system. core.dll és system.Data.
DatasetExtensions. dll szerelvény ekre; ám ebben a példában adjunk hozzá
még egy szerel vényreferenciát az AutaLotDAL.dll szerelvényre (lásd a 23. fe­
jezetet), és egészítsük ki a kezdeti kódfájlt a következő logikával:

using System.Data;
using AutoLotDisconnectedLayer;

namespace LinqOverDataSet
{
class Program
{
static void Main(string[] args)
{
console.WriteLine("***** LINQ over DataSet *****\n");
ll Beolvassuk az AutoLot adatbázis aktuális Inventory
ll táblájának DataTable típusát.
rnventoryDALDisLayer dal = new rnventoryDALDisLayer(
@''Data Source=(local)\SQLEXPRESS;Initial
catalog=AutoLot;" + "Integrated Security=True");
DataTable data = dal.GetAllrnventory();

ll Hívjuk meg az itt következő metódusokat.

console.ReadLine();
}
}
}

Amikor LINQ-kompatibilis objektummá szeretnénk átalakitani egy ADO.NET


DataTable típust, akkor egyszerűen hívjuk meg az AsEnumerable() bővítő me­
tódust, amelyet a DataTableExtensions típus definiál. Ez visszaad egy Enume­
rableRowcollection objektumot, amely egy DataRow gyűjteményt tartalmaz. Az
EnumerableRowcollection típus használatával az elvártak szerint dolgozhatunk
az egyes sorokkal. Nézzünk egy egyszerű példát:

243
24. fejezet: A LINQ API programozása

static void PrintAllcariDs(DataTable data)


{
ll Beolvassuk a DataTable felsorolható változatát.
EnumerableRowcollection enumData data.AsEnumerable();
=

ll Kiírja az autóazonosító értékeket.


foreach (DataRow r in enumData)
console.writeLine("car ID = {O}", r["cariD"]);
}

Mivel az EnumerableRowcollection megvalósítja az IEnumerable<T> típust,


ezért az is megengedett, hogy az alábbi utasítások egyikével kapjuk meg a
visszatérési értéket:

ll A visszatérési érték tárolása IEnumerable<T> típusként.


IEnumerable<DataRow> enumData = data.AsEnumerable();

ll A visszatérési értékek tárolása implicit módon.


var enumData = data.AsEnumerable();

Eddig ugyan még nem futtattuk le a LINQ-lekérdezést, ám az enumData ob­


jektumon immár végrehajthatunk LINQ-lekérdezéskifejezést. Az Enumerable­
Rowcollection valóban DataRow objektumok gyűjteményét tartalmazza, ezért

egy típusindexelőt alkalmazunk az összes alobjektumra, hogy kiírassuk a


cariD oszlop értékét

A legtöbb esetben nem kell EnumerableRowcollection típusú változót dek­


larálnunk azért, hogy tárolja az AsEnumerable() visszatérési értékét Ehelyett
hívjuk meg ezt a metódust magából a lekérdezéskifejezésbőL Akad egy még
érdekesebb metódus is, amely visszaadja a cariD/Makes oszlopok leképezését
a DataTable összes bejegyzéséből, ahol a car ID értéke nagyobb mint 5:

static void ApplyLinqQuery(DataTable data)


{
ll Képezzünk le egy új eredményhalmazt, amely tartalmazza
ll az ID/color értékeket az 5-nél nagyobb cariD-vel rendelkező
ll sorok esetében.
var cars = from car in data.AsEnumerable()
where
(int)car["cariD"] > 5
select new
{
ID = (int)car["CariD"],
color = (string)car["color"]
};

244
Programozás a LINQ to DataSettel

console.writeLine("Cars with ID greater than 5 :");


foreach (var item in cars)
{
Console.WriteLine("-> CariD ={O} is {1}'', item.ID, item.Color);
}
}

A DataRowExtensions. Field <T>() bővitő


metódus szerepe

A jelenlegi LINQ-lekérdezéskifejezésben hátrányos lehet az, hogy számos


kasztolási műveletet és DataRow indexelőt használunk az eredményhalmaz
összeállításához, és ez futásidejű kivételt eredményezhet, ha nem kompatibi­
lis adattípusra próbálunk kasztolni. Ahhoz, hogy erős tipizálást alkalmaz­
zunk a lekérdezésünkben, használhatjuk a DataRow típus Field<T>O bővítő
metódusát. Így megnöveljük a lekérdezés típusbiztonságát, ugyanis az adat­
típusok kompatibilitását a rendszer a fordítási időben ellenőrzi. Nézzük meg
a következőképpen módosított kódot:

var cars = from car in data.AsEnumerable()


where
car.Field<int>("carro") > 5
select new
{
ID = car.Field<int>("CariD"),
Color = car.Field<string>("Color")
};

Látható, hogy ebben az esetben meghívhatjuk a Field<T>() metódust, és


megadhatunk egy típusparamétert, amely az oszlop alaptípusát határozza
meg. A metódus argumentumaként átadjuk magát az oszlopnevet A fordítá­
si idejű ellenőrzés miatt tanácsos a Field<T>() metódust használni a DataRow
indexelője helyett Enumerabl eRowco llect ion feldolgozásakor.
Azon túl, hogy meghívjuk az AsEnumerable() metódust, a LINQ-lekérdezés
általános formátuma hasonló ahhoz, amelyet már láttunk az előző kötet 14. fe­
jezetében. Ezért most nem térünk ki újra a különböző LINQ-operátorok részle­
teire. Ha további példákra vagyunk kíváncsiak, nézzük át a "LINQ to DataSet
Examples" témakört a .NET Framework 3.5 SDK dokumentációjában.

245
24. fejezet: A LINQ API programozása

Új DataTable objektumok feltöltése


a LINQ-lekérdezésekből

Könnyedén fel lehet tölteni egy új DataTab le adatait egy LINQ-lekérdezés


alapján, feltéve, hogy nem használunk leképezéseket. Amikor van egy olyan
eredményhalmazunk, amelyben az alapul szolgáló típust IEnumerable<T> tí­
pusként lehet ábrázolni, meghívhatjuk a copyToDataTable<T>O bővítő metó­
dust az eredményen. Például:

static void BuildDataTableFromQuery(DataTable data)


{
var cars = from car in data.AsEnumerable()
where
car.Field<int>("cariD") > 5
select car;

ll Használjuk ezt az eredményhalmazt új DataTable létrehozásához.


DataTable newTable cars.copyToDataTable();
=

ll Kiírja a DataTable-t.
for (int curRow = O; curRow < newTable.Rows.Count; curRow++)
{
for (int curcol = O; curcol < neWTable.Columns.count; curcol++)
{
console.write(
newTable.Rows [eurRow] [eu rcol J .ToString().Trim() + "\t");
}
Console.writeLine();
}
}

Megjegyzés Az AsDataview<T>O bővítő metódussal is átalakíthatunk egy LINQ-lekérdezést


oa tavi ew típussá.

Ez a megközelítés akkor lehet nagyon hasznos, ha a LINQ-lekérdezés ered­


ményét adatkötés során forrásként szeretnénk használni. A Windows Forms
oataG ri dvi ew (valamint az ASP.NET GridView) vezérlőeleme például támo­

gatja a Datasource tulajdonságot. LINQ-eredményt az alábbiak szerint köthe­


tünk egy DataGri dhez:

ll Tegyük fel, hogy a myoataGrid GUI-alapú táblázatobjektum.


myoataGrid.Datasource (from car in data.AsEnumerable()
=

where
car.Field<int>("CariD") > 5
select car).CopyToDataTable();

A következőkben térjünk át a LINQ to SQL-re.

246
Programozás a LINQ to SQL használatával

Forráskód A LinqOverDataSet kódfájlokat a forráskódkönyvtár 24. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Programozás a LINQ to SQL használatával

A LINQ to SQL olyan API, amely lehetövé teszi jól megformázott LINQ-lekérde­
zéskifejezések alkalmazását relációs adatbázisokban lévő adatokra. A LINQ to
SQL több olyan típust kínál (a system. Data. Linq. dll szerelvényben), amelyek
megkönnyítik a kommunikációt a kód és a fizikaiadatbázis-mator között.
A LINQ to SQL legfőbb célja az, hogy konzisztenciát biztosítson a relációs
adatbázisok és a velük folytatott munkára használt programozási logika
számára. Ahelyett például, hogy az adatbázis-lekérdezéseket nagy sztringek­
kel jelképeznénk, használhatunk erősen típusos LINQ-lekérdezéseket. To­
vábbá ahelyett, hogy a relációs adatokat rekordok folyamaként kellene ke­
zelnünk, dolgozhatunk az adatokkal szabványos objektumorientált progra­
mozási módszerekkel. Mivel a LINQ to SQL lehetövé teszi az adatelérés in­
tegrálását közvetlenül a C#-forráskódba, nagymértékben csökken annak a
szükségessége, hogy manuálisan hozzuk létre egyedi osztályok tucatjait, va­
lamint azokat az adatelérési könyvtárakat, amelyek elrejtik szem elől az
ADO.NET csúnya kódjait.
Amikor LINQ to SQL felhasználatásával programozunk, nyoma sincs az
olyan megszakott ADO.NET-típusoknak, mint az sqlConnection, az sqlCom­
mand vagy az SqlDataAdapter. A LINQ-lekérdezéskifejezések, a (hamarosan
ismertetendő) entitásosztályok és a Datacontext típus segítségével végrehajt­
hatjuk a kívánt adatbázis-roűveleteket (létrehozás, eltávolítás, módosítás és
törlés), valamint definiálhatunk tranzakciós kontextusokat, létrehozhatunk új
adatbázis-entitásokat (vagy egész adatbázisokat), meghívhatunk tárolt eljárá­
sokat, és elvégezhetünk egyéb adatbázis-centrikus tevékenységeket.
Ezenfelül a LINQ to SQL típusok (példaként megint a Datacontextet em­
líthetjük) feladata, hogy megvalósítsák a standard ADO.NET-adattípusok in­
tegrációját. A Datacontext egyik túlterhelt konstruktora bemenetként például
egy robconnection objektumot vár, amely az összes ADO.NET-kapcsolatob­
jektum által támogatott közös interfész. Ilyen módon a már meglévő
ADO.NET adatelérési könyvtárak integrálhaták a C# 2008 LINQ-lekérdezés­
kifejezésekbe (és fordítva). Valójában - ami a Microsoftot illeti - a LINQ to
SQL egyszerűen az ADO.NET-család egyik új tagja.

247
24. fejezet: A LINQAPI programozása

Az entitásosztályok szerepe

Ha a LINQ to SQL-t szeretnénk használni az alkalmazásainkban, akkor az első


lépés az entitásosztályok definiálása. Röviden: az entitásosztályok olyan tipusok,
amelyek azokat a relációs adatokat jelképezik, amelyekkel dolgozni szeret­
nénk. Programozási szempontból az entitásosztályok olyan osztálydefiníciók,
amelyeket különböző LINQ to SQL attribútumokkal (például [Table] és
[column] ) jelölünk, amelyek leképezhetők fizikai táblákra egy adott adatbázis­

ban. A LINQ to SQL attribútumok többségének definícióját a system. Data.


L i nq. Mappi ng névtér foglalja magában (lásd a 24.2. ábrát).
Ahogy azt hamarosan látni fogjuk, a .NET Framework 3.5 SOK (valamint
a Visual Studio 2008) olyan eszközöket biztosít, amelyek automatizálják az
alkalmazás számára szükséges entitástípusok elkészítését. Ezt megelőzően az
első LINQ to SQL példánk fogja bemutatni, hogy hogyan hozzunk létre enti­
tásosztályokat.

eo System.Oata.Lmq.Mapp•ng
.
éJ-<\: AssociationAttribute
éJ.··<\: AttributeMappingSource
ID-cií-.1 AutoSync
$--� ColumnAttribute
éJ.-� DataAttribute
ffi.·<\: DatabaseAttribute
$-�� FunctionAttríbute
éJ...<\: lnheritanceMappingAttribute
tD ·� MappingSource
dJ..'if MetaAccessor
éJ.. � MetaAccessor<TEntity,TMember>
fil. .� MetaAssociation
$--� M�aDataM�mber
éJ 'if MetaFunction
éJ..c;íll MetaFunctionType
dJ ..<if MetaModel
$-·<is MetaParameter
@..� MetaTable
éJ.<if MetaType
dJ.."'$ ParameterAttribute
$---� ProviderAttribut�
$ . � Resu�TypeAttribute
!$J.·� TableAttribute
$ � UpdateCheck
24. 2. ábra: A System.Data.Linq.Mapp ing névt érszámos LINQ to SQLattribútumot definiál

A DataContext tipus szerepe

Ha meghatároztuk az entitásosztályokat, akkor a Datacontext típussal átadhat­


juk a lekérdezési kifejezéseket a relációs adatbázisnak Ez a LINQ to SQL-speci­
fikus osztálytípus felelős a LINQ-lekérdezéskifejezések megfelelő SQL-lekérde-

248
Programozás a LINQ to SQL használatával

zésekre történő fordításáért, valarnint az adott adatbázissal történő kommuni­


kációért Bizonyos tekintetben a Datacontext hasonlít egy ADO.NET-kapcsolat­
objektumra: szüksége van egy kapcsolatsztringre. A tipikus kapcsolatobjek­
tummal ellentétben azonban a Datacontext típus számos olyan taggal rendel­

kezik, amelyek a lekérdezéskifejezések eredményeit az általunk meghatározott


entitásosztályokba képezik le.
Továbbá a Datacontext típus egy factorymintát definiál a kódban használt
entitásosztályok példányainak megszerzésére. Ha megszereztünk egy enti­
táspéldányt, akkor a kívánt módon változtatha�uk meg annak állapotát (re­
kordok hozzáadása és frissítése stb.), és a módosított objektumot feldolgozásra
visszaküldhe�ük. Ily módon a Datacontext hasonló az ADO.NET adatillesztő
tipushoz.

Egy egyszerű LINQ to SQL példa

Először nézzük meg a LINQ to SQL használatának egy egyszerű példáját,


amely a 22. fejezetben létrehozott AutaLot adatbázis Inventory táblájával
dolgozik. Ebben a példában nem használjuk az Auto LotDAL. dll könyvtárat,
hanem minden kódot manuálisan készítünk el. Hozzunk létre egy új pa­
rancssori konzolalkalmazást simpleLinqToSqlApp névvel, valamint adjunk egy
referenciát a system. Data. Linq. dll szerel vényre.
Ezután adjunk hozzá egy új C#-osztályfájlt ( rnventory. cs) . Ez a fájl fogja
definiálni az entitásosztályunkat, amely megköveteli a típus kiegészítését kü­
lönböző LINQ-központú attribútumokkal; ezért mindenképpen adjuk meg,
hogy a system. Data. Linq. Mapping és system. Data. Linq névtereket használjuk.
A következőkben nézzük meg az rnventory típus definícióját:

[Table]
public class rnventory
{
[Column]
public string Make;
[Column]
public string color;
[Column]
public string PetName;

ll Az elsődleges kulcs azonosítása.


[Column(IsPrimaryKey = true)]
public int CariD;

249
24. fejezet: A Ll NQ API programozása

public override string ToString()


{
return string.Format("ID ={O}; Make = {l}; Color = {2};
PetName ={3}", carm, Make.Trim(),
color.Trim(), PetName.Trim());
}
}

Az entitásosztályunk megkapta a [Table] attribútumot, amíg a nyilvános


mezők mindegyikét megjelöltük a [column] attribútummaL A nevek mindkét
esetben közvetlen leképezései a fizikai adatbázistáblának. Ez azonban nem
szigorú követelmény, hiszen a Tab leAttribute és a columnAttribute típusok
támoga�ák a Name tulajdonságot, amely lehetövé teszi az adattábla progra­
mozott reprezentációjának elkülönítését magától a fizikai táblátóL A carm
mezőt tovább minősítettük a columnAttribute típus IsPrimaryKey tulajdonsá­
gának beállításával, a megnevezett tulajdonságszintaxis segítségéveL
Az egyszerűség kedvéért minden mezőt publikusan deklaráltunk. Ha
erősebb egységbe zárásra van szükségünk, akkor definiálhatunk nyilvános
tulajdonságokba (vagy akár automatikus tulajdonságokba) csomagolt privát
mezőket. Ekkor nem a mezők, hanem a tulajdonságok lesznek a [Column] attri­
bútummal megjelölve.
Egy entitásosztály bármennyi olyan tagot tartalmazhat, amelyek nem ké­
pezhetők le a reprezentált adattáblára. Ami a LINQ-futásidőt illeti, a rend­
szer csak a LINQ to SQL attribútumokkal megjelölt elemeket használja az
adatcsere során. Ez az rnventory osztálydefiníció például a Tostring() egyedi
megvalósítását biztosí�a ahhoz, hogy lehetövé tegye az alkalmazás számára
az állapot gyors megjelenítését.
Ha már rendelkezünk entitásosztállyal, használha�uk a Datacontext tí­
pust, hogy elküldjük (és lefordítsuk) a LINQ-lekérdezéskifejezéseinket a
megadott adatbázisnak Tanulmányozzuk az alábbi Main O metódust, amely
megjeleníti az AutaLot adatbázis Inventory táblájában lévő összes elemet:

class Program
{
const string cnstr =

@"Data source=(local)\SQLEXPRESS;rnitial catalog=AutoLot;" +

"Integrated security=True";
static void Main(string[] args)
{
console.writeLine("***** LINQ to SQL sample App *****\n");

ll Datacontext objektum létrehozása.


Datacontext db = new Datacontext(cnstr);

250
Programozás a LINQ to SQL használatával

ll Most hozzunk létre egy Table<> típust.


Table<Inventory> invTable = db.GetTable<Inventory>();

ll Az adatok megjelenítése LINQ-lekérdezés segítségével.


console.writeLine("-> Contents of rnventory Table from AutoLot
database:\n");
foreach (var car in from c in invTable select c)
console.WriteLine(car.ToString());
console.ReadLine();
}
}

Amikor létrehozunk egy Datacontext típust, meg kell adnunk a megfelelő


kapcsolatsztringet, amely itt egyszerű sztringkonstansként jelenik meg. Ezt
nyugodtan eltárolhatjuk az alkalmazáskonfigurációs fájlban, és/vagy hasz­
nálhatjuk az Sql ConnectionstringBui lder típust az objektumorientáltabb ke­
zelés érdekében.
Ezután megszerezzük az rnventory entitásosztályunk egy példányát a Data­
context típus generikus GetTab l e<T>() metódusának meghívásával, ahol az en­
titásosztályt adjuk meg típusparaméterként. Végezetül létrehozunk egy LINQ­
lekérdezéskifejezést, és végrehajtjuk azt az invTable objektumon. A végered­
mény az Inventory tábla minden elemének megjelemtése lesz.

Erősen tipusos DataContext létrehozása

Míg első példánk erősen típusos volt az adatbázis-lekérdezés szempontjából,


némileg elkülönítettük a Datacontext típust és az általa fenntartott rnventory
entitásosztályt. Ennek orvoslásához általában érdemes létrehoznunk egy
olyan osztályt, amely kibővíti a kezelt táblák mindegyike számára tagválto­
zókat definiáló Datacontext típust. Illesszünk be egy új osztályt AutaLotData­
base névvel, adjuk meg, hogy a system.core és a system. Data.Linq névtereket
használjuk, majd implementáljuk a típust az alábbiak szerint:

class AutaLotDatabase : Datacontext


{
public Table<Inventory> rnventory;

public AutoLotDatabase(string connectionstring)


: base(connectionstring){}
}

251
24. fejezet: A LINQ API programozása

Az új osztálytípussal most már egyszerűsíthetjük a Main O metódus kódját:

static void Main(string[] args)


{
Console.writeLine("***''* LINQ to SQL Sample App ***''*\n");

ll Hozzunk létre egy AutaLotDatabase objektumot.


AutaLotDatabase db= new AutoLotDatabase(cnstr);

ll Most már használhatjuk az AutaLotDatabase Inventory mezőjét.


Console.writeLine("-> contents of Inventory Table from AutaLot
database:\n");
foreach (var car in from c in db.Inventory select c)
console.writeLine(car.ToString());
console.ReadLine();
}

Az erősen típusos adatkörnyezet létrehozásának egyik meglepő jellemzője,


hogy a Datacontextből származó típus Gelen példában az AutoLotDatabase)

közvetlenül nem hozza létre a Table<T> tagváltozókat, és nincs nyoma a várt


GetTable O metóduslúvásnak. A fordítás során azonban, arnikor végigiterá­
lunk a LINQ-eredményhalmazon, a Datacontext a háttérben létrehozza a
Table<T> típust.
A LINQ-lekérdezések természetesen használhaták adott eredményhalmaz
megszerzésére is. Tegyük fel, hogy létrehoztuk a következő segédmetódust,
amelyet kilépés előtt meglúvunk a Main O metódusból (ez a metódus paramé­
terként vár egy AutaLotDatabase példányt):

static void showonlyBimmers(AutoLotDatabase db)


{
console.writeLine("***** only BMWs *****\n");

ll Megkapjuk a BMW-ket.
var bimmers = from s in db.Inventory
where s.Make == "BMW"
orderby s.CariD
select s;

foreach (var c in bimmers)


console.WriteLine(c.ToString());
}

A 24.3. ábra mutatja az első LINQ to SQL példánk kimenetét

252
Programozás a LINQ to SQL használatával

24.3. ábra: Első bepillantás a LINQ to SQL használatába

Forráskód A SimplelinqToSqlApp kódfájlokat a forráskódkönyvtár 24. fejezetének alkönyvtá·


ra tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A [Table] és [Column] attribútumok:


további részletek

A2 entitásosztályok különböző attribútumokkal rendelkeznek, amelyeket a

LINQ to SQL használ, hogy le tudja fordítani az objektumokon végzett lekér­


dezését adatbázis SQL-lekérdezésekre. Csak a [Table] és a [column] attribútu­
mokat fogjuk használni; mindazonáltal további attribútumok is léteznek azok­
nak a metódusoknak a megjelölésére, amelyek végrehaj�ák az SQL beszúró,
módosító és törlő parancsait. Ezenfelül a LINQ to SQL attribútumok mind­
egyike meghatároz egy olyan tulajdonságkészletet, amely tovább részletezi,
hogy a LINQ to SQL futtatómotor hogyan dolgozza fel a megjelölt elemet.
A [Table] attribútum nagyon egyszerű, ugyanis egyetlen tulajdonságot
definiál: ez a Name. Ez teszi lehetővé, hogy elkülönítsük az entitásosztály ne­
vét a fizikai táblázattóL
Ha nem állí�uk be a Name tulajdonságot a [Table] attribútum használata­
kor, a LINQ to SQL feltételezi, hogy az entitásosztály és adatbázistábla nevei
megegyeznek.
A [Column] attribútum kicsivel tartalmasabb, mint a [Table]. Az Is P ri mary­
Key tulajdonságorr túl a columnAttribute további olyan tagokat definiál, ame-

253
24. fejezet: A LINQ API programozása

lyek lehetövé teszik, hogy teljes mértékben minősítsük az entitásosztály ösz­


szes mezőjét, és azt, hogy hogyan képezhetők le a fizikai adatbázistábla egy
adott oszlopára. A 24.1. táblázat az érdekesebb tulajdonságokat mutatja be.

canBeNull Ez a tulajdonság azt jelzi, hogy az oszlop tartalmazhat


n ull értéket.

DbType Az adatmezők deklarációja alapján a LINQ to SQL auto­


matikusan kikövetkezteti, milyen tulajdonságokat kell az
adatbázismotornak átadni. Emiatt általában csak akkor
kell közvetlenül beállítani a DbType tulajdonságot, ha di­
namikusan hozunk létre adatbázisokat a Datacontext ti­
pus createDatabase () metódusának használatávaL

IsDbGenerated A tulajdonság azt állítja be, hogy az adatbázis automati­


kusan generálja egy mező értékét.

Isversi on Ez a tulajdonság határozza meg, hogy az oszlop adatbázis­


időbélyeg, esetleg verziószám-e, vagy sem. A verziószá­
mok növekednek, az időbélyegoszlopok pedig frissülnek
minden alkalommal, amikor a kapcsolódó sor módosul.

updatecheck Ez a tulajdonság vezérli, hogy a LINQ to SQL hogyan ke­


zelje az adatbázis-ütközéseket az optimista konkurencia­
szabályozás révén.

24.1. táblázat: A [Column] attribútum nenány tulajdonsága

Entitásosztályok generálása az
SqlMetal.exe használatával

Az első LINQ to SQL példánk meglehetősen egyszerű volt, részben annak a


ténynek köszönhetően, hogy a Datacontext típusunk egyetlen adattáblával
dolgozott. Elképzelhető azonban, hogy a termékszintű LINQ to SQL alkal­
mazások több, egymással kapcsolatos adattáblával dolgoznak, s ezek mind­
egyike oszlopok tucatjait definiálhatja. Ilyen esetekben unalmas lenne manu­
álisan létrehozni minden egyes kívánt entitásosztályt Szerencsére kétféle
módon is megoldhatjuk ezeknek a típusoknak az automatikus generálását.

254
Entitásosztályok generálása az SqlMetal.exe has ználatával

Az első lehetőség az sq l metal exe parancssori segédprogram használata,


.

amelyet a Visual Studio 2008 parancssorából indíthatunk el. Ez az eszköz úgy


automatizálja az entitásosztályok létrehozását, hogy generál egy megfelelő
C#-osztálytípust az adatbázis metaadataiból. Bár az eszköz több parancssori
paraméterrel rendelkezik, a 24.2. táblázat csak a lényegesebb kapcsalókat
mutatja be.

/serve r Megadja az adatbázist hosztoló kiszolgáló nevét.

/database Megadja annak az adatbázisnak a nevét, amelyből ki


kell olvasni a metaadatokat.

/user Megadja a felhasználónevet, amellyel be kell jelent­


kezni a kiszolgálóra.

/password Megadja a jelszót, amellyel be kell jelentkezni a ki­


szolgálóra.

/views Tájékozta�a az sq l me ta l . e x e-t, hogy létező adatbá­


zisnézetek alapján generáljon kódot.

/functions Tájékozta�a az sql me ta l exe-t, hogy készítse el az


.

adatbázisfüggvények megfelelőit.

/sprocs Tájékozta�a az sq l meta l exe-t, hogy készítse el a


.

tárolt eljárások megfelelőit.

/code Tájékozta�a az sq l me tal . exe-t, hogy C#-kódként


adja ki az eredményeket (vagy VB-kódként, ha beál­
lítottuk a /language kapcsolót).

/ l angu a g e Megadja, hogy a rendszer milyen nyelven definiálja


a generált típusokat.

/ namespace Megadja névteret a generált típusok definiálásához.

24. 2. táblázat: Az sqlmetal.exe parancs paraméterei

A következő utasítás például az AutoLot adatbázis minden táblájához gene­


rál entitásosztályokat, metódust készít a GetPetName tárolt eljáráshoz, és az
AutoLotoatabase névtérbe csomagolja a generált C#-kódot (természetesen egy
sorba kell beírni a Visual Studio 2008 parancssorában):

255
24. fejezet: A LINQAPI programozása

sqlmetal /server:(local)\SQLEXPRESS /database:AutoLot


/namespace:AutoLotDatabase /code:autoLotDB.cs /sprocs

Ha végrehajtottuk az utasítást, hozzunk létre egy új parancssorialkalmazás­


projektet Li nqwi thsqlMetalGe nedcode névvel, illetőleg adjunk referenciát a
system. Data. L i nq. dll szerelvényre, és adjuk hozzá az autoLotDB. cs fájlt a

projekthez a Project >Add Existing Item menüpont segítségéveL Ezenfelül


szúrjunk be egy új osztálydiagramot a projekthe (Project > Add New Item),
és bontsuk ki a generált osztályokat (lásd a 24.4. ábrát).

Q
Autalot ® lnventory ®
Class Class
-t DataContel

13 Fields
8 Fields
# _CartD
s{l mappingSourc�
.# _Color
B Properties i' _Make
iif CreditRisks # _Orders
iif Cus1omers ;;P _PetName
'fff lnventory &fl emptyChangin ...
iif Orders B Properties
8 Methods iif CartD
il"' Autalot (+4 ov... iif Color
}}� Del e�eCredítRís .. . �Make
�"# Dele!eC'ustcmers iif Orders
Ji._ Delete!nventory 1ft PetName
:},� DeleteOrder< 1±l Methods
•(; GetPetName
1±l Events
},(; InsertCreditRisks
2 InsertCus1omers
�., Inserdnventory
�· InsertOrders
j,.;; OnCre•ted
Sp_alterdiagram
,.;; Sp_creatediagr ...
;(; Sp_dropdiagram
Sp_helpdiagra ...
•(; Sp_helpdiagrams
S p_renamediag . ..

2.;; UpdateCreditRi .. .
.;} UpdateCustom .. .

},.;; Updatelnventory
·:},li; UpdateOrders

24 4 ábra: Az sqlmetal.exe által generált entitásosztályok


. .

Van tehát egy új, a Datacontextet kibővítő típusunk, amely tartalmazza a tu­
lajdonságokat a megadott adatbázisban lévő összes adattábla számára (továb­
bá a GetPetName O tárolt eljárást a megegyező névvel rendelkező nyilvános me­
tódus képviseli). Mielőtt elkezdenénk használni ezeket az új típusokat, vizsgál­
juk meg egy kicsit alaposabban ezt az automatikusan generált kódot.

256
Entitásosztályok generálása az SqlMetal.exe használatával

A generált entitásosztályok

Az sqlmetal.exe az AutaLot adatbázis minden táblája (Inventory, Custo­


mers, Orders, CreditRisks) számára külön entitásosztályt definiált, és minden
oszlopot beágyazott egy tulajdonságba. Továbbá az egyes entitásosztályok
két interfészt implementálnak ( INotifyPropertychanging és INotifyProperty­
)
changed , amelyek mindegyike egy-egy eseményt határoz meg:

namespace System.Data.Linq
{
public interface INotifyPropertychanging
{
ll Az esemény akkor következik be, mielőtt egy tulajdonság
ll értéke megváltozik.
event PropertychangedEventHandler Propertychanging;
}
}

namespace system.componentModel
{
public interface INotifyPropertychanged
{
ll Az esemény akkor következik be, amikor egy tulajdonság értéke
ll megváltozott.
event PropertychangedEventHandler Propertychanged;
}
}

Ezek az interfészek összesen két eseményt definiálnak (Propertychanging és


)
Propertychanged , amelyek a system.componentModel névtérben meghatározott

PropertyChangedEventHandler metódusreferenciával működnek együtt. Ez a

metódusreferencia meghívhat bármilyen metódust, amely egy objektumot vár


első paraméterként, rrúg a második paraméter egy PropertychangedEventArgs.
A fenti interfészek miatt minden entitásosztály támoga�a a következő tagokat:

[Table(Name="Inventory")]
public partial class rnventory : INotifyPropertychanging,
INotifyPropertyChanged
{
public event PropertychangedEventHandler Propertychanging;
public event PropertychangedEventHandler Propertychanged;

Ha megvizsgáljuk a három entitásosztály tulajdonságainak megvalósítását,


látha�uk, hogy a set hatókör kivált minden eseményt azok számára, akik a
tulajdonság értékének a változását figyelik. Példaként nézzük meg az rnven­
tory típus PetName tulajdonságát:

257
24. fejezet: A LINQ API programozása

[Column(Storage="_PetName", DbType="Varchar(50)")]
public string PetName
{
get
{
return this._PetName;
}
set
{
if ((this._PetName != value))
{
this.OnPetNamechanging(value);
this.sendPropertychanging();
this._PetName = value;
this.sendPropertychanged("PetName");
this.OnPetNamechanged();
}
}
}

A set hatókör meghívja az onPetNamechanging() és az onPetNamechanged() me­


tódusokat az entitásosztály-típuson, hogy valójában ezek váltsák ki az esemé­
nyeket. Ezeket a tagokat azonban részleges metódusokként definiáltuk, amelyek
könnyü eseménykezelést hajtanak végre (lásd az előző kötet 13. fejezetét),lehe­
tővé téve az érdekelt hívó számára, hogy biztosítsa a szükséges megvalásítást
(ha nem,akkor a fordítás során a rendszer eltávolítja őket a típusdefinícióból):

partial void OnPetNamechanging(string value);


partial void onPetNamechanged();

Kapcsolatok definiálása entitásosztályok


használatával

Azon túl, hogy tulajdonságokat és segédmezőket definiál az adattábla oszlo­


painak megjelenítésére, az sqlmetal. ex e segédprogram az Entityset<T> tí­
pussal modellezi az összefüggő táblázatok közti kapcsolatokat. Az AutoLot
adatbázis definíált három, egymáshoz kapcsolódó belső táblát, amelyeket el­
sődleges és idegen kulcsok kapcsoltak össze (lásd a 22. fejezet). Ahelyett,
hogy SQL-központú jain szintaxis megírására kényszerülnénk a táblázatok
közti navigáláshoz, a LINQ to SQL lehetövé teszi, hogy az objektumcentrikus
C# pont operátorral navigáljunk.

258
Entitásosztályok generálása az SqlMetal.exe használatával

Az ilyesfajta táblakapcsolat érdekében a szülő-entitásosztály tulajdonság­


ként hivatkozhat a gyerrnektáblára. Ezt a tulajdonságot az [Association] att­
ribútummal jelöljük, hogy létrehozzunk egy társítási kapcsolatot a táblák kö­
zött a megegyező oszlopértékek révén. Nézzük meg például a customer típus
számára generált (részleges) kódot, amelyben bármennyi rendelés lehet:

[Table(Name="Customers")]
public partial class customers
INotifyPropertychanging, INotifyPropertyChanged
{
private Entityset<Orders> _orders;

[Association(Name="FK_Orders_customers", Storage="_orders",
OtherKey="CustiD"', Del eteRul e="NO ACTION")]
public Entityset<Orders> Orders
{
get { return this._orders; }
set { this._orders.Assign(value); }
}

A LINQ to SQL futtatórnotor számára ezen a ponton lesz nyilvánvaló az,


hogy az orders tulajdonság olyan tag, amely lehetövé teszi a Custorners táb­
lábóZ az Orders táblába történő navigálást az otherKey tulajdonság által defi­
niált oszlop révén. Az Entityset<T> tagváltozót arra használjuk, hogy jelké­
pezze az adott kapcsolat "egy a többhöz" (one-to-rnany) természetét

Az erősen tipusos DataContext

Az sq l meta l . exe által generált kód utolsó figyelemre rnéltó eleme a Datacon­
textből származtatott típus. Az előző példában létrehozott AutaLotDatabase
osztályhoz hasonlóan rninden táblát Tab l e<T>-kornpatibilis tulajdonság jel­
képez. Ez az osztály ernellett rendelkezik olyan konstruktorokkal, amelyek
egyike paraméterként várja az I Dbean n ection osztályt megvalósító objektu­
mot, amely egy ADO.NET-kapcsolatobjekturnot jelképez (a LINQ to SQL és
az ADONET-típusok keverhetők egyetlen alkalmazásorr belül).
Emellett ez a Datacontextből származó osztály rnuta�a, hogyan dolgozha­
tunk az adatbázisban meghatározott tárolt eljárásokkal. Mivel megadtuk az
/sprocs kapcsolót a sqlmetal.exe-nek, találunk egy GetPetName O nevű rnetódust:

259
24. fejezet: A LINQAPI programozása

[Function(Name="dbo.GetPetName")]
[return: Parameter(DbType="Int")]
public int GetPetName([Parameter(DbType="Int")]
system.Nullable<int> cariD,
[Parameter(DbType="Char(lO)")]
ref string petName)
{
IExecuteResult result = this.ExecuteMethodcall(this,
((Methodinfo)(Methodinfo.GetcurrentMethod())),
cariD, petName);
petName = ((string)(result.GetParametervalue(l)));
return ((int)(result.Returnvalue));
}

A GetPetName() metódus a [Function] attribútumokkal és a [return:] attri­

bútumminősítővel rendelkezik. Továbbá az összes paraméter rendelkezik a


[Parameter] attribútummaL A megvalósítás az örökölt ExecuteMethodca ll O

metódust (és reflexiós szolgáltatást) használja, hogy biztosítsa a hívó számára


a tárolteljárás-hívást, valamint az eredmény visszaadását.

A generált tipusok használata

A továbbiakban vizsgáljuk meg a Program típus alábbi implementációját, amely

meghívja a tárolt eljárásunkat

class Program
{
const string cnstr =
@"Data source=(local)\SQLEXPRESS;rnitial catalog=AutoLot;" +

"Integrated security=True";

static void Main(string[] args)


{
console.writeLine("***** More Fun with LINQ to SQL *****\n");
AutaLot carsDB = new AutoLot(cnStr);
InvokeStoredProc(carsDB);
console.ReadLine();
}

private static void InvokeStoredProc(AutoLot carsDB)


{
int cariD = O;
string petName "";
Console.Write("Enter ID: ");
cariD = int.Parse(console.ReadLine());

260
Entitásosztályok generálása az SqlMetal.exe használatával

ll Meghívja a tárolt eljárást, és kiírja a becenevet.


carsDB.GetPetName(cariD, ref petName);
Console.writeLine("car ID {O} has the petname: {1}",
cariD, petName);
}
}

A LINQ to SQL teljes méctékben elrejti előlünk az alapul szolgáló tárolt eljá­
rás logikáját. Itt nem kell manuálisan sqlcommand objektumot létrehozni, fel­
tölteni a paramétergyűjteményt vagy meghívni az ExecuteNonQuery() metó­
dust. Ehelyett egyszerűen meghívjuk a Datacontextből származtatott típus
GetPetName() metódusát. A kimeneti paraméterek referenciaparaméterként
jelennek meg, ezért a C# ref kulcsszó használatával kell meghívni őket.
Tételezzük fel, hogy van egy másik segédfüggvényünk (amelyet szintén a
Main() metódusból hívunk meg), ennek neve PrintOrderForcustomer(). Ez a
metódus kiír néhány megrendelésinformációt a megadott ügyféllel kapcso­
latban, valamint az ügyfél kereszt- és vezetéknevét

static void PrintorderForcustomer(AutoLot carsDB)


{
int custiD = O;
Console.write(" Enter customer ID: ");
custiD = int.Parse(console.ReadLine());

var customerorders =

from cust in carsDB.Customers


from o in cust.orders
where cust.CustiD == custiD
select new { cust, o };

Console.writeLine("***** order Info for customer ID: {O}. *****"


custiD);
foreach (var q in customerorders)
{
Console.writeLine("{O} {l} is order ID# {2}.",
q.cust.FirstName.Trim(),
q.cust.LastName.Trim(),
q.o.orderiD);
console.writeLine("{O} bought car ID# {1}.",
q.cust.FirstName.Trim(), q.o.cariD);
}
}

A 24.5. ábra muta�a azt a kimenetet, amikor az l-es azonosítójú ügyféllel


kapcsolatban kérdezünk le.

261
24. fejezet: A Ll NQ API programozása

24. 5. ábra: Eg�j adott ÜgJJfél rendelési információinak kiírása

A LINQ to SQL előnye ismételten az, hogy következetes, objektumalapú mo­


dell használatával dolgozhatunk a relációs adatbázisokkaL A LINQ-lekérde­
zéskifejezésünk megvilágításához adjuk hozzá a következő utasítást a Print­
orderForcustomer() metódus végéhez:

console.writeLine("\ncustomerorders as a string: {O}",


customerorders);

Ha újra lefutta�uk a programot, azt tapasztalha�uk, hogy a lekérdezéskifeje­


zésünk sztringformátumú értéke az alapul szolgáló SQL-lekérdezést muta�a:

SELECT [tO]. [Fi rstName], [tO]. [LastName], [tO]. [CustiD],


[tl].[OrderiD], [tl].[CariD], [tl].[CustiD] AS [CustiD2]
FROM [customers] AS [tO], [orders] AS [tl]
WHERE ([tO].[CustiD] =@pO) AND ([tl].[CustiD] = [tO].[CustiD])

Forráskód A LinqWithSqlMetalGenedCode kódfájlokat a forráskódkönyvtár 24. fejezetének


alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Entitásosztályok létrehozása a Visual


Studio 2008 használatával

Végül hozzunk létre új parancssori konzolalkalmazást LinqTosql cr u d néven,


valamint adjunk hozzá egy referenciát a system. Data. L i n q. dll szerelvényre.
Ezúttal ahelyett, hogy az sqlmetal . exe segítségével generálnánk az entitásosz­
tályainkat, a Visual Studio 2008-ra bízzuk a "piszkos munkát". Ehhez válasz­
szuk a Project > Add New Item menüpontot, majd adjunk hozzá egy új LINQ
to SQL Classes elemet AutoLotobj eets néven (lásd a 24. 6. ábrát).

262
Entitásosztályok létrehozása a Visual Studio 2008 használatával

Add New ltem � ünqToSqiCrud \JLíii;lij

�attgori� Iemplate:s: �G
Visual C# Jtems Diagram Cli!�ss Control

Code
Data
e r
G ne al

DataSet

Debugger

HTML Page
li
leon File

Installeretass

Interiace
Web Visualizer
Windows Forms
n
�� �ld ll tre1. � �

WPF

! local
JS<:ript File
L �UNQto SQ
Clas�
local
Database Databa...
MDJ Parent
Form
Report

.
t]1 '�
Report
� ;,_ (j
Resources Servicewba...
[ii1
Settings File

Style Sheet
[i1
Text File
Wizard File Database

ll- � i)
. ..... ... ,.. [ji
.
"''
ti]
- li
--
UNQto SQl dasses mapped to retatíonal obj�.

t!ame: AutolotObjects.dbml

1\dd J[ Cancol

24.6. ábra: A LINQ to SQL Classes elem ugt;anazokat a feladatokat végzi el, mint az sqlmetal.exe

Nyissuk meg a Server Explorert, és győződjünk meg róla, hogy van aktív
kapcsolatunk az AutoLot adatbázissal (ha nincs, akkor jobb gombbal kattint­
sunk a Data Connections ikoma, és válasszuk az Add Connectiont). Jelöljük
ki az összes táblát, és húzzuk át őket a LINQ to SQL tervezőfelületre. Ekkor a
képernyő a 24. 7. ábrához hasonlít.

Cml- @ IIIW!IItory @

8 Properties 13 Properties

·J jöj' Cust!O ·t:ffi' CarlO

'fff FirstName '5' Make


'!if LastName ji' Color

� PetName

CUstomer @ •
Onkr @
8 Properties
� 13 Properties
·l 'fff CustiD
'fff' FirstName
':ji' Order!D

'fff' LastName
'fff' Cust!D
- �CarlO

24.7. ábra: Entitásosztályok létrehozása a LINQ to SQL tervezővel

Miután lefordítottuk, a Solution Explorerben nyissuk meg a kapcsolódó *.cs

fájlt (lásd a 24.8. ábrát).

263
24. fejezet: A LINQ API programozása

Class Vi""'

t=·,
l <Search>
,_
l±l·D Proj.ct R.terencos

.
G·O
.
LinqToSqiCrud
!±J � AutolotObjectsDataContext
@. <1$ CreditRisk
éJ· � Customer .
·

$�llllll!lm
!±J·� Order
, ffi . � Program
1'1 ..
�J
@ {) LinqToSqiCrud.Propertios

.
i ·{J� OnPetNameChangedO
L IJY OnPetNameChanging(string)
C::,
! ,;l OnValidaleO
· l
i . ,$ 11
,·-;jY SendPropertyChanged(string)
SendPropertyChangingO

1l i: ·'ffi'
·!5' Car!D


Color
� •.. fT Make --
_

i i -!5' Orders
! L'ffj' PetName

l
l ·. ;fi _CarlO 1

( ·� emptyChangingEventArgs
; :;fi _Color

�� Solution Explorer)� Class


E' l

===='�
View l
-- ---:::
-r =

24.8. ábra: A generált névtér a tartalmazott típusokkal

Végignézve a generált C#-kódot, látható, hogy ugyanezt az általános kódot


generálta az sql metal . exe parancssori segédprogram is. Továbbá a vizuális
LINQ to SQL tervező hozzáadott a projektünkhöz egy app. con fi g fájlt a
szükséges kapcsolatsztringadatok tárolására.
Most, hogy minden generált osztály rendelkezésre áll, nézzük meg a
Program osztályt, amely az Inventory táblán bemuta�a az adatok beszúrását,
módosítását és törlését.

Új elemek beszúrása

Ahhoz, hogy új elemet szúrjunk be egy relációs adatbázisba, csak annyit kell
tennünk, hogy létrehozzuk az adott entitásosztály egy új példányát, majd
hozzáadjuk a oatacontext által fenntartott Tabl e<T> típushoz, és meghívjuk
rajta a submitchanges() metódust. Az alábbi InsertNewcars() metódus hoz­
záad két elemet az Inventory táblához. Az első megközelítés közvetlenül be­
állí�a az rnventory entitásosztály minden mezőjét, míg a második a tömörebb
objektuminicializáló szintaxist használja:

264
Entitásosztályok létrehozása a Visual Studio 2008 használatával

static void lnsertNewcars(AutoLotObjectsDataContext ctx)


{
Console.writeLine("***** Adding 2 cars *****");
int newcarlO = O;
console.write("Enter ID for Betty: ");
newcarlO = int.Parse(Console.ReadLine());

ll új sor hozzáadása "terjengős" szintaxissal.


rnventory newcar = new lnventory();
newcar.Make = "Yugo";
newcar.color = "Pink";
newcar.PetName = "Betty";
newcar.carlO = newcarlO;
ctx.lnventories.lnsertonsubmit(newcar);
ctx.submitchanges();

console.write("Enter ID for Henry: ");


newcarlO = int.Parse(Console.ReadLine());

ll új sor hozzáadása a "tömör" objektuminicializáló szintaxis


ll használatával.
newcar = new lnventory {Make = "BMW", color = "silver",
PetName = "Henry", carlo = newcarlo };

ctx.lnventories.lnsertönsubmit(newcar);
ctx.submitchanges();
}

Létező elemek módositása

Egy elem módosítása meglehetősen egyértelmű. A LINQ-lekérdezés alapján


nyerjük ki az első elemet, amely megfelel a keresési feltételnek. Miután frissí­
tettük az objektum állapotát, hívjuk meg a submitchanges() metódust.

static void updatecar(AutoLotobjectsDatacontext ctx)


{
Console.writeLine("*'"'*1' updating color of 'Betty' ****;'");

ll Módosítsuk Betty színét világos rózsaszínre.


var betty = (from c in ctx.lnventories
where c.PetName == "Betty"
select c).First();
betty.color = "Green";
ctx.submitchanges();
}

265
24. fejezet: A LINQ API programozása

Létező elemek törtése

Végül, ha törölni szeretnénk egy elemet a relációsadatbázis-táblából, egysze­


rűen hozzunk létre egy LINQ-lekérdezést, hogy megtaláljuk azt az elemet,
amelyre nincs szükségünk, és távolítsuk el a Datacontext megfelelő Table<T>
tagváltozójából a Deleteonsubmit() metódus segítségéveL Ezután ismét a
su bmitchanges() metódust kell megívni.

static void DeleteCar(AutoLotobjectsDatacontext ctx)


{
int carToDelete = O;
Console.write("Enter ID of car to delete: ");
carToDelete = int.Parse(console.ReadLine());

ll Adott autó eltávolítása.


ctx.Inventories.Deleteonsubmit((from c in ctx.Inventories
where c.CariD carToDelete
==

select c).First());
ctx.submitchanges();
}

Most már meghívhatunk minden metódust a Main() metódusból, hogy ellen­


őrizzük a kimenetet:

static void Main(string[] args)


{
console.writeLine("***** CRUD with LINQ to SQL *****\n");
const string cnstr =

@"Data source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;" +

"Integrated Security=True";

AutoLotobjectsDatacontext ctx =

new AutoLotObjectsDatacontext(cnstr);
InsertNewcars(ctx);
Updatecar(ctx);
Deletecar(ctx);
console.ReadLine();
}

Ezzel befejeztük a LINQ to SQL áttekintését. Természetesen az itt leírtaknál


jóval többet el lehetne mondani; ám ennyi elegendő ahhoz, hogy belemerül­
jünk a részletekbe.

Forráskód A LinqToSqlCrud kódfájlokat a forráskódkönyvtár 24. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

266
XML-dokumentumok kezelése a LINQ to XML használatával

XML-dokumentumok kezelése a LINQ to


XML használatával

Végezetül bemutatjuk a LINQ to XML szerepét, amely azt lehetővé teszi, hogy
LINQ-lekérdezéskifejezéseket alkalmazzunk XML-dokumentumokon. Az
XML-adatok reprezentációja mélyen beágyazódott a .NET keretrendszerébe.
Az alkalmazások és a webalapú konfigurációs fájlok XML-ként tárolják az
adatokat. Az ADO.NET Datasetek könnyedén elmentik (vagy betöltik) XML­
ként az adatokat. A Windows Presentation Foundation egy XML-alapú nyelv­
tant (XAML) használ az asztali felhasználói felületek ábrázolására, a Windows
Communication Foundation (valamint a .NET-remoting API) szintén számos
beállítást tárol olyan jól formázott sztringként, amelyet XML-nek lúvunk.
Noha az XML valóban mindenhol jelen van, programozása mindig is
unalmas, terjengős és összetett volt, ha valaki nem volt tisztában számos
XML-technológiával (XPath, XQuery, XSLT, DOM, SAX stb.). A .NET plat­
form kezdetei óta a Microsoft egy olyan speciális szerelvényt biztosít az
XML-dokumentumok programozásához, amelynek system.xml.dll a neve.
Ezen a bináris fájion belül számos névtér és típus található a különböző XML­
programozási módszerekhez, valamint néhány .NET-specifikus XML API,
mint például az xml Reader/Xmlwri ter modellek.

LINQ to XML: egy jobb DOM

Ahogy a LINQ to SQL célja az, hogy a relációs adatbázisok kezelését közvet­
lenül a .NET programozási nyelvekbe integrálja, úgy a LINQ to XML is ezt a
célt tűzi ki az XML-adatok feldolgozása terén. Nemcsak olyan eszközként
használhatjuk a LINQ to XML-t, amellyel LINQ-lekérdezésekkel megszerez­
hetünk adatrészhalmazokat egy meglévő XML-dokumentumból, hanem
ugyanezt az API-t használhatjuk XML-adatok létrehozására, módosítására és
elemzésére is. Emiatt a LINQ to XML-re gondolhatunk úgy is, mint egy "jobb
DOM" programozási modellre. Emellett, ahogy a LINQ to SQL együttműköd­
het az ADO.NET-típusokkal, a LINQ to XML is dolgozhat a system.xml .dll
szerelvények számos tagjával.

267
24. fejezet: A LINQAPI programozása

A System.Xml.Xlinq névtér

Az alapvető LINQ to XML szerelvény (system.xml.Linq.dll) nagyon kevés


típust definiál a következő három különböző névtér ben: system. xml.Linq,
system.xml. Scherna és system.xml.XPath (lásd a 24. 9. ábrát).

El·.O System.Xmi.Linq G
éJ.-O System.Xmi.Linq
. � Extensions
$-· LoadOptions
GJ·-� SaveOptions
$··'1: XAttribute
Gr·'1: XCData
$-� XComment
ffi . � XContainer
. .

$··'\$ XDeclaration
$···'!$ XDocument
@ ·'1$ XDocumentType
$ '1$ XE!ement
·

@.� XName
S··"it XNamespace
é··'!$ XNode
$--� XNode:DocumentOrderComparer
$-'1: XNodeEqualityComparer
S· '1$ XObject
�h,jll XObjectChange
rB·· 'I$ XObjectChangeEventArgs
. tiJ·dil XObjectChangeEventHandler
j ffi-··'i: XProcessinglnstruction
: @ � XStreamingEiement
...

: s-01: xrext
8.-ol§§fi!Jffili'D
. rB···'I$ Extensions
EJ·O System.Xmi.XPath
@... t Extensions
13
24. 9. ábra: A System.Xml.Linq.dll névterei

Az alapnévtér, a system.xml . Linq olyan jól kezelhető típuskészletet tartalmaz,


amely egy XML-dokumentum különböző aspektusait jelképezi (elemeit és att­
ribútumait, XML-névtereket, XML-megjegyzéseket, feldolgozási utasításokat
stb.). A 24 3 táblázat muta�a be a system.xml .Li nq névtér alapvető tagjait.
. .

. . . ' . . .
{. �·. '
·. :

XAttribute Egy adott XML-elem XML-attribútumát jelképezi.

xcomment XML-megjegyzést jelképez.

XDeclaration Egy XML-dokumentum nyitó deklarációját jelképezi.

xoocument Egy XML-dokumentum összességét jelképezi.

268
XML-dokumentumok kezelése a LINQ to XML használatával

XElement Egy adott elemet jelképez egy XML-dokumentumban.

XNameiXNamespace Nagyon egyszerű módszert biztosít XML-névterek


definálására és hivatkozására.

24. 3. táblázat: A System.Xml.Linq névtér néhány tagja

Ennek (és más) típusoknak a vizsgálatához hozzunk létre egy parancssori kon­
zolalkalmazást LinqToxmlBas i cs névvel, majd importáljuk a system. xml. L i nq
névteret a kezdeti kódfájlba.

XML-dokumentumok létrehozása programozottan

Az eredeti .NET XML programozási modellel (system.xml.dll) ellentétben


XML-dokumentumot UN Q-val csak funkcionális módon lehet kezelni. Így ahe­
lyett, hogy létrehoznánk egy dokumentumot a nagyon terjengős DOM API­
val, a LINQ to XML lehetővé teszi, hogy ezt "DOM-mentesen" tegyük meg.
Ezzel nemcsak nagymértékben csökken a szükséges kód mennyisége, de a
programozási modell leképezhető majdnem közvetlenül a jól formált XML­
adatok formátumába. Ennek bemutatásához adjunk hozzá egy metódust a
Program osztályunkhoz createFunctionalxmlElement() névvel, az alábbi meg­

valósítással:

static void CreateFunctionalxmlElement()


{
ll "Funkcionális" megközelítés egy
ll XML-elem létrehozásához a memóriában.
XElement inventory =

new XElement("Inventory",
new XElement("car", new XAttribute("ID", "1"),
new XElement("Color", "Green"),
new XElement("Ma k e", "BMW"),
new XElement("PetName", "stan")
)
);
ll A ToString() meghívása az XElementünkön.
console.WriteLine(inventory);
}

Az inventory XElement objektumának konstruktora valójában további XElement


és XAttribute objektumok fája. Utasításaink gondos behúzásával a forráskód
megjelenése hasonló magához az XML-dokumentumhoz. Ha a Main() metódus­
ból meghívjuk a metódusunkat, a 24.10. ábrán látható kimenetet fogjuk kapni.

269
24. fejezet: A LINQ API programozása

24.1 O. ábra: XML-dokumentum létrehozásánakfunkcionális megközelítése

Ahhoz, hogy egy teljes XML-dokumentumot hozzunk létre a memóriában


(megjegyzésekkel, feldolgozási utasításokkal, nyitó deklarációkkal stb.),
megadhatjuk az objektumfát egy xoocument típus konstruktorába. Gondoljuk
végig a következő createFunctionalxmlooc() metódust, amely először létre­
hoz egy dokumentumot a memóriában, majd elmenti egy helyi fájlba:

static void CreateFunctionalxmlDoc()


{
XDocument inventoryooc =

new XDocument(
new xoeclaration("l.O", "utf-8", "yes"),
new xcomment("current Inventory of AutoLot"),
new XElement("Inventory",
new XElement("car", new XAttribute("ro", "1"),
new XElement("color", "Green"),
new XElement("Make", "BMW"),
new XElement("PetName", "stan")
) '

new XElement("Car", new XAttribute("ro", "2"),


new XElement("color", "Pink"),
new XElement("Make", "Yugo"),
new XElement("PetName", "Melvin")
)
)
);
ll A dokumentum megjelenítése és mentése a lemezre.
Console.writeLine(inventoryDoc);
inventoryooc.Save("simplernventory.xml");
}

A 24.11. ábra mutatja a Visual Studio 2008-ban megnyitott simplernventory.


xml fájlt.
Az XElement és az xoocument típusok meghatároznak egy olyan konstruk­
tort, amely XName típust vesz fel első paraméterként és objektumok paramé­
tertömbjét másodikként. Az XName típust a LINQ to XML-ben arra használjuk,
hogy ábrázoljuk vele (nyilvánvalóan) a létrehozandó elem nevét, míg az ob-

270
XML-dokumentumok kezelése a LINQ to XML használatával

jekturnak paramétertömbje tartalmazhat bármennyi további LINQ to XML


típust (xcomment, XProcessi nginstructi on, XE l ement, XAttribute stb.), illetve

egyszerű sztringeket (elemtartalomhoz) vagy az IEnumerable típust imple­


mentáló objektumot.

�x

1; �?xml version="l.O" encodinq="utf-8" standalone="yes"?>


2: <!--Currenc Inventory of AutcLot-->

�:1.
3�8 <!nventory>
4 'é <Car ID="l ">
sj <Color>Green</Color>
6' <Hake>BMW</Hake> '

7! <PetName>Stan</PetName> '
3! </Car>
l
2;- <Car !!)>=>:"2">
10' <Color>Piok</Color>
ll· <Make>Yugo</Make>
12� <PetName>Melvin</PetName>
13' </Car>
1"3[ </In�tentory>

' [_, __ _ · - -----.


- .. -· -·-
"'
- --�- - - - - · - - -·- - - .. - - -- - ·- ..:J
24. 11. ábra: A rögzített XML-dokumentum

Dokumentumok létrehozása LINQ-lekérdezésekből

Ami az utolsó pontot illeti, tételezzük fel, hogy van egy névtelen típusokból

álló névtelen tömbünk, amely egy egyszerű car osztályt képvisel. Készíthe­
tünk egy UNQ-lekérdezést, amely kiválogat minden név j érték párt a tömb­
ből, hogy dinamikusan létrehozhassunk egy új XElement típust:

static void createXmlDocFromArray()


{
ll Anonymaus típusok névtelen tömbjének létrehozása.
var data = new [] {
new { PetName "Melvin", ID = 10 },
new { PetName "Pat", ID = ll},
new { PetName "Danny", ID = 12 },
new { PetName "clun k er", ID = 13 }
};

ll Most listázzuk az egész tömböt, hogy létrehozzunk


ll egy XElement objektumot.
XElement vehicles =

new XElement("Inventory",
from c in data

271
24. fejezet: A LINQAPI programozása

select new XElement("car",


new XAttribute("ID", c. ID),
new XElement("PetName", c.PetName)
)
);
console.writeLine(vehicles);
}

XML-tartalom betöltése és elemzése

Az XElement és az xoocument típusok támoga�ák a Load() és Parse() metódu­


sokat, amelyek lehetövé teszik egy XML-objektummodell feltöltését sztring­
adatokból vagy külső fájlokbóL Vizsgáljuk meg a következő metódust, amely
mindkét megközelítést bemuta�a:

static void LoadExistingXml()


{
ll XElement létrehozása sztringből.
string myElement =
@"<Car ID ='3'>
<Color>Yellow</Color>
<Make>YUQO</Make>
</Car>";
XElement newElement = XElement.Parse(myElement);
Console.writeLine(newElement);
console.writeLine();

ll A simplernventory.xml fájl betöltése.


XDocument myDoc = XDocument.Load("Simplernventory.xml");
console.writeLine(myDoc);
}

Forráskód A LinqToXmlBasics kódfájlokat a forráskódkönyvtár 24. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Navigálás egy memóriában lévő


dokumentumban

A LINQ to XML-lel kapcsolatban a következőkben azt vizsgáljuk meg, hogy


hogyan navigálhatunk egy adott dokumentumban ahhoz, hogy meghatároz­
zuk bizonyos elemek/ attribútumok helyét.

272
Navigálás egy memóriában lévő dokumentumban

A LINQ to XML objektummodell számos olyan metódust biztosít, ame­


lyekkel programozottan navigálhatunk egy dokumentumban, ugyanígy a
LINQ-lekérdezéskifejezéseket is használha�uk erre a célra.
Rövid példaként először hozzunk létre új parancssori konzolalkalmazást
NavigationwithLinqToXml néven, és importáljuk a system.Xml.Linq névteret
Ezután adjunk hozzá egy új XML-dokumentumot aktuális projektünkhöz
rnventory. xml névvel, ez bejegyzések kis gyűjteményét tárolja a gyökér <In­
ventory> elemen belül, amelynek lehetséges tartalma a következő:

<?xml version="l.O" encoding="utf-8"7>


<Inventory>
<Car carro ="0">
<Make>Ford</Make>
<Color>Blue</Color>
<PetName>Chuck</PetName>
</Car>
<Car cariD ="l">
<Make>VW</Make>
<Color>Silver</Color>
<PetName>Mary</PetName>
</Car>
<Car cariD ="2">
<Make>Yugo</Make>
<Color>Pink</Color>
<PetName>Gipper</PetName>
</Car>
<Car cariD ="55">
<Make>Ford</Make>
<Color>Yellow</Color>
<PetName>Max</PetName>
</Car>
<Car cariD ="98">
<Make>BMW</Make>
<Color>Black</Color>
<PetName>Zippy</PetName>
</Car>
</Inventory>

Jelöljük ki ezt a fájlt a Solution Explorerben, és használjuk a Properties ablakot,


hogy átállítsuk a Copy to Output Directory tulajdonságot Copy Always értékre
(ezzel biztosí�uk, hogy a fájl másolata bekerüljön a Bin mappába). Végül mó­
dosítsuk a Main() metódust, hogy betöltse ezt a fájlt a memóriába az XElement.
Load() metódus használatávaL A helyi doc változót különböző segédmetódu­
soknak adjuk át, hogy különböző módszerekkel módosítsuk az adatokat:

273
24. fejezet: A LINQ API programozása

static void Main(string[] args)


{
Console.writeLine("***** Fun with LINQ to XML *****\n");

ll Az Inventory.xml dokumentum betöltése a memóriába.


XElement doc = XElement.Load("Inventory.xml");

ll Ezek mindegyikét mi fogjuk létrehozni...


PrintAllPetNames(doc);
console.writeLine();
GetAllFords(doc);
console.ReadLine();
}

A P rintAllPetNames() metódus bemutalja az X Elemen t használatát A oescen­

dants() metódus lehetővé teszi egy olyan adott alelem meghatározását,

amelyhez el akarunk navigálni annak érdekében, hogy LINQ-lekérdezéskife­


jezést alkalmazzunk. Ekkor kiválasztunk minden PetName értéket, és kiíraljuk
a tartalmukat a konzolra:

static void PrintAllPetNames(XElement doc)


{
var petNames = from pn in doc.oescendants("PetName")
select pn.Value;

foreach (var name in petNames)


console.WriteLine("Name: {O}", name);
}

A GetAllFords() metódus nagyon hasonló. A bejövő XEl ement alapján defini­


álunk egy where operátort, és kiválaszijuk az összes XElementet, ahol a Make
elem értéke "Ford":

static void GetAllFords(XElement doc)


{
var fords = from c in doc.oescendants("Make")
where c.value == "Ford"
select c;

foreach (var f in fords)


console.writeLine("Name: {O}", f);
}

A 24.12. ábra mutalja a program kimenetét

274
Navigálás egy memóriában lévő dokumentumban

24. 12. ábra: A LINQ to XML működés közben

Adatok módositása egy XML-dokumentumban

A LINQ to XML több módszert is biztosít XML-tartalom beszúrására, törlésére,


másolására és módosítására. Új XElement hozzáadása egy meglévő XElement

(vagy xoocument) típushoz csak annyiból áll, hogy az Add() metódust meghív­
juk, amely hozzáadja az adatot az elem/ dokumentum végéhez. Ennek alterna­
tívájaként meghívha�uk az AddFirst() metódust, hogy az adatot az elem/do­
kumentum elejéhez adja, illetve az AddAfterThis ()l AddBeforeThisO metódust,
hogy az adatot egy meghatározott helyre szúrja be.
A tartalom módosítása vagy törlése elég egyértelmű. Miután létrehoztunk
egy LINQ-lekérdezéskifejezést a manipulálni kívánt elem (vagy elemek) azo­
nosítására, egyszerűen hívjuk meg a R ep lacecontentO metódust (módosítás­
hoz) vagy a RemoveOIRemovecontentO metódust (adatok törléséhez). Egyszerű
példaként vizsgáljuk meg a következő kódot, amely hozzáad néhány új <Car>
elemet a bejövő XElement paraméterhez:

static void AddNewElements(XElement doc)


{
ll Hozzáad öt új lila Fordat a bejövő dokumentumhoz.
for (int i = O; i < 5; i++)
{
ll Létrehoz egy új XElement objektumot.
XElement newcar =

new XElement("car", new XAttribute("ID", + 1000)'


new XElement("Color", "Green"),
new XElement("Make", "Ford"),
new XElement(''PetName", "")
);
ll Hozzáadás a dokumentumhoz.
doc.Add(newcar);
}
ll A módosítások megjelenítése.
console.writeLine(doc);
}

275
24. fejezet: A LINQAPI programozása

A könyv további részében láthatunk még különböző LINQ-lekérdezéseket, ám


az itt vizsgált API-k mindegyikét (LINQ to DataSet, LINQ to SQL és LINQ to
XML) részletesen ismerteti a .NET Framework 3.5 SOK dokumentációja.

Forráskód A NavigationWithLinqToXml kódfájlokat a forráskódkönyvtár 24. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Összefoglalás

Ez a fejezet három LINQ-centrikus API bemutatásával bővítette a LINQ-kal


kapcsolatos ismereteinket. Vizsgálódásunkat azzal kezdtük, hogy hogyan
alakíthatunk át egy ADO.NET oatasetet LINQ-kompatibilis tárolóvá az As­
Enumerable() bővítő metódus használatávaL Az IEnumerable<T> típus elérése
után képessé váltunk tetszőleges LINQ-lekérdezések alkalmazására.
A LINQ to DataSethez szarosan kapcsolódik a LINQ to SQL API, amely
nemcsak azt teszi lehetövé, hogy lekérdezéskifejezéseket alkalmazzunk relá­
ciós adatbázisokban tárolt adatokra, hanem olyan infrastruktúrát is biztosít,
amely lényegében beágyazza az alapul szolgáló ADO.NET-adattípusok min­
den formáját. Ahogyan láthattuk, a Datacontext típus használható az összes
megszakott adatbázissal kapcsolatos művelet végrehajtására, beleértve a tá­
rolt eljárások meghívását is.
Végül a LINQ to XML API-t vizsgáltuk meg. A LINQ to SQL-hez hason­
lóan ez is használható LINQ-lekérdezések megadására egy XML-dokumen­
tum adatain, illetve XML-dokumentumok létrehozására funkcionális módon.
A végeredmény látványos egyszerűsítése annak, hogy a .NET platform ho­
gyan támogatja az XML-dokumentumok kezelését.

276
HUSZONÖTÖDIK FEJEZET

AWCF

A .NET 3.0-hoz, kifejezetten elosztott rendszerek készítéséhez fejlesztettek ki


egy API-t, a Windows Communication Foundationt (WCF). A többi -koráb­
ban használt- elosztott API-val ellentétben (DCOM, .NET-remoting, XML­
webszolgáltatások stb.) a WCF olyan egyszerű, egységesített és kiterjeszhető
programozási objektumrnodellt kínál, amely sok, korábban szerteágazó el­
osztott technológiával működik együtt.
Először megnézzük, rniért van szükség a WCF-re, valamint a korábbi el­
osztott API-k rövid áttekintésével megvizsgáljuk azokat a problémákat, ame­
lyekre a WCF megoldást kínál. Ezután áttérünk a programozási modellt ké­
pező .NET-szerelvényekre (és alapvető típusokra), végül pedig WCF-szolgál­
tatásokat, -hosztokat és -klienseket készítünk különböző WCF fejlesztési esz­
közök segítségéveL

Megjegyzés Jóllehet ez a fejezet kellő alapot nyújt a WCF-fejlesztéshez, a téma átfogó le­
írását azonban lásd Chris Peiris és Dennis Mulder Pro WCF: Practical Microsoft SOA Imp/emen­
tation című könyvében (Apress 2007).

Néhány elosztott API

A Windows operációs rendszer az idők folyamán több API-t is biztosított el­


osztott rendszerek építéséhez. Igaz, hogy az "elosztott rendszer" többnyire
legalább két, hálózatba kötött számítógépet jelent, ám tágabb értelemben a
kifejezés vonatkozhat egyszerűen két végrehajtható fájlra, amelyek adatokat
cserélnek, még akkor is, ha történetesen rnindkettő ugyanazon a gépen fut.
Ha ezt a definíciót használjuk, a következő fontos kérdést kell feltenni, ha el­
osztott API-t választunk ki az aktuális programozási feladathoz:

A rendszert csak "házon belül" használják majd, vagy külső felhasználóknak is hoz­
záférést kell biztosítanunk az alkalmazás funkcionalitásához?
25. fejezet: A WCF

Ha házon belüli elosztott rendszert készítünk, akkor sokkal nagyobb az esély


arra, hogy minden összekapcsolt számítógép ugyanazzal az operációs rend­
szerrel üzemel, valamint ugyanazt a programozási keretrendszert (.NET,
COM, J2EE stb.) használja, így a meglevő biztonsági rendszert használha�uk
hitelesítésre, engedélyezésre és így tovább. Ebben az esetben a teljesítmény
miatt hajlamosak lehetünk egy bizonyos API-t választani, olyat, amely egy
bizonyos operációs rendszerhez vagy programozási keretrendszerhez köt
bennünket.
Ám a kívülről is elérhető rendszer készítésekor jó néhány egyéb kérdést is
figyelembe kell venni. Először is, nagy valószínűséggel a külső felhasználók­
nak nem lehet meghatározni, milyen operációs rendszert használjanak, mi­
lyen programozási keretrendszerben készítsék programjaikat, vagy hogyan
konfigurálják a biztonsági beállításokat.
Továbbá, ha történetesen nagyobb vállalatnál vagy a felsőoktatásbán dolgo­
zunk, ahol többféle operációs rendszert és programozási technológiát is alkal­
maznak, a házon belüli alkalmazás is ugyanazokkal a nehézségekkel fog szem­
benézni, mint a kívülről elérhető program. Mindkét esetben rugalmasabb elosz­
tott API-t kell használni, hogy biztosítsuk az alkalmazás legjobb elérhetőségét.
Ha megvan a válasz erre a kulcskérdésre, a következő feladat a használni
kívánt API (vagy API-k) kiválasztása. A továbbiakban röviden áttekintünk
néhány olyan fontosabb elosztott API-t, amelyeket korábban a Windows­
szoftverfejlesztők használtak. Ez az áttekintés meg fogja mutatni a Windows
Communication Foundation hasznosságát.

Megjegyzés A WCF·nek (és az azt körülvevő technológiáknak) semmi köze a HTML·alapú we·
bes alkalmazások fejlesztéséhez. Igaz, hogy a webes alkalmazásokat is "elosztottnak" tekint·
hetjük olyan értelemben, hogy az adatforgalomban tipikusan két számítógép vesz részt, de a
WCF célja az, hogy számítógépekkel létesítsen kapcsolatot a távoli komponensek funkcionali·
tásának megosztásához - nem pedig HTML megjelenítése a böngészőben. (A 31. fejezet fog·
lalkozik a .NET platforrnon a webhelyek fejlesztésének vizsgálatávaLJ

A DCOM szerepe

A .NET platform kiadása előtt a Distributed Component Object Model


(DCOM - elosztott komponens-objektummodell) volt a választott elosztott
API a Microsoft-központú fejlesztésekhez. A DCOM révén elosztott rendsze­
reket lehetett építeni COM-objektumokkal, a rendszerleíró adatbázissal és
persze fáradságos munkával. A DCOM egyik előnye az volt, hogy lehetövé
tette a komponensek elhelyezkedésének átlátszóságát. Azaz ez lehetövé tette,

278
Néhány elosztott API

hogy a kliensprogramot úgy készítsük el, hogy a távoli objektumok elhelyez­


kedését nem "égettük" a kódba. Függetlenül attól, hogy a távoli objektumok
ugyanazon a gépen vagy egy másik, hálózatban levő gépen voltak, az alap­
kód semleges maradhatott, mivel a tényleges elhelyezkedést a külső rend­
szerleíró adatbázis rögzítette.
Bár a DCOM valamennyire sikeres lett, de csak egy Windows-centrikus
API maradt. Annak ellenére, hogy néhány más operációs rendszeren is ren­
delkezésünkre állt, önmagában nem volt elegendő olyan átfogó megoldások
elkészítéséhez, amelyek többféle operációs rendszert használnak (Windows,
Unix, MAC), vagy különböző architektúrák (COM, J2EE, CORBA stb.) között
támogatják az adatmegosztást

Megjegyzés Voltak próbálkozások, hogy a DCOM-ot átszabják többféle Unix/Linux verzióhoz, de a


végeredmény középszerűre sikerült, és végül csak egy lábjegyzet lett a technológia történetében.

Összességében a DCOM leginkább házon belüli alkalmazások fejlesztéséhez


volt a legmegfelelőbb, ugyanis a COM-típusok közzététele a cég falain túl to­
vábbi komplikációk sorát vonta maga után (tűzfalak és egyebek). A .NET
platform megjelenésével a DCOM gyorsan túlhaladott programozási modell
lett, és ha csak nem egy korábbi DCOM rendszert tartunk karban, valójában
tekinthetjük elavult technológiának is.

A COM+/Enterprise Services szerepe

A DCOM önmagában nem tett sokkal többet, mint kommunikációs csatorna


létesítésére definiált módszert két COM-alapú szaftver között. Sokoldalú, el­
osztott megoldások fejlesztéséhez, hogy a hiányzó részeket pótolja, a Micro­
soft végül kiadta a Microsoft Transaction Servert (MTS - Microsoft tranzak­
ció szerver), amely egy későbbi kiadásban a COM+ nevet kapta.
Az elnevezés ellenére a COM+-t nemcsak COM-programozók használják
- .NET-fejlesztők számára is teljes mértékben hozzáférhető. A .NET platform
első kiadása óta biztosítják az alaposztálykönyvtárak a system. En te rp ri se­
servi c es névteret. Itt a .NET -programozók felügyelt osztályokat készíthettek,
amelyeket a COM+-futtatókömyezetbe telepíthettek, hogy ugyanazokat a
szolgáltatásokat érhessék el, mint a hagyományos COM+-alapú COM-kiszol­
gáló. Mindkét esetben a COM+-kömyezetbe telepített COM+-alapú könyv­
tárt szaigáZtatás t használó komponensnek neveztük.

279
25. fejezet: A WCF

ACOM+ sok olyan funkciót kínál, amelyeket a szolgáltatást használó kom­


ponensek alkalmazhatnak, többek között a tranzakciókezelést, az objektumok
élettartamának kezelését, szolgáltatáskészletek kialakítását (pooling), a szerep­
alapú biztonsági rendszert, lazán kapcsolt eseménymadelit és így tovább. Ak­
kor ez jelentős előny volt, hiszen a legtöbb elosztott rendszemek ugyanazokra
a szolgáltatásokra van szüksége. Ahelyett, hogy a fejlesztőket manuális kódo­
lására kényszerítette volna, aCOM+ kész megoldást biztosított.
ACOM+ egyik pozitívurna az volt, hogy minden ilyen beállítást deklara­
tív módon adminisztrációs eszközök segítségével lehetett konfigurálni. Így,
ha biztosítani akartuk, hogy egy objektumot tranzakció közben meg lehessen
figyelni, vagy egy bizonyos biztonsági szerephez tartozzon, egyszerűen csak
be kellett kapcsalni a megfelelő jelölőnégyzeteket.
Bár aCOM+ /Enterprise Services technológiát ma is használjuk, ez is csak
a házon belüli fejlesztéshez megfelelő Windows-megoldás, vagy olyan hát­
térszolgáltatásként használható, amelyet a harciasabb front end közvetetetten
kezel (pl. egy publikus webhely, amely szolgáltatást használó komponense­
ket [más névenCOM+-objektumokat] hív meg a háttérben).

Megjegyzés A WCF-ben jelenleg nincs mód szolgáltatást használó komponensek készítésére,


arra azonban van lehetőség, hogy a WCF-szolgáltatások létező COM+-objektumokkal kommuni­
káljanak. Ha C# segítségével szeretnénk szolgáltatást használó komponenseket létrehozni,
közvetlenül a System. Enterpri seServi ces névteret kell használnunk. További részleteket a
.NET Framework 3.5 SOK dokumentációjában találunk.

Az MSMQ szerepe

A Microsoft Message Queuing (MSMQ) API olyan elosztott rendszerek fej­


lesztésére szolgál, amelyeknek biztosítaniuk kell az üzenetadatok megbízha­
tó kézbesítését a hálózaton. Minden elosztott rendszerben fennáll a kockázat,
hogy a kiszolgáló nem működik, az adatbázis éppen offline, vagy valamilyen
rejtélyes okból megszakadnak a kapcsolatok. Továbbá sok alkalmazásnak
szüksége van az üzenetadatok tárolására a későbbi kézbesítéshez (más néven
az adat-sorbaállításra).
Kezdetben az MSMQ csak alacsonyszintűC-alapú API-k ésCOM-objek­
tumok gyűjteménye volt. A system.Messaging névtér segítségével a .NET­
programozók is felhasználhatták az MSMQ-t, és így időnként csatlakoztatott
alkalmazásokkal megbízhatóan kommunikáló programokat írhattak.

280
Néhány elosztott API

Végül, de nem utolsósorban a COM+-réteg beépítette az MSMQ funkcio­


nalitását a futtatókörnyezetbe (egyszerűsített formában) a Queued Compo­
nents (QC - sorba állított komponensek) technológia segítségéveL
Függetlenül attól, hogy melyik programozási modellt használtuk az
MSMQ-futtatókörnyezettel történő kommunikációra, végül az alkalmazások
mindig megbízhatóan és pontosan kézbesítették az üzeneteket. A Windows
operációs rendszerben a COM+-hoz hasonlóan az MSMQ is feltétlenül része
az elosztott szoftverek készítéséhez szükséges eszközöknek.

A .NET-remoting szerepe

A .NET platform megjelenésével a DCOM gyorsan elavult. Helyette a .NET-re­

moting-réteget biztosították a system. Runti me. Remoti n g névteret alkotó .NET­


alaposztálykönyvtárak. Ezzel az API-val több számítógép megosztha�a az objek­
tumait, feltéve, ha mindegyikük a .NET platform alatt futta�a az alkalmazásait.
A .NET-remoting-API számos nagyon hasznos funkciót biztosított. Ezek
közül az egyik az XML-alapú konfigurációs fájlok voltak, amelyek deklaratív
módon definiálták a kliens- és a kiszolgálószaftver közötti alapvető összeköt­
tetéseket A *. confi g fájlokkal nagyon egyszerű radikálisan megváltoztatill
az elosztott rendszer funkcionalitását: egyszerűen a konfigurációs fájlok mó­
dosításával és az alkalmazás újraindításávaL
Emellett, mivel ezt az API-t csak .NET-alkalmazások tudják használni,
többféleképpen tehetünk szert teljesítménynövekedésre, mert az adatokat
kompakt bináris formában lehet kódolni, valamint paraméterek és visszaté­
rési értékek definiálásakor a közös típusrendszer (CTS - Common Type Sys­
tem) előnyeit is kihasználha�uk. De amíg a .NET -remoting lehetővé tette kü­
lönböző operációs rendszereket használó elosztott rendszerek készítését (a
MONO révén, lásd röviden az előző kötet l. fejezetében, részletesen a B mel­
lékletben), a más programozási architektúrákkal (pl. a J2EE) való együttmű­
ködés még mindig nem volt közvetlenül lehetséges.

Megjegyzés A könyv előző kiadásaiban egy teljes fejezetet szenteltünk a .NET-remoting-API-k


témájának, de a WCF megjelenésével ez a fejezetet ebből a kiadásból kimarad. A .NET-remo­
ting-API-kal kapcsolatos kihagyott fejezetet (címe: A .NET remeting réteg) térítésmentesen le­
tölthetik az Apress w ebhelyről ennek a kiadásnak a vásárlói (http: l /www.apress.com).

281
Z5. fejezet: A WCF

Az XML-webszolgáltatás szerepe

A korábban említett elosztott API-k egyike sem nyújtott sok támogatást (ha
egyáltalán nyújtott) ahhoz, hogy külső hívók agnosztikus módon hozzáférjenek
a program funkcionalitásához. Ha arra van szükségünk, hogy a távoli objek­
tumok szolgáltatásait felajánljuk bármilyen operációs rendszernek vagy bár­
milyen programozási modellnek, az XML-webszolgáltatások biztosítják eh­
hez a legegyszerűbb utat.
A hagyományos böngészőalapú webes alkalmazásokkal ellentétben a
webszolgáltatásokkal egyszerűen felajánlhatjuk távoli komponensek funkci­
onalitását szabványos webprotokollokon keresztül. A .NET kezdeti kiadása
óta a programozók kiváló támogatást kapnak XML-webszolgáltatások készí­
téséhez és felhasználásához a system. web. servi ces névtéren keresztül. Tulaj­
donképpen a teljes funkciójú webszolgáltatás készítése sokszor nem több,
mint a [webMethod] attribútum hozzárendelése minden olyan nyilvános me­
tódushoz, amelyhez hozzáférést szeretnénk biztosítani Továbbá a Visual
Studio 2008 azt is lehetővé teszi, hogy egy (vagy két) gombnyomással távoli
webszolgáltatáshoz kapcsolódjunk.
A webszolgáltatások segítségével olyan .NET-szerelvényeket lehet fejlesz­
teni, amelyek egyszerű HTTP-n keresztül elérhető típusokat tartalmaznak,
ráadásul a webszolgáltatás egyszerű XML-ként kódolja az adatait. Mivel a
webszolgáltatások nyílt ipari szabványokon alapulnak (HTTP, XML, SOAP
stb.), és nem védjeggyel rendelkező típusrendszereken vagy szabadalmazta­
tott műveletformátumokon (mint a DCOM vagy a .NET-remoting esetében),
nagymértékű együttműködést és adatforgaimat tesznek lehetővé. A 25.1. áb­
ra mutatja az XML-webszolgáltatások agnosztikus természetét.
Persze nincs tökéletes elosztott API. A webszolgáltatások egyik lehetséges
hátránya teljesítménybeli hiányosságuk (mivel HITP-t és XML-adatábrázolást
használnak), így nem igazán ideálisak házon belüli alkalmazásokhoz, ahol a
TCP-alapú protokoll és a bináris kódolás probléma nélkül használható lenne.

Példa NET -webszolgáltatásra


.

A .NET-programozók évek óta a Visual Studio ASP.NET Web Service pro­


jektsablonjával készítenek webszolgáltatásokat, amelyet a File> New> Web
Site párbeszédablakkal érhetünk el. Ez a projektsablon létrehozza az általá­
ban használt könyvtárstruktúrát, valamint néhány kiindulási fájlt, amelyek
magát a webszolgáltatást képviselik.

ZBZ
Néhány elosztott API

Bár ezzel a sablonnal könnyen elindulhatunk, egyszerű szövegszerkesz­


tővel is lehet .NET -es XML-webszolgáltatást készíteni, valamint az ASP.NET
fejlesztői webszerverrel, a webDev. webserve r. exe-ve l pedig azonnal tesztelni
is tudunk (a 31. fejezet bővebben ismerteti ezt az segédprogramot).

'!.NET....- .
HTIPésXML r-:-
ws � __. '
A
.

· · . Proxy
-
- ' (;) .
XML
rJava alkaknazés webszol-
N
U IX-on� HTIPésXML
gá Itatás
� ...
-
y
©
JaiJa (vagy .HET)"'
/.•

'
HTIPésXML


. . Proxy
�® .
- HTIPé
: XML
l . .,. � .... .._.._.. .

HTIPésXML +
� �
(l8tsidfllgaa . . . . . ..
� _. B.web-©
� Proxy
-
' �.
L....- -- ·· -···
·_ .':__ ..:..�---
_
.

25. 1. ábra: Az XML-webszolgáltatások nagt;Jokú egt;üttműködést tesznek lehetővé

Egy rövid példa kedvéért tételezzük fel, hogy a következő programrészt ké­
szítettük el a Hellowebservice. asmx fájlban ( * . asmx a .NET-es XML webszol­
gáltatás-fájlok alapértelmezett kiterjesztése). Ha készen vagyunk, mentsük a
C:\ HelloWebService könyvtárba.

<%@ webservice Language="(#" Class="Hellowebservice" %>


using system;
using System.web.Services;

public class Hellowebservice


{
[webMethod]
public string Helloworld()
{
return "Hello world";
}
}

283
25. fejezet: A WCF

Bár ez az egyszerű szolgáltatás nem végez semmilyen különös feladatot, a fájl a


webservice direktivával kezdődik, amellyel megadjuk a szolgáltatást jelképező
osztálytípus nevét és a fájlban használt .NET programozási nyelvet. A Hello­
world() rnetódust a [webMethod] attribútummal ruháztuk fel. Sokszor ennyi
elég is ahhoz, hogy a rnetódust HTTP-n keresztül külső lúvók számára elérhe­
tővé tegyük. Végül semmi különös nem kell ahhoz, hogy a visszatérési értéket
XML-ben kódoljuk, rnivel ez futásidőben automatikusan megtörténik
A webszolgáltatás tesztelésére nyissunk egy Visual Studio 2008 parancs­
sort, és írjuk be a következő parancsot (a fejlesztői webszerverrel használható
összes parancs listázásához csak írjuk be a-? paramétert):

webdev.webserver /port:8080 /path:"c:\Hellowebservice"

Ekkor elindul a böngésző, és megmutatja a könyvtár tartalrnát. A Hello­


world.asmx fájira kattintva erről az oldalról is látható lesz az összes "webrne­
tódus". Most már rákattinthatunk a Helloworld linkre, hogy HTTP-n keresz­
tül rneglúvjuk a rnetódust. Ha rnegtettük, a böngésző megjeleníti az XML-ben
kódolt visszatérési értéket:

<?xml version="l.O" encoding="utf-8" 7>


<string xmlns="http://tempuri.org/">
Hello world
</string>

Még ennél is könnyebb dolgunk van, hogy ha a webrnetódusokat lúvó ügy­


féloldali proxyfájlt szeretnénk generálni, ekkor ugyanis használhatjuk a
wsdl .ex e parancssori eszközt vagy a Visual Studio Add Service Reference
pontját a Project rnenüben. Ugyanezekkel az eszközökkel generálhatunk ügy­
féloldali *.config fájlt, amely deklaratív forrnában tartalmazza a proxy kü­
lönböző konfigurációs beállításait (pl. a webszolgáltatás végpontját).
A proxykód elrejti a kézi HITP-beállítások részleteit és az XML-adatok
visszafordítását .NET-adattípusokra. Tételezzük fel, hogy generáltunk egy
proxyt ehhez az egyszerű webszolgáltatáshoz, ilyenkor a kliensalkalmazás
nagyon egyszerűen képes rneglúvni a webrnetódust, például:

static void Main(string[] args)


{
ll Aproxytípus tartalmazza a kódot a *.config fájl olvasásához,
ll hogy megtalálja a webszolgáltatás helyét .

HellowebserviceProxy proxy = new HellowebserviceProxy();


console.writeLine(proxy.Helloworld());
}

284
Néhány elosztott API

Megjegyzés A könyv előző kiadásaiban egy teljes fejezetet szenteltünk az XML-webszolgáltatások


témájának, de a WCF megjelenésével ez a fejezet kimaradt ebből a kiadásból. A .NET-es XML­
webszolgáltatásokról szóló kihagyott fejezetet (címe: XML-webszolgáltatások megértése) térítés­
mentesen letölthetik az Apress webhelyről ennek a kiadásnak a vásárlói (http://www.apress.com).

Bár még mindig abszolút tökéletes az ilyen "hagyományos" jellegű XML­


webszolgáltatások készítése a .NET 3.5 alatt, a legtöbb új szolgáltatásprojekt­
nek hasznára válik, ha ehelyett WCF-sablonokat használunk. Sok HITP­
alapú kötés közül választhatunk, és lényegében mindegyikkel készíthetünk
webszolgáltatást anélkül, hogy kifejezetten valamelyik "webszolgáltatás"­
projektsablont választanánk.

Forráskód A HelloWorldWebService kódfájlokat a forráskódkönyvtár 25. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Webszolgáltatási szabványok

A webszolgáltatások egyik fő problémája már a kezdetektől az volt, hogy


minden nagy "játékos" a piacon (Microsoft, IBM és a Sun Microsystems)
olyan webszolgáltatásokat implementált, amelyek nem voltak 100 százalék­
ban kompatibilisek a többi webszolgáltatás-implementációval. Ez nyilvánva­
lóan problémát okozott, hiszen a webszolgáltatások lényege pont az, hogy
elérjék a különböző platforrnak és operációs rendszerek nagymértékben
együtt tudjanak működni.
Hogy biztosítsák a webszolgáltatások együttműködését, olyan csoportok,
mint a World Wide Web Consortium (W3C; http:l lwww.w3.org) és a Web
Services Interoperability Organization (WS-1; http: llwww.ws-i. org) elkezd­
tek különböző specifikációkat írni, amelyek részletesen megadják, hogy a
szaftvergyártó cégek (pl. az IBM, a Microsoft vagy a Sun Microsystems) ho­
gyan készítsék el webszolgáltatás-központú szoftverkönyvtáraikat a kompa­
tibilitás megőrzésének érdekében.
Ezeknek a specifikációknak az együttes gyűjtőneve WS-*, és ezek tartal­
mazzák a biztonsági előírásokat, a mellékletek és a webszolgáltatások leírását
(Web Service Description Language nyelven, azaz WSDL-ben), rendszabá­
lyokat, SOAP-formátumokat és rengeteg egyéb fontos részletet.
A Microsoft a legtöbb ilyen szabványának (felügyelt és nem felügyelt kód)
az implementációját a Web Services Enhancements (WSE) 3.0 eszközrendszer
tartalmazza, amely a támogatási webhelyről ingyenesen letölthető: http:ll
msdn2.microsoft.comlen-uslwebservices.

285
25. fejezet: A WCF

WCF-szolgáltatásalkalmazás készítésekor nem lesz közvetlenül szükség a


WSE 3.0 eszközrendszer szerelvényeire. Ehelyett, a HITP-alapú kötést hasz­
náló WCF-szolgáltatás készítésekor ugyanezeket a WS-* specifikációkat ké­
szen megkapjuk (pontosan azokat, amelyek az általunk választott kötésen
alapulnak).

Named pipe-ok, socketek és P2P

Ha a DCOM, .NET-remoting, webszolgáltatások, COM+ és az MSMQ közötti


választás nem lett volna elég nehéz, tovább bővíthetjük az elosztott API-k lis­
táját. A programozók használhatnak más folyamatok közötti kommunikációs
API-kat, mint például a named pipe-okat, a sacketeket és a peer-to-peer (P2P)
szolgáltatásokat. Ezek az alacsonyabb szintű API-k általában jobb teljesít­
ményt nyújtanak (különösen ugyanazon LAN hálózatban elhelyezkedő gé­
peken), de használatuk bonyolultabb (ha nem lehetetlen) kifelé kommunikáló
alkalmazásoknál.
Ha több alkalmazást magában foglaló, de egyetlen gépen futó elosztott
rendszert készítünk, célszerű lehet a named pipes API-t használni a system.
ro. Pi pes névtéren keresztül (újdonság a .NET 3.5-ben). Ez a módszer lehet a

legesleggyorsabb lehetőség az ugyanazon a gépen belüli alkalmazások közötti


adatcserére.
Ha olyan alkalmazást készítünk, amelynek teljes felügyelettel kell rendel­
keznie a hálózati hozzáférés biztosításának és fenntartásának módjáról, a
system.Net.Sockets és a System.Net. PeerToPeer névterekkel hasznosíthatjuk
a socketek és a P2P funkeionali tását a .NET platform alatt.

A WCF szerepe

Az elosztott technológiák széles skálája nagyon megnehezíti a megfelelő eszköz

kiválasztását. Ezt még tovább bonyolí�a, hogy ezek közül több technológia szal­
gáitatásai átfedik egymást (legfőképpen a tranzakciók és a biztonság területén).
Még ha a .NET-fejlesztő kiválasztotta is a feladathoz megfelelőnek tűnő
technológiát, az ilyen alkalmazás elkészítése, karbantartása és konfigurálása
a legenyhébb kifejezéssel is összetett. Minden API-nak megvan a maga prog­
ramozási modellje, egyéni konfigurációs segédprogramjai és így tovább.

286
A WCF szerepe

Emiatt a .NET 3.0 előtt nehéz volt elosztott API-kat egyszerűen csatlakoz­
tatni (plug and play) anélkül, hogy tekintélyes mennyiségű egyedi infra­
struktúrát ne kellett volna létrehozni. Ha például .NET-remoting-API-kal ké­
szí�ük a rendszerünket, és később úgy döntünk, hogy az XML-webszolgálta­
tások megfelelőbbek lennének, újra kell írni a kódalapot
A WCF a .NET 3.0-ban bevezetett egy elosztott eszközrendszert, amely in­
tegrálja ezeket a korábban független elosztott technológiákat egy áramvonalas
API-ban, amelyet elsősorban a system.serviceModel névtér képvisel. A WCF
révén a szolgáltatásokat a legkülönfélébb technikák segítségével elérhetővé le­
het tenni a hívók számára. Ha például olyan házon belüli alkalmazást készí­
tünk, ahol minden kapcsolódó számítógép Windows-alapú, többféle TCP pro­
tokoll használata biztosítha�a a lehető legjobb teljesítményt. Ugyanezt a szol­
gáltatást XML-webszolgáltatás-alapú protokollal elérhetővé tehe�ük úgy, hogy
külső hívók is birtokba vehessék a funkcióit a programozási nyelvtől vagy az
operációs rendszertől függetlenül.
Mivel a WCF lehetövé teszi a feladatnak megfelelő protokoll kiválasztását
(egy közös programozási modellel), elég könnyű lesz az elosztott alkalmazá­
sunk alapjául szolgáló összeköttetéseket beállítani. Legtöbb esetben ezt a kli­
ens/ szerver szaftver újraindítása vagy újratelepítése nélkül meg lehet tenni,
ugyanis a "piszkos" részleteket gyakran az alkalmazás konfigurációs fájljai
veszik át (úgy, mint a régebbi .NET-remoting-API-knál).

A WCF-funkciók áttekintése

A különböző API-k együttműködése és integrációja a WCF-nek csak két (na­


gyon fontos) aspektusa. Ezenkívül a WCF gazdag szoftveranyagot ad az el­
érhető remotingtechnológiák kiegészítésére. Nézzük meg a következő fő
WCF-jellegzetességeket:

• Erősen típusos és típus nélküli üzenetek támogatása jellemzi. Ezzel a


megközelítéssel a .NET-alkalmazások hatékonyan megosztha�ák egyedi
típusaikat, az egyéb platformokon készült szoftverek (pl. J2EE) feldol­
gozha�ák a gyengén tipizált XML-folyamokat.

• A többféle kötés (nyers HTTP, TCP, MSMQ és named pipe-ok) támoga­


tása lehetövé teszi, hogy a legmegfelelőbb összeköttetést válasszuk
üzenetek ide-oda szállítására.

287
25. fejezet: A WCF

• A legújabb és a legjobb webszolgáltatások specifikációjának (WS-*)


támogatása.

• Teljesen integrált biztonsági modell, amely a Win32f .NET natív biz­


tonsági protokolljait és sok más semleges, webszolgáltatási szabványra
épülő biztonsági módszert magában foglal.

• Munkamenetszerű állapotkezelési módszerek és egyirányú, állapot


nélküli üzenetek támogatása.

Bármilyen lenyűgöző is ez a lista, valójában csak a WCF-nyújtotta funkciona­


litás felszínét mutatja. A WCF nyomkövető és naplózó eszközöket, teljesít­
ménymérőket, "közzétesz és előfizet" (publish/ subscribe) eseménymodellt,
tranzakciós támogatást és más egyéb funkciókat is tartalmaz.

A szolgáltatásorientált architektúra áttekintése

A WCF egy másik előnye, hogy a szolgáltatásorientált architektúra (SOA) által


megalapozott tervezési elveken alapul. A SOA felkapott kifejezés az iparban,
és mint a legtöbb ilyen, többféleképpen definiálható. A SOA egyszerűen egy
olyan módszer elosztott rendszerek építésére, ahol több autonóm szaigáitatás
működik együtt úgy, hogy üzeneteket küldenek határaikon túlra (hálózatba
kötött gépek között vagy csak két folyamat között egyazon gépen belül) jól
definiált interfészek segítségéveL
A WCF világában ezek a "jól definiált interfészek" általában CLR-inter­
fésztípusokkal készülnek (lásd az előző kötet 9. fejezetét). Általánosabban
egy szaigáitatás interfésze egyszerűen csak leírja a külső hívók által hívható
tagok halmazát.
A WCF tervezése során a WCF-csoport felállította a SOA tervezési mód­
szer négy alapelvét Bár ezeket az alapelveket automatikusan tiszteletben
tartjuk már azzal is, hogy WCF-alkalmazást készítünk, a SOA négy fő terve­
zési szabályának megértése segíthet abban, hogy a WCF-et kellő megvilágí­
tásba helyezzük. A következő részek egy-egy alapelv rövid áttekintését tar­
talmazzák.

288
A WCF szerepe

1. alapelv: A határok explicitek

Ez az alapelv megerősíti azt, hogy a WCF-szolgáltatás funkcionalitása jól de­


finiált interfészeken keresztül jelenik meg (pl. minden tagjának, a paraméte­
reinek és a visszatérési értékének leírása). A külső hívó egyedül az interfé­
szen keresztül tud a WCF-szolgáltatással kommunikálni, ráadásul a külső
hívó nem tud az alapul szolgáló implementációs részletekről.

2. alapelv: A szolgáltatások autonómok

Ha "autonóm" entitásként beszélünk a szolgáltatásokról, akkor arra utalunk,


hogy az adott WCF-szolgáltatás (amennyire ez lehetséges) magányos sziget.
Az autonóm szolgáltatás legyen független a verziótól és telepítési kérdések­
től. Az alapelv támogatásának érdekében megint az interfészalapú progra­
mozás kulcskérdéséhez térünk vissza. Ha egyszer az interfész működésbe
lép, már nem szabad rajta változtatill (különben fennáll a kockázat, hogy lé­
tező kliensek munkáját tesszük lehetetlenné). Ha szeretnénk kibővíteni a
WCF-szolgáltatás funkcionalitását, egyszerűen a kívánt funkciót modellező
új interfészeket kell készíteni.

3. alapelv: A szolgáltatások szerződésen keresztül és nem


implementáción keresztül kommunikálnak

A harmadik alapelv is az interfészalapú programozás egyik mellékterméke,


mert a WCF-szolgáltatás implementációs részletei (milyen nyelven készült,
hogyan valósí�a meg a feladatait) nem érdeklik a külső hívót. A WCF-ügyfe­
lek kizárólag a kívülről elérhető nyilvános interfészeiken keresztül kommu­
nikálnak a szolgáltatásokkal. Továbbá, ha egy szolgáltatás interfésztagjai
egyedi összetett típusokat tesznek elérhetővé, ezeket egy adatszerződésben
részletesen le kell írni, hogy a hívók biztosan leképezhessék a tartalmat egy
meghatározott adatszerkezetbe.

4. alapelv: A szolgáltatás kompatibilitása házirenden alapul

Mivel a CLR-interfészek erősen típusos szerződéseket tartalmaznak minden


WCF-ügyfélhez (és ezekkel kapcsolódó, a választott kötésen alapuló WSDL­
dokumentumot lehet generálni), fontos azt hangsúlyozni, hogy az interfészek
vagy a WSDL egyedül nem elég kifejezőek ahhoz, hogy részletesen kifejtsük,
mire képes a szolgáltatás.

289
25. fejezet: A WCF

Ezek alapján a SOA segítségével "házirendeket" defirúálhatunk, amelyek


tovább minősítik a szolgáltatás szemantikáját (pl. az elvárt biztonsági elvárá­
sokat, amelyekkel a szolgáltatással kommunikálunk). Ezekkel az háziren­
dekkel tulajdonképpen elválaszthatjuk a szolgáltatás (az elérhető interfészek)
alacsony szintű szintaktikai leírását a szolgáltatás hívásának módját leíró
szemantikai részletektőL

WCF: A lényeg

Ez a kis összefoglalás bemutatta, hogy a WCF a legjobb módszer az elosztott


alkalmazások készítésére a .NET 3.0 és újabb verzióiban. Akár házon belüli
alkalmazást hozunk létre TCP protokollal, vagy adatokat mozgatunk prog­
rarnak között ugyanazon a gépen named pipe-okkal, vagy adatokat teszünk
elérhetővé a külvilág számára webszolgáltatás-protokollokkal, a WCF az
ajánlott API ennek a megvalósítására.
Ez nem azt jelenti, hogy új fejlesztéseknél nem használhatjuk az eredeti
.NET elosztott rendszerekhez tartozó névtereket (syst:em. Runt:i me. Remot:i n g,
Syst:em. Messagi n g, Syst:em. Ent:erpri se servi c es, syst:em. web. servi ces stb.). Tu­
lajdonképpen, néha (különösen, ha COM+-objektumokat kell létrehozni), ez
még kötelező is. Mindenesetre, ha már korábbi projektekben használtuk ezeket
az API-kat, a WCF megismerése nem fog gondot okozni. Csakúgy, mint a ko­
rábbi technológiák, a WCF nagy hasznát veszi az XML-alapú konfigurációs fáj­
laknak, a .NET-attribútumoknak és a proxygeneráló eszközöknek.
A következőkben rátérünk a konkrét WCF-alkalmazások készítésére. A WCF
teljes ismertetése egy egész könyvet igényeine, rnivel minden támogatott szaigál­
tatásnak (MSMQ COM+, P2P, named pipes stb.) önmagában egy teljes fejezetet
szentelhetnénk. Megismerjük a WCF-programok készítésének teljes folyamatát
TCP- és HTIP-alapú (pl. webszolgáltatás) protokollok használatával is. Ennek
segítségével már egyszerűen továbbléphetünk a jövőben.

Az alapvető WCF-szerelvények

A WCF programozási alapja a globális szerelvénytárba (GAC) telepített .NET­


szerelvények halmaza. A 25.1. táblázat leírja azon alapvető WCF-szerelvények
szerepét, amelyekre nagyjából minden WeF-alkalmazásban szükség lesz.

290
Az alapvető WCF-szerelvények

System.Runtime.serialization.dll Definiálja azokat a névtereket és típuso­


kat, amelyekkel a WCF keretrendszerben
objektumokat sorosíthatunk és állítha­
tunk vissza.

System.ServiceModel.dll Alapvető szerelvény, amely bármilyen


WCF-alkalmazás készítéséhez szükséges
típusokat tartalmazza.

25.1. táblázat: Alapvető WCF-szerelvények

Ez a két szerelvény sok új névteret és típust definiál. Míg a részletes referen­


ciát a .NET Framework 3.5 SDK dokumentációjában találjuk, a 25.2. táblázat
leírja a legfontosabb névterek szerepét.

System.Runtime.serialization Több olyan típust definiál, amelyek sza­


bályozzák az adatok sorosítását és a visz­
szaállítását a WCF keretrendszeren belül.

system.ServiceModel Az elsődleges WCF-névtér, amely defi­


niálja a kötési és hosztolási típusokat,
valamint az alapvető biztonsági és tran­
zakciós típusokat.

system.serviceModel.configuration Számos olyan típust definiál, amely


programozott elérést biztosít a WCF
konfigurációs fájlokhoz.

System.serviceModel .Description Olyan típusokat definiál, amelyek a


WCF konfigurációs fájlokban definiált
címekhez, kötésekhez és szerződésekhez
biztosítanak objektummodellt.

System.serviceMode l .Msmqintegration Az MSMQ-szolgáltatásokkal integrálha­


tó típusokat tartalmazza.

system.ServiceModel.Security Olyan típusokat definiál, amelyek a


WCF biztonsági rétegeinek beállításait
szabályozzák.

25.2. táblázat: Alapvető WCF-névterek

291
25. fejezet: A WCF

A system.serviceModel.dll-en és a System.Runtime.serialization.dll·en kívül a WCF


egy harmadik WCF-szerelvényt is biztosít: a system. Ident i tyMode l . dll-t. Itt számos olyan
egyéb névteret és típust találunk, amelyek a WCF CardSpace API· t támogatják. Ez a technológia
lehetövé teszi digitális azonosítók létrehozását és kezelését a WCF-alkalmazáson belül. Lénye·
gében a cardSpace API egységes programozási modellt biztosít a különbözö biztonsági részletek
kezelésére a WCF-alkalmazásokban, mint például a hívó azonosítója, felhasználó azonosító/hite­
lesítö szolgáltatások és egyebek.
Ebben a kiadásban nem lesz szó a CardSpace API-ról, így ha további részletekre van szük­
ségünk, forduljunk a .NET Framework 3.5 SOK dokumentációjához.
--------�-

A Visual Studio WCF projektsablonok

A WCF-alkalmazást általában három kapcsolódó szerelvény képviseli, amelyek

egyike egy *.dll, s ez azokat a típusokat tartalmazza, amelyekkel a külső hívók


kommunikálhatnak (más szóval ez maga a WCF-szolgáltatás). Ha WCF-szolgál­
tatást akarunk készíteni, tökéletesen elfogadható kiindulási pont egy szabványos
osztálykönyvtárprojekt-sablon kiválasztása (lásd az első kötet 15. fejezetét), utá­
na pedig manuálisan hivatkozhatunk a WCF-szerelvényekre.
De készíthetünk új WCF-szolgáltatást a Visual Studio 2008 WCF Service
Library projektsablon kiválasztásával is (lásd a 25.2. ábrát). Ez a projekttípus
automatikusan beállí�a a referenciákat a szükséges WCF-szerelvényekre, vi­
szont elég nagy mennyiségű "kezdeti kódot" generál, amelyet nagy valószí­
nűséggel később egyszerűen ki fogunk törölni.
A WCF Service Library projektsablon egyike előnye, hogy tartalmazza az

Ap p. con fi g fájlt is, ez pedig furcsának tűnhet, hiszen .NET *.dll fájlt készí­

tünk, és nem .NET *. exe fájlt. Ám ez a fájl nagyon hasznos hibakereséskor


vagy a WCF Service Library projekt futtatásakor, mert a Visual Studio IDE
automatikusan elindí�a a WCF Test Client alkalmazást. A program (wcfTest­
Cl i e nt. exe ) kiolvassa a beállításokat az App. c o n fi g fájlból, hogy tesztelés cél­

jából hasztolhassa a szolgáltatásunkat. A WCF tesztelőkliensével a későbbi­


ekben még foglalkozunk.

Megjegyzés A WCF Service Library projekt App. con fi g fájlja hasznos abból a szempontból
is, hogy megmutatja a WCF-hoszt-alkalmazás konfigurálásához szükséges alapbeállításokat
Igazából ennek a kódnak nagy részét bernásolhatjuk a saját hasztunk konfigurációs fájljába.

292
A Visual Studio WCF projektsablonok

f New Project !dEi .ilt.,;:t., ,�


Project types: Templates: j.NET framework 35 • �G
Vis:ual Basic Visual Studio imrtalled tempiates
Visual C�

Windows
Web
5g -�� ,it
Sequentia! State Syndication WCF S�ce
Database Worlálo... Machin... Service li... libnny �
Tost
My Tempiates
VICF
-----------�-----------------·-

Workflow
Visual (+•
_j]
Search
Other Project Typ�
Online Te...
Test Projects

�o------------------------··
A project for creating a WCF service class library (.dll} (.NET Framework 35)

Name: WdServicelibraryl

Location: C:\MyCode\WCF Examples\ProjecU • j Browse... l


Solution Name: WdServic6.ibraryl ft.] Create directory for solution

O Add to Source Control

OK J[ Cancel

25.2. ábra: A Visual Studio 2008 WCF Service Library projektsablonja

Az alap WCF Service Library sablonon kívül a New Project párbeszédablak


WCF projekt kategóriája még két WCF-könyvtárprojektet definiál, amelyek a
WCF-szolgáltatásba integrálják a Windows Workflow Foundation funkciona­
litását, valamint egy sablont az RSS-könyvtárak készítéséhez (amelyek szin­
tén láthatóak a 25.2. ábrán). A következő fejezetben szó lesz a Windows Work­
flow Foundation technológiáról, így most a WCF-projektsablonokat figyel­
men kívül hagyjuk.

A WCF Service Website projektsablon

Még egy Visual Studio 2008 WCF-központú projektsablon található a New


Web Site párbeszédablakban a File > New > Web Site menüpont alatt (lásd a
25.3. ábrát).
Ez a WCF Service (szolgáltatás) projektsablon akkor hasznos, ha már a kezde­
tektől tudjuk, hogy a WCF-szolgáltatásunk inkább webszolgáltatás-alapú proto­
kollokat használ, mint named pipe-okat. Ez az opció automatikusan elkészít egy
új IlS-beli virtuális könyvtárat a WCF-programfájlok számára, egy megfelelő

293
25. fejezet: A WCF

web. con fi g fájlt, hogy HTIP-n keresztül elérhetővé tegyük a szolgáltatást, és a

szükséges *.svc fájlt (az *.svc fájlokról a későbbiekben szálunk). Erről az oldal­
ról nézve a webalapú WCF Service projekt egyszerűen időmegtakarítás, mivel az
IDE automatikusan elkészíti a szükséges IlS-infrastruktúrát.

NewW�bSite
�.....;- �

Templates:

Visual Studio installed tempiates


'.!c# l� @l ,_l iEJ
ASP.NET ASP.NET EmptyWeb WCF Setvice ASP.NET
WebSite Web Service Site
� ReportsW,..

Sei!!rch
Online Te...

AWeb site for creating WCF s�ces (.NET Fram�ork 35)

location: lHTTP •l http://localhost/MyWCFService

language �v
� ;=.
, ,.=I=
C#===�� �

OK ll Cancd

25.3. ábra: A Visual Studio 2008 webalapú WCF Seroice projektsablonja

Ezzel ellentétben, ha a WCF Service Library opcióval készítünk új WCF-szol­


gáltatást, akkor azt többféleképpen is hosztolha�uk (egyedi hoszt, Windows­
szolgáltatás, manuálisan készített IlS-beli virtuális könyvtár stb.). Ez az opció ak­
kor célszerűbb, ha egyedi hosztot szeretnénk készíteni a WCF-szolgáltatáshoz.

A WCF -alkalmazás alapösszeállítása

A WCF elosztott rendszerhez általában három kapcsolódó szerelvényt készí­

tünk:

• A WCF Service (WCF szolgáltatás) szerelvény Ez a 1' . dll tartalmazza a

külső hívák számára elérhető funkcionalitást képviselő osztályokat és


interfészeket.

294
A WCF-alkalmazás alapösszeállítása

• A WCF Service hoszt: Ez a programmodul hasztolja a WCF-szolgáltatás


szerelvényét.

• A WCF-ügyfél: Ez az alkalmazás hozzáfér a szolgáltatás funkcionalitá­

sához egy közbeépülő proxyn keresztül.

A WCF-szolgáltatás szerelvénye olyan .NET-osztálykönyvtár, amely WCF­


szerződéseket és az implementációjukat foglalja magában. Az egyik fő különb­
ség az, hogy az interfészszerződéseket felvérteztük különböző olyan attribú­
tumokkal, amelyek adattípusok reprezentációját vezérlik, például azt, hogy a
WCF-futtatókömyezet hogyan működik együtt az elérhető típusokkal.
A második szerelvény, a WCF-hoszt, bármilyen .NET végrehajtható fájl
lehet. A WCF segítségével a szolgáltatások bármilyen típusú alkalmazásból
elérhetővé tehetök (Windows Forms-alkalmazások, Windows-szolgáltatások,
WCF-alkalmazások stb.). Ha egyedi hosztot készítünk, használjuk a servi ce­
H ost típust és a hozzá tartozó *.con fi g fájlt, amely a használni kívánt kiszol­

gálóoldali összeköttetések részleteit tartalmazza. Ha azonban liS-t haszná­


lunk WCF-szolgáltatásunk hosztjaként, nem kell programozottan egyedi
hosztot készítenünk, mivel az liS a háttérben a ServiceHost típust használja.

Megjegyzés A Vista-specifikus Windows Activation Service (WAS - Windows aktivációs szer­


verj segítségével is lehet a WCF-szolgáltatást hosztolni. További részletekért forduljunk a .NET
Framework 3.5_ SDK dokumentációjához.

Az utolsó szerelvény azt a klienst jelképezi, amely a WCF-szolgáltatást hívja.


Ez a kliens bármilyen típusú .NET-alkalmazás lehet. A hoszthoz hasonlóan a
kliensalkalmazások általában a kliensoldali *.con fi g fájlt használják, amely a
kliensoldali összeköttetéseket definiálja.

Kliens alkalmazás WCF hoszt

.t WCF-amlgéllalésj
·.

l J
.

Praty_
. r· , ;:
.

l Konfigurációs állomány
l l Konfigurációs állomány l
25 .4. ábra: Egy tipikus WCF-alkalmazás szerkezetének áttekintése

295
25. fejezet: A WCF

A 25.4. ábra (nagy vonalakban) mutatja a három összekapcsolódó WCF-sze­


relvény kapcsolatát. Feltételezésünknek megfelelőerr a háttérben több ala­
csony szintű részlet jelképezi a szükséges összeköttetéseket (factoryk, csator­
nák, figyelők stb.). Ezek az alacsony szintű részletek általában rejtve vannak,
ha azonban szükség van rá, igény szerint bővíteni vagy módosítani is lehet
őket. Szerencsére a legtöbb esetben az alapértelmezett működés megfelelő.
A kiszolgáló- vagy kliensoldali ;, .con fi g fájl használata opcionális. Ha

szeretnénk, a szükséges összeköttetéseket (végpontokat, kötéseket, címeket


stb.) specifikálhatjuk a hoszt (vagy a kliens) kódolásávaL Ezzel a megközelí­
téssel nyilvánvalóan az a probléma, hogy ha meg kell változtatnunk az ösz­
szeköttetések részleteit, sok szerelvényt újra kell kódolni, fordítani és telepí­
teni. A*. con fi g fájl sokkal rugalmasabban kezeli a kódot, mivel az összeköt­
tetések megváltoztatása csak annyit jelent, hogy frissítjük a fájl tartalmát, és
újraindítjuk az alkalmazást.

A WCF ABC-je

A hosztok és a kliensek úgy kommunikálnak, hogy megegyeznek az ABC­


ben. Ez egy egyszerű rövidítés a WCF-alkalmazás alapegységeinek jelölésére,
úgymint address (cím), binding (kötés) és contract (szerződés).

• Cím: A szolgáltatás helye. A kódban ezt egy system. uri típus jelképezi,
az értéket azonban általában * .con fi g fájlok tárolják.

• Kötés, A WCF-hez több különböző kötés jár, amelyek hálózati protokol­


lokat, kódoJási mechanizmusokat és a szállítási réteget specifikálják.

• Szerződés: A WCF-szolgáltatás által elérhetővé tett összes metódus leírása.

Az ABC rövidítés nem azt jelenti, hogy a fejlesztőnek először a címet, majd a
kötést és végül a szerződést kell definiálnia. A WCF-fejlesztő sokszor a szal­
gáltatás szerződésének definiálásával kezd, majd megadja a címet és a köté­
seket (de bármilyen sorrend jó, ha mindháromról gondoskodunk). Az első
WCF-alkalmazás elkészítése előtt nézzük meg részletesebben az ABC-ket.

296
A WCF ABC-je

A WCF -szerződések

A szerződés fogalma kulcsfontosságú a WCF-szolgáltatások készítésekor. Bár


nem kötelező, de a WCF-alkalmazások nagy többsége .NET-interfésztípusok
definiálásával kezdődik, amelyek olyan tagok gyűjteményét jelképezik, ame­
lyeket az adott WCF-típus támogat. Az olyan interfészeket, amelyek kifeje­
zetten WCF-szerződéseket jelképeznek, szolgáltatásszerződéseknek nevezzük.
Az osztályokat (vagy struktúrákat), amelyek ezeket implementálják, szolgálta­
tástípusoknak hívjuk.
A WCF-szolgáltatásszerződések több attribútummal rendelkeznek, ame­
lyek közül a leggyakoribbakat a system. serviceModel névtér definiálja. Ami­
kor a szolgáltatásszerződés tagjai csak egyszerű adattípusokat tartalmaznak
(mint pl. numerikus, logikai és sztringadatokat), teljes WCF-alkalmazást ké­
szíthetünk kizárólag a [Servi cecontract] és az [Operati oneont ract] attribú­
tumok használatávaL
Ha azonban a tagunk egyedi típusokat tesz elérhetővé, használnunk kell a
System.Runtime.Serialization.dll szerelvény System.Runtime.serialization
névterében található típusokat (lásd a 25.5. ábrát). Itt további attribútumokat
(mint pl. a [DataMember] és a [Datacontract] ) találunk az interfésztípusok fi­
nomításához.

8-�o System.Runtime.Serialization G
· so System.Runtime.Serialozatton
i±l·'1t CollectionDataContractAttribute
$---Vit ContractNamespaceAttribute
$---'1$ D�taContractAttribute
$··'-is DataContractSerializer
@ -'1:: OataMemberAttribute
@·-"it EnumMemberAttribute
$ 'it ExportOptions
�--"it ExtensionDataObject
ffi- ....o IOataContractSurrogate
$--·.,..() IExtensibleOataObject
tfj--""ft ImportOptions
@--� lnvalidDataContractException
� ·"t KnownTypeAttribute
&l '1$ NetDataContractSerializer
$---� XmiObjectSerializer
ffi---<1:$ XmiSerializableServices
@ ·'it XsdDataContractExporter
ffi-'it XsdDataContractlmporter
rtJ -- {} System.Runtime.Seríalization.Configuration G
25. 5. ábra: A System.Runtime.Serialization számos attribútumai definiál WCF-adatszerződések
készítése során

297
25. fejezet: A WCF

Valójában nem kötelező a CLR-interfészek használata WCF-szerződés defini­


álásához. Ezek közül az attribútumok közül sokat alkalmazhatunk nyilvános
osztályok (vagy struktúrák) nyilvános tagjaira is. De az interfészalapú prog­
ramozás (polimorfizmus, elegáns verziókövetés stb.) sok előnye ellenére is a
CLR-interfészek használata a legjobb a WCF-szerződések leírására.

A WCF -kötések

Miután a szerződést (vagy szerződéseket) a szolgáltatáskönyvtárban definiál­


tuk és implementáltuk, a következő logikai lépés maga a WCF-szolgáltatás
hasztolóprogramjának elkészítése. Több különböző hoszt közül választhatunk,
ezek közül mindegyiknek meg kell határoznia azokat a kötéseket, amelyeket a
távoli hívók használnak a szolgáltatástípus funkcionalitásának elérésére.
A kötések kiválasztása az egyik olyan terület, ahol a WCF-fejlesztés megle­
hetősen eltér a .NET-remoting és/vagy XML-webszolgáltatás fejlesztésétől ab­
ban, hogy a WCF-hez járnak azok a kötési választási lehetőségek, amelyek
mindegyike speciális igények kielégítésére szolgál. Ha a szállított kötések
egyike sem felel meg, akkor lehetőség van saját kötés létrehozására a custom­
sinding típus kibővítésével (ezt ebben a fejezetben nem tesszük meg). A WCF­
kötés egyszerűen a következő jellemzőket adha�a meg:

• A szolgáltatás által implementált szerződések.

• Az adatok mozgatására használt szállítási réteg (HTTP, MSMQ,


named pipe-ok, TCP).

• A szállításra használt csatornák (egyirányú, kérés-válasz, duplex).

• Magukkal az adatokkal foglalkozó kódolási mechanizmus (XML, biná­


ris stb.).

• Bármilyen támogatott webszolgáltatás-protokoll (ha a kötés engedé­


lyezi), mint például WS-Security, WSTransactions, WS-Reliability és
így tovább.

Nézzük meg a választási lehetőségeinket.

298
A WCF ABC-je

HITP-alapú kötések

A BasicHttpBinding, a WSHttpBinding, a w s oua lHttpBinding és a WSFederati on­

HttpBindin g opciók arra szolgálnak, hogy a szerződéstípusokat XML-web­


szolgáltatás-protokollokon keresztül elérhetővé tegyék. Ha a szolgáltatás
számára a lehető legtávolabbi elérésre van szükségünk (több operációs rend­
szer és többféle programozási architektúra között), ezekre a kötésekre kell
összpontosítanunk, mert rninden ilyen kötéstípus XML-megjelenítés alapján
kódolja az adatokat, és HITP-t használ.
A WCF-kötést megjeleníthetjük a kódban (a System. serviceMaclel névtér­
ben levő osztálytípusok révén), vagy definiálhatjuk XML attribútumként a
''.con fi g fájlokban (lásd 25.3. táblázat).

BasicHttpBinding <basicHttpBinding> WS-Basic Profile-kompatibilis


(WS-I Basic Profile 1.1) WCF­
szolgáltatás készítésére al­
kalmas. Ez a kötés szállításra
HTIP-t, az üzenetkódolásra
az alapértelmezés szerint szö­
veg/XML-t használ.

WSHttpBinding <WSHttpBinding> A Basi cHttpBindinghoz ha­


sonló, de több webszolgálta­
tási funkciót kínál. Ez a kötés
támogatást nyújt tranzakci­
ókhoz, megbízható üzenet­
küldéshez és WS-círnzéshez.

WSDualHttpBinding <WSDualHttpBinding> A WSHttpBinding elemhez


hasonló, de duplex szerződé­
sekhez használható (pl. a
szolgáltatás és a kliens üzene­
teket tud küldeni oda-vissza).
Ez a kötés csak a SOAP biz­
tonsági beállításait támogatja,
és megbízható üzenetküldést
követel meg.

299
25. fejezet: A WCF

WSFederationHttp­ <wsFederationHttp­ Biztonságos és együttműködő


Binding Binding> kötés, amely támoga�a a WS­
Fedaration protokollt, lehető­
vé téve felhasználók hatékony
azonosítását és engedélyezé­
sét szövetséget alkotó szerve­
zetek számára.

25. 3. táblázat: HITP-központú WCF-kötések

A Basi cHttpBindi ng a legegyszerűbb webszolgáltatás-központú protokoll. Ez


a kötés biztosí�a, hogy a WCF-szolgáltatásunk megfeleljen a WS-I-ben defi­
niált WS-I Basic Profile 1.1 specifikációnak Ezt a kötést főleg a visszamenő­
leges kompatibilitás megtartása rniatt használjuk olyan alkalmazásokkat
amelyek korábban készültek ASP.NET-webszolgáltatásokkal folytatott kom­
munikációra (amelyek az 1.0 verzió óta a .NET-könyvtárak részei).
A WSHttpBinding protokoll nemcsak a WS-* specifikációk részhalmazát
támoga�a (tranzakciók, biztonság és megbízható munkamenetek), hanem a
bináris adatkódolás kezelésének lehetőségét MTOM-technológiával (Message
Transrnission Optimizahon Mechanism - üzenetátvitel optimalizációs me­
chanizmusa).
A wsoua l HttpBinding legfőbb előnye az, hogy a hívónak és a küldőnek le­
hetövé teszi a kommunikációt duplex üzenetküldéssel, amely azt jelenti, hogy
két irányban tudnak egymással társalogni. Ha a wsoua l HttpBi ndi ngot választ­
juk, bekapcsalódhatunk a WCF publish/subscribe (közzétesz és előfizet)
eseménymodelljébe.
Végül a WSFederationHttpBinding az a webszolgáltatás-alapú protokoll,
amelyet akkor érdemes használni, ha a biztonság a legfontosabb szempont
A kötés támoga�a a WS-Trust, WS-Security és a WS-SecureConversation spe­
cifikációkat, amelyeket a WCF CardSpace API-k valósítanak meg.

TCP-alapú kötések

.NET 3.0/3.5 könyvtárakkal konfigurált számítógépekkel (más szóval minden


Windows XP, Windows Server 2003 vagy Windows Vista operációs rendszert
futtató gépet) üzemelő elosztott alkalmazás készítése során javulhat a teljesít­
mény, ha megkerüljük a webszolgáltatási kötéseket, és inkább TCP-kötést vá­
lasztunk, amely biztosí�a, hogy minden adat XML helyett kompakt bináris
formátumban legyen kódolva. A 25.4. táblázatban szereplő kötések használa­
takor a kliensnek és a hosztnak .NET -alkalmazásnak kell lennie.

300
A WCF ABC-je

&í13�t···._...-.J��. -�:�2��-,-�·:_-,�::__ ;:_··, ��;:;:,>:�-:�t�·.:;;::.��:zi;�irti:.,.: -�,-�- ';


NetNamedPipeBinding <netNamedPipe­ Biztonságos, megbízható, op­
Binding> timalizált kötés egy számítógé­
pen levő NET-alkalmazások
közötti kommurúkációhoz.

NetPeerTcpBinding <netPeerTcpBinding> Biztonságos kötést biztosít P2P


hálózati alkalmazásokhoz.

NetTcpBinding <netTcpBind.ing> Biztonságos és optimalizált


kötés .NET-alkalmazások kö­
zötti kommunikációhoz több
számítógép között.

25.4. táblázat: TCP-központú WCF-kötések

A NetTcpBinding osztály TCP-t használ bináris adatok átvitelére a kliens és a

WCF-szolgáltatás között. Ez jobb teljesítményt eredményez, mint a webszolgál­


tatás-protokollok, de így a lehetőségeink házon belüli Windows-megoldásokra
korlátozódnak Előnye viszont, hogy a NetTcpBinding tárnagalja a tranzakciókat,
a megbízható munkameneteket és a biztonságos kommunikációt
A NetTcpBindinghoz hasonlóan a NetNamedPi peBinding ugyancsak tárnagalja

a tranzakciókat, a megbízható munkameneteket és a biztonságos kommuniká­


ciót, de nem képes gépek közötti hívások lebonyolítására. Ha az egy gépen futó
WCF-alkalmazások közötti leggyorsabb adatküldési módot keressük (pL al­
kalmazástartományok közötti kommunikáció), akkor a NetNamedPipeBinding a
legjobb választása a kötésre. Ami a NetPeerTcpBindingot illeti, a P2P további
részleteiért forduljunk a .NET Framework 3.5 dokumentációjához.

MSMQ-alapú kötések

Végül, ha Microsoft MSMQ szervert próbálunk használni, a NetMsmqBinding


és az MsmqrntegrationBinding kötés kerül a középpontba. Ebben a fejezetben
nem vizsgáljuk meg részletesen az MSMQ-kötések használatát, de a 25.5. táb­
lázat bemutatja az alapvető szerepüket

301
25. fejezet: A WCF

Msmqintegration­ <msmqintegration­ Ez a kötés lehetövé teszi, hogy


Binding Binding> WCF-alkalmazások olyan létező
MSMQ-alkalmazásoknak küldje­
nek, és alkalmazásoktól fogadja­
nak üzeneteket, amelyek COM-ot,
natív C++-t vagy a system.
Messagi ng névtérben definiált tí­

pusokat használnak.

NetMsmqBinding <netMsmqBinding> Ez a kötés alkalmas .NET-alkal­


mazások gépek közöttí kommu­
nikációjára.

25.5. táblázat: Az MSMQ-központú WCF-kötések

Nincs olyan kötés, amely kifejezetten a COM+-objektumokkal folytatott kommunikádót szolgálja.


A WCF révén teljes mértékben tudunk COM+-komponensekkel kommunikálni, ez azonban azt je­
lenti, hogy a COM+-típusokat elérhetővé kell tennünk XML-webszolgáltatási kötésen keresztül a
Comsvcconfig.exe parancssori eszköz segítségéve!- amely a .NET Framework 3.5 SOK része­

vagy az svcconfi gEdi tor. exe eszközzel (amelyet a későbbiekben megvizsgálunk j. _

A WCF-cimek

Ha már készen vannak a szerződések és a kötések, végül meg kell adnunk a


WCF-szolgáltatás címét. Ez nyilvánvalóan meglehetősen fontos, hiszen távoli
hívák nem tudnak kommunikálni távoli típusokkal, ha nem tudják, hol van­
nak. A WCF egyéb funkcióihoz hasonlóan a címet beírha�uk egy szerelvény
kódjába (a system. uri típus segítségéve!) vagy betölthe�ük a*. c o n fi g fájlbóL
A WCF-cím pontos formátuma mindkét esetben a választott kötéstől (HTIP­
alapú, named pipe-ok, TCP- vagy MSMQ-alapú) függően változik. A WCF­
címek a következő infromációkat adha�ák meg:

• s che me: A szállítási protokoll (HTIP stb.).

• Ma ch ineName: A számítógép teljesen meghatározott tartománya.

• Port: Ez sok esetben opcionális. A HITP-kötések alapértelmezett porlja

például a 80-as.

• P a th: A WCF-szolgáltatás elérési útvonala.

302
A WCF ABC-je

Ezeket az információkat a következő általánosított sablon muta�a be (a Port


érték opcionális, mivel néhány kötés nem használja):

scheme://<MachineName>[:Port]/Path

Webszolgáltatás-alapú kötés (basicHttpBinding, wsHttpBinding, wsDualHttp­


Binding vagy wsFederati onHttpBinding) használatakor a cím így áll össze (ha

nem adunk meg portszámot, a HTTP-alapú protokollok por�a alapértelme­


zés szerint a 80-as):

http://localhost:8080/MyWCFService

Megjegyzés Ha SSL-t (Secure Sockets Layer) használunk, csak írjuk át a http-t https-re.

Ha TCP-központú kötéseket használunk (mint pl. NetTcpsinding vagy Net­


PeerTcpBinding kötéseket), az URI-formátuma a következő lesz:

net.tcp://localhost:8080/MyWCFService

Az MSMQ-központú kötések (NetMsmqBinding és MsmqintegrationBinding)


URI-formátuma kicsit egyedi, mivel az MSMQ használhat nyilvános vagy
privát sorokat (amelyek csak a helyi gépen állnak rendelkezésre), valamint a
portszámoknak nincs jelentése az MSMQ-központú URI-ben. Nézzük meg a
következő URI-t, amely a MyPrivateQ nevű privát sort jellemzi:

net.msmq://localhost/private$/MyPrivateQ

Végül, de nem utolsósorban a named-pipe kötéssel (NetNamedPipeBinding)


használt címformátum így épül fel (a named pipe-ok teszik lehetővé a folya­
matok közötti kommunikációt ugyanazon a gépen üzemelő alkalmazások
számára):

net.pipe://localhost/MyWCFService

Bár lehet, hogy egyetlen WCF-szolgáltatás csak egy címet tesz elérhetővé
(egyetlen kötésre alapozva), lehetőség van egyedi címek gyűjteményének a
definiálására is (különböző kötésekkel). Ezt több <endpoint> elem definiálá­
sával tehe�ük meg a *.con fig fájlban. Itt tetszőleges számú ABC-t megadha­
tunk ugyanahhoz a szolgáltatáshoz. Ez a megközelítés hasznos lehet, ha meg
akarjuk engedni a hívóknak, hogy kiválaszthassák a használni kívánt proto­
kollt, amikor a szolgáltatással kommunikálnak.

303
25. fejezet: A WCF

WCF -szolgáltatás készítése

Hozzuk létre első példaprogramunkat, hogy megnézzük, az ABC-k hogyan


jelennek meg a kódban. Első lépésként definiáljuk a WCF-szolgáltatáskönyv­
tárat, amely tartalmazza a szerződéseket és implementációjukat.
Az első példában nem használjuk a Visual Studio WCF projektsablonjait,
hogy a WCF-szolgáltatás készítésének egyes lépéseire figyeljünk. Kiindulás­
ként hozzunk létre egy C#-osztálykönyvtárt MagicEightBallServiceLib név­
vel. Nevezzük át az eredeti fáljt Classl.cs-ről MagicEightBallService.cs-re, és
adjunk hozzá referenciát a system.serviceModel.dll szerelvényre. A kiindu­
lási kódfájlban adjuk meg, hogy a system.serviceModel névteret használjuk.
Ekkor a C#-fájlnak így kell kinéznie:

using system;
using system.collections.Generic;
using System.Linq;
using System.Text;

ll A fő WCF-névtér.
using System.ServiceModel;

namespace MagicEightBallserviceLib
{
public class MagicEightBallservice
{
}
}

Az osztálytípusunk egyetlen WCF szolgáltatási szerződést valósít meg az


erősen típusos IEi ghtBa ll nevű CLR-interfész révén. Talán ismerjük a Magic
8-Ball nevű játékot, ebben egy feltett kérdésre előre megadott válaszok közül
kell kiválasztani egyet. Az interfészünk egyetlen metódust definiál, amellyel
a hívó feltehet egy kérdést aMagic8-Ball játéknak, a program pedig véletlen­
szerű választ ad.
A WCF szolgáltatási interfészek rendelkeznek a [Servi ceconract] attribú­
tummal, az interfész minden tagjához pedig hozzárendeljük a [Operation­
contract] attribútumot (ezek részleteit lásd később). Az IEightBall interfész
definíciója a következő:

304
WCF-szolgáltatás készítése

[Servicecontract]
public interface IEightBall
{
ll Kérdezz, hogy választ kapj!
[operationcontract]
string obtainAnswerToQuestion(string userQuestion);
}

Megjegyzés Olyan szolgáltatásszerződési interfészeket lehet definiálni, amelyek metódusai


nem tartalmazzák az [Operati oncontract] attribútumot, de az ilyen tagok nem lesznek el­
érhetőek a WCF-futtatórendszerben_

Az interfészek addig használhatatlanok, amig egy osztály vagy struktúra


nem implementálja őket, hogy kihasználja funkcionalitásukat (lásd az előző
kötet 9. fejezetét). Az igazi Magic 8-Ball játékhoz hasonlóan az általunk imp­
lementált szolgáltatástípus (Magic EightBallservice) véletlenszerűen ad visz­
sza egy választ az előre megadott sztringtömb elemei közül. Az alapértelme­
zett konstruktor üzenetet küld, amely (végül) a hoszt konzolablakában jele­
nik meg (az ellenőrzés miatt):

public class MagicEightBallservice : IEightBall


{
ll csak megjelenítési célból a hoszton.
public MagicEightBallservice()
{
console.WriteLine("The 8-Ball awaits your question...");
}

public string obtainAnswerToQuestion(string userQuestion)


{
string[] answers = { "Future uncertain", "Yes", "No",
"Hazy", "Ask again later", "Definitely" };

ll Véletlenszerűen visszaad egy választ.


Random r = new Random();
return string.Format("{O}? {1}.",
userQuestion, answers[r.Next(answers.Length)]);
}
}

Készen van a WCF-szolgáltatáskönyvtárunk. Mielőtt elkészítenénk a hasztot a


szolgáltatáshoz, nézzük meg bővebben a [Servicecontract] és az [operation­
contract] attribútumokat

305
25. fejezet: A WCF

A [ServiceContract] attribútum

A CLR-interfésznek tartalmaznia kell a [servicecontract] attribúturnot, hogy


részt tudjon venni a WCF szolgáltatásokban. Több más .NET-attribúturnhoz
hasonlóan a servi cecontractAttri bute típus számos tulajdonságot támogat
funkciójának jobb kiaknázására. A Name és a Namespace tulajdonságot beállíthat­
juk, hogy megadja a szolgáltatástípus és a szolgáltatástípust definiáló XML­
névtér nevét. Ha webszolgáltatás-specifikus kötést használunk, ezek az értékek
megadják a <portType> elemet a kapcsolódó WSDL-dokurnenturnban.
A Name tulajdonságnak nem adtunk értéket, rnivel a szolgáltatástípus alap­
értelmezett neve közvetlenül a C#-osztály nevén alapul. Az alapul szolgáló
XML-névtér alapértelmezett neve azonban egyszerűen http://tempuri.org
(amelyet illik megváltoztatni rninden WCF-szolgáltatásnál).
Egyedi adattípusokat küldő és fogadó WCF-szolgálatás készítése során
fontos, hogy értelmes értéket adjunk az alapul szolgáló XML-névtérnek, mert
ez biztosí�a, hogy az egyedi típusaink valóban egyediek legyenek. Ha már
készítettünk XML-webszolgáltatást, akkor tudjuk, hogy az XML-névtér mó­
dot ad arra, hogy az egyedi típusokat egyedi tárolóba helyezzük, hogy ezek
ne ütközzenek más szervezetek típusaivaL
Ezért az interfészdefiníciónkat frissíthe�ük egy jobb definícióval, amely
csakúgy, rnint a .NET-webszolgáltatás-projektben az XML-névtér definiálá­
sakor, igazából a szolgáltatás származási helyének URI-je, például:

[Servicecontract(Namespace = "http://Intertech.com")]
public interface IEightBall
{

A Namespace és a Name tulajdonságokon kívül a [servicecontract] attribúturnot


konfigurálha�uk egyéb, a 25.6. táblázatban látható tulajdonságokkal. A válasz­
tott kötéstől függően néhány beállítást a rendszer figyelmen kívül hagy.

callbackcontract Megadja, hogy a szolgáltatásszerződésnek szüksége van


visszahívási funkcionalitásra a kétirányú üzenetváltáshoz.

configurationName Az alkalmazás konfigurációs fájljában a szolgáltatáselem


megkereséséhez h asznált nevet foglalja magában. Az
alapértelmezés szerint a szolgáltatás implementációs osz­
tályának neve.

306
WCF-szolgáltatás készítése

... ,·.:-'•
!...•'_:: �

ProtectionLevel Megadja a szerződés kötése által megkövetelt titkosítás, a


digitális aláírások vagy mindkettő szükségességének
szin�ét a szerződést elérhetővé tevő végpontok számára.

SessionMode Megadja, hogy a munkamenetek engedélyezettek, nem


engedélyezettek vagy szükségesek-e a szolgáltatási szer­
ződésben.

25. 6. táblázat: A [ServiceContract] attribútum megnevezett tulajdonságai

Az [OperationContract] attribútum

A WCF keretrendszerben használill kívánt metódusokhoz hozzá kell rendelill

az [op e r a tioncontract] attribútumot, amelyet szintén több nevesített tulaj­


donsággal konfigurálhatunk. A 25.7. táblázatban található tulajdonságok
használatával megadha�uk, hogy az adott metódus természete egyirányú le­
gyen, támogassa az aszinkron hívást, kódolt üzenetadatokat követeljen meg,
és így tovább (az értékek közül a rendszer többet figyelmen kívül hagyhat a
választott kötéstől függően).

Action A kérési üzenet WS-Addressing tevékenységét állí�a be


vagy adja vissza.

AsyncPattern A szolgáltatásban aBegin/End metóduspár segítségével


jelzi, ha a művelet aszinkron módon van implementálva.
Ez teszi lehetövé a szolgáltatás számára, hogy átadja a fel­
dolgozást egy másik szerveroldali szálnak. Ennek semmi
köze a kliens aszinkron metódushívásához.

Isinitiating Megadja, hogy a művelet lehet egy munkamenet kezdő­


művelete.

rsoneway Jelzi, hogy a művelet csak egyetlen bemenő üzenetből áll


(és nincs hozzátartozó kimenet).

IsTerminating Megadja, hogy a művelet befejezése után a W eF-futtató­


környezet megpróbálja leállítani a jelenlegi munkamenetet.

25.7. táblázat: Az [OperationContract] attribútum megnevezett tulajdonságai

307
25. fejezet: A WCF

Ebben a kezdeti példában nem kell az obtainAnswerToQuestion() metódust


egyéb jellemzőkkel konfigurálni, az [operationeontract] attribútum hasz­
nálható az eredetileg definiált módon.

Szolgáltatástipusok mint müködési szerződések

WCF-szolgáltatástípus készítése során nem kell interfészeket használni. Tu­


lajdonképpen a [servicecontract] és az [Operationeontract] attribútumot
közvetlenül a szolgáltatástípusra is alkalmazhatjuk:

ll csak illusztráció,
//az aktuális példában nem szerepel.
[Servicecontract(Namespace "http://Intertech.com")]
=

public class ServiceTypeAsContract


{
[operationcontract]
void someMethod() { }
[Operationcontract]
void AnotherMethod() { }
}

Jóllehet ez is egy lehetséges megközelítés, sok előnye van az interfésztípus


explicit definiálásának a szolgáltatásszerződés elkészítésében. A legnyilván­
valóbb előny az, hogy az adott interfész többféle szolgáltatástípushoz (kü­
lönböző nyelveken és architektúrákon készült) használható a nagyfokú po­
limorfizmus biztosítása érdekében. Másik előny az, hogy a szolgáltatásszer­
ződési interfész használható új szerződések alapjaként (interfészszármaztatás
révén), az implementáció terhe nélkül.
Jelen pillanatban az első WCF-szolgáltatáskönyvtárunk készen van. Fordít­
suk le a projektet, hogy meggyőződhessünk róla, nincs-e benne gépelési hiba.

Forráskód A MagicEightBatlServiceClient kódfájlokat a forráskódkönyvtár 25. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WCF-szolgáltatás hasztolása

Bár a termékszintű szolgáltatásokat rendszerint Windows-szolgáltatás vagy


IlS-beli virtuális könyvtár hosztolja, az első hasztunk egyszerű parancssor lesz
Magi cEightBallserviceHost névvel.

308
A WCF-szolgáltatás hasztolása

Ha elkészült az új konzolalkalmazás-projekt, adjunk hozzá referenciát a


system. serviceModel.dll és a MagicEi ghtBallserviceLi b. dll szerelvényre,
majd frissítsilk az eredeti kódfájlt a system.serviceModel és a MagicEightBall­
serviceLi b névterek importálásával:

using System;
using system.collections.Generic;
using system.Linq;
using System.Text;

using system.serviceModel;
using MagicEightBallserviceLib;

namespace MagicEightBallserviceHost
{
class Program
{
static void Main(string[] args)
{
Console.writeLine("***** Console Based WCF Host *****");
console.ReadLine();
}
}
}

WCF-szolgáltatástípus készítésekor első lépésként el kell dönteni, hogy a szük­


séges hosztolási logikát teljes mértékben a kódban szereménk-e definiálni, vagy
néhány alacsony szintű részletet az alkalmazás konfigurációs fájljában szeret­
nénk megadni. A * .config fájlok előnye az, hogy a hoszt megváltoztatha�a az

alapul szolgáló beállításokat anélkül, hogy újra kellene fordítanunk és telepíte­


nünk a végrehajtható fájlt. Ez szigorúan opcionális, mivel a hosztolási logikát a
kódban a system.serviceModel . dll szerelvény típusaival is megadha�uk.
Ez a parancssori hoszt ténylegesen az alkalmazás konfigurációs fájlját hasz­
nálja, ezért adjunk hozzá az aktuális projekthez új alkalmazáskonfigurációs
fájlt a Project > Add New Item menüponttal.

ABC-k létrehozása az App.config fájlban

A WCF-szolgáltatástípushoz a hoszt készítése megjósolható lépések soroza­

tából áll - a konfigurációs fájlban és a forráskódban:

l. Definiáljuk a hasztolni kívánt WCF-szolgáltatáshoz a végpontot a hoszt


konfigurációs fájljában.

309
25. fejezet: A WCF

2. Programozottan használjuk a serviceHost típust az ebből a végpontból


rendelkezésre álló szolgáltatástípusok elérhetővé tételéhez.

3. Győződjünk meg róla, hogy a hoszt még fut azért, hogy kiszolgálja a

kliensektől bejövő kéréseket. Ez a lépés nyilván nem szükséges, ha Win­


dows-szolgáltatással vagy IlS-sei hasztoljuk a szolgáltatástípusokat.

A WCF világában a "végpont" kifejezés egyszerűen a címet, a kötést és a szer­


ződést jelenti, csinos kis csomagban összekötve. XML-ben a végpontot az <end­
point> elem, valamint az address, a binding és a cont ract elem fejezi ki. Fris­

sítsük a*. config fájlt, és adjunk meg egy végpontot (a 8080-as porton keresztül
érhető el), amelyet ezen a haszton teszünk elérhetővé:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<system.serviceModel>
<services>
<service name="MagicEightBallserviceLib.MagicEightBallService">
<endpoint address ="http://localhost:8080/MagicEightBallservice"
binding="basicHttpBinding"
contract="MagicEightBallserviceLib.IEightBall"/>
</service>
</services>
</system.serviceModel>
</configuration>

A <system.serviceModel> a gyökérelem a hoszt összes WCF-beállításához.


A haszton minden elérhető szolgáltatást a <service> elem képvisel, amelye­
ket a <services> alapelembe ágyazunk Itt az egyetlen <service> elem az (op­
cionális) name attribútummal adja meg a szolgáltatástípus barátságos nevét.
A beágyazott <endpoint> elem definiálja a címet, a kötési modellt (a példában
)
basicHttpBinding és a WCF-szolgáltatásszerződést definiáló interfésztípus telje­

sen meghatározott nevét ( rEightBall) . Mivel HTTP-alapú kötést használunk, a


http:/ j sémát alkalmazzuk, és tetszés szerinti portazonosítót határozunk meg.

A ServiceHost tipus használata

Az aktuális konfigurációs fájl birtokában a hoszt befejezéséhez a programozási


logika elkészítése nagyon egyszerű. Amikor a végrehajtható fájl elindul, létre­
hozzuk a ServiceHost típus egy példányát. Futásidőben az objektum automa­
tikusan kiolvassa a <system.serviceModel> elem hatókörében található adato­
kat a hoszt *.config fájljából, hogy meghatározza a helyes címet, kötést és
szerződést, és elkészíti a szükséges összeköttetéseket

310
A WCF-szolgáltatás hasztolása

static void MainCstring[] args)


{
Console.writel ineC"''*''** Console Based WCF Host '''*''**");
using CServiceHost serviceHost =

new serviceHostCtypeofCMagicEightBallservice)))
{
ll Megnyitja a hosztot, és figyelni kezdi a bejövő üzeneteket.
serviceHost.OpenC);

ll Az Enter billentyű leütéséig futtatja a szolgáltatást.


console.WriteLineC"The service is ready.");
Console.writeLineC"Press the Enter key to terminate service.");
console.ReadLineC);
}
}

Ha futta�uk az alkalmazást, kiderül, hogy a hoszt él a memóriában, készen a


távoli kliensektől érkező kérések fogadására (lásd a 25.6. ábrát).

i! C:\Windows\system32\cmd.exe

a
i

25.6. ábra: Hasztunk készen áll a külső hívásokra HITP-kötésen keresztül

H osztfej lesztési lehetőségek

Jelenleg olyan konstruktorral hozzuk létre a serviceHostot, amely csak a szal­


gáltatás típusáról igényel információt. Ezt azonban átadha�uk konstruktorpa­
ramétereként system. uri típusok tömbjében, amely olyan címek gyűjteményét
képviseli, ahonnan a szolgáltatás elérhető. A cím jelenleg a * .config fájl révén
érhető el, de ha a hatókört frissítenénk, akkor:

using CServiceHost serviceHost =

new serviceHostCtypeofCMagicEightBallservice),
new uri[]{new
uriC'' http:lllocalhost:8080IMagicEightBallservice")}))
{

311
25. fejezet: A WCF

így végpontot is tudnánk definiálni:

<endpoint address =""


binding="basicHttpBinding"
contract="MagicEightBallserviceLib.IEightBall"/>

A túl sok kódolás a hoszt programkódjában csökkenti a rugalmasságot, így a


jelenlegi hosztpéldánkhoz a szolgáltatási hasztot egyszerűen a típusinformá­
ció megadásával hozzuk létre, ahogy eddig is tettük:

using (ServiceHost serviceHost =


new ServiceHost(typeof(MagicEightBallservice)))
{

A hoszt *.config fájlok létrehozásának egyik (kissé zavaró) vonása az, hogy
az XML-leírókat többféleképpen összeállíthatjuk a programban levő forrás­
kód mennyiségétől függően (ahogyan éppen az előbb láttuk az opcionális
URI-tömb esetében). Hogy megmutassuk a *.config fájlok létrehozásának
egy másik módját, gondoljuk át a következő újrafeldolgozást:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<system.serviceModel>
<services>
<service name="MagicEightBallserviceLib.MagicEightBallservice">

<!-- A címet a <baseAddresses> adja meg.-->


<endpoint address ='"'
binding="basicHttpBinding"
contract="MagicEightBallserviceLib.IEightBall"/>

<!-- Megjelölt részben felsorolja az összes alapcímet.-->


<host>
<baseAddresses>
<add baseAddress
"http://localhost:8080/MagicEightBallService"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

312
A WCF-szolgáltatás hasztolása

Ebben az esetben az <end po i nt:> elem address attribútuma még üres, és füg­
getlenül attól, hogy a serviceHost: létrehozásakor a kódban nem adjuk meg
az URI-objektumok tömbjét, az alkalmazás ugyanúgy fut, rnint az előbb,
mert az értéket a baseAddresses hatóköréből veszi. Az alapcím tárolásának
előnye a <host:> <baseAddresses> területén az, hogy a*. confi g fájlnak (mint a
MEX, lásd később) ismernie kell a szaigáitatás végpontjának címét is. Így
ahelyett, hogy egyetlen *.confi g fájlba kellene másolni és abban küldeni a
címértékeket, a bemutatott módon elkülöníthetjük az egyetlen értéket.

Megjegyzés A fejezet későbbi részében bemutatunk egy grafikus konfigurációs eszközt a


konfigurációs fájlok kevésbé bonyolult elkészítéséhez.

Mielőtt a szerverünkkel kommunikáló kliensalkalmazást készítenénk, vizs­


gáljuk meg a serviceHost: osztálytípus, a <service. serviceModel> elem és a
MEX (metadata exchange- metaadatcsere) szerepét.

A ServiceHost tipus

A serviceHost: osztálytípus segítségével tudjuk beállítani és elérhetővé tenni

a WCF-szolgáltatást a hasztoló végrehajtható fájlbóL Ezt a típust csak akkor


használjuk közvetlenül, ha egyedi *.exe hasztolja szolgáltatásainkat. Ha a
szaigáitatás közzétételéhez IlS-t (vagy a Vista-specifikus W AS-t) használunk,
a serviceHost: objektum automatikusan létrejön.
Ez a típus teljes szolgáltatásleírást igényel, amelyet dinarnikusan kap meg
a h oszt *.confi g fájl konfigurációs beállításaibóL Amíg ez az objektum létre­
hozása során automatikusan megtörténik, a servi ceHost: objektum állapota
manuálisan állítható be néhány tag segítségéve!. A 25.8. táblázat mutatja az
Open() és close() tagok (amelyek aszinkron módon kommunikálnak a szol­

gáltatásunkkal) mellett szót érdemlő tagokat.

Aut:horizat:ion Ez a tulajdonság visszaadja a hasztolt szolgáltatás hite­


lesítési szin�ét.

AddserviceEndpoint:() Ezzel a metódussal programozottan regisztrálhatunk


végpontot a hoszthoz.

313
25. fejezet: A WCF

BaseAddresses Ez a tulajdonság megszerzi az adott szolgáltatás re­


gisztrált alapcímeinek a listáját.

BeginOpenO Ezek a metódusok teszik lehetővé a serviceHost objek­


BegineloseO tum aszinkron megnyitását és bezárását a szabványos
.NET-es metódusreferencia-szintaxis használatávaL

eleseTimeout Ezzel a tulajdonsággal beállíthatjuk vagy visszakapjuk


a szolgáltatás leállítására engedélyezett időt.

Credentials Ez a tulajdonság megszerzi az adott szolgáltatás biz­


tonsági igazolásait.

Endopen O Ezek a BeginopenO és a Begi neloseO metódusok


EndcloseO aszinkron megfelelői.

OpenTimeout Ezzel a tulajdonsággal beállíthatjuk vagy kiolvashatjuk


a szolgáltatás elindítására engedélyezett időt.

State Ez a tulajdonság visszaad egy értéket, amely a kom­


munikációs objektum- ezt a Communicationstate fel­
sorolt típus (opened, closed, c re ated stb.) képviseli -
aktuális állapotát jelzi.

25.8. táblázat: A SeroiceHost típus néhány tagja

A serviceHost néhány további funkciójának bemutatásához módosítsuk a

Program osztályt új statikus metódussal, amely kiírja az aktuális hoszt kü­


lönböző jellemzőit:

static void DisplayHostinfo(ServiceHost host)


{
Console.WriteLine();
console.writeline('' ***** Host Info *****");

console.WriteLine("Name: {O}",
host.Description.ConfigurationName);
console.writeline("Port: {O}",
host.BaseAddresses[O].Port);
console.writeLine("LocalPath: {O}",
host.BaseAddresses[O].LocalPath);
console.writeline("uri: {O}",
host.BaseAddresses[O].Absoluteuri);
Console.WriteLine("scheme: {O}",
host.BaseAddresses[O].scheme);
console. writeLine("**********************");
Console.WriteLine();
}

314
A WCF-szolgáltatás hasztolása

Tételezzük fel, hogy a hoszt megnyitása után ezt az új metódust lúvjuk a Main()
metódusból:

using CserviceHost serviceHost


new ServiceHost(typeof(MagicEightBallservice)))
{
ll Megnyitja a hosztot, és figyelni kezdi a bejövő üzeneteket.
serviceHost.Open();
DisplayHostinfo(serviceHost);

így a 25.7. ábrán látható statisztikákat kapjuk.

25. 7. ábra: A hoszt tulajdonságai

A <system.serviceModel> elem jellemzői

Más XML-elemekhez hasonlóan a <system.serviceModel> elemben is defini­


álhatunk részelemeket, amelyek mindegyikét több attribútum minősítheti.
Míg a lehetséges attribútumokat illetően részleteket a .NET Framework 3.5
SDK dokumentációja tartalmaz, a következő vázlat felsorolja az érvényes
részelemeket:

<system.serviceModel>
<behaviors>
<lbehaviors>
<client>
<lclient>
<commonBehaviors>
<lcommonBehaviors>
<diagnostics>
<ldiagnostics>
<serviceHostingEnvironment>
<lserviceHostingEnvironment>

315
25. fejezet: A WCF

<comcontracts>
</comcontracts>
<services>
</services>
<bindings>
</bindings>
</system.serviceModel>

Az egyes részelemek lényege megtalálható a 25.9. táblázatban.

behaviors A WCF különböző végpont- és szolgáltatási viselkedést


támogat. Röviden: abehavior segítségével tovább bő­
víthetjük a hoszt vagy a kliens funkeionalitását.

bindings Ezzel az elemmel beállíthatjuk a WCF által biztosított


kötéseket (basicHttpBinding, netMsmqBinding stb.),
valamint megadhatjuk a hoszt által használt egyedi
kötéseket.

cl ient Ez az elem tartalmazza azon végpontok listáját, ame­


lyeket a kliens a szolgáltatáshoz kapcsolódva használ.
Ez természetesen nem túl hasznos a hoszt *.config
fájljában.

comcontracts Ez az elem definiálja a WCF és COM együttműködésé­


hez engedélyezett COM-szerződéseket.

commonBehaviors Ezt az elemet csak a mach ine. con fig fájlban lehet al­
kalmazni. Segítségével lehet beállítani minden olyan
viselkedést, amelyet az egyes WCF-szolgáltatások egy
adott gépen használnak.

diagnostics Ez az elem tartalmazza a WCF diagnosztikai jellemzői­


nek beállításait. A felhasználó beállíthatja a nyomköve­
tés, a teljesítményszámlálók és a WMl-szolgáltató en­
gedélyezését/letiltását, valamint egyedi üzenetszűrő­
ket adhat meg.

serviceHosting­ Ez az elem határozza meg, hogy a művelet lehet egy


Environment munkamenet kezdőművelete.

services Ez az elem tartalmazza a haszton elérhető WCF­


szolgáltatások gyűjteményét.

25. 9. táblázat: A <seruice.seruiceModel> részelemei

316
A WCF-szolgáltatás hasztolása

Metaadatcsere (Metadata Exchange) engedélyezése

A WCF-ügyfélalkalmazások egy közbeépülő proxytípuson keresztül kom­


munikálnak a WCF-szolgáltatással. Bár a proxy kódja teljes egészében elké­
szíthető manuálisan, ez fárasztó és hibákat magában rejtő folyamat. Ideális
esetben rendelkezésre állna egy eszköz a szükséges kód generálására (a kli­
ensoldali *.con fi g fájlt is beleértve). Szerencsére a .NET Framework 3.5 SDK
pontosan erre a célra biztosít egy parancssori eszközt (svcuti l. exe) , valamint
a Visual Studio 2008 Project >Add Service Reference menüpontja is hasonló
funkcionalitást nyújt.
A szükséges proxykód/*. con fig fájl generálásához ezeknek az eszközök­
nek fel kell térképezniük a WCF-szolgáltatás interfészeinek és az összes defi­
niált adatszerződésnek a formátumát (a metódusneveket, a paraméterek tí­
pusát stb.).
A MEX (metadata exchange - metaadatcsere) olyan WCF szolgáltatási vi­
selkedés, amelynek megadásával beállíthatjuk, hogy a WCF-futtatókörnyezet
hogyan kezelje a szolgáltatást. Minden <behavior> elem megadhat egy olyan
tevékenységkészletet, amelyre egy adott szolgáltatás előfizethet A WCF több
viselkedést készen kínál, de a sajátunkat is elkészíthetjük
A MEX-viselkedés (amely az alapértelmezés szerint nem engedélyezett)
minden HTTP GET utasításan keresztül küldött metaadatkérést észlel. Ha
megengedjük az svcuti l . ex e-nek vagy a Visual Studio 2008-nak, hogy auto­
matikusan hozza létre a kívánt ügyféloldali proxy *. config fájlját, engedé­
lyezni kell a MEX-et.
A MEX engedélyezése a hoszt *.confi g fájljában a megfelelő beállítások
módosításával (vagy a megfelelő C#-kód létrehozásával) történik. Először
hozzá kell adni egy új <endpoi nt> elemet kizárólag a MEX miatt. Másodszor
WCF-viselkedést kell definiálni, hogy megengedjük a HTTP GET hozzáfé­
rést. Harmadszor ezt a viselkedést hozzá kell rendelni név alapján a szolgál­
tatáshoz a nyitó <service> elemen a behaviorconfiguration attribútum segít­
ségéveL Végül hozzá kell adni egy <host> elemet a szolgáltatás alapcímének
megadásához (a MEX itt fogja keresni a leírni kívánt típusok helyét).

Megjegyzés Ez a végső lépés megkerülhető, ha paraméterként egy alapcímet képviselő sys­


tem. uri objektumot adunk át a serviceHost konstruktornak.

317
25. fejezet: A WCF

Nézzük meg a következő módosított hoszt *.con fig fájlt, amely egyedi
<behavior> elemet definiál (a neve EightBallMEXBehavior) , amely a <service>
definícióban szereplő behaviorconfiguration attribútum révén kapcsolódik a
szolgáltatáshoz:

<?xml version="l.O" encoding="utf-8" ?>


<configuration>
<system.serviceModel>
<services>
<service name='' MagicEightBallserviceLib.MagicEightBallservice"
behaviorconfiguration = "EightBallserviceMEXBehavior">
<endpoint address =""
binding="basicHttpBinding"
contract="MagicEightBallServiceLib.IEightBall"/>

<!-- A MEX végpont engedélyezése. -->


<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />

<!-- Ezt hozzá kell adni, hogy a MEX tudja a


szolgáltatásunk címét -->
<host>
<baseAddresses>
<add baseAddress
"http://localhost:8080/MagicEightBallservice"/>
</baseAddresses>
</host>
</service>
</services>

<!-- viselkedésdefiníció a MEX-nek -->


<behaviors>
<serviceBehaviors>
<behavior name="EightBallserviceMEXBehavior" >
<ServiceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

Most már újraindítha�uk a szolgáltatást, majd bármelyik böngészőben meg­


tekinthe�ük a metaadatleírást. Ehhez egyszerűen írjuk be URL-ként a címet,
amíg a hoszt fut:

http://localhost:8080/MagicEightBallservice

318
A WCF-szolgáltatás hosztolása

A WCF-szolgáltatásunk kezdőoldalán (lásd a 25_7. ábrát) megkapjuk az alap­

vető részleteket arról, hogyan kommunikálhatunk ezzel a szolgáltatással


programozottan, valamint megtekinthetjük a WSDL-szerződést, ha a lap tete­
jén levő linkre kattintunk A WSDL (Web Service Description Language -
webszolgáltatás-leíró nyelv) olyan nyelvtan, amely leírja egy adott végpon­
ton elhelyezkedő webszolgáltatások szerkezetét_

Forráskód A MagicEightBallServiceHost kódfájlokat a forráskódkönyvtár 25_ fejezetének al­


könyvtára tartalmazza_ A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

� MagicEightBaiiService Service - Windows Intemet Explorer


�• ,....-. . �-.",!
l -

��-[ � MagicEightBaiiService Service ,:JJ • §l •

MagicE ig h tBaIIService Service


You have created a service.

To test this service, you will need to create a dient and use it to call the service. You can do this using the
svcutil.exe tool from the command line with the follawing syntax:
l
l
�l
svcutil.exe http://localbosc:B080/MagicEigbtBallService?wsdl

This will generate a configuration file and a code file that contains the client class. Add the two files to your client !
application and use the generated client class to call the Service. for example:
i
C#

clas:s Test

static void Main()

{
Eiqht3a11Cl1e�t client = new Eighc3al1Client();
u
ll üse the 'client' variable �o call operations o� �he service.

ll A2way3 clo�e the client.


elient. Clo:oe ();

local intranet l Proleeted Mode: Off 91.100% •

25_8_ ábra: MEX-en keresztül megtekinthető metaadatok

319
25. fejezet: A WCF

WCF- ügyfélalkalmazás készítése

Most, hogy a hoszt készen áll, már csak a szaftver egy részét kell elkészíte­
nünk, hogy a WCF-szolgáltatástípussal kommunikáljon. Bár választhatnánk
a szükséges infrastruktúra manuális felépítését (megvalósítható, de munka­
igényes feladat), a .NET Framework 3.5 SDK több módszert kínál ügyfélolda­
li proxy gyors generálására. Először hozzunk létre új parancssori alkalmazást
MagicEightBallServiceClient névvel.

Proxykód generálása az svcutil.exe segitségével

Ügyféloldali proxy készítéséhez az első módszer az svcutil.exe parancssori


eszköz használata. Az svcutil.ex e segítségével a proxykódot és az ügyfélol­
dali konfigurációs fájlt képviselő új C# nyelvű fájlt generálhatunk. Ehhez
csak első paraméterként kell megadnunk a szolgáltatás végpontját. Az /out:
kapcsaló adja meg a proxyt tartalmazó *.cs fájl nevét, a lconfig: opció pedig
a generált ügyféloldali *.config fájl nevét.
Tételezzük fel, hogy fut a szolgáltatásunk. Ekkor a következő, svcutil.exe­
nek átadott utasításkészlet két új fájlt generál a munkakönyvtárban (amelyet
természetesen egy sorban kell megadni a Visual Studio 2008 parancssorában):

svcutil http://localhost:8080/MagicEightBallservice
/out:myProxy.cs /config:app.config

A myProxy.cs fájl megnyitásakor megtaláljuk az IEightBall interfész ügyfél­


oldali megfelelőjét, valamint az új EightBallcl ient osztályt, amely maga a
proxyosztály. Ez az osztály a system.serviceModel.clientBase<T> generikus
osztályból származik, ahol T a regisztrált szolgáltatásinterfész.
Néhány egyedi konstruktoron kívül minden olyan metódust, amelyhez
hozzárendeltük az [operationcontract] attribútumot, úgy implementáltunk,
hogy az delegálja a szülőosztály channels tulajdonságát a megfelelő külső
metódus meghívásához. Nézzünk meg egy kódrészletet a proxytípusból:

[System.Diagnostics.DebuggerstepThroughAttribute()]
[System.CodeDom.compiler.GeneratedcodeAttribute(
"System.serviceModel", "3.0.0.0")]
public partial class EightBallclient :
System.serviceModel.clientBase<IEightBall>, IEightBall
{

320
WCF-ügyfélalkalmazás készítése

public string obtainAnswerToQuestion(string userQuestion)


{
return base_channel_obtainAnswerToQuestion(userQuestion);
}
}

A proxytípus példányának létrehozásakor az ősosztály kapcsolatot létesít a vég­


ponttal az ügyféloldali alkalmazás konfigurációs fájljában található beállítások
alapján_ A kiszolgálóoldali konfigurációs fájlhoz hasonlóan a generált ügyfélol­
dali App_config fájl tartalmaz egy <endpoint> elemet, valamint részleteket a szal­
gáitatással való kommunikációra használt basicHttpBindingot illetően.
Ezenkívül itt találjuk a következő <cli ent> elemet, amely (újra) létrehozza
az ABC-ket a kliens oldaláról:

<client>
<endpoint
address="http://localhost:8080/MagicEightBallservice"
binding="basicHttpBinding"
bindingconfiguration="BasicHttpBinding_IEightBall"
contract="ServiceReference.IEightBall"
name="BasicHttpBinding_IEightBall" />
</client>

Ezt a két fájlt hozzáadhatnánk a kliensprojekthez (referenciával a System_ ser­


viceModel . dll-re), és kommunikálhatnánk a proxytípus segítségével a távoli

WCF-szolgáltatással. Ehelyett nézzük meg, hogyan lehet a Visual Studio még


inkább a segítségünkre ügyféloldali proxyfájlok automatikus létrehozásában.

Proxykód generálása Visual Studio 2008-ban

Megfelelő parancssori eszközként az svcutil _ exe számos lehetőséget kínál


ügyféloldali proxy generálásának meghatározására. Ha nincs szükségünk
ezekre a speciális beállításokra, akkor a Visual Studio 2008 IDE segítségével
generálhaljuk ugyanezt a két fájlt. Válasszuk ki az Add Service Reference
pontot a Project menüből.
Ha kiválasztottuk a menüpontot, be kell írnunk a szolgáltatás URI-jét. Ek­
kor kattintsunk a Go gombra, és megtekintheljük a szolgáltatás leírását (lásd
a 25.9. ábrát).

321
25. fejezet: A WCF

Add Service Reference

T o see a lirt of available services on a specific server, enter a service URL and click Go. To browse for available
services. click Oiscover.

Address:

http://localhost8080/MagicEightBa11Service
• � ll Discover l ·l
Services: Operations-:

8··0 ,� MagicEightBaiiService oO:.ObtainAnswerToQuestion


'-ö" IEightBall

ll service(s) found at .. ddress 'http://localhost8080/MagicEightBaiiServico '.

Namespace;

ServiceRe:fen�nce

[ Advanced... J c..._ oK _
__
_,ll Cancel

25. 9. ábra: Proxyfájlok generálása Visual Studio 2008 segítségével

A proxyfájlok létrehozásán és projekthe illesztésén kívül az eszköz automati­


kusan létrehozza helyettünk a WCF-szerelvények referenciáit. Az elnevezési
konvenciók alapján a proxyosztályt a servi ceReference névtéren belül defi­
niáljuk, amelyet beágyazunk a kliens névterébe (hogy elkerüljük a lehetséges
névütközéseket). A teljes kliensforráskód a következő:

ll Aproxy helye.
using MagicEightBallserviceclient.ServiceReference;

namespace MagicEightBallserviceclient
{
class Program
{
static void Main(string[] args)
{
Console.writeLine("***** Ask the Magic 8 Ball *****\n");
using (EightBallclient ball = new EightBallclient())
{
console.write("Your question: ");
string question = console.ReadLine();
string answer =

ball.obtainAnswerToQuestion(question);

322
WCF-ügyfélalkalmazás készítése

console_writeLine("8-Ball says: {O } ", answer);


}
console_ReadLine();
}
}
}

Feltételezve, hogy a WCF-hoszt fut, futtathatjuk a klienst. A 25.10. ábrán egy


lehetséges választ látunk.

tli C:\Windows\system32\cmd.exe
a

l

25_1 O. ábra: A kész WCF-ügyfélhoszt

Forráskód A MagicEightBallServiceClient kódfájlokat a forráskódkönyvtár 25. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

TCP-alapú kötés konfigurálása

A hoszt- és a kliensalkalmazást úgy állítottuk be, hogy a basicHttpBindingot,

a legegyszerűbb HTTP-alapú kötést használja. A beállítások konfigurációs


fájlokba történő áthelyezésének előnye, hogy deklaratív módon megváltoz­
tathatjuk az alapul szolgáló beállításokat.
Ennek bemutatására nézzünk meg egy rövid kísérletet. Hozzunk létre egy
EightBallTCP könyvtárt a C meghajtón (vagy ahová a kódot mentjük), ebben
a mappában pedig hozzunk létre két alkönyvtárt Host és Client néven.
Utána Windows Intézőben keressük meg a hosztprojekt \bin\Debug
könyvtárát, és másoljuk a MagicEightBallServiceHost _exe, MagicEightBallser­
viceHost.exe.config és a MagicEightBallServiceLib.dll fájlokat a C:\Eight­
BallTCP\Host könyvtárba. Nyissuk meg a *. config fájlt egyszerű szövegszer­
kesztőben, és a következőképpen módosítsuk a tartalmát

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<system.serviceModel>
<services>
<service name="MagicEightBallserviceLib.MagicEightBallservice">
<endpoint address =""

323
25. fejezet:· A WCF

binding="netTcpBinding"
contract="MagicEightBallserviceLib.IEightBall"/>
<host>
<baseAddresses>
<add baseAddress
"net.tcp://localhost:8080/MagicEightBallservice"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

A hoszt *.config fájlja lényegében eltakarította az összes MEX-beállítást (rni­

vel már elkészítettük a proxyt), és meghatározta, hogy a netTcpBinding kötés­


típust használja. Kattintsunk kétszer a * .exe fájlra, és futtassuk az alkalma­
zást. Ha rninden jól sikerült, a 25.11. ábrán látható hosztkimenetet látjuk.

25.11. ábra: TCP-kötéseket alkalmazó WCF-szolgáltatás hasztolása

A teszt befejezéséhez másoljuk a MagicEightBallservieeelient.exe és a Magic­

EightBallservieeelient.exe.config fájlokat a kliensalkalmazás \bin\Debug


mappájából a C:\EightBallTCP\Client mappába. Módosítsuk az ügyfélkonfi­
gurációs fájlt a következőképpen:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<system.serviceModel>
<client>
<endpoint
address="net.tcp://localhost:8080/MagicEightBallservice"
binding="netTcpBinding"
contract="ServiceReference.IEightBall"
name="netTcpBinding_IEightBall" />
</client>
</system.serviceModel>
</configuration>

324
A WCF Service Library projektsablon használata

Ez az ügyféloldali konfigurációs fájl hatalmas egyszerűsítés ahhoz képest,


amelyet a Visual Studio proxygenerátora készített. Teljesen eltávolítottuk a
létező <bindings> elemet. Eredetileg a *.config fájl tartalmazta a <bindings>
elemet a <basi cHttpBinding> részelemmel, amely a kliens kötési beállításaival
kapcsolatos részleteket határozza meg (időtúllépések stb.).
Valójában a példánkban soha nem volt szükség ezekre a beállításokra,
mivel automatikusan megkaptuk az alapul szolgáló BasicHttpBi ndi ng objek­
tum alapértelmezett értékeit. Ha szükség lenne rá, módosíthatnánk a létező
<bindings> elemet, hogy megadjuk a <netTcpBinding> alelem részleteit, de ez
nem szükséges, ha elégedettek vagyunk a NetTcpBinding objektum alapér­
telmezett értékeiveL
Ekkor a kliensalkalmazást már futtathatjuk, és ha a hoszt még mindig fut
a háttérben, már képesek leszünk a TCP segítségével adatokat mozgatni a
szerelvények között.

Forráskód A MagicEightBallTCP kódfájlokat a forráskódkönyvtár 25. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WCF Service Library projektsablon


használata

A 22. fejezetben készített AutoLot adatbázisunkkal kommunikáló még külön­

legesebb WCF-szolgáltatások készítése előtt a következő példa számos fontos


témát mutat be, például a WCF Service Library projektsablont , a WCF Test
Client alkalmazást, a WCF-konfigurációszerkesztőt, WCF-szolgáltatások
Windows-szolgáltatásban történő hasztolásának és az aszinkron ügyfélhívá­
soknak az előnyeit. Ez a WCF-szolgáltatás szándékosan egyszerű, hogy az új
fogalmakra összpontosítsunk.

Egyszerű Matek-szolgáltatás készítése

Először készítsünk egy teljesen új WCF Service Library projektet MathService­


Library névvel, de figyeljünk arra, hogy a New Project párbeszédablak WCF­
csomópontjánál a megfelelő opciót válasszuk (segítségül lásd a 25.2. ábrát).
Változtassuk meg az IServicel.cs fájl eredeti nevét I BasicMath.cs-re.

325
25. fejezet: A WCF

Ha készen vagyunk, töröljük az összes példakódot a MathserviceLibrary


névtérből, és helyette illesszük be a következő kódot:

namespace MathserviceLibrary
{
[Servicecontract(Namespace="www.Intertech.com")]
public interface IBasicMath
{
[Operationcontract]
int Add(int x, int y);
}
}

Módosítsuk a Servicel.cs fájl nevét Math service. cs-re, majd (újra) töröljük ki
az összes példakódot a MathserviceL i brary névtérből, és a következőképpen
implementáljuk a szolgáltatássszerződésünket:

namespace MathserviceLibrary
{
public class Mathservice : IBasicMath
{
public int Add(int x, int y)
{
ll Hogy hosszú kérést szimuláljunk.
System.Threading.Thread.Sleep(5000);
return x + y;
}
}
}

Végül nyissuk meg az App.config fájlt, és cseréljük ki az r servicel összes elő­


fordulását !BasicMathre, és a servicel összes előfordulását Mathservice-re .
Ez a *.config fájl már engedélyezi a MEX-támogatását, és az alapértelmezés
szerint a wsHttpBinding protokollt használja.

A WCF -szolgáltatás tesztelése


a WcfTestClient.exe-vel

A WCF Service Library projekt használatának egyik előnye, hogy hibakeresés­


kor vagy futtatáskor kiolvassa a beállításokat a *.config fájlból, és ezekkel tölti
be a WCF Test Client alkalmazást (wcfTestclient.exe). Ezzel a GUl-alapú al­
kalmazással a WCF-szolgáltatás készítése közben tesztelhetjük a szolgáltatásin­
terfész minden tagját, ahelyett, hogy manuálisan kellene hasztot klienst készí­
teni csak a tesztelés kedvéért.

326
A WCF Service Library projektsablon használata

A 25.12. ábra mutatja a Mathservi ce tesztkömyezetét. Ha duplán kattin­


tunk egy interfészmetódusra, megadhatunk bemenő paramétereket, és meg­
hívhatjuk a tagot.
Ez az eszköz működésre készen áll, amikor elkészül a WCF Service Library
projekt, de tudnunk kell, hogy tesztelhetünk vele bármilyen WCF-szolgálta­
tást, ha parancssorból indítjuk MEX-végpont megadásával. Ha például sze­
retnénk elindítani aMagicEi ghtBall serviceHost. exe alkalmazást, akkor a Vi­
sual Studio 2008 parancssorából a következő parancsot adhatnánk ki:

wcftestclient http://localhost:8080/MagicEightBallservice

� WCFTest Client = l [§·lii3ioj


Fil� Help

8 �MySeMco�. =l�
--
8··� rttp :/Aoc.t>Ost :8080/Mah$eMco/mex
,.. ·-·
-- -· 1

8 S' !Basic� Request


'·991 N"""' Va/ue Type
·clo CO<fog Ae
. --x
l 22 System.lrt32
'··· System.lrt32
y

Response �
Nome Va!ue Type
l. fettm) 26 System.lrt32

Service invocation compl�ed.


Foonatted
""-=�"""-�=
[xt.iil "·'���-�=��=- - ,J
25.12. ábra: A WCF-szolgáltatás tesztelése WcfTestClient.exe alkalmazással

Hasonló módon hívhatjuk meg az obtainAnswerToQuestion() metódust.

A konfigurációs fájl módositása


az SvcConfigEditor.exe programmal

A WCF Service Library projekt másik előnye, hogy a Solution Explorerben az

Ap p. config fájira jobb gombbal kattintva aktiválhatjuk a GUl-alapú Service


Configuration Editort, az svcconfigEditor. exe alkalmazást (lásd a 25.13. áb­
rát). Ugyanezt a technikát használhatjuk a WCF-szolgáltatásra hivatkozó kli­
ensalkalmazásból is.

327
25. fejezet: A WCF
T"
Solution Explorer @l
����ri!I.G '-�<--
�--
l
� MathServicelibrary l
ffi. � Properties
i
ffi-·· 3I R�ferences
' . íj} . .. . .
l
. . � 'iB.:M.':h [j' l Open

'!,) MathServi l
il
·· OpenWith ...
- -_J._________ -- -· . . -

ü'4: EditWCF Configuration

l{
�l
l Exdude From Project

Cut

ll
l
- l
l
� Copy

l XI
Delete

Rename

l
�i Properties

� Solution Explorer �C lass View l

25.13. ábra: A GUl-alapú *.config fájl szerkesztése itt kezdődik

j� c\my books\c# and the .net platform 4th od\code\chapter �thservice\mathservi<elibrary\app:config - Micros... =+sliiii3iill
File Help

Configuration Servlee Endpolpt


B·D Servíc"'
8-•.v MathSeMceübrary.MolhServíce Gener.li Jlderüy l HeadeB \ -
; i··O Host El (ConfigLr.!lion)
Name
áD�s
:...=fl (En"(lly Name) B Endpoint Prq><rlies
:...r:g (En"(lly Name) Address
BehaviOJConfiouration
áDCiert
:D�• ,. wsltipllrdng G:
8 D Diagnostics BindíngCoofiguration

·· -Cl Message loggi1g SindingName


Sinding Namespace
-D u.teneB
Contract -hServiceUbrary.IBasic-h
'-·D Souces
Li s tenUr i
á D M�anc:ed
- D Endpon Behavio<s ListenUrlMode Explicit
ffi- D Service Behavio<s
:lJ-D Eictensions
s D HostEnvronmert

Sincing
ló... ..
Delete Endpoint
·""" ..":.. ��
,.
l Wllnt to
This setting lets you choose the type of the hinding you use for this endpoint
You can use one of the system-denned h'CF bindi119 types. or register your <MTI in the
<bindingExtensioos> section.
ll
l
Create � Ne·,o,· Service...

Create a New Client...

l J
25.14. ábra: A WCF Service Configuration Editor használata

328
WCF-szolgáltatás hasztolása Windows-szolgáltatásként

Az eszköz elindítása után felhasználóbarát környezetben megváltoztatha�uk


az XML-alapú adatokat. A *.con fi g fájlok karbantartására nyilvánvalóan
több szempontból előnyös az ilyen eszköz használata. Először is, biztosak le­
hetünk abban, hogy a generált címkézés megfelel az elvárt formátumnak, és
gépelési hibáktól mentes. Másodszor, kiváló módja annak, hogy adott attri­
bútumokhoz rendelhető érvényes értékeket lássunk. Végül, de nem utolsó­
sorban, nincs szükség XML-adatok manuális megírására.
A 25.14. ábra muta�a a Service Configuration Editor általános megjelené­
sét. Valójában hosszasan lehetne az svccon fi gEdi tor. exe által támogatott ér­
dekes lehetőségeket vizsgálni (COM+-integrálás, új *.confi g fájlok létrehozása
stb.). Érdemes időt szánni az eszköz tanulmányozására. Az Fl lenyomásával
részletes súgórendszer áll a rendelkezésünkre.

Megjegyzés Az svcconfi gEdi tor. exe eszközzel akkor is szerkeszthetünk (vagy létrehozha­
tunk) konfigurációs fájlokat, ha nem választunk kezdeti WCF Service Library projektet. A Visual
Studio 2008 parancssorából indítsuk el az eszközt, és a File>Open menüponttal töltsünk be szer·
kesztésre egy létező *.con fi g fájlt.

Mivel a WCF Mathservi c e-t nem kell tovább konfigurálni, továbbléphetünk


az egyedi hoszt építésére.

WCF -szolgáltatás h osztolása Windows­


szolgáltatásként

A WCF-szolgáltatás hosztolása a parancssori alkalmazásból (vagy GUI aszta­


li alkalmazásból) termékszintű kiszolgálóhoz nem ideális megoldás, mivel a
hosztnak láthatóan futnia kell a háttérben, hogy kiszolgálja a kilenseket. Ha
letesszük a hosztoló alkalmazást a tálcára, így is túl egyszerű lenne véletlenül
leállítani a hosztot és megszakítani a kapcsolatot a kliensalkalmazásokkal.

Megjegyzés Bár igaz, hogy az asztali Windows-alkalmazásnak nem kell feltétlenül mutatnia
a főablakot, egy tipikus *.exe-nek szüksége van felhasználói beavatkozásra a végrehajtható
fájl betöltéséhez. Egy Windows-szolgáltatás (lásd a következő részben) úgy is konfigurálható,
hogy akkor is fusson, ha pillanatnyilag egy felhasználó sem jelentkezett be a munkaállomásra.

329
25. fejezet: A WCF

Házon belüli WCF-alkalmazás készítése során másik alternatívaként a WCF­


szolgáltatáskönyvtárt a kijelölt Windows-szolgáltatásból is hasztolhatjuk En­
nek egyik előnye az, hogy a Windows-szolgáltatás úgy is konfigurálható, hogy
a célzott szánútógép indulásakor automatikusan induljon el. Másik előny az,
hogy a Windows-szolgáltatás láthatatlanul fut a háttérben (a parancssori al­
kalmazással ellentétben), és nincs szüksége felhasználói beavatkozásra.
Hogy bemutassuk, hogyan kell ilyen hosztot készíteni, először létreho­
zunk egy új Windows-szolgáltatásprojektet MathWindowsServiceHost név­
vel (lásd a 25.15. ábrát). Ha kész van, a Solution Explorerben nevezzük át a
kezdeti servicel. cs fájlt Mathwi nservice. cs-re.

Project types: Tcmplates: 1.NET Framework 35 •�!§ID G


Visual Basic Visual Studio instalied tempiates
Visual(# �
Windows
Web
@] r.7j
�---- IV..J
Windows Classlibrary WPF WPF BrOW"ser Console Empty
Database FormsAp... Application Application Application Project
Test
WCF
Werkflow
··\�r � �
Windows WPF Custom WPFUser Windows Reports
Visual C++ Service.. � Controllib... Control... Forms ... Application
Other Project Typ es
My Ternpiates
Test Projects
--�-----�------------·-

Search
Online Te...

A project for creating Windows �rvices (.NET Framework 35)

Name: MathWindowsServict:Hort

location: C:\My Books\C# and the .NET Platform 4th ed\Codc\Chapter 26\MathService • l Browse...

0 Add to Source Control

OK ll Cancel

25.15. ábra: Windows-szaigáitatás készítése a WCF-szolgáltatás hasztolására

Az ABC-k megadása a forráskódban

Ha beállítottunk referenciát a MathserviceLi brary. dll és a system. servi ce­


Madel. dll szerelvényre, elég csak a serviceHost típust használni a Windows­
szolgáltatástípus onstart() és onstop() metódusaiban. Nyissuk meg a szal­
gáltatás hosztosztályának kódfájlját Gobb gombbal kattintsunk a tervezőre, és
válasszuk a View Code opciót), és adjuk hozzá a következő kódot:

330
WCF-szolgáltatás hasztolása Windows-szolgáltatásként

ll Mindenképpen importáljuk a következő névtereket:


using MathserviceLibrary;
using System.ServiceModel;

public partial class Mathwinservice: serviceBase


{
ll serviceHost típusú tagváltozó.
private ServiceHost myHost;

public Mathwinservice()
{
Initializecomponent();
}

protected override void onstart(string[] args)


{
ll A biztonság kedvéért.
if (myHost != null)
{
myHost. close();
myHost = null;
}

ll Hoszt létrehozása.
myHost = new serviceHost(typeof(Mathservice));

ll ABC-k a kódban!
uri address =
new uri("http:/llocalhost:8080/MathserviceLibrary");
WSHttpBinding binding = new WSHttpBinding();
Type contract = typeof(IBasicMath);

ll Végpont hozzáadása.
myHost.AddserviceEndpoint(contract, binding, address);

ll Hoszt megnyitása.
myHost.open();
}

protected override void onstop()


{
ll Hoszt leállítása.
if(myHost != null)
myHost. Close();
}
}

Bár semmi nem gátol meg abban, hogy konfigurációs fájlt használjunk Win­
dows szolgáltatási hoszt készítésekor a WCF-szolgáltatáshoz, a *.config fájl
helyett programozottan hozzuk létre a végpontot az uri, WSHttpBinding és
Type osztályok segítségéveL Ha készen van az ABC minden összetevője, a

hasztot programozottan tájékozta�uk az AddserviceEndpoint() hívásával.

331
25. fejezet: A WCF

A MEX engedélyezése

Bár a MEX-et programkódból is engedélyezhetnénk, inkább a konfigurációs


fájlt választjuk Adjunk új App. config fájlt a a Windows-szolgáltatásprojekt­
hez, amely a következő MEX-beállításokat tartalmazza:

<?xml version="l.O" encoding="utf-8" 7>


<configuration>
<system.serviceModel>
<services>
<service name="MathServiceLibrary.Mathservice"
behaviorconfiguration = "MathServiceMEXBehavior">

<!-- A MEX végpont engedélyezése. -->


<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />

<!-- Ezt hozzá kell adni, hogy a MEX tudja a


szolgáltatásunk címét -->
<host>
<baseAddresses>
<add baseAddress ="http://localhost:8080/Mathservice"/>
</baseAddresses>
</host>
</service>
</services>

<!-- viselkedésdefiníció a MEX-nek -->


<behaviors>
<serviceBehaviors>
<behavior name="MathserviceMEXBehavior" >
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviars>
</behaviors>
</system.serviceModel>
</configuration>

Windows-szolgáltatástelepitő létrehozása

Ahhoz, hogy a Windows-szolgáltatást regisztráljuk az operációs rendszer­


ben, a projekthez kell adnunk egy telepítőt, amely tartalmazill fogja a szük­
séges forráskódot a szolgáltatás regisztrálásához. Ehhez csak kattintsunk
jobb gombbal a Windows szolgáltatástervező felületén, és válasszuk az Add
Installer lehetőséget (lásd a 25.16. ábrát).

332
WCF-szolgáltatás hasztolása Windows-szolgáltatásként

Moi!lJI'or&V;.;O.�-:;gy-Mot(lw�g)'-M.thWonSetvkr.cs(DesignJL �x
--,1
ll
ll
l
l,
1
,---,---
fi] ! View Code
. 1---
,,

ii
. ��Up--
1 � \1 �Lon• ---
llii To add components to your class, drag !hem fr( kons
li
he Properties window to set their properties_ To ·l
enj ! Show�ge�� re to switch to code view.
1,l'
create methods and ev
q ! ---Addlnrtaller

il
l! ll -BI' ;;;�;-;;;�----�
il ll
ll l _ji l

25.16. ábra: Telepítő hozzáadása a Windows-szolgáltatáshoz

Ha készen vagyunk, látni fogjuk, hogy két új komponenst adtunk az új ter­


vezőfelülethez. Az első komponens (a neve az alapértelmezés szerint service­
Processlnstallerl) képviseli azt a típust, amely telepíti az új Windows-szol­
gáHatást a célszárnítógépen. Válasszuk ki ezt a típust a tervezőben, és a Pro­
perties ablakban állítsuk az Acco unt tulajdonság értékét Local systemre.
A második komponens (a neve servicelnstallerl) azt a típust képviseli,
amely telepíti a Windows-szolgáltatásunkat. A Properties ablakban újra változ­
tassuk a ServiceName tulajdonságot Mathorderservice-re (ahogy már kitalálhat­
tuk, ezt a barátságos megjelenített nevet látha�uk a regisztrált Windows­
szolgáltatás neveként), a startType tulajdonságot állítsuk Autamatic értékre, és
adjunk felhasználóbarát leírást a Windows-szolgáltatásról a Descripti on tulaj­
donságban. Most lefordítha�uk az alkalmazást.

A Windows-szctgáltatás telepítése

A Windows-szolgáltatást a hosztgépre a hagyományos telepítőprogrammal


(pl. *.msi telepítővel) vagy az installutil.exe parancssori eszközzel telepít­
he�ük. A Visual Studio 2008 parancssorával lépjünk a MathWindowsService­
Host projekt \ bin\Debug könyvtárába. Írjuk be a következő parancsot:

installutil MathwindowsserviceHost.exe

Ha a telepítés sikeres volt, megnyitha�uk a Szolgáltatások appletet a Vezér­


lőpult Felügyeleti eszközök mappájában. Itt ábécésorrendbe rendezett listá­
ban látnunk kell a Windows-szolgáltatásunk barátságos nevét. Ha megtalál­
tuk, elindítha�uk a szolgáltatást a helyi gépen (lásd a 25.17. ábrát).
Ha a szolgáltatás működik, az utolsó lépés a kliensalkalmazás elkészítése,
amely használha�a a szolgáltatásokat.

333
25. fejezet: A WCF

.
Math Order Service Name D�cription Status Startup Type log On As

/�IPs ee Policy Agent Internet Pro.. . Started Automatic NetworkS...

?!QQ the service ;-' KtmRm for Oistrib... Coordinates... Started Automatic (D... NetworkS ...

Restart the service :�Unk-layerTopolo... Createsa N... Manual Local Service Q

ClM.rth Order Strw:e Thas IS the h... Started Autom.1bc local Syste.
(Ó6 Message Queuing Providesa ... Started Automatic NetworkS...

Description: :6 Message Queuing... Provides rul... Started Automatic NetworkS...

This is the host for the -:·� Microsoft .NET Fr... Microsoft .... Manual local Syste...

MathService :(-� Microsoft iSCSI Ini... ManagesIn... Manual local Syste...


:� Microsoft Office O... Run portion... Manual local Spe...
��.6-Microsoft Office G... Manual local Service .,.

25.17. ábra: A WCF-szolgáltatásunkat hasztoló Windows-szalgá/tatás megtekintése

Forráskód A MathWindowsServiceHost kódfájlokat a forráskódkönyvtár 25. fejezetének al­


A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.
könyvtára tartalmazza.

Szolgáltatás aszinkron hivása

Készítsünk új parancssori alkalmazást MathClient névvel, és a Visual Studio


Add Service Reference opciójával állítsuk a Service Reference értékét a futó
WCF-szolgáltatásra, amelyet a háttérben futó Windows-szolgáltatás hosztol.
De még ne kattintsunk az OK gombra (lásd a 25.18. ábrát)!
Az Add ServiceReference párbeszédablak bal alsó sarkában található egy
Advanced gomb. Kattintsunk a gombra, és további proxykonfigurációs beál­
lításokat tekinthetünk meg (lásd a 25.19. ábrát).
A párbeszédablakban a Generate Asynchronous Operators jelölőnégyzet
bekapcsalásával választha�uk olyan kód generálását, amellyel aszinkron
módon hívhatunk távoli metódusokat. Egyelőre jelöljük be ezt az opciót.
A párbeszédablakban másik fontos lehetőség az Add Web Reference
gomb. Ha már rendelkezünk némi háttértudással az XML-szolgáltatások ké­
szítéséről a Visual Studio 2005-ös vagy korábbi verziójában, tudjuk, hogy az

Add ServiceReference lehetőség helyett Add WebReference állt a rendelke­


zésünkre. Ha erre a gombra kattintunk, olyan proxykódot kapunk, amellyel
*. asmx fájlban leírt hagyományos webszolgáltatással tudunk kommunikálni.

334
Szolgáltatás aszinkron hívása

Add Service Reference 'il

To see a list of available seMe5 on a specitic se.rver, enter a service URL and cikk Go. To browse for available
services, click Oiscover.

Addres�

hit · llocalhost:8080!MathService T � ll Discovor H


Services: Operations:

8 0� MathS.rvice •<O Add


L-...� JBasicMath

l service(s) found at address 'http://localhost8080/MathS.rvice'.

Names�
pa __
c e_______________ __ _
ServiceReference

[.������;::1 l OK JI Cancel
25. 18. ábra: Referencia beállítása az ourMathService-re

ServiceReference Settings L'iliii:iiil

Client -------------------------- --- ===========


Access level for generated dasses:: [Public T]
� Generate asynchronous operation �
Data Type

O Atways generate message contraels

Collection type: [SystemArray T]


Dictionary collection type; [Systorn.Collections.Generic.Dictiona<y T]
� Reuse types in refBenced assemblies
@ Reuse types in ali r�erencHI assemblies
0 Reuse types in specitied referenced assemblies:

0 .. Q mscorlib
o .a system
[J·OSystem.Core
D ·OSystem.Data
D •O System.Data.Datas.tExtensions
O •OSystem.Xml
[J·ClSystem.Xmi.Linq

Compatibility

Add a WebReference instead of a ServiceReference. This will g�e.rate code bas� on .NET Frame:work 2.0
Web Services technology.

Add WebRefe<ence...

�� Cancel

25.19. ábra: Speciá/is ügtjféloldali proxykonfigurációs beállítások

335
25. fejezet: A WCF

A párbeszédablak többi opciója az adatszerződések generálásának beállítása­


ira szolgál, amelyeket a fejezet későbbi részében még megvizsgálunk Feltét­
lenül kapcsoljuk be a Generate Asynchronous Operators jelölőnégyzetet, és
kattintsunk az OK gombra minden párbeszédablakban, hogy visszatérjünk a
Visual Studio IDE-hez.
A proxykód olyan további metódusokat tartalmaz, amelyekkel az elvárt
Begin/End aszinkron meghívási móddal hívhatjuk a szolgáltatásszerződés
minden tagját (lásd az előző kötet 18. fejezetében). Az alábbiakban egy egy­
szerű implementációt találunk, amely az erősen típusos Asynccall ba ck metó­
dusreferencia helyett lam b da kifejezést használ.

using System;
using Mathclient.ServiceReference;
using system.Threading;

namespace Mathclient
{
class Program
{
static void Main(string[] args)
{
console. Writeline("***'"� The Async Math client *;«'*'''\n");

using (BasicMathclient proxy = new BasicMathclient())


{
pr oxy. Open();

ll számok hozzáadása aszinkron módon lambda kifejezés


ll segítségével.
IAsyncResult result = proxy.BeginAdd(2, 3,
ar =>

{
console.writeLine("2 + 5 {O}", proxy.EndAdd(ar));
}, null);

while (!result.rscompleted)
{
Thread.sleep(200);
Console.writeLine("client work ing ...");
}
}
Console.ReadLine();
}
}
}

Forráskód A MathClient kódfáj lokat a forráskódkönyvtár 25. fejezetének alkönyvtára tartal·


mazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

336
WCF-adatszerződések tervezése

WCF -adatszerződések tervezése

A fejezet utolsó példájában WCF-adatszerződéseket készítünk. Az előző WCF­


szolgáltatások nagyon egyszerű rnetódusokat definiáltak primitív CLR-adattí­
pusokkal történő rnűveletekhez. Ha bármilyen webszolgáltatás-központú kö­
téstípust használunk (basi cHttpBi ndi ng, wsHttpBi ndi n g stb.), a bejövő és kirne­
nő adattípusokat a rendszer automatikusan XML-elernekké forrnázza a system.
Runti me.seri a l i zati on.xml Formatter típus segítségéve!, amelyet a system. Run­

time.serialization.dll szerelvény definiál. Ugyancsak ide tartozik, hogy ha

TCP-alapú kötést használunk (rnint a netTcpBi ndi ng), az egyszerű adattípusok


paraméterei és visszatérési értékei kompakt bináris formáturnot kapnak.

Megjegyzés A WCF futtatókörnyezet szintén automatikusan kódol bármilyen, [seri a l i zab l e]


attribútummal jelölt típust.

Ha paraméterként vagy visszatérési értékként egyedi típusokat használó


szolgáltatásszerződéseket definiálunk, ezeket a típusokat nekünk kell adat­
szerződéssel rneghatároznunk. Tulajdonképpen az adatszerződés [Datacont­
ract] attribútummal ellátott típus. Minden rnezőt, amelyet az ajánlott szer­

ződésben várhatóan használni fogunk, hasonló rnódon a [DataMember] attri­


bútummal jelöljük.

Megjegyzés Ha az adatszerződés [DataMember] attribútum nélküli mezőket is tartalmaz,


ezeket a WCF-futtatókörnyezet nem sorosítja.

Az adatszerződések készítésének bemutatására hozzunk létre teljesen új WCF­


szolgáltatást, amely együttműködik a 22. fejezetben készített AutoLot adatbá­
zissal. Ez az utolsó WCF-szolgáltatás a webalapú WCF Service sablonnal ké­
szül. Az ilyen típusú WCF-szolgáltatás automatikusan az IlS-beli virtuális
könyvtárba kerüJ, és a hagyományos .NET XML-webszolgáltatásához hason­
lóan rnűködik. Ha megértjük ezen WCF-szolgáltatás felépítését, nem jelent
problérnát létező WCF-szolgáltatás áthelyezése új IlS-beli virtuális könyvtárba.

Megjegyzés Ez a példa feltételezi, hogy valamennyire tisztában vagyunk az liS-beli virtuális


könyvtárak (és maga az liS) szerkezetével. A 31. fejezetben megtaláljuk a részleteket.

337
25. fejezet: A WCF

Webközpontú WCF Service projektsablon


használata
Készítsünk új WCF-szolgáltatást AutoLotWCFService néven a File > New >
Web Site menüponttal, amely a következő URI-ről érhető el: http:/ /local­
host/AutoLotWCFService (lásd a 25.20. ábrát). A Location legördülő listában
feltétlenül a HITP-értéket válasszuk.

NewWebSite

Te:mplates: I.NETFram.work35 •j{g] G


Visual Studio installed t�plates

,3 '� c� ,it �
ASP.NET ASP.NET EmptyWeb WCFService ASP.NET
Web Sit!! Web Service: Site: Re:portsW...

MyTemplates------.----�----·--· --- ··

Search
Online Te
...

A WW site for creating WCF services (.NET Frame.work.3.5)

location:
lHTTP •l http://localhost/AutolotWCF�rvice: • l Browse.. l
language: �V� os=ua=I C#=== ====�. �
OK ll Cancel l

25.20. ábra: Web-központú WCF-szolgáltatás készítése

Ha ezzel készen vagyunk, állítsunk be referenciát a 22. fejezetben készített Auto­


LotDAL.dll szerelvényre (a Website >Add Reference menüponttal). A WCF Ser­
vice Library projekthez hasonlóan rendelkezünk . kezdeti példakóddal (az
App_Code könyvtárban található), amelyet tanácsos törölni. Először nevezzük át
az eredeti !Service.cs fájlt IAutoLotservice.cs-re és definiáljuk a kezdeti szol­ ,

gáltatásszerződést az átnevezett fájlban:

[Servicecontract]
public interface IAutoLotService
{
[Operationcontract]
void InsertCar(int id, string make, string color, string petname);

[Operationcontract]
void Insertcar(InventoryRecord car);

338
WCF-adatszerződések tervezése

[Operationcontract]
InventoryRecord[] Getinventory();
}

Ez az interfész három metódust definiál, amelyek közül az egyik Inventory­


Record típusú tömböt ad vissza (ezt még létre kell hozni). Az InventoryDAL
GetrnventoryO metódusa korábban egyszerűen egy oataTable objektumot
adott vissza, így feltehetjük a kérdést, hogy a szolgáltatásunk GetrnventoryO
metódusa Iniért nem ezt teszi.
Míg a WCF-szolgáltatásmetódusunk megtehetné, hogy oataTable típust
ad vissza, a WCF célja, hogy figyelembe vegye a SOA-elveket, amelyek egyi­
ke az, hogy szerződésekkel és nem implementációkkal programozunk. Ezért
ahelyett, hogy a .NET-specifikus oataTable típust adnánk a külső hívónak, az
egyedi adatszerződést ( InventoryRecord) adjuk vissza, amelyet a rendszer a
csatolt WSDL-dokumentumban agnosztikus módon kifejt.
Az interfész az InsertcarO túlterhelt metódust definiálja. Az első verzió
négy bejövő paramétert fogad, a második verzió pedig bemenetként Inven­
toryRecord típust. Az adatszerződést a következőképpen definiálhatjuk

[Datacontract]
public class InventoryRecord
{
[DataMember]
public int ro;
[DataMember]
public string Make;
[DataMember J
public string color;
[DataMember]
public string PetName;
}

Ha így szeretnénk implementálni az interfészt, akkor készítsünk egy hosztot,


próbáljuk meghívni ezeket a metódusokat a kliensből, és azt fogjuk tapasz�
talni, hogy futásidejű kivétel jelentkezik. Ennek az oka az, hogy a WSDL­
leírás egyik követelménye, hogy minden, egy adott végpontról elérhető me­
tódus egyedi névvel kell, hogy rendelkezzen. Így, míg a metódusfelültöltés a
C#-ban remekül működik, az aktuális webszolgáltatási specifikációk nem
engedik meg két azonos nevű InsertcarO metódus használatát
Szerencsére az [Operationcontract] attribútum támogat egy megnevezett
tulajdonságot (Name) , amellyel meghatározhatjuk a C#-metódus megjeleníté­
sének módját a WSDL-leírásban. Emiatt módosítsuk az InsertcarO második
verzióját:

339
25. fejezet: A WCF

public interface lAutoLatservice


{

[Operationcontract(Name= "InsertcarwithDetails")]
void Insertcar(InventoryRecord car);
}

Szolgáltatásszerződés implementálása

Az AutaLotservice típus a következő interfészt implementálja (mindenkép­


pen importáljuk az AutaLotconnectedLayer és a system.Data névteret a kód­
fájlban):

using AutoLotconnectedLayer;
using system.Data;

public class AutaLotservice lAutoLatservice


{
private const string connstring=
@"Data source=(local)\SQLEXPRESS;Initial catalog=AutoLot"+
";Integrated security
=True";
public void Insertcar(int id, string make, string color,
string petname)
{
InventoryDAL d= new InventoryDAL();
d.openconnection(connstring);
d.InsertAuto(id, color, make, petname);
d.closeconnection();
}

public void Insertcar(InventoryRecord car)


{
InventoryDAL d = new InventoryDAL();
d.Openconnection(connstring);
d.InsertAuto(car.ID, car.Color, car.Make, car.PetName);
d.closeconnection();
}

public InventoryRecord[] Getrnventory()


{
ll Először olvassuk be a DataTable objektumot az adatbázisból.
InventoryDAL d= new InventoryDAL();
d.Openconnection(connstring);
DataTable dt= d.GetAllrnventory();
d.Closeconnection();

ll A rekordokhoz készítünk List<T> listát.


List<InventoryRecord> records= new List<InventoryRecord>();

340
WCF-adatszerződések tervezése

ll Az adattáblát másoljuk az egyedi szerződéseket tartalmazó


ll List<> listába.
DataTableReader reader = dt.createDataReader();
while (reader.Read())
{
InventoryRecord r = new InventoryRecord();
r.ID = (int)reader["cariD"];
r.Color = ((string)reader["Color"]).Trim();
r.Make = ((string)reader["Make"]).Trim();
r.PetName = ((string)reader["PetName"]).Trim();
records.Add(r);
}

ll Átalakítjuk a List<T> listát InventoryRecord típusú tömbbé.


return (InventoryRecord[])records.ToArray();
}
}

Az egyszerűség kedvéért a kapcsolatsztring értékét ahelyett, hogy a web.


config fájlban tárolnánk, kódban adjuk meg (amelyet a gép beállításainak

megfelelően lehet, hogy módosítani kell). Mivel az adatelérési könyvtár végzi


a kommunikációt az AutoLot adatbázissal, a mi dolguk csak annyi, hogy a
bejövő paramétereket átadjuk az InventoryDAL osztálytípus InsertAuto() me­
tódusának. Másik érdekesség a DataTable objektum értékeinek leképezése
InventoryRecord típusok általános listájába (DataTab l eReader használatával),

utána pedig a List<T> lista átalakítása InventoryRecord típusú tömbbé.

*
A .svc fájl szerepe

Webközpontú WCF-szolgáltatás készítése során a projekt egy adott *.svc ki­


terjesztésű fájlt tartalmaz. Erre a fájira minden IIS-hosztolt WCF-szolgáltatás­
nak szüksége van, ez írja le a szolgáltatás implementációjának nevét és helyét
a telepítési ponton belül. Mivel megváltoztattuk az eredeti fájlok és WCF-tí­
pusok nevét, most módosítanunk kell a service. svc fájl tartalmát is:

<%@ ServiceHost Language="(#" Debug="true"


Service="AutoLotService"
codeBehind="-IApp_codeiAutoLotService.cs" %>

341
25. fejezet: A WCF

A Web.config fájl módositása

A szolgáltatás tesztelése előtt az utolsó feladatunk a web.con fig fájl <system.


serviceModel> részének módosítása. A web.con fig fájl szerepe hasonlít a vég­

rehajtható fájl *.config fájl szerepéhez, de az előbbi számos webspecifikus


beállítást is vezérel (lásd később részletesen az ASP.NET-ről szóló részben).
A példában nem kell mást tennünk, mint a következőképpen módosítani a
WCF-specifikus részt a fájlban:

<system.serviceModel>
<services>
<service name="AutoLotService"
behaviorconfiguration="ServiceBehavior">
<endpoint address="" binding="wsHttpBinding"
contract="IAutoLotservice"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailinFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

A szolgáltatás tesztelése

Most már bármilyen klienst készíthetünk a szolgáltatás tesztelésére, beleértve


az *.svc fájl végponljának átadását a wcfTestclient.exe alkalmazásnak:

wcfTestclient http://localhost/AutoLotWCFService/Service.svc

Nézzük meg a 25.21. ábrát, amely a Geunventory() hívásának eredményét


mutalja.

Forráskód Az AutalotService kódfájlokat a forráskódkönyvtár 25. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

342
Összefoglalás

45I WCFTest Clóeot ':lll." � f-'�111.�� �!ijpjjjil

8
Fil� Help
i!l My SoMce Project• � c..wt��=�t.Det.isc�--
�--;:: l Getlnv...ooy l

=ll
�-
2�;;,o==olaiWCFs.Mce
.. .,...� l
.._ n..rtCaoWihDetaioO N""" v.-", T",.

.. �
t} Cori;gfie

Reoponoe �
N""" v.-", T",.
8 ...'-"1) lef9h•13 kw...ooyRe<:oolO
E!IOJ

Ü"
. lnvertoryRecord
Color "Pn<"' SyotemSimg

u"
Syotem.kt32
·Make "BMW" SyotemSimg
PetName "Sidd" SyotemSimg
a 111 kwmooyRe<:ool
-- Color "Red" SyotemSimg
:..•o S,.tem.kt32
--· 'VW" SyotemSimg
· PetName "2lppy "
Syotem.Simg
B !21 kw...ooyRe<:ool
.. "Eladt"
.

Color
·
Syotem.Simg
·ID Syotem.kt32
·
-·Make "Ford" SyotemSimg
PetName "Mel" Syotem.Simg
s 131 lnv...ooyRe<:ool �
- Color "Siver" SyotemStmg
--------
.�
l (i. lll
' JlL=�r�Il --=
=
· "--- ----- ·-·
�- �
- �� --���-�-
· ---
JI
S.rvice invocatioo completed.

25.21. ábra: Web-központú WCF-szolgáltatás készítése

Egyedi kliensalkalmazás készítése során használjuk az Add Service Refence


párbeszédablakot, ahogyan ezt a fejezet korábbi részében a MagicEightBall­
ServiceClient és a MathClient példaprojektekben tettük.

Összefogla l ás

A fejezet bevezetett a Windows Communication Foundation (WCF) API haszná­


latába, amely a .NET 3.0 verziója óta az alaposztálykönyvtárak része. A WCF fő
célja, hogy egységes objektummodellt kínáljon számos (korábban egymással
nem kapcsolódó) elosztott API összefogására. Továbbá a fejezet elején láttuk,
hogy a WCF-szolgáltatást a megadott címek, kötések és szerződések képviselik
(könnyen megjegyezhető az ABC [address, binding, contract] rövidítésből).
Megnéztük, hogy egy tipikus WCF-alkalmazás három kapcsolódó szerel­
vényt h asznál. Az első definiálja a szolgáltatás funkcionalitását képviselő
szolgáltatásszerződéseket és a szolgáltatástípusokat. Ezt a szerelvényt azután
egyedi végrehajtható fájl, IlS-beli virtuális könyvtár vagy Windows-szolgál-

343
25. fejezet: A WCF

tatás hosztolja. Végül a kliensszerelvény generált kód segítségével definiálja


a proxytípust (és az alkalmazás konfigurációs fájljában a beállításokat), hogy
a távoli típussal kommunikáljon.
A fejezetben szintén megvizsgáltunk több WCF programozási eszközt,
mint például az svccon fi gEdi tor.exe (amellyel a *.con fi g fájlokat módosít­
ha�uk) és a wcfTestcl i ent.exe alkalmazást (WCF-szolgáltatás gyors tesztelé­
sére), valamint különböző Visual Studio 2008 projektsablonokat tanulmá­
nyoztunk.

344
HUSZONHATODIK FEJEZET

A Windows Workflow
Foundation - Bevezetés

A .NET 3.0 egy különleges programozási keretrendszert tartalmaz, a Windows


Workflow Foundationt (WF). Ez az API lehetővé teszi számunkra, hogy kü­
lönböző alkalmazásokban használt munkafolyamatokat (workjlow), amelyeket
- egyelőre - egyszerűen kapcsolódó feladatok gyűjteményének tekintünk, mo­
dellezzünk, konfiguráljunk, manitorozzunk és futtassunk Szoftverkészítés
közben a WF által biztosított beépített megoldások hatalmas előnye, hogy töb­
bé nem kell manuálisan összetett infrastruktúrákat fejlesztenünk a munkafo­
lyamatokat engedélyező alkalmazásokhoz.
A fejezet először meghatározza az üzleti folyamatok szerepét, és feltárja, ho­
gyan kapcsolódnak a WF-API-hoz. Továbbá megismerkedünk a WF "tevékeny­
ség" fogalmával, a munkafolyamatok két legfontosabb típusával (a szekvenciális
és az állapotgép-munkafolyamat), valamint a különböző WF-szerelvényekkel,
projektsablonokkal és programozási eszközökkel. Az alapok megismerése után,
elkészítünk néhány példaprogramot, amelyek bemuta�ák, hogyan használhat­
juk a WF programozási modellt olyan üzleti folyamatok létrehozásához, ame­
lyeket a WF-futtatómotor felügyelete alatt hajt végre a rendszer.

Megjegyzés A WF egészét nem lehet egyetlen bevezető fejezetben áttekinteni. Ha a témá­


ban a fejezetben bemutatottnál mélyebb ismeretekre van szükségünk, olvassuk el Bruce Buko­
vics Pro WF: Windows Workflow in .NET 3.0 című könyvét (Apress, 2007).

Egy üzleti folyamat definiálása

Bármelyik életszerű alkalmazás képes kell, hogy legyen különböző üzleti fo­
lyamatok modellezésére. Tulajdonképpen az üzleti folyamatok azoknak a felada­
toknak a csopor�a, amelyek logikailag kollektív egészként működnek. Tételez­
zük fel például, hogy olyan alkalmazást készítünk, amely lehetővé teszi a fel­
használó számára, hogy egy gépjárművet online megvásároljon. Arnikor a fel-
26. fejezet: A Windows Workflow Foundation - Bevezetés

használó elküldi a megrendelést, rengeteg tevékenység indul el. Kezdhe�ük a


hitelkeret ellenőrzéséveL Ha a felhasználó megfelel a hitelellenőrzésünknek, el­
indítunk egy adatbázis-tranzakciót azzal a céllal, hogy eltávolítsuk a bejegyzést
az Inventory táblából, új bejegyzést hozzunk létre az Orders táblában, és fris­
sítsük a vevő számlainformációját Miután az adatbázis-tranzakció befejező­
dött, visszaigazoló e-mailt küldhetünk a vevőnek, majd meghívhatunk egy tá­
voli XML-webszolgáltatást, hogy a rendelést eljuttassuk a kereskedéshez.
Mindezek együttesen egyetlen üzleti folyamatot képviselnek.
Történeti szempontból az üzleti folyamat modellezése újabb olyan részlet
volt, amelyről a programozóknak kellett gondoskodniuk gyakran egyedi kód
létrehozásával azért, hogy az üzleti folyamat ne csak megfelelően modelle­
zett, hanem magában az alkalmazásban megfelelően futtatható legyen. Szük­
ségünk lehet kód létrehozására például a hibahelyek ellenőrzéséhez, a
nyomkövetéshez és a naplózás támogatásához (hogy lássuk az adott üzleti
folyamat rnit csinál); tárolási támogatásához (menteni egy hosszú ideig futó
folyamat állapotát); valarnint egyéb műveletekhez. Ahogy saját tapasztalata­
inkból tudha�uk, az ilyen infrastruktúra felépítése a semmiből rengeteg időt
és manuális munkát igényel.
Tételezzük fel, hogy egy fejlesztőcsapat az alkalmazásaikhoz elkészítette
egy egyedi üzleti folyamat keretrendszerét, de a munkájuk még nem teljes.
A nyers C#-kódot nem könnyű elmagyarázni annak, aki nem programozó,
ám szintén meg kell értenie az üzleti folyamatot. Igazság szerint a menedzse­
rek, az üzletkötők és a grafikus tervezői csapat tagjai gyakran nem ismerik a
programozási nyelvet. Éppen ezért szükségünk lesz egyéb modellező eszkö­
zök használatára is (rnint pl. a Microsoft Visio), hogy grafikus módszerekkel,
szaktudást nem igénylő kifejezésekkel bemutathassuk a folyamatokat. A nyil­
vánvaló probléma az, hogy két egységet kell összhangban tartanunk: ha
megváltozta�uk a kódot, frissítenünk kell a diagramot. Ha megváltozta�uk a
diagramot, frissítenünk kell a kódot.
Továbbá, ha kifinomult szaftveralkalmazást hozunk létre 100%-os kódmeg­
közelítéssel, a forráskód csak nyomokban tartalmazza az alkalmazás belső "fo­
lyamát" . Egy tipikus .NET-program például egyedi típusok százaiból épülhet
fel (nem is említve az alaposztálykönyvtárak által használt számtalan típust).
Bár a programozó valószínűleg érzi, hogy melyik objektum hív meg egy másik

objektumot, maga a forráskód nagyon messze áll egy olyan élethű dokurnen­
tumtól, amely a tevékenység teljes folyamatát kifejti. Noha a fejlesztőcsapat lét­
rehozhat külső dokurnentációt és munkafolyamat-ábrákat, ismét beleütközünk
ugyanannak a folyamatnak a többszörös megjelenítési problémájába.

346
A WF építőkockái

A WF szerepe

A .NET 3.0 verziójának megjelenése óta rendelkezésünkre áll a Windows


Workflow Foundation API. Lényegében a WF a programozók számára dek­
laratív módon teszi lehetővé üzleti folyamatok tervezését, előre elkészített te­
vékenységek felhasználásával. Ahelyett, hogy az adott üzleti tevékenység és a
szükséges infrastruktúra megjelenítéséhez szerelvények egyedi készletét épí­
tenénk fel, a Visual Studio 2008 WF-tervezőjével létrehozhatjuk saját üzleti
folyamatunkat a tervezés ideje alatt. Így a WF lehetővé teszi egy üzleti fo­
lyamat vázának felépítését, amelyet aztán a kódban egészíthetünk ki.
Amikor a WF-API-val programozunk, egységesen képviselhetjük az egész
üzleti folyamatot, valamint a folyamatot definiáló kódot. Mivel egyetlen WF­
dokumentummal megjeleníthetjük a folyamatot vezérlő kódot, valamint lét­
rehozhatjuk a folyamat barátságos vizuális ábrázolását, kizárható, hogy a
dokumentumok szinkronizálása megbomoljon. A WF-dokumentum világo­
san bemutatja magát a folyamatot.

A WF építőkockái

Egy munkafolyarnat-alkalmazás létrehozása "másmilyen", mint egy tipikus


.NET-alkalmazásé. Eddig példáulminden példakód új projektworkspace létre­
hozásával kezdődött (leggyakrabban a parancssori konzolalkalmazáséval), és a
forráskód hosszadalmas elkészítésével mutatta be a programot. A WF-alkalma­
zás is egyedi kódot foglal magában, de egyúttal közvetlenül a szerelvényben hoz­
zuk létre magát az üzleti folyamatot. Nézzük meg a 26.1 ábrát, amely bemutatja
a Visual Studio 2008 által létrehozott kezdeti munkafolyarnat-diagramot új Se­
quentia! Workflow parancssorikonzolalkalmazás-projekt választásakor.
A tervező használatával (és a Visual Studiába épített különböző WF-köz­
pontú eszközökkel) modellezhetjük a folyamatunkat, és végül olyan kódot
készíthetünk, amely a megfelelő körülmények között végrehajtja a folyama­
tot. A későbbiekben részletesen megvizsgáljuk ezeket az eszközöket.
A WF sokkal több, mint egy kellemes tervező, amely lehetővé teszi egy üz­
leti folyamat tevékenységeinek modellezését. A WF-diagram készítésekor a
tervezőeszközök létrehozzák azt a forráskódot, amely a folyamatunk vázát
képviseli. A legfontosabb az, hogy a vizuális WF-diagram végrehajtható kódja
nem pusztán egyszerű Visio-szerű statikus kép. A WF-technológiát más .NET­
technológiákhoz hasonlóan .NET-szerelvények, névterek és típusok képviselik.

347
26. fejezet: A Windows Workflow Foundation - Bevezetés

orldlowl.a (Design) TX

Sequentia! Workflow


l
Drop Activities to E'
crea.te a
Sequential
Workflow

+
[!]

26. 1. ábra: Üres szekvenciális munkafolyamat-diagram teroezóje

A WF futtatókörnyezete

A WF-diagram valós típusoknak és egyedi kódnak felel meg, így a WF-API


magában foglal egy futtatómotort a munkafolyamat beolvasásához, végrehaj­
tásához, eltávolításához és egyéb kezelési műveleteihez. A WF-futtatómotor
hasztolható bármely .NET-alkalmazástartományban, ám egyetlen alkalma­
zástartomány a WF-motor egyetlen futó példányát foglalha�a magában.
Az alkalmazástartomány egy Win32-folyamaton belüli partíció (lásd az
előző kötet 17. fejezetét), amely egy .NET-alkalmazás és a külső kódkönyvtá­
rak számára a hosztot jelenti. A WF-motor beágyazható egyszerű parancssori
programba, GUI asztali alkalmazásába (Windows Forms vagy Windows Pre­
sentation Foundation [WPF]), vagy hozzáférhetővé tehe�ük WCF-szolgálta­
tásból vagy XML-webszolgáltatásból.
Ha olyan üzleti folyamatot modellezünk, amelyet a rendszerek széles vá­
lasztéka használ, akkor lehetőségünk van WF létrehozására C#-osztály­
könyvtárprojekten belül. Így az új alkalmazások egyszerűen a *.dll fájlunkra
hivatkoznak ahhoz, hogy az üzleti folyamatok előre definiált gyűjteményél
újrafelhasználják Így értelemszerűen nem kell többször létrehoznunk
ugyanazt a WF-et.
A WF-API teljes értékű objektummodellel rendelkezik, amely lehetövé te­
szi számunkra, hogy programozottan együttműködjünk a futtatómotorral,
valamint az általunk tervezett munkafolyamatokkal.

348
A WF építőkockái

A WF alapvető szolgáltatásai

A tervezőeszközökön, a tevékenységeken és a futtatómotoron kívül a WF


rendelkezik beépített szolgáltatásokkal is, amelyek teljessé teszik a munkafo­
lyamat-alkalmazás általános keretrendszerét A szolgáltatások felhasználásá­
val sokat "örökölhetünk" a leginkább szükséges WF-infrastruktúrákból, ahe­
lyett, hogy időt és energiát pazarolnánk ezeknek az infrastruktúráknak a
manuális felépítésére. A 26.1. táblázat bemutatja a WF-API beépített belső
szolgáltatásait.
A 26.1 táblázatban látható négy belső szolgáltatást együtt alapvető szolgál­
tatás olmak nevezzük. A WF-API-k a szolgáltatások mindegyikéhez alapér­
telmezett implementációt biztosítanak, kettő közülük kötelező (az ütemezés
és a tranzakciók), míg a nyomkövetés és a rögzítés opcionális, továbbá alap­
értelmezés szerint a rendszer nem regisztrálja őket a futtatómotorral. Mint­
hogy a .NET-alaposztálykönyvtárak minden alapvető szolgáltatás támogatá­
sához rendelkeznek típusokkal, ezeket lehetőségünk van lecserélni saját
egyedi megvalósításainkra. A hosszú futásidejű munkafolyamatok rögzíté­
sének módját is testre szabhatjuk Ha szeretnénk a szolgáltatás alapműködé­
sét új funkcionalitással bővíteni, ez is médunkban áll.

f+�-. :�:
. �ú"

[.•>!'.'···'
Rögzítés Ez a szolgáltatás lehetővé teszi WF-példány külső forrásba
(pl. adatbázisba) történő mentését. Ez akkor nagyon hasz­
nos, ha egy hosszú futásidejű üzleti folyamat hosszú ideig
tétlen.

Tranzakció A WF-példányokat tranzakciós környezetben is vizsgálhat­


juk, és biztosíthatjuk, hogy a munkafolyamat minden része
- vagy a munkafolyamat egy részhalmaza - önálló elemi
részként sikeresen (vagy sikertelenül) működjön.

Nyomkövetés A szolgáltatás elsődlegesen egy WF-tevékenység hibakere­


sésére és optimalizálására szolgál, lehetővé teszi egy adott
munkafolyamat tevékenységeinek a megfigyelését.

Ütemezés A szolgáltatás lehetővé teszi annak szabályozását, hogy a


WF-futtatómotor hogyan kezeli a munkafolyamatok száJait.

26. 1. táblázat: A WF belső szaigáItatásai

349
26. fejezet: A Windows Workflow Foundation - Bevezetés

Amikor létrehozzuk a WF-futtatómotor egy példányát azzal a céllal, hogy az


egyik munkafolyamatunkat futtassuk, lehetőségünk van az Addservice() me­
tódus meghívására, hogy belliesszük a nyomkövetés vagy a rögzítés szolgálta­
tásobjektumokat (mint pl. a SqlWorkflowPersistanceservice és a sqlTracking­
Servi ce), valamint valamely általunk tervezett egyedi szolgáltatást. Most már
képesek vagyunk egy adott WF-példány futtatására, és a lehetővé tehetjük a
kiegészítő szolgáltatások számára a példány élettartamának felügyeletét.
Ebben a fejezetben nem hozzuk létre az alapvető szolgáltatások egyedi
megvalósításait, és nem mélyedünk bele ezek alapértelmezett funkcionalitá­
sába sem, pusztán a munkafolyamat-alkalmazás építőelemeire összpontosí­
tunk, és megvizsgálunk több WF-tevékenységet. Az alapvető szolgáltatások­
kal kapcsolatos további információkért forduljunk a .NET Framework 3.5
SDK dokumentációjához.

A WF -tevékenységek első megközelítésben

A WF célja az, hogy deklaratív módon tegye lehetővé egy üzleti folyamat model­
lezését, amelyet majd a WF-futtatómotor hajt végre. A WF tekintetében egy üzle­
ti folyamat tetszőleges számú tevékenységbó1 áll. A WF-tevékenység a teljes fo­
lyamat egy elemi "lépése". Amikor egy új WF-alkalmazást hozunk létre a Visual
Studio 2008-cal, az eszköztárban egy Windows Workflow területet találunk,
amely a beépített tevékenységek ikonjait tartalmazza (lásd a 26.2. ábrát).

Toolbox

_8 WondowsiN�v3.0
� Pointer
Cll CaiiExternaiMethod
$;J Code
·:,. Compensate
� CompensatableSequence
@ ConditionedActivityGroup
Q Delay
:l!J EventOriven
[E EventHandlingScope
,P FaultHandJer
ctJ HandleExternaiEvent

}� IfEise
(!t InvokeWebService
� lnvokeWorlcflow
� listen
�Server Explo;��)l;' Toolbox j

26.2. ábra: A Windows Workflow eszköztára

350
A WF építőkockái

Megjegyzés A Visual Studio 2008 a tevékenységek eszköztárát .NET 3.0-s és .NET 3.5-ös te­
vékenységkategóriákra osztja. A Windows Workflow csomópont alatti tevékenységek lehetövé
teszik, hogy együttmüködjünk a Windows Communication Foundtaion (WCF-) szolgáltatásokkal.

A .NET 3.5 több olyan beépített tevékenységet nyújt, amelyekkel üzleti fo­
lyamatainkat modellezhetjük, ezek mindegyikét valós típusokra képezhetjük
le a system.workflow.Activities névtérben, és így megjelenítésük és irányítá­
suk kóddal történik. A fejezetben több ilyen a beépített tevékenységeket al­
kalmazunk. A 26.2. táblázat a kapcsolódó működés szerint csoportosítva is­
merteti néhány hasznos tevékenység magas szintű működését.

CodeActivity Ez a tevékenység a munkafolyamatokban vég­


rehajtható egyedi kódegységet képviseli.

If E lseActivity, Ezek a tevékenységek alapvető ciklus- és


whileActivity elágazástámogatásokat nyújtanak a munkafo­
lyamatban.

InvokewebserviceActivity, Ezek a tevékenységek lehetövé teszik a munka­


webserviceinput Activity, folyamat számára, hogy együttműködjön az
webserviceoutputActivity, XML-webszolgáltatással.
webserviceFaultActivity

sendActivity, Ezek a tevékenységek lehetövé teszik, hogy


ReceiveActivity együttműködjünk a Windows Communication
Foundation szolgáltatásaival. Ne feledjük, hogy
ez a két tevékenység .NET 3 .5-specifikus.

conditionedActivity, Ezek a tevékenységek biztosítják a kapcsolódó


GroupActivity tevékenységek csoportjának definiálását, ame­
lyeket a rendszer akkor hajt végre, ha egy adott
feltétel teljesül.

DelayActivity, Ezek a tevékenységek egy munkafolyamatban


suspendActivity, lehetövé teszik a várakozási idők, valamint a te­
TerminateActivity vékenység szüneteltetésének vagy leállításának
definiálását.

EventDrivenActivity, Ezek a tevékenységek a munkafolyamatban le­


E ven t Ha n d li n g sco peActivity hetövé teszik CLR-események hozzárendelését
adott tevékenységhez.

351
26. fejezet: A Windows Workflow Foundation - Bevezetés

ThrowActivity, Ezek a tevékenységek lehetővé teszik kivételek


FaultHandlerActivity előidézését és kezelését a munkafolyamatban.

ParallelActivity, Ezek a tevékenységek lehetővé teszik tevékeny­


sequenceActivity ségek sorozatának párhuzamos vagy szekvenci­
ális végrehajtását.

26. 2. táblázat: A belső WF-tevékenységek áttekintése

Bár nagyon sok, minden WF-alkalmazáshoz biztos alapot nyújtó beépített te­
vékenység létezik már most is, lehetőségünk van olyan újabb egyedi tevé­
kenységek létrehozására is, amelyek zökkenőmentesen illeszkednek a Visual
Studio integrált fejlesztői környezetébe és a WF-futtatómotorba.

Szekvenciális és állapotgép-munkafolyamatok

A WF-API az üzletifolyamat-munkafolyamatok két fajtájának modellezését


támoga�a: a szekvenciális munkafolyamatokat és az állapotgép-munkafolya­
matokat. Végtére is mindkét kategória tetszőleges kapcsolódó tevékenységek
összeillesztésével jön létre; így tulajdonképpen végrehajtásuk módja külön­
bözteti meg őket.
A leglényegretörőbb munkafolyamat-típus a szekvenciális. Ahogy a neve
sugallja, a szekvenciális munkafolyamat lehetővé teszi egy olyan üzleti fo­
lyamat modellezését, ahol az egyes tevékenységeket a rendszer sorban haj�a
végre az utolsó tevékenység befejezéséig. Ez nem azt jelenti, hogy a szekven­
ciális munkafolyamat szükségszerűen lineáris vagy kiszámítható jellegű -
létrehozhatunk olyan szekvenciális a munkafolyamatot is, amely különböző
elágazási és ciklikus tevékenységeket tartalmaz, valamint olyan tevékenysé­
geket, amelyek végrehajtása párhuzamosan külön szálakon történik.
A szekvenciális munkafolyamat kulcseleme az, hogy kristálytiszta kezdő­
és végponttal rendelkezik. A Visual Studio 2008 munkafolyamat-tervezőben
a futási útvonal a WF-diagram tetején kezdődik, és lefelé halad a végpontig.
A 26.3. ábrán egyszerű szekvenciális munkafolyamat látható. Egy olyan üzleti
folyamat részletét muta�a be, amely azt ellenőrzi, hogy egy adott gépkocsi
raktáron van-e.

352
A WF építőkockái

/Workflowl:cs· )�i:cs:) "'!orkltow


. L;,.[�}* (S§§iiey�er� l - �,

Sequentia! Wortcflow
"


l
13-lfCllrlslnStock

�·�
l
l l
HaveCar DontH•veCar

!ID !ID
l
Ci-· HaveCustomerEmail

.�

l • l
HaveErneit DootH&•.<eEmail

(g� )
!ID IID
1
l
[g�) rr( Jt
--·

• ! .
hlU>JEmr

L
l ' l

t
I �;
[!l
�·
� �
Ell
26. 3. ábra: A szekvenciális munkafolyamatok egyértelmű kezdő- és végponttal rendelkeznek

A szekvenciális munkafolyamatok akkor működnek, ha a munkafolyamat


különböző rendszerszintű entitások közötti együttműködést modellez, és ha
nincs szükség a folyamatban visszaléptetésre. A 26.3 ábrán madeHezett üzleti
folyamatnak például két lehetséges kjmenete van: az autó vagy raktáron van,
vagy nincs. Ha az autó valóban raktáron van, a rendelést a rendszer az egyedi
kód valamely részletének használatával dolgozza fel (bármi legyen is az). Ha
az autó nincs raktáron, értesítő e-mailt küldünk, ha az ügyfél e-mail címe a
rendelkezésünkre áll.
A szekvenciális munkafolyamatokkal ellentétben az állapotgép-munkafolya­
matok a tevékenységeket nem egyszerű lineáris útvonal segítségével modelle­
zik, hanem több kérésállapotot és kapcsolódó eseményeket definiálnak, amelyek
kivál�ák az állapotok közötti átmeneteket A 26.4. ábrán egyszerű állapotgép­
WF-diagram látható, amely egy rendelés feldolgozását muta�a be. A munkafo­
lyamatban bármely kérés néhány belső esemény hatására különféle állapotba
kerülhet.

353
26. fejezet: A Windows Workflow Foundation - Bevezetés

�/SampleWorkflow.cs [ Design]•je...__
.. _

4J OnOrderCreate<l +-

ü Order()penSiale ll
i;l OnOrderUpdate<l o--W
-1 ------ ---.,
:l±l OnOrderProcesse<l -----

l
U Orderf'rocessedStale
i;l On0rderUpdate<l2 ..._

:4J OnOrderShippe<l +­

i;J OnOrderCancele<l

[lilor�

26.4. ábra: Az állapotgép-munkafolyamatok nem követnek rögzített, lineáris útvonalat

Az állapotgép-munkafolyamatok jól hasznosíthaták olyan üzleti folyamatok


modellezésénél, amelyek különböző végrehajtási állapotban lehetnek, rend­
szerint annak köszönhetően, hogy az állapotok közötti átmenethez emberi
beavatkozás szükséges. Adott egy kérésállapot, amely a megrendelés létre­
hozására vár. Ha ez megtörténik, egy esemény a tevékenység folyamatát a
megrendelés nyitott állapotához irányí�a, amely kivál�a a megrendelés fel­
dolgozott állapotát (vagy visszatér az előző nyitott állapothoz), és így tovább.

Megjegyzés Ebben a fejezetben nem foglalkozunk az állapotgép-munkafolyamatok felépíté·


sének részleteiveL További információkért forduljunk a . N ET Framework 3.5 SOK dokumentá·
ciójához.

354
WF-szerelvények, -névterek és -projektek

WF-szerelvények, -névterek és
-projektek

Programozói szempontból a WF-et három alapvető szerelvény képviseli:

• system.workflow.Activities.dll: A beépített tevékenységeket és az


azokat irányító szabályokat definiálja.

• system.workfl ow.Runtime.dll: Azokat a típusokat definiálja, amelyek a

WF-futtatómotort és az egyedi munkafolyamat-példányokat képviselik.

• system.workflow.componentModel .dll: Számos olyan típust definiál, ame­

lyek lehetővé teszik a WF-alkalmazások tervezési idejű támogatását.

Ezek a szerelvények több .NET-névteret definiálnak, sokat közülük különböző


WF vizuális tervezőeszközök a háttérben használnak. A 26.3. táblázat néhány
figyelemreméltó WF-központú névteret mutat be.

System.workflow.Activities Ez a fő tevékenység-központú névtér,


amely típusdefiníciókat definíál a Win­
dows Workflow eszköztárának minden
egyes eleméhez. További airrévterek defi­
niálják a szabályokat, amelyek irányítják,
valamint a típusokat, amelyek konfigurál­
ják ezeket a tevékenységeket.

System.Workflow.Runtime Ez a névtér típusokat definiál, amelyek a


WF-futtatómotort (mint a workflowRun­
time) és (a workflowrnstance révén) egy

adott munkafolyamat egy példányát kép­


viselik.

System.workflow.Runtime.Hosting Ez a névtér típusokat szolgáltat hoszt létre­


hozásában a WF futtatómotol:jához, amely
egyedi WF-szolgáltatásokat használ (nyom­
követés, naplózás stb.). Továbbá a névtér
típusokat szolgáltat a beépített alapvető
WF-szolgáltatások megjelenítéséhez.

26.3. táblázat: Alapvető WF-névterek

355
26. fejezet: A Windows Workflow Foundation - Bevezetés

A N ET 3.5 WF-támogatása
.

A .NET 3.5 megjelenésével az alaposztálykönyvtárak egy negyedik WF-köz­


pontú szerelvénnyel bővültek, ez a system. workfl owse rvi ces. dll. Itt további
olyan típusokat találhatunk, amelyek lehetővé teszik WF-alkalmazások létre­
hozását, ezek pedig integrálhaták a Windows Communications Foundation
API-kkal. A szerelvény legfontosabb tulajdonsága az, hogy a system.work­
fl ow. Acti vi ti es névteret olyan új típusokkal bővíti, amelyek támogatják a

WCF-integrációt.

Megjegyzés Amikor WF-képes Visual Studio 2008-as projektet készítünk, az IDE minden egyes
Windows Workflow Foundation szerelvényhez automatikusan beállítja a referenciákat.

Visual Studio munkafolyamat-projektsablonok

A Visual Studio 2008 IDE rengeteg WF-projektsablont biztosít. Először is,


amikor kiválasztjuk a File> New> Project párbeszédablakot, egy Workflow
csomópontot találjuk a C# programozási kategóriája alatt (lásd a 26.5 ábrát).

New Proj.ct

Tt:mplates:

[> Visual Basic Visual Studio instalied tempiates


.. Visual C# � &npty W�rkti�-Pr�ject ��Sequentia! Worldlaw Console Applicat ..
Windows
�Sequentia! Werkfl o w library � State: Machine Worlilow Console Appl ...
Web
ft! state: Machine Worldlaw Líbrary ��Werkflow Activity library
Database:
Test
WCF a,3Search Online Te:mplates...

flo Visua l(++


t> Other Project Typ6
t> Test Proje:cts

An empty project for cre:�g a workflow. (.NET Fr.1mewori:: 3.5)

Name: WorkflowProje:ctl

.Location: ----------·--·M:,� l Jlrowse... J


Solution: jCreate Solution
new
j
... 0Cre:atef!irectoryforsolution

Solubon Na!2J.e: [\•VorkflowProjecU O Add to So.u.rce Control

OK ll Cancol

26. 5. ábra: Az alapvető WF-projektsablonok

356
WF-szerelvények, -névterek és -projektek

Itt találjuk a projekteket, amelyek lehetövé teszik szekvenciális és állapotgép­


munkafolyarnat felépítését egyszerű konzolos vagy egyedi *.dll szerelvé­
nyen belüL
A New Project párbeszédablak Windows Communication Foundation
(WCF-) csomópontja is rendelkezik WF-sablonokkal (lásd 26. fejezet). Itt talá­
lunk két projektsablont (Sequentia! Workflow Service Library és State Machine
Workflow Service Library), amelyek lehetövé teszik olyan WCF-szolgáltatás
létrehozását, amely belsőleg használja a rnunkafolyarnatot. Ebben a fejezet­
ben nem használjuk a WF-projektsablonoknak ezt a csoportját, ám ernlékez­
zünk rá, arnikor WCF-szolgáltatásokat építünk, új projekt létrehozásánál
dönthetünk a WF-funkcionalitás integrálása rnellett.

A munkafolyamat menete

Arnikor WF-API segítségével prograrnozunk, végeredményben üzleti folyama­


tot próbálunk rnodellezni, így az első lépés az üzleti folyamat vizsgálata lesz,
beleértve rninden allépést a folyamaton belül és rninden lehetséges kimene­
telt Tételezzük fel például, hogy egy online tanfolyami regisztrációs folya­
matot rnodellezünk. Arnikor érkezik egy kérés, rnit tehetünk, ha a kereske­
dők nincsenek az irodában? Mi történik, ha a tanfolyarn betelt? Mi történik,
ha a tanfolyamot törölték, vagy új időpontra helyezték át? Hogyan tudjuk
meghatározni, hogy az oktató elérhető-e, nincs-e szabadságon, nem oktat-e
egy másik tanfolyarnon ugyanazon a héten, és így tovább?
Az aktuális hátterünktől függően a követelmények összegyűjtésének fo­
lyamata teljesen új feladatként jelenthet meg, hiszen az üzleti folyamat feltér­
képezése "valaki más problémája" lehet. Kisebb cégeknél a szükséges üzleti
folyamat meghatározásának terhe a fejlesztök vállát nyornhatja. Nagyobb
szervezetek jellernzően üzleti elemzőket alkalmaznak, akik elvállalják az üz­
leti folyamat feltárásának (és gyakran a rnodellezésének) a szerepét.
Mindenesetre a WF-fel dolgozni nem egyszerűen egy "ugorjunk neki, és
kezdjünk kódolni" kísérlet. Ha a kódolás előtt nem szánunk időt arra, hogy
tisztán elemezzük az üzleti problérnát, amelyet megpróbálunk megoldani,
akkor egészen bizonyosan problémáink lesznek. Ebben a fejezetben a hang­
súlyt a rnunkafolyarnat-tervezés alaptechnikáira, a tevékenységek használa­
tára és a vizuális WF-tervezőkkel folytatott rnunka lehetőségeire fektetjük.
Az életszerű rnunkafolyarnatok jóval összetettebbé válhatnak.

357
26. fejezet: A Windows Workflow Foundation - Bevezetés

Egyszerű munkafolyamat-alkalmazás
létrehozása

Az első WF-példánk nagyon egyszerű szekvenciális folyamatot modellez. A cél


az, hogy létrehozzunk egy olyan munkafolyamatot, amely bekéri a felhasználó
nevét, és ellenőrzi az eredményt. Arrúg az eredmény nem felel meg az üzleti
szabályainknak, a nevet újra és újra bekérjük
Először hozzunk létre a UserDataWFApp Sequentia! Workflow parancs­
sorialkalmazás-projektet. Majd a Solution Explorer segítségével nevezzük át
a kezdeti WF-tervező fájlját workflowl. cs-ről a találóbb Processusername­
workflow. cs névre.

A kezdeti munkafolyamathoz tartozó kód vizsgálata

Mielőtt tevékenységeket használnánk az üzleti folyamatunk ábrázolásához,


nézzük meg, belsőleg hogyan jelenik meg ez a kezdeti diagram. Ha a So­
lution Explorer segítségével vizsgáljuk a Processusernameworkflow. cs fájlt,
látható, hogy más tervező által felügyelt fájlokhoz (űrlapok, ablakok, webol­
dalak) hasonlóan a WF-diagram részleges osztálydefiniciókból áll. Ha meg­
nyi�uk a *.cs fájlt, olyan osztálytípust találunk, amely a sequential workfl ow­
Activi y típusból származik, és egy alapértelmezett konstruktort tartalmaz,
amit az Initiali zecomponent() metódust hívja meg:

public sealed partial class Processusernameworkflow


SequentialworkflowActivity
{
public Processusernameworkflow()
{
Initializecomponent();
}
}

Megjegyzés A WF-fejlesztés egyik elve, hogy a munkafolyamatok önálló elemi részek. Ennek
a ténynek köszönhetően a munkafolyamat-osztálytípusok explicit módon lezártak, így megaka­
dályozzuk, hogy a leszármazott típusok számára szülőosztályként működjenek.

Ha megnyi�uk a kapcsolódó*. oesi g ner. cs fájlt, látható, hogy az Initiali ze­


Component() metódus beállította a Name tulajdonságot:

358
Egyszerű munkafolyamat-alkalmazás létrehozása

partial class Processusernameworkflow


{
[System.Diagnostics.DebuggerNonusercode]
private void Initializecomponent()
{
this.Name = "Processusernameworkflow";
}
}

Ha a Windows Workflow eszköztár segítségével különböző tevékenységeket


húzunk a tervezőfelületre, és ezeket a Properties ablak (vagy az inline intelli­
gens címkék) használatával konfiguráljuk, a rendszer a *.Des igner.cs fájlt
automatikusan frissíti. Más IDE-felügyelt fájlokhoz hasonlóan, teljesen fi­
gyelmen kívül hagyhatjuk a kódot ebben a fájlban, és az elsődleges *.cs fáj­
lon belüli kód létrehozására összpontosíthatunk

A Code tevékenység hozzáadása

Az első tevékenység, amelyet a sorozathoz hozzáadunk, egy Code tevékeny­

ség. Ehhez fogjunk meg a Code tevékenység elemet a Windows Workflow


eszköztárról, és húzzuk a munkafolyamat kezdő és végpontját összekötő
egyenesre. Ezután a Properties ablak segítségével nevezzük át ezt a tevé­
kenységet ShowlnstructionsActivityre. Ekkor a tervezőnk a 26.6. ábrához ha­
sonlóan fog kinézni.

·' ProcessUsemameW••Jow.desiQner.cs• \:ProcessUsemameWorkflow.cs•J :;:x

Sequentia! Workflow

o--· ···· L. -
\ •::<' Showlnstructions
i """ Activity
' - ·····­
m j' l·
L_�

�tn!
-�
26. 6. ábra: Egtj (nem egészen tökéletes) Code tevékenység

359
26. fejezet: A Windows Workflow Foundation - Bevezetés

A tervező éppen hibát jelez, amelyet a Code tevékenység tetején található piros
felkiáltójelre kattintva nézhetünk meg. A hiba arról tájékoztat, hogy az Exe­

cutecode értéket nem adtuk meg, pedig ez egy kötelező lépés minden Code te­
vékenység típushoz. Az Executecode adja meg annak a metódusnak a nevét,
amelyet a rendszer végrehajt, amikor a feladat a WF-futtatómotorral találkozik.
A Properties ablakban az Executecode értékét állítsuk a showrnstructions
metódusra. Amikor megnyomjuk az Enter billentyüt, az IDE a következő kód­
csonkkal egészíti az elsődleges * . cs fájlt:

public sealed partial class Processusernameworkflow


sequentialworkflowActivity
{
public Processusernameworkflow()
{
Initializecomponent();
}

private void showinstructions(object sender, EventArgs e)


{
}
}

Valójában az Executecode a codeActivity osztálytípus egy eseménye. Amikor


a WF-motor a szekvenciális munkafolyamat ezen fázisával találkozik, az
Executecode esemény elsül, amit a showrnstructions() metódus kezel. Ezt a
metódust azokkal a console.writeLine() utasításokkal valósítsuk meg, ame­
lyek alapvető utasításokat jelenítenek meg a végfelhasználó számára:

private void Showrnstructions(object sender, EventArgs e)


{
consolecolor previcuscolor = Console.Foregroundcolor;
console.Foregroundcolor Consolecolor.Yellow;
=

-::-.':*"
console.writel ine C'********')':****************-.'r************ );
console.writeLine("**** welcome to the first WF Example ****");
"
Console.WriteLine(''*****************************************\n );
console.writeLine("I will now ask for your name and validate
the data... \n");
Console.Foregroundcolor = previouscolor;
}

360
Egyszerű munkafolyamat-alkalmazás létrehozása

Whi le tevékenység hozzáadása

A szekvenciális folyamat a végfelhasználótól a nevét kéri mindaddig, amíg


az érték egy egyedi üzleti szabály szerint (amelyet még definiálnunk kell) el­
fogadható nem lesz. Az ilyen ciklikus viselkedés a whi l e tevékenységgel jele­
níthető meg. A whi l e tevékenységgel olyan kapcsolódó tevékenységeket de­
finiálhatunk, amelyeket a rendszer folyamatosan végrehajt addig, amíg egy
meghatározott feltétel nem teljesül.
Ennek bemutatásához húzzunk egy whi l e tevékenységet a Windows
Workflow eszköztárból közvetlenül az előző code tevékenység alá, és nevez­
zük át az új tevékenységet AskForNameLoopActivityre. A következő lépés a
ciklus befejezéséhez szükséges feltétel definiálása a condi ti on érték megadá­
sával a Properties ablakban.
A condi ti on érték (amely sok tevékenység számára közös tulajdonság) két
alapvető módon adható meg. Először is létrehozhatunk egy kódfeltételt.
Ahogy a neve sugallja, az opció egy olyan metódus meghatározását teszi le­
hetővé az osztályunkban, amelyet a tevékenység hív meg annak eldöntésére,
hogy folytassa-e a munkát. Hogy erről a tényről tájékoztassa a tevékenységet,
a megadott metódusnak szüksége van egy logikai visszatérési értékre (true
az ismétléshez, fal se a befejezéshez).
A condi ti on érték megadásának másik módja egy deklaratív szabályfeltétel
létrehozása. Ez az opció lehetővé teszi egyetlen olyan kódutasítás meghatá­
rozását, amely a feldolgozást követően t rue vagy fal se értéket kap; ezt az
utasítást nem kódoljuk a szerelvényünkbe, hanem egy *.rul es fájlban tárol­
juk. A szemlélet egyik előnye az, hogy a szabályok deklaratív módosítását te­
szi lehetővé.
A feltételünk azon az egyedi kódon alapul, amelyet még létre kell hoz­
nunk. Ehhez az első lépés a Code Condition opció kiválasztása a Condition
lehetőség értékeiből, majd annak a metódusnévnek a megadása, amely a
tesztet végrehaj�a. A Properties ablak segítségével adjuk a metódusnak a
GetAndval i dateuserName nevet (lásd a 26. 7. ábrát).

Amint megadtuk a whi l e tevékenység teszteléséhez a kódfeltétel nevét, az


IDE létrehoz egy metódusrészletet, amelynek második paramétere con di ti o­
-
nal Eve n t Args típusú. Ez a típus tartalmazza a Res ul t tulajdonságot, amelyet

true vagy fal se értékre állíthatunk az általunk modellezett feltétel teljesülé­

sétől vagy sikertelenségétől függően.

361
26. fejezet: A Windows Workflow Foundation - Bevezetés

Properties

l AskforNameloopActivity System.Workflow.Activities.WhileActivity • [
! �:�[Dl ::J l
(Name) AskforNameloopActivity l
El Cond�1on Code Condition
El
Condition
Description
O GetAndValidateUmName
l
Enabled True

1 Genera�e Hand!ers. Promote Bindab!e Properties Bind Property 'Condition' ...


Vie-v While. View Cancel Hand/er Vi�..v Fault Handlers

Condition
Please specify the When condition which causes the content activities of while
activity to e<e<ute.

jj>Properties !ii] Oynamic Help

26. 7. ábra: A While tevékenység beállítása

Adjuk az új userName sztringtípusú automatikus tulajdonságot a Processuser­


nameworkflow osztályunkhoz. A GetAndvali dateuserName() metódus hatókö­

rén belül kérjük a felhasználótól a neve megadását, és ha a név kevesebb,


mint tíz karakterből áll, a Result tulajdonságot ennek megfelelően állítsuk
true értékre. A kérdéses módosítás a következő:

public sealed partial class Processusernameworkflow


SequentialworkflowActivity
{
ll A c# automatikus tulajdonság használata.
public string userName { get; set; }

private void GetAndvalidateuserName(object sender,


conditionalEventArgs e)
{
console.write("Please enter name, which must be less than
10 chars: ");
userName = console.ReadLine();

ll Megnézzük, hogy a név hosszúsága megfelelő-e,


ll és beállítjuk az eredményt.
e.Result = (UserName.Length >= 10);
}

362
Egyszerű munkafolyamat-alkalmazás létrehozása

A while tevékenység elkészítésének utolsó lépéseként egy további tevékeny­


séget adunk hozzá a while logika hatókörén belüL Az új NameNotvalidActivity
nevű code tevékenységet rendeljük hozzá a NameNotvalid metódushoz az Exe­
cutecode értéken keresztüL A 26.8. ábrán látható a végső munkafolyamat­

diagram. A NameNotval i d O megvalósítása szándékosan egyszerű:

private void NameNotvalid(object sender, EventArgs e)


{
console.writeLine("sorry, try again... ");
}

,..ProCes.sUsername...fl2_w.designer.cs J' ProcessUsemameWorkflow.cs J :;- X

Sequentia! Workflow

@
l
'�
""
Showlnstructions
Activíty
i
t
S J,5,kFcr'l.,tr:�!..ccpt..c�l·il�;

j�l
NameNo�!
�· ············· ···· ·· ·······
t ;;$
! r
!l
tiVIly

..

l
s

26.8. ábra: A végső munkafolyamat-diagram

26.9. ábra: A munkafolyamat-alkalmazás működés közben

363
26. fejezet: A Windows Workflow Foundation - Bevezetés

Ekkor lefordítha�uk és futtatha�uk a munkafolyamat-alkalmazást. Amikor fut­


ta�uk a programot, néhányszor szándékosan adjunk meg tíznél több karaktert.
Azt lá�uk, hogy a futtatómotor arra kényszeríti a felhasználót, hogy újra adja
meg az adatot egészen addig, amíg az üzleti szabály (kevesebb, mint tíz karak­
terből álló név) teljesül. A 26. 9. ábrán láthatunk egy lehetséges kimenetet.

A WF-motor hosztolási kódja


Bár az első példánk az elvárásainknak megfelelően működik, meg kell vizs­
gálnunk azt a forráskódot, amely a WF-futtatómotort utasí�a azoknak a fel­
adatoknak a végrehajtására, amelyek az aktuális munkafolyamatot képvise­
lik. Ehhez nyissuk meg a Program.cs fájlt, amely a kezdő projektünk definiá­
lásakor jött létre. A Main O metódusban találjuk a kódot, amely két kulcsfon­
tosságú típus t használ, a workflowRuntime és a workflowrnstance típusokat.
Ahogy a nevükből sejthe�ük, a workflowRuntime típus képviseli magát a
WF-futtatómotort, míg a workflowrnstance adott munkafolyamat-példány
egy példányának megjelenítésére szolgál. A kérdéses Main() metódus, meg­
jegyzésekkel kiegészítve a következő:

static void Main(string[] args)


{
ll A futtatómotor leállításának biztosítása, ha elkészültünk.
using(workflowRuntime workflowRuntime = new workflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);

ll Események kezelése, amelyeket a rendszer észlel,


ll ha a futtatómotor befejezi a munkafolyamatot,
ll és ha a motor hibával áll le.
workflowRuntime.workflowcompleted
+= delegate(object sender, WorkflowCompletedEventArgs e)
{
waitHandle.Set();
};
workflowRuntime.workflowTerminated
+= delegate(object sender, workflowTerminatedEventArgs e)
{
Console.writeLine(e.Exception.Message);
waitHandle.set();
};

364
A WF-motor hosztolási kódja

ll Most létrehozzuk azt a WF-példányt,


ll amely a típusunkat képviseli.
workflowinstance instance =

workflowRuntime.createworkflow(
typeof(userDataWFApp.Processusernameworkflow));
instance.start();

waitHandle.waitone();
}
}

A workflowRuntime típus workflowcompleted és workfloWTerminated eseményeit


a rendszer a C# névtelen metódusszintaxis segítségével kezeli. A workflow­
completed eseményt akkor hajtja végre, arnikor a WF-motor befejezte a mun­

kafolyamat-példány futtatását, rníg a workflowTerminated eseményt akkor, ha


a futtatómotor hibával fejezi be a működését.
Nem kötelező ezeknek az eseményeknek a kezelése, bár az IDE által gene­
rált kód ezt megteszi, ugyanis az AutoReset Event típus segítségével így tájékoz­

ta�a a várakozó szálat arról, hogy ezek az események bekövetkeztek Ez külö­


nösen fontos egy konzolalkalmazásnál, amikor a WF-motor egy második szá­
lon fut. Ha a munkafolyamat-logika nem használ semmilyen várakozáskezelőt,
a fő szál még azelőtt leállhat, hogy a WF-példány elvégezhetné a munkáját.
A következő érdekesség a workfl owinstance típus létrehozása. A work­
flowRuntime típus a createworkflow() metódussal rendelkezik, amely típusin­

formációkat vár a létrehozni kívánt munkafolyamatról. Egyszerűen meghív­


juk a start() metódust a visszakapott objektumreferenciából. Ez minden,
amire szükségünk van a WF-futtatómotor elindításához és az egyedi munka­
folyamat feldolgozásának megkezdéséhez.

Egyedi indítási paraméterek hozzáadása

Mielőtt folytatnák egy sokkal érdekesebb munkafolyamat-példával, vizsgál­


juk meg, hogyan definiáljuk az alkalmazásszintű paramétereket. Ha meg­
vizsgáljuk a code tevékenységeink (különösen a showinstructions() és a Name­
Notvalid O ) által használt, tervező generálta metódusok szignatúráját, észre­
vehetjük, hogy ezeket a WF-eseményeken keresztül hívjuk, amelyek a sys­
tem. EventHandler metódusreferenciát használják (obj ect és system. EventArgs

bejövő paramétertípussal).

365
26. fejezet: A Windows Workflow Foundation - Bevezetés

Mivel ez a .NET-metódusreferencia megköveteli a regisztrált eseményke­


zel őt, és argumentumként használja a system. Ob ject és a system. EventArgs
elemeket, kérdés, hogy hogyan adhatunk át olyan egyedi paramétereket, ame­
lyeket a code tevékenység használhat. Tulajdonképpen arra vagyunk kíván­
csiak, hogy hogyan definiálunk egyedi paramétereket, amelyeket bármilyen
tevékenység használhat az aktuális munkafolyamat-példányban.
A WF-motor támoga�a az egyedi paraméterek használatát a generikus oic­
tionary<string, object> típus segítségéveL A o ictionary objektumhoz adott

név/ érték párokat társítani kell a munkafolyamat-példányunk (azonos névvel


rendelkező) tulajdonságaival. Ha ezt megtettük, a paramétereket átadha�uk a
WF-futtatómotomak a munkafolyamat-példányunk elindításakor. Ennek a
megközelítésnek a használatával beolvashatunk és beállíthatunk egyedi para­
métereket egy meghatározott munkafolyamat-példány egészében.

Megjegyzés A Dictionary objektumban definiált neveket nyilvános tulajdonságokra kell


képezni, nem nyilvános tagváltozókra. Ha ezt próbáljuk tenni, futásidejű kivételt generálunk.

A Main ( ) kódjának módosításával definiáljunk egy oictionary<string, object>

elemet, amely két adatelemet tartalmaz. Az első elem sztring, amely a túl hosszú
név esetében megjelenített hibaüzenetet muta�a, a második elem numerikus vál­
tozó, amelyet a felhasználónév maximális hosszának meghatározására haszná­
lunk. Regisztráljuk a paramétereket a WF-futtatómotorban, ehhez második pa­
raméterként adjuk át a oictionary objektumunkat a createworkflow() metódus­
nak. A releváns módosítások a következők:

using (WorkflowRuntime workflowRuntime new workflowRuntime())


{

ll Definiáljunk két paramétert, amelyet a munkafolyamat használ.


ll Ne feledjük, ezeket megegyező nevű tulajdonságokra
ll kell leképeznünk a munkafolyamat-osztálytípusunkban.
Dictionary<string, object> parameters =

new oictionary<string, object>();


parameters.Add("ErrorMessage", "Ack! Your name is too long!");
parameters.Add("NameLength", 5);

ll Most hozzunk létre egy WF-példányt, amely a típusunkat


ll képviseli, és adjuk át a paramétereket.
Workflowrnstance instance =

workflowRuntime.Createworkflow(
typeof(useroataWFApp.Processusernameworkflow), parameters);
instance.Start();
waitHandle.waitOne();
}

366
A WF-motor hosztolási kódja

Megjegyzés Az előbbi kódban az ErrorMessage és a Namelength könyvtár elemeihez tartozó


értékek be vannak kódolva. Dinamikusabb szemléletet tükröz ezeknek az értékeknek a kapcsolódó
*.con fig fájlból vagy az esetlegesen bejövő parancssori paraméterekből való beolvasása.

Ha megpróbáljuk futtatni a programunkat, futásidejű kivételt kapunk, hiszen


a bejövő értékeket még nem kapcsoltuk nyilvános tulajdonságokhoz a mun­
kafolyamat-típusunkban. A tulajdonságok összekapcsolása után a futtató­
rendszer a munkafolyamat létrehozásakor meghívja őket. Ezután ezeket a tu­
lajdonságokat használhatjuk az alapul szolgáló adatok értékeinek beolvasá­
sára és beállítására. A releváns módosítások a Processusernameworkflow osz­
tálytípushoz a következők:

public sealed partial class Processusernameworkflow :


sequentialworkflowActivity
{

ll Ezek a tulajdonságok leképezhetők a Dictionary objektumon


ll belüli nevekre.
public str in g ErrorMessage { get; set; }
public int NameLength { get; set; }

private void GetAndvalidateuserName(object sender,


ConditionalEventArgs e)
{
console.write("Please enter name, which much be less than {O}
chars: ", NameLength);
userName = console . ReadLine ( ) ;

ll Megnézzük, hogy a név megfelelő hosszúságú-e,


ll és beállítjuk az eredményt.
e.Result = (UserName.Length >= NameLength);
}

private void NameNotvalid(object sender, EventArgs e)


{
Console . WriteLi ne( ErrorMes sage) ;
}

Két új automatikus tulajdonsággal bővítettük a forráskódot, továbbá a


GetAndvalidateuserName() metódus ellenőrzi a NameLength tulajdonság segít­
ségével megadott hosszúságot, valamint a hibaüzenet az ErrorMessage tulaj­
donságban talált értéket írja ki. Ezeket az értékeket mindkét esetben a mun­
kafolyamat-példány létrehozásakor átadott Dictionary objektum határozza
meg. A 26.10. ábrán láthatjuk a módosított példa lehetséges kimeneteit

367
26. fejezet: A Windows Workflow Foundation - Bevezetés

26.1 O. ábra: A munkafolyamat működés közben, egtjedi paraméterekkel

Forráskód A UserDataWFApp kódfájtokat a forráskódkönyvtár 26. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Webszolgáltatások hivása a
munkafolyamatunkban

A WF számos olyan tevékenységet tartalmaz, amelyek biztosí�ák az XML­


webszolgáltatásokkal való együttműködést a munkafolyamat-alkalmazásunk
élettartama során. Ha egyszerűen csak egy létező webszolgáltatást szeret­
nénk meglúvni, használha�uk az Invokewebservi ce tevékenységet.

Megjegyzés Mivel a WCF a preferált API a szotgáttatások tétrehozásánát, így nem részletez­
zük az XML-webszolgáltatások létrehozását (a 25. fejezet érinti a témát); ezért az általunk itt
meghívott webszolgáltatások szándékosan egyszerűek.

A MathWeb szolgáltatás létrehozása

Az első feladat egy olyan XML-webszolgáltatás létrehozása, amelyet a mun­


kafolyamat-alkalmazás felhasználhat. Ehhez hozzunk létre egy teljesen új
XML-webszolgáltatás-projektet a File >New Web Site menüpont segítségé­
vel. Válasszuk az ASP.NET Web Service ikont, és állítsuk be a Locationt http­
re, hogy a webszolgáltatást egy új liS-beli virtuális könyvtárban hozzuk létre,
amelyet a http://localhost/MathWebService címre képezhetünk le (lásd a
26.11. ábrát).

368
Webszolgáltatások hívása a munkafolyamatunkban

..,.,
Add New Web Site �'

Templat6: 1.NET Fram.worlc 35 •l EJ�


Visual Studio installed te:mplat�

'"�ASP.NETWeb Site �ASP.NET WebService il. � Empty Web Site

�WCF Service �,il! ASP NET ReportsWeb -S�e


.

My Ternpiates ------ -·--- ____, ______ · ---- ----------------------------------..·-------------- ----- �-

:]]search Onlint!: Templat�...

A Web site for creating XML Web servic6 (.NET Framework 35)

locatiore lHTTP
•l http,//localhost/MathWebService ��-------:; l Browse...

Language: [Visual(# - ·l
�� Cancel

26.11. ábra: XML-webszolgáltatás-projekt hozzáadása a WF-alkalmazáshoz

Az XML-webszolgáltatás lehetővé teszi külső hívók számára matematikai


alapműveletek végrehajtását két egész számmal a következő [webMethod] att­
ribútummal ellátott nyilvános tagok segítségéve!:

[Webservice(Namespace "http://intertech.com/")]
=

[webserviceBinding(ConformsTo wsiProfiles.BasicProfilel_l)] =

public class Mathservice : system.web.services.webservice


{
[webMethod]
public int Add(int x, int y)
{ return x + y; }
[webMethod]
public int subtract(int x, int y)
{ return x - y; }
[WebMethod]
public int Multiply(int x, int y)
{ return x * y; }
[WebMethod]
public int DivideCint x, int y)
{
if (y O) ==

return O;
else
return x l y;
}
}

369
26. fejezet: A Windows Workflow Foundation - Bevezetés

Nullával való osztás esetén hiba helyett egyszerűen O a visszatérési érték, ha


az y értéke valóban nulla. Továbbá a szolgáltatást MathService-re neveztük
át, és ezért az alábbiak szerint módosítanunk kell a cl ass tulajdonságot az
* . asmx fájlban:

d@ webservice Language="c#" codeBehind="-/App_code/Service.cs"


class="Mathservice" %>

Most már tesztelhetjük az XML-webszolgáltatásunkat a projekt futtatásával


(Ctrl + FS) vagy hibakereséssel (FS). Ennek során egy webalapú tesztelési
front endet találunk, amely valamennyi webmetódus hívását lehetővé teszi
(lásd a 26.12. ábrát).

il MathServke Web Service - Windows Intemet Explorer

o Add

• Divide

• Multioly

• Subtract

26.12. ábra: A MathWebService tesztelése

Ezzel bezárhatjuk a webszolgáltatás-projektet.

Forráskód A MathWebService kódfájlokat a forráskódkönyvtár 26. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WF -webszolgáltatás-fogyasztó létrehozása

Hozzuk létre a WFMathClient új Sequentia! Workflow konzolalkalmazás­


projektet, és nevezzük át a kezdeti C#-fájlunkat MathWF. cs-re. Az alkalmazás
a feldolgozandó adatokat a felhasználótól kéri, és megkérdezi, hogy rnilyen
műveletet szeretne végrehajtani (összeadás, kivonás stb.). Először nyissuk
meg a kódfájl unkat, és definiáljunk egy új enum típust MathOpe rati on névvel:

370
Webszolgáltatások hívása a munkafolyamatunkban

public enum Mathoperation


{
Add, subtract, Multiply, Divide
}

Ezután definiáljunk négy automatikus tulajdonságot az osztályunkban, ket­


tőt, amelyek a feldolgozni kívánt numerikus adatokat képviselik, egyet,
amely a művelet eredményét jeleníti meg, és egyet, amely magát a matemati­
kai műveletet képviseli (a MathWF alapértelmezett konstruktora az operati on
tulajdonságot Mathoperation.Add értékre állítja):

public sealed partial class MathWF : SequentialworkflowActivity


{
ll Tulajdonságok.
public int FirstNumber { get; set; }
public int secondNumber { get; set; }
public int Result { get; set; }
public Mathoperation operation { get; set; }

public MathWF()
{
Initializecomponent();

ll Allítsuk az alapértelmezett Operatien értékét összeadásra.


Operation = Mathoperation.Add;
}

A WF-tervezővel a Properties ablakban az Executecode értékének beállításá­

val adjunk hozzá egy új GetNumerical Input nevű code tevékenységet, amely a
GetNumbrnput() nevű metódusra képzünk le. Ebben a metódusban a felhasz­

nálótól két numerikus érték meghatározását kérjük, amelyeket a FirstNumber


és secondNumber tulajdonságokhoz rendeljük:

public sealed partial class MathWF : SequentialworkflowActivity


{

private void GetNumbrnput(object sender, EventArgs e)


{
ll Az egyszerűség kedvéért nem foglalkozunk annak
ll ellenőrzésével, hogy a bejövő értékek valóban számok-e.
Console.write("Enter first number: ");
FirstNumber int.Parse(console.ReadLine());
=

Console.write("Enter second number: ");


SecondNumber int.Parse(Console.ReadLine());
=

}
}

371
26. fejezet: A Windows Workflow Foundation - Bevezetés

Adjunk hozzá egy második Code tevékenységet GetMathOplnput névvel,


amely a Getoprnput() metódusra képeződik le, amelyben megkérdezzük a
felhasználótól, hogyan szeretné a numerikus adatokat feldolgozni. Feltéte­
lezzük, hogy a felhasználó az A, S, M vagy D betűket adja meg, és ennek a
karakternek a függvényében beállí�uk a Mathoperation tulajdonságot a felso­
rolt típus megfelelő értékre. Ennek egy lehetséges megvalósítása a következő:

private void Getoprnput(object sender, EventArgs e)


{
console.writeLine("Do you wish to A[dd], S[ubtract],");
Console.write("Do you wish to M[ultiply] or D[ivide]: ");
string option =console.ReadLine();

switch (option.ToUpper())
{
case "A":
Operatien= Mathoperation.Add;
break;
case "s":
operatien= Mathoperation.subtract;
break;
case "M":
Operatien= Mathoperation.Multiply;
break;
case "D":
Operatien= Mathoperation.Divide;
break;
default:
numericalap = Mathoperation.Add;
break;
}
}

Ekkor már rendelkezünk a szükséges adatokkal. Próbáljuk ki, hogyan adhat­


juk át feldolgozásra az adatokat az XML-webszolgáltatásunknak.

Az lfElse tevékenység konfigurálása

Mivel a numerikus adatainkat a rendszer négy egyedi módon dolgozha�a fel,


egy lfElse tevékenységgel határozzuk meg, hogy a szolgáltatás melyik webmetó­
dusát hívjuk meg. Ha IfElse tevékenységet húzunk a tervezőnkbe, automatiku­
san két elágazást kapunk. Az lfElse tevékenység további elágazásainak hozzá­
adásához kattintsunk jobb gombbal az IfElse tevékenység ikonjára, és válasszuk
az AddBranch menüpontot. A 26.13. ábrán látha�uk az aktuális WF-tervezőt (az
egyes elágazások és a teljes IfElse tevékenység megfelelő nevet kapott).

372
Webszolgáltatások hívása a munkafolyamatunkban

Az IfElse tevékenység minden elágazása tartalmazhat tetszőleges számú


belső tevékenységet, amelyek meghatározzák mi történik, ha a döntési logika
az alkalmazás működését ezen az útvonalon viszi végig. Mielőtt hozzáad­
nánk ezeket az altevékenységeket, először hozzá kell adnunk a logikát, amely
lehetővé teszi a WF-motor számára, hogy eldöntse, melyik ágon haladjon to­
vább; ennek megvalósulásához állítsuk be minden egyes IfElseBranch tevé­
kenységhez a con dit: i on értéket.
A condi t:ion eseményt kódfeltétel vagy deklaratív szabályfeltétel létreho­
zására konfigurálhatjuk. A fejezet első példaprojektje bemutatta, hogyan hoz­
hatunk létre kódfeltételt, így ebben a példában a szabályfeltételt választjuk
Kezdjük az AddBranch elemmel, és a Visual Studio Properties ablakában ál­
lítsuk a con dit:ion tulajdonságot Deciaratíve Rule Condition értékre. Ezután
kattintsunk a ConditionName alcsomópont gombjára, és a megjelenő párbe­
szédablakban kattintsunk a New gombra. Ekkor létrehozhatjuk azt a kifeje­
zést, amely meghatározza az aktuális ág igaz vagy hamis értékét. Az első ág
esetén a kifejezés a következő:

t:his.Operat:ion == Mat:hoperat:ion.Add

,wF.a (DesigAJ" • x

Sequentia! Wor1<flow

@
l
(g�u�)
:t

(g�� +
"
B ifEiseMathOpActivity
...

o o+ o r-�------t-----------------,
AddBranch SubtractBranch MultiplyBranch Divid�Branch

!ID !ID !ID !ID


l l l i l
Orop Activities Orop Activities Drop Ac.tivities
! Drop Activities
H en. H"'e Here i Here

t t t l__ , t
______ ____ _______ j
T
C!l
<i

<i
26.13. ábra: Egy többelágazásos IfElse tevékenység

Ez a párbeszédablak támogatja az IntelliSense-t, amely mindig szívesen látott


szolgáltatás (lásd a 26.14. ábrát).

373
26. fejezet: A Windows Workflow Foundation - Bevezetés

Rule Condition Editor

� Edit constraints to create a rule condition.


� Example: this.Propl =="Yes" ll this.Prop2 ==2

Condition:

this.Operation == WFMathCiiont.MathOp..ration j
��o;f�'·!·!!H····;]
Divide O
-:�Equals
l óQOM-'

l L__________ ------------..·---··--- .. --- -------- -- ·-· - ·----- --· · ·--·-·- . -- '··-·· ·- J


OK ll Cancel

26.14. ábra: Deklaratív szabályfeltétel definiálása

Az összes szabály meghatározása után azt látjuk, hogy a projektünkben egy


új fájl jött létre *.ru l es kiterjesztéssei (lásd a 26.15. ábrát).

Solution Explorer

eJWF�t
$- � Prop..rties
ffi... 1:.1 References
B- eiD MathWF.cs

J L:\r.1 � IHffltAMfd
"•r.cs

..
L. . Program.cs

� Solution Explorer �Class View1

26.15. ábra: A *.rules fájl tartalmaz minden deklaratív szabályt, amelyet a WF számára létrehoztunk

Ha megnyitnánk ezt a fájlt, rengeteg <Ru l eExpressi oneondi ti on> elemet talál­
nánk benne, amelyek az általunk létrehozott feltételeket írják le.

Az lnvokeWebService tevékenységek konfigurálása

Az utolsó feladat a bejövő adatok átadása a megfelelő webmetódusnak, va­


lamint az eredmény kiírása. Húzzunk egy InvokeWebService tevékenységet
a legszélső bal oldali ágba. Ekkor automatikusan megnyílik az Add Web Re­
ference párbeszédablak, ahol megadhatjuk a webszolgáltatás URL-jét (amely
ebben a példában a http:/ jlocalhostjMathWebServicejService.asmx cím),
majd kattintsunk az Add Reference gombra (lásd a 26.16. ábrát).

374
Webszolgáltatások hívása a munkafolyamatunkban

AddWeb�e��

Navigate to a web service URL and click Add Reference to add ali the available services.

0Back ojlil � �
URL: : http://localhost/MathWebService/Service.asmx TJ C]Go

.. 1 Web seMces found at this URl:

MathService l SeJvice Found:

-Service
The following operations a re supported. For a formal
definition, please review the Service Description.

• Add

• Divide
Web rd�ence name
• Multiply
localhost

• Subtract
Add Ref«ence

; '

26.16. ábra: Az XML-webszolgáltatásunk hivatkozása


=J Concel

Ekkor az IDE a webszolgáltatásunkhoz létrehoz egy proxyt, és az Invoke­


WebService Proxycl ass tulajdonságát erre állítja. Ekkor a Properties ablakban
megadhatjuk a meghívandó webmetódust a MethodName tulajdonság segítsé­
gével (amely az Add metódus ehhez az ághoz), és leképezhetjük a két bejövő
paramétert a FirstNumber és a SecondNumber tulajdonságainkhoz és a visszaté­
rési értéket a Result tulajdonsághoz. A 26.17. ábrán láthatjuk az első Invoke­
WebService tevékenység teljes konfigurációját
A fennmaradó webmetódusok tevékenységhez rendelésével ismételhetjük
meg a folyamatot a maradék három IfElse ághoz. Bár az Add WebReference
párbeszédablak minden egyes InvokeWebService tevékenységhez megjelenik,
az IDE elég intelligens ahhoz, hogy újra felhasználja a már meglévő proxyt,
ugyanis minden tevékenység ugyanazzal a végponttal kommunikál.
Végül, de nem utolsósorban, egy utolsó Code tevékenységet adunk az
I fEl se kódhoz, amely megjeleníti a felhasználó által választott művelet ered­

ményét. A tevékenységnek adjuk a Disp l ayResult nevet, és állítsuk az alábbiak


szerint megvalósított showResu l t O metódust Executecode értékre:

375
26. fejezet: A Windows Workflow Foundation - Bevezetés

private void ShowResult(object sender, EventArgs e)


{
console.writeLine("{O} {l} {2} = {3}",
FirstNumber, Operation.ToString().ToUpper(),
SecondNumber, Result);
}

Properties íl
invokeWebServiceActivityl System.Workflow.ActivitiosJnvokeWebSe!vic •i

��:!!!JI@!] ;r l ':J
(Name) invokeWebServiceActivityl
BI@Mfflf!iil@Mi ActMty:::MathWF, Path=R�utt Q
Name MiothWF
Path Resuli
Description
Enabled True
Jnvoked G
lnvoking �
MothodName Add
ProxyCiass WFMathCient.Joa�Mst.MiothService
l Sossionld
URL http:/
/loQihost/MiothWebService/Service.-nx
Bx � Activity=MathWF, Path= firstNumber
Name MiothWF
l Path F'
ontHumber
By O Activi(y=MathWF, Path=SecondNumber
Name MiothWF
Path SecondNumber

Genmte Handlers PromoteBindable Proeertif!S Sind Prooerty i


'{R�umVaiMe1' ..,
[li
l
l
tRetumV-...l
l Please specify a value for parameter of type 'SystemJnt32', l
!ffilPropertios ;fDynamíc Ht!lpl

26.17. ábra: Teljesen kanfiguráit InvokeWebSeroice tevékenység

Az egyszerűség kedvéért az Operati on tulajdonság szöveges értékét használ­


juk a kiválasztott matematikai operátor ábrázolására, ahelyett, hogy további
kóddal képeznénk le a Mathoperation.Add elemet a+ jeihez, a Mathoperation.
subtract elemet a - jelhez és így tovább. A 26.18. ábrán láthatjuk a munkafo­
lyamat végleges tervét; a 26.19 ábra pedig egy lehetséges kimenetet mutat.

376
Webszolgáltatások hívása a munkafolyamatunkban

,thWF.cs [Oe5igf>JatiiiP, YX

Sequentia! Worl<flow

[g�Nu��J
+

(g�� J +
...

8 ifEiseMllthOpActivity

AddBranch SubtractBranch MultiplyBranch DivideBranch

!!ll
l

Qu -- *---- ---

i(g�.��� ]i
. --- ··--r--- ---�

[!] irJI

(ii

26.18. ábra: A kész webszalgáltatás-centrikus munkafolyamat

26.19. ábra: Kommunikáció XML-webszolgáltatással a WF-alkalmazásból

Kommunikáció WCF -szolgáltatással a SendActivity


segitségével

A példa befejezéséhez vizsgáljuk meg, hogyan kommunikál egy munkafolya­


mat-alkalmazás a WCF-szolgáltatással. A .NET 3.5-ös központú sendActivity és
ReceiveActivity típusok ugyanis lehetövé teszik olyan munkafolyamat-alkalma­
zások létrehozását, amelyek WCF-szolgáltatásokkal kommunikálnak. Ahogyan a
neve sugallja, a sendActivity típust WCF-szolgáltatás műveleteinek hívására
használhaljuk, rrúg a ReceiveActivity a WCF-szolgáltatás számára a WF vissza­
hívását teszi lehetövé (egy duplex hívási szerződés esetén).

377
26. fejezet: A Windows Workflow Foundation - Bevezetés

A 25. fejezetben egy WCF-szolgáltatás-szerződést definiáltunk, amely ugyan­


csak két számot adott össze, a következő szolgáltatásinterfész segítségéve!:

namespace MathserviceLibrary
{
[Servicecontract(Namespace "www.intertech.com")]
public interface IBasicMath
{
[Operationcontract]
int Add(int x, int y);
}
}

Ezt az interfészt a Math service típussal valósítottuk meg, és a Mathwin dows­


serviceHost.exe Windows-szolgáltatás hosztolta. A Windows-szolgáltatás az

említett funkcionalitást a következő végpontból biztosítja:

http://localhost:8080/Mathservice

Add Seivice Ileference

To see a list of available services on a specitic server, enter a service URL and cliclc. Go. To browse for available
services. dick Discover.

Address:

http://localhost8080/MathService

Services: Operations:

B-0 il MathService
L� IIIll!l.llD

r service(s) found at address 'http://localhost8080/MathService'.

Narnespace:

ServkeReference

l Advanced-. j oK
.__ _ _
_ _.II Cancel

26.20. ábra: A WCF MathService referenciájának létrehozása

Ha létrehoztuk és telepítettük ezt a szolgáltatást (részleteket lásd a 25. feje­


zetben), módosíthatjuk az aktuális WFMathClient projektünket úgy, hogy a
sendActi vity típus segítségével kommunikálhasson a szolgáltatással. Az első
lépés egy referencia hozzáadása a szolgáltatáshoz az Add Service Reference

378
Webszolgáltatások hívása a munkafolyamatunkban

párbeszédablak segítségével (lásd a 26.20 ábrát). Ezzel a lépéssel létrehozunk


egy ügyféloldali proxyt, és módosí�uk az App. con fi g fájlunkat a WCF-speci­
fikus beállításokkaL
Húzzunk egy sendActivity típust a WF-tervezőnk felületére (a neve WCF­
sendAddActiv i ty ) , közvetlen ül az utolsó Code tevékenység után. A Properties

ablakban kattintsunk a servi ceoperati onInfo tulajdonság gombjára, és a


megjelenő párbeszédablakban kattintsunk az Import elemre. Ekkor egy má­
sodik párbeszédablak jelenik meg, ahol összekapcsolha�uk a sendActivi ty
típust egy metaadat-leírással az adott WCF-szolgáltatás-szerződéshez. Vá­
lasszuk ennél a végpontnál az egyetlen lehetséges szerződést: az I Basi cMathot

(lásd a 26.21. ábrát).

Browse and Select a . N ET Type

Choose �service conti'Kt from bekaw

TypeName WFMathCiíe:nt.5e:rviceRderenc�JBasicMath

t?" C:� <Current Project> TypeName • Fulty QualifiedName


i '·· {l WfMathCiient.Servi<:eReference
é-� Referenced Assemblies 'U l8as1cMath WFMathChent.SeMceReferencelBaslcMath

00--..Q System.Servic�odel

Setected Type I&tsic�th mem� of {WFMathOient.Servic:-eReference}


Contained in Assembly <Current Projed>

OK l[ Cancet

26.21. ábra: Szolgáltatásmetódus kapcsolása a SendActivihJ típushoz

Ekkor kiválasztha�uk, hogy a szerződés melyik műveletét hívja a sendActi vi ty.


Ami esetünkben az egyetlen lehetőség az Add O metódus (lásd a 26.22. ábrát).
Még néhány további konfigurációs lépést el kell végeznünk, mielőtt a
sendActivity készen állna a FirstNumber és a secondNumber tulajdonságok értékei­

nek átadására, hogy a Mathservice feldolgozhassa őket. Különösen fontos a


channelToken tulajdonság értékének beállításával tájékoztatni a sendActivity te-

379
26. fejezet: A Windows Workflow Foundation - Bevezetés

vékenységet, hogy melyik kötést használjuk a hívás alatt (egyetlen WCF-szolgál­


tatást beállíthatunk úgy, hogy különböző kötésekhez álljon rendelkezésre). Ha
megnyitjuk a módosított App.config fájlt, és megkeressük a <client> részt, azt ta­
láljuk, hogy a létrehozott kötés neve WSHttpBinding_IBasic_Math:

<client>
<endpoint address="http://localhost:8080/MathServiceLibrary"
binding="wsHttpBinding"
bindingconfiguration="WSHttpBinding_IBasicMath"
contract="WFMathclient.ServiceReference.IBasicMath"
name="WSHttpBinding_IBasicMath"
>
<identity>
<servicePrincipalName value=
"host/Interuber.intertech-inc.com" />
</identity>
</endpoint>
</client>

Available Operations: ("Import...

t§ WFMathCiient.ServiceReferenceJBasicMath
--
c. �:� Ad� � -� ��-� --=�-:- ��--- .. ==�=-�: ___ .. _ ·=--·-= -� -_ ..

Operalion Name: Add

Name Dirertion
----- __ T�--- ·· ·---- --- --------- ...

(RoturnValue) lnt32 Out

ln!32 ln

y lnt32 ln

Ol( ll Cancel

26.22. ábra: Az Add() művelet kiválasztása

380
Webszolgáltatások hívása a munkafolyamatunkban

Másoljuk ezt az értéket a vágólapra, és illesszük be a channelToken tulajdon­


ságba az IDE Properties ablakának segítségéveL A channelToken tulajdonság
két alcsomópontot tartalmaz: az Endpoint Name és az ownerActivityName cso­
mópontokat. Mivel a Mathservi ce csak egyetlen végpontot tár fel, másoljuk át
ugyanezt az értéksort a channelToken be (wsHttpBi ndi ng_IBasi cMath) az End­

poi ntName-hez, és jelöljük ki a munkafolyamat-példányunk nevét (wcFsend­


AddActi vi ty) tulajdonosként.
Végül, de nem utolsósorban, az Add() metódus x és y paramétereit kap­
csoljuk a FirstNumber és secondnumber tulajdonságainkhoz, és a visszatérési
értéket a Result tulajdonságunkhoz. Az eljárás megegyezik az InvokeWeb­
Service tevékenység konfigurálásával (a tulajdonságnév megadásához kat­
tintsunk a kerek gombokra). A 26.23. ábrán látható a teljesen kanfiguráit
SendActivity tevékenység.

Properties

WCFSendAddActivity System.Worlcflow.Activities.SendActivity

, �l' [f]II!J ., ! !:
(Name) WCFSendAddActivity
GJ (ReturnValue) ll Activity=MathWF, Path=Result
AfterResponse o l
BeforeSend o

i
El ChanneiToken - WSHttpBincling_I�M.ith
El

l l
EndpointName WSHttpBinding_I�M.ith
OwnerActivityName WCFSendAddActivity
CustomAddress l)
li
l
Description

1, Enabled Tru�
ServiceOperationlnfo WF�hOient.SenriceReferenceJ�M.ith.Add l
Glx O Activity=MathWF, Path=FirstNumber
O Activity=MathWF. Path=SecondNumbor
iGJ y

Generate Handlers Promote Bindable Properties Bind Prcrerty 'Name' ... Show activities with
same ooe@tion Show activities with same contract

!
ChonneiToken
l Represents the channel that will be usedto send the message.

26.23. ábra: A teljesen kanfiguráit SendActivitlJ

Az eredmény megtekintéséhez helyezzünk el egy utolsó Code tevékenységet


a munkafolyamat-tervezőnkön, és rendeljük az Executecode értéket a WCFRe­

sult() metódushoz, amelyet a következőképpen valósítunk meg:

381
26. fejezet: A Windows Workflow Foundation - Bevezetés

private void WCFResult(object sender, EventArgs e)


{
Console.writeLine("****<' WCF Service Additi on **''**");
Console.writeLine("{O} +{l}= {2}",
FirstNumber, secondNumber, Result);
}

A 26.24. ábrán láthatjuk a végső kimenetet.

26.24. ábra: Kommunikáció WCF-szolgáltatással

Forráskód A WFMathClient kódfájlokat a forráskódkönyvtár 26. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Újrafelhasználható WF-kódkönytár
létrehozása

Az első példák lehetövé teszik, hogy körbejárjuk a különböző WF-tevékenysé­

geket a tervezés során, kapcsolatba lépjünk a munkafolyamat futtatómotorjá­


val (egyedi paraméterek átadásával), és megismerjük a teljes WF-szemléletet a
kanzolalapú WF-hosztolás használatával. Ez alapján könnyen elképzelhető
munkafolyamatot használó Windows Forms-alkalmazások, WPF-alkalmazá­
sok vagy ASP.NET-webalkalmazások készítése. Sőt érezhetően szükség lehet
alkalmazások között újrafelhasználható munkafolyamatra, amelyet a funkció
.NET-kódkönyvtárba való csomagolásával valósíthatunk meg.
A következő WF-példa bemutatja, hogyan csomagoljunk munkafolyama­
tokat *.dll szerelvényekbe, és hogyan használjuk azokat hasztoló Windows
Forms-alkalmazásból (amely mellesleg ugyanaz a folyamat, mint amelyik
külső munkafolyamatot hasztolhat bármely végrehajtható fájlban, pl. egy
WPF-alkalmazásban). Olyan munkafolyamatot tervezünk, amely egy sze­
mélyautó megvásárlásához szükséges rendelés hitelellenőrzésének alapfo­
lyamatát modellezi a 22. fejezetben létrehozott A utoLot adatbázisbóL

382
Újrafelhasználható WF-kódkönytár létrehozása

Kezdjük a CreditCheckWFLib nevű Sequentia! Workflow Library projekt


kiválasztásával (lásd a 26.25. ábrát), és nevezzük át a kezdeti fájlunkat cre­
di tcheckWF. cs-re.

New Project �:-<> �� nF'IIWS

Project types: Templates: l.NfT Framewo<H5 �l§�


VISual Basic Visual Studio installed tem plat�
Visual C# � Empty Workflow Project � Sequentia! Workflow Console Applicat ..
Windows
�----------------- :ZI State Machinc Werkflow Console Appl...
W•b
Database
�-�������������� (\'J Workflow Activity Library
T<>t My Templat6 ----­
WCF GJ Search Online Templat5...
Woitcflow
Visual C++
Other Project Types
Test Projects

A project for creating a sequential workflow líbrary. (.NET Framework 35}

Nam� CreclitCheclcWFlib

C:\My Boolts\C# and the .NET Platform 4th cd\Codc\Final Numb�ng\Chaptfi" 26


location:
� l Browse... l
Solution: lCtHte Solution
new
•) O Create directory for solution
Sol1.1t1on Name: j CreditCheckWFlib EJ Add to SOtJrc• Control

OK J[ Cancel

26.25. ábra: Sequentia[ Workflow Library projekt létrehozása

Ekkor egy kezdő munkafolyamat-tervezőt kapunk. Egyetlen munkafolya­


mat-kódkönyvtár több olyan munkafolyamatot tartalmazhat, amelyek mind­
egyike beilleszthető a Project > Add New ltem párbeszédablakkaL Adjunk
hozzá egy referenciát a 22. fejezetben létrehozott AutoLotDAl . dll szerelvé­
nyünkre, és egészítsük ki a kezdeti kódfájlunkat az AutoLotconnectedLayer
névtér importálásával:

ll Adjuk hozzá az alábbbi importutasítást.


using AutoLotconnectedLayer;

namespace creditcheckWFLib
{

Ezután adjunk hozzá egy automatikus tulajdonságot, amely az ügyfél azono­


sítóját képviseli:

383
26. fejezet: A Windows Workflow Foundation - Bevezetés

public sealed partial class creditCheckWF


sequentialworkflowActivity
{
ll Az ügyfélazonosító a hitelellenőrzéshez.
public int ID { get; set; }

Amikor egy későbbi lépésben létrehozzuk a kliensalkalmazást, ezt a tulaj­


donságot egy bejövő Dictionary<string, object> objektum segítségével állít­
juk be, amelyet a munkafolyamat futtatómotorjának adunk át.

Hitelellenőrzés végrehajtása

Módosítsuk az osztályunkat egy további automatikus tulajdonsággal (cre­


ditok) , amely azt jelenti, hogy az ügyfél megfelelt a "szigorú" hitelellenőrzési

folyamatnak:

public sealed partial class creditcheckwF :


sequentialworkflowActivity
{
ll Ennek segítségével határozzuk meg, hogy az ügyfél
ll megfelelt a hitelellenőrzésnek, vagy sem.
public bool creditOK { get; set; }

Helyezzünk egy ValidateCreditActivity nevű Code tevékenységet a WF-ter­


vezőnkre, és állítsuk az Executecode értéket az új va l i datecred it metódusra.
Nyilvánvaló, hogy egy termékszintű hitelellenőrzés rengeteg altevékenysé­
geket, adatbázis-keresést és egyebeket foglalna magában. Ebben a példában
egy véletlen számot fogunk generálni annak a lehetőségnek a megjelenítésére,
hogy a hívó megfelel-e a hitelellenőrzésünknek:

private void validateCredit(object sender, EventArgs e)


{
ll Tegyünk úgy, mintha végrehajtottunk volna
ll néhány egzotikus hitelellenőrzést...
Random r = new Random();
int value = r.Next(SOO);
if (value > 300)
creditOK = true;
else
CreditOK = false;
}

384
Újrafelhasználható WF-kódkönytár létrehozása

Ezután adjunk hozzá egy CreditCheckPassedActivity nevű IfElse tevékeny­


séget két elágazással, amelyek a CreditCheckOk és a CreditCheckFailed név­
vel rendelkeznek. Állítsuk be a bal oldali elágazást úgy, hogy a rendszer új
deklaratív szabályfeltételének a segítségével az alábbi kifejezés alkalmazásá­
val értékelje ki:

this.creditOK == true

Ha a felhasználó nem felel meg a hitelellenőrzésnek, akkor el kell távolítani a


Customers táblából és hozzáadni a CreditRisks táblához. Mivel a 22. fejezet
már gondoskodott erről a lehetőségről, az InventoryDAL típus Processcredit­
Risk() metódusának segítségéve!, adjunk új codeActivity típust a Process­

creditRiskActivity nevű creditcheckFai led ágban. A típus a Processcredit­

Risk() metódusra képezhető le. Hozzuk létre a metódust így:

Megjegyzés Logikai paramétert adtunk az I n ven t ory DAL.Praeesseredi tRisk() metó­


dushoz, hogy meghiúsítsuk a tranzakciót a tesztelés érdekében. Mindenképpen fa l se értéket
adjunk át első paraméterként.

private void ProcesscreditRisk(object sender, EventArgs e)


{
ll Ideális esetben a kapcsolatsztringet külsőleg tárolnánk.
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data source=
(local)\SQLEXPRESS;Integrated Security=SSPI;" +

"Initial catalog=AutoLot");
try
{
dal.ProcesscreditRisk(false, ID);
}
finally
{
dal.closeconnection();
}
}

Ha a hitelellenőrzés sikerült, egyszerűen megjelenítünk egy információs üze­


netdobozt, amelyben tájékoztatjuk a hívót, hogy a hitelellenőrzés sikeresen
megtörtént Egy valós munkafolyamatban a következő lépések lehetnének a
rendelés leadása, a rendelésellenőrző e-mail küldése és így tovább. Tételez­
zük fel, hogy hivatkoztuk a System.windows. Forrns.dll szerelvényre, ezért he­
lyezzünk Code tevékenységet az IfElse tevékenységünk legszélső, bal oldali
ágába PurchaseCarActivity névvel, amely a Purchasecar() metódusra képez­
hető le, és megvalósítása a következő:

385
26. fejezet: A Windows Workflow Foundation - Bevezetés

private void Purchasecar(object sender, EventArgs e)


{
ll Az egyszerűségre törekszünk. így könnyen módosíthatjuk
ll az AutoLotDAL.dll szerelvényt új metódussal, hogy új rendelést
ll helyezzünk az orders táblába.
system.Windows.Forms.MessageBox.show("Your credit has been
approved!");
}

A munkafolyamat befejezéséhez adjunk egy utolsó CodeActivity tevékeny­


séget a legszélső, jobb oldali ághoz közvetlenül a ProcessCreditRiskActivity
után. Legyen az új tevékenység neve ShowDenyMessageActivity, amely a
következő metódusra képezhető le:

private void creditoenied(object sender, EventArgs e)


{
System.Windows.Forms.MessageBox.show("You are a CREDIT RISK!",
"order Denied!");
}

A munkafolyamat megjelenése most nagyjából a 26.26. ábrához hasonlít.

CreditO>eckWF.cs [Designr�� �x
Af.� ,c:;,*

-

l
Sequentia! Workflow

@
l ,· l

[g ��) �
d

l
-t
l 8 CreditCheckPassedActivity

=l

l • l

!�:l
CreditCheckOK CreditCheckFailed

ffil ffil
l
l l �j
(g�u��j f :C��) -t

l (g�) l c....
l l .
'
l �
[;] Mt

26.26. ábra: A kész Sequentia! Workjlow Library projekt

386
Újrafelhasználható WF-kódkönytár létrehozása

Forráskód A CreditCheckWFLib kódfájlokat a forráskódkönyvtár 26. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Windows Form5-kliensalkalmazás létrehozása

Miután létrehoztunk egy újrafelhasználható .NET-kódkönyvtárat, amely


egyedi munkafolyamatot tartalmaz, most már bármilyen .NET-alkalmazást
létrehozhatunk, amely használhatja ezt a munkafolyamatot. Jóllehet a grafi­
kus felületek készítésének a részleteit még nem ismerjük, a Windows Forms
API használatával egy kezdetleges felhasználói felületet hozunk létre a mun­
kafolyamat logikájának tesztelésére (a GUl-alapú .NET-alkalmazások vizsgá­
latát lásd a 27. fejezetben). Kezdjük egy új CreditCheckApp nevű Windows
Forms-projekt létrehozásával (lásd a 26.27. ábrát).

·- · --
N.w Project �l l 'll••

frojoct types: lernpiatos I.NETFromewodc 3.5 ·l§�


Visual Basic . Visual Studio installed tempiates
Windows !�Windows F� Aeélkirtio� i!J Class Library
Database �ASP.NET Web Application M ASP.NET Web Service Application
Test
6,:WPF Application j"ijWPF Brows., Application
WCF
� Console Application A WCF Service Application
Web
�Windows Forms Control Ubrary
W erkflow
Vis�tc# = MyTemplat� ·
Windows il]Search OnlineTemplates...
Web
Database
Test
WCF
Woricflow
VtSual C++
Other Project T ypes -

A projKt for creating an application with a Windows Forms user interface (.NET Framework 35)

Name: Cred�CheclrApp

.l.ocation: C:\My Books\C# and the .NET Platform 4th ed\Code\Final Numbuing\Chapter 26 .
l �,l
· Solution Name: J CreditCh�ckAPp ___j O Create ,directory for solution
EJ Add to Soyrce Control

l OK
ll ._Concel
l
26.27. ábra: Windows Forms-alkalmazásprojekt létrehozása a munkafolyamat-könyvtárunk tesztelésére

Ezután nevezzük át a kezdeti Forml. cs fájlt a találó Mai n Form. cs névre, ezért
jobb gombbal kattintsunk a Forml.cs ikonra a Solution Explorerben, és vá­
lasszuk a Rename opciót. Ezután adjunk referenciát az alábbi .NET-szerelvé­
nyek mindegyikéhez:

387
26. fejezet: A Windows Workflow Foundation - Bevezetés

• creditcheckWFLib.dll

• System.Workflow.Runtime.dll

• System.Workflow.Activities.dll

• System.workflow.componentModel.dll

Az alkalmazásunk felhasználói interfésze az kezdő űrlapon a következőkből


áll: egy leíró Label, egy TextBox (txtcustomeriD néven) és egy egyszerű Button
típus (btnExecuteworkflow néven). A 26.28 ábra egy lehetséges tervet ábrázol.

26.28. ábra: Egyszerű felhasználói felület a munkafolyamat-könyvtárunk teszteléséhez

Miután elhelyeztük ezeket a felhasználóifelület-elemeket a tervezőnkön, ke­


zeljük a Button típus Click eseményét. A tervezői felületen kattintsunk kétszer
a gomb ikonjára.
A kódfájlunkban valósítsuk meg a kattintásiesemény-kezelőt, hogy elin­
dítsuk a WF-futtatómotort, és létrehozzuk az egyedi munkafolyamat egy pél­
dányát. A következő kód megegyezik a kanzolalapú munkafolyamat-alkal­
mazás kódjával (leszámítva a munkafolyamat befejezéséig a parancssori prog­
ram életben tartásához szükséges szál kódját):

ll A kezdeti utasításokat az egyszerűség kedvéért eltávolítjuk.

ll Szükség van a WF-futtatómotorra.


using System.workflow.Runtime;

ll Ne felejtsünk el referenciát létrehozni az egyedi


ll WF-könyvtárunkhoz.
using creditCheckWFLib;

namespace winFormsWFClient
{
public partial class MainForm Form
{

388
Újrafelhasználható WF-kódkönytár létrehozása

public MainForm()
{
Initializ�Component();
}

private void btncheckcustomercredit_Click(object sender,


EventArgs e)
{
ll Hozzuk létre a WF-futtatómotort.
workflowRuntime wfRuntime = new workflowRuntime();

ll Kérjük be az azonosítót a TextBox-ba, hogy az értéket


ll a munkafolyamatnak továbbíthassuk.
Dictionary<string, object> args = new
Dictionary<string, object>();
args.Add("ID", int.Parse(txtcustomeriD.Text));

ll Megkapjuk a WF egy példányát.


workflowrnstance myworkflow =
wfRuntime.Createworkflow(typeof(creditCheckWF), args);

ll Indítsuk el.
myworkflow.Start();
}
}
}

Az alkalmazás futtatásakor adjunk meg egy ügyfélazonosító értéket, és győ­


ződjünk meg róla, hogy a megadott ügyfélazonosító nem rendelkezik refe­
renciával az Orders táblában (ezzel gondoskodunk arról, hogy az elemet si­
keresen töröljük a Customers táblából).
A hitelképesség tesztelésekor végül is azzal szembesülünk, hogy a kockáza­
tos ügyfelet a rendszer törli a Customers táblából, és beúja a CreditRisk táblába.
Tesztelési céllal hozzáadhatunk egy mesterséges bejegyzést a Customers táblá­
ban, és megpróbálha�uk ellenőrizni egy rögzített vásárló hitelképességét is.

Forráskód A WinForm sWFClient kódfájlokat a for ráskódkönyvtár 26. fejezetének alkönyvtára


tartalmazza. A for ráskódkönyvtár r ó l lásd a Bevezetés xlv. oldalát.

389
26. fejezet: A Windows Workflow Foundation - Bevezetés

Az egyedi tevékenységek

Megvizsgáltuk, hogy hogyan kell konfigurálni általános WF-tevékenységeket


különböző típusú projektekben. Bár ezek a beépített tevékenységek sok WF­
alkalmazás számára jelentenek biztos kezdőpontot, nem gondoskodnak
minden lehetséges körülményrőL Szerencsére a WF-közösség új egyedi tevé­
kenységeket hoz létre, sok közülük ingyen letölthető, más tevényekségekhez
pedig harmadik félen keresztül férhetünk hozzá különböző árakon.

Megjegyzés Ha szeretnénk további munkafolyamat·tevékenységeket vizsgálni, jó kiindulási


pont a http://wf.netfx3.com címen található webhely. Itt szép számmal tölthetünk le további
tevékenységeket, amelyek bővítik a termékkel szállított választékot.

..,
_

Fi�ered by.

(unfiltered}
1!1-- o�elopment Toots and Languages
$-· Enterprise �rvers and Development n

IB- Mobile and Embedded Development


l';l· .NET Development
. B· .NET Framework SDK
B· .NET Framework
�h· .NET Framework Programming
$�Windows Presentation Foundation
�.. Windows Communication Foundation
0.. Windows Workflow Foundation
d:J-.Samples
ij).. CrossTechnology Samples
@.. Windows Communication Foundation Samples
d:J- Windows Workflow Foundation Samples
W·· Application Samples
d:J.. Technalogy Samples
ffi.. Activities
ffi. Activity Sinding
!ÍJ·· Basic Workflows
ffi. Communications
�..Affi'·'"i·G!MM
·!-·Basic Activity Designer Sampio
t- File System Watcher Activíty Sampio
, :- Simple Activity Sample
j L.. Send E-mail Activity Sample
!ÍJ.. Designer Hosting
� Contents l�lndex.J[illHelp Favorites1
26.29. ábra: A NET Framework 3.5 SOK dokumentáció több
. munkafolyamat-példát biztosít

Az online WF-közösségektől beszerezhető kiegészítő tevékenységek nagy


száma ellenére lehetséges (és néha szükséges is) egyedi tevékenységeket létre­
hozni. A Visual Studio 2008 rendelkezik egy Workflow Activity Library pro­
jektsablonnal pontosan erre a célra. Ha ezt a projekttípust válasz�uk, egy ter-

390
Összefoglalás

vezői felületet kapunk, amellyel létrehozhatjuk egyedi tevékenységünket ha­


sonló megközelítéssel, mint amellyel magát a munkafolyamatot hozzuk létre
(új tevékenységek hozzáadása, a tevékenységek hozzákapcsolása kódhoz stb.).
Hasonlóan egy egyedi Windows Forms-vezérlőelem készítésének folyama­
tához, az egyedi tevékenységhez is hozzárendelhető több .NET-attribútum,
amelyek azt felügyelik, hogyan kell az összetevőt integráini az integrált fejlesz­
tői környezetben - például melyik bittérkép képe jelenjen meg az eszköztáron,
melyik konfiguráció-párbeszéd jelenjen meg (ha megjelenik egyáltalán), ami­
kor egy tulajdonságot kanfigurálunk a Properties ablakban és így tovább.
Ha szeretnénk többet megtudni az egyedi tevékenységek létrehozásáról, a
.NET Framework 3.5 SDK dokumentáció nagyon sok érdekes példával szol­
gál, beleértve egy "Send E-mail Activity" tevékenység létrehozását is. Továb­
bi részletekért egyszerűen keressük a megadott dokumentáció WF Samples
csomópontjában található Custom Activities példákat (lásd a 26.29. ábrát).

Összefogla l ás

A Windows Workflow Foundation (WF) egy olyan API, amely a .NET 3.0

verzióval jelent meg. Lényegében a WF teszi lehetővé egy alkalmazás belső


üzleti folyamatainak közvetlen modellezését magában az alkalmazásban. Az
általános munkafolyamat puszta modellezésén túl a WF teljes futtatómotort
tartalmaz, és több szolgáltatást biztosít, amelyek kerek egésszé teszik az API
funkcionalitását (tranzakciószolgáltatások, rögzítési szolgáltatás és nyomozá­
si szolgáltatás stb.). Bár ez a bevezető fejezet nem vizsgálja részletesen ezeket
a szolgáltatásokat egy termékszintű WF-alkalmazás egészen biztosan hasz­
nálja ezeket a lehetőségeket.
Munkafolyamat-alkalmazás létrehozásához a Visual Studio 2008 számos
tervezői eszközt nyújt, beleértve egy munkafolyamat-tervezőt, konfigurálást
a Properties ablak segítségével és (a legfontosabbat) a Windows Workflow
eszköztárát Több beépített tevékenységet találunk, amelyek hozzájárulnak egy
meghatározott munkafolyamat teljes felépítéséhez. Miután modelleztük a
munkafolyamatot, futtathatjuk a munkafolyamat-példányt a workflowRuntime
típus segítségéve!, a választott hoszt felhasználásával.

391
6. rész

Felhasználói
felületek
HUSZONHETEDIK FEJEZET

Windows Forms-programozás

A .NET platform 2001-es megjelenése óta az alaposztálykönyvtárak egy sajá­


tos, Windows Forros nevű API-t is tartalmaznak (amelyet a system.windows.
Fo rrns.dll szerelvény képvisel). A Windows Forros eszközrendszere biztosí�a

azokat a típusokat, amelyek a grafikus felhasználói felület (GUI) készítésé­


hez, az egyedi vezérlőelemek létrehozásához, az erőforrások (sztringtábláza­
tok, ikonok stb.) kezeléséhez, valamint egyéb, GUI-központú programozási
feladatok elvégzéséhez szükségesek. Emellett egy GDI+ nevű, önálló (a Sys­
tem.Drawing.dll szerelvényben található) API további típusokat biztosít a

programozóknak a 2D-s grafikák létrehozására, a hálózati nyomtatókkal tör­


ténő kommunikációra, valamint a képadatok kezelésére.
A Windows Forros (és a GDI+) API-k a mai napig, a .NET 3.5 megjelené­
sekor is léteznek, és az alaposztály könyvtárban még jó ideig (valószínűsíthe­
tően örökre) jelen lesznek. A .NET 3.0 megjelenése óta azonban a Microsoft
egy vadonatúj CUI-eszközrendszert is rendelkezésre bocsátott, Windows
Presentation Foundation (WPF) névvel. A WPF óriási hajtóerőt biztosít a fej­
lesztés alatt álló felhasználói interfészek számára (lásd a következő fejezetet).
Most a középpontban azonban a hagyományos Windows Forros API-k áll­
nak, ugyanis számos GUI-alkalmazás egyszerűen nem igényli azt a hajtóerőt,
amelyet a WPF kínál. A WPF igazság szerint jó néhány grafikus felhasználói
felülettel rendelkező alkalmazás számára túlzás volna. Továbbá a .NET uni­
verzumban számos fenntartandó Windows Forros-alkalmazás létezik.
A jelen fejezetben megismerkedünk a Windows Forros programozási mo­
dellel, dolgozunk a Visual Studio 2008 beépített tervezőalkalmazásaival, kísér­
letezünk számos Windows Forros-vezérlőelemmel, valarnint áttekin�ük a
GDI+-t alkalmazó grafikus programozást. Az információk egységes megjelení­
téséhez létrehozunk egy (korlátozott funkcionalitással bíró) rajzolóalkalmazást

Megjegyzés A könyv korábbi kiadásai három (meglehetösen hosszú) fejezetet szenteltek a


Windows Forms API-nak. Mivel a WPF a .NET-es grafikusfelület-fejlesztés preferált eszközrend­
szere, ez a kiadvány a Windows Forms/GDI+ témának csupán ezt a fejezetet szenteli. A könyv vá­
sárlói, az Apress webhelyéről ingyenesen Letölthetik a korábbi, PDF-formátumú Windows Forms/
GDI+ fejezeteket.
27. fejezet: Windows Forms programozás

A Windows Forms-névterek

A Windows Forms API több száz típust (osztályt, interfészt, struktúrát, felsorolt
típust és metódusreferenciát) tartalmaz, amelyek a System. windows. Forms. dll
szerelvény különböző névtereibe rendeződnek A 27.1. ábra ezen névtereket áb­
rázolja a Visual Studio 2008 objektumböngészőn keresztül.

l Browse:
���·--�
���
P�
·� L-------------------��x
.NET Framew ork ... ! 4o1 ..
I
• .>:.:J 1 t:iJ •

<Search>
J
1

@ -o System.Web. obile
M
@ -o System.Web.RegularExp ressions
@·O System.Web.Services
s-o System.Winde>ws.Fe>rms
é-·O System.Resources
0 O System.Windows.Forms
l :.•' �
o nent ode. o m nte o
-· -.�; ��=::.:�::::.::;::.�:7gpn M i C 2J r p 1 Assembl C : \
y System.Windows.Forms
Windows\Mi crosoft.NET

Í
é-{} System.Windows.Forms.Lay out
ffi_ --O System.Windows.Forms.PropertyGridinternal
f ffi {} System.Windows.Forms.Visua iStyles
·
�:
\Framework\v2.0.S0727
\System.Windows.Forms.dll
é_- -_o_s _ yst_ •_m_.x_m_l .
_,_j Attrlbutes:
L _______
__ _________ _____ _

27 1 ábra: A System.Windows.Forms.dll Windows Forms névterei


. .

A legfontosabb névtér mindenképpen a system. windows. Forms. A System. win­


dows. Forms névtérben található típusokat az alábbi tágabb kategóriákba so­
rolhaljuk:

• Alapvető infrastruktúra: Ezek a típusok adják a Windows Forms-prag­


ram ( Form, Ap pl i cation stb.) alapvető működését, valamint azokat a
különböző típusokat, amelyek megkönnyítik az együttműködést az
örökölt ActiveX vezérlőelemekkeL

• Vezérlőelemek: Ezek a típusok gazdag felhasználói felületeket hoznak létre


(sutton, Menustri p, ProgressBar, DataGri dview stb.), amelyek mindegyike a
control ősosztályból származik. A vezérlőelemek tervezési időben konfi­
gurálhatók, és láthatók (alapértelmezés szerint) futási időben.

• Komponensek: Ezek a típusok nem a control ősosztályból származnak, de


ugyanúgy a Windows Forms vizuális funkcióiért felelősek (ToolTip,
ErrorProvider stb.). Számos komponens (például a Timer és a Back­
groundworker ) futási időben nem látható, de tervezési időben vizuálisan
konfigurálható.

396
Egyszerű Windows Forms-alkalmazás (IDE-mentes) létrehozása

• Általános párbeszédablakok: A Windows Forros jó néhány előre gyártott


párbeszédablakot kínál az általános műveletekhez (openFileDialog,
PrintDialog, colorDialog stb.). Igény szerint, ha a standard párbeszéd­

ablakok nem felelnek meg, természetesen egyedi párbeszédablakokat


is készíthetünk

Mivel a system.windows. Forms által kínált típusok teljes száma jóval megha­
ladja a százat, felesleges volna a Windows Forros-család listájához tartozó va­
lamennyi tagot felsorolni. A fejezet áttanulmányozása után olyan szilárd ala­
pot kapunk, amelyre később nyugodtan építhetünk További információkért
lapozzuk fel a .NET Framework 3.5 SDK dokumentációját.

Egyszerű Windows Forms-alkalmazás


(IDE-mentes) létrehozása

Elvárásainknak megfelelőerr a modern .NET IDE-k (rnint pl. a Visual Studio


2008, a C# 2008 Express vagy a SharpDevelop) számos űrlaptervezőt, vizuá­
lis szerkesztőt és integrált kódgeneráló eszközt (azaz varázslót) kínál a Win­
dows Forros-alkalmazások könnyebb szerkesztése érdekében. Hasznosságuk
ellenére ezek az eszközök meg is nehezíthetik a Windows Forms-elsajátítását,
mert nehezebb felfedezni a lényeget az általuk generált nagy mennyiségű
sablonkód között.
Az első Windows Forros-példát egy egyszerű szövegszerkesztővel és a C#
parancssoros fordítóval hozzuk létre (a esc. exe alkalmazással történő munka
leírása az előző kötet 2. fejezetében található).
Először hozzunk létre egy SimpleWinFormsApp mappát (közvetlenül a C
meghajtón), nyissuk meg a Visual Studio 2008 parancssort, majd a kívánt
szövegszerkesztővel hozzunk létre egy simpleWFApp.cs nevű fájlt. Az új fájl­
ban állítsuk össze az alábbi forráskódot, majd mentsük el a SimpleWin­
FormsApp mappába.

ll Minimálisan szükséges névterek.


using system;
using System.Windows.Forms;
namespace SimpleWFApp
{
ll Ez az alkalmazásobjektum.
class Program

397
27. fejezet: Windows Forms pr ogramozás

{
static void Main()
{
Application.Run(new Mainwindow());
}
}

ll Ez a főablak.
class Mainwindow Form {}
}

Ez a lehető legegyszerűbb Windows Forms-alkalmazás forráskódja. Minimáli­


san is szükségünk van egy olyan osztálytípusra, amely kibővíti a Form ősosz­
tályt a Main() metódusra, amely a statikus Application.Run() metódus hívása
(a Form és az App lication részleteit lásd később). Az alkalmazást az alábbi uta­
sítással fordítha�uk le (az alapértelmezett reakciófájl [esc. rsp] automatikusan
hivatkozik szám.os .NET-szerelvényre, köztük a system. windows. Forrns.dll és a
system. Drawing. dll szerelvényre; lásd az előző kötet 2. fejezetében):

esc /target:winexe *.cs

Megjegyzés Technikai értelemben a parancssornál a / target: exe lehetőség segítségével ké­


szíthetünk Windows-alkalmazást; ekkor azonban azt tapasztaljuk, hogy a háttérben levő parancs­
ablak láthatóvá válik (és az is marad, amíg be nem zárjuk a főablakot). A /target :wi nexe meg­
adásával a végrehajtható fájl mint eredeti Windows Forms-alkalmazás futtatható (a látható pa­
rancsablak nélkül).

Ha futtatni szeretnénk az alkalmazást, egy átmérétezhető, minimalizálható,


maximalizálható és bezárható főablakot kapunk (lásd a 27.2. ábrát).

27.2. ábra: Nagt;on egt;szerű Windows Fonns-alkalmazás

Az aktuális alkalmazás jól illusztrálja, milyen egyszerű lehet egy Windows


Forms-alkalmazás. Ezután adjunk a Mainwindow típushoz olyan egyedi konst­
ruktort, amely lehetövé teszi, hogy a hívó különféle tulajdonságokat állítson
be a megjelenítendő ablakban. Például:

398
Egyszerű Windows Forms-alkalmazás (IDE-mentes) létrehozása

ll Ez a főablak.
class Mainwindow : Form
{
public Mainwindow(string title, int height, int width)
{
ll Határozzunk meg néhány tulajdonságot a szülőosztályból.
Text = title;
width = width;
Height = height;

ll származtatott metódus az űrlapnak a képernyő


ll közepére helyezéséhez.
centerToScreen();
}
}

Ezután módosítsuk az Application. Run() hívását a következőre:

static void Main()


{
Application.Run(new Mainwindow("My window", 200, 300));
}

Minden jól működő ablak különböző felhasználói interfészelemeket (menü­


rendszereket, állapotsávokat, gombokat stb.) igényel a bevitelhez. Annak
megértéséhez, hogy egy Formból származó típus hogyan tartalmazhat ilyen
elemeket, meg kell értenünk a controls tulajdonság, valamint a mögötte álló
vezérlőelemek szerepét.

A vezérlőelemek gyűjteményének feltöltése

A system. windows. Forms. control alaposztály (amely a Form típus származta­


tási lánca) határozza meg a ControZs tulajdonságot. Ez a tulajdonság egyedi
gyűjteményt burkol be a controlscollection nevű control osztályba be­
ágyazva. A gyűjtemény (ahogy a neve is sugallja) hivatkozik minden egyes, a
származtatott típus által fenntartott felhasználóifelület-elemre. Ahogy a többi
tároló, ez a típus is támogat számos olyan metódust, mellyel felhasználói­
felület-elemeket tudunk beszúrni, eltávolítani, valamint megkeresni (lásd a
27.1. táblázatot).

399
27. fejezet: Windows Forms programozás

Add() Új, a control osztályból származó típust (vagy típustömböt)


AddRange O szúr be a gyűjteménybe.

clear O Eltávolítja a gyűjtemény valamennyi bejegyzését.

count Visszaadja a gyűjtemény elemeinek a számát.

GetEnumerator() Visszaadja a gyűjtemény IEnumerator interfészét.

Remove() Vezérlőelemet távolít el a gyűjteménybőL


RemoveAt()

27 .1. táblázat: ControlCollection tagok

Ha Form-leszármazott típusokat szeretnénk hozzáadni a felhasználói felület­


hez, az alábbi, jól meghatározott lépések sorát kell követnünk:

• Határozzuk meg a felhasználói felület elemének egy, a Formból szár­


mazó osztályon belüli tagváltozóját.

• Kanfiguráljuk a felhasználói felület elemének megjelenését és műkö­


dését.

• A controls. Add O metódus segítségével adjuk a felhasználói felület

elemét az űrlap control scoll ection tárolójához.

Tételezzük fel, hogy a File > Exit menürendszer támogatásához módosítani


szeretnénk a Mainwi ndow osztályt. A releváns módosítások, valamint a hozzá
tartozó magyarázatok a következők:

class Mainwindow : Form


{
ll Egyszerű menürendszer tagjai.
private Menustrip mnuMainMenu = new Menustrip();
private ToolstripMenurtem mnuFile = new ToolStripMenurtem();
private ToolstripMenurtem mnuFileExit = new ToolStripMenuitem();

public Mainwindow(string title, int height, int width)


{

ll A menürendszer létrehozásának metódusa.


BuildMenusystem();
}

400
Egyszerű Windows Forms-alkalmazás (IDE-mentes) létrehozása

private void BuildMenusystem()


{
ll Adjuk a File menüelemet a főmenühöz.
mnuFile.Text = "&File";
mnuMainMenu.Items.Add(mnuFile)�

ll Most adjuk az Exit menüt a File menühöz.


mnuFileExit.Text = "E&xit";
mnuFile.DropDownitems.Add(mnuFileExit);
mnuFileExit.Click +=

new System.EventHandler(this.mnuFileExit_Click);

ll végül állítsuk be az űrlap menüjét.


controls.Add(this.mnuMai nMenu);
MainMenuStrip = this.mnuMainMenu;
}

ll A File l Exit esemény kezelője.


private void mnuFileExit_click(object sender, EventArgs e)
{
Application.Exit();
}
}

A Mainwindow típus ez alkalommal három tagváltozót tartalmaz. A Menustrip tí­


pus a menürendszer teljességét ábrázolja, ahol egy adott Too l stripMenuitem jele­
rúti meg a hoszt által támogatott főmenü- (pl. File) vagy almenüelemet (pl. Exit).

Megjegyzés Korábban a Mai nMenu típus tetszőleges számú Me nurtern objektum tartására szol­
gált. A Menustri p vezérlőelem (amelyet a .NET 2.0-ben ismerhettünk meg) hasonló a Mai nMenu
típushoz, de a Menustri p "normál menüelemeken" kívüli vezérlőelemeket (legördülő listadobo­
zokat, szövegdobozokat stb.) is tartalmazhat.

A menürendszer konfigurációja a BuildMenusystem() segédfüggvényen belül


történik. Minden ToolStri pMenuitem szövegét a Text tulajdonságorr keresztül
adhatjuk meg, s ezek mindegyikéhez olyan sztringliterál tartozik, amely be­
ágyazott & karaktert tartalmaz. Ez a szintaxis határozza meg az Alt billentyű­
höz tartozó gyorsbillentyűt, azaz az Alt + F kombinációval aktiválhatjuk a File
menüt, míg az Alt + X billentyűkombináció az Exit menüt aktiválja. A File
Toolstri pMenuitem objektumhoz (mnuFile) alelemeket a oropoownitems tulaj­
donság révén adtunk. A Menustri p objektumhoz pedig az ltems tulajdonsá­
gorr keresztül adtuk hozzá a főmenüelemeket
A kész menürendszer (a controls tulajdonságorr keresztül) egy vezérlőelem­
gyűjteményhez adódik; majd a Menustrip objektumot az örökölt Mai nMenustrip
tulajdonsághoz rendeljük. Bár ez felesleges lépésnek tűnik, ám egy olyan megha-

401
27. fejezet: Windows Forms programozás

tározott tulajdonság révén, mint a MainMenustrip, lehetőség van dinamikusan


meghatározni, hogy melyik menürendszer jelenjen meg a felhasználó számára,
például a felhasználói vagy a biztonsági beállítások alapján.
A File > Exit menü cl ick eseményét azért kezeljük, hogy értesüljünk róla,
hogy a felhasználó mikor válasz�a ki ezt az almenüt. A cl ick esemény a szo­
kásos, system.EventHandl er nevű metódusreferencia-típussal működik együtt.
A click esemény csak azokat a metódusokat hívja, amelyeknek system.object
az első és system.EventArgs a második paramétere. Tehát implementáltuk a
metódusreferencia célját (mnuFil eExit_ cl ick) annak érdekében, hogy a statikus
Application. Exit() metódus felhasználásával kilépjünk az egész Windows­
alkalmazásbóL
Az alkalmazás újrafordítása és futtatása után, látha�uk, hogy az egyszerű
ablak egy egyedi menürendszert jelenít meg (lásd a 27.3. ábrát).

� MyWindow


27.3. ábra: Egyszerű ablak egyszerű menürendszerrel

A System.EventArgs és a System. EventHandter


szerepe

A system.EventHandl er egyike annak a számos metódusreferencia-típusnak,


amelyeket az eseménykezelő folyamat során a Windows Forms (és az
ASP.NET) API-kan belül használunk. Ez a metódusreferencia csak olyan me­
tódusokra mutathat, ahol az első argumentum az eseményt küldő típusra hi­
vatkozó system.object típus. Ha például módosítani szeretnénk a mnuFile­
Exit_cl ick() metódus implementációját az alábbiak szerint:

private void mnuFileExit_Click(object sender, EventArgs e)


{
MessageBox.show(string.Format("{O} sent this event",
sender.ToString()));
Application.Exit();
}

402
Egyszerű Windows Forms-alkalmazás (IDE-mentes) létrehozása

akkor megerősithetnénk, hogy a mnuFi leExi t típus küldte az eseményt,


ugyarús az alábbi sztring:

"E&xit sent this event"

jelenik meg az üzenetdobozban. Kérdés, mire jó a második, a System. Event­


Args argumentum. A System. EventArgs típus szerepe valójában kicsi: csak az
objectet terjeszti ki, de gyakorlatilag semmit nem ad hozzá magához a funkci­
onalitáshoz:

public class EventArgs


{
public static readonly EventArgs Empty;
static EventArgs();
public EventArgs();
}

Ám ez a típus kifejezetten hasznos a .NET-eseménykezelés teljes sémájában,


ugyanis a system. EventArgs a szülője sok más (igencsak hasznos) származta­
tott tipusnak. A MouseEventArgs típus például kibővíti az EventArgs típust,
hogy az egér aktuális állapotára vonatkozó részletekkel szolgáljon. A Key­
EventArgs szintén az EventArgs típust terjeszti ki, és a billentyűzet állapotára
(azaz a lenyomott billentyűkre) vonatkozó részletekkel szolgál; a PaintEvent­
Args az EventArgs kiterjesztésével grafikus adatokat hoz létre, és így tovább.
Számos EventArgs-leszármazott (és az őket használó metódusreferencia) nem
a Windows Forms, hanem a WPF és az ASP.NET API-k működése során fi­
gyelhető meg.
Ha egyszerű szövegszerkesztővel szeretnénk minél több funkcionalitást
(állapotsávokat, párbeszédablakokat stb.) építeni a Mainwindow típusba, ma­
nuálisan kellene összeállítanunk valamennyi, a vezérlőelemeket konfiguráló
logikát. Szerencsére a Visual Studio 2008 számos integrált tervezővel rendel­
kezik, amelyek megoldják ezeket a részleteket.

Forráskód A SimpleWinFormsApp projektet a forráskódkönyvtár 27. alkönyvtára tartalmazza.


A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

403
27. fejezet: Windows Forms programozás

A Visual Studio Windows Forms­


projektsablona

Ha ki szeretnénk használni a Visual Studio 2008 Windows Forms-tervezőesz­


közeit, először válasszuk ki a Windows Application projektsablont a File >

New Project menüelem segítségéveL Hogy megismerjük az alapvető Win­


dows Forms-tervezőeszközöket, hozzunk létre egy új, SimpleVSWinForms­
App nevű alkalmazást (lásd a 27.4. ábrát).

New Project �
Project typ<s: Templ.nes: ,.NfT F,_rl:3.5 ·rn!ID[B
Visual Basic . Visual Studio installed templat� -·

iJ
-·-
Windows
Database
�-- - Q l
.....
4!c# � �
Test Windows Classlibrary ASP.NET ASP.NET WPF WPF Browser
WCF Fomls W•bAppl... W<b s.rv;... Application Application
W•b p on
A ';;
Workflow
VrsuaiC#
'(!.lll � �
Console WCF Service Windows
=

Windows Application Application Forms ...


W<b
Database MyT•mplatos

Test
WCF SJ "

·Workflow Search
Online Te...
VtSuaiC++
Othor Project Typos .

a
A project for creating an application with a Windows Forms user interfi!tce (.NET Fr mcwork 35)

Name: SimpleVSWinFormsApp

location: C:\MyCod• .
l Browse..•
l
Solution Nami!:: l SímpleVSWinFormsApp l O Create dirffiory for solution

[J Add to Source Control

l Ol(
ll Concol
l
27.4. ábra: A Visual Studio 2008 Windows Forms-projektsablona

A vizuális tervezőfelület

Készítsük el ismét az előző alkalmazást, de most már használjuk a tervezőesz­


köz nyújtotta lehetőségeket. Ha létrehoztuk a Windows Forms projektet, a Vi­
sual Studio 2008 olyan tervezőfelületet jelenít meg, amelyen számos vezérlő­
elemet elhelyezhetünk az áthúzás funkció segítségéveL Ugyanezzel a tervező­
vel a készülő alkalmazásablak kezdeti méretét is beállíthatjuk a széleken talál­
ható kapaszkodó megragadásával és arrébb húzásával (lásd a 27.5. ábrát).

404
A Visual Studio Windows Forms-projektsablona

•X

27.5. ábra: A vizuális Forms-teroezó

Az ablak (és az űrlapon található bármelyik vezérlőelem) megjelenésének és


működésének konfigurálását a Properties ablakban tehe�ük meg. Ennek az
ablaknak a segítségével a tulajdonságokhoz értékeket rendelhetünk, valamint
egy adott vezérlőhöz eseménykezelőket hozhatunk létre (lásd részletesen ké­
sőbb). Ha a tervezőfelület vezérlőelemek gyűjteményével rendelkezik, akkor
a konfigurálni kívánt elemet a Properties ablak tetején található legördülő lis­
tamező segítségével választha�uk ki.
Az űrlap jelenleg nem rendelkezik tartalommal, ezért csak egy a kezdeti
űrlaphoz tartozó listát látunk. Alapértelmezés szerint az űrlap a Forml nevet
kapta, ahogy ez az írásvédett ( Name) tulajdonságban is olvasható (lásd a 27.6.
ábrát).

Properties

Forml System.Windows.Forms.Form

�l� [ll] l� ,1 ! Q
11±1 {ApplicationSettings) ��
" 1±1 {DataBindings) @
(Name. Form l
li AcceptButton {none)
AccessibleDescription
AccessihleName
AccessíbleRole Default
AllowDrop False
AutoScaleMode Font
(lUme)
l Indicates the name used in code to identify the object.

27.6. ábra: A Properties ablak segítségével beállíthatjuk a tulajdonságokat és az eseménykezelőket

Megjegyzés A Properties ablakot a legördülő listamező alatti első két gomb segítségével
aszerint is konfigurálhatjuk, hogy a tartalom kategóriánként vagy alfabetikus sorrendben je­
lenjen meg. Érdemes az elemeket ábécésorrendbe rendezni, hogy könnyen megtaláljunk egy
adott tulajdonságot vagy eseményt.

405
27. fejezet: Windows Forms programozás

A következő lényeges tervezőelem a Solution Explorer ablak. Bár minden Vi­


sual Studio projekt támogatja ezt az ablakot, a Windows Forros-alkalmazások
létrehozásakor különösen hasznos, hogy egyrészt az ablakokban található fáj­
lok és a kapcsolódó osztály nevét gyorsan megváltoztathatjuk, másrészt
megnézhetjük a tervező által karbantartott forráskódot tartalmazó fájlt (lásd
részletesen később). Jobb egérgombbal kattintsunk a Forml. cs ikonra, és vá­
lasszuk a Rename lehetőséget. Ennek a kezdeti ablaknak adjuk a sokkal in­
kább megfelelő Mainwindow. cs nevet. A végeredmény a 27.7. ábrán látható.

Solution Explorer

eJ SimpleVSW..FormsApp
�- � Properties
liJ.. r:iil References
�-· rrm '3M2!1.tJiJ
: ; � MaínWindow.Designer.cs
! L... � MainWindow.resx
L. '!J Program.cs

�Solution Explorer !G;fCiass View

27.7. ábra: A Solution Explorer ablak lehetővé teszi a Form-leszármazott típusok és


a kapcsolódó fájlok átnevezését

A kezdeti űrlap

A menürendszer kialakítása előtt vizsgáljuk meg, hogy az alapértelmezés sze­


rint mit is hozott létre a Visual Studio 2008. Először jobb egérgombbal kattint­
sunk a Mainwindow. cs ikonra a Solution Explorer ablakban, majd válasszuk a
View Code elemet. Az űrlap részleges típusként lett meghatározva, és ez lehe­
tővé teszi, hogy az egyes típusokat több kódfájlban határozzuk meg (lásd az

előző kötet 5. fejezetét). Az űrlap konstruktora meghívja az Initializecompo­


nent() metódust, a típusunk pedig "az egy" Form.

namespace simplevswinFormsApp
{
public partial class Mainwindow Form
{
public Mainwindow()
{
Initializecomponent();
}
}
}

406
A Visual Studio Windows Forms-projektsablona

Az Initia l izecomponent O definiálása egy önálló fájlban valósul meg, amely


kiegészíti a részleges osztálydefiníciót. Az elnevezés hagyományai szerint ez
a fájl mindig .Designer.cs végződésű, amelyet a kapcsolódó, Formból szár­
maztatott típust tartalmazó C#-fájl neve előz meg. A Solution Explorer ablak
segítségével nyissuk meg a Mainwindow.oesigner.cs fájlt. Nézzük meg az
alábbi (az egyszerűség kedvéért a megjegyzések nélküli) forráskódot:

partial class Mainwindow


{
private system.ComponentModel.IContainer components = null;

protected override void Dispose(bool disposing)


{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

private void Initializecomponent()


{
this.components = new System.componentModel.container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Forml";
}
}

Az IContainer tagváltozó és a DisposeO metódus egy kicsit többet jelent a


Visual Studio-tervezőeszközök által használt infrastruktúránáL Ám az Ini­
tial izecomponent O is jelen van, és figyelembe is kell venni. Nemcsak az űr­

lap konstruktora hívja meg ezt a metódust futási időben, hanem tervezési
időben a Visual Studio is ugyanezt a metódust használja annak érdekében,
hogy a Forms-tervezőben megfelelően jelenítse meg a felhasználói felületet.
Ennek illusztrálására módosítsuk az ablakban található Text tulajdonsághoz
rendelt értéket a következőre: "My Main Window". A tervező aktiválásakor
az űrlap címe ennek megfelelően frissül.
Ha pedig a vizuális tervezőeszközöket (pl. a Properties ablak) használjuk,
az IDE automatikusan frissíti az Initial izecomponentO metódust. Ennek il­
lusztrálására a Forms-tervező legyen az IDE aktív ablaka, és keressük meg a
Properties ablakban található opacity tulajdonságot. Az értéket írjuk át 0,8-ra
(80%), ennek hatására az ablak enyhén átlátszóvá válik, amikor legközelebb
lefordítjuk és futtatjuk a programot. Ha elvégeztük ezt a módosítást, vizsgál­
juk meg ismét az InitializecomponentO implementációját:

407
27. fejezet: Windows Forms programozás

private void Initializecomponent()


{
this.suspendLayout();
ll
ll Mainwindow
ll
this.AutoscaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.windows.Forms.AutoscaleMode.Font;
this.clientsize = new System.Drawing.Size(284, 264);
this.Name = "Mainwindow";
this.opacity = 0.8;
this.Text = "My Main window";
this.ResumeLayout(false);
}

Amikor a Visual Studiával hozunk létre Windows Forms-alkalmazásokat,


gyakorlatilag figyelmen kívül hagyha�uk (és jellemzően ezt is kell tennünk) a
*.Desig ner.cs fájlokat, mert az IDE karbantar�a őket helyettünk. Ha szintak­

tikailag (vagy logikailag) helytelen forráskódot állítunk össze az Initi ali ze­
component() metóduson belül, az a tervező összeomlásához vezethet. A Visu­

al Studio tervezési időben gyakran átformázza ezt a metódust. Ezért ha saját


forráskódot szeretnénk az Initializecomponent() metódushoz adni, meges­
het, hogy azt az IDE törölni fogja. Ne feledjük, hogy a Windows Forms-alkal­
mazás minden ablaka részleges osztályokból áll össze.

A Program osztály

Azon kívül, hogy a kezdeti, Form-leszármazott típus implementálja , a Windows


Application projekttípusai egy olyan statikus ( Program nevű) osztályt is biztosí­
tanak, amely meghatározza a program belépési pon�át: a Main() metódust:

static class Program


{
[STAThread)
static void Main()
{
Application.EnablevisualStyles();
Application.SetcompatibleTextRenderingDefault(false);
Application.Run(new Mainwindow());
}
}

A Main() metódus meghívja az Application.Run()-t, és elvégez néhány rende­

relési beállítást. Végül, de nem utolsósorban, a Main() metódus [STAThread]


attribútummal rendelkezik. Ez arról tájékozta�a a futtatókörnyezetet, hogy

408
A Visual Studio Windows Forms-projektsablona

ha ez a szál klasszikus COM-objektumot hoz létre (beleértve a korábbi ActiveX


felhasználói felületi vezérlőelemeket) saját élettartama során, akkor azokat
egy COM által fenntartott területre, az egyszálú tárrészbe (Single-Threaded
Apartment) helyezi. Röviden: ez biztosílja a COM-objektumok szálbiztos vol­
tát még akkor is, ha ennek biztosítására egy adott COM-objektum szerzője
nem gondoskodott a forráskódban.

Menürendszerek vizuális épitése

A továbbiakban aktiváljuk a Forms-tervezőablakot, keressük meg a Visual


Studio 2008 Toolbox ablakát, majd keressük meg a Menus & Toolbars cso­
móponton belül található MenuStrip vezérlőelemet (lásd a 27.8. ábrát).

Toolbox
-- ··-
ii
!±l Al W"IAdows Forms
ffi úxnmon Controls
3
1'


!±l Containers
El Menus & Tooloars
' 1\ Pointer -�·- . ..• ��· l

flí Conteot!MenuStrip l
�� MenuStrip l :i
i 6:: StatusStrip
'
ill Too1Sttip
'
l 0 TooiStripContainer i
�---�

l !±l Dato
l oo Components l
1±1 Printing Ld,
1±1 Diologs
l
1±1 CrysUI Reports
El Geneni

!
�Server Explorer )(< Toolbox j
27.8. ábra: A Toolbox ablak megjeleníti a tervezőfelülethez adható Windows Forms-vezérlóelemeket

Húzzuk a MenuStrip vezérlőelemet a Forms-tervező tetejére. A Visual Studio


a menüszerkesztő aktiválásával válaszol. Ha közelebbről megvizsgáljuk ezt a
szerkesztőt, egy (nagyon) kicsi háromszöget vehetünk észre a vezérlőelem
jobb felső sarkában. Az ikonra kattintva olyan kontextusérzékeny inline
szerkesztőt nyitunk meg, amely azonnal számos tulajdonság beállítását teszi
lehetővé (több Windows Forros-vezérlőelem hasonló inline szerkesztővel
rendelkezik). Erre példát az InsertStandardltems lehetőségre kattintva talá­
lunk, ahogy ez a 27.9. ábrán is látható.

409
27. fejezet: Windows Forms programozás

� �----��--\!.i!l_�-- ----- ·----[SJ[Gil �lMenuStrip Tasles


i l Type Here l
l. -- ----------------------------------------- Embed in TooiStripContainer

Ir. rt tanda ms

t Dock lTop
r;]
[iJJ
i GripStyle l Hidden Bi
Edithems...

i r_
9·---------------,
l � menuStripl 1i
�-------------------------------

27.9. ábra: Az inline menüszerkesztő

A Visual Studio egész menürendszert hozott létre helyettünk. Nyissuk meg a

tervező által karbantartott fájlt (Mainwindow.Designer.cs), és figyeljük meg az


Initializecomponent() metódushoz adott számos kódsort, valamint azt a jó
néhány tagváltozót, amelyek a menürendszert képviselik (bizony a tervező­
eszközök jó dolgok).

;x

rJJ My Main Wmdow EEJOOIID


JLType Here l
File

jEWI l l l Tvee Here l l


i l Type Here l l
l
l

� menuStripl

27.10. ábra: Menürendszer manuális készítése

410
A Visual Studio Windows Forms-projektsablona

Végül ugorjunk vissza a tervezőhöz, és a Ctrl + Z billentyűkombinációval


vonjuk vissza az előző műveletet. Így visszajutunk a kezdeti menüszerkesz­
tőhöz, és eltávolítjuk a generált forráskódot. A menütervezővel egyszerűen
csak beírjuk a fő File menüelemet, amelyet egy Exit almenü követ (lásd a
. 27.10. ábrát).
Az Initializecomponent() metódusban ugyanolyan forráskódot találunk,
mint amilyet manuálisan állítottunk össze a fejezet első példájában. A mosta­
ni feladat befejezéséhez ugorjunk vissza a Forms-tervezőhöz, és kattintsunk a
Properties ablakban található villámot ábrázoló gombra. Ez megjeleníti az
összes olyan eseményt, amelyeket kezelhetünk a kijelölt vezérlőelemen. Jelöl­
jük ki az Exit (alapértelmezésben exitTool Stri pMenurtem nevű) menüt, majd
keressük meg a cl ick eseményt (lásd a 27.11. ábrát).

Properties
if
exitTooiStripMenultem System.Windows.Forms.TooiStripMenuhem .

�)�[W:� [I] l SJ
BackColorChanged
·l
E.
CheckedChanged
-
CheckStateChanged

Chck exitTooiStripMenultem_Okk EJ!


DisplayStyleChanged

DoubleCiick

DropDownCiosed

DropDownltemCiicked
.:

Click
Occurs when the jtem is dicked.

27. 11. ábra: Események létrehozása az integrált fejlesztői környezet segítségével

Ekkor beírhatjuk annak a metódusnak a nevét, amelyet meg kell hívni, ha


rákattintanak az elemre; vagy egyszerűbben: kattintsunk kétszer a Properties
ablakban található eseménylista kívánt elemére. Ilyenkor az integrált fejlesz­
tői kömyezet kiválasztja helyettünk (a VezérlőelemNeve_EseményNeve() mintát
követő) eseménykezelő nevét. Az integrált fejlesztői kömyezet mindkét eset­
ben kódcsonkat hoz létre, amelybe beleilleszthetjük az konkrét implementá­
ciót. Például:

public partial class Mainwindow : Form


{
public Mainwindow()
{
Initializecomponent();
centerToScreen();
}

411
27. fejezet: Windows Forms programozás

private void exitToolStripMenurtem_Click(object sender,


EventArgs e)
{
Application.Exit();
}
}

Ha megnézzük az rnitializecomponent() metódust, látható, hogy a szüksé­


ges eseményfeliratkozás is megtörtént:

this.exitToolstripMenuitem.click +=

new System.EventHandler(this.exitToolstripMenuitem_click);

Annak ellenére, hogy nyilvánvalóan sokkal több parancsikon, szerkesztő és


integrált varázsló van az integrált fejlesztői környezetben, a fentiek már ele­
gendőek a továbblépéshez.

Az ürlapok anatómiája

A következőkben még részletesebben megvizsgáljuk a Form típust. A Win­


dows Forms világában a Form típus képviseli az alkalmazásablakokat, köztük
a főablakokat, a többdokumentumos felhasználói felülettel rendelkező (MDI)
alkalmazások gyermekablakait, valamint a modális és nem modális párbe­
szédablakokat

�· ,�Y�Br��
aa
t�� P�
-.lu_ __________
_ _____________ _____________ ���x
Browse .NET Framework .. . l "" • l :b i f§J • •

<Search>
... D <x r·'!. ActivateO
--� 1 !·-·i• ActivateMdiChild(System.Windows.Forms.Form)
· .'.-.
.. - �
i---s..,..-,
'� .------------------ G ,,.... •t AddOwnedForm(System.Windows.Forms.Form)
I?.'IP.'ll
.-
' B "e- Base Types
!·. ... �t
,. Ad;ustFormScrollbars(booO
'J
. j 8--:� ContainerControl
',:.. . ... CenterloParentQ
' .<>•
8 �ontro1
.-.. Scroll•bi-r
é-� Control
f··'f• CenterToScreenO
·
·
. . .4: Componeni
'
8
f· ·•• CloseQ

·

. S-4$ Ma,.haiByRefObject !-. • �· CreateControlslnstanceO


·
!---... � CreateHandleQ
áe Object ·
!-�· DefWndProc(System.Windows.Forms.Message)
·

t$l·-....O IComponent C j. . .._.. Disoose(booll


(E----o IDisposable "'
$···>-<> !Bindabi.Component
$--....O IComponent public class Form : Svstem.Windows.Forms.ContainerContro!
Member of System.Windows.Forms
$.. ..o !Oisposable [
riJ.. . a-<) IDropTarget:
$--·-o ISynchroniulnvoke
Summary:
Represents a window or dialog box that maltes up an application's
l
t!J.......o 1Win32Window user interface.
$----o IComponent
@ ....o !Disposable Attributes:
r±l--� IContainerControl [!j [System.ComponentModei.DesignercategoryAttribute('Fonn"),
LJ.-,,.......,,.,;;,;"'",.."""'"'"""'-
" ----------_:_
' __J System.ComponentModei.Oefau�EventAttribute("Load").

27.12. ábra: A System.Windows.Forms.Form származtatási lánca

412
Az űrlapok anatómiája

Ahogy azt a 27.12. ábra is muta�a, a Form típus nagy mennyiségű funkcionali­
tást örököl az ősosztályoktól, valamint az általa implementált, nagyszámú in­
terfésztől.
A 27.2. táblázat a Form származtatási láncában található szülőosztályokba
nyújt betekintést.

,,'
·�::�
System.Object Ahogy a .NET többi osztálya is, a Fonn
"
"az egy object.

system.MarshalByRefobject Az ebből az osztályból származtatott tí­


pusokhoz távolról lehet hozzáférni a tá­
voli típusra történő hivatkozással (és
nem egy helyi másolattal).

System.ComponentModel.component Ez az osztály biztosítja az rcomponent in­


terfész alapértelmezett implementációját
A .NET univerzumban a komponensek
olyan tipusok, amelyek támogatják a ter­
vezésidejű szerkesztést, de futási időben
nem feltétlenül láthatók.

System.windows.Forms.Control Ez a típus biztosítja valamennyi Win­


dows FormsUI-vezérlőelem általános
felhasználói felületének tagjait, beleértve
magát a Form típust is.

System.windows.Forms.scrollable­ Ez az osztály határozza meg a vízszintes


Control és a függőleges görgetősávra vonatkozó
támogatást, valamint azokat a tagokat,
amelyek lehetővé teszik a görgethető te­
rületen lévő minta kezelését.

System.Windows.Forms.Container­ Ez az osztály biztosítja azoknak a vezér­


Control lőelemeknek a fókuszkezelését, amelyek
más vezérlőelemek tárolójaként képesek
működni.

system.windows.Forms.Form Ez az osztály képviseli az egyedi űrlapo­


kat, a többdokumentumos felhasználói
felülettel rendelkező (MDI) alkalmazások
gyermekablakait és a párbeszédablako­
kat.

27.2. táblázat: Alaposztályok a Form származtatási láncban

413
27. fejezet: Windows Forms programozás

Bár a Form típus teljes származtatása számos ősosztályt és interfészt foglal


magába, mégsem kell megismernünk minden egyes szülőosztály vagy imp­
lementált interfész minden egyes tagjának a szerepét ahhoz, hogy kiváló
Windows Forms-fejlesztővé válhassunk. A napi rendszerességgel használt
tagok nagy részét (különösen a tulajdonságokat és az eseményeket) valójá­
ban könnyedén beállíthatjuk a Visual Studio 2008 Properties ablakában. Ami
különösen fontos, hogy megismerjük a control és a Form szülőosztályok által
nyújtott funkcionalitást.

A Control osztály funkcionalitása

A system.windows.Forms.control osztály határozza meg a CUI-típusokhoz


szükséges általános viselkedéseket A control alapvető tagjai lehetővé teszik
a vezérlőelemek méretének és pozíciójának konfigurálását, a billentyűzet és
az egér inputjának rögzítését, a tagok fókuszálásának/láthatóságának meg­
szerzését vagy beállítását és így tovább. A 27.3. táblázat néhány érdekesebb,
a kapcsolódó működés alapján csoportosított tulajdonságot határoz meg.

Backcolor Ezek a tulajdonságok (színek, a szöveg betűtípusa, a megjelení­


Forecolor tendő egérkurzor, amikor az egér a céleszköz felett áll stb.) ha­
Backgroundrmage tározzák meg a vezérlőelemek alapvető felhasználói felületét
Font

cursor

Anchor Ezek a tulajdonságok határozzák meg azt, hogy a vezérlőele­


Dock meket hogyan kell a tároJón belül pozicionálni.
Autosize

Top Ezek a tulajdonságok határozzák meg a vezérlőelem aktuális


Left dimenzióit.
Bottom

Right

Bounds

clientRectangle
Height

width

414
Az ürlapok anatómiája

Enabled Ezeknek a tulajdonságoknak rnindegyike olyan logikai értéket ad


Facu s e d vissza, amely az aktuális vezérlőelem állapotát határozza meg.
vi s ibl e

Modi fi erKeys Ez a statikus tulajdonság ellenőrzi a váltóbillentyűk (Shift, Ctrl


és Alt) aktuális állapotát, és ezt egy Keys típusban adja vissza.

MouseButtons Ez a statikus tulajdonság ellenőrzi az egérgombok (a bal, jobb


és középső egérgomb) aktuális állapotát, és ezeket egy Mouse­
Buttons típusban adja vissza.

Tabindex Ezek a tulajdonságok a vezérlőelemek tabulátorsorrendjének


Tabstop konfigurálására szolgálnak.

Op a c i ty Ez a tulajdonság határozza meg a vezérlőelemek átlátszóságát


(0,0: teljesen átlátszó; 1,0: teljesen átlátszatlan).

Text Ez a tulajdonság jelzi a vezérlőelemhez rendelt sztringadatokat.

controls Ez a tulajdonság teszi lehetövé a hozzáférést az erősen típusos


gyűjteményekhez (controlscollecti on) , amelyek tetszőleges
számú gyermek-vezérlőelemet tartalmaznak az aktuális vezér­
lőelemen belül.

27.3. táblázat: A Control típus alapvető tulajdonságai

A control osztály számos olyan eseményt is meghatároz, amelyek lehetővé


teszik (többek között) az egérrel, a billentyűzettet a képpel és az áthúzással
kapcsolatos tevékenységek elkapását. A 27.4. táblázat néhány érdekesebb, a
kapcsolódó működés alapján csoportosított eseményt tartalmaz.

eli ck Olyan események, amelyek lehetövé teszik az interakciót az


Doubleeli ck egérreL
MouseEnter

MauseLeave
Mouseoown

Mouseup

MouseMove

MouseHover

Mousewheel

415
27. fejezet: Windows Forms programozás

KeyPress Olyan események, amelyek lehetövé teszik az interakciót a bil­


Keyup KeyDown lentyűzetteL

DragEnter Olyan események, amelyek az áthúzás tevékenységét ellenőrzik.


Dragleave
Dragover

Paint Olyan esemény, amely lehetövé teszi a GDI+ grafikus


renderelő szolgáltatásaival folytatott interakciót

27 .4. táblázat: A Control típus eseményei

Végül a control ősosztály számos olyan metódust is meghatároz, amelyek lehe­


tővé teszik a control-leszármazott típusok használatát A control típus metódu­
sai közül jó néhány rendelkezik on prefixummal, amelyet aztán egy adott ese­
mény neve követ (onMouseMove, OnKeyup, OnPaint stb.). Ezek az on prefixummal
rendelkező virtuális metódusok a hozzájuk tartozó események alapértelmezett
eseménykezelői. Ha felüldefiniáljuk ezeket a virtuális tagokat, lehetőségünk nyí­
lik az események szükség szerinti elő- vagy utófeldolgozására, mielőtt (vagy mi­
után) meghívjuk a szülő alapértelmezett implementációját

public partial class Mainwindow : Form


{
protected override void OnMouseDown(MouseEventArgs e)
{
ll Adjunk egyedi forráskódot a MouseDown eseményhez.

ll Ha elkészültünk, hívjuk meg a szülő implementációját.


base.OnMouseDown(e);
}
}

Ez bizonyos körülmények között hasznos lehet (különösen, ha standard vezér­


lőelemből származó, egyedi vezérlőelemet készítünk), az eseményeket ugyanis
gyakran kell a standard C#-eseményszintaxissal kezelnünk (a Visual Studio­
tervezőknek valójában ez az alapértelmezett viselkedése). Ha ily módon kezel­
jük az eseményeket, a szülő implementációjának befejezését követően a keret­
rendszer meg fogja hívni az egyedi eseménykezelőt Nézzünk egy példát arra,
hogy hogyan kezelhetjük manuálisan a MouseDown eseményt:

416
Az ürlapok anatómiája

public partial class Mainwindow : Form


{
public Mainwindow()
{
Mouseoown += new MouseEventHandler(Mainwindow_Mouseoown);
}

private void Mainwindow_Mouseoown(object sender, MouseEventArgs e)


{
ll Adjunk forráskódot a Mouseoown eseményhez.
}
}

Felsorolunk néhány másik, ezeken az onxxx() metódusokon kívül található


metódust is:

• Hide(): Elrejti a vezérlőelemet, és a visible tulajdonságot harnis érték­

re állítja.

• show(): Megjeleníti a vezérlőelemet, és a visible tulajdonságot igaz ér­

tékre állítja.

• rnvalidate(): Arra kényszeríti a vezérlőelemet, hogy egy Paint esemény

küldésével újrarajzolja önmagát (a grafikus renderelés leírása ebben a fe­


jezetben található, a "GDI+-alapú grafikus adatok renderelése" círnű
részben).

A Form osztály funkcionalitása

A Form osztály jellernzően (de nem szükségszerűen) az egyedi Form típusok

közvetlen ősosztálya. A nagyszámú - a control, a scrollablecontrol és a


containercontrol osztályból - örökölt tagon kívül a Form típusok további

funkcionalitást is hozzáadnak elsősorban a főablakokhoz, az MDI-gyermek­


ablakokhoz és a párbeszédablakokhoz. Először nézzük meg az alapvető tu­
lajdonságokat. Ezeket a 27.5. táblázat tartalmazza.

AcceptButton Lekérdezi vagy beállítja az űrlapon azt a gombot,


amelyre a felhasználó az Enter billentyűvel kattinthat

ActiveMDIChild Az MDI-alkalmazások kontextusán belül használjuk.


ISMDIChildisMDIContainer

417
27. fejezet: Windows Forms programozás

cancelButton Lekérdezi vagy beállí�a azt a gombot, amelyre a fel­


használó az Esc billentyűvel kattinthat

controlBox Lekérdezi vagy beállí�a, hogy az űrlap rendelkezik-e


vezérlőelem-dobozzal.

FormBorderStyle Lekérdezi vagy beállí�a az űrlap szegélystílusát


A FormBorderstyle felsorolt típussal együtt haszná­
latos.

Me nu Lekérdezi vagy beállí�a az űrlapon rögzítendő menüt.

MaximizeBox Annak meghatározására szolgál, hogy az adott űrlap


MinimizeBox engedélyezi-e a dobazok maximalizálását és minima­
lizálását.

showinTaskbar Annak meghatározására szolgál, hogy az űrlap látha­


tó-e a Windows-tálcán.

StartPosition Az űrlap futásidejű kezdő pozícióját adja meg vagy


veszi fel értékül a FormStartPos ition felsorolás spe­
cifikációja alapján.

windowstate Azt konfigurálja, hogy az űrlap hogyan jelenjen meg


az indításkor. A FormwindowState felsorolt típussal
együtt használatos.

27.5. táblázat: A Form típus tulajdonságai

A számos, on prefixummal ellátott, alapértelmezett eseménykezelő mellett a

27.6. táblázat néhány, a Form típus által meghatározott, alapvető metódus lis­

táját tartalmazza.

Activate() Aktivál és az előtérbe helyez egy adott űrlapot.

Close() Bezárja az űrlapot.

centerToScreen() Az űrlapot a képernyő középpon�ába helyezi.

LayoutMDI () A gyermekűrlapoknak a szülőűrlapokon belüli elrendezésére


szolgál (a LayoutMDI felsorolt típus alapján).

showoialog() Az űrlapot madális párbeszédablakként jeleniti meg.

27.6. táblázat: A Form típus kulcsfontosságú metódusai

418
Az űrlapok anatómiája

Végül pedig a Form osztály számos olyan eseményt határoz meg, amelyek
közül sok az űrlap élettartama alatt sül el. A 27.7. táblázat a legfontosabbakat
tartalmazza.

Activated Mindig az űrlap aktiválásakor következik be, azaz akkor, ami­


kor az űrlap az asztal aktuális előterében van.

Closed, Annak meghatározására szolgál, hogy az űrlap be fog záródni,


elosing vagy már bezáródott.

Deactivate Mindig az űrlap deaktiválásakor következik be, azaz akkor,


amikor az űrlap elkerül az asztal aktuális előterébőL

Load Akkor következik be, amikor az űrlap már lefoglalódott a me­


máriába, de még nem látható a képernyőn.

MDIChildActive A gyermekablak aktiválásakor következik be.

27.7. táblázat: A Fonn típus néhány eseménye

A Form tipus életciklusa

Ha már programoztunk felhasználói felületeket GUI-elernkönyvtárakkal (pl.


Java Swing, Mac OS X Cocoa vagy nyers Win32 API), akkor tudjuk, hogy az
"ablaktípusok" számos olyan eseménnyel rendelkeznek, amelyek az élettar­
tamuk alatt elsülnek Ugyanez érvényes a Windows Form-alkalmazásra is.
Az űrlapok élettartama konstruktoruk meghívásakor kezdődik, még azelőtt,
hogy átadnánk őket az Appl ication. Run() metódusnak.
Miután az objektumot a felügyelt heapre helyeztük, a keretrendszer elsüti a
Load eseményt. A Load eseménykezelőjén belül lehetőségünk van az űrlap meg­
jelenésének és működésének konfigurálására, az űrlapon megjelenő gyermek­
vezérlőelemek (pl. List Box, Treeview stb.) készítésére vagy éppen az űrlap mű­
ködése közben használt erőforrások lefoglalására (adatbázis-kapcsolatok,
proxyk távoli objektumokhoz stb.).
A Load esemény után a következő kiváltódó esemény az Activated. Az
Act i vated akkor sül el, arnikor az űrlap, rnint aktív ablak, az asztal előterébe
kerül. Az Activated esemény logikai párja (természetesen) a oeactivate, amely
akkor következik be, arnikor az űrlap, rnint aktív ablak, elkerül az előtérbőL Az
Act i vated és a oeactivate esemény egy adott Form típus élettartama során
számtalan esetben kiváltódhat, ha aktív alkalmazások között navigálunk.

419
27. fejezet: Windows Forms programozás

Ha felhasználóként a kérdéses űrlap bezárása mellett döntünk, két ese­


ményt váltunk ki, ezek a clos ing és a closed. Először a clos in g esemény kö­
vetkezik be, amely kiváló alkalmat kínál arra, hogy a program feltegye a fel­
használónak az olyannyira utált (de hasznos) "Biztos, hogy be akarja zárni az
alkalmazást?" kérdést. A megerősítő lépés igencsak fontos annak biztosításá­
ra, hogy a felhasználónak maradjon lehetősége elmenteni az alkalmazás­
központú adatokat a program befejezése előtt.
A Closing esemény a system.ComponentModel névtérben definiált Cancel Event­
Handler metódusreferenciát használja. Ha a cancelEventArgs. cancel tulajdonság
igaz, megakadályozha�uk az ablak megsemmisítését, és arra kényszeríthe�ük,
hogy normál üzemmódba térjen vissza. Ha a cancelEventArgs.cancel tulajdon­
ság hamis, akkor bekövetkezik a closed esemény, a Windows Forms-alkalmazás
kilép, ez kidobja az alkalmazástartományt, és befejezi a folyamatot.
Az űrlap élettartama során bekövetkező események sorrendjének gyors
vizsgálatához tételezzük fel, hogy vagy egy új, FormLifeTime nevű Windows
Forms-projektünk, és a kezdeti űrlapot (a Solution Explorerrel) átneveztük a
következőre: Mainwindow.cs. Az űrlap konstruktorán belül regisztráljunk a
Load, az Activated, a oeactivate, a closing és a closed eseményekre. (A += ka­
rakterek beírása és a Tab billentyű kétszeri megnyomása után az integrált fej­
lesztői környezet automatikusan létrehozza a megfelelő metódusreferenciát és
eseménykezelőt, lásd az előző kötet ll. fejezetében):

public Mainwindow()
{
Initializecomponent();

ll Az élettartam során bekövetkező események kezelése.


elosing += new cancelEventHandler(Mainwindow_closing);
Load+= new EventHandler(Mainwindow_Load);
elosed += new EventHandler(Mainwindow_closed);
Activated += new EventHandler(Mainwindow_Activated);
Deactivate += new EventHandler(Mainwindow_Deactivate);
}

Megjegyzés Azért kezeljük manuálisan ezeket az eseményeket, mert a Properties ablak (vala·
mi különleges okból kifolyólag) nem listázza a clos i ng és a cl osed eseményt. A Load, Act iva­
ted és Deactivate eseményeket azonban tervezésidejű eszköz segítségével is kezelhetjük.

A Load, a closed, az Activated és a Deacti vate eseménykezelőkön belül olyan


egyszerű üzenet segítségével módosí�uk az új, Form-szintű (lifeTimernfo ne­
vű) sztringtagváltozó értékét, amely az éppen elkapott esemény nevét jeleníti
meg. A closed eseménykezelőn belül egy üzenetdobozban jelenítjük meg en­
nek a sztringnek az értékét:

420
Az űrlapok anatómiája

private void Mainwindow_Load(object sender, System.EventArgs e)


{
lifeTimeinfo += "Load event\n";
}

private void Mainwindow_Activated(object sender, system.EventArgs e)


{
lifeTimeinfo += "Activate event\n";
}

private void Mainwindow_Deactivate(object sender,


system.EventArgs e)
{
lifeTimeinfo += "Deactivate event\n";
}

private void Mainwindow_closed(object sender, system.EventArgs e)


{
lifeTimeinfo += "closed event\n";
MessageBox.show(lifeTimeinfo);
}

A closing eseménykezelőben a bemeneti caneelEventArgs használatával meg­


kérdezzük a felhasználót, hogy valóban be kívánja-e zárni az alkalmazást. Az
alábbi forráskódban látható, hogy a MessageBox.show() metódus olyan Dialog­
Resu lt típust ad vissza, amely tartalmazza annak az értékét, hogy a felhasználó

melyik gombot választotta. Olyan üzenetdobozt hoztunk létre, amely egy Yes
és egy No gombot tartalmaz; tehát az érdekel bennünket, hogy a show() metó­
dus a DialogResult. No értéket adja-e vissza, vagy sem.

private void Mainwindow_Closing(object sender, cancelEventArgs e)


{
lifeTimeinfo += "closing event\n";
ll Egy Yes és No gombot tartalmazó üzenetdoboz megjelenítése.
DialogResult dr = MessageBox.Show("Do you REALLY want to close
this app?", "closing event!", MessageBoxButtons.YesNo);

ll Melyik gombra kattintottak?


if (dr == DialogResult.No)
e.Cancel = true;
el se
e.cancel false;
}

Futtassuk az alkalmazást, és (az Activated és a Deactivate esemény kiváltá­


sához) váltogassunk az alkalmazások között, hogy az űrlap előtérbe, majd
pedig háttérbe kerüljön. Ha végül bezárjuk az alkalmazást, a 27.13. ábrán lát­
hatóhoz hasonló üzenetdoboz jelenik meg.

421
27. fejezet: Windows Forms programozás

load event
Activate �ent
Deactivate event
Activate event
Closing event
Deactivate event
Activate �ent
Closing event
Oeactivate event
Activate event
Closed event

OK

27. 13. ábra: A Fonn-leszánnazott típusok élete és tartama

Forráskód A FormlifeTime projektet a forráskódkönyvtár 27. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Reagálás az egér eseményeire

A contro l ősosztály olyan eseményeket határoz meg, amelyek lehetővé teszik,


hogy többféle módon reagáljunk az egértevékenységekre (pl. egér gombjának
lenyomása vagy felengedése, kattintás, egér mozgatása stb.). Ennek kipróbálá­
sára hozzunk létre egy új Windows Application-projektet MouseEventsApp
névvel (a Solution Explorerrel), nevezzük át a kezdeti űrlapot Mainwindow. cs-re,
és kezeljük a MouseMove eseményt a Properties ablakban. Ez létrehozza az alábbi
eseménykezelőt:

public partial class Mainwindow Form


{
public Mainwindow()
{
Initializecomponent();
}

ll Létrehozva a Properties ablak segítségével.


private void Mainwindow_MouseMove(object sender, MouseEventArgs e)
{
}
}

422
Reagálás az egér eseményeire

A MouseMove esemény a system. windows. Forms. MouseEventHandler metódusrefe­

renciára épül. Ez a metódusreferencia csak olyan metódusokat hívhat meg,


amelyekben az első paraméter egy system. obj ect, rrúg a második MouseEvent­
Args típusú. Ez utóbbi több olyan tagot tartalmaz, amelyek részletes információ­

val szalgálnak az egér által kiváltott esemény állapotáról:

public class MouseEventArgs : EventArgs


{
private readonly MouseButtons button;
private readonly int clicks;
private readonly int delta;
private readonly int x;
private readonly int y;

public MouseEventArgs(MouseButtons button, int clicks, int x,


int y, int delta);

public MouseButtons Button { get; }


public int clicks { get; }
public int Delta { get; }
public Point Location { get; }
public int x { get; }
public int Y { get; }
}

A 27.8. táblázat a nyilvános tulajdonságok jelentését ismerteti.

�c'• ., <

�j�
.. -.-;.-.;..... -4

Button Ez a tulajdonság lekérdezni, hogy melyik egérgombot nyom­


ták meg, és a MouseButtons felsorolt tipus megfelelő értékét
adja vissza.

clicks Ez a tulajdonság lekérdezi, hogy hányszor nyomták le és enged­


ték fel az egérgombot

De l t a Ez a tulajdonság az egérgörgő görgetésének előjeles számláló­


értékét kérdezi le.

Location Ez a tulajdonság visszaad egy Point tipust, amely az aktuális


X és Y értékeket tartalmazza.

x Ez a tulajdonság lekérdezi az egérkattintások x koordinátáját.

y Ez a tulajdonság lekérdezi az egérkattintások y koordinátáját.

27.8. táblázat: A MouseEventArgs típus tulajdonságai

423
27. fejezet: Windows Forms programozás

Implementáljuk a MouseMove eseménykezelőt az egér aktuális X és Y pozíció­


jának megjelenítésére az űrlap címében a Location tulajdonság használatával:

private void Mainwindow_MouseMove(object sender, MouseEventArgs e)


{
Text = string.Format("Mouse Position: {O}", e.Location);
}

Amikor futtatjuk az alkalmazást, és mozgatjuk az egeret az ablakban, az egér


pozíciója megjelenik a Mainwindow címében (lásd a 27.14. ábrát).

tl,i1 Mouse Position: {X- l79,Y-55}

27. 14. ábra: Az egérrnozgás kezelése

Az egérgombkattintás meghatározása

A következő kérdés annak meghatározása, hogy a Mouseup, a MouseDown, a


Mouseclick vagy a MouseDoubleclick esemény bekövetkeztekor melyik egér­

gombbal kattintott a felhasználó. Ha pontosan meg szeretnénk határozni,


melyik (azaz a jobb, a bal vagy a középső) gomb volt használatban, meg kell
vizsgálnunk a MouseEventArgs osztály Button tulajdonságát. A Button tulaj­
donság értékét a MouseButtons felsorolt típus határozza meg:

public enum MouseButtons


{
Left,
Middle,
None,
Right,
XButtonl,
XButton2
}

Ennek bemutatásához a Properties ablak segítségével kezeljük a Mainwindow


típusban található Mouseup eseményt. Az alábbi Mouseup eseménykezelő
megmutatja, hogy melyik egérgombbal történt kattintás az üzenetdobozban:

424
Reagálás a billentyűzet eseményeire

private void Mainwindow_Mouseup (object sender, MouseEventArgs e)


{
ll Melyik egérgombbal kattintottak?
if(e.Button MouseButtons.Left)
==

MessageBox.show("Left click!");
if(e.Button MouseButtons.Right)
==

MessageBox.Show("Right click!");
if (e.Button MouseButtons.Middle)
==

MessageBox.show("Middle click!");
}

Forráskód A MouseEventApp projektet a forráskódkönyvtár 27. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Reagálás a billentyűzet eseményeire

A Windows-alkalmazások jellemzően számos olyan beviteli vezérlőelemet

(pl. a TextBox) határoznak meg, ahol a felhasználó az információt a billentyű­


zet segítségével adha�a meg. Ha a billentyűzet inpu�át ily módon rögzí�ük,
nincs szükség a billentyűzetesemények konkrét kezelésére, hiszen a szöveges
adatot különböző tulajdonságok segítségével (pl. a TextBox típus Text tulaj­
donsága) egyszerűen kiolvasha�uk a vezérlőelembőL
Ha azonban ennél különlegesebb okokból (pl. a billentyűk leütésének szű­
résére egy adott vezérlőelemen vagy a billentyűk megnyomásának kezelése
magán az űrlapon) ellenőriznünk kell a billentyűzet inpu�át, az alaposztály­
könyvtárak rendelkezésre bocsá�ák a Keyup és a KeyDown eseményt. Ezek az
események a KeyEventHandler metódusreferenciát használják, ez pedig bár­
mely olyan metódusra mutathat, amelynek első paramétere egy objektum, a
második paramétere pedig a KeyEventArgs. A típus definíciója az alábbi:

public class
. KeyEventArgs : EventArgs
{
private bool handled;
private readonly Keys keyData;
private bool suppressKeyPress;

public KeyEventArgs(Keys keyData);

public virtual bool Alt { get; }


public bool control { get; }
public bool Handled { get; set; }
public Keys Keycode { get; }

425
27. fejezet: Windows Forms programozás

public Keys KeyData { get; }


pub l ic int Keyvalue { get;}
public Keys Modifiers { get; }
public virtua l bool shift { get;}
public bool suppressKeyPress { get; set; }
}

A 27.9. táblázat a KeyEventArgs által támogatott leglényegesebb tulajdonsá­


gokat tartalmazza.

Alt Lekérdezi, hogy megnyomták-e az Alt billentyűt.

control Lekérdezi, hogy megnyomták-e a Ctrl billentyűt.

Handled Lekérdezi vagy beállítja, hogy az esemény kezelése teljes mér­


tékben megtörtént-e a kezelőben.

Keycode A KeyDown és a Keyup esemény billentyűzetkódját kérdezi Ie.

Modifiers Jelzi, hogy mely váltóbillentyűket (Ctrl, Shift, és/vagy Alt)


nyomták meg.

shift Lekérdezni, hogy megnyomták-e a Shift billentyűt.

27.9. táblázat: A KeyEventArgs típus tulajdonságai

Ennek bemutatására tételezzük fel, hogy van egy új, a Keyup eseményt az alábbi
módon kezelő, KeyboardEventApp nevű Windows Applicaton-projektünk

public partial class Mainwindow : Form


{
public Mainwindow()
{
Initializecomponent();
}

private void Mainwindow_Keyup(object sender, KeyEventArgs e)


{
Text = string.Format("Key Pressed: {O} Modifiers:{1}",
e.Keycode.ToString(), e.Modifiers.ToString());
}
}

Fordítsuk le és futtassuk a programot. Ekkor már nemcsak azt tudjuk megha­


tározni, hogy melyik egérgombbal kattintottak, hanem azt is, hogy melyik
billentyűt nyomták meg. A 27.15. ábra például a P, a Ctrl és a Shift billentyűk
egyidejű megnyomásának eredményét ábrázolja.

426
Párbeszédablakok tervezése

�g Key Pressed: P Modifiers: Shíft, Control ld @J liiDíiil

li-

27.15. ábra: A billentyűesemények kezelése

Forráskód A KeyboardEventApp projektet a forráskódkönyvtár 27. fejezetének al könyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Párbeszédablakok tervezése

A grafikus felhasználói felülettel ellátott programokban általában a párbe­


szédablakok szalgálnak az alkalmazásban használt felhasználói adatok bevi­
telére. A többi, esetleg korábban már használt GUI API-tól eltérően itt nincs
"Di alog" ősosztály. A Windows Forms alatti párbeszédablakok a Form osz­

tályból származtatott egyszerű típusok


Emellett jó néhány párbeszédablakot fix méretűnek szánnak; ezért általá­
ban a FormBorderstyle tulajdonságot FormBorderstyle. FixedDi alogra állítjuk
A párbeszédablakoknál a Mini mi zeBox és a Maximi zeBox tulajdonság jellemző­
en hamis, így a párbeszédablakok rögzített konstansként vannak konfigurál­
va. Végül, ha a showinTa s k bar tulajdonságot hamis értékre állítjuk, akkor
megakadályozzuk, hogy az űrlap láthatóvá váljon a Windows-tálcán.
A párbeszédablakok készítésének és kezelésének illusztrálására hozzunk
létre egy új Windows Application-projektet CarOrderApp névvel. Nevezzük át
a kezdeti Forml. cs fájlt a Solution Explorerrel, és adjuk neki a Mainwindow. cs
nevet; majd az űrlaptervezővel hozzuk létre egy egyszerű menüt, amelynek
elemei: File > Exit, illetve Tool > Order Automobile. Ha ezzel végeztünk, a
Properties ablakban kezeljük az Exit és az Order Automobile almenük cl ick
eseményét. A 27.16. ábra a főablak kezdeti megjelenését mutatja.
Az alkalmazás egyszerű bezárásához implementáljuk a File > Exit menü
kezelőjét:

private void exitToolstripMenuitem_Click (object sender, Ev entArg s e)


{
Application.Exit();
}

427
27. fejezet: Windows Forms programozás

A Visual Studio Project menüjének segítségével válasszuk ki az Add Win­


dows Forms menüelemet. Az új űrlapnak adjuk az OrderAutoDialog.cs nevet
(lásd a 27.17. ábrát).

�Wmdow.cs [Design]*'------•_.:.X;_

· ;:;· CarOnlor�
l File Tools J! T)
_P <' Here _ _!
_

l
Order Automobile...

TvP"= �ere

� menuStripl

27.16. ábra: A főablak menürendszere

Add New Item · CarOrderApp

T�mplates:

Visual Studio install�d templi!!t�5

� r- � .�
· - ��
Flow
;,_j
Page(WPF)
'�_J '�:.J ��
PageFunct... ResourceOi... User Control
yw
Window

Class

Class

Interface
Docume... (WPF) (WPF) (WPF) (WPF)

tJ �jLJ .,
#.,. [j �
. � -� -m

Code File Custom WCF Service User Control Web Custom Inherited lnherited
Centr ... Configurat... Control Form User Control

.
g
Web Curtom Component
� �
HTMLPage SQL

DataSet

XMLFile
�.
XMLScherna
� �

XSLT File
[i1.
HTML Page
Control Class Database

�- li � � � � i @ @-
A blank Windows Form
---------------------------·

Name: OrderAutoDialog.cs

Add ll C•ncel

27.17. ábra: Új párbeszédablakok beszúrása a Visual Studiával

Ehhez a példához tervezzünk olyan párbeszédablakot, amely rendelkezik a szo­


kásos OK és Cancel gombokkal (ezek neve btnOK és btncancel) , valamint három
TextBox vezérlőelemmel, ezek a txtMake, a txtcolor és a txtPri ce. A Properties

ablak segítségével fejezzük be a párbeszédablak tervezését az alábbiak szerint:

428
Párbeszédablakok tervezése

• Legyen a FormBorderstyl e tulajdonság értéke Fi xedoi alog.

• Legyen a Mini mi zeBox és a Maximi zeBox tulajdonság hamis.

• Legyen a StartPosition tulajdonság értéke centerParent.

• Legyen a showinTaskbar tulajdonság hamis.

A DialogResult tulajdonság

Végül, de nem utolsósorban, jelöljük ki az OK gombot, és a Properties ablak


segítségével állítsuk be, hogy a oialogResult tulajdonság értéke OK legyen.
Hasonlóképpen állítsuk be a Cancel gomb Dial ogResult tulajdonságának ér­
tékét úgy, hogy az Cancel legyen. A oi alogResult tulajdonság igencsak hasz­
nos abból a szempontból, hogy az indító űrlapon gyorsan meghatározható,
hogy a felhasználó melyik gombot nyomta meg a kívánt művelet elvégzésé­
hez. A kapcsolódó oi al ogResult felsorolt típus bármely értékét felveheti a
oialogResult tulajdonság:

public enum DialogResult


{
Abort,. cancel, Ignore, No,
None, OK, Retry, Yes
}

A 27.18. ábra a párbeszédablak egy lehetséges megjelenését ábrázolja, amely


néhány Label vezérlőelemet tartalmaz.

•X

·ro;d;AutoDialog �l;
Make

Color

Price

�l Cancel

27.18. ábra: Az OrderAutoDialog típus

429
27. fejezet: Windows Forms programozás

A tabulátorsorrend konfigurálása

A következőkben formalizáljuk a tabulátorsorrend szerepét. Ha egy űrlap


több GUI-elemet tartalmaz, a felhasználók arra számítanak, hogy a vezérlők
között a Tab billentyű megnyomásával válthatnak A vezérlőelemek tabulá­
torsorrendjének konfigurálásához azonban meg kell ismernünk két kulcsfon­
tosságú tulajdonságot, a Tabstapot és a Tabrndexet.
A Tabstop tulajdonság az igaz vagy a hamis értéket veszi fel attól függően,
hogy a GUI-elemet elérhetővé kívánjuk-e tenni a Tab billentyűvel, vagy sem.
Ha a Tabstop tulajdonság értéke az adott vezérlőn igaz, a Taborder tulajdon­
ság az aktivációs sorrendet a tabulálás sorrendjének megfelelőerr (amely O
alapú) adja meg. Nézzük meg az alábbi példát:

ll Tabulálási tulajdonságok konfigurálása.


t xtMake.Tabindex =O;
txtMake.Tabstop t r ue ;
=

A tabulátorsorrend-varázsló

Míg a Tabstop és a Tabindex tulajdonságot a Properties ablakban manuálisan


is beállítha�uk, a Visual Studio 2008 IDE egy tabulátorsorrend-varázslót is
kínál, amelyet a View> Tab Order kiválasztásával nyithatunk meg (ez a me­
nüelem csak akkor érhető el, ha az űrlaptervező az aktív). Az aktiválást köve­
tően a tervezésidejű űrlap megjeleníti az egyes vezérlők aktuális Tabindex ér­
tékét. Ezeknek az értékeknek a módosításához kattintsunk az elemekre a kí­
vánt sorrendben (lásd a 27.19. ábrát).

/OrderAutoOialog.cs [Design)* 'MilinWondcM.cs 'MainWIIIdow.cs IDaignl) �x

:�iaii

27. 19. ábra: A tabulátorsorrend-varázsló

A tabulátorsorrend-varázslóból való kilépéshez egyszerűen nyomjuk meg az


Esc billentyűt.

430
Párbeszédablakok tervezése

Az űrlap alapértelmezett beviteli gombjának


a beállitása

Számos felhasználói beviteli űrlap (különösen a párbeszédablakok) rendel­


kezik olyan különleges gombbal, amely automatikusan válaszol, ha a fel­
használó megnyomja az Enter billentyűt. Ha azt szeretnénk, hogy ha a fel­
használó az aktuális űrlapon megnyomja az Enter billentyűt, akkor lefusson a
btnoK kattintási esemény kezelője, akkor egyszerűen állítsuk be az űrlap
AcceptButton tulajdonságát az alábbiak szerint (ugyanez a beállítás a Properties

ablakban is elvégezhető):

ll Az Enter billentyű megnyomása olyan, mintha


ll a felhasználó a btnOK gombra kattintott volna.
this.AcceptButton = btnOK;

Megjegyzés Néhány űrlap esetében, ha a felhasználó megnyomja az Esc billentyűt, az a


Cancel gombra történő kattintást szimulálja. Ez úgy érhető el, hogy a Form can ce l Button tu­
lajdonságot a Cancel gombra történő kattintást kiváltó Button objektumhoz rendeljük.

Párbeszédablakok megjelenitése

Ha egy párbeszédablakot szeretnénk megjeleníteni, először azt kell eldönte­


nünk, hogy a párbeszédablak indítása madális vagy ne madális legyen. A mo­
dális párbeszédablakokat a felhasználónak még az előtt be kell zárnia, mielőtt
visszatérne a párbeszédablakot először indító ablakhoz (pl. a legtöbb About
doboz modális). Egy madális párbeszédablak megjelenítéséhez egyszerűen
hívjuk meg a showDia log() metódust a párbeszédablak-objektumbóL A nem
madális ablakok a show() hívásával jeleníthetők meg. Ez a metódus lehetővé
teszi a felhasználó számára a párbeszédablak és a főablak közötti váltogatást
(ilyen pl. a Find/Replace párbeszédablak).
Példánkban módosítsuk a Mainwindow típus Tools> Order Automobile me­
nükezelőjét annak érdekében, hogy az orderAutoDialog objektumot modálisan
jelenítse meg. Vizsgáljuk meg a következő kezdeti forráskódot:

private void orderAutomobileToolstripMenuitem_click(object sender,


EventArg s e)
{
ll Hozzuk létre a párbeszédobjektumot.
orderAutoDialog dlg = new orderAutoDialog();

431
27. fejezet: Windows Forms programozás

ll Jelenítsük meg madális párbeszédablakként, és találjuk ki,


ll melyik gombra kattintottak; ehhez használjuk a DialogResult
ll visszaadott értékét.
if ( dlg. S h owoi a lo g ( ) == DialogResult.OK)
{
ll Az OK gombra kattintottak, tehát tennünk kell valamit...
}
}

Megjegyzés A showDialog() és a Show() metódusokat tetszés szerint meghívhatjuk akár a


párbeszédablak tulajdonosát képviselő objektum megadásával is (ezt a párbeszédablakot be­
töltő űrlap ábrázolja). A párbeszédablak tulajdonosának meghatározása létrehozza az űrlaptí­
pusok z-sorrendjét, és (a nem madális párbeszédablakok esetén) biztosítja, hogy a főablak meg­
semmisítésekor minden, "a tulajdonába tartozó" ablak is eltűnjön.

Ügyeljünk arra, hogy ha létrehozzuk egy Form-leszármazott típus egy példá­


nyát G elen esetben az ord erAutoDia log típust), a párbeszédablak nem látható a
képernyőn, egyszerűen csak lefoglalóclik a memóriában. Az űrlap valójában
csak a Show() vagy a showDialogO metódus hívásakor lesz látható. A sh ow ­

Dialo g () visszaadja a gombhoz rendelt DialogResult értéket (a show() metódus

egyszerűen voidat ad vissza).


A showoi alog O visszatérésével az űrlapot a továbbiakban nem lehet látni

a képernyőn, ám a memóriában megmarad. Ezért az értékek rninden TextBox


típusból kiolvashatók. Ha azonban az alábbi kódot szeretnénk lefordítani:

private void orderAutomobileToolstripMenuitem_Click(object sender,


EventArgs e)
{
ll Hozzuk létre a párbeszédobjektumot.
OrderAutoDialog dlg = new orderAutoDialog();

ll Jelenítsük meg madális párbeszédablakként, és találjuk ki,


ll melyik gombra kattintottak; ehhez használjuk a DialogResult
ll visszaadott értékét.
if ( dlg . S h owoialo g () == DialogResult.OK)
{
ll Minden szövegdobozban van érték? Fordítási hiba!
string orderinfo = string.Format("Make: {O}, Color: { 1},
cost: {2}", dlg.txtMake.Text, dlg.txtcolor.Text,
dlg.txtPrice.Text);
MessageBox.show(orderinfo, "Information about your o rder ! ");
}
}

432
Párbeszédablakok tervezése

akkor fordítási hibaüzenetet kapunk. Ennek az az oka, hogy a Visual Studio


2008 a Forros-tervezőhöz adott vezérlőelemeket az osztály privát tagváltozói­
ként deklarálja. Ezt a tényt megerősíthe�ük például akkor, ha meg szeret­
nénk nyitni az orderAutoDialog.Designer.cs fájlt. Bár a kifogástalan párbe­
szédablakok, ha ezekben a szövegdobozokban lévő értékek megadásához
vagy felvételéhez nyilvános tulajdonságokat adnak, megőrizhetik az egység­
be zárást, mi ennél rövidebb utat választunk, és a publ i c kulcsszóval egysze­
rűen újradefiniáljuk őket:

partial class orderAutoDialog


{

ll Az űrlap tagváltozóinak meghatározása a tervező által


ll fenntartott fájlban történik.
public System.windows.Forms.TextBox txtMake;
public System.Windows.Forms.TextBox txtcolor;
public system.windows.Forms.TextBox txtPrice;
}

Ekkor már lefordítha�uk és futtatha�uk az alkalmazást. A párbeszédablak el­


indításakor a beviteli adatok egy üzenetdobozban vannak (ha az OK gombra
kattintottunk).

Megjegyzés A vezérlőelemek hozzáférési módosítóinak meghatározásához a *.Des i g ner. cs


fájl közvetlen szerkesztése helyett jelöljük ki a szerkesztőben a finomhangolásra szánt vezér­
lőelemet, majd a Properties ablakban található Modi fi ers tulajdonság segítségével végezzük
el a műveletet.

Az űrlapok származtatása

Eddig minden egyes egyedi ablakunk/ párbeszédablakunk közvetlenül a


system. windows. Forms. Form leszármazo�a volt. A Windows Forms-fejlesztés

egyik érdekes aspektusa, hogy a Form típusok a származtatott Form típusok


ősosztályaként is működhetnek. Tételezzük fel például, hogy létrehoztunk
egy olyan .NET-kódkönyvtárat, amely tartalmazza a vállalatunkra vonatkozó
alapvető párbeszédablakokat Később úgy döntünk, hogy az About doboz
kissé semmitmondó, ezért szeretnénk hozzáadni a vállalat emblémájának 30-s
képét. Ahelyett, hogy újból létrehoznánk a teljes About dobozt, egyszerűen
bővítsük az eredeti About dobozt úgy, hogy örökölje az alapvető megjelenési
és működési jellemzőket:

433
27. fejezet: Windows Forms programozás

ll A ThreeDAboutBox "az egy" AboutBox.


public class ThreeDAboutBox : AboutBox
{
ll A vállalat logójának rendereléséhez adjunk hozzá forráskódot...
}

Az űrlapszármaztatás működésének megtekintéséhez a Project> Add Form


menüelem segítségével szútjunk a projekthe egy új űrlapot. Ezúttal azonban
válasszuk ki a Inherited Form ikont, és az új űrlapnak adjuk az Imageorder­
AutoDi al og. cs nevet (lásd a 27.20. ábrát).

Add New !tem • CarORlerApp

Templates: �G
Visual Studio installed tanpiates -·

�-u �
l'!- ' ,;;;j
\ v. r5U �
.. 'i � � � nl
Flow Page (WPF) Pagefunct... ResourcdJi... Us�r Control Window Class Class Interface �
Oocume... (WPF) ( WPF ) (WPF)

:f:·
(WPF )


Code File
,.,
Custom
Contr...
i
WCFService
[il .

Windows
Form
ll
User Control
�-
Web
Configurat...

Custom
Control


-�

Inherited
.

User Control
u
[fl �
Web Custom Component
[i1
HTMLPage SQL
jfl
DataSet
fil
XMLFile
IDl.
XMLScherna

XSLT F ile

HTMLPage
Control Class Database

� -
[i1 � � � l � @
A n� form based on an existing Windows Form

Name: ImageOrderAutoOialog.cs

Add JI Cancd

27.20. ábra: Származtatott űrlap hozzáadása a projekthez

Inheritance Picker

Speáythe �to inheli from:

C:\Usero\Andrew Troeben\My Ilooko\C# and the .NE

lll

lNew�name:lmage(JnlerJIUoOialog
&-... JI OK jj Ciw1cel

27 .21. ábra: Az Inheritance Picker párbeszédablak

434
Párbeszédablakok tervezése

Ezzel megjelenítjük az Inheritance Picker párbeszédablakot, amely megmu­


tatja az aktuális projekt valamennyi űrlapját. A Browse gomb lehetövé teszi a
külső .NET-szerelvényekből történő űrlapválasztást is. Most egyszerűen vá­
lasszuk ki az orderAutoDial og típust (lásd a 27.21. ábrát).

Megjegyzés A projektet legalább egyszer le kell fordítani annak érdekében, hogy az űrlapok
láthatók legyenek az lnheritance Picker párbeszédablakban, hiszen ez az eszköz olvassa a sze­
relvény-metaadatokat a lehetőségeink megjelenítéséhez.

Az OK gombra kattintva azt látjuk, hogy a vizuális tervezőeszközök megjelení­


tik a szülők valamennyi alapvető vezérlőelemét, s a szülők illindegyike ren­
delkezik egy kicsi, a vezérlőelem bal felső sarkában látható nyíllal (a származ­
tatás szimbolizálására). A származtatott párbeszédablak befejezéséhez keres­
sük meg a Toolbox Common Controls részében található Pi cture Box vezérlő­
elemet, és adjuk a származtatott űrlaphoz. Ezt követően az Image tulajdonság
segítségével válasszunk ki egy tetszőleges képfájlt Az 27.22. ábrán egy lehet­
séges felhasználói felület látható, ahol az Intertech Training logóját használtuk.

�eOrderAutoi>Wog.cs [Designrj • x

!Soke �
·1�,
·"·""'"""•""--'Jilr
!SJicr � b

� 1'8
��'"""'""�u�-" ="''". 1
� OK l f!l Cancel

27.22. ábra: Az ImageOrderAutoDialog tipus

Most már módosíthatjuk a Tools >Order Automobile kattintási esemény ke­


zelőjét, hogy létrehozzuk a származtatott osztály egy példányát az or der ­
AutoDial og ősosztály helyett:

private void orderAutomobileToolstripMenuitem_click(object sender,


EventArgs e)
{
ll Hozzuk létre a származtatott párbeszédobjektumot.
ImageorderAutoDialog dlg = new ImageorderAutoDialog();

Forráskód A CarOrderApp projektet a_ forráskódkönyvtár 27. fejezet alkönyvtára tartalmazza.


A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

435
27. fejezet: Windows Forms programozás

GDI+-alapú grafikus adatok renderetése

Számos CUI-alkalmazáshoz szükség van az ablakok felületén megjelenítendő


grafikus adatok dinamikus generálására. Ha például kiválasztottunk egy re­
kordhalmazt egy relációs adatbázisból, és szeretnénk egy olyan kördiagramot
(vagy oszlopdiagramot) renderelni, amely megjeleníti a kiválasztott elemeket.
Vagy például, ha a .NET platform segítségével szeretnénk valamilyen régi vi­
deojátékot újra elkészítení. Ha az adatokat, a céltól függetlenül, grafikusan sze­
retnénk rendereini egy Windows Forms-alkalmazásban, arra a GDI+ a legmeg­
felelőbb API. Ez a technológia szarosan kapcsolódik a system. Drawing. dll sze­
relvényhez, amely számos névteret határoz meg (lásd a 27.23. ábrát).

Object Browser
..
MainWindow.cs • x

l Browse: .NET Frameworlc

<Search> ll
�s-o�·!J;. =------:il
{) l
Sy<tem.Drawing
0 O Sy<tem.Orawing.Design
{)
!
Sy<tem.Orawing.Orawing20
{) Sy<tem.Orawing.Imaging Assembly System.Dr•wing �l
() Sy<tem.Orawing.Printíng C:\Wrndows\Mrcrosott.NET\Frameworl::\v2.0.50727
O
"'
Sy<tem.Orawing.Text _ \System.Drawing.dll

27.23. ábra: A System.Drawing.dll névterei

A 27.10. táblázat az egyes GDI+-névterek szerepét mutatja meg.

System.Drawing Ez az alapvető GDI+-névtér az alapszintű előkészí­


tés számos típusát (betűtípusok, tollak, egyszerű
ecsetek stb.), valamint a Graphics típust definiálja.

System.Drawing.Drawing2D Ez a névtér biztosítja a speciá!isabb 2D-sjvektorgra­


fikus funkcionalitáshoz használt típusokat (pl. szín­
átmenetes ecsetek, geometriai transzformációk stb.).

system.Drawing.Imaging Ez a névtér határozza meg azokat a típusokat, ame­


lyek lehetővé teszik a grafikus képek kezelését (pl. a
színskála módosítását, a képi metaadatok lekérde­
zését, a metafájlok kezelését stb.).

436
GDI+-alapú grafikus adatok renderetése

System.Drawing.Printing Ez a névtér határozza meg azokat a típusokat, ame­


lyek lehetővé teszik a képek renderelését a nyomta­
tott oldalra, a kommunikációt magával a nyomtató­
val, valamint a nyomtatott verzió megjelenésének
formázását.

System.Drawing.Text Ez a névtér teszi lehetővé a betűtípus-gyűjtemények


kezelését.

27.1 O. táblázat: Alapvető GDI+-névterek

A System. Drawing névtér

A GDI+-alkalrnazások programozásakor használt típusok igen nagy része a


system.Drawing névtérben található. Elvárásainknak rnegfelelően vannak
olyan tipusok, amelyek képeket, ecseteket, tollakat és betűkészleteket képvi­
selnek. Ernellett a system.Drawing szárnos kapcsolódó segédtípust (pl. a color,
)
Print és Rectangle is meghatároz. A 27.11. táblázat néhány ilyen (de nem az

összes) alapvető típust tartalmaz.

Bitmap Ez a típus foglalja magába a képadatokat (*. bmp és egyebek).

Brush Az ecsetobjektumok a grafikai alakzatok, mint például a tég­


Brushes lalapok, az ellipszisek vagy a sokszögek belsejének kitöltésére
SolidBrush szolgálnak.
SystemBrushes
TextureBrush

BufferedGraphics Ez a típus biztosítja a grafikai puffert a kettős puffereléshez,


amely a megjelenített felület újrarajzolásakor létrejövő vibrá­
lás csökkentését vagy megszüntetését szolgálja.

Color A color és a Systemcolars típusok határozzák meg azokat a


systemcolars statikus írásvédett tulajdonságokat, amelyek a különböző tol­
lak/ ecsetek szerkesztéséhez használt színek megszerzését
szolgálják.

Font A Font típus foglalja magába az adott betűtípus jellemzőit


FontFamily (mint pl. a típus neve, félkövér vagy dőlt betűs, pontméret
stb.). A FontFamily biztosítja a hasonló megjelenésű, de stí­
lusban eltérő betűtípusok csoportjainak absztrakcióját

437
27. fejezet: Windows Forms programozás

Gr a phic s Ez az alapvető osztály egy érvényes rajzfelületet, valamint


számos, a szöveg, a képek és a geometriai minták renderelé­
séhez használt metódust reprezentál.

Icon Ezek az osztályok egyedi ikonokat, valamint a rendszer által


systemicons biztosított, standard ikonok készletét képviselik.

Image Az Image olyan absztrakt ősosztály, amely a Bitmap, az Icon


ImageAnimator és a cursor típusok funkcionalitását biztosítja. Az Image­
Animator biztosítja az Image-Ieszármazott típusok egy adott

intervallumban történő iterálásának a lehetőségét.

Pen Tollak, vonalak és görbék rajzolására szolgálnak. A Pens tí­


Pens pus számos olyan statikus tulajdonságot definiál, amelyek
SystemPens egy adott szín új Pen típusát adják vissza.

Point PointF Ezek a struktúrák egy (x, y) koordínátát képeznek Ie a mögöt­


tes egész vagy lebegőpontos számra.

Rectangle Ezek a struktúrák egy téglalap méreteit adják meg (szintén a


RectangleF mögöttes egész vagy lebegőpontos számba Ieképezve).

Size SizeF Ezek a struktúrák egy adott magasságot/ szélességet hatá­


roznak meg (szintén a mögöttes egész vagy lebegőpontos
számba Ieképezve).

StringForrnat Ez a típus a szöveges elrendezés (mint pl. az igazítás, a sor­


köz stb.) tulajdonságainak egységbe zárására szolgál.

Region Ez a típus a téglalapokból és az útvonalakból álló geometri­


kus képek belső jellemzőit írja Ie.

27 .11. táblázat: A System.Drawing névtér alapvető típusai

A Graphics tipus szerepe

A system. orawing. Graphics osztály az á�áró a GDI +-renderelő funkcionalitá­


sához. Ez az osztály nemcsak a rajzfelületet (pl. az űrlap felületét, a vezérlő­
elemek felületét vagy a memóriaterületeket) tartalmazza, de több tucat olyan
tagot is meghatároz, amelyek lehetővé teszik a szövegek, a képek (ikonok,
bittérképek stb.) és számos geometriai minta renderelését is. A 27.12. táblázat
részleges listát ad a tagokról.

438
GDI+-alapú grafikus adatok renderetése

FromHdcO Ezek a statikus metódusok biztosítják az érvényes Graphics


FromHwndO objektumok egy adott képből (pl. ikonból, bittérképből stb.)
FromimageO vagy CUI-céleszközből történő megszerzését

ClearO Ez a metódus adott színnel tölti ki a Graphics objektumot, és


törli a folyamat aktuális rajzfelületét

DrawArc O Ezek a metódusok egy adott kép vagy geometrikus minta


DrawBeziersO renderelésére szolgálnak. Valamennyi Drawxxx() metódus
Drawcu rveO megköveteli a GDI+ Pen objektumok használatát
DrawElli p seO

DrawiconO
DrawLineO

DrawlinesO
DrawPieO

DrawPathO

DrawRectangle 0

DrawRectangles()

DrawstringO

Fi ll Ell i p seO Ezek a metódusok egy adott geometrikus alakzat kitöltésére


FillPie 0 szolgálnak. Valamennyi FillxxxO metódus megköveteli a
Fi llPo lygonO GDI+ Brush objektumok használatát
FillRectangleO

FillPath 0

27.12. táblázat: A Graphics osztály tagjai

A Graphics osztály nem hozható létre közvetlenül a new kulcsszó segítségé­

vel, ugyanis nincsenek nyilvánosan meghatározott konstruktorai. Ez esetben


felmerül a kérdés: hogyan kapunk érvényes Gra p hics objektumot?

Graphics objektumok megszerzése a Paint


eseményen keresztül

A Graphics objektumok legáltalánosabb megszerzési módja a Paint esemény

kezelése abban az ablakban, amelyet rendereini szeretnénk a Visual Studio


2008 Properties ablakának használatávaL Ezt az eseményt a Paint EventHand­
ler metódusreferencia határozza meg. Ez a metódusreferencia bármely olyan

metódusra képes rámutatni, amelynek első paramétere system.object, a má­


sodik pedig PaintEventArgs.

439
27. fejezet: Windows Forms programozás

A PaintEv entArgs paraméter tartalmazza azt a Graphics objektumot, ame­


lyet az űrlap felületére kell renderelni. Ennek illusztrálására hozzunk létre
egy új Windows Application-projektet PaintEventApp névvel. A Solution
Explorer segítségével nevezzük át a kezdeti Form.cs fájlt Mainwindow. cs-re, és
kezeljük a Pa i nt eseményt a Properties ablakban. Az eredmény az alábbi két
kódcsonk lesz:

public partial class Mainwindow Form


{
public Mainwindow()
{
rnitializecomponent();
}

private void Mainwindow_Paint(object sender, PaintEventArgs e)


{
}
}

A Pa i nt esemény kezelése után, kérdés, hogy mikor fog bekövetkezni. A Pa i nt


esemény mindig bekövetkezik akkor, amikor az ablak "piszkos" lesz. Az ablak
akkor "piszkos", amikor átméretezzük, amikor egy másik ablak (részben vagy
egészben) eltakarja, vagy amikor kis méretűvé tettük, majd visszaállítottuk az
előző méretre. Minden ilyen esetben, amikor az űrlapot újra kell rajzolni, a
.NET platform automatikusan meghívja a Paint eseménykezelőt. Nézzük meg
a Mainwindow_Paint() metódus alábbi implementációját:

private void Mainwindow_Paint(object sender, PaintEventArgs e)


{
ll Vegyük az aktuális űrlap grafikus objektumát.
Graphics g = e.Graphics;

ll Rajzoljunk egy kört.


g.FillEllipse(Brushes.Blue, 10, 20, 150, 80);

ll Egyedi betűtípussal hozzunk létre egy sztringet.


g.Drawstring("Hello GDI+", new Font("Times New Roman", 30),
Brushes.Red, 200, 200);

ll Egyedi tollal húzzunk egy vonalat.


using (Pen p = new Pen(Color.YellowGreen, 10))
{
g.orawLine(p, 80, 4, 200, 200);
}
}

440
GDI+·alapú grafikus adatok renderetése

Ha a Graphics objektumot megszereztük a bemeneti PaintEventArgs paramé­


terbőL a következő lépés a Fill Ell i pse() metódus hívása. Ennek a metódus­
nak (mint minden Fill prefixumos metódusnak) az első paramétere srush-le­
származott kell, hogy legyen. Bár a sy st em . Drawing. orawing2D névtérből szá­
mos érdekes ecsetobjektumot hozhatnánk létre ( Hatchsrush, LinearGradient­
Brush stb.), a Brushes segédosztály egyszerű hozzáférést biztosít számos egy­
színű ecsettípushoz.
A következő lépésben hívjuk meg a orawstring() metódust, amelynek el­

ső paramétere egy renderelendő sztring kell, hogy legyen. Ennek tükrében a


GDI+ biztosí�a a Font típust, amely nemcsak a szöveges adatok renderelésé­
hez használt betűtípus nevét adja meg, hanem a kapcsolódó jellemzőket is,
mint például a pontméretet (ez jelen esetben 30). A Drawstring() egy srush

típust is igényel, mert ami a GDI+-t illeti, a "Hello GDI+" a képernyőn kitöl­
tendő geometrikus minták egyszerű gyűjteményét jelenti. Végül pedig egy
egyedi Pen típust használó, 10 képpontnyi széles vonal rendereléséhez hívjuk
meg a DrawL i ne() metódust. A 27.24. ábra ennek az renderelési logikának a
kimenetét ábrázolja.

Hello GDI+

27.24. ábra: Egyszerű GDI+ renderelési művelet

Megjegyzés Az előző kódban egyértelműen rendelkezünk a Pen objektummaL Aranyszabály·


ként elmondhatj uk, hogy ha közvetlenül hozunk létre IDi sposab l e metódust implementáló
GDI+·típust, amint készen vagyunk az objektummal, rögtön hívjuk meg a Di spose() metó·
dust. Így a lehető leghamarabb felszabadíthatjuk a mőgöttes erőforrásokat. Ha nem e szerint
járunk el, az erőforrásokat végül nem determinisztikus módon a szemétgyűjtő szabadítja fel.

441
27. fejezet: Windows Forms programozás

Az űrlap felületének érvénytelenitése

A Windows Forrus-alkalmazás során szükségünk lehet a Pai nt eseménynek a


kódból történő közvetlen kiváltására ahelyett, hogy arra várnánk, hogy az ab­
lak a végfelhasználói műveletek során "magától piszkos" lesz. Ha például
olyan prograrnot készítünk, amely lehetövé teszi a felhasználó számára, hogy
egyedi párbeszédablak segítségével kiválasszon néhány előre meghatározott
képet. A párbeszédablakból való kilépést követően meg kell rajzolnunk az
újonnan kijelölt képet az űrlap ügyfélterületére. Nyilvánvaló, hogy ha arra
várnánk, hogy az ablak "automatikusan váljon piszkossá", a felhasználó nem
látná a változást egészen addig, amíg át nem méretezné az ablakot, vagy egy
másik ablak el nem takarná az előzöt. Annak kikényszerítésére, hogy az ablak
programozottan újrafesse önmagát, hívjuk meg az Invalidate O metódust:

public partial class MainForm: Form


{

private void MainForm_Paint(object sender, PaintEventArgs e)


{
Graphics g = e.Graphics;
ll A megfelelő képet itt rendereljük.
}

private void GetimageFromDialog()


{
ll Jelenítsük meg a párbeszédablakot, majd vegyük az új képet.
ll Fessük újra az egész ügyfélterületet.
Invalidate();
}
}

Az Inva lidate O metódus többszörösen túlterhelt annak érdekében, hogy


meghatározhassunk egy újrafestendő téglalap alakú területet ahelyett, hogy
az egész ügyfélterületet újrafestenénk (ami az alapértelmezett beállítás). Ha
az ügyfélterületnek csak a bal felső téglalap alakú területét szeretnénk frissí­
tem, írjuk az alábbiakat:

ll Az űrlap adott téglalap alakú területének újrafestése.


private void UpdateUpperArea()
{
Rectangle myRect = new Rectangle(O, O, 75, 150);
Invalidate(myRect);
}

Forráskód A PaintEventApp projektet a forráskódkönyvtár 27. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

442
Teljes Windows Forms-alkalmazás létrehozása

Teljes Windows Forms-alkalmazás


létrehozása

A Windows Forros és a GDI+ API-k bevezetésének lezárásaként hozzunk létre


egy olyan teljes GUI-alkalmazást, amely számos, a jelen fejezetben tárgyalt
módszert mint összefüggő egészet tartalmaz. A létrehozandó program egy
olyan alapvető rajzolóprogram lesz, amely lehetövé teszi a felhasználó szá­
mára, hogy kétfajta alakzat (az egyszerűség kedvéért egy kör és egy téglalap)
közül válasszon, és a kiválasztott alakzatot tetszőleges színben elhelyezze az
űrlapon. Emellett lehetövé tesszük, hogy a végfelhasználó a képeket helyi
fájlként a merevlemezre mentse, későbbi felhasználásra az objektumsorosító
szolgáltatásokkal.

A főmenürendszer készítése

Először hozzunk létre egy új Windows Forms-alkalmazást MyPaintProgram


névvel, és nevezzük át a kezdeti Form. cs fájlt Mainwindow. cs-re. Tervezzük
meg a kezdeti ablak menürendszerét, amely támoga�a a Save, a Load és az
Exit almenüket biztosító, felső File menüt (lásd a 27.25. ábrát).

�x


� menuStripl

27.25. ábra: A File menürendszer

Ezt követően hozzuk létre a második felső menüt, a Tools menüt, amelynek
segítségével kiválasztható a rendereléshez használatos alakzat és szín, vala­
mint törölhetök a grafikus adatok az űrlapról (lásd a 27.26. ábrát).

443
27. fejezet: Windows Forms programozás

/MOifíWIIMiow.c:s [Designr �·=


Stlot.:.:
P :::c.� ______ •_cX�
__:

� FunwilbtheP*.t� c:. H 81-


·

ll file Toolsll( T•;p•Here _l


11 Pick !ihape...
Pick Color...
Clear Surface
l Type Here: l

1lli! menuStripl

27.26. ábra: A Tools menürendszer

Végül pedig kezeljük ezeknek az alelemeknek a click eseményét. A példa


során minden egyes kezelőt implementálill fogunk, a File > Exit menükezelőt
viszont az Ap plication. Exit() metódus hívásával is lezárjuk

private void exitToolStripMenuitem_click(object sender, EventArgs e)


{
Application.Exit();
}

A ShapeData tipus meghatározása

Az alkalmazásunk lehetövé teszi, hogy a végfelhasználó két adott színű, előre


meghatározott alakzat közül válasszon. Mivel lehetőséget adunk arra, hogy a
felhasználó a grafikus adatokat fájlként elmentse, olyan egyedi osztálytípust
kell meghatároznunk, amely magába foglalja ezeket a részleteket. Az egysze­
rűség kedvéért ezt a C# automatikus tulajdonságainak segítségével végezzük
el (lásd az előző kötet 13. fejezetét). Adjunk új osztályt a shapeData. cs nevű
projekthez. Implementáljuk a típust az alábbiak szerint:

[Se riali zable]


class ShapeData
{
ll A rajzolandó alakzat bal felső része.
public Point UpperLeftPoint { get; set; }

ll A rajzolandó alakzat aktuális színe.


public color Color { get; set; }

ll Az alakzat típusa.
public selectedshape shapeType { get; set; }
}

444
Teljes Windows Forms-alkalmazás létrehozása

Ezen a ponton a shapeoata három automatikus tulajdonságot használ fel,


amelyek közül kettő (a P oint és a color) meghatározása a system.orawing
névtérben található, ezért ne felejtsük ezt a névteret a forráskádba importálni.
Ez a típus [Seriali zabl e] attribútummal is rendelkezik. Egy későbbi lépés­
ben majd konfigurálni fogjuk a Mainwindow típust a shapeoata típusok listájá­
nak karbantartásához, amelyek az objektumsorosíló szolgáltatások segítsé­
gével elmenthetök (lásd a 21. fejezetet).

A ShapePickerDialog tipus meghatározása

Annak érdekében, hogy a végfelhasználó a kör és a téglalap alakú képtípusok


közül választani tudjon, most egy egyszerű, egyedi párbeszédablakot hozunk
létre ShapePickerDialog névvel (szúrjuk be ezt az új űrlapot). A kötelező OK és
Cancel gombon kívül (amelyek mindegyikéhez a megfelelő oialogResult érté­
ket rendeltük), a párbeszédablak olyan önálló GroupBox típust is használni fog,
amely két RadioButtant tartalmaz. Ezek az objektumok a radioButtoncirele és
a radi oButtonRect. A 27.27. ábra egy ilyen lehetséges megoldást ábrázol.

. Select
Shape

o (ide o Redqle

27.27. ábra: A ShapePickerDialog típus

Nyissuk meg a párbeszédablakunk forráskódját, ehhez jobb egérgombbal kat­


tintsunk az űrlaptenrezőre, majd válasszuk a View Code menüelemet. A My­
Paint Program névtérben határozzunk meg egy olyan felsorolt típust (sel ected­
shape névvel), amely minden lehetséges alakzatnevet definiál:

public enum selectedshape


{
cir cle, Rectangle
}

445
27. fejezet: Windows Forms programozás

Az alábbiak szerint módosítsuk az aktuális shapePickerDialog osztálytípust:

• Adjunk a selectedshape típushoz automatikus tulajdonságot. A hívó


ezen tulajdonság segítségével meg tudja határozni, melyik alakzatot
kell renderelni.

• A Properties ablakban kezeljük az OK gombhoz kapcsolódó click


eseményt.

• Implementáljuk az eseménykezelőt annak meghatározására, hogy a kör


rádiógombját jelölték-e ki (a checked tulajdonságon keresztül). Amennyi­
ben igen, a currentshape változó legyen selectedshape.circle típus,
egyéb esetben a tagváltozó legyen selectedshape.Rectangle.

A teljes kód a következőképpen néz ki:

public partial class ShapePickerDialog : Form


{
public selectedshape selectedshape { get; set; }
public ShapePickerDialog()
{
Initializecomponent();
}

private void btnOK_Click(object sender, EventArgs e)


{
if (radioButtoncircle.checked)
selectedshape Selectedshape.Circle;
else
selectedshape selectedshape.Rectangle;
}
}

Ezzel elkészítettük a program infrastruktúráját Most egyszerűen implemen­


táljuk a főablak többi menüeleméhez kapcsolódó click eseménykezelőket

Infrastruktúra hozzáadása a MainWindow tipushoz

A főablak szerkesztésének folytatásaként adjunk három új tagváltozót az űr­


laphoz, amelyek lehetövé teszik, hogy nyomon kövessük a választott alakza­
tot (a selectedshape felsorolt típuson keresztül), a választott színt (a system.
Drawing.co l or típuson keresztül), valamint a rendereit képek mindegyéket a

generikus L is t<T>-n keresztül:

446
Teljes Windows Forms-alkalmazás létrehozása

public partial class Mainwindow : Form


{
ll Aktuálisan rajzolandó alakzat l szín.
private Selectedshape currentshape;
private Color currentcolor = Color.DarkBlue;

ll Ez tartja karban valamennyi shapeData típust.


private List<ShapeData> shapes = new List<ShapeData>();

A Properties ablakban kezeljük a Form-leszármazott típushoz kapcsolódó


MouseDown és Paint eseményeket. Ezeket majd egy későbbi lépésben implemen­

táljuk Egyelőre az a lényeges, hogy az IDE az alábbi kódcsonkat generálta:

private void Mainwindow_Paint(object sender, PaintEventArgs e)


{
}

private void Mainwindow_Mouseclick(object sender, MouseEventArgs e)


{
}

A Tools menü funkcionalitásának implementálása

Annak érdekében, hogy a felhasználók beállíthassák a currentshape tagválto­


zót, a párbeszédablak megjelenítéséhez, implementáljuk a Tools >Pick Shape
menüelemhez kapcsolódó click kezelőt, és a felhasználó választásának meg­
felelőerr rendeljük hozzá ezt a tagváltozót:

private void pickshapeToolstripMenuitem_click(object sender,


EventArgs e)
{
ll Töltsük be a párbeszédablakot, és állítsuk be a megfelelő
ll alakzattípust.
shapePickerDialog dlg = new ShapePickerDialog();
if (DialogResult.OK == dlg.ShowDialog())
{
currentshape = dlg.selectedshape;
}
}

Annak érdekében, hogy a felhasználó beállíthassa a currentcolor tagváltozót, a


system.windows. Forms. colorDialog típus használatához implementáljuk a
Tools >Pick Color menüelemhez kapcsolódó kattintási esemény kezelőjét

447
27. fejezet: Windows Forms programozás

private void pickcolorToolStripMenuitem_Click(object sender,


EventArgs e)
{
colorDialog dlg = new ColorDialog();
if (dlg.showDialog() == DialogResult.OK)
{
currentcolor = dlg.color;
}
}

Ha a programot mostani állapotában szeretnénk futtatni, és kijelölnénk a


Tools >Pick Color menüelemet, akkor a 27.28. ábrán látható párbeszédablak
jelenne meg.

Color

Basic colars:

rrr:;r•
.,, ...
•• • • •••
••• • • • ••
••• • • • ••
..... . ,
Custom colors:

rrrrrrrr
rrrrrrrr
l Define Custom Co:on; » J CokxlSolid Lun: 179 llue: 141

�l Uroeel J Addio Custom Cobs

27.28. ábra: A ColorDialog típus készlete

Végül a List<T> tagváltozó tartalmának kiürítéséhez és a Paint esemény In­


validate() metóduson keresztüli, programozottan történő kiváltáshoz imp­

lementáljuk a Tools > Clear Surface menükezelőt:

private void clearsurfaceToolstripMenuitem_click(object sender,


EventArgs e)
{
shapes. Clear();

ll Ez kiváltja a Paint eseményt.


Invalidate();
}

448
Teljes Windows Forms-alkalmazás létrehozása

A grafikus kimenet rögzítése és renderetése

Mivel az rnvalidate() hívása elsüti a Paint eseményt, mindenképpen szük­


ség van a Paint eseménykezelőben lévő forráskód összeállítására. A cél az,
hogy végigiteráljunk a (most még üres) L ist<T> tagváltozó minden egyes
elemén, és rendereljünk egy kört vagy egy négyzetet az egér aktuális helyén.
Az első lépés az, hogy implementáljuk a Mouseoown eseménykezelőt, hogy be­
szúrjuk az új shapeoata tipust a generikus List<T> tipusba, a felhasználó által
kijelölt szín, az alakzat tipusa és az egér aktuális helye alapján:

private void Mainwindow_Mouseclick(object sender, MouseEventArgs e)


{
ll Készítsünk Shapeoata típust a felhasználó aktuális
ll választása alapján.
shapeoata sd= new shapeoata();
sd.shapeType = currentshape;
sd.color = currentcolor;
sd.UpperLeftPoint = new Point(e.x, e.Y);

ll Adjuk a List<T> típushoz, és kényszerítsük az űrlapot,


ll hogy újrafesse önmagát.
shapes.Add(sd);
rnvalidate();
}

Implementáljuk a Paint eseménykezelőt az alábbiak szerint:

private void Mainwindow_Paint(object sender, PaintEventArgs e)


{
ll vegyük az ablak Graphics típusát.
Graphics g= e.Graphics;

ll Rendereljük az alakzatokat a választott színben.


foreach (ShapeData s in shapes)
{
ll Rendereljünk egy 20 x 20 képpont nagyságú téglalapot vagy
ll kört a megfelelőszín felhasználásával.
if (s.ShapeType== Selectedshape.Rectangle)
g.FillRectangle(new SolidBrush(s.Color),
s.upperLeftPoint.x,
s.upperLeftPoint.Y, 20, 20);
else
g.FillEllipse(new SolidBrush(s.color),
s.UpperLeftPoint.X,
s.upperLeftPoint.Y, 20, 20);
}
}

449
27. fejezet: Windows Forms programozás

Ha most futtatnánk az alkalmazást, immár tetszés szerinti számú, különböző


színű alakzatot tudnánk rendereini (lásd a 27.29. ábrát).

!G\ Fun with the Paint Event


file Iools



• •
• •


27.29. ábra: A MyPaintProgram működés közben

A sorositási logika implementálása

A projekt utolsó fázisában implementáljuk a File > Save... és a File > Load...
menüelemek kattintásiesemény-kezelőjét. Mivel a shapeData rendelkezik a [Se­
rialization] attribútummal (és mivel maga a L ist<T> sorosítható), a Windows

Forms saveFileDialog típus segítségével gyorsan kimenthetjük az aktuális gra­


fikus adatokat. A system.Runtime.serial ization.Formatters.Binary és a sys­
tem. ro névterek meghatározásához először egészítsük ki a using direktívákat.

ll A bináris formázóhoz.
using system.Runtime.serialization.Formatters.Binary;
using system.ro;

Ezzel frissítsük a File > Save... kezelőt az alábbiak szerint:

private void saveToolstripMenurtem_click(object sender, EventArgs e)


{
using (saveFileDialog saveDlg = new saveFileDialog())
{
ll Kanfiguráljuk a mentés párbeszédablak megjelenését és
ll működését.
saveDlg.InitialDirectory = ".";
saveDlg.Filter = "shape files (*.shapes)l*.shapes";
saveDlg.RestoreDirectory = true;
saveDlg.FileName = "MyShapes";

450
Teljes Windows Forms-alkalmazás létrehozása

ll Ha az OK gombra kattintanak, megnyílik az új


ll fájl, és megtörténik a List<T> sorosítása.
if (saveDlg.ShowDialog() == DialogResult.OK)
{
Stream mystream = saveDlg.openFile();
if ((mystream != null))
{
ll Mentsük el az alakzatokat.
BinaryFormatter myBinaryFormat = new BinaryFormatter();
myBinaryFormat.Serialize(myStream, shapes);
mystream.close();
}
}
}
}

A File > Load eseménykezelő megnyitja a kijelölt fájlt, és a Windows Forms


OpenFileDial og típusa segítségével visszaállítja az adatokat a List<T> tagvál­
tozóba:

private void loadToolstripMenuitem_Click(object sender, EventArgs e)


{
using (OpenFileDialog openDlg = new openFileDialog())
{
openDlg.InitialDirectory = ".";
openDlg.Filter = "shape files (*.shapes)l*.shapes";
openDlg.RestoreDirectory = true;
openDlg.FileName = "Myshapes";

if (openDlg.ShowDialog() == DialogResult.OK)
{
Stream mystream = openDlg.OpenFile();
if ((mystream != null))
{
ll vegyük az alakzatokat.
BinaryFormatter myBinaryFormat = new BinaryFormatter();
shapes =
(List<ShapeData>)myBinaryFormat.Deserialize(mystream);
mystream.close();
rnvalidate();
}
}
}
}

A sorosítás logikája már ismerős a 21. fejezet alapján. Érdemes hangsúlyoz­


nunk, hogy a saveFileDialog és az openFileDialog típusok mindegyike támo­
gatja a meglehetősen rejtélyes sztringértéket tartalmazó Filter tulajdonságot.

451
27. fejezet: Windows Forms programozás

Ez a szűrő számos beállítást, mint például a fájlok kiterjesztését(*. shapes)


vezérli a mentés/ megnyitás párbeszédablakokban. A Fi leN ame tulajdonság
pedig azt vezérli, hogy mi legyen a létrehozandó fájl alapértelmezett neve. Ez
a név ebben a példában Myshapes.
Ezzel elkészült a rajzolóalkalmazás. Az aktuális grafikus adatokat most
tetszőleges számú *. shapes fájlba menthetjük le vagy azokból tölthetjük be.
Ha szeretnénk bővíteni ezt a Windows Forms-alkalmazást, használhatunk
további alakzatokat, vagy tegyük lehetővé, hogy a felhasználók meghatároz­
zák a rajzolt alakzatok méreteit, esetleg kiválaszthassák a mentett adatok
formátumát(bináris, XML vagy SOAP).

Összefoglalás

A jelen fejezet célja az volt, hogy megvizsgáljuk a tradicionális asztali alkalmazá­


sok Windows Forms és a GDI+ API-k segítségével történő készítésének folyama­
tát, amelyek az 1.0 verzió óta a .NET keretrendszer részét képezik. A Windows
Forrus-alkalmazások minimum egy a Forrnot kiegészítő típussal és egy Main()
metódussal rendelkeznek, ez utóbbi kommunikál az App li cati on típussal.
Ha az űrlapot felhasználóifelület-elemekkel (pl. menürendszerek, GUI­
beviteli vezérlők stb.) szeretnénk feltölteni, mindezt úgy tehetjük meg, hogy
új objektumokat szúrunk be a származtatott contra ls gyűjtemény be. Azt is
bemutattuk, hogyan kezelhetjük az egér-, a billentyűzet- és a renderelési
_
eseményeket. Továbbá megismerkedhettünk a Graphics típussal, valamint a
grafikus adatok futásidejű generálásának számos módjával.
A Windows Forms API-t(valamilyen formában) felváltotta a .NET 3.0 be­
vezetésével megjelenő WPF API (ezt lásd a következő fejezetben). Amíg a
WPF lesz a felturházott grafikus felületek választott eszközrendszere, a Win­
dows Forms API használata a standard üzleti alkalmazások, házilag készített
alkalmazások és egyszerű konfigurációs segédprogramok összeállításának a
legegyszerűbb (és sokszor a legközvetlenebb) módja. Így a Windows Forms
még hosszú évekig a .NET-alaposztálykönyvtárak részét fogja képezni.

452
HUSZONNYOLCADIK FEJEZET

A WPF és az XAML

Az előző fejezetben megismerkedtünk a system. windows. Forms. dll és a system.


orawi ng. dll szerelvényekben rendelkezésünkre álló funkcionalitássaL Mint lát­

tuk, a Windows Forms API a .NET platform eredeti GUI-eszközrendszere,


amelynek típusait kifinomult felhasználói felületek készítése során alkalmazhat­
juk. Miközben a .NET 3.5 teljes mértékben tárnagalja a Windows Forms/GDI+­
technológiákat, a .NET 3.0 verziójától kezdve a Microsoft egy teljesen új asztali
API-t, a Windows Presentation Foundationt (WPF) jelentetett meg.
Ez a fejezet bevezető a WPF használatához, az új felhasználói felület ke­
retrendszer létrejöttének okát vizsgálja, és rövid áttekintést nyújt a különböző,
API által támogatott WPF-alkalmazástípusokról. Ezt követően megismerke­
dünk a WPF programozási modell lényegével, és megvizsgáljuk az Applica­
tion és a window típusok szerepét, valamint az kulcsfontosságú WFP-szerelvé­

nyeket és -névtereket.
A fejezet további részében egy vadonatúj XML-alapú nyelvet tanulmá­
nyozunk: a XAML-t (Extensible Application Markup Language - bővíthető
alkalmazás jelölőnyelv). Láljuk majd, hogy a XAML a WPF-fejlesztők számá­
ra lehetövé teszi a felhasználói felület definícióinak elkülönítését az őket ve­
zérlő logikától. A fejezetben rendkívül fontos témaköröket vizsgálunk, pél­
dául a csatolt tulajdonság szintaxist, a típusátalakítókat, a markup bővítőket,
és megtanuljuk, hogyan elemezzünk futásidőben XAML-kódot. A fejezet vé­
gén különböző WPF-specifikus eszközökkel ismerkedünk meg, amelyeket a
Visual Studio 2008 IDE foglal magában, és betekintést nyerünk a Microsoft
Expression Blend szerepébe is.

A WPF mozgatórugója
Az évek alatt a Microsoft több grafikus felhasználói felület eszközrendszert
készített (nyers C/C++/Win32 API fejlesztés, VB6, MFC stb.) asztali alkal­
mazások fejlesztéséhez. Az API-k a grafikus felülettel rendelkező alkalmazá­
sok alapelemeinek - például a főablakok, a párbeszédablakok, a vezérlőele-
28. fejezet: A WPF és az XAM L

mek, a menürendszerek és egyéb fontos összetevők - megjelenítéséhez bizto­


sították a alapot. A .NET platform első verziójának megjelenésével a Win­
dows Forms API (lásd a 27. fejezetet) hamar a felhasználói felület fejlesztés
közkedvelt modellje lett, mivel egyszerű és rendkívül hatékony objektum­
madellel rendelkezik.
Bár sikeresen létrehoztak szárnos teljes értékű asztali alkalmazást a Win­
dows Forms segitségével, igazság szerint ez a programozási modell elég aszim­
metrikus. A System. windows. Forms. dll és a system. Drawing. dll nem támogat
közvetlenül több olyan technológiát, amelyek kész asztali alkalmazások készí­
téséhez szükségesek. Gondoljunk csak a WPF megjelenése előtti GUI-fejlesztés
(például a .NET 2.0) ad hoc természetére (lásd a 28.1. táblázatot).

Vezérlőelemekkel rendelkező űrlapok Windows Forms


készítése

2D grafika támogatása GDI+ (system. Drawin g. dll)

3D grafika támogatása DirectX API-k

Vicleofolyam támogatása Windows Media Player API-k

Folyamatstílusú dokumentumok PDF-fájlok kezelése programozott


támogatása módon

28.1. táblázat: A .NET 2.0 megoldásai az elvárt működésre

Mint lá�uk, a Windows Forms fejlesztőknek több különböző API és objek­


tummodell típusait kell alkalmazniuk. Noha a különböző API-k használatá­
nak módja szintaktikai szempontból hasonlónak tűnhet (végül is C#-kódról
van szó), ahhoz azonban nem fér kétség, hogy a technológiák gyökeresen el­
térő gondolkodásmódot igényelnek. A DirectX segítségével készült 3D ani­
mációk létrehozásához például teljesen más tudás szükséges, mint az adat­
kapcsolatok felépítéséhez. Annyi bizonyos, hogy egy Windows Forms prog­
ramozó számára nem kis feladat eligazodni az API-k sokféleségén.

A különböző API-k egységesitése

A WPF célja az volt, hogy egységesített objektummodellben ötvözze ezeket a


korábban nem kapcsolódó programozási feladatokat. Ha tehát 3D animációt
kell készítenünk, nem kell kézzel programoznunk a DirectX API-t (de, ha

454
A WPF mozgatórugója

szeretnénk, megtehetjük), mivel a 3D funkcionalitást a WPF magában foglal­


ja. A letisztult lehetőségeket a 28.2. táblázatban láthatjuk, amely bemutatja a
.NET 3.0 megjelenésével rendelkezésünkre álló, asztali gépre történő fejlesz­
tés modelljét.

��1t� �,'�:�·,�>:')ii�: ,���0:L��:;c���������--


Vezérlőelemekkel rendelkező űrlapok WPF
készítése

2D grafika támogatása WPF

3D grafika támogatása WPF

Vicleofolyam támogatása WPF

Folyamatstílusú dokumentumok WPF


támogatása

28.2. táblázat: A NET 3.0 megoldásai az elvárt múködésre


.

Kapcsolatok elkülönítése a XAML segitségével

A WPF egyik legvonzóbb előnye, hogy biztosítja egy Windows-alkalmazás


megjelenésének és funkcionalitásának, valamint mögöttes programozási lo­
gikájának elkülönítését. A XAML nyelvben a markup segítségével definiálhat­
juk az alkalmazás felhasználói felületét A markup (amelyet ideális esetben
művészi érzékkel megáldott fejlesztők az erre kifejlesztett eszközökkel hoz­
nak létre) felügyelt programkódhoz kapcsolható, ezzel biztosíthatjuk a prog­
ram funkcionalitását.

Megjegyzés A XAML nem csak a WPF-alkalmazásoknál használható! Bármely alkalmazás leír­


hatja a .NET-objektumok hierarchiáját a XAML segítségéve!, még akkor is, ha azoknak semmi
közük a látható felhasználói felülethez. Például a XAML használatával egyedi tevékenységeket
készíthetünk Windows Workflow Foundation alkalmazások számára.

Ha elmélyülünk a WPF rejtelmeiben, meglepetéssel tapasztalhatjuk, hogy az


"
"asztali markup milyen rugalmas lehetőségeket biztosít. A XAML segítsé­
gével nemcsak egyszerű felhasználói felület elemeket (gombokat, rácsokat,
listamezőket stb.) definiálhatunk a markupban, hanem grafikus renderelést,
animációkat, adatkötési logikát és multimédia-funkcionalitást (például 'Vi-

455
28. fejezet: A WPF és az XAML

deolejátszást) is. Egy kerek gomb - amely a vállalat logójának animációja -


definiálása például csak néhány sornyi markupot igényel. A WPF-elemeket
stílusokon és sablonokon keresztül megváltoztatha�uk, ami lehetővé teszi,
hogy az alkalmazás központi feldolgozási forráskódjától függetlenül, a lehető
legkevesebb erőfeszítéssel módosítsuk az alkalmazás külső megjelenését és
funkcionalitását.
Ezeknek köszönhetőerr a WPF-rendszerekben az egyedi vezérlőelemek ké­
szítése szükségtelenné válik. A Windows Forms fejlesztéstől eltérőerr az egyedi
WPF vezérlőelem-könyvtárak készítésének egyetlen elfogadható oka, ha a ve­
zérlőelemek viselkedését kell megváltoztatnunk (egyedi metódusokat, tulajdon­
ságokat vagy eseményeket kell hozzáadnunk; a virtuális tagok felüldefiniálá­
sához létező vezérlőelemeket kell elhelyeznünk alosztályban; stb.). Ha pusztán
a vezérlőelem (például a kerek gomb az animációval) megjelenését és funkeionali­
tását kell módosítanunk, ezt a markup segítségével megoldha�uk.

Megjegyzés Egyedi WPF-vezérlőelemek készítésének másik oka lehet a bináris újrafelhasz­


nálás (a WPF vezérlőelem-könyvtáron keresztül), illetve olyan vezérlőelemek létrehozása,
amelyek egyedi tervezési idejű funkcionalitást és a Visual Studio 2008 IDE környezettel integ­
ráciét biztosítanak.

Optimalizált renderetési modell biztositása

Ne feledjük, hogy a WPF a Windows Vista operációs rendszerek által támo­


gatott új videoillesztőprogram-modellt optimalizált módon alkalmazza. No­
ha a WPF-alkalmazásokat fejleszthe�ük és telepíthe�ük Windows XP operá­
ciós rendszerre (valamint Windows Server 2003-ra ) is, ugyanaz az alkalma­
zás Vista operációs rendszeren jobb teljesítményt fog nyújtani, különösen
animációs/multimédia-szolgáltatások esetében. Ez annak köszönhető, hogy
a WPF megjelenítési szolgáltatásait a DirectX motor kezeli, amely hatékony
hardveres és szaftveres renderelést biztosít.

Megjegyzés Ismét fontos kiemelni: a WPF nem korlátozódik a Windows Vista operációs rend­
szerre! Noha a Vista operációs rendszerben már a telepítőben rendelkezésünkre állnak a .NET
3.0 könyvtárak (a WPF is), a .NET Framework 3.5 SOK (fejlesztők számára) és a .NET 3.5 futta­
tórendszer (végfelhasználók számára) telepítését követően XP és Windows Server 2003 rend­
szerekben is készíthetünk és futtathatunk WPF-alkalmazásokat.

456
A WPF mozgatórugója

A WPF-alkalmazások fegyelmezettebb viselkedést tanúsítanak Windows Vis­


ta rendszereken. Ha egy grafikus elemekben gazdag alkalmazás összeomlik,
akkor az alkalmazás nem rántja magával az egész operációs rendszert (a kék
halál módján); ehelyett a rendetlenkedő alkalmazás egyszerűen leáll. Köztu­
dott, a hírhedt kék halál oka gyakran az összeférhetetlen video-illesztő­
programokra vezethető vissza.

További WPF -központú hasznos tulajdonságok

A Windows Presentation Foundation egy olyan új API asztali alkalmazások


készítésére, amely egyetlen objektummodellben egyesíti a különböző asztali
API-kat, és a XAML segítségével világosan elkülöníti a kapcsolatokat. Ezen lé­
nyeges pontokon kívül a WPF-alkalmazások egyéb vonzó tulajdonságokkal is
rendelkeznek, ezekkel a következő fejezetekben ismerkedhetünk meg. A kö­
vetkező felsorolás a kulcsfontosságú szolgáltatásokat foglalja össze röviden:

• Különböző elrendezéskezelők Góval több mint a Windows Forms ese­


tén), amelyek a tartalom el- és áthelyezéséhez rendkívül rugalmas ve­
zérlést biztosítanak.

• Fejlett adatkötési motor alkalmazása, amellyel a tartalom különböző


módon köthető a felhasználói felület elemeihez.

• Beépített stílusmotor, amely segítségével a WPF-alkalmazás számára


"
"témák definiálhaták

• Vektorgrafika alkalmazása, amely segítségével az alkalmazást hosztoló


képernyő méretének és felbontásának megfelelően a tartalom automa­
tikusan újraméretezhető.

• 2D és 3D grafika, animációk, video- és audiolejátszás támogatása.

• Gazdag tipográfia API, például az XPS- (XML Paper Specification, do­


kumentumok, a rögzített dokumentumok (WYSIWYG), a folyamatos
dokumentumok és a dokumentumjegyzetek (például a Sticky Notes
API) támogatása.

• Együttműködés korábbi GUI-modellekkel (például Windows Forms,


ActiveX, Win32 HWND-k).

457
28. fejezet: A WPF és az XAML

A WPF-alkalmazások különböző tipusai

A WPF API segítségével sokféle GUI-központú alkalmazás készíthető, ame­


lyek alapvetőerr csak navigációs szerkezetükben és telepítési modelljükben
témek el egymástól. A következő részek rövid áttekintést nyújtanak az egyes
alkalmazástípusokróL

Hagyományos asztali alkalmazások

Az első (és talán legelterjedtebb) típus a hagyományos végrehajtható szerel­


vény, amely a helyi számítógépen fut. A WPF segítségével például szöveg­
szerkesztőt, rajzoló programot vagy multimédiás programot - digitális zene­
lejátszót, fényképnézegetőt stb. - készíthetünk A többi asztali alkalmazáshoz
hasonlóan ezeket az *.exe fájlokat hagyományos eszközökkel (telepítőprog­
ramokkal, Windows Installer csomagokkal stb.) telepíthetjük, vagy a Click­
Once technológia révén lehetővé tehetjük az asztali alkalmazások megosztá­
sát és telepítését távoli webkiszolgálón keresztül.
Mindezek ismeretében kijelenthetjük, hogy a WPF egy új API, amellyel ha­
gyományos asztali alkalmazásokat készíthetünk Programozási szempontból
az ilyen WPF-alkalmazások a párbeszédablakok, az eszköztárak, az állapotsá­
vok, a menürendszerek és egyéb felhasználóifelület-elemek megszakott készle­
tén kívül (legalább) a Windows és az Appl i cati on típusokat alkalmazzák

Navigációalapú WPF -alkalmazások

A WPF-alkalmazások igény szerint működhetnek navigációalapú szerkezettel


is, és így a hagyományos asztali alkalmazás egy webböngésző-alkalmazás
alapvető funkcionalitásával rendelkezik. Ezen modell révén az elkészült asztali
" "
*.exe
"előre és "vissza gombjaival a felhasználó előre és hátra lépkedhet a
felhasználói felület különböző képemyői, más néven oldalai között. Az alkal­
mazás nyilvántartja az oldalak listáját, biztosítja az oldalak közötti navigáció­
hoz szükséges infrastruktúrát és az oldalak közötti adatátvitelt (a webes alkal­
mazásokhoz hasonlóan), valamint karbantartja az előzménylistát A 28.1. ábrán
látható példa jól mutatja az imént említett funkcionalitást, a Vista Intézője épp
ezt használja. Figyeljük meg a navigációs gombokat (és az előzménylistát) az

ablak bal felső sarkában.

458
A WPF-alkalmazások különbözőtípusai

Függetlenül attól, hogy a WPF asztali alkalmazás a webes alkalmazások­


hoz hasonló navigációs sémát használhat, ne feledjük, hogy a felhasználói fe­
lület tervezésekor ez csupán egy lehetőség. Maga az alkalmazás valamivel
több egy helyi végrehajtható szerelvénynél, amely asztali számítógépen
üzemel, és a hasonló megjelenésen és funkcionalitáson kívül semmi köze a
webes alkalmazásokhoz. Programozási szempontból a navigációs szerkezetet
olyan típusok képviselik, mint a Page, a Navi gationwindow és a Frame.

-1 SQL Server 2000 �ple o�bbaseo 1 11!!1 E-mazl � Burn �?


-- -- - ---

1l Name
Startup
Date:mo
--· - - - ... - ···-·-··--

Notepad++ [§t instnwnd.sql 2/4/2004

Microsoft ASP.NET 2.0 AJAX Extensions


v
l W in<tpubs.sql
Ql NORTHWND.LDF
Maintenance
� l (9 NORTHWND.MDF
:
+ DOSBox-0.65 -. l Q-Puss MDF ····

Administrative Tools
� LJ ! (]l PUB5_LOG.LDF
Aceesseries
l � ReadMe_SQL2000SampleDbScripts.htm
Programs:

Start Menu
...- l • ! lll
.J
PUBS.MDF Date mcdified: 12/13/2004 5:14 PM
SQL Setver Database Pri mary Data File Size: 1.25 MB
Date created: 12/13/2004 5:14 PM

28. 1. ábra: Navigációalapú asztali program

XBAP-alkalmazások

A WPF lehetövé teszi olyan alkalmazások készítését, amelyeket webböngésző­


ben hasztolhatunk A WPF-alkalmazások ezen típusa az úgynevezett XAML­
böngészőalkalmazás (XAML browser application), röviden XBAP. Ebben a
modellben a felhasználó a meghatározott URL-re navigál, és ekkor az XBAP­
alkalmazás (amely *. xbap fájlkiterjesztéssei rendelkezik) a háttérben letöltő­

dik és települ a helyi számítógépre. A végrehajtható alkalmazások hagyomá­


nyos ClickOnce telepítésétől eltérően az XBAP-programot közvetlenül a bön­
gésző hosztolja, és az alkalmazás követi a böngésző belső navigációs rend­
szerét. A 28.2. ábrán egy XBAP-alkalmazást láthatunk működés közben
(konkrétan az Expenslt WPF példaprogramot, amelyet .NET Framework 3.5
SDK tartalmaz).

459
28. fejezet: A WPF és az XAML

=@l

Email Alias: Som�e�example.com

Employee Number. 57304

C ost Center: 4034

"'
Mexican Lunch U

____________ ;�:�0:"�;��:'��=-- �-��750


Taxi
Hotel
----- ��-=------�[
__ _ _ _ __ _ _ __ __

60 l
----'-----

����'MY7.-•";a{,.�".-;F.�:P"�'-l"
' · · ·· · · ·
...-"':-�
·
·
Total Expenses: $ 277

tili Computer l Proleeted Mode: Off

28.2. ábra: Az XBAP-programok letöltődnek a helyi számítógépre, és a webböngésző hasztolja őket

A WPF ezen típusának egyik lehetséges hátránya, hogy az XBAP-programo­


kat Microsoft Intemet Explorer 6.0 (és újabb verziói) vagy Firefax-böngésző­
ben kell hosztolni. Ha vállalati intraneten keresztül telepítjük ezeket az al­
kalmazásokat, a böngészők kompatibilitása nem jelenthet problémát, mivel a
rendszergazdák "diktatórikusan" meghatározhatják, hogy a felhasználék gé­
pére milyen böngészőket kell telepíteni. Ha azonban a külvilág számára sze­
retnénk elérhetővé tenni az XBAP-t, nem várhatjuk el, hogy az összes fel­
használó Intemet Explorer/Firefax-böngészőt használjon, így néhány külső
felhasználó nem fogja látni WPF XBAP alkalmazásunkat
A másik probléma, amellyel tisztában kell lennünk, hogy az XBAP-al­
kalmazások biztonságos futtatókömyezetben, úgynevezett internetzónában
üzemelnek. A 20. fejezetből emlékezhetünk rá, hogy az ilyen virtuális kör­
nyezetbe töltött .NET-szerelvények korlátozott módon fémek hozzá a rend­
szer erőforrásaihoz (például a helyi fájlrendszerhez vagy a rendszerleíró
adatbázishoz), és nem alkalmazhatják szabadon bizonyos .NET API-k olyan
tulajdonságait, amelyek biztonsági rést jelentenének. Az XBAP nem alkalmas
a következő feladatok végrehajtására:

• Egyedülálló ablakok létrehozása és megjelenítése.

• Az alkalmazás által definiált párbeszédablakok megjelenítése.

• Az XBAP által indított Save párbeszédablak megjelenítése.

460
A WPF-szerelvények vizsgálata

• Fájlrendszer-hozzáférés (elkülönített tárolóhely használata megengedett).

• Korábbi felhasználóifelület-modellek (Windows Forms, ActiveX) al­


kalmazása vagy nemfelügyelt kód hívása.

A másodiagos ablakok (vagy párbeszédablakok) létrehozásának hiánya első


pillantásra komoly korlátozásnak t űnhet. Valójában, az XBAP a korábban
említett oldalnavigációs modell segítségével több felhasználói felület megje­
lenítésére képes.

Silverlight-alkalmazások

A WPF és a XAML alapot biztosít a Silverlight plaiformközi, WPF-központú


bővítmény számára. A Silverlight SDK segítségével böngészőalapú alkalma­
zásokat készíthetünk, amelyeket MAC OS X, valamint Microsoft Windows
hasztolhat (a keretrendszer további operációs rendszereket is támogat).
A Silverlight segítségével rendkívül sokoldalú (és interaktív) webes alkalma­
zásokat építhetünk. A WPF-hez hasonlóan a Silverlight is rendelkezik vektorala­
pú grafikus rendszerrel, animáció támogatással, gazdag szöveges dokumentum­
madellel és multimédia-támogatással. Továbbá, a Silverlight 1.1 verziójától
kezdve néhány .NET alaposztály-könyvtárat is felhasználhatunk alkalmazása­
inkban. Ezek a könyvtárak WPF-vezérlőelemeket, LINQ-támogatást, generikus
gyűjtemény típusokat, webszolgáltatás-támogatást és az msco r l i b. dll tekinté­
lyes részét (fájl I/0, XML-műveletek stb.) foglalják magukban.

Megjegyzés A könyv jelen kiadása nem foglalkozik a Silverlight-technológiávaL Az API-val kapcso­


latban bővebb információkat a http://www.microsoft.com/silverlight webhelyen találunk. A web­
helyről letölthetjük az ingyenes Silverlight SDK-t (magát a Silverlightot is), mintaprojekteket tanul­
mányozhatunk, és további ismereteket szerezhetünk a WPF-fejlesztés ezen izgalmas típusáróL

A WPF-szerelvények vizsgálata

A fejleszteni kívánt WPF-alkalmazás típusától függetlenül a WPF végered­


ményben alig több a .NET-szerelvényekbe csomagolt típusok gyűjteményé­
néL A 28.3. táblázat a WPF-alkalmazások készítéséhez használt kulcsfontos­
ságú szerelvényeket ismerteti, amelyekre új projektek létrehozása során hi­
vatkoznunk kell (reményeinknek megfelelően a Visual Studio 2008 WPF­
projek*i automatikusan hivatkoznak a szükséges szerelvényekre).

461
28. fejezet: A WPF és az XAML

Presentationcore.dll A szerelvény a WPF GUI rétegének alapját képező ti­


pusok nagy részét definiálja. Ez a szerelvény magában
foglalja például a WPF Ink API (a Pocket PC-k és a
Tablet PC-k stylusceruza bemenetének programozásá­
hoz), több animációs primitív (a System. windows. Me­
dia.Ani mation névtéren keresztül) és számos grafikus

renderetési típus (a System.windows. Media névtér se­


gítségéve!) támogatását.

Presentation­ Ebben a szerelvényben WPF-vezérlőelem készletet, to­


Foundation.dll vábbi animációs és multimédia-típusokat, adatkötés­
támogatást és olyan típusokat találunk, amelyek lehe­
tővé teszik a XAML és egyéb WPF-szolgáltatások prog­
ramozott hozzáférését.

windowsBase.dll Ez a szerelvény definiálja azokat az alapvető (és sok


esetben alacsonyszintű) típusokat, amelyek a WPF API
infrastruktúráját képezik. Itt találjuk a WPF száltípusa­
it, a biztonsági típusokat, a különféle típusátalakítókat,
valamint más alapvető programozási primitíveket
(Point, vector, Rect stb.) képviselő típusokat.

28. 3. táblázat: Alapvető WPF-szerelvények

Ez a három szerelvény együttesen számos új névteret és több száz új .NET­


osztályt, -interfészt, -struktúrát, -felsorolást és -metódusreferenciát definiál.
A .NET Framework 3.5 SOK dokumentációja a teljes referenciát magában

foglalja, a 28.4. táblázat pedig a kulcsfontosságú névtereknek (de nem az ösz­


szesnek) a szerepét ismerteti.

System.windows Ez a WPF gyökérnévtere. A névtérben kulcsfontos­


ságú típusokat találunk (például az Ap p 1 i cation és
a window típust), amelyekre a WPF asztali projektek­
nek szüksége lehet.

System.windows.Controls Itt találjuk az összes WPF-vezérlőelemet, például a


menürendszerek készítéséhez szükséges típusokat,
az eszköztippeket és több elrendezéskezelőt

462
A WPF-szerelvények vizsgálata

System.windows.Markup A névtér több olyan típust definiál, amely lehetövé


teszi a XAML-markup (és a megfelelő bináris for­
mátum, a BAML) programozott elemzését és fel­
dolgozását.

System.Windows.Media Több médiaközpontú névtér gyökérnévterének neve.


Ezekben a névterekben találjuk az animációkkal, a
3D rendereléssel, a szövegrendereléssel és egyéb
multimédia-primitívekkel való munkához szükséges
típusokat.

System. windows. Navigat ion A névtér a XAML-böngészőalkalmazások (XBAP),


valamint a standard asztali alkalmazások- amelyek
a navigációs oldalmodellel dolgoznak - által alkal­
mazott navigációs logikát biztosító típusokat foglalja
magában.

System.windows.shapes Ez a névtér többféle 2D grafikai típust ( Rectangle,


Pol y gon stb.) definiál, amelyeket a WPF-keretrend­

szer különböző összetevői használnak.

28.4. táblázat: Alapvető WPF-névterek

A fejezet további részében részletesen megismerkedünk a WPF programozási


modelljével, és megvizsgáljuk a system. windows névtér két tagját, amelyet
minden hagyományos asztali fejlesztésben megtalálunk: az App l ication és a
window tagot.

Az Application osztály szerepe

A system.windows.Application osztálytípus a futó WPF-alkalmazás globális


példányát képviseli. A Windows Forms megfelelőjéhez hasonlóan ez a típus
biztosítja a Run() metódust (az alkalmazás indításához), az események soro­
zatát, amelyek segítségével az alkalmazás élettartamát befolyásolhatjuk (pél­
dául a startup és az Exit) , és a XAML-böngészőalkalmazásokra jellemző ta­
gokat (például eseményeket, amelyek kiváltódnak, amikor a felhasználó az
oldalak között navigál). A 28.5. táblázat a kulcsfontosságú tagok közül ismer­
tet néhányat.

463
28. fejezet: A WPF és az XAML

current Ez a statikus tulajdonság lehetővé teszi, hogy a kódunk bár­


mely pon�án hozzáférjünk a futtatott Application objektum­
hoz. A tulajdonság akkor hasznos, ha egy ablaknak vagy egy
párbeszédablaknak hozzá kell férnie ahhoz az Application
objektumhoz, amely létrehozta.

Mainwindow Ez a tulajdonság lehetővé teszi, hogy programozott módon le­


kérdezzük vagy beállítsuk az alkalmazás főablakát.

Properties A tulajdonság segítségével Jekérdezhe�ük és beállítha�uk az


adatokat, amelyek a WPF-alkalmazás minden összetevője (ab­
lakok, párbeszédablakok stb.) számára hozzáférhetők. Ez a tu­
lajdonság több szempontból nagyon hasonlít az ASP.NET we­
bes alkalmazásokban használt alkalmazásváltozókra.

startupuri Ez a tulajdonság beolvassa vagy beállí�a az URI-t, amely meg­


határozza az alkalmazás indítása során automatikusan megje­
lenő ablakot vagy oldalt.

windows A tulajdonság a windowco llection típust adja vissza, amely


az Applica tion objektumot létrehozó szálon belül készült ösz­
szes ablakhoz hozzáférést biztosít. A tulajdonság akkor lehet
rendkívül hasznos, ha az alkalmazás megnyitott ablakain kell
végigiterálnunk és módosítanunk az egyes ablakok állapotát
(például az összes ablakot kisméretűre állí�uk).

28. 5. táblázat: Az Application típus alapvető tulajdonságai

A Windows Forms megfelelőjétől eltérően a WPF Application típus nem ki­

zárólag a statikus tagokon keresztül muta* meg a funkcionalitását. A WPF­


programok inkább egy osztályt definiálnak, amely kiterjeszti ezt a típust, és
az belépési pontként szolgál a végrehajtható fájl számára, például a követke­
zőképpen:

ll A WPF-program globális
ll alkalmazásobjektumának definíciója.
class MyApp : Application
{
[STA Thread]
static void Main()
{
ll Események kezelése, az alkalmazás futtatása,
ll a főablak indítása stb.
}
}

464
A WPF-szerelvények vizsgálata

Az egyik következő példában egy teljes típust készítünk, amely az Appl i ca­
ti on típusból származik. Addig is tanulmányozzuk a window típus alapvető

funkcionalitását, és közben ismerkedjünk meg néhány kulcsfontosságú WPF­


alaposztállyaL

A Window osztály szerepe

A system. windows. window típus egyetlen ablakot képvisel - az ablak tulajdo­


nosa az Ap p l i cati on típusból származó típus -, amely a főablak által megje­
lenített párbeszédablakokat is magában foglalja. Mint az elvárható, a window
típus több szülőosztállyal rendelkezik, amelyek mindegyike további funkci­
onalitást biztosít. Tanulmányozzuk a 28.3. ábrát, amely a Visual Studio 2008
objektumböngészőjében a system. windows. window típus származtatási láncát
(és a megvalósított interfészeket) muta�a be.

'
�_I!"OidiiOl�:Xi!irtP!iYObj«tBrowse<
�X
. ... � l b<�.,.

1
Browse MySolution .. 1

<Se�trch> .,. i] � ;·· ·� Activate() jT ,.


-$: �
- ·�·------ ----- ---r-1 i_.;f'Q, Arrangf!Ovtnid�Systm�.Windows.5a:t) [j
EH::J &se Types
-� r-�� Calculat�en��ScreenPosition(System.RuntimeJnteropServi<es.HandleRd, Syrtem.Window
j :.- Q.� ClearRestZNripControi(System.Windows.Controls.Control}
8-� Cont6ltControl
l
! � V ClO§e()
·
é-� Control Í --../i CorrectStyleForBotde!rlessWindowC•se()
é-4{: Frame-torkElement r-�� CruteAIIStyle()
ll .l
(i}·a.<> IFrameworldnputElement
!--o IlnputElement r-uv CreatéourceVIindowlmpiO
r-...o J:Supportlnitialize l ;-uV DeviceTologicalUnits(S�t�.Windows.Point)
'
é-� UIEiement '
t·-....0 lAnimatab Ie public class Window: SvstcmWjndowsCo�;;tcQ�t;!--------�
t·· DnputElemtnt
-o
i!: l 1 Member of Systm.Windows
1
e..� v-tsu.,,
é-� O�dencyObject !Sum�ary: .. . . . .
8-� Oisp�tcherObject l P roVIdes the abohty to create, confogure, show, and maoage the löebme of wondows and
i�'\: Object dialog boxes.
L.�lA.ddChild
G .
28. 3. ábra: A Window típus hierarchiája

Az alaposztályok által biztosított funkcionalitást ez és a következő fejezet


muta�a be, a következő részekben az egyes alaposztályok funkcionalitását
elemezzük (erről a .NET Framework 3.5 SDK dokumentációjában bővebb in­
formációkat találunk).

A System.Windows.Controls.ContentControl alaposztály szerepe

A window közvetlen szülője a contentcont rol. Az alaposztály tartalom haszto­


lására alkalmas származtatott típusokat biztosít, ami tulajdonképpen a vezér­
lőelem felületén elhelyezett objektumok gyűjteményét jelenti. A WPF-tarta­
lommodellben a tartalom vezérlőelem az egyszerű sztring-adatok mellett
rengeteg felhasználóifelület-elemet tartalmazhat. Definiálhatunk például

465
28. fejezet: A WPF és az XAML

olyan Button elemet, amely tartalomként beágyazott scrollBar elemet foglal


magában. A contentcontrol alaposztály erre a célra (nem meglepően) a con­
tent kulcsfontosságú tulajdonságot biztosítja.

Ha az értéket, amelyet a content tulajdonsághoz szeretnénk rendelni,


képviselheti egy egyszerű sztring-literál, akkor a content tulajdonságot attri­
bútumként, explicit módon beállíthatjuk az elem nyitó definíciójában:

<!-- A content tulajdonság értékének beállítása explicit módon -->


<Button Height="80" width="100" content="ClickMe"/>

A content tulajdonságot implicit módon is beállíthatjuk, ha a tartalom vezérlő­


elem definíciójának hatókörén belül meghatározunk egy értéket. Nézzük
meg az előző gomb funkcionális szempontból egyenértékű XAML-leírását:

<!-- A content tulajdonság értékének beállítása implicit módon -->


<Button Height="80" Width="100">
clickMe
</Button>

Ha azonban az érték, amelyet a content tulajdonsághoz szeretnénk rendelni,


nem írható le egyszerű karaktertömbbel, a vezérlőelem nyitó definíciójában
nem rendelhetjük hozzá attribútum segítségével a content tulajdonságot: eb­
ben az esetben a tartalmat implicit módon vagy a tulajdonság-elem szintaxis
segítségével kell meghatároznunk. Tekintsük meg a következő, funkcionális
szempontból egyenértékű XAML-definíciót, amely scrollBar típusra állítja a
content tulajdonságot (a fejezet későbbi részében elmélyedünk a XAML rej­

telmeiben, most a részletekkel nem foglalkozunk):

<!-- Button elem, amely scrollBar implicit tartalmat foglal


magában -->
<BUtton Height = "80" Width = "100">
<ScrollBar width = "75" Height = "40"/>
</Button>

<!-- Button elem, amely tulajdonság-elem szintaxissal definiált


scrollBar elemet foglal magában -->
<Button Height = "80" width = "100">
<Button.content>
<ScrollBar width = "75" Height = "40"/>
</Button.content>
</Button>

466
A WPF-szerelvények vizsgálata

Ne feledjük, hogy nem minden WPF-vezérlőelem származik a contentcont­


rol alaposztályból, ezért csupán a vezérlőelemek kis csoportja támogatja ezt

az egyedi tartalommodellt. A Frame, a Groupitem, a Headeredcontentcontrol, a


L abel, a L ist Box rtem, a B utton B ase, a StatusBaritem, a scro llviewer, a Tool­

Tip, a usercontrol vagy a window alaposztályból származó osztályok alkal­

mazhatják ezt a tartalommodellt. Bármely más típussal, amellyel erre törek­


szünk, a markupban fordítási idejű hibát kapunk. Például nézzük meg a kö­
vetkező, hibás scrollBar típust:

<!-- Hiba! A scrollbar nem a contentcontrol alaposztályból


származik! -->
<ScrollBar Height = "80" width = "100">
<Button width = "75" Height = "40"/>
</ScrollBar >

Az új tartalommadellel kapcsolatos másik lényeges szempont, hogy a con­


tentcontrol alaposztályból származó vezérlőelemek (a Windows típust is be­

leértve) egyetlen értéket rendelhetnek a content tulajdonsághoz. Ezért a kö­


vetkező XAML Button definíció szintén szabálytalan, mivel a content tulaj­
donságot implicit módon kétszer állítja be:

<!-- TextBox és Ellipse elemet próbálunk a Button elemhez hozzáadni?


Hiba! -->
<Button Height = "200" width = "200">
< El lipse Fill = " Gr een " Heig ht = " 80" width = "80"/>
<TextBox width = "50" Heig ht = "40"/>
</Button >

Ez első pillantásra erősen korlátozónak tűnhet (képzeljük el, hogy egy párbe­
szédablak egyetlen gombbal milyen működésképtelen lenne!). A panelek se­
gítségéve! szerencsére több elemet is hozzáadhatunk a contentcontrol alap­
osztályból származó típusokhoz. Ehhez a tartalom minden egyes részét a
WPF-paneltípusok egyikébe kell rendezni, és ezt követően a panel válik azon
egyetlen értékké, amelyet a Content tulajdonsághoz rendelünk A WPF-tarta­
lommodellel, valamint a különböző paneltípusokkal (és a típusok vezérlő-
/
elemeivel) a 29. fejezetben ismerkedünk meg.

Megjegyzés A system. windows. contentcontra l osztály a Hascontent tulajdonság segít­


ségével állapítja m eg, hogy a Content értéke pillanatnyilag null vagy sem.

467
28. fejezet: A WPF és az XAML

A System.Windows.Controls.Control alaposztály szerepe

A contentcontrol alaposztálytól eltérően a WPF-vezérlőelemek közös szülője a


control ősosztály. Ez az alaposztály több kulcsfontosságú tagot foglal magában,

amelyek a felhasználói felület alapvető funkcionalitását biztosítják. A control


például olyan tulajdonságokat definiál, amelyekkel meghatározhatjuk a vezér­
lőelem méretét, átlátszóságát, tabulátorsorrendjének logikáját, a megjelenített
kurzort, a háttér színét és egyéb jellemzőket. Továbbá ez a szülőosztály támo­
gatja a sablonozási szolgáltatásokat. Mint a 30. fejezetben láthatjuk majd, a WPF­
vezérlőelemek dinamikusan módosíthatják megjelenésüket sablonok, stílusok
és témák segítségéveL A 28.6. táblázat a control típus néhány kulcsfontosságú
tagját ismerteti, a tagokat a kapcsolódó működés szerint csoportosítja.

Background, Foreground, Ezek a tulajdonságok a vezérlőelem megje­


BorderBrush, BorderThickness, lenésével és elhelyezésével kapcsolatos alap­
Padding, beállítások meghatározását teszik lehetövé.
HorizontalcontentAlignment,

verticalcontentAlignment

FontFamily, Fontsize, Ezek a tulajdonságok a betűtípussal kapcso­


Fontstretch, Fontweight latos különböző beállításokat szabályozzák.

IsTabstop, Tabindex Ezen tulajdonságok segítségével meghatá­


rozha�uk az ablakban a vezérlőelemek ta­
bulátorsorrendjét.

Mouseooubleclick, Ezek az események kezelik a vezérlőelemek


PreviewMouseooubleclick dupla kattintásának műveletét.

Template Ezen tulajdonság segítségéveilekérdezhet­


jük és beállítha�uk a vezérlőelem sablonját,
amellyel módosítha�uk a vezérlőelem
renderetési kimenetét

28.6. táblázat: A Control típus kulcsfontosságú tagjai

A System.Windows.FrameworkElement alaposztály szerepe

Az alaposztály több alacsonyszintű tagot biztosít, amelyeket a WPF-keret­


rendszer egészében alkalmazhatunk, például a forgatókönyv (storyboard)
támogatását (animációkban), az adatkötés támogatását, a tagok elnevezését
(a Name tulajdonság révén), a származtatott típus által meghatározott erőfor­
rások megszerzését és a származtatott típus általános dimenzióinak megha­
tározását. A 28. 7. táblázat a legfontosabb tagokat ismerteti.

468
A WPF-szerelvények vizsgálata

ActualHeight, Actualwidth, A származtatott típus méretét szabályozzák


MaxHeight, Maxwidth, (nem meglepő módon).
MinHeight, Minwidth,

Height, width

ContextMenu A származtatott típushoz tartozó előugró menüt


lekérdezi vagy beállítja.

cursor A származtatott típushoz tartozó egérkurzort


lekérdezi vagy beállítja.

HorizontalAlignment, A típus elhelyezését szabályozzák a tárolóban


verticalAlignment (például a panelben vagy a listamezőben).

Name Lehetővé teszi, hogy nevet rendeljünk a típus­


hoz, és így a kódfájlban hozzáférjünk a típus
funkcionalitásához.

Resources Hozzáférést biztosít a típus által definiált erő­


forrásokhoz (a WPF-erőforrásrendszert részle­
tesen a 30. fejezet ismerteti).

ToolTip Aszármaztatott típushoz tartozó eszköztippet


lekérdezi vagy beállítja.

28-7. táblázat: A FramewarkElement típus kulcsfontosságú tagjai

A System.Windows.UIEiement alaposztály szerepe

A window származtatási láncában az össze típus közül az UI Element ősosztály


biztosítja a leggazdagabb funkcionalitást. Az UI El emen t kulcsfontosságú fel­
adata, hogy a származtatott típus számára olyan eseményeket biztosít, ame­
lyekkel a típus lekérdezi az előteret és feldolgozza a bemeneti kéréseket. Ez
az osztály például a húzd-és-dobd műveletek, az egérmozgatás, a billentyű­
zetbemenet és a stylusceruza-bemenet (a Pocket PC-k és a Tablet PC-k szá­
mára) kezeléséhez biztosít több eseményt.
A 29. fejezet részletesen bemutatja a WPF-eseménymodellt; azonban több
alapvető esemény ismerősnek tűnhet (MouseMove, Keyup, Mouseoown, MouseEnter,
MauseLeave stb.). A tucatnyi esemény definiálásán kívül ez a szülőosztály ren­

geteg tulajdonságot biztosít, amelyek a fókusz beállításáról, az engedélyezett­


ségről, a láthatóságról és a találat tesztelési logikáról gondoskodnak. A tulaj­
donságokat a 28.8. táblázat ismerteti.

469
28. fejezet: A WPF és az XAML

Focusable, IsFocused Ezek a tulajdonságok lehetővé teszik, hogy a


származtatott típusra állítsuk a fókuszt.

IsEnabled Ezzel a tulajdonsággal szabályozhatjuk, hogy a


származtatott típus engedélyezett vagy letiltott.

IsMouseoirectlyover, Ezek a tulajdonságok a találattesztelési logika


IsMouseover végrehajtásának egyszerű módját biztosítják.

rsvisible, visibility, Ezek a tulajdonságok lehetővé teszik, hogy a


visible származtatott típus láthatóságának beállításai­
val dolgozzunk.

RenderTransform A tulajdonság segítségével transzformációt ké­


szíthetünk, amelyet a rendszer a származtatott
típus renderelése során alkalmaz.

28.8. táblázat: Az U/E/ement típus kulcsfontosságú tagjai

A System.Windows.Media.Visual szerepe
A Visual osztálytípus a WPF alapvető renderelési támogatását biztosítja. A tá­
mogatás magában foglalja a rendereit adatok találattesztelését, a koordináta­
transzformációkat és a határolódoboz-számításokat Valójában ez a típus jelenti
a kapcsolódási pontot a felügyelt WPF-szerelvény verem és a nem felügyelt
milcore. dll bináris között, amely a DirectX alrendszerrel kommunikál.

A 30. fejezetben láthatjuk majd, a WPF a grafikus adatok renderelésének


háromféle módját biztosítja, amelyek funkcionalitás és teljesítmény tekinteté­
ben különböznek egymástól. A visua l típus (és gyermekei, például a orawing­
visua l) alkalmazása jelenti a grafikus adatok renderelésének legkönnyebb

módját, de ez a típus igényli a legtöbb manuális kódolást az összes szükséges


szaigáitatás biztosításához. További részleteket erről a 30. fejezetben találunk.

A System.Windows.DependencyObject osztály szerepe


A WPF támogatja a .NET-tulajdonságok speciális típusát, az úgynevezett
függőségi tulajdonságokat. Ez a megközelítés lehetővé teszi, hogy a típus más
tulajdonságok értéke alapján kiszámítsa a tulajdonság értékét (innen a "füg­
gőség"). A típus akkor lehet része ennek az új tulajdonságsémának, ha a De­
pendencyobj e ct ősosztályból származik. Ezenkívül a oependencyobj e ct lehe­

tővé teszi a származtatott típusok számára, hogy csatolt tulajdonságokat támo­


gassanak, amelyek a függőségi tulajdonságok rendkívül hasznos típusát je­
lentik a WPF adatkötési modell programozása, valamint a különböző WPF­
paneltípusokban a felhasználói felület elemeinek elrendezése során.

470
(XAML-mentes) WPF-alkalmazás készítése

ADependencyobject ősosztály két kulcsfontosságú metódust biztosít az összes


származtatott típus számára: a Getval u e O és a setval u e O metódust. A tagok se­
gitségével létrehozha�uk magát a tulajdonságot. Az infrastruktúra további ele­
mei lehetővé teszik, hogy "regisztráljuk", ki használha�a a kérdéses függősé­
gi/ csatolt tulajdonságot. Noha a függőségi tulajdonságok a WPF-fejlesztés kulcs­
fontosságú szempon�át jelentik, részleteik az esetek többségében rejtve marad­
nak. Az új tulajdonságtípus részleteiben a 29. fejezetben merülhetünk el.

A System. Windows. Th reading. DispatcherObject szerepe

A window típus utolsó ősosztálya (a System.Object osztályon túl, amely ezen a


ponton nem szorul mélyebb magyarázatra) a D i spatcherobject osztály. Ez a tí­
pus egy érdekes tulajdonságot, a Di spatcher tulajdonságot foglalja magában,
amely a kapcsolódó System.windows. Th reading.D i spatcher objektumot adja
vissza. ADi spatcher típus a WPF-alkalmazás eseménysorának belépési pon�a,
és a párhuzamosság, valamint a száJkezeléshez szükséges alapvető szerkezete­
ket biztosí�a. Ezt az alacsonyszintű osztályt WPF-alkalmazásaink túlnyomó ré­
szében figyelmen kívül hagyha�uk.

(XAML-mentes) WPF-alkalmazás
készitése

A window típus szülőosztályainak funkcionalitásával az alkalmazásunkban


közvetlenül a wi ndow típus létrehozásával vagy az erősen típusos leszármazott
típus szülőjeként megadott wi ndow osztály segítségével készíthetünk ablakokat.
A következő kódpéldában vizsgáljuk meg mindkét megközelítést! Noha a leg­
több WPF-alkalmazás használja a XAML-t, a nyelv használata tetszőleges.
Bármi, ami a XAML segítségével leírható, az kifejezhető kódban is (az esetek
nagy részében), és fordítva. Igény szerint a mögöttes objektummodell és az
imperatív C#-kód segítségével elkészíthe�ük a teljes WPF-projektünket.
Ennek bemutatására készítsünk egyszerű, de teljes alkalmazást XAML
használata nélkül, az App l i cati on és a window típusok közvetlen használatávaL
Vizsgáljuk meg a következő C#-kódfájlt ( simpleWPFApp.cs), amely szerény,
egy funkcionalitással rendelkező főablakot hoz létre:

471
28. fejezet: A WPF és az XAML

ll Egyszerű WPF-alkalmazás, XAML-kód felhasználása nélkül.


using System;
using system.windows;
using System.windows.controls;

namespace simpleWPFApp
{
ll Az első példában egyetlen osztálytípust definiálunk, amely
ll magát az alkalmazást és a főablakot képviseli.
class MyWPFApp : Application
{
[STAThread]
static void Main()
{
ll A Startup és az Exit események kezelése, az alkalmazás
ll futtatása.
MyWPFApp app = new MyWPFApp();
app.startup += AppStartup;
app.Exit += AppExit;
app.Run(); ll Fires the Startup event.
}

static void AppExit(object sender, ExitEventArgs e)


{
MessageBox.show("App has exited");
}

static void AppstartUp(object sender, StartupEventArgs e)


{
ll window objektum létrehozása és néhány alapvető tulajdonság
ll beállítása.
Window mainwindow = new Window();
mainwindow.Title = "My First WPF App!";
mainwindow.Height = 200;
mainwindow.Width = 300;
mainwindow.windowstartupLocation
WindowstartupLocation.CenterScreen;
mainwindow.Show();
}
}
}

Megjegyzés A WPF-alkalmazás Main() metódusát a [STAThread] attribútummal kell minösí­


teni, amely biztosítja, hogy az alkalmazásunkban felhasznált korábbi COM-objektumok szálbiz­
tosak legyenek. Ha a Main() metódust nem látjuk el ezzel a jelöléssel, az eredmény futásidejű
kivétel lesz!

Vegyük észre, hogy a MyWPFApp osztály kibővíti a system. windows .Appl ication
típust. A Main() metódusban létrehozzuk az alkalmazásobjektum egy példá­
nyát, és a metóduscsoportátalakítás-szintaxis segítségével kezeljük a startup

472
(XAML-mentes) WPF-alkalmazás készítése

és az Exit eseményeket. Emlékezzünk rá a ll. fejezetből, hogy a rövidítés al­


kalmazásával nem szükséges manuálisan meghatároznunk az adott esemény
által használt mögöttes metódusreferenciákat.
Ha szeretnénk, közvetlenül a nevük segítségével is megadhatjuk a mögöt­
tes metódusreferenciákat. A következő, módosított Main() metódusban a
startup esemény a startupEventHandler metódusreferenciával együtt műkö­

dik. A metódusreferencia csak olyan metódusra mutathat, amelynek első pa­


ramétere az obj e ct, második paramétere pedig a startupEventArgs. Másrész­
ről, az Exi t esemény együttműködik az ExitEventHandler metódusreferenci­
ával, amely megköveteli, hogy a hivatkozott metódus második paramétere az
ExitEventArgs legyen:

[STAThread]
static void Main()
{
ll Ezúttal meghatározzuk a mögöttes metódusreferenciákat.
MyWPFApp app = new MyWPFApp();
app.Startup += new StartupEventHandler(AppstartUp);
app.Exit += new ExitEventHandler(AppExit);
app.Run(); ll Kiváltja a Startup eseményt.
}

Az Appstartup() metódus létrehozza a window típust, beállít néhány alapvető


tulajdonságot, és a show() metódus hívásával nem modálisan jeleníti meg a
képernyőn az ablakot (a Windows Forrnshoz hasonlóan a showoialog() me­
tódus segítségével indítható madális dialógus). Az AppExit() metódus a WPF
MessageBox típus segítségével diagnosztikai üzenetet jelenít meg, ha az al­

kalmazást leállítjuk
A C#-kód végrehajtható WPF-alkalmazásra fordítása során tételezzük fel,
hogy létrehoztuk a b uild.rsp C#-reakciófájlt, amely hivatkozik az összes
WPF-szerelvényre. Vegyük észre, hogy az egyes szerelvények elérési útvona­
lát egy sorban kell meghatároznunk (a reakciófájlokkal és a parancssoros
fordító használatával kapcsolatos bővebb információkat lásd a 2. fejezetben):

# build.rsp
#
ltarget:winexe
lout:SimpleWPFApp.exe
lr:"C:\Program Files\Reference
Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll"
lr:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\Presentationcore.dll"
lr:"C:\Program Files\Reference Assemblies\Microsoft\Framework
\v3.0\PresentationFramework.dll"
*.cs

473
28. fejezet: A WPF és az XAML

A WPF-programot a parancssorban a következőképpen fordítha�uk le:

esc @build.rsp

A program futtatása során egyszerű főablak jelenik meg, amelyet kisméretűre

vagy teljes méretűre állíthatunk, és bezárhatunk Egy kicsit "megfűszerezhet­


jük" az alkalmazást, és hozzáadhatunk néhány interfészelemet De mindezek
előtt szervezzük át a kódunkat, és gondoskodjunk egy erősen típusos és jól
egységbezártwindow-leszármazott osztályróL

A Window osztálytipus kibővítése

Pillanatnyilag az Application osztályból származó osztályunk az alkalmazás


indítása során közvetlenül létrehozza a window típus egy példányát. Ideális
esetben létrehozunk egy osztályt, amely a window osztályból származik, és
egységbe zárjuk ezen osztály funkcionalitását. Tételezzük fel, hogy az aktuá­
lis simpleWPFApp névterünkben létrehoztuk a következő osztálydefiníciót:

class Mainwindow : window


{
public Mainwindow(string windoWTitle, int height, int width)
{
this.Title = windowTitle;
this.windowstartupLocation = windowstartupLocation.Centerscreen;
this.Height = height;
this.Width = width;
this. Show() ;
}
}

Most már módosítha�uk a startup eseménykezelőt, hogy az közvetlenül lét­


rehozza a Mainwindow egy példányát:

static void AppstartUp(object sender, StartupEventArgs e)


{
ll Mainwindow objektum létrehozása.
Mainwindow wnd = new Mainwindow("My better WPF App!", 200, 300);
}

A program ismételt fordítása és futtatása során látha�uk, hogy a kimenet


megegyezik. A megoldás nyilvánvaló előnye, hogy a további fejlesztéshez
egy erősen típusos osztály áll rendelkezésünkre.
,

474
(XAML-mentes) WPF-alkalmazás készítése

Megjegyzés Ha window (vagy a Window osztályból származó) objektumot hozunk Létre, a


rendszer (a window osztályban található konstruktorlogika segítségéve!) automatikusan hozzá­
adja az Ap pl i ca tion típus belső ablak gyűjteményéhez. Ennek köszönhetően az ablak egé­
szen a leállításig a memóriában marad, illetve addig, amíg a gyűjteményből explicit módon el
nem távolítjuk az Appl ica tion . windows tulajdonsággaL

Egyszerű felhasználói felület létrehozása

A wi ndow-leszárrnazott típusban a felhasználóifelület-elemek hozzáadása ha­


sonlít (de nem egyezik meg teljesen) a system.windows_ Forms. Form osztályból
származó típusokban a felhasználóifelület-elemek hozzáadásához:

l. Definiáljuk a szükséges vezérlót képviselő tagváltozót!

2. A window típus létrehozása során állítsuk be a változó megjelenését és

funkcionalitását!

3. Az Addchi l d O rnetódus hívásával adjuk a window ügyfélterületéhez a

vezérlőt!

Noha a folyamat hasonlít a Windows Forrns fejlesztéshez, azonban van egy


nyilvánvaló különbség: a WPF által alkalmazott felhasználóifelület-vezérlő­
elemeket nem a system.windows.Forms, hanern a system.windows.controls
névtér definiálja (szerencsére a nevük és funkcionalitásuk sok esetben hasonlít
Windows Forrns rnegfelelőjükhöz).
Szembetűnőbb eltérés a Windows Forrnstól, hogy a window osztályból
származó típus kizárólag egyetlen gyennekelemet foglalhat magában (a WPF­
tartalornrnodellnek köszönhetően). Ha az ablakban több felhasználóifelület­
elemet szeretnénk használni (és ez szinte rninden ablakra igaz), akkor elren­
dezéskezelő, például a DockPanel , a Grid, a canvas vagy a StackPanel segítsé­
gével szabályozhatjuk az elemek elhelyezését.
Ebben a példában egyetlen Button típust adunk a window osztályból szár­
mazó típushoz. Ha erre a gornbra kattintunk, hozzáférünk a globális alkal­
mazásobjektumhoz (az Ap p l ica tion. eur rent tulajdonság révén), meghívjuk a
shutdown () rnetódust, és leállítjuk az alkalmazást. Gondoljuk végig a Mai n­

window osztály következő rnódosítását:

475
28. fejezet: A WPF és az XAML

class Mainwindow : window


{
ll A felhasználóifelület-elemünk.
private Button btnExitApp = new Button();

public Mainwindow(string windowTitle, int height, int width)


{
ll Gomb konfigurálása, és a gyermek-vezérlőelem beállítása.
btnExitApp.click += new RoutedEventHandler(btnExitApp_clicked);
btnExitApp.content = "Exit Application";
btnExitApp.Height = 25;
btnExitApp.Width = 100;

ll Az ablak tartalmának beállítása (egyetlen gomb).


this.Addchild(btnExitApp);

ll Az ablak beállítása.
this.Title = windowTitle;
this.windowstartupLocation = windowstartupLocation.centerScreen;
this.Height = height;
this.Width = width;
this.Show();
}

private void btnExitApp_clicked(object sender, RoutedEventArgs e)


{
ll Az aktuális alkalmazás leírójának megszerzése,
ll és az alkalmazás leállítása.
Application.current.shutdown();
}
}

Mivel a 27. fejezetben már dolgoztunk a Windows Formsszal, az ablak konst­


ruktorának kódja nem lehet ijesztő. Vegyük észre, hogy a WPF gomb kattin­
tási eseménye a RoutedEventHandler metódusreferenciával együtt működik,
és ez azonnal felveti a kérdést: mik azok az irányított események? A WPF­
eseménymodell részleteit a következő fejezetben vizsgáljuk meg; pillanatnyi­
lag elég megértenünk, hogy a RoutedEventHandler metódusreferencia cél­
pon�ainak első paraméterként objektumot, második paraméterként pedig a
RoutedEventArgs típust kell meghatározniuk

Ha ismét lefordí�uk és futta�uk az alkalmazást, a 28.4. ábrán bemutatott


testre szabott ablakot látha�uk. Vegyük észre, hogy a gombunk az ablak ügy­
félterületének közepén jelenik meg. Ha a tartalmat nem helyezzük el WPF­
paneltípusban, ez az alapértelmezett viselkedés.

476
Az Application típus további jellemzői

:&:• My better WPF App! l= l @J liitiil

l Exit Application l

28.4. ábra: Érdekes WPF-alkalmazás

Forráskód A SimpleWPFApp projektet a forráskódkönyvtár 28. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az Application tipus további jellemzöi

Miután a tisztán kódalapú megközelítéssel létrehoztunk egy egyszerű WPF­


programot, vizsgáljuk meg az Application típus további jellemzőit, és kezd­
jük a teljes alkalmazásra vonatkozó adatok felépítéséveL Ehhez az előző
simpleWPFApp alkalmazást újabb funkcionalitással fogjuk gazdagítani.

Az alkalmazás adatai és a parancssori


argumentumok feldolgozása

Emlékezzünk rá, hogy az Application típus meghatározza a Properties tu­


lajdonságot, amely lehetövé teszi, hogy a típusindexelővel kulcs/ érték párok
gyűjteményét definiál juk. Mivel az indexelő definíciója szerint system. obj ect
típuson működik, a gyűjteményben bármilyen elemet tárolhatunk (az egyedi
osztályainkat is beleértve), és az elemeket később a "barátságos" moniker se­
gitségével visszaolvasha�uk. A megközelítés segítségével a WPF-alkalmazás
ablakai között könnyen lehet adatokat megosztani.
Ennek bemutatására módosítsuk az aktuális Startup eseménykezelőt,
hogy ellenőrizze a /GODMODE értéket a bejövő parancssori paraméterek­
ben (ez aPC-s vicleojátékok gyakori csaláskódja). Ha ilyen tokent találunk, a
tulajdonsággyűjteményben megegyező névvel rendelkező logikai értéket ál­
lítsunk igaz értékre (egyébként az értéket állítsuk hamisra).

477
28. fejezet: A WPF és az XAML

Egyszerűnek hangzik, de felmerülhet a kérdés, hogyan adjuk át a bejövő


parancssori paramétereket (amelyek tipikusan a Main() metódustól kell meg­
szerezni) a startup eseménykezelőnknek? Az egyik lehetőség, hogy megillv­
juk a statikus Environment.GetcommandLineArgs() metódust. Azonban ugyan­
ezeket az argumentumokat a rendszer automatikusan hozzáadja a bejövő
startupEventArgs paraméterhez, és az Args tulajdonság segítségével hozzájuk

férhetünk Mindezek után lássuk az első módosítást:

static void Appstartup(object sender, StartupEventArgs e)


{
ll A bejövő parancssori paramétereknél
ll a IGODMODE kapcsaló ellenőrzése.
Application.current.Properties["GodMode"] false;

foreach(string arg in e.Args)


{
if (arg.Tolower() == "lgodmode")
{
Application.current.Properties["GodMode"] true;
break;
}
}

ll Mainwindow objektum létrehozása.


Mainwindow wnd = new Mainwindow("My better WPF App!", 200, 300);
}

Emlékezzünk rá, hogy ehhez az új névj érték párhoz a WPF-alkalmazáson be­


lül bárhonnan hozzáférünk Csupán annyit kell tennünk, hogy megszerezzük a
globális alkalmazásobjektum hozzáférési pon�át (az Application.current se­
gítségéve!), és megvizsgáljuk a gyűjteményt. A főablak gombjának kattintási
eseménykezelőjét például a következőképpen módosítha�uk:

private void btnExitApp_Clicked(object sender, RoutedEventArgs e)


{
ll A felhasználó engedélyezte a lgodmode kapcsolót?
if((bool)Application.current.Properties["GodMode"])
MessageBox.show("cheater!");

ll Az aktuális alkalmazás kezelőjének megszerzése,


ll és az alkalmazás leállítása.
Application.current.Shutdown();
}

478
Az Application típus további jellemzői

Ha a felhasználó elindí�a a programot a

simpleWPFApp.exe /godmode

paranccsal, akkor az alkalmazás leállításakor megjelenik a képernyőn az


üzenetünket tartalmazó üzenetdoboz.

Az Application tipus Windows gyűjteményének


feldolgozása

Az Application típus másik érdekes tulajdonsága a windows, amely hozzáfé­


rést biztosít az aktuális WPF-alkalmazás memóriába töltött ablakait képviselő
gyűjteményhez. Emlékezzünk rá, hogy amikor új window típusokat hozunk
létre, a rendszer automatikusan hozzáadja a típusokat a globális alkalmazás­
objektum windows gyűjteményéhez. Az aktuális példánkat ennek bemutatásá­
ra nem kell módosítanunk; lássunk azonban egy példametódust, amely kis
méretűre állí�a az alkalmazás összes ablakát (válaszu! a felhasználó által
kezdeményezett meghatározott billentyűzetműveletre vagy menüopcióra):

static void MinimizeAllwindows()


{
foreach (Window wnd in Application.current.windows)
{
wnd.windowstate = windowstate.Minimized;
}
}

Az Application tipus további eseményei

A .NET alaposztály-könyvtár több tagjához hasonlóan az Application is defi­


niál egy eseménykészletet, amelynek tagjait elkapha�uk. A Startup és az Event
eseményeket már láttuk működés közben. Fontos ismernünk az Act i vated és a
oeactivated eseményeket is. Ezek az események első pillantásra megtévesztő­
nek tűnhetnek, mivel a window típus megegyező nevű metódusokat biztosít.
A felhasználói felület megfelelőitől eltérően az Activated és a oeactivated ese­
mények akkor sülnek el, ha az alkalmazásobjektum által karbantartott bármely
ablak megszerzi vagy elveszíti a fókuszt (ellentétben a window típus ugyanezen
elemeivel, amelyek az adottwindow objektum alapján egyediek).

479
28. fejezet: A WPF és az XAML

Az aktuális példánkban nem szükséges a két eseményt kezelnünk, de, ha


muszáj, ne feledjük, hogy mindkét esemény a system. EventHandl er metódus­
referenciával együtt működik, ezért az eseménykezelő első paramétere egy
obj ect, második paramétere egy system. EventArgs (az EventHand l er metó­

dusreferenciával kapcsolatos ismereteinket a 27. fejezetben frissíthe�ük fel).

Megjegyzés Az Ap p l i cati on típus további eseményeinek többsége a navigációalapú WPF­


alkalmazásra jellemző. Az események segítségével programunkban nyomon követhetjük a
Page objektumok közötti mozgás folyamatát.

A Window tipus további jellemzöi

Mint a fejezet korábbi részében láttuk, a window típus szülőosztályai és a meg­


valósított interfészek révén gazdag funkcionalitással rendelkezik. A követke­
ző fejezetekben egyre több információt kapunk az alaposztályok funkcionali­
tásáról; fontos újra megvizsgálnunk magát a window típust, és meg kell ismer­
kednünk néhány alapvető szolgáltatással, amelyeket mindennapi munkánk
során alkalmaznunk kell. Kezdjük azzal az eseménykészlettel, amelyet a
rendszer az objektum élettartama során kivált.

A Window objektum élettartama

A System. windows. Forms. Form típushoz hasonlóan a system. windows. window is


rendelkezik eseménykészletteL Az eseményeket a rendszer az objektum élet­
tartama során süti el. Ha ezen események egyikét (vagy mindegyiket) kezel­
jük, kényelmes módszer áll rendelkezésünkre az egyedi logika végrehajtásá­
hoz, miközben a window objektum végzi a saját dolgát. Mivel az ablak tulaj­
donképpen osztálytípus, az inicializálás legelső lépése egy meghatározott
konstruktor hívását vonja maga után. Ezt követően az első WPF-központú
esemény - mely kiváltódik - a so u rcernitial i zed, amely akkor bizonyul
hasznosnak, ha a window objektumunk különböző együttműködési szolgálta­
tásokat alkalmaz (például korábbi ActiveX vezérlőelemeket használ WPF-al­
kalmazásban). Még ebben az is csak ritkán kell kezelnünk az eseményt (erről
bővebb információkat a .NET Framework 3.5 dokumentációban találunk).

480
A Window típus további jellemzöi

Az első közvetlenül használható esemény, amelyet a window konstruktora


után elsül: az Activate. Az esemény a system. EventHandl er metódusreferen­
ciával együtt működik. Az eseményt a rendszer akkor vál�a ki, ha az ablak
megszerzi a fókuszt, és így az előtérbeli abiakká lép elő. Ezen esemény párja
a Deactivate (szintén a system. EventHand l er metódusreferenciával működik
együtt), amely akkor sül el, amikor az ablak elveszíti a fókuszt.
Vizsgáljuk meg a window osztályból származó típusunk módosítását, amely
tájékoztató üzenetet ad egy privát sztringváltozóhoz (a sztringváltozó hasznát
rövidesen megtudjuk), amikor az Activate és a Deactivate események bekö­
vetkeznek:

class Mainwindow : window


{
private Button btnExitApp = new Button();

ll Ez a sztring rögzíti, hogy mely események


ll mely időpontban váltódnak ki.
private string l ifeTimeData = S trin g Empty ;
.

protected void Mainwindow_Activated(object sender, EventArgs e)


{
l ifeTimeData += "Activate Event Fired!\n";
}

protected void Mainwindow_Deactivated(object sender, EventArgs e)


{
l ifeTimeData += "Deactivated Event Fired!\n";
}

publ ic Mainwindow(string windowTitl e, int height, int width)


{
ll Feliratkozás az eseményekre.
this.Activated += Mainwindow_Activated;
this.Deactivated += Mainwindow_Deactivated;

}
}

Megjegyzés Emlékezzünk rá, hogy az Appl i cati on. Ac ti vated és az Appl i cati on. Deacti­
vated eseményekkel az összes ablak alkalmazásszintű aktivátását és deaktiválását kezelhetjük.

Ha az Activated esemény elsül, a rendszer kivál�a a Loaded eseményt (az


esemény a RoutedEventHandler metódusreferenciával dolgozik), amely jelzi,
hogy az ablak elrendezése és renderelése teljes, és készen áll a felhasználói
bemenet kezelésére.

481
28. fejezet: A WPF és az XAML

Megjegyzés Míg a fókusz megszerzésével és elvesztésével a rendszer többször kiválthatja az


Activated eseményt, a Lo aded esemény csak egyszer váltódik ki az ablak élete során.

Tételezzük fel, hogy a Mainwindow típus kezeli ezt az eseményt, és a következő


eseménykezelőt definiálja:

protected void Mainwindow_Lo aded(object sender, Ro utedEventArgs e)


{
lifeTimeData += "Lo aded Event Fired!\n";
}

Megjegyzés Ha szükség lenne rá (ez az egyedi WPF-vezérlöelemek esetén előfordulhat), elkap­


hatjuk a pillanatot, amikor az ablak tartalmát a cont entRendered esemény kezelője betölti.

A Window objektum Closing eseményének kezelése

A felhasznáJók több beépített rendszerszintű módszer segítségével leállíthat­


"

nak egy ablakot, (például, ha az ablakkereten az " X bezárás gombra kattinta­


nak), illetve közvetetten valamilyen felhasználói beavatkozásra (például a File >

Exit műveletre) adott válaszként a close() metódus meghívásával. A WPF két


eseményt biztosít, amelyek eikapásával eldönthetjük, hogy a felhasználó való­
ban kész az ablak leállítására és az ablak memóriából való eltávolítására. Az el­
sőként kiváltott esemény a closin g, amely a cane e lEventHandl er metódusrefe­
renciával együtt dolgozik.
A metódusreferencia megköveteli, hogy a célmetódusok második para­
métere a system. compon entMode l . cane e lEventArgs legyen. A cane e lEventArgs
a canc e l tulajdonsággal rendelkezik, amely igaz értéke megakadályozza,
hogy a rendszer valóban bezárja az ablakot (ez akkor hasznos, amikor meg­
kérdezzük a felhasználót, hogy tényleg szeretné-e bezárni az ablakot, vagy
előtte még szeretné menteni a munkáját).
Ha a felhasználó valóban szeretné bezárni az ablakot, a cane e lEventArgs.
canc e l tulajdonság értéke hamisral állítható. Ekkor elsül a elosed esemény

(amely a system. EventHandler metódusreferenciával dolgozik), és ebben a


pillanatban az ablak készen áll arra, hogy a rendszer végérvényesen bezárja.

l Az eredeti szövegbe itt az igaz érték szerepel (Cance!EventArgs.Cancel can be set to true), ezt
azonban javítottuk, lévén nyilvánvaló tévedés, ami az alábbi kódrészlettel támasztható alá:
ll Ha a felhasználó nem akarja bezárni az ablakot, a zárási kérelmet töröljük.
e. Cancel = true - a kiadó.

482
A Window típus további jellemzői

Tételezzük fel, hogy a Mainwindow típus kezeli ezt a két eseményt, és vizs­
gáljuk meg az utolsó eseménykezelőket

protected void Mainwindow_closing(object sender,


System.componentModel.cancelEventArgs e)
{
lifeTimeData += "closing Event Fired!\n";

ll Ellenőrizzük, hogy a felhasználó tényleg szeretné bezárni


ll az ablakot.
string msg = "Do you want to close without saving?";
MessageBoxResult result = MessageBox.show(msg,
"My App", MessageBoxButton.YesNo, MessageBoximage.warning);
if (result == MessageBoxResult.No)
{
ll Ha a felhasználó nem akarja bezárni az ablakot, a zárási
ll kérelmet töröljük.
e.Cancel = true;
}
}

protected void Mainwindow_closed(object sender, EventArgs e)


{
lifeTimeData += "closing Event Fired!\n";
MessageBox.Show(lifeTimeData);
}

Ha lefordí�uk és futta�uk az alkalmazást, az ablakot néhányszor helyezzük fó­


kuszba, majd vegyük el a fókuszt. Próbáljuk meg egyszer vagy kétszer bezárni
az ablakot. Ha valóban végleg bezárjuk az ablakot, a képernyőn megjelenő üze­
netdobozban találjuk azon események listáját, amelyek az ablak élete során mű­
ködésbe léptek (a 28.5. ábrán egy lehetséges tesztfutás eredménye látható).

Activate Event Fired!


Loaded Event Fired!
Oeactivated Event Fired!
Activate Event Fired!
Ciasing Event Fired!
Deactivated Event Fired!
Activate Event Fired!
Ciasing Event Fired!
D•activated Event Fired!
Activate Event Fired!
Oeactivated Event Fired!
Closing Event Fired!

l OK
l
28. 5. ábra: A System.Windows.Window élete

483
28. fejezet: A WPF és az XAML

Ablakszintü egéresemények kezelése

A Windows Forrnshoz hasonlóan a WPF API több eseményt biztosít, amelye­


ket elkaphatunk, és így együttműködhetünk az egérreL Az UIElement alaposz­
tály több egérközpontú eseményt definiál; például a MouseMove, a Mouseup, a
Mouseoown, a MouseEnter és a MauseLeave eseményeket.
Tanulmányozzuk például a MouseMove esemény kezelését. Ez az esemény
a system.windows.Input.MouseEventHandler metódusreferenciával együtt mű­
ködik, amely megköveteli, hogy a célmetódus második paramétere egy sys­
tem.windows. Input. MouseEventArgs típus legyen. A MouseEventArgs segítségé­
vel (a Windows Forros alkalmazáshoz hasonlóan) megszerezhetjük az egér
(x,y) pozícióját és más kapcsolódó részletet. Vizsgáljuk meg a következő de­
finíciórészletet:

public class MouseEventArgs InputEventArgs


{

public Point GetPosition(IInputElement relativeTo);


public MouseButtonState LeftButton { get; }
public MouseButtonstate MiddleButton { get; }
public MauseDevice Mouseoevice { get; }
public MouseButtonstate RightButton { get; }
public StylusDevice Stylusoevice { get; }
public MouseButtonstate XButtonl { get; }
public MouseButtonstate XButton2 { get; }
}

A GetPosition() metódus segítségével kinyerhetjük az ablakban a felhaszná­


lóifelület-eleméhez viszonyított (x,y) értéket. Ha az aktivált ablakhoz viszo­
nyított pozíciót szeretnénk rögzíteni, csak ezt az értéket kell átadnunk. A kö­
vetkező példa a MouseMove eseménykezelője, amely az ablak címterületén
megjeleníti az egér pozícióját (vegyük észre, hogy a visszakapott Point típust
a ToStri ng O metódussal sztring értékké alakítjuk):

protected void Mainwindow_MouseMove(object sender,


System.windows.Input.MouseEventArgs e)
{
ll Az ablak címének beállítása az egér aktuális x,y pozíciójára.
this.Title = e.GetPosition(this).ToString();
}

484
A Window típus további jellemzöi

Ablakszintü bi llentyüzetesemények kezelése

A billentyűzetbemenet feldolgozása nagyon hasonló az előbbiekhez. Az UI­


El ement több eseményt definiál, amelyeket elkaphatunk, és az aktív elemerr el­
foghatjuk a billentyűzetről érkező gombnyomásokat (például Keyup, KeyDown
stb.). A Keyup és a KeyDown események a system.windows.Input.KeyEventHandler
metódusreferenciával dolgoznak, amely megköveteli, hogy a célmetódus má­
sodik paramétere KeyEventArgs típusú legyen. A típus fontos nyilvános tulaj­
donságokat definiál:

public class KeyEventArgs : KeyboardEventArgs


{

public bool IsDown { get; }


public bool IsRepeat { get; }
public bool IsTaggled { get; }
public bool Isup { get; }
public Key Key { get; }
public Keystates Keystates { get; }
public Key SystemKey { get; }
}

A Keyup esemény kezelésének bemutatására a következő eseménykezelő az


ablak címsorában megjeleníti az előzőleg megnyomott gombot:

protected void Mainwindow_KeyUp(object sender,


system.windows.Input.KeyEventArgs e)
{
ll Gombnyomás megjelenítése.
this.Title e.Key.ToString();
=

A fejezet ezen pontján a WPF pusztán egy új grafikusfelület-modellnek tűn­


het, amely (nagyjából) ugyanazokat a szolgáltatásokat biztosítja, mint a sys­
tem. windows. Forms. dll szerelvény. Ha valóban ez lenne a helyzet, akkor jog­
gal megkérdőjelezhetnénk egy újabb felhasználóifelület-eszközrendszer lét­
jogosultságát. Hogy lássuk, mi teszi a WPF-et egyedivé, ismerkedjünk meg
az új XML-alapú nyelvvel, a XAML-lel.

Forráskód A SimpleWPFAppRevisited projektet a forráskódkönyvtár 28. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

485
28. fejezet: A WPF és az XAML

(XAML-központú) WPF-alkalmazás
készitése

A XAML (Extensible Application Markup Language - bővíthető alkalmazás


jelölőnyelv) egy XML-alapú nyelv, amely lehetővé teszi, hogy markupban
definiáljuk a .NET-objektumok hierarchiájának állapotát (és bizonyos mér­
tékben a funkcionalitását is). Noha a XAML-t gyakran használjuk a WPF-ben
felhasználói felületek készítésére, a nyelv segítségével a nem absztrakt .NET­
típusok bármely hierarchiáját leírhatjuk (az egyedi .NET-szerelvényünkben
definiált egyedi típusokat is), ha a típusok mindegyike támogat egy alapér­
telmezett konstruktort. Mint látjuk, a * . xaml fájlban található markup átala­
kul olyan teljes objektummodellé, amely közvetlenül leképezi az objektumo­
kat a kapcsolódó .NET-névtér típusaihoz.
Mivel a XAML egy XML-alapú nyelv, az XML által biztosított előnyök és
hátrányok egyaránt megtalálhatók benne. Hasznos tulajdonság, hogy a
XAML-fájlok önleíróak (mint az XML-dokumentumok általában). A XAML­
fájlban nagyjából minden elem az adott .NET-névtérben egy típus nevét kép­
viseli (például a Button, a window vagy az Appl i cati on típust). A nyitó elem
hatókörében az attribútumok a meghatározott típus tulajdonságaira (He i ght,
width stb.) vagy eseményeire ( startup, cl i ck stb.) képezhetők le.
Mivel a XAML az objektumok állapotának deklaratív meghatározására
szolgál, markup vagy forráskód segítségével definiálhatunk WPF-vezérlőt.
Például a következő XAML-kód:

<!-- WPF Button típus definíciója XAML-ben -->

<Button Name = "btncli ckMe" Hei ght = "40" wi dth = "100"


content = "click Me" l>

programozott módon a következőképpen jelenik meg:

ll ugyanazon WPF Button típus definíciója C#-kóddal.


Button btncli ckMe = new Button();
btncli ckMe.Height = 40;
btnclickMe.width = 100;
btnclickMe.content = "cli ck Me";

Hátrány viszont, hogy a XAML túl részletes lehet, és (az XML-dokumentu­


mokhoz hasonlóan) érzékeny a kis- és nagybetűkre, ezért az összetett XAML­
definíciók terjedelmes markupot eredményezhetnek A legtöbb fejlesztőnek
nem kell kézzel megírnia a WPF-alkalmazása teljes XAML-leírását. Ehelyett a

486
(XAML-központú) WPF-alkalmazás készítése

feladat nagy részét (szerencsére) átháríthatjuk olyan fejlesztőeszközökre,


mint a Visual Studio 2008, a Microsoft Expression Blend vagy bármely más,
külső gyártótól származó termékre. Ha az eszközök elkészítik az alapvető
markupot, ezt követően manuálisan végezhetjük el a XAML-definíciók szük­
séges apró simításait_
Noha az eszközök a XAML terjedelmes részét "tálcán kínálják", érdemes
megismerkednünk a XAML-szintaxis alapvető működésével, és meg kell érte­
nünk, hogy a rendszer hogyan alakítja át a markupot szabályos .NET-szerel­
vényekké. Tanulmányozzuk a XAML-t működés közben! A következő példánk­
ban kizárólag néhány *_xaml fájl felhasználásával készítünk WPF-alkalmazást

A MainWindow definiálása XAML-ben

Az első Window-leszármazott osztályunkat (Mainwindow) a C#-kódban olyan


osztálytípusként definiáltuk, amely kibővíti a system_ windows_window alaposz­
tályt Az osztály egyetlen Button típust foglal magában, amely ha a gombra
kattintunk, meghívja a regisztrált eseménykezelőt Ugyanezt a window típust
XAML nyelvben definiálva (tételezzük fel, hogy a markupot a Mainwindow _xaml
nevű fájlban definiáltuk) hasonló eredményt kapunk:

<!-- íme, a Window definíció -->

<Window x:clas$="Simplexam1App.Mainwindow"
xmlns="http://schemas.microsoft:.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft:.com/winfx/2006/xaml"
Title="My xaml App" Height="200" width="300"
windowstartupLocation ="Centerscreen">

<!-- Az ablak tartalmának beállítása -->


<Button width="133" Height="24" Name="btnExitApp"
click ="btnExitApp_clicked">
Exit Application
</Button>

<!-- A gomb kattintási eseménykezelőjének megvalósítása -->

<x:code>
<![CDATA[
private void btnExitApp_Clicked(object sender, RoutedEventArgs e)
{
ll Az aktuális alkalmazás leírójának megszerzése és az
ll alkalmazás leállítása. ·

Application.current.Shutdown();
}
] ]>
</x:code>
</Window>

487
28. fejezet: A WPF és az XAML

Először is vegyük észre, hogy a <Window> gyökérelem a class attribútum se­


gitségével definiálja a származtatott típus nevét. Az x prefix jelzi, hogy az att­
ribútumot a XAML-központú XML-névtér, a http:/ j schemas.microsoft.com/
winfx/2006/xaml definiálja (az XML-névterekkel a fejezet későbbi részében
foglalkozunk részletesen). A nyitó <Window> elem hatókörében meghatároz­
tuk a Title, a Height, a Width és a windowsstartupLocation attribútumokat,
amelyek - mint lá�uk - a system. windows.window típus hasonló nevű tulaj­
donságainak közvetlen leképezései.
Figyeljük meg, hogy az ablak definíciójának hatókörében elkészítettük a
Button példány megjelenését és funkcióját leíró markupot, amely segítségével

implicit módon beálli�uk az ablak content tulajdonságát. A változónév és a


külső főméretek beállításáll túl kezeltük a gomb kattintási eseményét: kijelöl­
tük a metódust, amelyhez fordulni kell, ha a kattintási esemény bekövetkezik.
A XAML-fájl utolsó említésre méltó pon�a a <Code> elem, amely lehetővé
teszi, hogy közvetlenül a*. xaml fájlban készítsük el az osztály eseménykeze­
lőit és egyéb metódusait. Biztonsági intézkedésként (noha erre az aktuális
példánkban nem feltétlenül lenne szükség) a kódot a CDATA hatókörbe
csomagoltuk, és így megakadályozzuk, hogy az XML-elemzők megpróbálják
közvetlenül értelmezni az adatokat.
Fontos rámutatnunk, a szerkesztési funkcionalitás implementálása nem
ajánlott a <Code> elemben. Bár ez az "egyetlen fájl megközelítés" az összes
műveletet elkülöníti egy helyen, a belső kód nem válasz�a szét egyértelműen
a felhasználói felület markupját és a programozási logika kapcsolatait. A leg­
több WPF-alkalmazásban az "igazi kód" egy kapcsolódó C#-kódfájlban ta­
lálható (végeredményben ezt a fájlt fogjuk elkészíteni).

Alkalmazásobjektum definiálása XAML-ben

Emlékezzünk rá, hogy a XAML segítségével bármely nem absztrakt .NET­


osztály markupját elkészíthe�ük, ha az osztály támoga�a az alapértelmezett
konstruktort. Ennek fényében az alkalmazásobjektum definícióját is minden
bizonnyal meghatározha�uk markupban. Vizsgáljuk meg a következő tar­
talmat az új MyApp.xaml fájlban:

<!-- A Main() metódus hiányzik! A Startupuri attribútum a metódus


funkcióját tekintve azonos megfelelője -->
<Application x:class="SimpleXamlApp.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startupuri="Mainwindow.xaml">
</Application>

488
(XAML-központú) WPF-alkalmazás készítése

Egyetérthetünk abban, hogy az Application osztályból származó C#-osz­


tálytípus és a XAML leírása közötti leképezés nem annyira áttekinthető, mint
a Mainwindow XAML-definíciója esetén. A példában nyomát sem találjuk a Ma.:
in O metódusnak. Mivel a .NET végrehajtható fájlnak rendelkeznie kell prog­
rambelépési ponttal, helyesen gondoljuk, hogy ez részben a startupurl tulaj­
donság alapján, a fordítás során generálódik. A hozzárendelt *.xaml fájl se­
gítségéve! határozható meg, hogy az alkalmazás indítása során melyik win­
dow-ból származó osztályt kell létrehozni.
A Main O metódus a fordítás során automatikusan elkészül, és a <Code>
elem segítségével - igény szerint - a következőképpen készíthetjük el az Exit
eseménykezelőt (vegyük észre, hogy többé nem statikus metódussal van
dolgunk, mivel a metódus példányszintű taggá alakul a MyApp osztályban):

<Application x:class="SimplexamlApp.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startupuri="Mainwindow.xaml" Exit ="AppExit">
<x:code>
<! [CDATA[
private void AppExit(object sender, ExitEventArgs e)
{
MessageBox.Show("App has exited");
}
JJ>
</x:code>
</Application>

XAML-fájlok feldolgozása az msbuild.exe


segitségével

Készen állunk rá, hogy a markupot érvényes .NET-szerelvénnyé alakítsuk


Az átalakítás során nem alkalmazhatjuk a C#-fordítót, és nem használhatunk
reakciófájlt. Pillanatnyilag a C#-fordító nem alkalmas a XAML-markup fel­
dolgozására. Azonban az msbuild.exe parancssati segédprogram tudja, ho­
gyan kell a XAML-t C#-kóddá alakítani, és a kódot menetközben lefordítani,
ha felhívjuk a figyeimét a megfelelő *.targets fájlokra.
Az msbuild. exe eszköz lehetővé teszi, hogy összetett fordítószkripteket
építsünk (micsoda meglepetés!) XML segítségéveL Az XML-definíciók egyik
érdekessége, hogy ugyanolyan formátumrnal rendelkeznek, mint a Visual
Studio *.csproj fájlok.

489
28. fejezet: A WPF és az XAML

Ennek köszönhetően egyetlen fájlt definiálhatunk az automatizált pa­


rancssori fordíták számára, valamint a Visual Studio 2008 projekt számára.
Vizsgáljuk meg a következő, lényegre törő simplexamlApp. csproj fájlt:

<Project DetaultTargets="Build"
xmlns="http://schemas.microsott.com/developer/msbuild/2003">
<PropertyGroup>
<RootNamespace>SimplexamlApp</RootNamespace>
<AssemblyName>SimplexamlApp</AssemblyName>
<OutputType>winexe</OutputType>
</PropertyGroup>
<ItemGroup>
<Reterence Include="System" />
<Reterence Include="Windowssase" />
<Reterence Include="Presentationcore" />
<Reterence Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDetinition Include="MyApp.xaml" />
<Page Include="Mainwindow.xaml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsott.csharp.targets" />
<Import Project="$(MSBuilds inPath)\Mi erosott.Wi nFX. targets" />
</Project>

A példában a <PropertyGroup> elem segítségével meghatározzuk a fordítás


néhány alapvető tulajdonságát, például a gyökémévteret, az előállított sze­
relvény nevét és a kimenet típusát (a esc. exe program /target:winexe beállí­
tásának megfelelője).
Az első <ItemGroup> azoknak a külső szerelvényeknek a gyüjteményét hatá­

rozza meg, amelyre hivatkoznunk kell. Látha�uk, hogy ezek azok a kulcsfontos­
ságú WPF-szerelvények, amelyeket a fejezet korábbi részében megvizsgáltunk
A második <ItemGroup> sokkal érdekesebb. Vegyük észre, hogy az <Application­
oetinition> elem Include aUribúturnát ahhoz az *. xaml fájlhoz rendeltük, amely

az alkalmazásobjektumunkat definiálja. A <Page> Include elemével listázha�uk a


többi * .xaml fájlt, amelyek az alkalmazásobjektum által feldolgozott ablakokat

(és oldalakat, amelyeket a XAML-böngészőalkalmazások készítése során gyak­


ran használunk) definiálják.
A *. csproj fájl "varázsereje" az utolsó <Import> elemekben rejlik. Vegyük
észre, hogy a fordító szkriptünk két *. targets fájira hivatkozik, amelyek

mindegyike további utasításokat foglal magában a fordítási folyamatra vonat­


kozóan. A Mierosott.winFX.targets fájl foglalja magában a XAML-definíciók
megfelelő C#-kódfájlra alakításához nélkülözhetetlen fordítási beállításokat, a
Mierosott.csharp.Targets pedig a C#-fordítóval folytatott kommunikációhoz

szükséges adatokat tartalmazza.

490
(XAML-központú) WPF-alkalmazás készítése

Megjegyzés A könyvben nem vizsgáljuk meg részletesen az msbuild.exe segédprogramot. Ha


szeretnék jobban megismerni az eszközt, a .NET Framework SDK dokumentációban keressük az
"
"MSBuild témakört.

Most feldolgozhatjuk a simplexamlApp.csproj fájlunkat az msbuild.exe prog­


rammal:

msbuild simplexamlApp.csproj

A fordítás befejezését követően a szerelvényünket a generált \ bin \Debug


könyvtárban találjuk Ekkor a várt módon elindíthatjuk WPF-alkalmazásun­
kat. Egyetérthetünk abban, hogy néhány sornyi markup megírása az érvé­
nyes .NET-szerelvények generálásának elég bizarr módja. Ha a biztonság
kedvéért megnyitjuk az ildasm.exe programmal a simplexamlApp.exe fájlt,
láthatjuk, hogy a XAML-kódunk (valamilyen módon) átalakult végrehajtható
alkalmazássá (lásd a 28.6. ábrát).

/7 SimpfeXamfApp.exe - ll DASM l= l @J i!oA-1


File View Help
8�·<)
'·· � M ANIF EST
B· • SimpleXarnlApp
9.. . SimpleXam!App.MainWindow
· :....... .class publicauto ansi beforefieldinit
: �
.. · extends [PresentationFramework]System.Windows.Window
'.... � implements [WindowsBase]System.Windows.Markup.!ComponentConnector
,... � _contentloaded: private bool
!.... � btnEx�App: assembly class [PresentationFromework)System.Windows.Controls.Button
�.. • .ctor: void()
·• lnitiaizeComponent: void()
[...... System.Windows.Marlwp.!ComponentConnector.Connect: void(lnt32,object)
L.... btnEx�App_Ciicked: void(object,class [PresentationCore]System.Windows.RoutedEventArgs)
é· ll: SimpleXamlApp.MyApp
i··· � .class public auto ansi beforefieldinit
; .... � extends [PresentationFramework)System.Windows.Application
' ...... ctor : void()
... AppEx�: void(object,class [PresentationFramework)System.Windows.Ex�EventArgs)
L. •· lnitiaizeComponent : void()
l. ·a Main : void()

l:
l{"'sernl:*y SimpleXarnlApp

28.6. ábra: Markup átalakítása .NET-szerelvénnyé? Érdekes...

491
28. fejezet: A WPF és az XAML

Markup átalakitása . NET-szerelvénnyé

Annak érdekében, hogy megértsük, a markup hogyan alakult át .NET-szerel­


vénnyé, kicsit mélyebbre kell ásnunk az msbui ld. exe folyamatba, és meg kell
vizsgálnunk néhány, a fordító által generált fájlt, beleértve a szerelvénybe fu­
tásidőben beágyazott fontos bináris erőforrást.

XAML leképezése C#-kódra

Mint említettük, az MSBuild szkriptekben meghatározott''. targets fájlok több


utasítást definiálnak, amelyek alapján a rendszer a XAML-elemeket a fordítás­
hoz C#-kódra alakítja. Amikor az msbuil d. exe feldolgozta a*. csproj fájlunkat,
két ''.g. cs fájlt készített (a "g" automatikusan generált fájlt jelöl), amelyeket az
\obj\Debug könyvtárba mentett. A *.xaml fájljaink neve alapján a kérdéses
C#-fájlok a Mainwindow. g. cs és a MyApp. g. cs fájlok.
Ha megnyitjuk a Mainwindow. g. cs fájlt, láthatjuk, hogy az osztály kibővíti a
window alaposztályt, és elvárásainknak megfelelően tartalmazza a btnExit­
App_clicked() metódust. Az osztály egy system. windows. controls. Button típusú
tagváltozót is definiál. Furcsa módon nem találunk olyan kódot, amely a Button
vagy a window típus tulajdonságainak (H eight, width, Titl e stb.) beállításáról
gondoskodna. A rejtélyre hamarosan fény derül.
Végül vegyük észre, hogy az osztály definiál egy bool típusú privát tagvál­
tozót (_content Loaded néven), amelyről a XAML-markup közvetlenül nem
tesz említést. Íme, a generált Mainwindow típus definíciójának egy része:

public partial class Mainwindow :


System. windows. window,
System. Windows. Markup. IComponentConnector
{
internal System.Windows. controls. Button btnExitApp;

ll Ezt a tagváltozót hamarosan részletesen megvizsgáljuk.


private bool _contentLoaded;

private void btnExitApp_Clicked(object sender, RoutedEventArgs e)


{
ll Az aktuális alkalmazás kezelőjének megszerzése,
ll és az alkalmazás leállítása.
Application. Current. shutdown();
}

492
Markup átalakítása .NET-szerelvénnyé

A window-ból származtatott osztály explicit módon megvalósí�a a system.


Windows.Markup névtérben definiált rcomponentconnector WPF-interfészt. Az
interfész egyetlen metódust definiál, a conntect() metódust, amelyet az ere­
deti Mainwindow. xaml fájlban meghatározott eseményre történő feliratkozást
valósí�a meg:

void system.Windows.Markup.IComponentconnector.connect(
int connectionrd, object target)
{
switch (connectionrd)
{
case 1:
this.btnExitApp = ((System.windows.Controls.Button)(target));
this.btnExitApp.Click += new
system.windows.RoutedEventHandler(
this.btnExitApp_clicked);
return;
}
this._contentLoaded = true;
}

Végül a Mainwindow osztály megvalósí�a az Initializecomponent() metódust.


Ez a metódus feloldja a szerelvényben a beágyazott erőforrás helyét az eredeti
,., .xaml fájl neve alapján. Ha az erőforrást a rendszer megtalálja, az Appl ica­
tion. Loadcomponent O meghívásával betölti az aktuális alkalmazásobjektum­
ba. Utoljára pedig a (korábban említett) privát logikai tagváltozót állí�uk igaz
értékre, és így biztosí�uk, hogy a rendszer a kért erőforrást az alkalmazás éle­
te során pontosan egyszer töltse be:

public void Initializecomponent() {


if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.uri resourceLocater = new
System.uri("/SimplexamlApp;component/mainwindow.xaml",
System.uriKind.RelativeorAbsolute);
System.windows.Application.Loadcomponent(this, resourceLocater);
}

Ezen a ponton felmerül a kérdés: pontosan mi is az a beágyazott erőforrás?

493
Z8. fejezet: A WPF és az XAML

A BAML szerepe

Amikor az msbui l d.exe feldolgozta a *csproj fájlunkat, *. baml kiterjesztéssei


létrehozott egy fájlt, amely a kezdeti Ma i nwi ndow. xaml fájl alapján kapta a ne­
vét. Mint azt a nevéből kitalálha�uk, a BAML (Binary Application Markup
Language - bináris alkalmazás jelölőnyelv) a XAML bináris megjelenítése.
Ezt a *.baml fájlt a rendszer (a generált *.g. resources fájl segítségéve!) erő­
forrásként beágyazza a lefordított szerelvénybe. A BAML révén a WPF-sze­
relvények (sokkal tömörebb formátumban) magukban foglalha�ák a teljes
XAML-definíciójukat. Ezt magunk is ellenőrizhe�ük, ha a refl ector.exe
programmal a 28.7. ábrán látható módon megnyi�uk a szerelvényünket.

Lutz R<><de�s .NIT Reflector

0 .O System
Name Value.
@ ·O System.Data
&J ·O System.Orawíng
maonwmdow.baml Oc 00 00 00 4d 00 53 00 . (767 bytes)
l!) ·Cl System.Web
00 ·O System.Windows.Forms
l!) .O System.Xml
8 ·Cl Simpi.Xam!App
G ll\ Simpi.Xam!App.exe
00@i Referenc�
{)
!!l o

0 {) Simpi.Xam!App
EJ W Resources
;;;J Simpi.XamiApp.g.resources

ll public resource SimpleXamiApp.g.resources


Size: 995 bytes

T 'L "'

Z 8. 7. ábra: A beágJ;azott *.baml erőforrás megtekintése Lutz Roeder .NET Rejleetor programjával

Az App l i cati on. Loadcomponent () hívása beolvassa a beágyazott BAML-erő­


forrást, és feltölti a definiált objektumok hierarchiáját a megfelelő állapotaik­
kal (például az ablak H ei ght és wi dth tulajdonságaival.). Valójában, ha a Vi­
sual Studio alkalmazásban megnyi�uk a *.baml vagy a *.g. resources fájlt,
látha�uk az eredeti XAML-attribútumok nyomait. A 28.8. ábra példaként a
startupLocati on.centerscreen tulajdonságot emeli ki.

494
Markup átalakítása .NET-szerelvénnyé

�x
6.x.http
://schemas.micro
soft.com/winfx/2
006/xaml. . .5 .
. . .T
.My Xa.-.1
.$
. $.


00
00 00 03 C9 FF 00 20 Ol 00 00 00 35 00 .5.
00 00 00 00 24 10 CA FF OA 62 74 6E 45 . $ . . . btnExit
41 70 70 99 FD 35 09 00 00 00 23 00 00 App . . 5. . l . .. $.

C7 FF 03 31 33 33 A4 FE 36 OB 00 00 00 24 08 Dl .. 133 . . 6 . . .. $ ..
FF 02 32 34 A4 FE 36 17 00 00 00 2E F2 FF 36 51 .24 .6.. . .60
10 45 78 69 74 20 41 70 70 6C 69 .. Exi t Aooli

Az automatikusan generált kód rejtvényének utolsó darabja a MyApp. g.cs fájl­

ban bújik meg. Látha�uk az Application-leszármazott osztályunkat a megfele­


lő Main() belépési pont metódussal. A metódus megvalósítása az Application­

leszármazott típuson hívja az Initializecomponent( ) metódust, amely cseré­


ben beállí�a a startupuri tulajdonságot, és így lehetövé teszi az objektumok
számára, hogy a bináris XAML-definíció alapján meghatározzák tulajdonsá­
gaik helyes beállításait.

namespace simplexarnlApp
{
public partial class MyApp : system.windows.Application
{
void AppExit(object sender, ExitEventArgs e)
{
MessageBox.Show("App has exited");
}

[System.Diagnostics.DebuggerNonusercodeAttribute()]
public void Initializecomponent() {
this.Exit +=

new system.windows.ExitEventHandler(this.AppExit);
;
this. startupuri = new System.uri("Mainwindow. xaml , ,
system.uriKind.Relative);
}

[System.STAThreadAttribute()]
[system.Diagnostics.DebuggerNonuserCodeAttribute()]
public static void Main() {
simplexamlApp.MyApp app = new SimplexamlApp.MyApp();
app.Initializecomponent();
app.Run();
}
}
}

495
28. fejezet: A WPF és a z XAML

XAML-ből szerelvény: a folyamat összefoglalása

Ejha! Összefoglalva, egy teljes .NET-szerelvényt hoztunk létre kizárólag három


XML-dokumentum felhasználásával (a dokumentumok egyikét az msbui l d. exe
segédprogram használta). Mint láttuk, az msbui l d. ex e a *. targets fájlokban
definiált segédbeállítások segítségével dolgozza fel a XAML-fájlokat (és gene­
rálja az*. bam l fájlt) a fordítási folyamat számára. Ezeket a részletek a háttérben
hajtódtak végre. A 28. 9. ábra bemuta�a a*. xaml fájlok fordításidejű feldolgozá­
sának átfogó képét.

A kimenet az \Obj\Debug mappa

MainWindow.xaml MainWindow.g.cs
MyApp.xmal My App.g.cs
*.cspro
MainWindow.baml
SimpleXamiApp.g.resources

SimpleXamiApp.exe

28. 9. ábra: A XAML-ből szerelvény fordításidejűfolyamat

Fontos rámutatnunk, hogy amikor a fordító a megfelelő C#-kód és a bináris


erőforrás előállításához feldolgozta az összes *. xaml fájlunkat, azokra gya­
korlatilag a továbbiakban semmiszükség (és soha nem kell őket a végrehajt­
ható fájllal együtt szállítani). A fejezet végén lá�uk majd, hogy a *. xaml fájl
programozott módon végrehajtott olvasásával módunkban áll dinamikusan
létrehozni window objektumot. Ebben az esetben a fizikai *. xaml fájlt nem
szükséges az alkalmazással együtt szállítani.

Forráskód A SimpleXarnlApp projektet a forráskódkönyvtár 28. fejezetének al könyvtára tar·


talmazza . A forráskódkönyvtárr ól lásd a Bevezetés xlv. ol dalát.

496
A kapcsolatok elkülönítése mögöttes kódfájlokkal

A kapcsolatok elkülönítése mögöttes


kódfáj lokkal

Mielőtt elkezdenénk beleásni magunkat a XAML részleteibe, meg kell vizs­


gálnunk az alap programozási modell még egy aspektusát: a kapcsolatok el­
különítését. Emlékezzünk rá, hogy a WPF fő célja, hogy elkülönítse a fel­
használói felület tartalmát a programozási logikától, és az aktuális példák er­
ről nem gondoskodtak.
Ahelyett, hogy közvetlenül a XAML <Code> elem hatókörébe ágyaznánk
az eseménykezelőinket (és az egyedi metódusokat), szerencsésebb különálló
C#-fájlt definiálni a megvalósítási logika meghatározásához, és így a *. xaml

fájlok kizárólag a felhasználói felület markupját foglalják magukban. Vizsgál­


juk meg a következő mögöttes Mainwindow.xaml. cs kódfájlt (megegyezés sze­
rint a C# mögöttes kódfájl neve * . xaml.cs formátumú):

ll Mainwindow.xaml.cs
using System;
using System.windows;
using system.Windows.Controls;

namespace SimplexarnlApp
{
public partial class Mainwindow : window
{
public Mainwindow()
{
ll Jegyezzük meg! A metódust a
//generált Mainwindow.g.cs fájl definiálja.
Initializecomponent();
}

private void btnExitApp_Clicked(object sender,


RoutedEventArgs e)
{
ll Az aktuális alkalmazás kezelőjének megszerzése,
ll és az alkalmazás leállítása.
Application.current.shutdown();
}
}
}

497
28. fejezet: A WPF és az XAML

A példában részleges osztályt definiáltunk (az eseménykezelő logika számára),


amelyet a rendszer a *.g .cs fájlban található ugyanolyan típusú részleges
osztálydefinícióval fésül össze. Mivel az Initi ali zecomponent () metódust a
Mainwindow.g.cs fájlban definiáltuk, az ablakunk konstruktora a beágyazott

BAML-erőforrás betöltéséhez és feldolgozásához meghívja a metódust.


Igény szerint mögöttes kódfájlt is készíthetünk az Application-leszármazott

típusunk számára. Mivel az események nagy része a MyApp.g.cs fájlban zajlik, a


MyApp.xaml.cs fájlban található kód alig több valamivel a következőknél:

ll MyApp.xaml.cs
using System;
using System.windows;
using System.windows.controls;

namespace SimplexarnlApp
{
public partial class MyApp : Application
{
private void AppExit(object sender, ExitEventArgs e)
{
MessageBox.show("App has exited");
}
}
}

Mielőtt az msbuild.exe segítségével lefordítanánk a fájljainkat, módosítanunk


kell a *.csproj fájlunkat A (félkövér betűvel szedett) <Com pile> elemek segít­
ségével biztosítanunk kell, hogy a rendszer az új C#-fájlokat is feldolgozza a
fordítás folyamat során:

<Project oefaultTargets="Build" xmlns=


"http:llschemas.microsoft.comldeveloperlmsbuildl2003">
<PropertyGroup>
<RootNamespace>SimplexamlApp<IRootNamespace>
<AssemblyName>SimplexamlApp<IAssemblyName>
<OutputType>winexe<IOutputType>
<IPropertyGroup>
<ItemGroup>
<Reference Include="System" l>
<Reference Include="WindowsBase" l>
<Reference Include="Presentationcore" l>
<Reference Include="PresentationFramework" l>
</ItemGroup>
<ItemGroup>
<Applicationoefinition Include="MyApp.xaml" l>
<Compile Include = "Mainwindow.xaml.cs" l>
<Compile Include = "MyApp.xaml.cs" l>
<Page Include="Mainwindow.xaml" />

498
A XAML-szintaxis

</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.csharp.targets" />
<Import Project="$(MSBuildBinPath)\Microsoft.winFX.targets" />
</Project>

Ha átadjuk a fordítószkriptünket az msbui l d. exe segédprogramnak, ered­


ményként ugyanazt a végrehajtható szerelvényt kapjuk. Fejlesztési szempont­
ból azonban a programozási logikából (C#) a leírás (XAML) letisztult részletét
állítottuk elő. Mivel ez a WPF-fejlesztés ajánlott módja, örömmel vehe�ük tu­
domásul, hogy a Visual Studio 2008 eszközzel létrehozott WPF-alkalmazások
mindig az előbbiekben bemutatott mögötteskód-modellt használják.

Forráskód A CodeBehindXamlApp projektet a forráskódkönyvtár 28. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A XAML-szintaxis

Mint azt a fejezet korábbi részében említettük, rendkívül kicsi az esélye annak,
hogy WPF-alkalmazásainkban kézzel kellene több oldalnyi XAML-markupot
megírnunk, hiszen ezt a feladatot a megfelelő eszközök (Visual Studio 2008,
Microsoft Expression Blend stb.) elvégzik helyettünk. Azonban minél többet
megtanulunk a jól formázott *. xaml fájlok szintaxisáról, annál felkészültebbek
leszünk az automatikusan generált markup beállítására és módosítására, va­
lamint mélyebb ismereteket szerzünk magáról a WPF-ről. Most pedig ássunk a
XAML-alapszintaxis mélyére (a következő fejezetekben- ahol szükség lesz rá­
további XAML-szintaxis példákkal találkozunk).

XAML-kisérletek a XamlPad segitségével

A XAML tanulmányozása során biztosan létrehozunk valamilyen tartalmat,


és az eredményt gyorsan szeretnénk látni. Ehhez a Microsoft az SDK része­
ként a xaml pad. exe segédprogramot bocsá�a rendelkezésünkre.

Megjegyzés Érdekes módon sem a .NET Framework 3.5 SDK, sem a Visual Studio 2008 nem
tartalmazza a xaml pad. exe programot. A Windows SDK letöltésével juthatunk hozzá ehhez az
eszközhöz, a fejezet legutolsó példájában C# segítségével elkészítjük a xaml pad. exe lecsupa­
szított verzióját.

499
28. fejezet: A WPF és az XAML

Ha letöltöttük a Windows SDK-t, hogy hozzájussunk a xaml pad.exe prog­


ramhoz, az eszközt a Start> All Programs> Microsoft Windows SDK > Tools
menüelem segítségével indíthatjuk A 28.10. ábra a xaml pad. exe kezdeti kép­
ernyőjét mutatja, amelyben aktiváltuk a Visual Tree Explorer beállítást (az
eszköztár megfelelő gombjával).

i3 Xam!Pad
:� Hefresh "-' ,g, � j CourierNew • 12 100%
L - '"---
- ��U '
-- --- --
�""
- l Visual Tree Explorer
A :Page
.. :Border
1 (Ml#t!;,wt1§

Property Tree Explorer

Name
c.
! base Framew
f· Content Grid

" L_� ··- � ' __-.-J

<PaQe
.. .
:ct.ln:�:c:: ht'tp: // ,!!:Che-r.a.s.micro-'o!t. co�.f'..;infx/2006/xar.tl/pr�.!�n't-a:.icn"'
..
�ln.s: X"" h;;;'tp: 1 /3chen:a.s. o.icro3oft. cc:n/•.:infx/2006/xa!:'U.
. H>
<Grid>

</G:=id>
</Pag-e>

Done. Markup saved to '"(:\Program Fi!M\Microsoft SOKs\WindO\vs\v6.0\bin\XamiPad_Save:d.xamf•.

28.1 O. ábra: A XamlPad a XAML-markup valósidejű megjelenítését biztosítja

A xaml Pad segítségével az ablak alján található panelben szerkeszthetjük a


XAML-markupot, és a felső részben megtekinthetjük a kimenetet. Amikor el­
ső alkalommal indítjuk az eszközt, egy üres <Page> deklarációt találunk, ame­
lyet arra használunk, hogy egy XBAP-alkalmazás markupját tartalmazza:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<!-- Ide írhatjuk a XAML-markupot! -- >

</Grid>
</Page>

Noha a xaml Pad nem teszi lehetővé, hogy a xaml Pad nézet ablakában közvet­
lenül megtekintsük egy <Window> elem markupját, a <Page> elemet <Window>
elemre módosíthatjuk, és az F5 funkciógombbal egy különálló ablakban meg­
jeleníthetjük a tartalmát. Ne feledjük továbbá, hogy a <Page> és a <Window>
elem markupja megegyezik!

500
A XAML-szintaxis

Megjegyzés A x am lPad nem teszi lehetövé, hogy olyan markupot készítsünk, amely a kód for­
dítását vonja maga után. Ez magában foglalja a class attribútum definiálását (kódfájl meghatá­
rozásához), a <Code> elemek használatát, illetve bármely XAML-kulcsszó alkalmazását, amely a
forráskód fordítását vonja maga után (mint például a FieldModifier vagy a classModifier ) .
Bármilyen erre irányuló próbálkozás markuphibához vezet.

A markup készítése közben a xa m l Pad programban feltűnik az IntelliSense hi­


ánya. Ha azonban a Show Visual Tree gombra kattintunk, megnyílik az a fel­
használói felület, amely a Visual Studio Properties ablakát utánozza, és segít
elképzelni a XAML-markup struktúráját. Sajnálatos módon a Visual Tree ab­
lak segítségével (pillanatnyilag) nem módosíthatjuk magát a XAML-t; csak a
XAML-markup írásvédett megtekintését teszi lehetövé.
Azt se feledjük, hogy a XarnlPad segítségével jelenleg nem menthetjük a
*. xaml fájlokat; a markupot automatikusan a xamlPad_saved. xaml fájlba menti,

és az eszköz következő betöltése során megjeleníti (azonban a markupot


igény szerint bemásolhatjuk az aktuális alkalmazásba).

XAML-névterek és -kulcsszavak

Mint azt a fejezet korábbi példáiban láttuk, a WPF-központú XAML-fájl gyö­


kéreleme (például a <Window>, a <Page> vagy az <Ap pl i c ati on> tag) rendsze­
rint két XML-névtérre hivatkozik:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microso ft.com/winfx/2006/xaml">
<Grid>

</Grid>
</Page>

Az első XML-névtér, a http:/jschemas.rnicrosoft.com/winfx/2006/xarnl/


presentation WPF-központú névtereket képez le, és az aktuális* .xaml fájl ren­
delkezésére bocsátja azokat (sy st e m.windows, system. Windows.controls, system.
Windows.Data, System.Windows.In� System.Windows.Media, System.Windows.Na­

vigation stb.). Ez az egy-többes leképezés gyakorlatilag harcikódolva van a


WPF-szerelvényekben (wi ndow sBa se. dll, Presentati oneore. dll és Presenta­
ti on F ra m ewor k.dll) a szerelvény szintű [xmlnsDefinition] attribútum révén.
Ha ezeket a WPF-szerelvényeket a refl eeto r. ex e programmal megnyitjuk,
közvetlen betekintést nyerhetünk a leképezésekbe (lásd a 28.11. ábrát).

501
28. fejezet: A WPF és az XAML

'fílo y;.. l- tlolp

;OOictfill :?P·,·E:::::JJs _"

28.11. ábra: A http//schemas.microsoft.com/winfx/2006/xaml/presentation


az alapvető WPF-névterekre képez le

A második XML-névtér, a http:/fschemas.microsoft.com/winfx/2006/xaml,


XAML-specifikus kulcsszavakat, valamint a system.windows.Markup névtér­
ben található típusok részhalmazát foglalja magában. Egy helyesen formázott
XML-dokumentumnak definiálnia kell egy gyökérelemet, amely elsődleges
névtérként egyetlen XML-névteret jelöl ki, amely tipikusan a leggyakrabban
használt elemeket tartalmazó névtér. Ha a gyökérelembe további másodiagos
névtereket kell belefoglalni (mint azt itt láttuk), a névtereket (a lehetséges
névülközések feloldásának érdekében) egyedi prefix alkalmazásával kell de­
finiálnunk. Megegyezés szerint a prefix egyszerűen "x"; azonban bármilyen
egyedi tokent használhatunk, például a xaml specificstuff tokent is:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:
xamlspecificstuff="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>

</Grid>
</Page>

Az XML-névtér prefixek definiálásának nyilvánvaló hátránya, hogy a xaml Spe­

ci ficstuff szót mindig be kell írnunk, ha a XAML-fájlunknak a kérdéses név­


térben definiált típusok egyikére kell hivatkoznia. Például a http://sche­
mas.microsoft.com/winfx/2006/xaml egyik eleme a Code XAML-kulcsszó,
amely - mint láttuk - lehetövé teszi, hogy XAML-dokumentumba ágyazzuk a
C#-forráskódot. A másik XAML-kulcsszó a class, amely segítségével megha­
tározhaljuk a generált C#-osztály nevét.
Ha a fejezet korábbi részében létrehozott MyApp XAML-definíciót módosí­
tani szeretnénk, mert ezt a részletesebb XML-névtér prefixet szeretnénk al­
kalmazni, a következő kódot kellene megírnunk:

502
A XAML-szintaxis

<Application xamlSpecificStuff:class="SimplexamlApp.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:xamlspecificstuff =

"http://schemas.microsoft.com/winfx/2006/xaml"
Startupuri="Mainwindow.xaml" Exit ="AppExit">
< xamlspecificstuff:code>
<! [CDATA[
private void AppExit(object sender, ExitEventArgs e)
{
MessageBox.show("App has exited");
}
J J>
</Xamlspecificstuff:code>
</Application>

Mivel a xamlSpecificstuff több billentyűleütést követel, maradjunk az x-nél.


Mindenesetre a class és a code kulcsszavakon túl az http:/ j schemas.micro­
soft.com/winfx/2006/xaml XML-névtér további XAML-kulcsszavakat bo­
csát rendelkezésünkre (és a system.windows.Markup névtér elemeit is). A 28.9.
táblázat ezen alapvető kulcsszavakat mutatja be.

Array . NET-tömbtípust képvisel a XAML-ben.

classModifier Lehetövé teszi a cl ass kulcsszóval jelölt (belső vagy nyilvá­


nos) osztály láthatóságának meghatározását.

DynamicResource Lehetövé teszi, hogy olyan WPF-erőforrásra hivatkozzunk,


amelynek változásait ellenőrizni kell.

FieldModifier Lehetövé teszi, hogy meghatározzuk egy típustag (belső, nyil­


vános, privát, védett) láthatóságát a gyökér bármely megneve­
zett részeleme (például a <Window> elemen belül egy <Button>
elem) számára. "Megnevezett elemet" a NameXAML kulcs­
szóval definiálunk.

Key Lehetövé teszi, hogy kulcs értéket határozzunk meg egy


XAML-elemhez - amelyet egy szótár elemben helyezünk el.

Name Lehetövé teszi, hogy meghatározzuk adott XAML-elem gene­


rált C#-nevét.

Null Nullreferenciát jelöl.

Static Lehetövé teszi, hogy egy típus statikustagjára hivatkozzunk.

StaticResource Lehetövé teszi, hogy olyan WPF-erőforrásra hivatkozzunk,


amelynek változásait nem kell ellenőrizni.

503
28. fejezet: A WPF és az XAML

Type A C# typeof operátor XAML-megfelelője (a megadott név


alapján System. Type osztályt hoz létre).

TypeArguments Lehetővé teszi, hogy az elemet generikus típusként hozzunk


létre a meghatározott típusparaméterrel (például a L i st<int>
elemet szemben a L i st<bool> elemmel).

28. 9. táblázat: XAML-kulcsszavak

Több említett kulcsszót látunk majd működés közben. Egy egyszerű példán
keresztül vizsgáljuk meg a következő XAML <WindoW> definíciót, amely a
classModifier és a FieldModifier, valamint a Name és a class kulcsszavakat al­

kalmazza (emlékezzünk rá, hogy a xamlpad.ex e nem teszi lehetővé, hogy olyan
XAML-kulcsszót használjuk, amelynek alkalmazása a kód fordítását vonja
maga után, mint például a code, a FieldModifier vagy a classModifier) :

<!-- Ez az osztály most belső osztály lesz.


Kódfájl használata esetén a részleges osztályt is belső
osztályként kell definiálni! -->
<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- Ez a gomb a *.g.cs fájlban nyilvános lesz -->


<Button x:Name ="myButton" x:FieldModifier ="public">
OK
</Button>
</Window>

Alapértelmezés szerint a C#/XAML típusdefiníciók nyilvánosak, míg a ta­


gok alapértelmezés szerint belsők. A XAML-definíciónk alapján azonban az
eredményként kapott, automatikusan generált fájl belső osztálytípust tartal­
maz egy nyilvános Button típussal:

internal partial class Mainwindow : System.windows.window,


system.windows.Markup.IComponentConnector
{
public System.windows.controls.Button myButton;

504
A XAML-szintaxis

XAML-elemek és -attribútumok

Miután létrehoztuk a gyökérelemünket, és bevezettük a szükséges XML­


névtereket, a következő feladat, hogy gyennekelemekkel töltsük fel a gyökeret.
Mint említettük, egy valós WPF-alkalmazásban a gyermek a paneltípusok
egyike lesz, amely a felhasználói interfészt leíró további felhasználóifelület­
elemeket foglal magában. A következő fejezet részletesen foglalkozik ezekkel
a paneltípusokkal, tehát most tételezzük fel, hogy a <Window> típusunk egyet­
len Button elemet foglal magában.
Mint azt a fejezet során láttuk, a XAML-elemek a meghatározott .NET­
névtérben osztályra vagy struktúra típusokra, a nyitó elem címke attribútu­
mai pedig tulajdonságokra vagy a típus eseményeire képeződnek le (nem hi­
vatkozhatunk a típus metódusaira XAML-attribútum segítségéve!). Ezért, ha
az alábbihoz hasonló kódot készítünk:

<Window x:class="MyWPFApp.Mainwindow" x:classModifier ="internal"


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- Ez feltételezi, hogy van egy myButton_click esemény


a kódfájlunkban! -->

<Button x:Name ="myButton" x:FieldModifier ="public"


Height ="50" Width ="100" Click ="myButton_click">
OK
</Button>
</WindOW>

akkor gyakorlatilag a következő kóddal kifejezett Buttant készí�ük el:

Button myButton = new Button();


myButton.Height = 50;
myButton.width = 100;
myButton.Content = "OK";
myButton.Click += new RoutedEventHandler(myButton_Click);

A fejezetben eddig látott példák után ez a leképezés egyértelmű; vizsgáljuk


meg azonban a gomb tartalmának hozzárendelését Emlékezzünk rá, hogy
több WPF-vezérlőelem a contentcontrol ősosztályból származik. Ennek kö­
szönhetően tetszőleges számú belső elemet (például Buttont, scrollBart) tar­
talmazhatnak A content tulajdonságot úgy állítottuk be implicit módon,
"

hogy az " OK szöveget elhelyeztük a nyitó és a záró elemben. Ha szükséges,


explicit módon a következőképpen állítha�uk be a content tulajdonságot:

505
28. fejezet: A WPF és az XAML

<Button x:Name ="myButton"


Height ="50" width ="100" content "OK">
</Button>

Ezen a ponton a content tulajdonság implicit vagy explicit módon elvégzett


beállítása pusztán személyes preferencia kérdésének tűnhet. A történet izgal­
masabbá válik, ha elgondolkodunk azon, hogy a XAML segítségével hogyan
alakítanánk a Button tartalmát objektummá egyetlen egyszerű sztring helyett
(grafikus renderelés, scrollBar, vagy TextBox stb.). Mint a fejezet korábbi ré­
szében már említettük, a megoldás a tulajdonságelem szintaxis alkalmazása.

A XAML tulajdonságelem szintaxisa

A tulajdonságelem szintaxis lehetővé teszi, hogy összetett objektumokat rendel­


jünk egy tulajdonsághoz. Íme a "görgetősáv a gombban'' eset XAML-leírása,
amely tulajdonságelem szintaxis segítségével állí�a be a content tulajdonságot:

<Button x:Name ="myButton" Height ="100" width ="100">


<Button.content>
<ScrollBar Height = "50" width = "20"/>
</Button.Content>
</Button>

Vegyük észre, hogy ebben az esetben a <Button. content> beágyazott elemben


definiáltuk a scrollBar típust. A tulajdonságelem szintaxist mindig lebont­
ha�uk <Tí pusNév. Tulaj donságNév> mintára; jelen esetben nyilván a <Button> a
típus, a content pedig a tulajdonság. A 28.12. ábra a xaml pad. ex e programban
látható kimenetet muta�a.
Emlékezzünk rá, hogy a contentcontrol-ból származtatott osztály gyer­
mekelemével a rendszer automatikusan beállja a content tulajdonságot, tehát
a következő definíció is helyes:

<Button x:Name ="myButton" Height ="100" Width ="100">


<ScrollBar Height = "50" width = "20"/>
</Button>

A tulajdonságelem szintaxis nem korlátozódik a content tulajdonság beállítá­


sára. Ezt a XAML-szintaxist bármikor alkalmazha�uk, ha típus tulajdonságnál
összetett objektumot kell beállítanunk. Vizsgáljuk meg például a Button típus
Background tulajdonságát.

506
A XAML-szintaxis

A tulajdonságot a WPF API-ban található bármely Brush típuson beállíthat­


juk Ha egyszínű ecsettípust szeretnénk használni, ehhez csak a következő
markup szükséges, mivel a Brush-leszármazott típust (például Background) vá­
, ró tulajdonságokhoz rendelt sztringérték automatikusan ecset típussá alakul:

<!-- Itt a "Green" Brushes.Green tipusra képeződik le -->


<Button x:Name ="myButton" Height ="100" width ="100"
Background ="Green">
<Button.content>
escrollBar Height = "50" width = "20"/>
</Button.content>
</Button>

�\ XamiPad l l @l lii:iiil
=

: IOAuto Parsej � Refresh � : ® ; Courier New ·lii]EJI1'!7:l


B
• 12


LlJ
I'7Paqe
I
xmlns=""ht:.t.p: l /.sche:cas .m.icrosof�. coc./winfx/2006/xar.ü/presentation"
..
xnüns: x:c ht:.t.p: l /3che:nas. micro.soft:. co:!!/wl.D'%.x/2006/xaml"'>
<Button x:Name ,.,.•myBut:ton"' Hel.Qht ='"too· Wideh ='"100'">
<Butt.on.Content.>

E
<ScrollBar F.eigh� = "50" Width = "20"' l>
</But.t.on.Content.>
<IBut.�on>
</Paqe>

Done. Markup saved to "C:\Progrom Files\Microsoft SDKs\Windows\v6.0\bin\XamiPad_Saved.xaml".

28. 12. ábra: A tulajdonságelem szintaxis lehetővé teszi, hog�} összetett tulajdonságokat
rendeljünk a tulajdonságokhoz

Ha kifinomultabb ecsetre (például LinearGradientBrush ecsetre) van szüksé­


günk, a név j érték szintaxis nem lesz elegendő. Figyelembe véve, hogy a L i­
nearGradi entBrush teljes osztálytípus, a tulajdonságelem szintaxis segítségé­

vel kell a típusnak a kezdeti értékeket átadnunk:

<Button x:Name ="myButton" Height ="100" width ="100">


<Button.content>
cSerollBar Height = "50" Width = "20"/>
</Button.content>
<Button.Background>
<LinearGradientBrush startPoint="O,O" EndPoint="1,1">
<Gradientstop color="Blue" offset="O" />
<Gradientstop Color="Yellow" offset="0.25" />

507
28. fejezet: A WPF és az XAML

<Gradientstop Color="Green" offset="0.75" />


<GradientStop Color ="Red" Offset="0.50" />
</LinearGradientBrush>
</Button.Background>
</Button>

Pillanatnyilag ne foglalkozzunk a LinearGradi entBrush típus konfigurálásá­


val (a 30. fejezet a WPF grafikus renderelési szolgáltatásait vizsgálja). Egysze­
rűen csak figyeljük meg, hogy a tulajdonságelem szintaxis segítségével hatá­
roztuk meg a Button típus content és Background tulajdonságait. A 28.13. áb­
rán ezen különleges gomb renderelése látható.

28. 13. ábra: Szakatlan gomb típus

Noha a tulajdonságelem szintaxissal leggyakrabban összetett objektumokat


(például a L inearGradi entBrush objektumot) rendelünk tulajdonság értékek­
hez, megengedett az egyszerű sztring értékek használata is:

<Button x:Name ="myButton" Height ="100" width ="100">


<Button.content>
<ScrollBar Height = "50" Width = "20"/>
</Button.content>
<Button.Background>
Pink
</Button.Background>
</Button>

Ebben az esetben nem nyertünk semmit, inkább feleslegesen bonyolítottuk a


folyamatot, miközben csak a következőket kellett volna beírnunk:

<Button x:Name ="myButton" Height ="100" width ="100"


Background = "Pink">
<Button.content>
<ScrollBar Height = "50" width = "20"/>
</Button.Content>
</Button>

508
A XAML-szintaxis

A XAML csatolt tulajdonságai

A. tulajdonságelem szintaxison kívül a XAML definiál egy olyan szintaxist,


amellyel csatolt tulajdonságokat határozhatunk meg. A csatolt tulajdonságok
sokoldalúak, és egyik céljuk, hogy lehetővé tegyék a különböző gyermek­
elemek számára egy olyan tulajdonság egyedi értékékeinek meghatározását,
amelyet valójában a szülőelemben definiáltunk. A csatolttulajdonság-szinta­
xis alkalmazásának legelterjedtebb módja a felhasználóifelület-elemek elhe­
lyezése a WPF-paneltípusok (Grid, DockPanel stb.) valamelyikén. A következő
fejezetben részletesen megvizsgáljuk ezeket a paneleket. Egyelőre tekintsük
meg a csatolttulajdonság-szintaxis következő példáját:

<Page
xmlns=''http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel LastchildFill ="True">
<!-- Elemek dokkolása a panelhez csatolt tulajdonságok
használatával -->
<Label DockPanel.Dock ="Top" Name="lblrnstruction"
Fontsize="15">Enter car Information</Label>
<Label DockPanel.Dock ="Left" Name="lblMake">Make</Label>
<Label DockPanel.Dock ="Right" Name="lblcolor">Color</Label>
<Label DockPanel.Dock ="Bottom"
Name="lblPetName">Pet Name</Label>
<Button Name="btnOK">OK</Button>
</DockPanel>
</Page>

A példában DockPanel típust definiáltunk, amely négy, a tárolóban dokkolt Label


típust foglal magában. Vegyük észre, hogy a csatolt tulajdonság szintaxisa
<SzülőTípus.szülőTulajdonság> (például DockPanel.Dock) . A Button típus nem
határoz meg dokkolási területet; elfoglalja a DockPanel fennmaradó területét, mi­
vel a nyitó <DockPanel> definícióban a LastchildFill tulajdonság van beállítva.
A csatolt tulajdonságokkal kapcsolatban néhány dologgal tisztában kell
lennünk. Nem univerzális szintaxissal állunk szemben, amelyet bármely szülő
bármely elemén alkalmazhatunk. A következő XAML-definíció például nem
elemezhető anélkül, hogy hibát ne kapnánk:

<!-- A Height tulajdonság beállítása Button objektumon csatolt


tulajdonsággal -->
<Button x:Name ="myButton" width ="100">
<Button.content>
<ScrollBar Button.Height = "100" Height = "50" width = "20"/>
</Button.content>

509
28. fejezet: A WPF és az XAML

<Button.Background>
Pi nk
</Button.Background>
</Button>

A csatolt tulajdonság valójában a WPF-specifikus fogalom, a függőségi tulajdon­


ság speciálls formáját képviselik. Röviden, a függőségi tulajdonságok lehetővé
teszik, hogy egy mező értékét több bemenet alapján számítsuk ki. A függőségi
tulajdonságoknak, és ennél fogva a csatolt tulajdonságoknak is "regisztrálni­
uk" kell, hogy mely tulajdonságokat mely objektumok állítha�ák be (ezt az
előzőleg bemutatott scroll Bar/Button példában nem tettük meg).
A WPF a háttérben több technológia- például az adatkötés, a stílusok és
témák és az animációs szolgáltatások- használata esetén alkalmazza a füg­
gőségitulajdonság-mechanizmust. A függőségi tulajdonság megvalósításával
önirányító ellenőrzést, alapértelmezett értékeket és visszahívási mechaniz­
must biztosíthatunk, és lehetővé teszi, hogy futásidejű információk alapján
állítsuk be a tulajdonságok értékét
A függőségi tulajdonságokkal kapcsolatos furcsaság, hogy a függőségi tu­
lajdonság értékének beállítása
pontosan úgy néz ki, mint egy " normál " .NET­
tulajdonság beállítása. Ezért az esetek többségében nem vesszük észre, hogy
beállítottuk egy függőségi tulajdonság értékét
Azonban a függőségi tulajdonságok megvalósítása a színfalak mögött való­
jában teljesen eltérő. A legtöbb WPF-alkalmazásunkban nem kell egyedi függő­
ségi tulajdonságokat készítenünk Ez csak abban az esetben válhat gyakori fel­
adattá, ha egyedi WPF-vezérlőelemeket kell készítenünk Ez a XAML megjele­
nésének köszönhetőerr szintén nem számít mindennapos tevékenységnek.
A 29. fejezetben részletesen megvizsgáljuk a függőségi tulajdonságokat.

XAML-tipusátalakitók

Gyakorlati szempontból, amikor értéket rendelünk az attribútumokhoz (pél­


dául Background = "Pi nk"), vagy a nyitó és a záró elemek hatókörében impli­
cit módon beállí�uk a tartalmat (például <Button>OK</ Button>), feltételezhet­
jük, hogy az értékek sztringadatok. Ha azonban ezt jobban átgondoljuk, rájö­
hetünk, hogy ez nem minden esetben igaz. Gondoljunk például a Button tí­
pus Background tulajdonságának definíciójára (amelyet a control ősosztálytól
örököltünk):

510
A XAML-szintaxis

ll A System.Windows.controls.control.Background tulajdonság.
pub lic B r ush B a c k g r o u nd
{

Mint látjuk, a tulajdonság a Syste m string helyett a Brush típust csomagolja


be! Felmerül a kérdés, hogy (ebben az esetben) a "Pink" értéket mi alakítja
solidcolorBrush objektummá, amely a rózsaszínnek megfelelő RGB értékek­
kel rendelkezik? Íme, egy másik példa, vizsgáljuk meg egy meghatározott
méretekkel rendelkező bíborszínű ellipszis XAML-definícióját:

<Ellipse Fi ll = "Purple" width = "100.5" Height = "87.4">


<lEl l i pse>

Ha megvizsgálnánk az Ell i p se típus wi d th és He ight tulajdonságait, láthat­


nánk, hogy prototípusuk szerint sztringek helyett doub l e típusú adatokkal
dolgoznak.
A színfalak mögött a XAML-elemzők különböző típusátalakíták segítségével
alakítják a sztringadatokat a megfelelő mögöttes objektummá. Ha például a
"

" Pink értéket olyan tulajdonsághoz rendeljük, amely prototípusa szerint ecset
típusokkal dolgozik, a rendszer a colorconverter és a Brushconverter típusokat
alkalmazza. Több átalakító létezik: a si z eco nv ert e r (az előző El li pse típus
width és Height tulajdonságainak beállítására), a Rectconverter, a ve c to rco n ­

verter és így tovább.

A nevétől függetlenül az összes típusátalakító a system.componentModel.


Typeconverter ősosztályból származik. Ez a típus több virtuális metódust defi­
niál, például a canconvertTo(), a convertTo(), a canconvertFrom() és a convert

From() metódusokat, amelyeket származtatott típusban felüldefiniálhatunk, és

így gondoskodhatunk a mögöttes fordításról.


Tulajdonképpen nem kell tudnunk, hogy melyik típusátalakító képezi le a
XAML-sztringadatainkat a megfelelő mögöttes objektumra. Legalábbis elég
megértenünk, hogy az átalakítókat a rendszer a háttérben a XAML-definíciók
egyszerűsítésére használja.

Megjegyzés Noha maga a XAML viszonylag új technológia, a típusátalakíták koncepciója a


.NET platform megjelenése óta létezik. A színfalak mögött a Windows Forms és a GDI+ külön­
böző átalakítókat használ. Például a G Dl+ system. Drawin g névtér a Fontconverte r típusát­
alakítót definiálja, amely a "Wingdings" sztringet a Wingdings betűkésztetet alkalmazó Font
objektumra képezi le.

511
28. fejezet: A WPF és az XAML

A XAML markupbővitményei

A típusátalakíták érdekes szerkezetek abból a szempontból, hogy nincs fizi­


kai bizonyítéka a XAML szin�én velük folytatott együttműködésnek. Ehe­
lyett a típusátalakítókat a rendszer a színfalak mögött a *. xaml fájl feldolgo­

zása során alkalmazza. Ezzel szemben a XAML támoga�a a markupbővító'ket


is. A típusátalakítóhoz hasonlóan a markupbővítők lehetővé teszik, hogy egy
egyszerű címkézési értéket futásidejű objektummá alakítsunk. A különbség
azonban az, hogy a markupbővítők nagyon speciális XAML-szintaxissal ren­
delkeznek.
Mivel a típusátalakíták és a markupbővítők ugyanazt a célt szolgálják,
felmerülhet a kérdés, hogy miért készíthetünk típusdefiníciókat két különbö­
ző módszerrel. A markupbővítők a típusátalakítóknál rugalmasabb műkö­
dést biztosítanak, és lehetővé teszik, hogy a XAML nyelvet letisztult módon,
új funkcionalitással bővítsük.
A markupbővítők segítségével egy másik típuson a statikus tulajdonság
visszatérési értékéhez rendelhe�ük egy tulajdonság értékét, címkézéssei
adattömböt deklarálhatunk, vagy típusinformációkat gyűjthetünk. Valójá­
ban, a XAML-kulcsszavak egy részhalmaza (például az Array, a Null, a sta­
tic és a Type ) álruhába bújtatott markup bővítése. A típusátalakítókhoz ha­
sonlóan a markupbővítőket a rendszer belsőleg olyan osztályként ábrázolja,
amely a MarkupExtension osztályból származik (megegyezés szerint a Markup­
Extension osztályból származó osztályok nevükben az Extension előtagot

használják).
Vizsgáljuk meg működés közben a markupbővítőket! Tételezzük fel, hogy
a Label típusok készletéhez beállí�uk a content tulajdonságot, és a system.
Environment statikus tagjaival így jelenítünk meg információkat arról a számí­

tógépről, amelyen az alkalmazásunkat futta�uk. Íme a teljes markup, magya­


rázattal:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:corLib="clr-namespace:System;assembly=mscorlib">

<StackPanel>
<Label content ="{x:Static corLib:Environment.MachineName}"/>
<Label content ="{x:static corLib:Environment.osversion}"/>
<Label content =''{x:Static corLib:Environment.Processorcount}"/>
</StackPanel>
</Page>

512
A XAML-szintaxis

Vegyük észre, hogy a <Page> defuúció új XML-névtér deklarációval rendelke­


zik, amelyet a corLib névtérprefixummal láttunk el (a prefixum neve bármely
más XML-névtér prefixumához hasonlóan tetszőleges). Az XML-névtérhez
rendelt érték azonban egyedi, mivel egy clr-namespace nevű regisztrált tokent
(amely lehetővé teszi, hogy a típus definíciót tartalmazó .NET-névtérre mutas­
sunk), valarnint egy assembly nevű másik tokent (amely a névteret magában
foglaló szerelvény "barátságos" nevét képviseli) használunk.
Az alkalmazott XML-névterekben figyeljük meg, hogy az egyes Label ti­
pusok a static jelölő kiterjesztés segítségével az Environment típus statikus
tagját indítják. Mint látjuk, a markupbővítőket mindig kapcsos zárójel fogja
közre. Legegyszerűbb formájában a jelölő kiterjesztés két értéket vesz fel: a
jelölő kiterjesztés nevét (ebben az esetben static) az az érték követi, amelyet
hozzárendelünk (például corLib.system.Environment.osversi on) .

<!-- A "static" jelölő kiterjesztés segítségével a content


tulajdonságot a statikus tulajdonság értékére állítjuk -->
<Label content ="{x:static corLib:Environment.osversion}"/>

Íme, egy másik példa. Tételezzük fel, hogy a különböző típusok teljesen meg­
határozott nevét szeretnénk megszerezni, hogy aztán az értékeket Label típu­
sok content tulajdonságaihoz rendeljük. Ebben az esetben a Type jelölő kiter­
jesztést alkalmazhatjuk:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http:jjschemas.microsoft.com/winfx/2006/xaml"
xmlns:corLib="clr-namespace:System;assembly=mscorlib">

<StackPanel>
<Label content ="{x:Static corLib:Environment.MachineName}"/>
<Label content ="{x:Static CorLib:Environment.OSVersion}"/>
<Label content ="{x:Static corLib:Environment.Processorcount}"/>
<Label content ="{x:Type Label}" />
<Label Content ="{x:Type Page}" />
<Label content ="{x:Type corLib:Boolean}" />
<Label Content ="{x:Type x:TypeExtension}" />
</StackPanel>
</Page>

A példában a WPF Labe l típusának, Button típusának, valarnint Boolean adat­


típusának az rnscorlib. dll szerelvényben teljesen meghatározott nevét kap­
juk, ráadásként pedig a Type jelölő kiterjesztés teljesen meghatározott nevét.
Ha az oldalt megtekintjük a xamlpad.exe segédprogramban, a 28.14. ábrához
hasonló képet kapunk.

513
28. fejezet: A WPF és az XAML

í3 Xamll'ad .._.... ._-... ___ __

\I(JAuto j .. Refresh lJ j <iP� j Courier New


Parse • 12 • �É] .U j 100%

MVDELLXPS

Microsoft Windows NT 6.0.6000.0

System,Windows.Controls.label

System,Windows.Controls.Page

System.Boolean

System.Windows.Markup.TypeExtension

<Page
xmln.s=..http: l/�chel'lB.s .:nicrosof't:. comt•,;infx/2006/xaol/pre��nta'tion"
xm.ln�: x="ht.tp: 1 /3cher::as .ru.cro�oft. co:n/TA•infx/2006/xa:ol'"
..
xmlns: CorLib= clr-nan:.�space: System; asse.mbly=t'!l..ecorlib">

<StackPanel>
t.
<Label Content ="{x: Si: a ic CorLib: Environr..�nt: .Hachine!la::te} "/>
<Label Content. =.. {x:Stat.ic CorLib:E.nvironr.:ent.OS""Ver3ion}"/>
n
<Label Conte t. ="{x: Sta.:.ic CorLib: Envirorur:em:. Proces.sorCcunt} "l>
<Label Con'tem: ="{x:Type Lab el} " l>
<Label Conten':. ..,•{x:Type Page}" l>
<Label Content """ {x:Type Corlib:Boolean}" l>
<Label Cont:e:l.'t ="{x:Type x:T:y-peE.xten"ion}" />
<IStackPan�l>
</Paoe>

Done. Markup saved to "(:\Program Files\Microsoft SDKs\Windows\v6.0\bin\XamiPad_Saved.xaml•.

28.14. ábra: Markupbóvítők alkalmazása a tulajdonságokon statikus tagok értékeinek beállítására és


tipusinformációk megszerzésére

Az erőforrások és adatkötések előzetes áttekintése

A XAML-szintaxis bevezetésének összefoglalásaként az utolsó példában nem­


csak az Array jelölő kiterjesztés (amelyet az ArrayExtension osztálytípus képvi­
sel) használatát vizsgáljuk meg, de tanulmányozunk néhány egyszerű deklara­
tív adatkötést is, és áttekintjük a WPF-erőforrásokat. Az Array jelölő kiterjesztés
lehetővé teszi, hogy adattömböt rendeljünk egy meghatározott tulajdonsághoz.
Amikor a XAML segítségével definiálunk ilyen tömböt, a Type jelölő kiterjesz­
téssei határozzuk meg, hogy milyen típusú tömböt hozunk létre (sztring töm­
böt, bittérkép tömböt stb.). Vizsgáljuk meg a következő <Page> definíciót:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:system;assembly=mscorlib">

<StackPanel>
<Label content ="{x:Array Type corLib:string}"/>
</StackPanel>
</Page>

514
A XAML-szintaxis

Ha a xamlpad_ exe segítségével megtekintjük a rendereit markupot, a megte­


kintési panelben láthatjuk kiírva a system.string [J értéket. A kapcsoszárójel­
szintaxis alkalmazásával nem tudjuk adatokkal feltölteni a tömböt. Ehhez
részelemek segítségével létre kell hoznunk a saját tömbünket, amelynek típu­
sa megegyezik az adott tömb típusával. Vizsgáljuk meg a következő XAML­
definició részletet:

<x:Array Type="CorLib:string">
<CorLib:String>Sun Kil Moon</CorLib:string>
<CorLib:String>Red House Painters</CorLib:string>
<CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>

A példában sztringtömböt hoztunk létre. Az <x:Array> típus hatókörében há­


rom szöveges értéket adunk hozzá, majd lezárjuk a definiciót. Noha ez szabá­
lyos XAML-markup, felmerülhet a kérdés, hogy hová helyezhetjük a tömb­
deklarációnkat? Ha közvetlenül a <Page> elem hatókörébe helyezzük, akkor
implicit módon beállítjuk a <Page> elem content tulajdonságát, és (ismét) lát­
hatjuk a system. String[] értéket a xamlpad.exe megtekintési panelében
(azonban nem ezt akartuk):

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
<!-- Hmm, éppen most állítottuk be a content tulajdonságot! -->
<x:Array Type="CorLib:String">
<CorLib:String>Sun Kil Moon</CorLib:string>
<CorLib:String>Red House Painters</CorLib:string>
<CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
</Page>

Igazából az a célunk, hogy nevet adjunk a tömbnek, majd a markup valamely


más pontján hivatkozzunk rá (például egy List Box feltöltéséhez). Pontosan
ezt tehetjük, ha a tömbünket erőforrás elemben definiáljuk Tisztában kell len­
nünk azzal, hogy a WPF "erőforrások" nem mindig arra képezhetőek le, mint
amit rendszerint elvárnánk (sztringtáblákra, ikonokra, bittérképekre stb.)
A WPF-erőforrások segítségével bármelyik egyedi markupdarabot ábrázol­
hatjuk, például a sztringömbünket is (a WPF-erőforrásokkal kapcsolatban
bővebb információkat a 30. fejezetben találunk).

515
28. fejezet: A WPF és az XAML

Vizsgáljuk meg az utolsó <Page> definíciót, amely a Key jelölő kiterjesztés


segítségével a <StackPanel> elemhez adja a "GoodMusic" sztringtömb-erőfor­
rást. Ezt követően a StaticResource jelölő kiterjesztés segítségével beállítjuk a
ListBox típus rtemssource tulajdonságát a tömbünkre (vegyük észre, hogy

ezúttal ugyanarra a kulcsra hivatkozunk):

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CorLib="clr-namespace:System;assembly=mscorlib">

<StackPanel>
<StackPanel.Resources>
<x:Array Type="CorLib:string" x:Key = "GoodMusic">
<CorLib:String>Sun Kil Moon</CorLib:String>
<CorLib:String>Red House Painters</CorLib:string>
<CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
</StackPanel.Resources>

<Label Content ="Really good music"/>


<ListBox Width = "200"
Itemssource ="{StaticResource GoodMusic}"/>
</StackPanel>
</Page>

Mint látjuk, a <StackPanel> hatókörében beágyazott <StackPanel.Resources>


elemet helyezünk el. Ez az elem biztosít helyet a sztringtömbünk számára.
A stati eResaurce markupbővítők olyan erőforrásokat képviselnek, amelyek
a kezdeti kötést követően nem változnak (innen a "statikus" erőforrás fogal­
ma). Ha olyan erőforrással dolgozunk, amely az első kötés után módosul
(például egy meghatározott rendszerszín), a másik jelölő kiterjesztést, a
Dynami eRes ou rce kiterjesztést használhatjuk A 28.15. ábra a xamlpad.exe

programban látható képet mutatja.

Megjegyzés A Key és a Stati cResource markupbővítőket azért nem minősítettük az x pre·


fixummal (az itt vizsgált többi jelölő kiterjesztésset ellentétben), mert azokat a http: //sche·
mas.microsoft.com/winfx/2006/xaml/presentation gyökér XML-névtérben definiáltuk (mivel
WPF-központúak).

Összefoglalva, eddig megvizsgáltunk több példát, amelyek bemutatták a


XAML-szintaxis alapvető szempontjait. Nem vitás, hogy a XAML rendkívül
érdekes technológia, amely lehetövé teszi, hogy deklaratív módon leírjuk
.NET-objektumok hierarchiáját. Ez rendkívül hasznos a grafikus felhasználói

516
WPF-alkalmazások készítése a Visual Studio 2008 segítségével

felületek konfigurálása során, de ne feledjük, hogy a XAML bármely szerel­


vény bármely típusát leírhatja, ha a típus az alapértelmezett konstruktort ma­
gában foglaló nem absztrakt típus.

Megjegyzés A XAML-t is feldolgozhatjuk futásidőben, erre a fejezet későbbi részében látunk


példát.

3 XamiPad

��id � Refresh : Courier New • 12 • !iiJ EI .,.


Really good music

Sun Kíl Moon


lil:ffli:M•&iimt
Besnard lakes

-----�---- - --�- -. --r• -


--- -- -
� ---- · ·

--
----- ---- -- · ·- - --·-

�t:acjc?e.:lel.•Re�ource;->------
---

<:i:Array T;.{pe="Co::Lib:S:.ri:lg" x:Ke:y = ..Gce-d!..fusic"'>


<Co::L:...b: St.rinq>Sun K:.l !-!con</CorLib:S�ring>
<CcrL:.b: String>Red :!ou se ?aint.e:r.s</CorLib: Str:...ng>
<G�:::"L:.b: St.::ing>5e!lnard Lake:!'I</CorL:.b: St.:::.nq>

lJ
<J:..: :Ar::-ay>

<l StackPa:"el. ?.. es:::�u:rc-es>

<Label Conten:. :t.'!�lly �ccd =.us:t.c.. !>

LJ
= ..

<L:.3t.8cx lfiid'ch =- .. 200" Itel:!.5Sou::ce: ="\Ste.ticRe"ource GcodHusic}"'/>


</St:ack:?anel>
<!?age>

Done. Markup saved to •c;iProgram Files\Microsoft SDKs\Windows\vli.O\bin\XamiPad_Saved.xaml".

28. 15. ábra: Markupbáuítók, statikus erőforrások és egt;szerú adatkötés

A WPF és a XAML-alapszintaxis bevezető áttekintésének végére értünk. A kö­


vetkező fejezet ezen információkra épül, és bemutatja a WPF-elrendezéskezelő­
ket, valamint az általuk tartalmazott vezérlőelemet Azonban mielőtt tovább
haladnánk, térjünk ki a Visual Studio 2008 WPF-projektsablonokra, és vizsgál­
juk meg a Microsoft Expression Blend szerepét

WPF -alkalmazások készitése a Visual


Studio 2008 segitségével

A fejezet során egyszerű szövegszerkesztőkkel, a parancssoros fordítóval és a


xaml pad. exe segítségével készítettük példáinkat Ennek az volt az oka, hogy a

WPF-alkalmazások alapszintaxisát vizsgáltuk, és nem akartuk, hogy egy gra-

517
28. fejezet: A WPF és az XAML

fikus tervező vonzó tulajdonságai eltereljék a figyelmünket Most, hogy lát­


tuk, hogyan lehet nyers WPF-alkalmazásokat készíteni, vizsgáljuk meg, a Vi­
sual Studio 2008 hogyan egyszerüsítheti a WPF-alkalmazások fejlesztését.

WPF -projektsablonok

A Visual Studio 2008 New Project párbeszédablaka WPF-központú projekt­


munkaterületet definiál, amelyek mindegyikét a Visual C# gyökér window
csomópontja foglal magában. Mint azt a 28.16. ábrán láthatjuk, a WPF Appli­
cation, a WPF User Control Library, a WPF Custom Control Library és a WPF
Browser Application (például XBAP) sablonok közül választhatunk

New Project � '� ;cc·� �


-",.,- -,- · -� ·-

froject typ<s: Tomplates: j.NET Ftomeoorl<3.5 y j§�


Visual Basic Visual Studio installed tempiZ!tes
Visual C# �Windows Forms Application f!)Ciassl;brary
Windows
!/:J WPF !\eJ!Ikati� 1\ _:.lWPF Browser Application
W•b
ílJ Console Application
Database
·

�Windows Service
- WPF Custom Controllibrary
ii2J Empty Projoct
Tost
WCF
j � WPF User Controllibrary iilJ Windows Forms Controllibrary
Woricflow � Reports Application
Visual C++ MyTemplat6 -�-·------------------

Other Project Ty-pes �Search Online Templates...


Tost Projocts

Windows Pr6erltation Foundation di�t application (.NET Framewarte 35)

�ame: MyWPFApp

l.ocation: C:\MyCod• . l llrowse...


l
Solution N;)roe; �.1y\'IPFApp l O Cr9te directory for solution
O Add to Soyrc• Control

l OK
ll Concol
l
28.16. ábra: A Visual Studio 2008 WPF projektsablonjai

Amikor WPF asztali alkalmazást készítünk, a WPF Application projekt típust


kell választanunk. Ekkor beállítódnak az egyes WPF-szerelvények (a Presen­
tationcore.dll, a PresentationFoundation.dll és a WindowsBase.dll ) hivat­
kozásai, és a kódfájlok, illetve a XAML-definíciók alapján rendelkezésünkre
bocsátja a studi o a window és az Ap p li cati on típusokból származtatott típuso­
kat (lásd a 28.17. ábrát).

518
WPF·alkalmazások készítése a Visual Studio 2008 segítségével

Solution Explorer

· References
·O PresentationCore
�o PresentationFrameworlc
·O System
·O System.Core
·O System.Data
·O System.Data.DataSetExtensions
· O System.Xml
· O System.Xmi.Linq
�o WindowsBase
8- � App.xaml
j i ':: � App.xaml.cs

i 8-- � •m••
;i ' � Windowl.xaml.cs
·l
!i
Ic,� Solution Explorer
28.17. ábra: A WPF Application projekt típus kezdeti fájljai

A kezdeti ablak nevének módositása

Termékszintű projekt esetén egészen biztosan szeretnénk más nevet adni a


window-leszármazott típusunknak (és a fájlnak, amely a típust definiálja), hogy a
Windowl alapértelmezett név helyett hozzáillöbb nevet kapjon. A WPF-alkal­
mazás számára szükséges összes mozgó részt figyelembe véve, ez jóval bonyo­
lultabb, mint az első pillantásra tűnhet. Íme a folyamat részletes ismertetése.
Ha először a jobb gombbal a kezdeti Windowl.xaml fájl nevére kattintunk,
majd a Rename menüelemet válaszljuk, örömmel láthaljuk, hogy a rendszer a
kapcsolódó Windowsl.xaml.cs fájlt is átnevezte a választásunknak megfelelő­
en (például Mainwindow. xaml-re). Azonban a *. xaml. cs fájlban az osztálytípus
neve továbbra is Windowl marad. Ha a jobb gombbal a *. xaml . cs fájlban az
osztály nevére kattintunk, és kiválasztjuk a Refactor > Rename lehetőséget,
akkor alkalmasabb nevet (MainWindow) választhatunk a típus számára. Ha
ezt követően megpróbáljuk futtatni a programunkat, az eredmény futásidejű
kivétel lesz.
Ennek egyik oka az, hogy a nyitó <Window> elem cl ass attribútuma még
mindig az eredeti wi ndowl osztálynévre hivatkozik, és ezt az új osztálynévnek
megfelelően módosítanunk kell:

519
28. fejezet: A WPF és az XAML

<Window x:
Class="MyWPFApplication.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Windowl" Height="300" Width="300">
<Grid>

</Grid>
</Window>

Továbbá, az <Application> deklarációban a startupuri tulajdonságot szintén


módosítanunk kell, hogy a kezdeti window típust magában foglaló, átnevezett
XAML-fájlt határozza meg (Mainwindow.xaml) .

<Application x:Class="MyWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startupuri="Mainwindow.xaml">

<Application.Resources>

</Application.Resources>
</Application>

Ezt követően már hiba nélkül lefordítha�uk és futtathaljuk az alkalmazásunkat

Megjegyzés Ha új window típusokat illesztünk a WPF·projektbe, a kezdeti fájlunk nevének


használatával a rendszer a megfelelő nevet osztja ki a fájlok és a típusdefiníciók számára, te·
hát nincs szükség további konfigurációra.

A WPF -tervező

A Windows Forms alkalmazásokhoz hasonlóan (lásd a 27. fejezetet) a Visual


Studio 2008 is biztosít egy olyan eszköztárat, amely több WPF-vezérlőelemet,
valamint egy vizuális tervezőt foglal magában. A tervező segítségével össze­
állíthaljuk a felhasználói felületet, a Properties ablakban pedig beállíthaljuk a
kiválasztott vezérlőelem tulajdonságait. A *. xaml fájlok tervezője két panelre
van osztva. A felső panel alapértelmezés szerint a készülő ablak megjelenését
és funkcionalitását muta�a, míg az alsó panelen a XAML-definíció látható
(lásd a 28.18. ábrát).

520
WPF-alkalmazások készítése a Visual Studio 2008 segítségével

WindöWt.x.mll;W-indowl.xoml.csr�:obieStlkow>er)
/
- y x

l
l
. ;··�;c,·....

------

1-·
l
�·

--- ---·

_Cl Des�"__:-� El XAML rD81!1


lJEl <l'iindow x: Class-"MyWPFApp. Windowl"
=

-:'ji
2·-.i\ xmlns="http: //schemas .mic�osoft.
com/win�x/20 0 6/xaml/present r
3i xmlns: x="http: //schemas .mlcrosoft. com/wlnfx/2006/xarol "
4! Title="Windowl" Height="300" Width="300"> IE
5' - <Grid>
6!
T </Grid>
8; </Window>
9i

l litw-..iow��d� l>

28.18. ábra: A WPF-tervező


---
-- --��--. _ __:;______ _�· -�--· ----- ----- .j
d
. •
-

Megjegyzés A vizuális tervező megjelenítési paneljeit az osztott ablak beágyazott gombjai·


val - például a Swap Panes gomb (amelyet a felfeléllefelé nyilak jeleznek), a Horizantal és a
Vertical osztágombek stb. - átrendezhetjük. Szánjunk rá pár percet, és alakítsuk ki a szá­
munkra legkényelmesebb konfigurációt.

Arnikor a XAML-panelben XAML-markupot készítünk, várakozásainknak


megfelelően az IntelliSense rendelkezésünkre áll. Ha például a kezdeti <G ri d>
típus hatókörén belül Button deklarációt kezdünk írni, a képernyőn megjele­
nik azon tulajdonságok és események listája, amelyeket a típus támogat. To­
vábbá, amikor kiválasztunk egy tulajdonság tagot, a 28.19. ábrán látható mó­
don a lehetséges értékek listája jelenik meg.
A Windows Forrnstól eltérően a WPF-alkalmazásokban az események ke­
zelése nem úgy történik, hogy a Properties ablakban a villám ikonnal rendel­
kező gombra kattintunk (valójában ez a gomb a WPF-alkalmazások fejleszté­
se során nem áll rendelkezésünkre).

521
28. fejezet: A WPF és az XAML

/WonciowJ..xaml• tWI!ldo!rlJijl!!l!.a)jij,itpá!Je:tobject11<owserl -��


�l
l l EJ <Window x:Class="MyWPFApp. Windowl"
2 xmlns="http: //schemas. microsoft. com/winfx/200�� � �
1
ll 3
4
xmlns:x="http: //schemas .microsoft .com/winfx/20
Title="Windowl" Height="300" vhdth="300">
1

[ !=l l
5 <Grid>
6i <Button Background="
l 7! � .;fi AliceBlue i
Dii .
.Jil·A�tiq;;;wt;i!;;--
�'

8:
.til' Aqua
</Window> 1
l
iif Aquamarine ;
l .l
9;

l
l _&l Azure
iiflBeige
ilf Bisque
;
.J

1, d' Bla k
c l l
�Jl�� c::�::d :_l
l __ _ ___

l
l Th_�;�l_�ú·��-(/ �' Wonclow Win ow f>
d
-�,J
mi� l
28.19. ábra: A XAML IntelliSense

Amikor a WPF-vezérlőeseményeit kezeljük, a megszakott C#-szintaxis segít­


ségével kézzel megírhatjuk a teljes forráskódot; ha azonban a XAML-szer­
kesztőbe beírjuk egy esemény nevét, azzal aktiváljuk a New Event Handler
előugró menüt (lásd a 28.20. ábrát).

'1
1

ii

3
l
/w..-��;:-=::;��::-=. Wlndowl"
xmlns="http; l/schemas .rnicrosoft . corn/ wwfx/2006/xaml/presentation"
xmlns;x="http;//schemas.rnlcrosoft.com/wjnfx/2006/xaml" (1
- ·-���'l'
J
4 Tltle="Windowl" Helght="300" Width="300">
1

,
s.

61
<Grld>
<Button Background="AllceBlue" Cllck=" l:
L' 1
r
{[uJ<�ewEv�nt�ndler> �}
l 7L
,�GrJ.d>
i
'
8: </Window> ,;,:;;_ ·
l
9 i
l

28.20. ábra: Események kezelése a vizuális tervező segítségével

Ha kézzel írjuk be egy esemény nevét (idézőjelek között - ahogyan a XAML


megköveteli), meghatározhatunk egy tetszőleges metódusnevet. Ha inkább
azt szeretnénk, hogy az IDE alapértelmezett nevet generáljon (amely Vezélő­
Neve_EseményNeve formátumú), kattintsunk kétszer a <New EventHandler>

előugró menüelemre! Mindkét esetben az IDE a forráskódhoz adja a megfele­


lő eseménykezelőt

522
XAML feldolgozása futásidőben: a SimpleXamlPad.exe

private void Button_click(object sender, RoutedEventArgs e)


{
}

Megjegyzés Emlékezzünk rá, hogy ha vezérlőelem típusú tagváltozót szeretnénk definiálni


az IDE-vel, értéket kell rendelnünk a Name t ulajdonsághoz. Ha névtelen vezérlőelemek esemé­
nyeit kezeljük, az eseménykezelő neve egyszerűen VezérlőNeve_EseményNeve{_Sorszám] (pél­
dául Button_cl i ck, Button_cl i ck_l, Button _ cl i ck_2 stb.).

Miután megvizsgáltuk a Visual Studio 2008 alapvető eszközeit, amelyekkel


WPF-alkalmazásokat kezelhetünk, készítsünk példaprogramot az IDE segít­
ségéve!, amely bemutatja a XAML futásidőben megvalósított elemzésének fo­
lyamatát.

XAML feldolgozása futásidőben:


a SimpleXamlPad.exe

A WPF API támogatja a XAML-leírások programozott módon végrehajtott


betöltését, elemzését és mentését. Ez a lehetőség sokszor rendkívül hasznos­
nak bizonyulhat. Tételezzük fel például, hogy öt különböző XAML-fájlunk
van, amelyek egy window típus megjelenését és funkcionalitását írják le. Felté­
ve, hogy minden vezérlőelem (és a szükséges eseménykezelők) neve minden
fájlban megegyezik, az ablakon (az alkalmazásnak átadott indítási argumen­
tum alapján) dinamikusan alkalmazhatunk "grafikus arculatokat" (skineket).
A XAML-lel futásidőben folytatott kommunikáció a xamlReader és a
xamlwriter típusokra épül, amelyek mindegyikét a system.windows.Markup
névtér definiálja. Annak illusztrálására, hogy programozott módon hogyan
nyerhetünk ki egy window objektumot külső *. xaml fájlból, létrehozunk egy
WPF-alkalmazás projektet ( simp l examl Pad néven), amely a fejezet korábbi ré­
szében vizsgált xaml pad. ex e alapműködését utánozza.
Az alkalmazásunk egészen biztosan nem lesz olyan sokoldalú, mint a
xaml pad. exe, de lehetővé teszi, hogy beírhassunk XAML-definíciókat, megte­
kinthessük az eredményeket és a XAML-kódot külső fájlba menthessük. Miu­
tán a Visual Studio 2008 segítségével létrehoztuk a SimpleXamiPad projektet,
a kiindulási ablakot nevezzük át MainWindow-ra (a korábban leírt módon),
és a következőképpen módosítsuk a XAML-definíciót:

523
28. fejezet: A WPF és az XAML

Megjegyzés A következő fejezet részletesen ismerteti a vezérlőelemeket és a paneleket,


most ne nyugtalankodjunk a vezérlőelem-deklarációk részletei miatt.

<Window x:class="SimplexamlPad.Mainwindow"
xmlns="http://schema's.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Simple XAMl viewer" Height="338" width="1041"
Loaded="Window_Loaded" Closed="Window_closed"
windowstartupLocation="Centerscreen">
<DockPanel LastchildFill="True" >

<!-- Ez a gomb elindít egy ablakot a definiált XAML-lel -->


<Button DockPanel.Dock="Top" Name = "btnviewxaml" Width="lOO"
Height="40" Content ="View xaml"
click="btnviewxaml_Click" />

<!-- Ez a terület, ahová gépelhetünk -->


<TextBox AcceptsReturn ="True" Name ="txtXamlData"
Fontsize ="14" Background="Black" Foreground="Yellow"
BorderBrush ="Blue" verticalscrollBarvisibility="Auto"
AcceptsTab="True">
</TextBOX>
</DockPanel>
</Window>

Először is vegyük észre, hogy a kezdeti <G rid> típust <DockPanel> típusra cse­
réltük, amely egy Button (btnViewXaml névvel) és egy TextBox (txtXamlData
néven) típust foglal magában, és a kód kezeli a Button típus kattintási esemé­
nyét. Azt is figyeljük meg, hogy a window Loaded és closed eseményeit a nyitó
<Window> elem kezeli. Ha a tervező segítségével kezeltük eseményeinket, a

következő forráskódot találjuk a Mainwindow. xaml.cs fájlunkban:

public partial class Mainwindow : window


{
public Mainwindow()
{
Initializecomponent(};
}

private void btnviewxaml_click(object sender, RoutedEventArgs e)


{
}

private void window_closed(object sender, EventArgs e)


{
}

private void window_Loaded(object sender, RoutedEventArgs e)


{
}
}

524
XAML feldolgozása futásidöben: a SimpleXamlPad.exe

Mielőtt folytatnánk, feltétlenül importáljuk a Mainwindow.xaml.cs fájlunkba a


következő névtereket

using System.ro;
using System.windows.Markup;

A Loaded esemény megvalósftása

A főablak Loaded eseményének feladata annak meghatározása, hogy az alkal­

mazást tartalmazó könyvtárban pillanatnyilag megtalálható-e a Yourxaml.xaml


fájl. Ha ez a fájl létezik, beolvassuk az adatokat, és a rendszer azokat a főablak­
ban egy TextBoxba helyezi. Ha nem létezik, a TextBoxot egy üres ablak alapér­
telmezett XAML-leírásával töltjük meg (ez a leírás pontosan ugyanaz a makup,
mint egy kezdeti ablakdefiníció, egyetlen különbség, hogy a window Content tu­
lajdonságának [implicit] beállításához a <Grid> helyett a <StackPanel> elemet
alkalmazunk).

Megjegyzés Az XML-névteret képviselő, éppen készülö sztringet nem könnyű feladat beírni a
beágyazott idézőjelekhez szükséges vezérlőkarakterek miatt (gépeljünk körültekintöen!).

private void window_Loaded(object sender, RoutedEventArgs e)


{
ll Az alkalmazás főablakának betöltése során
ll helyezzünk el egyszerű XAML-szöveget a szöveges blokkban!
if (File.Exists(System.Environment.CurrentDirectory +
"\\Yourxaml.xaml"))
{
txtxamlData.Text = File.ReadAllText("Yourxaml.xaml");
}
else
{
txtxamlData.Text
"<Window xmlns=\"http://schemas.microsoft.com"
+"/winfx/2006/xaml/presentation\"\n"
+"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\""
+" Height =\"400\" Width =\"500\"
windowstartupLocation=\"Centerscreen\">\n"
+"<StackPanel>\n"
+"</StackPanel>\n"
+"</Window>";
}
}

525
28. fejezet: A WPF és az XAML

A megközelítés segítségével a simplexamlPad.exe alkalmazás be tudja tölteni


az előző munkamenet során létrehozott XAML-t, vagy szükség szerint meg­
adja a markup alapértelmezett blokkját Ezt követően futtathatjuk a progra­
munkat, és a 28.21. ábrán látható képernyő jelenik meg a TextBoxban.

28.21. ábra: A SimpleXamlPad.exe elsőfuttatása

A Button kattintási eseményének megvalósitása

Amikor a Button-ra kattintunk, először a vourxaml.xaml fájlba mentjük a Text­


Box aktuális adatait. Ekkor a Fi le.open O metódussal beolvassuk a rögzített
adatokat, hogy megszerezzünk egy Stream-leszármazott típust. Erre azért van
szükség, mert a xamlReader. Load() megköveteli, hogy az elemezni kívánt
XAML-t a stream (nem pedig egyszerű system.string) típusból származtatott
típus képviselje.
Ha betöltöttük a létrehozni kívánt <Window> XAML-markupot, a memória­
beli XAML alapján hozzuk létre a system.windows.window egy példányát, és
modális párbeszédablakként jelenítsük meg:

private void btnviewxaml_click(object sender, RoutedEventArgs e)


{
ll A szöveges blokk adatainak kiírása helyi *.xaml fájlba.
File.writeAllText("Yourxaml.xaml", txtxamlData.Text);

ll Ez az ablak dinamikusan XAML-kódolást kap.


window mywindow = null;

ll A helyi *.xaml fájl megnyitása.


try
{
using (Stream sr = File.open("Yourxaml.xaml", FileMode.open))
{
ll A XAML összekapcsolása a window objektummal.
mywindow = (window)xamlReader.Load(sr);
mywindow.showDialog();
}
}

526
XAML feldolgozása futásidőben: a SimpleXamlPad.exe

catch (Exception ex)


{ MessageBox.Show(ex.Message); }
}

Vegyük észre, hogy a logika jelentős részét try/catch blokkba csomagoljuk lly
módon, ha a vourxaml.xaml fájl helytelen markupot tartalmaz, az eredmény­
ként kapott üzenetdobozban láthatjuk a hibákat, amelyeket okoztunk

A Closed esemény megvalósítása

Végül, a window típus closed eseménye biztosítja, hogy a TextBox legfrissebb


adatát rögzítse a vourxaml.xaml fájl:

private void window_closed(object sender, EventArgs e)


{
ll A szöveges blokk adatainak kiírása helyi *.xaml fájlba.
Fi le. w riteAllText(''You rxaml. xaml", txtxamlData. Text);
}

Az alkalmazás tesztelése

Most pedig indítsuk el a programunkat, és írjunk XAML-markupot a szöve­


ges területre. Ne feledjük, hogy ez a program (a xaml pad. exe programhoz ha­
sonlóan) nem teszi lehetövé, hogy bármilyen kódgenerálással kapcsolatos
XAML-attribútumot (például a class attribútumot vagy bármilyen esemény­
kezelőt) meghatározzunk. Tesztelés céljából írjuk be a következő XAML­
markupot a <StackPanel> hatókörébe:

<StackPanel>
<Rectangle Fill = "Green" Height = "40" width = "200" />
<Button Content = "OK!" Height = "40" Width = "100" />
<Label content ="{x:Type Label}" />
</StackPanel>

Ha a gombra kattintunk, feltűnik egy ablak, amely a XAML-definíciókat jele­


níti meg (de lehet, hogy egy üzenetdobozban elemzési hibát kapunk -na­
gyon figyelmesen gépeljünk!). A 28.22. ábra a lehetséges kimenetet mutatja.
Nagyszerű! Egészen biztosan eszünkbe jut több lehetséges javítás az alkal­
mazással kapcsolatban, de ehhez tudnunk kell, hogyan dolgozzunk a WPF­
vezérlőelemekkel és azokkal a panelekkel, amelyekben ezek vannak. Mielőtt a
következő fejezetben megvizsgálnánk a WPF-vezérlőelemmodellt, a fejezetet a
Microsoft Expression Blend alkalmazás gyors áttekintésével zárjuk.

527
28. fejezet: A WPF és az XAML

28.22. ábra: A SimpleXamlPad.exe működés közben

Forráskód A SimpleXamiPad projektet a forráskódkönyvtár 28. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A Microsoft Expression B len d szerepe

Miközben az új technológiák, mint például a XAML és a WPF, tanulmányo­


zása sok fejlesztő számára izgalmas, kevesen lelkesedünk a gondolatért, hogy
ablakok, 3D képek, animációk és más összetevők leírásához több ezer sornyi
markupot készítsünk. Az összetett entitások sokoldalú XAML-leírásának el­
készítése még a Visual Studio 2008 segítségével is fárasztó és hibára hajlamo­
sító folyamat. A Visual Studio 2008 jól felszerelt alkalmazás, amellyel forrás­
kódot készíthetünk, és finomhangolhatjuk a XAML-leírások automatizálását
szolgáló eszközök által generált XAML-definíciókat.
Emlékezzünk rá, hogy a WPF egyik legnagyobb előnye a kapcsolatok elkü­
lönítése. Azonban a WPF nem pusztán a fájlok (például C#-kódfájlok és
XAML-fájlok) szin�én alkalmazza a kapcsolatok elkülönítését. Valójában a

WPF-alkalmazás tiszteletben tar�a a kapcsolatok elkülönítését azon eszközök


szin�én, amelyek segítségével az alkalmazásainkat készí�ük. Ez fontos, mert a
profi WPF-alkalmazások rendszerint megkövetelik, hogy tehetséges grafikus­
művész szolgáltatásait vegyük igénybe az alkalmazás megjelenésének és funk­
cionalitásának megfelelő kialakításához. A nem technikai beállítottságú embe­
rek nem szívesen használják a Visual Studio 2008 alkalmazást XAML írásához.
Ezen problémák megoldására a Microsoft új termékcsaládot készített,
amely az Expression-családba tartozik. Az Expression család tagjainak részle­
tes ismertetőjét a http: // www.microsoft.com/expression webhelyen talál juk;
röviden, az Expression Blend a sokoldalú WPF front end alkalmazások fej­
lesztésére rendezkedett be.

528
A Microsoft Expression Blend szerepe

Az Expression Blend előnyei

Az Expression Blend első fontos előnye, hogy a mód, amellyel a grafikusmű­


vész elkészíti a felhasználói felületet (nem egyezik meg, de) hasonlít más mul­
timédia-alkalmazásokhoz, például az Adobe Photoshop vagy a Macromedia
Director programokhoz. Például az Expression Blend támogat az animációk­
hoz történetkeretek készítésére alkalmas eszközöket, színkeverő segédprogra­
mokat, elrendezési és grafikus átalakító eszközöket és így tovább. Az Ex­
pression Blend továbbá biztosít olyan, a forráskód világához közel álló tulaj­
donságokat, mint az adatkötések létrehozásának támogatása vagy a triggerek.
Ettől függetlenül egy grafikusművész rendkívül sokoldalú felhasználói felüle­
tet készíthet anélkül, hogy akár egyetlen sornyi XAML-markupot vagy C#­
kódot látna. A 28.23. ábrán az Expression Blend működés közben látható.

28.23. ábra: Az Expression Blend a háttérben a Jelhasználó számára láthatatlanul


generálja a XAML-markupot

Az Expression Blend másik fontos előnye, hogy ugyanazokat a projekt-mun­


katerületeket használja, mint a Visual Studio 2008! Ha a grafikusművész
rendereli a felhasználói felületet, a C#-fejlesztő megnyitha�a ugyanazt a pro­
jektet, és belenyúlhat a forráskódba, hozzáadhat eseménykezelőket, finom­
hangolha* a XAML-t és így tovább. A grafikusművész az Expression Blend
eszközzel hasonlóképpen megnyithat létező Visual Studio 2008 WPF-projek-

529
28. fejezet: A WPF és az XAML

teket, és kicsinosíthat egy jellegtelen front endet. Vagyis röviden, a WPF


rendkívül rugalmas próbálkozás a kapcsolódó kódfájlok és a fejlesztőeszkö­
zök együttműködésének támogatására.
Bár az igaz, hogy egy Expression Blendhez hasonló eszköz használatának
ismerete többé-kevésbé kötelező, ha a WPF segítségével élvonalbeli és sokolda­
lú alkalmazásokat készítünk, a könyv jelen kiadása nem foglalkozik ennek
részleteiveL Ezen könyv célja az, hogy megvizsgálja a WPF mögöttes progra­
mozási modelljét, de nem merül el művészetelméleti részletekben. Ha mélyeb­
ben foglalkoztat bennünket a témakör, a Microsoft Expression család tagjainak
próbaverzióját letölthe�ük a támogatási webhelyrőL Érdemes letölteni az
Expression Blend próbaverzióját is, és megtapasztalni, hogy az eszköz mire
képes.

Összefoglalás

A Windows Presentation Foundation (WPF) egy olyan interfész-eszközrend­


szer, amely a .NET 3.0 verziójának megjelenése óta áll rendelkezésünkre.
A WPF fő célja, hogy a korábban egymáshoz nem kapcsolódó asztali technoló­
giákat (2D grafika, 3D grafika, ablak- és vezérlőelem-fejlesztés stb.) összeol­
vassza és egységesítse egyetlen programozási modellben. A WPF-programok
rendszerint a XAML-t (Extensible Application Markup Language - bővíthető
alkalmazás jelölőnyelv) alkalmazzák, amely lehetővé teszi, hogy markup segít­
ségével deklaráljuk WPF-elemeink megjelenését és funkcionalitását.
Mint azt a fejezetben láttuk, a XAML deklaratív szintaxisa segítségével leír­
ha�uk .NET-objektumaink hierarchiáját. A fejezetben a XAML tanulmányozá­
sa során megismerkedtünk a szintaxis újabb elemeivel, például a tulajdonság­
elem szintaxissal, a csatolt tulajdonságokkal, valarnint a típusátalakíták és a
XAML-markupbővítmények szerepéveL A fejezet végén megvizsgáltuk, ho­
gyan tudunk a xamlReader és a xamlwriter típusok segítségével programozott
módon együttműködni a XAML-definíciókkal, bepillantást nyertünk a Visual
Studio 2008 WPF-specifikus funkcióiba, és röviden áttekintettük a Microsoft
Expression Blend szerepét.

530
HUSZONKILENCEDIK FEJEZET

Programozás WPF­
vezérlőelemekkel

Az előző fejezetben megismerkedtünk a WPF programozási modell alapjaival,


beleértve az ablak- és alkalmazástípusok vizsgálatát, valamint a XAML több
jellegzetességét. Itt a WPF-vezérlőelemek csopor�át muta�uk be részletesen, és
bőví�ük jelenlegi ismereteinket. A fejezetet a beépített WPF-vezérlőelemek
vizsgálatával kezdjük, ezt követi majd két jelentős, a vezérlőelemekhez kapcso­
lódó WPF-téma: a függőségi tulajdonságok és a továbbított események.
Ha a központi programozási madellen már túljutottunk, a fejezet további
részeiben több érdekes példával illusztráljuk, hogyan használha�uk a WPF­
vezérlőelemeket alkalmazásainkban. Például megismerjük, hogyan rendsze­
rezzük a vezérlőelemeket a különböző WPF-tárolókban (canvas, G ri d, stack­

Panel, wrapPanel stb.), és hogyan hozzunk létre főablakot menürendszerrel,


állapotsávval és eszköztárraL A fejezet végén megvizsgáljuk a vezérlőutasítá­
sok használatát (amelyekkel beépített viselkedéseket adhatunk a különböző
felhasználóifelületi-elemekhez és bemeneti parancsokhoz), valamint megis­
merkedünk a WPF adatkötési modellel.

Megjegyzés Sok vezérlőelem XAML-definíciója "laza (háttérkód nélküli) XAML·fájtként" sze·


repe! a letölthető forráskódokban. A megjelenített kimenet megtekintéséhez az adott *. xaml
fájlból átmásolhatjuk a markupot a 28. fejezetben létrehozott SimpleXamlPad.exe atkatmazá­
sunkba. Vagy a <Window> és </Window> elemeket átírhatjuk <Page> és </Page> elemekre,
majd a fájtra kétszer rákattintva megtekinthetjük az Internet Explorerben.

A WPF vezérlőelem-könyvtár vizsgálata

Hacsak nem vagyunk teljesen járatlanok a grafikus felhasználói felület készí­


tésében, a WPF-vezérlőelemek beépített készlete nem tartalmaz sok meglepő
dolgot számunkra, függetlenül attól, hogy korábban melyik GUI-eszközrend­
szert használtuk (MFC, Java AWT/Swing, Windows Forms, VB 6.0, Mac OS
X [Cocoa], GTK+/GTK# stb.). A 29.1. táblázatban láthatók a kapcsolódó mű­
ködésük szerint csoportosított alapvető WPF-vezérlőelemek.
29. fejezet : Programozás WPF-vezérlöelemekkel

Alapvető felhasználói Button, R adioButton, Mint elvárható, a WPF a


bemeneti vezérlőelemek comboBox, checkBox, vezérlőelemek egész csa­
Expander, ListBox ládjával rendelkezik a fel­
slider, ToggleButton, használói interfészek leg­
Treeview, bonyolultabb elemeinek
contextMenu, felépítéséhez.
ScrollBar, Slider,

Tabcontrol, TextBox,

RepeatButton,

RichTextBox, Label

Ablakkereteket díszítő Menu, ToolBar, A felhasználói felületek


vezérlőelemek StatusBar, ToolTip, ezen elemei a Window ob­
ProgressBar jektum keretét díszítik
bemeneti eszközökkel
(például Menu típussal) és
felhasználói információs
elemekkel (állapotsáv,
eszközleírás stb.).

Média-vezérlőelemek Image, MediaElement, Ezek támogatják az audio­


soundPlayerAction /videolejátszást és kép­
megjelenítést.

Elrendezést szabályozó Border, canvas, A WPF több olyan vezér­


vezérlőelemek DockPanel, Grid, lőelemet biztosít, amelyek
Gridview, GroupBox, segítségével a többi vezér­
Panel, StackPanel, lőelemet csoportosíthatjuk
viewbox, wrapPanel és rendszerezhetjük a
megfelelőbb elrendezés
érdekében.

29.1. táblázat: Alapvető WPF-vezérlőelemek

A 29.1. táblázatban bemutatott GUI-típusokon túl a WPF további vezérlőelemeket


biztosít speciális dokumentumfeldolgozáshoz (Documentviewer, FlowDocument­

Reader stb.), valamint (a Tablet PC fejlesztésekhez hasznos) Ink API-t támogató tí­
pusokat és különböző rögzített párbeszédablakokat (PasswordBox, PrintDialog,

FileDialog, Open FileDialog és save Fil eDialog) is rendelkezésünkre bocsájt.

Megjegyzés A Fil eDialog, az OpenFi leDi alog és a save Fi l eDial og típusokat a Presen­
tationFramework.dll szerelvény Microsoft. Win32 névtere definiálja.

532
A WPF vezérlőelem-könyvtár vizsgálata

Ha Windows Forros tapasztalatokkal kezdünk a WPF tanulmányozásába, fel­


tűnhet, hogy a beépített vezérlőelemek jelenlegi kínálata valamivel szegénye­
sebb, mint a Windows Forms esetében (például a WPF nem rendelkezik "lép­
tetőgomb" - spin button- vezérlőelemekkel). A jó hír az, hogy sok ilyen hi­
ányzó vezérlőelem gyorsan kifejezhető a XAML segítségéve!, sőt, a projektek
közötti újrafelhasználás érdekében akár még felhasználói vagy egyedi vezér­
lőelemként is modellezhető.

Megjegyzés A könyv jelen kiadása nem tér ki az egyedi WPF felhasználói vezérlőelemekre és
WPF vezérlőelem-könyvtárakra. Ha szeretnénk többet megtudni a témakörről, további infor­
mációért lapozzuk fel a .NET Framework 3.5 SDK dokumentációját.

A WPF -vezérlőelemek és a Visual Studio 200 8

Ha Visual Studio 2008 segítségével hozunk létre új WPF-alkalmazás projektet


(lásd az előző fejezetet), a vezérlőelemek többségét a 29.1. ábrán látható mó­
don (kategóriánként csoportosítva) megtaláljuk a Toolbox menüben.
A Windows Forms projektekhez hasonlóan ezeket a vezérlőelemeket át­
húzha�uk a vizuális tervezőbe, és a Properties ablakban konfigurálha�uk
őket. Továbbá emlékezzünk vissza a 28. fejezetből, hogy ha az eseményeket a
XMAL-szerkesztő segítségével kezeljük, az integrált fejlesztői környezet au­
tomatikusan generálja a megfelelő eseménykezelőt a kódfájlunkban.

l�
Toolbox
r - ·- -- - ......,....,... i
=,
· III Common CA>ntrols
i III CommonCA>ntllinen
;i[EJ MenuundTooiNors ' - 1
11 It Poínter
li' �.l- ContextMenu 1
� Menu t·:
l= StatusBar
� ToolBar
�{l
'
ToolSarTrwy
f
El Al CA>ntrols
It t
@
Poíntor
Button
!'

0 Checlc8ox
l
� ComboBox
l
ll& ComboBoxltom í
Ili!, ContentControl !
00 ContextMenu
�n �


29.1. ábra: A Visual Studio 2008 Toolbox mutatja a beépített WPF-vezérlőelemeket

533
29. fejezet: Programozás WPF·vezérlőelemekkel

A részletek a dokumentációban találhatók

A várakozásainkkal ellentétben ennek a fejezetnek nem az a célja, hogy vé­


gigvezessen minden egyes WPF-vezérlőelem minden egyes tagján. Inkább az
alapvető vezérlőelemekről nyújtunk átfogó képet, különös hangsúlyt fektetve
azokra az alapul szolgáló programozási modellekre (függőségi tulajdonsá­
gok, továbbított események, parancsok stb.) és kulcsfontosságú szolgáltatá­
sokra, amelyek a legtöbb WPF-vezérlőelem esetén azonosak.
Az egyes vezérlőelemek konkrét működésére vonatkozó ismereteinket a
.NET Framework 3.5 SDK dokumentációból mélyíthetjük el - konkrétan a
súgórendszer "Control Library" szakaszából, amely a .NET Framework De­
velopment > Windows Presentation Foundation > Controls útvonalon érhető
el (lásd a 29.2. ábrát).

Contents

j(nofoltor)
l .
i IfJ· Gett.ng Started
j B-- .NET Framework Development
l . 0 Microsoft .NET Framework 3.0 Programming Model
IfJ· .NET Framework Technologies

l : :��� Forms
á- Windows Presentation Foundation
r.)J· Getting Started
$. Application Development
ffi· WPF Fundamentals
ffi. Accessibility
B- Controls

�}-l§§ifi@
0--Border-
�)-- SuiletDecorator
0 -Button
�- Canvas
ffi-- CheckBox
ffi.ComboBox

29.2. ábra: Az egyes WPF-vezérlőelemek részletes leírása csak egtj gombnyomásnyira található (Fl)

Itt megtaláljuk az összes vezérlőelem teljes leírását, különböző mintakódokat


(XAML és C# nyelven), valamint a vezérlőelem származtatási láncára, meg­
valósított interfészeire és alkalmazott attribútumaira vonatkozó információ­
kat. A kis helyreigazítást követően gyorsan tekintsük át a vezérlőelemek dek­
larálását és konfigurálását a XAML-ben, valamint a kapcsolódó C#-kódban
való használatát

534
Vezérlőelemek deklarálása a XAML ben -

Vezérlőelemek deklarálása a XAML-ben

Hosszú évek során a fejlesztőket arra tanították, hogy a vezérlőelemeket vi­


szonylag rögzített és kiszámítható elemekként kezeljék Például a L abel esz­
közöknek mindig szöveges tartalma van, és szinte soha nincs látható keretük
(bár lehetne). A gombok szöveges tartalommal rendelkező szürke téglalapok,
és időnként akár beágyazott képekkel is rendelkezhetnek Amikor egy pro­
jekt azt igényelte, hogy a "standard" eszközöket (például Button típust) egye­
divé tegyük (például kör alakú képként megjelenő Button vezérlőelem), a fej­
lesztök gyakran arra kényszerültek, hogy az egyedi vezérlőelemet külön kó­
dolással állítsák elő.
A WPF alapjaiban változtatja meg a vezérlőelemekről alkotott nézetein­
ket Nemcsak arra van lehetőségünk, hogy a jelölőkkel kifejezzük a vezérlő­
elem megjelenését és használatát, de nagyon sok WPF-vezérlőelem (különö­
sen a contentcontrol leszármazottai) úgy készült, hogy tetszőleges tartalom­
mal rendelkezhessen. Emlékezzünk vissza a 28. fejezetből arra, hogy a con­
tent tulajdonságot beállíthatjuk mind explicit (az elemek nyitó címkéjében,

attribútumként), mind pedig implicit módon, ha a gyökér gyermekelemeként


megadjuk a beágyazott tartalmat.
Tételezzük fel, hogy létrehoztunk egy ControlReview nevű új Visual Studio
2008 WPF alkalmazás projektet
Ahelyett, hogy feltételeznénk, hogy "minden Button vezérlőelem egy
szürke téglalap, amely szöveggel és esetleg képpel rendelkezik", a XMAL se­
gítségéve! a következő implicit tartalmat adhatjuk meg a Button típushoz
(feltételezve, hogy a következőket a kezdeti <Window> elem <Grid> elemén be­
lül deklaráltuk):

<!-- Egyedi gomb beépített választási lehetőségekkel! -->


<Button Name="btnPurchaseOptions" Height="lOO" width = "300">
<StackPanel>
<Label Name="lblrnstructions" Foreground = "oarkGreen"
content = "select Your Options and Press to commit"/>
<StackPanel orientation = "Horizontal">
<Expander Name="colorExpander" Header = "color">
<!-- Tételezzük fel, hogy itt szerepelnek az elemek . . . -->

</Expander>
<Expander Name="MakeExpander" Header = "Make">
<!-- Tételezzük fel, hogy itt szerepelnek az elemek . . . -->

</Expander>
<Expander Name="paymentExpander" Header = "Payment Plan">
<!-- Tételezzük fel, hogy itt szerepelnek az elemek . . . -->

</Expander>

535
29. fejezet: Programozás WPF-vezérlőelemekkel

</StackPanel>
</StackPanel>
</Button>

Vegyük észre, hogy ez a <Button> típus három <Expander> típust tartalmaz


(erre a fejezet későbbi részében térünk ki), amelyeket a <StackPanel> típusok
(szintén a fejezet későbbi részében tárgyaljuk) egy halmazában rendeztünk
el. Mielőtt nagyon belemélyednénk az egyes vezérlők működésébe, nézzük
meg a 29.3. ábrát, amelyen a megjelenített kimenet látható.

Select Your Options and Press to Commit

v Color " Ma1ce �ayment Plan

29.3. ábra: Egt;edi Button vezérlőelem XAML-deklarációja

Egyszerű összehasonlításként nézzük meg, hogy ugyanezt a vezérlőelemet


hogyan építettük volna fel Windows Forms használatávaL Ebben az API-ban
ilyen vezérlőelemet csakis úgy hozhattunk volna létre, ha építünk egy Button
típusból származtatott egyedi típust, amely manuálisan kezeli a grafikus tar­
talmak megjelenítését, módosítja a belső vezérlőelemek gyűjteményét, felülír
bizonyos eseménykezelőket, stb.
Az asztali alkalmazások leíró nyelvének megszületésével az egyedi WPF­
vezérlőelemek létrehozásának egyetlen nyomós oka az, ha egyedi viselkedése­
ket (események, virtuális metódusok felülírása, további interfésztípusok támo­
gatása stb.) támogató vezérlőre van szükségünk, vagy mindenképpen egyedi,
tervezési idejű konfigurációs segédprogramokat kell támogatnunk. Ha csupán
egyedi renderelést létrehozásával kell törődnünk, a XMAL is megteszi.

Együttműködés a vezérlőelemekkel
a forráskódfájlokban

Emlékezzünk vissza az előző fejezetből, hogy a WPF típustulajdonságait attri­


bútumokkal állíthatjuk be az elem nyitó círnkéjén belül (vagy a tulajdonság­
elem szintaxis alkalmazásával). Az esetek többségében a XMAL-elemek attri­
bútumai közvetlenül a vezérlőelem osztályreprezentációjának tulajdonságaira
és eseményeire képezhetők le a system. windows. cont ro ls névtérben. Éppen
ezért mindig lehetőségünk van a vezérlőelemeket csak teljesen a jelölőben,
vagy teljesen a kódban definiálni, esetleg a kettő kombinációját alkalmazni.

536
Vezérlőelemek deklarálása a XAML-ben

Megjegyzés A vezérlőelemhez a kapcsolódó kódfájlban kizárólag akkor van közvetlen hozzá­


térésünk, ha a XMAL-definíció nyitó elemében a Name attribútum segítségével deklaráltuk.

Mivel az előző XAML-jelölés olyan típusokat tartalmaz, amelyekhez hozzá­


rendeltük a Name attribútumot, a típusokhoz közvetlen hozzáférésünk van a
kódfájlban, és szabadon kezelhetünk deklarált eseményeket is. Például a
Label vezérlőelem Fontsize tulajdonságának értékét a következőképpen mó­
dosíthatjuk:

public partial class Mainwindow : system.windows.Window


{
public Mainwindow()
{
Initializecomponent();

ll A Label elem Fontsize tulajdonságának módosítása.


lblinstructions.FontSize = 14;
}
}

Ez azért lehetséges, mert a XMAL-definíciójukban Name attribútummal ren­


delkező vezérlőelemek tagváltozóként szerepeinek az automatikusan gene­
rált*. g.cs fájlban (lásd a 28. fejezetet):

public partial class Mainwindow : system.windows.window,


System.windows.Markup.IComponentconnector
{
IIXAML-markup alapján definiált tagváltozók.
internal System.windows.Controls.Button btnPurchaseoptions;
internal System.Windows.Controls.Label lblinstructions;
internal System.windows.controls.Expander colorExpander;
internal System.windows.Controls.Expander MakeExpander;
internal System.windows.Controls.Expander paymentExpander;

Amikor egy adott vezérlőelem eseményeit kívánjuk kezelni, a XMAL-definí­


cióban a következőképpen rendelhetünk metódusnevet az adott eseményhez:

<Button Name="btnPurchaseOptions"
Click="btnPurchaseOptions_click"
Height="lOO" Width = "300">

<IButton>

537
29. fejezet: Programozás WPF·vezérlőelemekkel

A kapcsolódó kódfájl tartalmazza a metódus definícióját, amelynek formátuma


az alapul szolgáló metódusreferencián alapul (ismét emlékezzünk vissza arra,
hogy a Visual Studio 2008 IDE automatikusan frissíti a kódfájlunkat):

private void btnPurchaseoptions_Click(object sender,


RoutedEventArgs e)
{
MessageBox.show("Button has been clicked");
}

Fontos megjegyezni, hogy az eseményeket nyugodtan kezelhetjük teljes mér­


tékben a forráskódban. Ha például az előző kattintási esemény XAML-definí­
cióját törölnénk, a Window elem konstruktorát a következőképpen módosít­
hatnánk:

public Mainwindow()
{
Initializecomponent();

ll A Label elem Fontsize tulajdonságának módosítása.


lblrnstructions.Fontsize = 14;

ll A gomb kattintási eseményének kezelése.


btnPurchaseoptions.Click +=
new RoutedEventHandler(btnPurchaseoptions_Click);
}

Most, hogy az alapvető vezérlőelem-modell még frissen él emlékezetünkben,


a következő feladatunk, hogy megvizsgáljuk a WPF vezérlőelem-modell két
fontos (de kissé bonyolult) jellemzőjét: a függőségi tulajdonságokat és a to­
vábbított eseményeket. Bár ezeknek a fogalmaknak a részletei a napi WPF
programozási feladataink során általában rejtve maradnak, minél inkább tisz­
tában vagyunk ezekkel az alacsonyszintű részletekkel, a későbbiekben annál
felkészültebben vethetjük bele magunkat a bonyolultabb WPF programozási
feladatokba.

Forráskód A ControlReview kódfájlokat a forráskódkönyvtár 29. fej ezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

538
A függőségi tulajdonságok szerepe

A függőségi tulajdonságok szerepe

Mint sejthető, a Windows Presentation FoundationAPI-k a .NET-típusrend­


szer minden egyes tagját (osztályok, struktúrák, interfészek, metódusreferen­
ciák, felsorolások) és minden egyes lehetséges típustagját (tulajdonságok, me­
tódusok, események, konstans adatok/írásvédett mezők stb.) használják a
megv'alósításaikban.A WPF azonban bevezetett egy új programozási mecha­
nizmust, amelynek neve függőségi tulajdonság.

Megjegyzés A függőségi tulajdonságok WPF-specifikus programozási szerkezetek. A mai na­


pig egyetlen .NET programozási nyelvnek sincs saját szintaxisa a tulajdonságok ezen sajátos tí­
pusának definiálására. Viszont a "propdp" (#-kódrészlet új függőségi tulajdonság vázát fogja
generálni (a kódrészleteket lásd a 2. fejezetben).

A "normális" .NET-tulajdonságokhoz (amelyet a WPF-szakirodalom gyakran


CLR-tulajdonságoknak nevez) hasonlóan a függőségi tulajdonságokat beállít­
ha�uk deklaratív módon XAML segítségéve!, vagy programozott módon a
kódfájlban. Ráadásul létéznek függőségi tulajdonságok (a CLR-tulajdonsá­
gokhoz hasonlóan) adatmezők beágyazására, és konfigurálha�uk őket írás­
védettként, csak írhatóként vagy írható-olvashatóként, és így tovább.
A helyzetet még érdekesebbé teszi, hogy a legtöbb esetben szerencsére fo­
galmunk sincs arról, hogy CLR-tulajdonság helyett tulajdonképpen egy füg­
gőségi tulajdonságot állítottunk be! Például a WPF-vezérlőelemek a Hei ght és
a wi dth tagjaikat a FramewarkElement elemtől, míg a content tagot a cont­
rol content elemtől öröklik - ezek mind függőségi tulajdonságok:

<!-- Éppen most állítunk be három függőségi tulajdonságot! -->

<Button Name = "btnMyButton" Height = ''50" width = "100"


Content = "oK"/>

Ezeket a hasonlóságokat látva meglepőnek találha�uk, hogy a WPF miért ve­


zetett be új fogalmat egy általánosan használt elvre.A válasz a függőségi tu­
lajdonság mögött megbúvó megvalósításban rejlik. Implementálásuk után a
függőségi tulajdonságok számos hatékony szolgáltatást biztosítanak, ame­
lyeket a különböző WPF-technológiák, többek között az adatkötés, az animá­
ciós szolgáltatások, a témák és stílusok stb. használnak. Dióhéjban a függőségi
tulajdonságok - a CLR-tulajdonságoknál megismert egyszerű adatbeágyazá­
san túl - a következő előnyöket nyúj�ák:

539
29. fejezet: Programozás WPF-vezérlőelemekkel

• A függőségi tulajdonságok örökölhetik értéküket a szülőelem XAML­


definíciójából.

• A függőségi elemek támogatják annak lehetőségét, hogy az értékeket


külső típusokkal állítsuk be (emlékezzünk vissza a 28. fejezetből arra,
hogy a csatolt tulajdonságok éppen ezt teszik, mivel a csatolt tulajdon­
ságok a függőségi tulajdonságokra épülnek).

• A függőségi tulajdonságok lehetővé teszik a WPF számára az érték ki­


számítását több külső érték alapján.

• A függőségi tulajdonságok teremtik meg a visszahívási értesítések és


triggerek (elég gyakoriak animációk, stílusok és témák létrehozása so­
rán) számára szükséges infrastruktúrát.

• A függőségi tulajdonságok lehetővé teszik az adataik statikus tárolását


(ezzel csökkenthető a memóriafelhasználás).

A függőségi tulajdonságok egyik legjelentősebb eltérése, hogy lehetővé teszik


a WPF számára az érték kiszámitását több tulajdonságbemenet-értéke alap­
ján. Az egyéb kérdéses tulajdonságok közé tartozhatnak az operációs rend­
szer tulajdonságai (beleértve a rendszerszintű felhasználói beállításokat), az
adatkötésen és az animáción/ forgatókönyv-logikán, az erőforrásokon és a
stílusokon alapuló értékek, vagy más XAML-elemekkel való szülőj gyermek
kapcsolat alapján ismert értékek.
Egy másik jelentős eltérés az, hogy a függőségi tulajdonságokat beállíthat­
juk a tulajdonság értékváltozásának megfigyelésére, és ez alapján külső mű­
veletek végrehajtásának kikényszerítésére. Például egy függőségi tulajdonság
értékének változása kiválthatja azt, hogy a WPF megváltoztassa a Window
objektumban a vezérlőelemek elrendezését, újrakössön külső adatforrások­
hoz, vagy végighaladjon egy egyedi animáció lépésein.

Létező függőségi tulajdonság vizsgálata

A teljes igazsághoz hozzátartozik, hogy elég kicsi annak az esélye, hogy manu­
álisan kelljen függőségi tulajdonságot létrehoznunk WPF-projektjeink során.
Valójában kizárólag akkor kell ezt megtennünk, ha olyan egyedi WPF-vezérlő­
elemet hozunk létre, amelyhez alosztályként már létező vezérlőelemet adunk,
és így akarjuk annak viselkedését módosítani. Ebben az esetben, ha olyan tu­
lajdonságot hozunk létre, amelynek WPF adatkötő motorral, térnamatorral

540
A függőségi tulajdonságok szerepe

vagy animációs motorral kell dolgoznia, vagy, ha a tulajdonságnak üzenetszó­


rással közölnie kell, hogy megváltozott, a függőségi tulajdonság a megfelelő
eszköz. Minden más esetben a normál CLR-tulajdonság is megteszi.
Bár ez teljességgel igaz, azért nem árt tisztában lenni a függőségi tulaj­
dons'ágok alapvető felépítésével, mivel ezáltal a WPF egyes "misztikus" tu­
lajdonságai érthetőbbé válnak, és jobban megismerjük az alapul szolgáló
WPF programozási modellt. A függőségi tulajdonság belső felépítésének be­
mutatására vizsgáljuk meg a következő C#-kódot, amely a FramewarkElement
osztálytípus Height tulajdonságának megvalósítását mutatja be:

public class FramewarkElement : UIElement, IFrameworkinputElement,


IInputElement, ISupportinitialize, IHaveResources
{

ll Figyeljük meg, hogy ez DependencyProperty


ll típusú statikus mező.
public static readonly DependencyProperty HeightProperty;

ll A statikus DependencyProperty mezőt a statikus konstruktorban


ll hozzuk létre és .. regisztráljuk".
static FrameworkElement()
{
HeightProperty = DependencyProperty.Register(
"Height",
typeof(double),
typeof(FrameworkElement),
new FrameworkPropertyMetadata((double) 1.0 l (double) 0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new Propertychangedcallback(
FrameworkElement.OnTransformDirty)),
new validatevaluecallback(
FrameworkElement.IsWidthHeightvalid));
}

ll vegyük észre, hogy a Height tulajdonságnak még vannak get/set


ll blokkjai. viszont a megvalósítás az örökölt
ll GetValue()/Setvalue() metódusokat használja.
public double Height
{
get { return (double) base.Getvalue(HeightProperty); }
set { base.Setvalue(HeightProperty, value); }
}
}

Mint láthatjuk, a függőségi tulajdonságok jóval több kiegészítő logikát köve­


telnek a tipikus CLR-tulajdonságoknál! Lássuk részletesen, hogy mi is történik:
először is a függőségi tulajdonságokat a system.windows.DependencyProperty
osztálytípussal képviseljük, és szinte mindig publikus, statikus, írásvédett me-

541
29. fejezet: Programozás WPF-vezérlőelemekkel

zőként deklaráljuk Ne feledjük, hogy a függőségi tulajdonságok egyik előnye,


hogy nem kötődnek közvetlenül az objektum példányához (ami a memóriata­
karékosságot segíti), ezért használunk statikus adatokat.

A függőségi tulajdonságok regisztrálása

Mivel a függőségi tulajdonságokat statikusarr deklaráljuk, a kezdőértékeiket


a típus statikus konstruktorában kapják meg. Azonban az egyszerű numeri­
kus mezőktől eltérően a DependencyProperty objektum indirekt módon jön
létre a statikus DependencyProperty.Regi ster() metódus visszatérési értéké­
nek rögzítéséveL Ezt a metódust többszörösen túlterheltük; ebben a példában
a Register() metódust a következőképpen hívjuk meg:

HeightProperty = DependencyProperty.Register(
"Height",
typeof(double),
typeof(FrameworkElement),
new FrameworkPropertyMetadata((double) 1.0 l (do u ble) 0.0,
Framework PropertyMetadataoptions.AffectsMeasure,
new Propertychangedcallback(
FrameworkElement.OnTransformDirty)),
new va lidatevalu ecallback(
FrameworkElement.IsWidthHeightvalid));

A Regist er() első argumentuma az osztály azon "normál" CLR-tulajdonsá­


gának neve, amely a DependencyProperty mezőt használja Gelen esetben a
Height) , míg a második argumentum az alap adattípusra vonatkozó típusin­

formáció, amelyhez kötöttük (itt do u ble típusú).


A harmadik argumentum annak az osztálynak a típusinformációját hatá­
rozza meg, amelyhez a tulajdonság tartozik (esetünkben a FrameworkElement) .
Bár ez feleslegesnek tűnhet (hiszen a HeightProperty mezőt már definiáltuk a
FramewarkElement osztályban), ez a WPF nagyon hasznos sajátossága, hiszen
lehetővé teszi, hogy egy típus tulajdonságokat " csatoljon" egy másik típus­
hoz (még akkor is, ha az osztálydefiníció lezárt!).

Megjegyzés Emlékezzünk vissza, hogy a C# 2008 bővít ő metódusai ( lásd a 13. fejezetet) le·
hetövé teszik új tagok hozzáadását a lezárt típusokhoz. A bővítő metódusok jelentik a legköz­
vetlenebb módszert a típusok bővitésére olyan új funkcionalitásokkal, amelyeknek nem kell WPF­
központú szolgáltatásokban részt venniük (pl. animáció).

542
A függőségi tulajdonságok szerepe

A Regi ster() metódusnak átadott utolsó argumentumok azok, amelyek tény­


legesen megadják a függőségi tulajdonságok sajátos ízét. Itt tudjuk megadni
a FrameworkPropertyMetadata objektumot, amely leírja annak részleteit, hogy
a WPF hogyan kezelje a tulajdonságot visszahívási értesítések esetén (ha a tu­
lajdonságnak értesítenie kell másokat az értéke változásakor), az értéket hogy
hitelesítsük, valamint azokat a különféle lehetőségeket is itt adhatjuk meg
(amelyeket a FrameworkPropertyMetadataopti ons felsorolás képvisel), amelyek
azt szabályozzák, hogy a szóban forgó tulajdonság mire van hatással (dolgo­
zik-e adatkötéssel, örökölhető-e, stb.).

Wrapper tulajdonság definiálása


a DependencyProperty mezőhöz

Amint a DependencyProperty objektum konfigurálásának részleteiről gondos­


kadtunk egy statikus konstruktorban, az utolsó feladatunk, hogy a mezőt be­
csomagoljuk egy tipikus CLR-tulajdonságba (esetünkben a Height-ba). Ve­
gyük azért észre, hogy a "get" és a "set" blokkok nem csupán visszaadnak
vagy beállítanak egy osztályszintű double tagváltozót, hanem ezt indirekt
módon teszik a system. windows.Dependencyobj ect alaposztály Getvalue() és
setv al u e() metódusainak alkalmazásával:

public double Height


{
get { return (double) base.Getvalue(HeightProperty); }
set { base.setvalue(HeightProperty, value); }
}

Megjegyzés Az igazat megvallva, nem szükséges burkoló tulajdonságot létrehozni a oepen­


dencyProperty mezőhöz, ha a mező publikus, mivel statikusan is elérhetjük az örökölt Get­
value()/Setvalue() publikus metódusok hívása során. A gyakorlatban viszont a legtöbb füg­
gőségi tulajdonság rendelkezik barátságos burkolóval, mivel ez nagyon XAML-barát.

Most, hogy már láttuk, hogy a függőségi tulajdonságok hogyan épülnek fel a
felszín alatt, tartsuk észben, hogy teljességgel lehetséges normál CLR-tulaj­
donságok használata, amelyek ugyanazokat a szolgáltatásokat támogatják,
mint a WPF függőségi tulajdonságok (értesítések, statikus memóriafoglalás
stb.). Viszont ehhez nem kevés háttérkódolás szükséges, amelyet manuálisan
kell létrehoznunk, és sok helyen megismételnünk. A beépített DependencyPro­
perty típus (és további infrastruktúradarabok) alkalmazásával ugyanezeknek
a szolgáltatásoknak a készen elérhető megvalósítását kapjuk.

543
29. fejezet: Programozás WPF-vezérlőelemekkel

Mivel a függőségi tulajdonságok különböző WPF-központú típusok fel­


használásával állnak elő, egyértelműen lehetőségünk van saját függőségi tu­
lajdonságaink létrehozására - bár erre nem lesz szükség a könyvben található
példákhoz. A következő kód összefoglalja a függőségi tulajdonságok dekla­
rációjának központi elemeit (itt a tulajdonságot a statikus írásvédett Depen­
dencyProperty típus deklarálása során regisztráljuk):

public class MyOwnerclass : Dependencyobject


{
ll A DependencyProperty használata a MyProperty háttértáraként.
ll Ez lehetővé teszi az animációt, a stílus alkalmazását,
ll a kötést stb ...
public static readonly DependencyProperty MyPropertyProperty =

DependencyProperty.Register("MyProperty", typeof(int),
typeof(ownerclass), new UIPropertyMetadata(O));

ll XAML-barát burkoló a statikus írásvédett mezőhöz.


ll Ez azért szükséges, mert nem tudunk metódusokat
ll (Getvaluelsetvalue) hívni XAML-ben.
public int MyProperty
{
ll A Getvaluelsetvalue a Dependencyobject
ll alaposztályból származik.
get { return (int)Getvalue(MyPropertyProperty); }
set { setvalue(MyPropertyProperty, value); }
}
}

Ha további részleteket szeretnénk erről a WPF programozási szerkezetről


megtudni, olvassuk el a .NET Framework 3.5 SDK dokumentáció "Custom
Dependency Properties" témakörét.

Továbbitott események

Nem a tulajdonságok az egyetlen .NET programozási szerkezetek, amelyek­


nek egy kis frissítésre volt szükségük, hogy megfelelően működjenek a WPF
API-val. A szabványos CLR-eseménymodell is finomodott egy kicsit, hogy
biztosítsa az események feldolgozhatóságát oly módon, hogy az megfelelő
legyen egy objektumfa XAML-leírása számára. Tételezzük fel, hogy létrehoz­
tuk a WPFControlEvents nevű új WPF-alkalmazás projektet. Most frissítsük a
kezdő ablak kezdeti XAML-leírását a következő <Button> típus hozzáadásá­
val a kezdeti <Gri d> típushoz:

544
Továbbított események

<Button Name="btnclickMe" Height="75" width "250"


click ="btnclickMe_clicked">
<StackPanel orientation ="Horizontal">
<Label Height="50" Fontsize ="20">Fancy Button!<ILabel>
<Canvas Height ="50" Width ="100" >
<Ellipse Name = "outerEllipse" Fill ="Green" Height ="25"
width ="50" cursor="Hand" canvas.Left="25"
canvas.Top="12"1>
<Ellipse Name = "innerEllipse" Fill ="Yellow" Height = "15"
Width ="36" canvas.Top="17" canvas.Left="32"1>
<ICanvas>
<IStackPanel>
<IButton>

Figyeljük me� hogy a <Button> esemény nyitó definíciójában úgy kezeltük a


click eseményt, hogy megadtuk a metódus nevét, mely akkor fut le, ha az ese­
mény bekövetkezik. A kattintási esemény a RoutedEventHandler metódusreferen­
ciával működik, amely olyan eseménykezelőt vár, amely objektumot vesz fel el­
ső paraméterként, és system.windows.RoutedEventArgs típust másodikként:

public void btnclickMe_Clicked(object sender, RoutedEventArgs e)


{
ll csináljunk valamit, ha a felhasználó a gombra kattint.
MessageBox.show("clicked the button");
}

A 29.4. ábra mutatja az elvárt kimenetet, amikor rákattintunk a jelenlegi ve­


zérlőelemre (megjelenítési okok miatt módosítottam a kezdeti <Gri d> típust
<StackPanel> típusra, ami megmagyarázza, hogy a gomb miért az ablak tete­
jének közepén van, ahelyett, hogy az ablak közepén helyezkedne el).

;&.J MyWPFControlsApp lol©l �

Fancy Button!
c::>-
------

..

Clic ked the button l

B
l OK
l

29 .4. ábra: Események kezelése egy összetett Button típus esetén

545
29. fejezet: Programozás WPF·vezérlőelemekkel

Most vizsgáljuk meg a Button típusunk jelenlegi összetételét. Több beágyazott


elemet tartalmaz, a felhasználói felületének megjelenítéséhez (canvas, Ell i pse,
Label stb.). Képzeljük el, hogy a WPF-eseménykezelés milyen unalmas lenne,

ha arra kényszerülnénk, hogy kezeljünk egy kattintási eseményt illindezen


részelem esetében. Végül is a végfelhasználó bárhova kattinthat a gomb hatá­
rainak hatókörén belül (a Label objektumra, az ovális zöld területre, a gomb
felszínére stb.). Nemcsak munkaigényes lenne, ha a Button típus minden as­
pektusához külön eseménykezelőt hoznánk létre, hanem olyan utálatos kódot
kapnánk, amelyet nehéz lenne karbantartani a jövőben.
A Windows Forms eseménymodellben az ehhez hasonló egyedi vezérlő­
elem megkövetemé tőlünk, hogy a gomb minden eleme számára létrehozzuk
a kattintási eseményt. Szerencsére a WPF továbbított eseményei automatikusan
gondját viselik ennek. A továbbított események automatikusan továbbítanak
egy eseményt felfelé (vagy lefelé) az objektumfán, megfelelő kezelőt keresve.
"
Konkrétabban, a továbbított események három "bejárási stratégiát hasz­
nálhatnak. Ha egy esemény a kezdőponttól fölfelé mozog az objektumfán, ak­
kor az eseményt buborékeseménynek nevezzük. Ezzel ellentétben, ha egy ese­
mény a kezdőponttól lefelé halad a kapcsolódó részelemek felé, akkor lefutó
esemény. Végül, ha egy eseményt csak az eredeti elem váltja ki és kezeli (amit
normális CLR-eseményként jellemezhetünk), akkor a neve közvetlen esemény.

Megjegyzés A függőségi tulajdonságokhoz hasonlóan a továbbított események a WPF-specifi·


kus segédtípusok használatával megvalósított WPF-specifikus szerkezetek. Így nincs olyan spe·
ciális C#·szintaxis, amelyet a továbbított események kezeléséhez el kellene sajátítanunk.

A továbbitott buborékesemények szerepe

Ha a jelenlegi példában a felhasználó rákattint a belső sárga oválisra, a kattintási


"
esemény "kibuggyan a következő hatókör szintjére (canvas) , majd a stackPanel,
végül a Button típushoz, ahol a kattintási eseménykezelője található. Ehhez ha­
sonlóan, ha a felhasználó rákattint a Label objektumra, az esemény "kibuggyan"
a StackPanel, majd a Button típushoz.
A továbbított buborék eseménymintának köszönhetően nem kell foglalkoz­
nunk azzal, hogy egy összetett vezérlőelem minden tagjához regisztráljunk bi­
zonyos kattintási eseménykezelőket Azonban, ha egyedi kattintási logikát sze­
retnénk végrehajtani több elemen ugyanazon az objektumfán belül, ezt megte­
hetjük Példaként tételezzük fel, hogy az outerElli p se vezérlőelem kattintását

546
Továbbított események

egyedi módon szeretnénk kezelni. Először kezeljük a Mouseoown eseményt ezen


részelem esetében (a grafikusan rendereit tipusok, mint az Ellipse nem támo­
ga�ák a "kattintás" eseményt, azonban figyelhetik az egér gombjának tevé­
kenységét a Mouseoown, a Mouseup stb. események révén):

<Button Name="btnclickMe" Height="75" width = "250"


click ="btnclickMe_clicked">
<StackPanel orientation ="Horizontal">
<Label Height="50" Fontsize ="20">Fancy Button!</Label>
<Canvas Height ="50" width ="100" >
<Ellipse Name = "outerEllipse" Fill ="Green"
Height ="25" Mouseoown ="outerEllipse-...Mouseoown"
width ="50" cursor="Hand" Canvas.Left="25"
canvas.Top="12"/>
<Ellipse Name = "innerEllipse" Fill ="Yellow" Height = "15"
Width ="36" canvas.Top="17" canvas.Left="32"/>
<ICanvas>
</StackPanel>
</Button>

Ezután implementálunk egy megfelelő eseménykezelőt, amely szemléltetés­


képpen egyszerűen megváltozta�a a főablak Title tulajdonságát:

public void outerEllipse_MouseDown(object sender, RoutedEventArgs e)


{
ll Az ablak címének megváltoztatása.
this.Title = "vou clicked the outer ellipse!";
}

Ezzel most már különböző műveletek történnek attól függően, hogy a vég­
felhasználó hova kattintott (ami lényegében a külső ellipszis és a gomb ható­
körén belül minden más hely).

Megjegyzés A továbbított buborékesemények mindig a kezdőponttól a következő tartalmazó


hatókör felé mozognak. Így, ha ebben a példában az i nnerElli pse objektum ra kattintanánk,
akkor az esemény a canvas felé tartana, nem pedig az outerEllipse objektum felé, ugyanis
mindkét Ell i p se típus a canvas hatókörében van.

A buborékesemények folytatása és leátlitása

Jelenleg, ha a felhasználó az outerEllipse objektumra kattint, kivál�a az


Ellipse típushoz regisztrált Mouseoown eseménykezelőt, amikor a buboréklo­

gika megáll (ezért nem látha�uk, hogy a rendszer végrehaj�a a Button típus
kattintási eseménykezelőjét). Legtöbbször ez a kívánt hatás; azonban, ha arra

547
29. fejezet: Programozás WPF-vezérlőelemekkel

szeretnénk utasítani a WPF-et, hogy folytassa az esemény mozgatását fölfelé


az objektumfán, beállíthatjuk a RountedEventArgs típus Handl ed tulajdonságá­
nak értékét false-ra:

public void outerEllipse_MouseDown(object sender, RoutedEventArgs e)


{
ll Az ablak címének megváltoztatása.
this.Title = "vou clicked the outer ellipse!";

ll A továbbítás ("bugyborékolás") folytatódik!


e.Handled = false;
}

Ebben az esetben láthatjuk, hogy az ablak címe megváltozik, amit a Message­


Box megjelemtése követ a Button típus kattintási eseménykezelőjén belül. Rö­
viden, a továbbított buborékesemények lehetövé teszik, hogy egy összetett
tartalomcsoport akár egyetlen logikai elemként (pl. egy Button elemként),
akár külön elemként (pl. egy Ell ipse a Button típuson belül) szerepeljen.

A továbbitott lefutó események szerepe

A továbbított események tulajdonképpen buborék (ahogy az előbb leírtuk)

vagy lefutó jellegűek lehetnek. A lefutó események (amelyek mind a Preview


előtaggal kezdődnek, pl. Previ ewMouseDown) a kezdőelemtől az objektumfában
levő belsőbb hatákörök felé haladnak. Nagyjából a WPF alaposztály-könyv­
tárakban lévő összes buborékeseménynek van kapcsolódó lefutó esemény
párja, amely a buborék megfelelő előtt indul el. Például mielőtt a MouseDown
buborékeseményt a rendszer kiváltaná, először a Previ ewMouseoown lefutó
esemény következik be.
Egy lefutó esemény kezelése pont ugyanúgy néz ki, mint bármilyen más
esemény kezelésének folyamata; egyszerűen rendeljük hozzá az eseménykeze­
lő nevét XAML-ben (vagy, ha kell, használjuk a megfelelő C# eseménykezelő­
szintaxist a kódfájlban), majd implementáljuk a kezelőt a kódfájlban. A lefutó
és buborékesemények kölcsönös kapcsolatát szemléltessük a Previ ewMouseDown
esemény kezelésével az outerEllipse objektum esetében:

<Ellipse Name = "outerEllipse" Fill ="Green" Height ="25"


Mouseoown ="outerEllipse_MouseDown"
PrevieWMouseoown ="outerEllipse_PrevieWMouseoown"
width ="50" Cursor="Hand" Canvas.Left="25"
Canvas.Top="12"1>

548
Továbbított események

Ezután tervezzük át az aktuális C#-osztálydefiníciót az eseménykezelők mó­


dosításával (minden típusnál), hogy információt fűzzenek egy sztringhez, és
végül megjelenítsék az értékét Ez lehetővé teszi, hogy megvizsgáljuk a hát­
térben kiváltott események folyamatát:

public partial class Mainwindow : system.windows.window


{
ll Ezt használjuk arra, hogy tároljuk az egérhez kapcsolódó
ll tevékenység adatait.
string mouseActivity = string.Empty;

public Mainwindow()
{
Initializecomponent();
}

public void btnClickMe_Clicked(object sender, RoutedEventArgs e)

� A végsősztring megjelenítése.
mouseActivity
+= "Button click event fired!\n";

MessageBox.Show(mouseActivity);

ll A sztring törlése a következőteszthez.


mouseActivity = string.Empty;
}

public void outerEllipse_Mouseoown(object sender,


RoutedEventArgs e)
{
ll Adat hozzáadása a sztringhez.
mouseActivity += "MouseDown event fired!\n";

ll A "bugyborékolás" folytatódik!
e.Handled = false;
}

public void outerEllipse_PreviewMouseDown(object sender,


RoutedEventArgs e)
{
ll Adat hozzáadása a sztringhez.
mouseActivity = "PreviewMouseDown event fired!\n";

ll A "bugyborékolás" folytatódik!
e.Handled = false;
}
}

549
29. fejezet: Programozás WPF-vezérlőelemekkel

Ha futta�uk a programot, és nem kattintunk sehova a külső ellipszis határain


belül, akkor egyszerűen a " Button Click event fired!" üzenet jelenik meg az
üzenetdobozban. Azonban, ha a külső ellipszisképen belülre kattintunk, ak­
kor a 29.5. ábrán látható üzenetdoboz jelenik meg.

Preví�Mouse:Oown event fired!


MouseDown event fired!
Button Cikk event fi red!

OK

29.5. ábra: Először a lefutó, majd a bubore7cesemény

Elgondolkozhatunk, hogy vajon miért hajlamosak párban járni a WPF-esemé­


nyek (egy lefutó és egy buborékesemény). A válasz az, hogy az események
előnézetével lehetőségünk van bármilyen speciális logika (adatellenőrzés,
buborékesemény letiltása stb.) végrehajtására, mielőtt a buborékrész elindulna.
Az esetek nagy többségében nem lesz szükségünk a Preview prefixumos lefutó
események kezelésére, egyedül csak a (nem Preview prefixumos) buborék
eseményekkel kell törődnünk.
A függőségi tulajdonság kézi megírásának feladatához hasonlóan, a lefutó
események kezelése általában csak akkor szükséges, ha leszármaztatunk egy
már meglévő WPF-vezérlőelemet. Ehhez kapcsolódik, hogy ha egyedi WPF­
vezérlőelemet készítünk, akkor létrehozhatunk egyedi továbbított esemé­
nyeket (akár buborék-, akár lefutó eseményeket) egy olyan technika haszná­
latával, amely hasonlít egyedi függőségi tulajdonság készítéséhez. További
az

részletekért forduljunk a "How to: Create a Custom Routed Event" témakör­


höz a .NET Framework 3.5 SDK dokumentációjában.

Forráskód A WPFControlEvents kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

550
A Button típusok használata

A Button tipusok használata

Most, hogy áttekintettük a függőségi tulajdonságok és a továbbított esemé­


nyek részleteit, abban a helyzetben vagyunk, hogy jobban megérthetjük ma­
gukat a WPF-vezérlőelemeket, kezdve a gombtípusokkaL Ösztönösen tisztá­
ban lehetünk a gombtípusok szerepével. Ezek a felhasználói felület olyan
elemei, amelyeket megnyomhatunk az egér vagy a billentyűzet révén (az En­
ter vagy a szóköz billentyűvel), amikor ezek aktívak. A WPF-ben a Button­
Base szülőosztályként szolgál a három alapvető leszármazott típus ( sutton,

RepeatButton és Toggl eButton) számára.

A ButtonBase tipus

Mint minden szülőosztály, a ButtonBase egy polimorf interfészt biztosít a le­


származott típusok számára (az alaposztályából, a contentcontrol osztályból
örökölt tagok mellett). Például a ButtonBase definiálja a kattintási eseményt
is. Emellett ez a szülőosztály határozza meg az IsPressed tulajdonságot,
amellyel végrehajthatunk egy műveletsort, ha a leszármazott típust meg­
nyomták, de még nem engedték fel. Ezenkívül a 29.2. táblázat bemutatja a
ButtonBase absztrakt ősosztály néhány további érdekes tagját.

clickMode A cl i ckMode felsorolás értékeinek alapján ezzel a tulaj­


donsággal határozhatjuk meg, hogy mikor tüzeljen a kat­
tintási esemény.

command Ahogy a fejezet későbbi részében szó lesz róla, a felhasz­


nálói felület több elemének van társított "parancsa",
amelyet a command tulajdonság beállításával rendelhe­
tünk hozzá.

commandParameter Ezzel a tulajdonsággal paramétereket adhatunk át a


command tulajdonság által megadott elemnek.

commandTarget Ez a tulajdonság lehetővé teszi, hogy meghatározzuk a


command tulajdonság által megadott utasítás fogadóját

29.2. táblázat: A ButtonBase típus tagjai

551
29. fejezet: Programozás WPF·vezérlőelemekkel

Az utasításközpontú tagok mellett (amelyeket a fejezet végén vizsgálunk


meg) a legérdekesebb tag talán a clickMode, amellyel megadhatjuk egy
gombra kattintás három különböző módját. Ehhez a tulajdonsághoz a kap­
csolódó system. windows. controls. cli ckMode felsorolásból bármilyen értéket
hozzárendelhetünk

public enum clickMode


{
Release,
Press,
Hover
}

Tételezzük fel például, hogy a következő XAML-kóddal írunk le egy Button tí­
pust, melynek a clickMode tulajdonságát clickMode. Haver értékűre állitottuk

<Button Name = "bntHoverclick" ClickMode = "Haver"


click ="btnHoverclick_Click"/>

Ezáltal a kattintási esemény azonnal elindul, amint az egérkurzor bekerül a


Button típus határain belülre. Noha lehet, hogy nem ez a legszerencsésebb el­

járás egy tipikus nyomógomb esetében, a "lebegés" (hover) mód hasznos le­
het egyedi stílusok, sablonok vagy animációk készítésekor.

A Button tipus

Az első leszármazott típus, a Button, két azonnali érdeklődésre számot tartó tu­
lajdonságot biztosít számunkra: az rscancel és az rsoefault tulajdonságot,
amelyek nagyon hasznosak, ha OK és Mégse gombokat tartalmazó párbeszéd­
ablakokat hozunk létre. Amikor az rscancel értéke true, a gomb kattintási
eseménye elsül, ha a felhasználó megnyomja az Esc billentyűt. Ha az rsoefault
értéke true, a gomb kattintási eseménye elsül, ha a felhasználó megnyomja az
Enter billentyűt. Tekintsük meg a két Button típus következő XAML-leírását:

<!-- Tételezzük fel, hogy ezeket egy Window típus <StackPanel>


osztályán belül definiáljuk >
--

<Button Name ="btnOK" IsDefault = "true" click ="btnOK_Click"


content = "OK"/>
<Button Name ="btncancel" rscancel= "true"
click ="btncancel_Click" content = "cancel"/>

552
A Button típusok használata

Ha implementálnánk a deklarált eseménykezelők mindegyikét a kapcsolódó


kódfájlban, akkor futtathatnánk az alkalmazást, és ellenőrizhetnénk, hogy az
Enter vagy az Esc billentyű megnyomásakor a megfelelő eseménykezelőt
hívja-e a rendszer. Ez lenne a helyzet akkor is, ha az ablak egy másik felhasz­
nálóifelület-eleme (pl. egy szövegbeviteli mező) lenne aktív.

A ToggleButton tipus

Alapértelmezés szerint a ToggleButton típus (amelyet a system. windows.Cont­


rols. Primiti ves névtérben definiálunk) rendelkezik egy olyan felhasználói
felülettel, amely hasonló a Button típushoz; azonban egyedi képessége, hogy
megőrzi lenyomva tartott állapotát, ha a felhasználó rákattint. Ennek érdeké­
ben a Toggle Button biztosítja az rschecked tulajdonságot, amely váltogat a
true és a fal se érték között, amikor a végfelhasználó rákattint a felhasználói
felület elemére. Továbbá a ToggleButton két olyan eseményről gondoskodik
(checked és unchecked) , amelyekkel kezelhető az állapotváltozás elfogása. Íme,
egy olyan egyszerű kapcsaló XAML-leírása, amely két egyedi eseménykeze­
lőn kezeli az eseményeket:

<!-- Egy Igen/Nem kapcsalógomb -->

<ToggleButton Name ="toggleonoffButton"


Checked ="toggleonoffButton_checked"
unchecked ="toggleonoffButton_unchecked">
Off!
</ToggleButton >

Az eseménykezelők egyszerűen odaillő szöveges üzenettel módosítják a con­


tent tulajdonságot:

protected void toggleonoffButton_Checked(object sender,


RoutedEventArgs e)
{
toggleonoffButton.content = "On!";
}

protected void toggleonoffButton_unchecked(object sender,


RoutedEventArgs e)
{
toggleonoffButton.content = "off!";
}

553
29. fejezet: Programozás WPF·vezérlőelemekkel

Ha szeretnénk tömöríteni a mögöttes kódfájlunkat, hogy egyetlen esemény­


kezelőt használjon minden egyes eseményhez, módosíthatjuk a XMAL-defi­
níciót, hogy mind a checked, mind az unchecked esemény ugyanarra a kezelő­
re mutasson (vagyis a toggleonoff ButtonPressed eseménykezelőre), majd
használjuk az Ischecked tulajdonságot, hogy váltagassuk az üzenetet:

protected void toggleonoffButtonPressed(object sender,


RoutedEventArgs e)
{
if (toggleonoffButton.Ischecked == false )
toggleonoffButton.Content "off!";
else
toggleonoffButton.content "on!";
}

Végül ne feledjük, hogy a ToggleButton támogatja a háromállapotú működést


(az IsThreestate tulajdonság és az Indetermi nate esemény révén), és lehető­
vé teszi annak ellenőrzését, hogy a vezérlő bekapcsolt, nem bekapcsolt vagy
egyik sem. Noha furcsának tűnhet, hogy egy gomb ilyen módon figyelje ön­
magát, azonban értelmet nyer a dolog azon típusok esetében, amelyek a
ToggleButton típusból származnak, mint például a hamarosan bemutatásra

kerülő checkBox típus.

Megjegyzés Általános szabályként elmondható, a system.windows.control s. Primi tives


névtérben definiált típusok (beleértve a Toggl e Button típust) további testreszabás nélkül
nem számítanak azonnal hasznosnak.

A RepeatButton tipus

Az utolsó tárgyalt, ButtanBase-leszármazott típus a RepeatButton típus, ame­


lyet szintén a system.windows.controls.Primi ti ves névtérben definiálunk. Ez
a típus alapértelmezés szerint szintén hasonlít a standard Button típusra,
azonban megvan az a képessége, hogy folyamatosan elindítja saját kattintási
eseményét, arnikor a végfelhasználó megnyomott állapotban tartja a vezérlőt.
A kattintási esemény elindításának gyakorisága függ a oelay és az Interval
tulajdonságokhoz hozzárendelt értékektől (amelyek mindegyike ezredmá­
sodpercben rögzített).
A RepeatButton típus (a ToggleButton típushoz hasonlóan) önmagában va­
lójában nem túl hasznos. Azonban a feltárt viselkedés hasznos lehet testre sza­
bott felhasználói felületek létrehozásakor. Vegyük figyelembe a tényt, hogy - a

554
A Button típusok használata

Windows Forrnsszal ellentétben - a WPF kezdeti kiadása nem biztosít léptető­


gomb-vezérlőelemet, amely lehetövé teszi a felhasználó számára, hogy a fel- és
lefelé nyilak segítségével állítson be numerikus értéket. A RepeatButton funkci­
onalitásának köszönhetően viszonylag könnyedén elkészíthetünk egy léptető­
gomb-vezérlót XAML-ben.
Ennek bemutatásához hozzunk létre új Visual Studio WPF-alkalmazás
projektet CustomSpinButtonApp néven. Cseréljük le a kezdeti <Grid> definí­
ciót olyan <StackPanel> típussal, amely az alábbi markupot tartalmazza:

.<Window x:class="CustomspinButtonApp.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CustomSpinButtonApp" Height="300" width="300">
<StackPanel>
<!-- A "Fel" gomb -->
<RepeatButton Height ="25" Width = "25"
Name ="repeatAddvalueButton"
Delay ="200" Interval ="l"
click ="repeatAddvalueButton_click"
content = "+"/>

<!-- Megjeleníti az aktuális értéket -->


<Label Name ="lblcurrentvalue" Background ="LightGray"
Height ="30" width = "25"
verticalcontentAlignment="Center"
HorizontalcontentAlignment="Center" Fontsize="l5"/>

<!-- A "Le" gomb -->


<RepeatButton Height ="25" Width = "25"
Name ="repeatRemovevalueButton"
Delay ="200" Interval ="l"
click ="repeatRemovevalueButton_Click"
content = "-"/>
</StackPanel>
</Window>

Vegyük észre, hogy az egyes RepeatButton típusok egyedi eseménykezelővel


hogyan kezelik a kattintási eseményt. Ezzel a következő C#-logikát írha�uk
le, hogy növeljük vagy csökkentsük a <Label> címkében megjelenített értéket
(nyugodtan adjunk hozzá további logikát, ha szeretnénk rögzíteni a maximá­
lis és minimális értéket):

public partial class Mainwindow : System.windows.window


{
private int currvalue = O;

555
29. fejezet: Programozás WPF-vezérlőelemekkel

public Mainwindow()
{
Initializecomponent();
lblcurrentvalue.Content = currvalue;
}
protected void repeatAddvalueButton_click(object sender,
RoutedEventArgs e)
{
ll Hozzáad egyet az aktuális értékhez, és megmutatja a címkében.
currvalue++;
lblcurrentvalue.Content = currvalue;
}

protected void re p eatR emoveval ueButt on_cl i c k(o bject sen d e r ,


Ro utedEventArgs e)
{
ll Kivon egyet az aktuális értékből, és megmutatja a címkében.
currvalue--;
lblcur r entvalu e . c ontent = currvalue;
}
}

Ahogyan láthatjuk, amikor a felhasználó rákattint valamelyik RepeatButton


gombra, annak megfelelően növeljük vagy csökkentjük a privát eurrvalu e ér­
téket, és beállítjuk a Label típus content tulajdonságát. A 29.6. ábra mutatja
az egyedi léptetőgombunkat működés közben.

··- CustomSpinButtonApp l o l @J ilíoi:l.ol


l
l o
8
ll
29.6. ábra: Léptetőgomb készítése, kezdőpontként a RepeatButton használatával

Forráskód A CustomSpinButtonApp kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A jelölőnégyzetek és a rádiógembek
használata
"
Ahogy korábban említettük, a CheckBox "az-egy ToggleButton, "az-egy
ButtonBase, ami elég különösnek tűnhet, mivel egy gomb felhasználói felülete
meglehetősen különbözik egy jelölőnégyzetétőL Azonban, ha a ch e ckB ox tí-

556
A jelölőnégyzetek és a rádiógombok használata

pusra (mint a Button típusra) rákattinthatunk, reagál az egér- és billentyűzet­


bevitelre, valamint követi a WPF-tartalomrnodellt. A hasonlatosságoknak kö­
szönhetően kiderül, hogy a checkBox típus egyszerűen felülírja a Toggl e­
Button különböző virtuális tagjait, hogy létrehozza a jelölőnégyzet megjele­
nését és működését Gusson eszünkbe, hogy a WPF fő mozgatórugója az,
hogy elválasszuk egy vezérlőelem megjelenését annak funkcionalitásától).
Tekintsük át a következő <CheckBox> deklarációkat, amelyek a 29.7. ábrán
látható kimenetet eredményezik:

<StackPanel>
<!-- checkBox típusok -->
<CheckBox Name ="checkrnfo" >Send me more information</CheckBox>
<CheckBox Name ="checkPhonecontact" >Contact me over the
phone</CheckBox>
</StackPanel>

RJ Send me more information


O Contact me over the phone

29.7. ábra: Eg�;szerű CheckBox típusok

A RadioButton "az-egy" másik ToggleButton típus. A checkBox típussal ellen­


tétben azonban rendelkezik azzal a természetes képességgel, amely biztosítja,
hogy az ugyanabban a tárolóban (stackPanel, Grid stb.) lévő összes Radio­
Button típus kölcsönösen kizárólagos, és nem igényel további munkát a ré­
szünkről. Vizsgáljuk meg a következőt:

<StackPanel>
<!-- R�dioButton típusok zeneválasztáshoz -->
<Label Fontsize = "15" Content = "select Your Music Media"/>
<RadioButton>CD Player</RadioButton>
<RadioButton>MP3 Player</RadioButton>
<RadioButton>8-Track</RadioButton>

<!-- RadioButton típusok színválasztáshoz -->


<Label Fontsize = "15" Content = "select Your color choice"/>
<RadioButton>Red</RadioButton>
<RadioButton>Green</RadioButton>
<RadioButton>Blue</RadioButton>
</StackPanel>

Ha tesztelnénk ezt a XMAL-t, azt tapasztalnánk, hogy csak egyet választha­


tunk a hat lehetőség közül, ami valószínűleg nem áll szándékunkban, mivel
két különböző csoport van (rádió és szín lehetőségek).

557
29. fejezet: Programozás WPF·vezérlőelemekkel

Logikai csoportositások létrehozása

Ha egyetlen tárolót szeretnénk több RadioButton típussal, amelyek különálló


fizikai csoportosításként viselkednek, ezt a GroupName tulajdonság beállításá­
val tehe�ük a RadioButton típus nyitó elemén:

<StackPanel>
<!-- A zene csoport -->
<Label Fontsize = "15" content "select Your Music Media"/>
<RadioButton GroupName "Music" >CD Player</RadioButton>
<RadioButton GroupName "Music" >MP3 Player</RadioButton>
<RadioButton GroupName "Music" >8-Track</RadioButton>

<!--A szín csoport (tetszőlegesen választható ebben a példában,


lásd a megjegyzést lent) -->
<Label Fontsize = "15" content = "select vour color choice"/>
<RadioButton GroupName "color">Red</RadioButton>
<RadioButton GroupName "color">Green</RadioButton>
<RadioButton GroupName "color">Blue</RadioButton>
</StackPanel>

Ezáltal egymástól függetlenül beállítha�uk az egyes logikai csoportosítása­


kat, még akkor is, ha azok ugyanabban a fizikai tárolóban vannak.

Megjegyzés Alapértelmezés szerint a GroupName értékkel nem rendelkező tárolóban lévő


összes RadioButton egyetlen fizikai csoportként működik. Ezért, az előző példában a színköz·
pontú gombok kölcsönösen kizáróak, még az elhagyott GroupName értékkel is, a zene csoport
jelenlétének köszönhetően.

A kapcsolódó elemek összeállitása GroupBox


tipusokba

Amikor rádiógombok vagy jelölőnégyzetek gyűjteményét tervezzük, meg­


szakott dolog vizuális tárolóval körülvenni őket annak jelzésére, hogy cso­
portként viselkednek Az általános módszer ehhez a GroupBox vezérlőelem
használata. Mivel a Header tulajdonság alapvetően system obj ect típussal.

dolgozik, hozzárendelhetünk bármilyen objektumot (egyszerű sztringet, szí­


nes téglalapot, gombot stb.), hogy fejlécként működjön. Vizsgáljuk meg a kö­
vetkező két GroupBox deklarációt, amelyek különféle módokon foglalják egy­
ségbe az előző Radi o Button típusokat:

558
A jelölőnégyzetek és a rádiógombok használata

<StackPanel>
<GroupBox Header = "select vour Music Media" BorderBrush ="Black">
<StackPanel>
<RadioButton GroupName "Music" >CD Player</RadioButton>
<RadioButton GroupName "Music" >MP3 Player</RadioButton>
<RadioButton GroupName "Music" >8-Track</RadioButton>
</StackPanel>
</GroupBOX>

<GroupBox BorderBrush ="Black">


<GroupBox.Header>
<Label Background = "Blue" Foreground = "white"
Fontsize = "15" content = "select your color choice"/>
</GroupBox.Header>
<StackPanel>
<RadioButton>Red</RadioButton>
<RadioButton>Green</RadioButton>
<RadioButton>Blue</RadioButton>
</StackPanel>
</GroupBOX>
</StackPanel>

Akimenet a 29.8. ábrán látható.

Select Your Music Media


Select your color cho1ce
0 COPlayer
O MP3 Player O Red
O 8-Track 0Green
O Blue

29.8. ábra: RadioButton típusokat keretező GroupBox típusok

Kapcsolódó elemek bővitő tipusokba csoportositása

Aszokásos GroupBox típus mellett a WPF rendelkezik olyan új felhasználói fe­


lület elemmel, amely csoportosítja olyan felhasználóifelület-elemek gyűjte­
ményét, amelyek egy kapcsaló segítségével elrejthetők, illetve megmutatha­
ták Ez az elem, az Expander típus lehetővé teszi, hogy az ExpandDirection
tulajdonság segítségével meghatározzuk az elemek megjelenítésének irányát
(fel, le, balra vagy jobbra). Tekintsük meg az alábbi XAML-leírást (amely
alapvetőerr <GroupBox> típusról <Expander> típusra módosul):

<StackPanel>
<Expander Header = "select Your Music Media" BorderBrush ="Black">
<StackPanel>
<RadioButton GroupName "Music" >CD Player</RadioButton>
<RadioButton GroupName "Music" >MP3 Player</RadioButton>

559
29. fejezet: Programozás WPF-vezérlőelemekkel

<RadioButton GroupName = "Music" >8-Track</RadioButton>


</StackPanel>
</Expander>

<Expander BorderBrush ="Black">


<Expander.Header>
<Label Background = "Blue" Foreground = "white"
Fontsize = "15" content = "select your color choice"/>
</Expander.Header>
<StackPanel>
<RadioButton>Red</RadioButton>
<RadioButton>Green</RadioButton>
<RadioButton>Blue</RadioButton>
</StackPanel>
</Expander >
</StackPanel>

A 29.9. ábra muta�a az egyes Expandereket összecsukott állapotban.

C�) Select Your Music Media


�) Select you r color ch01ce

29.9. ábra: Összecsukott Expanderek

A 29.10. ábra muta�a az egyes Expandereket kinyitott állapotban.

Select Your Music Media

29.10. ábra: Kiterjesztett Expanderek

Forráskód A CheckRadioGroup.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtár ról lásd a Bevezetés xlv. oldalát.

A ListBox és a ComboBox tipusok


használata

Ahogyan azt remélhettük, a WPF biztosít olyan típusokat, amelyek választ­


ható elemek csopor�át tartalmazzák - ilyen a List Box és a comboBox, ezek
mindegyike az rtemscontrol absztrakt ősosztályból származik. A legfonto-

560
A ListBox és a ComboBox típusok használata

sabb, hogy ez a szülőosztály definiálja az Items nevű tulajdonságot, amely a


részelemeket tároló, erősen típusos Itemco lleetion objektumot adja vissza.
Az Hemcollection típus a system.object típusokkal dolgozik, ezért gyakorla­
tilag bármit tartalmazhat. Ha markup révén szeretnénk egyszerű szöveges
adatokkal feltölteni egy rtemscontrol-leszármazott típust, akkor ezt <List­
Boxrtem> típusok készletének használatával tehe�ük meg. Példaként nézzük

a következő XAML-kódot:

<!-- Egyszerű listamező -->


<ListBox Name = "lstvideoGameconsoles">
<ListBoxrtem>Microsoft XBox 360</ListBoxrtem>
<ListBoxrtem>Sony Playstation 3</ListBoxrtem>
<ListBoxrtem>Nintendo Wii</ListBoxrtem>
<ListBoxrtem>Sony PSP</ListBoxrtem>
<ListBoxrtem>Nintendo DS</ListBoxrtem>
</ListBOX>

<!-- Egyszerű legördülő listadoboz -->


<ComboBox Name = "combovideoGameconsoles">
<ListBoxrtem>Microsoft XBox 360</ListBoxrtem>
<ListBoxrtem>Sony Playstation 3</ListBoxrtem>
<ListBoxrtem>Nintendo Wii</ListBoxrtem>
<ListBoxrtem>Sony PSP</ListBoxrtem>
<ListBoxrtem>Nintendo DS</ListBoxrtem>
</ComboBOX>

Megjegyzés A comboBox típusokat feltölthetjük <Li st Boxrtem> elemek helyett <Combo­


bokszrtem> elemek segítségéveL Ezáltal hozzáférhetünk az IsHi g hl i g hted tulajdonsághoz,
amelyet a Li stBoxit em típus nem használ.

Nem meglepő módon a 29.11. ábrán látható renderelést lá�uk.

Microsoft XBox 360


Sony Playstatien 3
Nintendo Wii
Sony PSP
·l
Niniendo DS
Microsoft X Box 350
Sonv Playstation 3
' l" ••

Sony PSP It
Niniendo DS

29. 11. ábra: Egt;szerű ListBox és ComboBox

561
29. fejezet: Programozás WPF-vezérlőelemekkel

Lista-vezérlőelemek feltöltése programozott módon

A lista-vezérlőelemben lévő adatok gyakran csak futásidőben ismertek; lehet


például, hogy a listamező elemeit egy adatbázis olvasásból visszakapott érté­
kek, WFC-szolgáltatás meghívása vagy külső fájl beolvasása alapján kell feltöl­
tenünk Amikor programozott módon kell ListBox vagy comboBox elemet feltöl­
tenünk, egyszerűen használjuk az Itemcollection típus tagjait (AddO, RemoveO
stb.). Tételezzük fel, hogy új Listcontrols nevű Visual Studio 2008 WPF-alkal­
mazás projektünk van. A lstvideoGameconsole típus előző XAML-deklarációját
a következők szerint lehet XAML-ben definiálni:

<Window x:class="Listcontrols.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Listcontrols" Height="300" Width="300" >
<StackPanel>
<!-- Ezt kód segítségével töltjük fel -->
<ListBox Name = "lstvideoGameconsoles">
</ListBOX>
</StackPanel>
</Window>

és a kapcsolódó kódfájlban az alábbiak szerint tölthe�ük fel:

public partial class Mainwindow : system.Windows.window


{
public Mainwindow()
{
Initializecomponent();
FillListBoxO;
}

private void FillListBox()


{
ll Elemek hozzáadása a listamezőhöz.
lstvideoGameconsoles.Items.Add("Microsoft XBox 360");
lstvideoGameconsoles.Items.Add("sony Playstation 3");
lstvideoGameconsoles.rtems.Add("Nintendo wii");
lstvideoGameconsoles.Items.Add("sony PSP");
lstVideoGameconsoles.Items.Add("Nintendo DS");
}
}

Az egyik dolog, amely különösnek tűnhet a ListBox XAML-leírásában, hogy


a <Li stBoxrtem> típusokat használtuk az elemek feltöltéséhez; itt azonban
sztringtípusokat használtunk az AddO metódus meghívása során. Ennek rö-

562
A ListBox és a ComboBox típusok használata

vid magyarázata az, hogy a XMAL használatakor a <ListBoxrtem> típusok


kényelmesebbek abból a szempontból, hogy azokat a http:l l schemas.micro­
soft.coml winfxl 2006l xamll presentation XML-névtérben definiáltuk, ezért
rendelkezünk a közvetlen referenciájukkal.
A háttérben a rendszer a ToString O metódust hívja minden egyes <List­
Boxrtem> típuson, így a végeredmény azonos. Ha system. String névtér hasz­
nálatával töltenénk fel a ListBox (vagy comboBox) típust XAML-ben, akkor
meg kellene határoznunk egy új XML-névteret, hogy behozzuk az rnscor­
lib. dll fájlt (további részletekért lásd a 28. fejezetet):

<StackPanel xmlns:corLib = "clr-namespace:system;assembly=mscorlib">


<ListBox Name = "lstvideoGameconsoles">
<CorLib:string>Microsoft XBox 360</CorLib:String>
<CorLib:String>Sony Playstation 3</CorLib:String>
<CorLib:string>Nintendo Wii</CorLib:String>
<CorLib:string>Sony PSP</CorLib:String>
<CorLib:string>Nintendo DS</CorLib:string>
</ListBOX>
</StackPanel>

Ellenben, ha szeretnénk, programozott módon feltölthetünk egy Itemscontrol­


leszármazott típust erősen típusos Li stBoxitem objektumok segitségével; a je­
lenlegi példában azonban ezzel nem nyerünk semmit, hanem valójában csak
további munkát generálunk magunknak (mivel a ListBoxitem nem rendelkezik
konstruktorral a content tulajdonság beállításához!).

Tetszőleges tartalom hozzáadása

Mivel mind a ListBox, mind a comboBox származtatási láncában megtalálható


a Contentcontrol alaposztály, ezért azok egy egyszerű sztringnél jóval bo­
nyolultabb adatokat is tartalmazhatnak Tekintsük át a következő comboBox
típust, amely kétdimenziós grafikus objektumokat és egy leíró címkét tartal­
mazó <StackPane l> tárolókat foglal magában:

<StackPanel>
<!-- Egy ListBox tartalommal -->
<ListBox Name = "lstcolors">
<StackPanel Orientation ="Horizontal">
<Ellipse Fill ="Yellow" Height ="50" Width ="50"/>
<Label Fontsize ="20" HorizontalAlignment="Center"
verticalAlignment="Center">Yellow</Label>
</StackPanel>
<StackPanel Orientation ="Horizontal">

563
29. fejezet: Programozás WPF-vezérlőelemekkel

<Ellipse Fill ="Blue" Height ="50" Width ="50"/>


<Label Fontsize ="20" Hori zontalAlignment="Center"
verticalAlignment="Center">Blue</Label>
</StackPanel>
<StackPanel orientation ="Horizontal">
<Ellipse Fill ="Green" Height ="50" width ="50"/>
<Label Fontsize ="20" HorizontalAlignment="Center"
verticalAlignment="Center">Green</Label>
</StackPanel>
</Li stBOX>
</StackPanel>

A 29.12. ábra mutatja az aktuális lista típusaink kimenetét.

j} ListControl<

Pick a video game system


Mi<:rorolt XBox 360
Sony Playstation 3
Nintendo Wi i
Sony PSP
NiniendeDS

Pick a Color

Yellow

29. 12. ábra: ItemsControl-leszánnazott típusok tartalmazhatnak bánnilyen jellegű tartalmat

Az aktuális kiválasztás meghatározása

A ListBox vagy a comboBox típus feltöltése után a következő nyilvánvaló kér­

dés az, hogy futásidőben hogyan lehet eldönteni, melyik elemet választotta
ki a felhasználó. Mint látni fogjuk, erre három módszer létezik. Ha a kiválasz­
tott elem numerikus indexének a kiclerítése érdekel bennünket, akkor hasz­
nálhatjuk a selectedindex tulajdonságot (amely nulla alapú; a -1 érték azt
mutatja, hogy nincs kijelölés). Ha a listában a kiválasztott objektumot szeret­
nénk megszerezni, arra a selecteditem tulajdonság való. Végül, a selected­
value lehetővé teszi, hogy a kiválasztott objektum értékét megkapjuk (több­

nyire a Tostring() metódus meghívásával).

564
A ListBox és a ComboBox típusok használata

Nem hangzik bonyolultnak, igaz? Most, hogy teszteljük az egyes tulaj­


donságok viselkedését, tételezzük fel, hogy definiáltunk két új Button típust
az aktuális ablak számára, amelyek egyaránt kezelik a kattintási eseményt:

<!-- Gombok a kiválasztott elem megszerzéséhez -->

<Button Name ="btnGetGamesystem" Click ="btnGetGamesystem_click">


Get video Game System
</But ton>
<Button Name ="btnGetcolor" click ="btnGetcolor_click">
Get color
</Button>

A btnGetGamesystem esetében a kattintási esemény kezelője megszerzi a lst­


videoGameconsoles objektum Selectedindex, Selecteditem és Selectedvalue

tulajdonságainak értékeit, és megjeleníti azokat egy üzenetdobozban:

protected void btnGetGamesystem_click(object sender,


RoutedEventArgs args)
{
string data = string.Empty;
data += string.Format("selectedindex = {0}\n",
lstvideoGameConsoles.Selectedindex);
data += string.Format("selectedrtem = {0}\n",
lstvideoGameconsoles.selecteditem);
data += string.Format("Selectedvalue = {0}\n",
lstvideoGameconsoles.selectedvalue);
MessageBox.show(data, "Your Game Info");
}

Ha a "Nintendo Wü" -t választanánk a játékkonzolok listájából, és a kapcsolódó


gombra kattintanánk, akkor a 29.13. ábrán látható szövegdobozt kapnánk

Your Game Info ..

Selectedlndex = 2
Selecteditem = Niniendo Woi
Select�alue = Nintendo Wii

OK

29.13. ábra: Megtaláltuk a kiválasztott szringet

Azonban, mi a helyzet a kiválasztott szín megszerzésével?

565
29. fejezet: Programozás WPF-vezérlőelemekkel

Az aktuális kiválasztás meghatározása a beágyazott


tartalom esetében

Tételezzük fel, hogy a btnGetcolor Button típus kattintási eseményének kezelő­


je implementálja a btnGetcolor_cl ick() metódust, hogy kiírja a lstcolors
L istBox objektum aktuális kijelölését, indexét és értékét Ha most kiválaszta­
nánk az első elemet a 1stColars listamezőből (és a hozzátartozó gombra kattin­
tanánk), meglepetésünkre a 29.14. ábrán látható kimenettel szembesülnénk

Your Game Info

Solectedlnd<>< = O
S..lectedltem = S ystem.Windows.Controls.StackPanel
S..lectedValue = S ystom.Windows.Controls.StackPanel

OK

29. 14. ábra: Megtaláltuk a kiválasztott... StackPanelt?

Ennek a kimenetnek az oka az, hogy a lstcolors objektum három stackPanel


objektumot tart fenn, amelyek mindegyike rendelkezik beágyazott tartalom­
mal. Ezért a selecteditem és a selectedvalue tulajdonságok egyszerűen meg­
hívják a ToString() metódust a stackPanel típuson, amely visszaadja teljesen
meghatározott nevét.
Noha a selectedrndex tulajdonságból visszaadott numerikus érték alapján
könnyen kitalálhatnánk, hogy a felhasználó melyik elemet választotta, egy
másik megközelítés az, hogy leásunk a stackPanel gyermekgyűjteményébe,
hogy megkapjuk a Label típus content értékét a stackPanel belsőleg fenntar­
tott children gyűjteményének segítségéve!:

protected void btnGetColor_Clicked(object sender,


RoutedEventArgs args)
{
ll A Content érték megszerzése a StackPanel elemben
ll a kiválasztott Label típusban.
StackPanel selectedstack =
(StackPanel)lstcolors.Items[lstcolors.selectedrndex];
string color =

((Label)(selectedStack.Children[l])).Content.ToString();
string data string.Empty;
=

data += string.Format("selectedrndex {0}\n", =

lstcolors.selectedrndex);
data += string. Format("Col or {O}", color); =

MessageBox.show(data, "Your Game Info");


}

566
A ListBox és a ComboBox típusok használata

Noha ez a megoldás megteszi, mégis elég kényes, mivel kódolt pozíciók


vannak a stackPanel tárolóban (a második gyermek a L a b el), és számos kasz­
tolási műveletet kell végrehajtanunk Másik alternatíva minden StackPanel
Tag tulajdonságának beállítása, amelyet a Framewa rkEleme nt alaposztályban
definiálunk:

<Listsox Name = "lstcolors">


<StackPanel Orientation ="Horizontal" Tag ="Yellow">

</StackPanel>
<StackPanel orientation ="Horizontal" Tag ="Blue">

</StackPanel>
<StackPanel Orientation ="Horizontal" Tag ="Green">

</StackPanel>
</ListBOX>

Ezen megközelítés használatával a kódunk jelentősen letisztul, mivel prog­


ramozott módon kiszedhetjük a Tag tulajdonsághoz rendelt értéket, a követ­
kezők szerint:

protected void b t n Getcol o r_clicked(o b ject sender,


RoutedEventArgs args)
{
string data = string.Empty;
data += string.Format("Selectedrndex = {0}\n",
lstcolors.selectedrndex);
data += string.Format("selectedrtem = {0}\n",
lstcolors.selectedrtem);
data += string.Format("selectedvalue ={0}",
(lstcolors.Items[lstcolors.selectedindex] as
stackPanel).Tag);
Mess agesox.s h o w(da ta, "Your color Info");
}

Noha ez a megközelítés egy kicsit tisztább, mint az első kísérletünk, vannak


más módok is, ahol adatsablonok segítségével érhetjük el egy összetett vezér­
lőelem értékét. Ehhez meg kell értenünk a WPF adatkötési motor működését,
amelyet a fejezet végén vizsgálunk meg.

Forráskód A ListControls kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

567
29. fejezet: Programozás WPF-vezérlőelemekkel

A többsoros szövegbeviteli mezök


használata

A WPF több olyan felhasználóifelület-elemet biztosít, amelyek lehetövé te­


szik a szövegalapú bevitelek összegyűjtését. A két legegyszerűbb típus a
TextBox és a PasswordBox, amelyeket most a Textcontrols nevű, új Visual
Studio 2008 WPF alkalmazás használatával vizsgálunk meg.

A TextBox tipus használata

A múltban használt más TextBox típusokhoz hasonlóan a WPF TextBox típusát


beállítha�uk, hogy egy sornyi szöveget tartalmazzon (ez az alapértelmezett be­
állítás), vagy több sort, ha az AcceptReturn tulajdonság értéke true. A TextBoxon
belül az adatokat mindig karakteradatokként kezeljük, ezért a
"
"tartalom min­
dig olyan sztringtípus, amelyet a Text tulajdonság segítségével beállíthatunk és
lekérdezhetünk:

<TextBox Name ="txtoata" Text "Hello!" BorderBrush ="Blue"


Width ="100"/>

A WPF TextBox típusának egyik nagyon egyedi jellemzője, hogy beépített képes­
sége révén ellenőrizheti a bevitt adatok helyesírását, ha a spellcheck.rsEnabled
tulajdonság értéke true. llyen esetekben látni fogjuk, hogy- a Microsoft Office­
hoz hasonlóan - a helytelenül írt szavakat az alkalmazás vörös hullámos vonallal
aláhúzza. Ennél még jobb, hogy az alapul szolgáló programozási modell segítsé­
gével hozzáférhetünk a helyesírásellenőrző-motorhoz, ezáltal megkapha�uk a
helytelenül írt szavakra vonatkozó javaslatok listáját.
Az alábbiak szerint módosítsuk az aktuális ablak XAML-definícióját, hogy
használhassuk a Label, a TextBox és a Button típusokat (figyeljük meg, hogy a
TextBox többsoros szövegeket támogat, és engedélyezi a helyesírás-ellenőrzést):

<Window x:class="Textcontrols.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Textcontrols" Height="204" width="292" >
<StackPanel>
<Label Fontsize ="15">Is this word spelled correctly?</Label>

568
A többsoros szövegbeviteli mezők használata

<TextBox Spellcheck.IsEnabled ="True" AcceptsReturn ="True"


Name ="txtData" Fontsize ="12"
BorderBrush ="Blue" Height ="100">
</TextBOX>
<Button Name ="btnOK" Content ="Get Selections"
wi dt h = "100" click ="btnOK_Click"/>
</StackPanel>
</Window>

Ilyen funkcionalitással már látjuk majd, hogy amikor helytelenül írunk be


szavakat a TextBox-ba, akkor a rendszer megjelöli a helyesírási hibákat. Az
egyszerű helyesírás-ellenőrzőnk befejezéséhez a következőképpen módosít­
suk a gomb kattintási eseménykezelőjét:

protected void btnOK_Click(object sender, RoutedEventArgs args)


{
string spellingHints = string.Empty;

ll Próbáljunk helyesírási hibát keresni a kurzor aktuális


ll helyénél.
SpellingError error =
txtData.GetspellingError(txtData.caretrndex);
if (error l= null)
{
ll Készítsük el a helyesírási javaslatok sztringjét.
foreach (string s in error.suggestions)
{
spellingHints += string.Format('' { O } \n", s);
}

ll Mutassuk meg a javaslatokat!


MessageBox.show(spellingHints, "Try these instead'');
}
}

A kód meglehetősen egyszerű. Csak megkeressük a kurzor jelenlegi helyét a


szövegdobozban a caretindex tulajdonság használatával, hogy kinyerjünk egy
spellingError objektumot. Ha valami hiba van a mondott helyen (vagyis az ér­
ték nem null) , akkor végiglépkedünk a javaslatok listáján a stílszerűen elneve­
zett suggestions tulajdonság révén. Végül megjelenítjük a lehetőségeket egy
egyszerű MessageBox. show() kérés használatával. A 29.15. ábra lehetséges teszt­
futást mutat, amikor a kurzor a helytelenül írt "auromatically" szón áll.

569
29. fejezet: Programozás WPF-vezérlőelemekkel

[!J Tex\Controls \ol @J �l


ls this word spelled correctly?

Wow! Aspell �that� finds ali my errorsi


This is .ww; easier than using the COM interoperability layer!!

Try these instead lli3il


r--
l Gd Sel«tions l aromaticaily
automaticaily

l
l OK
l

29.15. ábra: Eg1j egyedi hel1Jesírás-ellenőrző!

A PasswordBox tipus használata

A PasswordBox típus - nem meglepően - egy olyan biztonságos helyet kínál,


ahova érzékeny szöveges adatokat írhatunk be. Alapértelmezés szerint a jel­
szó karakterkör típusú, ez azonban megváltoztatható a Passwordchar tulaj­
donság használatávaL A végfelhasználó által beírt érték megszerzéséhez egy­
szerűen ellenőrizzük a Password tulajdonságot. Módosítsuk a jelenlegi helyes­
írás-ellenőrző alkalmazásunkat a helyes jelszó kérésével, hogy láthassuk a
helyesírási javaslatok listáját. Először módosítsuk a már meglévő <Stack­
Panel> tárolót egy beágyazott <StackPanel> típussal, amely vízszintesen elhe­
lyezi a meglévő <Button> típus mellé a PasswordBox mezőt:

<StackPanel>
<Label Fontsize ="15">Is this word spelled correctly?</Label>
<TextBox Spellcheck.IsEnabled ="True" AcceptsReturn ="True"
Name ="txtFavoritecolor" Fontsize ="14"
BorderBrush ="Blue" Height ="100">
</TextBOX>
<StackPanel orientation ="Horizontal">
<PasswordBox Name ="pwdText" BorderBrush ="Black"
Width ="100"></PasswordBox>
<Button Name ="btnOK" content ="Get selections"
width = "100" click ="btnOK_Click"/>
</StackPanel>
</StackPanel>

570
A többsoros szövegbeviteli mezök használata

Most módosítsuk úgy a jelenlegi Button kattintási eseménykezelőjét, hogy


meghívja a checkPassword O segédfüggvényt, amely teszteli a kódolt sztringet.
Győződjünk meg arról, hogy a javaslatok csak akkor jelenhessenek meg, ha a
jelszó ellenőrzése sikeres volt. Íme a releváns módosítások:

p�blic partial class Mainwindow : System.windows.window


{

protected void btnOK_Click(object sender, RoutedEventArgs args)


{
if (CheckPassword())
{
ll ugyanaz a helyesírás-ellenőrző logika, mint korábban ...
}
else
MessageBox.show("security error!!");
}

private bool CheckPassword()


{
if (pwdText.Password == "chucky")
return true;
else
return false;
}
}

A TextBox és a PasswordBox mellett ne feledjük, hogy ha egy olyan alkalma­


zást készítünk, amely rendelkezik olyan szövegmezővel, amely bármilyen tí­
pusú tartalmat (grafikus renderelést, szöveget stb.) magában foglalhat, a
WPF biztosítja számunkra a RichTextBox típust. Emellett, ha kell a teljesít­
mény egy különösen szövegintenzív alkalmazás készítéséhez, a WPF biztosít
egy teljes dokumentumprezentációs API-t, amelyet elsősorban a system. wi n­
dows.Documents névtér képvisel.
Itt találunk olyan típusokat, amelyek lehetövé teszik folyászöveg-dokumen­
tumok (flow document) készítését, és ezek segítségével programozott módon
ábrázolhatunk (XAML- vagy C#-kódban) bekezdéseket, kapcsolódó szöveg­
blokk-részeket, feljegyzéseket, jegyzeteket, táblázatokat és egyéb gazdag do­
kumentumcentrikus típusokat. A könyv jelen kiadása nem tárgyalja a Rich­
TextBox típust, illetve a folyászöveg-dokumentum API-t; ha érdekesnek bi­
zonyul a téma, további részletekért lapozzuk fel a .NET Framework 3.5 SDK
dokumentációját.

571
29. fejezet: Programozás WPF·vezérlőelemekkel

Forráskód A TextControls kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Ezzel befejeztük a WPF vezérlőelemkészlet áttekintését. A fejezet későbbi ré­


szében látni fogjuk, hogyan készíthetünk menürendszereket, állapotsávokat
és eszköztárakat A következő feladatunk azonban annak áttekintése, hogyan
rendezhetjük a felhasználói felület elemeit window típuson belül tetszőleges
számú paneltípus használatávaL

A tartalomelrendezés kezelése panelek


használatával

Egy valós WPF-alkalmazás mindenképpen tartalmaz jó néhány felhasználóife­


lület-elemet (beviteli vezérlőelemeket, grafikus tartalmat, menürendszereket,
állapotsávokat stb.), amelyeket jól kell elrendezni a tároló ablakban. Emellett,
ha egyszer elhelyeztük a felhasználói felület vezérlőit az új helyükre, biztosak
akarunk lenni abban, hogy az elvártak szerint viselkednek, amikor a végfel­
használó átméretezi az ablakot vagy akár az ablak egy részét (osztott ablak ese­
tében). Ahhoz, hogy biztosítsuk, a WPF-vezérlőelemek megtartsák pozícióju­
kat a hosztoló ablakban, több paneltípus áll rendelkezésünkre.
Ahogy az előző fejezetből emlékezhetünk rá, hogy amikor tartalmat he­
lyezünk el olyan ablakban, amely nem használ paneleket, az hajszálpontosan
a tároló közepére kerül. Tekintsük át az alábbi egyszerű ablakdeklarációt,
amely egyetlen Button típust tartalmaz. Függetlenül attól, hogyan méretez­
zük át az ablakot, a felhasználóifelület-vezérlő mindig egyenlő távolságra je­
lenik meg az adott terület négy oldalától.

<!-- Ez a gomb mindig az ablak közepén van -->

<Window x:class="MyWPFApp.Mainwindow"
xmlns="ht:t:p://schemas.microsoft:.com/winfx/2006/xaml/present:at:ion"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fun with Panels!" Height="285" Width="325">
<Button Name="btnOK" Height = "100" Width="80">0K</Button>
</WindoW>

572
A tartalomelrendezés kezelése panelek használatával

Arra is emlékezzünk, hogy ha több elemet próbálunk elhelyezni közvetlenül


egy <WindoW> típus hatókörén belül, akkor címkézési és/vagy fordítási idejű
hibát kapunk. Ezeknek a hibáknak az az oka, hogy egy ablak (vagy ami azt il­
leti, a contentcontrol bármelyik leszármazottja) csak egyetlen objektumot
rendelhet a content tulajdonságához:

<!-- Hiba! A content tulajdonságot implicit módon többször


állítottuk be! -->
<Window x:class="MyWPFApp.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fun with Panels!" Height="285" Width="325">

<!-- Nyugtázval A <WindoW> két közvetlen gyermekelemel -->


<Label Name="lblinstructions"
width="328" Height="27" Fontsize="15">Enter
car Information</Label>
<Button Name="btnOK" Height = "100" width="80">0K</Button>
</Window>

Nyilvánvaló, hogy nem sok haszna van egy olyan ablaknak, amely csak egy
elemet tartalmazhat. Ha arra van szükség, hogy egy ablak több elemet tartal­
mazzon, akkor azokat tetszőleges számú panelbe kell rendeznünk. A panel tar­
talmazza az ablakot képviselő felhasználóifelület-elemeket, amely után a pa­
nelt magát csak a content tulajdonsághoz rendelt objektumként használjuk.

A WPF alapvető paneltipusai

A system.windows. Controls névtér több paneltípust biztosít, amelyek mind­


egyike azt szabályozza, hogy az egyes részelemek hogyan helyezkednek el.
A panelek használatával megadhatjuk, hogyan viselkedjenek a vezérlők,
amikor a végfelhasználó átméretezi az ablakot: pontosan ugyanott maradja­
nak, ahova tervezéskor kerültek; átrendeződjenek balra-jobbra, föl-le; stb.
Bonyolult felhasználói felület készítéséhez összekeverhetjük a panelbeli ve­
zérlőelemeket (pl. egy StackPanelt tartalmazó DockPanel) , ezáltal nagyfokú ru­
galmasságot és kezelhetőséget biztosíthatunk. Ezenfelül a paneltípusok együtt
tudnak dolgozni más dokumentumközpontú vezérlőelemekkel (mint a view­
Box, a TextBlock, a TextFlow és a Paragraph tipusok), hogy tovább lehessen fi­
nomítani a tartalom elrendezését egy adott panelen belül. A 29.3. táblázat be­
mutatja néhány gyakran használt WPF panelbeli vezérlőelem szerepét.

573
29. fejezet: Programozás WPF-vezérlőelemekkel

canvas "
"Klasszikus mód a tartalom elhelyezésére. Az elemek pontosan
ugyanott maradnak, ahová a tervezéskor tettük őket.

D ockP anel Zárolja a tartalmat a panel megadott oldalára (Top, Bottom, Left
vagy Ri ght).

G ri d Cellák sorozatába rendezi a tartalmat, táblázatos rácsformában.

StackPanel Függőlegesen vagy vízszintesen egymás után helyezi a tartal­


mat, ahogy azt az Orientation tulajdonság megkívánja.

wrapPanel Balról jobbra pozícionálja a tartalmat, új sorba tördelve azt a tá­


roló doboz szélénél. A további rendezés folyamatosan történik
felülről lefelé vagy jobbról balra, az Orientation tulajdonság
értékétől függően.

29.3. táblázat: Alapvető WPF panelbeli vezérlőelemek

A következő részben bemutatjuk ezeknek a gyakran használt paneltípusok­

nak a használatát, elkészítjük a 29.16. ábrán látható grafikus felületet külön­


böző paneleken belül, és megvizsgáljuk, hogyan változik az elhelyezés, ha az
ablakot átméretezzük.

l � FunWithPa�ls

Enter Car Information

29.16. ábra: A felhasználói felület megcélzott elrendezése

Tartalom elhelyezése a Canvas paneleken belül

Vitathatatlanul a legegyszerűbb panel a Canvas. A Canvas az a panel, amely­


ben leginkább otthon érezzük magunkat, ugyanis egy Windows Forms alkal­
mazás alapértelmezett elrendezését utánozza. A Canvas panel lehetővé teszi a

574
A tartalomelrendezés kezelése panelek használatával

felhasználói felület tartalmának abszolút elhelyezését. Ha a végfelhasználó a


Canvas panelen fenntartott elrendezésnél kisebb területűre rnéretezi az abla­
kot, a belső tartalom addig nem lesz látható, amíg a tárolót ugyanakkorára
vagy nagyobbra nem terjeszti ki, rnint a Canvas területe.
Ahhoz, hogy a canvas típushoz tartalmat adjunk, határozzuk meg meg a
szükséges részelerneket a nyitó <Canvas> és a záró </Canvas> címkék hatókö­
rén belül, valarnint adjuk meg a renderelés helyét Gegyezzük meg, hogy a
tartalom pozíciója relatív lehet a Canvas jobbjbal vagy alsó/felső oldalához
képest, de nem rnindkettőhöz). Ha azt szeretnénk, hogy a Canvas területe a
tároló egész területét fedje, egyszerűen hagyjuk el a Height és a width tulaj­
donságokat. Tekintsük meg a következő XAML-rnarkupot, amely a 29.16. áb­
rán látható elrendezést definiálja:

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fun with Panels!" Height="285" width="325">

<Canvas Background="LightsteelBlue">
<Button canvas.Left="212" canvas.Top="203" Name="btnOK"
width="80">0K</Button>
<Label Canvas.Left="17" canvas.Top="14" Name="lblinstructions"
width="328" Height="27"
Fontsi ze="15">Enter car Information</Label>
<Label canvas.Left="17" canvas.Top="60"
Name="lblMake">Make</Label>
<TextBox Canvas.Left="94" canvas.Top="60" Name="txtMake"
width="193" Height="25"/>
<Label canvas.Left="17" canvas.Top="109"
Name="lblcolor">Color</Label>
<TextBox canvas.Left="94" Canvas.Top="107" Name="txtcolor"
width="193" Height="25"/>
<Label canvas.Left="17" canvas.Top="155"
Name="lblPetName">Pet Name</Label>
<TextBox canvas.Left="94" Canvas.Top="153" Name="txtPetName"
width="193" Height="25"/>

</Canvas>

</Window>

Ebben a példában a <Canvas> hatókörén belül lévő összes elemet egy canvas.Left
és egy canvas. Top érték rninősít, amelyek a tartalom felső és bal oldali elhelyez­
kedését vezérlik a panelen belül, a csatolt tulajdonságszintaxis használatával
Qásd a 28. fejezetet). Ahogy kitalálhattuk, a függőleges elhelyezkedést a Top, il­
letve a Bottom tulajdonság, rníg a vízszintes elhelyezkedést a Left, illetve a Right
tulajdonság vezérli.

575
29. fejezet: Programozás WPF-vezérlőelemekkel

Mivel minden vezérlőt a <Canvas> elemen belül helyeztünk el, látni fog­
juk, hogy az ablak átméretezésekor a rendszer eltakarja a vezérlőket, ha a tá­
roló területe kisebb lesz, mint a tartalom (lásd a 29.17. ábrát).

29. 17. ábra : A Canvas panel tartalma lehetővé teszi az abszolút pozícionálást

Annak a sorrendje, hogyan deklarálunk tartalmat egy canvas típuson belül,


befolyásolja az elhelyezkedés kiszámítását, ugyanis ez a vezérlőelem mére­
tén, valamint a canvas.Top, a canvas.Bot:t:om, a canvas.Left: és a canvas.Right:
tulajdonságokon alapul. Ezek alapján az alábbi markup (amely csoportosí�a
a hasonló vezérlőelemeket) ugyanolyan renderelést eredményez:

<Canvas Background="Light:St:eelBlue">
<Text:Box Canvas.Left:="94" Canvas.Top="153" Name="t:xtcolor"
widt:h="193" Height:="25"/>
<TextBox canvas.Left="94" canvas.Top="60" Name="txtPetName"
width="193" Height:="25"/>
<TextBox Canvas.Left="94" Canvas.Top="107" Name="txtMake"
width="193" Height="25"/>
<Label canvas.Left="17" canvas.Top="14" Name="lblinstructions"
widt:h="328" Height:="27"
Fontsize="lS">Enter car Informat:ion</Label>
<Label Canvas.Left="17" Canvas.Top="109"
Name="lblcolor">Color</Label>
<Label canvas.Left="17" canvas.Top="155"
Name="lblMake">Pet: Name</Label>
<Label canvas.Left="17" canvas.Top="60"
Name="lblPet:Name">Make</Label>

<But:t:on canvas.Left="212" canvas.Top="203" Name="btnOK"


Width="80">0K</Button>
</Canvas>

Megjegyzés Ha egy canvas típuson belüli részelemek nem határoznak meg adott helyet a
csatolt tulajdonságszintaxis révén, akkor azok automatikusan a bal felső sarokba kerülnek.

576
A tartalomel rendezés kezelése panelek használatával

Noha a canvas típus használata kívánatosnak tűnhet a tartalom elrendezésé­


hez (mivel olyan ismerősnek tűnik), mégis vannak korlátai. Először is, egy
canvas típuson belüli elemek nem méretezik át magukat dinamikusarr stílu­
sok vagy sablonok alkalmazásakor (pl. a betűk mérete nem változik). A má­
sik nyilvánvaló korlát az, hogy a canvas nem próbálja meg láthatóan megtar­
tani az elemeket, amikor a végfelhasználó kisebbre méretezi át az ablakot.
A canvas típus legjobb használata talán a grafikus tartalom elhelyezése.
Például, ha egyedi képet készítenénk XAML használatával, akkor bizonyára
azt akarnánk, hogy a vonalak, a formák és a szöveg ugyanazon a helyen ma­
radjon ahelyett, hogy dinamikusarr áthelyeződjenek, ha a felhasználó átmére­
tezi az ablakot. Újra megvizsgáljuk a canvas típust a következő fejezetben,
amikor a WPF grafikus renderelési szolgáltatásairól lesz szó.

Forráskód A SimpleCanvas.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Tartalom elhelyezése a WrapPanel paneleken belül

A WrapPanel lehetővé teszi olyan tartalom definiálását, amely átnyúlik a pa­


nelen, ha az ablakot átméretezik Amikor elhelyezünk elemeket egy Wrap­
Panel panelben, nem kell megadnunk felső, alsó, bal, illetve jobb oldali elhe­
lyezési értékeket, mint általában a Canvas esetében tesszük. Ehelyett minden
részelem szabadon meghatározhat egy Height és egy width értéket (egyéb tu­
lajdonságértékek mellett), hogy szabályazza az általános méretét a tárolóban.
Mivel a WrapPanelen belül a tartalom nem " rögzül" a panel egy adott ol­
dalához, ezért fontos a sorrend, amelyben az elemeket deklaráljuk (a tartalom
renderelése az első elemtől az utolsóig történik). Nézzük meg a következő
XAML-kódrészletet:

<WrapPanel Background="LightsteelBlue">
<Label Name="lblinstruction" Width="328"
Height="27" Fontsize="15">Enter car rnformation</Label>
<Label Name="lblMake">Make</Label>
<TextBox Name="txtMake" width="193" Height="25"/>
<Label Name="lblcolor">Color</Label>
<TextBox Name="txtcolor" width="193" Height="25"/>
<Label Name="lblPetName">Pet Name</Label>
<TextBox Name="txtPetName" width="193" Height="25"/>
<Button Name="btnOK" width="80">0K</Button>
</WrapPanel>

577
29. fejezet: Programozás WPF-vezérlöelemekkel

Amikor megtekin�ük ezt a markupot, a tartalom változta�a a formáját a széles­


ség átméretezésekor, mivel balról jobbra halad az ablakban (lásd a 29.18. ábrát).

fj FunWithPanels l= l @íiflij
&Iter Car Information .......

JColor� l
PetHamel l OK
r

29.18. ábra: Eí51J WrapPanel panelben a tartalom Úí51J viselkedik, mint eí51J közönséges HTML-oldal

Alapértelmezés szerint a WrapPanel tartalma balról jobbra halad. Ha azon­


ban megváltozta�uk az orientation tulajdonság értékét vertical-ra, a tarta­
lom fentről lefelé halad:

<WrapPanel Background="LightsteelBlue" orientation ="Vertical">

A WrapPanel (valamint néhány más paneltípus) deklarálható az Itemwidth és


az ItemHeight értékek meghatározásával, amelyek az egyes elemek alapér­
telmezett méretét szabályozzák. Ha egy részelem megadja a saját Height és/
vagy width értékét, akkor az a panel által megállapított mérethez viszonyítva
helyezzük el. Tekintsük meg a következő markupot:

<WrapPanel Background="LightsteelBlue" Itemwidth ="200"


ItemHeight ="30">
<Label Name="lblrnstruction"
Fontsize="15">Enter car Information</Label>
<Label Name="lblMake">Make</Label>
<TextBox Name="txtMak e"/>
<Label Name="lblcolor">Color</Label>
<TextBox Name="txtcolor"/>
<Label Name="lblPetName">Pet Name</Label>
<TextBox Name="txtPetName"/>
<Button Name="btnOK" Width ="80">0K</Button>
</WrapPanel>

Renderelés után a 29.19. ábrán látható kimenetet kapjuk (figyeljük meg a


Button vezérlő méretét és pozícióját).

.
!J FunWithPanels ,-... l= l (ID ...
· c;m;. 1
Enler Car Jnrurmation ...
l
Color
J �Pet,._
l [ OK
l
29. 19. ábra: A WrapPanel megállapíthatja eí51J adott elem szélességét és magasságát

578
A tar talomelrendezés kezelése panelek használatával

A 29.19. ábra megtekintése után egyetérthetünk abban, hogy a WrapPanel ál­

talában nem a legjobb választás tartalom közvetlen elrendezésére egy ablak­


ban, rrúvel az elemek összekeveredhetnek, ha a felhasználó átméretezi az ab­
lakot. A WrapPanel a legtöbb esetben egy másik paneltípus részeleme lesz, és
lehetövé teszi az ablak egy kis területének, hogy átméretezéskor becsomagolja
a tartalmát.

Forráskód A SimpleWrapPanel.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Tartalom elhelyezése a StackPanel paneleken belül

A WrapPanelhez hasonlóan a StackPanel vezérlőelem egy sorba rendezi a


tartalmat, amely vízszintesen vagy függőlegesen igazítható (ez az alapértel­
mezett) az orientation tulajdonsághoz rendelt érték alapján. A különbség az,
hogy a StackPanel nem kísérli meg a tartalom becsomagolását, arrúkor a fel­
használó átméretezi az ablakot. Ehelyett, a StackPanel panelben lévő elemek
egyszerűen megnyúlnak (a tájalásuk alapján), hogy illeszkedjenek a Stack­
Panel méretéhez.

fi FunWithPanels

29.20. ábra: A tartalom függőleges egymás után helyezése

A következő markup például a 29.20. ábrán látható kimenetet eredményezi:

<StackPanel Background="Lightsteelslue">
<Label Name="lblrnstruction"
Fontsize="15">Enter car rnformation</Label>
<Label Name="lblMake">Mak e</Label>
<TextBox Name="txtMake"/>
<Label Name="lblcolor">Color</Label>
<Textsox Name="txtcolor"/>

579
29. fejezet: Programozás WPF·vezérlöelemekkel

<Label Name="lblPetName">Pet Name</Label>


<TextBox Name="txtPetName"/>
<Button Name="btnOK">OK</Button>
</StackPanel>

Ha az alábbiak szerint Hori zon ta l értékre változtatjuk az orientation tulaj­


donságot, akkor a renderelt kimenet a 29.21. ábrán láthatóval egyezik:

<StackPanel Background="LightsteelBlue" Orientation ="Horizontal">

l
] FunWithPanels

29 21 ábra: A tartalom vízszintes eg1;más mellé helyezése


. .

Ugyanúgy, mint a WrapPanel esetében, ritkán használjuk majd a stackPanel tí­


pust arra, hogy közvetlenül rendezzük el a tartalmat egy ablakon belül. A Stack­
Panel ehelyett jobban alkalmazható egy főpanel részpaneleként

Forráskód A SimpleStackPanel.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Tartalom elhelyezése a Grid paneleken belül

A WPF API-kon belül kínált panelek közül a Grid messze a legrugalmasabb.


A HTML-táblázatokhoz hasonlóan a Gri d típus felosztható cellákra, amelyek
mindegyikében lehet tartalom. Egy Grid definiálásához három lépést kell
végrehajtanunk

l. Az egyes oszlopok meghatározása és konfigurálása.

2. Az egyes sorok meghatározása és konfigurálása.

3. Tartalom hozzárendelése a rács minden cellájához a csatolt tulajdon­


ságszintaxis használatávaL

580
A tartalomelrendezés kezelése panelek használatával

Megjegyzés Ha nem határozunk meg sorokat vagy oszlopokat, akkor a <G ri d> alapértelme­
zés szerint egyetlen cella, amely kitölti az ablak teljes felületét. Továbbá, ha nem rendelünk
hozzá cellaértéket egy részelemhez a <G ri d> típuson belül, akkor az automatikusan a O. osz­
lop O. sorába kerül.

Az első két lépést (az oszlopok és a sarok definiálását) a <Grid.ColumnDefini­

tions> és a <Grid. RowDefinitions> elemek használatával érhetjük el, amelyek


a <Col umnDefi nition>, illetve a <RowDefinition> elemek gyűjteményét tartal­
mazzák. Mivel egy rácsan belülminden cella valójában egy igazi .NET-típus,
ezért az egyes elemek megjelenését és viselkedését tetszés szerint kanfigurál­
hatjuk Íme, egy meglehetősen egyszerű <Grid> definíció, amely a 29.22. áb­
rán látható módon rendezi el a felhasználói felület tartalmát:

<Grid showGridLines ="True" Bac kground ="AliceBlue">


<!-- A sorokjoszlopok meghatározása -->

<Grid.columnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.columnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>

<!-- Most hozzáadjuk az elemeket a rács celláihoz -->


<Label Name="lblrnstruction" Grid.column ="0" Grid.Row ="0"
Fontsize="lS">Enter Car Information</Label>
<Button Name="btnOK" Height ="30" Grid.Column ="0"
Grid.Row ="0" >OK</Button>
<Label Name="lblMake" Grid.column ="l" Grid.Row ="O">Make</Label>
<TextBox Name="txtMake" Grid.Column ="l" Grid.Row ="0"
width="l93" Height="25"/>
<Label Name="lblcolor" Grid.Column ="0"
Grid.Row ="l" >Color</Label>
<TextBox Name="txtcolor" Width="l93" Height="25" Grid.column ="0"
Grid.Row ="l" />

<!-- Hogy érdekesebb legyen, adjunk valamilyen színt a PetName


cellának -->

<Rec tan gle Fill ="LightGreen" Grid.column ="l" Grid.Row ="l" />
<Label Name="lblPetName" Grid.column ="l"
Grid.Row ="l" >Pet Name</Label>
<TextBox Name="txtPetName" Grid.Column ="l" Grid.Row ="l"
width="l93" Height="25"/>
</Grid>

581
29. fejezet: Programozás WPF-vezérlőelemekkel

Figyeljük meg, hogy minden elem (beleértve a ráadásként hozzáadott világos­


zöld Rectangle elemet is) a rács egy cellájához kapcsolja magát a Grid. Row és a
Grid. column csatolt tulajdonságok segítségéveL Alapértelmezés szerint a rács­
ban lévő cellák rendezése a bal felső sarokban kezdődik, amelyet a Gri d .
column="O" Grid.Row="O" ad meg. Mivel ami rácsunk összesen négy cellát defi­
niál, a jobb alsó cellát a Grid.column="l" G ri d. Row="l" azonosí�a.

Íl FunWrthPanels r= 1 @J liiiEiiil.
E"' -"'
1--' n
e
t"--
r .::e;
Ca",_r..!:Jn"'-fo"_,r_,_,m: .:::a_"_
t
io::-.:"__
n
:
_--i M_a_
k_e
________ .
OK

------------------------- ! ------ �------- .. - .. -- ------ ...


Color :Pet�
........,..�-----...1
29.22. ábra: A Grid panel működés közben

Forráskód A SimpleGrid.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára tartal·


mazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Rácsok GridSplitter tipusokkal

A Gri d típusok az úgynevezett választóvonalakat támoga�ák. Ahogy bizo­

nyára tudjuk, a választóvonalak lehetővé teszik, hogy a végfelhasználó átmé­


retezze egy rácstípus sorait vagy oszlopait. Ha ez készen van, az egyes átmé­
retezhető cellákban lévő tartalom átalakí�a magát a befoglalt elemek alapján.
Nagyon könnyű választóvonalat hozzáadni egy Gri d típushoz; egyszerűen
definiáljuk a <Grids plitter> típus t a csatolt tulajdonságszintaxis használatá­
val, és határozzuk meg, mely sorra vagy oszlopra érvényes. Figyeljünk arra,
hogy hozzá kell rendelni egy wi dth vagy egy Hei ght értéket (attól függően,
hogy függőleges vagy vízszintes osztásról van szó) annak érdekében, hogy
látható legyen a képernyőn. Vizsgáljuk meg a következő G rid típust, amely­
ben választóvonal van az első oszlopon (Gri d .column = "O") :

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FunwithPanels" Height="191" Width="436">
<Grid Background ="AliceBlue">
<!-- Az oszlopok meghatározása -->
<Grid.columnDefinitions>
<ColumnDefinition width ="Auto"/>
<ColumnDefinition/>
</Grid.columnDefinitions>

582
A tartalomelrendezés kezelése panelek használatával

<!-- Címke hozzáadása a O cellához -->


<Label Name="lblLeft" Background ="GreenYellow"
Grid.column="O" content ="Left!"/>

<!-- A választóvonal meghatározása -->


<Gridsplitter Grid.Column ="0" width ="5"/>

<!-- Címke hozzáadása az l cellához -->


<Label Name="lblRight" Grid.Column ="l" Content ="Right!"/>
</Grid>
</Window>

Mindenekelőtt vegyük észre, hogy az oszlop, amely támogatja a választóvo­


nalat, rendelkezik az Auto.Next width tulajdonságával, míg a <Gridsplitter>

a csatolt tulajdonságszintaxis segítségével állapítja meg, melyik oszloppal


dolgozik. Ha megnéznénk ezt a kimenetet, látnánk egy öt képpontos válasz­
tóvonalat, amely lehetővé teszi, hogy átméretezzük az egyes Label típusokat
(mivel nem adtunk meg Height vagy width tulajdonságot a Label elemekhez,
ezért azok betöltik az egész cellát). Nézzük meg a 29.23. ábrát.

!il FunWithPanels l l @) liiiEiil


=

Left! Ríght!

()=()

29.23. ábra: Választóvonalakat tartalmazó Grid típusok

Forráskód A GridWithSplitter.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Tartalom elhelyezése a DockPanel paneleken belül

A oockPanel típus t általában olyan főpanelként használjuk, amely tetszőleges


számú további panelt foglalhat magában a kapcsolódó tartalom csoportosítá­
sára. Ahogyan a canvas típusnál láttuk, a oockPanel panelek a csatolt tulaj­
donságszintaxis segítségével szabályozzák, hogy a bal felső sarkuk (az alap­
értelmezett) hova csatolja magát a panelen belül. Íme, egy nagyon egyszerű
oockPanel definíció, amely a 29.24. ábrán látható kimenetet eredményezi:

<DockPanel LastchildFill ="True">


<!-- Elemek dokkolása a panelhez -->
<Label DockPanel.Dock ="Top" Name="lblinstruction"
FontSize="l5">Enter car Information</Label>

583
29. fejezet: Programozás WPF-vezérlőelemekkel

<Label DockPanel.Dock ="Left:" Name="lblMake">Make</Label>


<Label DockPanel.Dock ="Right:" Name="lblcolor">Color</Label>
<Label DockPanel.oock ="Bot:t:om" Name="lblPet:Name">Pet: Name</Label>
<But:t:on Name="bt:nOK">OK</But:t:on>
</DockPanel>

'!!] Fun with Panels! l = l ® llif3iiil


Enter Car Information

Make Color

OK ---

Pet Name

29.24. ábra: EgJJ egJJSZerú DockPanel

Megjegyzés Ha több elemet adunk hozzá egy DockPanel ugyanazon oldalához, azokat a
megadott oldal mellett a deklaráció sorrendjében halmozza fel a rendszer.

A oockPanel típusok használatának előnye, hogy ha a felhasználó átméretezi


az ablakot, akkor minden elem a panel megadott oldalához "kapcsolt" marad
(a oockPanel . oock révén). Azt is vegyük észre, hogy a nyitó <DockPanel> elem
true értékre állítja a Last:childFill attribútumot. Mivel a But:t:on típus nem
adott meg semmilyen oockPanel. Dock értéket, ezért kitölti megmaradt helyet.

Forráskód A SimpleDockPanel.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A lapozás engedélyezése a paneltipusoknál

Érdemes rámutatni, hogy a WPF biztosítja a <Scrollviewer> típust, amely au­


tomatikus lapozási viselkedést kínál a beágyazott paneltípusok számára:

<Scrollviewer>
<St:ackPanel>
<But:t:on content: ="First" Background = "Green" Height: ="40"/>
<But:t:on content: ="Second" Background = "Red" Height: ="40"/>
<But:t:on Content: ="Third" Background = "Pink" Height: ="40"/>
<But:t:on content: ="Fourt:h" Background = "Yellow" Height: ="40"/>
<But:t:on Content ="Fift:h" Background = "Blue" Height: ="40"/>
</St:ackPanel>
</Scrollviewer>

584
Ablak kereteinek készítése beágyazott panelek használatával

Az előző XAML-definíció eredménye a 29.25. ábrán látható .

l..j t-un W1t'


.l Paneld l c;:> l @J liifliíl

l :. ..

29.25. ábra: A ScrollViewer típus használata


u �
Forráskód A ScrollViewer.xaml fájlt a forráskódkönyvtár 29. fejezetének alkönyvtára tar·
talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Ahogy várhaljuk, minden panel több tagot biztosít, amelyek lehetővé teszik a
tartalom elhelyezésének finomhangolását. Ehhez kapcsolódó megjegyzés, hogy
a WPF-vezérlőelemek mindegyike két érdekes tulajdonságot ( Padding és Margin)
támogat, amelyek segítségével maga a vezérlőelem tájékoztathalja a panelt arról,
hogy rnilyen bánásmódot igényel. Pontosabban a Padding tulajdonság szabá­
lyozza, hogy mekkora plusz hely vegye körül a belső vezérlőelemet, míg a
Margin szabályazza a vezérlőelem külső részét körülvevő extra helyet.

Ezzel befejeztük a WPF főbb paneltípusainak, valamint a tartalmuk pozí­


cionálását szolgáló különböző módszerek áttekintését. Ezután megnézzük a
beágyazott panelek használatának egy példáját, hogy létrehozzunk egy el­
rendezés-rendszert a főablak számára. Ehhez bővíljük a TextControls projekt
(vagyis a helyesírásellenőrző-alkalmazás) funkcionalitását, hogy támogasson
egy főmenüt, egy állapotsávot és egy eszköztárat.

Ablak kereteinek készitése beágyazott


panelek használatával

Az alkalmazás frissített verzióját (amely az új, MySpellChecker nevű Visual


Studio 2008 WPF alkalmazás projekt) kibővíljük és véglegesíljük a következő
oldalakon, tehát pillanatnyilag az alapelrendezést és működést fogjuk össze­
állítani.
Célunk olyan elrendezés kialakítása, ahol a főablak rendelkezik egy felső
menürendszerrel, egy eszköztárral, valamint az ablak alján elhelyezett álla­
potsávval. Az állapotsáv magában foglal egy panelt, amely megjelenít egy

585
29. fejezet: Programozás WPF-vezérlőelemekkel

szöveges sort akkor, ha a felhasználó kiválaszt egy menüelemet (vagy esz­


köztárgombot), míg a menürendszer és az eszköztár grafikusfelület-kapcsoló­
kat kínál fel az alkalmazás bezárásához és a helyesírás-javaslatok megmutatá­
sához egy Expander vezérlőben. A 29.26. ábra muta�a a megcélzott kezdeti
elrendezést, megjelenítve a helyesírási javaslatokat a ,,)CAML" kifejezéshez.

i]MySpeiiChecke< Tc::ri§Jii:iir
File Tools

i Exti !�
The previous chapter provided
Spelling Hints
a foundation for the lrY.fE
programming model.
0Tryth6e!
induding an examination of
CALM the Window and Application
AXEL
types as weil as several
UXMAL
details regarding the
BALM
PALM Extendable Application Markup
AM Language�.
AMYL
EXAM
CAMEL
DAM
- -- -- ·---------- � - - - �- --·

Show Spelling Suggostions

29.26. ábra: Beágyazott panelek használata egtj ablak felhasználói felületének létrehozásához

Figyeljük meg, hogy a két eszköztárgombunk nem egy várt képet, hanem egy
egyszerű szöveges értéket jelenít meg. Noha ez nem lenne elegendő egy termék­
szintü alkalmazáshoz, képek hozzárendelése az eszköztár gombjaihoz általában
beágyazott erőforrások használatát igényli, amelynek témakörét a 30. fejezetben
fogjuk megvizsgálni (tehát most megteszi a szöveges adat). Azt is vegyük észre,
hogy amikor az egér a Check button fölé kerül, a kurzor megváltozik, az állapot­
sáv egyetlen panele hasznos felhasználóifelület-üzenetet jelenít meg.
A felület készítésének megkezdéséhez módosítsuk a window típusunk kez­
deti XAML-definícióját, hogy <DockPanel> gyermekelemet használjon <Grid>
helyett:

<Window x:class="MySpellchecker.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Myspellchecker" Height="331" width="508"
WindowstartupLocation ="Centerscreen" >

<!-- Ez a panel alakítja ki az ablak tartalmát -->

<DockPanel>
</DockPanel>
</WindoW>

586
Ablak kereteinek készítése beágyazott panelek használatával

A menürendszer készítése

A WPF-ben a menürendszereket a Menu típus képviseli, amely Menurtem objek­


tumok gyűjteményét tartja karban. Amikor XAML-ben készítünk menürend­
szert, minden Menurtem különböző - többek között kattintási - eseményeket
kezelhet, amely akkor fordul elő, ha a végfelhasználó rákattint egy részelemre.
A példánkban két felső menüelemet (File és Tools) hozunk létre, amelyek az
Exit, illetve a Spelling Hints részelemeket tartalmazzák Amellett, hogy ke­
zeljük a kattintási eseményt az egyes részelemeknél, kezeljük a MouseEnter és
a MouseExit eseményeket is, amelyeket arra használunk, hogy egy későbbi
lépésben beállítsuk az állapotsáv szövegeit. Adjuk meg a következő kódot a
<DockPanel> hatókörén belül:

<!-- Menürendszer elhelyezése az ablak tetején -->

<Menu oockPanel.oock ="Top"


HorizontalAlignment="left" Background="Wh ite"
BorderBrush ="Black">
<Menurtem Header=" _File" click ="FileExit_click" >
<Separator/>
<Menurtem Header ="_Exit" MouseEnter ="MouseEnterExitArea"
Mauseleave ="MouseleaveArea" click ="FileExit_click"/>
</Menuitem>
<Menurtem Header="_Tools">
<Menurtem Header ="_Spelling Hints"
MouseEnter ="MouseEnterToolsHintsArea"
Mauseleave ="MouseleaveArea"
click ="ToolsspellingHints_click"/>
</Menurtem>
</Menu>

Láthatjuk, hogy a menürendszert a DockPanel tetejére helyeztük Emellett a


<Separator> elemet használtuk arra, hogy beszúrjunk egy vékony vízszintes
vonalat a menürendszerbe, közvetlenül az Exit lehetőség elé. Azt is vegyük
észre, hogy az egyes Menurtem elemek Header értékei tartalmaznak egy be­
ágyazott aláhúzás tokent (például _Exit) . Ezt használjuk annak meghatáro­
zására, hogy melyik betű legyen aláhúzott, ha a végfelhasználó lenyomja az
Alt billentyűt (a gyorsbillentyűkhöz).
A kész menürendszer-definícióval most a különböző eseménykezelőket kell
implementálnunk. Először is, adott a File > Exit kezelő, a FileExit_clickO,
amely egyszerűen bezárja az alkalmazást az Applicati on.eurrent. shutdown O
metódus révén. Az egyes részelemek MouseEnter és MouseExit eseménykezelői az
állapotsávot fogják frissíteni; most azonban csak shelleket fogunk megadni. Vé­
gül, a Tools > Spelling Hints menüelem Toolsspelli ngHi nts_cli ckO kezelője pil­
lanatnyilag szintén egy shell lesz. Itt van a mögöttes kódfájl aktuális módosítása:

587
29. fejezet: Programozás WPF-vezérlőelemekkel

public partial class Mainwindow System.windows.Window


{
public Mainwindow()
{
Initializecomponent();
}

protected void FileExit_click(object sender, RoutedEventArgs args)


{
ll Az alkalmazás bezárása.
Application.current.Shutdown();
}

protected void ToolsSpellingHints_click(object sender,


RoutedEventArgs args)
{
}
protected void MouseEnterExitArea(object sender,
RoutedEventArgs args)
{
}
protected void MouseEnterToolsHintsArea(object sender,
RoutedEventArgs args)
{
}
protected void MouseLeaveArea(object sender, RoutedEventArgs args)
{
}
}

A ToolBar tipus készítése

Az eszköztárak (amelyeket a WPF-ben a ToolBar típus képvisel) általában al­


ternatív módszert biztosítanak egy menüelem aktiválására. Adjuk hozzá a
következő markupot, közvetlenül a <Menu> definíció záró hatóköre után:

<!-- Az eszköztár elhelyezése a menü alá -->


<ToolBar DockPanel.Dock ="Top" >
<Button content ="Exit" MouseEnter ="MouseEnterExitArea"
MauseLeave ="MouseLeaveArea" click ="FileExit_Click"l>
<Separatorl>
<Button content ="Check" MouseEnter ="MouseEnterToolsHintsArea"
MauseLeave ="MouseLeaveArea"
click ="ToolsspellingHints_click"
cursor="Help" l>
<IToolBar>

588
Ablak kereteinek készítése beágyazott panelek használatával

A <Too lBar> típusunk két Bu tton típust tartalmaz, amelyek történetesen ugyan­
azokat az eseményeket kezelik, és a kódfájlunk ugyanazon metódusai kezelik
őket. A módszer segítségével összerakhaljuk az eseménykezelőinket, hogy ki­
szolgálják núnd a menüelemeket, núnd az eszköztárgombokat Noha ez az esz­
köztár tipikus nyomógombokat használ, tudnunk kell, hogy a ToolBar típus "az­
egy" contentcontra l, ezért nyugodtan beágyazhatunk bármilyen típust a felüle­
tébe (legördülő listákat, képeket, grafikákat stb.). Az egyetlen érdekes pont az,
hogy a Check gomb egyedi egérkurzot támogat a cursor tulajdonság révén.

Megjegyzés A Too l Bar típus tetszés szerint becsomagolható <Too l BarTray> elembe, amely
Too l Bar objektumok esetében szabályazza az elrendezést, a dokkolást és a húzd·és-dobd müve­
leteket. További részletekért forduljunk a .NET Framework 3.5 SDK dokumentációjához.

A StatusBar tipus készítése

A StatusBar típus a <DockPanel> alsó részére kerül, és egyetlen <TextBlock> típust


tartalmaz, amelyet a fejezet ezen ponljáig még nem használtunk. A TextBoxhoz
hasonlóan a TextBlack típus is szöveget tárol. A TextBlack típusok emellett tá­
mogalják többfajta szöveges jelölés használatát, például a félkövér szöveget, az
aláhúzott szöveget, a sortöréseket stb. Míg a StatusBar típusunknak gyakorlati­
lag nincs szüksége ilyen támogatásra, a TextBlack típus másik előnye az, hogy
olyan kis fülszövegekhez optimalizált, núnt arnilyenek a felhasználói felület
megjegyzései az állapotsávpanelen. Adjuk a kódhoz a következő markupot,
közvetlenül az előző Too l Bar definíció után:

<!-- Helyezzünk alulra egy StatusBar típust -->

<StatusBar oockPanel.Dock ="Bottom" Background="Beige" >


<StatusBaritem>
<TextBlock Name="statBarText">Ready</TextBlock>
</StatusBaritem>
</StatusBar>

Ezen a ponton a Visual Studio tervezőjének valahogy úgy kell kinéznie, núnt
a 29.27. ábrán látható képnek.

589
29. fejezet: Programozás WPF-vezérlöelemekkel

n
!
'
. ,,
i1
l

m""
--= "'·= ·:ll
a=
·n_� Design ./�t.,..•-/..".= t
== =
XA --

M �
.. - - - -- -
- --·- ---- ----------
s oo
--------------------�� 1

: �-
1 3: I t-e
- � -:- � -
- -· d --- 7 . � -7
,.- - �
<M u�
n_
e_
L_ m Hea er Ex i t
..

:;,4; • MouseEnter ="MouseEnterExitArea"


1 c:. ' MauseLeave ="Z..iouseLeaveArea"
_ / C
Click ="FileExit C lick" >

.t�i�oi·�
_

1 9: -
.
-
..
</Henuitem>
<l'1en"üi't'eiii Header="_Tools">
<Menuitem Header ="_Spelling Hints"
• t'
.. _""�
-- - ------"'
- -
· "--
""� ' ;---��--��-- -·r--
...

� � Menultem Window/DockPaneVMonu/Menultem l>

29.27. ábra: Helyesírásellenőrző-alkalmazásunk aktuális felhasználói felülete

A felhasználói felület véglegesitése

Felhasználó felületünk tervezetének utolsó aspektusa egy olyan osztható


G ri d típus definiálása, amely két oszlopot határoz meg. A bal oldalon lesz az

Expander típus, amely <StackPanel> típusba csomagolva megjeleníti a he­


lyesírási javaslatok listáját. A jobb oldalon lesz egy TextBox típus, amely több
sort támogat, és engedélyezte a helyesírás-ellenőrzést. A teljes <G ri d> a szülő
<DockPanel> bal oldalához kerül. Adjuk hozzá a következő XAML-markupot,

hogy befejezzük az ablak felhasználói felületének definícióját:

590
Ablak kereteinek készítése beágyazott panelek használatával

<Grid DockPanel.Dock ="Left" Background ="AliceBlue">


<!-- A sarok és az oszlopok definiálása -->
<Grid.columnoefinitions>
<ColumnDefinition />
<Columnoefinition />
</Grid.columnoefinitions>
<Gridsplitter Grid.column ="0" width ="5" Background ="Gray" />
<StackPanel Grid.column="O" verticalAlignment ="Stretch" >
<Label Name="lblspellinginstructions"
Fontsize="14" Margin="l0,10,0,0">
spelling Hints
</Label>
<Expander Name="expanderspelling"
Header ="Try these!" Margin="l0,10,10,10">
<!-- Ezt programozott módon töltjük fel -->
<Label Name ="lblSpellingHints" Fontsize ="12"/>
</Expander>
</StackPanel>

<!-- Ez az a terület, ahova lehet majd gépelni -->


<TextBox Grid.column ="l"
spellcheck.IsEnabled ="True"
AcceptsReturn ="True"
Name ="txtData" Fontsize ="14"
BorderBrush ="Blue">
</TextBOX>
</Grid>

A megvalósitás véglegesitése

A felhasználói felület immár készen van, még implementációt kell biztosíta­


nunk a megmaiadt eseménykezelők számára. Itt van a szóban forgó releváns
kód, amely a fejezet ezen pon�án már nem igényel sok magyarázatot:

public partial class Mainwindow : System.Windows.Window


{

protected void ToolsSpellingHints_click(object sender,


RoutedEventArgs args)
{
string spellingHints = string.Empty;

ll Próbáljunk helyesirási hibát keresni a kurzor aktuális


ll helyénél.
SpellingError error =
txtData.GetspellingError(txtData.caretindex);

591
29. fejezet: Programozás WPF·vezérlőelemekkel

if (error != null)
{
ll Készítsük el a helyesírási javaslatok sztringjét.
foreach (string s in error.Suggestions)
{
sp ell i n gH ints += string.Format(" { O } \n", s) ;
}

ll Mutassuk meg a javaslatokat a Label és az Expander


ll típuson belül.
lb l spelling H ints . Cont e nt = spellingHints;

ll Bontsuk ki a bővítőt.
expanderspelling.IsExpanded tru e;
}
}
protected void MouseEnterExitArea(object sender,
RoutedEventArgs args)
{
statBarText.Text = "Exit the Application";
}
protected void Mous eEnt erToo l s H intsAre a(object sender,
RoutedEventArgs args)
{
statBarText.Text = "Show spelling Suggestions";
}
protected void MouseL e aveAr e a(ob ject sender, Rout edEventAr gs args)
{
statBarText.Text = "Re ady";
}
}

Készen vagyunk! Mindössze néhány sornyi imperatív kóddal (és egy egészsé­
ges adag XAML-lel) elkészültek egy működő szövegszerkesztő alapjai. Ahhoz,
hogy még érdekesebbé tegyük, meg kell ismerkednünk a vezérlő utasításokkal.

A WPF vezérlőutasításai

A fejezet következő főbb egysége a vezérlőutasítások témakörének vizsgálatá­


val foglalkozik. A Windows Presentation Foundation a vezérlőutasítások ré­
vén támogatja azt a jelenséget, amelyet "vezérlőelem-független események­
nek" tekinthetünk. Mint tudjuk, egy tipikus .NET-eseményt adott alaposztály
határoz meg, és csak az az osztály vagy annak egy leszármazottja használhat­
ja. Ezenfelül, a normális .NET-események szarosan kötődnek ahhoz az osz­
tályhoz, amelyben definiálták őket.

592
A WPF vezérlőutasításai

A WPF-vezérlőutasítások ezzel szemben olyan eseményszerű entitások,


amelyek függetlenek egy adott vezérlőelemtől, és sok esetben sikeresen alkal­
mazhatók több (és látszólag nem kapcsolódó) vezérlőelem-típusra. A WPF pél­
dául támoga�a a másolás, a beillesztés és kivágás parancsokat, amelyeket szé­
les körben lehet alkalmazni különféle felhasználóifelület-elemekre (menüele­
mek, eszköztárgombok, egyedi gombok), valamint gyorsbillentyűkre (Ctrl +C,
Ctrl + V stb.).
Míg más felhasználóifelület-eszközrendszerek (mint a Windows Forms)
szabványos eseményeket biztosítanak ilyen célokra, a végeredmény általában
redundáns, és a kódot nehéz karbantartani. A WPF-modell alatt a parancsok
alternatívaként használhaták A végeredmény általában kisebb és rugalma­
sabb kód.

A belső vezérlőelem parancsobjektumok

A WPF számos beépített vezérlő utasítással érkezik, amelyek mindegyikéhez


beállíthatunk gyorsbillentyűt (vagy más beviteli lehetőséget). Programozási
szempontból egy WPF-vezérlőutasítás egy objektum, amely támogat egy
olyan tulajdonságot (gyakran command néven), amely visszaad egy itt látható,
!Command interfészt megvalósító objektumot:

public interface !Command


{
ll Akkor fordul elő, amikor a módosítások befolyásolják, hogy
ll a parancs végrehajtásra kerüljön-e vagy sem.
event EventHandler canExecuteChanged;

ll Definiálja azt a metódust, amely meghatározza, hogy a parancs


ll végrehajtható-e aktuális állapotában.
bool CanExecute(object parameter);

ll Definiálja a parancs indításakor meghívandó metódust.


void Execute(object parameter);
}.

Bár a saját interfészimplementációnkat is megalkothatnánk egy vezérlőutasí­


táshoz, kicsi annak az esélye, hogy erre szükség van, köszönhetőerr az azon­
nal működő öt WPF-parancsobjektumnak, amelyek biztosí�ák ezt a műkö­
dést. Ezek a statikus osztályok több olyan tulajdonságot definiálnak, amelyek
feltárják az !Command parancsot megvalósító objektumokat - általában a
Routedurcommand típust, amely támoga�a a WPF továbbított esemény modellt.

593
29. fejezet: Programozás WPF-vezérlöelemekkel

A 29.4. táblázat az egyes belső parancsobjektumok által kiajánlott alaptu­

lajdonságok közül ismertet néhányat (további részletekért tekintsük át a


.NET Framework 3.5 SDK dokumentációját).

Applicationcommands close, Copy, Cut, Olyan tulajdonságokat de­


Delete, Find, open, finiál, amelyek az alkalma­
Paste, save, saveAll, zásszintű parancsokat kép­
Redo, undo viselik.

componentscommands Moveoown, Azokat a tulajdonságokat


MoveFocusBack, határozza meg, amelyek a
MoveLeft, MoveRight, felhasználói felület elemei
scrollToEnd, által végrehajtott közös uta­
scrollToHome sításokra képezhetők le.

Mediacommands BoostBase, channelup, Olyan tulajdonságokat ha­


channeloown, tároz meg, amelyek lehető­
FastForward, vé teszik, hogy különböző
NextTrack, Play, médiaközpontú vezérlő­
Rewind, select, stop elemek közös parancsokat
adjanak ki.

Navigationcommands BrowseBack, Több olyan tulajdonságot


BrowseForward, definiál, amelyek a WPF
Favorites, LastPage, navigációs modelljét ki­
NextPage, zoom használó alkalmazások ese­
tében használatosak.

EditingCommands Aligncenter, Olyan tulajdonságokat de­


correctspellingError, finiál, amelyeket általában a
oecreaseFontSize, WPF-dokumentum API áJ­
EnterLineBreak, tal feltárt objektumokkal
EnterParagraphBreak, történő programozása ese­
MoveoownByLine, tén használunk.
MoveRightByWord

29.4. táblázat: A belső WPF-vezérlőelem parancsobjektumok

594
A WPF vezérlőutasításai

Utasítások kapcsolása a Command tulajdonsághoz

Ha ezen parancstulajdonságok bármelyikét szeretnénk hozzákapcsoini egy


olyan felhasználóifelület-elemhez, amely támoga�a a command tulajdonságot
(ilyen például egy Button vagy egy Menurtem) , akkor nincs sok tennivalónk.
Ahhoz, hogy lássuk, hogyan történik mindez, módosítsuk a jelenlegi menü­
rendszert úgy, hogy támogasson egy Edit nevű új felső menüelemet, és három
részelemet a szöveges adatok másolásához, beillesztéséhez és kivágásához:

<Menu DockPanel.Dock ="Top"


HorizontalAlignment="Left" Background="White"
BorderBrush ="Black">
<Menurtem Header="_File" click ="FileExit_click" >
<Separator/>
<Menurtem Header ="_Exit" MouseEnter ="MouseEnterExitArea"
MauseLeave ="MouseLeaveArea" click ="FileExit_click"/>
</Menurtem>

<!--új menüelem parancsokkal! -->


<Menurtem Header="_Edit">
<Menurtem Command ="Applicationcommands.Copy"/>
<Menurtem Command ="Applicationcommands.cut"/>
<Menurtem command ="Applicationcommands.Paste"/>
</Menurtem>

<Menurtem Header="_Tools">
<Menurtem Header ="_Spelling Hints"
MouseEnter ="MouseEnterToolsHintsArea"
MauseLeave ="MouseLeaveArea"
click ="ToolsSpellingHints_click"/>
</Menurtem>
</Menu>

Figyeljük meg, hogy minden részelem rendelkezik egy, a command tulajdon­


sághoz rendelt értékkel. Ezáltal a menüelemek automatikusan megkapják a
megfelelő nevet és gyorsbillentyűt (pl. Ctrl + C a másolás művelethez) a me­
nüelem grafikus felületében, így az alkalmazás már imperatív kód nélkül is
képes "másolás, kivágás és beillesztés" műveletekre. Ezért, ha futtatnánk az
alkalmazást, és kijelölnénk valamennyi szöveget, akkor a 29.28. ábrán látható
módon azonnal használhatnánk az új menüelemeket.

595
29. fejezet: Programozás WPF-vezérlőelemekkel

�l
File EditJ Toals
: Exit Copy Ctri+C

---- -c-.rt- -��-ci�x- _l


Sp - Paste Ctrl+V

0Tryth.S.!

Ready

29 28 . . ábra: A parancsobjektumok gazdag funkcionalitást biztosítanak ingyen

Utasitások kapcsolása a felhasználói felület


tetszőleges elemeihez

Ha a felhasználói felület olyan eleméhez szeretnénk utasítást kapcsolni,


amely nem tárnagalja a command tulajdonságot, akkor szükségünk lesz impe­
ratív kódra. Nem túlságosan bonyolult dolog, de egy kicsit több logikát igé­
nyel, mint amennyit a XMAL-ben láthatunk. Például mi történik, ha azt sze­
retnénk, hogy az egész ablak reagáljon az Fl billentyűre, így amikor a vég­
felhasználó megnyomja ezt a gombot, akkor aktiválhalja a társított menü­
rendszert?
Tételezzük fel, hogy a főablak kódfájlja definiál egy új, setFlCommandBinding()
nevű metódust, amelyet a konstruktorban hívunk meg az Initiali zecomponent O
meghívása után. Ez az új metódus programozott módon létrehoz egy új command­
sinding objektumot, amelyet úgy kanfigurálunk, hogy az Applica ti oncommands.
Help lehetőséggel működjön, amely automatikusan reagál az Fl-re:

private void SetFlCommandBinding()


{
Commandsinding helpsinding =

new CommandBinding(Applicationcommands.Help);
helpBinding.canExecute += CanHelpExecute;
helpBinding.Executed += HelpExecuted;
commandBindings.Add(helpBinding);
}

596
A WPF vezérlőutasításai

A legtöbb CommandBinding objektum kezelni akarja majd a canExecute ese­


ményt (amely lehetövé teszi, hogy megadjuk, hogy az utasítást a rendszer a
program működése alapján végrehajtsa-e vagy sem) és az Executed eseményt
(amelyben meghatározha�uk az utasítás végrehajtásakor megjelenített tar­
talmat). Adjuk hozzá a következő eseménykezelőket a window-leszármazott
típusunkhoz (figyeljük meg, hogy a társított metódusreferenciák az egyes
metódusok milyen formátumát követelik meg):

private void canHelpExecute(object sender,


canExecuteRoutedEventArgs e)
{
ll Itt beállíthatjuk a CanExecute false értékét, ha szükség esetén
ll megakadályoznánk, hogy az utasítás végrehajtása megtörténjen.
e.CanExecute = true;
}

private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)


{
MessageBox.show("Dude, it is not that difficult.
Just type something!", "Help!");
}

Itt implementáltuk a canHelpExecute() metódust, és mindig lehetövé tesszük


az Fl súgó megjelenítését a true érték visszaadásávaL Azonban, ha bizonyos
helyzetekben a súgórendszernek nem kellene megjelennie, erről gondoskod­
hatunk, és szükség esetén fal se értéket adhatunk vissza. A He l pExecute()
metódusban megjelenő "súgórendszerünk" alig több, mint egy üzenetdoboz.
Most futtatha�uk az alkalmazásunkat. Ha megnyomjuk az Fl billentyűt,
megtekinthe�ük a 29.29. ábrán látható, felhasználóknak készült útmutató­
rendszerünket (amely nem túl hasznos, és kicsit talán sértő is).

if.; MySpeiiChecker [ol@ll � J


File Edit Tools
: Exit l Check
i,
Spelling Hints
Help! ' ..,
e Trythes<
Dude, it is not that difficult. Just type something!

l
IL Q� -j
___ __

Ready

29.29. ábra: Az egyedi súgórendszerünk

597
29. fejezet: Programozás WPF-vezérlőelemekkel

Forráskód A MySpellChecker kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WPF adatkötési modell

A vezérlőelemek gyakran célpon�ai különböző adatkötési műveleteknek. Az


adatkötés tulajdonképpen vezérlőelem-tulajdonságok kapcsolásának művelete
olyan adatértékekhez, amelyek változhatnak az alkalmazás élettartama so­
rán. Ezáltal a felhasználói interfész eleme megjelenítheti a kódunkban egy
változó állapotát; például:

• checkBox vezérlőelem bejelölését az adott objektum Boolean tulajdon­

ságának alapján.

• Adatok megjelenítését TextBox típusokban egy relációs adatbázis­


táblázatbóL

• Egész számhoz kapcsolt Labelt, amely a fájlok számát muta�a egy


mappában.

A belső WPF adatkötési motor használata során tisztában kell lennünk a kötési
művelet forrása és célja közötti különbséggel. Ahogyan várha�uk, egy adatköté­
si művelet forrása maga az adat (egy Boolean tulajdonság, relációs adat stb.),
núg a cél (vagy célpont) az a felhasználói felületi vezérlőelem-tulajdonság,
amely az adattartalmat használja majd (egy checkBox, egy TextBox stb.).

Megjegyzés Egy adatkötési müvelet cél tulajdonságának egy felhasználóifelület-elem függő·


ségi tulajdonságának kell lennie.

Igazság szerint a WPF adatkötési infrastruktúrájának használata mindig op­


cionális. Ha egy fejlesztő a saját adatkötési logikáját alkalmazná, a forrás és a
cél közötti kapcsolat általában magában foglalná különböző események keze­
lését és imperatív kód létrehozását a forrás és a cél összekapcsolásához. Pél­
dául, ha van egy scrollBar egy olyan ablakon, amelynek Label típuson kell
megjelenítenie értékét, akkor kezelhe�ük a scrollBar valuechange eseményét,
és megfelelően frissíthe�ük a Label tartalmát.

598
A WPF adatkötési modell

Azonban a WPF-adatkötés használatakor közvetlenül XAML-ben (vagy


C#-kód használatával a kódfájlban) kapcsolha�uk össze a forrást és a célt
anélkül, hogy kezelnünk kellene különböző eseményeket, vagy kódolnunk
kellene a kapcsolatokat a forrás és a cél között. Attól függően, hogyan állítot­
tuk fel az adatkötési logikát, biztosítha�uk, hogy a forrás és a cél szinkronban
marad, ha valamelyikük értékei megváltoznak

Ismerkedés az adatkötéssel

Kezdjük el a WPF adatkötési lehetőségeinek vizsgálatát, és tételezzük fel,


hogy van egy új WPF-alkalmazás projektünk (SimpleDataBinding névvel),
amely a következő markupot határozza meg egywindow típus számára:

<Window x:class="SimpleDataBinding.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Simple Data Binding" Height="152" width="300"
WindowstartupLocation="CenterScreen">

<StackPanel width="250">
<Label Content="Move the scroll bar to see the current value"/>

<!-- A görgetősáv értéke az adatkötés forrása -->


<ScrollBar orientation="Horizontal" Height="30" Name="mySB"
Maximum = "100" Largechange="l" smallchange="l"/>

<!-- A címke tartalomértéke az adatkötés célja -->


<Label Height="30" BorderBrush="Blue" BorderThickness="2"
content = "{Binding ElementName=mySB, Path=Value}"
/>
</StackPanel>
</Window>

Vegyük észre, hogy a <ScrollBar> típust (amelynek a mySB nevet adtuk) a O


és 100 közötti tartománnyal konfiguráltuk. Ahogy újrapozícionáljuk a görge­
tősáv csúszkáját (vagy rákattintunk a balra vagy jobbra nyílra), a Label au­
tomatikusan frissül az aktuális értékkel. Az a " ragasztó", amely ezt lehetövé
teszi, a {Binding} jelölő kiterjesztés, amelyet a Label content tulajdonságához
rendelünk hozzá. Itt az El ementName érték képviseli az adatkötési művelet
forrását (a scrollBar objektumot), míg a Path érték jelképeziGelen esetben) a
forráselem megszerezni kívánt tulajdonságát.

599
29. fejezet: Programozás WPF·vezérlőelemekkel

Megjegyzés Az El ementName és a Path elnevezése furcsának tűnhet, ha olyan intuitív ne­


vekre számítottunk, mint a "Source" és a "Destination ". Azonban a fejezet későbbi részében
látni fogjuk, hogy XML-dokumentumok lehetnek egy adatkötési művelet forrásai (általában az
XPath használatával). Ebben az esetben az El ementName és a Path nevek a megfelelőek.

Alternatív formátumként kibővíthetjük a {B inding} jelölő kiterjesztés által


megadott értéket úgy, hogy kizárólagosan beállítjuk a Datacontext tulajdon­
ságot a kötési művelet forrásaként, az alábbiak szerint:

<!-- szétválasztja az objektumot/értéket a Datacontexten


keresztül -->
< L ab e l Hei gh t " 3 0 " BorderBrush="Blue" BorderTh i ckness="2"
=

Datacontext = "{Binding ElementName=mySB}"


content = "{Binding Path=Value}"
/>

Ha futtatnánk ezt az alkalmazást, akkor nagy örömünkre a Label típus anél­


kül frissülne, hogy bármilyen C#-kódot kellene írnunk (lásd a 29.30. ábrát).

D Simple Data Binding l= l @l--


Move the scroll bar to see the current value

29.30. ábra: A ScrollBar érték kötése Label típushoz

A DataContext tulajdonság

A jelenlegi példánkban láttunk két megközelítést adatkötési művelet forrásának


és céljának meghatározására, amelyek ugyanazt a kimenetet eredményezték
Ezen a ponton elgondolkozhatunk, hogy mikor lenne szükségünk a Datacontext
tulajdonság kizárólagos beállítására. Ez a tulajdonság nagyon hasznos lehet,
mert függőségi tulajdonság, ezért az értékét örökölheti a részelemeitőL ily mó­
don könnyedén beállíthatjuk ugyanazt az adatforrást vezérlőelemek családjára
ahelyett, hogy rengeteg redundáns "{B i nding El ementName=X, Path=Y}" XAML­
értéket kéne ismételnünk több vezérlőelemnéL Vizsgáljuk meg az alábbi frissített
XAML-definíciót a jelenlegi <StackPanel> tárolónkhoz:

<!-- Jegyezzük meg, hogy a StackPanel beállítja a Datacontext


tulajdonságot -->
<StackPanel wi dth="250" DataContext = "{B i ndi ng ElementName=mySB}">
<Label Content="Move the scroll bar to see the current value"/>

600
A WPF adatkötési modell

<ScrollBar Orientation="Horizontal" Height="30" Name="mySB"


Maximum = "100" Largechange="l" smallchange="l"/>

<!-- Most a felhasználói felület elemei egyedi módon használják


a görgetősáv értékét -->
<Label Height="30" BorderBrush="Blue" BorderThickness="2"
content = "{Binding Path=Value}"/>

<Button content="Click" Height="200"


Fontsize = "{Binding Path=Value}"/>
</StackPanel>

A Datacontext tulajdonságot itt közvetlenül a <StackPanel> típuson állítottuk


be. Ezért, ha mozgatjuk a csúszkát, nemcsak az aktuális értéket látjuk a Label
elemen, hanem azt is tapasztaljuk, hogy a Button típus mérete nő, illetve
csökken ugyanezen értéknek megfelelően. A 29.31. ábrán láthatunk egy le­
hetséges kimenetet.

u simp� Data 8inding �o.:.��


Move the scroll bar to see the current value

70

--EI-i·c·k--

29. 31. ábra: A Scrol!Bar érték kötése Label és Button típushoz

A Mode tulajdonság

Adatkötési művelet létrehozásakor választhatunk a különböző működési


módok között, ha a Path érték meghatározása során beállítjuk a Made tulaj­
donság értékét Alapértelmezés szerint a Made tulajdonság értéke oneway,
amely azt határozza meg, hogy a célon végzett módosítások nem befolyásol­
ják a forrást. A példánkban a Labe l content tulajdonságának megváltoztatása
nem állítja át a sc roll Bar csúszkájának pozícióját.

601
29. fejezet: Programozás WPF·vezérlőelemekkel

Ha szinkronban szeretnénk tartani a forrás és a cél közti módosításokat,


akkor a Mode tulajdonságot átállíthatjuk Twoway értékre. Így, ha megváltoztat­
juk a Label tartalmának értékét, változik a görgetősáv csúszkájának helyzete
is. Természetesen a végfelhasználó nem módosíthatja a Label tartalmát, mi­
vel az írásvédettként állítottuk be (természetesen programozott módon meg­
változtathatjuk az értéket).
A Twoway mód használatának bemutatásához tételezzük fel, hogy a görge­
tősáv aktuális értékét megjelenítő Label-t az alábbi Text:Box-ra cseréltük (fi­
gyeljük meg a Text: tulajdonság értékét). Ebben az esetben, ha új értéket írunk
a szövegmezőbe, a csúszka pozíciója (és a gomb betűmérete) automatikusan
módosul, amikor a Tab billentyűvel kilépünk a Text:Box objektumból:

<Text:Box Height:="30" BorderBrush="Blue"


BorderThickn ess="2" Text: = "{Binding Pat:h=Value}"/>

Megjegyzés A Mode tulajdonságot OneTime értékre is beállíthatjuk. Ezzel beállíthatjuk a


célt a kezdő érték megadásakor, de a további változtatásokat a rendszer nem követi nyomon.

Adatátalakitás az IValueConverter
segitségével

A scrollBar típus double értéket használ a csúszka értékének ábrázolásához


az elvárt egész szám (vagyis integer) helyett. Ennélfogva, ha mozgatjuk a
csúszkát, akkor különböző lebegőpontos számok jelennek meg a Text:Boxon

belül (például 61.0576923076923), ami nem túl intuitív a végfelhasználó szá­


mára, aki minden bizonnyal egész számokat vár (mint a 61, 62, 63 stb.).
Ha szeretnénk átalakítani egy adatkötési művelet értékét alternatív formá­
tumra, annak egyik módja, hogy létrehozunk egy egyedi osztályt, amely imp­
lementálja a syst:em.windows.Dat:a névtér IValueconvert:er interfészét. Ez az in­

terfész két olyan tagot definiál, amelyek lehetővé teszik a kétirányú konverzió
elvégzését a célpont és a cél között. Amint meghatároztuk ezt az osztályt, se­
gítségéve! tovább minősíthetjük az adatkötési műveletünk feldolgozását.

Megjegyzés Noha bármely adatkötési művelet megvalósítható pusztán imperatív kód haszná·
latával, a következő példák a XAML segítségével konvertálják az adattípusokat. Ehhez szükség
van egyedi erőforrások bevonására, amelyeket részletesen a 30. fejezetben vizsgálunk meg.
Ne nyugtalankodjunk, ha a markup egy része nem tűnik ismerősnek.

602
Adatátalakítás az IValueConverter segítségével

Tételezzük fel, hogy egész számokat szeretnénk megjeleníteni a TextBox ve­


zérlőelemben, így az alábbi osztálytípust hozhatjuk létre (mindenképpen im­
portáljuk a system. windows. Data névteret a definiáló fájlban):

.class MyDoubleconverter : IVal ue converter


{
public object convert(object value, Type targetType,
object parameter,
System.Globalization.Cultureinfo culture)
{
ll A double érték konvertálása egész számra.
double v = (double)value;
return (int)v;
}

public object convertBack(object value, Type targetType,


object parameter,
System.Globalization.cultureinfo culture)
{
ll A bejövő értéket közvetlenül visszaadjuk.
ll Ezt használjuk a kétirányú kötésekhez
ll a példánkban, amikor a felhasználó Tab billentyűvel
ll kilép a TextBox objektumból.
return val ue ;
}
}

Amikor az értéket átadjuk a forrásból (a ScrollBarból) a célnak (a TextBox


Text tulajdonságának), a rendszer a c o nv e rt O metódust hívja meg. Míg több

bejövő paramétert kapunk, ehhez az átalakításhoz csak a bejövő objectet kell


kezelnünk, amely az aktuális double érték. Egyszerűen egész számra kasztol­
juk a típust, és visszaadjuk az új számot.
A convertBack() metódust hívjuk, ha a forrás megkapja az értéket a céltól
(ha engedélyeztük a kétirányú kötésmódot). Itt egyszerűen közvetlenül vissza­
adjuk az értéket. Ezáltal beírhatunk egy lebegőpontos értéket (pl. 99,9) a
TextBox elembe, és automatikusan konvertálhatjuk egész számmá (99), ha a
felhasználó a Tab billentyűvel kilép a vezérlőelembőL Ez a "szabad" konverzió
annak köszönhető, hogy a convert() metódust a convertBack() meghívása
után a rendszer még egyszer meghívja. Ha egyszerűen null értéket adnánk
vissza a ConvertBack() metódusból, akkor úgy tűnne, hogy a kötés nincs szink­
ronban, mivel a szövegdoboz még mindig lebegőpontos számot mutatna!
Most, hogy az osztály a helyére került, vizsgáljuk meg a következő
XAML-módosításokat, amelyek kihasználják az egyedi átalakító osztályun­
kat az adatok megjelenítéséhez a TextBoxban:

603
29. fejezet: Programozás WPF-vezérlőelemekkel

<Window x:Class="SimpleDat:aBinding.Mainwindow"
xmlns="ht:t:p://schemas.microsoft:.com/winfx/2006/xaml/present:at:ion"
xmlns:x="ht:t:p://schemas.microsoft:.com/winfx/2006/xaml"

<!-- Definiálni kell egy CLR-névt:eret:, hogy hozzáférjünk


a típusunkhoz -->
xmlns:myconvert:ers ="clr-namespace:SimpleDat:aBinding"

Tit:le="Simple Dat:a Binding" Height:="334" widt:h="288"


WindowSt:art:upLocat:ion="Cent:erscreen">

<!-- Az erőforrásszótárak lehet:ővé teszik olyan objektumok


definiálását:, amelyek a kulcsuk alapján megszerezhetők.
További részletek a 30. fejezetben > --

<Window.Resources>
<myConvert:ers:MyDoubleconvert:er x:Key="Doubleconvert:er"/>
</Window.Resources>

<!-- A panel beállítja az Datacontext-et a görgetősáv­


objektumra -->
<StackPanel widt:h="250"
Datacontext = "{Binding ElementName=mySB}">

<Label Cont:ent:="Move the scroll bar to see t:he


current: value"/>

<ScrollBar Orientation="Horizontal" Height="30" Name="mySB"


Maximum = "100" Largechange="l" Smallchange="l"/>

<!-- Figyeljük meg, hogy a {Binding} kiterjesztés most beállítja


a converter tulajdonságot -->
<TextBox Height:="30" BorderBrush="Blue"
BorderThickness="2" Name="t:xtThumbvalue"
Text: = "{Binding Pat:h=Value,
Converter={Stat:icResource Doubleconverter}}"/>

<But:t:on cont:ent="Click" Height:="200"


Fontsize = "{Binding Pat:h=Value}"/>

</St:ackPanel>
</Window>

A fenti példában meghatároztunk olyan egyedi XML-névteret, amely leképez­


hető a projektünk gyökémévterébe (lásd a 28. fejezetet), hozzáadtuk a Win­
dows típus erőforrásszótárához a MyDoubleconvert:er típusunk egy példányát,
amelyet később megszerezhetünk a XMAL-fájlban a Doubleconverter kulcsnév
révén. A TextBox Text: tulajdonságát módosítottuk, hogy a MyDoubleconvert: tí­
pusunkat használja, hozzárendeltük a Converter tulajdonságot egy másik,
st:at:icResource nevű jelölő kiterjesztéshez.

604
Adatátalakítás az IValueConverter segítségével

A WPF erőforrásrendszerének további jellemzőit a 30. fejezetben találhat­


juk. Mindenesetre, ha futtatnánk az alkalmazásunkat, akkor azt látnánk,
hogy csak egész számok jelennek meg a TextBox objektumban.

·Konvertálás különböző adattipusok között

Az rvalueconverter interfész implementációja használható bármilyen, adat­


típusok közötti átalakításra, még akkor is, ha úgy tűnik, hogy azok nem kap­
csolódnak egymáshoz. Valójában a scrollBar csúszkájának aktuális értéke
használható arra, hogy visszaadjon bármilyen objektumtípust, hogy hozzá­
kapcsolhassuk egy függőségi tulajdonsághoz. Tekintsük meg az alábbi co­
lorconverter típust, amely a csúszka értékét használja, hogy visszaadja az új,

zöld solidcolorBrush típust (155 és 255 közötti zöld értékkel):

class MyColorconverter : rvalueconverter


{
public object Convert(object value, Type targetType,
object parameter,
system.Globalization.culturernfo culture)
{
ll A csúszka értékének használata egy változó zöld ecset
ll készítéséhez.
double d = (double)value;
byte v = (byte)d;

Color color = new color();


color.A = 255;
color.G = (byte) (155 + v);
return new solidColorBrush(color);
}

public object convertBack(object value, Type targetType,


object parameter,
system.Globalization.cultureinfo culture)
{
return value;
}
}

Ha a következők szerint hozzáadnánk egy új tagot az erőforrásszótárunkhoz:

<Window.Resources>
<myconverters:MyDoubleconverter x:Key="DoubleConverter"l>
<myconverters:Mycolorconverter x:Key="Colorconverter"l>
<IWindow.Resources>

605
29. fejezet: Programozás WPF-vezérlöelemekkel

akkor használhatnánk a kulcsnevet a Button típusunk Background tulajdonsá­


gának beállításához:

<Button Content="Click" Height="200"


Fontsize = "{Binding Path=Value}"
Background= "{Binding Path=Value,
converter={StaticResource colorconverter}}"/>

Ha megint futtatnánk az alkalmazásunkat, akkor a Button színe biztosan vál­


tozna a görgetősáv pozíciója alapján. Hogy a WPF-adatkötések vizsgálatát
befejezzük, nézzük meg, hogyan képezhetünk le egyedi objektumokat és
XML-dokumentum adatokat a felhasználóifelület-rétegünkre.

Forráskód A SimpleDataBinding kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Kötés egyedi objektumokhoz

Az adatkötés következő típusa, amelyet megvizsgálunk, annak módja, aho­


gyan az egyedi objektumok tulajdonságait a felhasználói felületünkhöz kap­
csolha�uk. Kezdjük egy új WPF-alkalmazás projekt létrehozásával (Car­
ViewerApp néven), majd a 28. fejezetben felvázolt lépések segítségével vál­
toztassuk meg a kezdeti Windowl típusunk nevét MainWindow-ra. Ezután
kezeljük a MainWindow Loaded eseményét, és módosítsuk a <Gri d> definíció­
ját, hogy két sort és két oszlopot tartalmazzon:

<Window x:Class="CarviewerApp.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Car Viewer Application" Height="294" width="502"
ResizeMode="NoResize" WindowstartupLocation="CenterScreen"
Loaded="Window_Loaded">
<Grid>
<Grid.columnoefinitions>
<Columnoefinition Width="200"/>
<Columnoefinition width="*"/>
</Grid.columnoefinitions>
<Grid.RowDefinitions>
<Rowoefinition Height="Auto"/>
<Rowoefinition Height="*"/>
</Grid.Rowoefinitions>
</Grid>
</Window>

606
Kötés egyedi objektumokhoz

A <Grid> első sora tartalmaz egy menürendszert File menüvel, amelyben két
almenü van (Add New Car és Exit). Vegyük észre, hogy kezeljük minden
almenü kattintási eseményét, és hozzárendelünk egy "beviteli mozdulatot" az
Exit menühöz, lehetővé téve az elem aktiválását, ha a felhasználó megnyomja az
Alt+ F4 billentyűkombinációt. Végül figyeljük meg, hogy a Grid.columnspan ér­
téke 2, amely lehetővé teszi a menürendszer elhelyezését az első sor celláiban.

<!-- Menüsáv -->


<DockPanel
Grid.Column="O"
Grid.columnspan="2"
Grid.Row="O">
<Menu DockPanel.Dock ="Top" HorizontalAlignment="Left"
Background="White">
<Menurtem He ader="File">
<Menurtem Header="New car" click="AddNewcarwizard"/>
<Separator />
<Menurtem Header="Exit" InputGestureText="Alt-F4"
click="ExitApplication"/>
</Menurtem>
</Menu>
</DockPanel>

A <Grid> fennmaradó bal oldali része egy ListBox típust tartalmazó <Dock­
Panel>, míg a jobb oldali rész egyetlen TextBlack típust foglal magában.
A ListBox típus lesz végül az egyedi objektumok gyűjteményét magába fog­
laló adatkötési művelet célja, úgyhogy állítsuk be az Itemssource tulajdonsá­
got a {Binding} jelölő kiterjesztésre (a kötés forrását hamarosan kódban adjuk
meg). Ahogy a felhasználó kiválaszt egy elemet a ListBox típusból, rögzítjük
a se l ectionchanged eseményt, hogy frissíthessük a tartalmat a TextBlock tí­
pusban. Itt van a fennmaradó típusok definíciója:

<!-- A rács bal oldali ablaktáblája -->


<ListBox Grid.column="O"
Grid.Row="2" Name="allcars"
selectionchanged="Listrtemselected"
Background="L ightBl ue" Itemssource=" {Binding}''>
</ListBOX>

<!-- A rács jobb oldali ablaktáblája -->


<TextBlock Name="txtcarstats" Background="LightYellow"
Grid.column="l" Grid.Row="2"/>

Ezen a ponton az ablak felhasználói felületének úgy kellene kinéznie, mint a


29.32. ábrán látható elemnek.

607
29. fejezet: Programozás WPF-vezérlőelemekkel

Mielőtt megvalósítanánk az adatkötési logikát, véglegesítsük a File > Exit


menü kezelőjét az alábbiak szerint:

private void ExitApplication(object sender, RoutedEventArgs e)


{
Application.current.shutdown();
}

p;'

29 32 ábra: A főablakunk felhasználói felülete


. .

Az ObservableCollection<T> tipus használata

A .NET 3.0 bemutatott egy új gyűjteménytípust a system.collections.object­


Model névtéren belül, observablecollection<T> névvel. Ezen típus használatá­

nak előnye, hogy amikor tartalma megváltozik, figyelmeztetést küld az érde­


kelteknek, például az adatkötési művelet céljának. Szúrjunk be új C#-fájlt az
alkalmazásunkba, amely a carL ist nevű osztályt definiálja, amely bővíti az
observablecollection<T> típust, ahol T valójában car típusú. A car típus ezen

verziója a C# automatikus tulajdonságait használja alap állapotadatok megha­


tározására (amelyeket egyedi konstruktor használatával lehet beállítani), vala­
mint biztosí�a a ToString O metódus megfelelő implementációját

using System;
using System.Collections.objectModel;

namespace carviewerApp
{
public class carList observablecollection<Car>
{

608
Kötés egyedi objektumokhoz

public carListO
{
ll Néhány bejegyzés hozzáadása a listához.
Add(new Car(40, "BMW", "Black", "sidd"));
Add(new car(SS, "'vw", "Black", "Mary"));
Add(new car(lOO, "Ford", "Tan", "Mel"));
Add(new car(O, "Yugo", "Green", "clunker"));
}
}

public class Car


{
public int Speed { get; set; }
public string Make { get; set; }
public string color { get; set; }
public string PetName { get; set; }
public Car(int speed, string make, string color, string name)
{
Speed = speed; Make = make; Color = color; PetName = name;
}
public Car(){}

public override string ToString()


{
return string.Format("{O} the {l} {2} is going {3} MPH",
PetName, Color, Make, speed);
}
}
}

Most nyissuk meg a Mainwindow osztályunk kódfájlját, és definiáljunk egy


carList típusú tagváltozót MyCars névvel. A window típus Loaded eseménykeze­

lőjén belül állítsuk be az allears ListBox Datacontext tulajdonságát a mycars


objektumra (jusson eszünkbe, hogy ezt az értéket nem XAML-ben állítottuk be
a {Binding} kiterjesztéssel, ezért, hogy gyorsítsunk, ezt most C#-kód használa­
tával tesszük meg):

private void Window_Loaded(object sender, RoutedEventArgs e)


{
ll Az adatkontextus beállítása.
allcars.Datacontext = mycars;
}

Most már futtatha�uk az alkalmazásunkat, és látha�uk a ListBox típust,


amely tartalmazza a Tostring() értékeket minden car objektum számára az
egyedi observablecollection<T> típusban (lásd a 29.33. ábrát).

609
29. fejezet: Programozás WPF·vezérlőelemekkel

-�-" Car Viewer Application liiili


File
Sidd � llladt BMW is going 40 MPI
Maly � Biacit VW is going 55 MPH
Mel �y., Ford is going 100 MPH
Clunlcl!r � Gn!en Yugo is going O t.

·L __
lll l •

29.33. ábra: A kezdeti adatkötés művelet

Egyedi adatsablon készftése

A ListBox jelenleg núnden elemet megjelenít a carList objektumban; rrúvel


azonban nem adtunk meg kötési útvonalat, ezért minden listabejegyzés egy­
szerűen a ToString O meghívásának az eredménye az alobjektumokon. Mivel
már megvizsgáltuk, hogyan állapítsunk meg egyszerű kötési útvonalakat,
ezúttal egyedi adatsablont készítünk. Az adatsablon használható arra, hogy
értesítse egy adatkötési művelet célját arról, hogyan jelenítse meg a hozzá
kapcsolt adatokat. A sablonunk a ListBox minden elemét feltölti olyan
<StackPanel> típussal, amely egy Ell i pse objektumból és egy TextBl ock tí­

pusból áll, amelyet hozzákötöttünk a carList típus összes elemének PetName


tulajdonságához. Itt van a L istsox típus módosított markupja.

<Lis-t:Box Grid.column="O"
Grid.Row="2" Name="allcars"
selectionchanged="Listrtemselected"
Background="LightBlue" Itemssource="{Binding}">
<ListBox.rtemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Height="lO" width="lO" Fill="Blue"/>
<TextBlock Fontstyle="rtalic" Fontsize="14"
Text="{Binding Path=PetName}"/>
</StackPanel>
</DataTemplate>
</ListBox.rtemTemplate>
</ListBOX>

610
A felhasználói felület elemeinek kötése XML-dokumentumokhoz

Itt csatoljuk a sablonunkat a L i stBox típushoz a <L i stBox.


<D ataTem pl ate>

rtemTemp late> elem segítségéveL Mielőtt megtekintenénk az adatsablon


eredményét, implementáljuk a L i stBox típusunk select i onchan ged kezelőjét,
hogy megjelenítsük az aktuális kijelölés ToString() metódusát a leginkább
jobb oldali TextBlock típusban:

private void Listitemselected(object sender,


selectionchangedEventArgs e)
{
ll A megfelelő autó beolvasása az observablecollection
ll gyűjteményből a listadobozból kiválasztott eleme alapján.
ll Majd a ToString() metódus meghívása.
txtCarStats.Text = mycars[allcars.Selectedrndex].ToString();
}

Ezzel a módosítással most már stílusosabb külsőt kapott az adatok megjelení­


tése (lásd a 29.34. ábrát).
-��-�
fj Car Vi.-r Application
�-� ."r';!�·�._..r·�·�..-..-- .....s-..--c--=.=-
..
File
.Sidd lunker the Green Yugo is going O MPH
eHaty
eMel

29.34. ábra: Adatkötés egt;edi adatsablonnal

A felhasználói felület elemeinek kötése


XML-dokumentumokhoz

A következő feladat olyan egyedi párbeszédablak készítése, amely adatkötést


használ egy külső XML-fájl tartalmának megjelenítéséhez egy stilizált Listview
objektumban. Először szúrjuk be a 24. fejezet NavigationWithLinqToXml pro­
jektjében létrehozott rnventory.xml fájlt a Project > Add Existing Item menü­
pont segítségéveL Válasszuk ki az elemet a Solution Explorerben, és a Pro-

611
29. fejezet: Programozás WPF-vezérlőelemekkel

perties ablak használatával állítsuk a Copy to Output Directory opciót a Copy


Always értékre. Ez biztosítja, hogy az alkalmazásunk lefordítása során az rn­

ventory.xml fájlt a rendszer átmásolja a \bin\Debug könyvtárunkba.

Egyedi párbeszédablak készítése

Szúrjunk be új WPF Window típust a projektbe (AddNewCarDialog néven) a


Visual Studio 2008 Project > Add Window menüpontjának használatávaL Az
új Window jeleníti meg az rnventory.xml fájl tartalmát egy testre szabott
Listview típusban az adatkötés segítségéveL Az első lépés a XAML megírása,

hogy meghatározzuk az új ablak megjelenését és működését. Íme, a teljes


markup, az elemzéssei együtt:

<Window x:class="CarviewerApp.AddNewcaroialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AddNewcarDialog" Height="234" width="529"
ResizeMode="NoResize" windowstartupLocation="CenterScreen" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="144" />
<RowDefinition Height="51" />
</Grid.RowDefinitions>

<!-- Az xmlDataProvider használata - ->


<Grid.Resources>
<XmlDataProvider x:Key="CarsxmlDoc"
source="Inventory.xml"/>
</Grid.Resources>

<!-- Most készítsünk egy adatrácsot, képezzük le az


attribútumokat/elemeket az oszlopokra az XPath kifejezések
használatával -- >

<Listview Name="lstCars" Grid.Row="O"


rtemssource="{Binding source={StaticResource carsxmlooc},
XPath=/Inventory/Car}">
<Listview.view>
<GridvieW>
<Gridviewcolumn width="lOO" Header="ID"
DisplayMemberBinding="{Binding XPath=@cariD}"/>
<Gridviewcolumn Width="lOO" Header="Make"
oisplayMemberBinding="{Binding XPath=Make}"/>
<Gridviewcolumn width="lOO" Header="Color"
DisplayMemberBinding="{Binding XPath=Color}"/>
<Gridviewcolumn width="150" Header="Pet Name"
DisplayMemberBinding="{Binding XPath=PetName}"/>

612
A felhasználói felület elemeinek kötése XML-dokumentumokhoz

</Gridview>
</Listview.view>
</Listview>

<WrapPanel Grid.Row="l">
<Label content="Select a Row to Add to your car collection"
Margin="lO" />
<Button Name="btnOK" Content="OK" width="80" Height="25"
Margin="lO" IsDefault="True" Tabrndex="l"
click="btnOK_Click"/>
<Button Name="btncancel" content="Cancel" width="80"
Height="25" Margin="lO" IsCancel="True" Tabrndex="2"/>
</WrapPanel>
</Grid>
</Window>

Kezdjük az elején, és figyeljük meg, hogy a nyitó <Window> elemet a Resize­


Made attribútum NeResize értékre állításával definiáltuk, mivel a legtöbb pár­
beszédablak nem teszi lehetővé a felhasználó számára az ablak méretének
megváltoztatását.
Azon túl, hogy a <Grid> típusunkat adott méretű sorokra bontjuk, a kö­
vetkező érdekes pont, hogy a rács erőforrásszótárában elhelyezünk egy xml­
DataProvider típusú új objektumot. Ezt a típust hozzákapcsolhatjuk külső
* .xml fájlhoz (vagy egy XML-databkához a XMAL-fájlban) a Source attribú­
tum segítségéveL Mivel úgy kanfiguráltuk az Inventory. xml fájlt, hogy a je­
lenlegi projektünk alkalmazáskönyvtára tárolja, nem kell foglalkoznunk egy
rögzített elérési út kódolásávaL
A markup igazán nagy része a L istview típus definíciójában található.
Először is, figyeljük meg, hogy az Itemsource attribútumot hozzárendeltük a
carsxmlDoc erőforráshoz, amelyet az XPath attribútum használatával minősí­
tettünk. A tapasztalataink alapján tudhatjuk, hogy az XPath olyan XML-tech­
nológia, amely lekérdezésszerű szintaxis használatával lehetővé teszi a navi­
gálást egy XML-dokumentumon belül. Itt azt mondjuk, hogy a kezdeti adat­
kötési útvonal az <Inventory> gyökér <Car> elemével kezdődik.
Ahhoz, hogy tájékoztassuk a L istview típust arról, hogy rácsszerű front
endet jelenítsen meg, <Listview.view> elemet kell használnunk négy <Grid­
Viewcolumns> típusból álló <Gridvi ew> definiálására. Ezen típusok mindegyi­
ke meghatároz egy Header értéket (megjelenítési célból), és legfőképp egy
Di spl ayMemberBi nding adatkötési értéket. Mivel maga a <Listview> már meg­
adta, hogy a kezdeti elérési útvonal az XML-dokumentumon belül az <Inven­
tory> elem <Car> részeleme legyen, ezért minden XPath kötés ezt használja
kezdőpontként az oszloptípusok esetében.

613
29. fejezet: Programozás WPF-vezérlöelemekkel

Az első <Gridviewcolumn> jeleníti meg a <Car> elem ID afuibúturnát XPath­


specifikus szintaxis használatával az attribútumértékek (@caro ) kiszedéséhez.
A megmaradó oszlopok egyszerűen tovább minősítik az elérési útvonalat az
XML-dokumentumban úgy, hogy a {Bindi ng} markupbővítmény XPath mi­
nősítőjének használatával csatolják a következő részelemet
Végül, de nem utolsósorban, a felhasználói felület befejezéséhez a <Grid>
utolsó sorában található a <WrapPanel> típus, amely két Button (és egy leíró
Label ) típust tartalmaz. Az egyetlen érdeklődésre számot tartó pont, hogy
kezeljük az OK gomb kattintási eseményét, valamint használjuk az IsDefaul t
és az Iscancel tulajdonságokat. Ezek állapítják meg, hogy az ablakon lévő
gombok közül melyik reagáljon a kattintási eseményre, ha a felhasználó
megnyomja az Enter, illetve az Esc billentyűket
Végül figyeljük meg, hogy ezek a Button típusok megadnak egy Tabindex
és egy Margi n értéket, az utóbbi lehetővé teszi a <WrapPanel> típusban lévő
elemek körüli tér beosztását.

A DialogResult érték hozzárendelése

Mielőtt megjelenítenénk ezt az új párbeszédablakot, implementálnunk kell az

OK gomb kattintási eseménykezelőjét A Windows Forrnshoz hasonlóan (lásd


a 27. fejezetet) a WPF-párbeszédablakok tájékoztathatják a hívót a o i a l ogResul t
tulajdonság révén, hogy a felhasználó melyik gombra kattintott. Azonban a
Windows Forms oi alogResult tulajdonságával ellentétben, a WPF-modellben ez
a tulajdonság egy nullázható logikai értékkel működik, nem pedig egy erősen
típusos felsorolással. Ezért, ha tájékoztatni szeretnénk a hívót, hogy a felhasz­
náló alkalmazná a párbeszédablakban lévő adatokat a programban (amit álta­
lában egy OK, egy Igen vagy egy Elfogad gombra kattintással jelez), állítsuk be
az örökölt oialogResult tulajdonságot true értékre az említett gomb kattintási
eseménykezelőjében:

private void btnOK_Click(object sender, RoutedEventArgs e)


{
DialogResult = true;
}

Mivel a o ia logResult alapértelmezett értéke false, ezért nem kell tennünk


semmit, ha a felhasználó a Cancel gombra kattint.

614
A felhasználói felület elemeinek kötése XML-dokumentumokhoz

Az aktuális kiválasztás megszerzése

Végezetül adjunk hozzá egyedi írásvédett tulajdonságot az AddNewcaroial o g


párbeszédablakhoz Select:edcar néven, amely új car objektumot ad vissza a
hívónak a rács kiválasztott sorának értékei alapján:

pu bli c Car select:edcar


{
get:
{
ll A kiválasztott: elem kaszt:olása a rácson xmlElement: t:ípusra.
Sy st: e m .xml.XmlElem e nt: carRow =
(Sy st: e m .xml.x ml E l e m e nt:)l s t:cars . se lect:e drt:em;

ll Győzödjünk meg róla, hogy a felhasználó kiválasztott: valamit:!


if (carRow == n ull )
{
ret:urn null ;
}
e lse
{
ll véletlenszerű sebesség generálása.
Random r = new Rand o m ();
int: speed = r.Next:(lOO);

ll új car elemet: adunk vissza a választott: xmlElement/sebesség


ll adatai alapján.
ret:urn .new car(speed, carRow["Make"].InnerText:,
carRow["color"].InnerText:, carRow["Pet:Name"].Innenext:);
}
}
}

Vegyük észre, hogy xmlEl ement: típusra kasztoltuk a sel ect:edrt:em tulajdonság
(amely syst:em.object: típusú) visszatérési értékét. Ez azért lehetséges, mert a
List:view típusunk valóban kapcsolódik az rnvent:ory.xml fájlhoz az adatkötési
műveletünk révén. Amint elcsíptük az aktuális xmlEl emen t: típust, hozzáfér­
hetünk a Make, a color és a PetName elemekhez (a típusindexelő használatá­
val), és kibonthatjuk az értékeket az Innenext meghívásával.

Megjegyzés Ha soha nem dolgoztunk a system.Xml névtér típusaival, akkor elég azt tud­
nunk, hogy az Innenext tulajdonság m�gszerzi az értéket egy XML-csomópont nyitó és záró
elemei között. Például a <Make>Ford</Make> belső szövege Ford lenne.

615
29. fejezet: Programozás WPF-vezérlőelemekkel

Egyedi párbeszédablak megjelenitése

Most, hogy a párbeszédablakunk elkészült, elindíthatjuk azt a File > Add


New Car menüelem kattintási eseménykezelőjéből:

private void AddNewcarwizard(object sender, RoutedEventArgs e)


{
AddNewcarDialog dlg = new AddNewcarDialog();
if (true == dlg.showDialog())
{
if (dlg.selectedcar != null)
{
mycars.Add(dlg.selectedcar);
}
}
}

A Windows Forrnshoz hasonlóan egy WPF-párbeszédablak megjelenhet mo­


dális párbeszédablakként (a showDialogO meghívásával) vagy nem modális
párbeszédablakként (a show() metódus meghívásával). Ha a showDialogO
visszatérési értéke true, akkor kérjük az új Car objektumot a párbeszédablak­
tól, és adjuk hozzá az observablecollee tion <T> típusunkhoz. Mivel ez a gyűj­
teménytípus értesítéseket küld, ha a tartalma megváltozik, látni fogjuk, hogy
a ListBox típusunk automatikusan frissíti magát, amikor új elemeket szúrunk
be. A 29.35. ábra mutatja az egyedi párbeszédablakunk felhasználói felületét

Color Pet Name

o Ford Blue Chuck

WI/ Silver

2 Yugo Pink Gipper

55 Ford Vellaw Max

98 BMW Black Zippy

Select a Row to Add ta you r car collection OK j Cancel l

29.35. ábra: Egyedi adatrács, hozzákötve eg�j XML-dokumentumhoz

Forráskód A CarViewerApp kódfájlokat a forráskódkönyvtár 29. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

616
Összefoglalás

Ezzel befejeztük a WPF adatkötési motorjának és a felhasználói felület API­


ban található alapvető vezérlőelemeinek vizsgálatát. A következő fejezetben
lezárjuk a Windows Presentation Foundation tanulmányozását, megvizsgál­
juk a grafikus renderelés, az erőforráskezelés és az egyedi témák készítésé­
nek szerepét.

Összefoglalás

Ez a fejezet áttekintette a WPF-vezérlőelemek néhány jellemzőjét, kezdve a


függőségi tulajdonságok és a továbbított események elemzéséveL Ezek a
WPF-mechanizmusok nagyon fontosak a WPF-programozás több szempont­
jából, beleértve az adatkötést, az animációs szolgáltatásokat és más funkció­
kat. A fejezet folyamán kanfigurálhattunk és átszabhattunk többféle vezérlő­
elemet, és megtanultuk elrendezni a felhasználói felület tartalmát különböző
paneltípusokban.
Még fontosabb, hogy megvizsgáltuk a WPF-utasítások használatát Emlé­
kezzünk rá, hogy ezek a vezérlőelem-független események hozzácsatolhatók a
felhasználói felület egy eleméhez vagy egy beviteli mozdulathoz, hogy auto­
matikusan örököljék a kész működő szolgáltatásokat (mint a vágólap-művele­
tek). Beleástuk magunkat a WPF adatkötési motor mechanikájába is, és megta­
nultuk, hogyan köthetünk tulajdonságértékeket, egyedi objektumokat és XML­
dokumentumokat a felhasználóifelület-réteghez. Megtanultuk azt is, hogyan
készítsünk WPF-párbeszédablakokat, és felfedeztük az IValueconverter és az
observabl ecoll e ct i on<T> típusok szerepét.

617
HARMINCADIK FEJEZET

WPF 20 grafikus renderelés,


erőforrások és témák

Ebben a fejezetben három független, de egymással kapcsolatban álló témával


foglalkozunk, amelyek segítségével az előző két fejezetben vizsgált progra­
moknál kifinomultabb Windows Presentation Foundation (WPF) alkalmazáso­
kat készíthetünk Elsőként a WPF kétdimenziós grafikus programozási API-kat
tanulmányozzuk Több módszert vizsgálunk meg, amelyekkel kétdimenziós
geometriai képeket jeleníthetünk meg (alakzatokkal, rajzokkal és vizuális esz­
közökkel), és olyan egyszerű grafikai eszközökkel dolgozunk, mint az ecset és
a toll. Vizsgálódásaink során megismerkedünk a WPF animációs szolgáltatásai­
val és a system. windows. Me di a. Anim ati on névtér típusaival.
Miután elsajátítottuk a WPF kétdimenziós grafikus renderelésil animációs
alapismereteit, áttaimlmányozzuk azokat a lehetőségeket, amelyeket az alkal­
mazás-erőforrások létrehozása, beágyazása és referenciái terén biztosít a WPF.
Általános értelemben az " alkalmazás-erőforrás" sztringtáblázatokat, képfájlo­
kat, ikonokat és az alkalmazások által használt, nem forráskód alapú entitáso­
kat jelent. Míg ez a WPF esetén igaz, az "erőforrás" képviselhet egyedi grafikus
és felhasználóifelület-objektumokat is, amelyeket későbbi használat céljából
szeretnénk beágyazni egy szerelvénybe.
A fejezet utolsó témaköre kapcsolatot teremt két, látszólag nem összefüg­
gő terület között: megvizsgáljuk, hogyan definiálhatunk stílusokat és sablo­
nokat vezérlőelemeink számára. Látjuk majd, hogy a stílusok és a sablonok
létrehozása szinte mindig a WPF grafikus renderelés j animációs szolgáltatá­
sainak átfogó alkalmazását jelenti, valamint azt, hogy a szolgáltatásokat al­
kalmazás-erőforrásként a szerelvényünkbe csomagoljuk

Megjegyzés A 28. fejezetből emlékezhetünk arra, hogy a WPF széles körű támogatást bizto·
sít a háromdimenziós programozás számára. Ez a téma azonban e könyv hatókörén kívül esik,
ha a WPF ezen funkciójával kapcsolatban további részletekre lenne szükségünk, érdemes elol·
vasnunk Matthew MacDonald Pro WPF in C# 2008: Windows Presentation Foundation with .NET
3.5, Second Edition (Apress, 2008) című könyvét.
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

A WPF grafikus renderetési


szolgáltatásának filozófiája

A WPF a grafikus renderelés különleges típusát, az úgynevezett visszatartott


üzemmódú grafikát alkalmazza. Ez annyit jelent, hogy mivel XAML vagy for­
ráskód segítségével generáljuk a grafikus rendereléseket, a WPF feladata
ezen képi elemek rögzítése, valamint az elemek optimális újrarajzolásának és
frissítésének biztosítása. Így a grafikus adatok renderelése során a WPF fo­
lyamatosan jelen van, függetlenül attól, hogy a felhasználó az ablak átmére­
tezésével, kicsinyítésével, másik ablak kitakarásával elrejti-e a képet.
Ezzel éles ellentétben, a korábbi Microsoft grafikus renderelési API-k (a
GDI+ is) azonnali üzemmódú grafikus rendszerek voltak. Ebben a modellben a
programozónak kellett gondoskodnia arról, hogy a megjelenített képi ele­
mekre a rendszer az alkalmazás élettartama alatt megfelelően "emlékezzen"
és frissítse azokat. Emlékezzünk rá a 27. fejezetből, hogy a GDI+ alatt egy
téglalap megjelenítéséhez kezelnünk kell a Paint eseményt (vagy felüldefini­
álhatjuk a virtuális onPaint() metódust). A téglalap rajzolásához be kell ol­
vasnunk a Graphics objektumot, és rendkívül fontos elkészítenünk a megfele­
lő infrastruktúrát - például tagváltozókat kell létrehozni, amelyek a téglalap
pozícióját képviselik, valamint a programban meg kell hívni az Inval i date()
metódust -, amely biztosítja, hogy a rendszer rögzítse a képet, ha a felhasz­
náló átméretezi az ablakot. Az azonnali üzemmódú grafikáról a visszatartott
üzemmódú grafikára váltás valóban hasznos előrelépés volt, mert így a prog­
ramozóknak kevesebb grafikus kódot kell írniuk és karbantartaniuk
Azonban ez nem jelenti azt, hogy a WPF grafikus API teljesen különbözik
a korábbi renderelési eszközrendszerektőL Például a GDI+ rendszerhez ha­
sonlóan, a WPF különböző ecset- és tolltípusokat támogat, kódolás segítsé­
gével lehetővé teszi a grafikák futásidejű megjelenítését, találattesztelési
módszereket biztosít, és így tovább. Ha rendelkezünk GDI+ (vagy C/C++­
alapú GDI) ismeretekkel, akkor bizonyára sokat tudunk arról, hogyan lehet a
WPF segítségével alapvető renderelési műveleteket végrehajtani.

620
A WPF grafikus renderetési szolgáltatásának filozófiája

A WPF grafikus renderetési tehetőségei

A WPF-fejlesztés más jellemzőihez hasonlóan, a XAML- vagy a C#-forráskód


mellett több lehetőség áll rendelkezésünkre, amikor azt próbáljuk eldönteni,
hogyan hajtsuk végre a grafikus renderelést. A WPF a grafikus adatok megje­
lenítésének három különböző módját biztosítja:

• system. windows. shapes: A névtér több típust definiál, amelyek kétdi­

menziós geometriai objektumok (téglalapok, ellipszisek, sokszögek)


megjelenítését teszik lehetővé. A típusok használata rendkívül egyszerű,
de alkalmazásuk jelentős többletköltséggel jár.

• system.windows. Medi a. Drawing: Ez az absztrakt ősosztály több egyszerű

szolgáltatáskészletet definiál leszármazott típusok (Geometryorawing,


rmageorawing stb.) számára.

• system.windows. Med ia.visual: Ez az absztrakt ősosztály a grafikus ada­

tok megjelenítésének lehető legkönnyebb megközelítését biztosítja;


azonban tekintélyes mennyiségű forráskód elkészítését követeli meg.

A motiváció, amely ugyanazon feladat (például a grafikus adatok megjelení­


tése) végrehajtásának három különböző módját biztosítja, a memória haszná­
lattal és az alkalmazás teljesítményével hozható összefüggésbe. Mivel a WPF
grafikaiművelet-intenzív rendszer, az alkalmazások nem ritkán több száz kü­
lönböző képet jelenítenek meg az ablak felületén, és a megvalósítás módjá­
nak kiválasztása (alakzatok, rajzok, vizuális eszközök) jelentős hatást gyako­
rol a végeredményre.
Készítsük elő a terepet a következő témakörök számára, és kezdjük az
egyes lehetőségek rövid áttekintéséveL Haladjunk a "legnehezebb"-től a "leg­
könnyebb" felé. Ha szeretnénk a lehetőségeket saját magunk kipróbálni, hoz­
zuk létre a WPFGraphicsOptions nevű új WPF Windows alkalmazást, a kez­
detiwindow típusunk nevét módosítsuk Main Window-ra, és az ablak kezdeti
XAML <Grid> definícióját cseréljük ki <StackPanel>-re:

<Window x:class="WPFGraphicsoptions.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPFGraphicsOptions" Height="300" width="300" >
<StackPanel>

</StackPanel>
</Window>

621
30. fejezet: WPF 2D grafikus renderelés, erőforrá sok és témák

A Shape leszármazott tipusainak használata

A System. windows. shapes névtér tagjai (Ellipse, L i ne, Path, Pol ygon, Po l y l i ne

és Rectangle) biztosítják a kétdimenziós képek megjelenítésének legegysze­


rűbb módját, és alkalmasak viszonylag ritkán használt képek (például stili­
zált gomb egy régiójának) renderelésére vagy felhasználói együttműködést
támogató grafikus kép alkalmazására. A típusok használata során rendsze­
rint "ecsetet" kell választanunk a belső terület kitöltésére, és " tollat" a határ­
vonal rajzolásához (a WPF ecset és toll opcióit a fejezet későbbi részében tár­
gyaljuk). Az alakzattípusok alapvető használatának bemutatására adjuk a
következő kódot a <StackPanel> típusunkhoz, és eredményként egy kék kör­
vonallal rendelkező világoskék téglalapot kapunk:

<!-- Rajzoljunk téglalapot a shape típusok segítségével -->


<Rectangle Height="55" width="lOS" Stroke="Blue"
StrokeThickness="S" Fill="LightBlue"/>

Noha használatuk nevetségesen egyszerű, a típusok- szülőosztályuknak kö­


szönhetően - elég terjedelmesek, rnivel a system. windows. shapes. .shape osztály
rninden lehetséges igénynek igyekszik megfelelni. A származtatási láncban a
shape rengeteg szolgáltatást örököl a szülőosztályok hosszú sorától: a shape
"
"az-egy FrameworkElement, amely egy UIElement, amely egy visual, amely egy
Dependencyobj ect, Dispatcherobj ect és obj ect!
Ezek az ősosztályok stílus- és sablon-, adatkötés-, valarnint erőforráskeze­
lés-támogatást biztosítanak a leszármazott típusok számára, és lehetővé teszik
több esemény küldését, a billentyűzet- és az egérbemenet felügyeletét, vala­
rnint összetett elrendezéskezelési és animációs szolgáltatásokat nyújtanak
A 30.1. ábra a shape típus szárrnaztatási láncát mutatja be a Visual Studio ob­
jektumböngészőjének segítségéveL

á� Shape
é-D Base Types
éJ. ..� FramoworkEiement
$...-o lFramework:InputEiement
: ·· -<> llnputEiement

!···'"" ISupportlnitialize
á'!$ U!Element
.
L . _., IAnimatable

: .. ...." DnputEiement
G·� Visual
B·� DependencyObject
á··"!$ DispatcherObject
L.� Object
30. 1. ábra: A Shape leszármazott típusai átfogó funkcionalitást örökölnek szülótípusaiktól

622
A WPF grafikus renderetési szolgáltatásának filozófiája

Noha minden szülőosztály újabb funkcionalitással bővíti az osztályt, a u rEle­


ment kiemeit jelentőséggel bír. A UI El ement például több mint 80 eseményt de­
finiál, amelyek a bemenet különböző formáit (egér, billentyűzet, Tablet PC-k
stylusceruzája) kezelik. A FramewarkEl ement egy másik fontos típus, mivel az
egérkurzor módosítását, az objektum élettartamát képviselő különböző ese­
ményeket, környezetfüggő menük támogatását stb. biztosító tagokkal ren­
delkezik. Ennek fényben a shape-leszármazott típusai ugyanolyan sokoldalú­
ak, mint más felhasználóifelület-elemek, például a Button, a ProgressBar és
más vezérlőelemek
A lényeg, hogy a shape-leszármazott típusainak használata egyszerű és

hatékony, de ugyanezen tény miatt ezek a típusok nyúj�ák a kétdimenziós


grafikus renderelés legnehézkesebb módját. A típusok alkalmazása helyénvaló
"
"alkalmi renderelés esetén (amelynek definíciója inkább megérzésen, nem
tudományos alapokon nyugszik), illetve akkor, ha valóban olyan grafikus
renderelésre van szükség, amely felhasználói együttműködést igényel. Ha
grafikaiművelet-intenzív alkalmazást készítünk, valószínűleg találkozunk
azokkal a teljesítménybeli előnyökkel, amelyeket a Drawing leszármazott tí­
pusainak használata biztosít.

A Drawing leszármazott tipusainak használata

A system.windows.Media.Drawing absztrakt ősosztály egy kétdimenziós felület

lecsupaszított vázát képviseli. A leszármazott típusok (például a Geometry­


Drawing, az rmageDrawing és a videoDrawing) egyszerűbbek, mint a shape-leszár­

mazott osztályai, mert származtatási láncukból a UIElement és a FramewarkEle­


ment is hiányzik. Emiatt a Drawing leszármazott típusai nem rendelkeznek belső

támogatással a bemeneti események kezelésére (noha programozott módon


végrehajthatunk találattesztelési logikát); azonban a típusok animálhatók, mi­
vel az Animatable osztály a származtatási lánc tagja.

$-�DDJ
i 8 Q Base Types
. Ó·<1$ Anímatable
G··� Freezable
. 9 ..� OependencyObject
8-·<1$ DíspatcherObject
L� Object
L._._" lAnímatable
30. 2. ábra: A Drawing leszármazott típusai jóval egyszerűbbek, mint a Shape leszármazott típusai

623
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Másik lényeges különbség a Drawing és a shape leszármazott típusai között,


hogy a Drawing leszármazott típusai nem képesek önmaguk megjelenítésére,
mivel nem a UI Element leszármazottai! A leszármazott típusokat a tartalmuk
megjelenítéséhez egy hasztoló objektumban (orawingimage, DrawingBrush vagy
Drawingvisual ) kell elhelyezni. Az összetevők szétválasztásának az az eredmé­
nye, hogy a Drawing leszármazott típusai egyszerubbek a shape leszármazott
típusainál, és közben a típusok megtartják a kulcsfontosságú szolgáltatásokat.
Anélkül, hogy túlságosan elmerülnénk a részletekben, gondoljuk végig, az
előző Rectangle objektumot hogyan lehetne rajzolásközpontú típusok segítsé­
gével rendereini (ha a könyv példáit követjük, helyezzük az alábbi markupot
közvetlenül az előző <Rectangle> típusunk után):

<!-- Rajzoljunk téglalapot a Drawing típusainak segítségével -->


<Image Height="55" width="105">
<Image.source>
<Drawingimage>
<Drawingimage.Drawing>
<GeometryDrawing Brush="LightBlue">
<Geometryorawing.Pen>
<Pen Brush="Blue" Thickness="S"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,50"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</Drawingimage.Drawing>
</Drawingimage>
</Image.source>
</Image>

A kimenet megegyezik az előző <Rectangl e> típus kimenetével, de ez nyil­


vánvalóan sokkal részletesebb. A klasszikus "hosszabb kód a jobb teljesít­
mény érdekében" dilemmával állunk szemben. Szerencsére, ha XAML grafi­
kus tervező eszközöket (például a Microsoft Expression Blend vagy a Micro­
soft Expression Design eszközt) alkalmazunk, a rendszer a színfalak mögött
elkészíti a mögöttes markupot (a Microsoft Expression termékcsalád részletes
ismertetését a 28. fejezetben találjuk).

A Visual leszármazott tipusainak használata

Az absztrakt system.windows.Media. visual típus szolgáltatások minimális, de


teljes készletét (renderelés, találattesztelés, átalakítás) biztosítja a leszármazott
típus rendereléséhez, de nem nyújt támogatást további nem vizuális szolgálta­
tásokhoz, ez a forráskód felduzzadásához vezethet (bemeneti események, el-

624
A WPF grafikus renderetési szolgáltatá sának filozófiája

rendezési szolgáltatások, stílusok és adatkötés). Ennek köszönhetően a visual


leszármazott típusai (orawingvisual, viewport3DVisual és containervisual ) a
grafikus renderelési opciók közül a legegyszerűbb lehetőséget jelentik, és a leg­
jobb teljesítményt biztosí�ák. Tanulmányozzuk a Visual típus egyszerű szár­
maztatási láncát a 30.3. ábrán!

$'t:-
' EH:::l Base Types
ÉJ·'f: DependencyObject
8-� DispatcherObject
L� Object

30.3. ábra: A Visual típus alapvető találattesztelést, koordináta-átalakítást és


határolódoboz-számításokat biztosít

Mivel a Visual típus "büszkélkedhet" a legszegényesebb funkcionalitással,


csak korlátozottan támoga�a a direkt XAML-definíciókat (hacsak nem olyan
típusban alkalmazzuk a visual típust, amely kifejezhető XAML-kóddal). Ezen
típusok alkalmazása hasonlít a GDijGDI+ renderelési API-k használatához,
mivel a típusokat gyakran forráskód segítségével kezelhe�ük. Ha így járunk
el, az ablakot képviselő objektumgráfot manuálisan népesí�ük be egyedi Vi­
sual-leszármazott típusokkaL Ehhez felül kell definiálnunk különböző vir­
tuális metódusokat, amelyeket a WPF grafikus rendszer azért hív meg, hogy
kiderítse, hány elemet kell renderelnie, és ismernie kell magát a megjeleníteni
kívánt Visual elemet is.
Vizsgáljuk meg, hogyan alkalmazha�uk a visual leszármazott típusait
kétdimenziós adatok megjelenítéséhez, ehhez nyissuk meg a főablak forrás­
kódfájlját, majd alakítsuk megjegyzésre a teljes definíciót (így később gyorsan
és kevés erőfeszítéssel visszaállítha�uk a kódot):

/*
public partial class Mainwindow : System.windows.window
{
public Mainwindow()
{
In itializecompon ent();
}
}
*/

Hozzuk létre a következő window-leszármazott típust, amely közvetlenül az


ablak felületén jelenít meg egy téglalapot, és kihagyja a XAML-markupban
definiált tartalmat (az előző XAML-leírásokat a rendszer figyelmen kívül
hagyja, és nem jeleníti meg):

625
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

public partial class Mainwindow : System.windows.window


{
ll Az egyetlen rajzolási vizuális elem.
private Drawingvisual rectvisual = new Drawingvisual();
private const int Numberofvisualrtems = l;

public Mainwindow()
{
Initializecomponent();

ll A téglalap létrehozásának segédfüggvénye.


createRectvisual();
}

private void createRectvisual()


{
u sin g (Drawingcontext drawctx rectvisual.Renderopen ())
=

{
ll A téglalap felső, bal oldali, alsó és jobb oldali
ll pozíciója.
Rect rect = new Rect(50, 50, 105, 55);
drawctx.DrawRectangle(Brushes.AliceBlue,
new Pen(Brushes.Blue, 5), rect);
}

ll Regisztráljuk a vizuális elemet az objektumfában,


ll így biztosítva, hogy támogassa az irányított eseményeket,
ll a találattesztelést stb.
Addvisualchild(rectvisual);
AddLogicalchild(rectvisual);
}

ll A szükséges felüldefiniálások. A WPF grafikus rendszer


ll meghívja ezeket a metódusokat, és kideríti, hogy
ll hány elemet és pontosan mit kell renderelnie.
protected override int visualchildrencount
{
get { return Numberofvisualrtems; }
}

protected override visual Getvisualchild(int index)


{
ll A gyűjtemény O alapú, tehát vonjunk le l-et.
if (index != (Numberofvisualrtems - l))
throw new ArgumentoutofRangeException("index",
"oon't have that visual!");
return rectvisual;
}
}

626
A WPF grafikus renderetési szolgáltatásának filozófiája

Vegyük észre, hogy a Drawingvisual típus (rectvisual) a RenderopenD metó­


dust biztosítja, amely Drawingcontext objektumot ad vissza. A GDI+ Graphics
objektumához hasonlóan a Drawi ngcontext több olyan metódussal rendelkezik,
amely segítségével az elemek széles köre (orawRectangle(), DrawEllipse() stb.)
renderelhető. Ha elkészítettük a téglalapot, ezt követően két örökölt metódust
(Addvisual child() és AddLog ical chi ld() ) hívunk meg, amelyek használata op­
cionális, de biztosítják, hogy az egyedi Visual-leszármazott típusunkat a rend­
szer az ablak objektumfájába integrálja.
Végül, de nem utolsósorban felül kell definiálnunk a vi sualehi ldreneaunt
írásvédett tulajdonságot és a Getvisual child() metódust. Ezeket a tagokat a
WPF grafikus motor hívja, hogy meghatározhassa, pontosan mit kell rende­
relnie (ebben a példában egyetlen Drawingvisual elemet).
Mint látjuk, amint belépünk a Visual-leszármazott típusok használatának
világába, rengeteg forráskóddal találkozunk, ennek azonban az az előnye,
hogy általában nálunk az irányítás (és az ezzel együtt járó összetettség is).

Egyedi vizuális renderetési program készítése

Úgy állítottuk be az aktuális egyedi Visual renderelési műveletet, hogy az ab­


lak tartalmát (például a <StackPanel> típust) szétrobbantottuk, ezért a rend­
szer azt nem jelenítette meg, csak a kódolt orawingvisua 1-t. Most ismerjük
meg egy kicsit mélyebben a Visual programozási rétegeit! Mi lenne, ha az ab­
lak renderelésének nagy részét XAML-leírásokkal valósítanánk meg, és a Vi­
sual-réteget csak a felhasználói felület egy apró részének megvalósítására
használnánk?
Egyik megközelítési lehetőség, ha a FramewarkElement egyedi leszármazott
osztályát definiáljuk, és felüldefiniáljuk a virtuális onRender() metódust. Ez a
metódus (amelyet a shape-leszármazott típusok a kimenetük megjelenítésére
használnak) az előző createRectvisual() segédmetódusunk forráskódjához
hasonló kódot foglalhat magában. Ha definiáltuk ezt az egyedi osztályt, ak­
kor hivatkozhatunk az egyedi osztálytípusunkra az ablak XAML-leírásában.
Ennek bemutatására adjuk az aktuális projektünkhöz a MycustomRenderer
osztályt, amely kibővíti a FramewerkEl emen t ősosztályt (ne felejtsük el a fáj­
lunkba importálni a system. windows és a System. windows. Media névtereket), és
a következőképpen valósítsuk meg a típust:

public class MyCustomRenderer : FramewarkElement


{
ll A téglalap alapértelmezett mérete.
double rectwidth = 105, rectHeight = 55;

627
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

ll Tegyük lehetövé a felhasználó számára az alapértelmezett


ll értékek módosítását.
public double RectHeight
{
set { rectHeight = value; }
get { return rectHeight; }
}
public double Rectwidth
{
set { rectwidth = value; }
get { return rectwidth; }
}

protected override void OnRender(Drawingcontext drawctx)


{
ll Először a szülő rendereléséről gondoskodjunk.
base.OnRender(drawctx);

ll Egyedi renderelésünk hozzáadása.


Rect rect = new Rect();
rect.Width = rectwidth;
rect.Height = rectHeight;
drawctx.DrawRectangle(Brushes.LightBlue,
new Pen(Brushes.Blue, 5), rect);
}
}

Az ·egyedi típusunkkal kapcsolatos események nagy része az OnRender()


implementációban játszódik le. Vegyük észre, hogy a lokális Rect változó
méretét a rectHei ght és a rectwidth tagok alapján állítottuk be, amelyek nem
feltétlenül szükségesek, de lehetövé teszik a kép méretének definiálását.
Úgy férhetünk hozzá ehhez a típushoz a XAML-kódon keresztül, ha
olyan egyedi XML-névteret definiálunk, amely a típusunk nevéhez képező­
dik le, és ezt a prefixumot használjuk a típusunk létrehozásához. Tekintsük
meg a <StackPanel> típusunk releváns módosításait (emlékezzünk arra a 28.
fejezetből, hogy az XML-névtereket, amelyek saját .NET-névtereinkhez ké­
pezhetőek le, a clr- namespace tokennel kell definiálni):

<StackPanel xmlns:custom = "clr-namespace:WPFGraphicsoptions">

<custom:MyCustomRenderer RectHeight ="100" Rectwidth ="100"1>


<IStackPanel>

628
A WPF grafikus renderetési szolgáltatásának filozófiája

Az alkalmazás futtatása előtt az eredeti főablak definíciónkat tegyük ismét a


forráskód részévé-ne legyen megjegyzés-, viszont az egyedi ablak definíciója
megjegyzés legyen. Ha ezt követően futta�uk az alkalmazásunkat, a képernyőn
három téglalap jelenik meg, amelyek mindegyike a WPF kétdimenziós prog­
ramozási módszereinek egyikével készült (lásd a 30.4. ábrát).

Forráskód A WPFGraphicsOptions kódfájlokat a forráskódkönyvtár 30. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

!] WPFGraphicsOptions l=·1-§--.:tilf

30.4. ábra: Három téglalap, három különböző megközelítés

A megfelelő megoldás kiválasztása

Megismerkedtünk három különböző megközelítéssel, amelyek segítségével a


WPF kétdimenziós renderelési szolgáltatásaival (alakzatokkal, rajzokkal és
vizuális eszközökkel) dolgozhatunk. A grafikák megjelenítésére Visual­
leszármazott típusokkal valószínűleg akkor lehet szükség, ha egyedi vezérlő­
elemeket készítünk vagy nagyobb befolyást szeretnénk a renderelési folya­
mat felett. Ez azért jó, mert a visual és a többi típus leszármazottaival folyta­
tott munka az egyszerű XAML-leírásokhoz képest rengeteg erőfeszítéssel jár.
Ennek fényében a fejezet ezen pon�án nem merülünk el mélyebben a visual
renderelési API-k rejtelmeiben (további információkért lapozzuk fel a .NET
Framework 3.5 SDK dokumentációját).
A Drawin g leszármazott típusainak alkalmazása a tökéletes "arany közép­
út" megközelítés, mivel ezek a shape típusokhoz képest kevesebb erőfeszítést
igényelnek ahhoz, hogy támogassák az alapvető, nem felhasználói felület
szolgáltatásokat (például a találattesztelést). Ez a megközelítés a shape típu­
sok alkalmazásához képest több markup elkészítését igényli, de az így elké­
szült alkalmazás többletköltsége alacsonyabb. A fejezet későbbi részében
megvizsgáljuk a Drawing leszármazott típusait részletesen.

629
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

A shape típusok teljesen tökéletes megközelítést biztosítanak, ha egy

meghatározott ablakban néhány kétdimenziós képet kell megjelenítenünk


Emlékezzünk rá, hogy ha olyan kétdimenziós alakzatokkal kell dolgoznunk,
amelyek a hagyományos felhasználóifelület-elemek funkcionalitásával ren­
delkeznek, a shape típusok tökéletes választást jelentenek, mivel a szükséges
infrastruktúra már rendelkezésünkre áll.

Megjegyzés Soha ne feledjük, hogy a renderetési szolgáltatás választása rányomja bélyegét


az alkalmazás teljesítményére! Szerencsére rendelkezésünkre áll különböző WPF profilkialakí­
tási segédprogramok gyűjteménye, amelyekkel az aktuális alkalmazásunkat felügyelhetjük. Er­
ről a .NET Framework 3.5 SDK dokumentációjának "Performance Profiling Tools for WPF" című
részében további információkat találunk.

A Shape leszármazott tipusainak


felfedezése

A kétdimenziós grafikus renderelés vizsgálatának folytatásaként vizsgáljuk


meg részletesen a system.windows.shapes névtér tagjait. Emlékezzünk rá,
· hogy ezek a típusok biztosítják a legegyértelműbb, de egyszersmind legterje­
delmesebb módját a kétdimenziós képek megjelenítésének. Ez a névtér (ame­
lyet a PresentationFramework. dll szerelvény definiál) viszonylag kicsi, és
csak hat lezárt típust foglal magában, amelyek a shape ősosztályt bővítik ki:
az Ellips e, a L ine, a Path, a Pol ygon, a Pol y line és a Rectangle típusokat.
Mivel a shape ősosztály "az-egy" FramewarkElement, a leszármazott típu­
sokat tartalomként, XAML- vagy C#-kód segítségével hozzárendelhetjük, és
e közben nem kell megküzdenünk a geometriai formák rajzolásának össze­
tettségével:

<Window x:class="Someshapes.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" width="300" >

<!-- Ablak, amelynek tartalma egy kör -->


<Ellipse Height = "100" Width = "100" Fill = "Black" />
</WindoW>

A többi felhasználóifelület-elemhez hasonlóan, ha olyan ablakot készítünk,


amelyben több vezérlót szeretnénk elhelyezni, a kétdimenziós típusokat a 29.
fejezetben ismertetett módon, egy panel típusban kell definiálnunk.

630
A Shape leszármazott típusainak felfedezése

A Shape ősosztály funkcionalitása

Míg a shape funkcionalitását szülőosztályok hosszú sora biztosí�a, a típus de­


finiál néhány olyan tulajdonságot (melyek többsége függőségi tulajdonság),
amelyeken a gyermektípusok is osztoznak. A tulajdonságok közül a legérde­
kesebbeket a 30.1. táblázat ismerteti.

Fill Lehetővé teszi, hogy a leszármazott típus belső részé­


nek megjelenítéséhez ecsettípus t válasszunk.

GeometryTransform Lehetővé teszi, hogy a leszármazott típus megjeleníté­


sén átalakítást hajtsunk végre.

Stretch Leírja, hogy a rendszer a lefoglalt helyen hogyan töltse


ki az alakzatot. A tulajdonságot a megfelelő System.
Windows. Me dia. Stretch felsorolt típus segítségével ál­

líthatjuk be.

Stroke, Ezen (és még további) vonásközpontú tulajdonságok


StrokeDashArray határozzák meg az alakzat határvonalának megrajzolá­
StrokeEndLineCap, sakor a vonalak beállításait.
StrokeThickness

30.1. táblázat: A Shape ősosztály kulcsfontosságú tulajdonságai

Emlékezzünk rá, hogy a shape leszármazott osztályai támoga�ák a találat­


tesztelést, a témákat és stílusokat, az eszköztippeket és sok más szolgáltatást.

A Rectangle, az Ellipse és a Line tipusok használata

Ha szeretnénk kipróbálni a leszármazott típusokat, hozzuk létre a FunWith­


SystemWindowsShapes új Visual Studio WPF Windows alkalmazást. A Rec- ·

tangl e, az Ell ipse és a L ine típusok XAML-deklarációja elég egyértelmű, és

kevés magyarázatot igényel. A Rectangl e típus egyik érdekes jellemzője,


hogy a Radi usx és a Radi usv tulajdonságok definiálásával szükség esetén lehe­
tővé teszi lekerekített sarkok renderelését. A Line az Xl, X2, Yl és Y2 tulaj­
donságokkal ábrázolja a kezdő- és végpon�ait (vonalak leírása esetén a "ma­
gasság" és a " szélesség" adatoknak nem sok értelme lenne): a túl sok magya�
rázat helyett tanulmányozzuk a következő <StackPanel> típust:

631
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

<StackPanel>
<!-- A sor ellenőrzi, hogy az alakzat területére húztuk-e
az egeret -->
<Line Name ="SimpleLine" X1 ="0" X2 ="50" Y1 ="0" Y2 ="50"
Stroke ="DarkoliveGreen" StrokeThickness ="5"
ToolTip ="This is a line!"
MouseEnter ="SimpleLine_MouseEnter"/>

<!-- Lekerekített sarkokkal rendelkező téglalap -->


<Rectangle Radiusx ="20" RadiusY ="50"
Fill ="DarkBlue" width ="150" Height ="50"/>
</StackPanel>

Arnikor a kurzort a Line objektumra húztuk, a simpleLine objektum MouseEnter


eseménye az egérkurzor pozíciójával frissítette az ablak Title tulajdonságát:

protected void simpleLine_MouseEnter(object sender,


MouseEventArgs args)
{
this.Title = String.Format("Mouse entered at: {O}",
args.GetPosition(SimpleLine));
}

A Polyline, a Polygon és a Path tipusok használata

A Polyline típus segítségével (x, y) koordináták gyűjteményét definiálha�uk


(a Points tulajdonság révén), és kapcsolt vonalszegmenseket rajzolhatunk,
amelyeknek nem feltétlenül kell összekötni a végpon�ait. A Polygon típus ha­
sonló; azonban a típus definíciója szerint mindig összeköti a kezdő- és vég­
pontokat. Vizsgáljuk meg az aktuális <StackPanel> alábbi bővítését:

<!-- A Polyline típusok nem rendelkeznek kapcsolódó


végpontokkal -->
<Polyline Stroke ="Red" StrokeThickness ="20"
StrokeLineJein ="Round"
Points ="10,10 40,40 10,90 300,50"/>

<!-- A Polygon mindig összeköti a végpontokat -->


<Polygon Fill ="Aliceslue" StrokeThickness ="5" Stroke ="Green"
Points ="40,10 70,80 10,50" />

A 30.5. ábrán látható a shape-leszármazott típusok megjelenített kimenete.


Az utolsó típus, a Path (amelyet most nem vizsgálunk meg) tulajdonkép­
pen a Rectangle, az Ell ipse, a Polyline és a Polygon típusok befoglaló halmaza,
mivel a Path alkalmas ezen típusok bármelyikének megjelenítésére. Valójá­
ban a kétdimenziós típusokat kizárólag a Path segítségével is renderelhet­
nénk (de ez többletmunkát eredményezne).

632
A WPF-ecsettípusok használata

t"l Mouse eniered at 40,43

Z\
30. 5. ábra: Renderell Shape-leszármazott típusok

Forráskód A FunWithSystemWindowsShapes kódfájlokat a forráskódkönyvtár 30. fejezetének


alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WPF-ecsettipusok használata

A WPF grafikus renderelési lehetőségeinek rnindegyike (alakzatok, rajzok és vi­

zuális eszközök) sokszor ecseteket használ, amelyek segítségével szabályozha�uk,


hogy a rendszer hogyan töltse ki a kétdimenziós felületek belsejét. A WPF hat
különböző ecsettípus t biztosít, amelyek rnindegyike a system. windows. Media.

Brush osztályt bővíti ki. A Brush absztrakt osztály, azonban a 30.2. táblázatban
ismertetett leszármazottainak a segítségével egy területet az összes rendelkezé­
sünkre álló opció felhasználásával kitölthetünk

orawingBrush A Drawing leszármazott objektumával (Geometry­


Drawi ng, ImageDrawing vagy vi deoDrawi ng) fest ki

egy területet.

ImageBrush Egy képpel (amelyet az Imagesource objektum képvi­


sel) tölt ki egy területet.

LinearGradientBrush Egy területet lineáris átmenettel kifestő ecset.

633
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

:
.. ' . ' -,.. ' .. '� '
�' . '�

R adialGra dientBrush Egy területet sugaras átmenettel kifestő ecset.

solidColorBrush Egyetlen színt tartalmazó ecset. A színt a Color tulaj­


donsággal lehet beállítani.

visu alBrush A Visual leszármazott objektumával (orawingvisual,


vi ewport3ovi sua l és Containervisual) fest ki egy te­

rületet

30. 2. táblázat: A WPF Brush leszármazott típusai

A orawingBrush és a visu al Brush típusok lehetővé teszik, hogy a fejezet elején


vizsgált ora wing- vagy visual-leszármazott típusok segítségével készítsünk
ecsetet. A további ecsettípusok használata eléggé egyértelmű, és funkcionali­
tásuk hasonlít a GDI+ ecsettípusainak működéséhez. A következő részekben
megvizsgáljuk a solidcolorBrush, a LinearGradientBrush, a R a dialGr adient ­

Brush és az rmageBrush ecsettípusokat

Megjegyzés Mivel nem kezelnek eseményeket, a következő példákat közvetlenül a 28. feje·
zetben elkészített XAML·nézegetőbe írhatjuk, és nem kell új Visual Studio 2008 WPF projekt·
munkaterületeket létrehoznunk az alkalmazásban.

Egyszfnü ecsettipusok készftése

A so lideol orBrush típus color tulajdonságával egyszínű ecsettípusokat ké­


szíthetünk. A color tulajdonság a system. windows. Media. color típussal dol­
gozik, amely különböző tulajdonságok (például A, R, G és B) segítségével ál­
líqa be a színt. Az egyszínű ecsetek hasznosak lehetnek, de a sors iróniája,
hogy általában nem kell explicit módon solideolorBrush típust létrehozn unk,
mivel a XAML támogat egy olyan típusátalakítót, amely a háttérben az is­
mert színneveket (például "Blue") solidcolorBrush objektumokra képezi le.
Tanulmányozzuk a következő lehetőségeket, amely segítségével egy színnel
tölthetünk ki egy Ellipse objektumot:

<S ta c k P anel >


<!-- A solidcolorBrush típus használata -->
<Ellipse Fill ="DarkRed" Height =" 5 0 " width ="50" / >

634
A WPF-ecsettípusok használata

<!-- Típusátalakítóval működő egyszínű ecset -->


<Ellipse Height ="50" width ="50">
<Ellip se_ Fill>
<SolidcolorBrush Color ="DarkGoldenrod"/>
</Elli pse. Fill>
</Ellipse>

<!-- A SolidcolorBrush és a color típusok alkalmazása -->


<Ellipse Height ="50" Width ="50">
<Ellipse.Fill>
<SolidColorBrush>
<S olidcolorBrush.color >
<Color A ="40" R ="100" G ="87" B ="98"/>
</SolidcolorBrush.color>
</SolidcolorBrush>
</Ellip se. Fi ll>
</Ell ipse>
</StackPanel>

A kimenet megfelel a várakozásoknak (három különböző színű kör); azon­


ban a színséma meghatározásának megközelítése attól függ, hogy mekkora
rugalmasságot várunk el az alkalmazásunktóL Ha például az o p a c ity tulaj­
donság (amely az átlátszóságot szabályozza) értékét kell módosítanunk, a
<SolidcolorBrush> elem deklarálásával közvetlenül hozzáférünk annak tagja­

ihoz_ Más esetekben egyszerű sztring értéket hívhatunk segítségül, amelyet a


Fil l tulajdonsághoz rendelünk_

Megjegyzés A WPF grafikus API a Brushes segédosztályt biztosítja, amely meghatározza


több tucat előre definiált szín tulajdonságait. Ez akkor bizonyul hasznos lehetőségnek, ha eljá­
ráskódunkban egyszínű ecsetre van szükségünk.

Átmenetes ecsetek használata

A két átmenetes ecsettípus (a LinearGradientBrush és a RadialGradientBrush)

lehetővé teszi, hogy két (vagy több) szín átmenetével töltsünk ki egy terüle­
tet. A két típus között annyi a különbség, hogy amíg a LinearGradi entBrush

mindig egyenes vonal (amelyet tetszőleges pozícióba forgathatunk grafikus


átalakítással vagy a kezdő- és végpont beállításával) segítségével képez a szí­
nek között átmenetet, a RadialGradientBrush meghatározott kezdőponttól ki­
felé elliptikus határvonal mentén képzi a színátmenetet_ Nézzük meg a kö­
vetkező kódrészletet:

635
30. fejezet : WPF 2D grafikus renderelés, erőforrások és témák

<!-- Lineárisan kitöltött téglalap -->


<Rect:angle Radiusx ="15" RadiusY ="15" Height ="40" widt:h ="100">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientSt:op Color="LimeGreen" Offset:="O.O" />
<GradientStop Color="Orange" Offset="0.25" />
<Gradient:St:op color="Yellow" Offset:="0.75" />
<Gradient:St:op Color="Blue" Offset:="l.O" />
</LinearGradient:Brush>
</Rect:angle.Fill>
</Rect:angle>

<!-- Ellipszis sugaras színátmenet kitöltéssel -->


<Ellipse Height: ="75" widt:h ="75">
<Ellipse.Fill>
<RadialGradient:Brush Gradient:origin="0.5,0.5"
cent:er="0.5,0.5"
RadiusX="0.5" RadiusY="0.5">
<Gradient:St:op Color="Yellow" Offset:="O" />
<Gradient:St:op color="Red" offset:="0.25" />
<Gradient:St:op Color="Blue" Offset:="0.75" />
<Gradient:St:op color="LimeGreen" Offset:="1" />
</RadialGradient:Brush>
</Ellipse.Fill>
</Ellipse>

Vegyük észre, hogy az ecsettípusok <Gradi ent:st:op> típusok listáját tarlják


karban (a listában tetszőleges számú típus lehet), amelyek a színt és az eltolás
értékét határozzák meg. Az érték jelöli ki a képen, hogy a következő szín hol
kezd összeolvadni az előző színnel.

Az lmageBrush tipus

Az utolsó ecsettípus, amelyet ebben a részben megvizsgálunk, az rmagesrush.

Mint a neve is sugallja, a típus lehetővé teszi, hogy külső képfájlt (vagy beágya­
zott képerőforrást) töltsünk be az ecsettípus alapjaként Ahhoz, hogy egy külső
fájlt rendeljünk az rmageBrush típushoz, az egyik lehetőség az, ha az rmagesource

tulajdonság értékét érvényes Bit:maprmage objekturnra állíljuk. Vizsgáljuk meg a


következő egyszerű definíciót, amely feltételezi, hogy az alábbi 1' • xaml fájl mellett
számítógépünkön található a Gooseberry0007. JPG fájl is:

<!-- Képi ecsettel készült nagy téglalap -->


<Rect:angle Height: ="100" widt:h ="300">
<Rect:angle.Fill>
<ImageBrush>
<ImageBrush.rmagesource>

636
A WPF-tollak használata

<Bitmaprmage urisource ="Gooseberry0007.JPG"/>


</ImageBrush.rmagesource>
</ImageBrush>
</Rectangle.Fill>
</Rectangle>

A 30.6 ábrán az ecsettípusainkat látha�uk működés közben.

30.6. ábra: Különbözó ecsettípusok működés közben

Forráskód A FunWithBrushes.xaml kódfájlt a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WPF-tollak használata

Az ecsetekhez képest a tollak témaköre egészen egyszerű, mivel a Pen típus


tulajdonképpen álruhába bújtatott Brush típus. A Pen olyan ecsettípust kép­
visel, amely egy double érték által meghatározott vastagsággal rendelkezik
Ennek ismeretében létrehozhatunk egy Pen típust, amelynek Thickness tulaj­
donsága olyan nagy értéket kap, hogy a típus valójában egy ecset! Rendsze­
rint a Pen típus szerényebb vastagsággal rendelkezik, amely meghatározza,
hogyan kell a kétdimenziós kép körvonalát megjeleníteni.
Az esetek többségében nem kell majd közvetlenül létrehoznunk Pen tí­
pust, mivel ezt a rendszer indirekt módon végrehaj�a, ha olyan tulajdonsá­
goknak, mint például a StrokeThi ckness, értéket megadunk. Egyedi Pen típus

637
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

készítése rendkívül hasznos lehet, ha a Drawing leszármazott típusaival dol­


gozunk (hamarosan látjuk, miért). Mielőtt megvizsgálnánk a tollak testre
szabását, tanulmányozzuk a következő példát:

<Pen Thickness="lO" LineJoin="Round" EndLineCap="Triangle"


StartLineCap="Round" />

Ez a Pen típus beállítja a LineJein tulajdonságot, amely szabályozza, hogyan


kell két vonal között megjeleníteni a kapcsolódási pontot (például a sarko­
kat). Az EndLinecap - mint azt a neve is sejteti- szabályozza, hogyan kell egy
vonal végpontját (ebben az esetben háromszög) megjeleníteni, a startlinecap
pedig a vonal kezdőpontjánál határozza meg ugyanezt a beállítást.
A Pen típusnál beállíthatjuk a vonal stausát, amely befolyásolja, hogy a toll
hogyan húzza a vonalat Az alapértelmezett beállítás szerint egyetlen színt
alkalmazunk (ahogyan azt az adott ecset megköveteli); azonban a oashstyle
tulajdonságot bármely egyedi Dashstyle objektumhoz hozzárendelhetjük
Míg egyedi Dashstyle objektumok létrehozásával szabályozhatjuk, hogy a
Pen típus hogyan jelenítse meg az adatait, a Dashstyle segédosztály több sta­
tikus tagot definiál, amelyek alapértelmezett stílusokat biztosítanak. Mivel
ezek egy osztály statikus tagjai, nem egy felsorolt típus értékei, alkalmaznunk
kell a XAML {x: static} markupbővítőt:

<Pen Thickness="lO" LineJoin="Round" EndLinecap="Triangle"


StartLineCap="Round"
Dashstyle = "{x:static Dashstyles.DashDotDot}" />

Már rendelkezünk ismeretekkel a Pen típusról, ezért most használjuk a típu­


sokat különböző Drawing-leszármazott típusokban.

A Drawing leszármazott tipusainak


vizsgálata

Emlékezzünk rá, hogy a shape típusok segítségével bármilyen interaktív két­


dimenziós felületet előállíthatunk, de a típusok terjedelmes származtatási
láncának köszönhetően használatuk jelentős többletköltséggel jár. Alternatív
megoldásként a WPF kifinomult rajz- és geometriaprogramozási interfészt
biztosít, amellyel egyszerűbb kétdimenziós képeket megjeleníteni. Az API
belépési pontja az absztrakt System.windows.Media.Drawing osztály, amely

638
A Drawing leszármazott típusainak vizsgálata

önmagában pusztán egy határoló téglalapot definiál a megjelenített kép be­


foglalására. A WPF öt típust biztosít, amelyek ki bővítik a Drawing típust. Mint
azt a 30.3. táblázatban láthatjuk, a típusok mindegyike a tartalom rajzolásá­
nak speciális fajtáját képviseli.

DrawingGroup Segítségével különálló Drawing-leszármazott típusok gyűj­


teményét lehet egyetlen összetett renderelésben egyesíteni.

GeometryDrawing Segítségével kétdimenziós alakzatokat lehet megjeleníteni.

GlyphRu norawing Segítségével szöveges adatokat lehet a WPF grafikus


renderelési szolgáltatásaival megjeleníteni.

ImageDrawing Segítségével határoló téglalapban lehet képfájlt megjeleníteni.

VideoDrawing Segítségével audio- vagy vicleofájlt lehet lejátszani (nem


"
"rajzolni ). A típus funkeionalitását teljes mértékben csak
eljáráskóddal aknázha�uk ki. Ha a XAML segítségével sze­
retnénk videofájlokat lejátszani, a MediaPlayer típus jobb
választás.

30. 3. táblázat: A WPF Drawing leszármazott típusai

Önmagában a típusok mindegyike rendkívül hasznos, de kétdimenziós képek


megjelenítéséhez a GeometryDrawing típus a legjobb választás, a következő rész­
ben ezt a típust mutatjuk be részletesen. Röviden, a GeometryDrawing típus egy
geometriai típust jelent, amely a kétdimenziós kép szerkezetét részletezi, Brush le­
származott típus tölti ki a belsejét, és Pen típus rajzolja meg a határvonalát.

A Geometry tipusok szerepe

A Geometryorawing típussal leírt geometriai szerkezet valójában a WPF geomet­


riaközpontú osztálytípusainak egyike, vagy geometriaközpontú típusok gyűj­
teménye, amelyek egyetlen egységként működnek. A geometriák mindegyikét
leírhatjuk XAML- vagy C#-forráskóddal is. A geometriák a system. windows.
Me dia. Geometry ősosztályból származnak, amely a leszármazott típusokban kö­

zös tagokat definiál. Néhány tagot megtekinthetünk a 30.4. táblázatban.

639
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Bounds Tulajdonság, amely segítségével létrehozhatjuk az aktuális


határoló téglalapot

Fi llContains () Lehetővé teszi annak eldöntését, hogy egy meghatározott


Point (vagy egyéb Geometry típus) egy adott Geometry­

leszármazott típus határain belül található-e. Ez a tag a ta­


lálattesztelési számításokhoz nyújt segítséget.

GetArea () oouble értéket ad vissza, amely a Geometry-leszármazott

típus által lefoglalt teljes területet képviseli.

GetRenderBounds() Rect típust ad vissza, amely a Geometry-leszármazott típus

megjelenítéséhez alkalmazható legkisebb befoglaló tégla­


lapot határozza meg.

Transform Lehetővé teszi, hogy a renderelés módosításához


Transform objektumot rendeljünk a geometriához.

30 .4. táblázat: A System.Windaws.Media.Geometry típus tagjai

A WPF rengeteg Geometry-leszármazott típust biztosít. A típusokat két egy­

szerű kategóriába sorolhatjuk: az alapvető alakzatok és az útvonalak csoport­


jába. A geometriai típusok első csoportjának segítségével-ide tartozik a Rec­
tangleGeometry, az Elli pseGeometry, a L ineGeometry és a PathGeometry - alap­
vető alakzatokat jeleníthetünk meg. Szerencsére ez a négy típus a már vizs­
gált system. windows. Me dia. shapes típusok funkcionalitását másolja (és sok
esetben azonos tagokkal rendelkezik).
A renderetési műveleteink nagy részében az alapvető alakzatokkal reme­
kül boldogulhatunk. Ne feledjük, ha különleges geometriák alkalmazására
van szükség, a WPF több olyan kiegészítő típust biztosít, amelyek a Path­
Geemetrytípussal együtt működnek. A PathGeometry "útvonaldarabok" gyűj­
teményét tartja karban, és a következőket foglalja magában: Archsegment,
Beziersegment, LineSegment, PolyBeziersegment, PolyLinesegment, PolyQuadra­

ti cBeziersegment és QuadraticBeziersegment.

640
A Drawing leszármazott típusainak vizsgálata

Egyszerű rajzoló geometria szétdarabolása

Vizsgáljuk meg közelebbről a fejezet elején létrehozott <Geomet:ryDrawing> típust:

<Geomet:ryDrawing Brush ="Light:Blue">


<Geomet:ryorawing.Pen>
<Pen Brush ="Blue" Thickness ="5"/>
</Geomet:ryDrawing.Pen>
<Geomet:ryDrawing.Geomet:ry>
<Rect:angleGeomet:ry Rect:="0,0,100,50"/>
</Geomet:ryDrawing.Geomet:ry>
</Geomet:ryDrawing>

Emlékezzünk rá, hogy a <Geomet:ryDrawing> típus ecsetet, tollat és tetszőleges


WPF-geometriatípusokat foglalhat magában. A példában a nyitó elemben a
Brush tulajdonság segítségével indirekt módon definiáltuk a világoskék
SolidcolorBrush típust. A Pentípust a tulajdonság-elem szintaxissal deklarál­
juk, és létrehozzuk a kék ecsetet, amely meghatározott vastagsággal rendel­
kezik. A <Geomet:ryDrawing> típus Geomet:ry tulajdonságához a <Rect:angl eGeo­
met:r y> értéket rendeljük.
Ha ezt a XAML-markupot közvetlenül a <Page> hatókörében (vagy bármely
cont:ent:cont:ro l-leszármazott típusban) szeretnénk elkészíteni a x aml pad. ex e
segítségéve!, markuphibát kapunk, amely lényegében azt tuda�a velünk, hogy
a <Geomet:ryorawing> nem bővíti ki a UI Element: ősosztályt, és ezért értékként
nem rendelhe�ük hozzá a cont:ent: tulajdonsághoz.
Ez egy roppant érdekes kérdést vet fel a Drawing-leszármazott típusok
használatával kapcsolatban: a típusok nem rendelkeznek semmilyen felhasz­
nálói interfésszel! Ezen tipusok, mint például a <Geomet:ryorawing> leírják, hogy
a kétdimenziós elem hogyan jelenne meg, ha megfelelő tárolába helyeznénk.
A WPF három különböző hosztoló objektumot biztosít a Drawing objektumok
számára: a Drawi ngrmage, a DrawingBrush és a Drawingvisua l objektumot.

Drawing tipusok a Drawingimage tipusban

A Drawing Image típus segítségével a rajzoló geometriát WPF <Image> vezérlő­


elembe helyezhetjük. Így, ha az előző <Geomet:ryorawing> típust szeretnénk
megjeleníteni, a következő módon kell a típust becsomagolnunk:

641
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

<Image>
<Image.source>
<Drawingimage>
<Drawingimage.Drawing>
<GeometryDrawing Brush ="LightBlue">

</GeometryDrawing>
</Drawingimage.Drawing>
</Drawingimage>
</Image.source>
</Image>

Vegyük észre, hogy az Image típus source tulajdonságához rendeltünk egy


Drawing-leszármazott típust.

Drawing tipusok a DrawingBrush tipusban

Ha inkább DrawingBrush típussal csomagoljuk a <GeometryDrawing> típust, gya­


korlatilag összetett egyedi ecsetet hozunk létre, mivel a DrawingBrush valójában
a Brush-leszármazott típusok egyike (az ecsetekkel kapcsolatos bővebb infor­
mációkat a következő részben találjuk). Bárhol alkalmazhaljuk, ahol Brush tí­
pus szükséges, például a <WindoW> típus Background tulajdonságánál:

<Window x:class="FunwithDrawingAndGeometries.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FunwithDrawingAndGeometries" Height="190" Width="224">

<!-- Az ablak hátterét állítsuk egyedi DrawingBrush típusra -->


<Window.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<GeometryDrawing Brush ="LightBlue">
<GeometryDrawing.Pen>
<Pen Brush ="Blue" Thickness ="5"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,50"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Background>
</WindoW>

642
A Drawing leszármazott típusainak vizsgálata

Összetett rajzoló geometria

A orawingImage objektum több különálló orawing objektumból állhat, ame­

lyeket a <Drawi ngGroup> típusba helyezünk bonyolultabb kétdimenziós képek


előállításához. Vizsgáljuk meg a következő Image típust, amely forrásaként a
<Drawingimage> típust alkalmazza:

<Image> ,
<Image.Source>
<Drawingimage>
<Drawingimage.Drawing>
<!-- Különböző geometriák csoportja -->
<DrawingGroup>
<Geometryorawing>
<Geometryorawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,20,20" />
<RectangleGeometry Rect="160,120,20,20" />
<EllipseGeometry center="75,75" RadiusX="50"
RadiusY="50" />
<LineGeometry startPoint="75,75"
EndPoint="lBO,O" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<!-- Egyedi toll, amellyel megrajzoljuk a
határvonalakat -->

<Geometryorawing.Pen>
<Pen Thickness="lO" LineJoin="Round"
EndLinecap="Triangle" StartLinecap="Round">
<Pen.Brush>
<LinearGradientBrush>
<GradientStop offset="O.O" Color="Red" />
<Gradientstop offset="l.O" color="Green" />
</LinearGradientBrush>
</Pen.Brush>
</Pen>
</Geometryorawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</Drawingimage.Drawing>
</Drawingimage>
</Image.source>
</Image>

A <Drawingimage> egy <GeometryGroup> típust tartalmazó <DrawingGroup> tí­


pus, amely létrehozza a két téglalapból, egy ellipszisből és egy vonalból álló
képet. A képek határvonalait egyedi tolltípussal jeleníljük meg, amely egyedi
L inearGradientBrush típust foglal magában. A végeredményt a 30.7. ábrán
láthaljuk

643
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

a
30.7. ábra: DrawingGroup típust tartalmazó kép

Ahhoz, hogy a Pen típus Dashstyl e típust - például a (korábban látott) Dash­
Styles.DashDotDot típust - alkalmazzon, a kódot a következőképpen kell
módosítani:

<Pen Thickness="lO" LineJoin="Round"


EndLinecap="Triangle" StartLinecap="Round"
Dashstyle = "{x:Static DashStyles.DashDotDot}" >
<Pen.Brush>
<LinearGradientB rus h>

<Gradientstop offset="O.O" color="Red" />


<Gradientstop offset="l.O" Color="Green" />
</LinearGradientBrush>
</Pen.Brush>
</Pen>

A végeredmény a 30.8. ábrán látható.


-=-.

2
•• 4I'
.�
l , l
• •
• •
7 7
-=-
30.8. ábra: Pen típus, amely vonás-pont-pont beállítással rendelkező DashStyle típussal dolgozik

Forráskód A FunWithDrawingGeometries.xaml kódfájlt a forráskódkönyvtár 30. fejezetének


alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

644
A felhaszná lóifelület-transzformációk szerepe

A felhasználóifelü let-transzformációk
szerepe

Mielőtt rátérnénk az animációs szolgáltatások témakörére, zárjuk le a kétdi­


menziós grafikus renderelés tanulmányozását a transzformációk vizsgálatávaL
A WPF több olyan típust biztosít, amely kibővíti a Transform absztrakt ősosz­
tályt Az ősosztályt bármely FramewarkEl ement típuson (például a shape típus
leszármazottain, valamint olyan felhasználóifelület-elemeken, mint a Button,
a TextBox stb.) alkalmazhatjuk. A típusok segítségével meghatározott szög­
ben jeleníthetjük meg a FramewarkElement típust, a felületen elferdíthetjük,
különböző módon növelhetjük vagy összenyomhatjuk a képet

A Transform-leszármazott tipusok

A 30.5. táblázatban a kulcsfontosságú Transform típusokat találjuk

·:_,.

�\-��-;��·J�_ .
MatrixTransform Tetszőleges mátrixtranszformációt hoz létre, amellyel két­
dimenziós síkban objektumokat vagy koordináta­
rendszereket kezelhetünk

RotateTransform Az óramutató járásával megegyező irányában elforgat egy


objektumot egy kétdimenziós (x,y) koordináta-rendszer
meghatározott pontja körül.

ScaleTransform Egy objektumot kétdimenziós (x, y) koordináta-rendszer­


ben méretez.

SkewTransform Elferdít egy objektumot egy kétdimenziós (x, y) koordináta­


rendszerben.

TransformGroup Összetett Transform típust képvisel, amely több


Transform objektumot foglal magában.

30. 5. táblázat: A System. Windows. Media. Transform típus kulcsfontosságú leszármazottai

Ha létrehozunk egy Transform leszármazott objektumot, alkalmazhatjuk a


FramewarkElement ősosztály két tulajdonságára. A LayoutTransform tulajdon­
ság azért hasznos, mert a rendszer végrehajtja a transzformációt mielőtt az
elemeket egy panelben megjelenítené. Másrészről a RenderTransform tulaj-

645
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

donságot akkor alkalmazza a rendszer, ha az elemek már a tárolóikban he­


lyezkednek el, ezért előfordulhat, hogy az elemek a transzformációt követően
fedik egymást. Azok a típusok, amelyek kibővítik a UI Element: típust, értéket
rendelhetnek a RenderTransformorigin tulajdonsághoz. A tulajdonság a
transzformáció kezdéséhez szükséges (x,y) pozíciót határozza meg.

Transzformációk alkalmazása

Tételezzük fel, hogy a <Grid> típusunk egyetlen sort tartalmaz négy oszlop­
pal. Minden egyes cellában elforgatunk, ferdítünk és átméretezünk különböző
UI Element: típusokat, például a következőképpen:

<!-- Rect:angle típus forgatási transzformációval -->


<Rect:angle Height: ="100" widt:h ="40" Fill ="Red" Grid.Row="O"
Grid.column="O">
<Rect:angle.Layout:Transform>
<Rot:at:eTransform Angle ="45"/>
</Rect:angle.Layout:Transform>
</Rect:angle>

<!-- Button típus ferdítési transzformációval -->


<But:t:on Content: ="Click Me!" Grid.Row="O" Grid.column="l"
Width="95" Height:="40">
<Button.RenderTransform>
<SkeWTransform Anglex ="20" Anglev ="20"/>
</Button.RenderTransform>
</But:t:on>

<!-- Ellipse típus, amelyet 20%-kal átméret:ezt:ünk -->


<Ellipse Fill ="Blue" Grid.Row="O" Grid.column="2" widt:h="5"
Height:="5">
<Ellipse.RenderTransform>
<ScaleTransform scalex ="20" scalev ="20"/>
</Ellipse.RenderTransform>
</Ellipse>

<!-- A Button t:ípust ferdített:ük, forgat:t:uk, majd ismét:


ferdítettük -->
<Button content ="Me Too!" Grid.Row="O" Grid.Column="3" width="50"
Height:="40">
<But:t:on.RenderTransform>
<TransformGroup>
<SkeWTransform Anglex ="20" Anglev ="20"/>
<RotateTransform Angle ="45"/>
<SkeWTransform Anglex ="5" Anglev ="20"/>
</TransformGroup>
</But:t:on.RenderTransform>
</Button>

646
A WPF animációs szolgáltatásai

Az első típusunk, a <Rectangl e> a RotateTransform típust alkalmazza, amely


az Angle tulajdonság segítségével 45 fokos szögben jeleniti meg a ur elemet.
Az el�ő <Button> típus a skewTransform objektummal dolgozik, amely- (leg­
alább) az Angl ex és az Angl eY tulajdonságok alapján- ferdére állí�a a vezérlő
megjelenitett képét. A <Scal eTransform> típus - amelyet az <Ell i pse> típus
alkalmaz -jelentős mértékben növeli a kör magasságát és szélességét. Ve­
gyük észre, hogy az <Ell i pse> típus Hei ght és wi d th tulajdonságainak értéke
5, Iniközben a megjelenitett kimenet sokkal nagyobb. Végül, de nem utolsó­
sorban a <Button> típus a <TransformGroup> típus segítségével alkalmazza a
ferdítést és az elforgatást. A megjelenitett kimenet a 30. 9. ábrán látható.

, \�
"--��\.
�- - · - - ·· - -...:)

30.9. ábra: Transzfonnációk alkalmazása

Forráskód A FunWithTransformations.xaml kódfájlt a forráskódkönyvtár 30. fejezetének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A WPF animációs szolgáltatásai

A kétdimenziós (és háromdimenziós) grafikus renderelés teljes körű támogatá­


sát biztosító API-n kívül a WPF programozási interfészt is nyújt az animációs
szolgáltatások támogatásához. Az "animáció" fogalma pörgő vállalati logók­
nak, forgó képi erőforrások sorozatának (amelyek a mozgás illúzióját keltik), a
képernyőn keresztülpattogó szövegnek vagy meghatározott programoknak
- például vicleojátékok vagy multimédia-alkalmazások- a képét is jelenti.
A WPF animációs API-kat valóban használha�uk ilyen célra is, de az
animációkat tulajdonképpen bármikor alkalmazha�uk, ha az alkalmazá­
sunknak szeretnénk további funkcionalitást kölcsönözni. Például egy képer­
nyőn animációt készíthetünk egy gombhoz, amelyet a rendszer kissé felna­
gyít, ha az egérkurzort az objektum határain belülre mozga�uk (majd vissza-

647
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

állí�a az objektum eredeti méretét, ha az egérkurzort a határvonalon kívülre


mozga�uk). Egy ablak bezárásához animációt készíthetünk, amely speciális
vizuális effektust alkalmaz (az ablak lassan elhalványul, és teljesen átlátszóvá
válik). Röviden, ha szeretnénk lebilincselő élményeket nyújtani a felhaszná­
lóknak, bármely alkalmazással (üzleti alkalmazás, multimédia-programok,
vicleojátékok stb.) használha�uk a WPF animációtámogatását.
A WPF többi lehetőségéhez hasonlóan az animációk készítése sem újdonság.
Azonban a korábban használt API-kkal ellentétben (mint például a GDI+) a fej­
lesztöknek a szükséges infrastruktúrát nem kell kézzel létrehozniuk. A WPF­
alkalmazásokban nem kell háttérszálakat (vagy időzítőket) létrehoznunk az
animációsorozat előreléptetéséhez, nem kell egyedi típusokat definiálnunk az
animáció számára, és nem szükséges matematikai számításokkal bajlódnunk.
A WPF többi funkciójához hasonlóan az animációkat elkészíthe�ük pusz­
tán, XAML-markup alkalmazásával, csak C#-forráskóddal vagy a kettő kom­
binációjával. Azonban, ha a Microsoft Expression Blendet (amelyet a WPF-fel
bővebben foglalkozó fejezetekben néhányszor már említettünk) használjuk,
integrált eszközökkel és varázslókkal tervezhetünk meg egy animációt anél­
kül, hogy egyetlen sornyi C#-kódot vagy XAML-markupot kellene használ­
nunk. Ez a megközelítés grafikusművészek számára ideális, akik nem tudná­
nak mit kezdeni ezekkel a részletekkel.

Az Animation utótaggal rendelkező tipusok szerepe

A WPF animációs támogatás felfedezéséhez kezdjük a vizsgálódást az alap­


vető animáció típusokkat amelyeket a Presentationeore. dll szerelvény sys­
tem. windows. Me dia. Animati on névtere foglal magában. A névtérben több osz­

tálytípust találhatunk, amelyek utátagja az Animation ( ByteAnimation, color­


Animation, DoubleAnimation, rn32Animation stb.). Nyilvánvaló, hogy ezekkel a

típusokkal közvetlenül nem alkalmazhatunk meghatározött adattípusú válto­


"

zón animációs sorozatot (pontosan hogyan animálnánk a " 9 értéket az


rnt32Ani mati on segítségéve!?). Ehelyett az Ani mati on utótaggal rendelkező tí­

pusokat a mögöttes típusokkal megegyező típusok függőségi tulajdonságaihoz


kapcsolha�uk.

Megjegyzés Fontos ismételten kiemelni, hogy az Ani mati on utótaggal rendelkező típusok
csak a függőségi tulajdonságokkal, és nem a CLR-tulajdonságokkal működnek együtt (lásd a 29.
fejezetet). Ha animációs objektumokat próbálunk alkalmazni CLR-tulajdonságokon, fordítási
idejű hibát kapunk.

648
A WPF animációs szolgáltatásai

Gondoljunk például a Label típus H eight és width tulajdonságaira, amelyek


mindegyike double értéket magában foglaló függőségi tulajdonság. Ha olyan
animációt szeretnénk definiálni, amely meghatározott időtartam alatt növeli
a címke magasságát, a DoubleAnimation objektumot a Height tulajdonsághoz
kapcsolha�uk, és a WPF-re bízha�uk a tényleges animáció végrehajtásának
részleteit. Egy másik példa, ha egy ecsettípus színénél a zöld-sárga átmenetet
szeretnénk megvalósítani, a co lorAnima tion típust hívha�uk segítségül.
Függetlenül attól, hogy melyik Animation utótaggal rendelkező típust sze­
retnénk használni, a típusok mindegyike több kulcsfontosságú tulajdonságot
,_

definiál, amelyek szabályozzák az animáció végrehajtásához szükséges kez-


dő- és záróértékeket

• To: Ez a tulajdonság határozza meg az animáció záróértékét

• From: Ez a tulajdonság határozza meg az animáció kezdőértékét

• By: Ez a tulajdonság határozza meg azt mennyiséget, amellyel az ani­


máció kezdőértéke változik.

Annak ellenére, hogy az Anima tion utótaggal rendelkező típusok támoga�ák


a To, a From és a By tulajdonságokat, a típusok nem az ősosztály virtuális tag­
jain keresztül kapják ezeket az értékeket. Ez annak köszönhető, hogy a tulaj­
donságok által becsomagolt mögöttes típusok nagyon eltérőek (egész szá­
mok, színek, vastagság objektumok stb.), és az összes lehetőség megjelemtése
a system. obj e ct segítségével bedobozolási/kidobozolási többletet jelentene a
verernalapú adatok esetén.
Felmerülhet bennünk a kérdés, hogy miért nem a .NET generikus típusok se­
gitségével definiáltunk egyetlen animációs osztályt egyetlen paramétertípussal
(például Animate<T>) . Mivel az animált függőségi tulajdonságok több alap adat­
típust (színek, vektorok, egész számok, sztringek stb.) alkalmaznak, várakozása­
inkkal ellentétben ez nem a legtisztább megoldás (továbbá ne feledjük, hogy a
XAML csak limitált támogatást biztosít a generikus típusok számára).

A Timeline ősosztály szerepe

Noha a virtuális To, From és By tulajdonságok definiálására nem készült külön


ősosztály, az Anima tion utótéiggal rendelkező típusok egy közös ősosztályon
osztoznak: a system. windows. Media. Timeline ősosztályon. Mint az a 30.6. táblá­
zatban látható, a típus további tulajdonságokat biztosít, amelyek az animáció
léptetését szabályozzák.

649
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

AccelerationRatio, Ezekkel a tulajdonságokkal az animációsorozat általá­


DecelerationRatio, nos léptetését szabályozha�uk.
speedRatio

AutoReverse A tulajdonság lekérdezi vagy beállí�a, hogy az idősor


visszafelé haladjon-e miután a normál iterációt befejezte.

BeginTime A tulajdonság lekérdezi vagy beállí�a az időt, amikor


az idősor elkezdi a lejátszást. Az alapértelmezett érték
O, amely azonnal indí�a az animációt.

ouration A tulajdonság segítségével beállíthatunk időtartamot,


amíg a rendszer az idősort lejátssza.

Fill Behavior, A tulajdonságokkal szabályozha�uk, hogy mi történjen,


RepeatBehavior ha az idősor lejárt (például a rendszer megismételje az
animációt, ne történjen semmi, stb.).

30.6. táblázat: A Timeline ősosztály kulcsfontosságú tagjai

Animáció készitése C#-forráskódból

A WPF animációs szolgáltatásainak vizsgálata során először a ooubleAnima­


tion típust alkalmazzuk, amellyel a főablakban lévő címkék különböző tulaj­

donságait szabályozzuk Készítsük el az AnimatedLabel WPF Windows al­


kalmazást, és tervezzünk <G rid> típust, amely két sort és két oszlopot foglal
magában. Az első oszlopban minden cellában helyezzünk el egy-egy gombot,
és kezeljük vezérlők kattintási eseményeit. A második oszlop celláiban he­
lyezzünk el címkéket (lbl H eight és lblTransparency névvel). A 30.10. ábrán
egy lehetséges felhasználói felületet láthatunk.
Az alábbi kód alapján implementáljuk a kattintási eseménykezelőket

public partial class Mainwindow : system.windows.window


{
public Mainwindow()
{
rnitializecomponent();
}

protected void btnAnimatelblMessage_click(object sender,


RoutedEventArgs args)
{
ll Növeljük a címke magasságát.
DoubleAnimation dblAnim = new ooubleAnimation();

650
A WPF animációs szolgáltatásai

dblAnim.From =40;
dblAnim.To = 60;
lblHeight.BeginAnimation(Label.HeightProperty, dblAnim);
}

protected void btnAnimatelblTransparency_Click(object sender,


RoutedEventArgs args)
{
ll Módosítsuk a címke átlátszóságát.
DoubleAnimation dblAnim = new DoubleAnimation();
dblAnim.From = 1.0;
dblAnim.To = 0.0;
lblTransparency.BeginAnimation(Label.opacityProperty, dblAnim);
}
}


u .::.,,j:·,�;:;:�:!L:-�:::
,.- - ------------·---:;::·;-·--------------.,..---- --:;;;:- ..
;j
---------.- ·---·----------- -- - ----

- .&: • - - ---- --

ft
.
- --- -
-� -

'·� -:l "'"'' [ .l


d
....
IEJ
________________ _________________ :

� Animate Transparency Animate Transparency!

30. 1 O. ábra: Az AnimatedLabel alkalmazás kezdeti felhasználói felülete

Vegyük észre, hogy minden kattintási esemény kezelőjében beálli�uk a Double­


Animation típus From és To értékeit, amelyek a kezdő- és záróértéket képviselik.

Ezt követően a megfelelő címkén hívjuk meg a BeginAnimation() metódust,


majd adjuk át a kapcsolódó vezérlő (ismét a címke) megfelelő függőségi tulaj­
donságát, amelyet az animációt végrehajtó Animation utótaggal rendelkező ob­
jektum követ.

Megjegyzés Emlékezzünk a 29. fejezetre, ahol a függőségi tulajdonságok kapcsán megtanul­


hattuk, hogy a Dependencyobj e ct típus nyilvános, írásvédett statikus mezőinek_ segítségével
jelenítjük meg az (opcionális) CLR-tulajdonságcsomagoló adott függőségi tulajdonságát.

Ha most futta�uk az alkalmazást, és a gombokra kattintunk, látha�uk, hogy


a lblHeight címke mérete nagyobb, a lblTransparency gomb pedig lassan el­
halványul, majd eltűnik.

651
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Az animáció ütemezésének szabályozása

Alapértelmezés szerint az animációban a From és a To tulajdonságokhoz rendelt


értékek közötti átmenet ideje egy másodperc. Ha például módosítani szeret­
nénk a kattintási eseménykezelőt, amely a címke Height tulajdonságának érté­
két 40-ről 200-ra növeli (pillanatnyilag kisebb mértékű növekedéssel dolgo­
zunk), az átmenet továbbra is nagyjából egy másodpercet venne igénybe.
Ha az animáció átmenetei idejére egyedi időtartamot szeretnénk definiálni,
akkor a Duration tulajdonság segítségével beállíthatjuk a Duration objektum
egy példányát. Az időtartamot meghatározhatjuk, ha a Duration konstruktor­
nak átadjunk egy Timespan objektumot. Vizsgáljuk meg az aktuális esemény­
kezelők következő módosítását, amely négy másodperc alatt növeli a címke
magasságát, és a másik címkét tíz másodperc alatt teljesen átlátszóvá teszi:

protected void btnAnimatelblMessage_click(object sender,


RoutedEventArgs args)
{
ll Az animáció 4 másodperc alatt befejeződik.
DoubleAnimation dblAnim new DoubleAnimation();
dblAnim.From = 40;
dblAnim.To = 200;
dblAnim.Duration = new Duration(Timespan.Fromseconds(4));
lblHeight.BeginAnimation(Label.HeightProperty, dblAnim);
}

protected void btnAnimatelblTransparency_Click(object sender,


RoutedEventArgs args)
{
ll Módosítjuk a címke átlátszóságát.
DoubleAnimation dblAnim = new DoubleAnimation();
dblAnim.From = 1.0;
dblAnim.To = 0.0;
dblAnim.Duration = new Duration(TimeSpan.Fromseconds(lO));
lblTransparency.BeginAnimation(Label.opacityProperty, dblAnim);
}

-
Megjegyzés Az Animation utótaggal rendelkező típusok Begi'nTi me tulajdonsága szintén
Timespan objektumot vesz fel. Emlékezzünk rá, hogy ezzel a tulajdonsággal beállíthatjuk az
animációsorozat elindítása előtti várakozási időt.

652
A WPF animációs szolgáltatásai

Animáció lejátszása visszafelé és folyamatos


isméttése

Beállíthatjuk az AutoReverse tulajdonságot true értékre, és az Animation utó­


taggal rendelkező típusokat utasíthatjuk, hogy az animációt a sorozat befeje­
zését követően visszafelé játsszák le. A kattintási eseménykezelőjének követ­
kező módosításával a címke magassága 40 képpontról 100 képpontra növek­
szik, majd visszazsugorodik 100 képpontról 40-re (mindezt nyolc másodperc
alatt, négy másodpercig egy-egy irányban):

ll Habefejeződött a sorozat,"az
- animációt visszafelé játsszuk le.
dblAnim.AutoReverse = true;

Ha szeretnénk az animációt néhányszor megismételni (vagy az aktíválást kö­


vetően folyamatosan lejátszani), beállíthatjuk a RepeatBehavior tulajdonságot,
amely minden Animation utótaggal rendelkező típusban rendelkezésünkre áll.
A RepeatBehavior tulajdonság értéke a megegyező névvel rendelkező objektum
egy példánya. Ha egyszerű numerikus értéket adunk át a konstruktornak,
meghatározhatjuk az ismétlések számát. Másrészről, ha konstruktornak átad­
junk egy Ti mespan objektumot, beállíthatjuk az időtartamot, ameddig a rend­
szer ismétli az animációt. Ha végtelenített animációt szeretnénk készíteni,
megadhatjuk a RepeatBehavior.Forever értéket. Vizsgáljuk meg a különböző
módszereket, amelyekkel módosíthatjuk a címke magasságát:

ll végtelen ciklus.
dblAnim.RepeatBehavior = RepeatBehavior.Forever;

ll Ismétlés háromszor.
dblAnim.RepeatBehavior = new RepeatBehavior(3);

ll Ismétlés 30 másodpercig.
dblAnim.RepeatBehavior =
new RepeatBehavior(TimeSpan.Fromseconds(30));

Forráskód Az Animatedlabel kódfájlokat a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

653
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

Animáció készitése XAML-leirással

Ha clinamikusan kell az animáció állapotát kezelnünk, ennek legjobb módja a


forráskód alkalmazása. Az előző példában pontosan ezt tettük Ha azonban előre
definiált, ,,fix" animációval dolgozunk, amely nem igényel futási idejű beavatko­
zást, a teljes animációsorozatot elkészítheljük XAML-leírással. Ez a folyamat
nagyrészt megegyezik azzal, amelyet már megvizsgáltunk; azonban a külön­
böző Animation utótaggal rendelkező típusokat forgatókönyv típusokba csomagol­
juk. A forgatókönyv típusokat a rendszer eseménytriggerekhez kapcsolja.
Vizsgáljunk meg egy teljes animációs példát, amelyet XAML-markuppal
definiálunk. A példa célja, hogy a XAML segítségével megjelenítse az előző
példában alkalmazott, lankadatlanul növekedő, majd zsugorodó címkét. Ta­
nulmányozzuk az alábbi markupot, amelyet a következő részekben alaposan
megvizsgálunk:

<Label content = "Interesting...">


<Label.Triggers>
<EventTrigger RoutedEvent = "Label.Loaded">
<EventTrigger.Actions>
<Beginstoryboard>
<Storyboard TargetProperty = "Height">
<DoubleAnimation From ="40" To = "20 0 "
Duration = "0:0:4"
RepeatBehavior = "Forever"/>
</Storyboard>
</Beginstoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Label.Triggers>
</Label>

A Storyboard tipus szerepe

Ha a legbelső elemtől indulunk kifelé, először a <DoubleAnimation> típussal ta­


lálkozhatunk. A típus ugyanazokkal a tulajdonságokkal dolgozik, amelyeket a
forráskódban beállítottunk (To, From, Duration és Repeatsehavior) . Mint említet­
tük, az Animation utótaggal rendelkező típusokat a <Storyboard> típusban he­
lyezzük el. A típus segítségével leképezheljük az animációt a szülőtípus meg­
határozott tulajdonságára a TargetProperty tulajdonsággal (amely ebben az
esetben a Height tulajdonság).

654
A WPF animációs szolgáltatásai

Az indirekció ezen szintjét az indokolja, hogy a XAML nem támogat


olyan szintaxist, amellyel objektumokon hívhatunk metódusokat, mint pél­
dául a BeginAnimat:ion() metódust a címkén. Tulajdonképpen a <St:oryboard>
és a <Beginst:oryboard> szülőtípusok képviselik a következő forráskód XAML­
központú verzióját:

ll Az animációs logikát: a XAML-ben a forgatókönyvek képviselik.


lblHeight:.BeginAnimat:ion(Label.Height:Property, dblAnim);

Az <EventTrigger> használata

Miután a <St:oryboard> típust definiáltuk, a következő feladat a típust magá­


ban foglaló eseménytrigger meghatározása. A WPF a triggerek három külön­
böző típusát támogatja, amelyek segítségével műveletkészletet definiálha­
tunk. A rendszer ezeket hajtja végre, ha adott esemény bekövetkezik.
A triggerek első típusa megvizsgálja a függőségi tulajdonságok feltételeit a
típuson. Ha függőségi tulajdonságokhoz definiálunk triggereket, a <Trigger>
elemet hívjuk segítségül a trigger meghatározásakor. A triggerek második tí­
pusa a normál .NET-tulajdonságtípusokról gondoskodik, és a <Dat:aTrigger>
elemben definiálhatjuk. A triggerek ezen típusa adatkötési műveletek végre­
hajtásához lehet hasznos. A stílusok és témák tanulmányozásakor, a fejezet ké­
sőbbi részében megvizsgáljuk a <Tri gger> elemet.
Az aktuális példa szempontjából utolsó fontos triggertípus az (<Event­
Tri gger> elemben definiált) esernénytrigger, amelyet WPF-animációk készítése
során használunk. Itt az <Event:Tri gger> elem a címke Loaded eseményéhez
kapcsolódik. Ha az esemény bekövetkezik, a rendszer végrehajtja a <Do ub le­
Ani mat: i on> sorozatot a címke Height tulajdonságán.

Forráskód Az AnimationlnXaml.xaml kódfájlt a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A kulcsképkocka-animáció szerepe

A WPF animációs rendszer utolsó jellemzője, amelyet megvizsgálunk, a


kulcsképkockák alkalmazása. A system.windows.Medi a.Animat:ion névtér több
tagot is tartalmaz, amelyek az Animationus ingKeyFrames utótaggal rendelkez­
nek ( syteAnimat:ionusingKeyFrames, ColorAnimat:ionusingKeyFrames, DoubleAni­

mationus i ngKeyFrames, In32Ani mat:ionus i ngKeyFrames stb.).

655
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

A típusok rnindegyikének van Duration tulajdonsága, amely szabályazza


az animációsorozat teljes időtartalmát Azonban a kulcsképkockák feladata
tájékoztatni az animációs rendszert arról, hogy mikor kell őket használni.
Az Ani mation utótaggal rendelkező típusokkal ellentétben- amelyek csak a
kezdő- és a végpont között mozoghatnak - a kulcsképkocka lehetővé teszi, hogy
a meghatározott időpontokban végrehajtott animáció számára speciális értékek
gyüjteményét hozzuk létre. Például az AnimationusingKeyFrames utótaggal ren­
delkező típusok segítségével elkészíthetünk egy animációt, amelyben egy kör
pattog az ablak körül, egyedi kép mozog egy geometriai alakzat határvonala
mentén vagy időszeletek szerint cseréli egy szövegdobozban a színeket.
Az Ani mationusingKeyFrames utótaggal rendelkező elem hatókörében há­
rom különböző kulcsképkocka-típusgyüjteményt helyezhetünk el, amelyek
rnindegyike az animált elem mozgáskeretét szabályozza:

• LinearXXXKey Frame: A lineáris kulcsképkocka típusok segítségével egye­


nes vonal pon�ai mentén mozgatha�uk az elemet.

• sp l ineXXXKey Frame: A spline kulcsképkocka típusokkal Bezier-görbe


ívén mozgathatunk egy elemet a Keyspl ine tulajdonság segítségéveL

• Di screteXXXKey Frame: A diszkrét kulcsképkocka típusok nem biztosíta­


nak átmenetet a kulcsképkockák között. Ez például akkor lehet hasznos,
ha sztringadatokat "animálunk", amelyek méretét növeljük, vagy határ­
vonallal dolgozunk, amelynek színe változik.

Hasonló rninta szerint a részelemek pontos neve az animált elem típusán


(double, szín, logikai érték stb.) alapul. Az XXX nyilvánvalóan helyőrző szerepet
tölt be. A három kulcsképkocka típus valódi neve a Li nearDoub l eKeyFrame, a
Spl ineDoub l eKeyFrame és a DiscreteDoub l eKeyFrame neveken alapul.

Animáció diszkrét kulcsképkockákkal

A diszkrét kulcsképkocka típusok bemutatásához tételezzük fel, hogy olyan


gombot szeretnénk készíteni, amely animálja a tartalmát három másodperc
alatt megjeleníti az "OK!" feliratot, másodpercenként egy-egy karaktert. Téte­
lezzük fel továbbá, hogy ezt a rendszer rögtön azután megteszi, hogy a gom­
bot betöltötte, és az animációt folyamatosan ismétli. Ezt a viselkedést megfi­
gyelhetjük, ha a 28. fejezetben elkészített simplexam l Ap p. ex e programban el­
helyezzük a következő XAML-kódot:

656
A WPF animációs szolgáltatásai

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Name="myButton" Height="40"
Fontsize="16pt" FontFamily="Verdana" width = "100">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loaded">
<Beginstoryboard>
<Storyboard>
<StringAnimationusingKeyFrames
RepeatBehavior = "Forever"
Storyboard.TargetName="myButton"
storyboard.TargetProperty="Content"
Duration="O:O: 3" FillBe
- havior="HoldEnd">
<DiscreteStringKeyFrame value="" KeyTime="O:O:O" />
<DiscreteStringKeyFrame value="O" KeyTime="O:O:l" />
<DiscretestringKeyFrame value="OK"
KeyTime="O:O:l.S" />
<DiscreteStringKeyFrame value="OK!"
KeyTime="0:0:2" />
</StringAnimationusingKeyFrames>
</Storyboard>
</Beginstoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</Window>

Vegyük észre, hogy először a gombunk számára definiáltunk egy esemény­


triggert, amely biztosí�a, hogy a gomb betöltése után a rendszer végrehaj�a a
forgatókönyvet. A Storyboard.TargetName és a Storyboard.TargetProperty ér­
tékek segítségével a StringAnimationusingKeyFrames típus feladata a gomb
tartalmának módosítása. A <Strin gAnimati onus ingKeyFrames> elemünk ható­
körében három diszkrét Diseretestri ngKeyFrame típust definiáltunk, amelyek
két másodperc alatt módosí�ák a gomb tartalmát (vegyük észre, hogy az idő­
tartam, amelyet a StringAnimationusingKeyFrames beállít, összesen három
másodperc, tehát a "!" és az újrakezdődő "o" megjelenése között rövid szü­
netet láthatunk).

Forráskód Az AnimatedButtonWithDiscreteKeyFrames.xaml kódfájlt a forráskódkönyvtár 30.


fejezetének alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

657
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Animáció lineáris kulcsképkockákkal

A lineáris kulcsképkockákat működés közben látha�uk, ha megvizsgáljuk a


következő XAML-kódot, amely kezdőpontként a gomb középpon�át hasz­
nálja, és teljesen körbeforga�a a gombot. Ha a 360 fokos fordulat befejező­
dött, a gomb fejjel lefelé fordul (majd ismét visszaáll). Tételezzük fel, hogy a
következő XAML-címkézést definiáltuk a <Grid> típusban:

<!-- A gomb körbeforog, majd megfordul, ha rákattintunk -->


<Button Name="myAnimatedButton" width="120" Height = "40"
RenderTransformOrigin="0.5,0.5" content = "OK">

<Button.RenderTransform>
<RotateTransform Angle="O"/>
</Button.RenderTransform>

<!-- Az animáció akkor indul, ha a gombra kattintunk -->


<Button.Triggers>
<EventTrigger RoutedEvent="Button.click">
<Beginstoryboard>
<Storyboard>
<DoubleAnimationusingKeyFrames
Storyboard.TargetName="myAnimatedButton"
Storyboard.TargetProperty=
"(Button.RenderTransform).(RotateTransform.Angle)"
Duration="0:0:2" FillBehavior="Stop">
<DoubleAnimationusingKeyFrames.KeyFrames>
<LinearooubleKeyFrame value="360" KeyTime="O:O:l" />
<DiscreteooubleKeyFrame value="180" KeyTime="0:0:1.5" />
</DoubleAnimationusingKeyFrames.KeyFrames>
</DoubleAnimationusingKeyFrames>
</Storyboard>
</Beginstoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

A gomb definíciójának elején értéket adunk a RenderTransformori gi n tulaj­


donságnak, amely biztosí�a, hogy a forgatás során pontosan a gomb közép­
pon�át használjuk az elforgatás középpon�aként. Ezt követően beállí�uk a
megjelenítési transzformáció kezdőértékét a beágyazott <Button. Render­

Transform> tag segítségével (vegyük észre, hogy a kezdeti szög nulla). Végül
eseménytriggert definiálunk, amely biztosí�a, hogy a rendszer végrehajtsa a
forgatókönyvet, ha a felhasználó a gombra kattint.

658
A WPF animációs szolgáltatásai

Miután elkészültünk a kezdeti beállításokkal, hozzuk létre a <Storyboard>


taget, amely a DoubleAnimationusingKeyFrames típust használja. Vegyük ész­
re, hogy a forgatókönyv célpon�a a gomb példányunk (myAni matedButton), és
az objektumon a tulajdonság, amellyel végül dolgozunk, az Angle tulajdon­
ság. Tanulmányozzuk az új XAML-szintaxist, amelyet akkor kell alkalmaz­
nunk, arnikor a tulajdonság értékéhez függőségi tulajdonságot rendelünk:

<DoubleAnimationusingKeyFrames
Storyboard.TargetName="myAnimatedButton"
storyboard.TargetProperty=
"(Button.RenderTransform).(RotateTransform.Angle)"
Duration="0:0:2" FillBehavior="Stop">

Mint lá�uk, a függőségi tulajdonságokat zárójelek közé kell helyeznünk; en­


nek köszönhetően az egyetlen - a félkövér betűkkel kiemeit - kódsorban le­
írha�uk, hogy "a gombhoz tartozó RotateTransform objektum Ang l e tulajdon­
ságához készítünk animációt". Az animáció végrehajtásához engedélyezett
időt két másodpercre állítsuk be.
A DoubleAnimationusingKeyFrames típusunk hatókörében két kulcsképkoc­
ka-típussal bőví�ük a kódot. Az első (LinearooubleKeyFrame) típus egy má­
sodperc alatt 360 fokkal elforga�a a gombot. Nagyjából fél másodperccel ké­
sőbb a Di screteDoubleKeyFrame típus 180 fokkal elforga�a (és fejtetőre állí�a)
a gombot. Ha az animáció (a DoubleAnimationusingKeyFrames típus Duration
tulajdonságának köszönhetően fél másodperccel később) véget ér, a gomb
visszafordul a megfelelő irányba, rnivel a DoubleAnimationusingKeyFrames tí­
pus Fi llBehavior tulajdonságának értéke Stop (amely a kiinduló állapotba ál­
lí�a vissza az elemet).

Forráskód A SpinButtonWithlinearKeyFrames.xaml kódfájlt a forráskódkönyvtár 30. fejeze·


tének alkönyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Ezzel a WPF révén rendelkezésünkre álló alapvető animációs szolgáltatások


(és a kétdimenziós renderelési módszerek) vizsgálatának végére értünk A té­
makörök rnindegyikéből készülhetne önálló könyv is, ha az összes újdonságot
és érdekességet ismertetni szeretnénk. Mostanra már biztosan megfelelő ala­
pokkal rendelkezünk a folytatáshoz, a következő témakörben figyelmünket az
alkalmazás-erőforrások beágyazása felé fordí�uk.

659
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

A WPF erőforrásrendszere

A következő feladatunk, hogy megvizsgáljuk az alkalmazás-erőforrások be­


ágyazásának és hozzáférésének látszólag nem kapcsolódó témakörét. A WPF
az erőforrások két típusát támoga�a. Az első típushoz a bináris erőforrások tar­
toznak, amelyek a legtöbb programozó által a hagyományos értelemben
"
"erőforrásnak tekintett elemeket (bittérkép fájlok, ikonok, sztringtáblázatok
stb.) képviselik.
Az erőforrások másik típusa, az objektum erőforrások vagy logikai erőforrások
bármilyen .NET-objektumot képviselhetnek, amelyet névvel látunk el és sze­
relvénybe ágyazzuk Lá�uk majd, hogy a logikai erőforrások akkor bizo­
nyulnak hasznosnak, ha grafikus adatokkal kell dolgoznunk, mivel az erő­
forráskönyvtárban rendszeresen alkalmazott grafikai primitíveket (ecseteket,
tollakat, animációkat stb.) definiálhatunk.

A bináris erőforrások használata

Mint említettük, a bináris erőforrások a .NET-alkalmazás kiegészítő darabjai,


mint például sztringtáblák, ikonok és képfájlok (például vállalati logók, ani­
mált képek stb.). Ha a Visual Studio segítségével készítünk WPF-alkalmazást,
a számítógépet utasítha�uk arra, hogy Resource fordítóopció meghatározása­
kor külső erőforrást ágyazzon a szerelvénybe. Ennek bemutatására készít­
sünk új WPF Windows alkalmazást FunWithResources néven. Egészítsük ki
a főablak kezdeti XAML-definícióját három oszloppal rendelkező <Grid>
elemmel, amelynek első cellájában egy Image vezérlő van:

<Window x:Class="FunwithResources.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FunwithResources" Height="207" width="612"
windowstartupLocation="Centerscreen">
<Grid>
<Grid.columnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.columnDefinitions>
<Image Grid.column ="0" Name ="companyLogo"/>
</Grid>
</Window>

660
A WPF erőforrásrendszere

Az első cél, hogy bináris erőforrásként beágyazzuk a képfájlt az alkalmazá­


sunkba. Az erőforrás segítségével beállítjuk a companyLogo Image vezérlő­
elem source tulajdonságát. A választott képfájl segítségével a Visual Studio
Project > Add Existing ltem menüpontjában hozzáadjuk az elemet az aktuális
projekthez (a példában feltételezzük, hogy a fájl neve IntertechBl ue.gif) .

A Resource Build Action

Ha külső képfájlt adtunk az alkalmazásunkhoz, akkor a fájl megjelenik a


Solution Explorer listájában. Ha kiválasztjuk, a Properties segítségével uta­
síthatjuk a fordítót, hogyan dolgozza fel a külső fájlokat a Build Action al­
kalmazásával (lásd a 30.11. ábrát).
Ha az alapértelmezés szerinti Resource beállítást választjuk, a fordító a
.NET-szerelvénybe ágyazza az adatokat, ezért ezeket a külső fájlokat nem
kell az alkalmazással együtt szállítanunk. Tételezzük fel, hogy a képfájlunk
Build Action elemét Resources értékre állítottuk Ekkor a következőképpen
módosíthatjuk az Image vezérlőelem XAML-definícióját (vegyük észre, hogy
a bináris erőforrás nevére név szerint hivatkozunk):

<Image Grid.column ="0" Name ="companyLogo"


source ="IntertechBlue.gif"/>

Ha lefordítjuk és futtatjuk a programunkat, láthatjuk, hogy a képünk kitölti a


<Grid> elemünk első celláját.

Properties @)
IJntertechlllue.gif File Properties"
�·· ÍATl l """
��! � • .
Bu•ld ActJon Resource

Copy to Output Directory None


CustomTool Compile

CustomToolNamespace Content

FileName Embedded Resource

Full Path ApplicationDefinition


Page
Rescuree

BuldAction
How the file rolates to the build and deployment processes.

30. 11. ábra: A bináris erőforrások csomagolásának lehetáségei

661
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

Megjegyzés Ne feledjük, hogy a WPF-alkalmazások a Resource opció segítségével ágyaznak be


erőforrásokat, amely WPF·alkalmazások esetén választható opció. A csábítónak tűnő Embedded
Resource beállítást a Windows Forms alkalmazások használják.

Ha a lefordított alkalmazást a re fl ee to r. ex e (lásd a 2. fejezetet) eszközbe


töl�ük, a 30.12. ábrán látható módon közvetlenül megtekinthe�ük a beágya­
zott erőforrást.

fl Lutz 1\oeder's .NET Reflector

i File v- Tools Help

@l :' P ..--· Irc•=-====..ll


(B 1\ FunWithResources.exe
Value
8 O Resources
OcOO 0000 4d00 53 00 ... (85
� FunWrthResources.g.resources
,•· FunWrthResources.Properties.Resources.resources
0 mtertechblue go 47 49 46 38 39 61 90 00 . (ll
..,.

11 public resource FunWithResources.g.resources


Size: 2279 bytes

..,. • l

30.12. ábra: Beágt;azott bináris erőforrás

A Content Build Action

A kapcsolódó külső fájl Build Action elemét Resource helyett Content értékre
is állítha�uk. Ebben az esetben a rendszer a fordítás során értesíti a szerel­
vényt a külső fájl relatív elérési ú�áról, de nem foglalja a szerelvénybe a biná­
ris adatokat. A beállítás segítségünkre lehet olyan alkalmazás telepítése során,
amely időről időre lecserélt külső erőforrásokat tároló alkönyvtárat (vagy al­
könyvtárakat) tartalmaz, illetve ha a külső erőforrás hálózati megosztási pon­
ton helyezkedik el.
ilyenkor a WPF-erőforráskezelőrendszer az egyszerű fájlnéven kívül további
URI-kat (a külső szerelvénybe ágyazott erőforrások betöltéséhez szükséges szin­
taxist is beleértve) definiál. Ha az olyan helyi erőforrásokkal alkalmazható URI­
formátumokat szeretnénk vizsgálni, amelyeket nem fordítunk le az aktuális sze­
relvénybe, ezzel kapcsolatban a .NET Framework 3.5 SDK dokumentáció "Pack
URis in WPF" című részében találunk részletes információkat.

662
Stílusok készítése és alkalmazása WPF-vezérlőelemeken

Az objektum (vagy másnéven logikai)


erőforrások szerepe
A WPF-erőforrásokat akkor tudjuk a leghatékonyabban használni, ha az egyedi
.NET-objektumokat ágyazunk a szerelvényekbe, hogy alkalmazásunkban
használhassuk őket. Első pillantásra ez elég furcsa ötletnek tűnhet. Azonban
így a programunk több teriiietén közösen használható grafikus elemeket (ecse­
teket, tollakat stb.) definiálhatunk. Ezt a módszert leginkább akkor használhat­
juk, amikor WPF-alkalmazásaink számára egyedi témákat és stílusokat készí­
tünk. A következőkben ezt a témakört vizsgáljuk meg részletesen.

Stilusok készítése és alkalmazása


WPF-vezérlőelemeken

A WPF-alkalmazás felhasználói felületének létrehozása során a vezérlőele­


mek gyakran közös megjelenéssei rendelkeznek. Gondoskodhatunk arról,
hogy a gombok magassága, szélessége, háttérszíne és sztringtartalmuk be­
tűmérete egységes legyen. Noha megtehe�ük, hogy az egyes gombok tulaj­
donságait megegyező értékre állí�uk, ez a megközelítés menetközben meg­
nehezíti a módosítások megvalósítását, mivel változtatások esetén ugyan­
azokat a tulajdonságokat kell több objektumon alaphelyzetbe állítanunk.
Szerencsére a WPE több módszert biztosít, amelyek segítségével minimá­
lis erőfeszítések árán módosítha�uk a felhasználói felület elemeinek (stílusok,
sablonok, arculatok stb.) megjelenését. Lá�uk majd, hogy a stílusok építése
érinti a fejezetben eddig tárgyalt témaköröket (kétdimenziós grafikákat, ani­
mációkat és erőforrásokat). Bemelegítésként kezdjük a stílusok alkalmazásá­
nak vizsgálatávaL

Az inline stilusok használata


A WPF-vezérlők megjelenését és működését a sh1usok segítségével módosít­
ha�uk. A stílus a webalapú stíluslapok rokona, mivel nem rendelkezik saját
felhasználói felülettel, csak bevezet néhány olyan tulajdonságot, amelyet más
felhasználóifelület-elemek alkalmazhatnak. A control osztály leszármazottai
- közöttük természetesen a window típus is - támoga�ák a stílusokat (a Styl e

663
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

tulajdonság révén). Ha szeretnénk stílust létrehozni, ennek egyik módja,


hogy a tulajdonság-elem szintaxist alkalmazzuk, amely lehetövé teszi a stílus
"
"inline hozzárendelését.
Ennek bemutatására módosítsuk a FunWithResources projekt <Grid> ele­
mét, és definiáljunk egy-egy gombot a fennmaradó két cellában. Tanulmá­
nyozzuk a következő markupot, amely egyedi stílust hoz létre a btnClickMe
gombhoz (de a második btnClickMeToo gombra nem vonatkozik):

<Window x:class="FunwithResources.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FunwithResources" Height="207" width="612"
windowstartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.columnDefinitions>
<Image Grid.column ="0" Name ="companyLogo"
Source ="IntertechBlue.gif"/>

<!-- A gomb inline stílussal rendelkezik! -->


<Button Grid.column ="l" Name="btnclickMe" Height="80"
width = "100" Content ="Click Me">
<Button.Style>
<Style>
<Setter Property ="Button.Fontsize" value ="20"/>
<Setter Property ="Button.Background">
<Setter.value>
<LinearGradientBrush StartPoint="O,O" EndPoint="l,l">
<Gradientstop color="Green" offset="O" />
<GradientStop color="Yellow" offset="0.25" />
<Gradientstop color="Pink" offset="0.75" />
<Gradientstop colo r ="Red" offset="l" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Button.style>
</Button>

<!-- Erre a gombra nem érvényes a stílus! -->


<Button Grid.Column ="2" Name="btnclickMeToo"
Height="80" Width = "100" content ="Me Too"/>
</Grid>
</Window>

664
Stílusok készítése és alkalmazása WPF-vezérlőelemeken

Mint lá�uk, a WPF-stílust a <Style> elem segítségével definiálha�uk Ebben a


hatókörben tetszőleges számú <Setter> elemet definiálhatunk, amelyek se­
gítségéve! bevezethe�ük a beállítani kívánt tulajdonságok név/ érték párjait
A példában a gomb Fontsize tulajdonságának értéke 20, a Background tulaj­
donságát pedig a LinearGradi entBrush típussal állí�uk be, amelyet négy ösz­
szekapcsolt szín alkot_

Megjegyzés Ha szükséges, programozott módon létrehozhatunk stílust a kódfájlunkban. Egy­


szerűen állítsuk be a Style tulajdonságot a vezérlőelem-leszármazott típuson.

Noha a stílusok készítésének ezen módja szintaktikai szempontból helyes, az


inline stílusok egyik nyilvánvaló korlátozása, hogy a felhasználóifelület-típus
meghatározott példányához (példánkban a btnClickMe gombhoz) kapcso­
lódnak, nem pedig a hatókörben található gombokhoz. Vegyük észre a 30.13.
ábrán, hogy a második gombra, a btnClickMeToo-ra a btnClickMe gombhoz
rendelt stílus nem vonatkozik

Click Me
EJ
30.13. ábra: Az inline stt1usok ahhoz a vez�rlóelemhez kapcsolódnak, amelyek definiálták őket

A megnevezett stilusok használata

Ha olyan stílust szeretnénk készíteni, amelyet ugyanazon típus több felhaszná­


lóifelület-eleme alkalmazhat (például az összes Button, az összes ListBox tí­
pus), a stílust létrehozha�uk a tároló erőforrásszótárában, ilyenformán objektum
(vagy másnéven logikai) erőforrást definiálva. Például megnevezett stílussal
bővíthe�ük a <WindoW> erőforrásszótárát, és a Key tulajdonságban meghatáro­
zott névvel azonosítha�uk azt Így a XAML-dokumentumban mindenhol
ugyanarra a témára hivatkozhatunk. Nézzük meg a következő módosítást:

665
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

<Window x:class="FunwithResources.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FunWithResources" Height="207"
Width="612" windowstartupLocation="Centerscreen">

<!-- Logikai erőforrás hozzárendelése az ablak


erőforrásszótárához -->
<Window.Resources>
<Style x:Key ="MyFunkyStyle">
<Setter Property ="Button.Fontsize" value ="20"/>
<Setter Property ="Button.Background">
<Setter.value>
<LinearGradientBrush StartPoint="O,O" EndPoint="l,l">
<GradientStop Color="Green" Offset="O" />
<GradientStop Color="Yellow" Offset="0.25" />
<GradientStop Color="Pink" Offset="0.75" />
<Gradientstop color ="Red" offset="l" />
</LinearGradientBrush>
</Setter.value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.columnoefinitions>
<Columnoefinition/>
<Columnoefinition/>
<Columnoefinition/>
</Grid.Columnoefinitions>
<Image Grid.column ="0" Name ="companyLogo"
source ="Intertechslue.gif"/>

<!-- Mindkét gomb ugyanazzal a stílussal rendelkezik -->


<Button Grid.column ="l" Name="btnclickMe" Height="80"
width = "100" Style ="{StaticResource MyFunkystyle}"
content = "click Me"/>
<Button Grid.column ="2" Name="btnclickMeToo" Height="80"
Width = "100" style ="{staticResource MyFunkyStyle}"
Content = "Me Too"/>
</Grid>
</WindoW>

Ezúttal vegyük észre, hogy a stílust a <Window. Resources> elem hatókörében


definiáltuk, és a Key attribútum segítségével a stílusnak a MyFunkyStyle ne­
vet adtuk. Ettől eltekintve maga a stílusdeklaráció megegyezik az előbbiek­
ben létrehozott inline stílussaL Vegyük észre azt is, hogy ha alkalmazni sze­
retnénk a stílust (mint a <Button> definiciók esetén), akkor a staticResource

markupbővítőt hívha�uk segítségül (lásd a 28. fejezetet). A módosítást köve­


tően- a 30.14. ábrán látható módon- minden gomb ugyanazzal a megjele­
néssei rendelkezik.

666
Stílusok készítése és alkalmazása WPF-vezérlöelemeken

�j] FunWithResources

Click Me Me Too

30_ 14- ábra: A megnevezett stausakat az adott hatókörben többJelhasználóifelület-elem alkalmazhatja

Stílusbeállítások felülbírálása

Fontos rámutatnunk arra, hogy ha a felhasználói felület eleme meghatározott


stílust alkalmaz (legyen az inline stílus vagy megnevezett stílus), az elemnek
módjában áll "felülbírálni" a tulajdonságok beállításait. Tételezzük fel például,
hogy szeretnénk a második gombon használni a MyFunkystyle stílus Back­
ground beállítását, de kisebb betűrnéretet alkalmaznánk. Hogy ezt megvaló­

sítsuk, egyszerűen rendeljünk új értéket a módosítani kívánt tulajdonsághoz


a nyitó XAML-elemben:

<Button Grid.column ="2" Name="btnclickMeToo" Height="80"


width = "100" style ="{staticResource MyFunkyStyle}"
Fon ts i ze = "10" content = "Me Too"/>

Létező stílusok leszármaztatása

Létező stílusok alapján létrehozhatunk új stílusokat a Basedon tulajdonság ré­


vén, ha a stílust, amelyet bővíteni szerétnénk, a Key tulajdonság segítségével
elláttuk a megfelelő névvel. Például a következő NewFunkystyle stílus (ame­
lyet a <Window. Resources> hatókör gyermekelemeként adtunk a kódhoz) al­
kalmazza a MyFunkystyl e stílus betűméretét és háttérszínét, de 20 fokkal el­
forgatja a felhasználói felület elemet:

<Style x:Key ="NewFunkyStyle"


Basedon = "{staticResource MyFunkystyle}">
<Setter Property = "Button.Foreground" value = "Blue"/>
<Setter Property = "Button.RenderTransform">
<Setter.value>
<RotateTransform Angle = "20"/>
</Setter.value>
</Setter>
</Style>

667
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

A 30.15. ábrán működés közben láthatjuk a gombokon alkalmazott új stílust.

30. 15. ábra: A leszármazott sh?us alkalmazása

A stilusok kiterjesztése

A stílus erőforrásszótárba helyezése mindenképpen megfelelő lépés. Mi tör­


ténik azonban akkor, ha több felhasználóifelület-elemen szeretnénk ugyanazt
a stílust alkalmazni? Pillanatnyilag a MyFunkystyle stílus csak gombokon al­
kalmazható, rnivel a stílus a tulajdonság-elem szintaxis segítségével explicit
módon hivatkozik a Button típusra.
A WPF-stílusok egyik roppant érdekes jellemzője, hogy a <Setter> ható­
körben hozzárendelt értékek tiszteletben tartják a származtatás fogalmát. Így,
ha a stílusunkban a felhasználóifelület-elemek közös szülőtípusán (system.
windows.contro ls.control ) állítunk be tulajdonságokat, hatékony módon de­
finiálhatunk közös stílust az összes WPF-vezérlőelem számára, például a kö­
vetkező stílusmódosítás:

<Window.Resources>
<Style x:Key ="MyFunkyStyle">
<Setter Property ="Control.Fontsize" value ="20"/>
<Setter Property ="Control.Background">
<Setter.value>
<LinearGradientBrush StartPoint="O,O" EndPoint="l,l">
<GradientStop Color="Green" Offset="O" />
<Gradientstop color="Yellow" offset="0.25" />
<Gradientst:op color="Pink" offset="0.75" />
<GradientStop Color ="Red" offset="l" />
</LinearGradientBrush>
</Setter.value>
</Setter>
</Style>

</Window. Resources>

668
Stílusok készítése és alkalmazása WPF-vezérlőelemeken

lehetővé teszi számunkra, hogy a MyFun ky sty l e stílust TextBox típusokon, il­
letve Button típusokon (vagy a control osztályból származó bármely elemen)
alkalmazzuk. Tételezzük f el, hogy a következő, új felhasználóifelület-elemet
adtuk az aktuális <Grid> elem új oszlopához:

<TextBox Grid.column ="3" N a me "txtA n dMe " Height="40" width = "100"


=

style ="{staticResource MyFunkyStyle}" Text = "And me!"/>

Megjegyzés Ha olyan stílust készítünk, amely ősosztálytípust alkalmaz, nem kell aggódnunk,
hogy a leszármazott típusok által nem támogatott értéket rendelünk a függőségi tulajdonság­
hoz. Ha a leszármazott típus nem támogatja az adott függőségi tulajdonságot, a rendszer fi­
gyelmen kívül hagyja.

A stilusok korlátozása

Ha olyan stílust szeretnénk definiálni, amelyet kizárólag a f elhasználóif elület­


elemek bizonyos típusain (például csak Button típusokon, semmi máson) le­
het alkalmazni, a stílus nyitó elemében beállítha�uk a TargetType tulajdonsá­
got. A tulajdonságnak az adott vezérlőelem metaadat-leírását kell meghatá­
rozni, tehát az x:Type markupbővítőt kell használnunk (lásd a 28. fejezetet).
Ennek bemutatására a következőképpen módosítha�uk a MyFunkyStyle stílust
(a módosítással markuphibát kapunk, ha az előző TextBox típuson próbáljuk
alkalmazni ezt a stílust):

<Style x:Key ="MyFunkystyle" TargetType = "{x:Type Button}">

</Style>

Stilusok hozzárendelése implicit módon

A WPF-stílusokat meghatározott XAML-hatókörben implicit módon is beál­


lítha�uk a felhasználói f elület vezérlőin. Megnevezett stílusok készítése során
a Key tulajdonság hozzárendelése gyakorlatilag opcionális, ha a Target tulaj­
donság segítségével korlátoztuk a stílus alkalmazását:

<Style TargetType = "{x:Type Button}">

</S ty l e>

669
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Ily módon, a hatókörben az összes gomb implicit módon alkalmazza a My­


Funkystyle stílust még akkor is, ha nem használják a StaticResource jelölő ki­

terjesztést:

<Button Grid.column ="l" Name="btnclickMe"


Height="80" width = "100" content "Click Me"/>

<Button Grid.column ="2" Name="btnClickMeToo"


Height="80" width = "100" content = "Me Too"/>

Ne feledjük, hogy amikor a TargetType tulajdonsággal olyan stílust definiá­


lunk, amelyben nem állítottuk be a Key értéket, a rendszer a stílust kizárólag
a megegyező névvel rendelkező típusokon alkalmazza. Ezért, ha az aktuális
stílust a következőképpen módosítanánk:

<Style TargetType = "{x:Type control}">

</Style>

sem a Button, sem a TextBox típus nem alkalmazná a stílust! Ez annak kö­
szönhető, hogy a stílusunk ezen iterációja a control ősosztályt célozza meg.
Soha ne feledjük, hogy az osztályt vagy valamely leszármazottat képviselő
stílus fogalma csak a megnevezett stílusok esetén alkalmazható.

Forráskód A FunWithResources kódfájlokat a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Stílusok definiálása triggerekkel

A WPF-stílusok következő jellemzője, amelyet megvizsgálunk, a triggerek fo­


galma. Atriggerek segítségével <Setter> elemeket definiálhatunk, amelyeket a
rendszer kizárólag meghatározott feltétel teljesülése esetén alkalmaz. Például
növelhetjük a betűméretet, ha az egeret a gomb fölé húzzuk. Vagy egy megha­
tározott színnel kiemelhetjük a fókuszban lévő szövegdobozt. A triggerek
rendkívül hasznosak lehetnek ilyen helyzetekben, mivel ha egy tulajdonság
módosul, lehetövé teszik meghatározott műveletek végrehajtását, és ehhez
nem szükséges C#-forráskódot készítenünk mögöttes kódfájlban.

670
Stílusok készítése és alkalmazása WPF-vezérlőelemeken

A következő XAML-markup három szövegdobozt definiál, amelyek núnd­


egyikénél a style tulajdonság értéke a TextBoxstyle stílus. A szövegdobozok
megjelenése (magasság, szélesség stb.) megegyezik, csak annak a szövegdo­
boznak a háttere sárga színű, amely az adott pillanatban fókuszban van.

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Window.Resources>
<Style x:Key ="TextBoxstyle" TargetType = "{x:Type TextBox}">
<Setter Property = "Foreground" value = "Black"/>
<Setter Property = "Background" value = "LightGray"/>
<Setter Property = "Height" value = "30"/>
<Setter Property = "Width" value = "100"/>

<!-- A következő <setter> elem csak akkor érvényesül,


ha a szövegdoboz az alkalmazás fókuszában van -->
<Style.Triggers>
<Trigger Property = "IsFocused" value = "True">
<Setter Property = "Background" value = "Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>

<StackPanel>
<TextBox Name = "txtone"
Style = "{StaticResource TextBoxstyle}" />
<TextBox Name = "txtTwo"
Style = "{staticResource TextBoxstyle}" />
<TextBox Name = "txtThree"
Style = "{staticResource TextBoxstyle}" />
</StackPanel>
</Window>

Ha az előző XAML-kódot beírjuk a SimpleXamlPad.exe alkalmazásba, láthat­


juk, hogy amikor a szövegdobozok között lépegetünk, az éppen kiválasztott
vezérlőelem háttere sárga színű, míg a többiek az alapértelmezés szerinti
szürke színű háttérrel rendelkeznek. A triggerek intelligens elemek, hiszen
ha a trigger feltétele nem teljesül, a rendszer automatikusan az alapértelmezés
szerinti értéket rendeli a vezérlőhöz. Ezért, mihelyst a szövegdoboz kikerül
az alkalmazás fókuszából, automatikusan az alapértelmezett színnel jelenik
meg anélkül, hogy bármit tennünk kellene.

671
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

A triggereket úgy is megtervezhetjük, hogy a defirúált <Setter> elemeket a

rendszer több feltétel teljesülése esetén alkalmazza (hasonlít a több feltétellel


működő if utasítás programozásához). Tételezzük fel, hogy egy szövegdoboz
hátterét csak akkor szeretnénk sárga színnel megjeleníteni, ha a doboz fókusz­
ban van, és az egérmutató a doboz határain belül helyezkedik el. Ennek meg­
valósításához a feltételeket a <MultiTrigger> elem segítségével defirúálhatjuk:

<Window. Resources>
<Style x:Key ="TextBoxstyle" TargetType "{x:Type TextBox}">
<Setter Property "Foreground" value "Black"/>
<Setter Property "Background" value "LightGray"/>
<Setter Property "Height" value = "30"/>
<Setter Property "width" value = "100"/>
<!-- A következő <Setter> elemet csak akkor alkalmazza
a rendszer, ha a szövegdoboz az alkalmazás fókuszában van,
és az egeret a doboz fölé húzzuk -->
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property = "IsFocused" value = "True"/>
<Condition Property = "IsMouseover" value = "True"/>
</MultiTrigger.Conditions>
<Setter Property = "Background" value = "Yellow"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>

Forráskód A StyleWithTriggers.xaml kódfájlt a forráskódkönyvtár 30. fejezetének alkönyvtá­


ra tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Stilusok hozzárendelése programozott módon

A stílusok vizsgálatának befejezéseként készítsünk egyszerű alkalmazást,

amely bemutatja, hogyan rendelhetünk a forráskódban a felhasználóifelület­


elemekhez stílusokat az új stylesAtRuntime nevű Visual Studio WPF Win­
dows alkalmazás projekt segítségéveL
Célunk, hogy egy gomb számára a <WindoW> elem erőforrásszótárában három
különböző stílust defirúáljunk. Az első, a Tiltstyle stílus 10 fokkal elforgatja a
gombot. A második, a Greenstyle stílus előre meghatározott értékre állítja a
Background, a Foreground és a Fontsize tulajdonságokat. Az utolsó, a Mouse­

overstyle stílus a Greenstyle stílusan alapul, de triggerfeltételt ad a kódhoz,

amely megnöveli a gomb betűiDéretét és szövegét. Íme a stílusok XAML-leírásai:

672
Stílusok készítése és alkalmazása WPF-vezérlőelemeken

<Window-Resources>
<!-- A stílus 10 fokos szögben megdönti a gombokat -->
<Style x:Key ="Tiltstyle" TargetType = "{x:Type Button}">
<Setter Property = "RenderTransform">
<Setter.value>
<RotateTransform Angle = "10"/>
</Setter.value>
</Setter>
</Style>

<!-- A stílus tavaszias színvilágot kölcsönöz a gomboknak -->


<Style x:Key ="Greenstyle" TargetType = "{x:Type Button}">
<Setter Property = "Background" value ="Green"/>
<Setter Property = "Foreground" value ="Yellow"/>
<Setter Property ="Fontsize" value ="15" />
</Style>

<!-- A stílus megnöveli a gomb méretét, amikor az egeret fölé


húzzuk -->
<Style x:Key ="Mouseoverstyle"
Basedon ="{staticResource Greenstyle}"
TargetType = "{x:Type Button}">
<Style.Triggers>
<Trigger Property ="IsMouseover" value ="True">
<Setter Property ="Fontsize" value ="20" />
<Setter Property ="Foreground" value ="Black" />
</Trigger>
</Style.Triggers>
</Style>
</Window. Resources>

A Window objektum tartalmaz egy olyan Grid objektumot, amely a TextBlock,


a ListBox és a Button típusok helyét rendezi el. A ListBox tartalmazza a té­
mák nevét, és kezeli a selectionehanged eseményt. Íme, a felhasználói felület
kapcsolódó XAML-kódja:

<Grid>
<Grid.columnoefinitions>
<Columnoefinition />
<ColumnDefinition />
</Grid.columnDefinitions>
<StackPanel Grid.column="O">
<TextBlock Textwrapping ="Wrap" Fontsize ="20"
Padding="5,5,5,5">
Please select a style for the button on the left.
</TextBlock>
<ListBox Name ="lststyles" Height ="60" Background = "Yellow"
selectionchanged ="combostyles_changed" />
</StackPanel>
<Button Name="btnMouseoverstyle" Grid.Column="1"
Height="40" width="100">My Button</Button>
</Grid>

673
30. fejezet: WPF 2D grafikus renderelés, erőforrások és témák

Az utolsó feladat, hogy kitöltsük a ListBox-ot, és a kapcsolódó kódfájlban ke­


zeljük a sel ecti onchanged eseményt. Tanulmányozzuk, hogy a következő
forráskódban hogyan nyerjük ki név szerint az aktuális erőforrást az örökölt
Fi nd Resou rce() metódus segítségéve!:

public partial class Mainwindow window


{
public Mainwindow()
{
I nitial iz e component() ;

ll Elemek hozzáadása a listamezőhöz.


lststyles.Items.Add("Tiltstyle");
lstStyles . I tems . Add ( " G reen s tyl e " ) ;
lststyles . I tems . Add ( "Mo u seove rstyle " ) ;
}

p rotect e d vo i d c ombostyles_c h anged(ob j ect sende r ,

Ro u ted EventA rgs a r g s)


{
ll Kiválasztott stílusnév beolvasása a listamezőből.
System . windows . S tyle cu r rstyle = ( Syst e m . Wi ndows . Style)
Find R eso u rce(lst Styles.selectedva l ue) ;

ll A gomb típus stílusának beállítása.


this.btnMouseoverStyle.Style = cu r r styl e ;
}
}

Ezt követően lefordíthaljuk az alkalmazást. Ha az egyes listaelemekre kattin­


tunk, a gomb megjelenése a stílusnak megfelelőerr változik (lásd a 30.16. ábrát).

ii] StyfesAtRuntime l l @} liii3illl


o

Please select a style for the


button on the left.
.
GreenStyfe
MouseOverStyle
l r:�---:-
�lton --.
J
L----
----

30. 16. ábra: 5 tausok beállítása programozott módon

Forráskód A StylesAtRuntime kódfájlokat a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

674
Vezérlőelem felhasználói felületének módosítása sablonok segítségével

Vezérlőelem felhasználói felületének


módositása sablonok segitségével

A stílusok segítségével hatékony (és egyszerű) módon megváltoztatha�uk a


WPF-vezérlőelemek alapvető megjelenését, ha definiáljuk az értékek alapér­
telmezett készletét a vezérlőelemek tulajdonságkészlete számára. Noha a stí­
lusok segítségével módosítha�uk a felhasználói felület különféle beállításait,
a vezérlőelem általános megjelenése érintetlen marad. Függetlenül attól, ho­
gyan alakí�uk a Button típus megjelenését a különböző tulajdonságok segít­
ségéve!, alapvetően ugyanaz a téglalap alakú vezérlő áll rendelkezésünkre,
amelyet évek óta ismerünk. Mi történik akkor, ha szeretnénk teljesen megvál­
toztatni (és hatszögletű háromdimenziós képre cserélni) a Button típus meg­
jelenését, de az objektumnak továbbra is Button típusként kellene viselked­
nie? Mi lenne, ha használni szeretnénk a WPF folyama*lző funkcionalitását,
de a készültségi fok százalékos arányát kördiagrarnmal jelenítenénk meg a
felhasználói felületen? Ahelyett, hogy manuálisan egyedi vezérlőelemet kel­
lene készítenünk (ahogyan más grafikus felület eszközrendszer esetén ten­
nénk), erre a célra a WPF vezérlőelem-sablonokat biztosít.
A sablonok egyértelműen elkülönítik a vezérlőelem felhasználói felületét (az­
az a megjelenését) a vezérlőelem viselkedésétóL (azaz az eseménykészletétől és a
metódusaitól). A sablonok segítségével igényeink szerint szabadon módosít­
hatjuk a WPF-vezérlők kimenetének megjelenítését. Programozási szempont­
ból a vezérlőelem-sablonokat a controlTemplate ősosztály képviseli, amelyet a
XAML-kódban a megegyező névvel rendelkező <ControlTemplate> elemmel
írhatunk le. Miután létrehoztuk a sablont, WPF-oldalakhoz, -ablakokhoz vagy
-vezérlőelemekhez kapcsolhatjuk a Temp l ate tulajdonság segítségéveL
A vezérlőelem-sablon készítésének egyik érdekes szempontja, hogy a
<ContentPresenter> elem révén a sablonban teljhatalommal rendelkezünk a

vezérlő tartalmának pozícionálása felett. Az elem segítségével meghatároz­


ha�uk a tartalom helyét és felhasználói felületét az adott vezérlőelem-sablon
számára. Továbbá, ha a sablonban nem definiáljuk a <ContentPresenter>
elemet, akkor a sablont alkalmazó vezérlőelem nem jelenít meg semmilyen
tartalmat, még akkor sem, ha definiálja azt:

<!-- Ha az alkalmazott sablon nem rendelkezik <ContentPresenter>


elemmel, nem jeleníti meg az "oK" feliratot -->
<Button Name ="myButton"
Template ="{StaticResource roundButtonTemplate}">
eli ck!
</Button>

675
30. fejezet: WPF ZD grafikus renderelés, erőforrások és témák

Ha ezen a ponton vezérlőelem-sablont definiálunk, nem meglepő, ha úgy


érezzük, a munka hasonlít a stílusok elkészítésének folyamatához. Például a
sablonokat is rendszerint erőforrásszótárban tároljuk, a sablonok is támogat­
ják a triggereket, stb. Mindezek ismeretében már jól felkészültünk a sablonok
létrehozására. Vizsgáljunk meg néhány sablont működés közben a control­
Templates új WPF Windows alkalmazás projekt segítségéveL

Egyedi sablon készítése

Vizsgáljuk meg a következő egyszerű sablont, amely a <Grid> elemben két


<Ellipse> típussal definiál egy kerek gombot. A tartalom a vezérlőelem kellős
közepén jelenik meg, mivel a HorizontalAlignment és a verticalAlignment tulaj­
donságokat Center értékre állítottuk. Vegyük észre, hogy a sablon a round­
ButtonTemplate nevet kapta (az erőforráskulcs segítségéve!), amelyre akkor hi­
vatkozhatunk, amikor hozzárendeljük a Button típus Template tulajdonságát:

<Window x:class="ControlTemplates.Mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fun with control Templates" Height="162" width="281" >
<Grid>
<Grid.Resources>
<!-- A kerek gomb egyszerű sablonja a rács elemei számára -->

<ControlTemplate x:Key ="roundButtonTemplate"


TargetType ="{x:Type Button}">
<Grid>
<Ellipse Name ="OuterRing" width ="75"
Height ="75" Fill ="DarkGreen"/>
<Ellipse Name ="InnerRing" width ="60"
Height ="60" Fill ="Mintcream"/>
<ContentPresenter HorizontalAlignment="Center"
</Grid>
</ControlTemplate>
</Grid.Resources>

<!-- A sablonunk alkalmazása Button típuson -->

<Button Name ="myButton" Foreground ="B_lack"


Fontsize ="20" Fontweight ="Bold"
Template ="{StaticResource roundButtonTemplate}"
Click ="myButton_Click"> Click!
</Button>
</Grid>
</Window>

676
Vezérlőelem felhasználói felületének módosítása sablonok segítségével

Azt is vegyük észre, hogy a Button típus kezeli a kattintási eseményt (tételez­
zük fel, hogy a kattintási esemény kezelője tájékoztató üzenetet jelenít meg a
MessageBox. show() metódus segítségéve!). Ennek azért van jelentősége, mert

a gomb - annak ellenére, hogy már nem a hagyományos "szürke téglalap"


formában jelenik meg - még mindig System.windows.Contro ls. Button típus,
és ugyanazokkal a tulajdonságokkal, metódusokkal és eseményekkel rendel­
kezik mint az előre becsomagolt felhasználóifelület-megjelenéssei rendelkező
gomb. A 30.17. ábra muta�a az egyedi gombtípusunkat működés közben.

ij ControiTernpiates lc:;Jf@J liiii;M"

e
30.17. ábra: Egyszerű sablon a Button típus számára

Triggerek hozzáadása a sablonokhoz

Pillanatnyilag a vezérlőelem-sablon lehetövé teszi, hogy a gomb körkörös


formában jelenjen meg. Ha rákattintunk, észrevehe�ük, hogy kiváltódik a
kattintási esemény (a MessageBox. show() metódus megjeleníti a sztringadata­
inkat); azonban a gomb megnyomásának látható jele nincs. Ez annak kö­
szönhető, hogy az alapértelmezett gombnyomás-animációt eltávolítottuk, és
egyedi felhasználói felületet alkalmaztunk. Ha szeretnénk visszatenni (vagy
kicserélni) a nyomógomb-animációt, saját egyedi triggereket kell hozzáad­
nunk a kódhoz.
A következőkben bemuta�uk az aktuális sablon egy újabb verzióját,
amely két triggert kezel. Az első trigger figyeli, hogy az egérmutató a gomb
felett helyezkedik-e el vagy sem. Ha igen, módosí�uk a külső ellipszis háttér­
színét (egyszerű vizuális effektus). A második trigger figyeli, hogy a gomb
felületén a felhasználó kattint-e az egérrel vagy sem. Kattintás esetén a külső
ellipszis H eight és wi dth értékeit megnövelj ük, hogy vizuális visszajelzést ad­
junk a felhasználónak:

<Grid.Resources>
<!-- Egyszerű sablon a kerek gomb szamara -->

<ControlTemplate x:Key ="roundButtonTemplate"


TargetType ="{x:Type Button}">

677
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

<Grid>
<Ellipse Name ="OuterRing" width ="75"
Height ="75" Fill ="DarkGreen"/>
<Ellipse Name ="InnerRing" width ="60"
Height ="60" Fill ="Mintcream"/>
<ContentPresenter HorizontalAlignment="Center"
verticalAlignment="Center"/>
</Grid>
<!-- Triggerekkel váltjuk ki a "kattintási" effektust -->
<ControlTemplate.Triggers>
<Trigger Property ="IsMouseover" value ="True">
<Setter TargetName ="OuterRing" Property ="Fill"
value ="MediumseaGreen"/>
</Trigger>
<Trigger Property ="IsPressed" value ="True">
<Setter TargetName ="OuterRing" Property ="Height"
value ="90"/>
<Setter TargetName ="OuterRing" Property ="Width"
value ="90"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Grid.Resources>

A 30.18. ábrán látha�uk az rsMouseover trigger effektusát, a 30.19. ábra pedig


az rsPressed trigger eredményét muta�a.

;f.: ControiTemplates l = l @l �

30.18. ábra: Az IsMouseOver frigger múködés közben

l�..l Controrremplates l = l @l liiiliiil

30.19. ábra: Az JsPressed frigger múködés közben

678
Vezérlőelem felhasználói felületének módosítása sablonok segítségével

Sablonok használata a stilusokban

Pillanatnyilag a sablonunk egyszerűen a gomb megjelenését definiálja. A ve­


zérlőelem alapvető tulajdonságainak (tartalom, betűméret, betűvastagság
stb.) beállítása a Button típus feladata:

<!-- Pillanatnyílag a Button típus állít be alapvető


tulajdonságértékeket, nem a sablon -->
<Button Name ="myButton" Foreground ="Black" Fontsize ="20"
Fontweíght ="Bold"
Template ="{staticResource roundButtonTemplate}"
clíck ="myButton_clíck">

Nagyszerű dolog lenne ezeket az értékeket a sablonban beállítani. Ily módon


hatékonyan kialakíthatnánk a típus alapértelmezett megjelenését. Talán már
kitaláltuk, hogy ez a feladat a WPF-stílusokra hárul. Amikor a stílust készít­
jük (és gondoskodunk az alapvető tulajdonság beállításokról), a sablont a stí­
lusban definiálhatj uk! Íme a módosított Grí d erőforrás magyarázattal együtt:

<Gríd.Resources>
<!-- A stílus a Button típus alapvető beállításait definiálja -->
<Style x:Key ="roundButtonTemplate" TargetType ="{x:Type Button}">
<Setter Property ="Foreground" value ="Black"/>
<Setter Property ="Fontsíze" value ="20"/>
<Setter Property ="Fontweight" value ="Bold"/>

<!-- íme, a sablon! -->


<Setter Property ="Template">
<Setter.value>
<!-- Egyszerű sablon a kerek gomb szamara -->
<ControlTemplate TargetType ="{x:Type Button}">
<Grid>
<Ellipse Name ="OuterRing" width ="75" Height ="75"
Fill ="DarkGreen"/>
<Ellipse Name ="InnerRing" width ="60" Height ="60"
Fill ="Mintcream"/>
<ContentPresenter HorizontalAlignment="Center"
verticalAlignment="Center"/>
</Grid>

<!-- Trigger, amely a nyomógomb effektust biztosítja -->


<ControlTemplate.Triggers>
<Trigger Property ="IsMouseover" value ="True">
<Setter TargetName ="OuterRing"
Prop_erty ="Fill" value ="MediumseaGreen"/>
</Trigger>
<Trigger Property ="IsPressed" value ="True">

679
30. fejezet: WPF 20 grafikus renderelés, erőforrások és témák

<Setter TargetName ="OuterRing" Property = "H eight"


value ="90"/>
<Setter TargetName ="OuterRing" Property ="Width"
value ="90"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.value>
</Setter>
</Style>
</Grid.Resources>

Először is vegyük észre, hogy a <ControlTemplate> helyett a <Style> elem ka­


pott erőforráskulcs értéket. Majd figyeljük meg, hogy a stílus ugyanazokat az
alapvető tulajdonságokat állítja be, mint amelyeket a Button típus deklaráció­
jában beállítottunk ( F oregro und, Fontsize és Fontweight) . A <ControlTemplate>
elemet a Template tulajdonság módosításával, egy normál stílusú <Setter>
elemmel definiáljuk Ezzel a módosítással létrehozhatjuk az egyedi gombun­
kat, ha a következőképpen állítjuk be a style tulajdonságot:

<!-- A stílus/sablon alkalmazása a Button típuson -->


<Button Name ="myButton"
Style ="{staticResource roundButtonTemplate}"
click ="myButt on _clic k ">
eli ck!
</Button>

Noha a gomb megjelemtése és viselkedése megegyező, a sablonok stílusokba


ágyazásának nyilvánvaló előnye az, hogy közös tulajdonságok számára rögzí­
tett értékkészletet biztosíthatunk Emlékezzünk rá, hogy természetesen módo­
síthatjuk az alapértelmezett értékeket egy vezérlőelemenként eltérő értékre:

<!-- Stílus beállítása, de az előtér színének módosítása -->

<Button N a me myButton "


"
=

Style ="{staticResource roundButtonTemplate}"


click ="myButton_click" Foreground ="Red">
eli ck!
</Button>

Forráskód A ControlTemplates kódfájlokat a forráskódkönyvtár 30. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Ezzel befejeztük a WPF stílus- és sablonmechanizmusainak vizsgálatát. Mint lát­


hattuk, a sablonok alapját rendszerint több grafikai stílus képezi, és a sablonokat
végeredményben bináris erőforrásként csomagoljuk az alkalmazásunkba.

680
Összefoglalás

Ezzel a könyv jelen kiadásában a WPF vizsgálatának végéhez érkeztünk.


Az utóbbi három fejezetben rengeteget tanultunk a mögöttes WPF progra­
mozási modellről, a XAML-szintaxisról, a vezérlőelem-kezelésről és a grafi­
kus tartalom elkészítéséről. Noha a WPF roppant nagy témakör, és a könyv­
ben csak egy részét vizsgáltuk, mindez, a célunknak megfelelően, szilárd
alapokat biztosít a további felfedezéshez.

Összefoglalás

Mivel a Windows Presentation Foundation (WPF) grafikaiművelet-intenzív


grafikus felület API, nem meglepő, hogy a grafikus kimenet megjelenítéséhez
több módszer áll rendelkezésünkre. A fejezet elején megvizsgáltuk a WPF­
alkalmazások grafikus renderelésének három rnódját (alakzatok, rajzok és vi­
zuális eszközök), és tanulmányoztuk a különböző renderelési primitíveket,
például az ecseteket, a tollakat és a transzformációkat
Ezt követően a C#-forráskód, valamint a XAML-deklarációk szempon�á­
ból vizsgáltuk meg a WPF animációs szolgáltatások szerepét. Ebben a rész­
ben megismerkedtünk az idősorok, a forgatókönyvek és a kulcsképkockák
jellemzőiveL Megismerkedtünk a WPF erőforráskezelési API-jaival, láthat­
tuk, hogy a WPF-erőforrások a sztringtáblázatok, ikonok és bittérképtipusok
elvárt készlefe rnellett további elemeket hozhatnak magukkal, de egyedi ob­
jektumokat is képviselhetnek, amelyeket az erőforrásszótár tárol.
A fejezet végén összefoglaltuk ezeket a témaköröket, és felfedeztük a
WPF-stílusok és -sablonok szerepét. Mint láttuk, a WPF a grafikai primitívek,
az animációs szolgáltatások és a beágyazott erőforrások gyűjteménye segít­
ségével megkönnyíti a vezérlőelemek megjelenésének kialakítását.

681
7. rész

Webes
alkalmazások
fejlesztése
ASP.NET
segítségével
HARMINCEGYEDIK FEJEZET

ASP.NET weboldalak készitése

A könyv eddigi alkalmazáspéldái konzol- és asztali GUl-alapú front endekre


koncentráltak. A következő három fejezetben bemuta�uk, hogy a .NET plat­
form hogyan könnyíti meg a böngészőalapú megjelenítő rétegek létrehozását
az ASP.NET nevű technológia révén. Bevezetésként áttekintünk néhány
kulcsfontosságú, webközpontú fogalmat (HTTP, HTML, ügyfél- és kiszol­
gálóoldali szkriptírás), valamint a Microsoft kereskedelmi webkiszolgálóját
(US), illetve az ASP.NET fejlesztői webkiszolgálót (weboev. webserve r. exe)
vizsgáljuk meg.
A webes bevezetést követően, a fejezet további részei az ASP.NET-webol­
dalak szerkezetével foglalkoznak (beleértve az egyoldalas és a mögötteskód­
modelleket is), és egy Page-leszármazott típus felépítését tanulmányozzák.
A fejezet emellett a web. con fi g fájl szerepét is bemuta�a, amelyet a későbbi,
főként a webbel foglalkozó fejezetekben fogunk használni.

A HTTP szerepe

A webes alkalmazások lényegesen eltérnek a hagyományos asztali alkalma­


zásoktóL Az első szembetűnő különbség, hogy egy termékszintű webes al­
kalmazás legalább két, hálózatba kötött gépet igényel (fejlesztés alatt termé­
szetesen elég egyetlen gép, amely egyszerre tölti be a böngészőalapú kliens
és a webkiszolgáló szerepét). A webes éiilkalmazások természetéből adódóan
a hálózatba kötött gépeknek meg kell egyezniük, hogy milyen átviteli proto­
kollon keresztül küldenek és fogadnak adatokat. A hálózati gépeket összekötő
átviteli protokoll a HTTP (Hypertext Transform Protocol).
31. fejezet: ASP. NET- weboldalak készítése

A HTTP kérés/válasz-ciklus

Arnikor a kliensgép elindít egy webböngészőt (mint például az Opera, a


Mozilla Firefox vagy a Microsoft Intemet Explorer), egy HITP-kéréssel fordul
egy adott erőforráshoz (általában weboldalhoz) a távoli kiszolgálón. A HTIP
szövegalapú protokoll, amely szabványos kérés/válasz paradigrnára épül.
Például, ha a http:/ j www.intertech.com oldalra navigálunk, a böngésző a
Domain Name Service (DNS) segítségével a regisztrált URL-t négy részből álló,
32 bites numerikus értékké konvertálja, amelyet IP-címnek nevezünk. Ekkor a
böngésző megnyit egy csatornát (általában nem biztonságos kapcsolatot a 80-
as porton keresztül), és elküldi a HITP-kérést a célhelyre, feldolgozásra.
A webkiszolgáló fogadja a bejövő HITP-kérést, és eldönti, hogy feldolgozza­
e a kilens által küldött bemeneti értékeket (például egy szövegdobozban lévő ér­
tékeket, egy jelölőnégyzet-jelölést stb.), és összeálli�a a megfelelő HITP-választ
A webprogramozók számos technológiát alkalmazhatnak (CGI, ASP, ASP.NET,
JSP stb.) a HITP-válaszban megjelenített tartalom dinamikus generálására. Ek­
kor az ügyféloldali böngésző megjeleníti a webkiszolgálótól kapott HTML-t.
A 31.1. ábra az alapvető HITP kérés/válasz-ciklust muta�a be.

Webldazolgéló
l<li8ns oldal bilrtgé6l6 Bejövő

HTTP-kérés Webalkalmazás
A HTTP válaszból ki nyert �··
HTML megjelenitése Kimenő (Tetszőleges számú kiszol-
gáló oldali erőforrás, pl:
.. HTTP-válasz
" *.aspx, *.asp, és *.htm fájlok)

( .•. :f···_-.,�";· ·'·� :\':;>;:- :�

31. 1. ábra: A HITP kérés/válasz-ciklus

A HTTP állapotmentes protokoll

A webes fejlesztést az a tény is markánsan megkülönbözteti a hagyományos


asztali programozástól, hogy a HTIP lényegében állapotmentes átviteli proto­
koll. Amint a webkiszolgáló elküldi a választ a kliensnek, mindent elfelejt az
előző interakcióróL Ez persze nem igaz a hagyományos asztali alkalmazások­
ra, ahol a végrehajtható fájl állapota általában élő és működik, amíg a fel­
használó le nem állí�a a kérdéses alkalmazást.

686
Webes alkalmazások és webkiszolgálók

Emiatt a fejlesztő felelőssége, hogy lépéseket tegyen a webhelyre bejelentke­


zett felhasználókkal kapcsolatos adatok (például bevásárlókosár elemei, hitelkár­
tyaszámok, otthoni és munkahelyi cúnek stb.) "megjegyzése " érdekében. Ahogy
az a 33. fejezetben kiderül majd, az ASP.NET több módszert (munkamenet-vál­
tozók, sütik és alkalmazásváltozók) biztosít az állapot kezelésére, amelyek közül
sok szinte bármely webplatformorr megtalállia tó, ugyanúgy, mint egyes .NET­
specifikus módszerek (például az ASP.NET profilkezelő API).

Webes alkalmazások és webkiszolgálók

A webes alkalmazásokat fájlok ( *. ht:m, *.asp, *. aspx, képfájlok, XML-alapú fájl­


adatok stb.) és kapcsolódó összetevőik (például egy .NET-kódkönyvtár vagy
egy korábbi COM-kiszolgáló) gyűjteményeként is felfogha�uk, amelyet egy
adott webkiszolgáló könyvtárkészlete tárol. Ahogyan azt a 33. fejezetben lát­
juk majd, az ASP-webalkalmazások sajátos életciklussal rendelkeznek, és
számos olyan eseményt (például kezdeti indítás vagy végső leállítás) biztosí­
tanak, amelyeket felhasználva speciális műveleteket hajthatunk végre a web­
hely működése közben.
A webkiszolgálók olyan szoftvertermékek, amelyek webalkalmazásokat
hosztolnak, és általában több kapcsolódó szolgáltatást nyújtanak, mint pél­
dául az integrált adatvédelem, a fájlátviteli protokoll (FTP), az üzenetváltási
szolgáltatás stb. Az Internet Information Services (IlS) a Microsoft vállalati
szintű webkiszolgáló terméke, ami természetesen a klasszikus ASP-webalkal­
mazások mellett az ASP.NET-webalkalmazásokat is támoga�a.
Termékszintű ASP.NET-webalkalmazás létrehozásakor gyakran kell majd
együttműködnünk az IIS-sel. Azonban jó, ha tudjuk, hogy a Windows operá­
ciós rendszer telepítésekor az IlS-t a telepítő nem telepíti automatikusan (rá­
adásul nem minden Windows-verzió támoga�a az IlS-t, például a Windows
XP Home sem). Ezért, a fejlesztő gépünk konfigurációjától függően, szükség
lehet az IlS telepítésére, mielőtt továbbhaladunk a fejezetben. Ehhez nyissuk
meg az Add/Remave Programot a Control Panel mappából, és válasszuk az
Add/Remave Windows Components lehetőséget. További részletekért for­
duljunk a Windows-súgóhoz.

687
31. fejezet: ASP.NET-weboldalak készítése

Megjegyzés A legjobb, ha az liS telepítését a Visual Studio 2008 telepítése előtt elvégezzük.
Ha a Visual Studio 2008 telepítését követően telepítjük az liS-t, az ASP.NET-webalkalmazásaink
nem fognak megfelelően működni (csak egy üres lapot kapunk). Szerencsére az liS-t újrakonfigu­
rálhatjuk, hogy hasztolja a .NIT-alkalmazásokat, az asp ne t _ r egi is. exe parancssori eszköz
futtatásával, és az /i opció megadásával.

Ha a megfelelő módon telepítettük az liS-t a munkaállomásunkra, az Admi­


nistrative Tools mappában elérhe�ük (amely a Control Panel mappában ta­
lálható), ha kétszer az Internet Information Services kisalkalmazásra kattin­
tunk. Ebben a fejezetben kizárólag a Default Web Site csomóponttal foglalko­
zunk (lásd a 31.2. ábrát).

o-o �����-�--����--�-��-It����_! -- ___ - ----�-- ----- - -··


- '� - l. 18-
11==�,_-
Fft: v--
--�-.... ---------------------- -
Hep
- - ----------- ---· --
.--
�--·-- � - �
Default Web Site Home
!ll M.ageWebSit!!
,. 11j INTERUBER (lNlERTECH\.atrodstn)
Groupby. Arn � Re::�rt
-0 AppMution Pook
ASP.NET ··
:--t\,RPSites •
� � it
Stop
•-Al VidiSites
...-öd��-w��� =
IT�L ' "' li>.!_�lelt
:. � Hpnd_cfóent .NET .NET .NEl Profile .NETRo� .NETTnr.t .NETUscrs B«�WSeWebSite
i• .� AutolotWCFSeMce
Compil;�tion Glob.1Muti4n '"'"" [il llrowse

� ��� @,
� -� AutoWdiServic.e EditSite
�-� C•rOrdetSavicd.ib .-J.J
t·-�� Chtddnventol)' Appiiettion Connection P;�ges1nd SesKm St.te SMTP E·m1il

l D��":���
Providus

�-::! FomuAuthentk.tion Sctting5 Sl:rin� Control.s


Adv;�nctd SeltlflgS..
1> ,., M�thWtbSeMct:
_

!·--� �ASMQ VJCW Appiaoi io:-.�


t--:} MyApp
;.. -:! My(rtd::Onc:eApp
-��
ASP Auther.tic-
'�(l
Authorizat.-
lll
CGl
o
Oef•ult
01]
Directory
View Ytrtwl Deectorm
Con......
r>-:} Mylot.Editor
Rules Docu�t Drawsing
;.-�MyWtb�t
i- :J Rqxuts
�-� RtportServtt' Gíl
Error P19� Fo�ilt-d
+o!!
Htnd!tt
� J�
ISAPI Filttts MIME Type:s
.:.il
Module
; ·V U!lfOfmApp
RrquestTr1... Mtppiogs
t> -i�Vb6ComP1usApp
�-::lWebSite
�--:} WrittTof"lkform
�-:} XBAPTestttApp

:r'-�-------___j ��cOfll�tv-

31. 2. ábra: Az liS-kisalkalmazás

Az liS virtuális könyvtárainak szerepe

Egyetlen liS-konfiguráció több olyan webalkalmazást képes hosztolni, ame­


lyek mindegyike egy virtuális könyvtárban található. Minden virtuális könyv­
tár a merevlemez egy fizikai könyvtárára képezhető le. Ezért, ha létrehozunk
egy új, CarsRUs nevű virtuális könyvtárat, a külvilág például a http:/ j
www.CarsRUs.com URL segítségével navigálhat erre a webhelyre (feltéve,
hogy a webhely IP-címét regisztráltattuk). Ez a virtuális könyvtár a háttérben
a webkiszolgáló egy fizikai gyökérkönyvtárára képezhető le, mint például a
C:\inetpub\www root\AspNetCarsSite, amely a CarsRUs webalkalmazás
tartalmát foglalja magában.

688
Webes alkalmazások és webkiszolgálók

Ahogy azt a fejezet későbbi részében láthatjuk, amikor ASP.NET-webal­


kalmazásokat hozunk létre a Visual Studio 2008 segítségéve!, lehetőségünk
van arra, hogy az IDE automatikusan generáljon egy új virtuális könyvtárat
az aktuális webhely számára. Ha szükséges, természetesen kézzel is létre­
hozhatunk virtuális könyvtárakat, ha a jobb gombbal az IlS Default Web Site
csomópontjára kattintunk, majd az úszómenüből (a context menüből) a New>
Virtual Directory lehetőséget választjuk (Vista esetében csak válasszuk az
Add Virtual Directory menüpontot).
Az új virtuális könyvtár létrehozása után meg kell adnunk a nevét és azt a
fizikai mappát, amelyben a webtartalmat elhelyezzük Az US használatának
bemutatására (és első webes példánk megalapozására), hozzunk létre egy új
könyvtárat a merevlemezen a webtartalom számára. A példa kedvéért téte­
lezzük fel, hogy ez a könyvtár a C:\ CodeTests\ CarsWebSite. Kattintsuk a
jobb gombbal az US Default Web Site csomópontjára az új, Cars nevű virtuá­
lis könyvtár létrehozásához, amely erre az új könyvtárra képezhető le. A 31.3.
ábra a végeredményt mutatja.

f�iJ!ntemetlnfonnotionSe�(DS)Monager -,- ·-- -w1-�-��1:; l=liiD-!PJi'



�w li.� • INTERUBER
- - - --- - ---
t W�b Sit6
-· --- ---- - - -
t Default Web Site
-- ---- ----·
------ -- - ---
t Cars t l ijj .( �j:; i 8 ...
File v� H�p

��
.......
®J Cars Home
......

M.nogeVIrtuol Directory
o.-·:J
;> -{J
CarOrderSc:rvicelib
Ched:In�ntory
Group by. Area � .. ... j.Qi Explore
__

ASP.NET ·-···-------·-----·---- • Br-.",... V"ortuol Directory


� -:1 FormsA.uthentication
Browse
� '9I- MathWebService
ti]


,) MSMQ � �
.NETProfile
� Edit Virt... Directory
O·� MyApp .NEl .NET .NETTrust [J Basóc s.ttiniJS...
Compilation Globalization
MyCiickOnceApp
l�els
;, . :} Advanc� Settings...
�-� MyTedEditor --�
t'-.� MyWebService
r<-:'1
LEJ
Application
lZ�l
Conm�ction

P�tg6 and
......
Providers
8 Help

t>--:} RfPorts
� -:} Report�rver Settings Strings Conttols

l> .z"'j UriFormApp


; ij Vb6ComPiusApp
�--W WebSite

Session State SMTP E-mail
� -:} WriteToFileform
� -:} _XB�PT6terApp
t>-� C�rs t\ � grf)Features Vi���Content View
'Ready �::

31 . 3. ábra: A Cars virtuális könyvtár

Nemsokára némi tartalommal töltjük meg ezt a webhelyet

689
31. fejezet: ASP.NET·weboldalak készítése

Az ASP.NET fejlesztőkiszolgálója

A .NET 2.0 előtt az ASP.NET-fejlesztöknek a webtartalom fejlesztése és tesz­


telése során az IlS virtuális könyvtárait kellett használniuk. Az US-től való
erős függőség eredményeképpen sok esetben a szükségesnél jóval bonyolul­
tabb volt a fejlesztőcsapat dolga (arról nem is beszélve, hogy a hálózati rend­
szergazdák nem örültek, hogy az összes fejlesztői gépen telepíteniük kellett
az US-t). Szerencsére ma már lehetőségünk nyílik a weboev.webserver.ex e
webkiszolgáló használatára. Ez a segédprogram lehetövé teszi, hogy a fej­
lesztök az IlS nélkül hasztoljanak ASP.NET-webalkalmazásokat. Az eszköz
segítségével a weboldalakat a gépünk bármely könyvtárában létrehozhatjuk
és tesztelhetjük Nagy hasznunkra válhat, ha csapatban fejlesztünk, vagy ha
olyan Windows-verzióval készítünk ASP.NET-webprogramokat, amely nem
támogatja az US-t (mint például a Windows XP Home).

Megjegyzés A weboev. webserv e r. exe nem használható klasszikus (COM-alapú) ASP-webal­


kalmazások tesztelésére vagy hosztolására. Ez a webkiszolgáló csak ASP.NET-webalkalmazáso­
kat és/vagy .NET-alapú XML-webszolgáltatásokat hosztolhat.

Amikor a Visual Studio 2008 segítségével készítünk webhelyeket, lehetősé­


günk van a weboev.webserver.exe segítségével hosztolni az oldalakat (ahogy a
fejezet későbbi részében látjuk majd). Emellett azonban manuálisan is együtt­
működhetünk az eszközzel a Visual Studio 2008 parancssorán keresztül. Ha
beírjuk az alábbi parancsot:

weboev.webserver.exe -7

a megjelenő üzenetdobozban az érvényes parancssori paramétereket láthatjuk.


Röviden, meg kell adnunk egy használaton kívüli portot (a/port: opcióval), a
webalkalmazás gyökérkönyvtárát (a/path: opcióval) és egy opcionális virtuá­
lis útvonalat a/vpath: opcióval (ha nem adjuk meg a/vpath: opciót, az alapér­
telmezés a/). Tekintsük meg a következő parancsot, amely tetszőleges portot
nyit meg a korábban létrehozott C:\ CodeTests\ CarsWebSite könyvtár tartal­
mának megtekintésére:

weboev.webserver.exe /port:12345 /path:"c:\CodeTests\CarswebSite"

690
A HTML szerepe

A parancs beírása után elindítha�uk a böngészőnket az oldalak lekéréséhez.


Ha a CarsWebSite mappában létezik Default.aspx nevű fájl, akkor írjuk be a
következő URL-t:

http://localhost:12345/Carswebsite/Default.aspx

Ebben és a következő fejezetben látható legtöbb példa a Visual Studio 2008 ré­
vén használja a webDev.webse rver. ex e kiszolgálót, ahelyett, hogy liS-beli vir­
tuális könyvtárban hosztolná a webtartalmat Miközben ez a megközelítés egy­
szerűsítheti a webalkalmazások fejlesztését, ne feledjük, hogy ezt a webkiszol­
gálót nem termékszintű webalkalmazások hasztolására hozták létre. Tisztán fej­
lesztési és tesztelési célokra alkalmas. Ha elkészültünk a webalkalmazással, a
webhelyet be kell másolnunk egy IlS-beli virtuális könyvtárba.

Megjegyzés A Mana-projektben (lásd a B függeléket) található egy ingyenes ASP.NET-bővít­


mény az Apache-webkiszolgálóhoz. Ennek segítségével lehetséges ASP.NET-webalkalmazásokat
létrehozni és hasztolni Microsoft Windowstól eltérő operációs rendszereken. További informá­
ciókért látogassunk el a http: //www.mono-project.com/ASP.NET oldalra.

A HTML szerepe

Most, hogy kanfiguráltunk egy könyvtárat a webalkalmazásunk hosztolá­


sára, és kiválasztottunk egy webkiszolgálót, amely hosztként szolgál, hozzuk
létre magát a tartalmat. Emlékezzünk vissza arra, hogy a "webalkalmazás"
csak egy kifejezés a webhely működését biztosító fájlkészletre. Igazság sze­
rint ezen fájlok nagy része HTML-ben definiált tokeneket tartalmaz. A HTML
szabványos jelölőnyelv, amelyet annak leírására használunk, hogy a literális
szövegek, képek, külső hivatkozások és különféle HTML-alapú felhasználói
felületi vezérlők hogyan jelenjenek meg az ügyféloldali böngészőben.
A webes fejlesztésnek ez a jellege az egyik fő oka annak, hogy sok progra­
mozó idegenkedik a webalapú prograrnak készítésétőL Noha a modem IDE-k
(köztük a Visual Studio 2008) és webes fejlesztői plattormok (mint például az
ASP.NET) automatikusan generálják a HTML nagy részét, jobban járunk, ha
gyakorlati HTML-tudásra teszünk szert az ASP.NET használata során.

691
31. fejezet: ASP. NET-weboldalak készítése

Megjegyzés Emlékezzünk rá a 2. fejezetből, hogy a Microsoft több ingyenes IDE-t kiadott az


Express-termékcsaláddal (mint például a Visual C# Expresst). Ha érdeklődünk a webes fejlesztés
iránt, letölthetjük a Visual Web Developert is. Ezt az ingyenes IDE-t kifejezetten az ASP.NET­
webalkalmazások létrehozására fejlesztették.

Annak ellenére, hogy ez a rész nem dolgozza fel teljes körűen a HTML-t, ve­
gyük át az alapokat, és hozzunk létre egyszerű webalkalmazásokat HTML,
klasszikus (COM-alapú) ASP, illetve liS segítségéveL Ez majd útmutatóul
szolgál azoknak, akik hagyományos asztali alkalmazásfejlesztési háttérrel
térnek át az ASP.NET-re.

Megjegyzés Ha már otthonosan mozgunk a weboldal-fejlesztés folyamatainak világában,


nyugodtan ugorjunk az "A klasszikus ASP problémái" című részhez.

HTML-dokumentumstruktúrák

A HTML-fájlok olyan tagekből épülnek fel, amelyek egy adott weboldal megje­
lenését és működését írják le. Ahogy várhattuk, a HTML-dokumentumok
alapstruktúrája ugyanolyan marad. Például a *. htm fájlok (vagy a *. html fáj­

lok) <html> és </html> tagekkel kezdődnek és végződnek, általában egy <body>


részt definiálnak, és így tovább. Ne feledjük, hogy a hagyományos HTML nem
különbözteti meg a kis- és nagybetűket. Ezért a hosztböngésző szempon�ából
a <HTML>, a <html> és a <HtmL> ugyanazt jelenti.
A HTML-alapok szemléltetéséhez nyissuk meg a Visual Studio 2008 alkal­
mazást, hozzunk létre egy üres HTML-fájlt a File > New > File menüpont segít­
ségéve!, majd mentsük a C:\ CodeTests\ CarsWebSite könyvtárba default. htm
néven. Látha�uk, hogy a kezdeti markupban nincs semmi különös:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/fEN"


"http://www.w3.org/TR/xhtmll/DTD/xhtmll-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Untitled Page</title>
</head>
<body>

</body>
</html>

692
A HTML szerepe

Először is figyeljük meg, hogy ez a HTML-fájl egy DOCTYPE feldolgozási


utasítással kezdődik. Ez tudatja az IDE-vel, hogy a tárolt HTML-tageket az
XHTML-szabvány szerint kell ellenőrizni. Ahogy gondoltuk, a hagyományos
HTML szintaxisa elég "laza" volt, például megtehettük, hogy olyan nyitó
elemet definiáljunk (például sortöréshez <br>), amelyhez nem tartozott meg­
felelő záróelem (ebben az esetben </br>), nem különböztette meg a kis- és
nagybetűket, és így tovább. Az XHTML-szabvány W3C-specifikáció, amely
néhány igen fontos korlátozással látja el a HTML jelölőnyelvet

Megjegyzés Alapértelmezés szerint a Visual Studio 2008 az összes HTML-dokumentumot az


XHTML 1.0 Transitional ellenőrző séma szerint érvényesíti. Tulajdonképpen a HTML-ellenőrző­
sémákkal biztosítjuk, hogy a markup megfeleljen bizonyos szabványoknak. Ha egy másik ellen­
őrzősémát szeretnénk megadni, nyissuk meg a Tools > Options párbeszédablakot, majd jelöljük
ki a Validation csomópontot a HTML alatt. Ha nem szeretnénk megtekinteni az ellenőrzés so­
rán talált hibákat, kapcsoljuk ki a Show Errors jelölőnégyzetet.

A <html> és a </html> címkékkel a dokumentumok kezdetét és végét jelöljük.


Figyeljük meg, hogy a <html> nyitó címkét egy xml ns (XML-névtér) attribútum
tovább minősíti, amely a dokumentumban esetlegesen előforduló, különféle
címkéket minősíti (ismétlésképpen, ezek a tagek alapértelmezés szerint az
XHTML-szabványra épülnek). A webböngészők ezen címkék segítségével tud­
ják, mikor kezdjék alkalmazni a dokumentum törzsében meghatározott megje­
lenítési formátumokat Az aktuális tartalom jelentős részét a <body> hatókör­
ében definiáljuk Hogy kissé csinosabbá tegyük, módosítsuk az oldal fejlécét az
alábbiak szerint:

<head>
<title>This Is the Cars web site</title>
</head>

Nem meglepő, hogy a <title> címkével a hívó webböngésző címsorában


megjelenő sztringet határoztuk meg.

HTML-űrlapok fejlesztése

A legtöbb *. htm fájl lényegi része a <form> elemek hatókörében található.


A HTML�űrlap olyan kapcsolódó felhasználóifelületi-elemek megnevezett
csoportja, amelyeket a felhasználói bemenetek összegyűjtésére használunk,
ami később a webalkalmazásba továbbítődik egy HTTP-kérésen keresztül.

693
31. fejezet: ASP.NET-weboldalak készítése

A HTML-űrlapot ne tévesszük össze a böngészőben látható teljes megje­


lenítő területtel. A HTML-űrlap valójában inkább a <form> és a </form> cím­
kékben elhelyezett vezérlők logikai csoportosítása:

<html xmlns="http://www.w3.org/1999/xhtml" >


<head>
<title>This Is the cars web site</title>
</head>
<body>
<form id="defaultPage">
<!-- Ide szúrjuk be a webes felhasználói felület tartalmát ->
</form>
</body>
</html>

Ehhez az űrlaphoz a "defaultPage" nevet és azonosítót rendeltük hozzá. A nyitó


<form> címke általában tartalmaz egy műveleti attribútumot, amely azt az URL-t

adja meg, ahová az űrlap adatait továbbí�uk, illetve meghatározza az adatátvitel


metódusát ( POST vagy GET). Hamarosan megvizsgáljuk a <form> címkét ebből a
szempontból, előtte azonban nézzük meg, milyen elemeket helyezhetünk el egy
HTML-űrlapon (az egyszerű szöveges részeken túl). A Visual Studio 2008 esz­
köztára tartalmaz egy HTML-fület, amelyen a 31.4. ábrán látható módon kivá­
lasztha�uk az egyes HTML-alapú felhasználóifelületi-vezérlőket

Toolbox
.. Ji
[8-ifTML l �-l
� Pointer
D Input (Button) i
� Input (Reset)
� Input (Submit) i
l
� Input (Text) l

!!§ Input (File) !


@ Input (Password) ;;i

0 Input (Checkbox) l
;
® Input (Radio) i
i�ti: Input (Hidden) l
ii:lll Textarea
B:) Table
Gil Image
g Select
B Horizantal Rule
�'"""'

§ Div
. Fl Geo�_.".. ..
.•
. _ - - ... �

�� Seiver Explorer!� Toolbox1


31.4. ábra: Az eszköztár HTML-füle

694
A HTML szerepe

Hasonlóan a Windows Forms vagy a WPF-alkalmazások készítésének folya­


matához, ezeket a HTML-vezérlőelemeket a HTML-tervezőfelületre húzhat­
juk. A HTML-szerkesztő alsó ablaktáblája alapértelmezés szerint a HTML vi­
zuális elrendezését mutatja, a felső pedig a kapcsolódó markupot. A szer­
kesztő másik előnye, hogy amikor kijelölünk egy markupot vagy egy HTML­
beli felhasználóifelület-elemet, a kapcsolódó ábrázolást a rendszer kiemeli.
Ezáltal könnyedén átláthatjuk a módosításaink hatását (lásd a 31.5. ábrát).

.;-�
fo�autt.iit.D1:_si�,te-J • x

<Mnt Objects & Events • (No Events)


l' <!DOCTYPE htm1 PUBLIC "-//W3C//DTD XHTML 1.0 Tran�i.tiona1//EN" "http;//r,rww.w3.org/TR/<
2 la <html. xmlns-"http: //WWJil. •113 orq/1 999/xhtmJ." >
3B <he od>
<t1t1e>Th�� I� the Car � Web �1te</tit.1e> r�
t
5 </head>
6- <body>
7 . � t "" "o" '""
'= r�
a i
:-<1-
= •" a"'' e"
" 'taul�P a oe•>
l
e r'- -��=- 1n•st;!i !f!-b..lll; s;gc��
3 �."J
10, </body>
1: </htm.l>
12�
, r _
... ;._." -
- -
-
Ul

form#defaultPaoel
ll �)

[-
Q Design l o Split
---
ls Source l GJI<html>ll<body>! l<t
orrn#defou�Poge>l [B
31. 5. ábra: A Visual Studio 2008 HTML-szerkesztője megjeleníti a maprkupot és
a felhasználói felületi elrendezést

HTML-alapú felhasználói felület készítése

Mielőtt a HTML <form> űrlaphoz hozzáadnánk a HTML-vezérlőket, érdemes


kiemelni, hogy a Visual Studio 2008 integrált HTML-tervezője és a Properties
ablak révén lehetővé teszi, hogy a *.h tm fájlok megjelenését és működését át­
szabjuk. Ha a 31.6. ábrán látható módon kijelöljük a DOCUMENT elemet a
Properties ablak legördülő listájában, beállíthatjuk a HTML-lap különböző
tulajdonságait, például a háttérszínt, a háttérképet, a fejlécet és így tovább.
Módosítsuk a default.htm fájl <body> törzsét, hogy olyan szöveget jelenít­
sen meg, amely felhasználónév és jelszó beírására szólítja fel a felhasználót,
majd válasszunk ki egy tetszőleges háttérszínt (szöveges tartalmat közvetle­
nül a HTML-tervezőbe is beírhatunk és formázhatunk):

695
31. fejezet: ASP. NET-weboldalak készítése

<html xmlns="http://www.w3.org/1999/xhtml" >


<head>
<title>This is the cars web site</title>
</head>
<body bgcolor="Navajowhite">
<!-- Felhasználói bemenet kérése -->
<hl align="center"> The cars Login Page</hl>
<P align="center"> <br/>
Please enter your <i>user name</i> and <i>password</i>.
</p>
<form id="defaultPage">
</form>
</body>
</html>

ProJl"rties

DOCUMENT
· ll
'

Sackground
l
BgColor
Q
Charsot l
Class
ld
Link

Styl•
Text
Trtlo This ls the C;ars Web site
Vlink

IIgColor
Documont background color.

�Properties Dynamic :.
,

31.6. ábra: HTML-dokumentum szerkesztése a Visual Studio 2008 Properties ablaka segítségivel

Most készítsük el magát a HTML-űrlapot. Általánosságban elmondható,


hogy minden HTML-vezérlőt egy név attribútummal (az elem programozott
azonosítására szolgál) és egy típus attribútummal (a <form> deklarációba be­
szúrt felhasználóifelület- elem típusának meghatározására szolgál) írunk le.
Attól függően, hogy melyik felhasználóifelület-elemet kezeljük, az adott
elemhez kapcsolódó további attribútumokat találunk a Properties ablakban,
amelyeket módosíthatunk.
A felhasználói felület, amelyet hamarosan elkészítünk, két szövegmezőt
(ebből az egyik egy Password vezérlő) és két gombot (az egyik az űrlapadatok
továbbításához, a másik az űrlapadatok értékének alaphelyzetbe állításához
szükséges) foglal magában:

696
A HTML szerepe

<!-- Készítsünk űrlapot a felhasználói adatok begyűjtésére -- >


<form id="defaultPage">
<P align="center">
user Name:
<input id="txtuserName" type="text" name="txtuserName"/></P>
<P align="center">
Password:
<input name="txtPassword" type="password" id="txtPassword"/></P>
<p align="center">
<input name="btnsubmit" type="submit" value="Submit"
id="btnsubmit"/>
<input name="btnReset" type="reset" value="Reset"
id="btnReset"/>
</P>
</form>

/clebult.htm• rsa.tp....]. �x
Client Objects & Events • {No Events)
l Si l O� er Name: -
16� <inpuc id-"t:xtU:serName" cype-"t:ext:" nare.e-"'txtU:serName"></p>
<p aliqn"'""center">

Password:
<input name-"'txtPassword" type•"password" id-"txtPassword"'></p>

E
<p aliqnu"'center">
<input na:r:;.e-"btnSubmi.t" type•"submit" value-"Submi.t" id.,."btnSubm.i.t">
22; �� �.!bt�e�e�•�·�-�e�• valqe••áe��-b��·1
23:
-
</p>
2�� </f.orm>
;
25

:�l �����;: '"


_]

The Cars Login Page

Please enter yom: ,...". Mmtl andpassword_

UserNamc:f
Password: l - -

G1 Design Ic Spi� js Source l Ejj<html>ll<body>ll<torm#dofoui!Page>l�l<inpu-Res<ot>j [B


31.7. ábra: A default.htm oldal kezdeti állapota

Figyeljük meg, hogy minden vezérlőhöz vonatkozó nevet és azonosítót ren­


deltünk (txtuserName, txtPassword, btnsubmit és btnReset) . Rendkívül fontos
megjegyezni, hogy minden egyes bemeneti elemhez tartozik egy típus nevű
kiegészítő attribútum.

697
31. fejezet: ASP.NET·weboldalak készítése

Ez az attribútum olyan felhasználóifelületi-elemként jelöli a gombokat,


amelyek automatikusan törlik az összes mezőt és visszaállí�ák a kezdőérté­
küket (type="reset"), jelszóként maszkolják a bemenetet (type="password"),
vagy továbbí�ák az űrlapadatokat (type="submit"). A 31.7. ábrán az eddig el­
készült oldal látha tó.

Az ügyféloldali szkriptirás szerepe

Egy adott *. htm fájl a HTML felhasználóifelületi-elemeken kívül olyan szkript­


kódblokkokat is tartalmazhat, amelyeket a válaszfolyamba továbbítunk azért,
hogy a kérelmező böngésző feldolgozhassa. Az ügyféloldali szkriptírás alkal­
mazásának két fő oka van:

• Már a böngészőben szeretnénk ellenőrizni a felhasználói bemenetet,


mielőtt visszaküldenénk azt a webkiszolgálónak

• Együttműködhetünk a böngésző dokumentum-objektummodelljével


(DOM).

Az első pontban leírtakkal kapcsolatban azt kell tudni, a webalkalmazások ve­


lejárója, hogy gyakori üzenetváltásokat (visszaküldést) kell folytatni a kiszolgá­
lóval a böngészőben megjelenített HTML frissítése érdekében. Amíg a vissza­
küldéseket nem lehet elkerülni, a hálózati adatátvitelt minimalizálha�uk. Az
üzenetváltások csökkentés�nek egyik módja, ha ügyféloldali szkriptírással el­
lenőrizzük a felhasználói bemenetet, mielőtt az űrlapadatokat továbbítanánk a
webkiszolgálónak. Ha hiba lép fel (például az egyik kötelező mező nem tar­
talmaz adatot), a felhasználót a webkiszolgálóhoz való visszaküldés nélkül fi­
gyelmeztethe�ük a hibára (hiszen semmi sem kellemetlenebb a felhasználók
számára, mint a lassú hálózati kapcsolaton a visszaküldések eredményeként a
bemeneti hibákkal kapcsolatos figyelmeztetések megjelenése!).

Megjegyzés Tudni kell, hogy még ügyféloldali ellenőrzés alkalmazásakor (amit a válaszidő
csökkentése érdekében teszünk) is előfordulhat ellenőrzés magán a webkiszolgálón. Ezzel biz­
tosíthatjuk, hogy az adatok nem változtak meg az adatátvitel során. Ahogy azt a következő fe·
jezet bővebben tárgyalja, az ASP.NET ellenőrző vezérlőelemek automatikusan végrehajtják az
ügyfél- és kiszolgálóoldali ellenőrzésekeL

698
Az ügyféloldali szkriptírás szerepe

A felhasználói bemenetek ellenőrzése mellett az ügyféloldali szkriptek révén


kapcsolatba léphetünk a webkiszolgáló objektummodelljével (DOM). A leg­
több kereskedelmi forgalomban lévő böngésző tartalmaz olyan objektum­
készletet, amellyel befolyásolha�uk a böngésző viselkedését. Azonban kel­
lemetlen lehet, hogy a különböző böngészők hasonló, de mégsem azonos ob­
jektummodelleket tartalmaznak. Ezért, ha olyan ügyféloldali szkriptkódblok­
kot bocsájtunk ki, amely használja a DOM-ot, nem biztos, hogy az összes
böngészőben ugyanúgy működik majd.

Megjegyzés Az ASP.NET biztosítja a HttpRequest tulajdonságot. A böngészőtulajdonság se·


gítségével futási időben meghatározhatjuk az aktuális kérést küldő böngésző képességeit.

Az ügyféloldali szkriptkódokat több szkriptnyelven megírha�uk. A két legked­

veltebb nyelv a VBScript és a JavaScript A VBScript a Visual Basic 6.0 progra­


mozási nyelv része. A Microsoft Intemet Explorer az egyetlen olyan webbön­
gésző, amely beépített támogatással rendelkezik az ügyféloldali VBScript számá­
ra (más böngészők tartalmazhatnak opcionális beépülő modulokat). Ezért, ha azt
szeretnénk, hogy a HIML-oldalaink bármely böngészőben megfelelőerr működ­
jenek, ne VBScriptben írjuk meg az ügyféloldali szrkipteket.
A másik népszerű szkriptnyelv a JavaScript Rendkívül fontos tudni, hogy a
JavaScript semmilyen tekintetben nem része a Java nyelvnek. Amíg a Java­
Script és a Java szintaxisa valamelyest hasonló, a JavaScript nem teljesen önálló
OOP-nyelv, ezért jóval kevésbé hatékony, mint a Java. Jó hír, hogy az összes
mai webböngésző támoga�a a JavaScriptet, ezért leginkább ez alkalmas
szkriptírásra.

Megjegyzés Hogy tovább bonyolítsuk a dolgot, emlékezzünk vissza arra, hogy a JScript.NET
olyan felügyelt nyelv, amellyel érvényes .NET·szerelvényeket készíthetünk egy szkriptszerű
szintaxissal.

Példa az ügyféloldali szkriptirásra

Az ügyféloldali szkritírás szerepének bemutatásához először vizsgáljuk meg,


hogyan kell elkapni az ügyféloldali HTML grafikus felületi vezérlők esemé­
nyeit. Tételezzük fel, hogy hozzáadtunk egy további HTML-gombot
(btnHel p) a default. h tm oldalhoz, amely révén a felhasználó súgóinformáció­
kat tekinthet meg.

699
31. fejezet: ASP. NET-weboldalak készítése

A gomb kattintási eseményének kezeléséhez jelöljük ki a HTML-űrlapter­


vező bal felső legördülő listájában a btnHelp lehetőséget, majd válasszuk az
"
"onclick eseményt a jobb oldali legördülő listábóL Ezzel egy onclick attri­
bútumot adtunk az új Button típus definíciójához:

<input id="btnHelp" type="button" value="Help" language="javascript"


onclick="return btnHelp_onclick()" l>

A Visual Studio 2008 szintén létrehoz egy üres JavaScript függvényt, amelyet
a rendszer akkor hív meg amikor a felhasználó a gombra kattint. Ebben az
üres rutinban az alert() metódust használjuk ügyféloldali üzenetdoboz
megjelenítésére:

<script language="javascript" type="text/javascript">


ll <!CDATA[
function btnHelp_onclick() {
alert("oude, it is not that hard. click the submit button!");
}
ll]]>
</script>

Figyeljük meg, hogy a szkriptblokkot egy CDATA szakaszba illesztettük be.


Ennek egyszerű oka van, ha az oldal olyan böngészőbe kerül, amely nem tá­
mogatja a JavaScriptet, a böngésző megjegyzésblokként értelmezi a kódot, és
figyelmen kívül hagyja. Az oldal természetesen kevésbé működőképes, de a
lényeg, hogy nem robban szét, amikor megjelenik a böngészőben.

A default.htm űrlapadatainak ellenőrzése

Most frissítsük a default.htm oldalt, hogy támogasson néhány ügyféloldali


ellenőrző algoritmust. A cél annak biztosítása, hogy amikor a felhasználó a
Submit gombra kattint, meghívjunk egy JavaScript függvényt, amely ellenőr­
zi, hogy nincsenek-e üres értékek az egyes szövegdobozokban. Ha vannak,
egy előugró figyelmeztetéssei utasítjuk a felhasználót a kért adat megadásá­
ra. Először kezeljük az onclick eseményt a Submit gombra:

<input name="btnsubmit" type="submit" value="Submit" id="btnsubmit"


language="javascript" oncl i ck="re turn btnsubmit_onclick()">

Az alábbiak szerint implementáljuk az eseménykezelőt:

700
Az űrlapadatok továbbítása (a GET és a POST )

function btnsubmit_onclick(){
ll Ha az egyik elemről megfeledkeztek, jelenitsünk meg egy
ll üzenetdobozt.
if((defaultPage.txtuserName.value " ) l l
== "

(defaultPage.txtPassword.value == "" ))
{
alert("You must su p p ly a user name and password!");
return false;
}
return true;
}

Most mentsük az eddigi munkánkat. Nyissuk meg a böngészőnket, navigál­


junk a default.htm oldalra, amelyet a Cars virtuális könyvtárunk hosztol, és
teszteljük az ügyféloldali szkriptünket:

http://localhost/Cars/default.htm

Ha a Help vagy a Submit gombra kattintunk, a böngészőnk a megfelelő üze­


netdobozokat jeleníti meg. Figyeljük meg, hogy ha véletlenszerű adatokat
írunk be a szövegdobozokba, a Submit gombra kattintva nem jelenik meg el­
lenőrzési hibaüzenet.

Az űrlapadatok továbbitása
(a GET és a POST )
Most, hogy már van egy egyszerű HIML-oldalunk, vizsgáljuk meg, hogyan kell
az űrlapadatokat visszaküldeni a webkiszolgálónak feldolgozásra. Arnikor
HTML-űrlapot készítünk, általában elhelyezünk egy műveletattribútumot a
nyitó <form> címkében a bemeneti űrlapadatok címzettjének meghatározásához.
A lehetséges címzettek levélkiszolgálók, más HIML-fájlok, egy Active Server
Pages (ASP-) fájl és egyéb objektumok lehetnek. Ebben a példában a classieAsp­
Page.asp nevű, klasszikus ASP-fájlt használjUk. Módosítsuk a default.htm fájlt a

következő attribútum megadásával a nyitó <form> címkében:

<form name="defaultPage" id="defaultPage"


action="http://localhost/Cars/ClassicAspPage.asp" method="GET">

</form>

701
31. fejezet: ASP. NET- weboldalak készítése

A kiegészítő attribútumok biztosítják, hogy amikor a felhasználó az űrlap


Submit gombjára kattint, az űrlapadatokat a rendszer a megadott URL-en ke­
resztül a ClassicAspPage.asp fájlba továbbítsa. Amikor az adatátvitel médja­
ként a met:hod="GET" metódust adjuk meg, az űrlapadatot egy & jellel elválasz­
tott névj érték párral fűzzük hozzá a querystringhez.

ht:t:p://localhost:/Cars/ClassicAspPage.asp?t:xt:UserName=
Andrew&t:xt:Password=Foo$&bt:nsubmit:=Submit:

Az űrlapadatok webkiszolgálóra _továbbításának másik módja a met:hod="POST"


metódus meghatározása:

<form name="default:Page" id="default:Page"


act:ion="ht:t:p://localhost:/Cars/classicAspPage.asp" met:hod = "POST">

</form>

Ebben az esetben az űrlapadatot nem fűzzük hozzá a querystringhez, hanem


az a HITP-fejléc külön soraként jelenik meg. A POST használatakor a külvilág
nem láthatja közvetlenül az űrlapadatokat Nagyon fontos, hogy a POST ada­
tok karakterszáma nem korlátozott (míg sok böngésző korlátozza a GET le­
kérdezések karakterszámát). Egyelőre a HTTP GET metódusa révén küldjük el
az űrlapadatokat a fogadó *.asp oldalra.

Klasszikus ASP-oldal készitése

Egy klasszikus ASP-oldal HTML és kiszolgálóoldali szkriptkód keveréke. Ha


eddig még nem használtuk a klasszikus ASP-t, tudnunk kell, az ASP célja,
hogy dinamikusan készíthessünk HTML-t olyan kiszolgálóoldali szkript révén,
amely COM-objektumok kis gyűjteményét és egy kis munkát igényel. Példá­
ul adott egy kiszolgálóoldali VBScript (vagy JavaScript) blokk, amely egy
adatforrás tábláját olvassa a klasszikus ADO segítségéve!, a sorokat pedig
generikus HTML-táblaként adja vissza.
Ebben a példában az ASP-oldal a belső ASP Request COM-objektumot
használja a querystringhez fűzött, bemeneti űrlapadatok értékeinek kiolvasá­
sára, majd visszaküldi azokat a hívónak (nem valami izgalmas, de jól szem­
léHeti a kérés/válasz ciklus alapvető működését). A kiszolgálóoldali szkript­
logika VBScriptet használ (ahogy azt a nyelvdirektíva jelzi).

702
Klasszikus ASP-oldal készítése

Ehhez hozzunk létre új HTML-fájlt a Visual Studio 2008 segítségéve!, és


mentsük el class icAspPage. asp néven abban a mappába, amelyre a virtuális
könyvtárunkat leképeztük (például C:\ CodeTests\ CarsWebSite). Az oldal
megvalósítása a következő:

<%@. language="VBScript" %>


<html>
<he ad>
<title>The Cars Page</title>
</he ad>
<body>
<hl align="center">Here is what you sent me:</hl>
<P align="center"> <b>User Name: </b>
<%= Request.QueryString("txtuserName") %> <br>
<b>Password: </b>
<%= Request.Querystring("txtPassword") %> <br>
</P>
</body>
</html>

Most a klasszikus ASP Request COM-objektum segítségével hívtuk a Query­


string() metódust a method="GET"-tel továbbított HTML-vezérlők értékeinek

vizsgálatára. A 4-'o= . ..%>jelölés röviden ennyit jelent: "A következők beszú­


rása közvetlenül a kimenő H1TP-válaszba." A rugalmasság növelése érdeké­
ben az ASP Response COM-objektummal együttműködhetünk egy kiszolgá­
lóoldali szkriptblokkon keresztül (ezt a <%,%>jelölés mutalja). Azonban erre
most nincs szükségünk, hiszen a következő példa rendkívül egyszerű:

<"h
Dim pwd
pwd = Request.QueryString("txtPassword")
Response.write(pwd)
%>

A klasszikus ASP Request és Response objektumai nyilvánvalóan rengeteg to­


vábbi taggal rendelkeznek az itt bemutatottakon kívül. Ráadásul a klasszikus
ASP is definiál néhány kiegészítő COM-objektumot ( session, server, Appl i­
cati on stb.),amelyeket webalkalmazásaink készítésekor felhasználhatunk.

Megjegyzés Ezek a COM-objektumok az ASP.NET alatt hivatalosan már nem léteznek. Azon·
ban látjuk majd, hogy a system. web. ur. Page ösosztály azonos nevű tulajdonságokat definiál,
amelyek hasonló működésű objektumokat foglalnak magukban.

703
31. fejezet: ASP.NET-weboldalak készítése

Ezt követően mentsük rrúndegyik webfájlunkat Az ASP-logika teszteléséhez


csak töltsük be egyszerűen a default.htm oldalt egy böngészővel, majd to­
vábbítsuk az űrlapadatokat. Miután a webkiszolgáló feldolgozta a szkriptet,
a 31.8 ábrán látható teljesen új (dinamikusan generált) HTML-t kapjuk vissza.

:ib The Cars Page - Windows Intemet Explorer l= l@�


.�O L�_http:0_ocalho��ars/Cia�icA .:J�_[�) �!-•
T
-----·- ______ !:___:_!
{;j & l� The Cars Page
r l �. � . � • fr} Page • (<} Tools • »

Á
---

Here is what you sent me:


User Name: Andrew
Password: Test
T

Done � Local intranet l Proleeted Mode: Off etlOO% .

31.8. ábra: A dinamikusan generált HTML

A default.htm fájl jelenleg a HTTP GET metódust adja meg az űrlapadatok


küldéséhez az *.as p célfájlba. Így a különféle grafikus felületi vezérlőkben tá­
rolt értékeket a querystring végéhez fűzzük hozzá. Fontos megjegyezni, hogy
az ASP Request.Querystring() metódus kizárólag a GET metódussal továbbí­
tott adatok kiemelésére képes.
Ha inkább a HTTP POST metódussal szeretnénk továbbítani az űrlapada­
tokat a webes erőforráshoz, az értékeket a Request.Form gyűjteménnyel ol­
vasha�uk a kiszolgálón, például a következőképpen:

<body>
<hl align="center">Here is what you sent me:</hl>
<P align="center">
<b>User Name: </b>
<%= Request.Form("txtuserName") %> <br>
<b>Password: </b>
4'o= Request.Form("txtPassword") %> <br>
</P>
</body>

Ezzel a webbel részletesen foglalkozó bevezető végére értünk. Ha most is­


merkedünk a webes fejlesztéssel, remélhetőleg nagyobb rálátásunk nyílt a
webalapú alkalmazások alapvető építőelemeire. Azonban mielőtt megnéz­
nénk, hogyan tökéletesíti az ASP.NET webplatform a jelenlegi helyzetet,
szánjunk néhány percet a klasszikus ASP kritikájára, valantint főbb korlátai­
nak vizsgálatára.

704
A klasszikus ASP problémái

Forráskód A ClassicAspCars kódfájlokat a forráskódkönyvtár 31. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A klasszikus ASP problémái

Noha sok sikeres webhelyet a klasszikus ASP-vel hoztak létre, ennek az ar­
chitektúrának is megvannak a maga hátrányai. A klasszikus ASP legnagyobb
hátránya talán ugyanaz, mint ami hatékony platformmá teszi: a kiszolgálóol­
dali szkriptnyelvek. A szkriptnyelvek, mint a VBScript és a JavaScript interp­
retált, típus nélküli entitások, amelyek nem alkalmasak robusztus 00 prog­
ramozási módszerekre.
A klasszikus ASP másik problémája, hogy egy * .as p oldal nem szolgál mo­
dularizált kóddal. Mivel az ASP HTML és szkript keveréke egyetlen oldalon, a
legtöbb ASP-webalkalmazás két különböző programozási módszer zavaros
elegye. Amíg igaz, hogy a klasszikus ASP révén az újrafelhasználható kódokat
különálló fájlokra osztha�uk, az alapul szolgáló objektummodell nem támogat­
ja a kapcsolatok valódi szétválasztását. Az ideális az lenne, ha a webes keret­
rendszerek lehetövé tennék, hogy a megjelenítő logika (például a HTML­
címkék) az üzleti logikától (például a funkcionális kódtól) függetlenül létezzen.
Szintén megfontolandó probléma, hogy a klasszikus ASP túl sok sablon­
szöveget és redundáns szkriptkódot igényel, amelyek a projektek között is­
métlődnek. Szinte az összes webalkalmazásnak ellenőriznie kell a felhaszná­
lói bemeneteket, újra fel kell töltenie a HTML-vezérlők állapotát a HTTP-vá­
lasz kiadása előtt, generálnia kell egy HTML-adattáblát, és így tovább.

Az ASP. NET 1.x főbb előnyei

Az ASP.NET első kiadása (l.x verzió) nagyszerűen feloldotta a klasszikus


ASP korlátait. Röviden, a .NET platform a következő rnódszereket vezette be
a Microsoft webes fejlesztési paradigmán:

• Az ASP.NET a mögötteskód-modellt használja, amely révén szétvá­


lasztha�uk a megjelenítési logikát az üzleti logikától.

• Az ASP.NET-oldalak implementálása .NET programozási nyelveken tör­

ténik, és nem interpretált szkriptnyelveken. A kódfájlokat érvényes .NET­


szerelvényekre fordí�uk (ami jóval gyorsabb végrehajtást eredményez).

705
31. fejezet: ASP.NET·weboldalak készítése

• A webes vezérlőelemek segítségével a programozók a Windows Forms/


WPF alkalmazásoknál megszakott módon készíthetik el a webalkalma­
zások grafikus felületét

• Az ASP.NET webes vezérlőelemek automatikusan karban tartják az ál­


lapotukat a visszaküldések alatt a _VIEWSTATE nevű rejtett űrlapmező
segítségéveL

• Az ASP.NET-webalkalmazások teljes mértékben objektumorientáltak


és az egységes típusrendszert (CTS) használják.

• Az ASP.NET-webalkalmazások könnyedén kanfigurálhaták szabványos


IlS-beállítások vagy webalkalmazás-konfigurációs fájl (web. con fi g) segít­
ségéveL

Az ASP.NET legfőbb újdonságai

Miközben az ASP.NET l.x volt az első fontos lépés a megfelelő irányba, az


ASP.NET 2.0 további újdonságokkal bővült, ezek a következők:

• A webDev. webserve r. ex e tesztelő webkiszolgáló bevezetése.

• További nagy számú webes vezérlőelem (navigációs vezérlőelemek,


biztonsági vezérlőelemek, új adat-vezérlőelemek, új felhasználóifelü­
let-vezérlőelemek stb.).

• A mesteroldalak bevezetése, amelyek révén közös felhasználóifelület­


keretet csatolhatunk az oldalakhoz.

• A témák támogatása, amelyek révén deklaratív módon módosíthatjuk a


teljes webalkalmazás megjelenését és működését.

• A webkijelzó'k (web part) támogatása, amellyel lehetövé tehetjük a vég­


felhasználók számára a weboldal megjelenésének és működésének
testreszabását.

• Webalapú konfigurációs és kezelési eszköz bevezetése, amely a web.


con fi g fájlokat tartja karban.

706
Az ASP.NET-névterek

A N ET 3.5
. legfőbb webes újdonságai

Ahogy gondolhattuk, a .NET 3.5 tovább növelte az ASP.NET programozási ma­


dell hatókörét. Talán a legfontosabb, hogy ma már tartalmazza a következőket:

• Új vezérlőelemeket, amelyek támogatják a Silverlight-fejlesztést (emlé­


kezzünk rá, hogy ez egy WPF-alapú API, amelyet a webhelyek gazdag
médiatartalmának tervezésére használhatunk).

• Integrált támogatást az Ajax-stílusú fejlesztéshez, amely lényegében


engedélyezi, hogy a "mikro-visszaküldések" a weboldal egy részét a
lehető leggyorsabban frissítsék.

Mivel a könyv nem kizárólag a webes fejlesztésre összpontosít, az itt nem emlí­
tett témákkal kapcsolatos további részletekért forduljunk a .NET Framework 3.5
dokumentációjához. Az igazság az, hogy ha minden szempontból megvizsgál­
nánk az ASP.NET-et, a könyv akár kétszer ilyen hosszú is lehetne. Biztosak lehe­
tünk benne, hogy mire a könyv ezen szakaszának a végére érünk, szilárd
ASP.NET-alapokkal rendelkezünk.

Megjegyzés Ha átfogóan szeretnénk tanulmányozni az ASP.NET-et, ehhez Matthew MacDo­


nald Pro ASP.NET 3.5 in C# 2008, Second Edition (Apress, 2007) című könyvét ajánljuk.

Az ASP.NET-névterek

A .NET 3.5 megjelenése óta jóval több mint 30 webközpontú névtér létezik az
alaposztály-könyvtárakban. Ezek a névterek az alábbi főbb kategóriába so­
rolhatók:

• alapvető funkcionalitás (például azok a típusok, amelyek révén kom­


munikálhatunk a HITP-kérésekkel és -válaszokkal, Web Form infra­
struktúra, téma- és profiltámogatás, webkijelzők , biztonság stb.),

• Web Form és HTML-vezérlőelemek,

• mobil webes fejlesztés,

707
31. fejezet: ASP.NET-weboldalak készítése

• Silverlight-fejlesztés,

• Ajax-fejlesztés,

• XML-webszolgáltatások.

A 31.1. táblázat néhány alapvető ASP.NET-névteret ismertet.

system.web Ez a névtér olyan típusokat definiál, amelyek en­


gedélyezik a böngésző és a kiszolgáló közötti
kommunikációt (mint például a kérési és reagálá­
si lehetőségek, a sütik kezelése és a fájlátvitel).

System.web.Caching Ez a névtér olyan típusokat definiál, amelyek


megkönnyítik a webalkalmazások gyorsítótára­
zási támogatását.

system.web. Hosti ng Ez a névtér olyan típusokat definiál, amelyek le­


hetővé teszik, hogy egyedi hasztokat hozzunk lét­
re az ASP.NET-futtatókörnyezet számára.

system.web.Management Ez a névtér az ASP.NET-webalkalmazások álla­


potát kezelő és megfigyelő típusokat definiál.

system.web. Profi l e Ez a névtér az ASP.NET felhasználói profilokat


megvalósító típusokat definiálja.

System. web.se eu ri ty Ez a névtér olyan típusokat definiál, amelyek le­


hetővé teszik, hogy programozott módon gon­
doskodjunk a webhely biztonságáróL

System.web. Sessi onstate Ez a névtér olyan típusokat definiál, amelyek le­


hetővé teszik az állapotmegőrző információk kar­
bantartását felhasználónként (például munkame­
netállapot-változók).

System. web. UI, Ezek a névterek több olyan típust definiálnak,


system.web. UI. webcontrol s, amelyek révén a webalkalmazásunk grafikus
System.web. UI. H tm l Contro ls front endjét készíthetjük el.

31. 1. táblázat: Az ASP.NET alapvető webközpontú névterei

708
Az ASP.NET weboldal·kódolási modellje

Az ASP. NET weboldal-kódolási modellje

Az ASP.NET-weboldalakat kétféle módon készíthetjük el. Létrehozhatunk


egyetlen *. aspx fájlt, amely a kiszolgálóoldali kód és a HTML keveréke (hason­

lóan a klasszikus ASP-hez). Az egyfájlosoldal-modell segítségével a kiszolgáló­


oldali kódot a <script> hatókörébe helyezzük, de maga a kód valójában nem
szkriptkód (például VBScript/JScript). A <scr:i pt> blokkban található utasítá­
sokat e helyett valarnilyen .NET-nyelven írjuk (C#, Visual Basic stb.).
Ha nagyon kevés kódot (de sok HTML-t) tartalmazó oldalt készítünk, az
egyfájlos oldal modell használata kényelmesebb, rnivel a kódot és a markupot
egy *. aspx fájlban láthatjuk. Emellett, ha az forráskódot és a HTML-markupot

egyetlen *. aspx fájlba helyezzük, további előnyökre tehetünk szert:

• Az egyfájlos modellel írt oldalakat valamelyest egyszerűbben lehet te­


lepíteni, és másik fejlesztőnek elküldeni.

• Mivel a fájlok között nincs függőség, az egyfájlos oldal átnevezése


könnyebb.

• A fájlokat egyszerűbben lehet kezelni egy verziókezelő rendszerben,


rnivel az összes művelet egyetlen fájlban történik.

A Visual Studio 2008 alapértelmezés szerint a mögöttes kódként ismert mód­


szert alkalmazza új webhelymegoldások létrehozásakor, amely révén a for­
ráskódot különválaszthatjuk a HTML megjelenítési logikától, két külön fájl
használatávaL Ezt a modellt akkor célszerű használni, arnikor az oldalaink
nagy mennyiségű kódot tartalmaznak, vagy több fejlesztő dolgozik ugyan­
azon a webhelyen. A mögötteskód-modellnek szintén több előnye van:

• Mivel a mögötteskód-oldalak jól elkülönítik a HTML-markupot és a


-kódot, elképzelhető, hogy amíg a tervezők a markuppal foglalkoznak,
addig a programozók a C#-kódot módosítják.

• Az oldaltervezők vagy akik csak az oldal markupjával foglalkoznak


nem férhetnek hozzá a kódokhoz (ahogy azt gondolhattuk, a HTML­
tervezők nem rnindig szeremének a C#-kódban gyönyörködni).

• A kódfájlokat több *. aspx fájlban is használhatjuk

709
31. fejezet: ASP.NET-weboldalak készítése

Függetlenül attól, hogy melyik módszert válasz�uk, jó, ha tudjuk, hogy telje­
sítmény szempon�ából nincs közöttük különbség. Valójában sok ASP.NET­
webalkalmazás előnyére válik, ha olyan oldalakat készítünk, amelyek mind­
két módszert alkalmazzák

Adatközpontú egyfájtos tesztoldal készítése

Először is, vizsgáljuk meg az egyfájlas oldal modellt. A célunk, hogy olyan
*. aspx fájlt készítsünk, amely a 22. fejezetben létrehozott AutoLot adatbázis

Inventory tábláját jeleníti meg. Amíg ezt az oldalt kizárólag Jegyzettömb se­
gítségéve! is elkészíthetnénk, a Visual Studio 2008 egyszerűsítheti a dolgokat
az IntelliSense, a kód kiegészítés és egy vizuális laptervező segítségéveL
Kezdésként nyissuk meg a Visual Studio 2008-at, és hozzunk létre új Web
Forrnot a File >- New>- File menüpont segítségével (lásd a 31.9. ábrát). Ha
ezzel elkészültünk, mentsük a fájlt Default.aspx néven a merevlemezre, egy
új C:\CodeTests\SinglePageModel könyvtárba.

Newfi5e
. 'rHiil:tiii

Categori5: T emplat:es:
Gen�al Visual Studio insta\led tempiates
Web mfw.;b-1'�.;;; LJMaster Plige
Visual Basic
ltJ WebUs;,. Control @ HTMLPage
ci
�WebS.<Vice �Class
Visual C++
� Style Sheet Q Global Application Class
Script
�Web Configuration File � XMLFile
1!1 TextFile �ResourceFíle
� Generic Handler !ms�eMap
lUJScript File

A form for Web Applications

31.9. ábra: Új *.aspx fájllétrehozása

Az AutolotDAL.dll fájl manuális hivatkezása

Most hozzunk létre egy "bin" nevű alkönyvtárat a SinglePageModel mappá­


ban, a Windows Intéző segítségéveL A speciális nevű bin alkönyvtár egy re­
gisztrált név az ASP.NET-futtatómotorban. A webhelyek gyökerének \bin

710
Az ASP.NET weboldal-kódolási modellje

mappájába a webalkalmazás bármilyen privát szerelvényét telepíthetjük Eb­


ben a példában az Au to LotDAL. dll (lásd a 22. fejezetet) másolatát helyezzük a
C:\ CodeTests \SinglePageModel\bin mappába.

Megjegyzés Ahogy az a fejezet későbbi részéből kiderül, amikor a Visual Studio 2008 segítségével
hozunk létre teljes ASP.NET-webalkalmazást, a \bin mappát az IDE tartja karban helyettünk.

A felhasználói felület tervezése

Válasszuk a Visual Studio 2008 Toolbox eszköztárának Standard fülét, és


húzzunk át egy Button, egy Label és egy GridView vezérlőelemet az oldal­
tervezőbe (a GridView a Toolbox Data fülén található). Nyugodtan állítsuk
be a Properties ablak (vagy a HTML IntelliSense) segítségével a különböző
felhasználóifelület-tulajdonságokat, és az ID tulajdonság révén minden ve­
zérlőnek adjunk megfelelő nevet. A 31.10. ábra egy lehetséges megoldást áb­
rázol. (A példa megjelenése és működése szándékosan nem túl bonyolult,
hogy a lehető legkevesebb vezérlőelem-markupot kelljen generálni, de nyu­
godtan alakítsuk át ízlésünk szerint.)

�·�
•X

l Lill'!l ___ _ ___'"
___ _______ _....... ___________..•..•...·--··--·-·····--·--·-···---,

lCiick on the Button to Fill the Grid


l


�o[�·�-1����
·[abc -;abc - j

��-- �
+-·---->------
1l
abc l
1;;t;;;··· ---t•
abc·---r'�
�- l
!
!
'
j
�-;;iii-c3ri;·l --·-·-···------···-.. --·----------··--·--·---_j

� '( ..

l Q Design l t:l Split l E3 Source l ����<form#forml �� jB >

31. 1 O. ábra: A Default.aspx felhasználói felülete

Most keressük meg az oldal <form> szakaszát. Figyeljük meg, hogyan defini­
áltuk az egyes webes vezérlőelemeket egy <as p:> címke segítségéveL A lezáró
címke előtt egy névj érték pár sorozatot találunk, amely a Properties ablak
beállításaival áll összhangban:

711
31. fejezet: ASP. NET-weboldalak készítése

<form id="forml" runat="server">


<div>
<asp:Label ID="lblrnfo" runat="server"
Text="Click on the Button to Fill the Grid">
</asp:Label>
<br />
<br />
<asp:Gridview ID="carsGridview" runat="server">
</asp:Gridview>
<br />
<asp:Button ID="btnFillData" runat="server" Text="Fill Grid" />
</div>
</form>

Az ASP.NET webes vezérlőelemeit a 32. fejezetben fogjuk részletesen tanul­


mányozni. Annyit már most tudnunk kell, hogy a webes vezérlőelemek a
webkiszolgálón feldolgozott objektumok, amelyek a HTML-ábrázolásukat
automatikusan visszaadják a kimenő HITP-válaszban (vagyis a HTML-t
nem mi programozzuk!). Ezen a nagyszerű előnyön túl, az ASP.NET webes
vezérlőelemei az asztali programozási modellt utánozzák, mivel a tulajdon­
ságok, a metódusok és az események elnevezései rendre a Windows Forms/
WPF megfelelőikre hasonlítanak

Adatelérési logika hozzáadása

Kezeljük a kattintási eseményét a gombnak vagy a Visual Studio Properties ab­


"

lakának " villám ikonjával, vagy a tervezőablak felső részén található legördülő
listák segítségéveL Ezzel a gomb definícióját kiegészítettük egy onelick attribú­
tummal, amelyet a click esemény kezelőjének nevéhez rendeltünk hozzá:

<asp:Button ID="btnFillData" runat="server"


Text="Fill Grid" onel i ck="btnFi llData_cl i ck"/>

Emellett egy üres <script> blokkot is kapunk, amellyel elkészíthetjük a ki­


szolgálóoldali kattintási esemény kezelőjét Adjuk hozzá a következő kódot,
és figyeljük meg, hogy bejövő paraméterek hajszálpontosan megegyeznek a
system. EventHand ler metódusreferencia céljával:

<script runat="server">
void btnFillData_Click(object sender, EventArgs args)
{
}
</script>

712
Az ASP.NET weboldal-kódolási modellje

A következő lépés a GridView feltöltése az AutaLotDAL.dll szerelvény segítsé­


géveL Ehhez határozzuk meg a<%@. Import %>direktíva segítségéve!, hogy az

AutoLotconnectedLayer névteret használjuk. Továbbá értesítenünk kell az

ASP.NET-futtatókömyezetet, hogy ez az egyfájlas oldal az AutoLotDAL.dll sze­


relvényre hivatkozik az <",.@. Assembly %> direktíva segítségével (hamarosan
többet megtudhatunk a direktívákról). Íme, a Default.aspx fájl megmaradt, re­
leváns logikája (a kapcsolatsztringet szükség szerint módosítanunk kell):

<%@ Page Language="C#" %>


<%@ Import Namespace = "AutoLotconnectedLayer" %>
<%@. Assembly Name ="AutoLotDAL" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/fEN"


"http://www.w3.org/TR/xhtmll/DTD/xhtmll-transitional.dtd">

<script runat="server">
void btnFillData_click(object sender, EventArgs args)
{
InventoryDAL dal = new InventoryDAL();
dal.Openconnection(@"Data source=(local)\SQLEXPRESS;" +

"Initial catalog=AutoLot;Integrated security=True");


carsGridview.DataSource = dal.GetAllinventory();
carsGridview.DataBind();
dal.closeconnection();
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >

</html>

Mielőtt elmélyülnénk az *. aspx fájl formátumának részleteiben, teszteljük az


oldalt. Először mentsük el az *. aspx fájlt. Ha manuálisan szeretnénk hasz­
nálni a weboev.webserver. exe-t, nyissunk meg egy .NET-parancssort, és fut­
tassuk a webDev.webserver.exe segédprogramot. Győződjünk meg arról,
hogy helyesen adtuk meg a tárolt oe fault. aspx fájl elérési útját, például a kö­
vetkezőképpen Gelen esetben egy tetszőleges 12345 portot adtunk meg):

webdev.webserver.exe
/port:12345 /path:"C:\CodeTests\SinglePageModel"

Most írjuk be a böngészőnkbe a következő URL-t:

http://localhost:12345/

713
31. fejezet: ASP. NET- weboldalak készítése

Arnikor az oldal betöltődik, kezdetben egy címkét és egy gombot látunk.


Azonban, arnikor a gombra kattintunk, adatokat küldünk vissza a webki­
szolgálóra, és a webes vezérlőelemek ekkor a megfelelő HTML-címkéiket je­
lenítik meg.
A weboev. webserver. ex e segédprogramot közvetett módon a Visual Studio
2008-ból is elindítha�uk. Csak kattintsunk a jobb gombbal arra az oldalra, ame­
lyet szeretnénk megtekinteni, majd válasszuk a View In Browser menüpontot.
A 31.11. ábrán mindkét eset kimenetét látha�uk, rniután a Fill Grid gombra
kattintottunk.

if Untitled Page -Windows Internet.. lo l §) liiiJiiil

@O [� http://localhost50'; l +,._(iJ§
� �

{;z 4/l l � Untitled Page


r l SJ
Click on the Button to FiR the Grid

CariD Make Color PetName


l BMW Pink Sid
2 vw Red Zippy
3 Ford Black Me!
4 BMW Silver Hemy
5 Yugo Pink Sally
6 Saab Blue Sven
7 BMW Black Bimmer
8 vw Tan Sal
10 MB Silver MB
ll Ford GreenZippy
222 Ford Blue Yodo
999 Yugo Pink Pinky

i '\! Local intranet l Proleeted Mode: C <fl.lOO% � ...

31.11. ábra: Webalapú adatelérés

Ez egyszerű volt, nem? Bár, ahogy mondják, az ördög a részletekben rejlik,


ezért ássuk bele jobban magunkat az *.as p x fájl felépítésébe. Kezdjük az ol­
daldirektívák szerepének vizsgálatávaL

714
Az ASP.NET weboldal·kódolási modellje

Az ASP.NET-direktivák szerepe

Az első dolog, amellyel tisztában kell lennünk, hogy az *.aspx fájlok általában

számos direktivával kezdődnek. Az ASP.NET-direktívákra a <X/9 ... %> jelölők


utalnak, és különböző attribútumok minősíthetik, amelyek utasí�ák az
ASP.NET-futtatókömyezetet, hogyan kell az adott attribútumot feldolgoznia.
Minden *.as p x fájlnak legalább egy <%@Page%> direktívát kell tartalmaznia,
amely definiálja az oldalon alkalmazott felügyelt nyelvet (a language attribú­
tum révén). A <%@Page%> direktíva emellett a kapcsolódó mögöttes kódfájl
nevét is definiálha�a (ha létezik), engedélyezheti a nyomkövetés támogatást,
és így tovább. A 31.2. táblázat a legfontosabb <%@Page%> központú attribútu­
mok közül mutat be néhányat.

codePage Ez az attribútum a kapcsolódó mögöttes kódfájl nevét


határozza meg.

co m p i l eropti o n s Ennek az attribútumnak a segítségével definiálha�uk a


parancssori kapcsalókat (egyetlen sztring formájában
jelennek meg), amelyeket az oldal feldolgozása során a
fordítónak átadunk.

EnableTheming Ez az attribútum megadja, hogy az*.asp x oldal vezér­


lőelemei támoga�ák-e az ASP.NET-témákat.

Enab l evi ews tat e Ez az attribútum jelzi, hogy a nézet állapotot (view
state-et) a rendszer az oldalkérések között megőrzi (a
tulajdonságról a 33. fejezetben találhatunk további rész­
leteket).

Inherits Ez az attribútum egy osztályt definiál a mögöttes kód­


ban található oldal részére, amelyből az*.aspx fájl
származik. Ez bármilyen System. web.ur. Page-ből
származtatott osztály lehet.

MasterPageFile Ez az attribútum az aktuális ;, .asp x oldal mesteroldalát


adja meg.

Trace Ez az attribútum jelzi, hogy engedélyeztük-e a nyom­


követést.

31.2. táblázat: A <%@Page%> direktíva néhányattribútuma

715
31. fejezet: ASP. NET-weboldalak készítése

Egy adott *. aspx fájl a <%@Page%> direktíván kívül különféle <%@Import%> di­

rektívákat is megadhat az aktuális oldal és a <%@Assembly%> direktívák által


igényelt névterek explicit kifejezésére, hogy a webhely által használt külső
kódkönyvtárakat meghatározza (amelyek általában a webhely \ bin mappá­
jában találhatóak).
Ebben a példában meghatároztuk, hogy az Au toLotDAL. dll szerelvény Auto­
LotconnectedLayer névterének típusait használjuk. Ahogy rájöhettünk, ha to­

vábbi .NET-névterekre van szükségünk, egyszerűen csak több <%@Import%>/


<"t6@Assembly%> direktívát kell megadnunk.

A jelenlegi .NET-tudásunkkal elcsodálkozhatunk azon, hogy ez az ''.asp x fájl

hogyan kerülhette el azt, hogy további <%@Import%> direktívákat határozzunk


meg benne ahhoz, hogy hozzáférjen a system névtér system.object és system.
EventHandler típusaihoz (többek között). Ez annak köszönhető, hogy az *. aspx

fájlok automatikusan hozzáfémek a .NET platform telepítési útvonalán található


machine.config fájlban definiált kulcsfontosságú névterek készletéhez. Ebben az

XML-alapú fájlban több automatikusan importált névteret találunk:

<pages>
<namespaces>
<add namespace="System"/>
<add namespace="System.coll ecti ons"/>
<add namespace="System.Collections.Specialized"/>
<add namespace="System.configuration"/>
<add namespace="System.Text"/>
<add namespace="System.Text.RegularExpressions"/>
<add namespace="System.web"/>
<add namespace="System.web.Caching"/>
<add namespace="System.web.sessionstate"/>
<add namespace="System.web.security"/>
<add namespace="System.web.Profile"/>
<add namespace="System.web.ur"/>
<add namespace="System.web.ur.webcontrols"/>
<add namespace="System.web.ul:.webcontrols.webParts"/>
<add namespace="System.web.UI.Htmlcontrols"/>

</namespaces>
</pages>

A szkriptblokk elemzése

Az egyfájlas modellben egy *. aspx fájl kiszolgálóoldali szkriptkódot tartalmaz­

hat, amelyet a rendszer a webkiszolgálón futtat. Emiatt kulcsfontosságú, hogy az


összes kiszolgálóoldali kódblokkot a runat="server" attribútummal definiáljuk,
hogy az a kiszolgálón fusson. Ha a runat="server" attribútumot nem határozzuk
meg, a futtatókörnyezet azt felételezi, hogy ügyféloldali szkriptblokkot írtunk,
amelyet a kimenő HITP-válaszba továbbítunk:

716
Az ASP.NET weboldal-kódolási modellje

<script runat="server">
void btnFilloata_Click(object sender, EventArgs args)
{
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data Source=(local)\SQLEXPRESS;" +

"Initial Catalog=AutoLot;Integrated security=True");


carsGridview.Datasource = dal.GetAllinventory();
carsGridview.DataBind();
dal.Closeconnection();
}
</script>

Ezen segédmetódus szignatúrája furcsán ismerősnek tűnhet. Emlékezzünk


vissza a Windows Forrns (vagy jelen esetben WPF-) tanulmányainkra, hogy
egy adott vezérlőelem eseménykezelőjének egyeznie kell a kapcsolódó .NET­
metódusreferenciában definiált mintával. Ugyanúgy, mint a Windows Forrns
esetében, ha egy kiszolgálóoldali kattintási eseményt szeretnénk kezelni, a kér­
déses metódusreferencia a system. EventHandler, amely - ha emlékszünk rá -
csak azokat a metódusokat hívja, amelyek első paramétere a system.object, a
második pedig a system. EventArgs.

Az ASP. NET vezérlőelem-deklarációinak áttekintése

A példa utolsó figyelemre méltó pon�a a Button, a Label és a GridView webes


vezérlőelemek deklarációja. A klasszikus ASP-hez és a nyers HlML-hez hason­
lóan, az ASP.NET webes vezérlőelemei is a <form> elemek hatókörében találha­
tók. Most azonban a nyitó <form> elemet a runat="server" attribútummal jelöltük
meg. Ez szintén rendkívül fontos, Inivel ez a címke tájékozta�a az ASP.NET­
futtatókömyezetet arról, hogy Inielőtt a HTML-t továbbítaná a válaszfolyamba, a
tárolt ASP.NET-vezérlők még alakítha�ák HTML-megjelenésüket:

<form id="forml" runat="server">


<div>
<asp:Label ID="lblinfo" runat="server"
Text="Click on the Button to Fi ll the Gri d">
</asp:Label>
<br />
<br />
<asp:Gridview ID="carsGridview" runat="server">
</asp:Gridview>
<br />
<asp:Button ID="btnFillData" runat="server" Text="Fill Grid" />
</div>
</form>

717
31. fejezet: ASP.NET-weboldalak készítése

Figyeljük meg, hogy az ASP.NET webes vezérlőelemeket <asp> és </asp>


címkékkel deklaráltuk, és a runat="server" attribútummal is megjelöltük
őket. A nyitó címkében a webes űrlap vezérlőelem nevét és tetszőleges szá­
mú névj érték párt adhatunk meg, amelyeket a megfelelő HTML megjelení­
tésére használunk futásidőben.

Forráskód A SinglePageModel kódfájlt a forráskódkönyvtár 31. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A mögötteskód-modell használata

A mögötteskód-modell szemléltetésére hozzuk létre újra az előző példát a


Visual Studio 2008 Web Site sablonja segítségéveL (Tudnunk kell, hogy a Vi­
sual Studio 2008 nem követeli meg a mögöttes kód használatát; azonban ez
az új webhelyek alapértelmezett viselkedése.) Válasszuk ki a File > New >
Web Site menüpontot, és a 31.12. ábrán látható módon válasszuk az
ASP.NET Web Site sablont.

Templat6:

Visual Studio ínstalled tempiates

�A5i>.Nrrw;b s�� � ASP.NET Web S.<Vice � Empty We b Site


d, WCf s.Mc� . �ASP.NET Reports Web Site

MyTe mplates-----·--�------------------------- ------------� --------�----------

1 t]) Search Online Templates...

A blank ASP.NET Web s�e (.NET fr.omeworlc 3.5)

location:

language:

OK ll Cancel l

31. 12. ábra: A Visual Studio 2008 ASP.NET Web Site sablonja

718
Az ASP.NET weboldal-kódolási modellje

Figyeljük meg a 31.12. ábrán, hogy kiválaszthatjuk az új webhely tárhelyét


Ha a File Systemet választjuk, a tartalomfájlok lokális könyvtárba kerülnek,
az oldalakat pedig a weboev. webserver.exe szolgáltatja. Ha az FTP vagy a
HTTP lehetőséget választjuk, a webhelyet egy új IlS-beli virtuális könyvtár
hosztolja. Ebben a példában nincs különbség a két lehetőség között, de az
egyszerűség kedvéért azt javaslom, válasszuk a File System opciót, és adjuk
meg a C:\ CodeBehindPageModel nevű új könyvtárat.
A tervező segítségével hozzunk létre felhasználói felületet, amely egy
Label, egy Button és egy GridView vezérlőelemet tartalmaz, majd alakítsuk
ki ízlésünk szerint a felhasználói felületet a Properties ablakkal.

Megjegyzés Ha meglévő webhelyet szeretnénk megnyitni a Visual Studio 2008-ban, válasszuk


a File > Open > Web Site menüpontot, majd a webtartalmat tartalmazó mappát (vagy az liS­
beli virtuális könyvtárat).

Figyeljük meg, hogy a <%@Pagero> direktíva néhány új attribútummal egészült ki:

<"..b@ Page Language="C#" AutoEventwireup="true"


CodeFile="Default.aspx.cs" Inherits="_oefault" %>

A cod eFi le attribútummal azt a kapcsolódó külső fájlt adjuk meg, amely az
oldal forráskódját tartalmazza. Alapértelmezés szerint ezek a mögöttes kód­
fájlok úgy kapnak nevet, hogy a .cs utótagot adjuk az *. aspx fájl nevéhez Ge­
len esetben Default.aspx. cs) . Ha megnézzük a Solution Explorert, észrevesz­
szük, hogy ez a mögöttes kódfájl a Web Form ikon egyik alcsomópontján lát­
ható (lásd a 31.13. ábrát).

Solution Explorer li
l li � l [ID li]l !21 811 llt!] lt il
. ,
f� C:\-\CodellehindPageModel\ ,
!- E:ij App_Data

l $· · �·�IIM1'M
l i 'e;1 Default.aspx.cs
L.. � web.config

�� Solution Explorer !§class Vi<wr-

31. 13. ábra: Adott *.aspx fájlhoz rendelt mögöttes kódfájl

Ha megnyitnánk a mögöttes kódfájlunkat, olyan részleges osztályt találnánk,


amely a System. web. ur. Page osztályból származik és implementálja a Load
esemény kezelését. Figyeljük meg, hogy az osztály neve (_oefault) megegye­
zik a <"..b@Page%> direktíva Inherits attribútumával:

719
31. fejezet: ASP.NET-weboldalak készítése

public partial class _Default : System.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
}
}

Hivatkozás az AutolotDAL.dll szerelvényre

Ahogy korábban említettük, �or a Visual Studio 2008 segítségével hozunk


létre webalkalmazás-projekteket, nem kell manuálisan létrehoztunk a \ bin al­
könyvtárakat, és kézzel átmásolnunk a privát szerelvényeket. A példa kedvé­
ért válasszuk ki az Add Reference párbeszédpanelt a Website menüpont segít­
ségéve!, és hivatkozzunk az AutoLotDAL.dll szerelvényre. Ekkor a 31.14. ábrán
látható módon a Solution Explorerben megjelenik az új \ bin mappa.

Solution Explorer

� C:\...\CodeBehindP;ogeModel\
;.. L:;j App_Data

é- lliiD -

' clJ � AutolotDAL.dll


..

' ... !i) AutolotDAL.pdb


B· i@ Default.aspx
. L � Default.aspx.e<
..

:... B web.config
� Solution Explorer �Class View_!

31.14. ábra: A Visual Studio rendszerint karbantart "speciális" ASP.NET-mappákat

A kódfájl módositása

Kezeljük a gomb kattintási eseményét úgy, hogy kétszer kattintunk a tervezőben


elhelyezett Button objektumra. Mint eddig is, a Button definíciója most is onelick
attribútummal frissült. A kiszolgálóoldali eseménykezelő azonban már nincs az
*. aspx fájl <script> hatókörében, hanem a _Default osztály metódusa lett.

A példa befejezéseként adjunk a mögöttes kódfájlhoz egy us ing utasítást


az AutoLotconnectedLayerre, majd implementáljuk a gomb eseménykezelőjét
az előző logika alkalmazásával:

using AutoLotconnectedLayer;

public partial class _Default : System.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
}

720
Az ASP.NET weboldal-kódolási modellje

protected void btnFillData_click(object sender, EventArgs e)


{
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data source=(local)\SQLEXPRESS;" +

"rnitial Catalog=AutoLot;Integrated security=True");


carsGridview.DataSource = dal.GetAllrnventory();
carsGridview.DataBind();
dal.Closeconnection();
}
}

Ha a File System opciót választottuk, a weboev. webserver.exe automatikusan


elindul a webalkalmazás futtatásakor (ha az IlS opciót választottuk, ez nyil­
ván nem történik meg). Az alapértelmezett böngésző most mindkét esetben
megjeleníti az oldal tartalmát.

Az ASP.NET-oldalak hibakeresése és nyomkövetése

ASP.NET-projektek létrehozásakor nagyjából ugyanazokat a hibakeresési


módszereket használha�uk, mint bármilyen más Visual Studio 2008 projekttí­
pus esetében. Megadhatunk töréspontokat a mögöttes kódfájlunkban (csak­
úgy, mint beágyazott szkriptblokkokat egy *.aspx fájlban), elindíthatunk hiba­
keresési munkameneteket (alapértelmezés szerint az FS billentyű megnyomá­
sával), és végiglépkedhetünk a kódon.
Az ASP.NET-webalkalmazás hibakereséséhez azonban a webhelynek
megfelelően konfigurált web. config fájlt kell tartalmaznia. A fejezet utolsó ré­
sze bemuta�a a web. config fájlokat, de addig is röviden annyit, hogy ezen
XML-fájlok általános rendeltetése megegyezik a végrehajtható szerelvények
App. config fájljának céljával. Alapértelmezés szerint az összes Visual Studio
2008 webprojekt automatikusan rendelkezik egy web. con fig fájllaL A hibake­
resési támogatás kezdetben le van tiltva (rnivel csökkenti a teljesítményt).
Hibakeresési munkamenet indításakor az IDE megkéri, hogy engedélyezzük
a hibakeresést Ha ezt megtettük, a web.confi g fájl <compilation> eleme a kö­
vetkezőképpen módosul:

<compilation debug="true"/>

Ehhez kapcsolódóan érdemes megjegyezni, hogy egy *.aspx fájl nyomkövetési


támogatását úgy is engedélyezhe�ük, ha a <%@Page%> direktíva Trace attribú­
tumának true értéket adjuk (a teljes webhely nyomkövetésének engedélyezé­
séhez módosítsuk a web. config fájlt):

<%@ Page Language="C#" AutoEventwireup="true"


CodeFile="Default.aspx.cs" Inherits="_Default" Trace="true" %>

721
31. fejezet: ASP.NET-weboldalak készítése

Ha ezzel készen vagyunk, a generált HTML számos, az előző HITP-kérésre/vá­


laszra vonatkozó részletet tartalmaz (kiszolgáló oldali változók, munkamenet­
és alkalmazásváltozók, kérés/válasz stb.). A saját nyomkövetési üzeneteink
beszúrásához használjuk a system.web. ur. Page típus Trace tulajdonságát. Ha
bármikor naplázni szeretnénk az egyedi üzeneteket (szkriptblokkból vagy C#­
forráskódfájlból), csak hívjuk meg a wri te() metódust:

protected void btnFillData_Click(object sender, EventArgs e)


{
Trace .write("My category", "Filling the grid!");

Ha újra futtatjuk a projektet, és visszaküldünk adatokat a webkiszolgálóra,


láthatjuk, hogy az egyedi kategóriánk és az egyedi üzenetünk jelen vannak.
Figyeljük meg a 31.15. ábrán látható kiemeit üzenetet, amely nyomkövetési
adatokat jeleníti meg.

� Untitled Page - Windows Inte� Explorer l l @) lilfJiil


p

@Q j@J ��tt�//loc�l���������� � ����_] L��}_e_


y ____, --------- - - _ _ -�-�-�
[=t_fl1·
»

'Ci <$/ f � Un titled Page � • � • @Page • ,;· Tools •

' �
aspx.page Begin Preinit
aspx.page End Preinit 2.45841301059213E-05 0.000025
aspx.page Begin Init 4.63746090634424E-05 0.000022
aspx.page End Init 7.6546041466164E-05 0.000030
aspx.page
aspx.page
Begin InitComplete
End InitComplete
9.30285832417249E-05 0.000016
0.000108952394787606 0.000016
8
aspx.page Begin LoadState O .OOO124317476103806 O.000015
aspx.page End LoadState 0.000215949233771331 0.000092
aspx.page Begin ProcessPostData 0.000234946061580452 0.000019
aspx.page End ProcessPostData 0.0002620444777199340.000027
aspx.page Begin PreLoad 0.000278527019495495 0.000016
aspx.page End Preload o .000305346070520136 0.000027
aspx.page Begin Load o .000322387342525377 0.000017
aspx.page End Load o.000343619091253218 o .000021
aspx.page Begin ProcessPostData Second Try0.000359822267913939 0.000016
aspx.page End ProcessPostData Second Try 0.0003749079841153 0.000015
aspx.page Begin Raise ChangedEvents 0.00039055243054634 0.000016
aspx.page End Raise ChangedEvents 0.000406476242092221 0.000016
=ocnv n=on• Beqin Raise n ;.". cc. 0.000422679418752942 0.000016
0.0016295367148618 0.001207
aspx.page End Raise PostBackEvent 0.00610440712436916 0.004475
aspx.page Begin LoadComplete 0.00626867381189509 0.000164
aspx.page End LoadComplete 0.00628850873504873 0.000020
aspx.page Begin PreRender 0.00630471191170945 0.000016 .Y
. -=-= ... _ ... "-- ""' - • < � ��,�-��-��-�Ann-� ... ,.,..,...........
r
.- lll '

Done '\�Local intranet 1 Proleeted Mode: Off fil.100% .


...

31. 15. ábra: Eg�jedi nyomkövetési üzenetek naplózása

Forráskód A CodeBehindPageModel kódfájlt a forráskódkönyvtár 31. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

722
ASP. NET -webhely könyvtárszerkezetének részletei

ASP. NET-webhely könyvtárszerkezetének


részletei

Az új Visual Studio 2008 webalkalmazások alapértelmezés szerint tartalmaz­


nak egy kezdeti weboldalt, egy Web.config fájlt és egy App_oata nevű mappát
(amely alapértelmezés szerint üres). Az ASP.NET-webhelyek bármennyi
speciáJisan elnevezett alkönyvtárat tartalmazhatnak, amelyek mindegyike
egyedi jelentéssei bír az ASP.NET-futtatókörnyezet számára. A 31.3. táblázat
ezeket a "speciális alkönyvtárakat" ismerteti.

App_Browsers Ez a mappa böngésződefiníciós fájlokat tartalmaz, ame­


lyekkel azonosíthatjuk a böngészőket és meghatároz­
hatjuk a képességeiket.

App_Code Ez a mappa olyan összetevők vagy osztályok forrás­


kódjait tartalmazza, amelyeket az alkalmazás részeként
szeretnénk lefordítani. Az ASP.NET az ebben a mappá­
ban található kódot akkor fordítja le, mikor elkérjük az
oldalt. Az alkalmazásunk automatikusan hozzáfér az
App_Code mappában lévő kódhoz.

App_Data Access *. mdb fájlokat, SQL Express ;, . mdf fájlokat, XML­

fájlokat vagy egyéb adattárakat tartalmazó mappa.

App_GlobalResources Az alkalmazáskódból programozott módon hozzáfér­


hető *. resx fájlok mappája.

App_LocalResources Meghatározott oldalhoz tartozó *. resx fájlok mappája.

App_Themes Fájlok gyűjteményét tartalmazó mappa, amelyek az


ASP.NET-weboldalak és -vezérlőelemek megjelenését
definiálják.

App_WebReferences Proxyosztályokat, sémákat, illetve egyéb olyan fájlokat


tartalmazó mappa, amelyek az alkalmazásban használt
webszolgáltatással kapcsolatosak.

B in Lefordított privát szerelvényeket (*.dll fájloka t) tar­


talmazó mappa. Az alkalmazásunk a Bin mappában ta­
lálható szerelvényekre automatikusan hivatkozik.

31. 3. táblázat: Speciá/is ASP.NET-alkönyvtárak

723
31. fejezet: ASP. NET-weboldalak készítése

Ha a fenti alkönyvtárak bármelyikét szeretnénk hozzáadni az aktuális webal­


kalmazásunkhoz, azt a Web Site> Add Folder menüpont segítségével meg­
tehetjük. Sok esetben ezt az IDE azonban automatikusan megteszi, mikor
kapcsolódó fájlokat szúrunk be a webhelyre (például ha új osztályfájlt illesz­
tünk a projektbe, az IDE automatikusan hozzáad egy App_Code mappát a
könyvtárszerkezethez, ha az még nem létezik).

Hivatkozás szerelvényekre

Ahogy azt néhány oldallal korábban említettük, az ASP.NET-weboldalakat


végül .NET-szerelvényekké fordítjuk. Így nem meglepő, hogy a webhelyek
tetszőleges számú privát vagy megosztott szerelvényre hivatkozhatnak. Az
ASP.NET alatt az ASP.NET l.x-hez képest meglehetősen eltérő módon rög­
zítjük a webhelyek külső szerelvényeit. Ennek az alapvető eltérésnek az az
oka, hogy a Visual Studio 2008 projekfájl nélkül kezeli a webhelyeket
Habár a Web Site sablon generál egy *. sln fájlt, amely betölti az *. aspx
fájlokat az IDE-be, a kapcsolódó*. csproj fájl már nem létezik. Ahogy azt bi­
zonyára tudjuk, az ASP.NET l.x Web Application projektek az összes külső
szerelvényt egy*. cspro j fájlban rögzítették Ez felveti a nyilvánvaló kérdést:
az ASP.NET hol rögzíti a külső szerelvényeket?
Láthattuk, hogy amikor egy privát szerelvényre hivatkozunk, a Visual
Studio 2008 a könyvtárstruktúránkban automatikusan létrehoz egy \ bin
könyvtárat a bináris fájlok helyi másolatainak tárolására. Amikor a kód
ezekben a kódkönyvtárakban tárolt típusokat használja, azokat a rendszer
igény szerint automatikusan betölti.
Ha megosztott szerelvényre hivatkozunk, a Visual Studio 2008 automati­
kusan beilleszt egy web.con fi g fájlt az aktuális solutionbe (ha még nincs ben­
ne), és az <assemblies> elemben rögzíti a külső referenciát. Például, ha újra
kiválasztjuk a Web Site > Add Reference menüpontot, és most megosztott
szerelvényt választunk (mint például a system.Data. oracleclient. dll) lát­
hatjuk, hogy a web.con fi g fájl a következőképpen módosul:

<assemblies>
<add assembly="System.Data.oracleclient, version=2.0.0.0,
cuhure=neutral, PublicKeyToken=B77A5C561934E089"/>
</assembli es>

Láthatjuk, hogy minden szerelvényt ugyanazzal a dinamikus betöltéshez szük­


séges adattal írtunk le az Assembly. Load() metódusban (lásd a 16. fejezetet).

724
ASP. NET- webhely könyvtárszerkezetének részletei

Az App_Code mappa szerepe

Az App_Code mappában olyan forráskódfájlokat tárolunk, amelyek nem tar­


toznak közvetlenül egy adott weboldalhoz (mint egy mögöttes kódfájl), de a
webhely használatához a rendszer lefordí�a azokat. Az App_Code mappában
található forráskódokat a rendszer automatikusan, menet közben lefordí�a, ha
szükséges. A szerelvény ezután a webhely bármely más forráskódja számára
elérhető. Ezért az App_Code mappa nagyon hasonlít a Bin mappára, kivéve,
hogy a lefordított kódok helyett forráskódokat tárolhatunk benne. Ennek a
megközelítésnek az a legnagyobb előnye, hogy anélkül defuúálhatunk egyedi
típusokat a webalkalmazás számára, hogy le kellene őket fordítanunk.
Egyetlen App_Code mappa több, különböző nyelven készült forráskód­
fájlt is tartalmazhat. Futásidőben a megfelelő fordító generálja a kérdéses sze­
relvényt Ha azonban inkább szétválogatnánk a forráskódokat, több alkönyv­
tárat definiálhatunk, amelyekben tetszőleges számú felügyelt kódfájlt ( *.vb,
* . cs stb.) tárolhatunk.

Tételezzük fel például, hogy hozzáadtunk egy App_Code mappát egy web­
hely gyökérkönyvtárához, amely két almappát (MyCSharpCode és MyVbNet­
Code) tartalmaz, és az almappák nyelvspecifikus fájlokat tárolnak. Ha ezt meg­
tettük, módosítsuk a web. config fájlt, és meghatározha�uk az alkönyvtárakat a
<configuration> elembe ágyazott <codesuboirectories> elem segítségéve!:

<compilation debug="true" strict="false" explicit="true">


<codesubDirectories>
<add directoryName="Mycsharpcode" />
<add directoryName="MyVbNetCode" />
</codesuboirectories>
</compilation>

Megjegyzés Az App_Code könyvtárban olyan fájlokat is tárolunk, amelyek nem nyelvfájlok,


ennek ellenére mégis hasznosak (*.xsd fájlok, * . wsdl fájlok stb.).

A Bin, az App_Code, az App_Data és az App_Themes mappákorr kívül még


létezik további két "speciális alkönyvtár", amellyel meg kell ismerkednünk,
mindkettőt megvizsgáljuk a következő �éhány fejezetben. Mint mindig, a
fennmaradó ASP.NET-alkönyvtárakkal kapcsolatos további információkért
forduljunk a .NET Framework 3.5 SDK dokumentációjához.

725
31. fejezet: ASP.NET·weboldalak készítése

Az ASP. N ET-oldal forditási ciklusa

Függetlenül attól, hogy melyik oldalmadelit használjuk (egyfájlos vagy mö­


göttes kód alapú), az*. aspx fájlokat (és a kapcsolódó mögöttes kódfájlokat)
menet közben fordítjuk érvényes .NET-szerelvényekké. Ezt a szerelvényt az­
tán az ASP.NET munkavégző folyamat ( aspnet_wp.exe) hasztolja a saját al­
kalmazástartományában (az alk�lmazástartományokkal kapcsolatos további
információkért lásd a 17. fejezetet). Az ASP.NET alatt azonban meglehetősen
eltérő módon fordítjuk a webhelyek szerelvényeit.

Egyfájlos oldalak forditási ciklusa


Ha az egyfájlosoldal-modellt alkalmazzuk, a HTML-markup, a kiszolgáló­
oldali <script> blokkok és a webes vezérlőelemek definícióját dinamikusan
fordítjuk egy system.web.UI. Page osztályból származó osztálytípusba. Az
osztály neve az*. aspx fájl nevén alapul, és egy _aspx utátagot kap (például
a MyPage.aspx oldalból MyPage_aspx osztálytípus lesz). A 31.16. ábra a ezt a fo­
lyamatot mutatja be.

31.16. ábra: Egyfájlos oldalak fordítási modellje

Ezt a dinamikusan fordított szerelvényt a C:\WINDOWS\Microsoft.NET\


Framework\ v2.0.50727\Temporary ASP.NET Files gyökérkönyvtár olyan
alkönyvtárába települ, amelyet a futtatókörnyezet definiált. A gyökér alatti
útvonal több tényezőnek köszönhetően (hash kódok stb.) eltérő lehet, de ki­
tartással végül megtaláljuk a keresett*. dll-t (és a támogatófájlokat). A 31.17.
ábra a fejezet korábbi részében bemutatott singlePageModel példa generált
szerelvényét ábrázolja.

726
Az ASP.NET-oldal fordítási ciklusa

Megjegyzés Mivel ezek az automatikusan generált szerelvények igazi .NET bináris fájlok, ha
megnyitnánk a webalkalmazásunkhoz kapcsolódó *.dll-t az i l dasm. exe vagy a refl ector. exe
segítségéve!, csakugyan köztes nyelvi kódot, metaadatokat és egy szerelvényszintű manifesztu­
mokat találnánk ott.

. ,.,....--··· ----__,_-··--�···--·--·�-·-·· --- -- �


.. Org�nr:e: • n '.ie.·.� • Cll Cp�n '.'.·1th.. l!!l E-m�:! 'J, Eum

Favcrite links ·Name Date modified


-
More »
j-: �bi;- -
� as; ·----------- ----�---� 8/'l$/200711:1 2 AM Fil
Í .t.hash 8/'l$/200711ol0 AM Fil
Fold�rs
v l [NApp Web_07!Jj17n5.dll 8/'l$/200711:14 AM Ap:
singlepagemodel ·1 [JApp_Web_0 79dl7n5.dll.del.te
l @ App_Web_unfzolpf.dll
8/'l$/200711:22 AM DEl
6f79b857 8/'lf,/200711:22 AM Ap
6dcb8db2 O App_W<b_unfzolpf.dll.del.te 8/'l$/200711:32 AM DE'
J,; ommbly l@
l
App_W<b_zov7v.,.p.dll 8/'l$/200711oll AM Ap
• dl3 QApp_Web_zov7v.,.p.dll.del.te 8/'l$/200711:14 AM OE
J.;20e34057 O default.aspx.cdcab7dl.compiled 8/'l$/200711:22 AM c
i 9lf74e2c_.Oe8c701
Jlltmp
1 ha!öh l
01
99oloee5
: 422b087e

l
assembly
J.\ hosh

i
.j tomp

wcfwebservice
websrte_formsbasedauthentication
� ----·------J�
--�
wr:bsite_w i dowsauthwebsite
:
nr

·· •• App_Web_079dl7nS.dll Date modified: 8/'l$/200711:14 AM Date cr6!ted: 8/]1./1!XJ711:14 AM


L
,

Application Extension Size: 7.00 KB


".:� ·

31. 17. ábra: Automatikusan generált ASP.NET-szerelvény

Többfájlos oldalak forditási ciklusa


A mögöttes kód alapú modellt alkalmazó oldalak fordítási folyamata hasonló
az egyfájl os oldaléhoz. Azonban a system. web. ur. Page osztályból származó
típus három fájlból áll (igen, háromból), és nem kettőbőL
Emlékezzünk vissza, hogy az előző codeBeh i ndPageModel példában a De­
fau l t. aspx fájlt a mögöttes kódfájlban található _Default nevű részleges osz­
tályhoz kapcsoltuk. ASP.NET 1.x tapasztalatok birtokában meglepődhetünk,
hogy mi történt a különféle webes vezérlőelemek tagváltozóinak, illetve az
Ini ti al i zecomponent () kódjának deklarációjával, például az eseménykezelő
logikával. ASP.NET alatt ezekről a részletekről a memóriában generált rész­
leges osztály egy harmadik jellemzője gondoskodik. A valóságban ez szó sze­
rint nem fájl, hanem a részleges osztály egy memórián belüli megjelenítése.
Nézzük meg a 31.18. ábrát.

727
31. fejezet: ASP.NET-weboldalak készítése

A futtató­
környezet System.We. U l.Page
fordítója (Minden *._aspx osztály ösosztálya)

MyPage.aspx.cs
(Részleges osztálydefiníció
.
a

31. 18. ábra: Többfájlos oldalak fordítási modellje

Ebben a modellben az *.aspx fájlban deklarált webes vezérlőelemekkel épít­


jük fel a kiegészítő részleges osztályt, amely a felhasználói felület összes tag­
változóját, valamint a konfigurációs logikát definiálja, amelyet korábban az
ASP.NET l.x rnitializecomponent() metódusában találhattunk (csak közvet­
lenill sosem látjuk). Ezt a részleges osztályt fordítási időben a rendszer a mö­
göttes kódfájllal egyesíti, aminek eredménye a generált _aspx osztálytípus ős­
osztálya lesz (az egyfájlas oldal fordítási modelljében a generált _aspx fájl
közvetlenül a system.web. ur. Page osztályból származik).
Mindkét esetben, ha a szerelvényt a kezdeti HTIP-kérés során hoztuk lét­
re, a rendszer az elkövetkező kérések során is használja, így nem kell a sze­
relvényt újra lefordítani. Ennek ismeretében könnyebb megérteni, hogy egy
* . aspx oldal első lekérése miért vesz jóval több időt igénybe, és ugyanazon
oldal későbbi találatai miért rendkívül hatékonyak.

Megjegyzés Az ASP.NET alatt lehetőségünk nyílik, hogy a webhelyek összes oldalát (vagy
azok közül néhányat) előfordítsunk az aspnet_compi l er. exe parancssori eszköz segítségéveL
További részletekért forduljunk a .NET Framework 3.5 SDK dokumentációjához.

728
A Page típus származtatási lánca

A Page tipus származtatási lánca

Ahogy az imént láthattuk, az utolsó generált osztály, amely az *.aspx fájlt kép­
viseli a system. web. UI. Page osztályból származik. Mint minden ősosztály, ez a
típus is polimorf interfészt biztosít az összes származtatott típus számára.
A Page típus azonban nem az egyetlen tagja a származtatási hierarchiának. Ha
megkeresnénk a Page típust (a system.web. dll szerelvényben) a Visual Studio
2008 objektumböngészője segítségéve!, azt látnánk, hogy a Page "az-egy"
Templatecontrol, "az-egy" control és "az-egy" object (lásd a 31.19. ábrát).
Ahogy már rájöhettünk, ezek az ősosztályok rengeteg funkcionalitással ru­
házzák fel az* .aspx fájlokat A projek�eink többségében a Page és a control
szülőosztályokban definiált tagokat alkalmazzuk majd. A system. web. UI. Temp­
latecontrol osztályból nyert funkcionalitással lényegében csak az egyedi Web
Form vezérlőelemek létrehozásakor vagy a renderelési folyamattal folytatott
együttműködés közben foglalkozunk.
Az első lényeges szülőosztály maga a Page. Ebben több olyan tulajdon­
sággal találkozhatunk, amelyek révén különféle webprimitívekkel dolgozha­
tunk, mint például az alkalmazás- és munkamenet-változókkal, a HTIP ké­
résekkelfválaszokkal, a tématámogatással és így tovább. A 31.4. táblázat né­
hány (de közel sem az összes) alapvető tulajdonságot mutat be.

�x

�Browse: .NEr Fram�ork 3.5 "'"'" :·· l � 1 •._ ! �


---4
1 \1 • AddContenn emp lat�(string, System.Web.UI.JTemplate)

l'
· - I'M
-
-- �ll;
-� t·'

j·-·,.
AddOnPreRenderCompleteAsync(System.Web.BeginEventHa
AddOnPreRenderCompleteAsync(System.Web.BeginEventHar
r[J1.
Base Types
éJ--� TemplateControl hi• AddWrappedFileDependencies(object)
' B·"!: Control
!··4• AspCompatBeginProcessRequest(System.Web.HttpContext, SJ
. $.. ><> !Component i···>j· AspCcmpatEndProcessRequest(System.!AsyncResult)
GJ-.-o IControiBuilderAccessor !·-·-f• AsyncPageBeginProcessRequest(System.Web.HttpContext. Sy:
�..--<1- IControiDesigne:rAccessor f·�· AsyncPageEndProcessRequest(System.JAsyncResult)
$-·� IDataBindingsAccessor t·<J• CreateHtmiToxtWriter(SystemJO.TextWritor)
�.. -o !Disposable
t···,. CreateHtmiToxtWriterFromType(SystemJO.ToxtWriter, System �

i .... !"
$�·....0 IExpressionsAccessor '"

; ffi .......o IParserAccessor public class Page: System.Web.UI.TemplateControl


Í ffi.....o IUriResolutionService
l ffi-s Object
Member of System.Web.UI Ol
! tiJ.-.-<1 JFifte:rResolutíonServict. Sum mary:
! riJ�.->0 lNamingContainer Represents an .aspx file, also l:nown as a Web Forms page,
@..-o IHttpHandler ·• • requested from a server that hosts an ASP.NET Web

31. 19. ábra: Egy ASP.NET-oldal származtatása

729
31. fejezet: ASP.NET-weboldalak készítése

Application Ez a tulajdonság az aktuális webhely alkalmazásváltozóival


folytatott komrnunikációt teszi lehetövé.

cache Ez a tulajdonság az aktuális webhely gyorsítótár-objektu­


maival folytatott komrnunikációt teszi lehetövé.

cl ientTarget Ennek a tulajdonságnak a segítségével megadhatjuk, hogy a


kérelmező böngészőtől függően az oldal hogyan jelenjen meg.

IsPostBack A tulajdonság értéke azt jelzi, hogy az oldal az ügyfél vissza­

küldésre adott válasz, vagy első ízben kerül letöltésre és hoz­


záférésre.

MasterPageFile Ez a tulajdonság az aktuális oldal mesteroldalát adja meg.

Request Ez a tulajdonság az aktuális HITP-kéréshez biztosít hozzáférést.

Respon se Ez a tulajdonság a kimenő HITP-válasszal folytatott komrnu­


nikációt teszi lehetövé.

server Ez a tulajdonság a HttpServeruti l i ty objektumhoz biztosít


hozzáférést, amely különböző kiszolgálóoldali segédfüggvé­
nyeket tartalmaz.

session Ez a tulajdonság az aktuális hívó munkamenetadataival foly­


tatott komrnunikációt teszi lehetövé.

Theme Ez a tulajdonság lekérdezi vagy beállítja az aktuális oldalon


alkalmazott téma nevét.

Trace Ez a tulajdonság a Tracecontext objektumhoz biztosít hozzá­


férést, amely egyedi üzenetek naplózását teszi lehetövé a ill­
bakeresési munkamenetek alatt.

31.4. táblázat: A Page típus néhány tulajdonsága

Együttműködés a bejövő HITP­


kérésekkel

Ahogy a fejezet korábbi részében láthattuk, a webes munkamenetek alapvető


folyamata úgy kezdődik, hogy az ügyfél belép a webhelyre, megadja a fel­
használói adatokat, majd egy Submit gombra kattint, és feldolgozásra vissza­
küldi a HTML-űrlapadatokat az adott weboldalnak A legtöbb esetben a form

730
Együttműködés a bejövö HTTP-kérésekkel

utasítás nyitó címkéje megad egy act i on és egy method attribútumot, amelyek
arra a fájira mutatnak a webkiszolgálón, amely megkapja a különböző HTML­
vezérlők adatait, valamint az adatok küldésének módját (GET vagy POST):

<form name="defaultPage" id="defaultPage"


action="http://localhost/Cars/ClassicAspPage.asp"
method = "GET">

</form>

Az ASP-től eltérően az ASP.NET nem támogatja a Request nevű objektumot.


Azonban az összes ASP.NET-oldal örökli a system_ web. ur. Page. Request tu­
lajdonságot, amely a HttpRequest osztálytípus egy példányához biztosít hoz­
záférést. A 31.5. táblázat néhány alapvető tagot sorol fel, amelyek nem meg­
lepő módon a klasszikus ASP Request objektum azonos tagjaira hasonlítanak

ApplicationPath Lekérdezi az ASP.NET-alkalmazás virtuális alkalma­


zásgyökerének elérési útját a kiszolgálón.

Browser A kliensböngésző lehetőségeiről biztosít információkat

ecokies Lekérdezi a kliensböngésző által küldött sütik gyűjte­


ményét.

FilePath Az aktuális kérés virtuális útvonalát jelzi.

Form Lekérdezi a HTTP-űrlapváltozók gyűjteményét.

Headers Lekérdezi a HTTP-fejlécek gyűjteményét.

HttpMethod A kliens által alkalmazott HTTP adatátviteli módot


{GET, POST) jelzi.

rssecureconnection A HITP-kapcsolat biztonságosságát jelzi (például


HTTPS).

Querystring Lekérdezi a HTTP-lekérdezéssztring változók gyűjte­


ményét.

Rawurl Lekérdezi az aktuális kérés nyers URL-jét.

RequestType A kliens által alkalmazott HTTP adatátviteli módot


(GET, POST) jelzi.

731
31. fejezet: ASP.NET-weboldalak készítése

servervariables Lekérdezi a webkiszolgáló-változók gyűjteményét.

userHostAddress Lekérdezi a távoli kliens IP-címét.

userHostName Lekérdezi a távoli kliens DNS-nevét.

31.5. táblázat: A HttpRequest típus tagjai

Ezeken a tulajdonságokon kívül a HttpRequest típus számos hasznos metó­


dussal rendelkezik, többek között a következőkkel:

• MapPath (): A kért URL virtuális útvonalát fizikai útvonalra képezi le a

kiszolgálón az aktuális kérés számára.

• saveAs (): Az aktuális HITP-kérés részleteit fájlba menti a webkiszol­

gálón (ez hibakereséskor lehet hasznos).

• validateinput(): Ha az oldaldirektíva validate afuibúturna révén enge­

délyeztük az ellenőrzési szolgáltatást, a metódus meghívásával az összes


felhasználói bemenetet (köztük a sütiket) egy adott lista alapján ellenőriz­
heljük, amely a veszélyesnek tartott bemeneti adatokat sorolja fel.

Böngészőstatisztikák

A HttpRequest típus első említésre méltó jellegzetessége a Browser tulajdon­


ság, amely az alapul szolgáló HttpBrowsercapabilities objektumhoz biztosít
hozzáférést. A HttpBrowsercapabi liti es viszont több olyan tagot bocsát a
rendelkezésünkre, amelyek révén programozott módon megvizsgálhatjuk a
bejövő HITP-kérést küldő böngészőre vonatkozó statisztikákat.
Hozzuk létre a FunwithPageMembers nevű új ASP.NET-webhelyet (ismét a
File System opció segítségéve!). Az első feladatunk, hogy olyan felhasználói
felületet készítsünk, amelyen a felhasználó a btnGetBrowserstats nevű gomb­
ra kattintva megtekintheti a hívó böngésző különféle statisztikáit. Ezeket a
statisztikákat dinamikusan generáljuk, és csatoljuk a lbloutput nevű címké­
hez. A kattintási esemény kezelője a következő:

protected void btnGetBrowserStats_click(object sender, EventArgs e)


{
string theinfo "";
=

theinfo += string.Format("<li>Is the client AOL? {0}</li>",


Request.Browser.AOL);

732
Együttműködés a bejövő HTTP-kérésekkel

theinfo += string.Format("<li>Does the client support Activex?


{O}<Ili>", Request.Browser.Activexcontrols);
theinfo += string.Format("<li>IS the client a Beta? {O}<Ili>",
Request.Browser.Beta);
theinfo += string.Format("<li>Does the client support Java
Applets? {O}<Ili>", Request.Browser.JavaApplets);
theinfo += string.Format("<li>Does the client support cookies?
{O}<Ili>", Request.Browser.Cookies);
theinfo += string.Format(''<li>Does the client support VBScript?
{O}<Ili>", Request.Browser.vsscript);
lbloutput.Text = theinfo;
}

Ezzel több böngészőlehetőséget tesztelünk Ahogy kitalálhattuk, rendkívül


hasznos, ha felfedezzük, hogy a böngésző támogatja-e az ActiveX vezérlő­
elemeket, a Java-kisalkalmazásokat és az ügyféloldali VBScript kódot. Ha a
hívó böngésző nem támogat egy adott webtechnológiát, az * . aspx oldalunk
más lehetőséget választhat a működés biztosítására.

Hozzáférés a bemeneti űrlapadatokhoz

A HttpResponse típus további jellegzetességei a Form és a Querystring tulajdon­


ságok. Ezen két tulajdonság lehetövé teszi, hogy megvizsgáljuk a bemeneti űr­
lapadatokat névj érték párok segítségéveL A tulajdonságok a klasszikus ASP­
hez hasonlóan működnek. Emlékezzünk vissza arra a klasszikus ASP korábbi
ismertetéséből, hogy ha az adatokatHTIP GET-tel továbbítjuk, az űrlapadato­
kat a Querystring tulajdonság révén érhetjük el, rrúg a HTIP POST-tal továbbí­
tott adatokhoz a Form tulajdonságon keresztül lehet hozzáférni.
Noha biztosan hozzáférhetnénk az ügyfél által küldött űrlapadatokhoz a
webkiszolgálón a HttpRequest.Form és a HttpRequest.Querystring tulajdonságok
segítségével is, ezek az elavult módszerek (nagyrészt) feleslegesek Mivel az

ASP.NET biztosítja számunkra a kiszolgálóoldali webes vezérlőelemeket, a


HTML felhasználóifelület-elemeket objektumokként kezelhetjük Ezért ahelyett,
hogy az alábbihoz hasonló szövegdobozzal megszereznénk az adatokat:

protected void btnGetFormData_click(object sender,


system.EventArgs e)
{
ll szerezzük meg a céleszköz értékét a txtFirstName azonosítóval.
string firstName = Request.Form("txtFirstName");

ll Használjuk ezt az értéket az oldalon...


}

733
31. fejezet: ASP.NET-weboldalak készítése

egyszerűen a kiszolgálóoldali vezérlőtől közvetlenül kérhe�ük el az adatokat


a Text tulajdonságon keresztül, hogy használhassuk azt a programunkban:

protected void btnGetFormData_Click(object sender,


System.EventArgs e)
{
ll szerezzük meg a céleszköz értékét a txtFirstName azonosítóval.
string firstName = txtFirstName.Text;

ll Használjuk ezt az értéket az oldalon ...


}

Ez a módszer nemcsak szilárd 00 elvekre épül, de azzal sem kell törődnünk,


hogy az űrlapadatokat milyen módon (GET vagy POST) továbbítottuk, mielőtt
az értékeiket megkaptuk Továbbá, a vezérlőelemek közvetlen használata jó­
val típusbiztosabb, mivel a gépelési hibák már fordítási időben kiderülnek,
nem csak futási időben. Ez természetesen nem azt jelenti, hogy soha nem lesz
szükségünk a Form vagy a Querystring tulajdonságra az ASP.NET alatt, de
egyre inkább nélkülözhetőek, és általában tetszés szerint használhaták

Az lsPostBack tulajdonság

A HttpRequest másik nagyon fontos tagja az IsPostBack tulajdonság. Emlé­


kezzünk vissza, hogy a "visszaküldés" arra utal, hogy visszatérünk egy adott
weboldalra, miközben munkafolyamatunk még él a kiszolgálón. A definíció
alapján az IsPostBack tulajdonság értéke true, ha az aktuális HITP-kérést a
pillanatnyilag bejelentkezett felhasználó küldi, és false, ha a felhasználó
most először lép kapcsolatba az oldallal.
Annak a meghatározása, hogy a HITP-kérés valóban visszaküldés-e, akkor
a leghasznosabb, ha egy kódblokkot csak abban az esetben szeretnénk végre­
hajtani, mikor a felhasználó első ízben fér hozzá az adott oldalhoz. Például elő­
fordulhat, hogy egy ADO.NET Dataset típust szeretnénk adatokkal feltölteni,
mikor a felhasználó először fér hozzá egy *. aspx fájlhoz, és az objektumot sze­

retnénk későbbi használatra gyorsítótárazni. Az IsPostBack tulajdonság vizsgá­


latával elkerülhe�ük a felesleges adatbázishoz fordulást, amikor az oldal visz­
szaküldés miatt kerül betöltésre (persze néhány oldal megkövetelheti, hogy a
Dataset típust minden egyes kéréskor frissítsük, de ez más kérdés). Tételezzük

fel, hogy az *. aspx fájlunk kezelte az oldal Load eseményét (erre a fejezet ké­

sőbbi részében részletesen visszatérünk), ekkor a visszaküldés feltételeit az


alábbiak szerint programozott módon tesztelhe�ük:

734
Együttműködés a kimenő HTIP-válaszokkal

protected void Page_Load(object sender, System.EventArgs e)


{
ll A Dataset típust csak akkor töltsük fel,
ll amikor a felhasználó legelőször fér hozzá az oldalhoz.
i f (!IsPostBack)
{
ll Töltsük fel a Dataset típust, és gyorsítótárazzuk!
}
ll Használjuk a gyorsítótárazott Dataset típust.
}

Együttműködés a kimenő HITP­


válaszokkal

Most, hogy már jobban átlátjuk, a Page típus hogyan teszi lehetővé a bejövő
HITP-kérésekkel folytatott együttműködést, a következő lépés a kimenő
HITP-válaszok együttműködésének vizsgálata. Az ASP.NET alatt a Page osz­
tály Response tulajdonsága biztosít hozzáférést a HttpResponse típus példá­
nyához. Ez a típus több olyan tulajdonságot definiál, amelyek révén formáz­
hatjuk a kliensböngészőnek visszaküldött HITP-válaszokat A 31.6. táblázat
néhány alapvető tulajdonságot sorol fel.

eae he A weboldal gyorsítótárazási szemantikáját adja vissza (pél­

dául lejárati idő, adatvédelem, kifejezések módosítása).

contentEncoding Lekérdezi vagy beállítja a kimeneti adatfolyam HTTP-ka­


rakterkészletét.

ContentType Lekérdezi vagy beállítja a kimeneti adatfolyam HITP


MlME-típusát.

Cookies Lekérdezi az aktuális kérésben küldött Httpcookie gyűjte­


mény értékét.

rsclientconnected Lekérdezi, hogy a kliens kapcsolódik-e még a kiszolgálóhoz.

Output Egyedi kimenetet engedélyez a kimenő HITP-tartalomtörzs


számára.

OutputStream Bináris kimenetet engedélyez a kimenő HITP-tartalomtörzs


számára.

735
31. fejezet: ASP.NET-weboldalak készítése

St atu s code Lekérdezi vagy beállítja a kliensnek visszaküldött kimenet


HTIP-állapotkódját.

StatusDescription Lekérdezi vagy beállítja a kliensnek visszaküldött kimenet


HTIP-állapotsztringjét.

Suppresscontent Lekérdezi vagy beállítja, hogy a HTIP-tartalmat a rendszer


elküldje-e a kliensnek.

31.6. táblázat: A HttpResponse típus tulajdonságai

A 31.7. táblázatban tekintsünk meg a HttpResponse típus által támogatott me­


tódusok közül néhányat.

AddcacheDependency() Ez a metódus egy objektumot ad hozzá az alkalmazás­


gyorsítótárhoz (lásd a 33. fejezetet).

cl ear() Törli az összes fejlécet és tartalomkimenetet a


pufferfolyamból.

End() Ez a metódus az összes aktuálisan pufferelt kimenetet el­


küldi a kliensnek, majd lezárja a csatornát.

Flush() Ez a metódus az összes aktuálisan pufferelt kimenetet el­


küldi a kliensnek.

Redi rect() Elirányítja a klienst az új URL-re.

write() Egy megadott értékeket ír a kimenő HTIP-folyamba.

write File() Ez a metódus közvetlenül a kimenő HTIP-tartalom­


folyamba ír egy fájlt.

31.7. táblázat: A HttpResponse típus metódusai

HTML-tartalom kibocsátása

A HttpResponse típusnak talán a legismertebb jellegzetessége, hogy képes a


tartalmat közvetlenül a HTTP-kimenetfolyamba írni. A HttpResponse. w rite()
metódus révén bármilyen HTML-címkét és/vagy-szöveget átadhatunk.

736
Együttműködés a kimenő HITP-válaszokkal

A HttpResponse. write File() metódus annyiban viszi ezt tovább, hogy


megadhatjuk annak a fizikai fájlnak a nevét a webkiszolgálón, amelynek tar­
talmát szeretnénk megjeleníteni a kimeneti folyamban (ez elég hasznos egy
meglévő *.htm fájl tartalmának gyors kibocsátásakor).
Ennek bemutatásához tételezzük fel, hogy újabb gombot adtunk az aktuá­
lis *.aspx fájlhoz, amely a következőképpen valósítja meg a kiszolgálóoldali
kattintási esemény kezelőjét

protected void btnHttpResponse_Click(object sender, EventArgs e)


{
Response.write("<b>My name is:</b><br>");
Response.Write(this.ToString());
Response.write("<br><br><b>Here was your last request:</b><br>");
Response.writeFile("MyHTMLPage.htm");
}

A segédfüggvény (amelyet feltehetően valamilyen kiszolgálóoldali esemény­


kezelő hív meg) szerepe elég egyszerű. Az egyetlen érdekessége, hogy a http­
Response.writeFile() metódus a webhely gyökérkönyvtárában bocsátja ki a

kiszolgálóoldali *.htm fájl tartalmát.


Ismétlésképpen, annak ellenére, hogy bármikor alkalmazhatjuk ezt az el­
avult módszert, és a HTML-címkéket és -tartalmat megjeleníthetjük a write O
metódus segítségéve!, ez az eljárás már jóval kevésbé elterjedt az ASP.NET
alatt, mint a klasszikus ASP esetében volt. Ennek oka a kiszolgálóoldali we­
bes vezérlőelemek megjelenése. Ezért, ha egy szöveges adatblokkot szeret­
nénk a böngészőben megjeleníteni, csak rendeljünk hozzá egy sztringet a
Label vezérlő Text tulajdonságához.

Felhasználók átirányítása

A HttpResponse típus másik jellegzetessége, hogy képes a felhasználót átirá­


nyítani egy új URL-re:

protected void btnsomeTraining_Click(object sender, EventArgs e)


{
Response.Redirect("http://www.intertech.com");
}

Ha az eseménykezelőt egy ügyféloldali visszaküldés hívja meg, a rendszer a


felhasználót automatikusan átirányítja a megadott URL-re.

737
31. fejezet: ASP.NET-weboldalak készítése

Megjegyzés A HttpResponse. Redi rect() metódus mindig igényel egy újabb kört a kliens­
böngésző felé. Ha egyszerűen csak szeretnénk átadni a vezérlést ugyanazon virtuális könyvtár
egy másik *.aspx fájljának, az a Httpserveruti l i ty. Transfer() metódus (az öröklött ser­
ver tulajdonság révén érhető el) használata sokkal hatékonyabb.

Ennyit a syst em. web. UI. Page működésének vizsgálatáróL A system. web. UI.

cont rol ősosztály szerepét a következő fejezetben tanulmányozzuk át. Ezek

után vizsgáljuk meg egy Page-leszármazott objektum életét.

Forráskód A FunWithP ageMembers kódfájlokat a forráskódkönyvtár 31. fejezetének alkönyv·


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az ASP.NET-weboldalak életciklusa

Minden ASP.NET-weboldal életciklusa rögzített. Amikor az ASP.NET-futta­


tókörnyezethez bemeneti kérés érkezik egy adott *. aspx fájira vonatkozóan,
a kapcsolódó Page-leszármazott system. web. ur. típus a memóriába kerül a tí­
pus alapértelmezett konstruktorának révén. A keretrendszer ezután egy sor
eseményt indít el automatikusan. A Load eseménykezelő alapértelmezés sze­
rint rendelkezésünkre áll, amelyben megadhatjuk az egyedi kódunkat:

public partial class Default : System . web. UI. Page


{
protected void Page_Load(object sender, Even tArgs e)
{
Response.write("Load event fired!");
}
}

A Page típusok a Load eseményen kívül képesek elkapni bármely, a 31.8. táb­
lázatban kiváltódási sorrendben felsorolt eseményt (az oldalak életciklusa
alatt felmerülő eseményekkel kapcsolatos további információkért lapozzuk
fel a .NET Framework 3.5 SDK dokumentációját).

Preinit A keretrendszer a webes vezérlőelemek allokálására, té­


mák alkalmazására, a mesteroldal és a felhasználói profi­
lok beállítására használja ezt az eseményt. Az esemény ke­
zelésével testre szabhatjuk a folyamatot.

738
Az ASP.NET-weboldalak életciklusa

. ·:�J��!�\'t�t::t�:1.-
�-���•,..., ���-!•·>>'1-�iMk.��,X:�
�'-·i7.;�.l.-�lt,��r:;��rL; _.·:k:���J.;i�1�i/�:;;ritt;:�tt;�i)[�3'f�
-�"·•'·---·>·���� w"''��-.Ao>�� , "'""'�J
...

Init A keretrendszer a webes vezérlőelemek korábbi értékeinek


visszaállítására használja ezt az eseményt visszaküldés
vagy viewstate révén.

Lo a d Amikor ez az esemény bekövetkezik, az oldalt és a vezér­


lőelemeit a rendszer teljesen inicializálja, és visszaállítja a
korábbi értékeiket. Ekkor biztonságosan együttműködhe­
tünk az összes webes vezérlőelemmeL

"A visszaküldést kiváltó Ilyen nevű esemény természetesen nincs. Ez az "esemény"


esemény" arra utal, hogy melyik esemény késztette a böngészőt a
webkiszolgálóra való visszaküldés végrehajtására (mint
például egy Button kattintás).

PreRender Az összes vezérlőelem-adatkötés és felhasználóifelület­


konfiguráció megtörtént, és a vezérlőelemek készen állnak
az adataik továbbítására a kimenő HTIP-válaszba.

un load Az oldal és vezérlőelemei befejezték a renderelési folyama­


tot, és az oldalobjektum megsemmisítés előtt áll. Ha ekkor
kommunikálunk a kimenő HTIP-válasszal, futási idejű hi­
ba lép fel. Azonban kezelhetjük ezt az eseményt bármilyen
oldalszintű takarítás végrehajtása érdekében (fájl- vagy
adatbázis-kapcsolatok lezárása, naplózási tevékenység
végrehajtása, objektumok eldobása stb.) is.

31.8. táb lázat : A Page típus néhány eseménye

Amikor egy C#-programozónak a Load eseményen kívül más eseményt kell


kezelnie, meglepődhet, hogy azt az IDE nem támoga�a! Ehelyett manuálisan
kell megírnunk egy Page_EseményNeve nevű metódust a kódfájlban. Az unload
eseményt például így kezelhe�ük:

p ublic partial class Default : system.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
Response.write("Load event fired!");
}
protected void Page_unload(object sender, EventArgs e)
{
ll Már nem lehet adatokat továbbítani a HTTP-válaszba,
ll így egy helyi fájlba írunk.
System.IO.File.writeAllText(@"C:\MyLog.txt", "Page unloading!");
}
}

739
31. fejezet: ASP.NET·weboldalak készítése

Megjegyzés APage típus minden eseménye együttműködik a system.EventHandler metó­


dusreferenciával, ezért az eseményeket kezelő szubrutinok első paramétere mindig egy object
típus, a második pedig egy EventArgs.

Az AutoEventWireup attribútum szerepe

Amikor eseményeket szeretnénk kezelni az oldalunkon, ki kell egészítenünk a


<script> blokkunkat vagy a mögöttes kódfájlunkat a megfelelő eseménykeze­

lőveL Azonban, ha megvizsgáljuk a <%@Page%> direktívát, egy AutoEventwi reup


nevű attribútumot figyelhetünk meg, amelynek alapértelmezett értéke true:

<%@Page Language="C#" AutoEventwireup="true"


codeFile="Default.aspx.cs" rnherits="_oefault" %>

Ezzel az alapértelmezés szerinti viselkedéssel minden egyes oldalszinhl ese­


ménykezelőt automatikusan kezelünk, ha megadjuk a megfelelőerr elnevezett
metódust. Azonban, ha kikapcsoljuk az AutoPagewi reup attribútumot azzal,
hogy false értékre állítjuk

<%@Page Language="C#" AutoEventwireup="false"


codeFile="Default.aspx.cs" Inherits="_oefault" %>

többé nem kapjuk el az oldalszinhl eseményeket. Ahogyan a neve is sugallja,


ez az attribútum (ha engedélyezett) generálja a szükséges eseményeket a fe­
jezet korábbi részében említett, automatikusan generált részleges osztályban.
Az oldalszinhl eseményeket akkor is feldolgozhatjuk, ha kikapcsoltuk az Auto ­

Eventwireup attribútumot, méghozzá a C# eseménykezelő logikája segítségé­


vel, például a következőképpen:

publ i c _Default()
{
ll Explicit módon kapcsolódjunk a Load és az unload eseményekhez.
this.Load += new EventHandler(Page_Load);
this.unload += new EventHandler(Page_Unload);
}

Ahogy gyaníthattuk, általában engedélyezzük az AutoEventwi reup attribútumot.

740
Az ASP.NET-weboldalak életciklusa

Az Error esemény

Egy másik esemény, amely bekövetkezhet az oldal életciklusa alatt, az Error.


Ez az esemény akkor következik be, ha a Page-leszármazott típus metódusa
olyan kivételt jelez, amelyet nem kezeltünk explicit módon. Tételezzük fel,
hogy kezeltük az oldalon egy adott gomb a kattintási eseményét, és az ese­
ménykezelőben (amelyet btnGetFi le_click névre kereszteltünk) megpróbál­
juk kiírni egy helyi fájl tartalmát a HTTP-válaszba.
Tételezzük fel azt is, hogy nem sikerült tesztelnünk a fájl meglétét szabvá­
nyos strukturált kivételkezelésset Ha az alapértelmezett konstruktorban lét­
rehoztuk az oldal Error eseményét, még van rá esélyünk, hogy megoldjuk a
problémát, rnielőtt a végfelhasználó egy csúnya hibával találkozna. Tekint­
sük meg a következő kódot:

public partial class _Default : System.web.UI.Page


{
void Page_Error(object sender, EventArgs e)
{
Response.clear();
Response.Write("I am sorry... I can't find a required
file.<br>");
Response.write(string.Format("The error was: <b>{O}<Ib>",
server.GetLastError().Message));
Server.ClearError();
}

protected void Page_Load(object sender, EventArgs e)


{
Response.Write("Load event fired!");
}

protected void Page_unload(object sender, EventArgs e)


{
ll Már nem továbbíthatunk adatokat a HTTP-válaszba,
ll ezért helyi fájlba fogjuk írni.
system.IO.File.writeAllText(@"C:\MyLog.txt", "Page unloading!");
}

protected void btnPostback_click(object sender, EventArgs e)


{
ll semmi nem történik, ezzel csak visszaküldést
ll biztosítunk az oldalra.
}
protected void btnTriggerError_click(object sender, EventArgs e)
{
System.IO.File.ReadAllText(@"C:\IDontExist.txt");
}
}

741
31. fejezet: ASP.NET-weboldalak készítése

Figyeljük meg, hogy az Error eseménykezelő azzal kezdi, hogy a HITP-vá­


lasz aktuális tartalmát törli, és generikus hibaüzenetet küld. Ha szeretnénk
hozzáférni a system. Exception objektumhoz, azt az öröklött Server tulajdon­
ság által kibocsátott Httpserverutil ity. GetLastError O metódus segítségével
tehe�ük meg.
Végezetül figyeljük meg, hogy mielőtt kilépünk ebből a generikus hibakeze­
lőből, explicit módon hívjuk a HttpServerutility.clearError() metódust a Ser­
ver tulajdonságon keresztül. Ez azért szükséges, mert így tuda�uk a futtatókör­
nyezettel, hogy foglalkoztunk a problémával, és nem igényel további intézke­
dést. Ha ezt elfelej�ük megtenni, a végfelhasználó számára a futtatókömyezet
hibalapja jelenik meg. A 31.20. ábra a hibaelkapó logika eredményét muta�a be.

:il http://localhost51661/PagelifeCycle/Detioult.aspx- Windows Intemet ·- l= l @l llif3iil

@0 TiJ·h����-"!-����--:I!!L� @ocgt•
· ·- ________________ �:._
fi r$1 l� r l �. fill •
»

http-J/localhosi:51661/P.•• . • li:;! Page •

I am sorry J can't finda required file_


..

The error was: Collld not find file 'C:\IDoatE:rist.trt'.


.

� local intranet l Proleeted Mode: Off Q\100% .


---

31. 20. ábra: Oldalszin tű hibakezelés

Ezek után már magabiztosak lehetünk az ASP.NET Page típusának felépítését


illetően. Ilyen alapok birtokában figyelmünket az ASP.NET webes vezérlő­
elemeinek, témáinak és mesteroldalainak szerepére fordítha�uk, amelyekkel
a következő fejezet témakörei foglalkoznak. A fejezet lezárásaként azonban
vizsgáljuk meg a web. con fig fájl szerepét.

Forráskód A PagelifeCycle kódfájlokat a forráskódkönyvtár 31. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A Web.config fájl szerepe

Alapértelmezés szerint az összes Visual Studio 2008 használatával létrehozott C#


ASP.NET webalkalmazás automatikusan tartalmaz egy web. config fájlt. Azon­
ban, ha bármikor szeretnénk manuálisan beszúmi egy web. config fájlt a web­
helyre (például amikor egyoldalas modellel dolgozunk, és nem hoztunk létre

742
A Web.config fájl szerepe

solutiont), azt a Web Site> Add New ltem menüpont segítségével tehe�ük meg.
Mindkét esetben hozzáadhatunk beállításokat a web. config fájl hatókörén belül,
amelyekkel a webalkalmazás futási idejű működését szabályozha�uk.

Megjegyzés A webalkalmazásnak nem kell feltétlenül tartalmaznia web. con fi g fájlt. Ha


nincs ilyen fájlunk, a webhelyünk a .NET könyvtárában található machine. con fig fájljában
rögzített, alapértelmezett webközpontú beállításokkal rendelkezik.

Emlékezzünk vissza a .NET-szerelvényekkel kapcsolatos tanulmányainkra (a


15. fejezetben), ahol azt mondtuk, hogy a kliensalkalmazások XML-alapú
konfigurációs fájl révén utasítják a CLR-t a kötési kérések, szerelvény-próba­
vételezések és egyéb futás közbeni események kezelésének módjára. Ugyan­
ez érvényes az ASP.NET-webalkalmazásokra is, azzal a jelentős különbség­
gel, hogy a webközpontú konfigurációs fájlok neve mindig web. con fig (az
''.ex e konfigurációs fájloktól eltérően, amelyek neve a kapcsolódó kliens vég­

rehajtható fájlon alapul).


A web. config fájlok alapértelmezett szerkezete a .NET 3.5 verziójában
meglehetősen részletes, de a lényeges beállításokat a következőkben bemu­
ta�uk. A 31. 9. táblázat a web. con fig fájlok legérdekesebb elemei közül sorol
fel néhányat.

<appSettings> Ez az elem olyan név/ érték párokat hoz létre, amelyeket


programozott módon beolvashatunk a memóriába a
configu rationManager típus segítségéveL

<authentication> Ezt a biztonsággal kapcsolatos elemet a webalkalmazás hi­


telesítési üzemmódjának definiálására használjuk.

<authorization> Ez egy másik biztonságközpontú elem, amellyel azt defi­


niáljuk, hogy mely felhasznáJók mely erőforrásokhoz fér­
hetnek hozzá a webkiszolgálón.

<connectionstrings> Ebben az elemben a webhelyen használt külső kapcsolat­


sztringeket tároljuk.

<customErrors> Ez az elem tudatja a futtatórendszerrel, hogy pontosan ho­


gyan jelenítse meg azokat a hibákat, amelyek a webalkal­
mazás működése közben lépnek fel.

<globalization> Ezzel az elemmel a webalkalmazás globalizációs beállításait


konfiguráljuk.

743
31. fejezet: ASP.NET-weboldalak készítése

<namespaces> Ez az elem az alkalmazni kívánt névtereket ismerteti, ame­


lyeket importálnunk kell, ha a webalkalmazásunkat előfor­
dí tottuk az új aspnet_compi l er. ex e parancssori eszközzel.

<s ess i ons t at e> Ezzel az elemmel szabályozhatjuk, hogy a .NET-futtatórend­


szer hogyan és hol tárolja a munkamenet-állapot adatait.

<trace> Ezzel az elemmel engedélyezzük (vagy letiltjuk) a


webalkalmazás nyomkövetési támogatását.

31. 9. táblázat: A Web.conjigfájlok néhány eleme

A web. con fi g fájlok, a 31.9. táblázatban felsoroltakon kívül további elemeket


is tartalmazhatnak Ezeknek az elemeknek a nagy része biztonságközpontú,
núg a fennmaradóak csak speciális ASP.NET-helyzetekben hasznosak, mint
például egyedi HTTP-fejlécek vagy egyedi HTTP-modulok létrehozása során
(olyan témákban, amelyeket nem érintünk).
Ha a web. confi g fájlokban megtalálható összes elemet (és a kapcsolódó
attribútumokat) szeretnénk megtekinteni, lapozzuk fel a .NET Framework
3.5 SOK dokumentációját. Keressük az "ASP.NET Configuration Settings"
témát, ahogy a 31.21. ábra mutatja, és tanulmányozzuk részletesen.
A Web.config fájl különféle jellegzetességeivel a könyv további részeiben
fogunk megismerkedni.

it! ASP.NET Configufllltion �ttlngs · Microsoft Visual Studio Codena� Orcas Documentation- MKrosoft Document Expk>rer {Administrator)

File Edit View Tools Window Help

Back.., ll A; !j:HowOol ·Q. Search u; tnde:x e: contents G'J Helpfavont� ('V � MSDNForums v
Contents____ • I,l X �P.NET cónfiguution Settings Surc ... X

""-::od_.b)".:-
r'"- .: ---------------; l URL: ms-help://MS.VSCC.\'90/MS.MSONQTR.v90.mtdv_aspnetgenref/htmV1166C ·l
(unfiltc:red)
G eneral Reference
.NET Framework
b ASP.NEl Ref6ence ASP.NET Configuration Settings
Browsf'r Definition Filf' Scht>ma {browsf':rs E!c:mt>:n(
lt.J Client Reference �
0 Configuration F•l� Syntax s Collapse Ali
- t}iall!i!tfr.Wi@ifl·M·b
:+� <syst�m.w�b>
.. <system.web.e:xtenSions>
ft; < system.�bServtr>
<system.servtceModel> ASP.NET configuration files a re XML f�es. The .NET
General Configuratton Settings

Framework defines a set of elements that implement
.. Global.asax Syntax configuration settings, and the ASP.NET configurabon
HTML �er Contfels schema contatns elements that control how ASP.NET
�� Page Syntax Web applications beha ve.

Default configuration settings a re specitied in the


Machine.config file located in the %SystemRoot%
{ \Microsoft.NET\Framework\verslonNumber\CONFlG\
·rerJf.l..Ol..:-.\la�_y_CP_j�_e_E.t:P_d_b'il chiliLsitPCU\Ort----=- �

31.21. ábra: A Web.conftgfájlok dokumentációjának részletei

744
A Web.config fájl szerepe

Az ASP.NET Website Administration segédprogramja

Habár a Web.config fájlok tartalmát bármikor közvetlenül módosíthatjuk a


Visual Studio 2008 segítségéve!, az ASP.NET-webprojektek hasznos web­
alapú szerkesztőt használhatnak, amely révén grafikus módon szerkeszthet­
jük a projekt web. con fi g fájljának több elemét és attribútumát. Az eszköz el­
indításához a 31.22. ábrán látható módon aktiváljuk a Web Site> ASP.NET
Configuration menüpontot.

Welcome to the Web Site Administration Tool

Application:/WebSite 1
Current User Name:INTERTECH\ATROELSEN

Enables you to set up and edit users, roles, �nd �ccess permiSSions for
your site.
Site is using windows authentíaltion for user management.

Applicatton
Enables you to manage your application's configuration settings.
Configuration
Enables you to specify where and how to store administration data used
Provider Configu ration
by your Web site.

31.22. ábra: Az ASP.NET Web Site Administration eszköze

Ha az oldal felső részén található fülekre kattintunk, hamar észrevehetjük,


hogy az eszköz funkcionalitása nagyrészt a webhely biztonsági beállításainak
létrehozására alkalmas. Ez az eszköz azonban azt is lehetövé teszi, hogy beál­
lításokat adjunk hozzá az <appSetti n gs> elemhez, hibakeresési és nyomköve­
tési beállításokat definiáljunk, és létrehozzunk egy alapértelmezett hibalapot
Az eszköz működését majd akkor láthatjuk részletesebben, ha szükséges.
Azonban jó, ha tudjuk, hogy ez a segédprogram nem teszi lehetővé az összes
lehetséges beállítás hozzáadását a web. confi g fájlhoz. Biztosan kerülünk
olyan helyzetbe, amikor egy választott szövegszerkesztő segítségével kézzel
kell módosítanunk ezt a fájlt.

745
31. fejezet: ASP. NET- weboldalak készítése

Összefogla l ás

A webalkalmazások létrehozásához más gondolkodásmódra van szükség,


mint a hagyományos asztali alkalmazások összeállításakor. Ezt a fejezetet
néhány alapvető webes téma - köztük a -HTML, a HTTP, az ügyféloldali
szkriptírás szerepe és a kiszolgálóoldali szkriptírás a klasszikus ASP segítsé­
gével - gyors, áttekinthető bemutatásával kezdtük. A fejezet nagy része az
ASP.NET-oldalak architektúráját vizsgálta. Ahogy azt láthattuk, a projektünk
núnden egyes '' . aspx fájlja tartalmaz egy kapcsolódó Syst:em. web. ur. Page-le­

származott osztályt. Ennek az 00 módszernek a révén az ASP.NET lehetővé


teszi, hogy újrafelhasználható és 00 alapú rendszereket építsünk fel.
Miután az oldalak származtatási láncának néhány alapvető szerepét meg­
vizsgáltuk, tanulmányoztuk az oldalak érvényes .NET-szerelvényekké fordí­
tásának módját. Befejezésül a web. con fi g fájl szerepét kutattuk, és áttekintet­
tük az ASP.NET Web Site Administration eszközét.

746
HARMINCKETTEDIK FEJEZET

ASP. NET vezérlőe lemek ,


-

- témák és mestero ldalak -

Az előző fejezetben az ASP.NET Page objektumok formázására és viselkedé­


sére helyeztük a hangsúlyt. A következőkben az oldalak felhasználói felülete­
it felépítő webes vezérlőelemek részleteit vizsgáljuk meg. Az ASP.NET webes
vezérlőelemek általános megvizsgálása után érthetővé válik, hogy hogyan
használunk jó néhány felhasználóifelület-elemet, köztük az ellenőrző és adat­
központú vezérlőelemeket
A fejezet második fele a mesteroldalak szerepét tekinti át, és bemutatja, ho­
gyan lehet azok segítségével egyszerűen definiálni olyan közös felhasználóife­
lület-vázat, amelyet a webhelyünk oldalain többször is alkalmazhatunk. A cé­
lunk az, hogy bemutassuk, hogyan használjunk témákat az oldalakra a vezérlő­
elemek következetes megjelenéséhez. Az ASP.NET témamodulja kiszolgáló­
oldali alternatívát nyújt a hagyományos ügyféloldali stíluslapokkal szemben.

A webes vezérlőelemek viselkedésének


megértése

Az ASP.NET egyik fő erénye, hogy képes összeállítani az oldalak felhasználói


felületét a system. web. ur. webcont ro ls névtérben definiált típusokkaL Ezek a
vezérlőelemek (amelyek server controls, web controZs vagy Web Form controZs né­
ven ismertek) rendkívül hasznosak, ugyanis automatikusan generálják a
szükséges HTML-kódot a böngésző számára, és a webkiszolgáló számára
feldolgozható eseménykészlettel szalgálnak Továbbá rnivel a system. web.
UI. webcont ro ls névtérben megvannak az egyes ASP.NET-vezérlőelemeknek
megfelelő osztályok, azok objektumorientáltan kezelhetők.
Amikor a Visual Studio 2008 Properties ablakának segítségével állítjuk be
egy webes vezérlőelem tulajdonságait, a módosításainkat az * . as p x fájl adott
elem deklarációjának nyitó tagjében rögzítjük névj érték párok sorozataként.
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

Így, ha hozzáadunk egy új TextBoxot az adott * . aspx fájl tervezőjéhez, és mó­


dosí�uk az ID, Borderstyle, Borderwidth, Backcolor és Text tulajdonságokat, a
nyitó <asp:TextBox> tag ennek megfelelően változik (a Text értéke a TextBox

hatókörén belüli szöveg lesz):

<asp:TextBox ID="txtNameTextBox" runat="server"


Backcolor="#COFFCO" Borderstyle="Dotted"Borderwidth="Spx">
Enter Your Name
<lasp:TextBox>

Mivel a webes vezérlőelemek HTML-deklarációja végül a system.web.UI.

webcontrols névtér egyik tagváltozója lesz (az előző fejezetben tárgyalt, di­
namikus fordítási ciklus révén), képesek vagyunk ennek a típusnak a tagjai­
val kommunikálni a kiszolgálóoldali <script> blokkon keresztül vagy az ol­
dal mögöttes kódfájljának a segítségéveL Ha például egy adott Button típus­
nak implementáltuk a click eseményét, a TextBox háttérszínét a következő­
képpen módosítha�uk:

partial class _Default : System.Web.UI.Page


{
protected void btnchangeTextBoxcolor_Click(object sender,
System.EventArgs e)
{
ll Kódból módosítjuk a TextBox színét.
this.txtNameTextBox.Backcolor = Drawing.Color.DarkBlue;
}
}

Az összes ASP.NET webes vezérlőelem végül is a system.web.UI. webcont­

rols.webcontro l nevű közös ősosztályból, a webcontrol pedig a system. web.

UI.Control osztályból származik (amely pedig a System.object osztályból).


control és a webcontrol számos olyan tulajdonságot definiálnak, amelyek
közösek az összes kiszolgálóoldali vezérlőelem számára. Mielőtt megismer-
nénk az öröklött működést, formalizáljuk a kiszolgálóoldali események keze­
lésének jelentését.

Kiszolgálóoldali események kezelése

A web jelenlegi állapota mellett lehetetlen elkerülni a böngésző/webkiszolgáló


interakció alapvető sajátosságait. Amikor ez a két entitás kommunikál, mindig
egy mögöttes, állapotmentes, HTTP kérés/válasz ciklus jön létre. Amíg az
ASP.NET kiszolgálójának vezérlőelemei sokat tesznek azért, hogy elkerülhes-

748
A webes vezérlőelemek viselkedésének megértése

sük a nyers HTIP protokollt, ha a webet eseményvezérelt egyedként kezeljük,


az nem más, mint a CLR "szemfényvesztő" produkciója, és nem egyezik meg a
windowsos felhasználói felületek eseményvezérelt modelljével.
J\ System.Windows.Forms, a System.Windows.Controls és a system.Web.ur.

webcontrol s névterek például ugyanolyan egyszerű elnevezésű típusokat de­


finiálnak ( sutton, TextBox, Label stb.), mégsem ugyanazokat az eseménykész­
leteket biztosítják. J\ Web Form Button típus segítségével például nem lehet
kiszolgálóoldali MouseMove eseményt kezelni akkor, amikor a felhasználó
mozgatja a kurzort. Ez nyilvánvalóan hasznos. (Ki szeretne ugyanis minden
alkalommal adatot visszaküldeni a kiszolgálónak, amikor megmozdítja az
egerét a böngészőben?)
Végezetül, egy adott 1\SP.NET webes vezérlőelem korlátozott számú ese­
ménykészletet biztosít, ezek mindegyike végül visszaküldést (postback) ered­
ményez a webkiszolgáló felé. J\z események ügyféloldali feldolgozásához ne­
künk kell elkészíteni azt az ügyféloldali JavaScript/VBScript kódot, amit a ké­
rést intéző böngészőnek futtatnia kell. Mivel az 1\SP.NET elsősorban kiszolgá­
lóoldali technológia, az ügyféloldali szkriptírást itt nem részletezzük

Megjegyzés A Visual Studio 2008 esetében ugyanúgy kezelhetjük egy adott webes vezérlő­
elem eseményét, mint egy Windows Forms vezérlőelemét. Válasszuk ki a tervezőben a vezér­
lőelemet, .majd kattintsunk a "villám" ikonra a Properties ablakban.

Az AutoPostBack tulajdonság

Sok 1\SP.NET webes vezérlőelem támogatja az AutoPostBack nevű tulajdon­


ságot (legjelentősebbek a jelölőnégyzet, a rádiógomb és a szövegdoboz-ve­
zérlőelemek, valamint az absztrakt Listcontrol típusból származó elemek).
Ennek a tulajdonságnak az alapértelmezett értéke hamis, így kikapcsolja a ki­
szolgálóoldali események automatikus feldolgozását (még akkor is, ha a mö­
göttes kódfájlban feliratkoztunk az eseményre). J\ legtöbb esetben erre a vi­
selkedésre van szükség, hiszen az olyan felhasználóifelület-elemek, mint a je­
lölőnégyzetek, nem igényelnek visszaküldést (ugyanis az oldalobjektum a
jóval természetesebb gomb kattintási eseménykezelőjével megtudhatja a ve­
zérlőelem állapotát).

749
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

Ha azonban szeretnénk, hogy ezek a vezérlőelemek visszaszóljanak a ki­


szolgálóoldali eseménykezelőknek, akkor az AutoPostBack értékét egyszerűen
állítsuk igazra. Ez a módszer nagyon hasznos lehet akkor, ha a vezérlőelem
állapota alapján szeretnénk beállítani az oldal egy másik elemének az értékét.
Ennek szemléltetéséhez tételezzük fel, hogy a weboldalunk egy txtAutoPost­
back nevű szövegdoboz, és egy l stTextBoxoata nevű L istBox vezérlőelemet
tartalmaz. Az ehhez tartozó kód az *. aspx fájlban a következő:

<form id="forml" runat="server">


<asp:TextBox ID="txtAutoPostback" runat="server"></asp:TextBox>
<br/>
<asp:ListBox ID="lstTextBoxoata" runat="server"></asp:ListBox>
</form>

Kezeljük a TextBox Textchanged eseményét, és a kiszolgálóoldali eseményke­


zelőben a L i stBox értéke felveszi a TextBox jelenlegi értékét:

partial class _Default : System.web.ur.Page


{
protected void txtAutoPostback_Textchanged(object sender,
System.EventArgs e)
{
lstTextBoxoata.Items.Add(txtAutoPostback.Text);
}
}

Ha futta�uk az alkalmazást, azt tapasztaljuk, hogy ha beírunk valamit a szö­


vegdobozba, nem történik semmi. Továbbá, ha írunk valamit a szövegdoboz­
ba, és a következő vezérlőelemre lépünk, ugyancsak nem történik semmi. Ez
azért van, mert a szövegdoboz AutoPostBack tulajdonságának alapértelmezett
értéke hamis. Viszont, ha az igaz értéket adjuk ennek a tulajdonságnak,

<asp:TextBox ID="txtAutoPostback"
runat="server" AutoPostBack="true">
</asp:TextBox>

és továbblépünk a szövegdobozról (vagy megnyomjuk az Enter billentyűt), a


L istBox automatikusan felveszi a szövegdoboz jelenlegi értékét. Biztosak le­
hetünk benne, hogy a más vezérlők értékeire épülő vezérlőelemek feltöltésén
kívül általában nem szükséges az AutoPostBack tulajdonság állapotát módosí­
tani (de még ebben az esetben is néha elég egy kliensszkriptet végrehajtani,
ezzel teljesen kizárva a kiszolgálóval való interakció szükségességét).

750
A System. Web. UI.Control típus

A System.Web. UI.Control tipus

A system. web . ur. control alaposztály szárnos olyan tulajdonságot, metódust és


eseményt definiál, amelyek engedélyezik a webes vezérlőelemek alapvető (ál­
talában nem grafikus felületi) funkcióival folytatott komrnunikációt. A 32.1.
táblázat a teljesség igénye nélkül, néhány jelentősebb tagot tartalmaz.

controls Ennek a tulajdonságnak az értéke olyan ControlColleetion


objektum, amely tartalmazza az aktuális vezérlőelem gyer­
mek -vezérlőelemeit.

DataBind() Ez a metódus egy adatforrást köt a hívott kiszolgáló-vezérlő­


elemhez, illetve annak összes gyermek-vezérlőeleméhez.

E n a bl e T h e min g Ettől a tulajdonságtól függ, hogy a vezérlőelem támogatja-e a


témákat.

Hascontrols() Ez a metódus határozza meg, hogy a kiszolgáló-vezérlőelem


tartalmaz-e gyermek-vezérlőelemeket

ID Ez a tulajdonság a kiszolgáló-vezérlőelemhez rendelt progra­


mozott azonosítót kérdezi le vagy állítja be.

Page Ez a tulajdonság visszaadja a kiszolgáló-vezérlőelemet tartal­


mazó Page példány referenciáját.

Parent Ez a tulajdonság visszaadja a kiszolgáló vezérlőelem szülő­


vezérlőelemének referenciáját az oldal vezérlőelem­
hierarchiájában.

skiniD Ez a tulajdonság lekérdezi vagy beállítja a vezérlőelemre al­


kalmazott "arculatot". Az ASP.NET-ben így a vezérlőelemek
megjelenését arcuJatok segítségével egyszerűen alakíthatjuk ki.

visible Ez a tulajdonság azt kérdezi le, vagy állítja be, hogy a kiszolgá­
ló-vezérlőelem felhasználóifelület-elemeként megjelenik-e az
oldalon.

32.1. táblázat: A System. Web. UI.Control neluíny tagja

751
32. fejezet : ASP NET vezérlőelemek
. - , · témák és mesteroldalak
·

Vezérlőelemek felsorolása

Az első, amit a system.web.ur.control esetében- megvizsgálunk, az a tény,


hogy az összes webes vezérlőelem (köztük maga az oldal) egyedi vezérlőelem­
gyűjteményt örököl, amely a controls tulajdonságon keresztül érhető el.
A Windows Forms-alkalmazásokban tapasztaltakhoz hasonlóan, a controls
tulajdonsággal férhetünk hozzá egy erősen típusos, webcontrol-leszármazott
típust tartalmazó gyűjteményhez. Mint a többi .NET-gyűjtemény esetében, eb­
ben is futásidőben, dinamikusan adhatunk hozzá, szúrhatunk be és távolítha­
tunk el elemeket.
Bár technikailag lehetséges webes vezérlőelemeket közvetlenül hozzáadni

Page-leszármazott típusokhoz, ennél egyszerűbb (és jóval hatékonyabb), ha


Paneleket használunk. A Panel osztály olyan vezérlőelemtároló, amelyet a
végfelhasználó vagy láthat, vagy nem láthat (a visible és a Borderstyle tu­
lajdonságainak értékétől függően).
Ennek szemléltetésére, hozzunk létre egy új, DynarnicCtrls nevű webhe­
lyet A Visual Studio 2008 oldaltervezőjében adjunk hozzá egy myPanel nevű
panelt, amely egy tetszőleges elnevezésű TextBox, Button és HyperLink vezér­
lőket tartalmaz (ügyeljünk arra, hogy a tervezőben a Panel típus felhasználói
felületére kell húznunk a belső elemeket). Ha ezt megtettük, az * . aspx fájl

<form> eleme az alábbiak szerint módosul:

<asp:Panel ID="myPanel" runat="server" Height="SOpx" width="l25px">


<asp:TextBox ID="TextBoxl" runat="server"></asp:TextBox><br/>
<asp:Button ID="Buttonl" runat="server" Text="Button"/><br/>
<asp:HyperLink ID="HyperLinkl" runat="server">HyperLink
</asp:HyperLink>
</asp:Panel>

Ezután helyezzünk egy lblcontrol Info nevű címkét a panel hatókörén kívülre,
amelyben megjelenik a kimenet. Tételezzük fel, hogy a Page_Load() esemény­
ben a panel összes vezérlőelemének listáját szeretnénk megkapni, az ered­
ményt pedig hozzá szeretnénk rendelni a lblcontrol Info nevű címkéhez:

public partial class _Default : system.web.UI.Page


{
private void ListcontrolsinPanel()
{
string theinfo ;
'"'
=

theinfo = string.Format("Has controls? {O} <br/>",


myPanel.Hascontrols());
foreach (Control c in myPanel.controls)
{

752
A System.Web. UI.Control típus

if (!object.ReferenceEquals(c.GetType(),
typeof(System.web.UI.Literalcontrol)))
{
theinfo +=
"*********************":*****<br/>";

theinfo += string.Format("Control Name? {O} <br/>",


c.ToString());
theinfo += string. Format("ID? {O} <b r>", c.ID);
theinfo += string.Format("Control Visible? {O} <br/>",
c.visible);
theinfo += string.Format("Viewstate? {O} <br/>",
c.Enableviewstate);
}
}
lblcontrolinfo.Text theinfo;
}

protected void Page_Load(object sender, System.EventArgs e)


{
ListcontrolsinPanel();
}
}

if Untitled Poge - Windows Intemet Explorer l= l@) ....

OO lj) "!f:!!�:��st�t;_�I�-!. -b..l�


· .. �- -- - - - - ·- - ---

<Ji .!$t r if Untitled Page


o������
»

- -
A

Dyoamic Controls

l Button l
HyperLink

l
Has coolrols? True
•••••••••••••••••••••••••••

Control Name? System.Web.UI.WebControls_Te-'ttBox


ID? TextBox l
Control Visible? True
ViewState7 True
•••••••••••••••••••••••••••

Control Name? System.Web.UI.WebControls.Button


ID? Btxtonl
Control Visible? True
ViewState? True
.............•.............

Control Name? System.Web.UI.WebControls.HyperLink


l
ID? HyperLiokl
Control Visible? True
ViewState? True

l
' local intranet l Proleeted Mode: Off 6llOO%
� . ..

32. 1. ábra: A panel vezérlőelemeinek felsorolása

753
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Végigiteráluk a panel webcontrol elernein, és ellenőrizzük, hogy az aktuális tí­


pus system.web.UI.Literalcontrol típusú-e. Ezzel a típussalliterális HTML­
címkéket és -tartalmakat jelenítünk meg (rnint a <br>, szövegliterálok stb.). Ha
ezt az ellenőrzést nem végezzük el, meglepetésünkre a panel hatókörében
rnind a hét típus megjelenhet (a korábban ismertetett*. aspx deklaráció rniatt).
Ha a típus nem literális HTML-tartalom, jelenítsünk meg különféle statisztiká­
kat a vezérlőelemrőL A 32.1, ábrán látha�uk az eredményt.

Vezérlőelemek dinamikus hozzáadása (és törlése)

Mit kell tenni, ha futás közben szeretnénk rnódosítani a Panel tartalrnát? Ad­
junk az aktuális oldalhoz egy btnAddwidgets nevű gombot, amellyel három új
szövegdobozt adunk hozzá dinamikusarr a panelhez, valarnint egy másik,
btnRemovePanelIt ems nevű gombot, amely törli az összes vezérlőelemet a pa­

nelből. Az alábbiakban látható rnindkét gomb kattintási esernénykezelője:

protected void btnRemovePanelrtems_click(object sender,


System.EventArgs e)
{
myPanel.Controls.clear();
ListcontrolsinPanel();
}

protected void btnAddwidgets_click(object sender,


System.EventArgs e)
{
for (int i = O; i < 3; i++)
{
ll Nevet rendel hozzá, így később
ll kapjuk meg a szövegértéket
ll a bemeneti űrlapadat segítségével.
TextBox t = new TextBox();
t.ID = string.Format("newTextBox{O}", i);
myPanel.controls.Add(t);
ListcontrolsinPanel();
}
}

Minden egyes szövegdobozhoz hozzárendeltünk egy egyedi azonosítót (pl.


newTextBoxl, newTextBox2 stb.) azért, hogy a HttpRequest. Form gyűjternény
segitségével, programozottan elérhessünk a bennük lévő szövegekhez.

754
A System. Web. Ul. WebControls. WebControl típus

Ahhoz, hogy megszerezzük az ezekben a dinanúkusan generált szöveg­


dobozokban levő értékeket, adjunk hozzá még egy gombot és címkét a fel­
használói felülethez. A gomb kattintási eseménykezelőjében iteráljunk végig
a HttpRequest. Namevaluecollee ti on típus elemein (amely a HttpRequest.Form
révén érhető el), és a szöveges adatokat fűzzük hozzá a helyi hatókörű sys­
tem. strin g típushoz. Ha teljesen feldolgoztuk a gyűjteményt, a sztringet ren­
deljük hozzá az új, lblTextBoxText nevű címke Text tulajdonságához:

protected void btnGetTextBoxvalues_Click(object sender,


System.EventArgs e)
{
string textBoxvalues = "";
for (int i = O; i < Request.Form.Count; i++)
{
textBoxvalues += string.Format("<li>{O}</li><br/>",
Request.Form[i]);
}
lblTextBoxText.Text = textBoxvalues;
}

Ha lefutta�uk az alkalmazást, meg tudjuk nézni az egyes szövegmezők tartal­


mát, köztük néhány meglehetősen hosszú (nem olvasható) sztringadatot. Ez a
sztring (amelyet a következő fejezetben fogunk megvizsgálni) tartalmazza az ol­
dal vezérlőelemeinek a viewstate-jét. Amint a kérés feldolgozódott, a szövegme­
zők eltűntek. Ez a HITP állapotrnentes természete miatt van. Ha a visszaküldé­
sek között módosítani szeretnénk ezeket a dinanúkusan létrehozott szövegme­
zőket, el kell menteni az objektumokat az ASP.NET állapotkezelési módszerei­
nek a segítségével (ezt ugyancsak a következő fejezetben ismerte�ük).

Forráskód A DynamicCtrls kódfájlokat a forráskódkönyvtár 3Z. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A System. Web. Ul. WebControls. WebControl


tipus

A control típus számos nem grafikus felülethez kapcsolódó viselkedéssel


rendelkezik (vezérlőelem-gyűjtemény, automatikus visszaküldés támogatása
stb.). A webcontrol alaposztály viszont grafikus polimorf interfészt biztosít az
összes webes vezérlőnek, ahogy azt a 32.2. táblázat muta�a.

755
32. fejezet: ASP.NET·vezérlöelemek, ·témák és ·mesteroldalak

Backcolor Lekérdezi vagy beállítja a webes vezérlőelem háttérszínét.

Bordercolor Lekérdezi vagy beállítja a webes vezérlőelem szegélyszínét.

BorderStyle Lekérdezi vagy beállítja a webes vezérlőelem szegélystílusát

Borderwidth Lekérdezi vagy beállítja a webes vezérlőelem szegélyének


vonalvastagságát.

Enabled Lekérdezi vagy beállítja, hogy a webes vezérlőelem engedé­


lyezett-e.

Cssclass Lehetövé teszi, hogy egy CSS-stíluslapon definiált osztályt


hozzárendeljünk egy webes vezérlőelemhez.

Font Lekérdezi a webes vezérlőelem betűtípusadatait.

Forecolor Lekérdezi vagy beállítja a webes vezérlőelem előtérszínét (álta­


lában a szöveg színét).

Height, Lekérdezi vagy beállítja a webes vezérlőelem magasságát és


width szélességét.

Tabindex Lekérdezi vagy beállítja a webes vezérlőelem tabulátorindexét

ToolTip Lekérdezi vagy beállítja a webes vezérlőelem eszköztippjét, amely


akkor jelenik meg, amikor a kurzor a vezérlőelem felett van.

32. 2. táblázat: A WebControl alaposztály néhány tulajdonsága

A felsorolt tulajdonságok szinte mindegyike önmagáért beszél, így a követke­

zőkben néhány ASP.NET Web Form vezérlőelem működését vizsgáljuk meg.

Az ASP.NET webes vezérlőelemeinek


főbb kategóriái

A system. web. UI. webcontrols típusokat több kategóriába sorolha�uk:

• egyszerű vezérlőelemek,

• sokoldalú vezérlőelemek,

• adatközpontú vezérlőelemek,

756
Az ASP.NET webes vezérlőelemeinek főbb kategóriái

• bemenet-ellenőrző vezérlőelemek (validátorok),

• webkijelzők,

• biztonsági vezérlőelemek

Az egyszerű vezérlőelemek elnevezése arra utal, hogy olyan ASP.NET-es vezér­


lőelemekről van szó, amelyeket szabványos HTML-elemmé képezünk le
(gombok, listák, hiperhivatkozások, képőrzők, táblázatok stb.). A következő
vezérlőelem-gyűjteménybe a sokoldalú vezérlőelemek tartoznak, amelyeknek
nincs közvetlen HTML-megfelelőjük (ilyen a cal endar, a Treevi ew, a Menu, a
wi zard stb.). Az adatközpontú vezérlőelemek olyan céleszközök, amelyeket álta­
lában adatkapcsolat révén töltünk fel. Az ilyen típusú vezérlőelemek legjobb
(és legkülönlegesebb) példája az ASP.NET GridView. A kategória többi tagjai
az Repeater vezérlőelemek és a DataL i st.
Az ellenőrző vezérlőelemek olyan kiszolgálóoldali elemek, amelyek ügyfél­
oldali JavaScriptet bocsátanak ki automatikusan, űrlapmező-ellenőrzés céljá­
ból. Végezetül, az alaposztálykönyvtár számos biztonsági vezérlőelemet tar­
talmaz. Ezek a felhasználóifelület-elemek magukban foglalják a webhelyre va­
ló bejelentkezés részleteit, a jelszó-visszakeresési szolgáltatásokat és felhasz­
nálóiszerepkör-kezelést.

Toolbox

�ro-s�

.. -
"" Pointer
� RequiredFieldValidator

Q RangeValidater

� RegularExpressionValidator
� CompareValidator

� CustomValidator

32. 2. ábra: Az ASP.NETwebes vezérlőelemek

757
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesterofdalak

Az ASP.NET teljes webes vezérlőelem-készletét a Visual Studio 2008 Toolbox


használatakor nézhetjük meg. A 32.2. ábrán megfigyelhetjük, hogy a kapcso­
lódó vezérlőelemek speciális elnevezésű fülben vannak csoportosítva.

Néhány szó a System.Web.UI.HtmlControls


tipusokról

Az ASP.NET két különböző webes vezérlőelem-eszközrendszert tartalmaz. Az


ASP.NET webes vezérlőelemein kívül (amelyek a system.web.ur.webcontrols

névtérben találhatók) az alaposztálykönyvtár a system.web. UI.Htmlcont rols

névtérben található vezérlőket is a rendelkezésünkre bocsátja.


A HIML-vezérlőelemek olyan típusok gyűjteménye, amelyek lehetövé te­
szik, hogy a hagyományos HTML-vezérlőelemeket webűrlapokon használjuk.
A nyers HIML-tagektől eltérően azonban a HTML-vezérlőelemek olyan objek­
tumorientált egyedek, amelyeket konfigurálhatunk úgy, hogy a kiszolgálón fus­
sanak, így támogassák a kiszolgálóoldali eseménykezelést Az ASP.NET webes
vezérlőelemeivel ellentétben a HTML-vezérlőelemek meglehetősen egyszerűek,
és a szabványos HTML-tageken túl csekély funkcionalitással szelgálnak (Html­
Button, Htmlinputcontrol, HtmlTable stb.). A Visual Studio 2008 egy speciális
részt biztosít a Toolboxban a HIML-vezérlőelemek számára (lásd 32.3. ábrát).

Toolbox

ils HTML
\ Pointer
D Input (Sutto n)
� Input (Reset)
f!tJ Input (Submit) .,
@Input (Tex!) l
@Input (File)
j @ Input (Password)

! 0 Input (Checkbox)
l 0 Input (Radio)
1 ,.it!' Input (Hidden)
! \im Textarea
i Table

l�
ill
i Gil Image
i, �
- Select
i B Horizental Rule

l [lj] Div
li - 9 Geaenl
l
�� SeiVer Explore� )(- Tcolboxj
32. 3. ábra: A HTML-vezérlőelemek

758
Sokoldalú ASP.NET-webhely készítése

A HTML-vezérlőelemek olyan nyilvános interfészt biztosítanak, amely a


szabványos HTML-attribútumokat utánozza. Ahhoz például, hogy hozzáfér­
jünk egy bemeneti tartomány adataihoz, használjuk a va l u e tulajdonságot a
webes vezérlőelem-központú Text tulajdonság helyett. A HTML-vezérlőele­
mek nem olyan sokoldalúak, mint az ASP.NET webes vezérlőelemek Ha
szeretnénk többet megtudni ezekről a típusokról, lapozzuk fel a .NET Fra­
mework 3.5 SDK dokumentációját.

Megjegyzés A HTML-vezérlőelemek hasznosak lehetnek, ha tisztán szétválnak a HTML fel­


használói felület tervezői és a .NET fejlesztői szerepek. A HTML-fejlesztők a kívánt webszer­
kesztőt használhatják, miközben ismert tageket alkalmazva továbbítják a HTML-fájlokat a fej­
lesztőcsapatnak. A fejlesztök ekkor kiszolgálóoldali vezérlőelemekké kenfigurálhatják ezeket a
HTML-vezérlőelemeket (ha a jobb gombbal a HTML-vezérlőre kattintanak a Visual Studio 2008-
ban). Így lehetőségük nyílik a kiszolgálóoldali események kezelésére, és hogy programozottan
dolgozzanak a HTML-vezérlőkkel.

Sokoldalú ASP.NET-webhely készítése

Mivel sok "egyszerű" vezérlőelem megjelenése hasonlít a Windows Forms


párjáéra, az alapvető vezérlőelemeket nem részletezzük (gombok, címkék,
szövegdobozok stb.). Ehelyett készítsünk egy olyan webhelyet, amely szá­
mos különlegesebb vezérlőelem, valamint az ASP.NET -mesterodal és a fejlett
adatkötés használatát szernlélteti. A következő példa kifejezetten az alábbi
módszereket mutatja be:

• mesteroldalak használata,

• a Menu vezérlőelem használata,

• a GridView vezérlőelem használata,

• a wi z ard vezérlőelem használata.

Először hozzunk létre egy új ASP.NET webalkalmazás-projektet, AspNet­


CarsSite néven.

759
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

Mesteroldalak használata

Sok webhely következetes megjelenést és működést biztosít több oldalon ke­


resztül (szokásos menüalapú navigációs rendszer, szokásos fejléc- és lábléc­
tartalom, vállalati logó stb.). Az ASP.NET l.x alatt a tervezők széles körben
alkalmazták a usercontrolokat és egyedi webes vezérlőelemeket a több olda­
lon keresztül felhasznált webes tartalom definiálásához. Jóllehet a usercont­
rolo k és az egyedi webes vezérlőelemek még mindig nagyon hatékonyak az
ASP.NET alatt, most már rendelkezésünkre állnak a mesteroldalak, amelyek a
meglévő módszereket egészítik ki.
A mesteroldal valamivel több, mint egy ASP.NET-lap, ugyanis * .master
fájlkiterjesztést kapott. A mesteroldalak önmagukban nem láthatók a böngé­
szőkben (azaz az ASP.NET-futtatókömyezet nem szolgál ki mesteroldalakra
mutató kéréseket). A mesteroldalak ehelyett közös felhasználóifelület-keretet
definiálnak, amelyet a webhely összes lapja (vagy a lapok egy részhalmaza)
használ.
Emellett a *.master fájl különféle tartalomhelyőrző területeket definiál,
amelyek olyan felhasználóifelület-tartományt alkotnak, amelyhez más ,., .aspx

fájlok csatlakozhatnak Azoknak az *.aspx fájloknak, amelyek egy mesterol­


dalhoz csatolják a tartalmukat, némileg eltérő a megjelenésük és a működé­
sük, mint az eddig vizsgált *.aspx fájloknak Az ilyen *.aspx fájlokat tarta­
lomlapnak nevezzük. A tartalomlapok tehát olyan *.aspx fájlok, amelyek nem
határoznak meg a HTML <form> elemét (ez a mesteroldal feladata).
Ám ami a végfelhasználót illeti, a kérés egy adott *.aspx fájlhoz fut be.
A webkiszolgálón a kapcsolódó *.master fájl és az egyéb kapcsolódó ''.aspx
tartalomlapok egy lapban egyesülnek. A mesteroldalak és a tartalomlapok
használatának szemléltetéséhez először adjunk a webhelyhez egy új mester­
oldalt a Web Site >Add New Item menüpont segítségével (a 32.4. ábrán az
eredményül kapott párbeszédablak látható).
A MasterPage.master fájl kezdeti markupja a következő:

<%@ Master Language="C#" AutoEventwireup="true"


codeFile="MasterPage.master.cs" Inherits="MasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"


"http://www.w3.org/TR/xhtmll/DTD/xhtmll-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="head" runat="server">

760
Sokoldalú ASP.NET·webhely készítése

</asp:ContentPlaceHolder>
</head>
<body>
<form id="forml" runat="server">
<div>
<asp:ContentPlaceHolder id="ContentPlaceHolderl" runat="server">
</asp:contentPlaceHolder>
</div>
</form>
</body>
</html>

Add New !tem - C:\�\WCF �mples\A..f.


s .NetWebSite"- .· --�:t'-->-��;,- ____,..._._.,.. c-_
.,.,.,..,l

Templatos: tri1IDG
Ví�ual Studio install�d tempiates .

@j ' D � � [i li u g Q
-
i
Web Form ; �aster �age WebUser AJAXCiient AJAXClient AJA)( Client AJAXMaster AJ!>;!. Web AJAX·enab...
Control Be.havior Control Library Page Form WCFService

;;
fl
Browser File

Class

Class

DataSet
.

Generic

Global
[iJ
HTM L Page
ri
JS<:riptFile
r&\
UNQtoSQL
Diagram Handter Applicati•.. Classes

� � . ill
- � � (j � [i �
Report Report Resource: File SiteMap Skin File SQL StyleSheet Text File WCFSf:rvice
WIZard Database

� -

... . .. .

, ..
�..... .
B1J
> ' & 0 0 ... l
rll
......... �.

AMaster Page for Web Applications

Name MasterPage.master

f LanglUlgc IV'ISUal C# --
�i li] Place code in �parate file
EJ Select master page

---------- --------- ------


l
-------------------- ------------ - - ----- --- - ------- --- --------- ----- ----- ----------
Add
ll Cancel
l
32 . 4 . ábra: Új *.master fájl beszúrása

Az első érdekesség az új <%@Master%> direktíva. Ez a direktíva nagyrészt


ugyanazokat az attribútumokat támogatja, rnint az előző fejezetben bernuta­
tott <%@Page%> direktíva. A Page típusokhoz hasonlóan a rnesteroldalak egy
adott ősosztályból származnak, amely jelen esetben a MasterPage. Ha meg­
nyitjuk a kapcsolódó forráskódfájlt, az alábbi osztálydefinícióval találkozunk:

public partial class MasterPage : System.web.UI.MasterPage


{
protected void Page_Load(object sender, EventArgs e)
{
}
}

761
32. fejezet: ASP.NET-vezérlöelemek, ·témák és -mesteroldalak

Amesteroldal markupjának másik érdekessége az <asp: contentPlaceHolder>


típus. Amesteroldalnak ez a tartománya azt a területet jelenti, amelyhez csat­
lakozhatnak a kapcsolódó *. aspx tartalomfájl felhasználóifelület-elemei. Ez a
tartomány nem magán a mesteroldalon definiál tartalmat. Ha a * . mas ter lap
tervezői nézetére váltunk, látha�uk rninden egyes <asp:ContentPl aceHol der>
elemhez tartozó megjelenést, ahogy a 32.5. ábra muta�a.
Ha egy *. aspx fájlban hivatkazunk erre a tartományra, akkor az <asp: con­
tentPlaceHolder> és az </asp:contentPlaceHol der> tagek hatókörében a mes­
teroldalon található tartalom nem jelenik meg, hanem azt a *.asp x fájl felülír­
ja. Amesteroldalon az <asp:contentPl aceHolder> és az </asp:contentPlace­
Ho l de r> tagek között megadott vezérlőelemek alapértelmezett felhasználói
felületként működnek azokban az esetekben, amikor a webhely egy adott
*. aspx fájlja nem hivatkozik erre a tartományra. Példaként tételezzük fel,
hogy a webhely rninden egyes *. aspx fájlja valóban hordoz egyedi tartalmat,
ezért az <asp: contentPl aceHo l de r> elemek üresek.

Megjegyzés A *. master lapok annyi tartalomhelyőrzöt definiálhatnak, amennyi csak szüksé·


ges. Emellett egy*. master lapba további*.master lapokat is beágyazhatunk.

T X

Client Objects & Events • (No Events)


11> <body>
12�- <form id="forml" runat="server">
-;:
1 3�? <di v r> - -=-
-- =---==""="'� ......-=:---o-=
..-: - '="' :"'7'" -:-:::--.

1 ;:? � -ap :Con�en�Plac e Bolder id-•Con�en�Pla ceHolderl• ·�

�11 �1
t::

:;::�:"'""'"'",....,""''
< l.-··- - lll .l
ContentPtaceHolde'l

l l

l'
Q Design � @l Source l 0�j<asp:ContentPiaceHolder#C... >j [!]
32. 5. ábra: EgJJ •. master fájl <asp:ContentPlaceHolder> címkéi teroezés közben

Ugyanazoknak a Visual Studio 2008 tervezőknek a segítségével hozhatunk


létre közös felhasználói felületet a *. master fájlok számára, rnint amelyekkel
*. aspx fájlokat készítettünk. Ehhez a webhelyhez egy leíró címkét (amely
egyszerű üdvözlőüzenetként fog szolgálni), egy AdRotatort (amely két kép

762
Sokoldalú ASP.NET·webhely készítése

közül fog egyet véletlenszerűen megjeleníteni) és egy Menu vezérlőelemet


(amelynek segítségével a webhely egyéb területeire navigálhat a felhasználó)
fogunk hozzáadni. A 32.6. ábra a mesteroldal felhasználói felületének egyik
lehetséges változatát muta�a be (a tartalomhelyőrző üres).

--- --- --
<:ént Objects & Evftlts � (No Ev..nts) �

13: <asp:Label ID="Labell" =unat="server" Fon�-s�ze="XX-Large"

<a�p:AdRotator !D="myAdRotacor" runat="server" Adverti3ereer�


1�1
'
15 &r.bsp;<br l>

16l �asp:Menu ID-•Men�; � runa.c.-•server:>J


17; rj</up : Menu �
B; <br l>

19! <hr />

20! <br />

21j <asp:contentplaceholder ict="ContentPlaceHold�rl"

22\ </asp:contentplaceholder>

23! &r.bsp; &!""..b!tp; &nbsp;


241 div>

25i form>

c__ lll

��o.>::ne to Cars R Us�


j
'

'-'

Q Design �@J Source l ��j<form#forml>j�j<asp:Menu#Menul>j [8


32. 6. ábra: A *.master fájl megosztott felhasználói felülete

A Menu vezérlőelem és a *.sitemap fájlok használata


Az ASP.NET számos olyan webes vezérlőelemet tartalmaz, amelyek a web­
helyen belüli navigáció kezelésére szolgálnak: sit:eMapPath, Treevi ew és Me nu.
Ezeket a webes vezérlőelemeket többféleképpen konfigurálha�uk. Képesek
például dinamikusan csomópontokat generálni egy külső XML-fájlból (vagy
egy XML-ala pú *.si ternap fájl alapján), programozottan forráskóddal vagy a
markupban, a Visual Studio 2008 tervezői segítségéveL A menürendszert di­
namikusan fogjuk feltölteni egy *.si ternap fájl használatával. Ez azért elő­
nyös, mert a webhely teljes struktúráját egy külső fájlban építhe�ük fel, majd
menet közben csatolha�uk egy Menu (vagy Treevi ew) vezérlőhöz. Így, ha vál­
tozik a webhely navigációs struktúrája, csak a *.si ternap fájlt kell módosíta-

763
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

nunk, majd az oldalt frissítenünk Először szúrjunk be egy új web. siternap


fájlt a projekthe a Web Site >Add New ltem menüpont kiválasztása után
megjelenő, a 32. 7. ábrán látható párbeszédablakkat

Add New Item- C:\�ode\WCF Examples�NotWobSi� �


�" .., ""

Templates: ll§] LB
VisualStudio instatled tempiates -· �

o = LJ � � � � Lj g =
il
WebForm Master Page WebUser AJAX Client AJAX Client AJAXCii�t AJAX Master AJAXW•b AJAX�enab...
Control BehoMor Control Library Page Form WCF S.rvie<

5
� � m � . i) i) [il � �
Browser File Class Class DataSet Geneóc Global HTM L Page JScript File UNQtoSQL
Diagram Handle:r Applicati... Classes
--------·
-

� @ � � � -
tli�
[i1 \i1
Report Repert Resource File Site M�p _ Skin File SQL StyloShoot Ted: File WCF Service
WtZard Database

�-
....
il
.... ....
ti
,$.01 �· .
&m
.. .. ,. .
� o

.,�,T�o

-
A file used to create a site map

Narrn:: Web.sitemap

language: jvisual c• --
·l O Plilcll! code in separate file
O Select marter page

l Add
ll Cancel
l
32. 7. ábra: Új Web.sitemap fájl beszúrása

A kezdeti web. sitemap fájl egy gyökérelemet és annak két alcsomópontját de­
finiálja:

<?xml version="l.O" encoding="utf-8" 7>


<siteMap xmlns="http://sc hemas.microsoft.com/AspNet/SiteMap-File-l.O" >
<siteMapNode url="" title='"' description="">
<siteMapNode url="" title="" description="" />
<siteMapNode url="" title="" description="" />
</siteMapNode>
</siteMap>

Ha ezt a szerkezetet összekapcsolnánk egy Menu vezérlőelemmel, egy fő menü­


elemet kapnánk két almenüvel. Ezért, ha alelemeket szeretnénk megadni, egy­
szerűen csak adjunk meg új <siteMapNode> elemeket egy meglévő <siteMapNode>
hatókörén belül. Minden esetben az a célunk, hogy különféle <siteMapNode> ele­
mek segítségével hozzuk létre a webhely teljes struktúráját egy web.siternap fájl­
ban. Ezekkel az elemekkel cím- és URL-attribútumokat definiálhatunk.

764
Sokoldalú ASP.NET·webhely készítése

Az URL-attribútum muta�a meg, hogy melyik *.aspx fájira kell navigálni,

amikor a felhasználó egy adott menüelemre (vagy a Treeview egy csomópont­


jára) kattint. A webhelyünk három oldalt tartalmaz, amelyek a következők:

• Home: De fault. aspx,

• Build a Car: Build car. aspx,

• View Inventory: rnventory. aspx.

A menürendszerünk egyetlen legfelsőbb szintű "Welcome" elemet és annak


három alelemét tartalmazza. Ezért, módosítsuk a web.siternap fájlt az alábbi
módon (ügyeljünk arra, hogy minden URL-érték egyedi legyen, ellenkező
esetben futásidejű hiba lép fel):

<?xml version="l.O" encoding="utf-8" ?>


<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-l.O" >
<siteMapNode url="" title="Welcome!" description="">
<siteMapNode url="-/Default.aspx" title="Home"
description="The Home Page" />
<siteMapNode url="-/Buildcar.aspx" title="Build a car"
description="Create your drearn car" />
<siteMapNode url="-/Inventory.aspx" title="View rnventory"
description="See what is in stock" />
</siteMapNode>
</siteMap>

Megjegyzés Az oldalak előtt látható-/ prefixum az URL-attribútumban a webhely gyökerét


jelenti.

Ebben az esetben nem csatoljuk közvetlenül a web. siternap fájlt egy Menu vagy
egy Treeview vezérlőelemhez egy adott tulajdorrág segítségéveL Ehelyett a
we b . siternap fájlt megjelenítő, felhasználóifelület-elemet tartalmazó *.master
vagy * .aspx fájlnak kell magában foglalnia egy siteMapDatasource kompo­
nenst. Ez a típus automatikusan betölti a we b . siternap fájlt az objektummo­
delljébe, amikor lekérjük az oldalt. Ezután a Menu és a Treeview típus Data­
sourceiD tulajdonsága a siteMapDatasource példányára mutat. Az indirekció
oka, hogy így olyan egyedi szolgáltatót hozhatunk létre, amely másik forrás­
ból tölti be a webhely struktúráját (pl. egy adatbázistáblából, egy meglévő
XML-fájlból stb.). A 32.8. ábra a we b sitemap, a siteMapDatasource és a külön­
.

böző felhasználóifelület-elemek közötti kapcsolatot muta�a be.

765
32. fejezet: ASP.NET-vezérlőelemek, -témák és - meste roldalal{

*aspx
SiteMapData.Source

Egyedi
SiteMapDataSource

32. 8. ábra: Az ASP.NET-webhelytérkép navigációs modellje

Új si teMapDatasou r ce *. master fájlhoz adásához és a DataSourceiD tulajdon­

ság automatikus beállításához, használjuk a Visual Studio 2008 tervezőt. Ak­


tiváljuk a Me nu vezérlőelem i n l i ne szerkesztőjét, és válasszuk a New Data
source lehetőséget, ahogy azt a 32.9. ábra mutatja.

Welcome toCars R Us�


asp:menu#Menul

'Root <
-
Roon f-A�u ma
or �
lo �F- 1...��---��--
-�
�-

Root
Choose Dala Source: j (None)
Root
-� _ Views: I<Newdata source l
... >
· ·· ·

Edit Menu hems...


��������------ It --------
--
' Convert to DynamichemTemplate
�..;...;...:.-�·--.:...-�·
Convert to StaticltemTemplate
------·--··---·----

Edit Ternpiales

32. 9. ábra: Új SiteMapDataSource hozzáadása

A megjelenő párbeszédablakban válasszuk a SiteMap ikont. Ezzel beállítjuk a


Menu elem DatasourceiD tulajdonságát, valamint hozzáadunk egy új site­
MapDatasource komponenst az oldalhoz. Csak ennyit kell tennünk, hogy a

menü segítségével a webhely további oldalaira navigálhassunk.

766
Sokoldalú ASP.NET·webhely készítése

Ha egyéb feldolgozást is szeretnénk végrehajtani, arnikor a felhasználó


egy adott menüelemet választ, kezeljük a Menurtemclick eseményt. Jelen pél­
dában erre nincsen szükség, de megadha�uk a kiválasztott menüelemet a
bemeneti MenuEvent:Args paraméter segítségéveL

Kenyérmorzsa-vezérlőelem létrehozása a SiteMapPath


tipus segitségével

Mielőtt továbblépnénk, még adjunk hozzá egy sit:eMapPat:h típust a * .mast:er

fájlhoz, a tartalomhelyőrző elem alá. Ez a vezérlő automatikusan beállí�a a


tartalmát a menürendszer aktuális kijelölésétől függően. Ez pedig hasznos
vizuális figyelmeztetéssei szalgálhat a végfelhasználó számára (ezt a felhasz­
nálói felületi megoldást hivatalosan kenyérmorzsának nevezzük). A feladat be­
fejezése után, arnikor a Welcome > Build a Car menüelemet válasz�uk, a
si t:eMapPat:h ennek megfelelően automatikusan frissül.

Az AdRotator használata

Az ASP.NET AdRot:at:or vezérlőelemének az a szerepe, hogy véletlenszerűen


kiválasztott képeket jelenítsen meg egy adott helyen a böngészőben. Arnikor
először helyezünk AdRot:at:ort:a tervezőbe, az üres helyőrzőként jelenik meg.
Ez a vezérlőelem ugyanis addig nem képes működni, amíg az Advert:i se­
ment:File tulajdonság nem mutat a képeket leíró fájlra. Ebben a példában az

adatforrás egy Ads.xml nevű, egyszerű XML-fájl.


Ha hozzáadjuk ezt az új XML-fájlt a webhelyhez, határozzunk meg egyedi
<Ad> elemeket az összes olyan képhez, amelyet meg szeretnénk jeleníteni. Az
<Ad> elemek legalább a megjelenítendő képet ( rmageurl), a képre kattintást
követően megjelenő URL-t (Target:url), az egérkurzor felett megjelenő szö­
veget (Alt:ernat:eText:) és az elem súlyát ( rmpressi ons) határozzák meg:

<Advert:isement:s>
<Ad>
<Imageurl>SlugBug.jpg</ImageUrl>
<Target:Url>ht:t:p://www.cars.com</Target:url>
<Alt:ernat:eText:>Your new car?</Alt:ernat:eText:>
<Impressions>80</Impressions>
</Ad>
<Ad>
<Imageurl>car.gif</Imageurl>
<Target:url>ht:t:p://www.carsupersit:e.com</Target:url>
<Alt:ernat:eText:>Like t:his car?</Alt:ernat:eText:>
<Impressions>80</Impressions>
</Ad>
</Advert:isement:s>

767
'

32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

Jelen esetben két képfájlt adtunk meg ( car.gif és slugbug.jpg) , ezért biztosí­
tanunk kell, hogy ezek a fájlok a webhely gyökerében legyenek (a fájlok a
könyvhöz tartozó letölthető forráskód mellett találhatóak.) Ahhoz, hogy a fáj­
lokat az aktuális projekthez adjuk, válasszuk a Web Site> Add Existing Item
menüpontot. Ekkor társítha�uk az XML-fájlunkat az ActRotator vezérlőele­
mekkel az AdvertisementFile tulajdonság révén (a Properties ablakban):

<asp:AdRotator ID="myAdRotator" runat="server"


AdvertisementFile="-/Ads.xml"/>

Később, amikor futás közben az alkalmazás visszaküld az oldalra, a két kép­


fájl egyike véletlenszerűen meg fog jelenni. A 32.10. ábra a mesteroldalon lát­
ható felhasználói felület tervezésének utolsó lépéseit muta�a.

� �eb.sitemao/' �
Mast�·erl'�age
�.mast��er:ht�����- ��
Start p�l========== �

�oCar sRUs�

� ;
x ,

l, asti":Menu#Menul
JW�ome! •IJJ
·1
'
Sit��o��e- SiteMapDataSour�el � _

�ll
l
l Q Design l o Split l 8 Source l G]l<html>lJ<body>ll<form#fonn2>ll<div>ll<asp:Menu#Menul> l
32.1 O. ábra: Amesteroldal végső megjelenése

A Default.aspx tartalomlap definiálása

Miután létrehoztuk a mesteroldalt, elkezdhe�ük az egyedi *.aspx fájlok terve­


zését, amelyek a felhasználói felület tartalmát a mesteroldal <asp: content­
PlaceHolder> tagjében egyesítik. Az új webhely létrehozásakor a Visual Studio

2008 automatikusan létrehozott egy kezdeti *.aspx fájlt, de az a mostani állapo­


tában nem fésülhető össze a mesteroldallal.
Ennek az az oka, hogy a* .master fájl definiálja a végső HTML-oldal <form>
részét. Ezért az *.aspx fájl meglévő <form> tartományát egy <asp: content> ha­
tókörrel kell helyettesítenünk. Ahelyett, hogy manuálisan módosítanánk a
kezdeti *.aspx fájl markupját, inkább töröljük a oefault.aspx fájlt a projektbőL

768
Sokoldalú ASP.NET-webhely készítése

Ha szeretnénk automatikusan beszúmi egy új tartalomlapot, egyszerűen csak


kattintsunk a jobb egérgombbal a *. master fájl tervezőfelületére, és válasszuk

az Add Content Page menüpontot. Ezzel az alábbi markappal ellátott, új *. aspx

fájlt generáltunk:

<%@ Page Language="C#" MasterPageFile="-/MasterPage.master"


AutoEventwireup="true" CodeFile="Default.aspx.cs"
rnherits="_Default" Title="Untitled Page" %>
<asp:content ID="Contentl"
ContentPlaceHolderiD="head" Runat="Server">
</asp:content>
<asp:Content ID="Content2"
contentPlaceHolderiD="ContentPlaceHolderl" Runat="Server">
</asp:Content>

Először is a <9'o@Page:V<» direktíva egy új MasterPageFile attribútummal egészült


ki, amely a *. master fájlhoz rendelődik hozzá. A <form> elem helyett egy Gelen­

leg üres) <asp:content> hatókörünk van, amely a contentPlaceHol deriD értékét a


mesteroldal <asp:contentPlaceHolder> tagjének az azonosítóját adja.

� ".:YDefault..spxi;'AdsJ!mi!'W���Si.liffiiQC] • x

Client Objects & Events (No Ev�nts)

�i�

:(,@ Paqe Languaqe="Cf" MasterPaQeFile="-/MasterPage.master"


1C
2l
sj
�i

<asp: Content

</a�p:Content>
<a���
IIF="Contentl" ContentPlaceHolderiD="head" Run at="s

Content ID="Content2" Conteot Pla c eSolder iD="ContentPlaceHo1�


tl
Aut:.c-J
A

:
;l
i
.

7 Welcome co our s�te.&r.b


. sp; Here you can purchase a new car or :
e car.

9 </p>
10 </asp:Content>

lll
!2 l

lll
. [. .... :J
_ Ma���age.m_� �

Welcome to Cars R Us�


Welcome! �

�-Sit<MapDetaSourW l
ContentPtaceHolaerJ.tt... usrormllr-\
• - �-
Dl
. •

1\'Telcome to our site. Here you can purcbase a new car or build your drearn car. L?J
�: pa....,t!!ocle: Current Node
u

·011
'<
Q Design l o Split l @l Sourc� \ 8] l<asp:Content#Content2 j� >

32. 11. ábra: Az első tartalomlap létrehozása

769
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Ezek Iniatt a társítások Iniatt tudja a tartalomlap, hova csatolja a tartalmát,


amíg a mesteroldal tartalma csak olvasható formában jelenik meg a tartalom­
lapon. Nincs szükség arra, hogy bonyolult felhasználói felületet készítsünk a
Default. aspx számára, így jelen példánkban csak valarnilyen literális szöve­
get adjunk hozzá, amely némi alapadattal szolgál a webhelyről, a 32.11. áb­
rán látható módon (a tartalomlap jobb felső részén egy hivatkozás található a
kapcsolódó mesteroldalra).
Ha lefutta�uk az alkalmazást, látható, hogy a *. master és a Default. aspx
fájlok felhasználóifelület-tartalmát egyetlen HTML-folyamba egyesítettük
Ahogy a 32.12. ábra muta�a, a végfelhasználó nem tudja, hogy a mesteroldal
egyáltalán létezik. Ha frissí�ük az oldalt (az F5-ös billentyű megnyomásával),
az AdRotator véletlenszerűen megjeleníti a két kép egyikét.

if Untitled Page -Windows Inter net Explorer

Welcome to C ars R Us
Want a RED slug bug?
Come to CarSuperSite.com

Welcome! �

Welcome to our site. Here you can purchase a new car or build your dream car.

Welcome! : Home

Done local intranet l Proleeted Mode: Off

32.12. ábra: Futás közben a mesteroldal és a tartalomlapok egt;ütt jelennek meg

Megjegyzés Egy oldal mesteroldalát programozottan egy Page osztályból származtatott típus
Preini t eseményén belül rendelhetjük hozzá az oldalhoz a Master tulajdonság segítségéveL

Az lnventory tartalomlap tervezése

Ahhoz, hogy az Inventory. aspx tartalomlapot beszúrjuk az aktuális projekt­


be, nyissuk meg az IDE*.master lapját, válasszuk a Web Site> Add Content
Page lehetőséget (ha nem* .master fájl az aktív elem a tervezőben, ez a me-

770
Sokoldalú ASP. NET-webhely készítése

nüpont nem jelenik meg), és nevezzük át a fájlt Inventory . aspx-re. Az Inven­


tory tartalomlap szerepe az, hogy egy GridView vezérlőelemben megjelenít­
se az AutaLot adatbázis Inventory táblájának a tartalmát
Habár ez a vezérlőelem sok tekintetben úgy viselkedik, mint elődje, az
ASP.NET l.x DataGrid, a GridView belső támogatással rendelkezik az ASP.NET
legújabb adatkötési modulját illetően. Az új modell alatt már lehetőség van
kapcsolatsztring-adatokat, illetve select, Insert, Update és Delete SQL-utasításo­
kat (vagy esetleg tárolt eljárásokat) a markupban megadni. Ezért ahelyett, hogy
manuálisan programoznánk az összes szükséges ADO.NET-forráskódot, hasz­
náljuk az új sqlDatasource típust. A vizuális tervezők alkalmazásával deklaratív
módon hozha�uk létre a szükséges markupot, majd rendelhe�ük hozzá a Grid­
View Datasau r ceiD tulajdonságát a sql Datasaurce komponenshez.

Megjegyzés Neve ellenére az sqloatasource kanfigurálható úgy, hogy képes legyen bár­
mely ADO.NET-adatszolgáltatóval (ODBC, Oracle stb.) kommunikálni, amelyet a Microsoft .NET
platform tartalmaz; nem korlátozódik a Microsoft SQL Serverre. A kívánt DBMS-t a Provider
tulajdonság segítségével állíthatjuk be.

Néhány kattintással beállítha�uk hogy a GridView a mögöttes adattároló re­


kordjait automatikusan lekérdezze, módosítsa és törölje. Ez a kódolásmentes
szemlélet jócskán lecsökkenti a sablonkóclak mennyiségét, ez az egyszerűség
azonban a testreszabhatóság csökkenésével is jár, és talán nem a legmegfele­
lőbb egy vállalati szintű alkalmazás számára. Ez a modell kis forgalmú olda­
lak, webhelyprototípusok vagy kisebb, házon belüli alkalmazások számára
lehet jó megoldás.
A GridView (és az új adatkötési modul) deklaratív használatának szemlél­
tetéséhez módosítsuk az Inventory . aspx tartalomlapot egy leíró címkével.
Majd nyissuk meg a Server Explorert (a View menün keresztül), és bizonyo­
sodjunk meg róla, hogy létrehoztuk az adatkapcsolatot az ADO.NET vizsgá­
lata közben létrehozott AutaLot adatbázishoz (az adatkapcsolatok hozzáadá­
sát a 22. fejezetben tekinthe�ük át). Fogjuk meg az Inventory ikont, és húz­
zuk az Inventory. aspx fájl tartalomlapjára. Ha ezzel megvagyunk, az IDE a
következő lépéseket haj�a végre:

l. A web. con fi g fájlt egy új <con necti onstri n gs> elemmel egészíti ki.

2. Az sqlDatasource számára konfigurálja a szükséges select, Insert, up­


date és Delet e utasításokat.

3. A GridView DatasourceiD tulajdonságának beállí�a az új Sqloata­


sou r ce azonosítóját.

771
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Megjegyzés Másik megoldásként beállíthatjuk, hogy a GridView az inline szerkesztőt hasz­


nálja. A legördülő Choose Data Source listában válasszuk a New Data Source lehetőséget. Ezzel
egy varázslót indítunk el, amely néhány tépésen keresztül összekapcsolja az összetevőt a kí­
vánt adatforrássaL

Ha megvizsgáljuk a GridView vezérlőelem nyitó tagjét, látható, hogy a Data­


saure eiD tulajdonság a definíciónak megfelelően az sql Datasour ce azonosító­

ját vette fel:

<asp:Gridview ID="Gridviewl" runat="server"


AutoGenerateColumns="False"
CellPadding="4" DataKeyNames="CariD"
DatasourceiD="SqlDatasourcel"
Forecolor="#333333" GridLines="None">

</asp:Gridview>

A lényeg az SqlDataSource típus leírásában található. A markupban a követ­


kező részben láthaljuk, hogy a típus felvette a szükséges SQL-utasításokat
(egyszerű paraméterezett lekérdezésekként) azAutoLot adatbázis Inventory
táblájával folytatott interakcióhoz. Emellett, az összetevő automatikusan ki­
olvassa a web.confi g <connectionstring> értékét a Connectionstring tulaj­
donság $ szintaxisa révén:

<asp:sqlDatasource ID="SqlDatasourcel" runat="server"


connectionstring=
"<%$ connectionstrings:carsconnectionstringl %>"
DeleteCommand="DELETE FROM [Inventory] WHERE [CariD] = @CarlO"
InsertCommand="INSERT INTO [Inventory] ([CariD], [Make], [Color],
[PetName]) VALUES (@CariD, @Make, @Color, @PetName)"
ProviderName=
"<%$ ConnectionStrings:CarsconnectionStringl.ProviderName %>"
sel ectCommand="SELECT [CariD], [Make] , [Color] , [PetName J
FROM [Inventory]"
Updatecommand="UPDATE [Inventory] SET [Make] = @Make,
[Color] =@Color, [PetName] = @PetName
WHERE [CarlO] = @CariD">
<DeleteParameters>
<asp:Parameter Name="CariD" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="Make" Type="String" />
<asp:Parameter Name="Color" Type="String" />
<asp:Parameter Name="PetName" Type="String" />
<asp:Parameter Name="CariD" Type="Int32" />
</UpdateParameters>

772
Sokoldalú ASP. NET-webhely készítése

<InsertParameters>
<asp:Parameter Name="CariD" Type="Int32" />
<asp:Parameter Name="Make" Type="String" />
<asp:Parameter Name="Color" Type="String" />
<asp:Parameter Name="PetName" Type="String" />
</InsertParameters>
</asp:SqlDataSource>

Futtassuk le az alkalmazást, közben kattintsunk a View Inventory menü­


elemre az adatok megtekintéséhez, ahogy azt a 32.13. ábra mutalj a. A si te­
MapPath "kenyérmorzsái" automatikusan frissültek

Want a Blue SLUG BUG?


Come to Cars.com!

Welcome! �

'CarlO �lake Color Pet:"ame

BM\V Pink Sid

2 VVl Red Zippy

3 Ford Black Me!

4 BMV/ Silver Henry

5 Yugo Pink SaDy

6 Saab Blue Sven

7 BMW Black Bimmer

8 VW Tan Sal

lO MB Silver MB

ll Ford Green Zippy

222 Ford Blue Yodo

999 Yugo Pink Pinky

Welcom e! : View Inventory

"'

� local intranet 1 Protected Mode: Off Q\100% •

32. 13. ábra: Az SqlDataSource "kódolásmentes" modellje

773
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Rendezés és lapozás engedélyezése

A GridView vezérlőelem egyszerűen kanfigurálható rendezésre (oszlopnév­


hivatkozásokkal) és lapozásra (numerikus vagy következőj előző hivatkozá­
sokkal). Ehhez használjuk az inline szerkesztőt, és ellenőrizzük a megfelelő
beállításokat, ahogy azt a 32.14. ábra mutatja.


• x

.. HasterPage.m

rt
Welcome toCars R UseP
Wdcome! •

Edit Columns...

Add New Column...

Move Column left

Move Column Right

Remove Column

l
IQ Design l O Split l @l Source l EJI<asp:Content#Content2>ll<asp:GridView#GridViowl>l

32.14. ábra: Rendezés és lapozás engedélyezése

Ha újra lefuttatjuk az oldalt, lehetőségünk nyílik az adatok rendezésére, az


oszlopok nevére kattintva, és lapozására a lapozóhivatkozások segítségével
(ha az Inventory tábla elég rekordot tartalmaz).

Helyben történő szerkesztés engedélyezése

Az utolsó lépés a GridView vezérlőelem helyben történő szerkesztésének az


engedélyezése. Mivel az sqloatasource már tartalmazza a szükséges törlési
és módosítási logikát, elegendő, ha a GridView Enable Deleting és az Enable

774
Sokoldalú ASP.NET·webhely készítése

Editing jelölőnégyzeteit bejelöljük (a 32.14. ábra alapján). Amikor visszaté­


rünk az rnventory. aspx lapra, lehetőségünk lesz rekordokat szerkeszteni és
törölni, ahogy azt a 32.15. ábra muta�a, valamint módosítani az AutaLot
adatbázis mögöttes Inventory tábláját.

� Untitled Pag< • Windows lntomot Elcpi<>fO' eJ diD

·----�J
Tools •

Want a RED slug bug?


Come to CarSuperSite.com
Welcome to Cars R Us
\Velcome!,.

CariD :.\lake Color Pet::'\ame l


-·-------

�Cancell BMW Yello-ni Sid

Edit 2 vw Red Zippy

Ford Black Me!

4 BMW Silver Hom)·

5 Yugo Pink SaDy

6 Saab Blue SYen

BMW Black Bimmer

V\V Tan Sal

10 MB Si!Yer ;vm

Welcome! : View Inventory

Done � local intrand l Proterted Mode: Off fl.lOO% •

32. 15. ábra: Szerkesztés és törlés

Megjegyzés A GridView helybeni szerkesztésének engedélyezéséhez egy elsődleges kulcsot


kell az adatbázistáblához rendelni. Ha nem tudjuk engedélyezni a fenti opciókat, előfordulhat,
hogy elfelejtjük a carm tulajdonságot az AutaLot adatbázis lnventory táblájának elsődleges
kulcsaként beállítani.

A Build-a-Car tartalomlap tervezése

A példa utolsó lépése a Bui l dcar. aspx tartalomlap megtervezése. Szúrjuk be ezt
a fájlt az aktuális projektbe (a Web Site> Add Content Page menüponton keresz­
tül). Ez az új oldal az ASP.NET Wizard webes vezérlőeleme révén egyszeruen
végigkíséri a felhasználót egy sor kapcsolódó lépésen. A szóban forgó lépések je­
len esetben egy beszerzendő autó összeszerelési folyamatát szemléltetik.

775
32. fejezet: ASP.NET-vezérlöelemek, -témák és ·mesteroldalak

Helyezzünk egy leíró címkét és egy Wizard vezérlőelemet a tartalomtar­


tományba. Ezután aktiváljuk az inline szerkesztőt a Wizard vezérlőelemre, és
kattintsunk az AddjRemave WizardSteps hivatkozásra. Végezzük el mind a
négy lépést úgy, ahogy a 32.16. ábra muta�a.

WizardStep Collection Editor lll�


Members: Pick Vour Model properties:

o ..
..!. PickYourColor G .�::�!j_
--- --

Appear;once
..;. Name Your Car G
El
Title Pkk Your Model
,.l Delivery Oate
El Bet.av;or
AllowReturn True
EnableTheming True

EnableViewState True

StepType Auto
.EJ Misc

(ID)

l Add
·ll Remove
l
l OK
ll Cancel
l
32.16. ábra: A varázsló konfigurálása

A lépések meghatározása után azt lá�uk, hogy a Wizard egy üres tartalom­
tartományt definiál, amelyre ráhúzha�uk az aktuálisan kijelölt lépés vezérlő­
elemeit. Jelen esetben minden lépéshez adjuk hozzá a következő felhaszná­
lóifelület-elemet (ügyeljünk rá, hogy minden elemhez csökkenő azonosító ér­
téket adjunk a Properties ablak használatával):

• modell kiválasztása: TextBox,

• szín kiválasztása: ListBox,

• autó elnevezése: TextBox,

• szállítás dátuma: Calendar.

A ListBox vezérlőelem a Wizard egyetlen olyan felhasználóifelület-eleme,


amely további lépéseket igényel. Jelöljük ki ezt az elemet a tervezőben (bizo­
nyosodjunk meg róla, hogy előtte kijelöltük a Pick Your Color hivatkozást),
és töltsük fel színkészlettel a Properties ablak Items tulajdonsága révén. Ez­
után az alábbi markupot látha�uk a Wizard definíció hatókörében:

776
Sokoldalú ASP.NET·webhely készítése

<asp:ListBox ID="ListBoxcolors" runat="server" width="237px">


<asp:Listrtem>Purple<lasp:Listrtem>
<asp:Listrtem>Green<lasp:Listltem>
<asp:Listltem>Red<lasp:Listrtem>
<asp:Listrtem>Yellow<lasp:Listltem>
<asp:Listrtem>Pea soup Green<lasp:Listrtem>
<asp:Listrtem>Black<lasp:Listrtem>
<asp:Listrtem>Lime Green<lasp:Listrtem>
<lasp:LiStBOX>

Az összes lépést definiáltuk, így kezeljük a FinishButtonclick eseményt az


automatikusan generált Finish gombra. A kiszolgálóoldali eseménykezelő­
ben kérdezzük le a vezérlőelemekben kiválasztott értékeket, és készítsünk
egy leírósztringet, amelyet az lblorder nevű újabb címke típus Text tulajdon­
ságához rendelünk hozzá:

public partial class Default2 : System.web.ur.Page


{
protected void Page_Load(object sender, EventArgs e)
{
}

protected void carwizard_FinishButtonclick(object sender,


system.web.ur.w�bcontrols.wizardNavigationEventArgs e)
{
ll Kérdezzük le az összes értéket.
string order = string.Format("{O}, your {l} {2} will arrive
on {3}.", txtcarPetName.Text,
ListBoxcolors.selectedvalue, txtcarModel.Text,
carcalendar.SelectedDate.ToShortDatestring());

ll Adjuk hozzá a címkéhez.


lblorder.Text = order;
}
}

Az AspNetCarsSite webalkalmazás elkészült. A 32.17. ábra a Wizard műkö­


dését muta�a be.
Ezzel befejeztük az ASP.NET különféle, felhasználóifelület-központú we­
bes vezérlőelemeinek vizsgálatát. A következőkben az ellenőrző vezérlőele­
meket vizsgáljuk meg.

Forráskód Az AspNetCarsSite kódfájlokat a forráskódkönyvtár 32. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

777
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

� Untrtl� P;,ge - Windows lnt�t Explorer

Wanta RED slug bug?


Come to CarSuperSite.com
Welcome toCars R Us

\\"ekomel >

Use this Wizard to build


your Drearn Car

.s August 2007 -;l


Sun Mon Tue Wed Thu Fri Sat 1
2.2 N ll l � J 1
,2 2 z l! 2 lQ ll
ll ll ll 12 lZ ll!
l2 2Q 2.1 ll n � z.:;
ll! u 2!! 2.2 N ;u l
l 1 1 :; 2 z l!

Pr�voOut; Fon:sh

Mel, your Pea Soup Green BMW will arrive on 8/14/2007.


Welcome! : Build a car

localmtranet l Prot�ct�d Mode: Off �100% •

32.17. ábra: A Wizard céleszköz nníködés közben

Az ellenőrző vezérlőelemek szerepe

A következő Web Form vezérlőelem-csoport gyűjtőneve: ellenőrző vezérlőele­


mek. Az eddig vizsgált Web Form-vezérlőelemektől eltérően, az ellenőrzőket
nem a megjelenést szolgáló HTML-tagek beszúrására használjuk, hanem kli­
ensoldali JavaScript (és esetleges kapcsolódó szerveroldali kód) űrlapellenőr­
zésre. Az kliensoldali űrlapellenőrzés meglehetősen hasznosnak bizonyul,
hiszen ezzel különböző korlátokat helyezhetünk el még a webkiszolgálóhoz
való visszaküldés előtt, költséges üzenetváltásokat kerülve el ezzel. A 32.3.
táblázat az ASP.NET ellenőrző vezérlőelemeinek áttekintését tartalmazza.

Comparevalidator Ellenőrzi, hogy egy bemeneti vezérlőelem értéke


egyenlő-e egy másik bemeneti vezérlőelem vagy
egy konstans értékéveL

778
Az ellenőrzö vezérlőelemek szerepe

customval idator Egyedi ellenőrző függvények készítését teszi lehe­


tővé vezérlőelemek ellenőrzésére.

Rangevalidator Meghatározza, hogy egy adott érték része-e egy


előre megadott tartománynak.

Regul arExpression va l idator Ellenőrzi, hogy a hozzárendelt bemeneti vezérlőelem


értéke kielégíti-e a megadott reguláris kifejezést.

Requi red Fi el dval i dator Biztosítja, hogy egy adott bemeneti vezérlőelem tar­
talmazzon értéket (azaz ne legyen üres).

validationsummary Egy lap ellenőrzési hibáit összesíti listázva, felsoro­


lással vagy egyszerű bekezdésformátumban. A hi­
bák inline módon és/vagy előugró üzenetdoboz­
ban jeleníthetők meg.

32. 3. táblázat: Az ASP.NET ellenőrző vezérlőelemei

Végül is az összes ellenőrző vezérlőelem a system. web. ur. webcontrols. sase­

validator nevű, közös ősosztályból származik, így közös tulajdonságkészlet­


tel rendelkeznek. A 32.4. táblázat a főbb tagokat tartalmazza.

TaJ Jelentés

ControlTaValidate Lekérdezi vagy beállítja az ellenőrizendő bemeneti vezér­


lőelemet

Display Lekérdezi vagy beállítja az ellenőrző vezérlőelem hiba­


üzenetének megjelenítési viselkedését.

Enableclientscript Lekérdezi vagy beállítja, hogy engedélyeztük-e az ügy­


féloldali ellenőrzést.

ErrorMessage Lekérdezi vagy beállítja a hibaüzenet szövegének értékét.

Forecolor Lekérdezi vagy beállítja a sikertelen ellenőrzéskor megje­


lenő üzenet színének értékét

32 .4. táblázat: Az ASP.NET ellenőrző vezérlőelemeinek közös tulajdonságai

Az ellenőrző vezérlőelemek használatának bemutatásához, hozzunk létre egy


új, ValidationCtrls nevű webhelyprojektet Először helyezzünk el négy (megfe­
lelően elnevezett) szövegdobozt (négy megegyező nevű címkével) a lapon.

779
32. fejezet: ASP.NET·vezérlöelemek, -témák és -mesteroldalak

Ezután helyezzünk el egy RequiredFieldvalidatort, egy Rangevalidatort,


egy RegularExpressionvalidatort és egy comparevalidatort a nnegfelelő nne­
zők nnellé. Végül adjunk hozzá egy gonnbot és egy cín1két (lásd 32.18. ábra).

, Defau

l Fun with ASP.NET Validators


Reqaired Field:
)Please enter your name Oops! Need to enter data.

Please enter value between O and 100.

Please enter a valid US SSN.

Enter a value Iess than 20.

32.18. ábra: Különböző ellenőrző vezérlőelemek

Ha nnár van egy felhasználói felületünk, nézzük végig az egyes tagok konfi­
gurálásának folyannatát

A RequiredFieldValidator vezérlőelem

A RequiredFieldValidator konfigurálása egyértelnnű. Állítsuk be az Error­


Message és a controlTavalidate tulajdonságokat a Visual Studio 2008 Proper­
ties ablakának használatávaL Az erednnény, hogy a txtRequi red Field szö­
vegdoboz nenn üres:

<asp:RequiredFieldvalidator ID="RequiredFieldvalidatorl"
runat="server" controlTovalidate="txtRequiredField"
ErrorMessage="Oops! Need to enter data.">
</asp:RequiredFieldvalidator>

780
Az ellenőrzö vezérlőelemek szerepe

A RequiredFieldValidator támogatja az Ini tialvalue tulajdonságot. Ezzel a


tulajdonsággal biztosíthatjuk, hogy a felhasználó által beírt értékek külön­
bözzenek a kapcsolódó szövegdoboz kezdőértékétőL Konfigurálhatunk pél­
dául egy szövegdobozt, amely a "Kérem adja meg a nevét" értéket tartal­
mazza arra az esetre, amikor a felhasználó először küld adatot az oldalra. Ha
nem állítottuk be a RequiredFieldValidator Initialvalue tulajdonságát, a fut­
tatókörnyezet azt feltételezi, hogy a "Kérem adja meg a nevét" sztring érvé­
nyes. Így annak biztosítására, hogy a kért szövegdoboz csak akkor legyen ér­
vényes, ha a felhasználó a "Kérem adja meg a nevét" szövegtől eltérő adatot
visz be, a következőképpen állítsuk be a vezérlőelemet:

<asp:RequiredFieldvalidator ID="RequiredFieldvalidatorl"
runat="server" controlToValidate="txtRequiredField"
ErrorMessage="Oops! Need to enter data."
Initialvalue="Please enter your name">
</asp:RequiredFieldvalidator>

A RegularExpressionValidator vezérlőelem

A RegularExpressionValidatort akkor használjuk, ha mintailletszést szeretnénk


alkalmazni az adott bemeneti mezőbe bevitt karakterekre. Ha azt szeretnénk,
hogy az adott szövegdoboz csak érvényes amerikai társadalombiztosítási azono­
sítót tartalmazzon, az alábbiak szerint állíthatjuk be a vezérlőelemet

<asp:RegularExpressionvalidator ID="RegularExpressionvalidatorl"
runat="server" ControlTovalidate="txtRegExp"
ErrorMessage="Please enter a valid us SSN."
validationExpression="\d{3}-\d{2}-\d{4}">
</asp:RegularExpressionvalidator>

Látható, hogyan definiálja a RegularExpressionValidator a validationExpression


tulajdonságot. A reguláris kifejezéseket egy adott sztringmintával való összeve­
tésre használjuk. Jelen esetben a "\d{3}-\d{2}-\d{4}" kifejezés egy xxx-xx-xxxx
formátumú, szabványos amerikai társadalombiztosítási azonosítót takar (ahol az
x bármilyen szám lehet).
Ez a reguláris kifejezés meglehetősen egyértelmű, ám tételezzük fel, hogy
egy érvényes japán telefonszámot szeretnénk tesztelni. A pontos kifejezés jó­
val bonyolultabb lett: "(O\d{l,4}-l\CO\d{l,4}\)?)?\d{l,4}-\d{4}". Amikor a
Properties ablakban kijelöljük a ValidationExpression tulajdonságot, egy előre
megadott reguláriskifejezés-készletből válogathatunk, a " ... " feliratú gombra
kattintva.

781
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Megjegyzés A . NET platform két olyan névteret tartalmaz (System. Text. RegularExpressi ons és
system.web. Regul arExpressions), amely az ilyen minták programozott kezelésével foglalkozik.

A RangeValidater vezérlőelem

A Mini mumvalue és a Maximumval ue tulajdonságokon kívül a RangeValidator

vezérlőelem egy Type nevű tulajdonsággal is rendelkezik. Mivel a felhaszná­


lók által megadott bemeneteket szeretnénk ellenőrizni az egész számok hal­
mazán, az Integer típust kell meghatároznunk (nem ez az alapértelmezett):

<asp:Rangevalidator ID="Rangevalidatorl"
runat="server" controlTovalidate="txtRange"
ErrorMessage="Please enter value between O and 100."
Maximumvalue="lOO" Minimumvalue="O" Type="Integer">
</asp:Rangevalidator>

A RangeValidator vezérlőelemmel azt is tesztelhetjük, hogy egy adott érték va­

luta, dátum, lebegőpontos szám vagy sztringadat-e (alapértelmezett beállítás).

A CompareValidator vezérlőelem

Végezetül figyeljük meg, hogy a CompareValidator egy operator tulajdonsá­


got támogat:

<asp:Comparevalidator ID="Comparevalidatorl" runat="server"


controlTovalidate="txtcomparison"
ErrorMessage="Enter a value less than 20." operator="LessThan"
valueTocom pare="20">
</asp:Comparevalidator>

Minthogy ennek az ellenőrző vezérlőelemnek az a szerepe, hogy a szövegdo­


bozban lévő értéket egy kétoperandusú operátor segítségével egy másikkal
összehasonlítsa, nem meglepő, hogy az Operator tulajdonság értéke LessThan,
GreaterThan, Equal és NotEqua l. A valueTocompare az összehasonlítás alapjául
szolgáló érték megadására használatos.

Megjegyzés A CompareValidator vezérlőelemet úgy is konfigurálhatjuk, hogy egy másik Web


Form vezérlőelem értékét hasonlítsa össze (inkább, mint egy bedrótozott értéket) a contro l To ­

val i date tulajdonság révén.

782
Az ellenőrzö vezérlőelemek szerepe

Az oldal kódjának befejezéseként, kezeljük a gomb click eseményét, és tu­


dassuk a felhasználóval, hogy az ellenőrzési folyamat sikeresen lezárult:

public partial class _Default : System.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
}

protected void btnPostback_click(object sender, EventArgs e)


{
lblvalidationcomplete.Text = "You passed validation!";
}
}

Ezt követően navigáljunk erre az oldalra a kívánt böngészővel. Egyelőre


semmi figyelemre méltó változást nem látunk. Ha azonban fiktív adatok be­
vitele után megpróbálunk rákattintani a Submit gombra, hirtelen megjelenik
a hibaüzenet. Ha érvényes adatot viszünk be, a hibaüzenetek eltűnnek, és
megtörténik a visszaküldés.
Ha megnézzük a böngésző által megjelenített HTML-t, azt lá�uk, hogy az
ellenőrző vezérlőelemek olyan ügyféloldali JavaScript függvényt generálnak,
amely egy automatikusan letöltendő JavaScript függvénykönyvtárat használ
(ami a weburvalidation.js fájlban található). Ha az ellenőrzés sikeresen befe­
jeződött, az űrlapadatok visszajutnak a kiszolgálóhoz, ahol az ASP.NET­
futtatókömyezet ugyanazokat az ellenőrzéseket fogja végrehajtani a webki­
szolgálón (csak azért, hogy kizárja a hamisítás lehetőségét a felküldés során).
Ha olyan böngészőből érkezik a HTTP-kérés, amely nem támoga�a az
ügyféloldali JavaScript-kódokat, az összes ellenőrzés a kiszolgálón fog meg­
történni. Ekkor úgy programozha�uk az ellenőrző vezérlőelemeket, hogy fi­
gyelmen kívül hagyjuk a böngészőt, és a visszaküldött HTML-oldal a webki­
szolgálóhoz irányí�a vissza a hibafeldolgozást

Ellenőrzés összegzésének létrehozása

A következő ellenőrzéssei kapcsolatos témánk a ValidationSummary haszná­


lata. Jelenleg az összes ellenőrző vezérlőelemünk pontosan ott jeleníti meg a
hibaüzenetét, ahol azt a tervezéskor elhelyeztük Sokszor ez éppen megfelelő.
Valószínűleg azonban nem szeretnénk véletlenszerűen felugró, vörös szöve­
geket látni egy olyan összetett űrlapon, amelyen számos bemeneti elem talál­
ható. A ValidationSummary típus segítségével utasítha�uk az ellenőrzőket,
hogy az oldal meghatározott pon�ain jelenítsék meg a hibaüzeneteiket

783
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Először helyezzünk el egy ValidationSummary típust az * .aspx fájlba.


Opcionálisan beállíthatjuk a típus HeaderText és oisplayMode tulajdonságait,
ez utóbbi egy felsorolásban jeleníti meg az összes hibaüzenetet az alapértel­
mezés szerint.

<asp:validat:ionsummary id="Validat:ionsummaryl"
runat:="server" Widt:h="353px"
HeaderText:="Here are t:he t:hings you must: correct:. ">
</asp:validat:ionsummary>

Ezután az oldalon lévő összes ellenőrző vezérlőelem o isplay tulajdonságá­


nak (pl. Required Fiel dval idat:or, Rangeval idat:or) adjunk None értéket. Ezzel
biztosíthatjuk, hogy egy adott sikertelen ellenőrzés hibaüzenete ne jelenjen
meg kétszer (egyik üzenet az összegző panelen és egy másik az ellenőrző ve­
zérlőelem helyén). A 32.19. ábra az összegző ablaktáblát mutatja be működés
közben.

� Untitled P�- Windows Intemet Explorer l l @) liiiliiti


=

QQ • Ji] �://locolhost53i �J�_L�_I [_G�t:____ ____ __

<(j <:SI
-
r jJ Untitled Page n�·�·�· - "

111-11-1111 �

Valae<20
2


l Post back l
Here. are the things you must correct.

• Oops! Need to enter data.


• Please enter value between O and l 00.

'\ local intranet l Proterted Mode: Off �100% .


32.19. ábra: Ellenőrzés összegzése

Végül, de nem utolsósorban, ha azt szeretnénk, hogy a hibaüzenetek ügyfél­


oldali MessageBox révén jelenjenek meg, a ShowMessageBox tulajdonság ér­
tékét állítsuk igazra, a ShowSummary tulajdonságét pedig harnisra.

784
Az ellenőrzö vezérlőelemek szerepe

Ellenőrzési csoportok definiálása

Az ellenőrző vezérlőelemek számára csoportokat is definiálhatunk. Ez akkor


lehet hasznos, amikor az oldal bizonyos tartományai együttműködésben áll­
nak. Lehet például egy olyan vezérlőelem-csoportunk egy panelben, amely
azt teszi lehetővé, hogy a felhasználó megadja a postacímét, egy másik panel
pedig hitelkártya-információkat gyűjtő felhasználóifelület-elemeket tartal­
maz. A csoportok használatával úgy konfigurálhatjuk az egyes vezérlőelem­
csoportokat, hogy azok ellenőrzése egymástól független legyen.
Szúrjunk be egy új lapot va l i dat i onGroups. aspx névvel az aktuális projek­
tünkbe, amely két panelt definiál. Az első panel egy olyan szövegdobozt igé­
nyel, amely tetszőleges felhasználói adatot tartalmaz (egy RequiredFieldVali­
dator vezérlőelemen keresztül), míg a második egy amerikai társadalombiz­
tosítási azonosító értéket (RegularExpressionValidator vezérlőelemen keresz­
tül) vár. A 32.20. ábra egy lehetséges felhasználói felületet ábrázol.

/\'�roups..ll5pl<"tOefty�t.aopLQt:Oetau�t.ase�l ,., x

l
!
.�:;:d tie;�,! �
,....,

aSD!Piiú�I#Pariel2
���=;SSN j Vali�� l ;

i!.o::�-:o=-::�::.�=:·::::::t:!�.�-==-:-:.�.-::: :�.�:·::.-:.�·=:-=:�.�::·.�::.��"!.�:.'!�=-':"::.��

l Q Design l o Split l a Source l 0l<asp:Panei#Panel2>l G


32. 20. ábra: Ezek a Panel objektumok egymástól függetlenül kanfigurálják a bemeneti tartományaikat

Annak biztosítására, hogy az ellenőrző vezérlőelemek egymástól függetlenül


működjenek, az ellenőrző és az ellenőrzött vezérlőelemeket rendeljük hozzá
egy egyedi elnevezésű csoporthoz a va l i dationGroup tulajdonság segítségé­
vel. Az alábbiakban megadunk néhány lehetséges megoldást (az itt használt
kattintási események kezelői lényegében a kódfájl üres rutinjai, és csak arra
használjuk őket, hogy engedélyezzék a visszaküldést a webkiszolgálóra):

785
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

<form id="forml" runat="server">

<asp:Panel ID="Panell" runat="server" Height="83px" width="296px">


<asp:TextBox ID="txtRequiredData" runat="server"
validationGroup="FirstGroup">
</asp:TextBox>
<asp:RequiredFieldvalidator ID="RequiredFieldvalidatorl"
runat="server" ErrorMessage="*Required field!"
controlTovalidate="txtRequiredData"
validationGroup="FirstGroup">
</asp:RequiredFieldvalidator>
<asp:Button ID="bntvalidateRequired" runat="server"
onclick="bntvalidateRequired_click"
Text="Validate" validationGroup="FirstGroup" />
</asp:Panel>

<asp:Panel ID="Panel2" runat="server" Height="119px"


width="295px">
<asp:TextBox ID="txtSSN" runat="server"
validationGroup="SecondGroup">
</asp:TextBox>
<asp:RegularExpressionvalidator ID="RegularExpressionvalidatorl"
runat="server" controlTovalidate="txtSSN"
ErrorMessage="*Need SSN"
validationExpression="\d{3}-\d{2}-\d{4}"
validationGroup="SecondGroup">
</asp:RegularExpressionvalidator>&nbsp;
<asp:Button ID="btnvalidateSSN" runat="server"
onclick="btnvalidateSSN_Click" Text="Validate"
validationGroup="SecondGroup" />
</asp:Panel>

</form>

Kattintsunk a jobb egérgombbal a lap tervezőjébe, és válasszuk a View In


Browser menüpontot, hogy ellenőrizzük, mindkét panel vezérlőelemei köl­
csönösen, egymást kizáróan működik.

Forráskód A ValidatorCtrls kódfájlokat a forráskódkönyvtár 32. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

786
Témák használata

Témák használata

Minden vezérlőelemhez tartozik egy olyan tulajdonságkészlet (ezek nagy része


a system. web. ur. webcontrol s. webcont rol alaposztályból származik), amelynek
segítségével kialakíthatjuk a felhasználói felületek megjelenését és működését
(háttérszín, betűméret, szegélystílus stb.). Egy többoldalas webhely esetében
persze megszakott dolog, hogy a különféle típusú vezérlők megjelenése és
működése megegyezik. Az összes szövegdoboz például egy adott betűtípust
támogat, az összes gomb elemhez tartozik egy egyedi kép, és az összes naptár
vezérlőelemnek világoskék a szegélye.
Nyilván nagyon sok munkát igényelne (és sok hibalehetőséget is rejtene
magában), ha a webhely összes lapjának vezérlőire ugyanazokat a tulajdon­
ságbeállításokat alkalmaznánk. Még ha képesek is lennénk manuálisan frissí­
teni az összes oldal összes felhasználóifelület-elemének tulajdonságát, na­
gyon fáradságos lenne az összes szövegdoboz háttérszínét szükségszerűen
újra megváltoztatni. Kell lennie egy jobb megoldásnak a teljes webhelyre ki­
tetjedő felhasználói felületi beállításokra.
Az egyik lehetőség a közös felhasználói felületi megjelenés és működés al­

kalmazásának leegyszerűsítésére stz1uslapok definiálása. Ha van webes fejlesztői


tapasztalatunk, tudjuk, hogy a stíluslapok böngészökre alkalmazott, közös
felhasználóifelület-központú beállításkészleteket definiálnak. Az ASP.NET we­
bes vezérlőelemeihez a csssty l e tulajdonsággal rendelhetünk hozzá stílusokat.
Az ASP.NET azonban egy másik technológiát is biztosít számunkra a kö­
zös felhasználói felületek definiálásához, mégpedig a témákat. A stíluslapok­
tól eltérően a témákat a webkiszolgálóra alkalmazzuk (és nem a böngészőre),
programozottan vagy deklaratív módon. Emiatt a témák a webhely összes ki­
szolgálóoldali erőforrásához hozzáférhetnek. Továbbá a témákat ugyanúgy
definiáljuk, ahogyan azt bármelyik * . aspx fájlban megtalálhatjuk (a stílusla­

pok szintaxisa valamivel tömörebb).


Az ASP.NET-webalkalmazások néhány "speciális" alkönyvtárat definiál­
hatnak, ezek egyike az App_Theme (lásd a 31. fejezetet). Ezt az alkönyvtárat
további alkönyvtárakra oszthatjuk, amelyek mindegyike a webhely egy-egy
lehetséges témája. Nézzük meg például a 32.21. ábrát, amelyen egy olyan
App_Theme mappa látható, amely három alkönyvtárat tartalmaz, az al­
könyvtárakban pedig az egy-egy témát felépítő fájlok találhatók.

787
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

Holiday.skin Premadonna.skin SimpleCtrls.skin


Holidaylmages.xml TheSerearn .Iif GridViewData.skin
Snow.tif Companylogo.tif

32.21. ábra: Egt;etlen App_I11eme mappa több témát is definiálhat

A* .skin fájlok
Az egyetlen fájl, amelyet rninden témaalkönyvtár biztosan tartalmaz, a *.ski n
fájl. Ezek a fájlok határozzák meg különféle webes vezérlőelemek megjelenését
és működését. Ennek szemléltetésére hozzunk létre egy új, FunWithThemes
nevű webhelyet Ezután szúrjunk be egy új, BasicGreen.skin nevű *.ski n fájlt (a
Web Site >Add New Item menüpont segítségéve!), a 32.22. ábrán látható módon.

Add New !tom - C:\My Books\C# and u,., .NET Platform 4th ed\Code\Chapter 33\funWilhThomes\ 'i}_.,
Templates:
(§IDG
� LJ e::J � � � LJ �§_;§ � .
W�b Form Mast�Page: WebUser AJAXCiient AJAXClient AJAXCiient AJAX Master AJAXW•b AJAX·enab...
Control Behavior Control library Page Form WCFServlee

<tl
Browser Fill!:

Class

Class
[ffJ
Data$g

Generic
jJ
Global
li]
.
HTML Pag•
i
JScriptFiJe:

UNQ to SQL
Olagram Handfer Applicati... Classes
"

� � � lll .-�--- � [j "il


Report R•port Resource File Site Map ·rt_.Skitl �!� SQL Styl•Sh..t Text File WCFs.Mco
Wizard Oahbase


-

W&>
i1 il
XML Fil•
[ffJo

XMLSch•ma

XSLT Fil•
W�Service:
Configurat...

A file used to define: an ASP.NET thcme:


···-·-

Name BasicGreen.skin

language: lvosuo� C# � �l O Piaci! code in sepafate file


O �lect marter page

l Add
ll Cancol
l
32.22. ábra: A •.skin fájlok beszúrása

788
Témák használata

A Visual Studio 2008 figyelmeztet, hogy hagyjuk jóvá a fájl hozzáadását egy
App_Theme mappához (ez pontosan az, mint amit szeretnénk). Ekkor az
App_Theme mappa valóban tartalmaz egy BasicGreen nevű almappát, és
benne az új BasicGreen.skin fájlt.
A *.skin fájlokban nyílik lehetőség a különféle vezérlőelemek megjelenésé­
nek és működésének meghatározására az ASP.NET-vezérlőelem deklarációs
szintaxisa révén. Az IDE sajnos nem támogatja a *.skin fájlok tervezését. Úgy
csökkenthetjük a gépelési időt, ha beszúrunk egy ideiglenes *.aspx fájlt a prog­
ramunkba (pl. temp.aspx) , amelyben elkészíthetjük a vezérlőelemek felhaszná­
lói felületét a Visual Studio laptervezője segítségéveL
Az eredményül kapott markupot bemásolhatjuk a *.sk in fájlba. Ekkor
azonban el kell távolítanunk az összes webes vezérlőelem azonosítóattribútu­
mát. Erre azért van szükség, mert nem egy adott, például gomb megjelenését
és működését szeretnénk megadni, hanem az összes gombét.
A BasicGreen.skin alapértelmezett megjelenést és működést definiál a Button,
a TextBox és a calendar típusok számára:

<asp:Button runat="server" Backcolor="#80FF80"/>


<asp:TextBox runat="server" Backcolor="#80FF80"/>
<asp:Calendar runat="server" Backcolor="#80FF80"/>

Minden vezérlőelem tartalmazza még a runat="server" attribútumot (ez kö­


telező), és egyikhez sem rendeltünk hozzá azonosítóattribútumot.
Definiáljunk egy másik, CrazyOrange nevű témát. A Solution Explorerben
kattintsunk a jobb gombbal az App_Theme mappára, és adjunk hozzá egy új,
CrazyOrange nevű témát. Ezzel új alkönyvtárat hozunk létre a webhely
App_Theme mappájában. Ezután kattintsunk a jobb gombbal a CrazyOrange
mappára a Solution Explorerben, és válasszuk az Add New Item lehetőséget.
Adjunk hozzá egy új *.skin fájlt a megjelenő párbeszédablakban. Módosítsuk
a CrazyOrange.skin fájlt úgy, hogy egyedi felhasználói felületi megjelenést és
működést definiálunk a fent említett vezérlőelemekre. Például:

<asp:Button runat="server" Backcolor="#FF8000"/>


<asp:TextBox runat="server" Backcolor="#FF8000"/>
<asp:Calendar Backcolor="White" Bordercolor="Black"
sorderstyle="Solid" cellspacing="l"
Font-Names="Verdana" Font-Size="9pt" Forecolor="Black"
Height="250px" NextPrevFormat="ShortMonth"
Width="330px" runat="server">
<SelectedoayStyle Backcolor="#333399" Forecolor="White" />
<Ot:herMonthDaystyle Forecolor="#999999" />
<Todayoaystyle Backcolor="#999999" Forecolor="White" />

789
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

<Daystyle sackcolor="#cccccc" />


<NextPrevstyle Font-Bold="True" Font-size="Bpt"
Forecolor="White" />
<DayHeaderstyle Font-Bold="True" Font-Size="Bpt"
Forecolor="#333333" Height="Bpt" />
<Titlestyle Backcolor="#333399" Borderstyle="Solid"
Font-Bold="True" Font-Size="12pt"
Forecolor="White" Height="12pt" />
</asp:calendar>

A Solution Explorer ekkor a 32.23. ábrán látható képet muta�a.

Solution Explorer

.-..
l ., C:\.-\FooWrthlbemes\
; ; ... � App_Data
�-- � @i,IJ"I§
! $· · � BasicGreen
� j �--�· tJ} BasicGreen.slcin
: EJ···
.. ,·. CrazyOrange
L. f.W' CrazyOrange.skín
d;J..
i@ Default.aspx
L. (!} web.config

��Solution Explorer§C:rass Viewr-


32. 23. ábra: Több téma egyetlen webhelyen

A következő logikus kérdés az, hogy hogyan alkalmazzuk a témákat a lapokra?


Ennek többféle módja van.

Megjegyzés Ezek a témapéldák meglehetösen egyszerűek, ám nyugodtan továbbalakíthatók.

Témák alkalmazása a teljes webhelyre

Ha szeretnénk meggyőződni arról, hogy a webhely összes lapjára ugyanazt a


témát alkalmaztuk, a legegyszerűbb, ha módosí�uk a web. config fájlt. Nyis­
suk meg az aktuális web. con fig fájlt, és keressük meg a <pages> elemet a
<system.web> elem hatókörében. Ha hozzáadunk egy témaattribútumot a
<pages> elemhez, a webhely összes lapjához a kijelölt témát rendeljük hozzá
(amely természetesen az App_Theme egyik alkönyvtárának a neve). A mó­
dosítás a következő:

790
Témák használata

<configuration>
<system.web>

<pages theme="BasicGreen">

</pages>
</system.web>
</configuration>

Ha különböző Button, calendar és TextBox elemeket helyeznénk a oefault.aspx


fájlba, majd futtatnánk az alkalmazást, láthatnánk, hogy az összes elemre a
BasicGreen felhasználói felületet alkalmaztuk. Ha CrazyOrange-ra módosíta­
nánk a témaattribútumot, és újra futtatnánk az alkalmazást, akkor már az ezen
téma által definiált felhasználói felületet látnánk.

Témák alkalmazása oldalanként

A témákat oldalak szintjén is beállíthatjuk Ez a lehetőség bizonyos körülmé­


nyek között rendkívül hasznosnak bizonyulhat. Akkor például, ha a web.
config fájl a teljes webhelyre kiterjedő témát definiál (ahogy az előző részben
leírtuk), mi azonban más témát szeretnénk alkalmazni egy adott oldalra. Ek­
kor egyszerűen módosítsuk a <%@Page%> direktívát. Ha ezt a Visual Studio
2008 használatával tesszük, az IntelliSense az App_Theme mappa összes de­
finiált témáját megjeleníti.

<%@ Page Language="C#" AutoEventwireup="true"


codeFile="Default.aspx.cs" Inherits="_oefault"
Theme ="Crazyorange" %>

Mivel a CrazyOrange témát rendeltük hozzá az oldalhoz, a web.config fájl


viszont a BasicGreen témát adja meg, ennek az oldalnak a kivételével az összes
oldal a BasicGreen témának megfelelően jelenik meg.

A SkiniD tulajdonság

Előfordulhat, hogy a lehetséges felhasználói felületi megjelenések készletét


egyetlen vezérlőelem számára szeretnénk meghatározni. Tételezzük fel pél­
dául, hogy két lehetséges megjelenítést szeretnénk definiálni a Button típus­
nak a CrazyOrange témán belül. Ekkor a skin ID tulajdonsággal tehetünk kü­
lönbséget az egyes megjelenési beállítások között:

791
32. fejezet: ASP.NET-vezérlöelemek, -témák és -mesteroldalak

<asp:Button runat="server" Backcolor="#FF8000"/>


<asp:Button runat="server" skiniD = "BigFontButton"
Font-Size="30pt" Backcolor="#FF8000"/>

Azzal, hogy az oldalunk a CrazyOrange témát használja, az alapértelmezés


szerint az összes Buttonhoz a névtelen Button arculatot rendeljük hozzá. Ha
azt szeretnénk, hogy az *. aspx fájlban a különböző gombok a B i gFontButton

arculatot használják, állítsuk be a ski n ro tulajdonságot a markupban:

<asp:Button ID="Button2" runat="server"


SkiniD="BigFontButton" Text="Button" /><br />

Erre mutat példát a 32.24. ábra, amelyen egy CrazyOrange témát használó
oldal látható. A legfelső gombhoz a névtelen arculatot rendeltük hozzá, míg
az oldal alján található gomb sk i n ID tulajdonsága a B i gFontButton értéket
vette fel.

� Un� Pag� - Windows Int�'""' Explor�r

00 [� httpJ/localho st53S • l +t]�J [Googl•


� Untitled Pag�

Here are the controls which wiD be themed

.:; August 2007 �

su .... Tu We Th Fr Sa
� 2Q .ll l J. d !!
� 2 z § 2 1.Q ll
li ll ll ll 12 !Z .!§ :
12 lQ il ll n Z!! �
2.2 ll � 2Q .ll l
2 1 !! � § z §

Loc al intranot l Proleeted Modt: Off lfl.lOO% •

32.24. ábra: Játék a SkiniD tulajdonsággal

792
Témák használata

Témák beállítása kódból

Végül, de nem utolsósorban, a témákat kódból is beállítha�uk. Ez a megoldás


akkor lehet hasznos, amikor biztosítani szeretnénk a végfelhasználók számára
a témaválasztás lehetőségét az aktuális munkamenetben. Az állapottal rendel­
kező webalkalmazások létrehozását még nem vizsgáltuk, így ebben az esetben
az aktuális témaválasztás eltűnik a visszaküldések között. Egy termékszintű
webhelyen valószínűleg szeretnénk tárolni a felhasználó aktuális témaválasz­
tását egy munkamenet-változóban vagy rögzíteni egy adatbázisban.
A témák programozott beállításának szemléltetésére egészítsük ki a De­

fault.aspx oldalunkat három új gombbal, a 32.25. ábra alapján. Ezután kezel­


jük a gombok kattintási eseményét.

af&lJLTbcmes/úa.. .Crazy(!range.s!ánJ::we�� .SQííf,s>Y O.. ÁuiUSpX"] •X

Fun with Themes

jNo:rh;;;;-II"G;;e�1h;;;;] r--thto;;;;;;n;;,;;-1

Here are the controls which wiD be themed

< � 2007 >

Su Moa Tae Wed Tb Fri Sat


l 29 30 31 l 2 3 4
5 6 7 8 9 10 ll

j Q Design l o Split l @l Sourc� l ��l<form#forml >l� l<asp:Button#btnC>rangeTheme>l [8


32.25. ábra: A módosított felhasználói felület

Csak az oldalak bizonyos életciklusfázisai alatt állíthatunk be témákat kód­


ból. Ezt általában a Page_Prernit esemény kiváltásakor tehe�ük. Ezek után a
következőképpen módosul a kódfájlt

partial class _Default : System.Web.UI.Page


{
protected void btnNoTheme_Click(object sender, System.EventArgs e)
{
ll Az üres sztringek azt eredményezik, hogy nem lesz téma
ll beállítva.
Session["userTheme"] = '"' ;

793
32. fejezet: ASP.NET-vezérlőelemek, ·témák és -mesteroldalak

ll újra kiváltja a Preinit eseményt.


Server.Transfer(Request.FilePath);
}

protected void btnGreenTheme_Click(object sender,


System.EventArgs e)
{
session["userTheme"] = "BasicGreen";

ll újra kiváltja a Preinit eseményt.


Server.Transfer(Request.FilePath);
}

protected void btnorangeTheme_click(object sender,


System.EventArgs e)
{
session["userTheme"] = "crazyorange";

ll újra kiváltja a Preinit eseményt.


server.Transfer(Request.FilePath);
}

protected void Page_Preinit(object sender, System.EventArgs e)


{
try
{
Theme Session["userTheme"] .ToString();
}
catch
{
Theme
1111.

'

}
}
}

A UserTheme nevű kiválasztott témát, amelyet egy munkamenet-változóban


tárolunk (további részletekért lásd a 33. fejezetet), formálisan a Page_Pre­
Init() eseménykezelőben állítottuk be. Amikor a felhasználó egy adott

gombra kattint, programozottan kényszerítjük a Preinit eseményt az indu­


lásra a Server.Transfer() metódus hívásával és az aktuális oldal újbóli leké­
réséveL Ha futtatnánk az oldalt, azt látnánk, hogy különféle gombokra kat­
tintva tudjuk bevezetni a témát.

Forráskód A FunWithThemes kódfájlokat a forráskódkönyvtár 32. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

794
Vezérlőelemek elhelyezése HTML-táblákkal

Vezérlőelemek elhelyezése HTML­


táblákkal

A vezérlőelemek tervezői felületen való elhelyezése nem intuitív. A Win­


dows Forrnsszal ellentétben például az alapértelmezés szerint egy felhaszná­
lóifelület-elemet nem húzhatunk ki az eszköztárból, és nem helyezhetjük pon­
tosan oda, ahova szeretnénk (ez elég zavaró).
Az ASP.NET korábbi verzióiban kétféle üzemmódban lehetett pozicioná­
lást végrehajtani (GridLayout és FlowLayout), amelyet a DOCUMENT page­
Layout attribútumában adhattunk meg. Amikor a GridLayout volt beállítva,

az abszolút pozicionálást lehetett elérni DHTML használatával. Ezzel azon­


ban az ASP.NET l.x weblapok a dinamikus HTML-t támogató böngészőkre
korlátozódtak. A FlowLayout (az ASP.NET aktuális alapértelmezett üzem­
módja) használatakor nem volt elérhető az abszolút pozicionálás, ez pedig
programozáskor elég zavaró lehet. A FlowLayout azonban biztosítja, hogy
minden böngésző megfelelően jeleruthesse meg a webtartalmat
Az ASP.NET tulajdonképpen még most is lehetővé teszi a GridLayout
használatát a vezérlőelemek (manuális) definiálásához. A tervezők azonban
panaszkodnak, hiszen a szükséges infrastruktúra nem érvényes az XHTML­
specifikációban. Nézzük meg például a következő *. aspx fájlt, amely egy

gomb sty l e attribútuma révén biztosítja a gomb abszolút pozicionálását:

<body MS_POSITIONING="GridLayout">
<form id="Form2" method="post" runat="server">

<asp:Button id="Button1" runat="server" Text="Button"


style="Z-INDEX: 101; LEFT: 106px; POSITION: absolute;
TOP: 79px">
</asp:Button>

<asp:TextBox id="TextBox1" runat="server">


</asp:TextBox>
</form>
</body>

A nem XHTML-kompatibilis kódok használata (és annak kockázata, hogy


nem működik minden böngészőben) helyett, sok webfejlesztő HTML­
táblákban helyezi el a vezérlőket. A HTML-tábla a szó szoros értelmében
nem látható a böngészőben, tervezés közben azonban a vezérlőelemek a cel­
lákba helyezhetők, abszolút pozicionálást biztosítva ezzel.

795
32. fejezet: ASP.NET-vezérlőelemek, -témák és -mesteroldalak

A Visual Studio 2008 lehetövé teszi ezeknek a celláknak a vizuális szerkesz­


tését és manipulálását úgy, mint egy Excel-táblázatban. A Tab billentyűvel
például mozoghatunk a cellák között, és, ha több cellát is kijelölünk, egyesít­
he�ük vagy átméretezhe�ük azokat az úszómenün (context menü) keresztül.
Továbbá a cellákat testre szabha�uk különféle stílusokkal a Properties ablak
révén. A példa kedvéért nézzünk meg egy tetszőleges *. aspx oldalon található
HTML-táblázatot a tervezőben, ezt látha�uk a 32.26. ábrán.

'l>mult:uspx·v� �x

" - "" , . . .. .. , . __ ,
.

..

, ... , _ . ·- .. . "'"' ·---··--· . , _.,_ , . , _ . _ , . .. ·- · .. " " " ... , . .


...
.

ít'ábie) ..
'

. _,
·_:.c
- --·

� Cut
·c •
[Q Copy
:""""'
� Paste

Paste Altemate
:
· ····-··
)( Deiete

NowStyle...

Insert •

Delete •

Select •

. � ---------·
--- --

t '-
Modify

l� . �:�;9;;- · c:;.��- -�
..
-
-

� View in Browser liil Split Cells...

Editimage SplitTable

Show Smart Tag Bt Di.tribute Rows Evenly

ttl l Distribute Columns Evenly


IE Refresh
Autofit to Contents
� Properties
T

• •

l Q Design l t:l Split l 8 Source l 81<html>l l<body>ll<form#Fonn2>j [B


32. 26. ábra: A Visual Studio 2008 kitűnő HTML-táblakonfigurációkat biztosít

Miután kanfiguráltuk a tábla celláit (amely általában további beágyazott táb­


lákat is tartalmaz), az ASP.NET webes vezérlőelemeket a kívánt módon ren­
dezhe�ük. Az egésznek az az előnye, hogy amikor a felhasználó átméretezi a
böngészőt, a vezérlőelemek megőrzik a relatív pozíciójukat.

Megjegyzés A könyv példái nem igénylik, hogy HTML-táblák segítségével tervezzünk oldala­
kat, ám nem haszontalan, ha tisztában vagyunk ezek hasznosságával a webes fejlesztésben.

796
Összefoglalás

Összefoglalás

Ebben a fejezetben a különféle ASP.NET webes vezérlőelemek használatával


foglalkoztunk. A control és a webcontrol alaposztályok szerepének vizsgála­
tával kezdtük, és megnéztük, hogyan kell dinamikusan kommunikálni egy
panel belsővezérlőelem-gyűjteményéveL Eközben bemutattuk az új webhely­
navigációs modellt (a*. sitemap fájlokat és a siteMapDatasource összetevőt),
az új adatkötési modult (az Sql Datasou r ce összetevőn és az új Gri dvi ew típu­
son keresztül), illetve különböző ellenőrző vezérlőelemeket
A továbbiakban a mesteroldalak és a témák szerepét tekintettük át. A mes­
teroldalakat egy közös keret definiálására használtuk, a webhely egy lapcso­
portja számára. A* .master fájl bármilyen számú "tartalomhelyőrzőt" definiál­
hat, amelyekhez a tartalomlapok csatolják az egyedi felhasználói felületi tar­
talmukat. Végezetül azt láthattuk, hogy az ASP.NET-téma lehetövé teszi, hogy
programozottan vagy deklaratív módon biztosítsunk közös megjelenést a ve­
zérlőelemeknek a webkiszolgálón.

797
HARMINCHARMADIK FEJEZET

ASP .NET állapotkezelési


technikák

Az előző két fejezet az ASP.NET webes vezérlőelemek és az azokat tartalma­


zó lapok felépítéséről és viselkedéséről szólt. Ez a fejezet az előzőkre épül, és
bemutatja a G l oba l. asax fájl és az alapul szolgáló HttpApp lication típus sze­
repét. Mint az látni fogjuk, a HttpApplica tion funkcionalitása több olyan
esemény kezelését lehetövé teszi, amelyek segítségével a webalkalmazást kü­
lönálló *. aspx fájlok helyett összetartó egységként kezelhetjük

A HttpApplica tion típus vizsgálata mellett a fejezetben szót ejtünk az ál­


lapotkezeléssel kapcsolatos valamennyi fontos témakörről. Megismerjük a
nézetállapotot (viewstate), a munkamenet- és az alkalmazásváltozók (az al­
kalmazás-gyorsítótárat is beleértve), a sütik és az ASP.NET-profil szerepét.

Az állapot

A 31. fejezet elején említettük, hogy a weben használt HTTP állapotiDentes


átviteli protokoll. Emiatt a webfejlesztés nagyban különbözik a végrehajtható
szerelvény készítésének folyamatátóL Ha például Windows Forms-alkalma­
zást készítünk, biztosak lehetünk abban, hogy a Form-leszármazott osztályban
meghatározott bármely tagváltozó addig megtalálható a memóriában, amíg a
felhasználó nem állítja le a végrehajtható fájlt:

public partial class Mainwindow : system.windows.Forms.Form


{
ll Allapotadati
private string userFavoritecar = "vugo";
}

A web világában azonban ez túl nagyvonalú feltételezés. Ennek bizonyítására


hozzunk létre egy új, simplestateExample nevű ASP.NET-webhelyet. Az*.aspx
fájlhoz tartozó mögöttes kódfájlban definiáljuk a userFavoritecar oldalszintű
sztringváltozót:
33. fejezet: ASP.NET állapotkezelési technikák

public partial class _Default : System,web.UI.Page


{
ll Allapotadat?
private string userFavoritecar = "Yugo";

protected void Page_Load(object sender, EventArgs e)


{

}
}

Alakítsuk ki a webes felhasználói felületet a 33.1. ábrának megfelelően .

rr,s:;��L�at

e Examplel
-
r Set Faw;i;; Ca;- l
,·---·----------
. --- 1
i Get Fawnte Car _ [lbiFavCar
J

l_i<
il Q Design j o Split IB Sourco l EJ�§]<form#forml>j�j<asp:Labei#Lobell>j G
33.1. ábra: Az egt;szerű állapotlap felhasználói felülete

A Set gomb kiszolgálóoldali (btnsetcar) kattintási eseménykezelőjének segít­


ségével a felhasználó a txtFavcar TextBox értékéhez a sztringtagváltozót ren­
delheti:

protected void btnsetcar_click(object sender, EventArgs e)


{
ll Kedvenc autó eltárolása a tagváltozóban.
userFavoritecar = txtFavcar.Text;
}

núg a Get gomb kattintási eseménykezelője (btnGetcar) a tagváltozó jelenlegi


értékét a lapon található címkében ( lblFavcar) jeleníti meg:

protected void btnGetcar_click(object sender, EventArgs e)


{
ll A tagváltozó értékének megjelenítése.
lblFavcar.Text = userFavoritecar;
}

800
Az állapot

Ha Windows Forms-alkalmazást készítenénk, joggal feltételezhetnénk, hogy


ha egyszer a felhasználó beállítja a változó kezdőértékét, azt a rendszer meg­
őrzi az asztali alkalmazás élettartama alatt. Sajnos, ha elindítjuk ezt a webal­
kalmazást, azt tapasztaljuk, hogy minden alkalommal, amikor adatot kül­
dünk vissza a webkiszolgálónak (ha a gombokra kattintunk), a userFavorite­
car sztringváltozó visszaáll a "Yugo" kezdőértékre, ezért a címke mindig
ugyanazt a szöveget jeleníti meg.
Mivel a HTIP protokoll automatikusan nem képes azután is megőrizni az
adatokat, miután elküldte a HTIP-választ, joggal feltételezhetjük, hogy
ilyenkor a Page objektum gyakorlatilag azonnal megsemmisül. Így, amikor az
ügyfél adatot küld vissza az *. aspx fájlnak, új Page objektum jön létre, amely

bármelyik lapszintű tagváltozót alaphelyzetbe állítja. Ez egyértelműen prob­


léma. Képzeljük el, milyen lenne egy olyan webáruház, ahol minden egyes
visszaküldés során a korábban megadott adatok (például a megvásárolni kí­
vánt termékek listája) elvesznének Ha fontos, hogy a webhelyre bejelentke­
zett felhasznáJók adatait eltároljuk, különböző állapotkezelési módszereket
kell alkalmaznunk.

Megjegyzés Ez a probléma semmi esetre sem korlátozódik az ASP.NET-technológiára. A Java­


szervletek, a CGI-alkalmazások, a klasszikus ASP-oldalak és a PHP-alkalmazások is megküzde­
nek az állapotkezelés problémájával.

Ha szeretnénk megőrizni a userFavoritecar sztringtípus értékét a visszakül­


dések között, értékét munkamenet-változóban kell el tárolnunk. A munkamenet­
állapot részleteit a következő oldalakon mutatjuk be. A teljesség kedvéért te­
kintsük meg az aktuális példához szükséges módosításokat (vegyük észre,
hogy ebben már nem használjuk a privát sztringtagváltozót, így akár töröl­
hetjük is a definíciót):

public partial class _Default : system.web.UI.Page


{
ll Allapotadat?
ll private string userFavoritecar = "Yugo" ;

protected void Page_Load(object sender, EventArgs e)


{

801
33. fejezet: ASP. NIT állapotkezelési technikák

protected void btnsetcar_Click(object sender, EventArgs e)


{
ll A megjegyezni kívánt értéket a munkamenet-változóban
ll tároljuk el.
session["userFavcar"] = txtFavcar.Text;
}

protected void btnGetCar_click(object sender, EventArgs e)


{
ll Beolvassuk a munkamenet-változó értékét.
lblFavcar.Text (string)Session["userFavcar"];
=

}
}

Ha így lefuttatjuk programot, a Httpsessionstate objektunmak köszönhetőerr


a kedvenc autónk márkája megőrződik a visszaküldések között is, amelyet
közvetett módon az örökölt sessi on tulajdonságorr keresztül kezelünk.

Forráskód A SimpleStateExample kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyv­


tára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Állapotkezelési módszerek
az ASP. NET-ben

Az ASP.NET számos olyan megoldást kínál, amellyel a webalkalmazás álla­


potinformációit kezelhetjük Az alábbi lehetőségek állnak rendelkezésünkre:

• ASP.NET-nézetállapot használata,

• ASP.NET-vezérlőelem állapotának használata,

• alkalmazásszintű változók definiálása,

• a gyorsítótár-objektum használata,

• munkamenetszintű változók definiálása,

• süti használata.

Közös pontjuk, hogy mindegyik megközelítés megköveteli, hogy a meghatáro­


zott felhasználó munkamenete és a webalkalmazás a memóriában helyezked­
jen el. Amint a felhasználó kijelentkezik a webhelyről (vagy lejár az időkorlát-

802
Az ASP.NET-nézetállapot szerepe

ja), esetleg a webhely leáll, az alkalmazás ismét állapotrneutessé válik. Ha ma­


radandóan szeretnénk a felhasználói adatokat rögzíteni, az ASP.NET által biz­
tosított Profile API-t kell használnunk. A következőkben valamennyi megol­
dást megvizsgáljuk, először az ASP.NET nézetállapotával ismerkedünk meg.

Az ASP.NET-nézetállapot szerepe

Ebben és az előző két fejezetben említettük már a nézetállapotot, de nem adtuk


meg a formális definícióját. Itt az ideje tisztázni ezt a kifejezést. A klasszikus
(COM-alapú) ASP alatt a kimenő HTTP-válasz összeállítása során a webfej­
lesztő feladata volt, hogy manuálisan újra adatokkal töltse fel a bejövő űrlap
vezérlőinek az értékeit. Ha például a bejövő HTTP-kérésben öt szövegdoboz
szerepelt meghatározott értékekkel, az *.as p fájl szkriptkódjának feladata
volt, hogy kiolvassa az aktuális értékeket (a Request objektum Form vagy Query­
string gyűjteményéből), és manuálisan visszahelyezze a HTTP-válaszfo­
lyamba (szükségtelen mondani, ez mennyire unalmas feladat). Ha a fejlesztő
ezt elmulasztotta megtenni, a felhasználó öt üres szövegdobozt kapott.
ASP.NET-ben nem szükséges manuálisan "kibányászni" és újra beállítani
a HTML-vezérlőkben tárolt értékeket, mivel az ASP.NET-futtatókörnyezet
automatikusan beágyaz egy rejtett űrlapmezőt (_VIEWSTATE néven), amelyet
a rendszer a böngésző és a megadott oldal között továbbít. A mezőben egy
Base64-kódolt sztring található, amely az adott oldalon a felhasználói felület­
elemek névj érték párjait foglalja magában.
A system.web. UI. Page alaposztály Init eseménykezelője felel a _VIEW­
STATE mezőben található bemeneti értékek beolvasásáért és a leszármazott
osztályban a megfelelő tagváltozók feltöltéséért (ezért kockázatos a webes
vezérlők állapotának hozzáférése az oldal Init eseménykezelőjében).
Mielőtt a kimenő választ elküldenénk a böngészőnek, a rendszer a _VIEW­
STATE adatok segítségével feltölti az űrlap vezérlőit, és biztosí�a, hogy a
HTML-vezérlők aktuális értékei az előző visszaküldés előtti állapotnak megfe­
lelően jelennek meg.
Az ASP.NET ezen szolgáltatásában az a legjobb, hogy anélkül működik,
hogy ezért nekünk bármit tennünk kellene. Természetesen, igény szerint al­
kalmazha�uk, módosítha�uk vagy akár letiltha�uk az alapértelmezett funk­
cionalitást. Hogy megértsük a működését, nézzünk meg egy konkrét példát.

803
33. fejezet: ASP.NET állapotkezelési technikák

A nézetállapot bemutatása

Hozzuk létre a ViewStateApp új ASP.NET-webalkalmazást. A kezdő *.asp x


oldalhoz adjunk hozzá egyetlen ASP.NET ListBox vezérlőelemet (myListBox
néven) és egy sima Buttont (btnPostBack néven). Kezeljük le a gomb kattintá­
si eseményét, ezzel biztosítsunk lehetőséget a felhasználónak, hogy adatokat
küldjön vissza a webkiszolgálónak

public partial class _Default : System.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnPostback_Click(object sender, EventArgs e)
{
ll Nincs művelet. csak azért van itt a metódus,
ll hogy biztosítsuk a visszaküldést.
}
}

A Visual Studio 2008 Properties ablakában válasszuk ki az Items tulajdonságot,

és vegyünk fel négy Listrtem vezérlőelemet a ListBoxhoz a kapcsolódó párbe­


szédablak segítségéveL Eredményül az alábbi markuphoz hasonlót kapunk:

<asp:ListBox ID="myListBox" runat="server">


<asp:Listrtem>Item one<lasp:Listrtem>
<asp:Listrtem>Item Two<lasp:Listitem>
<asp:Listrtem>Item Three<lasp:Listrtem>
<asp:Listrtem>Item Four<lasp:Listrtem>
<lasp:ListBOX>

Vegyük észre, hogy a ListBox elemeket közvetlenül az* .aspx fájlba drótoz­
zuk. Mint azt már tudjuk, a HTML-űrlapon található összes <asp:> definíció
automatikusan kirendereli a HTML megjelenítését a végső HITP-válasz előtt
(feltéve, hogy rendelkeznek a runat="server" attribútummal).
A <%@Page%> direktíva rendelkezik az opcionális Enableviewstate attribú­
tummal, amelynek értéke alapértelmezés szerint true. A viselkedés letiltásá­
hoz a következőképpen módosítsuk a <%@Page%> direktivát:

<%@ Page Language="C#" AutoEventwireup="true"


CodeFile="Default.aspx.cs" Inherits="_Default"
Enableviewstate ="false" %>

804
Az ASP.NET-nézetállapot szerepe

Pontosan mit jelent a nézetállapot letiltása? A válasz az, hogy attól függ. Ha a
kifejezés előző definícióját nézzük, úgy gondolhatnánk, hogy ha letil�uk az
*. aspx fájl nézetállapotát, akkor a webkiszolgálóhoz irányított visszaküldé­
sek között elvesznének a ListBox értékei. Ezzel szemben, ha így futta�uk az
alkalmazást, meglepve tapasztaljuk, hogy a ListBox értéke attól függetlenül
megmarad, hogy hányszor küldünk vissza adatot.
Sőt, ha megnézzük a böngészőnek küldött forrás-HTML-t (a böngészőben
az oldalon kattintsunk a jobb gombbal, majd válasszuk a View Source menü­
pontot), azt lá�uk, hogy a rejtett _VIEWSTATE mező még mindig jelen van:

<input type="hidden" name="_VIEWSTATE" id="_VIEWSTATE"


value="lwEPDwUKLTM4MTM2MDM4NGRkqGC6gjEV25JnddkJiRmoicl0SIA=" l>

A nézetállapotsztring azért látható még mindig, mert az *.aspx fájl a ListBox


elemeit a HTML <form> címkék hatókörében explicit módon definiálja. Így a
rendszer a ListBox-elemeket automatikusan újragenerálja, ha a webkiszolgá­
ló a kliensnek választ küld.
Tételezzük fel, hogy a ListBox-elemet dinamikusarr töl�ük fel a mögöttes
kódfájlban, nem pedig a HTML <form> definíciójában. Először is, töröljük az
<asp:List rtem> deklarációkat az aktuális *.aspx fájlból:

<asp:ListBox ID="myListBox" runat="server">


<lasp:ListBOX>

Majd a mögöttes kódfájlban a Load eseménykezelőjéből töltsük fel a listaele­


meket:

protected void Page_Load(object sender, EventArgs e)


{
if C! IsPostBack)
{
ll A ListBox dinamikus feltöltése!
myListBox.Items.Add("Item one");
myListBox.Items. Add("Item Two");
myListBox.Items.Add("Item Three");
myListBox.Items.Add("Item Four");
}
}

Ha először megnézzük a módosított oldalt, azt tapasztaljuk, hogy az első al­


kalommal, amikor a böngésző az oldalt kéri, a ListBox értékei jelen vannak és
használhaták Viszont, ha adatot küldünk vissza az oldalnak, a ListBox hirte­
len üres lesz. Az ASP.NET-nézetállapot első szabálya, hogy a hatását csak

805
33. fejezet: ASP.NET állapotkezelési technikák

akkor fejti ki, ha rendelkezünk olyan vezérlőkkel, amelyek értékeit dinami­


kusan, kódból generáljuk Ha az *. aspx fájl <form> címkéibe kódolunk érté­
keket, az elemek állapota a visszaküldések között mindig megmarad (még
akkor is, ha az adott oldalon az Enabl eviewstate értékét false ra állítjuk).
-

A nézetállapotnak akkor vesszük a legnagyobb hasznát, ha olyan dinami­


kusan feltöltött webes vezérlőkkel rendelkezünk, amelyet minden egyes visz­
szaküldés alkalmával újra fel kell tölteni (például egy ASP.NET GridView­
val, amely mindig adatbázisból jelenít meg találatokat). Ha nem tiltjuk le az
ilyen vezérlőket tartalmazó lapokon a nézetállapotot, a tábla egész állapotát a
rejtett _VIEWSTATE mező képviseli. Mivel az összetett oldalak számos
ASP.NET webes vezérlőelemet tartalmazhatnak, nem nehéz elképzelni, mek­
korára nőhet a sztring mérete. A HTTP kérés/válasz ciklusok költsége jelen­
tősen megnőhet, és ez problémát okozhat azok számára, akik betárcsázós in­
terneteléréssel rendelkeznek. Ilyen esetekben növelhetjük az adatátvitel se­
bességét, ha letiltjuk a lapon a nézetállapotot.
A nézetállapot teljes *.asp x fájira történő letiltása meglehetősen draszti­
kus lépésnek tűnhet. Jó, ha tisztában vagyunk azzal, hogy a system. web. ur.
c on tr ol ősosztály valamennyi leszármazottja örökli az Enableviewstate tulaj­
donságot, amely megkönnyíti a nézetállapotot letiltását vezérlőelemenként

<asp:Gridview id="myHugeDynamicallyFilledGridofoata" runat="server"


EnableviewState="false">
</asp:GridvieW>

Megjegyzés Az ASP.NET-oldalak a_VIEWSTATE kis részét belső használatra tartják fenn. Ez


azt jelenti, hogy a_VIEWSTATE mező akkor is megtalálható az ügyféloldali forráskódban, ha
az egész lapon (és annak minden vezérlőelemén) letittottuk a nézetállapotot.

Egyedi nézetállapot-adat hozzáadása

Az Enableviewstate tulajdonság mellett a system.web.ur.control ősosztály a


viewstate örökölt tulajdonságot biztosítja. Valójában ez a tulajdonság a sys­

tem.web. ur. StateBag típushoz biztosít hozzáférést, amelyben a _VIEWSTATE

mezőben tárolt összes adatot képviseli. A StateBag típus indexelőjének hasz­


nálatával egyedi információkat ágyazhatunk a rejtett _VIEWSTATE mezőbe
névj érték párok segítségéveL Íme, egy egyszerű példa:

806
Az ASP.NET·nézetállapot szerepe

protected void btnAddToVS_Click(object sender, EventArgs e)


{
viewstate["customviewstatertem"] = "some user data";
lblvsvalue.Text = (string)Viewstate["customviewstatertem"];
}

Mivel a System.web.ur. StateBag típus célja, hogy bármely, tetszőleges típus­


ból származó system.object objektummal működjön, ha egy adott kulcshoz
tartozó értékhez szeretnénk hozzáférni, azt explicit módon a megfelelő alap­
adattípusra Gelen esetben system. string) kell kasztolnunk. Legyünk óvato­
sak, mert a _VIEWSTATE mezőben nem helyezhetünk el bármilyen objektumot.
Valójában csak a strin g, az Integer, a Boolean, az ArrayList és a Hashtabl e tí­
pusok vagy az ezekből álló tömbök használhatók.
Mivel az *. aspx lapok egyedi információrészleteket helyezhetnek el a
_VIEWSTATE sztringben, a következő logikus kérdés, hogy mikor célszerű ezt
használni. Az esetek többségében az egyedi nézetállapot-adatok a felhaszná­
lói beállítások tárolására a legalkalmasabbak. A nézetállapotban például eltá­
rolhatuk olyan adatokat, amelyek meghatározzák, hogy a felhasználó hogyan
kívánja megtekinteni a GridView elem felhasználó felületét (például rende­
zéssorrendet). A nézetállapot-adatok nem alkalmasak nagy mennyiségű fel­
használói adat - mint például egy bevásárlókosár tartalma vagy gyorsítótára­
zott Dataset - kezelésére. Az ilyen jellegű bonyolult adatok tárolásához
munkamenet- vagy alkalmazásadatokkal kell dolgoznunk. De még mielőtt
ezeket megnéznénk, meg kell értenünk a Gl o b al . asax fájl szerepét.

Forráskód A ViewStateApp kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A .NET 2.0 megjelenése óta a vezérlőelemek állapotát a nézetállapot helyett vezérlőelem-ál­


lapot segítségével célszerű eltárolni. Ez a módszer akkor a leghasznosabb, ha olyan egyedi

ASP.NET webes vezérlőelemet fejlesztettünk, amelynek az üzenetküldések között meg kell


őriznie az adatokat. Noha a viewstate tulajdonságot használhatjuk erre a célra, ha a nézet-
- állapotot lapszinten tiltjuk le, az egyéni vezérlőelem gyakorlatilag használhatatlan. A webes
vezérlőelemek pontosan ezért támogatják a controlstate tulajdonságot.
A vezérlőelem-állapot a nézetállapothoz hasonlóan müködik. De ezt az állapotot a rend­
szer akkor sem tiltja le, ha a nézetállapotot lapszinten letiltottuk. Mint azt már említettük,
ez a szolgáltatás az egyedi webes vezérlőelemek fejlesztői számára a leghasznosabb. Erre a
témakörre nem térünk ki részletesen a könyvben, további információkért forduljunk a .NET
Framework 3.5 SOK dokumentációjához.

807
33. fejezet: ASP.NET állapotkezelési technikák

A Global.asax fájl szerepe

Jelenleg az ASP.NET-alkalmazás kicsit többnek tűnik, mint * .aspx fájlok és a


hozzájuk tartozó webes vezérlőelemek halmaza. Úgy is felépíthetünk egy
webalkalmazást, hogy csak összekapcsolunk néhány összetartozó weboldalt,
de valószínűleg szükségünk lesz egy módszerre, amellyel az egész webalkal­
mazással kommunikálhatunk. Ezért az ASP.NET-webalkalmazásokhoz hoz­
záadhatunk egy opcionális, G lobal . asax fájlt a Web Site > Add New ltem
menüpont segítségéve!, ahogyan azt a 33.2. ábrán láthatjuk (vegyük észre,
hogy a Global Application Class ikont kell választanunk).

Add New !tem • C:\My Books\C# and the .NET Platfenn 4th ed\Code\Chapter 34W.-5tateApp\

Templates:

Visual Studio installed tempiates

ü LJ � -
li � li LJ ®j �
Webform Master Pagi!! WBJUser AJAXC!i�t AJAX Client AJAX Client AJAXMaster AJ� Web AJAX·enab...
Control Behavior Control library Page Fenn WCF Service
�---

ü
Browser File

Cl ass
l)
Class

DataSt:t
-�
Generic
� -

Global
[!J
�HTML Page

JScript File
m
UNQto SQL

u
Diagram Handlex Appiielition Classes
Class

� l
� � � � - �
l:5. i �
Report Report R�ource Fill!: Site Map Skin File SQL StyleSheet Text File WCF Service
W11ard Database


� -
il
· -· · · -· · ·
� � '
� .

A class for handfing Web Application events

Name: Globc!li.asax

Languag� \Visual C# ....-.


__..... ---..- ·----.- •l O Pl�ce ccd<! in �€'p arat� fite
L-____ _ ____, 0Sel�ctmil"��rp<Jge

Add jj Cancel

33.2. ábra: A Global.asaxfájl

Tulajdonképpen a Global . asax fájl közel áll a hagyományos, két kattintással


indítható * . exe fájlokhoz, amelyekkel az ASP.NET világában találkozhatunk,
és ez azt jelenti, hogy a típus a webhely futási idejű működését képviseli. Ha
hozzáadjuk a Global . asax fájlt a webprojekthez, észrevehetjük, hogy alig
több mint egy eseménykezelő halmazt magában foglaló <script> blokk:

<%@ Application Language="C#" %>

<script runat="server">
void Application_Start(object sender, EventArgs e)

808
A Global.asax fájl szerepe

{
ll Az alkalmazás indulásakor lefuttatott kód.
}

void Application_End(object sender, EventArgs e)


{
ll Az alkalmazás leállásakor lefuttatott kód.
}

void Application_Error(object sender, EventArgs e)


{
ll Kezeletlen hibák bekövetkezésekor lefuttatott kód.
}

void session_Start(object sender, EventArgs e)


{
ll új munkamenet létrehozásakor lefuttatott kód.
}

void session_End(object sender, EventArgs e)


{
ll A munkamenet végén lefuttatott kód. Megjegyzés:
ll A session_End esemény csak akkor következik be, ha a
ll web.config fájlban a munkamenet-állapotmód értéke InProc.
ll Ha a munkamenet módja Stateserver vagy SQLServer, az esemény
ll nem következik be.
}
</script>

A látszat azonban csal. A futásidőben a <script> blokkban található kód egy,


a system. web. HttpAppl ication típusból származó osztálytípusba kerül (ha
rendelkezünk ASP.NET l.x háttérismeretekkel, emlékezhetünk rá, hogy a
G lobal . asax mögöttes kódfájlja valóban definiált egy HttpAppl i cation típus­
ból származó osztályt).
Mint említettük, a G lobal. asax fájlban definiált tagok eseménykezelők,
amelyek segítségével alkalmazás- és munkamenetszintű eseményekkel mű­
ködhetünk együtt. A 33.1. táblázat foglalja össze a tagok szerepét.

r::..;
. .. ,, ..,:,,.,:,:. , ,��i;:.·�.������;;.-�L·:<:{.;:;.·
.. -,<:I,<:i:.i�h ·r:� · ; ., , ,,

Application_Start() Ezt az eseménykezelőt a webalkalmazás legelső indulá­


sakor hívja a rendszer. Ez azt jelenti, hogy a webalkal­
mazás élettartama során az esemény pontosan egyszer
lép működésbe. Itt célszerű definiálni azokat az alkal­
mazásszintű adatokat, amelyeket a teljes webalkalma­
zásban használni kívánunk.

809
33. fejezet: ASP.NET állapotkezelési technikák

Application_End() Ez az eseménykezelő az alkalmazás leállítása során hi­


vódik. Akkor következik be, amikor a legutolsó fel­
használó időkorlá�a lejár, vagy az alkalmazást az IlS-en
keresztül leállí�uk.

session_Start() Az esemény akkor lép működésbe, amikor egy új fel­


használó bejelentkezik az alkalmazásba. Itt állíthatunk
be felhasználóspecifikus adatokat.

session_End() Ez az eseménykezelő a felhasználói munkamenet leállítása


során (általában előre definiált időtúllépés miatt) hivódik.

Application_Error() Ez a globális hibakezelő akkor hivódik, ha a


webalkalmazás kezeletlen kivételt jelez.

33.1. táblázat: A System.Web névtér alapvető típusai

A véső, globális kivételkezelő

Először is, nézzük meg az Application_Error() eseménykezelő szerepét. Em­


lékezzünk rá, hogy az oldalak kezelhetik az Error eseményt, és így gondos­
kodhatnak az oldal hatókörében keletkezett kezeletlen kivételekrőL Hason­
lóképpen, az Application_Error() eseménykezelő az utolsó lehetőség, ahol
az adott lap által fel nem dolgozott kivételek kezeléséről gondoskodhatunk.
Az oldalszinhl Error esemény kezeléséhez hasonlóan, az örökölt Server tu­
lajdonság segítségével hozzáférhetünk a konkrét system. Exception höz:

void Application_Error(object sender, EventArgs e)


{
ll A kezeletlen hiba beolvasása.
Exception ex = Server.GetLastError();

ll A hiba feldolgozása ...

ll A befejezést követően a hiba törlése.


Server.ClearError();
}

Mivel az Application_Error() eseménykezelő a webalkalmazásunk végső ki­


vételkezelője, ezt a metódust gyakran úgy szokták implementálni, hogy át­
irányítsa a felhasználót a kiszolgálón egy előre meghatározott hibaoldalra.
Az egyéb hibakezeléssei kapcsolatos műveletek közé tartozik például e-mail
értesítés küldése a webes rendszergazdának vagy külső hibanapló írása.

810
A Global.asax fájl szerepe

A HttpApplication ősosztály

Amint azt már említettük, a Gl o ba l . asax szkript dinamikusan a system. w e b .


HttpApp l i ca tion ősosztályból származó osztályba generálódik, amely nagyjából

ugyanazt a funkcionalitást biztosí�a, mint a system. web. ur. Page típus (a látható
felhasználói felület nélkül). A 33.2. táblázat bemuta�a a kulcsfontosságú tagokat.

Application Ez a tulajdonság lehetővé teszi, hogy alkalmazásszintű válto­


zókkal dolgozzunk a rendelkezésünkre álló HttpAppl i ca-
ti onstate típus segítségéve!.

Request Ez a tulajdonság az alapul szolgáló HttpRequest objektum se­


gítségével lehetővé teszi a bejövő HTIP-kérés kezelését.

Response Ez a tulajdonság az alapul szolgáló HttpResponse objektum


segítségével lehetővé teszi a bejövő HITP-válasz kezelését.

server Ez a tulajdonság az alapul szolgáló Httpserveruti l i ty objek­


tum segítségéveilekérdezi az aktuális kérés belső kiszolgáló­
objektumát.

session Ez a tulajdonság az alapul szolgáló Httpsessi onst ate objek­


tum segítségével lehetővé teszi munkamenetszintű változók
kezelését.

33.2. táblázat: A System.Web.HttpApplication típus kulcsfontosságú tagjai

� x

Server Objects & Events � (No Events)

..... <\@ Appll.catl.0:1 La:i.quaqe="Cf" \>

�8 <�cr1.pt r!..l�at:.=".server">

vo1.d Appll.catl.on_Start (ob)ect .sender, ;:.•.:e�.':.A::gs e)

{
Code t!:a:. ru!'!.s o:: apt:l.:..catl.o:: s:.art:.:p

thl..s. r ei
l PreRequestHandlerEx.cute
l PreSendRequest(ontent
VOl. d
l PreSendRequestHeaders
1 -J Profile
l ReleaseRequestState

Request · • •·

VOl. d

"'

33.3. ábra: Ne feledjük, ilogy a Global.asax fájlban rejtőzködő tagok sziilője a HtipApplication osztály

811
33. fejezet: ASP.NET állapotkezelési technikák

Mivel a Gl obal . asax fájl explicit módon nem dokumentálja, hogy alapjául a
HttpApplication ősosztály szolgál, fontos megjegyeznünk, hogy az "az egy"

kapcsolat szabályai valóban érvényesek. Ha például a Gl obal . asax fájl bár­


mely tagjában a base kulcsszón a pont operátort alkalmazzuk, tapasztalhat­
juk, hogy a származási lánc összes tagjához közvetlen hozzáféréssel rendel­
kezünk, mint az a 33.3. ábrán látható.

Az alkalmazás és a munkamenet közötti


különbség

ASP.NET-ben az alkalmazás állapotát a HttpAppl i cati onstate típus példánya


tar�a karban. Az osztály segítségével globális információkat oszthatunk meg
az ASP.NET-alkalmazásba bejelentkezett összes felhasználó (és minden lap)
között. Nemcsak alkalmazásadatokat oszthatunk meg a webhely felhasználói
között, de ha egy alkalmazásszintű adat értéke megváltozik, az új értékről a
következő visszaküldés során minden felhasználó értesül.
Ezzel szemben a munkamenet-állapot segítségével meghatározott fel­
használó információit tárolha�uk (mint például egy bevásárlókosár tartal­
mát). Fizikailag a felhasználó munkamenetének állapotát a Httpsessionstate
osztály képviseli. Amikor egy új felhasználó bejelentkezik az ASP.NET-web­
alkalmazásba, a futtatórendszer automatikusan új munkamenet-azonosítót
rendel a felhasználóhoz, amely alapértelmezés szerint 20 percnyi tétlenség
után lejár. Így, ha a webhelyre 20000 felhasználó jelentkezik be, 20000 külön­
böző Httpsessionstate objektummal rendelkezünk, amelyek mindegyike auto­
matikusan egyedi munkamenet-azonosítót kap. A webalkalmazás és a webes
munkamenetek kapcsolatát a 33.4. ábra szemlélteti.
Korábbi tapasztalataink alapján emlékezhetünk rá, a hagyományos ASP­
ben az alkalmazás- és munkamenetállapo- adatokat különálló COM-objektu­
mok (például Application és session) képviselik. ASP.NET-ben a Page osz­
tályból származó tipusok, valamint a HttpApplication megegyező nevű tu­
lajdonságokat (pl. Application és session) használhatnak, amelyeken keresz­
tül az alapul szolgáló HttpAppl icationstate és HttpsessionState típusok ér­
hetőek el.

812
Az alkalmazás és a munkamenet közötti különbség

Webalkalmazás (HttpApplication)

A munkamenet n munkamenet

(HttpSessionState) (HttpSessionState)
B munkamenet
(HttpSessionState)

33 4 ábra: Az alkalmazás- és a munkamenet-állapot megkülönböztetése


. .

Alkalmazásszintű állapotadatok karbantartása

A HttpAppli cati onstate típus segítségével a fejlesztök az ASP.NET-alkalma­


zásban több munkamenet között oszthatnak meg globális információkat Így
például karbantarthatunk alkalmazásszintű kapcsolatsztringeket, amelyeket
minden oldal használhat, közös Dataset típust, amelyet több oldal alkalmaz­
hat, vagy egyéb adatrészletet, amelyhez alkalmazásszinten kell hozzáfér­
nünk. A 33.3. táblázat bemuta�a a típus néhány alapvető tagját.

Add() A metódus segítségével új névj érték adatpárokat vehetünk fel


a HttpAppli cati onstate típusban. Jegyezzük meg, hogy a
metódust rendszerint nem részesítjük előnyben a HttpAppl i -
cati onstate osztály indexelőjével szemben.

AllKeys Visszaadja egy system. St ri n g típusú tömböt, amely a Http­


Appli cati onstate típusban található összes nevet képviseli.

Clear() A metódus törli aHttpAppli cati onState típus valamennyi


elemét. Funkcionális szempontból megegyezik a RemoveA ll()
metódussal.

Co unt Ez a tulajdonság lekérdezi a HttpAppli cati onstate típusban


található objektumok számát.

813
33. fejezet: ASP.NET állapotkezelési techniká k

. ·, ' .
.

� .
' : .,, � . •,
. �
' .
" .

Lock(), Ezt a két metódust akkor használjuk, ha az alkalmazásválto­


unlock() zók készletét szálbiztos módon szeretnénk módosítani.

RemoveAll(), Ezek a metódusok törölnek egy megadott elemet (a neve alap­


Remove(), ján) a HttpApplicationstate típusbóL A RemoveAt() az ele­
RemoveAt() met numerikus index alapján távolítja el.

33. 3. táblázat: A HttpApplicationState tipus tagjai

Az alkalmazásállapot működésének bemutatásához hozzunk létre új ASP.NET­

webalkalmazást Appstate néven, és adjunk hozzá új Global.asax fájlt. Az aktív


munkamenetek között megosztható adattagok létrehozása során meg kell ad­
nunk egy név/ érték párt. Általában ezt a HttpApplication osztályból származó
típus Application_start() eseménykezelőjében a legegyszerűbb megtenni, pél­
dául a következőképpen:

void Application_start(object sender, EventArgs e)


{
ll Alkalmazásváltozók beállítása.
Application["salesPersonOfTheMonth"] "chucky";
=

Application["currentcaronsale"] = "colt";
Application["MostPopularcolorOnLot"] = "Black";
}

A webalkalmazás élettartama során (amely addig tart, núg a webalkalmazást

manuálisan le nem állí�uk, vagy a legutolsó felhasználó időkorlá�a le nem


jár), bármely felhasználó (bármely lapról) szükség szerint elérheti ezeket az
értékeket. Tételezzük fel, hogy az oldalunk az éppen leértékelt autót jeleníti
meg egy címkén a gomb kattintási eseménykezelőjének segítségéve!:

protected void btnShowcaronsale_click(object sender, EventArgs arg)


{
lblcurrcaronsale.Text = string.Format("sale on {O}'s today!",
(string)Application["currentcaronsale"]);
}

Vegyük észre, hogy a viewstate tulajdonsághoz hasonlóan a HttpApplica­


tionstate típus által visszaadott értéket kasztoini kell a megfelelő adattípus­
ra, mivel az Appl i cati on tulajdonság is általános system.Object típusokkal
dolgozik.

814
Az alkalmazás és a munkamenet közötti különbség

Mivel a HttpApplicationstate típus tetszőleges típusú információt tárol­


hat, beláthatjuk, hogy egyedi típusokat (vagy bármilyen .NET-objektumot)
elhelyezhetünk a webhely alkalmazásállapotában. Tételezzük fel, hogy szíve­
sebben tárolnánk a három aktuális alkalmazásváltozót az erősen típusos car­
Lotinfo nevű osztályban:

public class carLotinfo


{
public CarLotinfo(string s, string c, string m)
{
salesPersonofTheMonth s;
currentCaronsale= c;
mostPopularcoloronLot m;
}
ll A könnyebb elérhetőség miatt publikus, de használhatnánk
ll az automatikustulajdonság-szintaxist is.
public string salesPersonOfTheMonth;
public string currentcaronsale;
public string mostPopularcolorOnLot;
}

Ha elkészítettük a segédosztályt, a következőképpen módosítsuk az Applica­


tion_start() eseménykezelőt

void Application_Start(Object sender, EventArgs e)


{
ll Az egyedi objektum elhelyezése az alkalmazás-adatszektorban.
Application["carSiteinfo"] =

new carLotrnfo("chucky", "colt", "Black");


}

majd a btnshowAppvariables nevű gomb kiszolgálóoldali kattintási esemény­


kezelőjéből a nyilvános adatmezők segítségével férjünk hozzá az adatokhoz:

protected void btnShowAppvariables_Click(object sender, EventArgs e)


{
carLotinfo appvars =
((CarLotrnfo)Application["carSiteinfo"]);
string appstate =
string.Format("<li>Car on sale: {0}</li>",
appVars.currentcaronsale);
appstate +=
string.Format("<li>Most popular color: {0}</li>",
appvars.mostPopularcoloronLot);
appstate +=
string.Format("<li>Big shot salesPerson: {0}</li>",
appvars.salesPersonOfTheMonth);
lblAppVariables.Text appState;
=

815
33. fejezet: ASP.NET állapotkezelési technikák

Mivel az aktuális leértékelt autót egyedi osztálytípus segítségével tesszük el­


érhetővé, a btnshowcaronsale kattintási eseménykezelőjét a következőképpen
kell módosítanunk:

protected void btnsh owcaronsale_clickl(object sender, EventArgs e)


{
lblcurrcaronsale.Text String.Format("sale on {O}'s today!",
=

((CarLotrnfo)Application["carsiternfo"]).currentcaronsale);
}

Ha futtatjuk a programot, láthatjuk, hogy a 33.5. ábrának megfelelőerr az al­


kalmazásváltozók listája megjelenik a lap címkéiben.

� Untitled Page - Windows Intemet Explorer l = l @) llii3iíl

Ou· \_!l_t:_�:[/�o:_•����:_!�!_L�J [G�_I: __ ___ __ _ _

_1Jl �� Untitled Page r l SI· � .


»

éi·
.
.
Fun with Application State
n
l

l
l
Get Car On Sale

Show App Variabies


J Sale on Coh"s today!

l
·l l
• Car on sale: Coh u
• Most popular color: Black
• Big shot SalesPerson: Chucky

.
y

� Local intranot l Protected Mode: Off @\100%

33. 5. ábra: Alkalmazásadatok megjelenítése

Alkalmazásadatok módosftása

A HttpAppli cation state típus tagjaival programozottan frissíthetjük vagy tö­

rölhetjük bármely vagy akár az összes tagot a webalkalmazás futása során.


Meghatározott elem törléséhez például egyszerűen a Remove () metódust kell
meghívni. Ha az összes alkalmazásszintű adatot szeretnénk megsemmisíteni,
a RemoveAll() metódust kell használnunk:

816
Az alkalmazás és a munkamenet közötti különbség

private void CleanAppData()


{
ll Egyetlen elem törlése sztringnév segítségével.
Application.Remove["somertemiDontNeed"];

ll Minden alkalmazásadat törlései


Application.RemoveAll();
}

Ha meglévő alkalmazásszintű változó értékét szeretnénk megváltoztatni,


egyszerűen hozzá kell rendelnünk az új értéket a kérdéses adatelemhez. Téte­
lezzük fel, hogy az oldal egy új Button típust támogat, amely lehetövé teszi a
felhasználó számára, hogy a NewSP nevű szövegdoboz segítségével beolvasott
értékre módosítsa a hónap kereskedőjét A kattintási eseménykezelője az el­
várásainknak megfelelő:

protected void btnsetNewSP_Click(object sender, EventArgs e)


{
ll új kereskedő beállítása.
((CarLotrnfo)Application["CarSiternfo"]).salesPersonofTheMonth
txtNewSP.Text;
}

Ha futtatjuk a webalkalmazást, azt tapasztaljuk, hogy az alkalmazásszintű


változó értéke módosult. Továbbá, mivel az alkalmazásváltozók minden fel­
használói munkamenetből elérhetők, ha a webböngésző három vagy négy
példányát indítjuk el, azt tapasztaljuk, hogy ha az egyik példány módosítja a
hónap kereskedőjének nevét, visszaküldéskor a változás a többi böngészőben
is megjelenik.
Előfordulhat olyan helyzet, amelyben több alkalmazásszintű változót egy­
ségként kell kezelnünk és módosítanunk. ilyenkor fennállhat az adatsérülés
veszélye (mivel megtörténhet, hogy alkalmazásszintű adatot éppen akkor mó­
dosítunk, amikor másvalaki megpróbál azokhoz hozzáférni). Bár választhat­
nánk a nehezebb utat, és zárolhatnánk manuálisan a logikát (a system.Threa­
ding névtér primitívjeinek segítségéve!), a HttpApplicationstate típus azonban

biztosítja a Lock() és az unlock() metódust, amelyek automatikusan gondos­


kodnak a szálbiztonságról:

ll Kapcsolódó alkalmazásadat biztonságos hozzáférése.


Application.Lock();
Application["salesPersonOfTheMonth"] = "Maxine";
Application["currentBonusedEmployee"] =

Application("SalesPersonofTheMonth");
Application.unlock();

817
33. fejezet: ASP.NET állapotkezelési technikák

A webalkalmazás leátlitásának kezelése

A HttpAppl i cationstate típus addig tartja karban a tárolt elemek értékeit,


amíg a következő két helyzet valamelyike be nem következik: a webhely
utolsó felhasználója túllépi az időkorlátot (vagy kijelentkezik), vagy valaki
manuálisan leállítja a webhelyet az IlS segítségéveL Mindkét esetben a rend­
szer automatikusan a HttpApp l i cati on-leszármazott típus App l i cati on_End ( )
metódusát hívja meg. Ebben az eseménykezelőben elhelyezhetjük az alkal­
mazás leállításakor végrehajtani kívánt kódot:

void App l i catio n End ( ob j e ct


_ sender, EventArgs e)
{
ll Az aktuális alkalmazásváltozók kiírása
ll adatbázisba vagy egyéb szükséges kód ...
}

Forráskód Az AppState kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára tartal­


mazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az alkalmazás-gyorsítótár használata

Az ASP.NET-ben másik, rugalmasabb módszerrel is kezelhetjük az alkalma­


zásszintű adatokat. Emlékezzünk rá, hogy a HttpAppl i cati on s t a t e objektum
addig tárolja memóriában az értékeket, amíg a webalkalmazás fut és műkö­
dik. Bizonyos esetekben az alkalmazásadatok egy részét csak meghatározott
ideig szeretnénk tárolni. Olyan ADO.NET Dataset objektumra van például
szükségünk, amely csak öt percig érvényes. Öt perc leteltével friss Dataset
objektumot olvasunk be, hogy gondoskodjunk a lehetséges adatbázis­
módosításokról. Bár gyakorlatilag ezt az infrastruktúrát a HttpApp l i cation­
st ate osztály és egy manuálisan megírt megfigyelő segítségével megvalósít­
hatjuk, feladatunkat az ASP.NET alkalmazás-gyorsítótár alkalmazása jelen­
tősen egyszerűbbé teszi.
Az ASP.NET sy st em. web. ca chi ng. eae he objektuma (amely a context. eae he
tulajdonságon keresztül érhető el) lehetővé teszi olyan objektum létrehozását,
amely meghatározott ideig minden felhasználó számára (minden oldalról)
hozzáférhető. A legegyszerűbb formájában a gyorsítótár használata megegye­
zik a HttpAppl i cationstate típussal folytatott munkával:

818
Az alkalmazás-gyorsítótár használata

ll Elem felvétele a gyorsítótárba.


ll Az elem érvényessége *nem* jár le.
Context.cache["somestringrtem"] = "This is the stri n g item";

ll Elem lekérdezése a gyorsítótárból.


string s = (string)Context.cache["someStringitem"]

Megjegyzés Ha a cache objektumot a Global.asax fájlból szeretnénk elérni, a context


tulajdonságot kell használnunk. Ha viszont a system. web. UI. Page-leszármazott típus ható·
körében vagyunk, közvetlenül használhatjuk a cache objektumot.

Ha nem áll szándékunkban az alkalmazásszintű adatpontot automatikusan


frissíteni (vagy eltávolítani), a cache objektum használatából nem származik
előnyünk, mível közvetlenül használhatjuk a HttpAppl icationstate típust.
Ha azonban azt szeretnénk, hogy az adatpont meghatározott idő után meg­
semmisüljön - és erről akár értesülni is szeretnénk-, a cache típus nagyon
hasznosnak bizonyulhat.
A system.web.cachin g. cache osztályban kevés tag található a típus inde­
xelőjén kívül. Az Add() metódussal vehetünk fel például új, még nem defini­
ált elemet a gyorsítótárba (ha a meghatározott elem már létezik, az Add()
gyakorlatilag semmit sem csinál). Az Insert() metódus is tagot helyez el a
gyorsítótárban. De ha az elemet már definiáltuk, az Insert() felülírja a típust
az aktuális elemmel. Mivel leginkább erre a viselkedésre van szükségünk, a
fejezet hátralévő részében kizárólag az Insert() metódusra koncentrálunk.

Adatok gyorsitótárazása

Nézzünk meg egy példát. Első lépésként hozzunk létre új ASP.NET-web­


alkalrnazást CacheState néven, és adjunk hozzá egy Global. asax fájlt. A Http­
Appl icationstate típus által karbantartott alkalrnazásszintű változóhoz hason­

lóan a eae he bármilyen system. obj ee t-leszármazott típust tárolhat, és általában


az Application_start() eseménykezelőből töltjük fel adatokkal. A következő
példában a Dataset objektum tartalmát 15 másodpercenként automatikusan
frissítjük A kérdéses Dataset az ADO.NET ismertetése során létrehozott Auto­
Lot adatbázis Inventory táblájának aktuális rekordkészletét tárolja.
A fentiek alapján állítsuk be az AutaLotDAL. dll szerelvény (lásd a 22. feje­
zetet) referenciáját, és módosítsuk a Global osztálytípust a következőképpen:

819
33. fejezet: ASP.NET állapotkezelési technikák

<%@ Application Language="C#" %>


<%@ Import Namespace "AutoLotconnectedLayer" %>
<%@ Import Namespace = "system.Data" %>

<script runat="server">
ll Definiáljunk statikus szintű gyorsítótár-tagváltozót.
static cache thecache;

void Application_start(Object sender, EventArgs e)


{
ll Először rendeljük hozzá a statikus 'thecache' változót.
thecache = context.cache;

ll Amikor az alkalmazás elindul, olvassuk be az AutaLot


ll adatbázis Inventory táblájának aktuális rekordjait.
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data source=(local)\SQLEXPRESS;" +

"Initial catalog=AutoLot;Integrated Security=True");


DataTable thecars = dal.GetAllinventory();
dal.closeconnection();

ll A DataTable objektumot a gyorsítótárban tároljuk.


thecache.Insert("AppDataTable",
thecars, nu ll,
DateTime.Now.Addseconds(15),
Cache.NoSlidingExpiration,
cacheitemPriority.Default,
new cacheitemRemovedcallback(updatecarinventory));
}

ll A cacheitemRemovedcallback metódusreferencia célpontja.


static void Updatecarinventory(string key, object item,
CacheitemRemovedReason reason)
{
InventoryDAL dal = new InventoryDAL();
dal.Openconnection(@"Data Source=(local)\SQLEXPRESS;" +

"Initial catalog=AutoLot;Integrated security=True");


DataTable thecars = dal.GetAllinventory();
dal.closeconnection();

ll A gyorsítótárban tároljuk.
thecache.Insert("AppDataTable",
thecars, null,
DateTime.Now.Addseconds(15),
cache.NoSlidingExpiration,
cacheitemPriority.Default,
new cacheitemRemovedcallback(updatecarinventory));
}

</script>

820
Az alkalmazás-gyorsítótár használata

Először vegyük észre, hogy a G loba l típus definiálta a statikus cache tagvál­
tozót. Ennek az az oka, hogy két olyan megosztott tagot definiáltunk, amely­
nek hozzá kell férnie a cache objektumhoz. Emlékezzünk rá, hogy a statikus
tagoknak nincs hozzáférése az örökölt tagokhoz, ezért nem használha�uk a
context tulajdonságot.

Az Applicati on_start () eseménykezelőben töltsünk fel egy DataTab le ob­


jektumot, és helyezzük el az alkalmazás gyorsítótárában. Sejtéseinknek meg­
felelőerr a context. cache. Insert() metódus többszörösen túlterhelt. A fenti
példában valamennyi lehetséges paraméterének értéket adtunk. Tekintsük
meg a következő Add O hívás t, amelyhez megjegyzéseket is fűztünk:

thecache.Add("AppDataTable",
ll Név, amellyel a gyorsítótárban elhelyezett
ll elemet azonosítjuk.
thecars, ll A gyorsítótárban elhelyezni kívánt objektum.
null, ll van függőségi kapcsolata az objektumnak?
DateTime.Now.Addseconds(lS),
ll Az elem meddig legyen a gyorsítótárban?
cache.NoslidingExpiration,
ll Rögzített vagy csúszó lejárati idő?
cacheitemPriority.Default,
ll A gyorsítótárelem prioritásszintje.
ll A CacheitemRemove esemény metódusreferenciája.
new CachertemRemovedcallback(updateCarrnventory));

Az első két paraméter az elem névj érték párját alko�a. A harmadik paraméterrel
definiálha�uk a cacheDependency típust (amely jelen esetben null, mivel nincs
más olyan entitás a gyorsítótárban, amely a DataTable objektumtól függene).

Megjegyzés A cacheDependency típus meghatározásának lehetősége meglehetősen érde­


kes. Létrehozhatunk például függőségi viszonyt egy tag és egy külső fájl között. Ha a fájl tar­
talma megváltozna, a rendszer automatikusan frissíthetné a típust. További részletekért la­
pozzuk fel a .NET Framework 3.5 SDK dokumentációját.

A következő három paraméterrel azt határozha�uk meg, hogy az elem med­


dig maradhat az alkalmazás gyorsítótárában, illetve megadha�uk az elem
prioritásának szin�ét. Meghatároztuk a cache.Nos lidi ngExpi rati on írásvé­
dett mező segítségéve!, amely tájékozta�a a gyorsítótárat, hogy a megadott
időkarlát (15 másodperc) abszolút időtartam. Végül, és a példában ez a leg­
fontosabb, létrehoztunk a cachertemRemovedcallback új metódusreferencia-tí­
pust, és átadtuk a Dataset törlésekor meghívni kívánt metódus nevét.

821
33. fejezet: ASP. NET állapotkezelési technikák

Amint az updatecarrnventory() metódus szignatúrájából látszik, a cache­

ItemRemovedcall back metódusreferencia csak a következő szignatúrának meg­

felelő metódusokat hívhatja meg:

static void updatecarrnventory(string key, object item,


CachertemRemovedReason reason)
{
}

Tehát, amikor az alkalmazás elindul, feltöltjük adatokkal a oataTable objek­


tumot, és elhelyezzük a gyorsítótárban. A DataTable objektumot 15 másod­
percenként kiürítjük, frissítjük, és ismét elhelyezzük a gyorsítótárban. Ahhoz,
hogy lássuk ennek hatását, létre kell hoznunk egy Page objektumot, amely le­
hetőséget ad a felhasználói beavatkozásra.

Az* .aspx fájl módosftása

Módosítsuk a kezdeti *.asp x fájl felhasználó felületét a 33.6. ábrának megfe­


lelően.
Az oldal Load eseménykezelőjében állítsuk be a Gridview objektumot, hogy
a gyorsítótárazott DataTable aktuális tartalmát jelenítse meg, amikor a felhasz­
náló először küld adatokat az oldalra (a kódfájlban ne felejtsük el importálni az

AutoLotConnectedLayer névteret):

protected void Page_Load(object sender, EventArgs e)


{
if (! IsPostBack)
{
carsGridview.oatasource (oataTable)cache["AppoataTable"];
carsGridview.DataBind();
}
}

Az "Add This Car" gomb kattintási eseménykezelőjében szú:rjuk be új rekordot


az AutoLot adatbázisba az rnventoryDAL segítségéveL Miután a rekordot be­
szúrtuk, hívjuk a RefreshGrid() segédfüggvényt, amely frissíti a felhasználói
felületet:

protected void btnAddcar_Click(object sender, EventArgs e)


{
ll Az Inventory tábla frissítése
ll és a RefreshGrid() hívása.
InventoryDAL dal = new rnventoryDAL();

822
Az alkalmazás-gyorsítótár használata

dal-OpenConnection(@"Data Source=(local)\SQLEXPRESS;" +

"rnitial Catalog=AutoLot;rntegrated security=True");


dal_rnsertAuto(int.Parse(txtcariD.Text), txtcarcolor.Text,
txtcarMake.Text, txtcarPetName.Text);
dal.closeconnection();
RefreshGri d();
}

private void RefreshGrid()


{
InventoryDAL dal = new InventoryDAL();
dal.openconnection(@"Data source=(local)\SQLEXPRESS;" +

"Initial Catalog=AutoLot;Integrated Security=True");


DataTable thecars = dal.GetAllrnventory();
dal.closeconnection();

carsGridview.Datasource = thecars;
carsGridView.DataBind();
}

Make

Color

l.
l Q Design l o Splít l Ei! Source l EJ l<asp;Button#btnAddCar>l [8
33.6. ábra: A gyorsítótár-alkalmazás grafikus felülete

823
33. fejezet: ASP.NET állapotkezelési technikák

Teszteljük az alkalmazás gyorsítótárának használatát: indítsuk el az aktuális


programot (Ctrl + F5), majd a böngészőben megjelenő URL-t másoljuk a vá­
gólapra. Ezt követően indítsuk el az Internet Explorer újabb példányát, és il­
lesszük az URL-t ebbe a böngészőpéldányba. Ekkor a webböngésző két pél­
dányával rendelkezünk, amelyek ugyanazt a Default. aspx oldalt muta�ák
azonos tartalommal.
A böngésző egyik példányában vegyünk fel egy új autó bejegyzését. Ab­
ban a böngészőben, amelyikből a visszaküldést kezdeményeztük, látható a
frissített GridView.
A másik böngészőpéldányban kattintsunk a Refresh (F5) gombra. A pél­
dányban még nem látszik az új elem, mivel a Page_Load eseménykezelő közvet­
lenill a gyorsítótárból olvas. (Ha mégis láttuk az értéket, akkor a 15 másodperc
már letelt. Vagy gépeljünk gyorsabban, vagy növeljük meg az időtartaroot
ameddig a DataTable a gyorsítótárban marad.) Várjuk néhány másodpercet,
majd ismét kattintsunk a második böngészőpéldány Refresh gombjára. Most
már látnunk kell az új elemet, mivel a DataTable érvényessége a gyorsítótárban
lejárt, és a cacheitemRemovedcallba ck metódusreferencia célmetódusa automa­
tikusan frissítette a gyorsítótárazott DataTabl e objektumot.
Látható, hogy a cache típus legnagyobb előnye, hogy biztosítha�uk a lehető­
séget a közbelépésre, ha egy tagot a rendszer eltávolít. Ebben a példában elke­
rülhettük volna a eae he használatát, és beállíthattuk volna, hogy a Page_Load ()
eseménykezelő mindig közvetlenül az AutoLot adatbázisból olvassa az adato­
kat. A célunk azonban az volt, hogy megmutassuk: a gyorsítótár lehetővé teszi
az adatok automatikus frissítését a gyorsítótárazási mechanizmus segitségével.

Forráskód A CacheState kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Munkamenetadatok kezelése

Eddig az alkalmazásszintű és a gyorsítótárazott adatok vizsgálatáról volt szó,


most pedig vizsgáljuk meg a felhasználónként történő adattárolás szerepét.
Ahogy azt már említettük, a munkamenet egy kicsivel több, mint a meghatáro­
zott felhasználó együttműködése a webalkalmazással, amelyet egy egyedi
Httpsessi on state objektum képvisel. Ha az adott felhasználóállapottal ellátott

adatainkat tárolni szeretnénk, a HttpApp li cati on-leszármazott típus és bármely

824
Munkamenetadatok kezelése

syst:em. web. UI. Page-leszármazott típus hozzáférhet a session tulajdonsághoz.

A felhasználónkénti adattárolás igényének klasszikus példája az online bevá­


sárlókosár. Ha tíz ember bejelentkezik az online áruházba, minden egyes fel­
használó a megvásárolni kívánt termékek egyedi készletét kezeli.
Amikor új felhasználó jelentkezik be a webalkalmazásba, a .NET-futtató­
kömyezet automatikusan a felhasználóhoz rendel egy egyedi munkamenet­
azonosítót, amely a kérdéses felhasználót egyértelműen azonosí�a. A rend­
szer minden munkamenet-azonosítóhoz a Ht:t:psessionst:at:e típus egyedi
példányát rendeli, amely felhasználóspecifikus adatokat rögzít. A munka­
menetadatok beszúrása és lekérdezési szintaxisa megegyezik az alkalmazás­
adatok kezelésével:

ll Az akt:uális felhasználóhoz t:art:ozó munkamenet:-vált:ozók


ll hozzáadása és lekérése.
Session["Desiredcarcolor"] "Green";
=

st:ring color = (st:ring) session["Desiredcarcolor"];

A Ht:t:pAppli cat:i on-leszármazott típus lehetövé teszi, hogy kezeljük egy mun­
kamenet indítását és leállítását a session_St:art:() és a session_End() ese­
ménykezelők segítségéveL A session_St:art:() metódusban szabadon létre­
hozhatunk tetszőleges felhasználónkénti adatelemet, míg a session_End()
biztosí�a azon feladatok végrehajtását, amelyeket a munkamenet lezárásakor
kell elvégeznünk:

<%@ Applicat:ion Language="C#" %>

void session_st:art:(Object: sender, Event:Args e)


{
ll Oj munkamenet:! Előkészülünk, ha szükséges.
}

void session_End(Object: sender, Event:Args e)


{
ll A felhasználó kijelent:kezet:t:lt:úllépt:e az időkorlát:ot:.
ll Lezárjuk a munkamenet:et:, ha szükséges.
}

A Ht:t:pAppl icat:i onst:at:e típushoz hasonlóan a Ht:t:psessionst:at:e tetszőleges


típust tárolhat, többek között az egyedi osztályainkat is. Tételezzük fel, hogy
van egy új webalkalmazásunk (sessionst:at:e) , amely a usershoppingcart: ne­
vű osztályt definiálja:

825
33. fejezet: ASP.NET állapotkezelési t echnikák

public class usershoppingcart


{
public string desiredcar;
public string desiredcarcolor;
public float downPayment;
public bool isLeasing;
public DateTime dateOfPickup;
public override string ToString()
{
return string.Format
("car: {O}<br>Color: {l}<br>$ Down: {2}<br>Lease:
{3}<br>Pick-up Date: {4}",
desiredcar, desiredcarcolor, downPayment, isLeasing,
dateOfPickup.ToShortDatestring());
}
}

A session_start() eseménykezelőben minden felhasználóhoz a usershopping­


eart osztályújabb példányát rendelhetjük

void session_Start(Object sender, EventArgs e)


{
session["usershoppingcartrnfo"] = new usershoppingcart();
}

Ahogy a felhasználó a különböző oldalak között navigál, minden oldalon le­


kérhetjük a felhasználó usershoppingcart példányát, és kitölthetjük a mező­
ket a felhasználóspecifikus adatokkal. Tételezzük fel például, hogy van egy
egyszerű * . aspx lapunk, amely bemeneti vezérlőelemek készletét definiálja.

A vezérlők lllindegyike a usershoppingcart típus mezőinek felel meg, Button­


nal állítjuk be az értékeket, és két Labellel jelenítjük meg a felhasználó mun­
kamenet-azonosítóját és a munkamenet-információkat (lásd a 33. 7. ábrát).
A gomb kiszolgálóoldali kattintási eseménykezelője magáért beszél (ki­
szedjük az adatokat a szövegdobozokból, és megjelenítjük a kosár adatait a
címkéken):

protected void btnsubmit_click(object sender, EventArgs e)


{
ll Aktuális felhasználói beállítások meghatározása.
usershoppingcart eart =
(usershoppingcart)Session["usershoppingcarunfo"];
cart.dateofPickup = myCalendar.selectedDate;
cart.desiredcar = txtcarMake.Text;
cart.desiredcarcolor = txtcarcolor.Text;
cart.downPayment = float.Parse(txtDownPayment.Text);
cart.isLeasing = chkisLeasing.checked;
lbluserrnfo.Text = cart.ToString();
session["usershoppingcarunfo"] = eart;
}

826
Munkamenetadatok kezelése

/i:>efau�t.aspxt��:J.� � x

.
Fun with Session State

. Which Make,

Down PaymenL
lasp:Ched<Box#chklsLeasi1gl
[r teasej
Delivery Date:

l\LHJUSt 2 UO l :;-

.... Se

30 31 l 2 3 4

5 6 7 8 9 10 ll

12 13 14 15 16 17 18

19 20 21 22 23 24 25

26 27 . 29 30 31

ls;;-�
[lbiUseriD]
[lb1User1nfo]

i 4

l Q Design l o Split l E3 Source l �l<asp:Check8ox#chldsleasing>l [8


3 3. 7. ábra: A munkamenet-alkalmazás grafikus felülete

A sessi on_End() metódusban a usershoppi ngcart mezőit rögzíthe�ük az adat­


bázisban vagy valahol (a fejezet végén azonban lá�uk majd, hogy az ASP.NET
Profile API ezt a feladatot automatikusan végrehaj�a). Valamint célszerű a ses­
si on_Error() megvalósításával elkapni a hibás bemenetet (vagy a Default. aspx

oldalon különféle ellenőrzési vezérlőelemek alkalmazásával gondoskodhatunk


az ilyen a felhasználói hibák kivédéséről).
Mindenesetre, ha két vagy három példányban indí�uk el a választott
böngészőt ugyanazzal az URL-lel (másolás/beillesztés műveletekkel, aho­
gyan azt a gyorsítótár példájában tettük), azt tapasztaljuk, hogy minden fel­
használó egyedi kosarat készíthet, amelyek mindegyike a Httpsessi onstate
típus egyedi példányára képezhető le.

827
33. fejezet: ASP.NET állapotkezelési technikák

A HttpSessionState további tagjai

A HttpsessionState osztály a típusindexelőn kívül több érdekes taggal ren­


delkezik. Először is, a session ID tulajdonság az aktuális felhasználó egyedi
azonosítóját adja vissza. Ha például szeretnénk megtekinteni a példában auto­
matikusan kiosztott munkamenet-azonosítót, a következőképpen kezeljük az
oldal Load eseményét:

protected void Page_Load(object sender, Event:Args e)


{
lbluseriD.Text = string.Format("Here is your ID: {O}",
session.sessioniD);
}

A Remove() és a RemoveAll() metódusokkal törölhetjük a felhasználó Http­


session state példányának elemeit:

Session.Remove["someitemweoontNeedAnymore");

A HttpSessionstate típus tagkészletet definiál, amely az aktuális felhasználói


munkamenet lejárati házirendjét szabályozza. Az alapértelmezés szerint min­
den felhasználói munkamenet HttpSessionstate objektumát 20 perc inaktivi­
tás után a rendszer megsemmisíti. Így, ha egy felhasználó belép a webalkal­
mazásba (és egyedi munkamenet-azonosítót kap), de 20 percen belül nem tér
vissza a webhelyre, a futtatókörnyezet feltételezi, hogy a felhasználót már
nem érdekli az oldal, és az összes munkamenettel kapcsolatos adatot törli. Az
alapértelmezett 20 perces lejárati értéket akár felhasználónként szabadon
módosíthatjuk a Timeout tulajdonság segítségéveL Ezt leggyakrabban a ses­
sion_start() metódusunk hatókörében lehet megtenni:

void session_start(object sender, Event:Args e)


{
ll Minden felhasználói munkamenet 5 perc inaktivitás után
ll megsemmisül.
session.Timeout 5; =

session["usershoppingcartinfo"] new usershoppingcart();


}

Megjegyzés Ha nem kell minden felhasználó Ti meout: értékét módosítani, a web. c onfi g
fájlban (amelyet a fejezet végén megvizsgálunk j a <sessi on state> elem Timeout attribú·
tumával az összes felhasználó számára módosíthatjuk az alapértelmezett 20 perces értéket.

828
A sütikről

A Timeout tulajdonság előnye az, hogy külön-külön rninden felhasználóhoz


egyedi időkorlátértéket állíthatunk be. Képzeljük el például, hogy a webalkal­
mazásunk lehetövé teszi a felhasználók számára, hogy készpénzért tagsági
szintet vásároljanak. Az arany fokozatú tagok munkamenete egy órán belül jár
.le, :rpíg a legalacsonyabb szinten a tagok csak 30 másodpercet kapnak. Felme-
rül a kérdés, hogy hogyan tárolhatunk felhasználóspecifikus adatokat (rnint
például az aktuális tagsági szintet) a látogatások között. Az egyik lehetséges
válasz a Httpcooki e típus használata.

Forráskód A SessionState kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A sütikről

A következő állapotkezelési módszer, amelyet megvizsgálunk, a felhasználói


adatok sütikben történő tárolása, amelyet gyakran a felhasználó gépén egy
szövegfájl (vagy több fájl) segítségével valósítunk meg. Amikor a felhasználó
bejelentkezik egy meghatározott webhelyre, a böngésző megvizsgálja, hogy a
felhasználó számítágépén létezik-e a kérdéses URL-hez sütifájl, és ha igen,
hozzáfűzi-e a fájl adatait a HTIP-kéréshez.
A fogadó kiszolgálóoldali weblap beolvassa a süti adatait, és az aktuális
felhasználói beállításoknak megfelelő grafikus felületet hoz létre. Biztosan
tapasztaltuk már, hogy amikor meglátogatjuk az egyik kedvenc webhelyün­
ket, az valahonnan "tudja", hogy milyen tartalmat szeretnénk megtekinteni.
Ennek (részben) a számítógépünkön tárolt süti az oka, amely a meghatáro­
zott webhely számára érdekes információkat foglal magában.

Megjegyzés A sütifájlok pontos helye attól függ, hogy milyen böngészőt és milyen operációs
rendszert használunk.

Az adott sütifájl tartalma URL-enként változó, de ne feledjük, hogy ezek vé­


gül is csak szövegfájlok. Ezért a süti a lehető legrosszabb választás, ha bizal­
mas információkat kell tárolnunk az aktuális felhasználóról (rnint például hi­
telkártyaszámot, jelszót stb.). Még akkor is, ha időt szakítunk az adatok titko­
sítására, egy alattomos hacker visszafejtheti az értékeket, és rossz célokra
használhatja.

829
33. fejezet: ASP.NET állapotkezelési technikák

Mindenesetre a sütik fontos szerepet játszanak a webalkalmazások fejlesz­


tésében, úgyhogy nézzük meg, hogy az ASP.NET hogyan dolgozik ezzel az
állapotkezelési módszerrel.

Sütik létrehozása

Mindenekelőtt nézzük meg az ASP.NET-sütik két fajtáját: az állandó és az


ideiglenes sütiket Az állandó sütiket tekintjük a sütiadatok hagyományos
meghatározásának, mivel a böngésző a névl érték párokat fizikailag a fel­
használó merevlemezére menti. Az ideiglenes süti (vagy munkamenetsüti)
ugyanolyan adatokat tartalmaz, mint az állandó süti, de a névl érték párokat
a böngésző soha nem menti a merevlemezre; az adatok csak a HITP­
fejlécben találhatók meg. Amikor a felhasználó bezárja a böngészőt, a
munkamenetsüti minden adata megsemmisül.

Megjegyzés A legtöbb böngésző legfeljebb 4096 bájtos sütifájlok használatát támogatja. A mé­
retkorlát miatt a sütikben leginkább kis adatmennyiséget, például felhasználói azonosítót célsze­
rű tárolni, amelynek segítségével azonosíthatjuk a felhasználót, és lekérdezhetjük az adatait az
adatbázisbóL

A sütiadatokat (akár állandó, akár ideiglenes) a kiszolgálóoldalon a system. web.


Httpcooki e osztálytípus képviseli. Új sütit a Response . cooki es tulajdonság segít­

ségével hozhatunk létre. Ha az új Httpcooki e objektumot beillesztjük a belső


gyűjteménybe, a névl érték párokat a InTP-fejlécben megkapja a böngésző.
A sütik viselkedésének megismerésére hozzunk létre új ASP.NET-web­
alkalmazást (CookieStateApp néven), és alakítsuk ki a 33.8. ábrán látható fel­
használói felületet.
Az első gomb kattintási eseménykezelőjében hozzunk létre új Httpcooki e ob­

jektumot, és adjuk hozzá a cook i e gyűjteményhez, amely a HttpRequest. cooki es


tulajdonsághoz biztosít hozzáférést. Ne feledkezzünk meg arról, hogy az adato­
kat a rendszer nem tárolja a felhasználó merevlemezén, hacsak explicit módon
nem állítjuk be a lejárat idejét a HttpCooki e. Expi res tulajdonság használatával.
A következő megvalósítás ideiglenes sütit hoz létre, amelyet a rendszer meg­
semmisít, amikor a felhasználó leállítja a böngészőt:

830
A sütikről

protected void btncookie_click(object sender, EventArgs e)


{
ll Ideiglenes süti létrehozása.
Httpcookie thecookie =

new HttpCookie(txtCookieName.Text,
txtcookievalue.Text);
Response.Cookies.Add(thecookie);
}

_/l)ebu�Uspxl x

1

-
run with cookies
·---- ---·- ---- .

llCookie Name
! �------
[Cookie Value
i
l
'
Write This Cookie
·
_
. ··-·- ·-- -·-·-----· · ----,
tr- ---
Show Cookie Data -,
l l
!�<:;��P��L--.---.-·. .----···--·-- .. . . .J
·-- ··-----·-----�----- -·-··----·------ ----..··-·-··-·--

l Q Design l <:l Split l 8 Source l EJ lB


33.8. ábra: A CookieStateApp alkalmazás felhasználói felülete

A következő metódus állandó sütit hoz létre, amely 2009. március 24-én jár le:

protected void btncookie_click(object sender, EventArgs e)


{
Httpcookie thecookie =

new HttpCookie(txtcookieName.Text,
txtcookievalue.Text);
thecookie.Expires = DateTime.Parse("03I24I2009");
Response.cookies.Add(thecookie);
}

Bemeneti sütik adatainak olvasása

Emlékezzünk rá, a böngésző feladata, hogy az állandó sütikhez hozzáférjen,


amikor egy korábban meglátogatott oldalhoz navigálunk ASP.NET alatt a
bemeneti süti adataival a HttpRequest. cookies tulajdonság hozzáférésével
dolgozhatunk Ennek bemutatásához a másik gomb kattintási eseménykeze­
lőjét a következőképpen valósítsuk meg:

831
33. fejezet: ASP.NET állapotkezelési technikák

protected void btnshowcookie_Click(object sender, EventArgs e)


{
string cookieData = "";
foreach (string s in Request.cookies)
{
cookieData +=
string.Format("<li><b>Name</b>: {O}, <b>Value</b>:
{1}</li>", s, Request.Cookies[s].value);
}
lblcookieData.Text = cookieData;
}

Ha most futtatjuk az alkalmazást, és az új gombra kattintunk, azt látjuk, hogy


a süti adatait a böngésző valóban elküldte (lásd a 33.9. ábrát).

� Untitled Page - Windows Intemet Explorer l l @) llii3liíl


=

00· jiJ http:/nocalhost551


-- ----�-------- ---·-
•J [X j j
+�
--
Goog!<
-------

1:1 <Jil l�
li�·
»

Untitled Page

Fun \vith Coekies

Cooltie Name: MyData

Cooltie VaJue: Testing ...

l Write This Cookie l

l Show Cookie Data l


• Name: MyData, Vahle: Testing...
T

'\; lcx:al intranet l Proleeted Mode: Off �100% .

33.9. ábra: A süti adatának megtekintése

Forráskód A CookieStateApp kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A <sessionState> elem szerepe

A fejezet során példák segítségével megvizsgáltuk, hogyan tárolhatunk in­


formációkat a felhasználóinkróL Mint láthattuk, a nézetállapotot, az alkalma­
zás-, a gyorítótár-, a munkamenet- és a sütiadatokat nagyjából egyformán

832
A <sessionState> elem szerepe

kezeljük (az indexelő segítségéve!). Azt is láthattuk, hogy a HttpApplication


típus segítségével elfoghatunk és reagálhatunk a webalkalmazás élettartama
során bekövetkezett eseményekre.
Az alapértelmezés szerint az ASP.NET a munkamenet-állapotot a folya­
maton belül (in-prae) tárolja, amelyet az ASP.NET feldolgozó folyamata
(aspnet_wp.exe) hasztoL A *.dll fájlokhoz hasonlóan ennek előnye, hogy az
információ hozzáférése a lehető leggyorsabb. Viszont hátránya, hogy ha az
alkalmazástartomány bármilyen okból leáll, minden felhasználó állapotadata
megsemmisül. Továbbá, ha az állapotadatokat folyamatbeli *.dll-ben tárol­
juk, nem dolgozhatunk együtt hálózati webfarmmaL Rögzítsük az alapértel­
mezés szerinti viselkedést a mach i ne.con fig fájlunk <sessionstate> elemében
a következőképpen:

<sessionstate
mode="InProc"
stateconnectionstring="tcpip=127.0.0.1:42626"
sqlConnectionstring="data source=127.0.0.1;Trusted_connection=yes"
cookieless="false"
timeout="20"
/>

Az adattárolás alapértelmezett módja jól használható, ha a webalkalmazást


egyetlen webkiszolgáló hosztolja. Gyaníthatjuk, hogy a modell nem ideális
webkiszolgáló-farmok esetén, mivel a munkamenet-állapotot az alkalmazás­
tartomány "zárolja".

Munkamenetadatok tárolása ASP.NET


munkamenet-állapotkiszolgálón

Az ASP.NET alatt utasíthatjuk a futtatókömyezet, hogy a munkamenet-álla­


pot *.dll-t helyettesítő folyamatban, az ASP.NET munkamenet-állapotki­
szolgálóban (aspnet_state. exe) hosztolja. Ha így teszünk, a *.dll fájlt áthe­
lyezhetjük az aspnet_wp.exe folyamatból egyedi *. exe fájlba, amelyet a web­
farm bármely számítágépén tárolhat. Ha az aspnet_state.exe folyamatot
ugyanazon a számítógépen futtatjuk, mint a webkiszolgálót, kihasználhatjuk
az előnyt, hogy az állapotadatokat egyedi folyamatban különítjük el (mivel
ez tartósabb megoldás).
A munkamenet-állapotkiszolgáló használatának első lépéseként indítsuk
el a célszámítógépen az aspnet_state.exe Windows-szolgáltatást. A követke­
zőt kell beírnunk a parancssorba:

833
33. fejezet: ASP.NET ál lapotkezelési technikák

net start aspnet_state

Az aspneLstate. exe szolgáltatást a 33.10. ábrán látható módon elindíthatjuk a


Control Panel Adrrúnistrative Tools mappájából, a Services kisalkalmazásbóL

- .!IL -- - - .- � -- -- -·�-..--- ·-- --�


'- ':'g:m :t: • �g ..e . ... []!l C're.r ..
&i E r. �d .; i?t!rn ...

Name Date modified


Favorite li nb

More n

Folders fl
• Desktop Data Sources Event Viewer Intemett Intemet
(ODBC) Jnformatio... Informatio ...
, Andr� Troelsen
,M Public
Computer
� Network
11!1 Control Panel Local Memory Microsoft Print
� ·Ad�-i�istrat�e T�ls Securi... Oiagnosti... .NIT Fram... Management

/!ij AutoPiay
\1.t Baclc:up and R6tore Center
't Bitloeker Drive Encryption
Defau� Programs Reliabilíty
Q Ease of Access Center and Perfor...
Fonts
�·_ tJ.P.twnd-_ ;m tS.hMinnLI!'!ntP.r -'---
r __ _•

Services Date mcdífied: 11/2/2006 7:52AM


_____.."...,
Date created: 1112/2006 7:52AM
_ ...."
",.."
_ __ �r,i
':,'""'-'
..-.h Shortcut Size; 1.64 KB

33.1 O. ábra: Az aspnet_state.exe indítása a Services kisalkalmazás segítségével

A megközelítés alapvető előnye, hogy a Properties ablak segítségével beállít­


hatjuk, hogy az aspneLstate. ex e szolgáltatás automatikusan elinduljon,
arnikor a számítógépet bekapcsoljuk Ha a munkamenet-állapotkiszolgáló
már fut, adjuk a web. config fájlunkhoz a következő <sessionstate> elemet:

<sessionstate
mode="Stateserver"
stateconnectionstring="tcpip=127.0.0.1:42626"
sqlconnectionstring="data source=127.0.0.l;Trusted_connection=yes"
cookieless="false"
timeout=" 20"
/>

Itt a mode attribútumot stateserver értékre állítottuk. Ennyi az egész. Ekkor a


CLR a munkamenettel kapcsolatos adatokat az aspneLstate. exe folyamatban

tárolja. Így, ha a webalkalmazást hasztoló alkalmazástartomány leáll, a mun­


kamenetadatok megmaradnak. Vegyük észre, hogy a <sessionstate> elem tá­
mogathatja a stateconnectionstring attribútumot. Az alapértelmezett TCP /IP

834
A <sessionState> elem szerepe

cím érték (127.0.0.1) a helyi gépre mutat. Ha azt szeretnénk, hogy a .NET­
futtatórendszer másik hálózati gépen található aspnet_state. ex e szolgáltatást
használjon (gondoljunk a webfarmokra), az értéket nyugodtan módosíthatjuk.

Munkamenetadatok tárolása dedikált adatbázisban

Végül, ha a legmagasabb szintű elszigetelést és a legnagyobb megbízhatóságot


kell a webalkalmazásunk számára biztosítani, utasíthatjuk a futtatókömyezetet,
hogy az összes munkamenetállapot-adatot a Microsoft SQL Server adatbázisban
tárolja. A web.config fájlban az alábbi egyszerű módosítást kell elvégezni:

<sessionstate
mode="SQLServer"
stateconnectionstring="tcpip=127.0.0.1:42626"
sqlconnectionstring="data source=127.0.0.l;Trusted_Connection=yes"
cookieless="false"
timeout=" 20"
/>

Mielőtt elindítanánk a webalkalmazást, győződjünk meg arról, hogy a célszámí­


tógépen (amelyet az sqlconnectionstring attribútum határoz meg) elvégeztük a
megfelelő beállításokat. A .NET Framework 3.5 SDK (vagy a Visual Studio 2008)
telepítése során az rnstallsqlstate.sql és az uninstallsqlstate.sql fájl is a
számítógépre kerül, amelyek az alapértelmezés szerint a C:\Windows\Micro­
soft.NET\Framework\ <verzió> könyvtárban találhatók A célszámítógépen fut­
tatnunk kell az rnstallsqlstate.sql fájlt például a Microsoft SQL Server Mana­
gement Studio programhoz hasonló eszközzel (amelyet a Microsoft SQL Server
2005 alkalmazással együtt áll a rendelkezésünkre).
Miután az SQL-szkript lefutott, láthatjuk, hogy létrehoztunk egy új SQL
Server adatbázist (ASPState), amely az ASP.NET-futtatórendszer által meg­
hívott tárolt eljárásokat, valamint a munkamenetadatokat tároló táblák kész­
letét foglalja magában (valamint a tempdb adatbázis kiegészült néhány táb­
lával, amelyek a cserét segítik majd). Sejthetjük, hogy a leglassabb lehetőség,
ha webalkalmazásunkban a munkamenetadatok SQL-adatbázisban történő
tárolását állítjuk be. A lehetőség előnye az, hogy a felhasználói adatok rend­
kívül tartósak (az adatok a webkiszolgáló újraindítása után sem vesznek el).

Megjegyzés Ha ASP.NET munkamenet-á l lapotkiszolgálón vagy SQL-kiszolgálón tároljuk a


munkamenet adatait, minden egyedi típust meg kell jelölnünk a [Se ri al i zabl e] attribú­
tummal, amelyet a Httpsessi onstate objektumban szeretnénk tárolni.

835
33. fejezet: ASP.NET átlapotkezelési technikák

Az ASP. NET profil-APl-ja

A fejezetben eddig több olyan módszert megvizsgáltunk, amellyel felhaszná­


ló- és alkalmazásszintű adatokat tárolhatunk. Ezen módszerek komoly hát­
ránya, hogy az adatokat csak addig őrzik meg, amíg a munkamenet él, vagy
a webalkalmazás fut. Sok webhelyen fontos követelmény, hogy a felhaszná­
lói adatokat munkamenetek között rögzítsük. Szükség lehet például arra,
hogy lehetövé tegyük felhasználói fiókok létrehozását a webhelyen. Előfor­
dulhat, hogy a shoppi ngcart osztály példányait szeretnénk tárolni munka­
menetek között (például online webáruházban). Esetleg a felhasználói felü­
lettel kapcsolatos alapvető beállításokat szeretnénk rögzíteni (témák stb.).
Bár ennek tárolására kialakíthatnánk egy egyedi adatbázist (több tárolt el­
járással), de akkor egyedi kódkönyvtárakat kellene fejlesztenünk az adatbá­
zis-objektum kezelésére. Ez nem feltétlenül bonyolult feladat, de a lényeg,
hogy ezt az infrastruktúrát akkor is saját magunknak kell kifejlesztenünk.
Hogy egyszerűbbé tegye az életünket, az ASP.NET erre a célra kész felhasz­
nálóiprofil-kezelő API-t és adatbázisrendszert biztosít számunkra. A szükséges
infrastruktúra biztosításán túl a profil-API lehetőséget nyújt arra, hogy a tárol­
ni kívánt adatokat közvetlenül a web. confi g fájlban definiáljuk (az egyszerűség
kedvéért); ennek ellenére bármilyen [Serializable] típust rögzíthetünk. Mie­
lőtt nagyon előre szaladnánk, nézzük meg, hogy a profil-API hol tárolja a
meghatározott adatokat.

Az ASPNETDB.mdf adatbázis

Minden Visual Studio 2008 segítségével készült ASP.NET-webhelyen a rendszer


automatikusan létrehozza az App_Data alkönyvtárat. Alapértelmezés szerint a
profil-API (egyéb szolgáltatásokhoz hasonlóan, mint például az ASP.NET sze­
repkörtagság-API-ja, amelyet a könyvben nem vizsgálunk) az App_Data map­
pában található helyi ASPNETDB. mdf nevű SQL Server 2008 adatbázist használja.
Ezt az alapértelmezett viselkedést a·helyi számítógépen az aktuális .NET-telepí­
tés machi ne. config fájljának beállítása határozza meg. Ha a programunk olyan
ASP.NET-szolgáltatást használ, amelynek az App_Data mappára van szüksége,
az ASPNETDB. mdf adatfájl menetközben automatikusan létrejön, ha az még nem

létezett.

836
Az ASP.NET profil·API·ja

Ha azt szeretnénk, hogy az A SP.NET-futtatókömyezet másik hálózati


számítógépen tárolt ASPNETDB. mdf fájllal kommunikáljon, vagy az adatbázist
inkább SQL Server 7.0 (vagy újabb verziójú) adatbázis-kezelőre telepítenénk,
az ASPNETDB. mdf fájlt manuálisan kell létrehoznunk az aspnet_regsql.exe pa­
rancssori segédprogram segítségéveL Mint minden jó parancssori eszköz, az
aspnet_regsql.exe is több opcióval rendelkezik, de ha az eszközt argumen­
tumok nélkül futtatjuk, így:

aspnet_regsql

akkor a grafikus felülettel rendelkező varázsló végigvezet az ASPNETDB.mdf


számítógépünkre (és a kívánt verziójú SQL-kiszolgálóra) történő létrehozá­
sának és telepítésének folyamatán.
Ha a webhelyünk nem az adatbázis helyi másolatát használja az App_Data
mappában, utolsó lépésként módosítsuk a web.config fájlt, hogy az ASPNETDB.mdf
fájl egyedi helyére mutasson. Tételezzük fel, hogy az ASPNETDB.mdf adatbázist a
ProductionServer nevű számítógépen telepítettük. Az alábbi web.config fájlrész­
let (a webhely neve ShoppingCart) segítségével elmagyarázha�uk a profil-API­
nak, hol keresse a szükséges adatbáziselemeket

<configuration>
<connectionstrings>
<add name="Sqlservices"
connectionstring ="Data Source=Productionserver;Integrated
security=SSPI;Initial catalog=aspnetdb;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.web>
<profile defaultProvider ="SqlProvider">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider"
connectionstringName="Localsqlserver"
applicationName="ShoppingCart"
type="System.web.Profile.sqlProfileProvider,
System.web,
version=2.0.0.0,
culture=neutral, PublicKeyToken=b03f5f7flld50a3a" />
</providers>
</profile>
</system.web>
</configuration>

837
33. fejezet: ASP.NIT állapotkezelési technikák

A legtöbb *.config fájlhoz hasonlóan ez is sokkal bonyolultabbnak tűnik a


valóságos helyzetnéL Gyakorlatilag megadjuk a <connection string> elemet a
szükséges adatokkal együtt, majd az Sql ProfileProvi der megnevezett pél­
dányát (ezt az alapértelmezett szolgáltatót használjuk attól függetlenül, hogy
fizikailag hol található az ASPNETDB. mdf fájl). A konfigurációs szintaxis továb­
bi részleteiért lapozzuk fel a .NET Framework 3.5 SDK dokumentációját.

Megjegyzés Az egyszerűség kedvéért a példában feltételezzük, hogy a webalkalmazás App_Data


alkönyvtárában található automatikusan generált ASPNETDB. mdf adatbázist használjuk.

Felhasználói profil meghatározása


a Web.config fájlban

Amint azt már korábban említettük, a felhasználói profilt a web . config fájl­
ban definiáljuk Igazán tetszetős ebben a megközelítésben, hogy a profilt az
örökölt Profile tulajdonság segítségével erősen típusos módon kezelhetjük
Ennek bemutatására hozzuk létre a FunWithProfiles új webhelyet, és nyissuk
meg we b . config fájlját szerkesztésre.
Az a célunk, hogy létrehozzunk egy profilt, amely a bejelentkezett felhasz­
nálők otthoni címét és az általuk kezdeményezett adatküldések számát tartja
nyilván. Nem meglepő, hogy a profil adatait a <P rofile> elemben kell megadni
névj érték típus párok formájában. Nézzük a következő profilt, amelyet a
<system.web> elem hatókörében hoztunk létre:

<profil e>
<properties>
<add name="StreetAddress" type="System.String" />
<add name="City" type="System.string" />
<add name="State" type="System.string" />
<add name="TotalPost" type="System.Int32" />
</properties>
</profil e>

Itt meghatároztunk egy nevet és CLR-adattípust a profil minden elemének


(természetesen további elemeket vehetnénk fel az irányítószám, név stb. táro­
lására, de a példa így is érthető). Valójában a type attribútum opcionális; az
alapértelmezett típus a system.string. Elvárásainknak megfelelően több att­
ribútumot megadhatunk a profilbejegyzésben, amelyekkel tovább finomít­
hatjuk, hogy az ASPNETDB.mdf hogyan rögzítse az információkat. A 33.4. táb­
lázat bemutat néhány alapvető attribútumot.

838
Az ASP.NET profil·API·ja

allowAnonymous True l False Ez az attribútum korlátozza vagy engedé­


lyezi az érték névtelen hozzáférését. Ha az
értéke fa l se, a névtelen felhasználók nem
férhetnek hozzá az adott profilértékhez.

defaultvalue String Ez az attribútum megadja a visszatérési ér­


téket, ha a tulajdonságot nem állítottuk be.

name String Ez az attribútum a tulajdonság egyedi azo­


nosítója.

provider String Ez az attribútum az érték kezeléséhez hasz­


nált szolgáltató, felülbírálja a web. con fig
vagy mach i ne. config fájl defaultPro-
vider beállítását.

readonly True l False Ez az attribútum korlátozza vagy engedé­


lyezi az írási hozzáférést.

serializeAs String l XML l Ez az attribútum az adattárban rögzített ér­


Binary tékek formátuma.

type Primitív l Ez az attribútum .NET-primitív-típus vagy


Egyedi típus -osztály. Az osztály teljesen meghatározott
nevét kell megadni (pl. MyApp. userData.
Col orPrefs).

33 4 táblázat: A profiladatok legfontosabb attribútumai


. .

Az aktuális profil módosítása során látjuk majd az attribútumokat működés


közben. Addig is tekintsük meg, hogyan érhetjük el ezeket az adatokat a
weblapokról programozottan.

Profi ladatok hozzáférése programozottan

Emlékezzünk rá, hogy az ASP.NET-profil egyetlen célja, hogy automatizálja


az adatok dedikált adatbázisba írásának (és az adatok olvasásának) folyama­
tát. Ennek kipróbálására módosítsuk a Default. aspx fájl felhasználói felületét:
adjunk hozzá néhány szövegdobozt (magyarázó címkékkel együtt), ame­
lyekben a felhasználó címét (utca, város, ország) kérjük be. Valamint adjunk
hozzá egy gombot (btnSubmit néven) és egy utolsó címkét (lblUserData né­
ven), amely a 33.11. ábrán látható módon a rögzített adatokat jeleníti meg.

839
33. fejezet: ASP.NET állapotkezelési technikák

•X

F un
_
_ _w
__.tl__
h_P_r_
o_fi_I_
es _____________ ·�
=·l!i
Street Addres . 'i

City l :1

1
State l
asp:Button#btnSt.brJj;:

1:Subnit Data -�
[Jb!UserData]

l •

'iQ Design l t:1 Split l 8 Source l 8JI<asp:Button#btnSubmit>l

33.11. ábra: A FunWithProfiles Default.aspx lapjának felhasználói felülete

·
<\:.Defauk j"'btnSubmit.Ciick(object sender, Ev< l
-:'
• •

procected void Page_Load(object s�nder, Eve:


{

protected void btnSubmit Click(object sende:


_
l
ll Databa�e write� happeninq here!

..:.�;
Profile.City- txtCity.Text;
Profile.StreetAddre�s

= txtStreetAddress.� r .
'

l:;! lastUpdatedDate
2{ Prorile.
zs!
26! J

5' Properties
27;
'fff PropertyValues l
5' Providers
•'> Save !

·���
'V SetPropertyValue r:' '-
!Tli'·"
StreetAddress
"!ft -!

-��-�---·.!
·�ToString

33.12. ábra: A profiladatok erősen típusosak

A gomb kattintási eseménykezelőjében az örökölt Profi l e tulajdonság segít­


ségével mentsük el a profilinformációkat, amelyeket a felhasználó a kapcso­
lódó szövegdobozban meghatározott. Mint azt a 33.12. ábrán látha�uk, a Vi­
sual Studio 2008 minden profiladatot erősen típusos tulajdonságként biztosít.
Valójában a web. confi g fájl segítségével definiáltuk az egyedi struktúrát.

840
Az ASP.NET profii·API·ja

Miután az összes adatot az ASPNETDB.mdf fájlban rögzítettük, olvassuk ki az


adatrészleteket az adatbázisból, majd formázzunk string típusként, amelyet a
lbluserData címkén jelenítünk meg. Végül kezeljük az oldal Load eseményét, és

jelenítsük meg ugyanezen adatokat a címkén. Így, amikor a felhasználó meglá­


togatja az oldalt, láthatja az aktuális beállításait. Íme, a teljes kódfájl:

public partial class _Default : System.web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{
GetuserAddress();
}
protected void btnsubmit_click(object sender, EventArgs e)
{
ll Itt történik az adatbázisírás!
Profile.city = txtCity.Text;
Profile.streetAddress = txtStreetAddress.Text;
Profile.state = txtState.Text;

ll Beállítások beolvasása az adatbázisból.


GetUserAddress();
}

private void GetuserAddress()


{
ll Itt történik az adatbázis-olvasás!
lbluseroata.Text = String.Format("You live here: {O}, {1}, {2}",
Profile.StreetAddress, Profile.city, Profile.state);
}
}

Ha lefuttatjuk a programot, azt tapasztaljuk, hogy az Default.aspx első letöltése


előtt hosszú késleltetés van. Ennek az az oka, hogy a rendszer menetközben hoz­
za létre az ASPNETDB.mdf fájlt, és az App_Data mappába helyezi. Ezt magunk is
ellenőrizhetjük, ha frissítjük a Solution Explorer ablakot (lásd a 33.13. ábrát).

l l!:!' C:\...\FunWrthProfiles\
8- b App_Data
l rfl... [] t·$1Q�IaUl:IXI•1j
) $ .. @] �efault.aspx
j \ :.... � Defau�.aspx.cs
l L. [!} web.config


"
� Solution Explorer§Ct�

33.13. ábra: ime az ASPNETDB.mdf

841
33. fejezet: ASP.NET állapotkezelési technikák

Továbbá azt tapasztaljuk, hogy az oldal első megnyitásakor az lbluserData


címke nem jelenít meg semmilyen profiladatot, mivel még nem írtuk be az
adatainkat az ASPNETDB. mdf adatbázis megfelelő táblájába. Miután beírjuk az
adatokat a szövegdobozba, és visszaküldjük a kiszolgálónak, a 33.14. ábrán
látható módon a címke a rögzített adatokat jeleníti meg.

� Untitled Page • Windows Intemet Explo� 1=1@1-.:.


@0 liJ htt!'�����-[>:<J l Googie
• _
_
______

'Ci � l�
»

[]_�. §'!
Untitled Page ... �.
�-

Pun with Profiles

Street Address 123 Happy Lane

City Grand Marais

State Minnesota

l Submit Data l
You live here: 123 Happy Lane, Grand Marais, Minnesota

� local intranet l Protocted Mode: Off Efi.lOO% .

33.14. ábra: A rögzítettfelhasználói adataink

A technológia legérdekesebb része csak most következik. Ha bezárjuk a böngé­


szőt, és újra meglátoga�uk a webhelyet, azt lá�uk, hogy a korábban megadott
profiladatokat valóban rögzítettük, és a címke a megfelelő információkat mutat­
ja. Felmerülhet a kérdés, hogy hogyan azonosított bennünket az alkalmazás.
Példánkban a profil-API a Windows hálózati azonosítót használta, ame­
lyet az aktuális azonosítóadatokból nyert ki. Ha nyilvános webhelyet fejlesz­
tünk (ahol a felhasználók nem tagjai egy meghatározott tartománynak), nyu­
godtak lehetünk, hogy a profil-API együttműködik az ASP.NET Forms-alapú
hitelesítési modelljével, és támogatja a "névtelen profilok" fogalmát, amelyek
használatával azon felhasználók profiladatait tárolhatjuk, akik pillanatnyilag
nem rendelkeznek az oldalon érvényes azonosítóval.

Megjegyzés A könyv jelen kiadása nem foglalkozik az ASP.NET biztonsági megoldásaival


(például a Forms-alapú hitelesítéssei vagy a névtelen profilokkal). További információkért la·
pozzuk fel a .NET Framework 3.5 SDK dokumentációját.

842
Az ASP.NET profil-APl-ja

Profi ladatok csoportositása és egyedi


objektumok tárolása

A fejezet végén szót kell ejtenünk arról, hogy a profiladatokat hogyan defini­
álhatjuk a web. con fig fájlban. A jelenlegi profil egyszerűen négy adatot hatá­
rozott meg, amelyeket közvetlenül a profiltípusból érhetünk el. Bonyolultabb
profilok készítése során segítséget jelenthet, ha az összetartozó adatokat
egyedi néven csoportosítjuk. Nézzük meg a következőt módosítást:

<profile>
<properties>
<group name ="Address">
<add name="StreetAddress" type="String" />
<add name="City" type="String" />
<add name="State" type="String" />
</group>
<add name="TotalPost" type="Integer" />
</properties>
</profile>

Ezúttal meghatároztuk az Address egyedi csoportot, amely a felhasználó cí­


mét (utca, város, ország) tartalmazza. Ahhoz, hogy az oldalainkon az adato­
kat elérjük, a forráskódot módosítanunk kell, és a részelemek beolvasásához
meg kell adnunk a Profile.Address objektumot. Például a GetuserAddress()
metódust a következőképp kell módosítani (a gomb kattintási eseménykeze­
lőjét hasonló módon kell frissítenünk):

private void GetuserAddress()


{
ll Itt történik az adatbázis-olvasás!
lbluserData.Text = String.Format(''You live here: {O}, {1}, {2}",
Profile.Address.streetAddress,
Profile.Address.City, Profile.Address.state);
}

Megjegyzés A profilon belül tetszőleges számú csoportot létrehozhatunk. Egyszerűen defini­


áljunk több <group> elemet a <prope rt i es> hatókörön belül.

Végül pedig érdemes megemlíteni, hogy a profil egyedi objektumokat tárol­


hat (és olvashat be) az ASPNETDB. mdf adatbázisban. A példa kedvéért tételez­
zük fel, hogy egyedi osztályt (vagy struktúrát) készítünk, amely a felhasználó
címadatait képviseli. A profil-API egyetlen követelménye, hogy a típust meg­
jelöljük a [serial izable] attribútummal, például így:

843
33. fejezet: ASP.NET állapotkezelési technikák

[Serializable]
public class userAddress
{
public string Street = string.Empty;
public string city = string.Empty;
public string State = string.Empty;
}

Az osztály elkészítése után a következőképpen módosíthatjuk a profil definí­


cióját (bár nem kötelező, de a példából eltávolítottuk az egyedi csoportot):

<profile>
<properties>
<add name="Addressrnfo"
type="UserAddress"
serializeAs ="Binary"/>
<add name="TotalPost" type="Integer" />
</properties>
</profile>

Vegyük észre, hogy arnikor a [serializable] típusokat adjuk a profilhoz, a típus


attribúturna a rögzített típus teljesen meghatározott neve. Ha például ArrayL i st
típusú objektumot szeretnénk a profilban tárolni, típusként a system. collec­
tions.ArrayList értéket kell beállítanunk. A serializeAs attribútum segítségével
szabályozhatjuk, hogy az állapotadatokat rniként mentjük az ASPNETDB.mdf

adatbázisban. A Visual Studio 2008 IntelliSense segítségével láthatjuk, hogy


alapvetően bináris, XML- vagy sztringadatok közül választhatunk.
Mivel a címadat-információkat egyedi osztályként rögzítjük, a forráskó­
dot (ismét) módosítanunk kell a következőképpen:

private void GetUserAddress()


{
ll Itt történik az adatbázis-olvasás!
lbluserData.Text = String.Format("You live here: {0}, {1}, {2}",
Profile.Addressrnfo.Street, Profile.Addressrnfo.city,
Profile.Addressrnfo.state);
}

A profil jóval többet tud annál, rnint amit ebben a fejezetben bemutathatunk.
A Profile tulajdonság például valójában a Profilecommon típust foglalja magá­
ban. A típus segítségével programozottan lekérdezhetünk információt adott fel­
használóról, törölhetünk (vagy felvehetünk új) profilokat az ASPNETDB.mdf adat­
bázisból, frissíthetjük a profilt, stb.

844
Összefoglalás

Ezen kívül a profil-API működését több módon bővíthe�ük, és így optimali­


zálha�uk, hogy a profilkezelő hogyan férjen hozzá az ASPNETDB. mdf adatbázis
tábláihoz. Elvárásainknak megfelelően, többféleképp csökkenthe�ük az adatbá­
zis által visszaadott találatok számát. Ha szeretnénk többet megtudni a profil­
API-ról, lapozzuk fel a .NET Framework 3.5 SDK dokumentációját.

Forráskód A FunWithProfiles kódfájlokat a forráskódkönyvtár 33. fejezetének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Összefogla lás

Ebben a fejezetben kiegészítettük eddigi ASP.NET tudásunkat a HttpApp li cati on


típus használatának vizsgálatávaL Láthattuk, hogy a típus több alapértelmezett
eseménykezelővel rendelkezik, amelyekkel különböző alkalmazás- és munka­
menetszintű eseményeket kezelhetünk A fejezet nagyobb részében több állapot­
kezelő módszert vizsgáltunk meg. Emlékezzünk rá, hogy a nézetállapot segítsé­
gével a HTML-vezérlők automatikusan visszakapják eredeti értéküket a vissza­
küldések között. Később megvizsgáltuk az alkalmazás- és munkamenetszintű
adatok közötti különbséget, a sütik kezelését és az ASP.NET alkalmazás­
gyorsítótárat
A fejezet hátralévő részében az ASP.NET profil-API-ját ismertük meg.
Láthattuk, hogy ez a technológia kész megoldást biztosít a felhasználói ada­
tok munkamenetek közötti tárolására. A webhely web. con fi g fájljának segít­
ségével tetszőleges számú profilelemet határozhatunk meg (többek között
csoportosított elemeket és [serializable] típusokat), amelyeket az ASP.NET
automatikusan eltárol az ASPNETDB. mdf adatbázisban.

845
8. rész

Függelékek
A FÜGGELÉK

A COM és a .NET
együttműködése

A könyv célja az, hogy a C# nyelvhez és a .NET platforrn által nyújtott alap­
vető szolgáltatásokhoz szilárd alapot biztosítson. Ha szernbeállí�uk egymás­
sal a .NET által biztosított objekturnrnodellt és a Microsoft korábbi kompo­
nensarchitektúráját (COM), nyilvánvalóvá válik, hogy két teljesen egyedi
rendszerről van szó. Függetlenül attól, hogy a COM már hagyományos ke­
retrendszemek tekinthető, lehetnek olyan COM-alapú rendszereink, amelye­
ket szeretnénk az új .NET-alkalrnazásainkba integrálni.
A .NET platforrn szerencsére olyan különböző típusokat, eszközöket és
névtereket biztosít, amelyek meglehetősen egyszerűvé teszik a COM és a .NET
együttműködését. Az A függelék a .NET és a COM együttműködési folyama­
tának, valarnint a kapcsolódó RCW-nek (Runtirne Callable Wrapper -futási
időben hívható burkoló) a vizsgálatával kezdődik. A második rész az ezzel el­
lentétes szituációt vizsgálja: COM-típus kommunikál .NET-típussal a CCW
(COM Callable Wrapper-COM-ból hívható burkoló) segítségéve!.

Megjegyzés A .NIT-együttműködési réteg teljes áttekintése külön könyvet igényeine. Ha több


információra van szükségünk, mint amennyit a függelékben találhatunk, forduljunk a COM and
.NET lnteroperability (Apress, 2002) című munkához.

A .NET együttműködési képesség


hatóköre

Arnikor .NET-képes fordító használatával készítünk szerelvényeket, akkor


olyan felügyelt kódot írunk, amelyet a közös nyelvi futtatórendszer (CLR)
hasztolhat A felügyelt kódnak több előnye van, ilyen például az automati­
kus rnernóriakezelés, az egységes típusrendszer (CTS), az önleíró szerelvé­
nyek és így tovább. A .NET-szerelvények sajátságos belső felépítéssel rendel­
keznek. A ClL-utasítások és a típusrnetaadatok rnellett a szerelvények olyan
A függelék: A COM és a .NET együttműködési képessége

manifesztumot tartalmaznak, amely teljes körűen dokumentálja a szükséges


külső szerelvényeket, illetve a fájlokhoz kapcsolódó további részleteket (erős
név, verziószám stb.)
A spektrum másik végén a korábbi COM-kiszolgálókat találjuk (amelyek
természetesen nem felügyelt kódok). Ezek a bináris fájlok a közös fájlkiterjesztésen
(.dll vagy . exe) kívül semmilyen más tekintetben nem kapcsolódnak a .NET­
szerelvényekhez. Először is a COM-kiszolgálók platformfüggő gépi kódot tar­
talmaznak, nem pedig platformfelismerő ClL-utasításokat, valamint egyedi adat­
típusok készletével dolgoznak (amelyet gyakran OLE-automatizmusnak vagy
variánsengedékeny adattípusoknak nevezünk), amelyek egyikét sem érti meg köz­
vetlenill a CLR.
A COM bináris fájlok (mint a rendszerleíró adatbázis bejegyzései és az
runknownhoz hasonló alapvető COM-interfészek támogatása) által igényelt COM­
központú infrastruktúra mellett a COM-típusoknak referenciaszámláltaknak kell
lenniük, hogy megfelelően szabályozhassák a COM-objektumok élettartamát.
Természetesen ez éles kontrasztban áll azokkal a .NET-objektumokkal, amelye­
ket a felügyelt heapen foglalunk le, és a CLR szemétgyűjtője kezeli őket.
Mivel a .NET- és a COM-típusokban nincs sok közös vonás, felmerülhet a
kérdés: vajon a két architektúra hogyan használha�a egymás szolgáltatásait?
Hacsak nem vagyunk olyan szerencsések, hogy "kizárólag" .NET-fejlesztése­
ket alkalmazó cégnél dolgozunk, akkor minden bizonnyal olyan .NET-megol­
dásokat kell majd fejlesztenünk, amelyek korábbi COM-típusokat használnak.
Emellett szembesülhetünk azzal, hogy egy korábbi COM-kiszolgáló esetleg
kommunikálni szeretne egy vadonatúj .NET-szerelvény típusaival.
A lényeg, hogy a COM-nak és a .NET-nek a közeljövőben meg kell tanulniuk
együttműködni. A következőkben megvizsgáljuk a felügyelt és nem felügyelt
típusok harmonikus együttélésének folyamatát a .NET együttműködési réteg
használatávaL Általánosságban, a .NET keretrendszer az együttműködés két
alapvető fajtáját támoga�a:

• COM-típusokat használó .NET-alkalmazásokat,

• .NET-típusokat használó COM-alkalmazásokat.

A .NET Framework 3.5 SDK és a Visual Studio 2008 több olyan eszközt biz­
tosít, amelyek segítenek áthidaini a szakadékat a két egyedi architektúra kö­
zött. A .NET-alaposztálykönyvtár emellett meghatároz egy olyan névteret
(system.Runtime.Interopservices) , amelyet egyedül az együttműködés tá­
mogatásának szenteltek. Nézzük meg először egy egyszerű példát arra, ami­
kor egy .NET-osztály COM-kiszolgálóval kommunikál.

850
A .NET és a COM együttműködésének egyszerű példája

Megjegyzés A .NET platform jelentősen megkönnyíti, hogy egy .NET-szerelvény meghívhassa az


operációs rendszer alapjául szolgáló API-ját (valamint bármilyen C-alapú, nem felügyelt *.dll-t)
a platformhívás (vagy egyszerűen Plnvoke) nevű technológiával. Ha a Plnvoke-kal dolgozunk, a
(#-tekintetében legalább a [Dll Import] attribútumot alkalmaznunk kell a futtatni kívánt külső
metódusra. A Plnvoke technológiát nem vizsgáljuk meg a függelékben, további információkat a
[DllImport] attribútumról a .NET Framework 3.5 SDK dokumentációjában találunk.

A .NET és a COM együttműködésének


egyszerű példája

Ebben a részben egy Visual Basic 6.0 ActiveX *.dll kiszolgálót készítünk,
amelyet aztán egy C#-alkalmazás fog felhasználni.

Megjegyzés Sok COM keretrendszer létezik a VB6-on kívül (pl. az Active Template Library
[ATL] és a Microsoft Foundation Classes [MFC]). A függelékben a VB6-ot használjuk a COM-ki­
szolgáló létrehozására, ez ugyanis rendkívül felhasználóbarát szintaxist biztosít a COM-alkal­
mazások készítéséhez. Ám nyugodtan használjuk akár az ATL-t, akár az MFC-t.

Indítsuk el a VB6-ot, és hozzunk létre új ActiveX *.dll projektet SimpleCom­


Server névvel, nevezzük át a kezdeti osztályfájlt cornealc.cls -re, és az osz­
tálynak adjuk a corneal c nevet. A projekt nevét és a tárolt osztályokhoz ren­
delt neveket arra használjuk, hogy definiáljuk a COM-típusok (ebben az
esetben simplecomserver.comcalc) program azonosítóját (ProgiD). Végül defi­
niáljuk a következő metódusokat a cornea lc. cls fájlban:

' A VB6 COM-objektum


Option E xp licit

Public Function Add(ByVal x As Integer, Byval y As Integer)


As Integer
Add = x + y
End Function

Public Function subtract(Byval x As Integer, ByVal y As Integer)


As Integer
subtract = x y -

End Function

851
A függelék: A COM és a .NET együttműködési képessége

Fordítsuk le a* .dll-ünket (a File> Make menüpont segítségéve!), és állítsuk


be a bináris kompatibilitást (a projekt Property oldalának Component lap­
ján), mielőtt kilépnénk a VB6 integrált fejlesztői környezetből. Ez biztosítja,
hogy ha majd újrafordítjuk az alkalmazást, a VB6 megőrzi a hozzárendelt
globális egyedi azonosítókat (GUID).

Forráskód A SimpleComServer projektet a forráskódkönyvtár A Függelékének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A C#-ügyfélalkalmazás elkészítése

Nyissuk meg a Visual Studio 2008-at, és hozzunk létre egy új C# parancssori


alkalmazást CSharpComClient névvel. Amikor olyan .NET -alkalmazást ké­
szítünk, amelynek korábbi COM-alkalmazással kell kommunikálnia, első lé­
pésként hivatkoznunk kell a COM-kiszolgálóra a projektünkben (hasonlóan
ahhoz, ahogyan egy .NET-szerelvényre hivatkozunk).
Ehhez használjuk a Project> Add Reference menüpontot, majd válasszuk
a COM lapot az Add Reference párbeszédpanelen. Az ablak a COM-kiszolgá­
lók nevét ábécésorrendben listázza, mivel a projekt fordítása során a VB6
fordító a szükséges listázásokkal frissítette a rendszerleíró adatbázist. Ahogy
az Al. ábrán látható, jelöljük ki a simplecomserver.dll-t, majd zárjuk be a

párbeszédablakot.

� Add Reference

Setup UI1.0 Type Library 1.0 C:\Program Files\<


SHGINA 1.0 Type Library 1.0 C:\Windows\syste
Shoclcwave Flash 1.0 C:\Windows\syste
S1mpleComServer 1.0 C:\Users\Andrew
SLCC1.0 Type Library 1.0 C:\Windows\Syste
SLLUAlib1.0 Type Library 1.0 C:\Windows\Syste
SLUil.O Type Library 1.0 C:\Windows\Syste
SNAGil1.0 Type library 1.0 (:\Program Files\"0
Snagh Add in 1.0 Type library 1.0 C:\Program Files\·
SQL Server 2005 Intogration Servi... 1.0 (:\Program Files\1

OK ll Cancel

A. 1. ábra: Hivatkozás COM-kiszolgálóra a Visual Studio 2008 segítségével

852
A .NET és a COM együttmüködésének egyszerü példája

Ha megnézzük a Solution Explorer References mappáját, láthatunk valami


olyasmit, ami a projekthez hozzáadott új .NET -szerelvényreferenciának tűnik
(lásd az A.2. ábrát). Azokat a szerelvényeket, amelyeket a COM-kiszolgálóre­
ferenciák létrehozása során generálunk, hivatalosan együttműködési szere/vé­
nyeknek hívjuk. Az együttműködési szerelvények a COM-típusok .NET-leírá­
sát tartalmazzák.

Solution Explorer ii
j � � ��
ii&J CSiworpComCi ient
li, Eh-·
ffi � Properties
a;. References
. : i�-n ·O dH§iJdiji
i : ··· System
.a
· \··- System.Data
·O
, :_ -o System.Xml
f.•- {il Program.cs

�Solution Explorer ]Gij"Ciass v;.."

A. 2. ábra: A hivatkozott együttműködési szerelvény

Solution Explorer

�����
ii! CSiworpComCiient
$-· §j Properties
EJ.. e;, References
. :.....
SimpleComServer
.O
System
: ... ·O
i-- ·O System.Data
L .a System.Xml
..

ciJ... Qbin
. B-� [C) D�bug
D CSharpComCiient.exe
u CSharpComCiient.pdb
D CSharpComCiient.vshost.exe
. D Interop Stmple-CcmServer.dll
ffi Dobj
L. '.!ol Program.cs
..

'[�Solution Explorer f� Class Vi�w

A. 3. ábra. Az automatikusan generált együttműködési szerelvény

Noha még nem írtunk semmilyen kódot a kezdeti C#-osztálytípusunkba, ha


most lefordíljuk az alkalmazást, és megvizsgáljuk a projekt bin\Debug könyv­
tárát, akkor láthaljuk, hogy a generált együttműködési szerelvény helyi máso­
lata bekerült az alkalmazás könyvtárába (lásd az A.3. ábrát). A Visual Studio
2008 automatikusan hozzáadja az Inte rop. prefixumot az Add Reference pár­
beszédpanellel létrehozott együttműködési szerelvényekhez - ez azonban csak
megegyezés; a CLR számára nem szükséges, hogy az együttműködési szerel­
vények ezt a sajátságos elnevezést kövessék

853
A függelék: A COM és a .NET együttműködési képessége

Kezdeti példánk befejezéséhez módosítsuk a kezdő osztályunk Main O


metódusát úgy, hogy meghívja az Add() metódust egy comcalc objektumon,
és megjelenítse az eredményt. Például:

using system;
using simplecomserver;

namespace csharpcomclient
{
class Program
{
static void Main(string[] args)
{
console.writeLine("***** The .NET COM Client App *****");
Comcalc comobj = new comcalc();
console.writeLine("coM server says 10 +832 is { O}",
comobj.Add(lO, 832));
Console.ReadLine();
}
}
}

Ahogy az előző példakódban láthattuk, a comcalc COM-objektumot tartal­


mazó névtér neve megegyezik az eredeti VB6 projekt nevével (lásd a usi ng
utasítást). Az A.4. ábrán látható a kimenet.

iij C:\Windows\system32\cmd.exe
a -

a
a-�•

A.4. ábra: .NET és COM együttműködése

Látható, hogy COM-típus felhasználása .NET-alkalmazásban valóban transz­


parens művelet lehet. Ám a háttérben sok minden történik, amely lehetövé te­
szi ezt a kommunikációt - ezek részleteit vizsgáljuk meg a következőkben, az
együttműködési szerelvények részleteivel kezdve.

854
Egy .NET együttműködési szerelvény vizsgálata

Egy .NET együttműködési szerelvény


vizsgálata

Ha egy COM-kiszolgálóra rnvatkozunk a Visual Studio 2008 Add Reference


párbeszédpaneljével, akkor az IDE válaszként létrehoz egy új .NET-szerelvényt,
amelyet ellát az Interop. prefixummal (pl. Interop.simplecomserver.dll) . Az ál­
talunk létrehozott szerelvényekhez hasonlóan az együttműködési szerelvények
is tartalmaznak típusmetaadatokat, szerelvénymanifesztumot, és - bizonyos kö­
rülmények között -tartalmazhatnak köztes nyelvi kódot. A "normális" szerelvé­
nyekhez hasonlóan az együttműködési szerelvényeket privát szerelvényként te­
lepíthe�ük (pl. az ügyfél szerelvénykönyvtárán belül), illetve rendelhetünk hoz­
zájuk erős nevet, hogy telepíteni lehessen őket a globális szerelvénytárba (GAC).
Az együttműködési szerelvények alig többek, mint puszta tárolók az ere­
deti COM-típusok .NET-metaadat-leírásainak megőrzéséhez. Sokszor az
együttműködési szerelvények nem foglalnak magukban ClL-utasításokat
metódusaik implementálásához, rnszen az igazi munkát maga a COM-kiszol­
gáló végzi. Az együttműködési szerelvényekben csak akkor találunk végre­
hajtható ClL-utasításokat, ha a COM-kiszolgáló olyan COM-objektumokat
tartalmaz, amelyek képesek eseményeket kiváltani az ügyfél számára. ilyen­
kor az együttműködő szerelvényekben lévő köztes nyelvi kódot a CLR hasz­
nálja arra, hogy a COM kapcsolódási pontok eseménykezelő logikáját lefor­
dítsa .NET-metódusreferenciákká.
Első pillantásra az együttműködő szerelvények nem tűnnek túl haszno­
sak, rnszen nem tartalmaznak megvalósítási logikát. Az együttműködő sze­
relvényekben tárolt metaadat-leírások azonban rendkívül fontosak, ugyanis a
CLR ezeket fogja felhasználni arra, hogy futási időben futásidejű proxyt ké­
szítsen (ez a futási iMben hívható burkoló vagy röviden RCW), amely kommu­
nikációs hídként szolgál a .NET-alkalmazás és a COM-objektum között.
Az RCW részleteit a későbbiekben még megvizsgáljuk; most nyissuk meg
az Interop. si mpl ecomserver. dll szerelvényt az ildasm. ex e segítségével (lásd
az A.S. ábrát).
Noha az eredeti VB6 projekt egyetlen COM-osztályt (comcalc ) definiált, az
együttműködési szerelvény három típust tartalmaz. Ezt ellenőrizhe�ük a Vi­
sual Studio 2008 Objektum böngészőjével is (lásd az A.6. ábrát).

855
A függelék: A COM és a .NET együttműködési képessége

/1 C:\Users\Andrew Troelsen\My Boolcs\C:f J =l®-­


file Vitw . Help .
e--� C:\Users\Andrew Troelsen\My Books\C# and the .MOT Platforn
:···· � MANIFEST
á···• etraW§fi§l
m···IJ: SimpleComServer.ComCalc
S,...• SimpleComServer.ComCak:Ciass
m ··0: SimpleComServer._ComCak:

< 1 ... lll .J


.asse!OOiy Interop.SinpleComServer
�{----------------------------� l:
A. 5. ábra: Az Interop.SimpleComSeroer.dll egt;üttműködési szerelvény lényege

�,:1

r:.�B·� ·�--------
MySolution
-
--
---
... i •
--------

at i � l tm ·
-----

� x

�J
h>

l$ ;l!J CSharpComCiient -
liJ ·.O Interop.SimpleComServer
i El-· o MWiAH§I
·

I±J·...O _ComCalc • a ·;.= __

i±J·...O ComCalc na�:s���� s·�mp leCo Se.


l
m rver
, �l � ComCalcCiass 0
ffi··.O mscorlib Interop.SimpleComServer
'tJ· ·O System
�����----�
� l1
A. 6. ábra: Hogyan eredményezhet egyetlen COM-típus három .NET-típust?

Minden COM-osztályt három különböző .NET-típus képvisel. Először is, van


egy .NET-típusunk, amelynek a neve megegyezik az eredeti COM-típus nevé­
vel (ebben az esetben comcalc) . Másodszor, van egy .NET-típusunk, amely fel­
veszi a cl ass utátagot (cornealcelass) . Ezek a típusok akkor nagyon hasznosak,
ha van olyan COM-típusunk, amely több egyedi interfészt valósít meg, ugyan­
is a class utótaggal rendelkező típusok tárják fel a COM-típus által támogatott
összes interfész összes tagját. Így .NET-programozóként nem kell manuálisan

létrehoznunk egy referenciát a speciális COM-interfészre, mielőtt annak funk­


eionalitását alkalmaznánk. Bár a cornealc nem implementál több egyedi inter­
fészt, az alábbiak szerint meghívha�uk az Add() és a subtract() metódusokat
(comcalc objektum helyett) egy comcalcelass objektumon:

static void Main(string[] args)


{
Console.writeLine("***** The .NET COM client A p p *****");

ll Most használjuk a class utótaggal rendelkező típust.


comcalcclass comobj = new comcalcclass();
Console.writeLine("COM server says 10 +832 is {O}",
comobj.Add(lO, 832));
console.ReadLine();
}

856
A futási időben hívható burkoló

Végül az együttműködő szerelvények meghatározzák a COM-kiszolgálón de­


finiált eredeti COM-interfész .NET-ekvivalensét. Ebben az esetben egy _com­
calc nevű .NET-interfészt találunk. Hacsak nem vagyunk járatosak a VB6

COM szerkezetében, akkor ez bizonyára furcsának tűnhet, hiszen nem hoz­


tunk közvetlenül létre interfészt a SimpleComServer projektünkben (most ne
törődjünk a furcsa nevű _comca lc interfésszel). Az aláhúzásjeles interfészek
szerepe a későbbiekben világossá válik majd; egyelőre csak azt kell tudnunk,
hogy használhatunk interfészalapú programozási módszereket az Add O, illetve
a subt:ract:() metódusok meghívásához:

st:at:ic void Main(st:ring[] args)


{
Console.writ:eLine("***** The .NET COM Client: App *****");

ll Most: manuálisan szerezzük meg a rejt:et:t: interfészt:.


comcalc it:fcomrnt:erface = null;
comcalcclass comobj = new comcalcclass();
it:fcomrnt:erface = (_comcalc)comobj;

Console.writ:eLine("COM server says 10 +832 is {O}",


it:fcomint:erface.Add(lO, 832));
console.ReadLine();
}

Ritkán lesz szükségünk metódusok hívására a class utótaggal rendelkező


vagy az aláhúzásjeles interfészek használatávaL Ha azonban olyan bonyolul­
tabb .NET-alkalmazásokat készítünk, amelyeknek kifinomultabban kell
együttműködniük a COM-típusokkal, akkor fontossá válik ezeknek a típu­
soknak az ismerete.

Forráskód A CSharpComClient projektet a forráskódkönyvtár A függelékének alkönyvtára


tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

A futási időben hivható burkoló

A CLR futásidőben a .NET együttműködési szerelvény metaadatait használja


fel olyan proxytípus létrehozásához, amely a .NET -COM-kommunikáció fo­
lyamatát kezeli. Ez a proxy a futási időben hívható burkoló, amelynek volta­
képpen híd szerepe van a valódi COM-osztályhoz (gyakran a coelass elneve­
zéssel ille�ük). Minden coelass, amelyhez egy .NET-kliens hozzáfér, megfe-

857
A függelék: A COM és a .NET együttmüködési képessége

lelő RCW-t igényel. Ezért, ha .NET-alkalmazásunk három COM-coclasst


használ, akkor végül három különböző RCW-nk lesz, amelyek a .NET-hívá­
sokat a COM-kérésekre képezik le. Az A.7. ábra mutatja a teljes képet.

Megjegyzés COM-objektumonként mindig egyetlen RCW lesz, függetlenül attól, hogy a .NIT­
kliens hány interfészt kapott a COM-típustól (a többinterfészes VB6 COM-objektumokat a függelék
későbbi részében vizsgáljuk). Ezzel a módszerrel az RCW karbantarthatja a COM-objektum meg­
felelő COM-azonosítóját {és referenciaszámlálóját).

Az RCW-t a rendszer automatikusan létrehozza akkor, amikor a CLR-nek


szüksége van rá. A korábbi COM-kiszolgálókban pedig nincs szükség sem­
milyen módosításra ahhoz, hogy a .NET-alapú nyelvek feldolgozhassák őket.
Az RCW közbenjár, és gondoskodik a belső munkáról. Ennek megismerésé­
hez formalizáljuk az RCW néhány alapvető feladatát.

A. 7. ábra: Az RCW-k a .NET-es hívó és a COM-objektum között helyezkednek el

RCW: a COM-tipusok mint .NET-tipusok

Az RCW felelős a COM-adattípusok átalakításáért a .NET-ekvivalensükre (és


fordítva). Tételezzük fel, hogy egy VB6 COM-szubrutinunk a következő de­
finícióval rendelkezik:

' VB6 COM-metódus definíciója.


Public sub Dis pl ayThi s string ( syva l s as String)

858
A futási időben hívható burkoló

Az együttműködő szerelvény .NET system.stringként definiálja a metódus­


paramétert:

' A COM-metódus C#-beli leképezése.


public void DisplayThisString(string s)

Amikor a .NET-kódbázis meghívja a metódust, az RCW automatikusan fel­


veszi a bejövő system. st ring típust, és átalakítja VB6 string adattípussá
(amely valójában COM BSTR) Minden COM-adattípusnak megvan a maga
.

.NET-ekvivalense. Az A.l. táblázat összefoglalja a COM lDL (interfészdefiní­


ciós nyelv) adattípusok, a hozzájuk kapcsolódó .NET System adattípusok és
a megfelelő C#-kulcsszavak (ha van) közötti leképezéseket

wchar_t, short System.Int16 short

long, int System.Int32 int

Hyper System.Int64 long

unsigned char, byte System.Byte byte

single system.single

double System.Double double

VARIANT_BOOL System.Boolean bool

BSTR system.string string

VARIANT system.object object

DECIMAL system.Decimal

DATE System.DateTime

GUID System.Guid

CURRENCY system.Decimal

IUnknown System.Object object

IDispatch System.Object object

A. 1. táblázat: Valódi COM-típusok leképezése .NET-típusokra

859
A függelék: A COM és a .NET együttműködési képessége

RCW: coelassok referenciaszámlálójának kezelése

Az RCW másik fontos feladata a COM-objektum referenciaszámlálójának ke­


zelése. A COM referenciaszámlálási sémája a közös pont a coelass és az ügy­
félprogram között, középpontjában pedig az AddRef() és a Release() hívások
megfelelő használata áll. A COM-objektumok megsemmisítik magukat, ha
azt észlelik, hogy nincsenek külső hivatkozásaik
A .NET-tipusok azonban nem a COM referenciaszámlálási sémáját hasz­
nálják, ezért a .NET-kliensek nem tudják meghívni a Release() metódust a
felhasznált COM-típusokon. Az RCW belsőleg gyorsítótárazza az összes in­
terfészreferenciát, és aktiválja a felszabadításukat, ha a .NET-kliens már nem
használja a típust. A lényeg az, hogy a VB6-hoz hasonlóan a .NET-kliensek
soha nem hívják meg expliciten az AddRef(), a Release() és a Queryinter­
face() metódusokat.

Megjegyzés Ha közvetlenül a . NIT-alkalmazásból szeretnénk hozzáférni a COM-objektum re­


ferenciaszámlálójához, akkor rendelkezésünkre áll a Marshal típus, amelyet a system . Run­
ti me . InteropServi ces névtér biztosít. Ez az osztály több statikus metódust definiál, ame­
lyek közül több használható arra, hogy manuálisan kezeljük a COM-objektumok élettartamát.
Bár az alkalmazások többségében nem használjuk a Marshal típust, további részletekért for­
duljunk a .NET Framework 3.5 SDK dokumentációjához.

RCW: alacsony szintü COM-interfészek elrejtése

Az RCW által biztosított utolsó alapvető szaigáitatás az alacsony szintű COM­


interfészek használata. Mivel az RCW mindent megtesz azért, hogy elhitesse a
.NET-klienssel, hogy közvetlenül egy natív .NET-típussal kommunikál, ezért
bizonyos alacsony szintű COM-interfészeket el kell rejtenie szem elől.
Amikor például olyan COM-osztályt készítünk, amely támogatja az ICon ­
necti on Poi ntcontati ner interfészt (és van egy vagy két alobjektuma, amely az

IConnecti on Poi nt interfészt támogatja), akkor a kérdéses coelass képes esemé­

nyeket kiváltani a COM-kliensnéL A VB6 az Event és a Rai seEvent kulcssza­


vakkal a teljes folyamatot elrejti szem elől. Ugyanígy az RCW a .NET-kliens
elől elrejti a hasonló COM-"felesleget". Az A.2. táblázat felvázolja azoknak a
rejtett COM-interfészeknek a szerepét, amelyeket az RCW használ.

860
A COM lDL szerepe

;- --; �·� -�;.


..,,. � ... ·.;o:. .....·_; ,:,':'"·-.,�
.- -::·....

rconnectionPointContainer Lehetővé teszi, hogy a coelass eseményeket


küldjön vissza az érdekelt kliensnek

rconnectionPoint A VB6 automatikusan biztosítja az interfészek


alapértelmezett megvalósítását.

IDispatch Lehetővé teszi a "késői kötést" egy coclasshoz.


IProvideclassrnfo Arnikor VB6 COM-típusokat készítünk, ezeket
az interfészeket automatikusan támogatja az
adott COM-típus.

rerrorrnfo Az interfészek lehetővé teszik, hogy a COM­


rsupportErrorrnfo kliensek és a COM-objektumok COM-hibákat
rcreateErrorrnfo küldjenek, és azokra válaszoljanak.

runknown A COM nagyapja. Ez kezeli a COM-objektum re­


ferenciaszámlálóját, és lehetővé teszi, hogy a kli­
ensek megkapják az interfészeket a coclasstól.

A. 2. táblázat: Rejtett COM- interfészek

A COM lDL szerepe

A következőkben tekintsük át a COM lDL néhány apróbb részletét A függe­


léknek nem célja, hogy teljes COM lDL gyakorlatot biztosítson, az együttmű­
ködési réteg jobb megértéséhez csak néhány IDL-szerkezetet kell ismernünk.
Minden .NET-szerelvény tartalmaz metaadatot. A metaadatot hivatalosan
arra használjuk, hogy leírjuk egy .NET-szerelvény minden egyes jellemzőjét,
beleértve a belső típusokat (a tagjaikat, ősosztályaikat és így tovább), a sze­
relvény verzióját, valamint az opcionális szerelvényszintű információkat
(erős név, kultúra stb.).
A .NET-metaadat sok tekintetben a klasszikus COM-kiszolgálók leírására
szolgáló korábbi metaadat-formátumok nagy testvérének tekintethető. A klasz­
szikus ActiveX COM-kiszolgálók (*.dll-ek vagy *.ex e-k) típuskönyvtár segítsé­
gével dokumentálják belső típusaikat Ezeket a típuskönyvtárakat megvalósít­
ha�uk önálló *.t l b fájlként, vagy beágyazott erőforrásként elhelyezhe�ük őket a
COM-kiszolgálón (ez a VB6 alapértelmezett viselkedése). A COM-típuskönyvtá­
rak általában lDL (Interface Definition Language - interfészleíró nyelv) meta­
adatnyelven készülnek a speciális midl .exe fordítóban (Microsoft lDL-fordító).

861
A függelék: A COM és a .NET együttműködési képessége

A VB6 kiválóan elrejti szem elől a típuskönyvtárakat és az IDL-t. Igazából


számos sikeres VB COM-programozó teljesen figyelmen kívül hagyja az lDL
szintaxisát. Mindenesetre, ha ActiveX projekt workspace típusokat fordítunk, a
VB automatikusan létrehozza és beágyazza a típuskönyvtárat a fizikai * . dll

vagy *.exe COM-kiszolgálón. Ezenkívül a VB6 biztosí�a, hogy a rendszer a tí­


puskönyvtárat a rendszerleíró adatbázis speciálls részében automatikusan re­
gisztrálja: HKEY_CLASSES_ROOT\ TypeLib (lásd az A.8. ábrát).

gt Registry Editor .,.


File Edit View fiiYDrit<'S Help -

�-�_!lrtfile _

� ! _TYf'<'lib :
o.. {000020 0 -0 0000-0010-8000-00AA006D2EA4}
� • - {000020 0 1-0000-0010-8000- 00AA006D2EA4J
� -� {000020 05-0000 -0010-8000-00AA006D2EA4J
o. � {00000206-0000-0010-8000 -00AA006D2EA4J
o .. � {00000 30-0 0000-0010-8000 -00AA006D2EA4J Q
� ·· .� {00000600- 0000-0010-8000 -00AA006D2EA4J
o- � {00020430-0000-0000-C000-0J00046 ?

• l. • "'lll
or l • 0'---;:..;------,
Computei\HKEY.ClASSES.ROOl\Typelib

A. 8. ábra: A HKCR l TypeLib felsorolja az adott gépen található összes regisztrált típuskönyvtárat

A típuskönyvtárakra több integrált fejlesztői környezet hivatkozik folyama­


tosan. Ha például a VB6-ban a Project> References menüpontot válasz�uk,
az IDE a HKCR\ TypeLib segítségével az A. 9. ábrán látható módon meghatá­
rozza az összes regisztrált típuskönyvtárat.

. . .. ••y
--- - . ..
-- ,,
--L..

01'\crosoft Activ!!Movie Control Concel


01'\crosoftActiveX Data Objects (lo\iti� 6.
01'\crosoft AdiveX Data Qbjects 2.0 Lbrary
O 1'\crosoft AdiveX Data Qbjects 2.1 Lbrary Browse••.
O 1'\crosoft AdiveX Data Qbjects 2. 5 Lbrary
O 1'\crosoft AdiveX Data Qbjects 2.6 Lbr1!fy
o Mcrosoft AdiveX Data Qbjects 2. 7 Lbrary _!j
C@f1Mii·!#Mi•$b§i#@i Priority
O 1'\crosoft AdiveX Data Objects 6.0 Lbr1!fy Hel>
O 1'\crosoft AdiveX Data Objects ReaJrdset 6.0 Lbrar) +l
O 1'\crosoft AdcHn Desic;roer ...:.J
01'\crosoft MXJ Ext. 6.0 for OOL and Searity
01'\crosoftAdomdaentServia! C0f1'4>000flt l.O Type l ..
nMn-MnftAnPntr.nntTnl ?.n
•l lll .l
rl'tcrosoft AdiveX Data Qbjects 2.8 Lbrll'y

Location: C:'f'rogram Fles� Fles\'ysll!m\arlo'fnsadc>ZS.tll


_ i..ar9Jage: Standard

A. 9. ábra: COM típusadat-információinak hivatkozása a VB 6-ból

862
A COM lDL szerepe

Ugyanígy, ha megnyitjuk a VB6 Objektum böngészőjét, a VB6 IDE beolvassa


a típusadatot, és megjeleníti a COM-kiszolgáló tartalmát egy barátságos GUI
segítségével (lásd az A.10. ábrát).

lc1asses
1ri!' CompareEnum
i'm Connect•on -
ri!' ConnectModeEnum D
ri!' ConnectOptionEnun
ri!' ConnectPromptEnw
'ri!' CopyRecordOplions

lleni>er of A!lQl!!!

A.10. ábra: Típuskönyvtárak megtekintése a VB6 objektum böngészőjével

A VB COM-kiszolgálónkhoz generált lDL

Noha a VB6 objektumböngészője megmutatja egy típuskönyvtár összes típu­


sát, az OLE View segédprogram (o levi ew. exe) lehetővé teszi a megfelelő tí­
puskönyvtár létrehozásához használt, alapul szolgáló IDL-szintaxis megte­
kintését. Ha telepítettük a VB6-ot, akkor az OLE View eszközt a Start > All
Programs > Microsoft Visual Studio 6.0 > Microsoft Visual Studio 6.0 Tools
útvonalon nyithatjuk meg, és a SimpleComServer szervert a Treeview vezérlő
Type Libraries csomópontja alatt találhatjuk (lásd az A. ll. ábrát).
--
li OlE/COM Dt:�;ra v- lil

,�..<:"'/"'_'!"" """---­
---------------- 1
���!!]
.·::rSENS Mntslyp<tlibfary (VEt l,. A:.,�N-1.01
L$f�l.OTypeLibntoi)'{Va-l � tDrnM4M'Ef0-.41�77013S100o\l
�t' Setup KRmtll.OlYJ)41' l.ibQiy ('1.
i j f'Sctuputl.OTypel.ibnry(VI'O'l _,
: i-P SHGJNA 1.0 Type.Libtary (Ver 1. Typdib
i [--t" Shoc:kw-�sh(V�l.o) i- (D724MU-FEFD-U6B-BAEC·B8(77088ADOAI

: i-r;w • '-l.O=�ont.Sawf
l "- -f' SlCCl.DType.l.iM!y{Verl.O) i--O
. ��o"'�\lkm\AndrewTrocbcn\M)'Boob\C#•ndlM.NfTP!..tformCthed\Cod�A\SOmpleeomSuwt\SirnpleC�.dll
J-
!-r suJJALibl.OTypt:Libr•.ytverO
l :.-f' SlUll.OTypeUbrM)'(Vtrl.O)
l-t' SNAGITl.OTypelib.-..y(VerlJ L HELPOIR::: C:\Uscn\AndftwTroelsen\My Boola\C# llnd tM .NfT Pt.tform 4th ed\Co.M\Ap� A\SimplrCom�r
.�
\
.; Adöinl.Ollll)el'tb'IMV.rl• !_:_-------'
;....,
A.11. ábra: A SimpleComServer keresése az OLE/COM objektummegjelenítő segítségével

863
A függelék: A COM és a .NET együttműködési képessége

Ha kétszer kattintunk a típuskönyvtár ikonjára, megnyílik egy új ablak,


amelyben látható a VB6-fordító által létrehozott típuskönyvtárt alkotó összes
lDL-token. A releváns- és kissé formázott- lDL a következő (az [uuid] érté­
kek különbözőek lesznek):

[uuid(8AED93CB-7832-4699-A2FC-CAE08693E720), version(l.O)]
library simplecomserver
{
importlib("stdole2.tlb");
interface _comcalc;

[odl, uuid(5844CD28-2075-4E77-B619-9B65AA0761A3), version(l.O),


hidden, dual, nonextensible, oleautomation]
interface _comcalc : IDispatch {
[id(Ox60030000)]
HRESULT Add([ in] short x, [in] short y, [out, retval] short* ) ;
[id(Ox60030001)]
HRESULT subtract([in] short x, [in] short y,
[out, retval] short*);
};

[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(l.O)]
coelass comcalc {
[default] interface _comcalc;
};
};

Az IDL-attribútumok

Az lDL elemzéséhez először figyeljük meg, hogy az IDL-szintaxis szögletes záró­


jelek ([...J) közé foglalt kódblokkokat tartalmaz. A zárójeleken belül vesszővel
elválasztott IDL-kulcsszavakat találunk, amelyek egyértelművé teszik, hogy mi a
"
"következő dolog (közvetlenül a blokk alatt, illetve a tőle jobbra lévő elem).
Ezek a blokkok az IDL-attribútumok, amelyek ugyanazt a célt szolgálják, mint a
.NET-attribútumok (azaz leírnak valamit). Az egyik kulcsfontosságú lDL­
attribútum az [uuid], amellyel adott COM-típushoz GUID-t rendelhetünk.
A COM-ban mindenhez tartozik GUID (interfészek, COM-osztályok, típus­

könyvtárak és így tovább), ennek célja az adott elem egyedi azonosítása.

864
A COM lDL szerepe

Az lDL-könyvtár-utasitás

A legfelső szint a COM-könyvtár-utasítás, amelyet az lDL l i b rary kulcsszavá­


val jelölünk. A könyvtárutasítás magában foglal minden egyes interfészt és
COM-osztályt, valamint az összes felsorolást és felhasználó által definiált tí­
pust. A simplecomserver esetében a típuskönyvtár pontosan egy COM-osz­
tályt listáz, a comcalcot, amelyet a coelass (azaz COM-osztály) kulcsszóval
jelöltünk meg.

A [default] interfész szerepe

A COM szabályai szerint a COM-kliens COM-osztállyai folytatott kommuni­


kációjállak az egyetlen lehetséges útja az interfészreferencia (nem objektum­
referencia) használata. Ha már hoztunk létre C++-alapú COM-ügyfélprogra­
mokat, akkor tisztában vagyunk egy adott interfész lekérdezésének folyama­
tával, a szükségtelenné váló interfész felszabadításával stb. Amikor azonban
VB6-ban készítünk COM-klienseket, a COM-osztályhoz automatikusan egy
alapértelmezett interfészt kapunk.
VB6 COM-kiszolgálók esetében egy*. cl s fájl nyilvános tagjait (például az
Add() függvényt) a rendszer a COM-osztály "alapértelmezett interfészére"

helyezi. A comcalc osztálydefinícióját megvizsgálva kiderül, hogy az alapér­


telmezett interfésze neve _comcal c:

[uuid(012B1485-6834-47FF-8E53-3090FE85050C), version(l.O)]
coelass Comcalc {
[default] interface _comcalc;
};

A VB6 által a háttérben létrehozott alapértelmezett interfész neve mindig


_AzosztályNeve (az aláhúzás névadási konvenció a rejtett interfészek jelölésé­
re). Így, ha van egy car osztályunk, az alapértelmezett interfész _car, a Data­
connector osztály alapértelmezett interfésze _Dataconnector és így tovább.
A VB6 alatt az alapértelmezett interfész egyáltalán nem látható. Ha azon­
ban megírjuk a következő VB6 kódot:

' VB 6.0 COM-kliens kód.


Dim c As Comcalc
set c = New comcalc ' A [default] _comcalc interfész automatikusan
' vissza adódik!

865
A függelék: A COM és a .NET együttműködési képessége

a VB automatikusan lekérdezi az alapértelmezett interfészt az objektumtól


(ahogy a típuskönyvtár meghatározza), és elküldi a kliensnek Mivel a VB
mindig visszaadja a COM-osztály alapértelmezett interfészét, úgy tűnik,
mintha igazi objektumreferenciánk lenne. Ez azonban csak egy része a VB6
által biztosított szintaktikai egyszerűsítésnek A COM-ban nem létezik köz­
vetlen objektumreferencia. Interfészreferencia viszont mindig van (még ak­
kor is, ha ez a referencia az alapértelmezett).

Az IDispatch szerepe

Az alapértelmezett_cornealc lDL-leírását megvizsgálva, látha�uk, hogy az in­


terfész az roispatch COM-interfészből származik. Ez az interfész teszi lehe­
tővé, hogy klasszikus ASP-ből kommunikálhassunk a COM-objektumokkal
akár a weben, akár bárhol másutt, ahol késői kötésre van szükség.

IDL-paraméterattribútumok

Az lDL-lel kapcsolatosan még tudni kell, hogy a VB6 paraméterei hogyan je­
lennek meg a háttérben. A VB6 alatt az összes paramétert referenciaként ad­
juk át, hacsak nem használjuk explicit módon a syva l kulcsszót, amelyet az
lDL [i n] attribútumával ábrázolunk. Egy függvény visszatérési értékét az
[out, retvalJ attribútumokkal jelöljük. Ezért az alábbi VB6 függvény:

' VB függvény
Public Function Add(Byval x as Integer, ByVal y as Integer) as
Integer
Add = x + y
End Function

lDL-ben a következőképpen nézne ki:

HRESULT Add([in] short* x, [in] short* y, [out, retval] short*);

Másrészről, ha nem jelölünk meg egy paramétert a syval kulcsszóval, akkor a


VB6 automatikusan a ByRef kulcsszót feltételezi:

' Ezek a paraméterek átadják a ByRef-et VB6 alatt!


Public Function subtract(x As Integer, y As Integer) As Integer
subtract = x - y
End Function

866
Típuskönyvtár használata együttműködési szerelvény készítéséhez

Az ID L-ben a ByRef paramétereket az [in, out] attribútumok jelölik:

HRESULT subtract([in, out] short x, [in, out] short y,


[out, retval] short*);

Tipuskönyvtár használata együttműkö­


dési szerelvény készitéséhez

A VB6 fordító a biztonság kedvéért számos egyéb IDL-attribútumot is létre­


hoz a háttérben, amelyekből a megfelelő helyeken további részleteket látunk.
Felmerülhet a kérdés: pontosan miért foglalkoztunk a COM lDL-lel? Az ok
egyszerű: ha a Visual Studio 2008 segítségével adunk referenciát a COM­
kiszolgálóhoz, az IDE beolvassa a típuskönyvtárat, hogy felépítse a megfelelő
együttműködési szerelvényt Bár a VS 2008 tökéletesen megfelelő az együtt­
működő szerelvények generálásához, az Add Reference párbeszédpanel az
alapértelmezett szabálykészlethez igazodik az együttműködő szerelvény lét­
rehozásakor, és nincs lehetőségünk a felépítés finomhangolására.
Ha nagyobb rugalmasságra van szükségünk, akkor a parancssorban is
generálhatunk együttműködési szerelvényt a tlb imp. exe nevű (típuskönyv­
tár-importáló eszköz) .NET-segédprogram segítségéveL Egyebek között a
tlbimp. ex e lehetövé teszi a típusokat és a kimeneti fájl nevét tartalmazó
.NET-névtér nevének a kezelését. Továbbá, ha erős nevet szeretnénk hozzá­
rendelni az együttműködő szerelvényhez, hogy telepíteni tudjuk a globális
szerel vénytárba (GAC), a tlb imp.exe biztosítja számunkra a /keyfile kapcso­
lót az * . snk fájl meghatározásához (az erős nevekkel kapcsolatban lásd az
előző kötet 15. fejezetét). Az összes opció megtekintéséhez írjuk be a tlbimp
szót a Visual Studio 2008 parancssorába, majd nyomjuk meg az Entert (lásd
az A.12. ábrát).
Bár az eszköz számos opciót felkínál, a következő paranccsal lehet erős
névvel rendelkező együttműködési szerelvényt készíteni calcinteropAsm.dll
névvel (feltételezzük, hogy van egy myKeyPair. snk nevű *. snk fájlunk):

tlbimp simplecomserver.dll /keyfile:myKeyPair.snk


/out:CalcinteropAsm.dll

Ha a Visual Studio 2008 által létrehozott együttműködési szerelvény megfe­


lelő, akkor nem kell közvetlenül használnunk a tlbi mp.exe segédprogramot.

867
A függelék: A COM és a .NET együttműködési képessége

A. 12. ábra: A tlbimp.exe opciói

Késői kötés a CoCalc coelasshoz

Ha létrehoztuk az együttműködési szerelvényt, a .NET -alkalmazás unk már


használha�a a típusait a korai kötési vagy késői kötési technikák valamelyi­
kével. Azt már láttuk, hogy hogyan lehet COM-típust létrehozni a korai kötés
alkalmazásával (a C# new kulcsszava révén), most nézzük meg a COM-objek­
tumok aktiválását a késői kötés által.
A system. Refl ee ti on névtér lehetőséget biztosít arra, hogy programozot­
tan futásidőben vizsgáljuk meg az adott szerelvény típusait (lásd az előző kö­
tet 16. fejezetét). A COM-ban ugyanezt a működést a szabványos interfészek
(pl. ITypeL i b, ITypernfo és így tovább) alkalmazása támoga�a. Ha a kliens fu­
tási időben (és nem fordítási időben) egy taghoz kötést hoz létre, akkor ezt a
"késői" kötés létrehozásának nevezzük.
Voltaképpen mindig célszerű a C# new kulcsszavát és ezzel a korai kötési
technikát alkalmazni. Természetesen vannak olyan esetek, amikor a késői kö­
tést kell használnunk. Néhány korábbi COM-kiszolgálót például úgy is fel­
építhettünk, hogy azok semmilyen típusinformációt nem biztosítottak. Ilyen
helyzetben nyilvánvaló, hogy nem futtatha�uk azonnal a t l b imp. exe segéd­
programot. Mivel ez ritkán fordul elő, a klasszikus COM-típusokhoz a .NET
reflexiós szolgáltatásának segítségével is hozzáférhetünk

868
Típuskönyvtár használata együttműködési szerelvény készítéséhez

A késői kötés folyamata azzal kezdődik, hogy a kliens megszerzi az


IDi spatch interfészt egy adott coelasstóL Ez a COM-interfész összesen négy
metódust definiál, ezek közül jelenleg kettővel kell foglalkoznunk. Az egyik
a GetiDsOfNames(). Ez a metódus lehetővé teszi, hogy a hívó késői kötést al­
kalmazzon: megszerzi a hívni kívánt metódus azonosítására szolgáló nume­
rikus értéket (ennek neve dispatch ID vagy DISPID).
A COM IDL-ben egy taghoz az [id] attribútummal rendelhetjük hozzá az
DISPID-ját. Ha megvizsgáljuk a VB6 által generált lDL-kódot (az OLE View
eszközzel), látható, hogy az Add() metódus DISPID-ja a következőképpen
kapott értéket:

[id(Ox60030000)]
HRESULT Add( [in] short x, [in] short y, [out, retval] short* ) ;

Ez az az érték, amelyet a GetiDsOfNAmes O metódus visszaadott a késői kötésű


kliensnek Amint a kliens megszerzi ezt az értéket, meghívja a következő, ér­
deklődésre számot tartó metódust, az rnvoke()-ot. Az IDispatch ezen metódu­
sa számos argumentumot felvesz, közülük az egyik a GetiDsOfNames () haszná­
latával megszerzett DISPID. Továbbá az rnvoke() felveszi COM VARIANT ti­
pusok egy tömbjét is, azt, amelyik a függvénynek átadott paramétereket ábrá­
zolja. Az Add() metódus esetén ez tömb két (valamilyen értékű) shortot tar­
talmaz. Az rnvoke() utolsó argumentuma egy másik VARIANT, amely a metó­
dushívás visszaadott értékét tárolja (ismét egy shortot) .
Noha a késői kötést alkalmazó .NET-kliensek közvetlenül nem használják
az IDispatch interfészt, a system.Re fl e ction névtérrel ugyanez az általános
működés valósítható meg. Ennek bemutatásához készítsünk egy másik C#­
klienst, amely késői kötést használ az Add O logika kiváltásához. Ez az alkal­
mazás nem készít semmilyen referenciát a szerelvényhez, ezért nincs szükség
a tlb imp.exe segédprogram használatára.

ll Mindenképpen használjuk a system.Reflection névteret.


static void Main(string[] args)
{
console.writeLine("***** The Late Bound .NET client *****");

ll Először megszerezzük az IDispatch referenciát a coclasstól.


Type calcobj =

Type.GetTypeFromProgiD("SimpleCOMServer.comcalc");
object calcDisp Activator.creaternstance(calcobj);
=

ll Készítsük el az argumentumok tömbjét.


object[] addArgs { 100, 24 };
=

869
A függelék: A COM és a .NET együttműködési képessége

ll Hívjuk az Add() metódust, majd kérjünk összegzést.


object sum = null;
sum = calcobj.InvokeMember("Add", BindingFlags.InvokeMethod,
null, calcDisp, addArgs);

ll Az eredmények megjelenítése.
console.writeLine("Late bound adding: 100 +24 is: {0}'', sum);
console.ReadLine();
}

Forráskód A CSharpComlateBinding kódfájlokat a forráskódkönyvtár A Függelékének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Bonyolultabb COM-kiszolgáló készitése

A következőkben olyan VB6 ActiveX szervert készítünk, amely bonyolultabb


COM programozási módszereket használ. Hozzunk létre egy új ActiveX
*.dll workspace-t Vb6ComCarServer névvel. Kezdeti osztályunkat nevez­

zük át CoCarra, ez a következő megvalósítással rendelkezik:

Option Explicit

' Egy COM-felsorolás.


Enum CarType
Viper
colt
BMW
End Enum

' Egy COM-esemény.


Public Event BlewUp()

' Tagváltozók.
Private currsp As Integer
Private maxsp As Integer
Private Make As carType

' Ne feledjük! Minden nyilvános tagot


' az alapértelmezett interfész biztosít.
Public Property Get currentspeed() As Integer
CurrentSpeed = currsp
End Property

Public Property Get carMake() As CarType


carMake = Make
End Property

870
Bonyolultabb COM-kiszolgáló készítése

Public sub Speedup()


currsp = currsp + 10
If currSp >= maxsp Then
RaiseEvent Blewup
' Esemény kiváltása, ha túlerőltetjük a motort.
End If
End Sub

Private Sub Class_Initialize()


MsgBox "Init: COM car"
End sub

Public sub create(ByVal max As Integer, _

Byval cur As Integer, Byval t: As carType)


maxsp = max
currsp = cur
Make = t
End sub

Ez egy egyszerű COM-osztály, amely a könyvben használt C# car osztályá­


nak működését irrútálja. Az egyetlen érdekes pont a create() szubrutin,
amely lehetővé teszi, hogy hívó átadja a car objektumot leíró állapotadatokat

Még egy COM-interfész támogatása

Mindezek után szúrjunk be egy új *.cl s fájlt, amely a következő IDriverinfo


interfészt definiálja:

Option Explicit:

' A sofőrnek van neve.


Public Propert:y Let: DriverName(ByVal s As string)
End Property
Public Propert:y Get DriverName() As String
End Propert:y

Ha már készítettünk több interfészt támogató COM-objektumokat, tudjuk,


hogy a VB6 biztosí�a számunkra az Implement:s kulcsszót. Ha megadjuk az
adott COM-osztály által megvalósított interfészeket, akkor használha�uk a
VB6-kódablakot a metódus csonkjainak elkészítéséhez. Tételezzük fel, hogy
hozzáadtunk egy privát sztringváltozót (driverName) a cocar osztálytípushoz,
és az alábbiak szerint megvalósítottuk az IDriverinfo interfészt:

871
A függelék: A COM és a .NET együttműködési képessége

' Megvalósított interfészek


' [General][Declarations]
Implements IDriverrnfo

' ***** IDriverrnfo implementációja ***** '

Private Property Let roriverrnfo_DriverName(ByVal RHS As string)


driverName = RHS
End Property

Private Property Get IDriverrnfo_DriverName() As String


IDriverrnfo_driverName = driverName
End Property

Az interfészmegvalósítás vizsgálatának befejezéséhez állítsuk az roriverrnfo


Instancing tulajdonságát PublicNotcreatabl e értékre (annak érdekében, hogy
kívülről ne lehessen lefoglalni interfésztípusokat).

Belső objektumok feltárása

A VB6 (és maga a COM) alatt nem áll rendelkezésünkre a kényelmes klasszi­
kus megvalósítási származtatás. Ehelyett kénytelenek vagyunk a tartalma­
zás/delegálás modellt (a "van egy" kapcsolatot) használni. Tesztelési célból
adjunk egy utolsó 1'. cls fájlt az aktuális Engine nevű VB6-projektünkhöz, és
állítsuk az rnstancing tulajdonságát Publi cNotcreatable értékre (rnivel sze­
retnénk azt megakadályozni, hogy a felhasználó közvetlenül létrehozhasson
egy Engine objektumot).
Az Engine alapértelmezett nyilvános interfésze rövid és egyszerű. Defini­
áljunk egy függvényt, amely sztringtömböt ad vissza, és ez képviseli a motor
rninden egyes hengerének becenevét (igaz, nem szokás barátságos nevet adni
a hengereknek, de hát... ) :

Option Explicit

Public Function Getcylinders() As String()


Dim c(3) As String
c (O) "Grimey"
c(l) = "Thumper"
c(2) = "oily"
c(3) = "crusher"
Getcylinders = c
End Function

872
Az együttműködési szerelvény

Végül adjuk a GetEngi ne() nevű metódust a cocar alapértelmezett interfészé­


hez, amely visszaadja a tároltEngine egy példányát (ehhez létre kell hoznunk
az Engine típusú, eng névvel rendelkező privát tagváltozót):

' visszaadjuk az Engine-t a külvilágnak.


Public Function GetEngine() As Engine
Set GetEngine = eng
End Function

Van tehát egy ActiveX szerverünk, amely két interfészt támogató COM­
osztályt foglal magában. Emellett visszaadhatunk egy belső COM-típust a
cocar [default] interfészének használatával, valamint együttműködhetünk
néhány alapvető programozási szerkezettel (felsorolt típusokkal és COM­
tömbökkel). Folytassuk a kiszolgáló lefordításával (és ha ezzel készen va­
gyunk, állítsuk be a bináris kompatibilitást), majd zárjuk le az aktuális VB6-
munkaterületet.

Forráskód A Vb6ComCarServer kódfájlokat a forráskódkönyvtár A Függelék alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Az együttműködési szerelvény

Ahelyett, hogy a t l b imp. ex e segédprogramot használnánk az együttműködé­


si szerelvény generálásához, hozzunk létre új konzolalkalmazást (CSharp­
CarClient névvel) a Visual Studio 2008 használatával, és az Add Reference
párbeszédpanel COM-lapján állítsunk be referenciát a vb6comcarserver. dll
szerelvényre. Vizsgáljuk meg az együttműködési szerelvényt a VS 2008 Ob­
jektum böngészőjében (lásd az A.13. ábrát).
Megint lett néhány class utótaggal rendelkező és aláhúzásjeles interfésztípu­
sunk, valamint néhány új elemünk, amelyeknek a neve azt sugallja, hogy min­
den bizonnyal a COM-.NET-eseményértesítések (nevezetesen a cocar_Event,
cocar_sinkHelper és cocarBlewUpEventHandler) kezelésére szolgálnak. Amikor

egy COM-objektum COM-eseményeket tár fel, az együttműködési szerelvény


további ClL-kódot tartalmaz, amelyet a CLR arra használ, hogy leképezze a
COM-eseményeket .NET-es eseményekre (lásd ezt működés közben később).

873
A függelék: A COM és a .NET együttműködési képessége

�oqectBr� t�
'P
� �
�� ·a
�--------�---- ---------------- ----
·�X
Browse: My Solution • ... J • q l !b l � •

<Search>
• li] .).
!±l&J CSharpCarCiient
$--a lnterop.Vb6ComCarServer
� B···<.> ri!'§MGJ.YJI
�--c(ill CarType
1!1--�_CoCar
$-�_CoCar
$···>-O CoCar
EiJ"'"" CoCar_vO
lfl··· _CoCar_BiewUpEventHandler
ffi---� _CoCar_Event
$--<1$ _CoCar_SinkHelper namespace Vb6ComCar5erver
Member of Interop.Vb6ComCarServer
$-� CoearClass
Eil->oO _Engine

_j
$· �Engine
..

$--� EngineClass
$--� _IOriverlnfo
ffi--..o !Oriverlnfo
w____rn_- -� oriv__
-_ _I_ re _nI _fo _cla_ _ss________ �L- ----- -----�
A. 13. ábra: Az lnterop. VbComCarServer.dll szerelvény

Saját C#-kliensalkalmazás készítése

Mivel a CLR futási időben automatikusan létrehozza a szükséges RCW-t, a


C#-alkalmazásunk cocar, carType, Engine és IDrive Info típusai közvetlenül
programozhatók, mintha mindegyiküket felügyelt kód segítségével imple­
mentáltuk volna. A teljes megvalósítás az elemzéssei együtt a következő:

ll Mindenképpen importáljuk a Vb6comcarserver névteret.


class Program
{
static void Main(string[] args)
{
console.writeLine("***** cocar client App *****");

ll Korai kötés használatával létrehozzuk a COM-osztályt.


cocar mycar = new cocar();

ll A Blewup esemény kezelése.


mycar.Blewup += new __ cocar_BlewUpEventHandler(mycar_Blewup);

ll A create() metódus meghívása.


mycar.create(SO, 10, carType.BMW);

ll sofőr nevének beállítása.


IDriverinfo itf = (IDriverinfo)mycar;
itf.DriverName = "Fred";
Console.writeLine("orive is named: {O}", itf.oriverName);

874
Az együttműködési szerelvény

ll Autó típusának kiírása.


console.writeLine("Your car is a {O}.", myCar.CarMake);
Console.writeLine();

ll Az Engine beolvasása és a hengerek nevének kiírása.


Engine eng= mycar.GetEngine();
Console.writeLine("Your Cylinders are named:");
string[] names (string[])eng.Getcylinders();
=

foreach (string s in names)


{
console.writeLine(s);
}
Console.writeLine();

ll Az autó gyorsítása az esemény kiváltásához.


for (int i = O; i < 5; Í++)
{
mycar. speedup O ;
}
}

ll A Blewup esemény kezelője.


static void mycar_Blewup()
{
console.writeLine("Your car is toast!");
}
}

A Geteylinders O hívásakor a visszatérési értéket sztringtömbbé kasztoltuk.


Ennek oka az, hogy a COM-tömböket (általában) SAFEARRAY COM-típus
képviseli (mindig ez a helyzet, ha a VB6 használatával készítünk COM-alkal­
mazásokat). Az RCW system.Array objektumokra képezi le a SAFEARRAY
típusokat ahelyett, hogy automatikusan egy C#-szintaxissal ábrázolt tömbbe
képezné le őket. Ezáltal, ha az Array objektumot egy string [J típusba
kasztoljuk, jóval könnyebben tudjuk feldolgozni a tömböt.

Együttműködés a CoCar tipussal

Amikor létrehoztuk a cocar típust a VB6-ban, a VB6-fordító által automatiku­


san generált alapértelmezett interfészen (_cocar) kívül definiáltuk és imple­
mentáltuk az IDriverinfo egyedi COM-interfészt is. Amikor a Main() metó­
dusunk létrehozza a cocar egy példányát, közvetlen hozzáféréssel csak
_cocar interfész tagjaihoz rendelkezünk, ez pedig a COM-osztály nyilvános

tagjaiból áll:

875
A függelék: A COM és a .NET együttműködési képessége

ll Most valóban a [default] intertésszel dolgozunk.


mycar.Create(SO, 10, CarType.BMW);

Így ahhoz, hogy meghívhassuk az IDriverrnfo interfész DriverName tulajdon­

ságát, az alábbiak szerint a cocar objektumot explicit módon kasztolnunk kell


az IDriverinfo interfészre:

ll Sofőr nevének beállítása.


IDriverinfo itf = (IDriverrnfo)mycar;
itf.DriverName " Fred";
=

Console.WriteLine("Drive is named: {O}", itf.DriverName);

Amikor egy típuskönyvtárat együttműködő szerelvénnyé konvertálunk, az


olyan class utótaggal rendelkező típusokat fog tartalmazni, amelyek feltár­
ják az összes interfész összes tagját. Így egyszerűsíthe�ük a programozást, ha
cocar objektum helyett cocarelass objektumot hozunk létre és használunk.

Nézzük meg például a következő metódust, amely a cocar alapértelmezett


interfészének tagjait, valamint az IDriverinfo tagjait alkalmazza:

static void usecar()


{
ll A class utótaggal rendelkező típusok megmutatják az összes
ll interfész összes tagját.
cocarelass c = new cocarclass();

ll Ez a tulajdonság az IDriverrnfo tagja.


c.DriverName "Mary";
=

ll Ez a metódus a _cocar tagja.


c. speedup O ;
}

Ahhoz, hogy választ kapjunk arra a kérdésre, hogy ez az egy típus pontosan
hogyan muta�a meg az összes megvalósított interfész tagjait, a Visual Studio
2008 Objektum böngészőjében nézzük át a cocarel ass implementált interfé­
szeinek és ősosztályainak listáját (lásd az A. l4. ábrát).
Ez a típus megvalósí�a a _cocar és _IDriverrnfo interfészeket, és "normá­
lis" nyilvános tagként teszi hozzáférhetővé őket.

876
Az együttműködési szerelvény

/Object Browser 6r•m.csj �


x,
.
r Browse: My Solution l Í :tJ i IT§
l· Search.
h<
. ... ..
ll : . ·Q
,_ •
l
D
. ". l j•· · · '•Y Create(short. short. Vb6ComCarServer.Carji
> • · CoC"CiassQ

$ ...o
. CoCar_vO
$· .· � _CoCar_BiewUpEventHandler
,
l ' l \. . •Q GetEngineQ
j . . •..· .·9 SpeedUpQ
ffi- . .-o _CoCar_Event
�· '1$ _CoCar_SmkHelper
'

�-. - -�. "ii§iM@


.
.

e t:.J Base Types


�·ij l
1 !. · · >':.� CarMake
5
: .·

···!fff
'
· CurrentSpeed
--'

l-·· �
· DriverName
'i
l

. i : .. f BlewUp
;..... ....o CoCar
@ . . -o CoCar j"1•�-==--==-
- �-�--"7.- =�-=�7-':..'.u·'1··---
CoCarCiass

l
_, _CoCar_Event

J
public class
.

.-o JDriverlnfo Member of Vb6ComCarServer


··<1$ Object l,;;" .._
________ _j
A. 14. ábra: A CoearClass összetétele

COM-események elfogása

Az előző kötet 11. fejezetében megismerkedhettünk a .NET eseménymodell­


jével. Ez az architektúra azon alapul, hogy a programlogika folyását az al­
kalmazás egyik részéből a másikba delegáljuk A kérések továbbításáért fele­
lős entitás a system. Multi eastDelegate egyik leszármazott típusa, amelyet
közvetetten a delegate kulcsszó használatával hozunk létre a C#-ban.
Amikor a tl b imp . exe segédprogram eseménydefiníciókat talál a COM-ki­
szolgálók típuskönyvtárában, válaszként olyan felügyelt típusokat hoz létre,
amelyek beburkolják az alacsony szintű COM kapcsolódási pontot. Ezeknek
a típusoknak a használatával azt a látszatot kelthetjük, mintha hozzáadtunk
volna egy tagot a system. Multi castDe l egate belső metóduslistájához. A háttér­
ben természetesen a proxy a felügyelt megfelelőjére képezi le a bejövő COM­
eseményt. Az A.3. táblázat röviden ismerteti ezeket a típusokat.

�.,-�io'flt#·�·,�'!i�'v'ft .._ .)' •.�on,o�""l:'�'!"t�;.;.�··�- -'·i\." .,_..�·>JI�"--�'UD"<ii '


�-;
. ��-� , ., , ... ... ; �.
.., · ·.� "'� !�!�1'-����.J f.:· ._;...·-.: -·:e��-..� �.� �� ..·�:.. �·
.Jl ?z��!f�...
"'b-,;'.. �... �........ 7··\'�-�-....�-:- �· ..-t�- w� . . �1

..��,.
,
, ,
_·-r:;::.- � �·. .:·•...... ..,\:.-· .
- �.., '"!
... ':."�·-
.-�.-, -: : .. �� .-..,�:!.
.-r'"!. r:..
� .. ..i!"�::,s"·�
•.;.l'- • . .J1'··�·
• ' • ._
: �:>l':'-.. "7���� � ... -...t�,�.��- .. . '· . ,. '
.
_cocar Event Ez a felügyelt interfész definiálja az add és a
remave tagokat egy metódus hozzáadásá­

hoz (vagy eltávolításához) a system.


Multi castDelegate láncolt listáján.

_cocar_BlewUpEventHandler Ez egy felügyelt metódusreferencia (amely


a System. Multi castDe l egate osztályból
származik).

877
A függelék: A COM és a .NET együttműködési képessége


.-,�
t-"'�'-, "'. �-..
_ �� .---�- - �...-- . -.·-- "'...�l!/:
�t �

-l'
...
�• • •
.. '!...
{ ' • "

_,,..\.. � .
- �
. . '' - ' -- .;t:· . - ..•,

_cocar_sinkHelper Ez a generált osztály valósítja meg a kime­


nő interfészt egy .NET-alapú nyelőobjek­
tumban.

A. J. táblázat: COM-esemény segédtípusok

A bejövő COM-eseményeket ugyanúgy kezelhetjük, mint ahogyan a metó­


dusreferenciám alapuló .NET-eseményeket:

class Program
{
static void Main(string[] args)
{
console.writeLine(";'**** cocar client App **''**");
cocar mycar = new cocar();

ll A Blewup esemény kezelése.


mycar.Blewup += new cocar_BlewUpEventHandler(mycar_Blewup);
__

ll A Blewup esemény kezelője.


static void mycar_Blewup()
{
console.writeLine("Your car is toast!");
}
}

A C#-kódunk az összes eseményközpontú jelölést (névtelen metódusok, me­


tóduscsoport átalakítás, lambda kifejezések stb.) alkalmazhatja a COM-objek­
tumok eseményeinek elfogása során.

Forráskód A CSharpCarClient kódfájlokat a forráskódkönyvtár A Függelék alkönyvtára tar·


talmazza. A forráskódkönyvtárról lásd a Bevezető XIV. oldalát.

A fent megvizsgált módszerek bármilyen COM-kiszolgáló esetében működ­


nek. Ezt azért fontos megjegyezni, mert előfordulhat, hogy több COM­
kiszolgálót soha senki nem fog natív .NET-alkalmazásként újraírni. A Micro­
soft Outlook objektummodellje például jelenleg COM-könyvtárként jelenik
meg. Ezért, ha olyan .NET-programot kell készítenünk, amely ezzel a ter­
mékkel dolgozik együtt, akkor az együttműködési réteg alkalmazása (pilla­
natnyilag) kötelező.

878
A COM és a .NIT együttműködési képessége

A COM és a .NET együttműködési


képessége

A továbbiakban azt a folyamatot vizsgáljuk meg, amelyben a COM-alkalma­


zások .NET-típussal kommunikálnak. Az együttműködésnek ez az "iránya"
lehetővé teszi, hogy a korábbi COM-kódalapok (például egy már létező VB6-
projekt) kihasználják az újabb .NET -szerelvényekben rejlő funkcionalitást. Ez
a helyzet ritkábbarr fordul elő, mint a .NET-COM-együttműködés, ám érde­
mes erre is figyelni.
Ahhoz, hogy a COM-alkalmazás .NET-típust használhasson, valahogy át kell
vernünk a COM-programot, hogy azt higgye, a felügyelt .NET-típus valójában
nem felügyelt típus. Lényegében lehetővé kell tenni a COM-alkalmazás számára,

hogy a COM-architektúra által megkövetelt funkcionalitás segítségével működ­


hessen együtt a .NET-típussal. A COM-típusnak például meg kell szereznie az új
interfészeket a Queryrnterface() hívásokon keresztül, szimulálnia kell a nem
felügyelt memóriakezelést az AddRef() és Release() metódusok segitségével,
használnia kellene a COM kapcsolódásipont-protokollját, és így tovább.
A COM-kliens rászedésén túl a COM és a .NET együttműködési képesség
magába foglalja a COM-futtatómotor átverését is. A COM-kiszolgálót a CLR
helyett a COM-futtatórendszerrel aktiváljuk Ennek érdekében a COM-futtató­
rendszemek több információdarabot meg kell keresnie a rendszerleíró adatbá­
zisban (programazonosítókat, Cl.S-azonosítókat, interfészazonosítókat és így
tovább). A probléma természetesen az, hogy a .NET-szerelvényeket először is
nem a rendszerleíró adatbázisban regisztráljuk
Ennek köszönhetőerr a következő lépéseket kell végrehajtanunk, ha a .NET­
szerelvényeinket szeretnénk elérhetővé tenni a COM-kliensek számára:

l. Regisztráljuk a .NET-szerelvényünket a rendszerleíró adatbázisban,


hogy a COM-futtatórendszer megtalálhassa.

2. Hozzunk létre COM-típuskönyvtárfájlt (*.t l b) (a .NET-metaadat alap­


ján), és tegyük lehetővé a COM-kliens számára, hogy együttműköd­
hessen a nyilvános típusokkaL

3. Telepítsük a szerelvényt ugyanabba a könyvtárba, mint ahol a COM­


ügyfélprogram található, vagy (inkább) telepítsük a globális szerel­
vénytárba.

879
A függelék: A COM és a .NET együttműködési képessége

Ezeket a lépéseket végrehajtha�uk a Visual Studio 2008-ban vagy a parancs­


sorban azokkal a különböző eszközökkel, amelyeket a .NET Framework 3.5
SDK foglal magában.

A System.Runtime.lnteropServices attribútumai

A fenti lépések végrehajtása mellett általában a C#-típusainkat különböző


olyan .NET-attribútumokkal kell ellátnunk, amelyeket a syst e m . Run ti m e .
Interopservices névtér definiál. Végeredményben ezek az attribútumok sza­
bályozzák azt, hogy a COM-típuskönyvtár hogyan készüljön el, egyúttal
meghatározzák azt is, hogy a COM-alkalmazás hogyan legyen képes együtt
dolgozni a felügyelt típusainkkaL Az A.4. táblázat bemutat néhány (de nem
minden) attribútumot, amelyeket a létrehozott COM-típuskönyvtár vezérlé­
sére használhatunk.

-� ·.· . '
' ' . ·-.· �·-·:.:;�7.}.���:-���1;.:;:�: -�-.-� �·: '·: '._- i
.
.
• . ·.,t'.. ::.".. :,,.v ...'.. . . ' .

[Classinterface] Ezzel az attribútummal hozha�uk létre egy .NET­


osztálytípus alapértelmezett COM-interfészét

[comclass] Ez az attribútum a [Cl ass i c i nte r f a ce ] attribútum­


hoz hasonlít, azt leszámítva, hogy ezzel előállítha�uk
a típuskönyvtárban lévő COM-típusok osztályazono­
sítójához (CLSID) és interfészazonosítóihoz használt
GUID-azonosítókat

[Dis p id] Ezt az attribútumot egy taghoz rendelt DISPID-értékek


bedrótozására alkalmazha�uk késői kötés céljából.

[Guid ] Ez az attribútum egy GUID-érték bedrótozására


szolgál a COM-típuskönyvtárban.

[In] Ez az attribútum a COM lDL-ben bemeneti paramé­


terként biztosít egy tagparamétert

[InterfaceType] Ez az attribútum szabályazza azt, hogy egy .NET­


interfész hogyan jelenjen meg a COM számára (csak
IDi spatch, kettős vagy csak IUnknown legyen).

[Out] Ez az attribútum a COM IDL-ben kimeneti paramé­


terként biztosít egy tagparamétert

A.4. táblázat: A System.Runtime.InteropServices legfontosabb tagjai

880
A CCW szerepe

Egyszerű COM-.NET együttműködési helyzetekben nem szükséges tucatnyi


attribútummal ellátni a .NET-kódot annak érdekében, hogy szabályozhassuk
az alapul szolgáló COM-típuskönyvtár definiálását. Ha azonban nagyon spe­
cifikusnak kell lennünk abban, hogy a .NET-típusok hogyan jelenjenek meg a
COM számára, akkor célszerű minél többet tudni a COM IDL-attribútumok­
ról, hiszen a system. Run ti me. Interopservi c es névtérben definiált attribútu­
mok nagyjából ezeknek az IDL-kulcsszavaknak a felügyelt definíciói.

A CCW szerepe

Nézzük meg röviden azt, hogy a COM-prograrnak pontosan hogyan mű­


ködnek együtt a .NET-típusokkal a CCW (COM Callable Wrapper - COM­
ból hívható burkoló) segítségéveL Amikor .NET-program COM-típussal
kommunikál, a CLR létrehoz egy futási időben hívható burkolót. Ehhez ha­
sonlóan, amikor a COM-kliens hozzáfér egy .NET -típushoz, a CLR felhasznál
egy proxyt, amely a COM-ból hívható burkoló névre hallgat, hogy gondos­
kadjon a COM-.NET-átalakításról (lásd az AlS. ábrát).
Mint minden COM-objektum, a CCW is referenciaszámlált entitás, ugyan­
is a COM-kliens feltételezi, hogy a CCW valódi COM-típus, és ezért tartania
kell magát az AddRef() és a Release() szabályaihoz. Amikor a COM-kliens
véglegesen eldobta, a CCW elengedi a referenciáját, amely a valódi .NET-tí­
pusra hivatkozik, és az ettől a pillanattól szemétgyűjtésre lesz jelölve.

COM­
alkalmazás

A.15. ábra: A COM-típusok a CCW segítségével kommunikálnak a .NET-típusokkal

881
A függelék: A COM és a .NET együttműködési képessége

A CCW több COM-interfészt automatikusan megvalósít, és így még inkább az a


látszat, hogy a proxy eredeti coclass. A .NET-típus által definiált egyedi interfé­
szek készlete mellett (beleértve az osztályinterfész nevű entitást, ezt lásd később), a
CCW támoga�a az A.5. táblázatban leírt szabványos COM-viselkedéseket

IconnectionPoint Ha a .NET-típus eseményeket támogat, akkor eze­


IConnectionPointContainer ket COM kapcsolódási pontok képviselik.

IEnumvariant Ha a .NET-típus támogatja az IEnumerable inter­


fészt, akkor a COM-kliens számára szabványos
COM-felsorolásként jelenik meg.

Ierrorrnfo Ezek az interfészek teszik lehetövé, hogy a


ISupportErrorinfo coelassok COM-hibaobjektumokat küldjenek.

ITypeinfo Ezek az interfészek teszik lehetövé a COM-kliens


IProvideclassrnfo számára, hogy szimulálja egy szerelvény COM-tí­
pusinformációinak kezelését. Valójában azonban, a
COM-kliens a .NET-metaadatokkal működik együtt.

Iunknown Ezek a COM-interfészek a .NET-típusok késői és


rdispatch korai kötését támogatják.
IDispatchEx

A. 5. táblázat: A CCW támogatszámos belső COM-interfészt

A .NET-osztályinterfész szerepe

A klasszikus COM-ban a COM-kliens csak egy interfészreferencia használa­


tával kommunikálhat a COM-objektumokkaL Ezzel szemben a .NET-típusok­
nak nem kell támogatniuk semmilyen interfészt, ez pedig nyilvánvalóan
probléma a COM-hívó számára. Mivel a klasszikus COM-kliensek nem dol­
goznak objektumreferenciákkal, a CCW másik feladata olyan osztályinterfész
biztosítása, amely a típus nyilvános szektora által definiált tagokat képviseli.
A CCW ugyanazt a megközelítést használja, mint a Visual Basic 6.0.

882
A .NET-osztályinterfész szerepe

Osztályinterfész definiálása

Ahhoz, hogy meghatározhassunk egy osztályinterfészt a .NET-típusaink


számára, alkalmaznunk kell a [classrnterface] attribútumot minden olyan
nyilvános osztályon, amelyet biztosítani szeretnénk a COM-nak. Ezzel azt ér­
jük el, hogy az osztály összes nyilvános tagja megjelenik egy alapértelmezett,
automatikusan generált interfészen, és ez ugyanazt az elnevezési hagyo­
mányt használja, mint a VB6 (_p.zosztályNeve). Az attribútum alkalmazása
alapvetően tetszőleges, ám meglehetősen gyakran használjuk majd. Ha még­
sem, akkor az egyetlen mód, amellyel a COM-hívó kommunikálni tud a tí­
pussal, a késői kötés használata (ez jóval kevésbé típusbiztos, és általában ki­
sebb teljesítményt eredményez).
A [classinterface] attribútum támogat egy megnevezett tulajdonságot
(cl assinte rfaceType), amely szabályozza, hogy az alapértelmezett interfész
pontosan hogyan jelenjen meg a COM-típuskönyvtárban. Az A.6. táblázat
ismerteti a lehetséges beállításokat.

AutoDispatch Jelzi, hogy az automatikusan generált alapértelmezett


interfész csak a késői kötést támogatja, és megfelel a
[Classinterface] attribútum elhagyásának

AutoDual Jelzi, hogy az automatikusan generált alapértelmezett


interfész "kettős interfész", így korai és késői kötés
használatával is együttműködhetünk vele. Ez ugyan­
olyan viselkedés, mint amelyet a VB6 követ, amikor
definiálja az alapértelmezett COM-interfészt.

None Azt jelzi, hogy a rendszer nem készít interfészt az osz­


tály számára. Ez akkor lehet hasznos, amikor definiál­
tuk a saját erősen típusos .NET-interfészeinket, ame­
lyeket elérhetővé teszünk a COM számára, és nem sze­
retnénk, hogy "potyautas" interfészünk legyen.

A.6. táblázat. A ClassinterfaceType felsorolás értékei

A következő példában a cl assinterfaceType. AutoDual tulajdonsággal jelöljük


meg az osztályinterfészt Így a késői kötéssel működő kliensek, mint a
VBScript, az IDispatch használatával hozzáférhetnek az Add() és a subtract()
metódusokhoz, míg a korai kötéssel működő kliensek (mint a VB6 vagy a
C++) az osztályinterfészt használhatják (amelynek vbootNetcalc a neve).

883
A függelék: A COM és a .NET együttműködési képessége

Saját .NET-tipusok készítése

Annak bemutatásához, hogy a COM-típus hogyan kommunikálhat felügyelt


kóddal, tételezzük fel, hogy létrehoztunk egy egyszerű C#-osztálykönyvtár­
projektet corneallableDotNetserver névvel, amely definiálja a DotNetcal c osz­
tályt. Ez az osztály két egyszerű metódust definiál: az Add() és a subtract()
metódusokat. A megvalósítás logikája nagyon egyszerű, figyeljük meg azon­
ban a [Class Interface] attribútum használatát:

ll Szükségünk van rá, hogy megszerezzük a szükséges


ll együttműködési attribútumokat.
using system.Runtime.Interopservices;

namespace ComcallableDotNetserver
{
[Classinterface(ClassinterfaceType.AutoDual)]
public class DotNetcalc
{
public int Add(int x, int y)
{ return x + y; }

public int subtract(int x, int y)


{ return x - y; }
}
}

A COM világában majdnem mindent egy 128 bites számmal, az úgynevezett


GUID-dal azonosítunk Ezeket az értékeket a rendszerleíró adatbázis rögzíti
azért, hogy definiálja egy COM-típus azonosítóját. Jelenleg nem határoztunk
meg GUID-értékeket a DotNetca l c osztályunk számára, ezért a típuskönyv­
tár-exportáló eszköz (tlbexp.exe) menet közben hozza létre a GUID azonosí­
tókat Ezzel a megközelítéssel az a probléma, hogy minden alkalommal, ami­
kor a típuskönyvtárat generáljuk (és ezt hamarosan megtesszük), egyedi
GUID-értékeket kapunk, amelyek lehetetlenné tehetik a meglévő COM-klien­
sek működését.
Saját GUID-értékek definiálásához használha�uk a gui dg en. exe segéd­
programot, amely a Visual Studio 2008 Tools > Create Guid menüpon�ából
érhető el. Bár az eszköz négy GUID-formátumot biztosít a számunkra, a
[Gui d] attribútum megköveteli, hogy az A.l 6. ábrán látható módon a GUID­
értéket a rendszerleíró formátumopció használatával adjuk meg.

884
Saját .NET-típusok készítése

CreateGUID ld ®liliif3íli
Choose the desired for!Mt below. then select '"Copy'" to
copy the results to the cfiptxwd (the results can then be l� l
pasled in!o )'OU! SOU<ce code� Choos e '"E><it'" when
l t!ewGUIO
done. l
GUID F011Mt l
EJ!Íl l
6!. IMPLEMENT_OLECREATE(.. )
0� DEFINE_GUIO(... )
0;). sl�ic const struct GUIO { ... )

@�J!��[���.:::rc.��''''!:�:::�Ji
Resuli

{4137CFAB·5300-4667·ADF2·8E2CD63CB462}

A. 16. ábra. GUID-érték előállítása

Ha ezt az értéket vágólapra másoljuk (a Copy GUID gomb segítségéve!), ar­


gumentumként beilleszthe�ük a [Gu id] attribútumba. A kapcsos zárójeleket
el kell távolítani a GUID-érték ből. A módosított DotNetca lc osztálytípus (a
GUID-érték különböző lehet) a következő:

[Classinterface(ClassinterfaceType.AutoDual)]
[Guid("4137CFAB-530B-4667-ADF2-8E2CD63CB462")]
public class DotNetcalc
{
public int Add(int x, int y)
{ return x + y; }

public int Subtract(int x, int y)


{ return x - y; }
}

A Solution Explorerben kattintsunk a Show All Files gombra, majd nyissuk


meg a Properties ikon alatt található Assemblyinfo. cs fájlt. Az alapértelmezés
szerint az összes Visual Studio 2008 projekt munkaterülete rendelkezik szerel­
vényszintű [Guid] attribútummal, amely a .NET-kiszolgáló alapján generált tí­
puskönyvtár GUID-jának azonosítására szolgál (ha a COM hozzáférhet).

ll A következő GUID a típuskönyvtár azonosítója, ha ez a projekt


ll hozzáférhető a COM számára.
[Assembly: Guid("EB268C4F-EB36-464C-8A25-93212C00DC89")]

885
A függelék: A COM és a .NET együttműködési képessége

Erős név definiálása

Hasznos gyakorlat, ha a COM számára biztosított összes .NET -szerelvény


erős nevet kap, és a globális szerelvénytárba (a GAC-ba) telepítjük őket. A
normál működéshez ez nem alapfeltétel, ha azonban nem a globális szerel­
vénytárba telepítjük a szerelvényt, ugyanabba a könyvtárba kell másolnunk,
ahol az a COM-alkalmazás van, amely a szerelvényt szeretné használni.
A Properties szerkesztő Signing lapján hozzunk létre új *. snk fájlt aláírási

célokra. Ekkor lefordíthatjuk a szerelvényünket, és a gacuti l.exe segédprog­


rammal telepíthetjük a corneallab leDotNetserver.dll fájlt a globális szerel­
vénytárba (ehhez részleteket az előző kötet 15. fejezetében találhatunk).

gacutil -i ComcallableDotNetserver.dll

A tipuskönyvtár létrehozása és
a .NET-tipusok regisztrálása

Már elkészíthetjük a szükséges COM-típuskönyvtárat, és regisztrálhatjuk a


.NET-szerelvényünket a rendszerleíró adatbázisban ahhoz, hogy a COM
·használhassa őket. Ennek végrehajtásához két lehetséges megközelítés áll a
rendelkezésünkre. Az első lehetőség, hogy a .NET Framework 3.5 SDK
regasm. exe parancssori eszközét alkalmazzuk Ezzel az eszközzel több listá­
zást hozzáadhatunk a rendszerleíró adatbázishoz, és ha megadjuk a /tlb
kapcsolót, akkor létrehozhatjuk a szükséges típuskönyvtárat is:

regasm comcallableDotNetserver.dll /tlb

Megjegyzés A .NET Framework 3.5 SDK a t l bexp. exe nevű eszköz biztosítja. A regasm. exe
segédprogramhoz hasonlóan ez az eszköz .NET-szerelvényből hoz létre típuskönyvtárakat, vi­
szont nem adja hozzá a szükséges bejegyzéseket a rendszerleíró adatbázishoz. Emiatt általá­
ban a regasm. exe-t használjuk az összes szükséges lépés végrehajtásához.

Bár a regasm. exe a lehető legrugalmasabb eszköz, ha a COM-típuskönyvtár

készítését szeretnénk szabályozni, a Visual Studio 2008 egy kezesebb alterna­


tíva. A Properties szerkesztőben az A.17. ábrán látható módon kapcsoljuk be
a Register for COM interop jelölőnégyzetet a Compile lapon, majd fordítsuk
le újra a szerelvényünket.

886
Az exportált típus adatainak a vizsgálata

Arnint futtattuk a regasm.exe segédprogramot, vagy engedélyeztük a Re­


gister for COM interop lehetőséget, láthatjuk, hogy a bin\Debug mappa ( * .tlb
kiterjesztéssel) egy COM-típuskönyvtárfájlt tartalmaz.

Forráskód A ComCallableDotNetServer kódfájlokat a forráskódkönyvtár A Függelékének al­


könyvtára tartalmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

/�DotNetserverr��csrQbject� • x

Application
Configuration: lActíve (Oebug)
·l
Build
Platform: lActíve (Any CPU)
·l
Build Ev�nts

Output path: bm\Uebug\


Deb ug

O XML dcx:umentation file:
Resources l
0 Register for COM interop
Settings

"' Generat� serializatíon assembly: I Auto - •l [J


---�---�� l'
l
j
ur

[l
.c

A. 17. ábra: Szerelvény regisztrálása COM-mal való együttműködésre a Visual Studio 2008 használatával

Az exportált tipus adatainak a vizsgálata

A megfelelő COM-típuskönyvtár tartalmát az OLE View segédprogrammal


nézhetjük meg, ha betöltjük a *.tlb fájlt. Ha betöltjük a comcallableootNet­
server.tlb fájlt (a File > View Type Library menüpont segítségéve!), láthat­
juk az összes .NET-osztálytípusunk COM-típusleírását. A ootNetcalc osztály
defirúciója például előírja, hogy az osztály a [class Interface] attribútumnak
köszönhetően támogassa az alapértelmezett _ootNetclass interfészt, valamint
az _object interfészt. Ez a System.obj ect által definiált funkcionalitás nem
felügyelt defirúciója:

[uuid(8 873 7214-2E55-4DlB-A354-7A538BD9AB2D),


version(l.O), custom({OF21F359-AB84-41E8-9A78-36Dll0E6D2F9},
"comcallableootNetServer.DotNetcalc")]
coelass ootNetcalc {
[default] interface _ootNetCalc;
interface _object;
};

887
A függelék: A COM és a .NET együttműködési képessége

Ahogy a [Classrnterface] attribútummal megadtuk, az alapértelmezett in­


terfészt kettős interfészként állítottuk be, ezért korai és késői kötés segítségé­
vel is hozzáférhetünk

[odl, uuid(AC807681-8C59-39A2-AD49-3072994ClEBl), hidden,


dual, nonextensible, oleautomation,
custom({0F21F359-AB84-41E8-9A78-36Dll0E6D2F9},
"comcallableDotNetServer.DotNetcalc")]
interface _DotNetcalc : IDispatch {
[id(OOOOOOOO), propget,
custom({54FC8F55-38DE-4703-9C4E-250351302BlC}, "l")]
HRESULT ToString([out, retval] BSTR* pRetVal);
[id(Ox60020001)]
HRESULT Equals( [in] VARIANT obj,
[out, retval] VARIANT_BOOL* pRetVal);
[id(Ox60020002)]
HRESULT GetHashcode([out, retval] long* pRetval);
[id(Ox60020003)]
HRESULT GetType([out, retval] _Type** pRetval);
[id(Ox60020004)]
HRESULT Add([in] long x, [in] long y,
[out, retval] long* pRetval);
[id(Ox60020005)]
HRESULT subtract( [in] long x, [in] l ong y,
[out, retval] long* pRetval);
};

A _DotNetCalc interfész nemcsak az Add() és a subtract() metódusokat írja


le, hanem hozzáférhetővé teszi a system. obj ect által örökölt tagokat. Sza­
bálynak tekinthe�ük, hogy· amikor egy .NET -osztálytípust a COM számára
hozzáférhetővé teszünk, a származási láncban található összes nyilvános me­
tódus megjelenik az automatikusan generált osztályinterfészerr keresztül.

Visual Basic 6.0 tesztkliens készitése

Mivel a .NET-szerelvényt megfelelőerr konfiguráltuk, így együttműködhet a


COM-futtatórendszerrel, készíthetünk egy COM-klienst. Hozzunk létre egy
egyszerű VB6 Standard *.exe projekttípust (VB6DotNetClient néven), majd
állítsunk be referenciát az újonnan generált típuskönyvtárra (lásd az A.18.
ábrát).

888
Visual Basic 6.0 tesztkliens készítése

References - Project! ..
Avaiable Refurenc:es:
l OK
l
DCmí20
DColeaguein1:>0rt 1.0 Type Lilrory
A <:ancel
l
D colaader 1.0 Type Lilrary o
D colaader 8.0 Type Lilrary
DCOM + l.O Aam Type Lilrary
Browse.••
l
��i���.l� ��ari ..!J
D Common L� Ru:ltime Execution Engine 2.0 Lb Priocity
DC<>mpat!Jl 1.0 Type Lilrary
D ComPkJs 1.0 Catalog Repiicalion Type Lilrary

l
D COI!"(lOrlentelrle VSf1exGrld 8.0 (lig1t) �
D Comect to a Networl: Projector l. O Type Lilrary
D eRDesigner 9 Type Lilrary �
nrrvntpyt 1.0TvnP l iv;vv
•L�--'� •

-CotnCalableOotNetServer

l
l Location:
L�:
C:"tlsers\Andrew Troe!lsen.,...y Bool<s�# ..-.d the .!ET Platfon
St..-.dard

A. 18. ábra: Hivatkozás a .NET-kiszolgálóra a VB 6-ból

Ami a GUI front endet illeti, legyen teljesen egyszerű. Egyszerűen egy gomb­
bal kezeljük a DotNetcalc .NET-típust. A kód a következő (figyeljük meg, hogy
meghívjuk az _obj ect interfész által definiált ToSt ring() metódust):

Private sub btnuseDotNetobject_click()


' A .NET-objektum létrehozása.
Dim c As New DotNetcalc
MsgBox c.Add (lO , 10), , "Adding with .NET"

' A system.object néhány tagjának meghívása.


MsgBox c.ToString, , "ToString value"
End Sub

Forráskód A VB6DotNetClient kódfájlokat a forráskódkönyvtár A Függelékének alkönyvtára


A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.
tartalmazza.

Láttuk olyan .NET-alkalmazások készítésének folyamatát, amelyek a COM­


típusokkal kommunikálnak, és olyan COM-alkalmazásokét, amelyek a .NET­
típusokkal kommunikálnak. Ezzel már biztos alappal rendelkezünk a továb­
bi kutatáshoz.

889
A függelék: A COM és a .NET együttműködési képessége

Összefoglalás

A felügyelt és nem felügyelt kódnak a jövőben meg kell tanulnia időről időre
együtt dolgozni. Emiatt a .NET platform különböző módszereket kínál a két
világ összeolvasztására.
A függelék nagyobbik része a korábbi COM-komponenseket alkalmazó
.NET -tipusok részleteire összpontosított. A folyamat egy szerelvényproxy
készítésével kezdődik a COM-típusok számára. Az RCW továbbítja a híváso­
kat az alapul szolgáló COM-binárisnak, és felügyeli a COM-típusok leképe­
zését .NET -ekvivalensükre.
Ezután azt vizsgáltuk, hogy a COM-típusok hogyan hívhatják az újabb .NET­
típusok szolgáltatásait. Ehhez arra van szükség, hogy a .NET-szerelvényben lét­
rehozható típusokat regisztráljuk a COM számára, és a .NET -típusokat leírja egy
COM-típuskönyvtár.

890
B FÜGGELÉK

{llatformfüggetlen .NET­
fejlesztés a Manóval

Ez a függelék a platforrnak közötti C#- és a .NET-fejlesztés témakörébe en­


ged betekintést, méghozzá egy nyílt forráskódú .NET-implementáció segít­
ségéve!, amelyet Manónak hívnak (ha valaki a név eredetére kíváncsi, a
" "
"Mono spanyolul majmot jelent, azaz "kódmajmot , azokat illetik ezzel a
névvel, akik foglalkozásszerűen írnak kódot). Ebben a függelékben megvizs­
gáljuk a CLl (Common Language Infrastructure- közös nyelvi infrastruktú­
ra) szerepét, a Mono általános hatókörét és a Mono néhány fejlesztőeszközét.
Mire a függelék végére érünk, ha kedvünk tartja, teljes nyugalommal vethet­
jük bele magunkat a Mono-fejlesztés további részleteibe.

Megjegyzés Ha további részletekre van szükség a plattormok közötti .NIT-fejlesztéssel kapcso­


latban, olvassuk el Mark Easton és Jason King Cross-Platform .NET Development: Using Mono,
Portab/e .NET, and Microsoft . NET című könyvét (Apress, 2004).

A .NET platformfüggetlen természete

A múltban, amikor a programozók egy Microsoft fejlesztői nyelvet vagy egy


programozási keretrendszert (VB6, MFC, COM, ATL stb.) használtak, bele kellett
törődniük abba, hogy (nagyjából) csak Windows operációs rendszeren működő
szaftvereket írhatnak. Sok .NET-fejlesztő, miután hozzászokott a korábbi Micro­
soft fejlesztői nyelv lehetőségeihez, meglepődik, amikor megtudja, hogy a .NET
platformfüggetlen. És ez valóban így van. A Microsoft Windowstól eltérő operáci­
ós rendszereken is lehet .NET-szerelvényeket fordítani és futtatni.
A Manóhoz hasonló nyílt forráskódú .NET -megvalósítások használatával a
Mac OS X, a Solaris, az AIX, valamint több Unix/Linux disztribúció kezelheti
.NET bináris fájljainkat Emellett a Manónak a Microsoft Windows (micsoda
meglepetés...) rendszerhez van telepítőcsomagja.
B függelék: Platformfüggetlen .NET-fejlesztés aMONO-val

Ezért lehetséges .NET-szerelvényeket készíteni és futtatni a Windows ope­


rációs rendszeren anélkül, hogy valaha telepíteni kellene a Mkrosoft .NET
Framework 3.5 SDK-t vagy a Visual Studio 2008 IDE-t.

Megjegyzés Azt azonban ne feledjük, hogy ha csak Windows operációs rendszer alá szeret­
nénk programokat írni, akkor erre aMicrosoft .NET Framework 3.5 SDK és a Visual Studio 2008
a legjobb megoldás.

Még azután is, hogy a fejlesztök tudomást szereznek a .NET-kód plattormok


közötti fejlesztést biztosító lehetőségéről, gyakran azt feltételezik, hogy a
platformfüggetlen .NET-fejlesztés hatóköre a "Hello World" jellegű parancs­
sori alkalmazásokra korlátozódik. Valójában azonban a szövegben látott
alapvető névterek és nyelvi funkciók használatával készíthetünk olyan ter­
inékszintű szerelvényeket, amelyek az ADO.NET-, a Windows Forms (olyan
más CUI-eszközrendszerek mellett, mint a GTK# és a Cocoa#), az ASP.NET­
techÍlológiát és az XML-webszolgáltatásokat használják.
A .NET plattormok közötti természete máshogyan alakult, mint amilyen
megközelítést a Sun Microsystems használt a Java programozási platform ké­
szítésekor. A Javával ellentétben a Microsoft nem adja ki a saját .NET-telepítőit
Mac, Linux stb. alá. Ehelyett formalizált specifikációkészletet biztosít, amelyet
más entitások útmutatóként használhatnak .NET-disztribúciók készítéséhez a
választott platform(ok)on. Ezen specifikációk gyűjtőneve a CLL

A Cll szerepe

Ahogy röviden említettük az l. fejezetben, amikor a C# és a .NET platform


megjelent, a Microsoft Corporation két formális specifikációt adott be az
ECMA-nak (European Computer Manufacturers Association - Európai Szá­
mítógépgyártók Szövetsége). Amikor ezeket elfogadták, benyújtották őket a
Nemzetközi Szabványügyi Szervezetnek (ISO), ahol a specifikációkat hama­
rosan jóváhagyták
Nos, akkor miért kellene aggódni? Ezek a specifikációk csak irányjelzők
más cégek, fejlesztők, egyetemek és egyéb szervezetek számára, hogy elké­
szíthessék saját disztribúcióikat a C# programozási nyelv és a .NET platform
használatával. A két szóban forgó specifikáció:

892
A .NET platformfüggetlen természete

• az ECMA-334, amely a C# programozási nyelv szintaxisát és szeman­


tikáját definiálja,

• és az ECMA-335, amely a .NET platform számos, CLl-t (Common


Language Infrastructure - közös nyelvi infrastruktúra) meghatározó
részletét definiálja.

Az ECMA-334 különösen precíz és tudományos módon írja le a C# lexikális


nyelvtanát (sejthetjük, hogy az ilyen szintű részletezettség mennyire fontos
azoknak, akik saját C#-fordítójukat implementálják). Azonban a két specifi­
káció közül az ECMA-335 a jelentősebb, olyannyira, hogy hat particióra oszt­
ható, amelyeket a B. l. táblázatban láthatunk.

�:�c'-':j�;':'f�,ji'l�>;.�·"fl#f•t;;"�;'�
···.'-".1f·.;:.. t." '''L·'" � l�l
��'Y.�"f:��·"·,,·�.�".D};t':r·
�'f.Pii,;-_,�·v.•i�!: ,.;�'.fi1"•';);' ,_;_!
l, •

, '*�-..;; ·:..�.; . ._-.·��


;;\"t·��'tp';;.:.:';
''Y.�T-
. .

:l.���".t���t-.W·� E é,";;._.· �.,.,./·


.•
·
.. �:•'�·S:..: ·�·"?":�"":'.r��� A:�_:....�,:,..1.:
_ ._ 1;:;...:-...
. �1
I. rész: Architektúra Ez a partíció a CU általános architektúráját írja le,
beleértve a közös típusrendszer szabályait, a CLS­
t és a .NET-futtatómotor működését.

II. rész: Metaadatok Ezek a partíciók a .NET metaadat-formátum rész­


leteit írják le.

III. rész: ClL Ez a partíció a köztes programozási nyelv (ClL)


szintaxisát és szemantikáját írja le.

IV. rész: Könyvtárak Ez a partíció magas szintú áttekintést ad a CLl-kom­


patibilis .NET-disztribúciók által kötelezően támo­
gatandó minimális és teljes osztálykönyvtárakróL

V. rész: Bináris formáturnak Ez a partíció a hordozható hibakeresési csere


formátumról (CILDB) szalgáltat részleteket. A
hordozható, szabványoknak megfelelő ClLDB­
fájlok a hibakeresési adatok cseréjét szalgálják a
eu készítői és használói között.

VI. rész: Mellékletek Ez a partíció az "apró-cseprő" dolgok gyűjtemé­


nye, amely olyan témakörökkel foglalkozik, mint
az osztálykönyvtár tervezési útmutatók és a ClL­
fordító implementációjának részletei.

B.1. táblázat: Az ECMA-335 specifikáció részei

Ennek a függeléknek nem célja, hogy elmélyedjen az ECMA-334 és az


ECMA-335 specifikációk részleteiben, és nekünk sem kell ismernünk ezeket
ahhoz, hogy platformfüggetlen .NET-szerelvényeket készíthessünk. Ha azon-

893
B függelék: Platformfüggetlen .NET-fejlesztés aMONO-val

ban érdekel bennünket a téma, mindkét dokumentum ingyenesen letölthető


az ECMA webhelyéről (http:/jwww.ecma-international.org/publications/
standards).

Megjegyzés Még ha nem is vagyunk kíváncsiak a .NET platforrnak közötti lehetőségeire, ta­
nácsos elolvasni az ECMA-334 és az ECMA-335 specifikációkat, mivel alapos betekintést en­
gednek a C# nyelv és a .NET platform belső működésébe.

A népszerű CIL-disztribúciók

Manapság két népszerű CLl-megvalósítás létezik a piacon a Microsoft CLR, a


Microsoft Silverlight és a Microsoft .NET Compact Framework mellett (lásd a
B.2. táblázatot).

Mono http://www. mono­ A Mono a .NET nyílt forráskódú


project.com disztribúció, amelyet a Novell keres­
kedelmileg támogat. A Mono célja,
hogy a Unix/Linux, a Mac OS X, a
Solaris és a Windows operációs
rendszerek több verzióján működjön.

Portable .NET http ://www.dotgnu. A Portable .NET a GNU General Pub­


org lic License alatt kerül terjesztésre.
Ahogy a neve is mutatja, a Portab le
.NET célja, hogy a lehető legtöbb ope­
rációs rendszeren és architektúrán
működjön, beleértve az olyan külön­
leges platformokat, mint a BeOS, a
Microsoft Xbox és a Sony PlayStation
(nem, ez tényleg nem vicc!).

8_ 2_ táblázat: A népszerűbb .NET CLI-disztribúciók

A B.2. táblázatban látható CLI-megvalósítások mindegyike biztosít egy teljesen


működőképes C#-fordítót, több parancssori fejlesztőeszközt, egy globális sze­
relvénytár (GAC) implementációt, példakódokat, hasznos dokumentációt, va­
lamint több tucat szerelvényt, amelyek az alaposztálykönyvtárakat alkotják.

894
A .NET platformfüggetlen természete

Az ECMA-335 N. része által definiált alapvető könyvtárak irnplementációja


mellett a Mono és a Portable .NET az mscorlib.dll, a system. Data . dll, a sys­
tem.web.dll, a system.Drawing.dll és a system.windows.Forms.dll szerelvé­
nyek (valamint sok egyéb szerelvény) Microsoft-kompatibilis megvalósítását
kínálják. Ezenfelül, a Mono- és a Portable .NET disztribúciók jó néhány olyan
szerelvényt biztosítanak, amely kifejezetten a Unix/Linux és a Mac OS X ope­
rációs rendszereket célozzák meg. A Cocoa#, például, a Mac OX GUI-eszköz­
rendszerének, a Cocoának a .NET-burkolója. Ebben a függelékben nem ássuk
bele magunkat ezekbe az operációsrendszer-specifikus bináris fájlokba, hanem
az operációsrendszer-tudatosabb programozásra összpontosítunk

Megjegyzés A Portable .NET-et nem vizsgáljuk meg ebben a függelékben. Azt azonban fon­
tos tudni, hogy nem a Mono az egyetlen pillanatnyilag elérhető platformfüggetlen .NET·disztri­
búció. Érdemes rászánni az időt, és a Mono platform mellett kipróbálni a Portable .NET·et is.

A Mono hatóköre

Mivel a Mono a Microsoft Corporationtől származó ECMA-specifikációra


épülő API, helyesen feltételezhetjük, hogy a Mono állandóan felzárkózik,
amint a Microsoft .NET platformjának újabb verziója megjelenik. Ezen könyv
írásakor a Mono a C# 2.0/.NET 2.0 verziókkal kompatibilis. Ezért készíthe­
tünk ASP.NET-webhelyeket, Windows Forms alkalmazásokat, adatbázis­
központú alkalmazásokat az ADO.NET segítségéve!, és (természetesen) egy­
szerű parancssori alkalmazásokat is.
A Mono jelenleg nem teljesen kompatibilis a C# 2008-cal, illetve a .NET
3.0/3.5 verzióivaL Ez azt jelenti, hogy a Mono-alkalmazások jelenleg nem
képesek használni (ismételten, ezen könyv írásakor) a következő API-kat:

• Windows Presentation Foundation (WPF)

• Windows Communication Foundation (WCF)

• Windows Workflow Foundation (WF)

• LINQAPI-k

• C# 2008 specifikus nyelvi funkciók

895
B függelék: Platformfüggetlen .NIT-fejlesztés aMONO-val

Abban biztosak lehetünk, hogy a Novell Mono csapata már dolgozik azon,
hogy átültesse ezeket az API-kat és C# 2008 programozási szolgáltatásokat a
Mono-projektbe. Valójában több C# 2008 nyelvi funkció már része a Mono
legutolsó verziójának (1.2.5), köztük az implicit típus hozzárendelés, az ob­
jektuminicializáló szintaxis és a névtelen tipusok.

Megjegyzés A C# 2008 támogatás úgy engedélyezhető, ha átadjuk a -l angversi on: l i n q


. beállítást aMono (#-fordítójának.

Ezenkívül már készül az Olive-projekt, amely a WPF, a WCF és a WF imple­


mentálását tervezi a Mono platformba. Azt is örömmel hallha�uk, hogy a
UNQ-támogatás jól halad.

Megjegyzés A Mono webhelyén található egy oldal, amely leírja a Mono működésének álta­
lános struktúráját és a jövőbeli verziók terveit (http:/ /www.mono-project.com/plans).

A Mono funkciókészletével kapcsolatos utolsó érdekes dolog, hogy a Micro­


soft .NET Framework 3.5 SDK-hoz hasonlóan a Mono SDK is több .NET
programozási nyelvet támogat. Míg ez a függelék továbbra is a C#-ra össz­
pontosít, a Mono támogat egy Visual Basic .NET-kompatibilis fordítót, vala­
mint sok más .NET-alapú programozási nyelvet.

A Mono beszerzése és te lepitése

Ezt a kis bevezetést követően foglalkozzunk a Mono beszerzésével és az álta­


lunk használt operációs rendszerre való telepítéséveL Navigáljunk a Mono
webhelyére (http:/ jwww.mono-project.com), és kattintsunk a Download
Now gombra, hogy a letöltési oldalra jussunk. Innen különféle telepítőket
tudunk letölteni.
Feltételezem, hogy többségünk a Mono Windows-disztribúcióját telepíti
(a Mono telepítése nem zavarja a Microsoft .NET és a Visual Studio IDE-k
már telepített példányait). Kezdjük a jelenlegi stabil Mono installációs cso­
mag Windows-verziójának letöltésével, és mentsük a telepítőprogramot a he­
lyi merevlemezre.

896
A Mono beszerzése és telepítése

Megjegyzés Ha Linux-alapú operációs rendszerre telepítjük a Monót, akkor javaslom, hogy


tegyük ezt az x86-os csomagokhoz való Linux-telepítövel, amely lehetövé teszi, hogy barátsá­
gos telepítővarázsló használatával installáljuk a szaftvert (így nem kell a forrásból telepíteni;
mindenképpen olvassuk el a telepítési megjegyzéseket a Mono webhelyén). Ha Mac OS X alatt
telepítjük a programot, akkor az eljárás hasonló lesz más Mac-alapú szoftverek telepítéséhez.

A telepítőprogram futtatásakor lehetőségünk lesz egyéb Mono fejlesztési


eszközök installálására az elvárt alaposztálykönyvtárakon és C# programo­
zási eszközökön kívül. A telepítő megkérdezi, hogy a GTK#-ot (amely egy
nyílt forráskódú, Linux-központú GTK-eszközrendszeren alapuló .NET GUI
API) és az XSP-t (amely egyedülálló webkiszolgáló, hasonló a Microsoft
webdev. webserve r. ex e-jéhez) is szereménk-e telepíteni. A függelék példáiban

feltételezzük, hogy a teljes telepítést választottuk, ezért mindenképpen jelöl­


jük be az összes lehetőséget a telepítőablakban (lásd a B. l. ábrát).

li!] Setup - Mono 1.2.5 with GTK# 2.10.2 [=l®iiaii

[i
Select�s
wtlch componerts shoUd be i1staled?

Select the �s you want to i1st.. : clearthe componerts you do not wart to
nst.. . Click Nelll when you are ready to cortnx!.

lhj ilOlotion TI
0 Mono Ries 1261 �lB �

�GTK+ 2.10and Gnome 2.16 Fles

"''"'�
L. 0 Gtk#2.10.2 Plles 26.5MB
!-0 Monodoc 9.2MB _

t· � Geckolt Fles 0.3MB "


L. 0 San1)les 2.5MB
�XSP!ieo O.BMB
i-· � XSP Shellríegaiion
L rJ1 xsP 2.0 Shellrí� T

Curm sele<:tion reqLires Z1t leaot 243.0MB d <isk space.

l <Back
ll Noolt >
ll Cancel _J

B. 1. ábra: Jelöljük be az összes lehetőségetaMono telepítésekor

A Mono-telepítő többi beállítását hagyhatjuk a javasolt alapértelmezett érté­


keken.

897
B függelék: Platformfüggetlen .NIT-fejlesztés aMONO-val

A Mono könyvtárszerkezetének vizsgálata

A Mono alapértelmezés szerint a C:\Program Files\Mono-<version> (a könyv


írásakor a legutolsó - és legjobb - Mono-verzió az 1.2.5 volt) könyvtárba kerül.
Ezen belül több alkönyvtár található (lásd a B.2. ábrát).
A függelékben csak a következő alkönyvtárakkal foglalkozunk:

• bin: ez tartalmazza a Mono fejlesztési eszközeinek többségét, köztük a


C# parancssoros fordítókat

• lib\ mono \ gac: a Mono globális szerel vénytárának helye.

-- _ _ _ _ _::_b]§�- - ----

F.:r:orite links 'i Name"


---
Date modifitd
·----- -
Type Slze

t
More

Folders
>,

v i . �ji� Feldor
fli�
etc
File Fold�r

Mono·ll.S
indu de
f"" -·
j info
j; bin file Folder File Fcld�r
----·
�.i. gc
[• Jt; include libexec
j., info File Folder

mon

File Foldet

share
File Folder

samples Date modified: 8/31/2007 2:19PM


File Folder

B. 2. ábra: A Mono-könyvtárstruktúra

Mivel a Mono fejlesztői eszközök többségét parancssorból futtatjuk, a Mono­


parancssor automatikusan felismeri ezeket a parancssori fejlesztőeszközöket
A parancssor (amely funkcionális megfelelője a Visual Studio 2008 parancs­
sorának) a Start > Ali Prograros > Mono <version> For Windows menüből ér­
hető el. A telepítés teszteléséhez írjuk be a következő parancsot, majd nyom­
juk meg az Enter billentyűt:

mono -version

Ha minden rendben, akkor különböző információkat látunk a Mono futtató


környezetről (lásd a B.3. ábrát).

898
AMono-fejlesztőeszközök

!ijj ..
. w:� Pro l l€1!ííiniíííl
=

8
!.'or1c �·er·s�or� . 2 . 5 Cliilrl

:i:i:;��;:l:; ll;l):m:�;:;r:�dj;!i;�;;;:,,;:":.'.:,,,. ,. ......·.·.... ", . ,. , . ... . co

���S EG\: ���;���i 8vEh:· -� ... itb t·:;péd GC>

Ci :;ab l ed:
:..r
nen�
.. ·ch itecture : :.;(.G

C: 1·.lndo :::- �.s"':.�"�2


..

0 L!I_...
6.3. ábra. A Mono futtató környezet

A Mana-fejlesztőeszközök

A Microsoft CLR-disztribúciójához hasonlóan a Mono több felügyelt fordítót


biztosít:

• mcs/gmcs: C#-fordítók,

• vbnc: a Mono Visual Basic fordító,

• booc: a Boo nyelv fordítója,

• il asm/ilasm2: a Mono köztes nyelvi fordítói_

Mivel ez a függelék csak a C#-fordítókra összpontosít, ne feledjük, hogy a


Mono-projekt biztosít egy Visual Basic .NET-fordítót is. Bár ez az eszköz még
fejlesztés alatt áll, a célja, hogy az ember által olvasható kulcsszavak ( rnherits,
Mustoverri de, Implements stb.), illetve a Unix/Linux- és a Mac OS X világát kö­
zelebb hozza egymáshoz (további részletekért lásd a http://www -mono­
projectcom/Visual_Basic webhelyet)-
A Boo objektumorientált, statikusan típusos programozási nyelv a CLl
számára, amely Python-alapú szintaxist jelenít meg. A Boo programozási
nyelvről további információkat a http:/ /boo.codehaus.org oldalon találha­
tunk. Végül, ahogy sejthető, az ilasm és az ilasm2 a Mono ClL-fordítói (ame­
lyek közül az utóbbi támogatja a .NET 2.0 programozási szerkezeteket is).

899
B függelék: Platformfüggetlen .NET·fejlesztés a MONO·val

A C#-forditók használata

A Mana-projekt első C#-fordítója az mcs volt, amely teljesen kompatibilis a C#


1.1-gyel (valójában mcs-t is C#-ban írták). A Microsoft C# parancssoros for­
az

dítójához (esc. exe) mcs tárnagalja a reakciófájlokat, a /target:


hasonlóan az
kapcsolót (a szerelvény típusának rneghatározásához), az /out kapcsolót (a le­
fordított szerelvény nevének rnegadásához) és a /reference: kapcsolót (az ak­
tuális szerelvény rnanifeszturnának rnódosításához a külső függőségi kapcsola­
tokkal). Az mcs összes beállítását megnézheljük az alábbi paranccsal:

mcs -?

A "generikus rnono C#-fordító" vagy gmcs az mcs egy olyan verziója, amely
- rnint ahogy azt a neve sugallja - tárnagalja a .NET 2.0-specifikus C# nyelvi
funkciókat (generikus tipusok, kovariancia/kontravariancia, nullázható típu­
sok, részleges tipusok, névtelen rnetódusok stb.) és rendelkezik a .NET 2.0
alapú alaposztálykönyvtárak referenciáival. A gmcs parancssori beállításai
ugyanazok, rnint az mcs hasonló beállításai, és ezeket a következő paranccsal
ellenőrizheljük

gmcs -?

Mivel két C#-fordító van, joggal feltételezheljük, hogy csak a gmcs használható
olyan .NET-alkalmazások készítésére, amelyek kihasználják a C# 2.0 nyelv le­
hetőségeit. Valójában a két fordító közül először az mcs támogatta a 2.0 funkció­
it, amelyeket aztán tökéletesítettek és áthelyeztek a gmcs-be is. A függelékben
található példakódok rnindkét C#-fordító segítségével lefordíthatók

Microsoft-kompatibi lis Mono-fej lesztöeszközök

A különböző felügyelt fordíták rnellett a Mono különböző fejlesztői eszkö­


zökkel rendelkezik, amelyek funkcionális szempontból megegyeznek a Mic­
rosoft .NET SDK eszközeivel (néhányuknak még a neve is ugyanaz). A B.3.
táblázat felsorolja néhány gyakran használt Mono segédprogram Microsoft
.NET megfelelőjét.

900
A Mana-fejlesztőeszközök

.i ·"· �.�.:�t'..-:��..i:
���
.. �

.."- - .---��\1}'�� ·:1':7t\-


�:ft!-''· .... ��·
.,. • '= .;1.." A•1.).... ���
;_.-�._·r!..:;:':"�->����

.
:·!· .. ;. ..r:-.. ( ( ..:.;· �::.·'.J._:A,.'·_,._' . • \";:.\o .._ ,..·L , . .. ,�· .• _.";..�·,···"'.:-. �:.

al al. exe A szerelvénymanifesztumokat


kezeli és többfájlas szerelvényeket
készít (egyebek mellett).

gacutil gacutil.exe Együttműködik a GAC-cal.

mana, arnikor - aot ngen.exe Szerelvények köztes nyelvi kódjá­


beállítással nak előfordítását végzi el.
indítjuk

wsdl wsdl . exe Ügyféloldali proxykódot generál


XML-webszolgáltatások számára.

disca disco.exe A webkiszolgálón található XML­


webszolgáltatások URL-jeit térké­
pezi fel.

xsd xsd.exe Típusdefiníciókat generál XSD­


sémafájlból.

sn sn.exe Kulcsadatokat generál erős névvel


ellátott szerelvények számára.

manadis ildasm.exe A ClL diszasszembler.

i l asm ilasm.exe A ClL asszembler.

xsp2 webdev.webserver.exe ASP.NET-webkiszolgáló tesztelési


és fejlesztési célokra.

B.3. táblázat: A Mana parancssori eszközei és azok Microsoft .NE T megfelelői

Mono-specifikus fejlesztőeszközök

Ezeken kívül vannak olyan Mono-fejlesztőeszközök, amelyeknek nincs .NET


Framework 3.5 SDK megfelelőjük, ezeket a B.4. táblázat ismerteti.

monop/monop2 Amonap (mono print) segédprogram megjeleníti a


meghatározott típus definícióját C#-szintaxisban.

901
B függelék: Platformfüggetlen .NET·fejlesztés a MONO·val

�us
Jelenté
fejteszt&iszköz
SQL A Mono-projekt tartalmaz egy grafikus front endet
(SQL), amelynek segítségével relációs adatbázisokkal
dolgozhatunk, különböző ADO.NET-adatszolgáltatók
használatávaL

Glade 3 Ez az eszköz GTK# grafikai alkalmazások készítésére


szolgáló vizuális fejlesztő IDE.

B 4 táblázat. A közvetlen Microsoft .NET SOK megfelelővel nem rendelkező Mana-eszközök


. .

Megjegyzés Az SQL·t és a Glade·et a Start menü Applications mappáján, azon belül pedig a
Mono telepítési könyvtárán keresztül is elindíthatjuk. Ezt mindenképpen próbáljuk ki, mert így
világosan láthatjuk, milyen tartalmassá vált a Mono platform.

A monop(2) használata

A monop és a monop2 segédprogramok (mindkettő a mana print rövidítése) meg­


jelenítik egy egy adott típus C#-definícióját a meghatározott szerelvényen be­
lül. A különbség a két segédprogram között az, hogy a monop csak a Microsoft
.NET l. l verzióval kompatibilis típusokat jeleníti meg, míg a monop2 a .NET 2.0
verziójával kompatibiliseket is. Ezek az eszközök akkor nagyon hasznosak, ha
gyorsan szeretnénk megnézni egy metódusszignatúrát, és nem akkor, ha sze­
retnénk átrágni magunkat a hivatalos dokumentáción. Egy gyors teszt erejéig
írjuk be a következő parancsot a Mono-parancssorba:

monop2 system.object

A B.4. ábra mutatja régi barátunk, a system. object definícióját.

B .4. ábra. A monop(2) megjeleníti a lefordított típusok C#-forráskód definícióit

902
.NIT-alkalmazások készítése Monóval

Később még lesz szó a további Mono-eszközök használatáról; ha azonban


látni szeretnénk egy eszköz elérhető utasításkészletét, akkor argumentum­
ként írjunk az eszköz neve után egy -? jelzőt.

.NET-alkalmazások készítése Manóval

A Mono használatának bemutatásához kezdjük a CoreLibDumper.dll kódkönyv­


tár létrehozásával. Ez a szerelvény egyetlen, coreLibournper nevű osztálytípust
tartalmaz, amely támogatja a DumpTypeToFil e() statikus metódust. Ez a metódus
olyan sztringparamétert vesz fel, amely az rnscorlib.dll szerelvényben található
bármely típus teljesen meghatározott nevét jelképezi, és megszerzi a kapcsolódó
típusinformációt a reflexiós API használatával (lásd a 16. fejezetet), majd kiírja az
osztálytagok szignatúráját a merevlemezen egy helyi fájlba.

Mono-kódkönyvtár készitése

Hozzunk létre egy új mappát a C meghajtón MonoCode névvel. Ebben az új


mappában készítsünk egy CoreLibDumper nevű almappát, amely a következő
C#-fájlt tartalmazza (cor Libournper. cs néven):

ll coreLiboumper.cs
using System;
using System.Reflection;
using System.ro;

ll szerelvény verziójának meghatározása.


[assembly:AssemblyVersion("l.O.O.O")]
namespace coreLibDumper
{
public class TypeDumper
{
public static bool DumpTypeToFile(string typeToDisplay)
{
ll A típus memóriába töltésének kísérlete.
Type theType = null;
try
{
ll Kivétel kiváltása, ha nem találjuk.
theType = Type.GetType(typeToDisplay, true);
} catch { return false; }

903
B függelék: Platformfüggetlen .NEr-fejlesztés aMONO-val

ll Helyi *.txt fájl létrehozása.


using(Streamwriter sw =

File.createText(string.Format("{O}.txt",
theType.FullName)))
{
ll Típ u s kiírása a fáj lba.
sw.writeLine("Type Name: {O}", theType.FullName);
sw.writeLine("Members:");
foreach(Memberrnfo mi in theType.GetMembers())
sw.writeLine("\t-> {O}", mi.Tostring());
}
return true;
}
}
}

A Microsoft C#-fordítóhoz hasonlóan a Mono C#-fordító is támogatja a reak­


ciófájlok használatát (lásd a 2. fejezetet). Bár úgy is lefordíthatnánk ezt a fájlt,
hogy a parancssorban kézzel megadunk minden egyes szükséges argumen­
tumot, e helyett hozzunk létre egy L ibraryBuild.rsp új fájlt (ugyanott, ahol a
coreLibournper. cs található), amely az alábbi utasításokat tartalmazza:

ltarget:library
lout:CoreLiboumper.dll
coreLiboumper.cs

Most már lefordíthatjuk a könyvtárunkat a parancssorban a következők szerint:

gmcs @LibraryBuild.rsp

Ez a megközelítés a működés tekintetében megegyezik az alábbi (részletesebb)


utasítással:

gmcs ltarget:library lout:coreLiboumper.dll CoreLibDumper.cs

Erős név hozzárendelése a CorelibDumper.dll szerelvényhez

A Mono támogatja az erős névvel ellátott és megosztott szerelvények (lásd a


15. fejezetet) telepítését a Mono GAC-ba. A szükséges nyilvános/magán kulcs­
adatok generálásához a Mono az sn parancssori segédeszközt biztosítja, amely
nagyjából ugyanúgy működik, mint az azonos nevű Microsoft-eszköz. A kö­
vetkező parancs például új*. snk fájlt hoz létre (a- 7 beállítással az összes lehet­
séges parancsot megnézhetjük):

sn -k myTestKeyPair.snk

904
.NIT-alkalmazások készítése Monóval

Ahhoz, hogy arra utasítsuk a C#-fordítót, hogy ezeket a kulcsadatokat hasz­


nálja a coreLi bournper.dll erős nevének hozzárendeléséhez, egészítsük ki a
LibraryBuil d. rsp fájlt további paraméterekkel:

/target: library
/out:coreLiboumper.dll
/keyfile:myTestKeyPair.snk
coreLibDumper.cs

Most fordítsuk le újra a szerelvényünket

gmcs @LibraryBuild.rsp

A módositott manitesztum megtekintése a monodis használatával

Mielőtt telepítenénk a szerelvényt a Mono GAC-ba, ismerkedjünk meg a


monodis parancssori eszközzel, amely funkcióját tekintve azonos a Microsoft
ildasm.ex e-jével (a GUI front end nélkül). A monodis használatával megte­
kinthe� ük az adott szerelvény köztes nyelvi kódját, manifesztumát és típus-
. metaadatát. Most minket az érdekel, hogy megtekintsük a (már erős névvel
ellátott) szerelvényünk alapadatait az --assembly jelző segítségéveL A B.S.
ábra muta�a a következő utasításkészlet eredményét:

monodis --assembly coreLibDumper.dll

B.5. ábra: Amanadis lehetóvé teszi, hogy megtekintsük egr} szerelvény köztes nyelvi kódját,
metaadatát és önleírását

Ahogy látha�uk, a szerelvény önleírása már muta�a a myTestKeyPair. snk


fájlban definiált nyilvános kulcs értékét.

905
B függelék: Platformfüggetlen .NEr-fejlesztés aMONO-val

Szerelvények telepitése a Mono GAC-ba

Most, hogy a coreL i bournper. dll szerelvényünk erős névvel rendelkezik, tele­
pítsük a Mono GAC-ba, a gacutil használatával. A Microsoft azonos nevű
eszközéhez hasonlóan a Mono gacutil segédprogramja támoga�a a telepítést,
az eltávolítást és a C:\Program Files\Mono-<version>\ lib\mono\gac könyv­
tárba telepített aktuális szerelvények listázását. Az alábbi parancs aGAC-ba te­
lepíti a coreL i bournper. dll fájlt, és beállí�a azt megosztott szerelvényként.

gacutil -i coreL i boumper . dll

Megjegyzés Mindenképpen a Mana-parancssort használjuk a bináris fájl telepítésnek végre­


hajtására! Ha a Microsoft gacutil.exe programját használjuk, akkor a CoreLibDurnper.dll
szerelvénytMicrosoft GAC-ba installáljuk!

Ha a parancs futtatása után megnyitjuk a \gac könyvtárat, látunk egy új


mappát CoreLibDumper névvel (lásd a B.6. ábrát), ebben egy alkönyvtár ta­
lálható, amely a Microsoft globális szerelvénytárának elnevezési hagyomá­
nyát követi (Szerelvény Verzió_nyilvánosKulcsToken) .

.. � mono
• ;j 1.0
..
. 2.0

-i.boo

B.6. ábra. Kódkönyvtárunk telepítése a Mono GA C-ba

Megjegyzés A -l opcióval a gacutil listázza aMana GAC-ban található összes szerel vényt.

906
.NET-alkalmazások készítése Manóval

Konzolalkalmazás készitése Manóban

Az első Mono-ügyfélprogramunk egyszerű konzolalapú alkalmazás lesz


ConsoleClientApp.exe névvel. Hozzunk létre a consoleclientApp.cs új fájlt a
C:\MonoCode\ CorLibDumper mappában, amely a következő Progarn típust
tartalmazza:

ll Ez az ügyfélalkalmazás a coreLiboumper.dll szerelvényt használja


ll arra, hogy kiirja a tipusokat egy fájlba.
using system;
using coreLibDumper;

namespace consoleclientApp
{
public class Program
{
public static void Main()
{
console.writeLine(
" ***** The Type Dumper App *****\n");

ll Megkérdezzük a felhasználót a típus nevéről.


string typeName = "";
console.write("Please enter type name: ");
typeName = console.ReadLine();

ll Most elküldjük azt a segédkönyvtárnak.


if(TypeDumper.DumpTypeToFile(typeName))
Console.writeLine("Data saved into {O}.txt",
typeName);
else
console.writeLine("Error! Can't find that type...");
}
}
}

Figyeljük meg, hogy a Main() metódus egyszerűen megkérdezi a felhasználó­


tól a teljesen meghatározott típusnevet A TypeDumper. DumpTypeToFile() me­
tódus a felhasználó által beírt nevet használja arra, hogy kiírja a típus tagjait
egy helyi fájlba. Ezután hozzuk létre a clientBuild.r sp fájlt az ügyfélalkal­
mazáshoz, ami hivatkozzon a coreLi bournper. dll-re:

ltarget:exe
lout:ConsoleclientApp.exe
lreference:coreLiboumper.dll
consoleclientApp.cs

907
B függelék: Platformfüggetlen .NET-fejlesztés aMONO-val

Most a Mono-parancssor használatával navigáljunk abba a mappába, amely


az ügyfélalkalmazásunk fájljait tartalmazza, és fordítsuk le a végrehajtható
fájlt a gmcs segítségéve!:

gmcs @ClientBuild.rsp

Ügyfélalkalmazásunk betöltése a Mana-futtatókörnyezetbe


Töltsük be a ConsoleelientApp.exe-fájlt a Mono futtatórendszerébe a végre­
hajtható fájl nevének megadásával (az*. exe fájlkiterjesztéssei együtt) a mono
argumentumaként:

mana ConsoleclientApp.exe

A kérdésnél próbaképpen írjuk be, hogy system.Threading.Thread, majd nyom­


juk meg az Enter billentyűt. Látunk egy új fájlt system.Threading.Thread.txt
névvel, amely a típus metaadat-definícióját tartalmazza (lásd a B.7. ábrát).

1 �- Notepad++ - C:\My Books\C# and the .NET Platform 4th ed\Code\Appendix 8\CorelibDumper\Syst...l = l lEl lii&liil
File Edit Search View Forrnat Language Settings Mac:ro Run TextFX Plugins Window ? X
�-- � -�- -"c--
.
·-·
- - --- - � - -� ---�- � �--
-· =-
· - -- --·--
· -
· ��---- ---=--:
.o G Et Ci -� ''ro l "' .. lt:ll :» --c
--l •-
bs
-
l ->t Ili Eii l ::;."
t--------------- OOo-
l c

-----
@ r� litl
---· -----
l
BSyotem�Tive��-�
!ype N"""': System.Threading .Thread
Hembers:
->Void .cwr(lhreadStart)
->Void .ctor(!hreadS�art., Int32)
->Void . c tor (Parairtet.erizedThreadStart)
-> Void .c'tor(Parameterizedihread.Start, Int32)
-> Sys-cem.Runtime .Remotinq.Contexts .Contex"t qet._Current.Context ()
-> IPrincipal qet_CurrentPrincipal O
-> Void se't_CUrrent:Principal (IPrincipal)
10 -> Sy�'te!tl. Threading. Thread get_Curren'tihread ()
i l. -> Syst:em.LocalDat:aStoreSlot AllocateNa.med.DataSlot (System. String)
12 ->Void freeNamedDataSlot{Sy�'tem.St:ring)
13 -> Sy� tem.LocalDataSt.oreSlot AllocateDa'taSlot: ()
l-i -> System.Object GetData(Sy�t:em.LocalDa'taStoreSlot)
15 -> Void Set:Dat.a (Sys'tem. LocalDataS'toreSlot, Sy3tem.Object)
ló -> Syor;em.AppDomain Ger; Domain()
P -> Int32 GetDomainiD(I
19 -> System.LocalDataStoreSlot. GetNamedDataSlot {System.St:ring)
1� ->Void Reset.Abort{)

� �
�-�i.:__.=� --�:r-- .. �·

Nermal text file nbchar:4124 Ln:l Col:l Sei:O Dos\Windows ANSI INS

B. 7. ábra. Üg�Jfélalkalmazásunk futtatásának eredménye

Mielőtt folytatnánk egy Windows Forms alapú ügyféllel, végezzük el a követke­


ző kísérletet. A Windows Intéző segítségével nevezzük át az ügyfélalkalmazás
mappájában lévő coreL i bournper.dll szerelvényt oontusecoreLi bournper.dll-re .
Még ekkor is sikeresen futtathaljuk az ügyfélalkalmazást, mivel az egyetlen ok,

908
.NET·alkalmazások készítése Monóval

arniért hozzá kellett fémünk ehhez a szerelvényhez a kliens készítése során, az

volt, hogy frissítsük annak manifesztumát. AMono futási környezet futásidőben


betölti aMono GAC-ba telepített coreLibournper. dll verziót.
Ha azonban a Windows Intézőből próbáljuk futtatni az ügyfélalkalmazást
'
.

úgy, hogy kétszer a consoleclientApp. exe programra kattintunk, FileNot-


FoundExcepti on kivételt kapunk. Elsőre azt gondolhatnánk, hogy ez annak a

ténynek köszönhető, hogy az ügyfélalkalmazás könyvtárában átneveztük a


coreLibournper.dll szerelvényt Azonban ennek igazi oka az, hogy pont most

töltöttük be a consolecli en tAp p.exe-t a Microsoft CLR-be!


Ahhoz, hogy egy alkalmazást Mono alatt futtathassunk, át kell adnunk a
Mono futási környezetnek a mono parancs segítségéveL Ha nem így teszünk,
akkor a szerelvényünket a Microsoft CLR-be töltjük be, ami feltételezi, hogy
az összes megosztott szerelvényt telepítettük a Microsoft GAC-ba, mely a
<%windir%>\Assembly könyvtárban található.

Windows Forms ügyfélprogram készitése

Mielőtt folytatnánk, ne felejtsük el visszanevezni a oontusecoreL ibournper.


dll-t coreLiboumper.dll névre. Ezután hozzunk létre új C#-fájlt winForms­

cl ientApp. cs névvel, és mentsük ugyanabba a könyvtárba, ahol az aktuális

projektfájlokat tároljuk Ez a fájl két olyan típust definiál, amelyek használ­


nak néhány C# nyelvi funkciót, mint például statikus osztályokat és névtelen
metódusokat:

using System;
using System.windows.Forms;
using CoreLiboumper;
using System.orawing;

namespace winFormsClientApp
{
ll Alkalmazásobjektum.
public static class Program
{
public static void Main()
{
Application.Run(new Mainwindow());
}
}

ll Az egyszerű ablakunk.
public class Mainwindow : Form
{
private Button btnoumpToFile = new Button();

909
B függelék: Platformfüggetlen .NIT-fejlesztés aMONO-val

private TextBox txtTypeName = new TextBox();


public Mainwindow()
{
ll Kanfiguráljuk a felhasználói felületet.
configcontrols();
}

private void configcontrols()


{
ll Kanfiguráljuk az űrlapot.
Text = "My Mono Win Forms App!";
this.clientsize = new system.Drawing.size(284, 90);
StartPosition = FormstartPosition.centerscreen;
AcceptButton = btnDumpToFile;

ll Kanfiguráljuk a gombot.
btnDumpToFile.Text ="Dump";
btnDumpToFile.Location new system.Drawing.Point(13, 40);

ll Kezeljük névtelenül a kattintási eseményeket.


btnDumpToFile.Click += delegate
{
if(TypeDumper.DumpTypeToFile(txtTypeName.Text))
MessageBox.show(string.Format(
"Data saved into {O}.txt",
txtTypeName.Text));
else
MessageBox.show("Error! can't find that type...");
};
Controls.Add(btnDumpToFile);

ll Kanfiguráljuk a szövegdobozt.
txtTypeName.Location = new System.Drawing.Point(l3, 13);
txtTypeName.size = new System.Drawing.Size(341, 20);
Controls.Add(txtTypeName);
}
}
}

Ahhoz, hogy reakciófájl használatával lefordítsuk ezt a Windows Forms al­


kalmazást, hozzuk létre a winFormsclientApp.rsp fájlt (ennek következik
most a tartalma), és az előzőekben látottak szerint adjuk meg argumentum­
ként a gmcs-nek.

ltarget:winexe
lout:winFormsclientApp.exe
lr:coreLiboumper.dll
lr:System.windows.Forms.dll
lr:System.Drawing.dll
WinFormsclientApp.cs

910
.NIT-alkalmazások készítése Manóval

Végül futtassuk Windows Forms alkalmazásunkat a mono segitségével:

mono WinFormsClientApp.exe

A B.S. ábra mutatja a kimenetet.

� My Mono Win Forms App! r;;rglii:iiiifi

[System.Mailt
lc:=���::JI

8.8. ábra: Manóval fordított Windows Forms alkalmazás

Windows Forms alkalmazásunk futtatása Linux alatt

Eddig a függelékben olyan szerelvényeket készítettünk, amelyeket a Micro­


soft .NET Framework 3.5 SDK segítségével lehetett lefordítani. Azonban a
Mono fontossága akkor válik igazán világossá, amikor megnézzük a B.9. áb­
rát, amely ugyanezen Windows Forms alkalmazás futását mutatja SuSe Li­
nux alatt. Figyeljük meg, hogy a Windows Forms alkalmazásunk hogyan vet­
te át a jelenlegi téma működését és megjelenését.

B. 9. ábra: A Windows Fo rrns alkalmazásunk működés közben SuSe Linux alatt

Forráskód A CorlibDumper kódfájlokat a forráskódkönyvtár B függelékének alkönyvtára tar­


talmazza. A forráskódkönyvtárról lásd a Bevezetés xlv. oldalát.

Tehát lefordíthatjuk és futtathatjuk ugyanazt a C#-forráskódot, amelyet ebben


a fejezetben láttunk Linux (vagy a Mono által támogatott bármelyik másik
operációs rendszer) alatt ugyanazon Mana-fejlesztőeszközök alkalmazásával.
Valójában a függelékben létrehozott bármelyik szerelvényt, amely nem hasz­
nál 3.0 vagy 3.5 programozási szerkezeteket, telepíthetjük másik, Monót tá­
mogató operációs rendszerre, és futtathatjuk őket a mono futási környezeté­
ben. Mivel a szerelvények csak platformfüggetlen köztes nyelvi kódot tar­
talmaznak, nem kell újrafordítanunk az alkalmazásokat.

911
B függelék: Platformfüggetlen .NIT-fejlesztés aMONO-val

Megjegyzés Ne feledjük, hogy aMono 1.2.5 csak korlátozott módon támogatja a C# 2008 nyelvi
szolgáltatásait, úgyhogy néhány példa a 13. fejezetből csak úgy-ahogy müködik.

Javaslatok további tanuláshoz

Ha figyelmesen követtük a könyvben szereplő anyagokat, akkor sok mindent


tudunk a Monóról, mivel az a CLl ECMA-kompatibilis implementációja. Ha en­
nél is részletesebben érdekel bennünket a Mono, akkor a legjobb hely a vizsgála­
tok elkezdéséhez a hivatalos Mono-webhely (http: jjwww.mono-project.com).
Különösen a http:/ jwww.mono-project.com/Use weboldalt érdemes felkeresni,
ugyanis itt megtalálunk több fontos témát, például az adatbázis-hozzáférést
ADO.NET-tel, a webfejlesztést ASP.NET-tel és így tovább.
Emellett megjelent néhány Manóval kapcsolatos cikk a DevX weboldalon
(http:/ jwww.devx.com), amelyek érdekesek lehetnek:

"
"Mono IDEs: Going Beyond the Command Line : Mono-kompatibilis

IDE-ket vizsgál.

"
"Building Robust Uls in Mono with Gtk# : Asztali alkalmazások ké­

szítését vizsgálja a GTK#-eszközrendszer segítségéve!, a Windows


Forms alternatívájaként.

Végül, de nem utolsósorban érdemes tudni a Mana-dokumentáció webhe­


lyéről (http:/ jwww.go-mono.com/ docs) is. Itt a Mono alaposztály-könyvtá­
rairól, a fejlesztőeszközökről és egyéb témakörökről találunk dokumentációt
(lásd a B.lü. ábrát).

Megjegyzés A Mono online dokumentációs webhelyét a közösség tartja fenn, ezért ne le­
gyünk meglepődve, ha néhány befejezetlen dokumentáció linkjére bukkanunk! Mivel a Mono a
Microsoft .NET ECMA-kompatibilis disztribúciója, ezért használhatjuk a sokoldalú MSDN online
dokumentációt is aMono felfedezésekor.

912
Összefoglalás

� Mono Docum.,ntation -Window< Jnte'""t Explorer [ l §l ws·


o

00 I'J��!�·�!'.:_�� ., �:'���"2:1 ,_L • :.l:d�-�-('�=�.t: -�- � !:_-:.!


]:J
• . ______ _ _ _ _ • ___ �·
• "� _--
• -.c -----

{!:l � [':J Mono Docum.",ution SJ • � • � • 11} Page • 11} Tools• »


jc-;;,tents l
----' ---

�1· Mono Documentation Ubrary


----

l�A·
l l
t:rmt
. •t� .!,li..IJ.ifflffltii
l Class Libran.
' Mono Libraries
=j
=l
-i
G-class Library nnm<> ibrari<>�
ffi-Mono Libraries czilla Libraries
ov<>ll Lihr=>ri<>c
,j
ffi-Gnome Libraries

J
onoDevelon IDE Libraries
- lÍJ-Mozilla Libraries Mnnnlinht/<;jlv<>rlinht
[$r Novetl Libraries
[3--MonoDevelop IDE Libraries
éJ Mocnlight/Silverlight
l # Lannuane Snecificatinn
C# omnil<>r Error R<>fer<>nce
Mono Dev<>lonment Tools
Mono Embeddinn
8-C# Language Specification
GJ-c# Compiler Error Reference �
" Internet l Proleeted Mode: Off �100% •

B.1 O. ábra: Az online Mana-dokumentáció

Összefoglalás
A függelék célja az volt, hogy bemutassa a C# programozási nyelv és a .NET
platform közötti programozás jellegzetességeit a Mana-keretrendszer haszná­
latávaL Ahogy láthattuk, a Mono több parancssori eszközzel rendelkezik, ame­
lyek lehetővé teszik különböző .NET-szerelvények - beleértve aGAC-ba tele­
pített, erős névvel ellátott szerelvényeket, a Windows Forms alkalmazásokat és
a .NET-kódkönyvtárakat- készítését.
A Mono nem teljesen kompatibilis a .NET 3.0 és a .NET 3.5 programozási
API-kkal (WPF, WCF, WF és LINQ), illetve a C# 2008 nyelvi funkcióival (bár
a Mono 1.2.5 korlátozott módon tárnagalja a C# 2008 nyelvet). A munkála­
tok már zajlanak (Olive-projekt), hogy a Microsoft .NET platformjának ezen
jellemzői bekerüljenek a Manóba. Bárhogyan is, ha olyan .NET-alkalmazást
szeretnénk készíteni, amelyet különböző operációs rendszerek alatt is lehet
futtatni, a Mana-projekt remek választás e célra.

913
Tárgymutató

aktuális elem, 819


1 aktuális felhasználó,57,59, 62,590,825,
828,829
128 bites szám,174,884
aktuális folyam,94
aktuális könyvtár,7
A,Á aktuális módosítás,587
aktuális változat,182
abszolút elérési útvonal,27
alacsony szintú,94,290,296,309,860,877
absztrakt osztály,5,633
alacsony szintű részlet,296,309
absztrakt ősosztály,58,107,113, 115,124,
aláhúzásjeles interfész, 857,873
130,167,438,551,560,621,623,645
alap adattípus,542, 649
Actíve Server Page,701
alapértelmezett engedélykészlet,49, 69
Actíve Template Library,851
alapértelmezett érték, 67,76,134,138,159,
ActiveX,396,409,457,461,480,733,851,
175,176, 184,325,510,614,650,680,
862,870,873
740,749,750,897
adatbázis,99, 103-108,117-128,131-134,
alapértelmezett kimenet,26
139-144, 160-178,204,206,209,210,212,
alapértelmezett konstruktor,86,89,136,
213,215,222,234,239,243,247,249-
192,193,201,305,358,371,486,488,
258,279,280,346,384,419,562,710,
517,738,741
739,771,772,775,818,819,820,835,
alaphelyzetbe állít,24,663,696,801
836,837,841-845,895,912
alaposztály,21,25,399,461,465,468,469,
adatbázis-központú, 239,895
479,484,543,548,563,592,707,751,
adatelérési logika,220
755,756,803,912
adathozzáférés,99,108,126,143,211
alaposztálykönyvtár,107,162,279,346,
adatkötés,169,190,246,462,468,510,517,
395,757,758,850
539,598,599,606,610,612,622,625,
alapszintaxis,499
739,759
alapvető LINQ 268
adatokat tárol,836
alfabetikus,405
adatszerződés,289,336-339
alkalmazás konfigurációs fájlja,287
adatszolgáltató,100-110,115,116,124-128,
alkalmazás könyvtár,10,853
131,133,138,147,162,204
alkalmazás típus,461
Add Reference párbeszédpanel,78,101,
alkalmazásobjektum,397,464,488,493,
720,852,853,855,867,873
472,478,479,488,490
ADO, 99-117,120,124,126,129, 131,136,
alkalmazástartomány,53,54,55,57,58,59,
140-148,158, 162-164, 167,169,170,182,
60,348,833
186,190,193,200,205,209,220,237,
alkalmazásszintű,365,481,594,802,809,
239-243,247,249,259,267,276,702,
811-813,816-819,824,836
734,771,818,819,892,895,902,912
alkönyvtár,7,55,710,725,898,906
adott gép, 133,316,352,862
állapot,250,288,423,687,744,799,801,
adott interfész,308,865
807,812,813
adott szerelvény,65,868,905
állapotgép,345,352,353,354
Advanced gomb,334
állapotmentes,686,748,755,799
agnosztikus, 282,339
általános működés, 112,869
AIX, 891
általános System.Object,814
aktív ablak,407,419
ASCII,37
aktuális alkalmazás, 27,29,202,398,476,478,
ASP.NET webalkalmazás,382, 687,690,
487,492,493,497,501,630,815,818
691,708,742,759
Tárgymutató

ASP-oldal,702 belépési pont,234, 408,464,471,495,638


aszinkron metódusreferencia, 37, 159 belső osztály,504
aszinkron viselkedés, 37 belső típus,225, 861
asztali,3,100,143,267,329,348,452-459, bemeneti,28,32,93,96,140,145,152,165,
462,463,518,530,536,685,686,692, 421,441,469,531,532,623,624,686,
712,746,801 697,698,701,702,732,733,738,754,
asztali alkalmazás,3,329,348,452,453, 759, 767, 778,779, 781, 783,785,803,
454,457-459,463,518,536,685,686, 826,831,880
692, 746,801 BeOS,894
asztali alkalmazás készítés,3 bináris,3,4, 20,31-33, 61, 72,73, 77,78,80,
átmeneti,75 82,83,85,90,97,100,150,170,172,184,
átméretezhető, 398,582 189,190,267,281,282,298,300,301,
attribútum,65,76,87,91,125,253,254,282, 337,450,452,456,463,470,492-496,
306-308,315,317,318,337,339,391, 660,661,662,680,724,727,844,850,
466,488,501,505,537,613,666,693, 852,873,890,891,895,906
697,698,701,715, 740,765,835,838, bináris fájl,82, 83,100, 189,267, 724, 727,
839,844,864,880,883,884 850,891,895,906
átviteli protokoll,685,686,799 bináris kód,282
Autó típusának kiírása,875 bináris kompatibilitás, 852,873
automatikus tulajdonság, 192,250,362, bináris olvasó,32
367,371,383,384,444,445,446,608 bittérkép,391,514,515,660
automatikusan generált,67,222,225,230, biztonsági beállítás,41,45,49, 65, 67,133,
233,234,235,236,256,492,495,499, 278,299,402,745
504,537,727,740,777,838,853,875, Boo nyelv fordítója, 899
883,888 böngésző,284,459,686,698-702, 708,721,
automatikusan generált együttműködési 732,733,747,748,783,795,803,805,
szerelvény,853 824,829,830,831,832
automatikusan generált típus,236
azonos másolat, 91
c
azonos név,366
azonos tag, 640,731 C meghajtó,15,17,18,21,34,44,323,397,903
azonosító,53,58,292,776, 785 C# nyelv, 320, 534, 849, 894, 900, 909
C# ref kulcsszó, 261

B C# typeof operátor,504
C#-definíció,87,902
barátságos név,49,172,207,310,333,513, C#-fordító,489,490,893,896, 899,900,904,
872 905
base kulcsszó,812 C#-forrás,247,502,621, 639,648,650,670,
beágyazott,40,150,158,225,257,310,401, 681,722,902,911
466,492,493,494,498,506,516,521, C#-kulcsszó,859
525,535,546,566,570,584-587,636, C#-metódus,339
658,662,681,721,796,861 CAS,3,38,40,41,46,50,52,69
beágyazott erőforrás,493,586, 662,681, 861 CAS-beállítás,46,52
beépített,162,190,345,349-352,355,390, ccw, 849, 881,882
391,395,482,531,533,535,543,568, CCW által megvalósított interfész,882
593,699 CIL,849,855,873,893,894,899,901
beépített támogatás,699 címke,505,599,649,650,651,652,653,655,
beépülő modul, 699 694,711,717,755,777,801,842
befejezetlen dokumentáció,912 címkézés,329
bejövő adat,374 ClassinterfaceType felsorolás,883
bejövő paraméter,339,341,365,375, 603, CLI,143,891,892,893,894,899,912
712 Click esemény, 64, 194,196,211,215,217,
bejövő parancssori paraméterek,367,477, 231,388,402,411,427,444,446,505,
478 545,712,748,783

916
Tárgymutató

ClickOnce gyorsítótár, 67 doboz,431,433,561,574,672


ClickOnce-telepítés, 64 DriverName tulajdonság,876
CLl-kompatibilis,893 dupla katt,468
CLR,38-46,49,51,56,65, 69,73,74,100,
107,148,288,289,298,304,306,337,
E,É
351,539,541-546,604,648,651,743,
749,834,838,849,850,853,855,857, ECMA,892,893,894,895,912
858,873,874,879,881,894,899,909 ECMA-334, 893,894
CLR-disztribúció,899 ecset,203,507,511,605,619,620,622,633,
CLR-interfész,288, 289,298,304,306 634,635,637, 638
CLS,879,893 egy fájl, 6,21,32,59,494,736,907
CLS-azonosítók, 879 egy sor,75,78,108,175,182,183,195,200,
Cocoa#, 892,895 232,255,320,473,568,579,738,775
Code tevékenység,359,360,361,363,365, egyedi adatok,115
366,371,372,375,379,381,384,385 egyedi alkalmazás,54
COM BSTR, 859 egyedi azonosító,90,174,754,828,839,852
COM együttműködés,316,849, 851,854 egyedi érték, 175,509
COM lDL,859,861,867,869,880,881 egyedi folyamat, 833
COM lDL adattipus,859 egyedi gyűjtemény,399
COM kapcsolódási pont,855,877,882 egyedi interfész, 856, 882
COM-adat,858,859 egyedi kivétel, 144
COM-alkalmazás,850,851,852,875,879, egyedi konstruktor,76,86,93,208,320,
880,886,889 398,608
COM-ból hívható burkoló,849,881 egyedi könyvtár,59
COM-kommunikáció, 857 egyedi megvalósítás,250,349,350
COM-könyvtár,865,878 egyedi módon, 101,372,547,601
COM-metódus, 858, 859 egyedi név,339,843
COM-objektum, 105, 278, 280,409,472,702, egyedi osztály, 247,444, 477,602,627,816,
703,812,850,851,854,855,858,860, 825,843,844
861,866,868,871,873,878,881,882 egyedi rendszer, 849
Component Object Model,278 egyedi tároló,306
CORBA,279 egyedi típus,287,297,306,337,346,486,
cs fájl,192,256,263,320,325,326,330,338, 536,628,648,725,815,835
358,359,360,364,387,407,408,427, egyedi tulajdonság,102,106
433,440,443,492,495,497,498,504, egyedüli paraméter, 188
519,524,525,537,885 egyetlen alkalmazás, 130,259,348
csc,397,398,474,490,900 egyetlen alkalmazástartomány, 348
CTS,281,706,849 egyetlen érték,313,467
egyetlen fájl,488,490,709,788
egyetlen paramétertípus, 649
Cs
egyfájlos, 709, 710,713,716,726,727,728
csomópont46,118,119,123,351,391,409, egyszálú tárrész, 409
518,615,688,719,763,863 egyszeres függvényhívás,10
egyszerű metódus,337,884
együttműködés,281,729,850,879
D
együttműködési képesség,849,879
deklaratív módon,38,124,129,280,281,323, együttműködési réteg,849,850,861,878
347,350,516,539,706,771,787,797 együttműködő szerelvény,855,857,859,867
DevX, 912 elengedi a referenciáját,881
digitális aláírás, 307 elérhető,28,32,54,75,77,100,113,278,
digitális tanúsítvány,40 282,287,289,290,294,295,310,311,
dinamikus betöltés,724 316,339,357,543,725,795,895,903
DISPID,869,880 elérhető utasításkészlet,903
diszasszembler,901 elérhetőség,815

917
Tárgymutató

életszerű,345,357 felsorolható, 244


életszerű alkalmazás, 345 felsoroló, 43, 44, 67
ellenőrzés,245,305,693,698,757,783,784 felső korlát,53
elosztott rendszer,277,278,280, 281,286, felszabadít,23,26
288,290,294 felügyeleti eszköz,45,333
előugró menü,469, 522 FileMode felsorolás,60
előző kód,135,200,209,441 fix érték, 75
előző példa,73,97,233,854 fizikai könyvtár, 688
előző példakód, 73,854 foreach ciklus, 193
elsődleges kulcs, 118,121, 122, 156,163, formális definíció, 109, 803
170,175,176,177,184,210,249,775 formális specifikáció, 892
eltávolít,173,824 formalizált specifikáció, 892
elválasztott, 133,702 főablak,398,399,427,428,431,432,�,
eredményhalmaz,141,245,252 474,478,525,547,585,596,625,629,660
erőforrás,162,494, 496,498,515, 516, 619, főmenü,401
661,662,666,679 frissítés,232
erőforrásszótár, 604,605,613,665,681 front end,143,152,159,183, 211,236, 280,
erős név,39,44,45,49,54,850,861,867, 370,528,530,613,685,889,905
901,904,905,906,913 funkcióját tekintve azonos, 488,905
erősen típusos,6,ll, 110,112,136,148,150, funkcionális mód,269,276
169,171,206,226-228,230,231,234, funkcionalitás,23,117,240,277,282,288, 295,
236,237-243,247,251,252,259,289, 357,438,469,470,488,707,879,887
304,336,415,471,474,561,563,614, futási idejű hiba, 739
752,815,838,840,883 futási környezet,909,911
Error,741,742,809,810,827,907,910 futási útvonal,352
érték típus,838 futásidejű,39,41,50,96,113,130, 144,152,
érvényes érték,135,329 163,180,183,245,339,349,366,367,418,
Esc,418,430,431,552,553,614 452,472,510,512,519,620,765,855
eseménykezelő,35,402,411,418,424,451, futásidejű ellenőrzés, 130
480,485,498,523,548,720, 727,737, futásidejű hiba, 765
738,740,742,808,810,824,855 futásidejű kivétel,96,113,152,163,180,
Eseménykezelő,809 183,245,339,366,367,472,519
eszközrendszer, 285,286,485,530,675,912 futásidejű proxy,855
eszközmr,359,500,586,588,589,694 futó alkalmazás,3,7,64
European Computer Manufacturers futtatható,65,224,346,398
Association,892 futtató környezet,898,899
exe szerelvény,126 függőség,74,470,690,709
explicit kaszt,83,130 függvény visszatérési érték,866
Express Edition,107,117

G
F
generikus gyűjtemény, 461
F1,329,534,596,597 generikus Ust<T>, 147,192,446,449
fejlesztőeszköz,901 generikus osztály,320
fejlesztői nyelv,891 generikus tipusok, 649,900
fejlesztői platform,691 globális szerelvénytár,126,290,855,867,
felhasználóbarát,112,175,206,329,333, 879,886,894,898,906
851 globális szerelvénytár (GAC),894
felhasználóbarát szintaxis,851 GNU General Public Ucense,894
felhasználói felület eleme,190,455,457, grafikai alkalmazás,902
470,572,594,601,611,663,667 grafikus asztali alkalmazás, 72
felhasználónkénti,825 grafikus felhasználói felület,152, 191,395,
felsorolás,6,"58,134,137,182,418,457,543, 427,453,517,531
551,870

918
Tárgymutató

grafikus felület,117, 194,212,387,452,453, l' í


574,595,675,681,699,704,706,751,
755,823,827,829,837 ideiglenes,4,789,830
grafikus front end,708,902 ideiglenes tároló,4
grafikus renderelés, 417,455,462,506,508, IDisposable interfész, 15,144
571,577,617,619,620,621,623,625, IDL,861,862,863,864,865,866,867,869,881
630,633,639,645,647,681 lDL-könyvtár,865
GTK#,531,892,897,902,912 IDL-szintaxis, 863,864
GlJllJ,174,852,859,864,880,884,885 lDL-token,864
Guid típus, 174 időpocsékolás,143
IEnurnerator interfész,400
IfElse tevékenység,372,373,385
Gy
IlS, 66, 67, 293,294,295,308,310,313,337,
gyakori tagok,78 341,343,368,685,687,688,689,690,
gyermek-,415,419,476,751 691,692,706,719,721,810,818
gyors teszt,344, 902 liS-beli virtuális könyvtár, 293,294,308,
337,343,368,691,719
implementáció,308,891
H index, 626

hasznos metódus,732 indexelő, 112,130,186,477,833


indirekció, 655,765
hatókö�15,54,59,164,257,258,546,547,667
ingyenes IDE,692
hely, 547,585,912
inicializálás, 480
helyi fájl,39,52, 68,72,73,84,95,188,270,
inline,220,359, 409,410, 663-667,766,772,
443,460,739,741,903,907
774,776,779
helyi fájlrendszer,39, 52,68,460
helyi gép, 42,46, 67,118, 133,303,333,835 integrált fejlesztői környezet, 118,220,352,
391,411,412,420,533,852,862
helyi intranet, 45
helyi másolat, 100, 169,170,206, 413, 724, IntelliSense,373,501,521,522,710,711,
791,844
837,853
helyi menü,52, 192 Interface Definition Language,861

helyi merevlemez,30,38,45-48,50,51,64, interfész,79,88,93,96,98,109-112,159,163,


289,304,308,320,339,413,414,493,530,
896
598,602,605,856,857,861,865-869,870,
helyi szárnítógép,38,50,66,458, 459,460,
836 871,875,876,877,880,883,888,889
internet, 39
helyőrző,148,150,656
írásvédett,17,31,32,59, 103, 108, 134,138,
hibakezelő,742,810
hibára hajlamos, 528 139,142,147,176,186,228,405,501,
539,541,544,615,627,651,821
hibás bemenet,827
írásvédett mező,539,542,821
hivás,380
hivó,60,102,103,112,113,144,146,170, írásvédett tulajdonság,31,32,59,134,615,
627
205,258,260,284,289,292,304,384,
írható-olvasható,539
398,446,693,730,732,733,858,869,
871,882,883 ISO,892

hoszt, 292-298,305,309-318,320,323,324, Item menüpont, 207,256,262,309,611,661,

325,329,331,355,391,401 743,760,764,768,788,808

hosztolás,382 izolációs hatókör,59

hosztoló ablak,572
hosztoló alkalmazás,41,329,834 J
hosztolt,313, 341,378,726,728
li�L,278,578,580,685,686,691-696,698- J2EE,81,278,279,281,287

709,711,712,714,717,718,722,726, Java,419,531,699,733,801,892
730,733,736,737,746-748,754,757-760, jelölőnégyzet,67,280,334,336,556,557,
768,770,778,783,795,796,803-805,845 686,693,749,886

húzd-és-dobd, 469,589 jelző,50,83, 91,905

919
Tárgymutató

jobb felső,409,770 kliens,100, 170,281,287,295,296,299,301,


jobb gombbal kattint, 46,118,263,327,330, 307,316,321,322,325,685,731,732,
387 735,743,857,858,860,865,868,869,
jobb teljesítmény, 150,286,301,456,624 879,881,882,909
jogosultság,14,41,52,65,69,485 kódbázis,124,130, 859
JScript,699,709 kódcsonk,360,411,440
kódcsoport,40,45,46,47,50,51

K kódhozzáférés-szabályozás,40
kódol,337
kapcsolat nélküli,100,101,107,109, 143, kódolás,24,25,312,357,620
147,167,169-171,186,190,212,230, kódrészlet,539
236,237,240 kódsor,190
kapcsolat nélküli réteg, 147 kód-újrafelhasználás,143
kapcsolatsztring, 105,127,129,130,134,136, komponenstálca,224
144,154,160,205,214,225,341,771 konfiguráció,391,688,739
kapcsolódó működés,351,414,415,468,531 konfigurációs fájl, 115,267,281,291,292,
kapcsolódó objektum,71,73, 74, 97 306,309,310,313,320,321,323,325,
kapcsolódó típusinformáció, 903 327,329,331,332,344,706,743
kapcsos zárójel, 513, 885 konstans adat,539
karakterek sorozata,175 konstans érték,778
karbantartható,130 konstruktor, 94,98,132,151, 214,227,480
kattintási esemény kezelője,431,565,677, konzol,24,28,61,274,347,565,685
732 koordináta,625,645
képpont,441,449,583,653 korai kötés, 868,882, 883
keretrendszer,3, 36,45,81,416,419,452, kovariancia, 900
453,461,468,738,739,850,851,913 könyvtár,6,7, 8,9,10, 14,39,142,152,284,
késői kötés,861,866,868,869,880,883,888 341,367,531,688,689,690
késői kötés folyamata,869 könyvtárstruktúra,4,5,62,282,724,898
kétoperandusú operátor,782 kötés,7, 190,194,285,287,296, 298-303,
keyfile kapcsoló,867 307,323,380,516,600,601,603,607,
kezdeti érték,507 611,613,868,874
kezdeti indítás, 687 Kötéselem,299,301,302
kezdeti méret,404 Kötési osztály,299,301,302
kezdőérték,178 közös funkció,99,113
kezeletlen kivétel, 810 közös interfész, 107,108, 114,247
kezelő,64,99,106,134,143,148,162,196,426, közös nyelvi infrastruktúra,891,893
478,492,497,587,708,712,740,836 közös típusrendszer,281, 893
kiinduló állapot, 659 köztes nyelvi kód,727,855,901,905,911
kiinduló kód, 220 közvetlen hozzáférés,537, 812,875
kimenet8,188,307,449,474,490,531,536, közvetlen létrehozás,29
559,580,624,635,647,681,736,752,854 közvetlen objekturnreferencia,866
kimeneti folyam, 737 közvetlen ősosztály,417
kis- és nagybetü érzékeny,172 kulcsszó,133,198,439,501,502,503,877
kis gyűjtemény, 93,273,702 kurzor,103,147,569,586,591,756
kisalkalmazás,688,834 külön sor,702
kisbetüs,97 külön szál,352
kisebb teljesítmény,883 különálló fájl,5,705
kiszolgálóoldali szkriptkód,702,716 különálló szerelvény,78
kivétel,67,144 - különböző névtér,268,396
klasszikus ActiveX,861 külső fájl,4,272,523,562,636,661,662,719,
klasszikus ASP-, 687,701,702,705,709,717, 763,821
733,801,866 külső felhasználó,39,277,278,460
klasszikus COM, 105, 409, 861, 868,882 külső függőség, 900
külső gyártótól származó,487

920
Tárgymutató

külső hivatkozás,691,860 megvalósítás,42,260,541,591,621,830,


külső hívó,282,284,287,288, 289, 292,294, 884,894
339,369 megvalósított,102,108,375,465,480,523,
külső kódkönyvtár,348,716 534,546,871,876
külső metódus,320, 851 memórián belüli,727
külső szerelvény,38,45,490,662,724,850 menüpont,117,293,368,692,710,720,724,
külső URL,39,48,49 852
külső webhely,38 metaadat,209,379,669,855,861,879,893
metaadat-formátum,861,893
metaadat-leírás, 379,669,855
L
metódusreferencia,34,159,257,314,336,
lambda kifejezés,336,878 366,402,403,423,439,473,476,482,
leállítás,687 712,717,820,821,822,824,877
legördülő lista,67,401,405,561 metódusreferencia-típus,34,402,821
legördülő listamező,67,405 11FC,453,531,851,891
legutolsó verzió, 896 Microsoft .NET Compact Fram:ework,894
lehetséges kivétel, 144 Microsoft fejlesztői nyelv,891
lehetséges megvalósítás,372 Microsoft Foundation Classes, 851
lehetséges névütközés,322,502 Microsoft GAC,906,909
lehetséges tesztfutás, 483,569 Microsoft lDL, 861
lehetséges út, 865 Microsoft Intemet Explorer,460,686,699
leképezés,489,501,505 Microsoft Silverlight,894
lekérdezés,103,137,139,148,161,206,208, Microsoft Visual Studio,863
245,251 Microsoft Xbox,561-563,894
létrehozás ideje,6,14 midl,861
limitált támogatás,649 rninden mező,250,264
LINQ,101, 239-254, 258-276,461,895,896, módosítás,103,108,148,247,362,790
913 módosító,253
LINQ API,239, 895 módszer,29,33,286,288,290,320,480,482,
LINQ-lekérdezés, 239-248, 250,251,252, 558,564,589,681,705,734,750,807,829
262,265-276 moniker,163,215,477
LINQ-támogatás,461,896 Mono GAC,904,905,906,909
Linux,81,279,891,892,894,897,899,911 Mono platform, 895,896,902
localhost,67,303,310,311,312,318,320, Mono SDK, 896
321,324,327,331,332,338,342,368, Mono Visual Basic fordító,899
374,378,380,691,701,702,713,731 MSBuild, 491,492
logikai,4,89,179,200,297,298,361,415, MSDN,912
419,477,493,548,558,614,656,660, MSDN online dokumentáció,912
663,665,694 munkaállomás,39
logikai csoportosítás,558,694 munkamenet, 307,316,526,687,721,722,
logikai erőforrás,660,665,666 729,744,793,794,799-802,807,809,
logikai érték,4,179,200,415,477,614,656 810,812,813,824-828,832,833-836
logikai tagváltozó,493 működés,64,202,275,296,363,368,450,454,
lokális adat,169 455,459,479,487,504,512,528,529,556,
lokális könyvtár,719 582,637,658,668,676,677,678,733,778,
784,787,839,873,904,911

M
N
MAC,279,461
megadott metódus,96,361 nagybetűs,97
megfelelő együttműködési szerelvény,867 napló, 722
megfelelő mód,688 nem absztrakt,4,486,488,517
megjegyzés,240,585,629 nem statikus metódus,489
nemfelügyelt kód,461

921
Tárgymutató

névtelen metódus,365,878, 900,909 osztálytípus,12,147,171,207,248,284,313,


névtelen metódusszintaxis,365 341,360,463,470,474,480,489,507,514,
Novell,894,896 519,541,726,728,731,830,880,885
null érték, 113,149, 175, 179, 254, 603
nullázható adat, 113
nullázható típus, 900
ö,ő
numerikus adatok,371, 372 ősosztály,6, 25, 73, 124, 321, 416, 422, 427,
numerikus érték,73,371,555,566,653,686, 435,468,469,471,645,649,650,675,
869 703,729,738,806,811,812
numerikus index,564,814 összekapcsolhatóság, 104
numerikus változó,366 összes elem,250,575,610,744,791

Ny p
nyilvános adat,77,86,92,815 parancssor,308,898,908
nyilvános konstruktor,179 parancssori alkalmazás, 159, 166,174, 205,
nyilvános mező,76,250 320,329,330,334,852,892,895
nyilvános osztály,207,298,883 parancssori fejlesztőeszköz,894,898
nyilvános tag,298,366,369, 865,870,875, parancssori fordító, 490
876,883 parancssori paraméter, 255,690
nyilvános tulajdonság,76,77,250,366,367, parancssoros fordító,397,473,517,898,900
423,433,485 párbeszédablak,293,325,334,336,357,
nyilvánvaló probléma, 116,346 373-379,428,429,431-435, 442,445, 447,
448,450,460,467,611,612,613,616,
760,804
o,ó
példány, 14,349, 350,364,365,366,367,
Objektum böngésző,855,863,873,876 488,751,817
objektum sorositás, 91 platform,3,38,83, 90,97, 99,162, 167,190,
objektuminicializáló szintaxis, 264,265,896 267,276,278,279,281,286,395,436,
objektumrnodell,170,193,272,273,278, 440,453,454,511,685,705,716,771,
454,471,705 782,849,851,890,892,893,894,913
objektumok gyűjteménye, 170,178,192 platforrnfelismerő OL,850
objektumreferencia,4,25,365,865 platforr.nfüggetlen,4,891,892,893,895,911
objektumváltozó,83 platforr.nfüggő,850
OLE,104,105,106,129,850,863,869,887 platforrnfüggő gépi kód,850
OLE View eszköz,863,869 platformközi,461
OLE View segédprogram, 863,887 Pocket PC,106, 462,469
00, 73,76,734,746 polimorf,5,551,729,755
OOP,699 polimorf interfész,5,551,729,755
opció,293,294,320,361,633,662, 688,732, polimorfizmus, 79,113,298,308
867 prefix,488,502
opcionális szerelvényszintű információ,861 prefixumos,441,550
operációs rendszer,16,26,38,50,55,73,81, privát adat,76,77
277-285,287,299,300,332,456,457, privát mező,76,250
461,540,687,691,829,851,891,892, privát szerelvény,711,720,723,724,855
894,895,896,897,911,913 privát sztringváltozó,481, 871
operátor,376 privát tag,77, 144,433, 492,873
optimalizált, 301,456,589 privát tagváltozó, 144,433,492,873
osztálydefiníció,250,542 Pro ASP.NET 3.5 in C# 2008, Second
osztálykönyvtár,233,234,295,893 Edition, 707
osztálykönyvtár tervezési útmutató,893 Pro WPF in C# 2008
osztálykönyvtár-projekt,233,234 Windows Presentation Foundation with
.NET 3.5,Second Edition,619

922
Tárgymutató

progranrrozott,4,34,110,194,200,250,291, Solution Explorer ablak,406,407,841


454,462-464,486,496,523,530,539, Sony PlayStation, 894
562,563,567,571,591,596,602,623, sorosítás, 71,87, 96,98,189,451
665, 672,674,696,708,723,732,734, sorosítási folyamat,74,90,91,92,93,94, 96,
743,751,782,793 97
programozott nrródon,454,464,486,496, sorosítható,76,77,450
523,530,539,562,563,567,571,591, speciális konstruktor, 93,94
596,602,623,665,672,674,708,723, specifikus engedély,49
732,734,743 SQL Server Express, 118,133
projekt workspace tipusok,862 SQL-kiszolgáló,143,835,837
public kulcsszó,433 Start nrrenü, 902
statikus adat,542
statikus írásvédett nrrező,544
R
statikus írásvédett tulajdonság,437
rádiógonrrb,67,446,556,749 statikus konstruktor,541,542,543
referencia,78,79,378,866 statikus nrretódus, 8,9,ll, 153,314,439,
reflector,225,494,501, 662,727 860,903
reflexiós API,42,49,903 statikus nrrező,541,651
relációs adatbázis,99,106, 134,148,169, statikus osztály, 593,909
205,220,239,240,241,247,248,262, statikus segédfüggvény, 135
264,267,276,436,598,902 statikus tag,4,5,ll, 19,464,512,513,514,
releváns nrródosítás, 116,366,367,400,571, 638,821
628 statikus tulajdonság,415,438,464,512,513
renrroting,71,79,267,277,281,282,286, StreamWriter osztály,26
287,298 StringBuilder objektum,30,136
rendszergazda,39,53,206,460,690,810 súgórendszer, 329,534
rendszerleíró adatbázis,38,53,278,460, Sun Microsystenrrs,285, 892
850,852,862,879,884,886 süti,480,687,731,802,829,830,831,832
rendszerleíró fornrrátunrropció,884
Részelenrr,316
Sz
részleges nrretódus, 258
részleges osztály,226,358,407,408,498, szabad,12,289,603
504,719,727,728,740 szabványos interfész,868
részleges típus,406,900 szálbiztos,409,814
robusztus 00,705 szálbiztos nrródon,814
rögzített,271,354,389,427,457,526,532,535, szánrros olvasó,71
554,613,680,738,743,839,842,844 SZálllOS opció, 867
származik,25,27,30,31,78,79,103,124,133,
137,162,320,358,396,465,467,470,474,
s
505,511,512,544,560,715,719,728,729,
sablon,292,303,622, 675-680,724 748,779,787,819,866,877
segédprogranrr,255,258,264,489,496,690, származtatás, 435, 668, 872
745,837,867,869,877,900,901,902 személyes preferencia, 506
sénrra,71,90,121, 186,693 szenrrétgyűjtő,441, 850
Shape ősosztály,630,631 szerelvény nrretaadat, 857
SharpDevelop,397 szerelvény szintű,501
Sofőr nevének beállítása,874,876 szerelvény típusa,309,850
sokoldalú,52,461,523,528,529,530,756, szerelvény verzió, 861
757,912 szerelvényazonosító,59
Solaris, 891,894 szerelvény-nrretaadatok,435
Solution Explorer,66,224,226,263,273, szerelvényszintű attribútum,65
327,330,358,387,406,407,420,422, szerelvényszintű izoláció,54
427,440,611,661,719,720,789,790, szintaktikai egyszerűsítés, 866
841,853,885 szkript, 702,705, 811,835

923
Tárgymutató

szkriptblokk,700,716,7'22 tervezési idejü,355,456,536


szkriptírás,685,698,746 típus indexelő, 806,819
szkriptnyelv,699,705 típus metaadat,908
szóköz,140,206,551 típusbiztos,883
szolgáltatásszerződés,297,308,336,343 típusindexelő,615
szolgáltató, 103,105, 106, 109, 127,128, 130, Típuskönyvtárak megtekintése,863
131,316,839 típuskönyvtár-importáló eszköz, 867
szögletes zárójel,864 titkosítás, 307, 829
szöveges adat,20,23, 25,26,30,65,149,425, Toolbox, 192,409,435,533,711,758
441,561,570,586,595,639,737,755 további tanulás, 912
szöveges leírás,179 több adatszolgáltató,102
szövegszerkesztő,283,323,397,403,458, több egyedi interfész,856
517,592,745 több fájl,829
sztring,3,4,7,24,146,148,172, 175,184, több interfészt támogat,871
196,366,403,441,465,466,481,484, több kódfájl,406
506,508,514,549,635,715,755,781, több példány,54
803,806 több sor, 568, 590
sztring értéke,508,635 többdimenziós tömb,147
sztring tömb, 514 többfájlas szerelvény, 901
sztringliterál,lll,125,148, 205,401 többinterfészes, 858
szubrutin,740,858,871 többször,99,139,209,348,482,573,747
szükséges információ,71 tökéletes választás,630
szükséges infrastruktúra,241,320,347,630, transzparens művelet,854
795,836 Treeview vezérlő,763,765,863
szülő-,259,751 tulajdonság értékének beállítása,466,510

T U,Ú
tábla,l18-123,132,141,145,159,163,171, Ul elem,647
174,175,176,178,195,200,205,206, újrafelhasználás,'220,456,533
210,223,224,228,237,251,774,795, újrafelhasználható, 382,387,705,746
796,806,8'22 Unicode,25
tagváltozó,192,227,331,407,446,448,449, Unix,279,891,894,895,899
799,800 tnRI,38,303,306,312,313,321,338,464,662
tárolt eljárás,103,ll O, lll,118,120,121, lnRL,39,42,45,56,66,67,106,318,374,
137,150,151,152,158,161,162,167, 459,686,688,691,694,702,713,731,
170,247,255,256,259,260,261,276, 732,736,737,764,765,767,824,827,
771,835,836 829,901
tartalmazás, 872 utasítás,120,142,145,153,255,551,594,
távoli alkalmazás,66 597,672,731,865
távoli hely,38,83 utasításkészlet,320, 903,905
távoli kiszolgáló,686 utolsó argumentum,543,869
távoli webkiszolgáló,65,67,458 utolsó feladat,123,342,374,543,674
távoli webszolgáltatás,282
telepítés,125,333,836, 898
telepítő,687,897
ü,ű
teljes COM lDL gyakorlat, 861 ügyfélalkalmazás, 852,907,908,909
teljes definíció,625 ügyfélalkalmazás könyvtár, 909
teljes elérési útvonal,6,44 ügyfélkonfigurációs fájl,324
teljes implementáció,153 ügyféloldali proxy, 284,317,320,321,335,
teljes kód,132,236, 446,841 379
teljes leírás,147,534 üres érték, 700
teljes megvalósítás,874 üres sztring,793
teljesen működőképes C#-fordító,894

924
Tárgymutató

v w

vágólap,381,617,824,885 WCF,104,152,162,277,278,280,281,285-
valódi COM,857,881 334,337,338,339,341,342,343,344,
valós típus,348,351 348,351,356,357,368,377,378,379,
változónév,488 380,382,895,896,913
van egy,24,38,64,73,117,127,198,199, WCF-ügyfél,289,295,317,320,323
207,211,243,246,249,261,271,303, web,706,747,748,771,772,790,791,799,
361,398,426,475,505,573,598,599, 837,838
602,701,780,825,826,856,860,865, webes alkalmazás,278,282,458,459,461,
867,872 464,685,687
\TB,143,255,531,862,863,865,866 webhely,106,280,390,687,688,689,691,
\TB6,453,851,852,854,855,857,858,859, 708,716,719,721,723,725,730,737,
860,861,862,863,864,865,866,867, 745,759-790,797,803,808,812,815,
869,870,871,872,873,875,879,883, 818,829,837,845,912
888,889,891 webszolgáltatás,282,283,284,285,290,
\TB6 fordító,852,867 293,298,300,301,306,319,337,368,
\TB6 kód,865,871 370,374,377,461
végfelhasználó,53,65,183,360,443,444, WF,162,345,347-353,355,356,357,358,360,
445,546,547,553,554,570,572,573, 364,365,366,368-374,377,379,382,384,
575,577,582,587,596,602,741,742, 388,389,390,391,895,896,913
752,767,770 while ciklus, 153, 186
végrehajtható,38,50,99,100,277,295,309, While tevékenység, 361, 362, 363
310,313,329,342,343,347,351,382, Win32 API,419,453
398,458,459,464,473,489,491,496, Windows Communication Foundation,71,
499,593,686,721,743,799,855,908 162,267,277,278,343,351,357,895
végrehajtható alkalmazás,459,491 Windows Forms ügyfélprogram,909
végrehajtható fájl,38,50,100,277,295,309, Windows Forrns-alkalmazás, 64,104,190,
310,313,329,342,343,382,398,464, 191,211,212,220,295,382,387,395,
489,496,686,743,799,908 397,398,406,408,420,436,442,443,
végrehajtható szerel vény,458,459,499, 452,752,799,801
721,799 Windows Forrns-vezérlőelem,52,391,395,
végső engedélykészlet,67 409
végső kivételkezelő,810 Windows Intéző,10,36,57,323,710,908,909
verem,470 Windows Presentation Foundation,190,
veremalapú,649 267,348,395,453,457,530,534,539,
vesszővel elválasztott, 133,864 592,617,619,681,895
'View Type Library menüpont,887 Windows Workflow,162,164,293,345,
virtuális könyvtár,66,67,337,688-691,701, 347,350,351,355,356,359,361,391,
703,738 455,895
virtuális metódus,416,511,536,625 WindowsXJP,55,81,300,456,687,690
virtuális tag,416,456,557,649 windowsos,749
'Visual Basic,699,709,851,882,888,896,899 Workflow,293,347,351,355,356,357,358,
'Visual C#,117,518,692 370,383,386,388,390
'Visual Studio IDE,292,336,896 workspace,862,870
'Visual Web Developer,692
visszaküldés,698,734,737,739,755,778,
x
783,801,803,806,812
visszatérési érték,10,88,113,114,120,140, X509 digitális aláírás,39
148,244,281,284,289,337,361,370, X509-es digitális tanúsítvány,42
375,381,512,542,615,616,839,875 x86,897
vizuális tervező,220,224,355,404,407, XAML böngésző,52,463,490
435,520,521,522,533,771 XBAP,52,459,460,461,463,500,518

925
Tárgymutató

XML-adatok,80,85,86,100,173,189,267,
269,284,329
XML-alapú konfiguráció, 281,290,743
X11L-dokumenhln1,78,86,87,88,175,188,
239,267-273,275,276,486,496,502,
600,606,611,613,614,616,617
X11L-elem,85,86,87,188,268,269, 315,
337,488
XML-fájl,52,85,188,611,721,723,763, 765,
767,768
XML-megjegyzés, 268
XML-névtér,80,306,488,501-503,513,516,
563,693
XML-tartalom,272,275
XML-webszolgáltatás,71,221,277,282,
283,285,287,298,299,302,306,337,
346,348,351,368,369,370,372,375,
377,690,708,892,901
XSD, 188,725,901

926
A szakmai lektorról

GAVIN SMYTH hivatásos szoftverrnérnök, aki években mérve sokkal több

fejlesztői tapasztalattal rendelkezik, mint amennyit hajlandó bevallani. Esz­


közillesztőktől kezdve az elosztott webes alkalmazások fejlesztésével foglal­
kozó projekteken át dolgozott sokféle platforrnon - például a 8 bites "nyers
vas" rendszereken, beágyazott valós idejű operációs rendszereken, Unix és
Windows rendszereken -, valamint az assembler, a C++, az Ada és a C#
nyelvek mellett más nyelveken is fejlesztett. Olyan ügyfeleknek dolgozott,
mint a BT és a Nortel. Jelenleg a Microsoft alkalmazottja. Gavin ugyan publi­
kált néhány tanulmányt (EXE, w here are you now?), de úgy véli, hogy mások
munkájának kritizálása, értékelése sokkal nagyobb kihivást jelentő feladat.
Amikor nem a gyomnövényekkel és a hangyákkal harcol a ker�ében, Gavin
Lego-robotokat próbál rávenni arra, hogy engedelmeskedjenek akaratának
(kizárólag a gyerekek kedvéért- őszintén).
A szerzőről

ANDREW TROELSEN a Microsoft MVP (Visual C#) cím tulajdonosa, part­

ner, oktató, valamint az Intertech Training (http:/ / www .lntertech.com) .NET


és J2EE fejlesztői oktatási központ tanácsadója. Számos könyvet írt, többek
között a Developer's Workshop to COM and ATL 3.0 (Wordware Publishing,
2000), a COM and .NET Interoperability (Apress, 2002), a Visual Basic .NET and
the .NET Platfonn: An Advanced Guide (Apress, 2001) és a díjnyertes C# and the
.NET Platfonn (Apress, 2003) című munkákat. Rengeteg cikke jelent meg a
.NET for MSDN online, a DevX és a MacTech fórumain, és gyakori előadó a
különböző .NET-konferenciákon és felhasználói csoportokban.
Andrew a Minnesota állambeli Minneapolisban él feleségével, Amandá­
vaL Szabadidejében türelmetlenül várja, hogy a Minnesota Wild megszerezze
a Stanley-kupát, de feladta a reményt, hogy a Minnesota Vikings megnyeri a
Super Bowlt, és komolyan hiszi, hogy a Minnesota Timberwolves nem kerül
a rájátszásba, amíg a jelenlegi vezetés a helyén marad.
Felelős kiadó: a SZAK Kiadó Kft. ügyvezetője
Felelős szerkesztő: Kis Ádám
Korrektor: Laczkó Krisztina és Trepák Mónika
Tördelő: Mamira György
Borítóterv: Flórián Gábor (Typoézis Kft.)
Terjedelem 61 (BS) ív.
Készült az OOK Press Nyomdában (Veszprém)
Felelős vezető: Szathmáry Attila

You might also like