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 class Bier { [ XmlElement ( naam ) ] public string Naam ; [ XmlElement ( percent ) ] public double AlcoholPerc ; [ XmlElement ( brouwer ) ] public Brouwerij Brouwer ; [ XmlAttribute ( s o o r t ) ] public string type { get { i f ( AlcoholPerc <= 5. 0 ) return p i n t j e ; else return s t r a f ; public Bier ( ) { public Bier ( string N, double P, Brouwerij B) { Naam = N; AlcoholPerc = P; Brouwer = B; public class Brouwerij { [ XmlAttribute ( brouwer naam ) ] public string Naam ; [ XmlElement ( brouwer l o c ) ] public string L o c a t i e ; 1
public Brouwerij ( ) { public Brouwerij ( string N, string L) { L o c a t i e = L ; Naam = N; De volgende Main methode zal dan twee Bieren aanmaken, en deze omzetten naar XML: Brouwerij inbev = new Brouwerij ( AB InBev, Leuven ) ; Bier s t e l l a = new Bier ( S t e l l a, 3. 0, inbev ) ; Bier l e f f e = new Bier ( L e f f e, 6. 0, inbev ) ; 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 ( Bier ) ) ; s. S e r i a l i z e ( System. Console. Out, s t e l l a ) ; Console. WriteLine ( ) ; s. S e r i a l i z e ( System. Console. Out, l e f f e ) ; De geproduceerde uitvoer bestaat dan uit volgende twee XML-bestandjes: <?xml version="1.0" encoding="utf-8"?> <biertje xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"> <naam>stella</naam> <percent>3</percent> <brouwer brouwer-naam="ab InBev"> <brouwer-loc>leuven</brouwer-loc> </brouwer> </biertje> <?xml version="1.0" encoding="utf-8"?> <biertje xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"> <naam>leffe</naam> <percent>6</percent> <brouwer brouwer-naam="ab InBev"> <brouwer-loc>leuven</brouwer-loc> </brouwer> </biertje> 2.1 Het publish-subscribe patroon dmv. events Het publish-subscribe of listener patroon is één 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 BehandelVerandering ( object o ) ; Dit betekent dat de luisteraar-objecten deze delegate zullen moeten implementeren: public class L u i s t e r a a r { public void PrintWaarde ( object source ) {... // Implementatie van c a l l back f u n c t i e public L u i s t e r a a r (.. ) { BehandelVerandering bhv = new BehandelVerandering ( 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 class I n t e r e s s a n t O b j e c t { public event BehandelVerandering Verandering ;... 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 obj ) { BehandelVerandering bhv = new BehandelVerandering ( PrintWaarde ) ; obj. Verandering += 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 class I n t e r e s s a n t O b j e c t { public event BehandelVerandering Verandering ; private int g e t a l ; public int Ge ta l { get { return g e t a l ; s e t { g e t a l = value ; Verandering ( this ) ;... Heel ons voorbeeld samen genomen, genereert de volgende Main methode: 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. Getal++; i. Getal++; i. Getal++; 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ördinaten of afmetingen. Volgende code toont een eenvoudig schermpje met daarop een begroetende boodschap. using System ; using System. Windows. Forms ; using System. Drawing ; public class V e n s t e r t j e : Form { 4
private Label boodschap ; public V e n s t e r t j e ( ) { boodschap = new Label ( ) ; boodschap. Text = Hallo daar ; Controls. Add( boodschap ) ; Text = Begroeting ; S i z e = new S i z e ( 2 0 0, 2 0 0 ) ; 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 source, EventArgs args ) ; 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 foo ( object src, EventArgs args ) { Console. 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 class Button { public event EventHandler C lick ;... Met andere woorden, de koppeling van gedrag een knop gebeurt dus zo: Button knop = new Button ( ) ; knop. C l i c k += new EventHandler ( foo ) ; 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 System ; using System. Windows. Forms ; using System. Windows. Forms. Layout ; using System. Drawing ; public class V e n s t e r t j e : Form { private Label 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 = Begroeting ; boodschap = new Label ( ) ; boodschap. Text = Hallo daar ; boodschap. Location = new Point ( 1 0, 1 0 ) ; boodschap. S i z e = new S i z e ( 1 0 0, 3 0 ) ; Controls. Add( boodschap ) ; knop = new Button ( ) ; knop. Text = Klik mij ; knop. Location = new Point ( 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 ( foo ) ; Controls. Add( knop ) ; t e k s t = new TextBox ( ) ; t e k s t. Location = new Point ( 1 0, 9 0 ) ; t e k s t. S i z e = new S i z e ( 1 0 0, 3 0 ) ; Controls. 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. Location = new Point ( 1 0, 1 3 0 ) ; Controls. Add( cb ) ; gb = new GroupBox ( ) ; gb. S i z e = new S i z e ( 1 2 0, 8 0 ) ; gb. Location = new Point ( 1 0, 1 7 0 ) ; gb. Text = Geslacht ; rb1 = new RadioButton ( ) ; rb1. Text = man ; rb1. S i z e = new S i z e ( 1 0 0, 3 0 ) ; rb1. Location = new Point ( 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. Location = new Point ( 1 0, 5 0 ) ; gb. Controls. Add( rb1 ) ; gb. Controls. Add( rb2 ) ; Controls. Add( gb ) ; S i z e = new S i z e ( 2 0 0, 4 0 0 ) ; 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 foo ( object o, EventArgs e ) { Console. Write ( U z e i \ {0\,, t e k s t. Text ) ; Console. Write ( rb1. Checked? meneer : mevrouw ) ; i f ( cb. Checked ) Console. Write ( de i n g e n i e u r ) ; Console. 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 class DialoogTest : Form { public void ToonDialoog ( object o, EventArgs e ) { OpenFileDialog f i l e = new OpenFileDialog ( ) ; DialogResult r e s u l t = f i l e. ShowDialog ( ) ; i f ( r e s u l t == DialogResult.OK) { MessageBox. Show( U koos voor bestand + f i l e. FileName ) ; public DialoogTest ( ) { Button b = new Button ( ) ; b. Text = Dialoog ; b. C l i c k += new EventHandler ( ToonDialoog ) ; Controls. Add( b ) ; A p p l i c a t i o n. Run(new DialoogTest ( ) ) ; 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 één 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: Label t e k s t = new Label ( ) ; 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ëel zal de tekst van het label overeenkomen met de 1 e 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 System ; using System. Windows. Forms ; using System. Drawing ; using System. C o l l e c t i o n s ; using System. C o l l e c t i o n s. S p e c i a l i z e d ; public class Venster : Form { private Label 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 één 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 Label ( ) ; 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. Location = new Point ( 1 0, 1 0 ) ; knop. Location = new Point ( 1 0, 6 0 ) ; S i z e = new S i z e ( 2 0 0, 2 0 0 ) ; Controls. Add( t e k s t ) ; Controls. 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 Venster ( ) { I n i t L i j s t ( ) ; I n i t U I ( ) ; Binding bind = new Binding ( Text, l i j s t, ) ; t e k s t. DataBindings. Add( bind ) ; A p p l i c a t i o n. Run(new Venster ( ) ) ; 10