Beschouw bijvoorbeeld de twee onderstaande klassen, waarvan de attributen en eigenschappen geannoteerd zijn met bijkomende XML-annotaties: using System ; using System . Xml ; using System . Xml . S e r i a l i z a t i o n ; [ XmlRoot ( ” b i e r t j e ” ) ] public c l a s s B i e r { [ XmlElement ( ”naam” ) ] public string Naam ; [ XmlElement ( ” p e r c e n t ” ) ] public double A l c o h o l P e r c ; [ XmlElement ( ” brouwer ” ) ] public B r o u w e r i j Brouwer ; [ XmlAttribute ( ” s o o r t ” ) ] public string type { get { i f ( A l c o h o l P e r c <= 5 . 0 ) return ” p i n t j e ” ; e l s e return ” s t r a f ” ; } } public B i e r ( ) { } public B i e r ( string N, double P , B r o u w e r i j B) { Naam = N; AlcoholPerc = P; Brouwer = B ; } } public c l a s s B r o u w e r i j { [ XmlAttribute ( ” brouwer−naam” ) ] public string Naam ; [ XmlElement ( ” brouwer−l o c ” ) ] public string L o c a t i e ;
1
public B r o u w e r i j ( ) { } public B r o u w e r i j ( string N, string L) { Locatie = L; Naam = N; } } De volgende Main methode zal dan twee Bieren aanmaken, en deze omzetten naar XML: public s t a t i c void Main ( ) { B r o u w e r i j i n b e v = new B r o u w e r i j ( ”AB InBev ” , ” Leuven ” ) ; B i e r s t e l l a = new B i e r ( ” S t e l l a ” , 3 . 0 , i n b e v ) ; B i e r l e f f e = new B i e r ( ” L e f f e ” , 6 . 0 , i n b e v ) ; X m l S e r i a l i z e r s = new X m l S e r i a l i z e r ( typeof ( B i e r ) ) ; s . S e r i a l i z e ( System . C o n s o l e . Out , s t e l l a ) ; C o n s o l e . WriteLine ( ) ; s . S e r i a l i z e ( System . C o n s o l e . Out , l e f f e ) ; } De geproduceerde uitvoer bestaat dan uit volgende twee XML-bestandjes:
Stella 3 Leuven Leffe 6 Leuven
2.1
Het publish-subscribe patroon dmv. events
Het publish-subscribe of listener patroon is ´e´en van de meest gebruikte ontwerppatronen. De kern van dit patroon is dat objecten die ge¨ınteresseerd zijn in 2
de toestand van een ander object, zich bij dit andere object kunnen registeren; als dit andere object dan van toestand verandert, zal het al zijn geregistreerde “luisteraars” verwittigen. C#ondersteunt dit patroon dmv. een combinatie van events en delegates. Om hiervan gebruik te maken, moet je eerst een delegate declareren, die bepaald wat de signatuur van de call-back functie zal zijn, waarmee de luisteraar van verandering op de hoogte wordt gebracht. public delegate void B e ha nde lV e ra nde ri ng ( object o ) ; Dit betekent dat de luisteraar-objecten deze delegate zullen moeten implementeren: public c l a s s L u i s t e r a a r { public void PrintWaarde ( object s o u r c e ) { . . . // I m p l e m e n t a t i e van c a l l −b a c k f u n c t i e } public L u i s t e r a a r ( . . ) { B e ha nde lV e ra nde ri ng bhv = new B e ha nde lV e ra nde ri ng ( PrintWaarde ) ; ... } } Het publisher object, dwz. het object waarnaar geluisterd wordt, kan dan gebruik maken van een datatype “event BehandelVerandering”. Dit datatype stelt een gebeurtenis voor, waarnaar de BehandelVerandering delegates kunnen luisteren. public c l a s s I n t e r e s s a n t O b j e c t { public event B e ha nde lV e ra nde ri ng V e r a n d e r i n g ; ... } Aan een dergelijke gebeurtenis kunnen dan luisteraars worden toegevoegd, met behulp van de overladen operator +=. Die luisteraar moet dan natuurlijk een delegate van het juiste type zijn. In het bovenstaande voorbeeld hadden we hiervoor de functie PrintWaarde van de klasse Luisteraar. We kunnen deze dan als volgt koppelen aan het event, bijvoorbeeld in de constructor van e klasse Luisteraar: public L u i s t e r a a r ( I n t e r e s s a n t O b j e c t o b j ) { B e ha nde lV e ra nde ri ng bhv = new B e ha nde lV e ra nde ri ng ( PrintWaarde ) ; o b j . V e r a n d e r i n g += bhv ; } Een event is zelf op zijn beurt ook weer een delegate, dwz. dat het eigenlijk een functie is die je kan oproepen. Het effect hiervan zal zijn dat het event al zijn geregistreerde luisteraars verwittigt. Onderstaande code, bijvoorbeeld, zal 3
ervoor zorgen dat de luisteraars verwittigd worden telkens de waarde van de property Getal van het InteressantObject verandert. public c l a s s I n t e r e s s a n t O b j e c t { public event B e ha nde lV e ra nde ri ng V e r a n d e r i n g ; private int
getal ;
public i nt G e t a l { g e t { return g e t a l ; } set { g e t a l = value ; Verandering ( this ) ; } } ... } Heel ons voorbeeld samen genomen, genereert de volgende Main methode: public s t a t i c void Main ( ) { I n t e r e s s a n t O b j e c t i = new I n t e r e s s a n t O b j e c t ( ) ; L u i s t e r a a r l = new L u i s t e r a a r ( i ) ; i . G e t a l++; i . G e t a l++; i . G e t a l++; } nu deze uitvoer: 1 2 3
3
WinForms
De naamruimte System.Windows.Forms bevat klassen die gebruikt kunnen worden voor het maken van een grafische user interface. In de naamruimte System.Drawing zitten ook nog een aantal klassen die daarbij van pas komen: deze gaan dan bijvoorbeeld over co¨ ordinaten of afmetingen. Volgende code toont een eenvoudig schermpje met daarop een begroetende boodschap. using System ; using System . Windows . Forms ; using System . Drawing ; public c l a s s V e n s t e r t j e : Form { 4
private L abe l boodschap ; public V e n s t e r t j e ( ) { boodschap = new L abe l ( ) ; boodschap . Text = ” H a l l o daar ” ; C o n t r o l s . Add( boodschap ) ; Text = ” B e g r o e t i n g ” ; S i z e = new S i z e ( 2 0 0 , 2 0 0 ) ; } public s t a t i c void Main ( ) { A p p l i c a t i o n . Run(new V e n s t e r t j e ( ) ) ; } }
3.1
Knopjes met gedrag
Een knop wordt logisch genoeg gemaakt met behulp van de klasse Button. Om gedrag te koppelen aan een knop, wordt gebruik gemaakt van het publishsubscribe patroon, zoals ge¨ımplementeerd met delegates en events. Concreet declareert men volgende delegate: public delegate void EventHandler ( object s o u r c e , EventArgs a r g s ) ; Met andere worden, de functies die je aan een knop kan koppelen moeten dus als argumenten een object (= de knop waarop geklikt werd) en een EventArgs (= mogelijke bijkomende argumenten) nemen. Hier is een voorbeeld van een dergelijke functie: public void f o o ( object s r c , EventArgs a r g s ) { C o n s o l e . WriteLine ( ”Er werd op \”{0}\” g e k l i k t ” , s r c ) ; } De klasse Button voorziet nu volgend event, waaraan dergelijke EventHandlerdelegates gekoppeld kunnen worden: public c l a s s Button { public event EventHandler C l i c k ; ... } Met andere woorden, de koppeling van gedrag een knop gebeurt dus zo: Button knop = new Button ( ) ; knop . C l i c k += new EventHandler ( f o o ) ;
5
3.2
Andere invoer elementen
Een tekstveldje wordt gemaakt met de klasse TextBox. De tekst die de gebruiker invoert komt terecht in het attribuut Text. Een checkboxje wordt gemaakt met de klasse CheckBox. Het attribuut Checked geeft dan aan of deze checkbox al dan niet aangevinkt is. Een radiobutton wordt gemaakt met de klasse RadioButton. Radiobuttons die samenhoren, moeten natuurlijk gegroepeerd worden; dit kan bijvoorbeeld met een GroupBox. Onderstaand voorbeeld illustreert deze elementen. using using using using
System ; System . Windows . Forms ; System . Windows . Forms . Layout ; System . Drawing ;
public c l a s s V e n s t e r t j e : Form { private L abe l boodschap ; private Button knop ; private TextBox t e k s t ; private CheckBox cb ; private RadioButton rb1 ; private RadioButton rb2 ; private GroupBox gb ; public V e n s t e r t j e ( ) { Text = ” B e g r o e t i n g ” ; boodschap = new L abe l ( ) ; boodschap . Text = ” H a l l o daar ” ; boodschap . L o c a t i o n = new Po int ( 1 0 , 1 0 ) ; boodschap . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; C o n t r o l s . Add( boodschap ) ; knop = new Button ( ) ; knop . Text = ” K l i k mij ” ; knop . L o c a t i o n = new Po int ( 1 0 , 5 0 ) ; knop . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; knop . C l i c k += new EventHandler ( f o o ) ; C o n t r o l s . Add( knop ) ; t e k s t = new TextBox ( ) ; t e k s t . L o c a t i o n = new Po int ( 1 0 , 9 0 ) ; t e k s t . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; C o n t r o l s . Add( t e k s t ) ; cb = new CheckBox ( ) ; cb . Text = ” I n g e n i e u r ” ; 6
cb . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; cb . L o c a t i o n = new Po int ( 1 0 , 1 3 0 ) ; C o n t r o l s . Add( cb ) ; gb = new GroupBox ( ) ; gb . S i z e = new S i z e ( 1 2 0 , 8 0 ) ; gb . L o c a t i o n = new Po int ( 1 0 , 1 7 0 ) ; gb . Text = ” G e s l a c h t ” ; rb1 = new RadioButton ( ) ; rb1 . Text = ”man” ; rb1 . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; rb1 . L o c a t i o n = new Po int ( 1 0 , 2 0 ) ; rb2 = new RadioButton ( ) ; rb2 . Text = ” vrouw ” ; rb2 . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; rb2 . L o c a t i o n = new Po int ( 1 0 , 5 0 ) ; gb . C o n t r o l s . Add( rb1 ) ; gb . C o n t r o l s . Add( rb2 ) ; C o n t r o l s . Add( gb ) ; S i z e = new S i z e ( 2 0 0 , 4 0 0 ) ; } public s t a t i c void Main ( ) { A p p l i c a t i o n . Run(new V e n s t e r t j e ( ) ) ; } public void f o o ( object o , EventArgs e ) { C o n s o l e . Write ( ”U z e i \ ” { 0 } \ ” , ” , t e k s t . Text ) ; C o n s o l e . Write ( rb1 . Checked ? ” meneer ” : ”mevrouw” ) ; i f ( cb . Checked ) C o n s o l e . Write ( ” de i n g e n i e u r ” ) ; C o n s o l e . WriteLine ( ) ; } }
3.3
Dialoogjes
Om de gebruiker kort om wat bijkomende informatie te vragen, kunnen subklassen van System.Windows.Forms.CommonDialog gebruikt worden. In deze klasse zit er een methode ShowDialog, die dient om de dialoog effectief op het scherm te brengen. using System ; using System . Drawing ; using System . Windows . Forms ;
7
public c l a s s D i a l o o g T e s t : Form { public void ToonDialoog ( object o , EventArgs e ) { O p e n F i l e D i a l o g f i l e = new O p e n F i l e D i a l o g ( ) ; D i a l o g R e s u l t r e s u l t = f i l e . ShowDialog ( ) ; i f ( r e s u l t == D i a l o g R e s u l t .OK) { MessageBox . Show ( ”U koos voor b e s t a n d ” + f i l e . FileName ) ; } } public D i a l o o g T e s t ( ) { Button b = new Button ( ) ; b . Text = ” D i a l o o g ” ; b . C l i c k += new EventHandler ( ToonDialoog ) ; C o n t r o l s . Add( b ) ; } public s t a t i c void Main ( ) { A p p l i c a t i o n . Run(new D i a l o o g T e s t ( ) ) ; } }
3.4
Data binding
In het MVC paradigma, staat de View component in voor visualisatie van een bepaalde gegevens uit de Model component. Het is dus, met andere woorden, de bedoeling dat GUI elementen uit de View op ´e´en of andere manier gekoppeld worden aan achterliggende datastructuren uit het Model. C#voorziet hiervoor zogenaamde Data Bindings. De GUI-elementjes hebben een property DataBindings waarin gegevens kunnen worden toegevoegd. Dit gebeurt bijvoorbeeld als volgt: L abe l t e k s t = new L abe l ( ) ; Binding b = new Binding ( ” Text ” , l i j s t , ” ” ) ; t e k s t . DataBindings . Add( b ) ; Hiermee wordt de property Text van het label tekst gekoppeld aan de variabele lijst. Er zijn twee soorten van objecten die kunnen dienen als gegevensbron voor een dergelijke binding: lijsten (dwz. alles wat de interface IList implementeert) en ADO.NET databank objecten. Voorlopig kijken we naar het eerste geval en beschouwen een databron die bestaat uit een lijst van strings: private I L i s t l i j s t ; private void I n i t L i j s t ( ) { l i j s t = new S t r i n g C o l l e c t i o n ( ) ; l i j s t . Add( ”Morgen” ) ; 8
l i j s t . Add( ”Middag” ) ; l i j s t . Add( ”Avond” ) ; l i j s t . Add( ” Nacht ” ) ; } Het effect van de databinding is dat de tekst van het label genomen wordt uit de lijst van strings. Achter de schermen wordt een object, de CurrencyManager, aangemaakt dat instaat voor deze link. Initi¨eel zal de tekst van het label overeenkomen met de 1e string uit de lijst. Om ook de andere strings aan bod te laten komen, kunnen we de CurrencyManager aanspreken: deze heeft immers een property Position die aangeeft welk van de vier string getoond wordt. Het Form waarin het Label zich bevindt, heeft een property BindingContext, die verwijzingen bijhoudt naar alle CurrencyManagers. Deze BindingContext gedraagt zich als een hash-tabel, die ge¨ındexeerd kan worden met de gegevensbron (lijst in ons geval), waarvoor we de CurrencyManager willen hebben.1 Om een lang verhaal kort te maken, kunnen we dus als volgt overgaan naar de volgende string: CurrencyManager cm = ( CurrencyManager ) BindingContext [ l i j s t ] ; cm . P o s i t i o n ++; In onderstaand voorbeeld wordt deze functionaliteit gekoppeld aan een knop op de GUI: using using using using using
System ; System . Windows . Forms ; System . Drawing ; System . C o l l e c t i o n s ; System . C o l l e c t i o n s . S p e c i a l i z e d ;
public c l a s s V e n s t e r : Form { private L abe l t e k s t ; private Button knop ; private I L i s t l i j s t ; private void I n i t L i j s t ( ) { l i j s t = new S t r i n g C o l l e c t i o n ( ) ; l i j s t . Add( ”Morgen” ) ; l i j s t . Add( ”Middag” ) ; l i j s t . Add( ”Avond” ) ; l i j s t . Add( ” Nacht ” ) ; } 1 Merk op dat er ´ e´ en CurrencyManager is per gegevensbron. Als meerdere UI elementen gelinkt zijn met dezelfde gegevensbron, zullen deze dus allemaal veranderen wanneer we overgaan naar een volgende string.
9
private void I n i t U I ( ) { t e k s t = new L abe l ( ) ; knop = new Button ( ) ; knop . Text = ” Volgende ” ; t e k s t . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; knop . S i z e = new S i z e ( 1 0 0 , 3 0 ) ; t e k s t . L o c a t i o n = new Po int ( 1 0 , 1 0 ) ; knop . L o c a t i o n = new Po int ( 1 0 , 6 0 ) ; S i z e = new S i z e ( 2 0 0 , 2 0 0 ) ; C o n t r o l s . Add( t e k s t ) ; C o n t r o l s . Add( knop ) ; knop . C l i c k += new EventHandler ( Volgende ) ; } public void Volgende ( object o , EventArgs e ) { CurrencyManager cm = ( CurrencyManager ) BindingContext [ l i j s t ] ; cm . P o s i t i o n ++; } public V e n s t e r ( ) { InitLijst (); InitUI ( ) ; Binding bind = new Binding ( ” Text ” , l i j s t , ” ” ) ; t e k s t . DataBindings . Add( bind ) ; } public s t a t i c void Main ( ) { A p p l i c a t i o n . Run(new V e n s t e r ( ) ) ; } }
10