Content extract
Budapesti Műszaki és Gazdaságtudományi Egyetem Villamosmérnöki és Informatikai Kar Villamosmérnöki Szak Műszaki Informatika Szak Választható tárgy Szoftverfejlesztés a .NET platformra Mérési segédlet I. mérés Bevezető feladatok A labor célja, hogy a .NET és a Visal StudioNET legalapvetőbb tulajdonságaival és funkcióival megismerkedhessenek a hallgatók. 1. Első lépésként a Visual StudioNET környezettel kell ismerkedni Bemutatásra fog kerülni, hogy hogyan lehet a különböző ablakokat (class view, solution view, stb.) elrejteni, átpakolni Az ábrán a Visual Studio.NET látszik, és a munka során legsűrűbben használt ablakok, éppen szétdobálva a munkaterületen. Ezen ablakok mindegyik -1- dokkolható bal és jobboldalra, így csak akkor ugranak elő (hasonlatosan a Windows taskbarjához), mikor az egérmutatót föléjük visszük. A labor alatt részletesen lesz szó arról, hogy melyik ablakban mit lehet látni, mikor fogjuk őket
használni. 2. Második lépésként a hallgatóknak létre kell hozniuk egy új Solution-t (File-NewBlank Solution), melybe a labor alatt az összes elkészülő projectet bele fogják illeszteni. A Solution hivatott arra, hogy összefogja a Project-eket Egy Solution bárhány projectből állhat, egy project pedig bárhány fájlból. Fontos hangsúlyozni, hogy csak a fejlesztés megkönnyítésére lett létrehozva a project feletti szemlélet, hiszen a tényleges végeredmény (legyen az EXE, DLL, stb) a projectet állítják elő, a solution csak „összefogja” ezeket. 3. Hozzunk létre az üres solution-ben egy üres C#-os projectet (File-New-Project / Visual C# Projects-Empty Project).Az így létrehozott Project alapértelmezésben egy konzolos alkalmazás mindenféle kód nélkül. A laborvezető megmutatja a hallgatóknak, hogy mi dönti el egy projectről, hogy az konzolos alkalmazás/DLL/windows alkalmazás legyen fordításkor. Hozzunk létre egy üres C# állományt a
projectben (A projecten jobb-kattintás: Add New Item-Class). Így egy teljesen üres C# állományt kapunk CS kiterjesztéssel (VB.NET esetén VB kiterjesztés, stb) Készítsük el első konzol alkalmazásunkat, azaz gépeljük be a következő pár sort: namespace Sample { using System; public class HelloWorld { // nem feltetlenül szükséges public HelloWorld() { } public static int Main(string[] args) { Console.WriteLine( „Szia világ!“ ); return 0; } } } Futtassuk le az alkalmazást Debug módban, azaz F5-el. Tegyük Breakpointot a Console.WriteLine sorhoz, és nézzük meg, hogyan lehet lépkedni debug módban futtatás közben. Fontos még itt megérteni, hogy hogyan tudtunk osztályt, névtereket definiálni és felhasználni. Ezt a laborvezető részletesen kifejti 4. A következő feladat a paraméterek átadása a konzolos alkalmazásnak A laborvezető bemutatja, hogy hol lehet beállítani a project futtatatáskor átadandó paramétereket. A feladat, hogy az összes
megkapott paramétert kiírjuk a konzolra -2- egy új sorban. Ezt kétféleképpen kell megvalósítani, az egyik megoldás, mikor egy for() ciklussal oldjuk meg, melyben a ciklusváltozó 0-tól args.Lenght-ig fut, és indexelve az args tömböt hívjuk a Console.WriteLine() metódust A másik módszer a C#-ra inkább jellemző foreach() használatával történik a következő módszerrel: foreach( string s in args ) { s változó értékének kiírása} 5. A szöveg-szám átalakítás egy igen sűrűn használt módszer A feladata tehát, hogy konzolról olvassuk be a felhasználó által beírt értéket (Console.ReadLine()) és ezt alakítsuk int értékké az Int32.Parse() segítségével Fontos, hogy a Parse() függvények kivételt dobhatnak, és mivel a konzolról nem szükségszerűen érkezik int-é alakítható szöveg, ezért jelen esetben fontos is a kivétel kezelése. Készítsük tehát el a programot úgy, hogy a szöveg beolvasása egy try{} blokkban, a
hibakezelés pedig az ezt követő catch(Exception ex){} blokkban legyen. A hibakezelő függvényben jelezzük vissza a felhasználónak, hogy a beírt érték hibás volt. 6. A most elkészült alkalmazást alakítsuk át egy komponensé Ennek lépesi igen egyszerűek: a project tulajdonságai között állítsuk át, hogy nem konzol alkalmazássá kell fordítani, hanem Class Library-vé, ami egy DLL-t fog eredményezni. A Main() függvényt tegyük hagyományos publikus függvénnyé, azaz ne legyen static. Emellett nevezzük át valamilyen más névre 7. Miután elkészült a komponensünk, hozzunk létre egy új projectet a solution-ön belül, és készítsünk belől egy konzolos alkalmazást. A most elkészült projecthez adjuk hozzá referenciaként a másik projectet (references ikonon jobb gombbal, majd Projects). Ilyen módon meg tudjuk hívni a komponensünk egy függvényét Tegyünk is így: példányosítsuk a komponenst egy Komponenstipus kt1 = new Komponenstipus()
hívással, és hívjuk meg a már átnevezett Main() függvényét az éppen aktuális paraméterekkel (tehát adjuk tovább az args-t a komponensnek). II. mérés A mérés során a hallgatók a .NET keretrendszer legfontosabb alaposztályaival, illetve azok használatával fognak megismerkedni. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! 1. A hallgatók megismerkednek a SystemArray-el, azaz a tömbbel Ez az egyetlen gyűjtemény, mely kívül esik a Collections névtéren. Ebben a feladatban a hallgatóknak létre kell hozniuk egy konzolos alkalmazást, és abban a command line paraméterként kapott értékeket átmásolni egy Object tömbbe: object[] paramArr = new object[ args.Length ]; args.CopyTo( paramArr, 0 ); string elso = paramArr[1]; Fontos megérteni, hogy az utolsó sor fordítási hibát fog okozni. Ennek oka, hogy a létrehozott Object tömb tetszőleges
típust (object) képes tárolni, így implicit nem képes String-é alakítani. Ehhez szükséges CAST-olni a visszakapott értéket: string elso = (string)paramArr[1]; Igen ám, de mi történik abban az esetben, ha a visszakapott érték nem String? Hiszen az object tömbből bármi visszajöhet. Ilyen esetben a rendszer kasztolási kivételt dob -3- Próbáljuk is ki ezt: paramArr[1] = 4; //string elso = (string)paramArr[1]; Kommentezzük ki a második sort. Futtatás közben kivélelt kapunk Ezt azzal kerülhetjük el, hogy nem kasztoljuk, hanem az AS operátor segítségével alakítjuk típusos értékké az adott elemet. A következő sor soha nem dob kivételt: string elsov2 = paramArr[1] as string; Igaz, hogy az elsov2 értéke, ha a tömb második eleme (nullától számozunk!) nem string, akkor „null” lesz, de kivételt nem dob a rendszer! 2. Az Array osztály támogatja a benne található elemek rendezését, amennyiben azok implmentálják az IComparable
interfészt. A legtöbb beépített alaposztály implementálja, így például egy String tömb is egyszerűen rendezhető: Array.Sort( args ); Ezzel a hívással az args bemeneti String tömb elemei sorrendbe kerülnek. Próbáljuk ki más típusokkal is (Int32): hozzunk létre egy Int32-es tömböt, töltsük fel értékekkel és rendezzük. Figyeljük meg a tömb tartalmát debuggerrel! 3. Az Array osztály, a rendezés mellett, támogatja a bináris keresést is elemei között Ehhez persze szükség van arra, hogy az adott tömb rendezett legyen! Használjuk fel az előző feladatban elkészített Int32-es, rendezett tömböt, és keressük meg benne az értékeket: Array.Sort( intArr ); int pos = Array.BinarySearch( intArr, 50 ); Ha van a tömb elemei között 50, akkor a pos értéke a tömbbeli helyére fog mutatni. 4. Ebben a fejezetben a legsűrűbben használt dinamikus tömböket fogjuk áttekinteni (az Array létrehozása után nem átméretezhető!). A legsűrűbben a
típus nélküli System.CollectionsArrayList-et használjuk fejlesztés közben. Pontosan úgy kell használni, mint egy hagyományos tömböt, annyi extrával, hogy újabb elemek hozzáadása egyszerűen az Add() hívással történik: using System.Collections; ArrayList arl = new ArrayList(); arl.Add( "Hello" ); arl.Add( "World" ); string hello = arl[ 0 ] as string; Vegyük észre, hogy az ArrayList nem típusos, fontos átalakítani (futás közben) a visszakapott értéket a nekünk kívánatosra. Mivel számos esetben nem megfelelő nekünk az ArrayList az Array helyett, így egyetlen hívással típusos Array-é konvertálhatjuk (ha minden eleme az adott típusba tartozik!): string[] strArr = (string[])arl.ToArray( typeof(string) ); A System.CollectionsHashTable segítségével a különösen sok elemet tartalmazó tömbökben való keresést egyszerűsíthetjük le. A HashTable kulcs-elem párosokat használ a tároláshoz, ahol a kulcs az adott elem hash
kulcsa szokott lenni. Hashtable ht = new Hashtable(); for( int i = 0; i < 10; i++ ) { ht.Add( i, "Ertek: " + iToString() ); } if( ht[ 3 ] != null ) { -4- Console.WriteLine( "Letezo ertek: " + ht[ 3 ] ); } Mint látható a HastTable feltöltése hasonlatos az ArrayListnél látottakhoz. Keresés benne a tábla indexelésével történik, ahol az index értéke az általunk megadott kulcs. 5. IEnumerable interfész: a legtöbb dinamikus tömb implementálja ezt az interfészt, minek segítségével az összes benne található elemen végig lehet lépkedni. Tegyünk így, írassuk ki a konzolra a HashTable teljes tartalmát: IDictionaryEnumerator enumerator = ht.GetEnumerator(); Console.WriteLine( "Kulcs - Ertek" ); while( enumerator.MoveNext() ) { Console.WriteLine( enumeratorKeyToString() + " " + enumerator.ValueToString() ); } 6. Ebben a feladatban a fájlrendszer használatával fog megismerkedni a hallgató A fájlrendszert a
System.IODirectory és SystemIOFile statikus metódusok segítségével, illetve a System.IODirectoryInfo és SystemIOFileInfo példányok függvényeivel lehet elérni. Kérjük le a konzolról beolvasott könyvtár fájljait, és írjuk ki a konzolra: using System.IO; DirectoryInfo di = new DirectoryInfo( Console.ReadLine() ); foreach( FileInfo fi in di.GetFiles("*.*") ) { Console.WriteLine( fiFullName ); } 7. A konzolon megadott TXT kiterjesztésű fájlt nyissuk meg, ha létezik, és írjuk ki a tartalmát. Ha nem létezik, akkor a konzolról kapott értékkel töltsük fel: Console.WriteLine( " Kerem adja meg a szovege fajl nevet (kiiras/feltoltes) " ); FileInfo fi2 = new FileInfo( Console.ReadLine() ); if( fi2.Exists ) { StreamReader sr = fi2.OpenText(); Console.WriteLine( srReadToEnd() ); sr.Close(); } else { StreamWriter sw = new StreamWriter( fi2.FullName ); sw.Write( ConsoleReadLine() ); sw.Close(); } Figyeljük meg a felhasznált osztályokat és azok
funkcióit: FileInfo, StreamReader, StreamWriter! 8. Az utolsó feladat keretén ki kell írjuk egy HTML állományba a konzolról beolvasott webcímen található tartalmat, majd el kell indítani egy Internet Explorert, paraméterként megadva a HTML fájllal: Console.WriteLine( "Kerem adja meg HTTP keres URL-jet (pl http://www.googlecom)" ); -5- HttpWebRequest HttpWReq = (HttpWebRequest)WebRequest.Create( ConsoleReadLine() ); HttpWebResponse HttpWResp = (HttpWebResponse)HttpWReq.GetResponse(); StreamReader srw = new StreamReader( HttpWResp.GetResponseStream() ); string wstr = srw.ReadToEnd(); Console.WriteLine( "Kerem adja meg a kimeneti HTML fajl eleresi utjat: " ); string HTMLFileName = Console.ReadLine(); StreamWriter sww = new StreamWriter( HTMLFileName ); sww.Write( wstr ); sww.Close(); ProcessStartInfo pr = new ProcessStartInfo( @"C:Program FilesInternet Exploreriexplore.exe", HTMLFileName ); Process ieProc = Process.Start( pr ); III.
mérés A mérés során a hallgatók a .NET keretrendszer belső világával ismerkedhetnek meg, az szemétgyűjtő algoritmussal (GC), az köztes kóddal (IL) illetve néhány fontosabb optimalizációs technikával. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! 1.A GC megismerése Készítsünk olyan osztályt, amely rendelkezik a Finalize metódussal, azaz takarító kóddal (hasonló a C++-ból már jól ismert destruktorhoz). ~Class1() { } Console.WriteLine( "Generacio: {0}, finalizer !", GCGetGeneration( this ) ); A takarító kód futását a konzolra kiírt üzenet jelenti. A GC csak a referencia típusú objektumokkal foglalkozik az érték típusokkal nem. Az érték típusok nem is rendelkezhetnek takarító kóddal. A GC futása nem ismert, a kód észre sem veszi – esetleg csak a felhasználó. Viszont a takarítás elindítható a System.GC
osztály statikus metódusának segítségével Erre szolgál a Collect metódus, mely paraméterben megkaphatja a takarítandó generáció számát. Minden olyan objektum, ami rendelkezik takarító kóddal, legalább egy takarítást túl él, így sohasem fog elsőgenerációs objektumként megszűnni. Attól függően, hogy a megszünése előtt élt-e túl GC-t, 1-es vagy 2-es generációs objektumként hal el. 2. Az IL kód Készítsünk egy egyszerű osztályt, amely elvégez egy összeadást és kiírja az eredményt: -6- class Class1 { public int add( int a, int b ) { } return a + b; static void Main(string[] args) { Class1 c = new Class1(); int r = c.add( 11, 12 ); } } Console.WriteLine( "11+12 = " + rToString() ); Fordítás után az ILDASM eszközzel vizsgáljuk meg a keletkezett il kódot. Láthatjuk, hogy a keletkezett szerelvény a típusokról teljes metainformációt tárol. A MANIFEST-et megvizsgálva láthatjuk a külső dll-ekre való
hivatkozást, a hivatkozásba épített nyilvános kulcs tokent. Elolvashatjuk a szerelvény szintű attribútumokat is. -7- Figyeljük meg az add metódus köztes kódját. A legfelső sor a metódus fejlécét jelenti, visszatérési értékkel, paraméterekkel, stb. Minden metódusban kötelezően belekerül, hogy maximum milyen hosszú vermet használ. Ez feltétele az ellenőrizhető kódnak, így nem készíthetünk ilyan állományt (még ilasmmal sem), amely ne tartalmazná ezt az információt. A következő sor egy, a C# fordító által generált lokális változó definícióját tartalmazza. Utána a kód beolvassa a két paramétert a végrehajtási veremre. A NET végrehajtó motorja (VES – Virtual Execution Engine) egy kiértékelési vermet használ a programok futtatásához. Ennek lényege: műveletet csak olyan objektum(ok)on (illetve érték(ek)en) hajthatunk végre, amely ezen a bizonyos vermen legfölül helyezkedik/helyezkednek el. Az művelet eredménye
is a veremre kerül, így rögtön tovább lehet vele dolgozni. Lokális -8- változókba, statikus változókba, egyéb objektumok változóiba (illetve rendre változóiból) erről (illetve erre) a veremről lehet emelni a referenciákat, értékeket. Folytatva a kód vizsgálatát, láthatjuk hogy a két felső értéket összeadjuk. Figyeljük meg, hogy sem az összeadó kód, sem pedig a paraméter betöltő kódok nem tartalmazzák az objektum típusát. Az eredmény újból a veremre kerül Ezt az értéket az stloc.0-val a 0-ik lokális változóba tesszük A következő utasítások a logika szempontjából feleslegesek, csak a nem optimlizált kódban található meg (feladat: fordítsuk le Release módban is a projektet és vizsgáljuk meg a különbségeket !). Végül a metódus a ret utasítással tér vissza, a verem tetején található érték válik a visszatérési értékké. Vegyük most szemügyre a Main metódust. Néhány mondat az itt látható kódról:
A newobj utasítás új objektumot hoz létre, amit rögtön rátesz a verem tetejére. Az ldc.i4 konstans értékeket tölt a veremre – ez esetben 11-et és 12-őt A callvirt metódus meghívja az add metódust, amely paraméterei a verem tetjén lévő értékek. Az ldstr konstans sztring értéket tölt a veremre, míg az ldloca egy lokális változóra mutató referenciát (jelen esetben az r nevűt). Ezen meghívjuk a ToString metódust, majd a Concat-ot. Végül a WriteLine utasítás hatására kerül a konzolra a teljes szöveg. Jól látható, hogy a kiértékelési verem hatékony adatstruktúra a program szekvenciális végrehajtásához. HÁZI FELADAT: -9- Pirospontért adja meg azt a kódot, amely a következő IL kódot generálja (Debug, azaz nem optimalizált kódban): 3. Optimalizációs technikák A .NET-es sztringek megváltoztathatatlanok Ez azzal a kellemetlen következménnyel jár, hogy minden alkalommal, mikor a sztringen egy műveletet végrehajtunk,
nem a meglévő módosul, hanem egy új objektumot kapunk vissza. Ha a régire nem mutat referencia, akkor az eltűnik majd a memóriából. Éppen ezért a sorozatos sztring műveletek jelentősen visszafogják a .NET keretrendszer teljesítményét, elsősorban a memória használatában figyelhető meg lényeges különbség. Készítsük el a következő kódrészletet, és merjük le a sebességét. const int N = 20000; static void Main(string[] args) { DateTime t0 = DateTime.Now; string s = ""; for( long i = 0; i < N; i++ ) - 10 - s += " alma"; DateTime t1 = DateTime.Now; ).TotalSeconds ); Console.WriteLine( "string concat time: {0} ", ( t1 - t0 } A CLR profilerrel vizsgáljuk meg az alkalmazásunkat. Jól látható, hogy szinte csak string típusú osztállyt foglaltunk mégpedig elég sokat (összesen mintegy 2GB méretben). A AllocationGraph grafikont használva láthatjuk, hogy 1.9 GB-ot a StringConcat metódus foglalt, amit mi a
Main metódusból hívtunk. - 11 - Másik grafikonon azt olvashatjuk le, hogy szinte csak a String.Concat metódust hívtuk Végül pedig idő szerint láthatjuk a végrehajtott GC-k számát, ami összesen 1862 darab. Javítsunk az algoritmuson a StringBuilder segítségével: DateTime t0 = DateTime.Now; StringBuilder sb = new StringBuilder(); for( long i = 0; i < N; i++ ) sb.Append( " alma" ); string s = sb.ToString(); DateTime t1 = DateTime.Now; Console.WriteLine( "string concat with StringBuilder time: {0} ", ( t1 - t0 ).TotalSeconds ); - 12 - Elkészítve a kódot láthatjuk, hogy még a 10x nagyobb feladatot is alig mérhető idő alatt elvégzi. Érdemes most is megvizsgálni néhány grafikont a CLR Profiler segítségével, nagyon tanulságosak. 4. Optimalizáció a sorosításhoz Készítsük el azt a kódot, ami egy egész számokat tartalmazó DINAMIKUS tömböt sorosít. Sorosítunk XML illetve bináris formátumba Hasonlítsuk
össze a két eredményt teljesítmény illetve helyigény szempontjából. ArrayList array = new ArrayList( N ); for( int i = 0; i < N; i++ ) array.Add( i ); { DateTime t0 = DateTime.Now; BinaryFormatter bf = new BinaryFormatter(); FileMode.Create ) ) using( FileStream fs = new FileStream( @"c:data.bin", { } bf.Serialize( fs, array ); DateTime t1 = DateTime.Now; ).TotalSeconds ); Console.WriteLine( "binary serialization: {0} ", ( t1 - t0 } { DateTime t0 = DateTime.Now; SoapFormatter sf = new SoapFormatter(); FileMode.Create ) ) using( FileStream fs = new FileStream( @"c:data.xml", { } sf.Serialize( fs, array ); DateTime t1 = DateTime.Now; ).TotalSeconds ); Console.WriteLine( "binary serialization: {0} ", ( t1 - t0 } Megvizsgálva az eredményt, láthatjuk, hogy az XML alapú sorosítás közel 10x lassabb és több mint 6x több helyet foglal. Alkalmazzunk egy típusos tömböt az általános ArrayList helyett és
vizsgáljuk meg az eredményeket ! - 13 - IV. mérés A mérés során a hallgatók a .NET keretrendszer XML támogatásával ismerkedhetnek meg. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! XML betöltés, lementés (alapok) Az XML DOM számnos osztálya áll rendelkezésünkre Egy teljes XML fájl beolvasására vagy lementésére. Amit mindenképpen használnunk kell, azaz XMLDocument Feladat Készítsünk egy alkalmazást, amiben kódból létrehozunk egy XML dokumentumot, vegyünk fel bele elemeket (element), majd mentsük le egy tetszőleges fájlba. using System.Xml; . XmlDocument doc = new XmlDocument(); doc.AppendChild( docCreateXmlDeclaration( "10", "UTF-8", "yes" ) ); doc.AppendChild( docCreateElement( "Hello" ) ); doc.Save( "axml" ); Feladat Vizsgáljuk meg, hogy mi a fájl tartalma, és miért. (Húzzuk
rá a VSNET-re, vagy az IEre) Feladat Vegyünk fel bele elemeket, és ismét nézzünk bele. XmlNode root = doc.CreateElement( "Hello" ); doc.AppendChild( root ); root.AppendChild( docCreateElement( "Elem1" ) ); root.AppendChild( docCreateElement( "Elem2" ) ); XML fájl elemenként Amennyiben méret vagy egyéb probléma miatt nem akarjuk/tudjuk használni az XML DOM-ot, úgy lehetőség van az XMLReader és az XMLWriter osztályok felhasználására. Segítségükkel tag-enként írhatjuk, vagy olvashatjuk a fájlt, az értelmezés azonban a mi feladatunk. Feladat Az előbb létrehozott XML fájlt olvassuk be XMLReader-rel, és az elemeket (element) listázzuk ki. XmlTextReader r = new XmlTextReader( "a.xml" ); - 14 - while( r.Read() ) { if( r.NodeType == XmlNodeTypeElement ) Console.WriteLine( rName ); } r.Close(); Feladat Olvassuk végig az XML fájlt, és egyből írjuk is ki egy másik fájlba az XmlTextWriter segítségével.
XmlTextWriter w = new XmlTextWriter( "b.xml", System.TextEncodingUTF8 ); w.WriteNode( r, false ); w.Close(); Feladat Nézzük meg az eredményt, és formázzuk. w.Formatting = FormattingIndented; Feladat Olvassuk végig az XML fájlt, és közben minden elemhez (element) adjunk hozzá egy megjegyzést (comment), amely a XML fában lévő mélységét adja meg. w.WriteComment( "Depth: " + rDepthToString() + " Type: " + r.NodeTypeToString() ); Vegyük észre, hogy a fabejárásunk nem rekurzív, a részfába nem tudunk belemenni, ha nem ismerjük előre a szerkezetét. XPath Az XML DOM lehetőséget biztosít navigálásra. Egy faelem (XmlNode) SelectNodes tagfüggvényének adhatjuk meg az XPath kifejezést, amely visszaadja azokat a faelemeket, amelyet a kifejezés kiválaszt. Feladat Listázzuk ki az összes elemet, amelynek a Hello a szülője. foreach( XmlNode node in doc.SelectNodes( "/Hello/*" ) ) Console.WriteLine( nodeName ); - 15 -
Ellenőrzés Amennyiben DOM-ot használunk, lehetőségünk van az XML ellenőrzésére betöltéskor. Ellenőrizni lehet, hogy a fájl a megadott formában van-e, és így az alkalmazásunk képes lesz értelmezni, avagy meg se próbálkozzunk a betöltésével. Feladat Adott a következő XML fájl (kézzel vigyük be): <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <AddressBook> <contact name="Rajacsics Tamás" email="raja@aut.bmehu"/> </AddressBook> Készítsünk egy XSD fájlt, amely ellenőrizni fogja, hogy az XML fájl ebbean a formában van-e, illetve észreveszi, hogy: • a name attribútum legalább 6 karakter • van benne szóköz • a szóköz előtt és után is van legalább 1 karakter • az email mezőben van @ • előtte és utána is van legalább 1 karakter Figyelnünk kell arra, hogy – mivel az XML fájlban nem használtunk névtereket – lecseréljük az XML nézetben a
schema elemet a következőre: <xs:schema xmlns:xs="http://www.w3org/2001/XMLSchema"> - 16 - Feladat Készítsünk alkalmazást, amely beolvassa az XML és az XSD fájlt is és összeveti őket. Hiba esetén, írja ki, hogy hol és mi okozza azt. XmlTextReader r = new XmlTextReader( "b.xml" ); XmlValidatingReader vr = new XmlValidatingReader( r ); vr.SchemasAdd( null, new XmlTextReader( "//Helloxsd" ) ); vr.ValidationType = ValidationTypeSchema; - 17 - vr.ValidationEventHandler += new ValidationEventHandler(ValidationCallback); XmlDocument doc = new XmlDocument(); doc.Load( vr ); Illetve a validáló függvény: static void ValidationCallback(object sender, ValidationEventArgs args ) { Console.Write( "Hibás az xml fájl szerkezete: " ); if( args.Message != null ) Console.Write( argsMessage ); Console.WriteLine( argsSeverity ); } V. mérés A mérés során a hallgatók a az ADO.NET osztálykönyvtárát ismerik meg, melynek
segítségével különböző adatbázisok illetve egyéb adatforrások kezelhetőek a .NET keretrendszerben. A labor segítségével mind a kapcsolat alapú, mind a kapcsolat nélküli adatmodell használata elsajátítható a keretrendszerben. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! 1.A fejlesztőkörnyezet lehetőségei A mérésvezető bemutatja, hogy a fejlesztőkörnyezeten belül a Server Explorer-ben hogyan tekinthetőek meg, és módosíthatóak az SQL szerverben található adatbázisok, táblák, tárolt eljárások, stb. (A mérésvezető megmutatja azt is, hogy hogyan lehet tetszőleges adatforrást elérni a Server Explorer-ből.) Ezután módosítsa a Northwind adatbázisban a „Ten Most Expensive Products” nevű tárolt eljárást, hogy az a termék mesterséges kulcsát is visszaadja: ALTER procedure [Ten Most Expensive Products] AS SET ROWCOUNT 10
SELECT Products.ProductID, ProductsProductName AS TenMostExpensiveProducts, Products.UnitPrice FROM Products ORDER BY Products.UnitPrice DESC Futtassa a tárolt eljárást, és figyelje meg, amint a fejlesztőkörnyezetben megjelenik a lekérdezés eredménye. Ezután pedig próbálja ki a debug-olás lehetőségét is a tárolt eljárásra. Hozzon létre egy új tárolt eljárást UpdateProduct néven, mely a kulcs alapján módosítja az adott terméket: ALTER PROCEDURE UpdateProduct ( - 18 - @ProductID INT, @ProductName NVARCHAR(40), @UnitPrice MONEY ) AS UPDATE Products SET ProductName = @ProductName, UnitPrice = @UnitPrice WHERE ProductID = @ProductID - 19 - 2.A DataReader A kapcsolat alapú adatmodellben egy adatbázis kapcsolaton keresztül, egy kurzor segítségével kérdezzük le az eredményhalmaz sorait, melyre a .NET keretrendszerben a IDataReader interfész szolgál. Hozzon létre egy konzol alkalmazást, mely futtatja a „Ten Most Expensive Products” nevű
tárolt eljárást, és a termékek nevét és árát megjeleníti a konzolon: SqlConnection conn = null; try { // Kapcsolódás azadatbázishoz conn = new SqlConnection( "Integrated Security=SSPI;initial catalog=northwind;data source=localhost" ); // A kapcsolat megnyitása conn.Open(); // Az adatbázis parancs létrehozása SqlCommand command = new SqlCommand( "Ten Most Expensive Products" ); // Adatbázis kapcsolat megadása command.Connection = conn; command.CommandType = CommandTypeStoredProcedure; // Az adatok lekÉrdezése és kiiratása using ( SqlDataReader reader = command.ExecuteReader() ) { while ( reader.Read() ) Console.WriteLine( "{0}: {1}", ( string ) reader[ "TenMostExpensiveProducts" ], ( decimal ) reader[ "UnitPrice" ] ); } } catch ( Exception ex ) { // Kivétel szövegének kiiratása Console.WriteLine( exMessage ); } finally { // Az adatábzis kapcsolat lezárása, ha meg lett nyitva if ( ( conn != null ) &&
( conn.State == ConnectionStateOpen ) ) connClose(); } Figyelje meg, amint az adatbázis kapcsolat lezárása a finally blokkba került! Próbálja ki az alkalmazást a DataReader létrehozásánál használt úgy, hogy a using által definiált blokkot kétszer egymás után futtatja le. (pl for ciklussal) Próbálja ki ezt a using kulcsszó elhagyása mellett is, és magyarázza meg, amit tapasztal! - 20 - 3.A DataSet A keretrendszerben található DataSet osztály egy egyszerű, memóriában található kis adatbázist reprezentál. A DataSet adatbázisának szerkezete forráskódból is megadható (akár futás közben is), XML sémából is beolvasható, illetve egyes esetekben annak kialakítása automatikus is lehet. Adjon hozzá a projekthez egy új XML sémát users.xsd néven, majd az XML fülre másolja be ezt a sémát a gyökér elem belsejébe. (Edit | Paste as HTML ) <xs:element name="User" xmlns:xs="http://www.w3org/2001/XMLSchema">
<xs:complexType> <xs:sequence /> <xs:attribute name="name" type="xs:string" /> <xs:attribute name="password" type="xs:string" /> </xs:complexType> </xs:element> Ennek hatására a Schema fülön megjelenik az grafikusan ábrázolva: Ez a séma tehát felhasználók nevét és jelszavát tartalmazó User nevű táblát definiál. A konzol alkalmazásban hozzon létre egy új DataSet-et, abba töltse be az imént definiált sémát, majd hozzon benn létre két teszt rekordot: // A dataset létrehozása DataSet dataSet = new DataSet(); // A séma beolvasása dataSet.ReadXmlSchema( "//usersxsd" ); // A user tábla lekérdezése DataTable userTable = dataSet.Tables[ "User" ]; // Egy új user létrehozása DataRow newRow = userTable.NewRow(); newRow[ "name" ] = "Zoli"; newRow[ "password" ] = "xxx"; userTable.RowsAdd( newRow ); // Mégegy user
létrehozása newRow = userTable.NewRow(); newRow[ "name" ] = "Peti"; newRow[ "password" ] = "zzz"; userTable.RowsAdd( newRow ); Jelenítse meg a DataSet tartalmát a konzolon XML formátumban: // A DataSet tartalmának iiratása a konzolra XML-ként dataSet.WriteXml( ConsoleOut ); Console.WriteLine(); - 21 - Hajtson végre lekérdezéseket az adott táblába rendezést illetve feltételt megadva: DataRow[] rows; // Lekérdezés név szerinti sorrendben rows = userTable.Select( "", "name" ); foreach ( DataRow row in rows ) Console.WriteLine( "{0}: {1}", row[ "Name" ], row[ "Password" ] ); // Lekérdezés jelszó szerinti sorrendben rows = userTable.Select( "", "password" ); foreach ( DataRow row in rows ) Console.WriteLine( "{0}: {1}", row[ "Name" ], row[ "Password" ] ); // Lekérdezés név szerint rows = userTable.Select(
"name=Peti" ); foreach ( DataRow row in rows ) Console.WriteLine( "{0}: {1}", row[ "Name" ], row[ "Password" ] ); A DataSet képes annak megjegyzésére is, hogy milyen változtatásokat végeztünk el rajta. (Ez praktikus a többrétegű alkalmazások esetében, ahol csak a változtatásokat kívánjuk átküldeni a hálózaton.) Véglegesítse az adott tábla módosításait, változtassa meg az egyik rekord tartalmát, majd kérdezze le a módosításokat a táblából, és a módosított rekordokat jelenítse meg a konzolon: // Eddigi módosítások elmentése userTable.AcceptChanges(); userTable.Rows[ 0 ]["Name"] = "Jani"; dataSet.WriteXml( ConsoleOut ); Console.WriteLine("----------------"); // A módosítások lekérdezése DataTable changesTable = userTable.GetChanges(); DataSet tempDs = new DataSet(); tempDs.TablesAdd( changesTable ); tempDs.WriteXml( ConsoleOut ); Console.WriteLine(); 4.A DataSet
használata adatbázissal A DataSet igazi erőssége abban rejlik, hogy egy adatbázis lekérdezés eredményével egyszerűen feltölthető, majd a rajta végzett módosítások (amit tehát a DataSet megjegyez) szintén egyszerűen visszaszinkronizálhatóak az adatbázisba. Ezt a két funkciót az adapter végzi el, mely az adatbázist és a DataSet-et illeszti egymáshoz. Írja meg a 2. feladatot, de most úgy, hogy a lekérdezés eredménye egy DataSet-be kerüljön, és annak tartalmát írja ki: // Kapcsolódás azadatbázishoz SqlConnection conn = new SqlConnection( "Integrated Security=SSPI;initial catalog=northwind;data source=localhost" ); // Az adapter létrehozása SqlDataAdapter adapter = new SqlDataAdapter(); // A lekérdező parancs létrehozása adapter.SelectCommand = new SqlCommand( "Ten Most Expensive Products" ); adapter.SelectCommandConnection = conn; adapter.SelectCommandCommandType = CommandTypeStoredProcedure; // A dataset létrehozása és
feltöltése DataSet dataSet = new DataSet(); - 22 - adapter.Fill( dataSet ); // A lekérdezett rekordok kiiratása foreach ( DataRow row in dataSet.Tables[ 0 ]Rows ) Console.WriteLine( "{0}: {1}", row[ "TenMostExpensiveProducts" ], row[ "UnitPrice" ] ); Az adapter tehát lekérdezéskor automatikusan létrehoz egy a lekérdezés eredményének megfelelő sémájú táblát a paraméterként kapott DataSet-ben, és feltölti azt a megfelelő adatokkal. Az adatok visszaszinkronizálásakor pedig lekérdezi a módosult rekordokat az adott táblá(k)ból, és a módosítás típusától függően meghívja a megadott InsertCommand, UpdateCommand, és DeleteCommand egyikét. Egészítse az alkalmazást úgy, hogy az a lekérdezést követően módosítsa a DataSet tartalmát, és a változtatást visszaszinkronizálja az adatbázisba: // A kijelölt termék árának módosítása DataRow selectedRow = dataSet.Tables[ 0 ]Rows[ 0 ]; selectedRow[
"UnitPrice" ] = ( decimal ) selectedRow[ "UnitPrice" ] + new decimal( 1.5 ); // A módosító parancs létrehozása adapter.UpdateCommand = new SqlCommand( "UpdateProduct" ); adapter.UpdateCommandConnection = conn; adapter.UpdateCommandCommandType = CommandTypeStoredProcedure; adapter.UpdateCommandParametersAdd( "@ProductID", SqlDbTypeInt, 4, "ProductID" ); adapter.UpdateCommandParametersAdd( "@ProductName", SqlDbTypeNVarChar, 40, "TenMostExpensiveProducts" ); adapter.UpdateCommandParametersAdd( "@UnitPrice", SqlDbTypeMoney, 4, "UnitPrice" ); // A dataset visszaszinkronizálása az adatbázishoz adapter.Update( dataSetTables[ 0 ] ); VI. mérés – Windows Forms I A mérés során a hallgatók a .NET keretrendszer felületkezelésével, felhasználói interfészével ismerkedhetnek meg. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a
mérésen a hallgatók rendelkezzenek a segédlettel! Szia világ Az első és legegyszerűbb WinForms program a Szia világ kategória. Feladat Készítsünk egy új Windows Forms alkalmazást a tervező segítségével. Az ablakra húzzunk rá egy TextBox-ot és egy Button-t. A gomb eseménykezelőjébe (amely például a duplaklikk-re jön létre) írjuk bele, hogy a szövegmező Text tulajdonságát állítsa „Szia világ”-ra. Feladat Vizsgáljuk meg a teljes alkalmazást! Nézzük át, hogy a tervező mit generált! Módosítsunk bele kézzel, és vizsgáljuk meg, hogy változik-e a tervezőben! Tegyünk hibát a generált kódba majd az ablak többi részébe és nézzük meg, hogy a tervező hogyan reagál mindezekre! - 23 - Feladat Nevezzük át az ablakot, a gombot és a szövegmezőt megfelelő nevűre és értékűre (pl. az ablak neve legyen MainForm, Text-je „Szia világ mintaalkalmazás”, a gomb neve legyen buttonSzia, a szövegmezőben alapban ne legyen
semmi, stb.) Ha nem fordul le ezek után, akkor folytassuk az átnevezéseket, amíg jó nem lesz. Figyeljük meg, hogy az eseménykezelő neve nem változik meg. Töröljük ki (2 helyen kell), oldjuk meg, hogy forduljon le, és generáljuk újra dupla klikkel. Feladat Ismerkedjünk meg az alapvető vezérlőelemekkel, és a laborvezető segítségével készítsünk egy ehhez hasonló dialógus ablakot. Az ablak ne legyen méretezhető, legyen félig átlátszó, ne lehessen maximalizálni, a használt karakterkészlet legyen Comic 12, a ForeColor legyen piros, minden szöveg legyen jobbra, középre rendezve. Minden legyen középre rendezve Figyeljük meg, hogy így sem kerül egymás alá a kettőspont (habár véletlenül lehetséges). GDI+ laboron visszatérünk rá. A karakterkészlet és egyéb, az egész ablakra jellemző tulajdonság beállításakor ne egyesével tegyük azt meg minden vezérlőnél, hanem a Form-ét állítsuk át, amit a vezérlők örökölnek.
Összetettebb vezérlő elemek A következőkben a TreeView és a ListView kerül terítékre. Feladat Az ablakról töröljük le az eddig feltett vezérlőket (mindet kijelöl, del), majd tegyünk fel 3 újat a következőképpen: Egy TreeView-t, majd Dock-oljuk balra Egy Splitter-t (ez alapban a TreeView-hoz ragad) Egy ListView-t, majd Dock-oljuk Fill-re Próbáljuk ki, működnie kell az elválasztónak (lehet húzni, és méretezi a két oldalát). A fába vegyünk fel új elemeket (több szinten)! A listát állítsuk Detail nézetre, vegyünk fel 2 oszlopot Név és Méret névvel! - 24 - Egyszerű windows explorer készítése Feladat Töltsük ki kódból (konstruktorban) a bal oldali fát egyelőre csak egy tetszőleges könyvtár tartalmával. using System.IO; treeView.NodesClear(); TreeNode root = treeView.NodesAdd( "Local Disk (C:)" ); root.Tag = new DirectoryInfo( "C:\" ); foreach( DirectoryInfo di in new DirectoryInfo( "C:\"
).GetDirectories() ) root.NodesAdd( diName )Tag = di; Feladat Kezeljük le a TreeView AfterSelect eseményét, és töltsük fel rá a listát. Figyeljük meg, hogy az előző kódban is és most is a Tag adatmezőt használjuk az elemhez tartozó információ eltárolására. DirectoryInfo di = (DirectoryInfo)e.NodeTag; listView.ItemsClear(); foreach( FileInfo fi in di.GetFiles() ) listView.ItemsAdd( new ListViewItem( new string[] { fiName, fi.LengthToString() } ) ); Opcionális feladat (ha marad idő) Mivel egy teljes könyvtár lista lekérdezése rengeteg ideig tart, azt a konstruktorban nem tehetjük meg. Ehelyett csak a kinyitott könyvtárat töltjük fel Módosítsuk emiatt a kezdeti feltöltést. treeView.NodesClear(); TreeNode root = treeView.NodesAdd( "Local Disk (C:)" ); root.Tag = new DirectoryInfo( "C:\" ); root.NodesAdd( "Kamu elem" ); Vegyük észre, hogy beszúrtunk egy kamu elemet, mivel a kis plusz jelre szükségünk van. Ezek után
kezeljük le a BeforeExpand eseményt. if( e.NodeNodesCount == 1 && eNodeNodes[0]Tag == null ) { e.NodeNodesClear(); DirectoryInfo parentDI = (DirectoryInfo)e.NodeTag; foreach( DirectoryInfo di in parentDI.GetDirectories() ) { TreeNode node = e.NodeNodesAdd( diName ); - 25 - } node.Tag = di; try { if( di.GetDirectories()Length > 0 ) node.NodesAdd( "Kamu elem" ); } catch{} } A hibakezelő rész fontos, mert nem minden könyvtárba fogunk tudni belépni. Értelmezzük a kódot! Csak akkor csinálunk valamit, ha ez a könyvtár még nincs kitöltve, azaz benne van a kamu elem. Ezt onnan tudjuk, hogy csak az van benne és a Tag-je null Ekkor töltsük fel a már ismert módon a fát, és tegyünk be kamu elemeket a nem üres könyvtárakba. Ezek után módosítsuk a listafeltöltést is (AfterSelect), hogy belekerüljön a hibakezelés (megjegyzem ez így nem jó, de most nem az a lényeg). DirectoryInfo di = (DirectoryInfo)e.NodeTag; listView.ItemsClear(); try
{ foreach( FileInfo fi in di.GetFiles() ) listView.ItemsAdd( new ListViewItem( new string[] { fi.Name, fiLengthToString() } ) ); } catch{} VII. mérés – Windows Forms II A mérés során a hallgatók a .NET keretrendszer felületkezelésével, felhasználói interfészével ismerkedhetnek meg. Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! Chart: saját vezérlőelem készítése Feladat Készítsünk egy új Windows Forms alkalmazást. A projektbe hozzunk létre egy új UserControl-t Chart névvel. Vizsgáljuk meg a generált kódot Miben különbözik a sima Form-tól. Feladat Fordítsuk le az alkalmazást, hogy létrejöjjön az exe. - 26 - Húzzuk rá az új vezérlőt a form-ra. (A Toolbox-on a My User Controls kategóriában található.) Nevezzük át chart1-ről pl chart-ra Vizsgáljuk meg a generált kódot a form-ban. Feladat Vegyük fel a rajzoláshoz
mindenképpen szükséges adatokat, a pontok halmazát. ArrayList points = new ArrayList(); Override-oljuk az OnPaint metódust (írjuk be, hogy override, és a space után előjön a lista), majd töröljük a tartalmát. protected override void OnPaint(PaintEventArgs e) { } Feladat Használjuk a GDI+ objektumait, és a bal felső sarokba írjuk ki, hogy mit fogunk rajzolni (tesztelési célból). Graphics g = e.Graphics; g.DrawString( "Number of points: " + pointsCountToString(), new Font( "Aria", 12 ), SystemBrushes.ActiveCaption, 0, 0 ); if( points.Count > 0 ) g.DrawString( "Type of points: " + points[0]GetType()ToString(), new Font( "Aria", 12 ), SystemBrushes.ActiveCaption, 0, 20 ); Mivel nincs egyetlen pont sem a tömbben, nem látjuk a 2. kiírást Ideiglenesen vegyünk fel egy pontot a konstruktorban. points.Add( new Point( 10, 10 ) ); Figyeljük meg a kiírást mind futás közben, mind tervezési időben! Vegyük ki a pontot a
listából, mentsük el a fájlt, de tervezési időben nem tűnik el. Fordítsuk le, vizsgáljuk meg megint. Mivel szemmel láthatólag a saját vezérlőelem tervezési időben is fut, nagyon óvatosan kell eljárjunk külső erőforrások használatával, hiszen ekkor a vezérlő úgy fut, hogy közben a program többi része nem. PropertyGrid Feladat Mivel nem szeretnénk bedrótozni a programba a beállításokat, készítsünk egy külön osztály, amelyik ezért lesz felelős. public class ChartSettings { public Font DebugMessageFont = new Font( "Arial", 12 ); public SolidBrush DebugMessageBrush = new SolidBrush( Color.Blue ); - 27 - public bool EnableDebugMessages = true; } Majd vegyük fel a ChartSettings-et a változók közé, és írjuk át a rajzoló rutint, hogy ezeket a tulajdonságokat használja fel. public ChartSettings settings = new ChartSettings(); if( settings.EnableDebugMessages ) { g.DrawString( "Number of points: " +
pointsCountToString(), settings.DebugMessageFont, settingsDebugMessageBrush, 0, 0 ); if( points.Count > 0 ) g.DrawString( "Type of points: " + points[0]GetType()ToString(), settings.DebugMessageFont, settingsDebugMessageBrush, 0, 20 ); } Feladat Kövessük ugyanazt a módszert két vezérlő csúszkával elválasztott elhelyezésére, mint az explorer készítésekor, és vegyünk fel egy PropertyGrid-et a Chart mellé a szerkesztőben. Nevezzük át például propertyGrid-re. Teszteljük az alkalmazást. Feladat A beállítások osztályt alakítsuk át a következőképpen public class ChartSettings { public Font debugMessageFont = new Font( "Arial", 12 ); public SolidBrush DebugMessageBrush = new SolidBrush( Color.Blue ); public bool enableDebugMessages = true; [Category( "Debug Messages" ),Description( "Debug message font" )] public Font DebugMessageFont { get { return debugMessageFont; } set { debugMessageFont = value; } } [Category(
"Debug Messages" ),Description( "Debug message color" )] public Color DebugMessageForeColor { get { return DebugMessageBrush.Color; } set { DebugMessageBrush = new SolidBrush( value ); } } [Category( "Debug Messages" ),Description( "Enable debug messages" )] public bool EnableDebugMessages { - 28 - } } get { return enableDebugMessages; } set { enableDebugMessages = value; } Látható, hogy a funkcionalitása azonos maradt, a formája azonban jelentősen megváltozott. Feladat A konstruktorban adjuk meg a propertyGrid-nek, hogy mihez jelenítsen meg adatokat. propertyGrid.SelectedObject = new ChartSettings(); Majd kezeljük le a tulajdonság változott eseményt. private void propertyGrid PropertyValueChanged(object s, System.WindowsFormsPropertyValueChangedEventArgs e) { chart.settings = (ChartSettings)propertyGridSelectedObject; chart.Invalidate(); } Látható, hogy a grafikonunk beállításait módosítjuk vele, majd invalidate-et
hívunk, hogy rajzolja ki magát újra. Vizsgáljuk meg a teljes programot. Feladat (opcionális) Rajzoljuk ki a pontokat a grafikonban. ChartSettings módosítások: public Pen ChartPen = new Pen( Color.Red, 10 ); public Point[] points = new Point[] { new Point( 10, 100 ), new Point( 200, 300 ), new Point( 250, 150 ), new Point( 400, 500 ), new Point( 600, 10 ) }; [Category( "Chart" ),Description( "Chart width" )] public float ChartWidth { get { return ChartPen.Width; } set { ChartPen.Width = value; } } - 29 - [Category( "Chart" ),Description( "Chart points" )] public Point[] Points { get { return points; } set { points = value; } } public ChartSettings() { ChartPen.LineJoin = LineJoinRound; ChartPen.EndCap = LineCapRound; ChartPen.StartCap = LineCapRound; } Chart módosítások. Először töröljük ki a pontokat, mert már a beállításokban megvannak Majd a rajzoló függvényhez adjuk hozzá a rajzolást. if( settings.PointsLength
> 0 ) g.DrawLines( settingsChartPen, settingsPoints ); Majd a Points tulajdonságot töröljük. Teszteljük a teljes alkalmazást. Feladat Miután a laborvezető elmagyarázta az egyszerű és összetett adatkötés lehetőségeit, valamint az ahhoz kapcsolódó „currency manager” feladatát, készítsünk egy egyszerű osztályt személyekhez, mely azok nevét és életkorát tartalmazza, melyek tulajdonságokon keresztül is elérhetőek, és egy olyan tulajdonságot, mely együttesen jeleníti meg ezeket az adatokat: public class Person { private string name; private int age; public Person( string name, int age ) { this.name = name; this.age = age; } public string Name { get - 30 - { } set { } } public int Age { get { } set { } } } return name; name = value; return age; age = value; public string Description { get { return name + " - " + age; } } Ezután készítse el az alábbi űrlapot: - 31 - A form konstruktorában hozzon létre
személyekből egy tömböt: persons = new Person[ 3 ]; persons[ 0 ] = new Person( "Szalacsi Sándor", 58 ); persons[ 1 ] = new Person( "Fogarassi Árpád", 57 ); persons[ 2 ] = new Person( "Fogarassi Malvin", 58 ); A személyek nevét és korát egyszerű adatkötés segítségével kösse hozzá a megfelelő textbox-okhoz: name.DataBindingsAdd( "Text", persons, "Name" ); age.DataBindingsAdd( "Text", persons, "Age" ); A tömböt kösse hozzá a listbox-hoz is úgy, hogy az azok leírását jelenítse meg: listBox.DataSource = persons; listBox.DisplayMember = "Description"; Próbálja ki, mi történik, ha a listbox-ból kiválaszt egy elemet! Magyarázza meg a jelenséget! VIII. mérés A mérés során a hallgatók a .NET keretrendszer távoli eljáráshívás technológiájával, a .NET Remotinggal ismerkedhetnek meg Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy
elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! 1.Hello világ alkalmazás készítése - 32 - A .NET Remoting technológia két távoli alkalmazás közötti üzenetcserét tesz lehetővé A távoli alkalmazás különböző alkalmazástartományokat (application domain) jelent. Ez lehet külön gépeken (akár más platformon) működő alkalmazásokat, egy gépen két taskot (futtatott exe) vagy akár egy Win32-es folyamatban futó két elszigetelt alkalmazást is (például ASP.NET-es alkalmazások) Az példában készített egyszerű alkalmazás két számot ad össze. A megoldás három projektből áll. Egy kliensből, egy szerverből és egy közös projektből, amelyben található kódok, interfészek mindkét oldalon felhasználásra kerülnek. A közös projektben található a Common.RemoteObj nevű osztály, amely egy virtuális metódust tartalmaz és a MarshalByRef objektumból származik. Minden távolról elérhető objektum ebből az
objektumból kell, hogy származzon. namespace Common { public class RemoteObj : MarshalByRefObject { public virtual int Add( int a, int b ) { } } } return 0; - 33 - A kliens oldali kód egyszerűen hivatkozik a Kozos projektre, és a RemoteObj osztályból példányosít egy darabot. Majd a megszokott módon meghívja annak Add metódusát private void button1 Click(object sender, System.EventArgs e) { } RemoteObj ro = new RemoteObj(); textBox1.Text = roAdd( 10, 3 )ToString(); A hozzá tartozó konfigurációs állomány (kliens.execonfig) tartalmazza azokat az információkat, amelyek meghatározzák, hogy a RemoteObj létrehozásakor milyen távoli gépre kell csatlakozik. <configuration> <system.runtimeremoting> <application> <client> <wellknown url="http://localhost:9000/Server" /> type="Common.RemoteObj, Kozos" </client> </application> </system.runtimeremoting> </configuration> A
szerver oldal egyszerű implementációt tartalmaz: namespace Server { public class FullRemoteObj : RemoteObj { override public int Add( int a, int b ) { } } } return a + b; - 34 - A hostolás kérdésével itt is a konfigurációs állomány foglalkozik, amely az alábbi módon néz ki: <configuration> <system.runtimeremoting> <application> <service> <wellknown mode="SingleCall" type="Server.FullRemoteObj, Server" objectUri="Server" /> </service> <channels> <channel ref="http" port="9000" /> </channels> </application> </system.runtimeremoting> </configuration> Látható, hogy a két alkalmazás a 9000-es porton, http protokoll fölött érintkezik egymással. A létrehozott távoli objektum SingleCall, azaz minden egyes hívásra létrejön majd megszűnik, így teljesen állapotmentes. - 35 - 2. Biztonság A .NET Remoting
alapértelmezésben nem támogatja a biztonságos kommunikációt, ezt külön be kell állítani. Beépített biztonsági szolgáltatások híján az IIS webszerver alkalmazható. Az IIS-ben úgy kell hostolni az alkalmazást, hogy a dll-eket a BIN könyvtárba tesszük, és elkészítjük a megfelelő web.config fájlt: <configuration> <system.runtimeremoting> <application> <service> <wellknown mode="SingleCall" type="Server.FullRemoteObj, Server" objectUri="Server.soap" /> </service> </application> </system.runtimeremoting> </configuration> Lényeges, hogy az objektum neve .soap-ra vagy rem-re végződjön, egyéb esetben a webszerver egyéb konfigurálása szükségeltetik. Az alkalmazást természetesen meg kell osztani webes módon, hogy az IIS ismerje mint webes alkalmazás és foglalkozzon az ide befutó kérésekkel. Az IIS-ben hosztolt alkalmazás minden olyan biztonsági megoldást
támogat, amely az webszerverben megtalálható. Így az adatforgalmat titkosíthatjuk SSL-el, és alkalmazhatjuk a különféle azonosítási módokat. Erre két példát említünk, egyik a BASIC AUTHENTICATION, amely Base64 kódolva de CLEARTEXT-ben küldi át a jelszót. Másik megoldás a Windows integrált azonosítás, mikor a ActiveDirectory-val azonosítjuk a felhasználót, a Kerberos protokoll felhasználásával. Az azonosítási módokat a szerver oldalon az IIS-ben kell állítani. További beállításokat lehet eszközölni a web.config fájlban, azonban ezek nem feltétlenül szükségesek, csak a .NET-es azonosítási moduloknak szólnak: <system.web> <authentication mode="Windows" /> <identity impersonate="true" /> </system.web> - 36 - A kliens oldalon a Basic authentikációt így lehet bekapcsolni: IDictionary props = ChannelServices.GetChannelSinkProperties( mgr ); props[„username”] = „user1”;
props[„password”] = „12345”; A windows integrált azonosítást a következő konfigurációs megoldás kapcsolja be: <channel ref = “http” useDefaultCredentials = “true” /> Szerver oldalon megszemélyesítés után különböző deklaratív vagy imperatív ellenőrzések végezhetők tetszés szerint. 3. Sorosítási típusok és protokollok A beépített .NET Remoting két fajta sorosítást támogat: a bináris és a SOAP alapú sorosítást. Ezeket például a konfigurációs állományban tudjuk megváltoztatni Lehetőség van a transzport protokoll kiválasztására is, beépített módon a HTTP és a TCP csatornát találjuk meg a .NET keretrendszerben Ugyancsak a konfigurációs állományban állíthatjuk be a kívánt csatornát. Érdemes megvizsgálni, hogy az egyes megoldások mennyi és milyen típusú adatokat mozgatnak a hálózaton, használjuk erre a tcptrace segédeszközt. IX. mérés – Web Szolgáltatások A mérés során a hallgatók a
Web Szolgáltatások készítésével és használatával ismerkednek meg a .NET keretrendszerben Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! A Google kereső szolgáltatásának használata A feladatban a google web szolgáltatása segítségével tesszük lehetővé, hogy egy Windows Forms alkalmazásból kereshessünk a weben. Sajnos a szolgáltatást nem tudták teljesen elkülöníteni a webes megjelenítéstől, ezért helyenként HTML elemeket is visszakapunk az eredményben., így az alkalmazás a web browser control-t használ az eredmények megjelenítésére. Feladat Készítsünk egy új Windows Forms alkalmazást. A formra húzzunk rá egy Web Browser Control-t (COM komponens!), illetve egy textbox-ot és egy gombot. Feladat A konstruktorban kezeljük le az browser control azon eseményét, melynek segítségével saját HTML-t jeleníthetünk meg:
AxSHDocVw.DWebBrowserEvents2 StatusTextChangeEventHandler handler = - 37 - new AxSHDocVw.DWebBrowserEvents2 StatusTextChangeEventHandler (this.browser StatusTextChange); browser.StatusTextChange += handler; Implementáljuk a gomb eseménykezelőjét úgy, hogy hatására a browser control megjelenítsen egy dokumentumot: string url = "about:blank"; object o = System.ReflectionMissingValue; browser.Navigate ( url,ref o,ref o,ref o,ref o); Feladat Hozzunk létre egy webes referenciát a google WSDL állománya segítségével, majd implementáljuk a browser control eseményét, hogy egy olyan dokumentumot jelenítsen meg, melyet mi állítunk össze a google szolgáltatása segítségével: private void browser StatusTextChange (object sender, AxSHDocVw.DWebBrowserEvents2 StatusTextChangeEvent e) { mshtml.HTMLDocument doc = (mshtmlHTMLDocument)thisbrowserDocument; GoogleSearchService service = new GoogleSearchService(); GoogleSearchResult res = service.doGoogleSearch(
"JeXpJfBQFHLna/IS5ydo1N8rj2U9l2kl", textBox.Text, 0, 10, false, "", false, "", "", "" ); StringBuilder sb = new StringBuilder(); sb.Append( "<html><body>" ); for ( int i = res.startIndex; i < resendIndex; i++ ) { sb.AppendFormat( "<a href = {1}>{0}</a><br>", resresultElements[ i ]title, res.resultElements[ i ]URL ); sb.AppendFormat( "{0}<br><br>", resresultElements[ i ]snippet ); } sb.Append( "</body></html>" ); doc.bodyinnerHTML = sbToString(); } Saját web szolgáltatás készítése és használata Ebben a feladatban egy egyszerű játék-kezdeményt készítünk el, a 3x3-as amőba játékhoz. Feladat Hozzon létre egy ASP.NET Web szolgáltatást, és valósítsa meg benne az alábbi metódust, mely egy lépést végez el a játékban, ennek megfelelően módosítja a táblát, és visszaadja az elvégzett lépést: [WebMethod] public
string Play( ref char[][] t ) { int i = 0,j = 0,n = 0; - 38 - } for ( i = 0; i < 3; i++ ) for ( j = 0; j < 3; j++ ) if ( ( t[ i ][ j ] != X ) && ( t[ i ][ j ] != O ) ) n++; if ( n > 0 ) { Random r = new Random(); n = r.Next( n ); for ( i = 0; i < 3; i++ ) { for ( j = 0; j < 3; j++ ) { if ( ( t[ i ][ j ] != X ) && ( t[ i ][ j ] != O ) ) { if ( n == 0 ) goto x; n--; } } } x : t[ i ][ j ] = O; } return ( i.ToString() + "," + jToString() ); Feladat Hozzunk létre a solution-ön belül egy Windows Forms alkalmazást, és helyezzünk el rajta 3x3 címkét. Ezek betűméretét állítsuk nagyra, háttérszínét pedig fehérre, majd mindegyik Click eseményéhez rendeljünk hozzá egy közös metódust. A címkék Tag tulajdonságát a pozíciójuktól függően állítsuk be úgy, hogy a koordinátákat tartalmazzák vesszővel elválasztva (0,0 – 2,2) Feladat Hozzunk létre egy webes referenciát a saját szolgáltatásunkhoz, hozzuk létre
a szükséges tagváltozókat, és a konstruktorban inicializáljuk azokat: private GameService service; private char[][] t; service = new GameService(); t = new char[ 3 ][]; t[ 0 ] = new char[ 3 ]; t[ 1 ] = new char[ 3 ]; t[ 2 ] = new char[ 3 ]; Implementáljuk a közös eseménykezelőt, hogy megjelenítse a lépést, majd a Web Szolgáltatás segítségével a másik fél lépését is elvégezze: Label label = ( Label ) sender; - 39 - string[] s = label.TagToString()Split( new char[] { , } ); int i = Convert.ToInt32( s[ 0 ] ); int j = Convert.ToInt32( s[ 1 ] ); if ( ( t[ i ][ j ] == X ) || ( t[ i ][ j ] == O ) ) return; t[ i ][ j ] = X; label.Text = "X"; label.ForeColor = ColorGreen; string res = service.Play( ref t ); foreach ( Control control in this.Controls ) if ( ( control is Label ) && ( ( string ) control.Tag == res ) ) { control.ForeColor = ColorRed; control.Text = "O"; } Feladat Helyezzünk el egy breakpointot a webes metódus
meghívásánál, és debug-oljunk bele a web szolgáltatásba. Feladat Állítsuk át a webes referencia „URL” behaviour tulajdonságát dinamikusra, majd nézzük meg, hogy az URL ténylegesen megjelent a konfigurációs állományban (app.config) Módosítsuk az URL-t úgy, hogy az alkalmazás a 8080-as portra csatlakozzon. Indítsuk el a Trace programot, és segítségével jelenítsük meg az XML üzeneteket, melyek részt vesznek a kommunikációban. X. mérés – Webalkalmazások A mérés során a hallgatók az ASP.NET webalkalmazások készítésével ismerkednek meg a .NET keretrendszerben Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! A mérés során számos ASP.NET weboldal elkészítésére kerül sor Minden alkalommal érdemes az ASPX fájlok és a generált HTML oldalak kódját összehasonlítani. Nagyon fontos megérteni, hogy bár kliens oldalon,
böngészőben jelenik meg az alkalmazás felhasználói felülete, a fejlesztő által írt .NET kód csak kiszolgáló oldalon fut! Egyszerű ASP.NET oldal készítése Az első feladatban a mérésvezető bemutatja, hogyan lehet egy HTML oldalból előbb egyszerűbb, majd összetettebb ASP.NET weblapot készíteni Készítse el a következő fájlokat a C:InetpubwwwrootAspNetDemo mappában Notepad segítségével: - 40 - MyPage.aspx: <%@ Page Language="C#" Inherits="ASPDemo.MyPageClass" %> <html> <body> <form runat="server"> Írd be a neved: <asp:textbox runat="server" id="txtName" /> <asp:button runat="server" id="btnGreet" text="Mehet" onclick="btnGreet Click" /> <asp:label runat="server" id="lblAnswer" /> <hr> © 2004. Pontos idő: <%= DateTimeNowToString() %> </form> </body> </html>
MyPage.aspxcs: using System; using System.WebUIWebControls; namespace ASPDemo { public class MyPageClass : System.WebUIPage { protected TextBox txtName; protected Button btnGreet; protected Label lblAnswer; public MyPageClass() { txtName = new TextBox(); btnGreet = new Button(); lblAnswer = new Label(); btnGreet.Click += new EventHandler( btnGreet Click ); } protected void btnGreet Click( object sender, EventArgs e ) { lblAnswer.Text = "Hello " + txtNameText; } - 41 - } } Hozzon létre egy „bin” nevű alkönyvtárat és fordítsa le a forráskódot: csc /t:library /r:system.webdll /out:binaspnetdemodll "mypageaspxcs" Az IIS Manager MMC Snap-in segítségével az AspNetDemo mappára hozzon létre egy alkalmazást, majd tesztelje az oldalt böngészőben! Eseménykezelés weblapokon A feladatban egy egyszerű webes festékáruházat fogunk létrehozni, ahol festék színeket lehet kiválasztani. Alakítsa ki a felhasználói felületet: • Hozzon létre
egy StoreEnter.aspx oldalt, majd helyezzen fel rá két ListBox kontrollt. Ezek fogják tartalmazni a lehetséges (lbAllColors) és a kiválasztott (lbSelectedColors) színeket. • A két lista kontroll közé helyezzen fel két Button vezérlőt, melyekkel kiválasztani lehet színeket (btnSelect és btnUnselect). • Helyezzen fel egy Label kontrollt (lblMessage), amivel a kiválasztás tényét fogjuk nyugtázni. • Helyezzen fel egy Button kontrollt (btnFinish), amivel a felhasználó befejezheti a válogatást. Figyelje meg a designer által generált ASPX kódot és a böngészőben megjelenő HTML kódot! Valósítsa meg a következő eseménykezelőket: • Az oldal betöltésekor (Page Load) töltse fel az lbAllColors vezérlőt elemekkel, de csak az oldal első betöltésekor. • Kezelje le a btnSelect és btnUnselect gombok Click eseményét úgy, hogy ha van kiválasztott elem, akkor azt töltsék át a másik listába. Az áttöltésről tájékoztassák a
felhasználót a lblMessage vezérlőbe írt szöveggel. • A btnFinish megnyomására mentse el a kiválasztott színeket a Session változóba és irányítsa át a felhasználót a StoreExit.aspx oldalra this.Session[ "SelectedColors" ] = thislbSelectedColorsItems; this.ServerTransfer( "StoreExitaspx" ); Hozza létre a StoreExit.aspx oldalt, és a felületre helyezzen fel egy Literal vezérlőt Az oldal betöltésekor jelenítse meg a felhasználó által az előző oldalon kiválasztott színeket a Sessionből a Literal kontroll segítségével: ListItemCollection colors = this.Session[ "SelectedColors" ] as ListItemCollection; if( colors != null ) { foreach( ListItem color in colors ) - 42 - { } } this.litColorsText += colorText + "<BR>"; Tesztelje az oldalak működését, figyelje meg, hogy az ASP.NET motor milyen HTML kódot generál az egyes vezérlőkből. Gondolja végig, hogy az ASPNET hogyan fedi el az állapotmentes
HTTP protokollt a fejlesztő elől. Fájlok kezelése webalkalmazásban Ebben a feladatban egy olyan weboldal készítése a cél, amelyre a felhasználó feltölthet egy JPEG képfájlt és megadhat egy szöveget, amit az alkalmazás vízjelként ráír a képre, majd az így módosított képet megjeleníti böngészőben. Hozzon létre egy új ASPX oldalt és alakítsa ki a felhasználói felületet: • A HTML vezérlők közül dobjon fel egy File Field kontrollt (hifImage). Állítsa be a runat attribútumát server-re: <INPUT id="hifImage" type="file" runat="server"> • Helyezzen fel egy TextBox kontrollt, ahol a felirat szövegét adhatja meg a felhasználó (txtMessage). • Tegyen fel egy gombot, aminek eseménykezelőjében a kép módosítását fogjuk elvégezni. • Tegyen fel egy Image kontrollt, ami majd az eredményt jeleníti meg (imgResult). A code behind fájlban hozza létre a fájl feltöltő kontrollhoz tartozó mezőt:
protected System.WebUIHtmlControlsHtmlInputFile hifImage; A gomb eseménykezelőjében módosítsa a képfájlt GDI+ segítségével: if( this.hifImagePostedFile != null ) { // Vigyázat: Két névtérben is van Image osztály! System.DrawingImage img = new Bitmap( this.hifImagePostedFileInputStream ); Graphics g = Graphics.FromImage( img ); Font f = new Font( "Arial", 60 ); Brush b = new SolidBrush( Color.FromArgb( 100, ColorWhite ) ); g.DrawString( thistxtMessageText, f, b, 10, 100 ); string folder = this.MapPath( "~/Images" ); string fileName = Guid.NewGuid()ToString() + "jpg"; string fullPath = Path.Combine( folder, fileName ); - 43 - } // Vigyázat: Létre kell hozni az IMAGES könyvtárat és // az ASPNET felhasználónak Modify jogot kell rá adni img.Save( fullPath, ImageFormatJpeg ); this.imgResultImageUrl = "~/Images/" + fileName; this.imgResultVisible = true; Ahhoz, hogy az alkalmazás el tudja menteni a módosított képet,
létre kell hozni az Images almappát és az ASPNET felhasználónak Modify jogot kell rá adni NTFS szinten. Validátorok (ha marad idő) Validátor vezérlők segítségével ellenőrizze, hogy a felhasználó érvényes adatokkal küldte-e vissza az oldalt a szerverre. XI. mérés – Adatkötés webalkalmazásokban A mérés során a hallgatók az ASP.NET webalkalmazások készítésével ismerkednek meg a .NET keretrendszerben Mivel a mérés igen sokrétű, ezért fontos, hogy valamilyen formában (papíros vagy elektronikus) a mérésen a hallgatók rendelkezzenek a segédlettel! Nagyon fontos megérteni, hogy bár kliens oldalon, böngészőben jelenik meg az alkalmazás felhasználói felülete, a fejlesztő által írt .NET kód csak kiszolgáló oldalon fut! Minden feladat esetén gondolja végig, hogy az eseménykezelők milyen sorrendben futnak le illetve, hogy az egyes lépések esetén milyen adatok állnak már rendelkezésre és azok honnan származnak (viewstate,
session, adatbázis)! Adatok megjelenítése adatbázisból Ebben a feladatban a Northwind adatbázis Products táblájának adatait kívánjuk egy weblapon megjeleníteni. A felhasználó listázhatja az összes rekordot, vagy megadhat szűrési feltételt. A felhasználói felület kialakítása Hozzon létre egy új ASP.NET alkalmazást és alakítsa ki egy webes űrlap felhasználói felületét a következő módon: • Tegyen fel két rádió gombot, amelyekkel a felhasználó kiválaszthatja, hogy minden rekordot meg akar-e jeleníteni, vagy keresni akar. Használja a GroupName tulajdonságot a két rádió gomb összekapcsolásához. • Tegyen fel egy szöveg dobozt, amiben a felhasználó megadhatja, hogy milyen termék nevekre akar keresni. • Tegyen fel egy gombot, amivel a felhasználó a megjelenítést kezdeményezheti. • Tegyen fel egy DataGrid kontrollt (dgProducts), ami az eredményeket fogja megjeleníteni. - 44 - Az adatkötés konfigurálása • A Server
Explorer ablakban keresse meg az SQL Serverben található Northwind adatbázis Products tábláját és „dobja rá” a webform tervező felületére. Konfigurálja úgy a kapott sqlConnection1 objektumot, hogy a ConnectionString tulajdonságot a konfigurációs állományból olvassa fel. Ellenőrizze a beállítást a web.config állományban • Az sqlDataAdapter1 segítségével generáljon egy ProductsDS nevű típusos DataSet objektumot. Vizsgálja meg a kapott ProductsDSxsd állományt és hasonlítsa össze a tábla szerkezetével. • Kösse a dizájner által generált productsDS1 változót az adatrácshoz a következő módon: o DataSource: productsDS1 o DataMember: Products o DataKeyField: ProductID A DataGrid konfigurálása Használja a DataGrid Property Builder segédablakát az alábbiak beállításához: • Kapcsolja ki az oszlopok automatikus megjelenítését, helyette csak a ProductName és UnitPrice oszlopokat jelenítse meg. • Állítsa be az oszlopok
fejlécének szövegét. • Használja a {0:c} kifejezést a UnitPrice oszlop pénznem formában történő megjelenítéséhez. A helyes magyar megjelenítéshez módosítsa a webconfig állományt is: <globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="hu-hu" /> Az adatok megjelenítése A gomb eseménykezelőjében jelenítse meg az adatokat: if( this.radFilteredChecked ) { this.adapterSelectCommandCommandText = "SELECT * FROM Products WHERE ProductName LIKE @ProductName"; this.adapterSelectCommandParametersAdd( "@ProductName", String.Format( "%{0}%", thistxtProductNameText ) ); } this.adapterFill( thisdsProducts ); this.dgProductsDataBind(); Egyetlen rekord kiválasztása Ebben a feladatban az a cél, hogy a felhasználó kiválaszthasson egy terméket a listából és megjeleníthesse annak részletes adatait. - 45 - • Tegyen fel az oldal aljára egy Label kontrollt, ami az adatokat
meg fogja jeleníteni. • Tárolja el a gomb eseménykezelőjének végén a DataSet tartalmát Session változóban: this.Session[ "ds" ] = thisdsProducts; • A Page Load eseménynél olvassa vissza a Session tartalmát: ProductsDS ds = this.Session[ "ds" ] as ProductsDS; if( this.IsPostBack && ds != null ) this.dsProducts = ds; Jelenítse meg a kiválasztott termék QuantityPerUnit mezőjének értékét a DataGrid SelectedIndexChanged eseménykezelőjében: int id = (int) this.dgProductsDataKeys[ thisdgProductsSelectedIndex ]; ProductsDS.ProductsRow product = (ProductsDSProductsRow) this.dsProductsProductsSelect( thisdgProductsDataKeyField + "=" + id.ToString() )[ 0 ]; this.lblQuantityText = StringFormat( "Csomagolás: {0} ", product.QuantityPerUnit ); • • A helyes működéshez a keresés előtt törölni kell a DataSet tartalmát: this.dsProductsClear(); • A villódzás elkerülése érdekében kapcsolja be az oldal
SmartNavigation tulajdonságát. Rekordok rendezése Tegyük lehetővé a felhasználó számára, hogy az oszlopok fejlécére kattintva rendezhesse a táblázat tartalmát. • Kapcsolja be a DataGrid AllowSorting tulajdonságát. • A SortCommand eseménykezelőben változtassa meg a rendezést: this.dsProductsProductsDefaultViewSort = eSortExpression + " ASC"; this.dgProductsDataBind(); • DataSet helyett DataView-t kell kötnünk, ezért törölje a DataSet hozzárendelését, és a Page Load eseménykezelőben kösse a DataView-t a DataGridhez: this.dgProductsDataSource = thisdsProductsProductsDefaultView; - 46 - Táblázat lapozása A hálózati forgalom csökkentése érdekében korlátozzuk az egyszerre megjelenítendő rekordok számát és biztosítsuk a lapozás lehetőségét a felhasználónak. • Konfigurálja a lapozással kapcsolatos beállításokat a DataGrid Property Builder ablakának Paging fülén. • A SelectedIndexChanged eseménykezelőben
állítsa be az oldal számát: this.dgProductsCurrentPageIndex = eNewPageIndex; this.dgProductsDataBind(); • Keresés esetén állítsa vissza a DataGridet az első oldalra: this.dgProductsCurrentPageIndex = 0; Adatok módosítása Tegyük a termék nevét módosíthatóvá! • A DataGrid Property Builder ablakának segítségével kapcsolja be az ár oszlopra a Read only opciót. • Helyezzen fel egy Edit, Update, Cancel típusú oszlopot és állítsa be a tulajdonságait. • Ha a felhasználó a Szerkesztés gombra kattint (EditCommand), kapcsolja át a DataGridet szerkesztő módba: this.dgProductsEditItemIndex = eItemItemIndex; this.dgProductsDataBind(); • Ha a felhasználó ezután a Mégsem gombra kattint (CancelCommand), kapcsolja vissza a DataGridet megjelenítési módba: this.dgProductsEditItemIndex = -1; this.dgProductsDataBind(); A Mentés gombra történő kattintásra (UpdateCommand) mentse el a változtatásokat az adatbázisba: int id = (int)
this.dgProductsDataKeys[ eItemItemIndex ]; ProductsDS.ProductsRow product = (ProductsDSProductsRow) this.dsProductsProductsSelect( thisdgProductsDataKeyField + "=" + id.ToString() )[ 0 ]; TextBox txtProductName = (TextBox) e.ItemCells[ 0 ]Controls[ 0 ]; product.ProductName = txtProductNameText; this.adapterUpdate( thisdsProducts ); this.dgProductsEditItemIndex = -1; this.dgProductsDataBind(); • - 47 -