Skip to main content

CONCEPT_c-sharp-Geheel-2025

Page 1

C

O

N

C

EP

T

Leren programmeren in C


C EP

C

N

O T


iii

Michiel Rotteveel

C

O

N

C

EP

T

Leren programmeren in C

Boom Mbo · Meppel 2025


T EP

Boom Mbo klantenservice@boom.nl www.boom.nl

N

C

Auteur: M. Rotteveel Redactie: J.G. Neurink / M.H. van Haren Opmaak: Henk Pel Titel: Leren programmeren in C Omslagfoto: Boy Surminski Photography isbn 978 90 3726 9192 Eerste druk / eerste oplage © Boom 2025

C

O

Behoudens de in of krachtens de Auteurswet gestelde uitzonderingen mag niets uit deze uitgave worden verveelvoudigd, opgeslagen in een geautomatiseerd gegevensbestand, of openbaar gemaakt, in enige vorm of op enige wijze, hetzij elektronisch, mechanisch, door fotokopieën, opnamen of enige andere manier, zonder voorafgaande schriftelijke toestemming van de uitgever. Auteursrecht ten aanzien van tekst- en datamining en machinelearning is nadrukkelijk voorbehouden. Voor zover het maken van reprografische verveelvoudigingen uit deze uitgave is toegestaan op grond van artikel 16h Auteurswet dient men de daarvoor wettelijk verschuldigde vergoedingen te voldoen aan de Stichting Reprorecht (www.reprorecht.nl). Voor het overnemen van (een) gedeelte(n) uit deze uitgave in compilatiewerken op grond van artikel 16 Auteurswet kan men zich wenden tot de Stichting PRO (www.stichting-pro.nl). De uitgever heeft ernaar gestreefd de auteursrechten te regelen volgens de wettelijke bepalingen. Degenen die desondanks menen zekere rechten te kunnen doen gelden, kunnen zich alsnog tot de uitgever wenden. Door het gebruik van deze uitgave verklaart u kennis te hebben genomen van en akkoord te gaan met de specifieke productvoorwaarden en algemene voorwaarden van Boom, te vinden op www.boom.nl.


v

Inhoudsopgave

Deel 1: Introductie Leeswijzer 2 Inleiding Deel 1: Introductie 3 Programmeren in C 3

C

EP

T

Hoofdstuk 1 De programmeeromgeving (IDE) 5 Wat is een IDE? 5 Opstarten 5 Naam geven 8 Spelregels naamgeving 9 De IDE 10 Je eerste programma 11 De Code Editor 12 Opdracht IDE 1: Disclaimer 14 Wat weet je nu? 14

C

O

N

Hoofdstuk 2 Rekenen 15 Rekenen 15 Opdracht Rekenen 1: Optellen 15 Opdracht Rekenen 2: Meerdere getallen optellen 15 Opdracht Rekenen 3: Aftrekken 16 Opdracht Rekenen 4: Gebroken getallen aftrekken 16 Opdracht Rekenen 5: Meerdere getallen aftrekken 17 Opdracht Rekenen 6: Meer getallen vermenigvuldigen 17 Opdracht Rekenen 7: Negatieve getallen 18 Opdracht Rekenen 8: Delen 18 Opdracht Rekenen 9: Modulo 18 Bibliotheek 19 Haakjes en volgorde 19 Opdracht Rekenen 10: Machtsverheffen 19 Wat moet je nu kunnen en kennen? 19 Hoofdstuk 3 Variabelen 21 Wat is een variabele? 21 De tafel van X 21 Hoe ‘denkt’ een computer? 21 Werking van een variabele 21 Opdracht Variabelen 1: Vraag? 25


vi

N

C

EP

T

Gebruikersinput 25 Opdracht Variabelen 2: Verklaring 26 Typen variabelen 26 Teksten 27 Opdracht Variabelen 3: Variabele verklaring 29 Getallen 29 Omzetten van variabelen 32 Logische fout 42 Syntactische fout 42 Opdracht Variabelen 4: Fahrenheit naar Celsius 42 Logische variabele 42 DatumTijd-variabele 43 Bereik van variabelen 45 Opdrachten Variabelen 46 Opdracht Variabelen 5: Bereken oppervlak 46 Opdracht Variabelen 6: Bereken omtrek 46 Opdracht Variabelen 7: Hoelang nog? 47 Opdracht Variabelen 8: Euro naar ponden 1 47 Opdracht Variabelen 9: Euro naar ponden 2 47 Opdracht Variabelen 10: Verbruik 47 Opdracht Variabelen 11: Vermogen elektrische apparaten berekenen * 47 Opdracht Variabelen 12: Verbruik elektrische apparaten berekenen * 47 Opdracht Variabelen 13: Golfsnelheid * 47 Opdracht Variabelen 14: Het gulden getal uitrekenen * 48 Wat moet je nu kunnen en kennen? 48

C

O

Hoofdstuk 4 Selectie 50 Wat is selectie? 50 Hoe maak je een selectie: if 50 Opdracht Selectie 1: Leeftijdchecker 1 52 Hoe maak je een selectie: if ... else 52 Opdracht Selectie 2: Leeftijdchecker 2 53 Opdracht Selectie 3: Vriezen of dooien 53 Nesten 53 Opdracht Selectie 4: Nesten 57 Opdracht Selectie 5: AddYears 58 Opdracht Selectie 6: Aanvragen vergunning 58 Opdracht selectie 7: Reisbeleid 60 Opdracht Selectie 8: Motorrijbewijs * 60 Opdracht Selectie 9: Soort motor * 61 If ... else 61 Opdracht Selectie 10: Maandnamen met if... else 62 Switch 63 Opdracht Selectie 11: Maandnamen met switch 64 Wat moet je nu kunnen en kennen? 64


Inhoudsopgave

vii

EP

T

Hoofdstuk 5 Herhaling 65 Wat is herhaling? 65 Het gebruik van een loop (herhaling) 68 Opdracht Herhaling 1: Rij getallen zonder loop 70 Opdracht Herhaling 2: Rij getallen met loop 70 Herhaling met behulp van een variabele 70 Opdracht Herhaling 3: Rij getallen met variabele loop 71 Opdracht Herhaling 4: De tafel van ... 72 Opdracht Herhaling 5: Meerdere tafels 72 Opdracht Herhaling 6: Machten 1 72 Opdracht Herhaling 7: Machten 2 * 73 Opdracht Herhaling 8: Faculteit uitrekenen * 73 Opdracht Herhaling 9: De rij van Fibonacci * 73 Meer loops 74 do ... while 74 Opdracht Herhaling 10: Do...While loop 74 for 74 Opdracht Herhaling 11: For ... 75 foreach 75 Wat moet je nu kunnen en kennen? 75

C

O

N

C

Hoofdstuk 6 Arrays 76 Wat is een Array? 76 Hoe maak je een Array? 76 Opdracht Array 1: Hele getallen 77 Opdracht Array 2: Decimale getallen 77 Opdracht Array 3: Teksten 77 Toegang tot een element van de Array 78 Opdracht Array 4: Hele getallen 78 Opdracht Array 5: Teksten 78 Opdracht Array 6: Teksten en nummers 78 Bewerkingen met Arrays 79 Opdracht Array 7: Een element van een Array bewerken 79 Opdracht Array 8: Letters tellen* 79 Opdracht Array 9: De elementen van twee Arrays combineren 79 Herhaling en Arrays 79 Een Array sorteren 80 Opdracht Array 10: Getallen sorteren 81 Opdracht Array 11: Teksten sorteren 81 Opdracht Array 12: Bewerken 81 Problemen met Arrays 81 Opdracht Array 13: Resize 81 Opdracht Array 14: Te groot 81 Wat moet je nu kunnen en kennen? 82


viii

Hoofdstuk 7 Forms & Controls 83 Een Windows-programma 83 Properties (Eigenschappen) 86 De Code Editor 87 Controls 89 Opdrachten Controls 91 Opdracht Controls 1: Voorbeeld controls 91 Opdracht Controls 2: Temperatuur 91 Opdracht Controls 3: Aanvraag vergunning 91 Opdracht Controls 4: Rekenmachine 92 Opdracht Controls 5: Datum – tijd * 92 Wat moet je nu kunnen en kennen? 93

EP

T

Hoofdstuk 8 Foutafhandeling 94 Wat is foutafhandeling? 94 Foutafhandeling met de MessageBox 94 Opdracht Foutmelding 1: Messagebox 98 Foutafhandeling met de ErrorProvider 98 Opdracht Foutmelding 2: ErrorProvider 100 Wat moet je nu kunnen en kennen? 100

O

N

C

Hoofdstuk 9 Methoden 101 Wat is een methode? 101 Hoe maak je een methode? 102 Opdracht Methode 1: Welkomstboodschap 103 Voorbereidende opdracht Methode 2 en 3 104 Opdracht Methode 2: Oppervlakte 104 Opdracht Methode 3: Omtrek 104 Opdracht Methode 4: Euro naar ponden 104 Opdracht Methode 5: Het gulden getal uitrekenen * 105 Wat moet je nu kunnen en kennen? 105

C

Hoofdstuk 10 Events 106 Wat is een event? 106 Het gebruik van events 107 Toepassing 111 Opdracht Event 1: Geheimschrijver 111 Opdracht Event 2: Kleurenwisselaar * 111 Wat moet je nu kunnen en kennen? 112 Hoofdstuk 11 Tekenen 113 Waarom tekenen? 113 Eenvoudig tekenen 113 Opdracht Tekenen 1: Verticale tralies 114 Opdracht Tekenen 2: Horizontale tralies 114 Opdracht Tekenen 3: Raster 114


Inhoudsopgave

ix

T

Interactief tekenen met invoervelden 115 Opdracht Tekenen 4: Variabel raster 115 Opdracht Tekenen 5: Scrollbar toevoegen 116 Interactief tekenen met events 117 Opdracht Tekenen 6: Een halve lijn 117 Opdracht Tekenen 7: De hele lijn 117 Andere objecten tekenen 118 Opdracht Tekenen 8: Rechthoek 118 Opdracht Tekenen 9: Rechthoek met diagonaal * 118 Opdracht Tekenen 10: Ellips * 119 Tekenen door meerdere punten 119 Opdracht Tekenen 11: Lijnen 120 Opdracht Tekenen 12: Golven * 121 Wat moet je nu kunnen en kennen? 121

C

O

N

C

EP

Hoofdstuk 12 Klasse (Class) 122 Wat is een class? 122 Interface 122 Encapsulation 122 Herbruikbaar 123 Gebruik van een klasse 123 Het maken van de klasse (beschrijving) 123 Vormgeven van de klasse 124 Public versus Private 124 Eigenschappen (properties) 125 Opdracht Klasse 1: Rechthoek aanmaken 126 Gebruik van een klasse 126 Constructor 128 Opdracht Klasse 2: Rechthoek berekeningen 129 Opdracht Klasse 3: Rechthoek constructor 129 Het invoerveld 129 Overloaden 130 Opdracht Klasse 4: Rechthoek overloaden 131 Het tekenen 131 Opdracht Klasse 5: Rechthoek tekenen 132 Uitbreiden van de klasse 132 Opdracht Klasse 6: Rechthoek uitbreiden 134 Wat moet je nu kunnen en kennen? 135 Hoofdstuk 13 WPF 136 Wat is WPF? 136 XAML 138 Namespace 143 WPF-controls 144


x

Opdrachten WPF-Controls 148 Opdracht WPF-Controls 1: Voorbeeld controls 148 Opdracht WPF-Controls 2: Samengestelde control 148 Opdracht WPF-Controls 3: Temperatuur 148 Opdracht WPF-Controls 4: Aanvraag vergunning 149 Opdracht WPF-Controls 5: Rekenmachine 149 Opdracht WPF-Controls 6: Mondriaan 149 Opdracht WPF-Controls 7: Samengestelde control 2 * 150 Opdracht WPF-Controls 8: Datum – tijd * 150 Wat moet je nu kunnen en kennen? 151

Deel 2: Gegevens

EP

Inleiding Deel 2: Gegevens 155

T

Nawoord Deel 1 152

C

O

N

C

Hoofdstuk 1 Interne gegevens 157 Array 157 Opdracht Array 1: Telefoonmerkenlijst 173 Value type versus Reference type 174 Opdracht Array 2: Uitgebreide telefoonmerkenlijst 193 List<T> 194 Opdracht List<T> 1: Telefoonmerkenlijst 197 List versus Array 197 Enumerable (opsommingen) 197 Opdracht Enum 1: Telefoonmerkenlijst 203 Eenvoudige objecten (Structs) 223 Opdracht Struct 1: 3D-punt 231 Opdracht Struct 2: Telefoonnummer 231 Opdracht Struct 3: Geografische coördinaten 232 Opdracht Struct 4: Geografische coördinaten aanvulling 232 Hoofdstuk 2 Bestanden (externe gegevens) 234 Tekstbestanden 234 Opdracht Tekstbestanden 1: StringArray wegschrijven 238 Opdracht Tekstbestanden 2: String Array meerdere selecties 240 Opdracht Tekstbestanden 3: Bewaren gebruikersinstellingen * 241 .csv-bestanden (Comma Separated Values) 242 Opdracht .csv-bestanden 1: Flexibele opslag 248 Opdracht .csv-bestanden 2: Klassenlijst 249 Opdracht .csv-bestanden 3: Klassenlijst inlezen 250 Opdracht .csv-bestanden 4: Klassenlijst inlezen Extra * 250


Inhoudsopgave

xi

Afbeeldingen 250 Opdracht Afbeeldingen 1: Liquid layout-form 256 Opdracht Afbeeldingen 2: Instellingen 256 Opdracht Afbeeldingen 3: Crashbestendig maken 256 Opdracht Afbeeldingen 4: Draaien en flippen 257 Opdracht Afbeeldingen 5: Kopiëren 257 Opdracht Afbeeldingen 6: Afbeeldingen en tekenen 257 Opdracht Afbeeldingen 7: Thumbnails * 258 Video 258 Opdracht Video 1: Extra knoppen en bestandsinformatie 265 Opdracht Video 2: Extra instellingen toevoegen 266 Opdracht Video 3: Meerdere bestanden selecteren en afspelen * 266

C

Nawoord Deel 2 284

EP

T

Hoofdstuk 3 Systeembestanden (externe gegevens) 267 Windows registry 267 XML-bestanden 271 Opdracht XML 1: ListBox vullen vanuit XML 279 Opdracht XML 2: Extra selectieveld 279 Opdracht XML 3: DataGridView 279 Opdracht XML 4: Weerstation * 280 Opdracht XML 5: Landenlijst 283

N

Deel 3: Databases

C

O

Inleiding Deel 3: Databases 287 C en databases 287 Benodigde voorkennis 287 Hoe werk je met dit boek? 288

Hoofdstuk 1 Introductie databases 289 Wat is een database? 289 Relationele databases 289 De databases in dit boek 291 Hoofdstuk 2 Hoe werkt het? 292 DataSets 292 Opdracht DataSet 1: Garage / tabel Auto’s 296 Opdracht DataSet 2: Landen, tabellen en werelddelen 296 Binding data aan controls 297 Opdracht Binding 1: Koppelen van controls 301 Opdracht Binding 2: Garages 301 1-op-n master-detail-binding 302


xii

T

Opdracht 1-op-n-koppeling 1: Facturen 311 Opdracht 1-op-n-koppeling 2: Wielerploeg 311 Opdracht 1-op-n-koppeling 3: Provincies 312 Look-up-tabellen in een DataGridView 302 Opdracht look-up DataTable 1: Facturen 320 Opdracht look-up DataTable 2: Auto’s 320 Value & display members 320 Opdracht Value- & DisplayMembers 1: Auto’s 323 Opdracht Value- & DisplayMembers 2: Eigenaren 323 N-op-m (n:m) koppeling tussen DataTables 323 Opdracht n:m-koppeling 1: Auto’s en eigenaren 337 Opdracht n:m-koppeling 2: Aanvullen voorbeeld 6 n:m-koppeling 338 Opdracht n:m-koppeling 3: Klassen en leerlingen * 338 Wat heb je geleerd? 338

C

EP

Hoofdstuk 3 Database versus Dataset 340 Inleiding 340 Desktop-database: Microsoft Access 340 Opdracht Access 1: Rugbyteams 356 Opdracht Access 2: Auto’s en eigenaren 356 Opdracht Access 3: Klassen en leerlingen * 356 Opdracht Access 4: Politici en partijen 357

O

N

Hoofdstuk 4 Drag & Drop 358 Inleiding 358 Opdracht Drag & Drop 1: Vakantiewoningen 363 Opdracht Drag & Drop 2: Kastelen en pachters * 363 Wat heb je geleerd? 364

C

Hoofdstuk 5 Multiformulierapplicatie 365 Eén applicatie, meerdere formulieren 365 Opdracht Multiformulier 1: Rugbyteams 372 Opdracht Multiformulier 2: Auto’s en eigenaren 372 Meerdere formulieren bij start applicatie 372 Opdracht Multiformulier 3: Login 382 Wat heb je geleerd? 382 Hoofdstuk 6 Online databases 383 MySQL 383 Opdracht MySQL 1: Adressenlijst 391 Opdracht MySQL 2: Meerdere adressen 391 Opdracht MySQL 3: Voetbaluitslagen 391 Opdracht MySQL 4: Classic Models compleet * 392 Microsoft SQL Server Express 392 Opdracht SQL Server Express 1 * 392 Wat heb je geleerd? 392 Nawoord Deel 3 394


Inhoudsopgave

xiii

Deel 4: Objectoriëntatie Inleiding Deel 4: Objectoriëntatie 397 C OO & UML 397 Benodigde voorkennis 398 Opmerkingen bij deel 4 398

T

Hoofdstuk 1 Voorbereiding 399 Objectoriëntatie 399 Kenmerken 399 UML 399 Voorbereiding voorbeeldprogramma 400 Wat heb je geleerd? 406

O

N

C

EP

Hoofdstuk 2 Klassen (Classes) 407 Beschrijving 407 Inhoud (instantie) 407 UML 407 Vuistregels 408 Soorten klassen 408 Naamgeving 409 Opdrachten Klassen 410 Opdracht 2.1: Aanmaken klasse Gras 410 Opdracht 2.2: Aanmaken klasse Venijnboom 410 Opdracht 2.3: Aanmaken klasse Vingerhoedskruid 410 Opdracht 2.4: Aanmaken klasse Koe 410 Opdracht 2.5: Aanmaken klasse Lynx 410 Opdracht 2.6: Aanmaken klasse Beer 410 Wat heb je geleerd? 410

C

Hoofdstuk 3 Encapsulation, variabelen en eigenschappen 411 Encapsulation 411 Naamgeving variabelen én eigenschappen 412 Variabelen 412 UML 413 Visual Studio klassendiagram 415 Eigenschappen (properties) 416 Opdrachten Encapsulation, variabelen en eigenschappen 420 Opdracht 3.1: Voorbereiding klasse Gras 420 Opdracht 3.2: De klasse Gras implementeren 421 Verkorte schrijfwijze eigenschappen 422 Opdracht Verkorte schrijfwijze 425 Opdracht 3.2: Verkorte schrijfwijze: de klasse Venijnboom 425 Wat heb je geleerd? 426


xiv

Hoofdstuk 4 Methoden 427 Beschrijving 427 Eisen 427 Overloaden 427 UML 428 Opdrachten Methoden 434 Opdracht 4.1: Methoden toevoegen aan de klasse Koe 434 Opdracht 4.2: Methoden toevoegen aan de klassen Gras en Venijnboom 434 Reflectie 434 Wat heb je geleerd? 434

C

EP

T

Hoofdstuk 5 Constructor 435 Beschrijving 435 Eisen constructor 435 UML 435 Opdrachten Constructor 437 Opdracht 5.1: Constructors voor Koe 437 Opdracht 5.2: Constructors voor Gras en Venijnboom 437 Opdrachten Tekenen 439 Opdracht 5.3: Tekenen van Koe 439 Opdracht 5.4: Tekenen van Gras en Venijnboom 439 Wat heb je geleerd? 440

O

N

Hoofdstuk 6 Structs 441 Beschrijving 441 Stack 441 Heap 441 Eigenschappen struct 442 UML 442 Wat heb je geleerd? 445

C

Hoofdstuk 7 Compositie 446 Beschrijving 446 Wat heb je geleerd? 450 Hoofdstuk 8 Overerving 451 Beschrijving 451 Voordelen 451 UML 451 NatSimII 453 Erven 454 Afspraken 455 Opdracht Overerving 468 Opdracht 8.1: Klasse Vingerhoedskruid 468 Wat heb je geleerd? 468


Inhoudsopgave

xv

Hoofdstuk 9 Abstracte klasse 469 Beschrijving 469 UML 469 Opdrachten Abstracte klassen 470 Opdracht 9.1: Abstract maken van de klasse Leven 470 Opdracht 9.2: Abstract maken van de klasse Plant 470 Wat heb je geleerd? 470

EP

T

Hoofdstuk 10 Oefening, uitbouwen van het project 471 Opdracht Typering object 473 Opdracht 10.1: IsDier( ) 473 Opdrachten Uitbouwen van het project 475 Opdracht 10.2: Vleeseter 475 Opdracht 10.3: Methode Eet( ) bij Vleeseter 475 Opdracht 10.4: Alleseter 475 Opdracht 10.5: Methode Eet( ) bij Alleseter 475 Opdracht Vernieuwde klasse 477 Opdracht 10.6: Nieuwe klasse Koe 477

N

C

Hoofdstuk 11 Interfaces 479 Beschrijving 479 UML 480 Opdracht Interfaces 484 Opdracht 11.1: Klok toevoegen 484 UML-klassendiagram Dier 484 Wat heb je geleerd? 485

C

O

Hoofdstuk 12 Delegates 486 Voorafje 486 Beschrijving 486 UML 486 Aanmaken delegate 487 Waarom delegates? 491 Callback-functies 491 Opdrachten Delegate 495 Opdracht 12.1: De ProgressBar 495 Opdracht 12.2: Spreektijd 495 Opdracht 12.3: Spreektijd met eindtijd waarschuwing * 495 Multithreading 495 Wat heb je geleerd? 496 Hoofdstuk 13 Events 497 Beschrijving 497 UML 498 Opdrachten Completeren formulier 507 Opdracht 13.1: Toevoegen code 507 Opdracht 13.2: Test het programma 507


xvi

T

NatSimII tot nu toe 519 Opdrachten Events 520 Opdracht 13.3: Extra informatie tonen 520 Opdracht 13.4: Nog meer informatie tonen * 520 Collision detection 520 Opdrachten Collision detection 525 Opdracht 13.5: Red de Venijnboom 525 Opdracht 13.6: Vleeseter, Jaguar 525 Opdracht 13.7: Vleeseter, Lynx 525 Opdracht 13.8: Alleseter, Beer * 526 Opdracht 13.9: Red de Venijnboom opnieuw 527 Opdracht 13.10: Conversie van Leven naar Venijnboom 527 Opdracht 13.11: Conversie van Leven naar Vingerhoedskruid 527 Wat heb je geleerd? 528

O

N

C

EP

Hoofdstuk 14 Oefening baart kunst 529 Opdrachten NatSimII 529 Opdracht 14.1: Venijnboom 529 Opdracht 14.2: Vingerhoedskruid 529 Opdracht 14.3: Zaaien 529 Opdracht 14.4: Over gras lopen * 529 Opdracht 14.5: Voortplanting 529 Opdracht 14.6: Voortplanten Planten * 530 Opdracht 14.7: Shooting game * 530 Opdracht 14.8: Snelheid wijzigen * 530 Opdracht 14.9: Plaatjes * 530 Opdracht 14.10: Geluid * 530 Opdracht 14.11: Collision detection * 530 Opdracht 14.12: Evenwicht * 531

C

Nawoord Deel 4 532

Bijlagen

ASCII-tabel deel 1: Besturingscodes 535 ASCII-tabel deel 2: Tekens 536 Begrippen 537 Bronvermelding 539 Cookbook Events 540 Kleurcodering Visual Studio 2019 C editor 543 Naamgeving klasse 544 Naming conventions C 545 UML-diagram NatSimII 550


C

O

N

C

EP

T

Deel 1: Introductie


2

Deel 1: Introductie

Leeswijzer do...while Commentaar double KleinGetal Naam Standaardcode Overbodige code Show() “Tekst”

XAML XAML attribuut XAML waarde XAML scheidingsteken XAML keyword XAML Class XAML Naam

naam van een attribuut waarde van een attribuut scheidingstekens bij een attribuutwaarde aanhalingstekens binnen XAML binnen XAML gedefinieerde keywords XAML-klasse naam binnen een XAML-bestand naam van een XAML-parameter waarde van een XAML-parameter commentaar binnen een XAML-bestand

C

XAML Aanhalingstekens

EP

T

Variabele

speciale combinaties van commando’s, ook wel constructies genoemd commentaar in de code gereserveerde woorden C de naam / het type van een object de zelfbedachte naam van een control of project alle normale code code die niet uitgevoerd wordt de naam van een methode de waarde van een string-variabele lokale variabelen en globale variabelen

N

XAML Parameternaam

XAML Parameterwaarde

C

O

XAML commentaar


3

Inleiding Deel 1: Introductie

Programmeren in C# Voor je ligt een boek waarmee je de beginselen leert van het programmeren met behulp van de programmeertaal C (C sharp). Behalve bij de taal C wordt ook stilgestaan bij algemene programmeerprincipes. Je hebt dus geen voorkennis nodig om met dit boek te beginnen.

O

N

Een computerprogramma kent een paar basisprincipes: Alle programmeerregels worden een voor een na elkaar uitgevoerd. Een programma maakt meestal gebruik van variabelen. Je kunt sommige regels laten herhalen in een lus. Je kunt een computer laten kiezen. Een programmeertaal gebruikt woorden die gereserveerd zijn om opdrachten te geven aan de computer (reserved words). Die kun je niet voor iets anders gebruiken.

C

– – – – –

C

EP

T

Programmeren is niets anders dan een stel opdrachten aan een computer geven. Gelukkig kun je deze opdrachten in een tekstfile opslaan, zodat je hetzelfde programma meerdere keren kunt laten uitvoeren. Een computer verstaat geen menselijke taal, maar begrijpt alleen enen en nullen. Daarom moet je tekstbestand vertaald worden naar een machinetaal van enen en nullen. In computertermen heet dit compileren. Dat doet het programma waarmee je programmeert (de programmeeromgeving of IDE, zie hoofdstuk 1) voor je. Wij gebruiken daarvoor Visual Studio Community 2019. Dat kun je gratis downloaden van www.visualstudio.com. Dit boek is gemaakt met Windows 10.

Dit boek bevat een eerste kennismaking met het programmeren. Je hebt dus geen voorkennis nodig van wat voor programmeertaal dan ook. Keerzijde is wel dat sommige zaken eenvoudiger worden voorgesteld dan ze in werkelijkheid zijn om je de achterliggende principes te leren. Maar voor een eerste kennismaking met programmeren zul je aan dit boek meer dan voldoende hebben. Dit boek bestaat niet alleen uit leeswerk, maar ook uit een reeks voorbeelden en opdrachten. Lees de voorbeelden dus niet alleen, maar voer ze regel voor regel in, in een eigen programma. Probeer iedere regel te begrijpen. Maak daarna de opdrachten. Sla iedere opdracht apart op in een bestandje. Soms heb je het voor een latere opdracht nog nodig. De opdrachten met een sterretje (*) zijn bedoeld als extra opdrachten (moeilijker of als verdieping van de lesstof ). Ze zijn niet essentieel voor je leerproces, maar wel leerzaam.


4

Deel 1: Introductie

Waarom zoveel eenvoudige opdrachtjes? Waarom zelf typen en niet knippen en plakken? Je begrijpt dan beter wat je doet en leert zo al doende. Bovendien... goed programmeren leer je alleen door het heel veel te doen!

C

O

N

C

EP

T

Veel succes & plezier.


5

Hoofdstuk 1 De programmeeromgeving (IDE)

Wat is een IDE?

T

IDE staat voor Integrated Development Environment, of geïntegreerde programmeeromgeving. Dit is een programma om programma’s te ontwerpen, te schrijven en te testen. Dit laatste noemen we debuggen. Een programmeertaal is niets anders dan een taal waarin je opdrachten opschrijft die een computer kan uitvoeren.

EP

En net zoals Word een grammatica- en spellingcontrole heeft, heeft een IDE dat ook voor de programmeertalen waar die voor gemaakt is.

N

C

Omdat een programmeertaal heel erg precies is, kan de IDE je ook helpen en suggesties geven over wat je allemaal kunt maken. Dat noemen we IntelliSense. De IDE van C die wij gebruiken heet Microsoft Visual Studio.NET. Visual Studio heet ‘visual’ omdat je direct kunt zien wat je maakt. Binnen Visual Studio wordt er heel veel gebruikgemaakt van kleur om je te laten zien wat er gebeurt. Met Visual Studio.NET – of zoals de meeste mensen zeggen: .NET (spreek uit als dot net) – kun je, behalve in C, ook in Visual Basic.NET, C++, F, Javascript, HTML, Python en nog veel meer programmeertalen programmeren.

O

Opstarten

C

Als je .NET 2019 opstart kom je in het volgende scherm terecht. Andere versies kunnen op details afwijken, maar het idee is hetzelfde.


Deel 1: Introductie

Schermafbeelding 1: Startscherm

EP

T

6

C

Eigenlijk spreekt het startscherm voor zich. Aan de linkerkant zie je de projecten die je recent geopend hebt, rechts heb je opties om projecten op te zoeken, een map te openen en een nieuw project aan te maken.

N

Omdat we eerst Visual Studio willen leren kennen starten we het programma op zonder project. Dit doen we door op de blauwe tekst te klikken Continue without code->

C

O

Je komt nu in het volgende scherm:


7

EP

T

Hoofdstuk 1 De programmeeromgeving (IDE)

Schermafbeelding 2: Onderdelen startscherm

C

O

N

C

Je ziet in Schermafbeelding 2 twee belangrijke gebieden: 1 Het Menu en de Toolbar. Deze zijn vergelijkbaar met Word en andere Officepakketten. Via de Toolbar kun je allerlei instellingen veranderen en functies aanroepen. 2 De werkomgeving bestaat uit: een linkerpanel, een middengebied en een rechterpanel.

Schermafbeelding 3: Keuze New Project


8

Deel 1: Introductie

Naam geven We gaan nu een nieuw programma maken. Net zoals bij Word klik je dan op File, New. Anders dan bij Word kies je nu voor Project.

C

O

N

C

EP

T

Nu kom je in het scherm Create a new project. In de eerste dropdown-list, die in de afbeelding is opengeklapt, kun je een selectie maken voor een van de vele programmeertalen die je kunt gebruiken binnen Visual Studio. Wij kiezen voor C. Kies je voor een andere taal dan kan het zijn dat de omgeving en de code heel veel op C lijken. Alleen zal het niet werken omdat de syntax, de regels van de taal, afwijkend zijn. Voor een Amerikaan lijken Duits en Nederlands erg veel op elkaar. Alleen als ik Nederlands tegen een Duitser spreekt verstaat hij me vaak niet. Zo verschillend zijn computertalen minstens ook.

Schermafbeelding 4: Create a new project selectie C#

Let op! Bij sommige andere talen, zoals Visual Basic, staan exact dezelfde keuzemogelijkheden en kom je in hetzelfde scherm terecht, alleen de taal is anders! Vervolgens dubbelklik je op Console App (.NET Framework).

Schermafbeelding 5: Selectie console-app


9

EP

T

Hoofdstuk 1 De programmeeromgeving (IDE)

Schermafbeelding 6: Configure your new project

C

Vervolgens geef je een naam aan je project. Standaard staat daar zoiets als ConsoleApp1.

N

Het volgende project dat je maakt krijgt dan de naam ConsoleApp2, enzovoort. Niet zo handig, want hoe weet je nu welk programma dat mooie spel is dat je ooit gemaakt hebt?

C

O

Er bestaat een mogelijkheid om een programma achteraf een nieuwe naam te geven, alleen gaat dat regelmatig mis, omdat de naam van het programma overal in jouw programma wordt gebruikt, ook op de achtergrond. Zelfs als je alleen de verschillende onderdelen van je programma (in C heten die onderdelen ‘methoden’) een duidelijke naam geeft, bijvoorbeeld BerekenDeVersnelling(), dan heet die methode in werkelijkheid ConsoleApp1.BerekenDeVersnelling(). We beginnen dus met het programma een betekenisvolle naam te geven, een naam waaraan je kunt zien wat het programma doet. Bijvoorbeeld Mahjong als je een Mahjongspel wilt maken, Rekenmachine als je een rekenmachine gaat maken, enzovoort. Ons programma heet KennismakenProgrammeerOmgeving.

Spelregels naamgeving C is hoofdlettergevoelig. Dit houdt in dat het programma met de naam MijnProgramma voor de computer iets totaal anders is dan het programma met de naam mijnprogramma!


10

Deel 1: Introductie

Aan de naamgeving van verschillende onderdelen in een programma zijn spelregels verbonden voor de schrijfwijze. In de bijlage Naming conventions C zijn de belangrijkste afspraken gebundeld. Algemeen: – Gebruik geen spaties in een naam. – Gebruik geen liggend streepje (-) in een naam. Een laag streepje, of underscore (_), mag wel, maar wordt sterk afgeraden. – Gebruik de PascalCase-notering voor variabelen. Dit houdt in dat je alle woorden aan elkaar schrijft en dat ieder nieuw woord met een hoofdletter begint: KennismakenProgrammeerOmgeving of BerekenVersnelling.

EP

T

De naam van het programma (KennismakenProgrammeerOmgeving) voer je in bij Name. Bij Location geef je de directory aan waar je je programma op wilt slaan. Standaard is dat de project-directory die .NET bij installatie aanmaakt. Deze kun je vinden in c:\users\gebruikersnaam\source\repos.

N

De IDE

C

De Solution Name wordt automatisch voor je ingevuld als je de naam van het programma invoert. Voorlopig laten we dat zo. Een programma bestaat meestal uit een aantal kleinere programma’s. Samen heten deze programmaatjes in de VisualStudio-taal een Solution (oplossing).

C

O

Je komt nu in de IDE zelf terecht, waarbij het scherm er ongeveer als Schermafbeelding 7 uit zal zien. Wijkt het af, zijn sommige vensters niet actief, dan kun je ze aanzetten door in de knoppenbalk te kiezen voor View en daarna de naam van het ontbrekende venster op te zoeken. Let op! Zie je niet direct de juiste naam? Er is ook een optie Other Windows met nog meer mogelijkheden.

Het scherm bevat de volgende belangrijke onderdelen (zie Schermafbeelding 7): 1 De knoppenbalk bovenaan het scherm. Hiermee bestuur je de IDE. 2 Direct daaronder een balk met veelvoorkomende functies waaronder de Debugfunctie. Hier komen we zo op terug. 3 Links de Toolbox met de grafische bouwstenen voor je programma. 4 Rechtsboven de Solution Explorer waarin een overzicht wordt getoond van alle onderdelen en bestanden van je programma. 5 Rechtsonder het Properties-scherm. Hier stel je de eigenschappen van je programma in zoals achtergrondkleur, gebruikt lettertype, maar ook hoe je programma moet reageren op een muisklik. 6 Het Design-scherm. Hier ontwerp je je programma.


11

Schermafbeelding 7: Overzicht IDE

Je eerste programma

EP

T

Hoofdstuk 1 De programmeeromgeving (IDE)

Maak een console-app met de naam KennismakenProgrammeerOmgeving.

O

1

N

Instructie

C

Een C-programma in Windows bestaat uit een project met één of meerdere bestanden met code. We gaan nu eerst een console-app maken.

Debuggen

C

Nu hebben we een programma dat functioneert, maar waar je nog niets mee kunt doen. Als je op de Debug-knop klikt, gaat het programma in een soort testmodus draaien. De IDE wordt niet afgesloten en het programma functioneert gewoon. Echter, als er een fout optreedt, springt de IDE direct naar de programmaregel waar de fout veroorzaakt wordt. De Debug-knop is de groene driehoek (Start) bovenaan het scherm.

Schermafbeelding 8: Debug-knop


12

Deel 1: Introductie

Als er geen fouten meer in zitten, zal het programma helemaal uitgevoerd worden. We noemen dit ook wel het runnen van het programma. 2 Je eerste programma is klaar. Klik op de Debug-knop: run het programma! Helaas... er lijkt nog niets te gebeuren. Dat klopt. Er gebeurt van alles maar je ziet nog niets. En dat gaan we nu veranderen.

De Code Editor Code Editor

EP

T

Dit is de eigenlijke tekstverwerker waarin je je programma(code) gaat schrijven. Dit scherm kent ook een soort spellingscontrole en de eerdergenoemde IntelliSense. In Broncode 1 staat de code die wordt uitgevoerd als je het programma runt. Deze code wordt voor je aangemaakt. Je cursor komt te staan tussen twee {accolades}. In Broncode 1 staat een gele streep. Hier kun jij gaan programmeren wat jij wilt dat gebeurt.

C

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

O

N

namespace KennismakenProgrammeerOmgeving { class Program { static void Main(string[] args) {

C

} } }

Broncode 1

Wij willen nu in de console de tekst Hello World laten zien. Het is de gewoonte dat iedere programmeur deze tekst in zijn eerste programma laat zien. 3 Typ tussen de accolades het volgende: Console.WriteLine(“Hello World!”);

Deze regel kent de volgende onderdelen: – Het object Console. – Een beschikbare functie van het object: WriteLine(). Als je achter een object een punt zet dan laat .NET alle beschikbare properties en functies zien. Dit noemen ze puntnotatie.


Hoofdstuk 1 De programmeeromgeving (IDE)

13

– Haakje openen, hierna kun je eventuele parameters (bijzonderheden of kenmerken) meegeven, gescheiden door een komma. – De parameters zijn soms getallen, soms teksten, soms objecten. – Haakje sluiten. Dan weet .NET dat alle parameters opgegeven zijn. – En de ; – want elke regel wordt afgesloten met een puntkomma;

EP

4 Typ na de vorige regel het volgende:

T

Nu doet het programma nog steeds weinig zichtbaars. Als je op de Debug/startknop drukt om het programma te runnen, zie je hooguit het zwarte scherm van de console voorbij flitsen. C opent het scherm, laat de tekst zien en sluit het scherm ogenblikkelijk weer af. En dat gaat zo snel dat je het bijna niet of niet ziet. Dit gaan we verhelpen door een regel toe te voegen die wacht op een reactie van de gebruiker. We gebruiken de functie Console.ReadKey(). Deze functie leest een toetsaanslag van een gebruiker in het geheugen en gaat dan verder. In dit geval zorgt deze regel ervoor dat het consolescherm net zolang actief blijft totdat je op een willekeurige knop drukt.

Console.ReadKey();

Als het goed is ziet je hele programma er nu als volgt uit:

N

C

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

C

O

namespace KennismakenProgrammeerOmgeving { class Program { static void Main(string[] args) { Console.WriteLine(“Hello World!”); Console.ReadKey(); } }

}

Broncode 2

5 Test je programma door op de Debug-knop te klikken. Als het goed is krijg je het volgende resultaat:


14

Deel 1: Introductie

Schermafbeelding 9: Console Hello World

Opdracht IDE 1: Disclaimer

T

6 Maak een console-app met de naam Disclaimer. Zorg ervoor dat als je het programma runt de volgende tekst in beeld komt:

EP

De voorwaarden van deze disclaimer zijn van toepassing op deze website.

C

Door deze website te bezoeken en/of de op of via deze website aangeboden informatie te gebruiken, verklaar je akkoord te gaan met de toepasselijkheid van deze disclaimer. In geval van tegenstrijdigheid tussen de voorwaarden van specifieke producten en diensten besteld via deze website en deze disclaimer, prevaleren de voorwaarden van deze producten en diensten.

Een disclaimer is een stuk tekst dat vaak wordt gebruikt bij software zoals websites om de maker op voorhand te beschermen tegen claims van gebruikers.

O

N

Tip: Een lege regel is ook een regel.

Wat weet je nu?

C

In dit hoofdstuk heb je geleerd hoe je de IDE (programmeeromgeving) .NET (dot net) gebruikt. Daarin werden de volgende zaken uitgelegd: Het belang van naamgeving Het begrip PascalCase Debuggen De Code Editor De puntnotatie Disclaimer Syntax

– – – – – – –


15

Hoofdstuk 2 Rekenen

Rekenen Computer is het Engelse woord voor rekenaar. Het Duitse woord voor computer is Rechner, het Franse woord is ordinateur en dat betekent allemaal rekenen. En dat is wat een computer goed kan: veel en snel rekenen.

1

EP

T

Je kunt een computer alles laten uitrekenen. De syntax (taalregels) hiervoor is bijna hetzelfde als bij het gewone rekenen. Als je twee getallen moet optellen kun je dus gewoon 6 + 3 opschrijven. Voorbereiden Maak een console-app aan met de naam Rekenen.

Console.ReadKey();

C

2 Zorg ervoor dat de console-app blijft wachten op een actie van de gebruiker. Voeg daarom de volgende regel toe:

N

Optellen 3 Voeg voor de regel Console.ReadKey(); de volgende regel toe:

O

Console.WriteLine(6 + 3);

C

De eerste regel zorgt er nu voor dat de computer een sommetje uitrekent en de tweede regel is nodig om te weten of de computer klaar is met rekenen. Test Optellen 4 Start je programma. Als het goed is (en er zijn geen bugs), staat er een 9 in beeld. De computer heeft namelijk eerst de berekening uitgevoerd en laat vervolgens het resultaat zien.

Opdracht Rekenen 1: Optellen Voeg een regel code toe aan het programma zodat onder de 9 op het scherm de uitkomst van een optelling komt te staan die als resultaat 289 heeft.

Opdracht Rekenen 2: Meerdere getallen optellen Voeg een regel toe aan het programma waarbij de computer de volgende getallen optelt en (ook de uitkomst) op het scherm laat zien:


16

Deel 1: Introductie

7 8 345 9 3 23 10 Klopt het antwoord? Aftrekken Voor aftrekken geldt hetzelfde als voor optellen. De computer laat het resultaat van de berekening zien.

EP

Console.WriteLine(19253 - 1243);

T

5 Ga verder met de console-app Rekenen en voeg de volgende regel toe:

Test Aftrekken 6 Start je programma. Als het goed is komen de volgende vier getallen in het consolevenster te staan:

N

C

9 289 405 18010

O

Opdracht Rekenen 3: Aftrekken

Voeg een vijfde regel toe waarmee je de getallen 17 en 19 van elkaar aftrekt.

C

Opdracht Rekenen 4: Gebroken getallen aftrekken Voeg een zesde regel toe waarmee je de kommagetallen 17,15 en 19,7234 van elkaar aftrekt. Let op! C is een Amerikaans product, wat betekent dat binnen C de Amerikaanse schrijfwijze voor getallen wordt gehanteerd. In deze notatie schrijf je bijvoorbeeld een half niet als 0,5 maar als 0.5! De output, dus datgene wat je op je scherm ziet, houdt zich aan de instellingen van het besturingssysteem. Met andere woorden, als je werkt met een Nederlandse versie van Windows worden de punten in komma’s veranderd, werk je met een Amerikaanse of Engelse versie dan blijven de punten gewoon punten.


Hoofdstuk 2 Rekenen

17

Opdracht Rekenen 5: Meerdere getallen aftrekken Voeg een zevende regel toe waarbij de getallen 1962 – 62 – 19 – 0,5 van elkaar afgetrokken worden. De computer werkt gewoon van links naar rechts. Vermenigvuldigen Voor het vermenigvuldigen van getallen wordt het sterretje gebruikt: *. 7 Voeg als achtste regel toe: Console.WriteLine(150.44 * 12.5);

Test Vermenigvuldigen 8 Start je programma. Als het goed is, is het laatste getal op je scherm 1880,5.

EP

T

Als je te grote getallen gebruikt voor je berekening kun je de volgende foutmelding krijgen:

C

Schermafbeelding 10: Overflow exception

O

N

Het rode lijntje onder de regel code betekent dat er een fout in je programma zit. Als je vervolgens met de muis over de rode lijn gaat (hovert) wordt de foutmelding getoond. Deze foutmelding betekent dat het resultaat van de berekening een te groot getal oplevert. In het hoofdstuk over variabelen komen we hier uitgebreid op terug.

C

Opdracht Rekenen 6: Meer getallen vermenigvuldigen Voeg tien nieuwe regels toe waarbij je de tafel van 7 laat zien. Dat zijn eigenlijk de uitkomsten van tien kleine sommetjes onder elkaar. Zorg dus dat ieder getal op een nieuwe regel terechtkomt. Je kunt ook meerdere getallen met elkaar vermenigvuldigen. 9 Voeg de volgende regel toe: Console.WriteLine(1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9);

Test Meer getallen vermenigvuldigen 10 Start je programma. Als het goed is, is het laatste getal op je scherm 362.880.


18

Deel 1: Introductie

Opdracht Rekenen 7: Negatieve getallen Voeg een regel toe waarbij je -5 met 3 en -9 vermenigvuldigt. Maakt hier de volgorde uit? Unary bewerkingen Een unary bewerking is een bewerking met één getal: een negatief getal zoals -1 is dus een unary bewerking. Dit in tegenstelling tot de binary bewerking, daar zijn twee getallen mee gemoeid: 7 + 3 is dus een binary bewerking.

Delen Ook delen doe je net zoals je gewend bent. Er is ook een symbool voor: /

T

11 Voeg een regel toe waarbij 24 door 4 gedeeld wordt.

EP

Console.WriteLine(24 / 4);

Test Delen 12 Na uitvoeren van het programma moet het laatste getal op het scherm 6 zijn.

C

Opdracht Rekenen 8: Delen

N

Voeg een berekening toe waarbij je twee gebroken getallen deelt en de uitkomst een geheel getal is.

O

Modulo: restfunctie Een bijzondere functie is de modulo (rest)functie. Als je twee getallen deelt dan blijft er soms een rest over. 7 gedeeld door 2 is 3 rest 1. De modulo van 7 / 2 is dus 1. De modulo van 13 / 5 is 3.

C

Verwarrend is dat het symbool bij de berekeningen van C voor de restberekening het procentteken () is. De regel 13 % 5 geeft dus 3 in C. En geen 0,65 (5 procent van 13)!

13 Voeg deze regel toe om de modulo van 76 en 13 te bepalen: Console.WriteLine(76 % 13);

Test Modulo Het laatste getal in de console moet nu 11 zijn.

Opdracht Rekenen 9: Modulo Voeg een moduloberekening toe waarbij de uitkomst 17 is.


Hoofdstuk 2 Rekenen

19

Machtsverheffen Natuurlijk kun je machtsverheffen door een getal met zichzelf te vermenigvuldigen; 22 kun je gewoon uitrekenen door in C 2 * 2 uit te voeren. 26 kun je uit laten rekenen door 2 * 2 * 2 * 2 * 2 * 2 uit te voeren. Maar het kan ook anders.

Bibliotheek

T

Binnen C kun je gebruikmaken van code die door anderen is geschreven en in een bibliotheek (library) wordt bewaard. Zo is er een bibliotheek met rekenkundige functies, die Math heet. Binnen deze bibliotheek kun je allerlei rekenkundige functies vinden waaronder Pow (van power = macht). Deze functie kent twee variabelen, het grondtal en de macht die je gescheiden door een komma moet invoeren. In het hoofdstuk over variabelen komen we uitgebreid terug op wat een variabele is.

EP

14 Voeg een regel code toe om de 2e macht van 8 (dus 82) te bepalen. Console.WriteLine(Math.Pow(8 , 2));

C

Test Machtsverheffen 15 Het laatste getal in de console moet nu 64 zijn.

N

Haakjes en volgorde

C

O

Net zoals bij rekenen kun je ook binnen C met haakjes werken om de volgorde van de bewerkingen te bepalen. Normaal is deze volgorde: 1 haakjes; 2 machtsverheffen en worteltrekken; 3 vermenigvuldigen en delen; 4 optellen en aftrekken.

Opdracht Rekenen 10: Machtsverheffen Voeg als laatste een berekening toe waarbij je 5 * 5 * 5 aftrekt van 54.

Wat moet je nu kunnen en kennen?

– – – –

In deze les heb je geleerd hoe je rekenkundige bewerkingen in C gebruikt. Daarin werden de volgende zaken uitgelegd: Optellen Aftrekken Vermenigvuldigen Delen


20

Unary bewerkingen Modulo Machtsverheffen Library Math

Werken met haakjes Volgorde van bewerkingen

C

O

N

C

EP

T

– – – – – – –

Deel 1: Introductie


21

Hoofdstuk 3 Variabelen

Wat is een variabele?

T

Vroeger werd voor elk probleem een nieuw programma gemaakt. Al vlug vond men dat niet handig. Als je bijvoorbeeld een programma gemaakt had om de tafel van 5 uit te rekenen, moest je namelijk een nieuw programma maken om de tafel van 3 of van 4 uit te rekenen.

EP

De tafel van X

C

Door gebruik te maken van een variabele kun je een programma maken dat je vaker kunt gebruiken voor hetzelfde soort probleem. De gebruiker kan dan bijvoorbeeld aangeven of hij de tafel van 10 wil hebben, of de tafel van 3 of van 4, maar de computer maakt gebruik van slechts één tafelprogramma.

N

Hoe ‘denkt’ een computer?

C

O

De computer ‘denkt’ binair (tweetallig). Dat komt omdat de geheugencellen maar twee waarden kunnen hebben: ze zijn aan of ze zijn uit. Deze kleinste informatieeenheid noemen we een bit. We zeggen dan: één geheugencel kan één bit aan informatie opslaan. Met acht geheugencellen die allemaal áán of uit kunnen staan, kunnen we 255 verschillende combinaties maken. Kijk maar eens goed naar de ASCII-tabellen in de bijlagen. De computer gebruikt die 255 combinaties om (besturings)acties uit te voeren en om cijfers en letters op het scherm te laten verschijnen. Als je in Word bijvoorbeeld je Alt-knop indrukt en daarna een getal (tussen 33 en 126) op het numerieke blok van je toetsenbord intypt, verschijnt het bijbehorende teken op je scherm.

Werking van een variabele Maar hoe werkt zo’n variabele nu precies? Een variabele is een geheugenvakje met een naam. Zo’n geheugenvakje kun je vergelijken met een schoenendoos. Zoals je in een schoenendoos niet alleen schoenen kunt bewaren maar ook andere dingen, zoals foto’s of Legostenen, zo kun je in een geheugenvakje ook allerlei soorten gegevens bewaren. Die soorten gegevens noe-


22

Deel 1: Introductie

men we gegevenstypen. De meest gebruikte gegevenstypen zijn teksten en gehele of gebroken getallen in soorten en maten. Samen vormen de geheugenvakjes het werkgeheugen van de computer. Omdat de computer niet in de war mag raken heeft ieder geheugenvakje zijn eigen volgnummer gekregen. We noemen zo’n volgnummer het adres van de variabele. Helaas raken wij programmeurs juist makkelijk in de war van al die adressen vol lange getallen. Daarom geven programmeurs namen aan variabelen die we makkelijker kunnen onthouden. De compiler vertaalt dan die namen in adressen van geheugenvakjes. Een computerprogramma maakt gebruik van het computergeheugen om zaken in op te slaan en uit te voeren. Een computergeheugen is opgebouwd uit bits en bytes. Er passen 8 bits in een byte en in een geheugen van 1 gigabyte zitten 1.073.741.824 bytes. Elke byte heeft dus een (getal)adres, zodat de computer hem kan terugvinden.

Geheugen

1 2 3

5 6

1

0

0

1

0

1

1

0

1

0

1

1

0

1

0

1

0

0

0

0

1

1

1

1

0

1

0

1

1

0

1

0

C

O

N

7

C

4

EP

Adres

T

In onderstaande figuur staan het eerste en het laatste stukje van de geheugenadressen van 1 GB weergegeven. Het geheugen in dit voorbeeld is bijna helemaal gevuld:

1.073.741.820

1

0

1

0

1

0

0

0

1.073.741.821

0

0

0

0

1

1

1

1

1.073.741.822

1

0

0

1

1

1

1

0

1.073.741.823 1.073.741.824 Figuur 1: Geheugenadressen

In figuur 2 staat een ander voorbeeld. De geheugenadressen 3 tot en met 14 zijn gereserveerd voor een kort stukje tekst: Hello World. Voor de computer is dat een rijtje letters: een ‘string’ (het Engelse woord voor touwtje of rij). Een variabele is het gereserveerde stukje geheugen dat gevuld kan worden met informatie.


Hoofdstuk 3 Variabelen

23

Adres Geheugen 1 2 1

0

0

0

1

1

1

0

3

0

1

0

0

1

0

0

0

H

4

0

1

1

0

0

1

0

1

e

5

0

1

1

0

1

1

0

0

l

6

0

1

1

0

1

1

0

0

l

7

0

1

1

0

1

1

1

1

o

8

0

0

1

0

0

0

0

0

9

0

1

0

1

0

1

1

1

W

0

1

1

0

1

1

1

1

o

11

0

1

1

1

0

0

1

0

r

12

0

1

1

0

1

1

0

0

l

13

0

1

1

0

0

1

0

0

d

14

0

0

1

0

1

1

1

0

.

EP

T

10

Figuur 2: Gereserveerd geheugenadres

C

Een string is één van de verschillende typen variabelen die je in C kunt gebruiken. Verderop wordt een aantal andere typen variabelen besproken.

N

In C wordt de zin “de variabele van het type string met de naam tekst krijgt de waarde Hello World.” als volgt opgeschreven: tekst

=

type

naam

krijgt de waarde

O

string

“Hello World.” ; waarde

afsluiten

C

Als programmeur hoef je dan ook geen geheugenadres te onthouden, je vindt de inhoud van de variabele door te verwijzen naar de naam, in dit geval tekst.

Een variabele bestaat altijd uit drie onderdelen: 1 Het type variabele: wat voor soort variabele is het? 2 De naam: met behulp van de naam vindt de computer het juiste geheugenadres. 3 De inhoud: welke waarde heeft de variabele? In het voorbeeld worden vanaf geheugenadres 3 twaalf bytes gereserveerd voor de variabele met de naam tekst. Het type (soort) variabele wordt aangegeven door een keyword, in dit geval het woord string. De computer weet aan de hand van de soort variabele hoeveel bytes er gereserveerd moeten worden. Voor een variabele van het type string wordt altijd per teken één byte gereserveerd. Als je een letter toevoegt of verwijdert zal er op de achtergrond altijd eerst een nieuwe variabele aangemaakt worden waar de oude variabele naar toe gekopieerd wordt. Hierdoor kan het werken met strings aardig traag worden!


24

Deel 1: Introductie

Voorbeeld Begroeting Instructie 1

Voorbereiden Maak een console-app aan met de naam Begroeting.

Aanmaken variabele 2 Maak een variabele aan van het type string met de naam voornaam. Doe dit door de volgende regels in te voeren: // Declaratie variabelen string voornaam = “Arnout”;

T

Een goede gewoonte is om als het kan iedere variabele direct een waarde (in dit geval ‘Arnout’) toe te kennen. Dit voorkomt fouten.

EP

Een andere goede gewoonte (eigenlijk zo belangrijk dat je het bijna een wet zou kunnen noemen) is om commentaar bij je code te schrijven. De twee schuine streepjes zorgen ervoor dat het commentaar groen wordt. Meestal geef je in je commentaar aan wat er gaat volgen, als geheugensteuntje voor jezelf en als uitleg voor iemand die je code probeert te lezen. Je kunt achter de // schrijven wat je wilt: de computer voert niet uit wat er staat, deze regel wordt overgeslagen.

C

Uitvoer 3 Voeg de volgende regels toe zodat Arnout begroet wordt door jouw programma:

N

// Output Console.WriteLine(“Welkom ”+ voornaam);

O

Afsluiten 4 Voeg de volgende regel toe zodat het programma pauzeert aan het eind:

C

Console.ReadKey();

Test 5 Test je programma. Je krijgt nu de volgende output:

Schermafbeelding 11: Begroeting


Hoofdstuk 3 Variabelen

25

Opdracht Variabelen 1: Vraag? Maak een console-app met de naam Vraag. De app moet gebruikmaken van een variabele met de naam Voornaam en van het type string. Geef de variabele voornaam een waarde. Als de voornaam Lisette is en je runt het programma, moet de volgende tekst op je scherm verschijnen: Hoe oud ben je Lisette? Let op! Er moet een vraagteken achter de naam geplaatst worden! In stap 3 staat hoe je dat kunt doen.

T

Gebruikersinput

EP

Wat heb je nu aan zo’n programma? Eigenlijk niet veel, want of je nu de naam rechtstreeks in de methode WriteLine() invoert of bij de variabele, dat maakt niet zo heel veel uit. Hooguit dat je geen problemen hebt met het vergeten van spaties en zo. Nee, het wordt pas interessant als je de gebruiker een naam kunt laten invoeren, dan krijg je voor iedereen een persoonlijke begroeting.

N

C

Bij een console-app doe je dit door de methode Readline() te gebruiken van het object Console.

Voorbeeld Begroeting aanpassen

Voorbereiden Open de console-app Begroeting in Visual Studio.

C

1

O

Instructie

Wijzigen 2 Zoek de volgende regel op: string voornaam = “Arnout”;

3 Vervang de waarde van de variabele voornaam, “Arnout” door de methode Readline van het Console object. De regel ziet er nu als volgt uit: string voornaam = Console.ReadLine();

Test 4 Test het programma. Als je Sint Nikolaas als invoer gebruikt krijg je het volgende scherm te zien:


26

Deel 1: Introductie

Schermafbeelding 12: Begroeting aangepast

Opdracht Variabelen 2: Verklaring

T

Maak een console-app met de naam Verklaring. De app moet gebruikmaken van twee variabelen: – de variabele met de naam voornaam en het type string; – de variabele met de naam achternaam van het type string.

EP

De twee variabelen moeten samengevoegd worden tot de volgende tekst: Hierbij verklaar ik, voornaam achternaam, dat ik gezond ben, mij niet ziek voel en geen koorts heb.

C

Uiteraard is het de bedoeling dat op de plek van de voornaam en de achternaam de door de gebruiker ingevoerde waarde wordt getoond.

N

Typen variabelen

O

Je krijgt nu een stuk theorie dat je nodig hebt om verder te kunnen. Eerst krijg je uitleg over de verschillende soorten variabelen en waarom deze er zijn, vervolgens wordt uitgelegd hoe je (soms) de ene soort variabele kunt omzetten in een andere. Met die kennis kun je vervolgens de nodige voorbeelden en opdrachten maken.

C

Je hebt al kennisgemaakt met één type variabele, de string. Maar wat voor typen variabelen zijn er eigenlijk, en waarom? Er zijn veel verschillende typen variabelen, omdat je verschillende dingen met een variabele wilt doen. Met getallen wil je bijvoorbeeld kunnen rekenen. Plaatjes wil je kunnen kantelen, teksten wil je bijvoorbeeld ordenen of aan elkaar kunnen plakken. De standaardvariabelen (built-in types) zijn eigenlijk de ‘oer’-variabelen die de basis vormen van C. De standaardvariabelen kun je verdelen in drie groepen: – teksten – getallen – logische variabelen


Hoofdstuk 3 Variabelen

27

Teksten Alles wat je met een tekstverwerker doet – letters schrijven op het beeldscherm, maar ook cijfers schrijven naar je beeldscherm – beschouwen we als tekst. In programmeertaal noemen we één teken een char (van character/karakter). Pas als er meer teksttekens bedoeld worden, heet dit een string. Een teken kun je naar je beeldscherm of de printer schrijven; het heeft een bepaalde vorm die wij mensen herkennen als een letter, een leesteken of... een cijfer. Alleen kan de computer niet rekenen met zo’n teken. Dat komt omdat alle teksttekens worden gecodeerd volgens een lijst, de ASCII-tabel (tegenwoordig Standaard Ansi, Unicode of UTF-8).

ASCII

C

EP

T

ASCII (American Standard Code for Information Interchange) is een standaard om letters, cijfers, leestekens, enkele andere symbolen en stuurcodes weer te geven door aan ieder teken een getal te koppelen waarmee dat teken wordt aangeduid. De standaard ASCII-tabel bestaat uit twee delen: de 95 zichtbare tekens (hoofdletters en kleine letters, cijfers, leestekens, de spatie en enkele andere symbolen) en 33 stuurcodes. De stuurcodes representeren geen zichtbare tekens, maar zijn opdrachten aan uitvoerapparaten of geven informatie over de data die verstuurd worden. Een aantal ASCII-codes staat hieronder: Binair

Teken

Dec

Binair

Teken

48

00110000

0

96

01100000

`

49

00110001

1

97

01100001

a

50

00110010

2

98

01100010

b

O

N

Dec

51

00110011

3

99

01100011

c

52

00110100

4

100

01100100

d

53

101

01100101

e

00110110

00110101

6

102

01100110

f

55

00110111

7

103

01100111

g

56

00111000

8

104

01101000

h

C

5

54

57

00111001

9

105

01101001

i

58

00111010

:

106

01101010

j

59

00111011

;

107

01101011

k

60

00111100

<

108

01101100

l

Tabel 1: ASCII

In de bijlage staat de complete ASCII-tabel.


28

Deel 1: Introductie

Teksttype char Voor de naamgeving van variabelen zijn afspraken gemaakt (naming conventions, zie bijlage 2). In de naam van een variabele mag je wel een underscore of laag streepje (_) gebruiken en hoef je geen hoofdletters te gebruiken. Als je een variabele hebt van één letter dan gebruik je daar het type char voor. In onderstaand voorbeeld zijn er twee variabelen van het type char. Eén met de naam letter-b en de waarde b, en één met de naam cijfer1 en de waarde 1. char letter-b = ‘b’; en char cijfer1 = ‘1’;

Adres Geheugen

2 0

1

0

0

0

0

1

4

0

1

1

0

0

0

1

0

5

0

0

0

0

0

0

0

0

6

0

0

0

0

1

0

0

0

7

0

0

1

1

0

0

0

1

8

1

1

0

1

0

1

0

1

C

cijfer 1

1

EP

letter-b

3

T

1

N

Figuur 3: Twee char-variabelen

O

Zoals je ziet, staan er om de waarden ‘b’ en ‘1’ enkele aanhalingstekens. Op deze manier geef je in C een char een waarde. Bij de string zijn het dubbele aanhalingstekens: “tekst”.

C

Teksttype string

Het type char gebruik je dus voor tekens. Om een hele zin in het geheugen op te slaan zou je dan echter heel veel variabelen nodig hebben: voor elke letter in de zin één. Omdat je nooit weet hoe lang een zin wordt die de gebruiker gaat typen, is dit erg onhandig. Daarom is de string-variabele bedacht (string betekent in het Engels ‘touwtje’). Deze is bedoeld om in één keer een hele zin of zelfs een hele tekst op te kunnen slaan. Een string bestaat per definitie uit een hoeveelheid tekens, dus chars! In het volgende voorbeeld wordt aan de string-variabele met de naam stukjeTekst de tekst: “Dit is een stuk tekst dat zo in het geheugen geplaatst wordt.” toegekend. Er zijn in totaal 60 bytes nodig om deze tekst in het geheugen te plaatsen. Ook iedere spatie en de punt zijn tekens die opgeslagen worden.


Hoofdstuk 3 Variabelen

29

Let op! Dit is een vereenvoudigde weergave van de werkelijkheid, maar het voert te ver om hier dieper op in te gaan. String stukjeTekst = “Dit is een stuk tekst dat zo in het geheugen geplaatst wordt.”;

Adres 1

1

1

1

0

1

0

1

1

2

0

1

0

0

0

1

0

0

D

3

0

1

1

0

1

0

0

1

i

4

0

1

1

1

0

1

0

0

t

0

1

0

0

0

0

0

0

0

1

1

0

1

0

0

1

7

0

1

1

1

0

0

0

1

0

0

0

0

59

0 0

1

1

0

0

s

EP

0 58

i

T

5 6

1

1

0

1

1

1

1

o

1

1

1

0

0

1

0

r

1

1

0

C

stukjeTekst

Geheugen

1

0

0

d

0

1

1

1

0

1

0

0

t

61

0

0

1

0

1

1

1

0

.

O

N

0

60

Figuur 4: Zo wordt een string opgeslagen in het geheugen.

C

Opdracht Variabelen 3: Variabele verklaring Pas Opdracht Variabelen 2 zo aan dat je ook andere stukken tekst kunt gebruiken in wisselende samenstelling. Hint: Een tekst kun je in verschillende stukken opknippen die je aan verschillende variabelen toekent.

Getallen Met getallen kun je rekenen, met teksten niet. In de loop van de tijd zijn er verschillende soorten getalvariabelen ontstaan. Het grote verschil tussen deze variabelen is de hoeveelheid geheugen die ze in beslag nemen. Er zijn variabelen voor


30

Deel 1: Introductie

hele getallen en gebroken getallen, voor negatieve en positieve getallen en grote en kleine getallen: Omschrijving

Grootte

Bereik van

tot en met

byte

positieve hele getallen

1 byte

0

255

sbyte

negatieve en positieve hele getallen

1 byte

-128

127

short

negatieve en positieve hele getallen

2 bytes

-32.768

32.767

ushort

positieve hele getallen

2 bytes

0

65.535

int

negatieve en positieve hele getallen

4 bytes

-2.147.483.648

long

negatieve en positieve hele getallen

8 bytes

nint

negatieve en positieve hele getallen (native signed integer)

ulong

float

T

Type

EP

9.223.372.036.854.770.807

Afhankelijk van het gebruikte besturingssysteem 32 of 64 bits

Afhankelijk van het gebruikte besturingssysteem

Afhankelijk van het gebruikte besturingssysteem

positieve hele getallen

8 bytes

0

18.446.744.073.709.551.615

gebroken getallen

4 bytes

± -3,4 × 1038

± 3,4 × 1038

O

N

C

-9.223.372.036.854.770.808

gebroken getallen

8 bytes

± -5,0 × 10324

± 1,7 × 10308

C

double

2.147.483.647

16 bytes

± -7,9 × 1028

± 7,9 × 1028

decimal

financiële getallen

Tabel 2: Soorten getalvariabelen

Afhankelijk van het soort programma dat je maakt kies je het juiste type voor je variabelen. Bijvoorbeeld, als je een programma schrijft dat iets doet met maandnummers (januari = 1, februari = 2 enzovoort) kun je voor het maandnummer het beste een variabele gebruiken van het type byte, omdat je alleen de getallen 1 tot en met 12 nodig hebt. Een byte neemt maar 1 byte (= 8 bits) in beslag aan geheugenruimte. Een decimal neemt daarentegen 16 bytes (16 x 8 bits) aan (vooral lege) geheugenruimte in beslag. Ook al heb je die ruimte niet nodig om iets in op te slaan, is die toch gereserveerd. En hoe minder geheugenruimte er in beslag genomen wordt, des te sneller werkt het programma! Kijk maar eens naar de volgende voorbeelden.


Hoofdstuk 3 Variabelen

31

Stel, we definiëren de volgende drie variabelen en we geven steeds in een byte-tabel aan hoeveel ruimte die inneemt: byte kleinGetal = 12;

Adres

Geheugen

1 2

kleinGetal

3

1

0

1

0

0

0

0

1

4

0

0

0

0

1

1

0

0

5

0

0

0

0

0

0

0

0

6

1

0

1

0

1

0

1

0

T

7

EP

Figuur 5: Geheugenruimte die een byte inneemt.

short kleinGetal = -12

Geheugen

1 2

0

1

0

0

0

0

1

4

1

0

0

0

1

1

0

0

5

0

0

0

0

0

0

0

0

6

1

0

1

0

1

0

1

0

O

kleinGetal

1

N

3

C

Adres

Deze bit bepaalt dat het getal negatief is!

7

C

Figuur 6: Geheugenruimte die een short inneemt.

decimal kleinGetal = 12;

Adres

Geheugen

1 2

kleinGetal

3

1

0

1

0

0

0

0

1

4

0

0

0

0

1

1

0

0

5

0

0

0

0

0

0

0

0

6

0

0

0

0

0

0

0

0

7

0

0

0

0

0

0

0

0


32

Deel 1: Introductie

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

10

0

0

0

0

0

0

0

0

11

0

0

0

0

0

0

0

0

12

0

0

0

0

0

0

0

0

13

0

0

0

0

0

0

0

0

14

0

0

0

0

0

0

0

0

15

0

0

0

0

0

0

0

0

16

0

0

0

0

0

0

0

0

17

0

0

0

0

0

0

0

0

18

0

0

0

0

0

0

0

0

19

0

0

0

0

0

0

0

0

20

0

0

0

0

0

0

0

0

21

1

0

1

0

1

0

1

0

EP

22

T

8 9

Figuur 7: Geheugenruimte die een decimal inneemt.

C

In dit laatste geval wordt er dus heel veel ruimte gereserveerd voor één getal, terwijl je alleen de eerste byte nodig hebt.

O

N

Let op! Als je echter een te klein type neemt voor je variabele dan kan het zijn dat een getal niet past in de variabele. Je krijgt dan bij de uitvoering van je programma een foutmelding.

Omzetten van variabelen

C

In deze paragrafen gaan we leren hoe je een variabele omzet van het ene type naar het andere: van tekst naar getal van getal naar tekst van teken naar getal van getal naar teken van int naar short van double naar int

– – – – – –

Niet alle omzettingen komen aan de orde. Maar als je de behandelde omzettingen begrijpt en in de vingers hebt, dan zijn de andere omzettingen niet moeilijk meer.


Hoofdstuk 3 Variabelen

33

Van getal naar tekst De meeste variabelen, ongeacht de soort, zijn om te zetten naar een tekstvariabele, naar iets wat je op het scherm of met de printer kunt laten zien. Dit doe je door voor zo’n variabele de methode ToString() aan te roepen. Een binair getal wordt dan omgezet naar het bijbehorende symbool. double getal1 = 9; getal1.ToString();

Impliciete omzetting C is zo slim gemaakt dat het programma zelf, zonder dat jij als programmeur er iets voor hoeft te doen, soms getallen omzet naar een string. Bijvoorbeeld in het geval dat het getal al aan een string of string-variabele wordt gekoppeld.

EP

Console.WriteLine(“Uitkomst is: ” + 10 * 100)

T

Voordat C er een string van maakt zal het programma eerst alle mogelijke berekeningen uitvoeren. Dus als er staat:

Dan wordt eerst de berekening 10 * 100 uitgevoerd en zal het resultaat 1000 naar de bijpassende string vertaald worden.

C

Maar dit is ook linke soep! Want als je de volgende regel hebt: Console.WriteLine(“Uitkomst is: ” + 10 + 100)

O

N

dan krijg je als resultaat:

C

Schermafbeelding 13: Omzetting van getal naar string (1).

De getallen 10 en 100 worden eerst omgezet naar tekst en dan achter elkaar geplakt. Als je nu haakjes om deze getallen zet, gaat het wel goed: Console.WriteLine(“Uitkomst is: ” + (10 + 100))

Schermafbeelding 14: Omzetting van getal naar string (2).

Als C zoiets uit zichzelf doet dan noemen we dat een impliciete omzetting.


34

Deel 1: Introductie

Expliciete omzetting Roep je de methode ToString() zelf aan dan is dit een expliciete omzetting. Vuistregel is dat als je soms vreemde resultaten krijgt en je werkt met meerdere getalvariabelen en string-variabelen in je uitvoer, je achter de getalvariabelen .ToString() typt. Je maakt dan gebruik van een expliciete omzetting. Jij als programmeur bepaalt wanneer iets gebeurt. Probeer je aan een string-variabele het resultaat van een optelling toe te kennen dan zul je dit expliciet moeten doen. Dus: string Optelling = (prijs + BTWBedrag).ToString();

in plaats van:

Instructie 1

EP

Voorbeeld van Getal naar tekst

T

string Optelling = prijs + BTWBedrag;

Voorbereiden Maak een console-app aan met de naam VanGetalNaarTekst.

C

Declaratie variabelen 2 Voeg de volgende regels toe:

N

// Declaratie variabelen double prijs = 200; double BTWBedrag = prijs * 0.21; string Optelling = “”;

O

Algoritme 3 We voegen nu de volgende regels code toe:

C

// Algoritme Optelling = (prijs + BTWBedrag).ToString();

Een algoritme is je ‘recept’: het geheel van handelingen dat de computer in een bepaalde volgorde moet uitvoeren om een bepaald resultaat te krijgen.

Uitvoer en afsluiten 4 Voeg de volgende regels toe om de uitvoer te laten zien: // Uitvoer Console.WriteLine(Optelling); // Afsluiting Console.ReadKey();


Hoofdstuk 3 Variabelen

35

Van tekst naar getal We hebben nu kennisgemaakt met verschillende typen variabelen. We hebben ook gezien dat de computer met sommige variabelen (namelijk getallen) kan rekenen en met andere (tekst) niet. Tekstvariabelen gebruik je om de lettertekens en symbolen op het scherm of via de printer te laten zien. Ook gebruik je tekstvariabelen bij de invoer van gegevens. Let op! Een symbool van een getal, bijvoorbeeld 9, op het scherm is geen getal maar een teken! Het wordt standaard een string of char. Daar kun je niet mee rekenen.

T

Om met ingevoerde getallen te kunnen rekenen moeten ze eerst omgezet worden naar een getalvariabele, dus een byte, sbyte, long, ulong, float, int, double of decimal.

C

byte getalByte = byte.Parse(“9”); sbyte getalSbyte = sbyte.Parse(“9”); long getalLong = long.Parse(“9”); ulong getalUlong = ulong.Parse(“9”); short getalShort = short.Parse(“9”); ushort getalUshort = ushort.Parse(“9”); float getalFloat = float.Parse(“9”); int getalInt = int.Parse(“9”); double getalDouble = double.Parse(“9”); decimal getalDecimal = decimal.Parse(“9”);

N

– – – – – – – – – –

EP

Hiervoor gebruik je in C de Parse-methode. Deze methode is beschikbaar bij iedere getalvariabele. Bij deze methode geef je tussen haakjes de string-variabele aan die omgezet moet worden naar een getal. Dus:

O

Voorbeeld Tekst naar getal Instructie

Voorbereiden Maak een console-app aan met de naam TekstNaarGetal.

C 1

Aanmaken variabelen 2 Maak twee variabelen aan: – één met de naam getalInt en het type int, – één met de naam getalAlsTekst en het type string. // Declaratie van variabelen int getalInt = 0; string getalAlsTekst = “0”;

Inlezen gebruikersinput 3 Wat de gebruiker invoert op het scherm is altijd van het type string. Voeg de volgende regels toe:


36

Deel 1: Introductie

// Invoer getalAlsTekst = Console.ReadLine();

Omzetten van tekst naar getal 4 Om te kunnen rekenen met een getal moet de variabele van een type zijn waarmee je kunt rekenen. In dit geval willen we de invoer met 100 vermenigvuldigen. Voeg de volgende regels toe: // Omzetting van tekst naar getal getalInt = int.Parse(getalAlsTekst);

N

C

EP

T

Let op! Als je hier iets invoert dat niet omgezet kan worden naar een getal zal je programma crashen. Later zul je leren hoe je zo’n fout kunt opvangen. Een dergelijke fout ziet er als volgt uit:

O

Schermafbeelding 15: Foutmelding verkeerd format

Uitvoer 5 Voeg de volgende regels toe:

C

// Uitvoer Console.WriteLine(getalInt + “ x 100 = ” + getalInt * 100);

Afsluiten 6 Voeg de volgende regels toe: // Afsluiten Console.ReadKey();

Test 7 Test je programma. Als je 1314 invoert moet het resultaat er als volgt uitzien:


Hoofdstuk 3 Variabelen

37

Schermafbeelding 16: Omzetting van getal naar tekst

Van teken naar getal

Voorbeeld Teken naar getal Instructie

Voorbereiden Maak een console-app aan met de naam TekenNaarGetal.

C

1

EP

T

Je kunt ook een teken, dus een variabele van het type char, omzetten naar een getal. Dit doe je door een zogenaamde cast. Door te casten zeg je eigenlijk: ik heb iets van het type char, maar ik wil dat C er een getal van maakt. De programmeurs van C hebben bedacht dat als je een getal maakt van een teken, dat getal de code is uit de ASCII-tabel. De letter A heeft als code 97. Als je A dus omzet (cast) naar een geheel getal krijgt die variabele de waarde 97.

N

Aanmaken variabelen 2 Voeg de volgende regels toe om de benodigde variabelen aan te maken (declareren):

O

//Declaratie variabelen int asciiCode = 0; char teken;

C

Invoer Om alleen één teken in te lezen kunnen we de methode ReadKey() van Console gebruiken. We moeten dan nog wel aangeven welke eigenschap we van het type char willen gebruiken. In dit geval willen we de eigenschap KeyChar hebben, dat is het teken zelf. Dit keer voegen we ook een regeltje toe dat de gebruiker duidelijk maakt wat er moet gebeuren.

3 Voeg de volgende regels toe: // Invoer Console.WriteLine(“Voer 1 teken in: ”); teken = Console.ReadKey().KeyChar;

Algoritme 4 Nu gaan we een char omzetten naar een int met behulp van een cast. Voeg de volgende regels toe:


38

Deel 1: Introductie

// Algoritme, omzetten (cast) van char naar int. asciiCode = (int)teken;

Uitvoer 5 De volgende regels geven de uitvoer aan. Door een extra Console.WriteLine() op te nemen krijgen we een extra, lege regel. // Uitvoer Console.WriteLine(); Console.WriteLine(“Het ingevoerde teken heeft als ASCII-code ” + asciiCode);

Afsluiten 6 Voeg de volgende regels toe:

T

// Afsluiten Console.ReadKey();

C

EP

Test 7 Test je programma. Als je Z invoert moet het resultaat er als volgt uitzien:

N

Schermafbeelding 17: Omzetting van teken naar getal

O

Van getal naar teken

C

We hebben net een teken vertaald naar de bijbehorende ASCII-waarde. Omgekeerd kun je ook een getal omzetten naar het bijbehorende teken uit de ASCIItabel. Let op! Als je een getal gebruikt dat niet in de ASCII-tabel voorkomt krijg je geen foutmelding maar een vraagteken te zien.

Voorbeeld van Getal naar teken Instructie 1

Voorbereiden Maak een console-app aan met de naam VanGetalNaarTeken.

Declaratie variabelen 2 Voeg de volgende regels toe om de benodigde variabelen te declareren:


Hoofdstuk 3 Variabelen

39

//Declaratie variabelen int asciiCode = 0; char teken;

Invoer 3 Voeg de volgende regels toe die de invoer (gebruikersinteractie) regelen: // Invoer Console.WriteLine(“Voer een getal in: ”); asciiCode = int.Parse(Console.ReadLine());

Algoritme 4 Voeg de volgende regels toe die met behulp van een cast een int omzetten naar een char.

EP

Uitvoer 5 Voeg de volgende regels toe:

T

// Algoritme, omzetten (cast) van int naar char. teken = (char)asciiCode;

C

// Uitvoer Console.WriteLine(); Console.WriteLine(“De ingevoerde ASCII-code hoort bij het teken: ” + teken);

Afsluiten 6 Voeg de volgende regels toe:

N

// Afsluiten Console.ReadKey();

C

O

Test 7 Test je programma. Als je 51 invoert moet het resultaat er als volgt uitzien:

Schermafbeelding 18: Omzetting van getal naar teken

Het kan ook zijn dat je niets ziet als je test met bepaalde getallen: dat zijn de zogenaamde stuurtekens of unprintable characters. Het woord zegt het al: deze tekens zijn niet te printen, je kunt ze dan ook niet op een beeldscherm laten zien.


40

Deel 1: Introductie

Voorbeeld Celsius naar Fahrenheit In dit voorbeeld gaan we een console-app maken die de temperatuur van graden Celsius omrekent naar de temperatuur in graden Fahrenheit. (De Fahrenheitschaal wordt officieel in vijf landen gebruikt. Dit zijn de Bahama’s, Belize, de Kaaimaneilanden, Palau en de Verenigde Staten.) Instructie 1

Voorbereiden Maak een console-app aan met de naam CelsiusNaarFahrenheit.

EP

// Declaratie van variabelen double celsius = 0; double fahrenheit = 0;

T

Aanmaken variabelen 2 Maak twee variabelen aan van het type double, één met de naam celsius en één met de naam fahrenheit.

N

C

Inlezen gebruikersinput 3 Wat de gebruiker invoert op het scherm is altijd van het type string. Een string moet vertaald worden naar het type double. Dit doe je met de methode Parse die bij het type double hoort: double.Parse(...) Tussen de haakjes plaats je dan een stuk tekst of zoals in dit geval Console.ReadLine() dat de invoer van het toetsenbord inleest en er een string-variabele van maakt. Voeg de volgende regels toe:

O

// waarde toekennen aan variabele celsius = double.Parse(Console.ReadLine());

C

Berekenen 4 Nu kunnen we de berekening uitvoeren. De formule om van graden Celsius te komen tot graden Fahrenheit is de volgende: °F = °C x 1,8 + 32 Voeg de volgende regels toe: // Berekening // Het aantal graden Fahrenheit = graden Celsius keer 1,8 plus 32 fahrenheit = celsius * 1.8 + 32;

In het commentaar schrijven we de formule uit zodat iemand anders kan zien of datgene wat we geprogrammeerd hebben ook de bedoeling is. Output 5 We hebben nu Celsius omgerekend naar Fahrenheit. Nu moeten we het resultaat nog laten zien aan de gebruiker.


Hoofdstuk 3 Variabelen

41

Voeg de volgende regels toe: // Output Console.WriteLine(celsius.ToString() + “ ” + (char)0176 + “C is gelijk aan: ” + fahrenheit + “ ” + (char)0176 + “F”);

Afsluiten 6 Zorg ervoor dat de console-app blijft wachten op een actie van de gebruiker. Voeg de volgende regel toe: Console.ReadKey();

EP

T

Test 7 Test je programma. Als je 21 invoert moet je het volgende resultaat krijgen:

Schermafbeelding 19: Celsius naar Fahrenheit

C

Als je de outputregel bekijkt, dan zie je misschien iets raars:

Console.WriteLine(celsius.ToString() + “ ” + (char)0176 + “c is gelijk aan: ” +

N

fahrenheit + “ ” + (char)0176 + “F”);

Bij de variabele celsius wordt de methode ToString() aangeroepen, bij de variabele fahrenheit niet.

C

O

Als je bij beide variabelen de ToString()-methode aanroept zal het resultaat hetzelfde blijven. Als je ToString() achter de variabele celsius weghaalt en je haalt de code + “ ” die een spatie toevoegt weg, dan krijg je echter een heel ander resultaat. Dit komt omdat de compiler (vertaler) die jouw code vertaalt naar machinetaal van links naar rechts werkt. Als er een variabele staat van het type double (of een andere getalvariabele) en de eerstvolgende variabele levert iets op dat ook vertaald kan worden naar een getal, dan doet de vertaler dat. Pas daarna wordt er een string (tekst) van gemaakt. Je zult in dit geval zien dat eerst de inhoud van de variabele bij het getal 0176 wordt opgeteld. Als je in dit geval 21 invoert krijg je het volgende resultaat:


42

Deel 1: Introductie

Schermafbeelding 20: Celsius naar Fahrenheit fout

Logische fout

Syntactische fout

EP

T

Je ziet, 21 + 176 = 197, het programma heeft dus exact gedaan wat je gevraagd hebt, het is alleen fout. Dit noemen we een logische fout. Logische fouten zijn vervelend omdat ze de gebruiker op het verkeerde been zetten. De getoonde informatie is fout maar de computer rekent het goed!

C

Een syntactische fout gaat over de structuur van de taal. Als je daar fouten in maakt geeft Visual Studio allerlei waarschuwingen en foutmeldingen. Je kunt je programma dan ook niet uitvoeren. Syntactische fouten zijn vervelend voor de programmeur, maar de gebruiker heeft er (meestal) geen last van.

N

Opdracht Variabelen 4: Fahrenheit naar Celsius

O

Maak een console-app waarbij je als invoer de temperatuur in graden Fahrenheit geeft en als uitvoer de temperatuur in graden Celsius.

C

Als test kun je het programma CelsiusNaarFahrenheit gebruiken; de uitvoer van dat programma moet als je dat getal invoert in het programma FahrenheitNaarCelsius de invoer van het programma CelsiusNaarFahrenheit opleveren.

Logische variabele

De bool is een echte computervariabele. Een bool is een variabele die slechts twee waarden kan bevatten: waar of niet waar. Deze variabelen worden gebruikt om het resultaat van een ja/nee-vraag te bewaren. Bijvoorbeeld: ‘Is de invoer kleiner dan 10?’ Afhankelijk van de ingevoerde waarde wordt het resultaat dan waar (true) of niet waar (false). Dit noemen we ook wel een logische test. In C ziet dat er zo uit: bool logischeTest = true;


Hoofdstuk 3 Variabelen

43

DatumTijd-variabele Datum- en tijdvariabelen worden veel gebruikt. Denk aan geboortedata, leeftijden, maar ook de dag dat iemand geslaagd is, de tijdsregistratie bij sportevenementen of de betaaltermijnen bij facturen. Het DateTime-type loopt van 1 januari 0001 middernacht (00:00:00 uur) tot 31 december 9999 middernacht (11:59:59 uur). De tijd wordt gemeten in eenheden van 100 nanoseconden, die we ‘ticks’ noemen. Er gaan 10 miljoen ticks in een seconde! Een datum is eigenlijk het aantal ticks dat sinds 1 januari 0001 is verstreken. Dus dat zijn er ergens tussen de 600 en 700 biljard inmiddels. (Een biljard is een 1 met 15 nullen... 1.000.000.000.000.000!)

EP

T

Bij het DateTime-type horen veel eigenschappen en methoden; zo zijn er methoden om de dag van het jaar aan te geven, of alleen het jaar, methoden om te bepalen of een jaar een schrikkeljaar is, om aan te geven welke dag van de week het is, en nog veel meer: DateTime vandaag = DateTime.Now; int jaar = vandaag.Year;

Maandnaam

C

bool schrikkeljaar = DateTime.IsLeapYear();

O

N

Je kunt een maand op verschillende manieren weergeven: als maandnaam, als cijfer of als afkorting. Binnen C doe je dit door binnen de ToString()-functie met een aantal M’s het format weer te geven. Zo betekent DateTime.Now.ToString(“M”) dat je de maand als enkel getal krijgt. December wordt dus 12. Je kunt de volgende formats hanteren. Als voorbeeld nemen we de maand juni: Format

Voorbeeld januari

Maandnummer; 1 t/m 12

“M”

1

Maandnummer, altijd twee cijfers; 01 t/m 12

“MM”

01

Drieletterige afkorting van de maandnaam

“MMM”

jan

Volledige maandnaam

“MMMM”

Januari

C

Omschrijving

Rekenen met datums Ook met datums kun je rekenen. Zo kun je dagen bij een datum optellen of aftrekken. Ook kun je datums van elkaar aftrekken. Maar wat krijg je dan? Krijg je dan het aantal jaar als gebroken getal of het aantal maanden? Nee, je krijgt als resultaat een variabele van het type TimeSpan. In deze variabele zit het aantal dagen, minuten en seconden opgeslagen van het verschil.


44

Deel 1: Introductie

Als je het aantal jaar wilt weten dan zul je TimeSpan om moeten rekenen naar het aantal jaar door bijvoorbeeld te delen door 365,25 (je houdt dan meteen rekening met de schrikkeljaren).

Voorbeeld Datum We gaan een programma maken dat verschillende onderdelen van de datum van vandaag laat zien. Instructie 1

Voorbereiding Maak een nieuwe console-app aan met de naam DatumVoorbeeldconsole.

EP

T

Declaratie variabelen 2 Een datum bestaat uit een aantal onderdelen: de dag, de maand en het jaar. Maar dagen en maanden kennen zowel een nummer als een naam. Voeg de volgende regels toe om voor de verschillende onderdelen een variabele aan te maken:

N

C

// Declaratie variabelen DateTime vandaag = DateTime.Now; int jaar = vandaag.Year; string dagnaam = vandaag.DayOfWeek.ToString(); int maand = vandaag.Month; string maandNaam = vandaag.ToString(“MMMM”); string maandAfkorting = vandaag.ToString(“MMM”); DateTime geboortedatum = vandaag; double leeftijdInJaren = 0;

C

O

Invoer 3 We vragen de gebruiker om zijn of haar geboortedatum in te voeren. We geven wel het juiste format aan. Als de gebruiker een ander format gebruikt dat de computer niet herkent dan krijgen we een foutmelding. Voeg de volgende regels toe: // Invoer Console.WriteLine(“Voer je geboortedatum in, in het format dd-mm-jjjj”); geboortedatum = DateTime.Parse(Console.ReadLine());

Algoritme 4 Voeg de volgende regels toe om de leeftijd te berekenen: // Algoritme: berekenen leeftijd // Om de leeftijd uit te rekenen heb je een variabele nodig van // het type TimeSpan. Dit type kent geen jaren. Wil je het aantal jaren // uitrekenen, dan zul je het aantal dagen door 365 moeten delen. En als je het // helemaal goed wilt doen moet je ook nog rekening houden met schrikkeljaren. // Dan deel je door 365,25. TimeSpan leeftijd = DateTime.Now - geboortedatum; leeftijdInJaren = leeftijd.Days / 365.25;


Hoofdstuk 3 Variabelen

45

Uitvoer 5 We gaan nu de gebruiker uitgebreide informatie geven over de dag van vandaag en zijn of haar leeftijd. Voeg de volgende regels toe: // Uitvoer Console.WriteLine(“Het is vandaag ” + dagnaam + “ ”+ vandaag + “ van de maand ” + maandNaam + “ in het jaar ” + jaar); Console.WriteLine(“De maand ” + maand + “ (“ + maandNaam + “) wordt afgekort weergegeven als: ” + maandAfkorting); Console.WriteLine(“Je bent ” + leeftijdInJaren + “ jaar.”);

T

Afsluiten 6 Zorg ervoor dat de console-app blijft wachten op een actie van de gebruiker. Voeg de volgende regel toe: Console.ReadKey();

N

C

EP

Test 7 Test je programma. Afhankelijk van de datum zal er uiteraard steeds een andere uitkomst staan. Op 3 oktober 2020 en een geboortedatum van 13-06-1962 krijg je het volgende scherm te zien:*

O

Schermafbeelding 21: Datum/tijd-voorbeeldconsole

C

Bereik van variabelen Een C-programma wordt ingedeeld in kleinere programma’s (methoden) met behulp van accolades: { }.

Een variabele is slechts bekend binnen de accolades waartussen de variabele is gedefinieerd. Dit noemen we het bereik van een variabele.

* Zoals je wellicht ziet worden sommige woorden zoals de weekdagen en maanden in het Engels weergegeven. Ook worden zowel de datum als de tijd weergegeven. Natuurlijk kan dit anders. Maar het voert te ver om dat nu al uit te leggen.


46

Deel 1: Introductie

Globale variabele Een variabele die direct achter de regel wordt gedefinieerd waarin het woord class staat (bovenaan in het programma) noemen we een algemene variabele of vanwege het Engelse ‘global’ ook wel globale variabele. Deze kun je bij alle onderliggende programmaonderdelen (methoden) gebruiken.

Lokale variabele Een variabele die binnen een methode wordt gedefinieerd is een lokale variabele. Bijvoorbeeld:

EP

public FrmMeetkundigeFiguren() { InitializeComponent();

T

public partial class FrmMeetkundigeFiguren: Form { int straal = 0;

DateTime vandaag = DateTime.Now; } }

N

C

De variabele straal is hier een globale variabele. De variabele vandaag is een lokale variabele. Voorlopig hebben wij alleen met lokale variabelen te maken.

O

Opdrachten Variabelen

Opdracht Variabelen 5: Bereken oppervlak

C

Maak een console-app waarmee je de oppervlakte van een rechthoek uitrekent. De oppervlakte bereken je door lengte met breedte te vermenigvuldigen (lengte x breedte = oppervlak). Uiteraard zorg je voor passend commentaar en duidelijke teksten bij zowel in- als uitvoer.

Opdracht Variabelen 6: Bereken omtrek Maak een console-app waarmee je de omtrek van een rechthoek uitrekent. De omtrek bereken je door twee keer de lengte op te tellen bij twee keer de breedte (2 x lengte + 2 x breedte = omtrek). Uiteraard zorg je voor passend commentaar en duidelijke teksten bij zowel in- als uitvoer.


Hoofdstuk 3 Variabelen

47

Opdracht Variabelen 7: Hoelang nog? Maak een console-app die berekent hoeveel dagen het nog duurt voor de zomervakantie begint. Vraag aan de gebruiker de eerste dag van de zomervakantie en laat het programma dan het aantal dagen tot de zomervakantie teruggeven.

Opdracht Variabelen 8: Euro naar ponden 1 Maak een console-app waar de gebruiker een bedrag in euro’s invoert en er als output dezelfde waarde in Engelse ponden ( = Alt 0163) verschijnt. Op het moment dat dit boek geschreven werd was 1 euro 0,91 pond waard.

Opdracht Variabelen 9: Euro naar ponden 2

EP

T

Voeg nu aan de console-app waarmee je euro’s naar ponden omrekent de wisselkoers als variabele toe (in plaats van als vast getal).

Opdracht Variabelen 10: Verbruik

C

Maak een console-app waarbij het verbruik van een brandstofauto wordt berekend. De volgende variabelen worden hierbij aan de gebruiker gevraagd: – Afstand die gereden is – Aantal liters brandstof dat daarvoor verbruikt is

N

Het verbruik wordt aangegeven in aantal liters per 100 km.

Opdracht Variabelen 11: Vermogen elektrische apparaten berekenen *

C

O

Het vermogen van een elektrisch apparaat bereken je door stroomsterkte en spanning met elkaar te vermenigvuldigen. In Nederland is de spanning meestal 240 volt. Maak een console-app die op basis van de gegeven stroomsterkte in ampère het vermogen in kW (kilowatt) uitrekent. De formules hiervoor kun je op het internet vinden.

Opdracht Variabelen 12: Verbruik elektrische apparaten berekenen * Maak een console-app die het verbruik van een elektrisch apparaat in kWh (kilowattuur) berekent. De gevraagde invoer is het vermogen van het elektrisch apparaat en het aantal uren dat het apparaat gebruikt wordt.

Opdracht Variabelen 13: Golfsnelheid * De formule die de samenhang tussen golflengtefrequentie en snelheid weergeeft ziet er als volgt uit:


48

Deel 1: Introductie

Maak een console-app die gegeven de golflengte en de frequentie van de golf (hoe lang duurt het voordat de volgende golf komt) de golfsnelheid uitrekent.

Opdracht Variabelen 14: Het gulden getal uitrekenen *

EP

T

Maak een console-app die het gulden getal van een jaar berekent. Het gulden getal van een jaar is de rest die we overhouden als we het jaartal door 19 (het aantal maanmaanden in een maanjaar) delen, en deze rest met 1 wordt vermeerderd. Het gulden getal van 1900 is dus 1, en het gulden getal van 1983 is 8. Dit gulden getal speelt een belangrijke rol bij de bepaling van de datum waarop eerste paasdag valt.

N

C

De invoer van deze app is een datum waarvan je het gulden getal gaat bepalen, de uitvoer is het gulden getal.

Wat moet je nu kunnen en kennen?

C

O

In deze les heb je geleerd hoe je variabelen in C gebruikt. Daarin werden de volgende zaken uitgelegd: – ASCII-tabel – Verschillende typen variabelen – Het verschil tussen: string, int, double, char, byte, ulong, short, bool, DateTime en TimeSpan


49

C

O

N

C

EP

– Het omzetten van: een string naar een getal, iets naar een string, een char naar een getal en een getal naar een char – DateTimevariabele: Rekenen met datums Weergeven van datums – Het weergeven van bijzondere tekens zoals ° – Logische fout – Syntactische fout – Opslag van variabelen in het geheugen – Geheugenbeslag van variabelen – Algoritme – Lokale variabele – Globale variabele – Bereik van variabelen – Het nut van variabelen

T

Hoofdstuk 3 Variabelen


50

Hoofdstuk 4 Selectie

Wat is selectie?

EP

Hoe maak je een selectie: if

T

Bij een selectie laat je het programma een beslissing nemen. Bijvoorbeeld bij een examenuitslag: als het gemiddelde cijfer van een reeks toetsen lager is dan 5,5 dan wordt de uitslag ‘gezakt’. Een selectie heet ook wel een als-dan-constructie. In het Engels: if-(then-)else. Selecties komen binnen programma’s vaak voor.

In C ziet een selectie-statement (stukje programmacode) er als volgt uit:

C

if (bewering == waar) { ... programmaregels ... }

N

Waarbij de bewering een wiskundige vergelijking is: twee variabelen worden met elkaar vergeleken. Er zou dus bijvoorbeeld kunnen staan:

O

10 - 4 > 5 levert ‘waar’ (true) op 10 - 6 > 5 levert ‘niet waar’ op (false) 10 - x > 5 levert nog niets op, want we kennen x (nog) niet

C

Let op! Je kent het symbool = vanuit het rekenen en de wiskunde. Daar betekent = is gelijk aan: 6 = 5 + 1 betekent: zes is gelijk aan vijf plus een. == is het symbool binnen C om te onderzoeken of twee waardes gelijk zijn aan elkaar. In C (en in de meeste programmeertalen) betekent = echter ‘krijgt de waarde’. De code a = 7 betekent dus bij het programmeren: de variabele a krijgt de waarde 7. Het gevolg hiervan is dat a gelijk is aan 7. En of dit gelukt is controleer je door de regels uit te voeren: if (a == 7) { Console.WriteLine(“a is gelijk aan 7”); } == kan dan ook alleen voorkomen bij een selectie. Het is een stelling of een vraag: is a gelijk aan 7?


Hoofdstuk 4 Selectie

51

Voorbeeld Selectie We gaan een programma maken dat bekijkt of een ingevoerd cijfer voor schoolwerk tussen de 1 en de 10 ligt. Instructie 1

Voorbereiden Maak een console-app aan met de naam CijferCheck.

Declaratie variabelen 2 Voeg de volgende regels toe om de benodigde variabelen te declareren:

T

// Declaratie variabelen double cijfer = 1; string uitvoerTekst = “Het ingevoerde cijfer is correct.”;

EP

Invoer 3 Voeg de volgende regels toe om een cijfer in te kunnen voeren: // Invoer Console.WriteLine(“Voer hier een beoordelingscijfer in:”); cijfer = double.Parse(Console.ReadLine());

C

Algoritme 4 Vervolgens voegen we de regels toe die checken of het ingevoerde getal kleiner is dan 1:

C

O

N

// Algoritme // Bepalen of het cijfer onder de ondergrens zit. if (cijfer < 1) { uitvoerTekst = “Het ingevoerde cijfer (“ + cijfer + “) is kleiner dan 1, ” + “het ingevoerde cijfer moet tenminste 1 zijn.”; }

Zoals je ziet wordt er ingesprongen bij het if-statement. Voor de computer maakt het niets uit of je al je commando’s op één lange regel zet, de werking blijft hetzelfde. Toch is er een afspraak dat we in dit soort gevallen altijd inspringen. Doe je dit niet, dan kun je niet aan het werk als professionele programmeur, omdat een ander jouw code dan niet goed kan lezen.

5 Je gaat nu de tweede conditie (zo noemen we een if-statement ook wel) toevoegen. Voeg de volgende regels toe: // Bepalen of een cijfer boven de bovengrens zit. if (cijfer > 10 ) { uitvoerTekst = “Het ingevoerde cijfer (“ + cijfer + “) is groter dan 10, ” + “het ingevoerde cijfer moet kleiner of gelijk zijn aan 10.”; }


52

Deel 1: Introductie

Uitvoer 6 Voeg de volgende regels toe: // Uitvoer Console.WriteLine(uitvoerTekst);

Afsluiten 7 Voeg de volgende regels toe: //Afsluiten Console.ReadKey();

T

Test 8 Uiteraard test je je programma voor drie situaties: cijfer kleiner dan 1, een cijfer tussen de 1 en de 10, en een cijfer groter dan 10.

EP

Als je het cijfer 10,1 invoert moet je scherm er als volgt uitzien:

C

Schermafbeelding 22: Voorbeeld cijfercontrole

Opdracht Selectie 1: Leeftijdchecker 1

C

O

N

Maak een console-app met een invoerveld voor de geboortedatum. Op basis van de geboortedatum wordt uitgerekend hoe oud iemand is. Als de berekende leeftijd groter of gelijk is dan 18, toon dan als uitvoer de tekst: ‘Drink bier in plaats van water, dan heb je morgen een flinke kater.’ Als de berekende leeftijd kleiner is dan 18 dan geef je de tekst: ‘Je mag nog geen alcohol drinken.’

Hoe maak je een selectie: if ... else

De vorige opdracht heb je wellicht opgelost met twee if-statements. Dit kan eenvoudiger. Als iemand ouder is dan 18, is hij nooit tegelijkertijd jonger dan 18. Dit noemen we twee situaties die elkaar uitsluiten. We kunnen in dit geval een if...else-combinatie gebruiken. Dat betekent: als de voorwaarde bij het if-statement niet waar is, dan wordt automatisch het else-gedeelte uitgevoerd. if (bewering == waar) { ... programmaregels ... } else { ... programmaregels ... }


Hoofdstuk 4 Selectie

53

Opdracht Selectie 2: Leeftijdchecker 2 Pas de leeftijdchecker zo aan dat je gebruikmaakt van een else-statement.

Opdracht Selectie 3: Vriezen of dooien Maak een programma dat checkt of het vriest of dooit op basis van de ingevoerde temperatuur. Maak gebruik van een else-statement.

Nesten

EP

T

De selectie-statements if en else kun je ook ‘nesten’. Dat betekent niets anders dan dat je op de plek van de programmaregels gewoon weer een if ... else-statement kunt gebruiken. Binnen de selectie mag je wéér een selectie uitvoeren.

Voorbeeld Selectie : Beslisboom verkoudheid

C

O

N

C

Een beslisboom is een schematische weergave van beslissingen. In het schema staan de voorwaarden en als je daar aan een voorwaarde voldoet ga je naar het volgende item. Zo’n beslisboom kun je eigenlijk zo overnemen in je programma.


Deel 1: Introductie

C

EP

T

54

Instructie

Voorbereiden Maak een console-app aan met de naam BeslisboomVerkoudheid.

O

1

N

Figuur 8: Beslisboom verkouden kind (bron: www.boink.nl)

C

Declaratie variabelen 2 Van iedere vraag is een variabele gemaakt. Voordeel hiervan is dat ze overzichtelijk bij elkaar staan én dat de vraag die twee keer gebruikt wordt slechts één keer ingegeven hoeft te worden. Voeg de volgende regels toe: // Declaratie variabelen string vraag1 = “Is het kind neusverkouden en/of heeft het een loopneus?”; string vraag2 = “Heeft het kind in de afgelopen 10 dagen (nauw) contact gehad ” + “met iemand (binnen of buiten het huishouden van het kind) die corona ” + “heeft?”; string vraag3 = “Heeft iemand binnen het huishouden van het kind” + “(waartoe ” + “ook het kind zelf behoort) op dit moment corona, koorts (38,0 of ” + “hoger) of benauwdheid?”; string vraag4 = “Is er sprake van (meer dan incidenteel) hoesten en/of ” + “plotseling verlies van reuk/smaak?”;


Hoofdstuk 4 Selectie

55

string vraag5 = “Past het bij de gebruikelijke / bekende klachten, zoals ” + “hooikoorts of astma?”; char antwoord = ‘n’;

Input / Algoritme 3 Nu gaan we iets anders doen dan in de vorige opdrachten. Omdat de input direct bepaalt welke vraag als volgende gesteld wordt, is hier de input als het ware verweven met het algoritme. Er zijn betere constructies, maar om het begrip nesten te ervaren is dit een goede manier. Overigens is het raadzaam om zo min mogelijk te nesten. Met elk niveau van nesten neemt de complexiteit van je programma toe. En een goed programma is de eenvoud zelve.

EP

// Input Console.WriteLine(vraag1); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘j’) { } else if (antwoord == ‘n’) {

T

Voeg de volgende regels toe om de eerste vraag te stellen en het antwoord op te halen:

C

}

N

Op zich een heldere structuur, maar je zult al vlug merken dat, ook al wordt de structuur slechts herhaald, het al snel best complex wordt.

O

Hier wordt bewust gebruikgemaakt van de if...else-structuur omdat een gebruiker ook iets anders kan invoeren dan een ‘j’ of ‘n’. Dit zal later afgevangen worden.

C

4 Nu gaan we tussen de accolades achter (antwoord == ‘j’) de regels die betrekking hebben op vraag 2 toevoegen: Console.WriteLine(); Console.WriteLine(vraag2); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘n’) { }

We hoeven ons hier niet druk te maken als er ‘j’ is ingevuld, want er komen geen vragen meer. Alleen in geval van ‘n’ komt er een vervolgvraag. 5 Nu gaan we tussen de accolades achter (antwoord == ‘n’) van vraag 2 de regels die betrekking hebben op vraag 3 toevoegen:


56

Deel 1: Introductie

Console.WriteLine(); Console.WriteLine(vraag3); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘n’) { }

Ook hier geen ‘j’-optie, om dezelfde reden.

Console.WriteLine(); Console.WriteLine(vraag4); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘n’) { }

T

6 Ook in dit gedeelte van de boom gaan we tussen de accolades achter (antwoord == ‘n’) van vraag 3 de regels die betrekking hebben op vraag 4 toevoegen:

EP

7 Als gedeelte van de boom gaan we tussen de accolades achter (antwoord == ‘n’) van vraag 4 de regels die betrekking hebben op vraag 5 toevoegen: Console.WriteLine(); Console.WriteLine(vraag5); antwoord = Console.ReadKey().KeyChar;

C

Nu volgt er geen conditie (= voorwaarde) meer. Dit is de laatste vraag in dit gedeelte van de boom die beantwoord wordt.

O

N

8 Nu gaan we naar het andere gedeelte, het ‘n’-gedeelte van de eerste vraag. Als je overzicht wilt houden kun je links in de kantlijn van Visual Studio diverse stukjes van de boom dichtklikken. En als je ze weer nodig hebt klik je ze weer open. Voeg ook hier tussen de accolades de volgende regels toe:

C

Console.WriteLine(); Console.WriteLine(vraag3); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘n’) { }

9 En voeg vervolgens tussen de accolades bij het ‘n’-gedeelte van de derde vraag de volgende regels toe: Console.WriteLine(); Console.WriteLine(vraag4); antwoord = Console.ReadKey().KeyChar; if (antwoord == ‘n’) { }


Hoofdstuk 4 Selectie

57

10 Ook hier, in het laatste deel van de beslisboom, moet vraag 5 nog tussen de accolades bij het antwoord == ‘n’ worden toegevoegd: Console.WriteLine(); Console.WriteLine(vraag5); antwoord = Console.ReadKey().KeyChar;

Output 11 Afhankelijk van de waarde van het antwoord – ‘j’, ‘n’ of iets anders – wordt de output gegenereerd. Voeg de volgende regels toe:

C

EP

T

// Output Console.WriteLine(); if (antwoord == ‘j’) { Console.WriteLine(“Kind blijft thuis.”); } else if (antwoord == ‘n’) { Console.WriteLine(“Kind mag komen.”); } else { Console.WriteLine(“Ongeldige invoer gegeven (“ + antwoord + “), invoer kan alleen j of n zijn.”); }

12 Voeg toe als laatste regel:

N

Console.ReadKey()

O

13 Test je programma samen met iemand anders. Neem de beslisboom erbij en onderzoek of het programma dezelfde vragen en uitkomsten laat zien als de beslisboom.

C

Opdracht Selectie 4: Nesten Werk met het formulier van Opdracht Selectie 2. Vraag aan de meisjes die jonger zijn dan 18 wat ze dan wél willen drinken. Zeg tegen de jongens dat ze pech hebben en vandaag alleen nog maar water krijgen. Je krijgt nu binnen de if ... else-code wéér een if ... else-gedeelte. Je hebt hiervoor het volgende if-statement nodig: Console.WriteLine(“Geslacht?”); string geslacht = Console.ReadLine(); if (geslacht == “jongen”) ...


58

Deel 1: Introductie

Opdracht Selectie 5: AddYears Bedenk hoe je de jongens kunt vertellen op welke datum ze wél alcohol mogen drinken. Gebruik de methode AddYears() als volgt: Console.WriteLine (“Je mag pas alcohol drinken vanaf ” + geboortedatum.AddYears(18).ToShortDateString());

Let op! Zie je dat je, als je een punt typt, de verschillende mogelijkheden al te zien krijgt? Deze puntnotatie is een handig hulpmiddel van Visual Studio. De code voor de punt noemen we objecten of classes. Na de punt komen de eigenschappen van het object, zonder haakjes, en de methoden (tussen haakjes).

T

Opdracht Selectie 6: Aanvragen vergunning

C

O

N

C

EP

Maak een console-app om een vergunning aan te vragen voor het plaatsen van een reclamebord bij de gemeente volgens onderstaande beslisboom:


59

O

N

C

EP

T

Hoofdstuk 4 Selectie

C

Figuur 9: Beslisboom aanvragen vergunning (bron: reclamebeleid Gemeente Sittard-Geleen)


60

Deel 1: Introductie

Opdracht selectie 7: Reisbeleid

O

N

C

EP

T

Maak een programma op basis van onderstaande beslisboom:

C

Figuur 10: Beslisboom reisbeleid (bron: rug.nl University of Groningen)

Opdracht Selectie 8: Motorrijbewijs * Het motorrijbewijs is verdeeld in drie categorieën: A1, A2 en A3. De verschillende categorieën kennen de volgende eisen: A1: minimumleeftijd 18 jaar, verder geen eisen A2: minimumleeftijd 20 jaar en je moet twee jaar in het bezit zijn van rijbewijs A1 A3: minimumleeftijd 22 jaar en je moet twee jaar in het bezit zijn van rijbewijs A2 Maak eerst een beslisboom. Maak vervolgens een console-app met invoer voor geboortedatum, rijbewijs en de datum waarop je dat rijbewijs eventueel behaald hebt. Op basis van het rijbewijs dat je hebt en je leeftijd bepaalt het programma op welke datum je het volgende rijbewijs kunt behalen. Zorg voor de juiste uitvoer.


Hoofdstuk 4 Selectie

61

Opdracht Selectie 9: Soort motor * Motoren zijn ingedeeld in klassen. Hoe groter het vermogen, hoe sneller de motor is. Maar er wordt ook gekeken naar het gewicht (een zwaardere motor is moeilijker te hanteren) en de cilinderinhoud (een grotere inhoud betekent een sterkere motor). Het rijbewijs bepaalt op welke motor je mag rijden. Maak een programma dat op basis van het vermogen van het voertuig in kilowatt, het aantal wielen, het gewicht, de cilinderinhoud én het geboortejaar van de bestuurder bepaalt welk type motor je mag rijden:

Rijbewijs A2: Motorrijwiel

EP

T

Rijbewijs A1, twee soorten voertuigen: Motorrijwiel maximaal 11 kW, maximaal 125 cc, maximaal 0,1 kW per kilo ledig gewicht Gemotoriseerde driewieler maximaal 15 kW 30 tot 35 kW, maximaal 0,2 kW per kilo ledig gewicht onbeperkt vermogen onbeperkt vermogen

C

Rijbewijs A3: Motorrijwiel Gemotoriseerde driewieler

O

If ... else

N

Maak ook nu weer eerst een beslisboom.

C

Stel je hebt een programma nodig om de maaltijd van de dag te bepalen. Op zondag wordt er vegaburger gegeten, op maandag kip, op dinsdag schnitzel, op woensdag gehakt, op donderdag verse worst, op vrijdag vis en op zaterdag spek. Je voert de dag in en het programma laat zien wat er die dag gegeten wordt: // Declaratie variabelen: string uitvoer = “”; // Invoer vanaf het scherm string dagVanDeWeek = Console.ReadLine(); // Algoritme if (dagVanDeWeek == “zondag”) { uitvoer = “vegaburger”; } else if (dagVanDeWeek == “maandag”) { uitvoer = “kip”; }


Deel 1: Introductie

EP

else if (dagVanDeWeek == “dinsdag”) { uitvoer = “schnitzel”; } else if (dagVanDeWeek == “woensdag”) { uitvoer = “gehakt”; } else if (dagVanDeWeek == “donderdag”) { uitvoer = “verse worst”; } else if (dagVanDeWeek == “vrijdag”) { uitvoer = “vis”; } else if (dagVanDeWeek == “zaterdag”) { uitvoer = “spek”; }

T

62

// Uitvoer Console.WriteLine(“Het is vandaag ” + dagVanDeWeek + “ dus wij eten ” + uitvoer); // Afsluiten

C

Console.ReadKey();

N

Broncode 3

Opdracht Selectie 10: Maandnamen met if... else

O

Maak een programma dat op basis van de maand die je invoert kiest uit de volgende tabel om de uitvoer te geven: Wintermaand

Februari

Sprokkelmaand

Maart

Lentemaand

April

Grasmaand

C

Januari

Mei

Bloeimaand

Juni

Zomermaand

Juli

Hooimaand

Augustus

Oogstmaand

September

Herfstmaand

Oktober

Wijnmaand

November

Slachtmaand

December

Sneeuwmaand

Tabel 3: Elke maand een missie


Hoofdstuk 4 Selectie

63

Switch Een andere manier om een selectie te maken is met het switch-commando. Nadeel van het switch-commando is dat het niet dezelfde selectiemogelijkheden kent als de if ... else-structuur. Zo kun je geen gebruik maken van een voorwaarde groter dan of kleiner dan. Afhankelijk van de structuur van je programma is de ene keer het switch-commando en de andere keer de if ... else-constructie sneller of langzamer. Met switch ziet het weekmenu er als volgt uit:

// Invoer vanaf het scherm string dagVanDeWeek = Console.ReadLine();

C

O

N

C

EP

// Algoritme switch (dagVanDeWeek) { case “zondag” : { uitvoer = “vegaburger”; break; } case “maandag” : { uitvoer = “kip”; break; } case “dinsdag” : { uitvoer = “schnitzel”; break; } case “woensdag” : { uitvoer = “gehakt”; break; } case “donderdag” : { uitvoer = “verse worst”; break; } case “vrijdag” : { uitvoer = “vis”; break; }

T

// Declaratie variabelen: string uitvoer = “”;


64

Deel 1: Introductie

case “zaterdag” : { uitvoer = “spek”; break; } } // Uitvoer Console.WriteLine(“Het is vandaag ” + dagVanDeWeek + “ dus wij eten ” + uitvoer); // Afsluiten Console.ReadKey();

Broncode 4

T

Opdracht Selectie 11: Maandnamen met switch

EP

Schrijf het programma van Opdracht Selectie 10 nog een keer, maar maak nu gebruik van de switch ... case-structuur.

Wat moet je nu kunnen en kennen?

C

In dit hoofdstuk heb je geleerd hoe je selecties in C maakt. Daarin werden de volgende zaken uitgelegd:

C

O

N

– if – if ... else – switch – Beslisboom – Nesten – Begrippen true en false


65

Hoofdstuk 5 Herhaling

Wat is herhaling?

EP

T

Eén van de basispatronen in programmeren is de herhaling. Iedere programmeertaal kent de mogelijkheid om opdrachten te herhalen. Door een opdracht te herhalen is het niet nodig om dezelfde opdracht tien, twintig of duizend keer te programmeren. We kunnen beter één regel programmeren en deze tien, twintig of duizend keer herhalen. Als er namelijk iets gewijzigd moet worden aan deze regel dan moeten we in het eerste geval misschien wel duizend keer dezelfde wijziging doorvoeren. In het tweede geval hoeven we maar één wijziging door te voeren.

C

De code voor een nieuwe regel is “\r\n”. Dit is een zogenaamde escape sequence. Een escape sequence begint altijd met een \ gevolgd door een letter, de combinatie wordt altijd in lila weergegeven. In plaats van “\r\n” kun je ook het commando System.Environment.NewLine gebruiken. System.Environment.NewLine is in principe platformonafhankelijk. “\r\n” werkt alleen

N

op het Windows-platform. (Bij Unix is de code voor een nieuwe regel “\u” en bij Mac “\r”.)

O

Voorbeeld Herhaling 1: Strafregels schrijven 1 Jantje, die pruimen stal, moet de volgende regel dertig keer opschrijven:

C

“Ik mag nooit meer pruimen stelen, ook al zijn er nog zo veel.”

Oplossing 1: gewoon opschrijven. Instructie

1

Voorbereiding Maak een console-app aan met de naam Strafregels.

Declaratie variabelen 2 Voeg de volgende regels toe: // Declaratie variabelen string strafregel = “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”;


66

Deel 1: Introductie

Algoritme 3 We gaan nu binnen het algoritme steeds een regel aan de variabele toekennen, zodat er aan het eind één grote string-variabele is met alle strafregels. Voeg de volgende regels toe:

C

O

N

C

EP

T

// Algoritme strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”;


Hoofdstuk 5 Herhaling

67

strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”; strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog” + “ zo veel.” + “\r\n”;

T

Output 4 Voeg de volgende regels toe om het resultaat van het algoritme op het scherm te laten zien:

Afsluiten 5 Voeg de volgende regels toe:

C

O

N

Test 6 Test je programma.

C

// Afsluiten Console.ReadKey();

EP

// Uitvoer Console.WriteLine(strafregel);


Deel 1: Introductie

O

N

C

EP

T

68

Schermafbeelding 23: Voorbeeld strafregels 1

C

Voor dertig regels is dit heel veel werk, voor duizend is het ondoenlijk. Het heeft ook weinig met programmeren te maken, want het is minder werk om de strafregels in een tekstverwerker te knippen en plakken.

Het gebruik van een loop (herhaling) Oplossing 2: Gebruik een loop (spreek uit: loep) en laat de computer het telwerk doen. Dat doet een computer sneller en nauwkeuriger dan welke mens ook. En het schrijven van de strafregels ook! Je moet dus de computer laten tellen. Hiervoor heb je een variabele nodig van het type int. Tellen bestaat immers uit gehele getallen: 1, 2, 3, 4, 5... In C ziet zoiets er als volgt uit:


Hoofdstuk 5 Herhaling

69

// Iets waarmee je kunt tellen. int teller = 0; // De loop (herhaling) en dat wat herhaald moet worden. while (teller < 30) { strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ook al zijn er nog zo veel.” + “\r\n”; teller = teller + 1; }

Let op! Als je de regel: teller = teller + 1 vergeet, dan telt de computer niet. De variabele teller houdt dan voor altijd de waarde 0, er wordt nooit iets bij geteld. Met als gevolg een eeuwigdurende loop...

T

Voorbeeld Herhaling 2: Strafregels schrijven 2 Instructie

Declaratie variabelen 2 Voeg de volgende regels toe:

N

C

// Declaratie variabelen string strafregel = “”; int teller = 0;

EP

1

Voorbereiding Maak een console-app aan met de naam Strafregels2.

O

Algoritme 3 Voeg één strafregel toe en een teller:

C

while (teller < 30) { strafregel = strafregel + “Ik mag nooit meer pruimen stelen, ” + “ook al zijn er nog zo veel.” + “\r\n”; teller = teller + 1; }

Output 4 Voeg de volgende regels toe om het resultaat van het algoritme op het scherm te laten zien: // Uitvoer Console.WriteLine(strafregel);

Afsluiten 5 Voeg de volgende regels toe: // Afsluiten Console.ReadKey();


70

Deel 1: Introductie

Test 6 Test je programma. Je zult zien dat de uitvoer exact hetzelfde is als bij Voorbeeld Herhaling 1, alleen nu met aanzienlijk minder werk. Dus deze code is veel beter!

Opdracht Herhaling 1: Rij getallen zonder loop Maak een programma dat de getallen 50 t/m 100 onder elkaar laat zien, maar gebruik geen loop. Dit is omslachtig, maar leerzaam om een keer gedaan te hebben.

Opdracht Herhaling 2: Rij getallen met loop

T

Pas het programma van Opdracht Herhaling 1 aan met het gebruik van een loop, en laat het programma doortellen tot 250.

EP

Wijze les! Als je meerdere keren hetzelfde aan het typen bent in je code, is de kans groot dat je het beter en korter kunt programmeren.

Herhaling met behulp van een variabele

N

C

Voorbeeld 2 is ten opzichte van voorbeeld 1 een aanmerkelijke verbetering. Echter, als je in plaats van dertig opeens honderd strafregels moet schrijven, zou je nog steeds de code moeten veranderen. Dus laten we dat de eindgebruiker doen. Op de plek waar je net het getal hebt gewijzigd, kun je ook een getalvariabele gebruiken. Dus in plaats van: while (teller < 30)

O

krijg je:

C

– eerst de declaratie van de variabele: de variabele declareer je aan het begin van het formulier, zodat deze door het hele formulier gebruikt kan worden. int aantalkeer = 30;

– en daarna het gebruik van de variabele in de loop: while (teller < aantalkeer)

Nu is de loop variabel geworden. Vervolgens zorg je voor een invoerveld en zorg je ervoor dat de variabele, voordat je de loop gaat uitvoeren, de waarde krijgt die de gebruiker heeft aangegeven.


Hoofdstuk 5 Herhaling

71

Voorbeeld Herhaling 3: Strafregels 3 Instructie 1

Voorbereiding Open de bestaande console-app Strafregels2.

Declaratie 2 Ga naar het gedeelte waar de variabelen gedeclareerd worden en voeg daar de volgende regel aan toe: int aantalKeer = 30;

T

Invoer 3 Voeg na de laatste declaratieregel met variabelen maar vóór het gedeelte met het algoritme de volgende regels toe om de invoer te regelen:

Algoritme 4 Zoek de volgende regel op:

C

while (teller < 30)

EP

// Invoer Console.WriteLine(“Hoeveel strafregels moet je schrijven?”); aantalKeer = int.Parse(Console.ReadLine());

Vervang het getal 30 hier door de variabele aantalKeer:

N

while (teller < aantalKeer)

C

O

Test 5 Test je programma en geef bij aantalkeer 3 op. Je scherm moet er nu als volgt uitzien:

Schermafbeelding 24: Voorbeeld strafregels 3

Opdracht Herhaling 3: Rij getallen met variabele loop Pas het programma van Opdracht Herhaling 2 zo aan dat de gebruiker de gelegenheid krijgt om zelf aan te geven welke getallen getoond worden.


72

Deel 1: Introductie

Opdracht Herhaling 4: De tafel van ... Maak een console-app waarbij je een getal kunt invoeren. Het programma moet de tafel van dat getal weergeven. De eerste 10 vermenigvuldigingen komen netjes onder elkaar te staan.

C

Schermafbeelding 25: Tafel van 2

EP

T

Als het ingevoerde getal 2 is, moet de uitvoer dus zijn:

Opdracht Herhaling 5: Meerdere tafels

O

N

Maak een console-app waarbij je twee getallen invoert. Het programma moet nu de tafels van die twee getallen en alle tussenliggende getallen op dezelfde manier weergeven als in Opdracht Herhaling 4.

Opdracht Herhaling 6: Machten 1 Maak een console-app met de naam Machten waarbij je de eerste 10 machten van een bepaald grondtal laat zien. Ook hier maak je gebruik van een loop. Overigens geldt na dit hoofdstuk dat als je geen gebruik maakt van een loop daar waar het kan én het minder code regels oplevert, je programma niet goed is.

C

1

De uitvoer ziet er uiteindelijk als volgt uit voor het grondtal 7:

Schermafbeelding 26: Macht van 7


Hoofdstuk 5 Herhaling

73

Opdracht Herhaling 7: Machten 2 * Maak een programma waarbij je, gegeven twee getallen (die we in ons voorbeeld even x en y noemen), de verschillende machten van het eerste getal (x) uitrekent. Stop bij de macht die wordt aangegeven door het tweede getal (xy).

EP

Bij 2 en 4 moet de uitvoer er zo uitzien: 2x2=4 2x2x2=8 2 x 2 x 2 x 2 = 16 2 x 2 x 2 x 2 x 2 = 32

T

Dus, als je bijvoorbeeld 3 en 4 in zou typen, dan wordt de uitvoer: 3x3=9 3 x 3 x 3 = 27 3 x 3 x 3 x 3 = 81

Opdracht Herhaling 8: Faculteit uitrekenen *

De wiskundige functie faculteit wordt geschreven als een uitroepteken. Zo betekent 5! = 1 x 2 x 3 x 4 x 5 = 120.

N

C

Maak een programma waarbij je, gegeven een getal, de bijbehorende faculteit van dat getal uitrekent. Laat het programma ook de berekening opschrijven. Dus bij invoer van het getal 4 moet de uitvoer er zo uitzien: 4! = 1 x 2 x 3 x 4 = 24

O

Opdracht Herhaling 9: De rij van Fibonacci *

C

Dit is een rij getallen waarbij je bij 0 en 1 begint. Vervolgens is elk getal in de rij de optelling van de twee voorgaande getallen. Dus: 0+1=1 1+1=2 1+2=3 2+3=5 En dan krijg je deze rij met getallen: 0, 1, 1, 2, 3, 5, 8, 13, 21 enzovoort. De rij van Fibonacci blijkt interessante eigenschappen en verbanden te bezitten met onder andere de gulden snede die iets zegt over verhoudingen in het menselijk lichaam en de architectuur. Maak een programma waarbij je de gebruiker om een getal vraagt. Het programma laat dan de Fibonacci-rij van zoveel getallen zien. Hierboven staat de Fibonacci-rij van 9 getallen.


74

Deel 1: Introductie

Meer loops Behalve de while-loop zijn er nog andere manieren om een herhaling te programmeren. De basis is echter vrijwel steeds hetzelfde: je hebt een voorwaarde en zolang die waar is voert het programma een aantal handelingen uit.

do ... while

//iets waarmee je kunt tellen. string strafregels = “”; int teller = 0;

T

Het bijzondere aan deze herhaling is dat, wat de waarde van de teller ook is, alle instructies van de herhaling tenminste één keer worden uitgevoerd. Hieronder het strafregelvoorbeeld uit het begin, maar nu als do while-herhaling:

C

EP

//de loop (herhaling) en dat wat herhaald moet worden. do { strafregels = strafregels + “Ik mag nooit meer pruimen stelen, ook al zijn er nog zo veel.” + “\r\n”; teller = teller + 1; } while (teller < 30);

N

Welke waarde teller ook heeft, het programma zal altijd tenminste één keer de regel Ik mag nooit meer pruimen stelen, ook al zijn er nog zo veel. laten zien.

Opdracht Herhaling 10: Do...While loop

C

O

Pas het programma van Opdracht Herhaling 3 (pagina 71) zo aan dat in plaats van een while-loop een do...while-loop gebruikt wordt. Wat gebeurt er als je bij het aantal strafregels 0 invoert? Juist: er verschijnt toch een strafregel op het scherm. Bij do...while wordt de opdracht dus inderdaad altijd tenminste één keer uitgevoerd.

for De for-loop is vooral handig en compact omdat alles wat van belang is voor een herhaling in één regel staat: for (int teller = 0; teller < 30; teller++) { strafregels = strafregels + “Ik mag nooit meer pruimen stelen, ook al zijn er nog zo veel.” + “\r\n”; }


Hoofdstuk 5 Herhaling

75

Opdracht Herhaling 11: For ... Maak het programma van Opdracht Herhaling 4 nu met behulp van een for-loop.

foreach De foreach-loop kan alleen gebruikt worden in combinatie met collecties. In het hoofdstuk over Arrays wordt dit nader toegelicht.

Wat moet je nu kunnen en kennen?

C

EP C

O

N

– while – do .. while – for – Het begrip teller – Escape sequence – \r\n – Environment.NewLine

T

In dit hoofdstuk heb je geleerd hoe je herhalingen in C maakt. Daarin werden de volgende zaken uitgelegd:


76

Hoofdstuk 6 Arrays

Wat is een Array?

EP

T

Een Array bestaat uit één of meerdere kolommen met variabelen van hetzelfde type. Een rij geef je aan met blokhaken [ ]. Een Array kun je vergelijken met een tabel of een lijst met gegevens. Je kunt het ‘adres’ van iedere cel in een tabel beschrijven zoals in een spreadsheet (Excel) of op een schaakbord: het nummer van de rij en de kolom zijn het adres van de cel. Zo is dat ook met de gegevens in een Array: een cel heet hier een element. Let op! Als je jouw programma de naam Array geeft, werkt er opeens niets meer. Dit komt omdat de computer de systeembibliotheek met de naam Array niet meer kan vinden omdat jouw programma met de naam Array eerst gevonden wordt.

C

Hoe maak je een Array?

N

Een eendimensionale Array is een Array met maar één kolom. Een eendimensionale Array declareer (maak) en initialiseer (vul) je op de volgende wijze.

O

Let op! Twee blokhaken [ en ] naast elkaar kunnen eruitzien als een blokje []. Daarom hebben we er hier een spatie tussen geplaatst. In jouw programma doe je dat niet.

C

int[] getallen = {20,22,3,13,15,700,25,33,48,69,14,4,2,654,852,456,2751,125,10,0};

Nu is de Array direct gevuld. Een lege Array maak je als volgt aan: int[] getallen = new int[20];

Het resultaat van deze twee opdrachten kun je je als volgt voorstellen, waarbij voor de Array de regelnummers (dus de adressen van de gegevens) staan: 0

20

1

22

2

3

3

13

4

15


700

6

25

7

33

8

48

9

69

10

14

11

4

12

2

13

654

14

852

15

456

16

2751

17

125

18

10

19

0

EP

5

77

T

Hoofdstuk 6 Arrays

Tabel 4: Array

N

C

Let op! Men begint te nummeren bij regel 0. Deze Array heeft 20 elementen, dus het laatste regelnummer is 19. Je moet van tevoren de grootte van een Array aangeven of direct de elementen specificeren.

Opdracht Array 1: Hele getallen

O

Maak een Array aan met de naam temperatuur. Vul de Array met vijftien willekeurige waarden tussen -40 en +60. De temperatuur wordt altijd in hele graden gegeven.

C

Opdracht Array 2: Decimale getallen Maak een Array aan met de naam prijzen. Vul de Array met vijf willekeurige waarden tussen de 0 en 1.000.000. Een prijs is een decimaal getal met twee cijfers achter de komma.

Opdracht Array 3: Teksten Maak een Array aan met de naam namen. Vul de Array met de voornamen van je klasgenoten. Heb je ontdekt dat een Array op verschillende manieren te vullen is?


78

Deel 1: Introductie

Toegang tot een element van de Array Door het regelnummer van de desbetreffende rij aan te geven tussen de blokhaken krijg je de waarde van dat element. Als je het elfde element wilt hebben van de Array getallen, dan schrijf je dat zo: getallen[10]

Wil je dit element op het scherm laten zien dan doe je dat als volgt: Console.WriteLine(“Het elfde getal is: ” + getallen[10].ToString()); ToString() gebruik jeomdat het een Array is van getallen, en als je dat wilt laten zien

Opdracht Array 4: Hele getallen

T

op het scherm dan moet je deze eerst vertalen naar een string.

EP

Laat het zevende element van de Array temperatuur op het scherm zien met de tekst: ‘Op de zevende dag was het ... graden.’

Opdracht Array 5: Teksten

Laat het eerste element van de Array namen zien op het scherm met de tekst: “De

C

eerste naam is: ...” met daarachter de naam.

N

Uiteraard mag je in plaats van een vaste waarde ook een variabele gebruiken om het regelnummer aan te duiden. Je kunt dan de gebruiker een nummer in laten voeren. Je definieert dan een variabele om het regelnummer aan te geven:

O

int regelnummer = 0;

Vervolgens gebruik je deze variabele om een element op te halen:

C

getallen[regelnummer];

Een stuk code waarbij de gebruiker het regelnummer invoert en het programma het desbetreffende element ophaalt en laat zien op het scherm ziet er dan als volgt uit: int regelnummer = int.Parse(Console.ReadLine()); Console.WriteLine(getallen[regelnummer].ToString());

Opdracht Array 6: Teksten en nummers Zorg ervoor dat de gebruiker een regelnummer van de Array namen kan invoeren. Toon op het scherm de volgende tekst: Op regelnummer x staat de naam y.

Voor x vul je het regelnummer in, voor y de naam uit de Array namen.


Hoofdstuk 6 Arrays

79

Bewerkingen met Arrays Met Arrays kun je op dezelfde manier bewerkingen uitvoeren als met andere variabelen. Het enige verschil is dat je het juiste regelnummer moet aangeven. Als je het vijfde element uit de Array getallen wilt vermenigvuldigen met 15 dan doe je dat als volgt: int resultaat = getallen[4] * 15;

Opdracht Array 7: Een element van een Array bewerken

EP

Opdracht Array 8: Letters tellen*

T

Gebruik de Array getallen. Maak twee invoermogelijkheden: één waar de gebruiker het regelnummer invoert, en één waar de gebruiker het getal invoert waarmee vermenigvuldigd moet worden, zoals in het voorbeeld hierboven. Het resultaat laat je zien op je scherm.

Maak een console-app waar de gebruiker een willekeurige tekst kan invoeren. Vraag de gebruiker daarna om maximaal 1 teken in te voeren.

C

Zorg ervoor dat de volgende uitvoer gegenereerd wordt: Het aantal keer dat het ingevoerde teken voorkomt in de ingevoerde tekst. Het aantal klinkers van de ingevoerde tekst. Het aantal spaties. Het aantal medeklinkers.

N

1 2 3 4

O

Maak gebruik van het variabeletype char en maak gebruik van de methode stringVariabeleNaam.ToCharArray().

C

Opdracht Array 9: De elementen van twee Arrays combineren Zorg voor een invoermogelijkheid om een regelnummer in te voeren. Gebruik dit regelnummer om die regel van de Array temperatuur te vermenigvuldigen met dezelfde regel van de Array prijzen. Laat tot slot de volgende tekst op het scherm zien: “Dit is onzin, maar temperatuur x prijs = ”.

Herhaling en Arrays De echte kracht van een Array zit in de mogelijkheid om meerdere waarden in één variabele op te slaan en met een loop alle waarden te bekijken. Wil je alle getallen van de Array getallen op het scherm zien dan kun je dat op de volgende manieren doen:


80

Deel 1: Introductie

Manier 1 int index = 1; int lengteArray = getallen.Length; string uitvoer = getallen[0].ToString(); while (index < lengteArray) { uitvoer = uitvoer + “,” + getallen[index].ToString(); index++; }

In plaats van een teller gebruiken we ook wel (Engelstalig) een index. De code index++ betekent hetzelfde als index = index + 1.

EP

int lengteArray = getallen.Length; string uitvoer = getallen[0].ToString();

T

Manier 2

for (int index=1;index<lengteArray; index++) { uitvoer = uitvoer + “,” + getallen[index].ToString(); }

N

C

Manier 3 Dit is de laatste herhalingsmethode die in dit boek behandeld wordt. De foreachinstructie zorgt ervoor dat één voor één alle elementen van de Array doorlopen worden. string uitvoer = “”;

C

O

foreach (int getal in getallen) { uitvoer = uitvoer + “,”+ getal.ToString(); }

De laatste methode biedt minder mogelijkheden om in te grijpen. Maar deze is wel bijzonder krachtig als je iets met alle elementen van een Array moet doen.

Wat je misschien opvalt, is dat het lijkt of je geen index declareert bij methode 2 en 3. Maar als je goed kijkt bij de declaratie van beide methoden begint de instructie tussen haakjes, direct achter het woord for en foreach met de declaratie van de index!

Een Array sorteren Een Array kun je erg gemakkelijk sorteren. Dit doe je als volgt: Array.Sort(getallen);


Hoofdstuk 6 Arrays

81

Opdracht Array 10: Getallen sorteren Laat je console-app de Array getallen sorteren. Gebruik de while-loop van Manier 1 om het resultaat op je scherm te laten zien.

Opdracht Array 11: Teksten sorteren Laat je console-app de Array namen sorteren. Gebruik een for-loop (Manier 2) om het resultaat op je scherm te laten zien. Zet iedere naam op een aparte regel.

Opdracht Array 12: Bewerken

EP

Problemen met Arrays

T

Laat je console-app de totale prijs uitrekenen van de Array prijzen. Maak gebruik van de foreach-loop (Manier 3).

Het werken met Arrays kent de volgende problemen:

Je moet van tevoren de grootte van de Array opgeven. Als je meer elementen hebt die je in een Array wilt opslaan, dan moet je eerst de Array vergroten. Dit doe je als volgt:

C

1

N

Array.Resize(ref getallen, 30);

O

Dit commando vergroot de Array getallen van 20, de bestaande grootte, naar 30 elementen.

Opdracht Array 13: Resize

C

Pas je console-app zo aan dat je pas later in je programma de grootte van de tabel prijzen zo groot maakt dat er tien prijzen in opgeslagen kunnen worden. Vul vervolgens de nieuwe plekken met prijzen. Sorteer de prijzen en laat deze op het scherm zien.

2 Als je een element buiten de Array aanroept stopt het systeem en geeft het een foutmelding. Dit kun je opvangen door te controleren of de gebruikte waarde niet groter is dan de lengte van de Array -1 (want, weet je nog: je begint te tellen bij 0...).

Opdracht Array 14: Te groot Wijzig Opdracht Array 6 zo dat je een foutmelding krijgt als het ingevoerde nummer groter is dan de lengte van de Array.


82

Deel 1: Introductie

Tip: Maak gebruik van wat je geleerd hebt bij het hoofdstuk over selecties. Een Array van objecten, bijvoorbeeld instanties van een zelf gedefinieerde klasse, werkt met referenties (verwijzingen). Dit houdt in dat als je een dergelijke Array kopieert, niet de inhoud wordt gekopieerd maar dat je een extra verwijzing krijgt naar de oorspronkelijke Array. Daarom maken we vaak gebruik van een List<T> functie. Die komt in deel 2 aan de orde. Let op! Een string kent de property Length, hiermee bepaal je het aantal tekens van de string.

–

T

Wat moet je nu kunnen en kennen? Arrays

Vergroten 0 Grootte bepalen 0 Grootte wijzigen 0 Sorteren 0 Loops gebruiken om de elementen te benaderen 0 Bewerkingen uitvoeren – while, for, foreach in Arrays

C

O

N

C

EP

0


83

Hoofdstuk 7 Forms & Controls

Een Windows-programma Tot nu toe heb je console-programma’s gemaakt, programma’s die zonder de grafische schil van Windows werken. Console-programma’s kom je veelal tegen in technische IT-omgevingen.

EP

T

We gaan nu programma’s maken die wél gebruikmaken van de grafische mogelijkheden van Windows. Dit kun je binnen C doen met het Windows forms template. Visual Studio zorgt dan voor een werkend programma waar je de benodigde onderdelen (controls) aan toe kunt voegen.

C

Het formulier (Form) laat zien hoe je programma eruit gaat zien voor de gebruiker. Je begint altijd, vóór je ook maar iets anders doet, met het wijzigen van de standaardnaam van je formulier. Naamgeving is heel belangrijk, weet je nog? Ook hier. Daarom:

1

N

Instructie Start Visual Studio.

C

O

2 Klik op Create a new project, je komt nu in het volgende scherm:

Schermafbeelding 27: Create a new project, forms-selectie


84

Deel 1: Introductie

3 Vul het volgende in: 0 language: C 0 platform: Windows 0 projecttype: desktop 4 Klik nu op Windows Forms App (.NET Framework). 5 Geef het project de naam MijnEersteWindowsForm.

C

EP

T

Je ziet nu het volgende scherm:

N

Schermafbeelding 28: Forms openingsscherm

C

O

Dit scherm wijkt af van wat je gewend bent. Je ziet niet een code editor. Je ziet nu op het scherm het uiterlijk (de lay-out) van het programma wat je aan het maken bent. Omdat je nog niets hebt gedaan laat Visual je hier de basisvorm van elke Windows-applicatie zien. Een Form kun je maximaliseren op je scherm, minimaliseren en de vorige grootte geven. Ook kun je een Form over je scherm verplaatsen of groter en kleiner maken. Allemaal zaken die je niet meer hoeft te maken, die krijg je cadeau. Wel zijn er allerlei manieren om dat ‘standaardgedrag’ van Windows te beïnvloeden en veranderen, maar dat komt later aan bod.

Debuggen 6 Test je programma. Ook hier kun je dat doen door op de groene driehoek (startknop) te klikken, wat dat betreft is het dus hetzelfde als een console-app. Je zult zien dat je alles met jouw programma kunt doen wat je met de meeste Windowsapplicaties kunt doen.


Hoofdstuk 7 Forms & Controls

85

Wijzigen programma 7 Ga naar de Solution Explorer en wijzig de standaardnaam form1.cs in FrmMijnEersteWindowsForm.cs door net zoals in de Windows Verkenner op de naam van het formulier te klikken.

EP

T

Je krijgt nu het volgende venster te zien. Klik hier altijd op ja, tenzij je heel precies weet wat je doet.

Schermafbeelding 29: Waarschuwing renaming files

C

Het programma gaat nu in alle bestanden, ook de bestanden die je niet ziet, de naam van het formulier aanpassen. 8 Klik op het formulier. Het formulier is nu ‘actief ’.

C

O

N

9 Ga naar het Properties-scherm (functietoets <F4>) en zoek de eigenschap ‘Text’ op. Verander de waarde van Form1 in Mijn eerste windows form. Deze tekst wordt nu in de titelbalk van het formulier getoond.


Deel 1: Introductie

O

N

C

EP

T

86

C

Schermafbeelding 30: Properties-scherm

Properties (Eigenschappen) Je kunt je programma verslepen over het scherm, groter en kleiner maken en beëindigen. Dit zijn, zoals gezegd, allemaal standaardeigenschappen van een formulier. Maar je wilt meer: je wilt knoppen plaatsen, teksten kunnen invoeren of laten zien. Kortom: wat leven in de brouwerij. We gaan nu dus een aantal controls (onderdelen van een programma) toevoegen. Dat doe je door ze te kiezen uit de Toolbox en naar het formulier te slepen. 10 Selecteer ‘Button’ in de Toolbox (<Ctrl><Alt><x>), sleep deze op het formulier en laat los.


Hoofdstuk 7 Forms & Controls

87

Je hebt nu een knop met standaardgedrag. Als je erop klikt lijkt het alsof de knop wordt ingedrukt, maar verder gebeurt er nog niets. De knop heeft ook een naam en opschrift die niets zeggen. We gaan eerst de naam wijzigen: 11 Klik met je rechtermuisknop op de knop die je op het formulier hebt geplaatst. 12 Ga naar Properties en klik daarop. Je komt nu in het Properties-scherm dat je al kent van het formulier, alleen zijn het nu, ten dele, andere eigenschappen. 13 Verander de naam (Name) van de knop in btnHelloWorld. 14 Verander het opschrift (Text) in Hello World.

T

15 Verander de achtergrondkleur (BackColor) in turquoise.

EP

De Code Editor

Nu heb je een knop met een naam, opschrift en kleur die afwijken van de standaardinstellingen. Als je naar de properties kijkt zul je zien dat ze vetgedrukt zijn. Nu gaan we de knop iets laten doen als je erop klikt!

C

16 Dubbelklik op de knop. Je komt nu in de Code Editor.

O

N

We zijn weer precies daar waar we ook al waren met de console-app. We zijn weer bij de eigenlijke tekstverwerker waarin je je programma(code) schrijft. Je zult zien dat er wel wat verschillen zijn tussen de console-app en de forms-app. Het belangrijkste is dat er aan het einde van regel 13 : Form staat. Dit is het stukje code dat ervoor zorgt dat jouw nieuwe programma alle functionaliteit van een standaard Windows form krijgt. Men noemt dit in de programmeerwereld overerven.

C

Je cursor komt te staan in het geel gemarkeerde gedeelte onderaan (zie Broncode 5). In deze broncode staat de code die wordt uitgevoerd als je met de muis op de knop klikt. Deze code wordt voor je aangemaakt. Jij moet alleen tussen de {accolades} (dus in het gele gedeelte) aangeven wat jij wilt dat gebeurt.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;


88

Deel 1: Introductie

namespace MijnEersteWindowsForm { public partial class FrmMijnEersteWindowsForm : Form { public FrmMijnEersteWindowsForm() { InitializeComponent(); } private void btnHelloWorld_Click(object sender, EventArgs e) { } } }

T

Broncode 5

EP

We willen met deze code nu een MessageBox laten zien met de boodschap Hello World. Een MessageBox is een object, een stuk code dat al klaar is en dat je zo kunt

gebruiken. Je moet alleen wat parameters aangeven, bijvoorbeeld de titel, de tekst die je wilt laten zien enzovoort. 17 Typ tussen de accolades het volgende:

C

MessageBox.Show(“Hello World”);

C

O

N

Deze regel kent de volgende onderdelen: – Het object MessageBox. – Een beschikbare functie van het object: Show(). Als je achter een object een punt zet dan laat .NET alle beschikbare properties en functies zien. Weet je nog? Puntnotatie! – Haakje openen, hierna kun je eventuele parameters meegeven, gescheiden door een komma. – De parameters: soms zijn het getallen, soms zijn het teksten, soms zijn het objecten. – Haakje sluiten. Dan weet .NET dat alle parameters opgegeven zijn. – En de ; – elke regel wordt afgesloten met een puntkomma. Je ziet het, het is precies hetzelfde qua regels als een console-app.

Test 18 Je programma is klaar. Run het (klik op de Debug-knop) en probeer het uit en verbeter eventueel je fouten.


Hoofdstuk 7 Forms & Controls

89

Controls Zoals we al hebben gezien zijn controls stukjes programmatuur die je op je formulier kunt slepen en waarvan je de eigenschappen kunt instellen in het Propertiesscherm. In dit hoofdstuk maken we kennis met een aantal controls. Eerst zie je een overzichtje van de controls met de belangrijkste properties en het doel van de control.

Button

Schermafbeelding 31: Voorbeeld Button

T

– Uiterlijk:

EP

– Doel: Een mogelijkheid voor de gebruiker creëren om een stukje van het programma op een eenvoudige manier op te starten.

O

N

C

– Properties: FlatStyle, hier bepaal je het uiterlijk van de knop als je er met de muis overheen gaat en klikt. Anchor, hiermee kun je een control vastmaken aan de bovenkant, onderkant enzovoort van je formulier. TabStop, de volgorde waarin de controls worden geselecteerd als je op de Tabtoets drukt. Enabled, hiermee kun je een knop ‘uitzetten’: de knop wordt lichtgrijs en is niet meer bruikbaar (Enabled = aan, Disabled = uit).

C

TextBox

– Uiterlijk:

Schermafbeelding 32: Voorbeeld TextBox

– Doel: Een TextBox is een control die de gebruiker in staat stelt gegevens in te voeren. – Properties: TextAlign, hiermee positioneer je de tekst in de TextBox. MultiLine, hiermee zorg je ervoor dat je meer dan één regel tekst in de TextBox kunt plaatsen.


90

Deel 1: Introductie

BorderStyle, hiermee bepaal je het uiterlijk van de randen. MaxLength, standaard 32767 tekens, hiermee bepaal je het maximaal aantal in te voeren tekens.

Label – Uiterlijk:

Schermafbeelding 33: Voorbeeld Label

T

– Doel: Het plaatsen van teksten op een formulier.

EP

– Property: AutoSize, deze eigenschap staat standaard aan en houdt in dat het Label automatisch iets groter is dan de waarde die ingevoerd is bij Text.

RadioButton

N

C

– Uiterlijk:

O

Schermafbeelding 34: Voorbeeld RadioButtons

C

– Doel: Je plaatst altijd tenminste twee RadioButtons bij elkaar op een formulier. Als je er één aanklikt, staat/staan de andere RadioButton(s) automatisch uit. In het voorbeeld zie je drie RadioButtons.

– Property: Checked, met deze property kun je aangeven of deze knop bij het opstarten geselecteerd moet zijn.


Hoofdstuk 7 Forms & Controls

91

Webbrowser – Uiterlijk:

EP

– Doel: Een interactieve koppeling met het internet.

T

Schermafbeelding 35: Voorbeeld Webbrowser

C

– Properties: URL, hier geef je aan welke website geladen moet worden. ScrollBarsEnabled, hiermee geef je aan of er wel of geen scrollbars getoond worden (Enabled = áán).

N

Opdrachten Controls

O

Opdracht Controls 1: Voorbeeld controls

C

Maak een programma met de naam VoorbeeldControls. Plaats van alle hiervoor genoemde controls tenminste één op het formulier en pas in ieder geval de genoemde properties bij alle controls aan. Experimenteer ook met andere properties.

Opdracht Controls 2: Temperatuur Maak een programma waarbij de gebruiker een temperatuur in graden Celsius invult in een TextBox en je met behulp van een Button de temperatuur in Kelvin uitrekent. 0 °C = 273,15 K. Kelvin wordt aangegeven met de hoofdletter K zonder het gradensymbool. Je mag het resultaat laten zien in een MessageBox, een Label of een TextBox.

Opdracht Controls 3: Aanvraag vergunning Maak een Windows forms app van Opdracht Selectie 6: Aanvragen van een vergunning (zie pagina 58). Zorg ervoor dat de gebruiker alle gevraagde informatie net-


92

Deel 1: Introductie

jes in een aantal TextBoxes kan invoeren. Geef het programma een knop waarmee bepaald gaat worden of je al dan niet een vergunning nodig hebt. Kies zelf hoe je de output laat zien. Bespreek je keuze met een medeprogrammeur of een gebruiker.

Opdracht Controls 4: Rekenmachine Maak een Windows forms rekenmachine die behalve de gewone rekenkundige bewerkingen ook kan machtsverheffen. Toon je uitvoer in een groen Label met rode letters.

Opdracht Controls 5: Datum – tijd *

C

O

N

C

EP

T

Zet Voorbeeld Datum (zie schermafbeelding 36) om naar een Windows forms applicatie. Het resultaat moet er als volgt uitzien:

Schermafbeelding 36: Voorbeeld Datum

Voor deze opdracht moet je gebruikmaken van een DateTimePicker. De waarde die deze control teruggeeft is van het type TimeSpan. De knoppen Nummer, Naam en Afkorting geven respectievelijk het nummer van de maand, de naam van de maand en de afkorting van de maand weer.


Hoofdstuk 7 Forms & Controls

93

Wat moet je nu kunnen en kennen? In dit hoofdstuk heb je geleerd hoe je een Windows forms applicatie maakt. Daarin werden de volgende zaken uitgelegd: – Controls 0

Button

0

DateTimePicker

0

Label

0

RadioButton

0

TextBox

0

Webbrowser

C

O

N

C

EP

T

– Eigenschappen (properties) – Form – Overerven


94

Hoofdstuk 8 Foutafhandeling

Wat is foutafhandeling?

O

N

C

EP

T

Je hebt inmiddels wel gemerkt dat als je iets fout doet, het programma meestal niet werkt. Er zijn echter ook situaties waarbij het programma op zich werkt, maar als de gebruiker iets verkeerd invoert, het programma afbreekt met een rare melding. Als je een programma hebt dat twee getallen moet optellen en de gebruiker voert in plaats van een getal een letter in, dan krijg je bijvoorbeeld de volgende foutmelding:

Schermafbeelding 37: Foutmelding

C

Het programma beëindigt de Debug-modus en springt meteen naar de Code Editor. Dat wil je je gebruiker niet aandoen. Je kunt code schrijven die dit soort fouten voorkomt of ervoor zorgt dat er een duidelijke melding voor de eindgebruiker op het scherm komt. Dit noemen we foutafhandeling.

Foutafhandeling met de MessageBox Omdat dit soort fouten vaak met een verkeerde invoer door de gebruiker te maken heeft, handel je deze ook daar af. Invoerfoutgevoelige code zoals het vertalen van een ingevoerde waarde naar een getal plaats je in een try ... catch-blok. Hiermee zeg je tegen de computer: ‘Probeer alle code in het try-blok uit te voeren, maar als het fout gaat, ga dan naar het catch-blok en voer de regels die daar staan alsnog uit.’ Je vangt de fouten daar af.


Hoofdstuk 8 Foutafhandeling

95

Het bijzondere is dat veel fouten opgeslagen worden in een soort foutvariabele en daarin staat dan ook precies wat er fout is gegaan. Deze tekst kun je aan de gebruiker laten zien, zodat hij weet wat hij moet veranderen.

Voorbeeld Foutafhandeling We gaan een programmaatje maken om een breuk weer te geven. Het getal dat ingevoerd wordt, is de noemer (het getal onder de streep) van een breuk: 3 wordt 1/3, 4 wordt 1/4 enzovoort. Het resultaat wordt getoond in een MessageBox en we gaan alle fouten afvangen.

Instructie Maak een nieuw project aan met de naam FoutVoorbeeld.

EP

2 Plaats een Label met het opschrift Getal.

T

1

3 Plaats daarachter een TextBox met de naam txbGetal en de standaardwaarde 0. Dit doe je door bij de property Text 0 in te vullen. 4 Plaats een Button met de naam btnBreuk en het opschrift Breuk.

C

Nu gaan we ervoor zorgen dat het fout gaat!

5 Dubbelklik op de Button btnBreuk. Je komt nu in de Code Editor.

N

6 Hier voeg je de code toe om de breuk te berekenen en het resultaat in een MessageBox te laten zien:

O

// initialisatie variabele double getal = 0; double breuk = 0;

C

// toekenning van waarden / berekeningen getal = double.Parse(txbGetal.Text); breuk = 1 / getal;

// tonen van de uitkomst MessageBox.Show(“De breuk van ” + getal.ToString() + “ is: ” + breuk.ToString());

7 Test het programma met het volgende testplan: a Voer een getal groter dan 0 in. Wat gebeurt er? Klopt de berekening? b Voer het getal 1 in. Wat gebeurt er? Klopt de berekening? c Voer het getal 0 in. Wat gebeurt er? d Voer een letter in. Wat gebeurt er? Zeker in het laatste geval breekt het programma af. Dat willen we niet en dat gaan we voorkomen. We plaatsen daarvoor de regel waar het fout kan gaan (de vertaling van een schermtekst naar een getal) in een try ... catch-blok.


96

Deel 1: Introductie

8 Ga naar de regel: getal = double.Parse(txbGetal.Text);

Geef aan het begin van de regel een return. Ga naar de lege regel, typ: try

en druk daarna twee maal op de Tab-toets. De volgende code wordt nu ingevoerd. Dit is een compleet try ... catch-blok: try {

EP

T

} catch (Exception) { throw; }

Het try-gedeelte is het stuk waar we de code plaatsen die verkeerd kan gaan. Het catch-gedeelte is het stuk waar we de code plaatsen die de fouten opvangt en ‘netjes’ weergeeft.

C

We zetten de volgende code in het try-blok: getal = double.Parse(txbGetal.Text); breuk = 1 / getal;

N

De code van de btnBreuk ziet er nu als volgt uit: {

O

// initialisatie variabele double getal = 0; double breuk = 0;

C

// toekenning van waarden / berekeningen try { getal = double.Parse(txbGetal.Text); breuk = 1 / getal; } catch (Exception) { throw; } // tonen van de uitkomst MessageBox.Show(“De breuk van ” + getal.ToString() + “ is: ” + breuk.ToString());

}

Broncode 6


Hoofdstuk 8 Foutafhandeling

97

9 Test het programma. Voer dezelfde tests uit als bij stap 7. Wat is het verschil? 10 Het programma crasht nog steeds, het stopt nu bij throw. En dat is precies wat throw doet: een foutmelding ‘over de schutting gooien’, en als er geen ander programma is dat de melding opvangt, dan krijg je een crash. We vervangen nu throw door een nette melding: MessageBox.Show(“Er is een fout opgetreden.”);

EP

T

11 Test het programma. Voer dezelfde tests uit als bij stap 7. Wat is het verschil? Als het goed is krijg je uiteindelijk de volgende melding:

Schermafbeelding 38: Algemene foutmelding

N

C

12 Het is mooi dat het programma niet meer crasht. Het is wel vervelend dat de gebruiker niets wijzer wordt over wat er fout is gegaan. We laten nu de melding van Windows over de fout zien. Dat doen we door gebruik te maken van een variabele van het type Exception. Die staat al in ons programma (zie Broncode 6). De variabele moet alleen nog een naam krijgen. Dat doen we door er een naam achter te typen. De regel wordt dan:

O

catch (Exception exception)

C

13 Een variabele van het type Exception heeft verschillende eigenschappen, waaronder de eigenschap Message. Daarin staat de omschrijving van de fout. En die kunnen we laten zien met MessageBox.Show(): MessageBox.Show(“Er is een fout opgetreden. ” + exception.Message);

Let op! Het type schrijven we met een hoofdletter, de variabele zelf krijgt in dit geval dezelfde naam, maar dan met een kleine letter. 14 Test het programma. Voer dezelfde tests uit als bij stap 7. Als het goed is krijg je uiteindelijk de volgende melding:


98

Deel 1: Introductie

Schermafbeelding 39: Specifieke foutmelding

Opdracht Foutmelding 1: Messagebox

T

Pas het programma van Opdracht Variabelen 1: Invoer getallen zo aan dat het nette foutmeldingen geeft als er iets verkeerd gaat.

EP

Foutafhandeling met de ErrorProvider

N

C

Het programma dat je net hebt gemaakt, toont een MessageBox op het scherm met een foutboodschap. De gebruiker moet op de OK-knop klikken om verder te kunnen gaan. Je dwingt de gebruiker met veel programmeergeweld om naar je te luisteren. Hij kan niet om je heen. Veel gebruikers vinden dat niet prettig. Maar... er is een andere manier die minder bruut is: de ErrorProvider.

O

De ErrorProvider is een control die je naar je formulier kunt slepen, net zoals een TextBox, een Label of een Button. Maar wat doet een ErrorProvider dan? Laat die foutboodschappen zien en gaat dat vanzelf?

C

Nee, een ErrorProvider doet eigenlijk maar twee dingen: 1 Hij toont een knipperend rood uitroepteken. 2 Als je over de control beweegt met je muis (hover) wordt er een tekst getoond.

Je kunt dan ook twee dingen aangeven bij een ErrorProvider: 1 De boodschap, een stuk tekst. 2 De naam van de control waar de tekst getoond moet worden. Wil je een ErrorProvider uitzetten, dan geef je gewoon een lege tekst mee bij die control.


Hoofdstuk 8 Foutafhandeling

99

Instructie 1

Open het project met de naam FoutVoorbeeld. Als het goed is ziet de code van de btnBreuk er nu als volgt uit: {

EP

// toekenning van waarden / berekeningen try { getal = double.Parse(txbGetal.Text); breuk = 1 / getal; } catch (Exception exception) { MessageBox.Show(exception.Message); }

T

// initialisatie variabele double getal = 0; double breuk = 0;

// tonen van de uitkomst MessageBox.Show(“De breuk van ” + getal.ToString() + “ is: ” + breuk.ToString()); }

C

Broncode 7

N

2 Open de Form Designer.

O

3 Sleep vanuit de Toolbox de ErrorProvider-control op het formulier en laat deze los op een willekeurige plek. Onderaan het formulier verschijnt nu de tekst errorProvider1. Als je maar één ErrorProvider gebruikt hoef je de naam niet aan te passen: het is duidelijk welke ErrorProvider je bedoelt.

C

4 Ga naar de Code Editor achter de knop btnBreuk. 5 Vervang de code in het catch-blok door de volgende code: errorProvider1.SetError(txbGetal, exception.Message); txbGetal is de naam van de control, dus zonder de .Text-aanduiding.

6 Start het programma. Je zult zien dat het werkt. Maar als je vervolgens een goede waarde invoert gaat de fout niet meer weg! Helaas: ook dat moeten we zelf programmeren. 7 Stop het programma en voeg in het try-blok een laatste regel toe. errorProvider1.SetError(txbGetal, “”);


100

Deel 1: Introductie

Waarom hier? Omdat je zeker weet dat als die regel uitgevoerd wordt alle voorgaande regels goed zijn gegaan. Je kunt dan met een gerust hart de foutmelding uitzetten. Het programma is aangepast. Nu kun je voortaan een ErrorProvider gebruiken om je programma te beschermen tegen onoordeelkundig gebruik.

Opdracht Foutmelding 2: ErrorProvider Gebruik in het programma van Opdracht Variabelen 1: Invoer getallen een ErrorProvider in plaats van een MessageBox als er iets verkeerd gaat.

T

Wat moet je nu kunnen en kennen?

EP

In dit hoofdstuk heb je geleerd hoe je jouw programma robuuster maakt en de gebruiker beschermt tegen het maken van fouten. In dit hoofdstuk zijn de volgende zaken aan de orde geweest:

C

O

N

C

– Exception – ErrorProvider – try...catch – throw – Het verschil tussen een Control van het type TextBox en de eigenschap Tekst van dezelfde TextBox.


101

Hoofdstuk 9 Methoden

Wat is een methode?

T

Een methode is een stukje code met een naam dat je meerdere keren kunt gebruiken, of, in programmeurstermen, kunt aanroepen. Binnen Visual Studio 2019 worden methoden aangegeven met de kleur bruin. Zo is er een methode om een tekst op het scherm te zetten: dit is de methode WriteLine() die bij het type Console hoort. Of de methode die een MessageBox op het scherm zet: dit is de methode Show() die bij het type MessageBox hoort.

EP

Een andere methode is de methode IsDigit() die bij het gegevenstype char hoort. Deze methode onderzoekt of een teken een cijfer is of niet. Behalve de methoden die al standaard in C aanwezig zijn, kun je ook zelf een methode maken.

N

Methoden die iets doen Deze methoden herken je aan het codewoord void voor de naam van de methode. Dit betekent dat door deze methode niets in het geheugen wordt geschreven. Er wordt geen ruimte gereserveerd in het geheugen. Void betekent leeg.

O

1

C

Er zijn twee soorten methoden: methoden die iets voor je doen en methoden die een vraag beantwoorden.

Voorbeeld:

C

void Boodschap() { ............... }

2 Methoden die een vraag beantwoorden Deze methoden worden voorafgegaan door een typeaanduiding, net zoals je dat kent van een variabele. Deze methoden beantwoorden een gestelde vraag en bergen het antwoord op in het geheugen, net zoals een variabele ook geheugenruimte reserveert. Een methode van het type int levert een getal van het type int op als resultaat. Een methode van het type bool levert de waarde true of false op als resultaat. && Met de twee ampersandtekens achter elkaar (&&) kun je twee logische tests uitvoeren die allebei waar moeten zijn. In het voorbeeld hieronder moet het uur groter dan 6 én kleiner dan 12 zijn om te kunnen concluderen dat het ochtend is.


102

Deel 1: Introductie

Voorbeeld:

if (uur <= 6) { dagdeel = “nacht”; } else if (uur > 6 && uur <= 12) { dagdeel = “ochtend”; } else if (uur > 12 && uur <= 18) { dagdeel = “middag”; }

EP

return dagdeel;

T

string getDagdeel() { int uur = DateTime.Now.Hour; string dagdeel = “avond”;

}

In een (ander) programma kun je dan bijvoorbeeld deze methode gebruiken om iemand die inlogt op een systeem te begroeten:

C

MessageBox.Show(“Goede” + getDagdeel() + “ ” + Environment.UserName);

O

N

Environment.UserName Met de Environment-bibliotheek kun je allerlei informatie ophalen over het computersysteem waarop je programma gebruikt wordt. Met Environment.UserName krijg je de username van de ingelogde gebruiker te zien.

C

Hoe maak je een methode? 1

Een methode bestaat uit de volgende onderdelen: Het type methode void voor de methoden die iets doen, een type-aanduiding voor de andere methoden (int, bool, string, char enzovoort).

2 De naam van de methode Iedere methode heeft een unieke naam. Deze naam moet je zorgvuldig kiezen. Gebruik bij voorkeur een werkwoord als naam, bijvoorbeeld: telOp, Bereken, laatZien. Als je geen goed werkwoord kunt bedenken voor je methode is het de vraag of de structuur van je programma wel goed is. Een methode met de naam doeHeelVeelVoorMij() geeft eigenlijk aan dat je een onduidelijke programmastructuur hebt en wordt daarom sterk afgeraden. Een goede methode doet één ding.


Hoofdstuk 9 Methoden

103

3 De te gebruiken parameters Als je een methode maakt, is het vaak handig om gebruik te maken van parameters. Een parameter van een methode is een variabele die gebruikt wordt om gegevens van buiten door te geven aan de code van een methode. De parameters geef je aan tussen twee ronde haakjes, pal achter de naam van de methode. In void methodeNaam(int X, string letter,...) zijn int X en string letter de parameters, dus de gegevens die de methode gebruikt om z’n werk te doen. Maar niet iedere methode maakt gebruik van parameters. 4 Tussen {accolades} komt de programmacode van de methode.

T

5 Als het een methode is die een vraag beantwoordt, moet er een waarde teruggegeven worden. Dit gebeurt met het codewoord return gevolgd door een variabele of een waarde van hetzelfde type als de methode.

EP

Voorbeeld Methode

C

int berekenAfstand(int kilometerPerUur, int aantalUur) { int afstand = 0; afstand = kilometerPerUur * aantalUur; return afstand; }

int

N

Toelichting: berekenAfstand

O

int kilometerPerUur en int aantalUur

het type van de methode de naam van de methode de parameters van de methode

Deze drie onderdelen samen worden de signatuur van een methode genoemd.

C

In de body, het gebied tussen de accolades { }, gebeurt het volgende: int afstand = 0; Er wordt een variabele aangemaakt van het type int, met de naam afstand en de waarde 0. afstand = kilometerPerUur * aantalUur; De variabele afstand krijgt de waarde van kilometerPerUur * aantalUur. return afstand; De waarde van de variabele afstand wordt teruggegeven als resultaat van de methode.

Opdracht Methode 1: Welkomstboodschap Zet in een form in het load-event (dubbelklik ergens op je formulier) de code om de volgende welkomstboodschap op het scherm te zetten. In het volgende hoofdstuk gaan we dieper in op wat een event precies is.


104

Deel 1: Introductie

Beste (naam van de ingelogde gebruiker), Welkom in ons systeem. Zoals u zult begrijpen zijn er enkele regels op ons systeem van toepassing waar iedere gebruiker zich aan moet houden. Door op de knop ‘ok’ te klikken gaat u ermee akkoord dat de systeembeheerder onbeperkt toegang heeft tot uw computer.

Hint: De naam van de ingelogde gebruiker krijg je met behulp van Environment.UserName.

Voorbereidende opdracht Methode 2 en 3

EP

T

Maak een Windows forms programma met de naam Meetkunde. Zet op dit formulier twee TextBoxes met bijbehorende Labels om de lengte en breedte in te kunnen voeren. Maak een mooi formulier: zet de gegevens netjes naast of onder elkaar en gebruik Labels om duidelijk te maken wat je getallen betekenen.

Opdracht Methode 2: Oppervlakte

Open het programma Meetkunde dat je gemaakt hebt bij Voorbereidende opdracht Methode 2 en 3 en werk hierin verder.

O

N

C

Maak een methode die de oppervlakte van een rechthoek uitrekent. Gegeven de lengte en de breedte wordt de oppervlakte (lengte x breedte) uitgerekend. Voeg aan het programma een knop toe om de oppervlakte uit te rekenen en op het scherm te zetten. Deze knop moet gebruikmaken van de ingevoerde waarden en de zojuist gemaakte methode.

Opdracht Methode 3: Omtrek

C

Maak in hetzelfde programma een methode die voor jou de omtrek van een rechthoek uitrekent, gegeven de lengte en de breedte. Voeg aan het programma een knop toe om de omtrek (dus 2 x (lengte + breedte)) uit te rekenen en op het scherm te zetten. Ook hier maak je gebruik van de zojuist gemaakte methode en de bestaande variabelen voor lengte en breedte.

Opdracht Methode 4: Euro naar ponden Maak een Windows forms programma met de naam Wisselkoers waar de gebruiker een bedrag in euro’s invoert en er in een TextBox dezelfde waarde in Engelse ponden verschijnt. Ook moet de gebruiker de gelegenheid hebben om een andere wisselkoers in te voeren.


Hoofdstuk 9 Methoden

105

Maak een methode die voor jou de omrekening verzorgt. (In hoofdstuk 3 Opdracht Variabelen 8 en 9, pagina 47, heb je dit ook gemaakt, maar toen zonder gebruik te maken van een methode en Forms.) Daarnaast moet het ook mogelijk zijn om met een knop de omrekening te doen van ponden naar euro’s.

Opdracht Methode 5: Het gulden getal uitrekenen *

T

Zet de console-app die het gulden getal van een jaar berekent (hoofdstuk 3 Variabelen, Opdracht 10) om naar een methode. Gebruik deze methode binnen een Windows forms applicatie.

Wat moet je nu kunnen en kennen?

EP

C

N

void

C

O

– – – – – – –

In dit hoofdstuk heb je geleerd hoe je je code structureert met behulp van methoden. Dezelfde code, of bijna dezelfde code, kopiëren en plakken om die ergens anders nog een keer te gebruiken? Dat is bijna een doodzonde. In dit hoofdstuk zijn de volgende zaken aan de orde geweest: Environment-bibliotheek Methode Naamgeving Parameters return-type Meerdere logische tests uitvoeren met &&


106

Hoofdstuk 10 Events

Wat is een event?

EP

T

Event betekent gebeurtenis en dat is precies waar het om gaat: een event is een gebeurtenis van buiten die je programma kan beïnvloeden. Bijvoorbeeld een gebruiker die een toets indrukt, een gebruiker die een window (scherm) verkleint of vergroot, maar ook een signaal van een ABS (antiblokkeersysteem) dat aangeeft dat een wiel gaat slippen. Wie of wat bepaalt nu wat er gaat gebeuren naar aanleiding van een gebeurtenis? Jouw programma. Jij dus. En je wilt niet dat je programma altijd reageert op elke gebeurtenis. Jij bepaalt wanneer en hoe.

Soorten events

N

C

Veel objecten in C maken standaard gebruik van events. Daar hoef je dus niets voor te doen: – Een Button maakt gebruik van een onclick() event. – Een TextBox maakt gebruik van de key() events zodat je tekst kunt invoeren.

Eigenschappen van events

C

O

Een event is ook een object: informatie over een event wordt vaak doorgegeven in een object met de eenvoudige en korte naam e. Als je een toets indrukt vind je bij e.KeyValue de waarde van die toets. Als je K indrukt vind je daar de K en als je de W indrukt de W. Als je echter met een muis klikt heb je niets aan een KeyValue, want je drukt geen toets in. De muis kan wel de plaats van de cursor doorgeven aan de computer. De locatie van de muisklik wordt met de X- en Y-coördinaten van de cursor op het scherm aangeduid.

Bij een MouseEvent met de naam e heb je dan ook de volgende properties: – e.Location.X (of korter: e.X) – e.Location.Y (of korter: e.Y)


Hoofdstuk 10 Events

107

Het gebruik van events Wat moet je doen om een event te kunnen gebruiken in C? Bepaal met welk object (Window, TextBox, Button, enzovoort) je het event wilt gebruiken. Bepaal welk event je wilt gebruiken (MouseClick, KeyDown, KeyUp enzovoort). We maken in dit hoofdstuk een eenvoudig programma met de naam Waar Ben Ik? dat laat zien op welke plek je op het scherm hebt geklikt.

Voorbeeld Event: Waar Ben ik?

EP

2 Het form krijgt de volgende instellingen: – Name: FrmWaarBenIk – Text: Waar Ben Ik?

T

1

Voorbereiden Maak in Visual Studio een nieuw Windows forms application-project aan met de naam WaarBenIk.

3 In de Solution Explorer wijzig je de naam van Form01.cs in FrmWaarBenIk.cs.

N

C

4 Je voegt een Panel toe midden op het formulier met de volgende eigenschappen: – Name: pnlLocation – BackColor: ActiveCaption – Width: 105 – Height: 44

C

O

5 Plaats in het Panel twee Labels onder elkaar met de volgende eigenschappen: Label1: – Name: lblX – Tekst: 0 – AutoSize: False Label2:

– – –

Name:

lblY

Tekst:

0

AutoSize: False

Pas de grootte van de Labels aan zodat ze netjes midden in het Panel passen. Je hebt nu een formulier dat er als volgt uitziet en waar je nog helemaal niets mee kunt doen:


Deel 1: Introductie

T

108

Gedrag van Waar Ben ik?

EP

Schermafbeelding 40: Waar ben ik?

1

C

Instructie We willen dat als je ergens op het formulier klikt, de Labels lblX en lblY gevuld worden met de X- en Y-coördinaten van het punt waar je hebt geklikt. Maak het formulier actief (klik ergens op het formulier).

C

O

N

2 Klik met je rechtermuisknop op het formulier en ga naar Properties. Het volgende scherm wordt actief:


109

N

C

EP

T

Hoofdstuk 10 Events

O

Schermafbeelding 41: Properties

C

3 Klik op de bliksemschicht. Binnen het Properties-scherm wordt het tabblad Events actief. Dit kun je zien doordat het tabje met het bliksemschichtje gekleurd is.


Deel 1: Introductie

C

O

N

C

EP

T

110

Schermafbeelding 42: Events

4 Ga naar het event MouseClick en dubbelklik daarop. Als het goed is kom je in de Code Editor terecht en wordt de volgende code voor je aangemaakt: private void FrmWaarBenIk_MouseClick(object sender, EventArgs e) { }

Dit stukje code noemen we de ‘event-handler’: de code die het event afhandelt. De code tussen de accolades wordt uitgevoerd. Die code gaan we nu schrijven. 5 Tussen de accolades schrijven we de code om de X- en de Y-waarden te tonen in de twee Labels.


Hoofdstuk 10 Events

111

lblX.Text = e.X.ToString(); lblY.Text = e.Y.ToString();

6 Run het programma en klik op het formulier. Als het goed is zie je nu in het Panel de X- en de Y-waarde van de plek waar je geklikt hebt. Klik maar eens in de linkerbovenhoek. Wat gebeurt er? We gaan het programma zo uitbreiden dat het Panel daar komt te staan waar je het laatst geklikt hebt: 7 Ga in de Code Editor naar het stuk code dat je net geschreven hebt. 8 Voeg de volgende regel toe:

EP

9 Run het programma nogmaals. Is het gelukt?

T

pnlLocation.Location = e.Location;

Toepassing

C

Op dezelfde manier als je hiervoor hebt gedaan, kun je ook andere events programmeren. De werkwijze is steeds hetzelfde: bepaal waar je in je programma een event wilt toepassen, zoek via het bliksemschichtje het bijpassende event en programmeer de gewenste code.

N

Let op! Sommige events lijken erg op elkaar. Click werkt bijvoorbeeld voor alle klik-

O

events (toetsen en muis), maar MouseClick gaat alleen over de muis.

Opdracht Event 1: Geheimschrijver

C

Bedenk een geheimschrift waarbij de ene letter een andere letter betekent. Maak dan een geheimschrijver. Met andere woorden: als je op toets a drukt wordt bijvoorbeeld b geschreven. Zorg dat alles precies klopt met je geheimschrift.

– – – –

Hints: Maak gebruik van het event KeyPress. e.KeyChar geeft het teken van de ingedrukte toets. (int)e.KeyChar geeft het ASCII-nummer van dit teken weer. (char)100 vertaalt het getal 100 naar het bijbehorende ASCII-teken.

Opdracht Event 2: Kleurenwisselaar * Maak een kleurenwisselaar. Als je met je linkermuisknop één keer klikt wordt het formulier rood, als je met je rechtermuisknop klikt wordt het formulier blauw.


112

Deel 1: Introductie

Als je dubbelklikt met je linkerknop wordt het formulier geel en dubbelklik je met rechts dan wordt het groen. Hint: Er bestaat zoiets als e.Button en MouseButtons.Left...

Wat moet je nu kunnen en kennen?

EP

T

Wat is een event? Hoe activeer je een event? Hoe verwerk je de informatie die met een event samenhangt? De standaardvariabele e Het begrip event-handler Het verschil tussen Click() en MouseClick() De event-tab binnen het Properties-scherm

C

O

N

C

– – – – – – –


113

Hoofdstuk 11 Tekenen

Waarom tekenen?

T

Een computer is in eerste instantie gebouwd om snel en veel te kunnen rekenen. Maar zoals je weet kun je er ook andere dingen mee doen, zoals tekstverwerken, gamen, maar ook tekenen. Deze les gaat over hoe je met C de computer kunt laten tekenen.

C

Eenvoudig tekenen

EP

In de grafische omgeving van een formulier ben je eigenlijk ook aan het tekenen. Veel objecten hebben een grafisch uiterlijk. Denk maar aan knoppen, formulieren, invoervakken enzovoort. Je zult dus regelmatig iets, al dan niet bewust, tekenen. Niet bewust? Ja, want Visual.NET heeft het dan al voor je gedaan.

Voorbeeld Tekenen: Lijn trekken

O

N

We maken een nieuw project met de naam LijnTrekken. Plaats een knop op het formulier met de naam btnLijn. Plaats een PictureBox met de naam pbTekening op het formulier. Als je in het echte leven gaat tekenen heb je de volgende dingen nodig: Tekengereedschap met een bepaalde kleur en dikte (stiften, potloden, pennen).

C

1

2 Iets om op te tekenen, meestal papier, maar het kan ook een muur zijn voor een graffiti-artiest. Het object waarop je wilt tekenen moet je eerst klaarleggen, gereedmaken om op te tekenen. Het papier leg je op tafel, de muur maak je even schoon. En je kunt tekenen. In C moet je in feite dezelfde stappen uitvoeren als in het echte leven. Zet de volgende code in het click event van de knop btnLijn. // Een tekengereedschap met een bepaalde kleur en dikte. Pen tekenpotlood = new Pen(Color.Blue, 4); // Iets om op te tekenen: in C# moet je een variabele van het type // Graphics aanmaken bij een bepaald grafisch object. Graphics papier = pbTekening.CreateGraphics();


114

Deel 1: Introductie

// Nu kun je tekenen. Een lijn heeft een begin- en een eindpunt. // Die moet je aangeven. papier.DrawLine(tekenpotlood, 10, 10, 250, 0);

Schermafbeelding 43: Lijn trekken

EP

T

Deze laatste opdracht betekent: Teken een lijn met de Pen met de naam tekenPotlood. Het wordt dus een blauwe lijn van 4 pixels dik. Teken deze lijn vanaf punt A met de coördinaten (10, 10) naar het punt met de coördinaten (250, 0). Het resultaat ziet er als volgt uit:

N

C

Let op! In een PictureBox begint men in de linkerbovenhoek met (0, 0). Naar rechts is positief, maar naar beneden wordt ook positief geteld. In dit geval heeft de rechterbenedenhoek de coördinaten 300,300.

Opdracht Tekenen 1: Verticale tralies

O

Maak met behulp van een loop een programma dat 20 blauwe verticale lijnen met een tussenruimte van 10 pixels naast elkaar zet.

C

Opdracht Tekenen 2: Horizontale tralies Breid het vorige programma uit met een knop waarbij je 20 rode horizontale lijnen met een tussenruimte van 10 pixels onder elkaar zet.

Opdracht Tekenen 3: Raster Gebruik bij deze opdracht de methoden die je bij opdracht 1 en 2 hebt gemaakt en breid het programma uit met een knop waarmee je een raster tekent van 20 rode horizontale lijnen met een tussenruimte van 10 pixels en 20 blauwe verticale lijnen met een tussenruimte van 10 pixels die samen een raster (ruitjespapier) vormen.


Hoofdstuk 11 Tekenen

115

Interactief tekenen met invoervelden Natuurlijk kun je ook hier de diverse getallen vervangen door variabelen. Als je meer lijnen wilt tekenen dan doe je dat op dezelfde manier als bij de getallenreeksen in hoofdstuk 5. In opdracht 4 ga je hiermee aan het werk. C kent een variabele type Point. Een dergelijke variabele kent twee waarden: X en Y. Samen vormen ze de coördinaten van een punt. Een variabele van het type Point declareer je als volgt: Point puntA = new Point(0,0);

T

Zoals altijd geef je het type en de naam van de variabele aan. Maar in tegenstelling tot de int- en string-variabelen hiervoor moet je de eerste keer de variabele vullen met het commando new. Dit komt omdat het een ingewikkelder variabele is.

De ColordDialog kun je op twee manieren aanmaken: Vanuit de Toolbox sleep je de ColorDialog op je formulier. Dit werkt net zoals de ErrorProvider.

C

1

EP

ColorDialog( ) Een ColorDialog is een stukje programmacode dat de gebruiker een keuzescherm aanbiedt, waarin de gebruiker een kleur kan uitkiezen.

N

2 Je maakt zelf een nieuwe ColorDialog aan door de volgende regel ergens in je code toe te voegen: ColorDialog colorDialog = new ColorDialog();

O

De geselecteerde kleur wordt opgeslagen in de eigenschap Color van de variabele van het type ColorDialog. In het voorbeeld is dit dus colorDialog.Color.

C

Om een ColorDialog te kunnen gebruiken, moet je de ColorDialog oproepen (in het Engels ‘show’). Het volgende commando laat de ColorDialog zien met de naam ColorDialog1: colorDialog1.Show()

Stel je wilt dat de achtergrondkleur van een formulier de geselecteerde kleur krijgt, dan kun je dat als volgt doen: MyForm.BackColor = colorDialog1.Color;

Opdracht Tekenen 4: Variabel raster 1

Breid het programma Tekenen 3 uit met ColorDialog waarmee je de kleur van het raster kunt wijzigen.


116

2 a b c

Deel 1: Introductie

Gebruik drie invoervelden: Het aantal verticale lijnen Het aantal horizontale lijnen De tussenruimte in pixels Je wilt de gebruiker natuurlijk ook laten bepalen waar het raster begint en waar het eindigt.

T

Let op! Iedere lijn heeft een startpunt A en een eindpunt B. Ieder punt heeft een locatie die aangegeven wordt met een X-coördinaat en een Y-coördinaat. Als je de gebruiker deze wilt laten invoeren, heb je voor een lijn vier invoervelden nodig: de X-coördinaat van A, de Y-coördinaat van A, de X-coördinaat van B en de Y-coördinaat van B.

EP

Stel je maakt de globale variabelen AX, AY, BX en BY aan van het type int. Dan wordt de code om een lijn te tekenen: papier.DrawLine(tekenpotlood, AX, AY, BX, BY);

Een andere mogelijkheid is om twee globale (algemene) variabelen aan te maken van het type Point. Een Point bestaat standaard uit een X- en een Y-coördinaat. Dan wordt de code:

C

papier.DrawLine(tekenpotlood, A, B);

N

3 Vul je programma zo aan dat het raster wordt getekend, waarbij gebruik wordt gemaakt van de ingevoerde waarden.

O

Opdracht Tekenen 5: Scrollbar toevoegen

C

Voeg een verticale scrollbar toe aan je programma en programmeer deze als volgt. Wat zou er gebeuren? private void frmRasterTekenen_Load(object sender, EventArgs e) { vScrollBar1.Value = tussen; } private void vScrollBar1_Scroll(object sender, ScrollEventArgs e) { tussen = vScrollBar1.Value; txbTussen.Text = vScrollBar1.Value.ToString(); pbTekening.Refresh(); tekenHorizontaal(kleurX); tekenVerticaal(kleurY); }


Hoofdstuk 11 Tekenen

117

Interactief tekenen met events Het invoeren van coördinaten door middel van TextBoxen is lang niet altijd handig. We hebben de muis niet voor niets uitgevonden. In hoofdstuk 10 Events heb je geleerd hoe je de coördinaten van een muisklik uitleest. Nu gaan we die coördinaten gebruiken om een lijn te tekenen.

Opdracht Tekenen 6: Een halve lijn Vul Opdracht Tekenen 4 zo aan dat er een lijn getekend wordt van de oorsprong (coördinaten 0,0) naar het punt waar je geklikt hebt. 1

Je hebt een globale variabele nodig van het type Point. Noem deze puntA.

T

Point puntA = new Point(0,0);

EP

2 Je gaat naar het MouseClick event (bliksemschichtje in het Properties-scherm) van de PictureBox. Dubbelklik op het woord MouseClick. 3 In de Code Editor voer je de volgende stappen uit:

// Zorg dat puntA de coördinaten krijgt van het punt waar je geklikt hebt. puntA = e.Location;

C

Voeg de volgende code toe om te tekenen:

papier.DrawLine(tekenpotlood, 0, 0, puntA.X, puntA.Y);

N

4 Test je programma.

O

Opdracht Tekenen 7: De hele lijn

C

Pas Opdracht Tekenen 5 zo aan dat er pas een lijn getekend wordt als je op twee plekken op het scherm hebt geklikt. Wat er verandert is dat je bij moet houden hoe vaak je geklikt hebt. Na één keer klikken moet je het punt even onthouden, na twee keer klikken kun je de lijn tekenen. Let op! Er is een verschil tussen click en mouseclick!

1 Je hebt nu twee globale variabelen nodig. – Eén van het type Point. Noem deze puntB. – Eén van het type int. Noem deze aantalKlikken. Hiermee hou je bij hoe vaak er geklikt is. 2 Je gaat naar het MouseClick event (bliksemschichtje in het Properties-scherm) van de PictureBox.


118

Deel 1: Introductie

3 Voeg code toe om te kijken hoe vaak er geklikt is. if (aantalKLikken == 0) { // Er is geklikt, dus we moeten bij aantal 1 optellen. aantalKlikken = 1; // Bewaar de locatie puntA = e.Location; } else {

T

// aantalKlikken is niet 0, dus er is al 1 x geklikt. We zetten aantalKlikken weer // op 0 voor de volgende lijn. aantalKLikken = 0; puntB = e.Location;

4 Voeg de volgende code toe om te kunnen tekenen:

EP

papier.DrawLine(tekenpotlood, puntA, puntB); }

5 Test je programma.

C

Andere objecten tekenen

O

N

Als je bij een Graphics object (zoals ‘papier’) kijkt naar de bijbehorende methoden, zijn er behalve DrawLine nog veel meer methoden om te tekenen: DrawRectangle, DrawEllipse, DrawLines enzovoort. Iedere methode heeft andere variabelen.

Opdracht Tekenen 8: Rechthoek

C

Pas het programma van Opdracht Tekenen 6 zo aan dat: – er twee invoervelden komen voor de breedte en de hoogte van de rechthoek; – als je ergens dubbelklikt op het scherm, er vanaf die plek (de linkerbovenhoek) een rechthoek getekend wordt met de opgegeven afmetingen.

Opdracht Tekenen 9: Rechthoek met diagonaal * Pas het programma van Opdracht Tekenen 6 zo aan dat: – als je op twee plekken klikt er niet alleen een lijn tussen die twee punten getrokken wordt, maar er ook een rechthoek omheen getekend wordt. De getekende lijn is de diagonaal.


Hoofdstuk 11 Tekenen

119

Schermafbeelding 44: Diagonaal in rechthoek

Opdracht Tekenen 10: Ellips *

EP

T

Pas het programma van Opdracht Tekenen 6 zo aan dat: – als je op twee plekken klikt er niet alleen een lijn tussen die twee punten getrokken wordt, maar dat er ook een ellips getekend wordt binnen de rechthoek. De getekende lijn is de diagonaal.

Schermafbeelding 45: Diagonaal in rechthoek met ellips

C

Tekenen door meerdere punten

N

In hoofdstuk 6 heb je iets geleerd over Arrays. Een Array is een variabele in de vorm van een tabel. We hebben Arrays met teksten en getallen gevuld. Maar je kunt een Array ook vullen met punten (Point).

1

O

Wil je de eerste 10 punten waar je geklikt hebt op een scherm bewaren dan kun je dit nu eenvoudig doen: Eerst maak je aan het begin van je programma een Array aan waarin je deze punten kunt bewaren:

C

Point[] punten = new Point[10];

2 Vervolgens moet je ergens een variabele hebben (we hebben deze regelIndex genoemd) waarmee je het juiste punt kunt ophalen of wegschrijven: int regelIndex = 0;

De regelIndex begint bij 0 omdat iedere Array begint met 0. 3 En je moet bijhouden hoe vaak je geklikt hebt: int aantalKliks = 0;

Nu kun je elke keer als je ergens klikt de coördinaten waar je geklikt hebt wegschrijven naar de Array punten.


120

Deel 1: Introductie

Maak een click event aan voor het formulier. Kies bij Properties het event MouseClick en Visual Studio maakt de volgende code voor je: private void Form1_MouseClick(object sender, MouseEventArgs e) { }

4 Nu kun je nog bijhouden hoe vaak je geklikt hebt: private void Form1_MouseClick(object sender, MouseEventArgs e) { aantalKliks = aantalKliks + 1; }

5 Vervolgens wil je de coördinaten op de juiste plek in de Array opslaan:

T

regelIndex = aantalKliks;

EP

6 Je bent er bijna. Nu alleen nog de coördinaten opslaan in de Array. Voeg de volgende regel toe bij het MouseClick event: punten[regelIndex] = e.Location;

Eerst de beveiliging:

C

7 Het enige probleem dat er nu nog is, is dat als je elf keer klikt, de Array vol is en je een foutmelding krijgt. Als er tien keer geklikt is mag het programma niets meer opslaan. Het programma gaat dan een lijn tekenen tussen de punten van de Array.

O

N

if (regelIndex < 10) { punten[regelIndex] = e.Location; }

8 En tot slot het tekenen van de lijnen:

C

else { Pen tekenpen = new Pen(Color.Red); Graphics papier = this.CreateGraphics(); papier.DrawLines(tekenpen,punten); }

Opdracht Tekenen 11: Lijnen Maak een nieuw programma waarbij na 20 keer klikken tussen alle punten lijnen worden getrokken. Gebruik hiervoor een Array. Kijk naar de functie DrawLines(), niet DrawLine().


Hoofdstuk 11 Tekenen

121

Opdracht Tekenen 12: Golven * Plaats een CheckBox in het programma van Opdracht Tekenen 10 met de naam ‘Golven’. Als je de CheckBox aanklikt moet het programma tussen de diverse punten golven tekenen in plaats van lijnen. Kijk naar de functie DrawBeziers(). Mocht je daarbij een foutmelding krijgen, lees dan goed wat er staat!

Wat moet je nu kunnen en kennen?

EP

T

De bibliotheek System.Drawing Tekenen van lijnen op een object De wijze waarop Windows punten aangeeft op het scherm Verschillende manieren om iets te tekenen op het scherm Instellen van kleuren Het gebruik van Pen Het aanmaken van een Graphics object Het gebruik van scrollbars

C

O

N

C

– – – – – – – –


122

Hoofdstuk 12 Klasse (Class)

Wat is een class?

T

Een class (klasse) is een beschrijving van een object. Binnen het programma ‘Hoe bouw je een auto’ is de class de bouwtekening van bijvoorbeeld een auto zoals een BMW Z3. Het object is in dit voorbeeld de BMW Z3 met het nummerbord NS-76TV.

EP

Een klasse is een verzameling code die samen een geheel vormt. Ieder C-programma bestaat uit klassen. Als je je Code-scherm opent, staat er public partial class. Je formulier is dus ook een klasse. Elke klasse heeft een duidelijke functie.

N

C

Het doel van een klasse is helder omschreven en een klasse is herbruikbaar. We vergelijken het opnieuw met een auto. Een auto bestaat uit een heleboel onderdelen: de motor, het stuur, de carrosserie, de wielen enzovoort vormen samen de auto.

Interface

C

O

Net zoals bij een auto hoef je niet te weten hoe een motor werkt, wat een differentieel is of hoe schijfremmen werken om een auto te kunnen besturen en gebruiken. Een auto moet wél aan je verwachtingen voldoen. Met andere woorden: als je het stuur naar rechts draait verwacht je dat de auto rechtsaf slaat en niet linksaf. Het stuur en alle andere knoppen en pedalen vormen samen de interface van de auto. De interface is dat gedeelte van de auto waarmee je hem bedient. De interface van een klasse is dat gedeelte van de klasse waarmee je de klasse bedient.

Encapsulation Een eigenschap van een klasse is dus dat je hem kunt gebruiken door middel van een interface (hoe je programma er op het scherm uitziet en hoe je het gebruikt). De gebruiker hoeft niet te weten hoe de klasse precies geprogrammeerd is, maar de klasse moet wel aan de verwachtingen van de gebruiker voldoen. Deze eigenschap (dat de gebruiker de achterliggende code niet kent) noemen we encapsulation: de klasse is verborgen of ingekapseld in het programma.


Hoofdstuk 12 Klasse (Class)

123

Herbruikbaar Een goed geschreven klasse kun je in verschillende programma’s gebruiken zonder de code te moeten veranderen. Zodra je je rijbewijs hebt kun je in iedere personenauto rijden, je hoeft je niet aan te passen en de auto ook niet, omdat alle auto’s min of meer dezelfde interface gebruiken: stuur, gaspedaal, koppelingspedaal, rem, versnellingspook, handrem, claxon, richtingaanwijzers en knoppen voor het licht.

Gebruik van een klasse Wat moet je doen om een klasse te kunnen gebruiken in C? Je moet eerst een beschrijving van een klasse maken. Om in autotermen te spreken: je moet eerst een blauwdruk, een bouwtekening, van de auto maken. 2 Om in je programma deze klasse te kunnen gebruiken, moet je een variabele van deze klasse aanmaken. Met andere woorden: je gaat de auto bouwen.

EP

T

1

Het maken van de klasse (beschrijving)

Instructie

Maak in Visual Studio een nieuw Windows forms application-project aan met de naam: MeetkundigeFiguren.

O

1

N

C

Als je een klasse gaat maken, bedenk je eerst wat de functie van de klasse wordt. Hoe eenduidiger beschreven, des te beter het resultaat. Stel je voor dat je een klasse wilt maken die een cirkel beschrijft en tekent.

2 Selecteer in het menu onder Project de optie Add Class.

C

3 Geef deze class de naam: Cirkel. Als het goed is zie je nu de volgende code op het scherm:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MeetkundigeFiguren { class Cirkel { } }


124

Deel 1: Introductie

Bij using staan die bibliotheken die standaard door Microsoft bij een class worden aangeboden.

De code voor de klasse Cirkel komt tussen de accolades te staan onder de naam van de klasse.

EP

Vormgeven van de klasse

T

Let op! Zoals we al in hoofdstuk 1 hebben gezegd, is C hoofdlettergevoelig. Cirkel is dus voor C iets totaal anders dan cirkel! En nog iets vreemds. In dit boek zijn alle zelfbedachte namen voorzien van een grijze achtergrond. Alleen wordt in je code de naam van een class lichtblauw. Ook als je deze zelf bedacht hebt. Omdat ook jouw class onderdeel wordt van Visual Studio en Visual Studio alle object-typen lichtblauw maakt, wordt in de Code Editor jouw class ook lichtblauw. Daarom maken wij deze namen ook lichtblauw.

We hebben een klasse, maar nu moeten we hem gaan vormen.

C

Wat zijn de eigenschappen van een cirkel? Een cirkel wordt bepaald door de straal. Dat is de afstand van het middelpunt naar de rand van de cirkel. Weet je de straal, dan weet je de omtrek en de oppervlakte van de cirkel.

N

Een cirkel kent dus de volgende variabelen (eigenschappen): – Het middelpunt: hier wordt de cirkel getekend. – De straal van de cirkel: dit is een waarde die de gebruiker moet kunnen aangeven.

C

O

Daarnaast kent de cirkel de volgende methoden: – Het berekenen van de omtrek. – Het berekenen van de oppervlakte. – Het tekenen van de cirkel.

Public versus Private Sommige onderdelen van een klasse zijn niet interessant voor de gebruiker. Een automobilist is niet geïnteresseerd in de werking van de koppeling van een auto. Door het keyword private voor een methode of variabele te zetten worden deze onzichtbaar voor de gebruiker. Standaard zijn alle onderdelen private.

Moet een methode of variabele wel zichtbaar zijn voor een medeprogrammeur bij het gebruik van die klasse, dan zet je het keyword public voor de methode of variabele.


Hoofdstuk 12 Klasse (Class)

125

Eigenschappen (properties) Publieke variabelen noemen we ook wel de eigenschappen (properties) van een klasse. Instructie (vervolg) We hebben in ons voorbeeld dus twee variabelen nodig, het middelpunt en de straal. Als de variabele een private variabele is laten we de variabelenaam voorafgaan door een underscore (deze variabele is buiten de klasse niet zichtbaar). _Straal is van het type int. Je wilt er immers een heel getal in opslaan. _Middelpunt is van het type Point. Dit is een meetkundig punt met een X- en een

EP

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing;

T

Y-waarde. Het probleem is dat dit type standaard niet bekend is binnen C. Hiervoor moet je de bibliotheek laden waarmee je kunt tekenen, en dat doe je door using System.Drawing; aan je code toe te voegen.

O

Broncode 8

N

C

namespace MeetkundigeFiguren { class Cirkel { private Point _Middelpunt = new Point(0, 0); private int _Straal = 0; } }

C

Behalve de twee variabelen hebben we zoals gezegd ook drie methoden nodig: stukken code die iets voor ons doen.

–

Oppervlak(). Hiermee bereken je de oppervlakte van de cirkel. De wiskundige for-

mule hiervoor is: π x r2. Hierbij is π het getal pi (C: Math.Pi) en r2 staat voor de straal in het kwadraat. Dus in ons programma _Straal * _Straal.

4 Voeg de volgende regels toe aan de klasse Cirkel: public double Oppervlak() { return Math.Pi * _Straal * _Straal; }

–

Omtrek(). Hiermee bereken je de omtrek van de cirkel. De wiskundige formule hier-

voor is: 2 x π x r. In ons programma ziet dat er als volgt uit: 2 * Math.Pi * _Straal.


126

Deel 1: Introductie

5 Voeg de volgende regels toe aan de klasse Cirkel: public double Omtrek() { return 2 * Math.Pi * _Straal; }

–

Teken(). Hiermee teken je de cirkel. We voegen nu alleen de methode toe. De code

om te tekenen voegen we later toe. Let op! Als er een foutmelding verschijnt omdat er een lege methode wordt toegevoegd, is het geen probleem om deze voorlopig weg te laten. 6 Voeg de volgende regels toe aan de klasse Cirkel:

EP

T

public void Teken() { }

Opdracht Klasse 1: Rechthoek aanmaken

C

N

O

– – – – –

Maak naast de klasse Cirkel een klasse met de naam Rechthoek. Een rechthoek kent zoals we in hoofdstuk 11 hebben gezien de volgende onderdelen: Een beginpunt waar de rechthoek getekend wordt. De breedte en de lengte van de rechthoek. De oppervlakte van de rechthoek (lengte x breedte). De omtrek van de rechthoek (2 x (lengte + breedte)). Een methode om de rechthoek te tekenen (deze hoef je nog niet te vullen met code).

C

Gebruik van een klasse Als je een klasse hebt, wil je de klasse in verschillende programma’s kunnen gebruiken. Maar hoe maak je gebruik van de klasse Cirkel in bijvoorbeeld het programma MeetkundigeFiguren? Eerst moet je je realiseren dat de klasse die je gemaakt hebt niets anders is dan een bouwtekening voor een cirkel. Je hebt bepaalde eigenschappen gedefinieerd die je kunt gebruiken. Wat doe je met een bouwtekening van een auto? Juist: op basis van die tekening bouw je bijvoorbeeld een rode auto met chassisnummer WAUZZZ8V5KA106598. En wij? Wij maken een Cirkel met bijvoorbeeld de naam cirkel. En in feite hebben we dit al diverse keren gedaan. Kijk maar. Point punt = new Point(0,0); betekent niets anders dan:


Hoofdstuk 12 Klasse (Class)

127

Maak een nieuw object aan van het type Point en met de naam punt en geef X en Y de waarde 0. Voor onze cirkel ziet dat er als volgt uit: Cirkel cirkel = new Cirkel();

Dit kunnen we ook in ons formulier doen.

Instructie

Cirkel cirkel = new Cirkel();

EP

T

– Ga naar het tabblad Form. – Plaats een Button op het form met de naam btnCirkel en de tekst Cirkel. – Plaats twee Labels op het form: 0 Eén met de naam lblOmtrekCirkel en de tekst Omtrek =. 0 Eén met de naam lblOppervlakCirkel en de tekst Oppervlak =. – Dubbelklik op de Button btnCirkel zodat je in het click event van de Button terechtkomt. – Nu voegen we de regel toe om een cirkel aan te maken:

C

Vervolgens kun je de publieke methoden (zichtbaar buiten de klasse) gaan gebruiken door achter de naam van de variabele een punt en de naam van de methode te kiezen uit het lijstje dat getoond wordt. – Typ de volgende programmaregels, waarmee je de berekende oppervlakte en omtrek in de desbetreffende Labels toont:

N

lblOmtrekCirkel.Text = “Omtrek = ” + cirkel.Omtrek().ToString(); lblOppervlakCirkel.Text = “Oppervlak = ” + cirkel.Oppervlak().ToString();

C

O

Test het programma Als je alles goed gedaan hebt, kun je op de Button btnCirkel klikken en krijg je als output in de twee Labels het volgende:

Schermafbeelding 46: Scherm klasse

Dit komt omdat we een Cirkel hebben aangemaakt zonder aan te geven hoe groot de straal moet worden. De variabele _Straal is private en dus kunnen we deze niet


128

Deel 1: Introductie

zien of wijzigen. Om dit te verhelpen moet er nog iets aan de class worden toegevoegd: een constructor.

Constructor Een constructor is een speciale methode waarmee je variabelen kunt doorgeven aan een klasse op het moment dat je hem maakt. Dit doe je met het keyword new. Een constructor heeft altijd exact dezelfde naam als de klasse zelf (let op hoofdletters en kleine letters). Een constructor is altijd public, anders kun je hem buiten de klasse niet zien of gebruiken.

T

Let op! Een constructor heeft nooit een type en is geen void!

EP

Voor het uitrekenen van de omtrek en de oppervlakte van een cirkel hebben we in eerste instantie de straal nodig. De benodigde variabelen om te tekenen voegen we later in een andere constructor toe. In code ziet het er nu eerst als volgt uit:

C

public Cirkel(int straal) { _Straal = straal; }

O

N

Instructie – Open de klasse Cirkel. – Voeg bovenstaande regels toe direct onder de { na class Cirkel.

C

De eerste regel noemen we de declaratie van de constructor. Het keyword public gevolgd door de naam van de klasse met tussen haakjes het type en de naam van de variabele(n) die je bij het aanmaken van de klasse wilt meegeven. In dit geval is dat int straal.

De variabele straal is alleen bekend tussen de accolades. Het gevolg hiervan is dat de variabele straal in de rest van de klasse niet bekend is. Daarom geven we binnen de constructor de waarde van straal door aan _Straal: _Straal = straal;

Op deze manier geven we variabelen door aan een klasse. We noemen dit ook wel het parametriseren van de code. Echter, als je nu je programma runt, krijg je de volgende foutmelding:


Hoofdstuk 12 Klasse (Class)

129

Schermafbeelding 47: Constructor-fout

En inderdaad, we hebben een constructor gemaakt die eist dat je een straal opgeeft. Maar in het formulier hebben we geen parameter opgegeven bij de Button btnCirkel: Cirkel cirkel = new Cirkel();

T

We moeten hier tussen de haakjes van de Cirkel een waarde opgeven voor de straal, bijvoorbeeld drie pixels. Cirkel cirkel = new Cirkel(3);

EP

Als je het programma test, zul je zien dat er bij de twee Labels nu een berekende waarde komt te staan, maar wel met veel cijfers achter de komma.

Opdracht Klasse 2: Rechthoek berekeningen

N

C

Maak behalve een knop voor de Cirkel ook een knop aan voor een Rechthoek. Deze knop berekent de oppervlakte en de omtrek van een rechthoek en laat deze waarden zien in de desbetreffende Labels. Beperk het aantal cijfers achter de komma door (“0.00”) na ToString te typen.

O

Opdracht Klasse 3: Rechthoek constructor Maak ook een constructor voor de klasse Rechthoek.

C

Let op! Deze constructor kent twee variabelen: de breedte en de lengte.

Het invoerveld

Als je een TextBox txbStraal wilt koppelen aan de bijbehorende variabele in de klasse kun je het beste een globale variabele introduceren binnen het formulier. Ook deze variabele noem je straal. Binnen het onChange event van de TextBox txbStraal zorg je ervoor dat deze variabele de waarde krijgt van de TextBox (txbStraal.Text). Daarna vervang je de waarde 3 bij het maken van de Cirkel met de naam cirkel door de variabele straal. Dus:


130

Deel 1: Introductie

Instructie Voeg de volgende code toe aan het formulier, direct na de tweede accolade openen ({): int straal = 0;

1

Maak op het formulier een TextBox aan met de naam txbStraal.

2 Dubbelklik op de TextBox txbStraal en voeg de volgende code toe: straal = int.Parse(txbStraal.Text);

3 Dubbelklik op de Button btnCirkel en wijzig de 3 in straal zodat de coderegel wordt:

T

Cirkel cirkel = new Cirkel(straal);

4 Test je code.

EP

5 Voeg een Errorprovider (foutafhandeling) toe aan de TextBox txbStraal zodat je alleen nog maar getallen kunt invoeren.

C

Overloaden

N

Als je wilt gaan tekenen heb je, zoals we al gezien hebben in hoofdstuk 11, de volgende zaken nodig: een Pen van een bepaalde kleur en iets om op te tekenen. Alles waar Windows op kan tekenen (een PictureBox, een formulier, een Button) heet in Windows een graphics object.

C

O

De Cirkel willen we tekenen op het formulier dat we nu gebruiken. Maar de klasse Cirkel kent dit formulier niet. We moeten het formulier dus als een variabele doorgeven. Daarvoor moeten we de constructor uitbreiden, of een nieuwe maken. We mogen namelijk net zo veel constructors in een klasse maken als we willen, zolang ze maar verschillende aantallen en/of typen variabelen gebruiken. Als je meer dan één constructor hebt, noemen we dit Overloaden.

We moeten het volgende doen: – Een nieuwe private variabele maken van het type Form. Deze kent de klasse echter niet: we moeten daarom de bibliotheek System.Windows.Forms toevoegen. – Een nieuwe constructor toevoegen met daarin de straal en het formulier.

1

Instructie Voeg bovenin de klasse Cirkel, bij de andere regels die beginnen met using, de volgende regel toe: using System.Windows.Forms;


Hoofdstuk 12 Klasse (Class)

131

2 Maak bij de private variabelen een variabele aan met de naam _Form: private Form _Form = new Form();

3 Maak een nieuwe constructor aan: public Cirkel(int straal, Form form) { _Straal = straal; _Form = form; }

Opdracht Klasse 4: Rechthoek overloaden

EP

T

Voeg twee invoervelden toe aan je formulier voor de lengte en breedte. Voeg ook twee algemene (global) variabelen aan je formulier toe voor de lengte en breedte en pas de Button btnRechthoek zo aan dat deze gebruikmaakt van deze twee variabelen.

Het tekenen

Instructie Voeg de volgende code toe in de klasse Cirkel en de methode Teken():

N

1

C

We hebben nu een formulier waarop getekend kan worden. Dus kunnen we gaan tekenen. Hiervoor moeten we alleen nog de methode Teken() in de klasse Cirkel aanpassen. Dit doe je zoals in hoofdstuk 11.

O

Pen myPen = new Pen(Color.Red); Graphics papier = _Form.CreateGraphics(); papier.DrawEllipse(myPen, _Middelpunt.X, _Middelpunt.Y, _Straal * 2, _Straal * 2);

C

Let op! DrawEllipse() tekent de cirkel waarschijnlijk anders dan je verwacht. C neemt het gegeven punt als linkerbovenhoek van een rechthoek waarbinnen de ellips getekend wordt. De rechthoek wordt gemaakt op basis van de opgegeven lengte en breedte. Als je dit vervelend vindt kun je dit oplossen door het punt te verplaatsen (één keer de straal naar rechts en één keer naar beneden).

2 Als je het middelpunt wilt verplaatsen, voeg je het volgende toe: papier.DrawEllipse(myPen, _Middelpunt.X - _Straal, _Middelpunt.Y - _Straal, 2 * _Straal, 2 * _Straal);

3 Pas het formulier zo aan, dat het formulier zelf wordt doorgegeven aan de klasse en pas de code van btnCirkel zo aan, dat er een cirkel getekend wordt.


132

Deel 1: Introductie

4 Pas de regel in de Button btnCirkel (waarin de variabele cirkel wordt aangemaakt) zo aan, dat nu het formulier wordt doorgegeven aan de klasse. this is het codewoord voor het huidige formulier. Cirkel cirkel = new Cirkel(straal,this);

5 Voeg een extra regel toe om de cirkel te tekenen: cirkel.Teken();

6 Test het programma.

Opdracht Klasse 5: Rechthoek tekenen

EP

T

Breid de klasse Rechthoek dusdanig uit en pas het formulier zo aan, dat je met de knop btnRechthoek een rechthoek kunt tekenen.

Uitbreiden van de klasse

N

C

We hebben nu een werkende klasse. Je kunt op basis van de straal en het formulier een cirkel laten tekenen, de oppervlakte en de omtrek berekenen. Je kunt alleen nog niet aangeven waar je de cirkel wilt tekenen, welke kleur de cirkel heeft of hoe dik de lijn moet zijn waarmee je de cirkel tekent. De eigenschap middelpunt (een variabele van een klasse noemen we ook wel een eigenschap) bestaat al. We moeten dus alleen de eigenschappen voor kleur en breedte van de cirkel nog toevoegen. Instructie Open de klasse Cirkel.

O

1

2 Voeg bij de private variabelen de volgende regels toe:

C

private Color _Kleur = Color.Red; private int _Lijndikte = 1;

3 Vervang in de methode Teken() bij het aanmaken van myPen de kleur door deze twee variabelen, gescheiden door een komma: Pen myPen = new Pen(_Kleur,_Lijndikte);

De klasse heeft nu nieuwe eigenschappen gekregen. Nu moet je er nog voor zorgen dat je deze kunt invoeren. Dit gaan we doen door een derde constructor aan te maken. En nu één waarbij de variabelen middelpunt, kleur en breedte worden doorgegeven:


Hoofdstuk 12 Klasse (Class)

133

4 Voeg de volgende constructor toe aan de klasse Cirkel: public Cirkel(int straal, Form form, Point middelpunt, Color kleur, int lijndikte) { _Straal = straal; _Form = form; _Middelpunt = middelpunt; _Kleur = kleur; _Lijndikte = lijndikte; }

Nu ben je klaar om de vernieuwde klasse te gebruiken in je formulier. Het toevoegen van kleur aan de cirkel

EP

T

5 Voeg een Button toe aan het formulier met een ColorDialog. Eigenschappen van de Button: 0 Naam: btnKleur 0 Tekst: Kleur

6 Voeg in het codescherm van het formulier een algemene variabele toe voor de kleur, direct achter de tweede accolade openen ({):

C

Color kleur = Color.Blue;

7 Ga terug naar het formulier en dubbelklik op de knop btnKleur.

N

8 Voeg de volgende code toe aan de btnKLeur om de ColorDialog te tonen en de waarde van de ColorDialog toe te kennen aan de variabele kleur.

O

ColorDialog colorDialog1 = new ColorDialog(); colorDialog1.ShowDialog(); kleur = colorDialog1.Color;

C

Het toevoegen van een variabele Lijndikte aan de cirkel 9 Voeg een TextBox toe om de breedte te kunnen invoeren van de te tekenen cirkel. Eigenschappen van de TextBox: 0 Naam: txbLijndikte 0 Tekst: 1

10 Voeg in het codescherm een algemene variabele toe voor de lijndikte, direct achter de tweede accolade openen. int lijndikte = 1;

11 Ga terug naar het formulier en dubbelklik op de TextBox txbLijndikte. 12 Voeg de volgende code toe om de inhoud van de TextBox txbLijndikte door te geven aan de variabele lijndikte: lijndikte = int.Parse(txbLijndikte.Text);


134

Deel 1: Introductie

13 Foutafhandeling voeg je zelf toe. Het toevoegen van de mogelijkheid om de cirkel daar te tekenen waar de gebruiker wil Nu gaan we een event handler (zie hoofdstuk 10) toevoegen zodat als je ergens op het scherm klikt, daar de Cirkel wordt getekend. Maar eerst moeten we nog een algemene variabele aanmaken van het type Point. Daarin slaan we de plek op waar we geklikt hebben en waar we dus de cirkel willen tekenen. 14 Voeg in het codescherm een algemene variabele toe voor het middelpunt, direct achter de tweede accolade openen. Point middelpunt = new Point(0,0);

T

15 Ga terug naar het formulier en open nu het Properties-scherm van het formulier. 16 Klik op het bliksemschichtje.

EP

17 Dubbelklik op MouseClick. Je komt nu in de code terecht die de muisklikken afhandelt.

C

Nu gaan we de code toevoegen om de plek waar geklikt is om te zetten naar de variabele middelpunt. Voeg de volgende code toe: middelpunt = e.Location;

O

N

Het wijzigen van de tekeninstructie Aan het tekenen zelf hoeft niets gewijzigd te worden. Alleen een aantal kenmerken (kleur, middelpunt en lijndikte) moeten netjes geïnitialiseerd worden: bij het aanmaken van de klasse moeten de variabelen een beginwaarde krijgen. 18 Daarom gaan we in de knop btnCirkel de regel wijzigen die onze cirkel aanmaakt:

C

Cirkel cirkel = new Cirkel(straal,this);

wordt:

Cirkel cirkel = new Cirkel(straal, this, middelpunt, kleur, lijndikte);

Opdracht Klasse 6: Rechthoek uitbreiden Breid de klasse Rechthoek uit en pas het formulier zo aan dat je met de knop btnRechthoek niet alleen een rechthoek kunt tekenen, maar dat de kleur van de rechthoek en de lijndikte van de rechthoek ook worden aangepast. Zorg ervoor dat de rechthoek daar getekend wordt waar het laatst geklikt is.


Hoofdstuk 12 Klasse (Class)

135

Wat moet je nu kunnen en kennen? Wat is een class? Gebruik van een class Constructor

Eigenschappen (Properties) van een klasse Verschil tussen private en public Hoe een cirkel getekend wordt door C Overloaden

C

O

N

C

EP

T

– – – – – – –


136

Hoofdstuk 13 WPF

Wat is WPF?

T

WPF staat voor Windows Presentation Foundation en is een andere manier om schermen vorm te geven met C dan met Windows Forms. In hoofdstuk 7 heb je geleerd hoe je met behulp van Windows Forms (WinForms) een scherm kunt vormgeven. Waarom dan nu ook nog WPF?

EP

Windows Forms

N

C

Windows Forms maakt gebruik van de Grafische User Interface (GUI) van Windows. Hierdoor heb je de beschikking over alle controls zoals TextBox, Button, Label enzovoort die ook in Windows gebruikt worden. Dit is erg makkelijk. Je kunt gebruikmaken van de kant-en-klare controls: ze zijn mooi ontworpen en doen meteen de dingen die je wilt dat ze doen. Alleen als je iets anders wilt dan de standaard Windows-controls, dan wordt het lastig. Denk bijvoorbeeld aan een Button waar behalve de tekst ook een plaatje op staat, dan moet je zo’n control zelf maken of ergens anders vandaan halen. Gelukkig zijn er op het internet talloze mogelijkheden om aan zulke controls te komen, maar het blijft toch handig om ze zelf te kunnen maken.

C

O

Windows Forms wordt gemaakt in C – voor een C-programmeur geen probleem, maar voor een ontwerper is dat lastiger. Windows Forms is ook meer geïntegreerd met C zelf. Windows Forms maakt gebruik van: – GDI+ (Grafics Device Interface), – Win32 – Windows Kernel GDI+ en Win32 worden beide uitgevoerd door de CPU (Centrale Processor Unit, de processor).


Hoofdstuk 13 WPF

137

C

EP

T

Schematisch ziet de opbouw er bij WinForms als volg uit:

N

Figuur 11 Werking WindowsForms

Eigenschappen WPF

C

O

WPF is een nieuwere techniek die je meer mogelijkheden geeft om zelf de controls naar je hand te zetten. Zo kun je knoppen maken met teksten, plaatjes en verschillende kleuren letters. Je kunt natuurlijk de standaardinstellingen van Windows gebruiken, maar als je een unieke vormgeving wilt, kun je dit met WPF voor elkaar krijgen. WPF maakt geen gebruik van de GUI van Windows. Bij WPF maak je je controls zelf met behulp van XAML. Door gebruik te maken van XAML is WPF als het ware losgeknipt van C. Hierdoor is het makkelijker voor een ontwerper om een scherm (de gebruikersinterface) los van het achterliggende programma in C te ontwerpen en te maken. Hierdoor kun je sneller werken als je in grotere teams samenwerkt. Microsoft heeft hiervoor een aparte tool: Blend for Visual Studio.

Daar waar Windows Forms gebruikmaakt van GDI+ en Win32, maakt WPF gebruik van: – MilCore (Media Integration Layer Core, de koppeling tussen WPF en DirectX); – DirectX (voor gebruik van multimedia, in eerste instantie voor games en video’s met nadruk op beeldverwerking, maar ook voor geluid).


138

Deel 1: Introductie

MilCore en DirectX maken allebei gebruik van de grafische processor van je PC. Hierdoor is de opbouw van je schermen veel sneller. Beide technieken maken gebruik van de Windows-kernel. Hierdoor kun je zowel de Windows Forms als de WPF-applicaties alleen in Windows gebruiken.

Managed versus unmanaged code

C

O

N

C

EP

T

Een ander belangrijk verschil is dat je C-applicatie normaal gesproken valt onder ‘managed code’. Managed code betekent dat je code bij de uitvoering beheerd wordt. Dit voorkomt dat verschillende stukken software gebruikmaken van hetzelfde geheugen. Dit is veiliger maar ook trager. De programmeur kan ook niet zelf direct de hardware aansturen: er zit altijd een laagje tussen. Bij unmanaged code kun je als programmeur wél direct de hardware aansturen. Dit is minder veilig, maar wel veel sneller. MilCore valt onder de unmanaged code.

Figuur 12 Werking Windows Presentation Framework (WPF)

XAML XAML staat voor eXtensible Application Markup Language. Dit betekent dat de onderdelen van de GUI net zoals een HTML-pagina worden beschreven. Let op: – XAML lijkt heel erg veel op HTML, maar is geen HTML. – XAML is hoofdlettergevoelig, net zoals C, HTML is niet hoofdlettergevoelig.


Hoofdstuk 13 WPF

139

Een XAML-bestand herken je aan de extensie .xaml. Voor het gebruik van XAML gelden de volgende regels: – Gebruik tags. Een tag is iets wat tussen driehoekige haken staat: <Grid>

– Een tag moet altijd afgesloten worden. Dit kan op twee manieren: 0 Door gebruik te maken van een begin- en een eind-tag <Grid> </Grid>

0

Hierbij is de eindtag voorzien van een forward slash: / Door gebruik te maken van een forward slash aan het einde van de eerste tag: <Button Content=”Button” Margin=”142,217,0,0”/>

– Je mag nooit een tag binnen de ene tag beginnen en in een andere tag sluiten. Tijd voor de eerste opdracht.

Start Visual Studio.

EP

1

T

Instructie

C

O

N

C

2 Klik op Create a new project en kies voor WPF C Application.

Schermafbeelding 48: Create a new project, WPF selectie

3 Je komt nu in het Configure your new project scherm. Vul het volgende in: Project name: MijnEersteWPFApplicatie


Deel 1: Introductie

EP

T

140

Schermafbeelding 49: Configure your new project

C

En klik op Next.

C

O

N

4 Je ziet nu het volgende scherm:

Schermafbeelding 50: Additional information


Hoofdstuk 13 WPF

141

EP

T

5 Klik op Create. Je ziet nu het volgende scherm:

Schermafbeelding 51: MainWindow.xaml

C

In het bovenste middelste scherm zie je hoe je programma eruit komt te zien – nu een leeg venster met als titel linksboven MainWindow.

C

O

N

In het onderste middelste scherm zie je de XAML-beschrijving van je programma. In het XAML-scherm zie je een <Grid>-tag die omsloten wordt door een openings- en een sluittag: <Window>. Dit betekent dat de <Grid>, waar je van alles op kunt tekenen en plaatsen, binnen de Window valt. Een Window is een venster dat je kunt openen, sluiten, groter en kleiner kunt maken enzovoort. Je kunt in beide schermen werken. Als je in het XAML-scherm fouten maakt dan ‘verdwijnt’ je mooi vormgegeven applicatie uit het voorbeeldscherm. Herstel je je fout, dan wordt de applicatie weer netjes getoond. Als je de breedte van je venster wilt wijzigen van 800 naar 400 pixels kun je dat doen door in het XAML-scherm de Width-eigenschap van Window aan te passen, of door in het voorbeeldscherm de rechterkant van het venster te verslepen totdat de breedte 400 pixels is.

6 Zorg ervoor dat je applicatie 1000 pixels breed en 250 pixels hoog wordt. 7 Sleep vanuit de Toolbox een Ellipse op het scherm en zorg ervoor dat deze over de hele breedte en hoogte van je applicatie te zien is. 8 Ga in het XAML-venster naar de Ellipse-tag en voeg het volgende attribuut toe om de achtergrondkleur te veranderen in aquamarijn: Fill=”Aquamarine”

9 Ga naar de <Grid>-tag en maak op de volgende manier een label aan. Typ op de volgende regel na de <Ellipse/>-tag, maar vóór de sluit-tag van </Grid> het volgende:


142

Deel 1: Introductie

<Label Content=”Hello Windows Presentation Framework !” HorizontalAlignment=”Center” VerticalAlignment=”Center” Height=”90” Width=”880” FontFamily=”Bauhaus 93” FontSize=”48” Grid.ColumnSpan=”3” Foreground=”#FFBF29D8”/>

10 Ga in het voorbeeldscherm naar de tekst “Hello Windows Presentation Framework” en klik erop. Je krijgt dan een lampje en een driehoekje te zien:

T

Schermafbeelding 52: Toegang tot attributen

EP

Dit zijn de attributen (eigenschappen) van het label.

C

O

N

C

11 Klik op het lampje en het volgende scherm wordt getoond waarin je allerlei attributen kunt instellen:

Schermafbeelding 53: WPF-attributen


Hoofdstuk 13 WPF

143

12 Wijzig de naam van de Window van MainWindow in Mijn eerste WPF-applicatie. 13 Klik op debug. Als het goed is ziet je eerste WPF-applicatie er nu als volgt uit:

Schermafbeelding 54: Mijn eerste WPF-applicatie

T

Namespace Iedere WPF-applicatie begint met de Window-tag:

C

EP

Window x:Class=”MijnEersteWPFApplicatie.MainWindow” xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” xmlns:d=”http://schemas.microsoft.com/expression/blend/2008” xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006” xmlns:local=”clr-namespace:MijnEersteWPFApplicatie” mc:Ignorable=”d”

N

Background=”AliceBlue” Height=”250” Title=”Mijn eerste WPF-applicatie” Width=”1000”>

O

De attributen na de witregel, Background, Height, Title en Width spreken voor zich; dit zijn de kenmerken (=attributen) van je applicatie die bepalen hoe je applicatie eruit komt te zien.

C

De regels daarvoor geven de namespace aan. In dit geval heet de namespace xmlns.

Xmlns staat voor XML namespace. Maar wat is een namespace?

Definitie

Een namespace is een verzameling namen die uniek zijn (binnen die namespace). Als je gaat programmeren moeten alle namen die je gebruikt uniek zijn. Anders weet de computer bijvoorbeeld niet op welke Button je geklikt hebt. Maar op een Windowscomputer draaien heel veel verschillende programma’s van even zoveel programmeurs/bedrijven. Wat is dan een unieke naam voor een Button?


144

Deel 1: Introductie

Fully qualified name Gelukkig hoeven de namen niet zó uniek te zijn. Door gebruik te maken van namespaces zorg je ervoor dat alle namen binnen je programma/applicatie in ieder geval niet meer dan één keer voorkomen. Als je bijvoorbeeld in het venster MainWindow.xaml.cs kijkt zie je daar als namespace staan MijnEersteWPFApplicatie. Bij alles wat je binnen jouw applicatie een naam geeft wordt MijnEersteWPFApplicatie voor deze naam geplakt. Dus als je een button met de naam btnEinde maakt om je applicatie te sluiten dan wordt de eigenlijke naam: MijnEersteWPFApplicatie.btnEinde

Zo’n naam noemen we een fully qualified name.

T

WPF-namespaces

EP

Behalve dat je zelf een namespace en namen moet bedenken bij het programmeren kun je ook gebruikmaken van door anderen bedachte namespaces en namen. Zo is bijvoorbeeld voor XML een standaardbibliotheek van namen, maar ook voor HTML en voor nog heel veel meer. Zo’n voorgedefinieerde set met namen noemen we ook wel een schema.

C

Door xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation als attribuut aan je Window mee te geven geef je aan welk schema er gebruikt moet worden voor je WPF-omgeving, namelijk het schema dat te vinden is op http://schemas.micro-

N

soft.com/winfx/2006/xaml/presentation

xmlns:x, xmlns:d, xmlns:mc zijn verwijzingen naar onderdelen van de namespace xmlns

O

Als je naar de eerste regel kijkt staat daar: x:Class=”MijnEersteWPFApplicatie.MainWindow” Dit is de naam van de klasse van het

C

programma. Een paar regels verder staat: xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

Samen betekent dit dat het programma gebruikmaakt van het door Microsoft gedefinieerde schema /winfx/2006/xaml

WPF-controls Net zoals bij Windows Forms kent WPF ook controls. Het verschil is dat Windows Forms controls op zichzelf staan maar dat je WPF-controls kunt combineren. Wil je binnen Windows Forms een Button hebben met daarop een plaatje én een tekst dan kan dat wel, maar dan moet je óf zelf heel veel werk verzetten óf je kunt een custom control kopen/downloaden.


Hoofdstuk 13 WPF

145

De ToolBox binnen Visual Studio laat je alle WPF-controls zien en ook nu kun je ze door middel van slepen en plakken op je formulier zetten en wordt er automatisch WPF-XAML gegenereerd.

Instructie 1 Maak een nieuw WPF-project aan met de naam WpfSamengesteldeKnop 2 Geef de Window-attributen de volgende waarden: Title=”WPF Samengestelde knop” Height=”450” Width=”800”

EP

x:Name=”btnSamgesteldeKnop” Height=”150” HorizontalAlignment=”Center” VerticalAlignment=”Center” Width=”300”

T

3 Sleep vanuit de Toolbox een Button op het formulier. 4 Geef de Button-attributen de volgende waarden:

N

De attributen Content en Margin halen we weg. Het sluitteken / halen we uit de begin-tag. En we plaatsen een eind-tag: </Button> . Tussen de twee Button-tags plaatsen we een <Grid>-tag door <Grid> ertussen te typen.

O

5 6 7 8

C

Je ziet dat er standaard één tag geplaatst wordt, de openingstag wordt afgesloten met de tekens /> en alle attributen op een regel worden geplaatst. Voor de leesbaarheid plaatsen we alle attributen onder elkaar. We beginnen met het attribuut x:Name en vervolgens zetten we alle attributen op alfabetische volgorde.

C

Visual Studio typt er zelf meteen een eind-tag achter: <Grid></Grid>. Met een <Grid> verdeel je een stuk van je scherm in kolommen en rijen. Je begint te tellen met 0 voor zowel de kolommen als de rijen. Kolom A = 0, kolom B = 1 enzovoort.

Vergeet je om het Content-attribuut weg te halen dan krijg je direct een foutmelding. Dat komt omdat je maar één object in een button als content mag plaatsen. Dus je haalt de content weg en vervangt die door een <Grid>. Een <Grid> is weliswaar één object maar daarop mag je wél meer objecten plaatsen zoals een Image en een TextBox.

9 Nu gaan we de twee kolommen van de <Grid> definiëren. Toets tussen de twee <Grid>-tags het volgende: <Grid.ColumnDefinitions> <ColumnDefinition Width=”150” /> <ColumnDefinition Width=”*” /> </Grid.ColumnDefinitions>

Het sterretje geeft aan dat deze kolom alle resterende ruimte gebruikt.


146

Deel 1: Introductie

10 Na de Grid.ColumnDefinitions plaatsen we een <Image>-tag: <Image x:Name=”imgPlaatjeOpKnop” Grid.Column =”0” HorizontalAlignment=”Left” Margin=”10” Width=”150”>

Zoals je ziet komt het plaatje in de eerste kolom (Grid.Column =”0”).

T

11 Nu moeten we nog ergens een plaatje vandaan halen. Dit doen we met het Image. Source-object. Hiermee kun je verschillende soorten afbeeldingen koppelen aan het WPF Image-object. Wij hebben hier het plaatje lighthouse.jpg uit MijnAfbeeldingen\Sample Pictures\ aan gekoppeld. Let op dat je hier het volledige pad en de naam van een plaatje op jouw systeem invoert. Typ binnen de <Image>-tags het volgende:

EP

<Image.Source> </Image.Source>

C

12 Binnen de <Image.Source>-tags plaatsen we de <BitmapImage>-tags. Hiermee kunnen we onder andere de bron aangeven waar het plaatje gevonden kan worden en hoe we ermee omgaan. Het attribuut DecodePixelWidth zorgt ervoor dat een megagrote afbeelding verkleind wordt als we maar een klein plaatje nodig hebben. Typ binnen de <Image.Source>-tags het volgende:

N

<BitmapImage DecodePixelWidth=”150” UriSource=”C:\Afbeeldingen\Sample Pictures\lighthouse.jpg” />

O

13 Het plaatje staat nu op de knop. Als alles goed gaat zie je dit direct in het voorbeeldscherm. Nu moeten we nog een <TextBox> in de tweede kolom plaatsen. Deze willen we verticaal gecentreerd hebben. Voeg na de laatste </Image>-tag de <TextBox>-tags toe:

C

<TextBox x:Name=”txbInputOpKnop” Grid.Column=”1” Margin=”10” VerticalAlignment=”Center” Width=”140”> </TextBox>

14 Zoals je op je voorbeeldscherm kunt zien heb je nu een knop met daarop een plaatje en een TextBox. Alleen gebeurt er nog weinig als je erop klikt. Je zult zien dat de knop even verandert maar verder gebeurt er nog niets. Daar gaan we nu voor zorgen door in het voorbeeldscherm te dubbelklikken op de knop. Je komt nu in het codescherm en er wordt automatisch een methode aangemaakt die je zelf verder kunt invullen. Tegelijkertijd wordt in XAML de volgende code toegevoegd aan de Button-tag. Deze code zorgt voor verbinding tussen XAML en C:


Hoofdstuk 13 WPF

147

Click=”btnSamgesteldeKnop_Click”

Het codescherm ziet er als volgt uit: private void btnSamgesteldeKnop_Click(object sender, RoutedEventArgs e) { }

15 Voeg de volgende regels toe. Je ziet dat het vrijwel hetzelfde werkt als bij een Windows Forms-applicatie. MessageBox.Show(txbInputOpKnop.Text,”Bericht van samengestelde knop”, MessageBoxButton.OK, MessageBoxImage.Error); txbInputOpKnop.Text = “”;

EP

T

Vervelend is wel dat er soms dingen zijn die niet of net even anders werken. Wat je dan het beste kunt doen is op internet zoeken op hetgeen je wilt doen met daarbij de toevoeging ‘verschil WPF WinForms’. Bijvoorbeeld: MessageBox verschil WPF WinForms

C

16 Test de applicatie. Als je binnen de TextBox klikt kun je tekst invoeren. Klik je buiten de TextBox maar binnen de Button dan zal er een MessageBox verschijnen met daarin de tekst die je in de TextBox hebt ingevoerd. Zodra je de MessageBox wegklikt wordt de TextBox weer leeggemaakt. Kun je ook beredeneren waarom?

C

O

N

Het resultaat ziet er dan als volgt uit:

Schermafbeelding 55: WPF Samengestelde knop


148

Deel 1: Introductie

Opdrachten WPF-Controls Opdracht WPF-Controls 1: Voorbeeld controls Maak een programma met de naam VoorbeeldWPFControls. Plaats van de volgende controls tenminste één op het formulier en pas in ieder geval twee properties bij alle controls aan.

EP

T

BulletDecorator Button Calendar Toolbar OpenFileDialog TextBlock PasswordBox Image

Opdracht WPF-Controls 2: Samengestelde control

C

O

N

C

Maak een programma waarbij je gebruikmaakt van de mogelijkheid om op een knop twee plaatjes en een tekst te plaatsen. Zorg ervoor dat tenminste één knop er als volgt uitziet:

Schermafbeelding 56: WPF Opdracht samengestelde knop

Als je op de knop klikt beëindig je het programma.

Opdracht WPF-Controls 3: Temperatuur Maak een programma waarbij de gebruiker een temperatuur in graden Celsius invult in een TextBox en je met behulp van een Button het programma de temperatuur in Kelvin laat uitrekenen. 0 oC = 273,15 K. Kelvin wordt aangegeven met de hoofdletter K zonder het gradensymbool. Je mag het resultaat laten zien in een MessageBox, een Label of een TextBox.


Hoofdstuk 13 WPF

149

Opdracht WPF-Controls 4: Aanvraag vergunning Maak een WPF-app van Opdracht Selectie 4: aanvragen van een vergunning (zie pagina 57). Zorg ervoor dat de gebruiker alle gevraagde informatie netjes in een aantal TextBoxes kan invoeren. Geef het programma een knop waarmee bepaald gaat worden of je al dan niet een vergunning nodig hebt. Vergelijk je programma met Opdracht Controls 3: Aanvraag vergunning uit hoofdstuk 6. Wat zijn de verschillen?

Opdracht WPF-Controls 5: Rekenmachine

Opdracht WPF-Controls 6: Mondriaan

T

Maak een WPF-app Rekenmachine die behalve de gewone rekenkundige bewerkingen ook kan machtsverheffen. Toon je uitvoer in een groen label met rode letters.

N

C

EP

Maak een WPF-app die het uiterlijk heeft van een Mondriaan-schilderij. Er zijn drie knoppen verstopt in het schilderij met de volgende namen en functionaliteit: – Einde: beëindigen van de programma – Info: deze knop doet het volgende: 0 Er wordt een MessageBox getoond met de tekst Compositie in Rood, Blauw en Geel. 0 In het middelste witte vlak wordt de volgende rode en vetgedrukte tekst in het lettertype Verdana Pro Black getoond: ‘Deze Mondriaan hangt in het Stedelijk Museum te Amsterdam’ – Wis tekst: het wissen van de tekst in het middelste witte vlak.

C

O

Maak gebruik van een Grid dat je opdeelt in 5 kolommen en 7 rijen. Door gebruik te maken van de Grid.Columnspan-functie kun je aangeven dat een item gebruikmaakt van twee of meer kolommen. Iets soortgelijks doet de Grid.RowSpan-functie. Het verstoppen van de knoppen doe je door de drie kleinste vakjes (B4, D4 en D5) als knop te definiëren en de Foreground gelijk te maken aan de Background. Als je dan over de knop gaat met je muis wordt de tekst heel even zichtbaar. Je programma komt er als het goed is als volgt uit te zien:


Deel 1: Introductie

T

150

EP

Schermafbeelding 57: Mondriaan

Opdracht WPF-Controls 7: Samengestelde control 2 *

O

N

C

Wijzig het programma van opdracht WPF-Controls 2 Samengestelde control zo dat er een tweede knop komt met een plaatje en een tekst. Alleen: – als je één keer klikt op de knop dan verandert het plaatje; – als je twee keer klikt op de knop, dan is alleen de tekst zichtbaar die dan gecentreerd wordt over de hele knop; en – als je drie keer klikt wordt het oorspronkelijke plaatje en de oorspronkelijke tekst weer zichtbaar.

Opdracht WPF-Controls 8: Datum – tijd *

C

Zet Voorbeeld Datum (zie schermafbeelding 36 op blz. 92) om naar een WPFapplicatie. Het resultaat moet er als volgt uitzien:


151

T

Hoofdstuk 13 WPF

EP

Voor deze opdracht moet je gebruikmaken van een DatePicker. De knoppen met de tekst Nummer, Naam en Afkorting geven respectievelijk het nummer van de maand, de naam van de maand of de afkorting van de maand weer.

O

N

Waar WPF voor staat Het verschil tussen WinForms en WPF Het verschil tussen managed en unmanaged code Wat XAML is Hoe je een control definieert in XAML Hoe je een formulier maakt in WPF Wat een namespace is Hoe een namespace is opgebouwd Wat een fully qualified name is Wat bedoeld wordt met een schema bij het programmeren Wat een Grid is in WPF en waar je dit voor gebruikt

C

– – – – – – – – – – –

C

Wat moet je nu kunnen en kennen?


152

Nawoord Deel 1

Je hebt nu kennisgemaakt met C, maar belangrijker: je hebt kennisgemaakt met de bouwstenen van ieder computerprogramma: Variabelen Volgorde Selectie Herhaling Events Methoden

EP

T

– – – – – – – Arrays – Foutafhandeling – Classes

C

Met deze basiskennis kun je verder gaan leren. Wat kun je nog meer leren? Heel veel... Als programmeur ben je namelijk nooit uitgeleerd. Het gebruik van databases, objectgeoriënteerd programmeren, het maken van (smartphone)apps en webapplicaties... er valt nog veel te ontdekken!

N

In deel 2 leggen we uit hoe C omgaat met gegevens. Deel 3 gaat over databases en deel 4 behandelt objectgeoriënteerd programmeren.

C

O

Succes met Leren programmeren in C!


C

O

N

C

EP

T

Deel 2: Gegevens


C EP

C

N

O T


155

Inleiding Deel 2: Gegevens

In deel 1 (Introductie) heb je de basis geleerd. In dit deel ga je leren werken met gegevens. We schrijven in dit deel code in het Forms.cs-bestand in plaats van in het Program. cs-bestand. Niet alleen omdat we hier in deel 1 al mee begonnen waren, maar ook omdat we je willen stimuleren een formulier bij je programma te maken. Zo kun je goed zien of en hoe je programma werkt.

EP

T

Eerst leer je in dit deel hoe je binnen C omgaat met gegevens. Hoe stel je lijstjes op en laat je deze gegevens zien aan de gebruiker? Hoe zoek je gegevens op? Dat soort dingen. Je leert als eerste werken met Arrays: een oude techniek die omslachtiger is dan de List die je later leert. Alleen worden Arrays wel nog overal gebruikt, je moet ze gewoon kennen en kunnen gebruiken.

N

C

Daarna leer je allerlei manieren om tekst op te slaan. We beginnen met het aloude tekstbestand (.txt), en via .csv-bestanden en Windows registry maak je kennis met .xml-bestanden. Behalve het omgaan met teksten leer je ook hoe je videobestanden en afbeeldingen kunt inlezen en laten zien in je programma. Bovendien maak je nader kennis met een aantal controls zoals de DataGridView en de ListBox.

O

Deel 3 van deze serie borduurt voort op het werken met gegevens, want dat gaat over het gebruik van databases.

C

Veel succes & plezier!


C EP

C

N

O T


157

Hoofdstuk 1 Interne gegevens

Array In deel 1 hebben we het uitgebreid over Arrays (tabellen) gehad en ze in diverse programma’s toegepast. Van ieder type object kun je een Array maken.

T

string[] vruchten = {“Meloen”, “Appel”, “Peer”, “Aardbei”, “Mandarijn”, “Banaan”};

EP

In dit voorbeeld haal je met de regel vruchten[2] het derde element op en dat heeft dan de naam “Peer”. (Weet je nog? Een Array begint te tellen bij 0.) Deze Array heeft zes elementen. Wil je er een zevende aan toevoegen, dan moet je eerst de grootte van de Array aanpassen met het commando Array.Resize();

C

En alsof dit allemaal nog niet ingewikkeld genoeg is, heeft de Array nog een heel vervelende eigenschap, zoals je zult zien in voorbeeld 2.

N

Voorbeeld Array 1: Smoothies met ingrediëntenlijst

O

Een beginnende smoothy-bar heeft een programma nodig waarin ze per drankje bijhouden welke ingrediënten er in gaan. Wij maken het onderdeel dat de lijst met ingrediënten bijhoudt. Om de ingrediënten te laten zien, maken we gebruik van ListView. Dit voorbeeld leert je meteen om een Listview in te stellen.

C

Array: Voorbereiding We maken een nieuw project met de naam Smoothies. Hernoem het formulier naar frmSmoothies.

1

Plaats de volgende controls op het formulier en pas de genoemde eigenschappen aan:


Deel 2: Gegevens

Type

Naam

Eigenschap

Waarde

Button

btnZoek

Text

Zoek in lijst

BackColor

PaleTurquoise

Button

btnWisSelectie

Text

Selectie ongedaan maken

BackColor

PaleTurquoise

Size

75; 52

Text

Wis ingrediënt

BackColor

LightCoral

Size

75; 58

Text

Default lijst

BackColor

PaleTurquoise

Text

Einde

BackColor

T

158

btnWisIngrediënt

Button

btnDefaultLijst

Button

btnExit

Label

label1

TextBox

txbIngrediënt

ListView

livIngrediënten

Text

Tabel 1: Controls Smoothies 1

LightCoral

Ingrediënt:

EP

Button

C

O

N

C

Het formulier kan er dan als volgt uitzien:

Schermafbeelding 1: Smoothies 1

2 Instellen standaard-controls. Voeg aan de knop btnExit de volgende code toe: this.Close();

3 Definiëren van de standaard-Array. Maak een globale Array string-variabele aan met de naam ingrediënten. string[] ingrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” };

4 Tonen van de Array in de ListViewBox. We maken een aparte methode aan met de naam fillLivIngrediënten:


Hoofdstuk 1 Interne gegevens

159

private void fillLivIngrediënten() { // Deze methode vult de ListView livIngrediënten op basis van de // Array ingrediënten. // Eerst maken we de livIngrediënten leeg. livIngrediënten.Items.Clear(); // Vervolgens vullen we livIngrediënten met behulp van de Array // ingrediënten. foreach (var ingrediënt in ingrediënten) { // Eerst maak je een nieuw ListViewItem aan. ListViewItem livIngrediëntenItem = new ListViewItem(ingrediënt); // Tot slot voeg je het nieuwe ListViewItem toe aan de ListView: livIngrediënten.Items.Add(livIngrediëntenItem);

T

} }

fillLivIngrediënten();

EP

5 We willen dat de ListView direct bij het opstarten wordt gevuld. Daarom voegen we deze methode toe aan het Load event van het formulier. Dubbelklik op het formulier en voeg de volgende regel toe:

C

O

N

C

6 Nu zetten we de property View van livIngrediënten op List. Als je het programma runt moet het er ongeveer als volgt uitzien:

Schermafbeelding 2: Smoothies 2

1

Array: Standaardopmaaklijst We maken nu de lijst op. Omdat we de lijst vanuit een Array vullen en de kolom niet via de properties gedefinieerd hebben, moeten we dit in de code zelf doen. Daarom maken we een methode aan met de naam opmaakLivIngrediënten(). private void opmaakLivIngrediënten() { }


160

Deel 2: Gegevens

2 We voegen de volgende regel toe aan deze methode: livIngrediënten.OwnerDraw = true;

Hiermee zetten we de eigenschap OwnerDraw op true. Dit stelt ons in de gelegenheid om zelf de opmaak te tekenen. 3 Vervolgens moeten we de property View op Detail zetten. Doen we dat niet, dan laat de ListView niets zien. Voeg de volgende regel dus toe aan de methode: livIngrediënten.View = View.Details;

4 Nu zijn we klaar om de opmaak van de kop te regelen. Hiervoor moeten we eerst een kolom aan de ListView toevoegen. Voeg de volgende regel aan de methode toe:

T

ColumnHeader headerIngrediënten = livIngrediënten.Columns.Add(“Ingrediënten”, -2);

EP

Het getal -2 betekent dat de volledige breedte die over is binnen de ListView voor deze kolom gereserveerd wordt. We hebben één kolom, dus deze kolom vult de volledige ListView. 5 Nu moeten we ervoor zorgen dat bij de start van het programma deze methode wordt uitgevoerd. Voeg de volgende regel toe aan de methode frmSmoothies_Load():

C

opmaakLivIngrediënten();

O

N

6 Als je nu het programma runt, gebeurt er nog niets. En dat klopt. Want we hebben ingesteld dat we zelf de inhoud willen tekenen, en die hebben we nog niet aangemaakt. Eerst gaan we naar het event DrawColumnHeader van de ListView livIngrediënten. Je kunt dit vinden als je boven het Properties-scherm op het tabblad met het bliksemschichtje klikt. 7 Dubbelklik op de naam van het event en de volgende methode wordt voor je aangemaakt:

C

private void livIngrediënten_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) { }

8 Eerst stellen we de uitlijning van de kolomkop in. Voeg de volgende twee regels aan de methode livIngrediënten_DrawColumnHeader toe: StringFormat stringFormat = new StringFormat(); stringFormat.Alignment = StringAlignment.Center;

9 Nu stellen we het lettertype van de kop in. Voeg de volgende regel toe aan de methode: Font headerFont = new Font(“Microsoft Sans Serif”, 8, FontStyle.Bold);


Hoofdstuk 1 Interne gegevens

161

10 Om de achtergrondkleur van de ListView in te stellen moeten we de volgende regel toevoegen: e.Graphics.FillRectangle(Brushes.Turquoise, e.Bounds);

Eigenlijk wordt er op de plek van de kolomkop een rechthoek getekend (e.Bounds zijn de afmetingen van de rechthoek!) en gevuld met de juiste kleur. 11 Tot slot moet ook de tekst nog getekend worden. Voeg de volgende regel toe: e.Graphics.DrawString(e.Header.Text, headerFont, Brushes.Black, e.Bounds, stringFormat);

C

EP

T

12 Als je je programma nu runt ziet het er als volgt uit:

N

Schermafbeelding 3: Smoothies 3

O

13 Wel een mooie kop, maar geen ingrediënten. Ook die moeten we zelf tekenen. Hiervoor moeten we het event DrawSubItem aan de ListView livIngrediënten toevoegen. Dubbelklik op event DrawSubItem.

C

Je komt in de methode livIngrediënten_DrawSubItem(): private void livIngrediënten_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { }

14 Instellen van de uitlijning. Voeg de volgende regels toe aan de methode livIngrediënten_DrawSubItem(): StringFormat stringFormat = new StringFormat(); stringFormat.Alignment = StringAlignment.Near;

15 Instellen van het lettertype. Voeg de volgende regel toe: Font subItemFont = new Font(“Microsoft Sans Serif”, 8, FontStyle.Regular);


162

Deel 2: Gegevens

16 Instellen van de regelkleur. Voeg de volgende regel toe: Brush regelKleur = Brushes.PaleTurquoise;

We doen dit met een variabele, omdat we zo meteen iets bijzonders willen doen met de regelkleuren (zie hieronder bij Array: Gestreepte opmaak). 17 Eerst weer het instellen van de achtergrondkleur. Voeg de volgende regel toe: e.Graphics.FillRectangle(regelKleur, e.Bounds);

18 En tot slot tekenen we de tekst van het ingrediënt. Voeg de volgende regel toe: e.Graphics.DrawString(e.SubItem.Text, subItemFont, Brushes.Black, e.Bounds, stringFormat);

N

C

EP

T

19 Als je het programma runt, ziet het er nu als volgt uit:

Schermafbeelding 4: Smoothies 4

C

O

Array: Gestreepte opmaak Wat je vaak ziet, is dat verschillende regels om en om een iets afwijkende kleur hebben. Vooral bij lange lijsten kun je dan makkelijker zaken vinden, ook als er meer gegevens op één regel staan. Dit doen we door te kijken of een regel even of oneven is. Ieder item van de ListView heeft een index die bij 0 begint en bij de laatste eindigt. We kijken of iets even of oneven is met de modulus-functie. Deze functie geeft de rest van een getal nadat je het gedeeld hebt. Delen we een getal door twee, dan is het getal even als de rest 0 is en oneven als de rest 1 is.

1

Voeg in de methode livIngrediënten_DrawSubItem() na de regel: Brush regelKleur = Brushes.PaleTurquoise;

de volgende regels toe: if (e.ItemIndex % 2 == 1) // oneven getal { }


Hoofdstuk 1 Interne gegevens

163

2 Als de ItemIndex even is, wordt de kleur PaleTurquoise. Voeg daarom bij het ifstatement tussen de accolades de volgende regel toe: regelKleur = Brushes.WhiteSmoke;

Schermafbeelding 5: Smoothies 5

EP

T

3 Als je het programma nu runt ziet het er als volgt uit:

Als we willen bepalen of een element geselecteerd is of niet, gebruiken we de property e.Item.Selected in de methode livIngrediënten_DrawSubItem().

N

1

C

Array: Opmaakselectie Een nadeel is dat, als je een ingrediënt aanklikt, er eigenlijk niet veel gebeurt. Het standaardgedrag dat een item een andere kleur krijgt hebben we uitgeschakeld op het moment dat we aangaven dat we alles zelf wilden tekenen.

Ga in de methode livIngrediënten_DrawSubItem() naar de regel

O

Brush regelKleur = Brushes.PaleTurquoise;

Voeg daaronder de volgende regels toe (if-statement):

C

if (e.Item.Selected == true) { regelKleur = Brushes.SteelBlue; }

2 Ga in de methode livIngrediënten_DrawSubItem() naar de regel regelKleur = Brushes.WhiteSmoke;

3 Vervang alleen deze regel door de volgende regels (if-statement): if (e.Item.Selected == true) { regelKleur = Brushes.LightSteelBlue; } else { regelKleur = Brushes.WhiteSmoke; }


164

Deel 2: Gegevens

Je hebt nu, als je het goed gedaan hebt, een genest if-statement. Dat is een ifstatement binnen een if-statement. 4 Als je wilt dat je selectie altijd zichtbaar is, dus ook nadat je control de focus verliest (als je bijvoorbeeld klikt op een andere control) moet je het volgende doen: bij de properties van de ListView livIngrediënten zet je de property HideSelection op false.

C

Schermafbeelding 6: Smoothies 6

EP

T

5 Als je je programma runt en je klikt op het woord Aardbei, ziet het er als volgt uit:

Let op! Als je naast het woord klikt, gebeurt er niets. En dat laten we zo.

C

O

N

Array: Invoer We hebben nu een prachtige ListView, maar we kunnen nog geen ingrediënt toevoegen of verwijderen. Hiervoor breiden we de functionaliteit zo uit dat iemand in het tekstvak een ingrediënt kan invoeren, dat, als je op de knop Zoek in lijst klikt, opgezocht wordt in de Array en geselecteerd wordt in de ListView livIngrediënten. Wordt de ingevoerde waarde niet gevonden, dan krijg je de gelegenheid om het ingrediënt toe te voegen aan de Array en de ListView. Dit maken we nu.

1

Ga naar het code-venster en voeg na de regel

string[] ingrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” };

de volgende regel toe: string ingrediënt = “”;

2 Ga naar het design-venster en dubbelklik op het tekstvak txbIngrediënt. Voeg dan de volgende regel toe: ingrediënt = txbIngrediënt.Text;


Hoofdstuk 1 Interne gegevens

165

Array: Zoeken We maken nu een methode om ingrediënten op te zoeken in de Array. Deze methode geeft, als het ingrediënt gevonden is, de index van de Array ingrediënten terug. Is het ingrediënt niet gevonden, dan geeft de methode de waarde -1 terug. 1

Maak een nieuwe methode aan van het type int, met de naam zoekIngrediënt en de parameter ingrediënt van het type string. private int zoekIngrediënt(string ingrediënt) { }

2 We maken gebruik van een while-loop omdat we de index van de while-loop gebruiken om te constateren of een waarde gevonden is of niet.

T

Maak een variabele aan van het type int met de naam index en de waarde 0: int index = 0;

EP

3 Maak een while-loop die door de Array ingrediënten loopt:

C

while (index < ingrediënten.Length) { index++; }

Let op! Vergeet vooral de regel index++; niet, anders telt je teller niet!

N

4 Voeg in de while-loop boven de regel index++; het volgende if-statement toe:

O

if (ingrediënten[index] == ingrediënt) { break; }

C

5 Als een ingrediënt voorkomt in de Array ingrediënten stopt de loop (het break-commando) en heeft de index de waarde van het elementnummer van de Array. 6 Als er niets gevonden is, heeft index een waarde gekregen die hetzelfde is als de lengte van de Array. Als dat zo is, dan geven we index de waarde -1. Daaraan kun je zien dat er niets gevonden is. Voeg het volgende if-statement toe na de while-loop: if (index == ingrediënten.Length) index = -1;

7 En tot slot geeft de index het regelnummer terug van het gevonden ingrediënt aan de methode die deze methode aangeroepen heeft. Voeg de volgende regel toe: return index;


166

Deel 2: Gegevens

Array: Toevoegen Nu hebben we een methode die aangeeft of een ingrediënt voorkomt in de Array ingrediënten of niet. We hebben ook een methode nodig die een ingrediënt toevoegt aan de Array en aangeeft waar in de Array dit element is toegevoegd. 1

Maak een methode aan van het type int met de naam voegIngrediëntToe, met als parameter een variabele met de naam ingrediënt en van het type string. private int voegIngrediëntToe(string ingrediënt) { }

int grootte = ingrediënten.Length + 1;

T

2 We voegen een ingrediënt toe aan de Array. Deze moet dus met 1 vergroot worden. We gebruiken daarom een variabele die 1 groter wordt dan de lengte van de Array. Voeg de volgende regel toe aan de methode voegIngrediëntToe().

EP

3 Ook willen we weten op welke positie het ingrediënt is toegevoegd. Deze positie is het resultaat van deze methode. Voeg de volgende regel toe: int positie = 0;

4 Nu kunnen we de Array vergroten. Voeg de volgende regel toe:

C

Array.Resize(ref ingrediënten, grootte);

positie

N

5 De Array is vergroot. Aan het einde van de Array is nu een lege plek. Hier voegen we het ingrediënt toe. De positie is gelijk aan grootte -1 omdat de posities beginnen te tellen bij 0. Voeg de volgende regel toe: = grootte – 1;

O

6 Nu kunnen we het ingrediënt toevoegen: ingrediënten[positie] = ingrediënt;

C

7 En tot slot geven we de positie terug. Voeg de volgende regel toe: return grootte - 1;

Array: Toepassen methodes We hebben nu enkele basismethodes gemaakt die we gaan gebruiken. Als het ingrediënt gevonden is, moeten we het ingrediënt opzoeken in de ListView livIngrediënten en selecteren. Als het ingrediënt niet gevonden is, moeten we de gebruiker vragen of hij het ingrediënt wil toevoegen. Zo ja, dan moet het ingrediënt toegevoegd worden en alsnog geselecteerd worden.

1

We maken nu een methode die het bovenstaande voor ons doet. Maak een methode van het type void, met de naam selecteerIngrediënt:


Hoofdstuk 1 Interne gegevens

167

private void selecteerIngrediënt() { }

2 We maken eerst een variabele van het type int aan met de naam positie. In deze variabele wordt het resultaat van de methode zoekIngrediënt() opgeslagen. Als er niets gevonden is in de Array ingrediënten krijgt deze variabele de waarde -1. Voeg de volgende regel toe: int positie = zoekIngrediënt(ingrediënt);

3 Als de positie -1 is moet er een vraag aan de gebruiker getoond worden. In elk ander geval moet in de ListView livIngrediënten de juiste regel geselecteerd worden. Voeg het volgende if ... else-statement toe:

EP

T

if (positie == -1) { } else { }

4 We voegen nu eerst bij de else-voorwaarde de regel toe die het geselecteerde ingrediënt toont:

C

livIngrediënten.Items[positie].Selected=true;

N

5 Nu handelen we de situatie af voor het geval dat het ingrediënt niet gevonden is. We laten een boodschap zien en afhankelijk van wat de gebruiker kiest, wordt een ingrediënt toegevoegd. Voeg de volgende regels toe:

C

O

if (MessageBox.Show(“Ingrediënt “ + ingrediënt + “ komt niet voor.” + Environment.NewLine + Environment.NewLine + “Nieuw ingrediënt “+ ingrediënt + “ aanmaken?”, “Waarschuwing”, MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.OK) { }

Je krijgt de volgende boodschap te zien:

Schermafbeelding 7: Smoothies melding


168

Deel 2: Gegevens

Als er op OK wordt gedrukt, geeft de MessageBox de waarde DialogResult.Ok terug. 6 En dan moet het ingrediënt toegevoegd worden. Voeg nu deze regel toe om het ingrediënt toe te voegen tussen de accolades van het vorige if-statement: voegIngrediëntToe(ingrediënt);

7 Het ingrediënt is toegevoegd aan de Array maar moet nu nog toegevoegd worden aan de ListView livIngrediënten. Hiervoor kunnen we dezelfde methode gebruiken die we in het Load event gebruikt hebben. Voeg de volgende regel toe aan het ifstatement: fillLivIngrediënten();

EP

selecteerIngrediënt();

T

8 Nu moeten we het ingevoegde ingrediënt nog in de ListView livIngrediënten selecteren. En daar hebben we een methode voor gemaakt, namelijk de methode selecteerIngrediënt()! Deze kunnen we gewoon weer aanroepen. Dit noemen ze ook wel recursief programmeren. Voeg de volgende regel toe aan het if-statement:

9 Als laatste voegen we deze methode toe aan de knop Zoek in lijst.

C

Ga naar het design-venster en dubbelklik op de knop Zoek in lijst. Je ziet nu de volgende code:

N

private void btnZoek_Click(object sender, EventArgs e) { }

10 Voeg de volgende regel toe aan het Click event en de knop is gereed voor gebruik:

O

selecteerIngrediënt();

C

Array: Selectie ongedaan maken De knop Zoek in lijst is gereed. We kunnen ingrediënten selecteren maar we willen een selectie ook ongedaan kunnen maken. Dit gebeurt met de knop Selectie ongedaan maken. We maken eerst een methode die dit voor ons doet. Daarna voegen we deze methode toe aan de knop.

11 Ga naar het code-venster en maak een methode aan van het type void met de naam deSelecteerIngrediënten. private void deSelecteerIngrediënten() { }

12 We doorlopen nu alle ListView-items en zetten alle waardes voor selected op false. Dit is de eenvoudigste manier. We gebruiken een for-loop omdat je bij een foreachloop de items zelf niet mag wijzigen. Voeg het volgende for-statement toe binnen de methode deSelecteerIngrediënten():


Hoofdstuk 1 Interne gegevens

169

for (int i = 0; i < livIngrediënten.Items.Count; i++) { livIngrediënten.Items[i].Selected = false; }

13 Nu voegen we deze methode toe aan de knop Selectie ongedaan maken. Ga naar het design-venster en dubbelklik op de knop Selectie ongedaan maken. Je ziet nu de volgende code: private void btnWisSelectie_Click(object sender, EventArgs e) { }

14 Voeg de volgende regel toe aan het Click event en de knop is gereed voor gebruik:

T

deSelecteerIngrediënten();

C

EP

Array: Verwijderen items Als we ingrediënten toe willen voegen, willen we ze meestal ook kunnen verwijderen. We maken nu de methode om ingrediënten te verwijderen. Bij ieder ingrediënt dat we verwijderen moet de Array ook 1 kleiner worden. Zo voorkomen we lege plekken. Het verwijderen gaat als volgt. Stel, we willen het ingrediënt van rij vier verwijderen. Dat is de Aardbei. Dit doen we door eerst alle ingrediënten na rij 4 een rij te laten opschuiven en vervolgens de laatste rij te verwijderen. Klinkt ingewikkeld, maar in de praktijk valt het mee.

O

N

Rij 4 (Aardbei) willen we verwijderen. We kopiëren rij 5 (Framboos) naar rij 4. Aardbei is nu weg, maar we hebben twee keer Framboos in de lijst staan. Daarom kopiëren we nu rij 6 (Rode bes) naar rij 5 en we hebben nog maar één keer Framboos in de lijst staan, maar nu hebben we twee keer Rode bes in de lijst. Toevallig is rij 6 ook de laatste rij, dus die gooien we gewoon weg.

C

En nu in de praktijk:

1

Ga naar het code-venster en voeg de volgende methode van het type void toe met de naam verwijderIngrediëntUitIngrediëntenArray, met een parameter met de naam startPositie en van het type int. Waarom positie? Omdat we die zo kunnen opzoeken met de methode zoekIngrediënt(). private void verwijderIngrediëntUitIngrediëntenArray(int startPositie) { }

2 De laatste rij doet er eigenlijk niet toe. We beginnen direct met de nieuwe grootte te bepalen. Voeg de volgende regel toe aan de methode verwijderIngrediëntUitIngrediëntenArray(): int grootte = ingrediënten.Length - 1;


170

Deel 2: Gegevens

3 Nu kunnen we de regels kopiëren vanaf de positie waarop het te verwijderen ingrediënt staat tot de één na laatste regel. Omdat we steeds de volgende regel kopiëren, komt deze regel vanzelf aan bod. Ook hier kunnen we geen foreach gebruiken omdat we elementen verwijderen. Voeg de volgende for-loop toe aan de methode: for (int positie = startPositie; positie < grootte; positie++) { ingrediënten[positie] = ingrediënten[positie + 1]; }

4 Daarna moeten we de Array nog met 1 verkleinen. Voeg na de for-loop de volgende regel toe: Array.Resize(ref ingrediënten, grootte);

EP

T

5 We hebben nu een methode om één ingrediënt uit de Array te verwijderen. Maar wat moeten we als er meerdere geselecteerd zijn? Dan moet deze methode voor ieder geselecteerd ingrediënt uitgevoerd worden. Tegelijkertijd kan dan de ListView livIngrediënten opgeschoond worden. Maak een methode aan van het type void en met de naam verwijderGeselecteerdeIngrediënten. private void verwijderGeselecteerdeIngrediënten() { }

C

6 Omdat het om de geselecteerde ingrediënten gaat en de ListView als het ware een kopie is van de Array kunnen we voor beide onderdelen dezelfde index gebruiken.

N

We hebben bij het deselecteren al eens alle waardes op false gezet. We kunnen nu op dezelfde manier door de ListViewItems lopen om ieder item dat geselecteerd is (waar true bij staat) te verwijderen uit zowel de ListView als de Array.

O

We voegen de volgende for-loop toe aan de methode verwijderGeselecteerdeIngrediënten:

C

for (int i = 0; i < livIngrediënten.Items.Count; i++) { }

7 Nu kijken we in de loop of een element geselecteerd is. Voeg binnen de for-loop het volgende if-statement toe: if (livIngrediënten.Items[i].Selected == true) { }

8 Eerst verwijderen we het element uit de ListView livIngrediënten. Voeg de volgende regel toe aan het if-statement: livIngrediënten.Items[i].Remove();


Hoofdstuk 1 Interne gegevens

171

9 Daarna verwijderen we het ingrediënt uit de Array ingrediënten. Voeg de volgende regel toe aan het if-statement: verwijderIngrediëntUitIngrediëntenArray(i);

10 We hebben een element verwijderd uit beide lijsten. De lijsten zijn daardoor minder lang geworden, maar alle elementen zijn ook een positie opgeschoven. Daarom moeten we i, de index, een pas op de plaats laten maken. Bij een for-loop wordt i automatisch bij iedere iteratie (herhaling) met 1 opgehoogd. Dit willen we niet, dus we halen er 1 van af. Met als resultaat dat i gelijk blijft. Voeg daarom als laatste regel binnen het if-statement de volgende regel toe: i = i - 1;

T

11 Na de for-loop voegen we binnen de void verwijderGeselecteerdeIngrediënten() nog de volgende regel toe:

EP

this.Refresh();

Hiermee dwing je het programma om, nadat alles opnieuw is ingesteld, het hele formulier opnieuw te tekenen. Omdat de code om de regels om en om te tekenen gekoppeld is aan het Paint event worden dan ook alle regels opnieuw met de juiste kleur getekend.

C

12 Nu voegen we deze methode toe aan de knop Wis ingrediënt.

N

Ga naar het design-venster en dubbelklik op de knop Wis ingrediënt. Je ziet nu de volgende code:

O

private void btnWisIngrediënt_Click(object sender, EventArgs e) { }

13 Voeg de volgende regel toe aan het Click event en de knop is gereed voor gebruik:

C

verwijderGeselecteerdeIngrediënten();

Listview Een Listview is een control waarin je een lijst met gegevens kunt tonen. Met de properties kun je heel veel zaken van tevoren instellen: de kolomkoppen, de opmaak van de regels, enzovoort. Als je een Listview met behulp van de code wilt instellen, moet je rekening houden met: – Subitems, deze heb je nodig als je meerdere kolommen wilt gebruiken. – Property OwnerDraw, deze moet op true staan als je zelf de opmaak wijzigt. – Property HideSelection moet op false staan als je een selectie altijd zichtbaar wilt hebben.


172

Deel 2: Gegevens

Array: Default instellingen Het programma is uitgerust met een standaardlijst met ingrediënten. Soms vinden gebruikers het fijn als ze terug kunnen gaan naar de standaard (default) instellingen van een programma. Hiervoor is de knop Default lijst. Omdat we al een methode gemaakt hebben om de ListView te vullen bij de start van het programma, gaan we die nu weer gebruiken. Natuurlijk kunnen we die code kopiëren maar het is verstandiger om er een methode van te maken die we vaker kunnen gebruiken. 1

We maken eerst een methode die de Array ingrediënten vult met de standaardwaardes. Ga naar het code-venster en maak een methode aan met de naam

EP

private string[] defaultIngrediëntenArray() { }

T

defaultIngrediëntenArray() van het type string:

2 Nu maken we een Array-variabele aan en vullen die met de gewenste waardes. Voeg de volgende regel toe aan de methode defaultIngrediëntenArray():

C

string[] defaultIngrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” };

3 De variabele defaultIngrediënten geven we terug. Voeg de volgende regel toe aan de methode:

N

return defaultIngrediënten;

4 We voegen de methodes toe aan de knop Default lijst.

O

Ga naar het design-venster en dubbelklik op de knop Default lijst. Je ziet nu de volgende code:

C

private void btnDefaultLijst_Click(object sender, EventArgs e) { }

5 Voeg de laatste regels toe aan het Click event en de knop is gereed voor gebruik: ingrediënten = defaultIngrediëntenArray(); fillLivIngrediënten();

Array: Subroutine Het programma werkt. Alleen staat er nog twee keer de vulling van de default ingrediëntenarray in het programma. Om dit te herstellen wijzigen we het programma als volgt: 1

Ga in het code-venster naar de regel string[] ingrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” };


Hoofdstuk 1 Interne gegevens

173

Deze regel is een globale variabeledeclaratie geweest en als het goed is staat deze regel ergens helemaal bovenin het programma. We verwijderen het =-teken en alles wat erachter staat, behalve de ;. Het resultaat is dan: string[] ingrediënten;

We kunnen hier nog geen gebruik maken van de methode defaultIngrediënten() omdat deze op dit niveau niet beschikbaar is. Dat kunnen we wel doen bij het Load event van het formulier. 2 Ga naar de methode frmSmoothies_Load en voeg de volgende regel als eerste regel toe:

T

ingrediënten = defaultIngrediëntenArray();

EP

Array: Selectie koppelen aan TextBox Nu zijn alle knoppen gemaakt. Het zou echter goed zijn dat als je met een muis een ingrediënt selecteert, dit element ook in de TextBox txbIngrediënt getoond wordt.

C

3 We gaan in het design-venster naar de events van de livIngrediënten. We kiezen voor het Click event en dubbelklikken daarop. De volgende code wordt aangemaakt:

N

private void livIngrediënten_Click(object sender, EventArgs e) { }

4 We voegen de volgende regel toe om de waarde van het actieve element toe te voegen aan de TextBox txbIngrediënt:

O

txbIngrediënt.Text = livIngrediënten.FocusedItem.SubItems[0].Text;

C

In het Nederlands luidt deze opdracht: Ga naar het item van de ListView livIngrediënten waar de focus op ligt en neem de waarde van het eerste subitem (er is er maar één, dus dat gaat hier goed) en kopieer deze naar de tekst-property van de TextBox txbIngrediënt. Zo, de applicatie is gereed voor gebruik.

Opdracht Array 1: Telefoonmerkenlijst Maak een programma waar je in een Array de volgende namen van telefoontoestellen opslaat. Microsoft Lumia 640, Huawei Smartphone Y330, Samsung Galaxy Core II, J&S Supply zwart, Apple iPhone 6, Apple iPhone 5, Samsung Galaxy j5, Huawei Ascend y625, HTC Desire 820, Samsung Galaxy A3 Gold, Wiko Slide Wit, Acer Liquid z530


174

Deel 2: Gegevens

Wit, Alcatel One Touch Idol 3 4.7 inch Zwart, Archos 50c Oxygen 16 GB, Microsoft Nokia Lumia 435 Wit Pack Proximus. Het moet mogelijk zijn om een nieuwe telefoon toe te voegen en om er een te verwijderen. De Array mag geen lege plekken bevatten. Het resultaat laat je zien in een ListView.

• • • • •

Daarnaast kent het programma de volgende functionaliteit: Je mag maar één telefoon tegelijkertijd kunnen selecteren. Als je Enter geeft in de TextBox moet het programma direct de telefoon gaan opzoeken. Wordt de telefoon niet gevonden, dan wordt de vraag gesteld of je de telefoon alsnog wilt toevoegen.

C

•

T

• •

EP

•

Het programma kent de volgende knoppen: Toevoegen. Hiermee voeg je een ingevoerde telefoon toe aan de Array en de ListView, mocht deze telefoon nog niet voorkomen. Wissen. Hiermee wis je de telefoon die je hebt ingevoerd in de TextBox. Zoeken. Hiermee zoek je de ingevoerde telefoon op in de Array en de ListView. Als de telefoon gevonden is laat je de regel in een andere kleur zien. Selectie uitzetten. Hiermee deselecteer je een geselecteerde telefoon. Sorteren van de telefoons op alfabet van A tot Z, zowel in de Array als ListView. Sorteren van de telefoons op alfabet van Z tot A, zowel van de Array als de ListView.

Het omgaan met Arrays Het gebruik van een ListView

O

• •

N

Wat heb je nu geleerd?

C

Value type versus Reference type –

In C zijn er twee grote groepen variabelen: Value (waarde) types Dit zijn alle variabelen die standaard al een waarde hebben en waar je eenvoudig een waarde aan kunt toekennen: int getal = 13;

Op een geheugenadres met de naam getal wordt voldoende ruimte gereserveerd om een getal van het type int op te kunnen slaan. In dit geval wordt er direct het getal 13 opgeslagen. De ingrediëntenlijst uit Voorbeeld Array 1 en de telefoonmerkenlijst uit Opdracht Array 1 zijn voorbeelden van waarde (value) types.


Hoofdstuk 1 Interne gegevens

–

175

reference (referentie) types Dit zijn alle objecten (klassen) die je zelf gemaakt hebt. Eigenlijk alle variabelen die je aan moet maken door het keyword new te gebruiken: Pen myPen = new Pen(Color.Red);

Waarom heet dit nu een referentietype? Op het moment dat je Pen myPen aanmaakt, wordt er in het geheugen niet een compleet object van het type Pen voor je aangemaakt. Nee, op dat moment wordt er een stukje geheugen met de naam myPen gereserveerd waarin een verwijzing (referentie) wordt opgeslagen naar het hele object, zodra dat aangemaakt wordt. En als het object vervolgens niet aangemaakt wordt? Dan staat er null en kun je foutmeldingen krijgen.

EP

T

Met het keyword new maak je je object aan en wordt ook de verwijzing gevuld. Het is dan ook een goede gewoonte om objecten zo snel mogelijk na de declaratie aan te maken, het liefst tegelijkertijd.

C

En wat heeft dit nu met Arrays te maken? Heel eenvoudig: bij het gebruik van waardetypes gedraagt een Array zich zoals je verwacht. Kopieer je de Array dan wordt de gehele inhoud meegekopieerd. Als je een kopie wilt hebben van de vruchtentabel dan kan dit eenvoudig door: string[] vruchtenKopie = vruchten;

N

Vervolgens kun je de Framboos vervangen door een Ananas: vruchtenKopie[5] = “Ananas”;

O

Met als resultaat dat in vruchtenkopie Ananas voorkomt en in vruchten niet.

C

Bij referentietypes werkt dit anders: In de Array staat dan alleen het geheugenadres naar het werkelijke object. Maak je een kopie van de Array dan bestaat de kopie Array uit een zelfde verzameling adressen. En als je dan het vijfde element wijzigt, wijzig je automatisch het vijfde element van de originele Array! Dat kan heel vervelend zijn. Vooral als je zaken weggooit nadat je denkt ze in een kopie veiliggesteld te hebben...

Voorbeeld Array 2: Smoothies met uitgebreide ingrediëntenlijst De Smoothy-bar is succesvol en trekt steeds meer prijs-, gezondheid- en milieubewuste mensen die meer informatie willen over hun drankje. We breiden daarom onze ingrediëntenlijst uit. Niet een simpele lijst van vruchten, maar we willen van alles weten van iedere vrucht. Uiteraard de naam, maar ook bijvoorbeeld de voedingswaarde (het aantal calorieën), de kleur, prijs per kilo en het land van herkomst.


176

Deel 2: Gegevens

Je kunt natuurlijk een dergelijke ingrediëntenlijst opslaan in een multidimensionale Array, maar dat wordt onoverzichtelijk. Is de derde kolom bijvoorbeeld de kleur of de prijs per kilo? Hiervoor kun je beter een klasse gebruiken. We maken dan ook een klasse aan voor de ingrediënten. Hernoemen project Maak een kopie van het Smoothies-project en noem het SmoothiesPlus. Dit kun je doen door de hele mappenstructuur te kopiëren. De projectmap ziet er nu als volgt uit:

EP

T

1

Schermafbeelding 8: Projectmap SmoothiesPlus

C

2 Hernoem de map Smoothies naar SmoothiesPlus.

N

Let op! Als je nu het project Smoothies met het .csproj- of .sln-bestand start verschijnt er een foutmelding! Eerst moet je in Smoothies.sln handmatig de naam van de map wijzigen.

O

3 Open het bestand Smoothies.sln met een tekst-editor, bijvoorbeeld kladblok. Smoothies.sln is een XML-bestand met de basisinstellingen van het project

C

Smoothies. In het Kladblok ziet dat bestand er als volgt uit:

Schermafbeelding 9: Smoothies.sln


Hoofdstuk 1 Interne gegevens

177

4 Ga naar de regel waarin de mapnaam staat. Deze regel begint met: Project(“{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}”) = “Smoothies”, “Smoothies\Smoothies.csproj”, “{15E286BD-522A-4 Let op! De cijfers zullen voor iedereen anders zijn! Die laten we gewoon staan. 5 Verander de mapnaam Smoothies in SmoothiesPlus. De regel wordt nu: Project(“{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}”) = “Smoothies”,“SmoothiesPlus\Smoothies.csproj”, “{15E286BD-522A-4

T

6 Start Visual Studio met de projectfile Smoothies.csproj, of met de solutionfile Smoothies.sln. 7 Ga naar de Solution Explorer.

EP

8 Hernoem het project in de Solution Explorer naar SmoothiesPlus. 9 Hernoem de C-naam naar SmoothiesPlus.

10 Hernoem het formulier frmSmoothies naar frmSmoothiesPlus.

C

O

N

C

11 Ga naar het project-properties-venster.

Schermafbeelding 10: Project-properties

12 Verander de Assembly-name van Smoothies in SmoothiesPlus. 13 Verander de Default Namespace van Smoothies in SmoothiesPlus. Je zult zien dat je in de .cs-bestanden die je al gemaakt hebt, de Namespace handmatig moet veranderen. Hernoem er eentje en klik dan op het lampje dat verschijnt zodat je de Namespace overal in je hele programma kunt hernoemen.


178

Deel 2: Gegevens

14 Test het programma. Werkt alles nog? Dan kunnen we doorgaan met het maken van een klasse voor de ingrediënten. Werkt het nog niet? Kijk dan bijvoorbeeld nog even in Program.cs of alle Smoothies in SmoothiesPlus zijn veranderd. Toevoegen Class 1

Ga naar het project-tabblad en klik Add Class aan.

namespace SmoothiesPlus { class Ingrediënt { } }

EP

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

T

2 Vul bij de naam Ingrediënt in. Je komt nu in het code-venster en ziet het volgende:

N

C

3 We willen gebruikmaken van een variabele van het type Color om de kleur van het ingrediënt in op te slaan. Dit type staat in de bibliotheek System.Drawing. Maar omdat we alleen dit kleine deel van de bibliotheek nodig hebben gebruiken we de volledige naam van dit type.

O

Let op! De namen van publieke variabelen beginnen altijd met een hoofdletter! Voeg de volgende regels met variabelen toe aan de klasse:

C

// Variabelen public string Naam = “”; public System.Drawing.Color Kleur = System.Drawing.Color.AliceBlue; public string LandVanHerkomst = “Nederland”; public int Calorieën = 0; public double PrijsPerKilo = 0;

De variabelen zijn nu meteen voorzien van standaardwaardes. 4 We willen een nieuw ingrediënt kunnen aanmaken met de juiste waardes. Daarom voegen we een constructor toe die dit mogelijk maakt. Weet je nog dat een constructor altijd public is, altijd dezelfde naam heeft als de class en geen type kent? Let op! De namen van de parameters beginnen altijd met een kleine letter!


Hoofdstuk 1 Interne gegevens

179

Op deze manier kun je een publieke variabele van een parameter onderscheiden en kun je in een constructor de waarde van een parameter doorgeven aan een variabele. Dat doen we nu. Voeg de volgende regels toe: public Ingrediënt(string naam, System.Drawing.Color kleur, string landVanHerkomst, int calorieën, double prijsPerKilo) { this.Naam = naam; this.Kleur = kleur; this.LandVanHerkomst = landVanHerkomst; this.Calorieën = calorieën; this.PrijsPerKilo = prijsPerKilo; }

EP

T

Het woord this geeft aan dat de variabele onderdeel is van deze klasse. Als je de namen van de variabelen en parameters goed gegeven hebt (hoofd- en kleine letters) dan is de toevoeging van this niet nodig. We doen het hier voor de duidelijkheid.

public Ingrediënt() { }

C

Er moet ook een constructor komen die eigenlijk niets anders doet dan een leeg ingrediënt met standaardwaardes aanmaken. Voeg daarom de volgende regels toe:

O

N

5 Tot slot hebben we nog een methode nodig die een nette opsomming geeft van alle eigenschappen van een ingrediënt. Het moet er als volgt uitzien voor de gebruiker.

C

Om dit voor elkaar te krijgen, maken we gebruik van een op maat gemaakte ToString()-methode. Iedere klasse kent namelijk standaard een ToString()-methode, maar deze methode geeft de namespace en de naam van de klasse. In dit geval levert de regel ingrediënt.ToString();

het volgende resultaat: “SmoothiesPlus.Ingrediënt”. We moeten de standaard ToString()-methode dus vervangen door onze eigen ToString()-methode. Dit doen we door het woord override toe te voegen aan de methode. Voeg de volgende regels toe aan de klasse: public override string ToString() { return “Ingrediënt: “ + Naam + “ met de kleur “ + Kleur + “, het aantal calorieën “ + Calorieën + “ en een prijs per kilo van €” + PrijsPerKilo + “ uit “ + LandVanHerkomst; }


180

Deel 2: Gegevens

Let op de spaties! De klasse is klaar voor gebruik. Nu passen we stap voor stap het hoofdprogramma (het formulier) aan. Wat moeten we doen? De string-variabele ingrediënt, waarin alleen de naam van het ingrediënt werd opgeslagen, moet vervangen worden door een variabele van het type Ingrediënt (die hebben we net gemaakt). Zo moeten we ook de Array ingrediënten vervangen door een Array van het type Ingrediënt.

1

Aanpassen formulier Ga naar het code-venster van het formulier frmSmoothiesPlus.

2 Zoek in de code (waarschijnlijk bovenaan) naar de volgende regels:

Vervang deze regels door de volgende regels:

EP

Ingrediënt[] ingrediënten; Ingrediënt ingrediënt = new Ingrediënt();

T

string[] ingrediënten; string ingrediënt = “”;

3 Nu moeten we de Array met standaardwaardes gaan vullen. Dat gebeurde in de methode defaultIngrediëntenArray(). Deze methode ziet er als volgt uit:

N

C

private string[] defaultIngrediëntenArray() { string[] defaultIngrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” }; return defaultIngrediënten; }

O

4 We vervangen eerst het returntype van de Array. Vervang (in de handtekening van de methode) string[] door Ingrediënt[]. De regel ziet er nu als volgt uit: private Ingrediënt[] defaultIngrediëntenArray()

C

5 We zullen eerst de Array vullen met lege elementen. Voeg de volgende regel toe: Ingrediënt[] defaultIngrediënten = new Ingrediënt[6];

6 Vervolgens moet de Array met veel meer waardes gevuld worden. Immers van ieder ingrediënt zijn vijf kenmerken bekend. Hiervoor gebruiken we de tweede constructor uit de klasse Ingrediënt. Vervang de regel string[] defaultIngrediënten = { “Appel”, “Peer”, “Meloen”, “Aardbei”, “Framboos”, “Rode bes” };

door de volgende regels: defaultIngrediënten[0] = new Ingrediënt(“Appel”,Color.Green,”Nederland”,10,1.76); defaultIngrediënten[1] = new Ingrediënt(“Peer”,Color.Green,”Luxemburg”,10,1.71); defaultIngrediënten[2] = new Ingrediënt(“Meloen”,Color.Orange,”België”,15,2.15); defaultIngrediënten[3] = new Ingrediënt(“Aardbei”,Color.Red,”Israël”,5,12.50);


Hoofdstuk 1 Interne gegevens

181

defaultIngrediënten[4] = new Ingrediënt(“Framboos”,Color.IndianRed, “België”, 7,7.33); defaultIngrediënten[5] = new Ingrediënt(“Rode bes”,Color.Red, “Frankrijk”,17,14.10);

7 Voeg de volgende regels toe om de aangemaakte Array terug te geven: // Teruggeven van de Array return defaultIngrediënten;

8 De variabelen zijn aangepast. Alleen hebben we nog geen invoervelden. Daar zorgen we nu voor. Maak eerst ruimte. Verbreed het formulier en voeg de volgende controls toe:

TextBox

txbLandVanHerkomst

TextBox

txbCalorieën

TextBox

txbPrijsPerKilo

Button

btnKleurIngrediënt

Label

label1

Label

label2

Label

label3

Label

label4

Label

label5

Waarde

T

Eigenschap

EP

Naam

Text

Ingrediënt:

Text

Land van herkomst:

Text

Kleur:

Text

Aantal calorieën:

C

Type

Text

Prijs per kilo:

N

Tabel 2: Controls SmoothiesPlus

C

O

9 Je formulier ziet er nu als volgt uit:

Schermafbeelding 11: SmoothiesPlus lay-out

O ja, vergeet niet om ook de ListView livIngrediënten te vergroten. Ook hier moet zo meteen veel meer informatie in getoond worden.


182

Deel 2: Gegevens

10 Voeg aan het formulier een ErrorProvider toe. Behoud de standaardnaam (errorProvider1). 11 Voeg aan het formulier een ColorDialog toe. Behoud de standaardnaam (colorDialog1). 12 Nu koppelen we de TextBoxen aan de variabele ingrediënt. Dubbelklik op de TextBox txbIngrediënt. Je komt in de volgende code terecht: private void txbIngrediënt_TextChanged(object sender, EventArgs e) { ingrediënt = txbIngrediënt.Text; }

Dit werkt niet meer omdat ingrediënt niet meer van het type string is maar van het ingrediënt = txbIngrediënt.Text;

EP

door:

T

type Ingrediënt. Daarom vervangen we de regel

ingrediënt.Naam = txbIngrediënt.Text;

13 Ga terug naar het design-venster en dubbelklik op de TextBox txbLandVanHerkomst. Voeg hier de volgende regel toe aan de methode txbLandVanHerkomst_TextChanged().

C

ingrediënt.LandVanHerkomst = txbLandVanHerkomst.Text;

N

14 Ga terug naar het design-venster en dubbelklik op de TextBox txbCalorieën. Omdat je hier een tekst om moet zetten naar een getal voeren we twee controles uit. a

Is er iets ingevoerd? Voeg de volgende regels toe aan de methode

O

txbCalorieën_TextChanged:

C

// Als er iets is ingevoerd in de TextBox is de lengte van de tekst > 0 if (txbCalorieën.Text.Length > 0) { }

b Is de ingevoerde waarde een getal? Dit doen we met een try-catch-blok. Voeg de volgende regels toe binnen het if-statement: try { ingrediënt.Calorieën = int.Parse(txbCalorieën.Text); errorProvider1.SetError(txbCalorieën, “”); } catch (Exception exception) { errorProvider1.SetError(txbCalorieën, exception.Message); }

15 Ga terug naar het design-venster en dubbelklik op de TextBox txbPrijsPerKilo. Ook hier moet je een tekst omzetten naar een getal. Alleen moet je dit keer een double


Hoofdstuk 1 Interne gegevens

183

gebruiken omdat de PrijsPerKilo een gebroken getal is. Voeg de volgende regels toe aan de methode: if (txbPrijsPerKilo.Text.Length > 0) { try { ingrediënt.PrijsPerKilo = double.Parse(txbPrijsPerKilo.Text); errorProvider1.SetError(txbPrijsPerKilo, “”); } catch (Exception exception) { errorProvider1.SetError(txbPrijsPerKilo, exception.Message); } }

EP

T

Rest ons nog het aangeven van de kleur. Er is voor gekozen om een variabele van het type Color te gebruiken zodat we de kleur ook kunnen laten zien. Daarom maken we niet gebruik van een TextBox maar van een Button die de geselecteerde kleur krijgt en waarop de naam van de kleur verschijnt. 16 Ga terug naar het design-venster en dubbelklik op de Button btnKleurIngrediënt. Uiteraard wil je alleen maar een kleur aanpassen als de gebruiker in de DialogBox op OK klikt. Voeg de volgende regels toe:

N

C

if (colorDialog1.ShowDialog() == DialogResult.OK) { ingrediënt.Kleur = colorDialog1.Color; btnKleurIngrediënt.BackColor = ingrediënt.Kleur; btnKleurIngrediënt.Text = ingrediënt.Kleur.Name; }

O

Kleur.Name geeft de naam van de kleur terug, of de RGB-code als deze combinatie

C

geen naam heeft. Omdat we later, als er een ingrediënt gekozen wordt, ook de bijbehorende kleur op de knop willen laten zien, maken we hiervoor alvast een methode aan. Dit doen we als volgt: als de tekst-property van de Button verandert dan veranderen we de achtergrondkleur van de Button.

17 Ga terug naar het design-venster en ga naar de properties van de btnKleurIngrediënt.

18 Selecteer het event TextChanged. De volgende methode wordt aangemaakt: private void btnKleurIngrediënt_TextChanged(object sender, EventArgs e) { }

19 Als de tekst van de knop verandert zal het ingrediënt veranderd zijn en kan de knop de kleur van het ingrediënt krijgen. Voeg de volgende regels toe aan het TextChanged event van de btnKleurIngrediënt:


184

Deel 2: Gegevens

ingrediënt.Kleur = Color.FromName(btnKleurIngrediënt.Text); btnKleurIngrediënt.BackColor = ingrediënt.Kleur;

Het formulier is klaar. Nu gaan we verder met de achterliggende code, zodat alles aangepast is aan het type Ingrediënt. Eerst passen we de code aan voor de opmaak voor de ListView livIngrediënten. Er zijn immers meer kolommen met gegevens gekomen.

1

Aanpassen van de headers / kolommen Ga naar het code-venster en zoek de methode opmaakLivIngrediënten() op. Zoek binnen de methode de volgende regel op:

2 Vervang deze regel door de volgende regels:

T

ColumnHeader headerOmschrijving = livIngrediënten.Columns.Add(“Ingrediënten”, -2);

EP

ColumnHeader headerNaam = livIngrediënten.Columns.Add(“Naam”, 75); ColumnHeader headerKleur = livIngrediënten.Columns.Add(“Kleur”, 80); ColumnHeader headerLandVanHerkomst = livIngrediënten.Columns.Add(“Land van herkomst”, 150); ColumnHeader headerCalorieën = livIngrediënten.Columns.Add(“Calorieën”, 70); ColumnHeader headerPrijsPerKilo = livIngrediënten.Columns.Add(“Prijs per Kilo”, -2);

N

C

De methodes livIngrediënten_DrawColumnHeader() en livIngrediënten_DrawSubItem() blijven onveranderd. Immers de lay-out blijft hetzelfde. We moeten er alleen voor zorgen dat alle elementen van het type Ingrediënt aan een kolom in de ListView livIngrediënten gekoppeld worden. 3 Ga naar de methode fillLivIngrediënten() en zoek de volgende regel op:

O

ListViewItem livIngrediëntenItem = new ListViewItem(ingrediënt);

Vervang in deze regel ingrediënt door ingrediënt.Naam.

C

Omdat er meerdere subitems zijn, is het niet nodig om de naam nog een keer als subitem te herhalen.

4 Voeg de volgende regel toe onder livIngrediënten.Items.Add(livIngrediëntenItem);: livIngrediëntenItem.SubItems.Add(ingrediënt.Kleur.Name);

Omdat de ListView-items van het type string zijn moet de kleur vertaald worden naar iets leesbaars. Hier is gekozen voor de naam van de kleur. ToString() had ook gekund, maar geeft altijd de RGB-waardes terug. 5 Nu voegen we een tekstkolom toe. Voeg hiervoor de volgende regel toe: livIngrediëntenItem.SubItems.Add(ingrediënt.LandVanHerkomst);

6 Na de tekstkolommen volgen de kolommen met getallen. Ook deze moeten van getal omgezet worden naar tekst. Dit doen we met de methode ToString(). Voeg de volgende regels toe:


Hoofdstuk 1 Interne gegevens

185

livIngrediëntenItem.SubItems.Add(ingrediënt.Calorieën.ToString()); livIngrediëntenItem.SubItems.Add(ingrediënt.PrijsPerKilo.ToString());

De opmaak is gereed. Nu moeten er nog aanpassingen in de ondersteunende methodes aangebracht worden.

1

Toevoegen methodes We hebben een extra methode nodig die de synchronisatie regelt tussen de TextBoxen en de listView livIngrediënten. Voeg de volgende methode toe:

T

private void synchroniseerTextBoxenMetList() { // Opnieuw initialiseren van de variabele ingrediënt. ingrediënt = new Ingrediënt();

EP

// Synchroniseren van de TextBoxen met de ListView. txbIngrediënt.Text = livIngrediënten.FocusedItem.SubItems[0].Text; btnKleurIngrediënt.Text = livIngrediënten.FocusedItem.SubItems[1].Text; btnKleurIngrediënt.BackColor = ingrediënten[livIngrediënten.FocusedItem.Index].Kleur; txbLandVanHerkomst.Text = livIngrediënten.FocusedItem.SubItems[2].Text; txbCalorieën.Text = livIngrediënten.FocusedItem.SubItems[3].Text; txbPrijsPerKilo.Text = livIngrediënten.FocusedItem.SubItems[4].Text;

C

}

N

2 We hebben ook een extra methode nodig die de TextBoxen leeg maakt en de variabele ingrediënt weer op nul zet. Voeg de volgende methode toe:

C

O

private void maakTextBoxenLeeg() { txbCalorieën.Text = “”; txbIngrediënt.Text = “”; txbLandVanHerkomst.Text = “”; txbPrijsPerKilo.Text = “”; btnKleurIngrediënt.Text = “”; }

1

Aanpassen methodes In de methode SelecteerIngrediënt() wordt een vraag getoond (met de naam van het ingrediënt) als een ingrediënt niet gevonden wordt. We passen dus de boodschap aan in de methode, maar ook nog een aantal andere zaken in de onderliggende methodes. Zoek binnen de methode de regels op die beginnen met if (MessageBox.Show(

Vervang binnen de parameters van Show het gedeelte “Ingrediënt “ + ingrediënt door ingrediënt.ToString()


186

Deel 2: Gegevens

Zoek vervolgens binnen het parametergedeelte naar “Nieuw ingrediënt “ + ingrediënt. Vervang hier het woord ingrediënt door ingrediënt.Naam 2 Voeg aan het else-gedeelte de volgende regel toe: livIngrediënten.Items[positie].Focused = true;

Deze regel zorgt ervoor dat livIngrediënten de focus krijgt zodat de TextBoxen goed gesynchroniseerd kunnen worden. 3 We moeten nu niet één TextBox vullen maar een heel stel. Dit doen we door de methode synchroniseerTextBoxenMetList() aan te roepen. Voeg de volgende regel als laatste toe aan het if-gedeelte:

4

T

synchroniseerTextBoxenMetList(); SelecteerIngrediënt() maakt gebruik van de methode voegIngrediëntToe(string ingrediënt) om een nieuw ingrediënt aan de Array toe te voegen. Alleen is het

EP

ingrediënt van type veranderd. Voor de rest verandert er niets!

Zoek de volgende regel op (de methode-declaratie): private int voegIngrediëntToe(string ingrediënt)

C

en verander deze regel in:

private int voegIngrediëntToe(Ingrediënt ingrediënt)

N

5 Voor zoekIngrediënt geldt precies hetzelfde als voor voegIngrediëntToe. Zoek de volgende regel op:

O

private int zoekIngrediënt(string ingrediënt)

en verander in deze regel string in Ingrediënt.

C

6 Als er in de lijst op een item geklikt wordt, wordt in het oude programma de naam van het ingrediënt getoond. Nu willen we ook alle andere bijbehorende waardes tonen. Dit doen we in de methode die bij het Click event hoort: livIngrediënten_Click. Zoek de methode livIngrediënten_Click() op. Vervang de volgende regel: txbIngrediënt.Text = livIngrediënten.FocusedItem.SubItems[0].Text;

door: synchroniseerTextBoxenMetList();


Hoofdstuk 1 Interne gegevens

187

Het is niet nodig om de variabele ingrediënt aan te passen. Dit gebeurt vanzelf! Waarom? Bij iedere TextBox is een TextChange event aangemaakt. Zelfs bij de Button btnKleurIngrediënt is een soort Change event aangemaakt... Dus als er iets wijzigt wordt de bij deze events behorende code uitgevoerd. Met als gevolg dat de variabele ingrediënt wordt aangepast. 7 Als we een geselecteerde regel deselecteren moeten de TextBoxen leeggemaakt worden. Dat is wel zo netjes. Zoek de methode deselecteerIngrediënten() op en voeg als laatste regel toe: maakTextBoxenLeeg();

T

8 Ook als we een regel verwijderd hebben moeten we de TextBoxen leeg maken. Zoek de methode btnWisIngrediënt_Click() op en voeg hier ook als laatste regel toe: maakTextBoxenLeeg();

EP

C

O

N

C

1

Test 1 Test je programma. Als het goed is ziet je programma er als volgt uit:

Schermafbeelding 12: SmoothiesPlus resultaat

Als je alle functies getest hebt, kom je iets vreemds tegen. Voer de volgende test maar eens uit.

2 Run je programma. 3 Klik op Aardbei. Als het goed is ziet het scherm er uit als in Schermafbeelding 12. 4 Ga met de pijltjestoetsen naar Appel. Je scherm ziet er nu als volgt uit:


188

Deel 2: Gegevens

T

Schermafbeelding 13: SmoothiePlus test aardbei

C

O

N

C

EP

5 Klik nu op Zoek in lijst. Vreemd genoeg krijg je nu een foutmelding. Bijvoorbeeld:

Schermafbeelding 14: SmoothiesPlus test aardbei foutmelding

Vreemd: dit ingrediënt staat in de Array ingrediënten en er is een variabele met de naam ingrediënt. En toch kan het programma het ingrediënt niet vinden en vraagt het of er een nieuw ingrediënt aangemaakt moet worden. Hoe kan dit? Dit heeft alles met referentie- en waardetypes te maken. We hebben de Array gevuld door zes keer een nieuw ingrediënt aan te maken met de volgende regels in de methode: defaultIngrediënten[0] = new Ingrediënt(“Appel”,Color.Green,”Nederland”,10,1.76); defaultIngrediënten[1] = new Ingrediënt(“Peer”,Color.Green,”Luxemburg”,10,1.71); defaultIngrediënten[2] = new Ingrediënt(“Meloen”,Color.Orange,”België”,15,2.15); defaultIngrediënten[3] = new Ingrediënt(“Aardbei”,Color.Red,”Israël”,5,12.50);


Hoofdstuk 1 Interne gegevens

189

defaultIngrediënten[4] = new Ingrediënt(“Framboos”,Color.IndianRed, “Duitsland”,7,17.33); defaultIngrediënten[5] = new Ingrediënt(“Rode bes”,Color.Red, “Frankrijk”,17,14.10);

In de Array is echter niet het ingrediënt zelf opgeslagen, maar de plek in het geheugen waar het ingrediënt zich bevindt. We hebben, zeg maar, zes doosjes: ingrediënt-1, ingrediënt-2, ingrediënt-3, ingrediënt-4, ingrediënt-5 en ingrediënt-6. In de doosjes zit alle informatie over de ingrediënten. In de Array staat in werkelijkheid niets anders dan een verwijzing naar het juiste doosje. Dit komt omdat een class altijd van het referentietype is! Op basis van deze Array wordt de ListView livIngrediënten gevuld.

EP

T

Daarnaast maken we ook gebruik van de variabele ingrediënt. Deze is gekoppeld aan bijvoorbeeld doosje ingrediënt-13. Waarom? Ieder ingrediënt krijgt zijn eigen unieke doosje, doosje 13 is gewoon een willekeurig leeg doosje (= plek in het geheugen).

N

C

De variabele ingrediënt wordt gekoppeld aan de controls op het scherm. Als je nu klikt op een rij in de ListView livIngrediënten worden die waardes gekopieerd naar de andere controls op het scherm. En in welk doosje zitten die? Juist, doosje ingrediënt-13! Als je gaat zoeken, dan vergelijkt het programma de elementen van Array ingrediënten met de variabele ingrediënt (methode zoekIngrediënt(Ingrediënt ingrediënt). De regel waar de vergelijking plaatsvindt ziet er als volgt uit: if (ingrediënten[index] == ingrediënt)

C

O

Maar wat gebeurt er? Er wordt niet gekeken of de waardes van de ingrediënten overeenkomen, nee, er wordt gekeken of de verwijzing hetzelfde is! En doosje ingrediënt-4 is nu eenmaal niet hetzelfde als het doosje ingrediënt-13. De inhoud is hetzelfde maar de doosjes niet. Vervolg Test 1 6 Antwoord met OK op de vraag of je een ingrediënt wilt toevoegen. 7 Er wordt netjes een nieuwe rij met dezelfde Aardbeien toegevoegd. 8 Klik op Meloen. Alle controls op het scherm worden aangepast. 9 Ga met de pijltjestoetsen naar Appel. 10 Klik op Zoek in lijst. Het resultaat is dat de cursor weer naar Aardbei gaat.


190

Deel 2: Gegevens

Natuurlijk gebeurt dit. De inhoud van doosje ingrediënt-13 wordt netjes aangepast, maar de laatste regel van de Array ingrediënten, de regel die we hebben toegevoegd, is nog steeds doosje ingrediënt-13. En we hebben niets op het scherm aangepast. Het algoritme gaat op zoek naar de verwijzing, vindt doosje ingrediënt-13 en zet de ListView op doosje ingrediënt-13. Het resultaat is ook dat je nooit meer ingrediënten kunt toevoegen, want er is een doosje ingrediënt-13 en die verwijzing is toegevoegd. De inhoud wijzigt steeds, maar de verwijzing (reference) blijft hetzelfde. Voor dit probleem bestaat geen eenvoudige oplossing. De simpelste oplossing bestaat uit het vervangen van het standaardgedrag van de ==-operator. Dus dat doen we nu. Wijzigen operatoren van een class Ga naar het code-venster van de klasse Ingrediënt.

T

1

EP

2 Ga naar de laatste methode die je hebt toegevoegd. 3 Sla twee regels over.

4 Voeg de volgende commentaarregel in:

C

// Operatoren

5 Hieronder vervangen we het standaardgedrag van == en !=. Eerst de ==-operator. Voeg de volgende methode toe:

O

N

public static bool operator ==(Ingrediënt ingrediënt1, Ingrediënt ingrediënt2) { }

C

Deze methode is public omdat deze buiten de klasse gebruikt moet worden. De methode is static omdat je de methode direct wilt gebruiken op twee objecten. Het return-type is bool. Als de waardes van de velden overeenkomen is de waarde true. En met je zelfgemaakte operator == vervang je zo dus eenvoudig de standaardoperator ==.

6 Vervolgens moeten we in deze methode alle velden van ingrediënt1 met ingrediënt2 vergelijken. Voeg de volgende regels toe: // De bewering is waar als alle velden overeenkomen. return ingrediënt1.Calorieën == ingrediënt2.Calorieën && ingrediënt1.Kleur == ingrediënt2.Kleur && ingrediënt1.LandVanHerkomst == ingrediënt2.LandVanHerkomst && ingrediënt1.Naam == ingrediënt2.Naam && ingrediënt1.PrijsPerKilo == ingrediënt2.PrijsPerKilo;

Als de beweringen allemaal waar zijn wordt het resultaat ook waar.


Hoofdstuk 1 Interne gegevens

191

7 Nu nog de ongelijkheid != implementeren. Voeg de volgende methode toe: public static bool operator !=(Ingrediënt ingrediënt1, Ingrediënt ingrediënt2) { }

8 Een ongelijkheid is waar als de gelijkheid niet waar is. En dat is in C eenvoudig: zet er gewoon een ! voor. Voeg de volgende regel toe aan de methode !=: return !(ingrediënt1 == ingrediënt2);

Let op! Je kunt een waarschuwing krijgen waar het volgende in staat: ‘SmoothiesPlus.Ingrediënt’ defines operator == or operator != but does not override Object.Equals(object o)

T

Deze waarschuwing kun je nu negeren. In deel 3 komen we hierop terug.

EP

Je hebt hiervoor de operator == aangepast. Er zit nog een addertje onder het gras. Behalve de operator == bestaat er ook een methode Equals(). Deze methode doet functioneel hetzelfde: er worden twee objecten vergeleken en een resultaat teruggegeven. Maar onze operator == is aangepast, daarom moeten we de methode Equals() ook aanpassen.

C

9 In de klasse Ingrediënt maken we de methode Equals() aan. Voeg de volgende regels toe aan de klasse Ingrediënt:

N

public override bool Equals(System.Object obj) { }

O

Ook hier zorgt het woord override ervoor dat het standaardgedrag (de werking) van de methode Equals() wordt vervangen door deze code.

C

De parameter moet van het type System.Object zijn omdat de standaardmethode daar ook mee werkt. 10 Soms heeft een object dat meegegeven wordt geen waarde. Het resultaat van de methode Equals() moet dan false zijn. Voeg om dit te bereiken de volgende regels toe: if (obj == null) { return false; }

11 Een object kan van het type Ingrediënt zijn. Maar een object zou ook een heel ander type kunnen zijn. Als het object van een ander type is, dan is het object per definitie niet gelijk aan het Ingrediënt waarmee het object vergeleken moet worden. We gaan proberen het object om te zetten naar een object van het type Ingrediënt. Dit doen we met een cast.


192

Deel 2: Gegevens

Voeg de volgende regel toe aan de methode Equals(): Ingrediënt ingrediënt = obj as Ingrediënt;

Hier wordt het object obj omgezet naar een object van het type Ingrediënt. Als dit mislukt krijgt de variabele ingrediënt de waarde null! 12 Als je ingrediënt dan weer omzet naar een variabele van het type System.Object en dat is gelijk aan null, dan is het object niet gelijk aan het Ingrediënt waarmee het object vergeleken moet worden.

if ((System.Object)ingrediënt == null) { return false; }

T

Voeg de volgende regels toe, direct na de regels van de vorige stap:

EP

13 De uitzonderingen zijn afgevangen. Nu komt de werkelijke vergelijking. Die is in feite dezelfde als bij de operator ==. Voeg de volgende regels toe aan de methode Equals():

C

return this.Calorieën == ingrediënt.Calorieën && this.Kleur == ingrediënt.Kleur && this.LandVanHerkomst == ingrediënt.LandVanHerkomst && this.Naam == ingrediënt.Naam && this.PrijsPerKilo == ingrediënt.PrijsPerKilo;

N

Dit zal werken. Blijft er nog één probleem over: als je een nieuw ingrediënt hebt toegevoegd kun je er niet nog meer toevoegen. We voeren nog een test uit.

O

1

Test 2 Vul in het veld ingrediënt de nieuwe vrucht Kersen in.

C

2 Klik op Zoek in Lijst.

3 De volgende boodschap verschijnt:

Schermafbeelding 15: SmoothiesPlus test aardbei foutmelding kersen

4 Klik op OK en de Kersen worden toegevoegd. 5 Vul als ingrediënt nu Watermeloen in.


Hoofdstuk 1 Interne gegevens

193

EP

Schermafbeelding 16: SmoothiesPlus kersen

T

6 Wat is het resultaat? Schijnbaar niets:

Toevoegen nieuw ingrediënt Zoek in het code-venster van frmSmoothiesPlus de methode private void selecteerIngrediënt() op.

N

1

C

Hoe komt dit? Op het moment dat je Kersen in Watermeloen verandert, verandert de inhoud van doosje ingrediënt-13. Op de achtergrond heb je een bestaand item in de Array aangepast, maar de ListView blijft ongewijzigd! Dit is eenvoudig op te lossen door, zodra een ingrediënt is toegevoegd, een nieuw doosje te gebruiken voor de variabele ingrediënt:

2 Voeg als laatste regel binnen het if-statement het volgende toe:

O

ingrediënt = new Ingrediënt();

C

Probeer je programma uit. Nu moet je ingrediënten kunnen opzoeken, verwijderen en toevoegen. Succes!

Opdracht Array 2: Uitgebreide telefoonmerkenlijst • • • • • • • •

Wijzig het programma van Opdracht Array 1 zo dat je niet alleen een overzicht krijgt van telefoons maar dat per telefoon de volgende zaken worden getoond: Merk Model (splits dus de naam in merk en model) Besturingssysteem Afmetingen (hoogte en breedte telefoon) Beeldscherm diagonaal (inch) Levensduur accu Prijs Soort scherm (display)


194

Deel 2: Gegevens

Uiteraard maak je een klasse aan met de naam Telefoon en gebruik je deze in een Array. Daarnaast breid je de functionaliteit zo uit dat als je dubbelklikt, je een MessageBox krijgt met alle informatie van de geselecteerde telefoon. Je kunt hiervoor de FocusedItem.Index van de ListView gebruiken.

Wat heb je nu geleerd? • • •

Wat een reference (referentie) type is Wat een value (waarde) type is Waar je met een reference type op moet letten

T

List<T>

EP

Een List<T> is een Array, maar dan zonder de nukken van een Array. List<T> is een lijst. Op de plaats van de T geef je het type, of de klasse aan waaruit de lijst bestaat. Bijvoorbeeld List<int> of List<Klanten>.

C

Een List<T> kent de beperkingen van een Array niet. Een List<T> past zelf zijn grootte aan. Een List<T> is altijd net zo groot als het aantal elementen dat er in opgeslagen is. En voeg je er een element aan toe, dan vergroot de List<T> zichzelf.

N

Een ander voordeel is dat een List<T> ‘strongly typed’ is. Dit betekent dat Visual Studio.net jouw object herkent en direct gebruikt in IntelliSense, je weet wel: die aanwijzingen die je van .Net krijgt als je code aan het ‘inkloppen’ bent.

O

Voorbeeld List<T> 1: Ingrediëntenlijst

C

We zetten nu de ingrediëntenlijst voor de Smoothy-bar over van een Array naar een List<T>. Je zult zien dat de code een stuk eenvoudiger wordt.

1

Eerst de voorbereiding Eerst maak je een kopie van het project SmoothiesPlus (zie ook het vorige voorbeeld).

2 Hernoem de kopie naar SmoothiesPlusList. 3 Hernoem het formulier naar frmSmoothiesPlusList. Het is nu niet nodig om ook maar iets in het formulier te wijzigen. De klasse Ingrediënt is ook helemaal klaar. We gaan dan ook naar het code-venster om stukken code te wijzigen. Wat moet er gebeuren? We zetten de Array ingrediënten om naar de List<T> ingrediënten.


Hoofdstuk 1 Interne gegevens

1

195

Omzetten van een Array naar een List<T> Ga naar het code-venster dat bij het formulier frmSmoothiesPlusList hoort.

2 Zoek de volgende regel op: Ingrediënt[] ingrediënten;

3 Vervang in deze regel Ingrediënt[] door List<Ingrediënt>. 4 Zoek de volgende methode op: private Ingrediënt[] defaultIngrediëntenArray()

5 Vervang de Array-declaratie door een List-declaratie. Dit doe je door Ingrediënt[] in deze regel door List<Ingrediënt> te vervangen.

EP

T

Nu wijzigen we ook de naam van de methode. Niet omdat het anders niet zou werken, maar omdat de naam iets anders suggereert dan we doen. En dat is een voorbeeld van slecht programmeren. 6 We wijzigen de naam defaultIngrediëntenArray() in defaultIngrediëntenList().

C

Let op! Klik direct op het kleine rode streepje onder de naam van de methode. Je krijg dan de volgende vraag:

N

Schermafbeelding 17: Rename method

O

Kies hier voor de eerste optie. Overal waar je de methode hebt gebruikt wordt de naam nu gewijzigd. 7 Ga naar de regel Ingrediënt[] defaultIngrediënten = new Ingrediënt[6];

C

en vervang deze door de volgende regel: List<Ingrediënt> defaultIngrediënten = new List<Ingrediënt>();

Behalve dat de Array-declaratie hier ook door een List-declaratie vervangen wordt, zie je ook dat je nu geen grootte meer hoeft aan te geven. Wel moet je de List ‘in het leven roepen’ met het keyword new. Dit betekent dat een List een klasse is en dus ook een referentietype is.

8 Nu moeten we de zes regels waarin er elementen aan de Array worden toegevoegd, vervangen door regels die elementen toevoegen aan de List<T>. Dit doe je met de methode Add(). Vervang de zes regels: defaultIngrediënten[0] = new Ingrediënt(“Appel”,Color.Green,”Nederland”,10,1.76); defaultIngrediënten[1] = new Ingrediënt(“Peer”,Color.Green,”Luxemburg”,10,1.71);


196

Deel 2: Gegevens

defaultIngrediënten[2] = new Ingrediënt(“Meloen”,Color.Orange,”België”,15,2.15); defaultIngrediënten[3] = new Ingrediënt(“Aardbei”,Color.Red,”Israël”,5,12.50); defaultIngrediënten[4] = new Ingrediënt(“Framboos”,Color.IndianRed, “Duitsland”,7,17.33); defaultIngrediënten[5] = new Ingrediënt(“Rode bes”,Color.Red, “Frankrijk”,17,14.10);

door de volgende zes regels:

T

defaultIngrediënten.Add(new Ingrediënt(“Appel”,Color.Green,”Nederland”,10,1.76)); defaultIngrediënten.Add(new Ingrediënt(“Peer”,Color.Green,”Luxemburg”,10,1.71)); defaultIngrediënten.Add(new Ingrediënt(“Meloen”,Color.Orange,”België”,15,2.15)); defaultIngrediënten.Add(new Ingrediënt(“Aardbei”,Color.Red,”Israël”,5,12.50)); defaultIngrediënten.Add(new Ingrediënt(“Framboos”,Color.IndianRed, “Duitsland”,7,17.33)); defaultIngrediënten.Add(new Ingrediënt(“Rode bes”,Color.Red, “Frankrijk”,17,14.10));

EP

Wat opvalt is dat je geen plek hoeft aan te geven waar je de nieuwe gegevens toevoegt. Add voegt namelijk de nieuwe gegevens altijd achteraan de lijst toe. Zoek de volgende methode op:

private void verwijderIngrediëntUitIngrediëntenArray(int startPositie)

C

9 VERWIJDER de hele methode. Waarom? Deze hele methode wordt vervangen door één regel! Dat doen we nu. 10 Zoek de volgende methode op:

N

private void verwijderGeselecteerdeIngrediënten()

11 Zoek in deze methode de volgende regel op:

O

verwijderIngrediëntUitIngrediëntenArray(i);

12 Vervang deze regel door de volgende regel:

C

ingrediënten.RemoveAt(i);

13 Zoek de volgende methode op: private int voegIngrediëntToe(Ingrediënt ingrediënt)

14 Vervang alle regels van deze methode door de volgende regels: // Toevoegen van een nieuw ingrediënt. Een element wordt altijd // als laatste toegevoegd aan de List. ingrediënten.Add(ingrediënt); return ingrediënten.LastIndexOf(ingrediënt);

15 Zoek de volgende regel op binnen de methode private void selecteerIngrediënt(). int positie = zoekIngrediënt(ingrediënt);

16 Vervang deze regel door:


Hoofdstuk 1 Interne gegevens

197

int positie = ingrediënten.IndexOf(ingrediënt);

Deze methode doet exact hetzelfde als de methode die we zelf geschreven hadden, maar dan met een List<T>. Ook deze methode geeft -1 terug als er geen overeenkomstige waardes zijn gevonden. 17 Verwijder de methode zoekIngrediënt(). 18 Test je programma. Als het goed is werkt het nog precies zo als de vorige versie die met een Array werkte. Je hebt alleen nu minder code gebruikt.

Opdracht List<T> 1: Telefoonmerkenlijst

T

Pas Opdracht Array 2 zo aan dat deze gebruikmaakt van een List<T> in plaats van een Array.

• •

EP

Wat heb je nu geleerd? Het werken met een List<T> Het verschil tussen een Array en een List<T>

C

List versus Array

N

Je hebt nu kennisgemaakt met twee technieken om met een lijst te werken, een Array en een List<T>. Maar wanneer moet je nu wat gebruiken? Gebruik een Array als de data in de Array niet wijzigen, je alleen eenvoudige bewerkingen uitvoert op de gegevens in de Array, je een methode gebruikt die een Array als resultaat teruggeeft, letterlijk iedere bit telt in je programma, de control werkt met een Array.

•

Een List<T> gebruik je: Zoveel mogelijk!

C

O

• • • • •

Enumerable (opsommingen) Je hebt in deel 1 kennisgemaakt met de verschillende typen variabelen die C kent. Voor hele getallen gebruik je int, voor een stuk tekst string, enzovoort. Maar wat nu als een tekstvariabele maar een aantal waardes mag hebben? Je kunt dan een Array maken en elke ingevoerde waarde controleren, maar dat is veel werk. C heeft daar een oplossing voor: de enumerable (enum).


198

Deel 2: Gegevens

Eigenlijk maak je nu zelf een type variabele aan en geef je aan welke waardes deze kan bevatten. Stel je wilt iets doen met de wegenverkeerswet en die gaat over voertuigen. Op de volgende manier maak je dan een enumerable (opsomming) aan. enum Voertuig { Auto, Motor, Fiets, Brommer, Snorfiets, Landbouwvoertuig, Vrachtauto, Autobus }

Als je zo’n lijst hebt wil je hem ook gebruiken. Dat doe je als volgt. Je maakt een variabele aan van het type Voertuig (de enum) en je kiest welke waarde deze aanvankelijk heeft:

EP

Voorbeeld Enum 1: Landenlijst

T

Voertuig voertuig = Voertuig.Motor;

We bouwen de landenlijst van de Smoothy-bar om naar een enum. Het voordeel is misschien niet direct zichtbaar in dit programma, maar als we ooit nog eens een landenlijst nodig hebben, dan kan deze zo gebruikt worden. Voorbereiden Kopieer het project SmoothiesPlusList naar een nieuw project: SmoothiesPlusLanden.

C

1

N

2 Hernoem de belangrijke onderdelen net zoals in de vorige voorbeelden. 3 Ga naar de klasse Ingrediënt.

O

1

Toevoegen van de enum LandVanHerkomst Voeg buiten de klasse Ingrediënt, maar binnen de namespace, de enum LandvanHerkomst toe. Voeg de volgende regels toe:

C

public enum LandVanHerkomst { }

De enum moet public zijn omdat we deze ook buiten deze klasse willen gebruiken. Er zijn meer dan 180 landen in de wereld. We kunnen natuurlijk een atlas pakken, alle landen opzoeken en één voor één overnemen in de code. Er is een eenvoudiger manier: gebruik internet! Dan kun je namelijk kopiëren en plakken. Een goed programmeur maakt gebruik van hetgeen voorhanden is en gebruikt zijn kennis om die gegevens te verwerken.

2 Zoek op het internet naar een site waarop alle landnamen als een te kopiëren lijst worden aangeboden. Bijvoorbeeld http://www.indexmundi.com/nl/. 3 Selecteer en kopieer de landenlijst.


Hoofdstuk 1 Interne gegevens

199

4 Plak de lijst tussen de accolades van enum LandVanHerkomst. 5 Het begin ziet er nu als volgt uit: public enum LandVanHerkomst { •Afghanistan •Akrotiri •Albanië •Algerije •Amerikaanse Maagdeneilanden •Amerikaans-Samoa •Andorra •Angola •Anguilla

6 Selecteer en kopieer een punt. 7 Ga naar de menu-optie Edit.

EP

T

Uiteraard klopt dit niet met de C-syntax (taalregels). Zo moeten landen gescheiden worden door komma’s, mogen er geen spaties in voorkomen en geen speciale tekens. Dit passen we nu aan. We vervangen eerst de punten door komma’s:

8 Ga in de menubalk naar Find and Replace.

N

C

9 Klik op Quick Replace. Het volgende deelvenster verschijnt:

O

Schermafbeelding 18: Find and replace

10 Plak in het bovenste vak de punt.

C

11 Zet in het onderste vak een komma. 12 Klik op de tweede knop rechts naast het onderste vak (Replace all (Alt+A)). De volgende boodschap verschijnt:

Schermafbeelding 19: Replace-boodschap


200

Deel 2: Gegevens

Nu zijn alle punten in komma’s veranderd. Alleen moet de eerste komma weggehaald worden, want daar hebben we niets aan. 13 Verwijder de eerste komma (voor het woord Afghanistan). Nu staat er een rode kronkellijn ander Amerikaanse Maagdeneilanden. Waarom? Omdat er geen spatie in een enum mag staan. 14 Wijzig Amerikaanse Maagdeneilanden in Amerikaanse_Maagdeneilanden. Er staat ook nog een rood kronkelstreepje bij Amerikaans-Samoa. Dit komt omdat leestekens zoals - ‘ “ ( ) ook niet zijn toegestaan. 15 Vervang daarom alle leestekens en spaties door een underscore: _.

T

Ja, dit is handwerk, maar nog altijd vele malen sneller dan alles overtypen.

1

EP

We hebben nu een enum voor land van herkomst. De volgende stap is om de klasse Ingrediënt aan te passen. Aanpassen klasse Zoek in de klasse Ingrediënt de volgende regel op: public string LandVanHerkomst =”Nederland”;

C

2 Vervang deze regel door de regel:

public LandVanHerkomst LandVanHerkomst = LandVanHerkomst.Nederland;

O

N

We hebben nu de string LandVanHerkomst vervangen door de enum LandVanHerkomst LandVanHerkomst. Het grote voordeel is dat je bij een enum alleen maar kunt kiezen uit de aangeboden lijst. Bij een string kun je alles invullen (dus ook bijvoorbeeld Ndrlnd of Nedelrand).

C

De variabele is aangepast. Nu moet de variabele nog aangepast worden in de constructor: Zoek in de klasse Ingrediënt de volgende constructor op:

public Ingrediënt(string naam, System.Drawing.Color kleur, string landVanHerkomst, int calorieën, double PrijsPerKilo)

3 Verander het type van de parameter landVanHerkomst van string naar LandVanHerkomst. Als je het goed gedaan hebt ziet de handtekening van de constructor er als volgt uit: public Ingrediënt(string naam, System.Drawing.Color kleur, LandVanHerkomst landVanHerkomst, int calorieën, double prijsPerKilo)

De klasse is klaar. Nu moet de invoer aangepast worden.


Hoofdstuk 1 Interne gegevens

1

201

Aanpassen invoer Ga naar het design-venster van het formulier. Dubbelklik op de TextBox txbLandVanHerkomst. Je komt nu in het TextChanged event van de TextBox txbLandvanHerkomst en je ziet de volgende code: private void txbLandVanHerkomst_TextChanged(object sender, EventArgs e) { ingrediënt.LandVanHerkomst = txbLandVanHerkomst.Text; }

2 Maar ingrediënt.LandVanHerkomst is niet meer van het type string maar van het type enum LandVanHerkomst.

T

Vervang de regel ingrediënt.LandVanHerkomst = txbLandVanHerkomst.Text; door de regel:

EP

ingrediënt.LandVanHerkomst = (SmoothiesPlus.LandVanHerkomst)Enum.Parse(typeof(SmoothiesPlus. LandVanHerkomst),txbLandVanHerkomst.Text);

Wat gebeurt er? Centraal staat hier de Parse()-methode van Enum. Een string-variabele wordt omgezet naar een enum: Enum.Parse.

C

Binnen deze Parse moet je aangeven om welk soort enum het gaat: typeof(SmoothiesPlus.LandVanHerkomst)

N

En je moet het om te zetten tekstveld aangeven: txbLandVanHerkomst.Txt.

O

Tot slot moet je de algemene enum omzetten (casten) naar de enum LandVanHerkomst, en dat doe je door voor Enum (SmoothiesPlus.LandVanHerkomst) te zetten.

C

3 Je zult begrijpen dat dit fout kan gaan. Als je een niet bestaand land invoert breekt het programma af met een foutmelding. Je weet inmiddels hoe je hiermee moet omgaan. Pas de methode zo aan dat de methode er uiteindelijk als volgt uitziet (standaard foutafhandeling):

private void txbLandVanHerkomst_TextChanged(object sender, EventArgs e) { if (txbLandVanHerkomst.Text.Length > 0) { try { ingrediënt.LandVanHerkomst = (SmoothiesPlus.LandVanHerkomst)Enum.Parse(typeof (SmoothiesPlus.LandVanHerkomst), txbLandVanHerkomst.Text); errorProvider1.SetError(txbLandVanHerkomst, “”); }


202

Deel 2: Gegevens

catch (Exception exception) { errorProvider1.SetError(txbLandVanHerkomst, exception.Message); } } }

Ondersteunende methodes Nu passen we de ondersteunende methodes aan. Overal waar de string landVanHerkomst voorkomt moet deze gewijzigd worden in het type LandVanHerkomst. 1

Ga naar de methode DefaultIngrediënten().

2 De methode ziet er nu als volgt uit:

T

private List<Ingrediënt> defaultIngrediëntenList() { List<Ingrediënt> defaultIngrediënten = new List<Ingrediënt>();

N

C

EP

defaultIngrediënten.Add(new Ingrediënt(“Appel”,Color.Green, “Nederland”,10,1.76)); defaultIngrediënten.Add(new Ingrediënt(“Peer”,Color.Green, “Luxemburg”,10,1.71)); defaultIngrediënten.Add(new Ingrediënt(“Meloen”,Color.Orange, “België”, 15, 2.15)); defaultIngrediënten.Add(new Ingrediënt(“Aardbei”, Color.Red, “Israël”, 5, 12.50)); defaultIngrediënten.Add(new Ingrediënt(“Framboos”,Color.IndianRed, “Duitsland”,7,17.33)); defaultIngrediënten.Add(new Ingrediënt(“Rode bes”,Color.Red, “Frankrijk”,17,14.10)); return defaultIngrediënten;

O

}

C

3 Vervang nu overal de strings voor land van herkomst door de overeenkomende LandVanHerkomst enum. Als je dit gedaan hebt ziet je code er als volgt uit: private List<Ingrediënt> defaultIngrediëntenList() { List<Ingrediënt> defaultIngrediënten = new List<Ingrediënt>(); defaultIngrediënten.Add(new Ingrediënt(“Appel”,Color.Green, LandVanHerkomst.Nederland,10,1.76)); defaultIngrediënten.Add(new Ingrediënt(“Peer”,Color.Green, LandVanHerkomst.Luxemburg,10,1.71)); defaultIngrediënten.Add(new Ingrediënt(“Meloen”, Color.Orange, LandVanHerkomst.België, 15, 2.15)); defaultIngrediënten.Add(new Ingrediënt(“Aardbei”, Color.Red, LandVanHerkomst.Israël, 5, 12.50)); defaultIngrediënten.Add(new Ingrediënt(“Framboos”,Color.IndianRed, LandVanHerkomst.Duitsland,7,17.33)); defaultIngrediënten.Add(new Ingrediënt(“Rode bes”,Color.Red, LandVanHerkomst.Frankrijk,17,14.10));


Hoofdstuk 1 Interne gegevens

203

return defaultIngrediënten; }

4 Zoek de methode private void fillLivIngrediënten() op. 5 Zoek binnen deze methode de volgende regel op: livIngrediëntenItem.SubItems.Add(ingrediënt.LandVanHerkomst);

6 Zet .ToString() achter ingrediënt.LandVanHerkomst De regel ziet er dan als volgt uit: livIngrediëntenItem.SubItems.Add(ingrediënt.LandVanHerkomst.ToString());

T

Omdat de variabele landVanHerkomst geen string meer is maar een enum moet de variabele eerst vertaald worden naar een string. Vandaar ToString(). 7 Test je programma.

EP

8 Wat hebben we nu bereikt met de enum LandVanHerkomst? Een betere invoer: gebruikers kunnen nu niet zomaar een willekeurige tekst invoeren.

Opdracht Enum 1: Telefoonmerkenlijst

C

Pas het programma uit Opdracht List<T> 1 zo aan dat gebruik wordt gemaakt van een enum van telefoonmerken.

N

Voorbeeld Enum 2: Uitgebreide enum

O

Je hebt nu kennisgemaakt met een enum. In dit voorbeeld leer je hoe je handig gebruik kunt maken van enums. Zo kun je heel makkelijk een switch-statement maken op basis van een enum.

C

We maken nu twee Classes: één waarin je landeninformatie opslaat en één waarin je een lijst met landen samenstelt. En natuurlijk een formulier waarin je deze Classes toepast. Start project uitgebreide enum We maken een nieuw project met de naam frmLandenOverzicht.

1

Hernoem het formulier naar frmLandenOverzicht.

2 Plaats de volgende controls op het formulier en pas de genoemde eigenschappen aan:


204

Naam

Label ComboBox

cmbAfkorting2

Label ComboBox

cmbAfkorting3

Label ComboBox

cmbLandnaam

Label ComboBox

cmbVolledigeLandnaam

Label ComboBox

cmbHoofdstad

Label

Eigenschap

Waarde

Text

Korte landcode (2 letters)

TabIndex

1

Text

Lange landcode (3 letters)

TabIndex

2

Text

Naam

TabIndex

3

Text

Volledige naam

TabIndex

4

Text

Hoofdstad

TabIndex

5

Text cmbLandnummer

TabIndex

ListBox

lbxLanden

Tabindex

Tabel 3: Controls landenoverzicht

Landnummer

6 7

EP

ComboBox

T

Type

Deel 2: Gegevens

C

O

N

C

Het formulier kan er dan als volgt uitzien:

Schermafbeelding 20: Enum landen-form

Ons programma zal, als het af is, voor elk land de gegevens kunnen teruggeven zoals voor Nederland: de korte landcode NL, de lange landcode NLD, de volledige naam Koninkrijk der Nederlanden, de hoofdstad Amsterdam en het landnummer (telefoon) 0031.


Hoofdstuk 1 Interne gegevens

1

205

Aanmaken Enum en Classes Maak binnen het project LandenOverzicht een class aan met de naam Land.

2 Maak tussen de regels namespace LandenOverzicht {

en de regel public class Land

de enum LandItems aan:

T

public enum LandItems { }

EP

De enum LandItems wordt gebruikt voor de volgende zaken: – Het selecteren van een eigenschap van de class Land. Door een enum te gebruiken kunnen invoerfouten voorkomen worden. – Het programmeren van een switch kan hierdoor gemakkelijker worden. – De code wordt betrouwbaarder doordat de enum opgenomen wordt in IntelliSense (de interactieve spellingscontrole) van Visual Studio.

O

N

Afkorting2, Afkorting3, Naam, VolledigeNaam, Hoofdstad, Landnummer

C

3 Voeg de volgende items toe aan de enum LandItems:

Deze items komen overeen met de eigenschappen van de class Land. Class Land: vormgeven Maak de volgende eigenschappen (properties) aan binnen de class Land:

C 1

public string Afkorting2 public string Afkorting3 public string Naam public string VolledigeNaam public string Hoofdstad public string Landnummer

Class Land: constructors De class Land krijgt twee constructors: één waarbij de gebruiker van de class alle eigenschappen meegeeft en instelt, en één waarbij een string opgeknipt en omgezet wordt naar de eigenschappen van de class Land.


206

1

Deel 2: Gegevens

Maak de volgende constructor aan met de bijbehorende variabelen/parameters: public Land(string afkorting2, string afkorting3, string naam, string volledigeNaam, string hoofdstad, string landnummer) { }

2 Voeg de volgende regels toe tussen de {accolades}. Deze regels zetten de variabelen om naar de eigenschappen van de class Land. this.Afkorting2 = afkorting2; this.Afkorting3 = afkorting3; this.Naam = naam; this.VolledigeNaam = volledigeNaam; this.Hoofdstad = hoofdstad; this.Landnummer = landnummer;

EP

T

Nu maken we de constructor waarbij één string omgezet wordt naar de eigenschappen van de class Land. In de string moeten de eigenschappen in een vaste volgorde aangeboden worden, waarbij de eigenschappen van elkaar gescheiden worden door een komma. Maak een constructor aan met één parameter/variabele.

C

public Land(string landinformatie) { }

N

3 Nu moet de string gesplitst worden in een Array. De komma’s worden gebruikt om de string te splitsen. Voeg de volgende regel toe: string[] landInfo = landinformatie.Split(‘,’);

O

4 Nu is er een string Array ontstaan van 6 elementen. Ieder ander aantal houdt automatisch in dat de string niet goed is opgebouwd. Voeg het volgende if-statement toe:

C

if (landInfo.Length == 6) { } else { }

5 Als het aantal niet gelijk is aan 6 moet er een foutmelding doorgegeven worden aan het formulier. Dit doen we door het codewoord throw te gebruiken en tegelijkertijd een variabele van het type Exception aan te maken. Voeg de volgende regels toe aan het else-statement: throw new Exception(“De landinformatie string bestaat uit te veel of te weinig elementen \r\n” + landinformatie);

6 Nu moeten de elementen van de string Array omgezet worden naar de verschillende eigenschappen. We gaan uit van een vaste volgorde. Voeg de volgende regels toe binnen het if-statement:


Hoofdstuk 1 Interne gegevens

207

this.Afkorting2 = landInfo[0]; this.Afkorting3 = landInfo[1]; this.Naam = landInfo[2]; this.VolledigeNaam = landInfo[3]; this.Hoofdstad = landInfo[4]; this.Landnummer = landInfo[5];

Class Land: ToString() De class Land heeft nu zes eigenschappen en twee constructors. Van een land wil je soms, als je de afkorting weet, de landnaam opzoeken of het landnummer. Dit maken we mogelijk door verschillende uitvoeringen (implementaties) van de methode ToString() te programmeren. Voeg de volgende regels toe aan de class Land: public string ToString(LandItems[] items) { }

T

1

EP

2 Als parameter wordt er een Array van LandItems meegegeven. Op deze manier is het niet belangrijk in welke volgorde de items worden weergegeven of hoeveel items er getoond moeten worden. Dit kan er één zijn, twee of alle zes.

C

Eerst wordt er een parameter aangemaakt waarin de string wordt opgeslagen die wordt teruggeven. Voeg de volgende regel toe: string returnValue = “”;

N

3 Het programma moet nu per element van de Array de juiste eigenschap toevoegen aan de variabele returnValue.

O

Daarom gebruiken we de foreach-structuur om alle elementen van de Array items te doorlopen. Typ het keyword foreach gevolgd door twee maal tab. Visual Studio maakt de volgende structuur aan:

C

foreach (var item in collection) { }

4 Vervang nu var item in collection door LandItems item in items.

5 Nu maken we gebruik van de combinatie switch, IntelliSense en een enum. Er moet per item bekeken worden welke eigenschap moet worden toegevoegd aan de returnValue. Typ tussen de accolades van foreach het keyword switch gevolgd door tweemaal tab. Visual Studio maakt de volgende structuur aan: switch (switch_on) { default: }


208

Deel 2: Gegevens

6 Vervang nu switch_on door item en sluit af met een enter.

EP

switch (item) { case LandItems.Afkorting2: break; case LandItems.Afkorting3: break; case LandItems.Naam: break; case LandItems.VolledigeNaam: break; case LandItems.Hoofdstad: break; case LandItems.Landnummer: break; default: break; }

T

7 Druk op een pijltje naar beneden om naar de volgende regel te gaan. Als het goed is maakt Visual Studio de volgende structuur aan:

C

8 Als een geselecteerd item niet het eerste item is dat toegevoegd moet worden, dan moet er voor het item een komma geplaatst worden en een spatie. Zoek de volgende regel op: case LandItems.Afkorting2;

N

9 Voeg de volgende regels toe:

if (returnValue.Length == 0) returnValue = Afkorting2; else returnValue = returnValue + “, “ + Afkorting2;

O

Deze regels voegen de waarde van de eigenschap Afkorting2 toe aan de variabele returnValue.

C

10 Dit moeten we nu ook voor alle andere eigenschappen doen. Voeg daarom soortgelijke regels toe aan de andere case-statements. Uiteindelijk moet het switch-statement er als volgt uitzien: switch (item) { case LandItems.Afkorting2: if (returnValue.Length == 0) returnValue = Afkorting2; else returnValue = returnValue + “, “ + Afkorting2; break; case LandItems.Afkorting3: if (returnValue.Length == 0) returnValue = Afkorting3; else returnValue = returnValue + “, “ + Afkorting3; break; case LandItems.Naam: if (returnValue.Length == 0) returnValue = Naam; else returnValue = returnValue + “, “ + Naam; break;


Hoofdstuk 1 Interne gegevens

209

case LandItems.VolledigeNaam: if (returnValue.Length == 0) returnValue = VolledigeNaam; else returnValue = returnValue + “, “ + VolledigeNaam; break; case LandItems.Hoofdstad: if (returnValue.Length == 0) returnValue = Hoofdstad; else returnValue = returnValue + “, “ + Hoofdstad; break; case LandItems.Landnummer: if (returnValue.Length == 0) returnValue = Landnummer; else returnValue = returnValue + “, “ + Landnummer; break; default: break; }

T

11 Tot slot moet de returnValue worden teruggegeven. Voeg daarom de volgende regel toe na de tweede accolade:

EP

return returnValue;

De basis ToString()-methode is gereed. Hierop worden de andere ToString()-methodes gebaseerd. De standaard ToString()-methode moet een string teruggeven met daarin alle landitems gescheiden door een komma. Voeg de volgende methode toe:

C

1

N

public override string ToString() { }

O

Het keyword override moet hier gebruikt worden, zodat de standaard ToString()methode die bij iedere class wordt aangemaakt, vervangen wordt door deze methode.

C

2 Eerst maken we een Array aan van LandItems in de volgorde zoals ze achter elkaar moeten staan. Voeg de volgende regels toe aan de methode ToString(): LandItems[] landItems = {LandItems.Afkorting2, LandItems.Afkorting3, LandItems.Naam, LandItems.VolledigeNaam, LandItems.Hoofdstad, LandItems.Landnummer};

3 Met de variabele landItems kan de al eerder gemaakte ToString()-methode gebruikt worden. Deze kan de gewenste string direct teruggeven. Voeg de volgende regel toe na de regel LandItems.Landnummer}; return ToString(landItems);

Het kan ook handig zijn om de variabelen door te geven als string. Dat doen we ook met een ToString()-methode.


210

1

Deel 2: Gegevens

Voeg de volgende ToString()-methode toe: public string ToString(string strLandItems) { }

2 Ook hier wordt gebruik gemaakt van een Array van LandItems. Voeg tussen de accolades de volgende twee regels toe: // Privévariabelen. LandItems[] landItems = new LandItems[0];

3 De string-variabele strLandItems moet omgezet worden naar een Array zodat de items één voor één omgezet kunnen worden naar een LandItem. Voeg de volgende twee regels toe:

T

// Maken string Array. string[] landItemsStringArray = strLandItems.Split(‘,’);

EP

4 De Array landItems moet net zoveel items bevatten als de landItemsStringArray. Voeg de volgende regels toe: // Aanpassen van de grootte van de LandItems Array. Array.Resize(ref landItems, landItemsStringArray.Length);

C

5 De string Array moet omgezet worden naar een Array van LandItems. Hier wordt for gebruikt omdat we de index i nodig hebben bij de TryParse-methode. Voeg de volgende regels toe:

N

// Omzetten string Array naar landItems Array. for (int i = 0; i < landItemsStringArray.Length; i++) { }

O

6 Ieder element wordt eerst gecontroleerd en dan toegevoegd. Voeg de volgende regels toe aan de foreach-structuur:

C

// Probeer het item toe te voegen aan de landItems Array. if (!Enum.TryParse<LandItems>(landItemsStringArray[i], out landItems[i])) { }

Er wordt gebruik gemaakt van TryParse. Deze functie probeert een string om te zetten naar een aangegeven type. Je geeft het type aan, het item dat je om wilt zetten en het item dat de waarde moet krijgen. Als het lukt geeft deze functie true terug, anders false. Het uitroepteken zorgt ervoor dat het tegengestelde geldt. 7 Als het item niet als LandItem voorkomt moet er een foutmelding aangemaakt en doorgegeven worden aan het aanroepende programma (het formulier). Voeg de volgende regel toe tussen de accolades van het if-statement:


Hoofdstuk 1 Interne gegevens

211

throw new Exception(“Het item “ + landItemsStringArray[i] + “ vormt geen onderdeel van landItems”);

8 Voeg als laatste regel, vlak voor de laatste accolade van deze ToString(), de regel toe die de string teruggeeft. Ook hier wordt gebruik gemaakt van de eerder gemaakte ToString()-functie die werkt met een Array van LandItems. return ToString(landItems);

Er zijn nu meerdere ToString()-functies die werken op basis van strings of de enum LandItems. Het zou echter veel makkelijker zijn om een item aan te duiden met één enkel teken, want dat scheelt een boel typewerk. Om dit voor elkaar te krijgen, maken we eerst de functie die een teken omzet naar een LandItem. Maak de functie fromChar aan door de volgende regels aan de class Land toe te voegen:

EP

private LandItems fromChar(char itemAfkorting) { }

T

1

2 Deze functie geeft een LandItems variabele terug en die maken we aan door de volgende regel toe te voegen tussen de accolades: LandItems landItem;

C

3 Het ingevoerde teken moet gekoppeld worden aan het bij dat teken behorende LandItem.

N

Voeg het volgende switch-item toe:

C

O

switch (itemAfkorting.ToString().ToUpper()) { case “2”: { landItem = LandItems.Afkorting2; break; } case “3”: { landItem = LandItems.Afkorting3; break; } case “N”: { landItem = LandItems.Naam; break; } case “V”: { landItem = LandItems.VolledigeNaam; break; } case “H”: { landItem = LandItems.Hoofdstad; break; } case “L”: { landItem = LandItems.Landnummer; break; } default: { throw new Exception(“De afkorting “ + itemAfkorting + “ is niet toegestaan \r\n” + “Alleen de waardes 2, 3, N, V, H en L of \r\n “ + “n, v, h en l zijn toegestaan.”); } return landItem; }

4 De functie waarin een teken wordt omgezet naar een type LandItems is gereed. Nu kan deze functie toegepast worden in twee ToString()-methodes. Voeg de volgende regels toe aan de class Land om een volgende ToString()-methode te maken:


212

Deel 2: Gegevens

public string ToString(char[] tekens) { }

5 Dit is een ToString()-functie die gebruik maakt van een Array van tekens. Deze moet omgezet worden naar een Array van LandItems. Voeg de volgende regels toe aan deze ToString()-functie: // Aanmaken van een LandItems-Array op basis van de tekens-Array. LandItems[] landItems = new LandItems[tekens.Length];

EP

for (int i = 0; i < landItems.Length; i++) { landItems[i] = fromChar(tekens[i]); }

T

6 Nu kan één voor één elk teken omgezet worden naar een item in de LandItems Array. Mocht een teken niet voorkomen dan genereert de functie fromChar een foutmelding. Voeg de volgende regels toe:

7 Als laatste moet ook hier de regel toegevoegd worden die een string teruggeeft: return ToString(landItems);

C

Behalve het gebruik van een Array van tekens is het ook handig om de ToString()functie aan te kunnen roepen met een enkel teken. 8 Voeg een ToString()-functie toe door de volgende regels toe te voegen:

N

public string ToString(char landItemTeken) { }

O

9 Door gebruik te maken van alle hiervoor ontwikkelde functies kunnen we nu volstaan met een paar regels commentaar en één regel code. Voeg de volgende regels toe:

C

// Eerst wordt er van een char een enum Landitems gemaakt // daarna wordt deze in een string omgezet // om er vervolgens het juiste landitem bij te zoeken. return ToString((fromChar(landItemTeken).ToString()));

Hiermee is de class Land gereed. Class Landen aanmaken Er is ook een class Landen nodig waarin de lijst met landgegevens opgeslagen kan worden. Deze class gebruiken we uiteindelijk binnen het formulier. Deze class bestaat uit een lijst met landgegevens en enkele methodes om specifieke gegevens op te halen. 1

Maak een nieuwe class aan onder de class Land maar binnen de accolades van de namespace LandenOverzicht.


Hoofdstuk 1 Interne gegevens

213

public class Landen { }

Class Landen: methode Lijst() Omdat deze class alleen gebruikt wordt om een vooraf gedefinieerde lijst met gegevens te genereren, maken we alle functies in deze class static. Als we aangeven dat een class static is dan kunnen we de class gebruiken zonder eerst een instantie aan te maken met het keyword new. Dit is ook logisch omdat er door de eindgebruiker geen gegevens in deze class worden opgeslagen. Maak binnen de class Landen de volgende methode aan: public static List<Land> Lijst() { }

T

1

EP

2 De lijst bestaat uit instanties van de class Land en deze moet bij aanmaak van de Lijst gevuld worden. Maar eerst moet de List gedefinieerd worden. Voeg de volgende regels toe tussen de accolades van de methode Lijst():

C

List<Land> lijst = new List<Land> { };

N

Let op! Na de laatste accolade moet je een ; zetten. Het is namelijk één statement.

// Landen A --> AZ new Land(“AF,AFG,Afghanistan,Islamitische Republiek Afghanistan,Kaboel,0093”), new Land(“AL,ALB,Albanië,Republiek Albanië,Tirana,00355”), new Land(“DZ,DZA,Algerije,Democratische Volksrepubliek Algerije, Algiers,00213”), new Land(“AD,AND,Andorra Vorstendom,Andorra,Andorra,00376”), new Land(“AO,AGO,Angola,Republiek Angola,Luanda,00244”), new Land(“AI,ATG,Antigua en Barbuda,Antigua en Barbuda,Saint John’s, 001 268”), new Land(“AR,ARG,Argentinië,Argentijnse Republiek,Buenos Aires,0054”), new Land(“AM,ARM,Armenië,Republiek Armenië,Jerevan,00374”), new Land(“AU,AUS,Australië,Gemenebest Australië,Canberra,0061”), new Land(“AZ,AZE,Azerbeidzjan,Republiek Azerbeidzjan,Bakoe,00994”),

C

{

O

Zoals bij elke List kun je meteen bij de aanmaak tussen accolades de op te nemen waardes definiëren. Voeg de volgende regels toe tussen de accolades:

// Landen B --> BZ new Land(“BS,BHS,Bahama’s,Gemenebest van de Bahama’s,Nassau,001 242”), new Land(“BH,BHR,Bahrein,Koninkrijk Bahrein,Manama,00973”), new Land(“BD,BGD,Bangladesh,Volksrepubliek Bangladesh,Dhaka,00880”), new Land(“BB,BRB,Barbados,Barbados,Bridgetown,001 246”),


214

Deel 2: Gegevens

new Land(“BE,BEL,België,Koninkrijk België,Brussel,0032”), new Land(“BZ,BLZ,Belize,Belize,Belmopan,00501”), new Land(“BJ,BEN,Benin,Republiek Benin,Porto-Novo,00229”), new Land(“BT,BTN,Bhutan,Koninkrijk Bhutan,Thimphu,00975”), new Land(“BO,BOL,Bolivia,Plurinationale Staat Bolivia,Sucre,00591”), new Land(“BA,BIH,Bosnië en Herzegovina,Bosnië en Herzegovina,Sarajevo,00387”), new Land(“BW,BWA,Botswana,Republiek Botswana,Gaborone,00267”), new Land(“BR,BRA,Brazilië,Federale Republiek Brazilië,Brasilia,0055”), new Land(“BN,BRN,Brunei,Staat Brunei Darussalam,Bandar Seri Begawan,00673”), new Land(“BG,BGR,Bulgarije,Republiek Bulgarije,Sofia,00359”), new Land(“BF,BFA,Burkina Faso,Burkina Faso,Ouagadougou,00226”), new Land(“BI,BDI,Burundi,Republiek Burundi,Bujumbura,00257”),

C

EP

T

// Landen C --> CZ new Land(“KH,KHM,Cambodja,Koninkrijk Cambodja,Phnom Penh,00855”), new Land(“CA,CAN,Canada,Canada,Ottawa,001”), new Land(“CF,CAF,Centraal-Afrikaanse Republiek, Centraal-Afrikaanse Republiek,Bangui,00236”), new Land(“CL,CHL,Chili,Republiek Chili,Santiago,0056”), new Land(“CN,CHN,China,Volksrepubliek China,Beijing,0086”), new Land(“CO,COL,Colombia,Republiek Colombia,Bogota,0057”), new Land(“KM,COM,Comoren,Unie der Comoren,Moroni,00269”), new Land(“CG,COG,Congo-Brazzaville,Republiek Congo,Brazzaville,00242”), new Land(“CD,COD,Congo-Kinshasa,Democratische Republiek Congo, Kinshasa,00243”), new Land(“CR,CRI,Costa Rica,Republiek Costa Rica,San José,00506”), new Land(“CU,CUB,Cuba,Republiek Cuba,Havana,0053”), new Land(“CY,CYP,Cyprus,Republiek Cyprus,Nicosia,00357”),

O

N

// Landen D --> DZ new Land(“DK,DNK,Denemarken,Koninkrijk Denemarken,Kopenhagen,0045”), new Land(“DJ,DJI,Djibouti,Republiek Djibouti,Djibouti,00253”), new Land(“DM,DMA,Dominica,Gemenebest Dominica,Rosea,001 767”), new Land(“DO,DOM,Dominicaanse Republiek,Dominicaanse Republiek, Santo Domingo,001809 001829 001849”), new Land(“DE,DEU,Duitsland,Bondsrepubliek Duitsland,Berlijn,0049”)

C

// Land N new Land(“NL,NLD,Nederland,Koninkrijk der Nederlanden,Amsterdam,0031”),

Dit voorbeeld laat de landnamen met beginletters A t/m D zien. Wil je de lijst compleet maken, bijvoorbeeld om te gebruiken in andere programma’s, dan zijn deze gegevens te vinden op de volgende websites: http://www.landafkorting.nl/ https://nl.wikipedia.org/wiki/Lijst_van_landnummers_in_de_telefonie https://nl.wikipedia.org/wiki/Lijst_van_landen_in_2017

3 De lijst moet teruggeven worden aan de aanroepende functie. Voeg vóór de afsluitende accolade van de methode de volgende regel toe: return lijst;


Hoofdstuk 1 Interne gegevens

215

Class Landen: methode ToStringArray De lijst moet verschillende soorten output kunnen genereren. Hiervoor maken we de functie ToStringArray() in twee versies: één op basis van een teken dat voor een ListItem staat en één op basis van een Array met ListItems. 1

Voeg de volgende regels toe om de static methode ToStringArray() te maken met als input één teken: public static string[] ToStringArray(char landItemTeken) { }

2 Deze functie geeft een string Array terug. Die moet aangemaakt worden. Voeg de volgende regel toe:

T

string[] stringArray = new string[Lijst().Count()];

EP

Omdat de stringArray altijd net zoveel elementen zal bevatten als het resultaat van de functie Lijst() wordt de stringArray met deze grootte aangemaakt. 3 Hoewel de foreach-functie gebruikt gaat worden, hebben we ook een index nodig die aangeeft op welke plek een item toegevoegd moet worden aan de stringArray. Voeg de volgende regels toe:

C

// Teller om bij te houden waar een item moet worden toegevoegd. int index = 0;

O

N

4 Ieder item van de functie Lijst() moet omgezet worden naar een item van de stringArray. We gebruiken een foreach omdat dan de functie Lijst() maar één keer aangeroepen wordt. Iedere keer dat je deze functie aanroept, wordt er in het geheugen een nieuwe lijst van landen aangemaakt. En dat willen we beperken, gezien de grootte van de lijst. Voeg de volgende regels toe:

C

// Omzetten van de Lijst naar de stringArray. foreach (var landItem in Lijst()) { stringArray[index] = landItem.ToString(landItemTeken); index++; }

5 Voeg tot slot deze regel toe om de stringArray terug te geven: return stringArray;

De vorige ToStringArray()-methode wordt gebruikt om de ComboBoxen te vullen. Iedere ComboBox heeft maar één soort item. Er is ook een ToStringArray-functie nodig die meer elementen teruggeeft.

1

Class Landen: overloaded methode ToStringArray Maak nog een ToStringArray()-methode aan met als inputparameter een LandItems Array. Voeg de volgende regels toe:


216

Deel 2: Gegevens

public static string[] ToStringArray(LandItems[] landItems) { }

2 Ook deze functie geeft een string Array terug. Typ de volgende regels om deze toe te voegen: // Aanmaken output Array, heeft per definitie een gelijk // aantal elementen als de Lijst(). string[] stringArray = new string[Lijst().Count()];

3 Voeg de benodigde teller toe met de volgende regels: // Teller om bij te houden waar een item moet worden toegevoegd. int index = 0;

T

4 Voeg de volgende regels toe om de Lijst om te zetten naar de stringArray:

return stringArray;

C

5 Voeg de laatste regel toe:

EP

// Omzetten van de Lijst naar de stringArray. foreach (var landItem in Lijst()) { stringArray[index] = landItem.ToString(landItems); index++; }

N

Er zijn ook methodes nodig die op basis van de landnaam een bepaald item teruggeven. Voor ieder item van de class Land moet er één gemaakt worden.

O

6 Voeg de volgende regels toe aan de class Landen om de methode getLandNummer() te maken:

C

public static string getLandNummer(string landNaam) { }

7 Ieder land in de Lijst() moet bekeken worden. Voeg daarom de volgende regels toe tussen de accolades: foreach (Land land in Lijst()) { }

8 Als de landnaam gelijk is aan de ingevoerde landnaam moet deze methode het landnummer teruggeven. Voeg tussen de accolades van foreach de volgende regels toe: if(land.Naam == landNaam) { return land.Landnummer; }

9 Omdat de methode wordt afgebroken bij het uitvoeren van een return kunnen we volstaan met het toevoegen van een laatste return die een lege string teruggeeft.


Hoofdstuk 1 Interne gegevens

217

Deze wordt namelijk alleen maar uitgevoerd als een landnaam niet gevonden is. Voeg de volgende regel toe na de laatste accolade van het if-statement: return “”;

Maak nu soortgelijke methodes aan met de volgende handtekening: 10 public static string getAfkorting2(string landNaam). Deze methode geeft de bij het land behorende tweeletterige afkorting terug. 11 public static string getAfkorting3(string landNaam). Deze methode geeft de bij het land behorende drieletterige afkorting terug. 12 public static string getHoofdstad(string landNaam). Deze methode geeft de bij het land behorende hoofdstad terug.

EP

T

13 public static string getVolledigeNaam(string landNaam). Deze methode geeft de bij het land behorende volledige naam terug.

De methode geeft aan op basis van welk type item in de Lijst() gezocht moet worden, welke waarde dit item heeft en welk type item teruggegeven moet worden als string. Voeg de volgende regels toe:

N

1

C

Class Landen: methode getItem De vorige functies geven op basis van de landnaam een ander item terug. Als we voor iedere mogelijke combinatie een aparte methode moeten maken, dan moeten er 36 methodes gemaakt worden die erg op elkaar lijken. Dit kan anders. We zullen dit laten zien met de laatste methode getItem().

O

public static string getItem(LandItems inputItem, string inputWaarde, LandItems outputItem) { }

C

2 Maak de output-variabele aan door de volgende regels toe te voegen binnen de accolades: string outputWaarde = “”;

3 Per land moet bekeken worden of het land voldoet aan de ingevoerde eis. Voer de volgende regels toe na de definitie van de output-variabele: foreach (Land land in Lijst()) { }

4 Maak nu binnen de foreach-loop een switch aan op basis van het inputItem: switch (inputItem) { case LandItems.Afkorting2: break; case LandItems.Afkorting3: break;


218

Deel 2: Gegevens

case LandItems.Naam: break; case LandItems.VolledigeNaam: break; case LandItems.Hoofdstad: break; case LandItems.Landnummer: break; default: break; }

T

5 Met de switch wordt het juiste item geselecteerd. Vervolgens moet code toegevoegd worden die checkt of de ingevoerde waarde van dat item overeenkomt met de ingevoerde waarde. Voeg na de regel case LandItems.Afkorting2: de volgende regels toe:

EP

if (land.Afkorting2 == inputWaarde) { outputWaarde = land.ToString(outputItem.ToString()); }

6 Voeg na de regel case LandItems.Afkorting3: de volgende regels toe:

C

if (land.Afkorting3 == inputWaarde) { outputWaarde = land.ToString(outputItem.ToString()); }

N

7 Voeg na de regel case LandItems.Naam: de volgende regels toe:

O

if (land.Naam == inputWaarde) { outputWaarde = land.ToString(outputItem.ToString()); }

C

8 Voeg soortgelijke regels toe bij de resterende switch-items (VolledigeNaam, Hoofdstad, Landnummer). 9 Voeg vóór de afsluitende accolade van GetItem() de slotregel van deze methode toe: return outputWaarde;

De class Landen is hiermee gereed voor gebruik. Inrichten formulier: Load De bedoeling is om, zodra het formulier geladen wordt, de verschillende ComboBoxen te voorzien van de juiste lijsten. 1

Ga naar het formulier frmLandenOverzicht. Dubbelklik ergens op het formulier zelf, niet op een control. Je komt nu in het Load event van het formulier. Je ziet de volgende regels op het scherm:


Hoofdstuk 1 Interne gegevens

219

private void frmLandenOverzicht_Load(object sender, EventArgs e) { }

2 Per ComboBox wordt een andere lijst gegenereerd vanuit de class Landen. We kunnen steeds als parameter meegeven om welk item het gaat. Omdat het om static methodes gaat is het niet nodig om eerst een variabele van het type Landen aan te maken en die te vullen met het keyword new. Voeg de volgende regels toe aan frmLandenOverzicht_Load: try {

EP

} catch (Exception exception) { MessageBox.Show(exception.Message); }

T

cmbAfkorting2.Items.AddRange(Landen.ToStringArray(‘2’)); cmbAfkorting3.Items.AddRange(Landen.ToStringArray(‘3’)); cmbLandnaam.Items.AddRange(Landen.ToStringArray(‘N’)); cmbVolledigeLandnaam.Items.AddRange(Landen.ToStringArray(‘V’)); cmbHoofdstad.Items.AddRange(Landen.ToStringArray(‘H’)); cmbLandnummer.Items.AddRange(Landen.ToStringArray(‘L’));

C

Het try-catch-blok zorgt ervoor dat, als er in het onderliggende proces iets verkeerd gaat, er een nette foutmelding wordt weergegeven.

N

3 Behalve de ComboBoxen moet ook de ListBox gevuld worden met landinformatie. Alleen moet daar alle informatie van een land getoond worden.

O

Hiervoor moeten we eerst een Array vullen met LandItems in de volgorde waarop ze getoond moeten worden. Voeg de volgende regels toe net voor de laatste accolade van het try-blok:

C

LandItems[] landItems = {LandItems.Afkorting2, LandItems.Afkorting3, LandItems.Naam, LandItems.VolledigeNaam, LandItems.Hoofdstad, LandItems.Landnummer};

4 Nu kan met behulp van de Array LandItems de ListBox lbxLanden gevuld worden. Voeg de volgende regel toe aan het Load event: lbxLanden.Items.AddRange(Landen.ToStringArray(landItems));

Inrichten formulier: Selecteer land in ListBox Als er in de ComboBoxen een andere waarde geselecteerd wordt, moet in de ListBox lbxLanden de desbetreffende regel geselecteerd worden. 1

Maak een nieuwe methode aan door de volgende regels toe te voegen aan het formulier: private void selecteerLand() { }


220

Deel 2: Gegevens

2 Eerst moet een zoekstring gemaakt worden door de waardes van de ComboBoxen in de juiste volgorde te zetten. Voeg de volgende regels toe aan de methode selecteerLand(): //Instellen van de zoekstring. string zoekString = cmbAfkorting2.Text + “, “ + cmbAfkorting3.Text + “, “ + cmbLandnaam.Text + “, “ + cmbVolledigeLandnaam.Text + “, “ + cmbHoofdstad.Text + “, “ + cmbLandnummer.Text;

3 Nu kan er gezocht worden. Er wordt gebruikgemaakt van de standaardfunctie FindString. Hier geef je het op te zoeken item in aan en de waarde die teruggegeven wordt als er niets wordt gevonden, in dit geval -1. Voeg de volgende regel toe: int index = lbxLanden.FindString(zoekString, -1);

EP

if (index != -1) { lbxLanden.SetSelected(index, true); }

T

4 Als de zoekstring niet gevonden is, wordt de index -1. Als de index dus ongelijk (het symbool ! in C) is aan -1 moeten we de regel met die index selecteren. Voeg de volgende regels toe:

Inrichten formulier: ComboBoxen individueel geprogrammeerd Ga naar het design-venster en dubbelklik op de ComboBox cmbLandnaam. De volgende regels verschijnen:

O

1

N

C

Inrichten formulier: Synchroniseren controls Als in één van de comboboxen een andere waarde geselecteerd wordt, moeten de bij die control behorende waardes in de andere controls opgezocht worden. Tegelijkertijd moet in de ListBox lbxLanden de juiste regel geselecteerd worden.

C

private void cmbLandnaam_SelectedIndexChanged(object sender, EventArgs e) { }

2 Als er een ander item geselecteerd is, moeten de andere ComboBoxen aangepast worden. Voeg de volgende regels toe tussen de accolades: cmbAfkorting2.Text = Landen.getAfkorting2(cmbLandnaam.Text); cmbAfkorting3.Text = Landen.getAfkorting3(cmbLandnaam.Text); cmbVolledigeLandnaam.Text = Landen.getVolledigeNaam(cmbLandnaam.Text); cmbHoofdstad.Text = Landen.getHoofdstad(cmbLandnaam.Text); cmbLandnummer.Text = Landen.getLandNummer(cmbLandnaam.Text);

3 Alle ComboBoxen zijn aangepast, nu moet de ListBox nog aangepast worden. Daarbij maken we gebruik van de methode selecteerLand(). Voeg de volgende regels toe: // Selecteren van het land in de ListBox. selecteerLand();


Hoofdstuk 1 Interne gegevens

221

Inrichten formulier: ComboBoxen individueel geprogrammeerd met algemene methode Als je voor elke ComboBox een apart stel methodes moet maken is dit erg veel werk en bovendien onderhouds- en foutgevoelig. Er is in de class Landen ook een methode getItems() die in een oplossing voor dit probleem voorziet. Die maken we nu. 1

Ga naar het design-venster en dubbelklik op de ComboBox cmbAfkorting2. De volgende regels verschijnen: private void cmbAfkorting2_SelectedIndexChanged(object sender, EventArgs e) { }

2

GetItems() vraagt om het type van het veld, de waarde van het veld en welk veld er

teruggegeven moet worden. Voeg de volgende regels toe:

C

EP

T

cmbAfkorting3.Text = Landen.getItem(LandItems.Afkorting2, cmbAfkorting2.Text, LandItems.Afkorting3); cmbLandnaam.Text = Landen.getItem(LandItems.Afkorting2, cmbAfkorting2.Text, LandItems.Naam); cmbVolledigeLandnaam.Text = Landen.getItem(LandItems.Afkorting2, cmbAfkorting2.Text, LandItems.VolledigeNaam); cmbHoofdstad.Text = Landen.getItem(LandItems.Afkorting2, cmbAfkorting2.Text, LandItems.Hoofdstad); cmbLandnummer.Text = Landen.getItem(LandItems.Afkorting2, cmbAfkorting2.Text, LandItems.Landnummer);

N

Als zoekitem wordt steeds de waarde van cmbAfkorting2 meegegeven, want daar wordt op gezocht. Vervolgens wordt het item voor de juiste ComboBox teruggegeven.

O

Het is niet nodig de methode selecteerLand() aan te roepen. Omdat de landnaam wijzigt, wordt automatisch de methode cmbLandnaam.SelectedIndexChanged() aangeroepen. En daarin wordt de methode selecteerLand() uitgevoerd.

C

3 Ga naar het design-venster en dubbelklik op de ComboBox cmbAfkorting3. De volgende regels verschijnen: private void cmbAfkorting3_SelectedIndexChanged(object sender, EventArgs e) { }

4 Pas deze methode op dezelfde manier aan als de vorige methode (stap 1 en 2). Let op dat je naar de juiste ComboBoxen en waardes verwijst. 5 Ga naar het design-venster en dubbelklik op de ComboBox cmbVolledigeNaam. De volgende regels verschijnen: private void cmbVolledigeLandnaam_SelectedIndexChanged(object sender, EventArgs e) { }


222

Deel 2: Gegevens

6 Pas deze methode ook op dezelfde manier aan als de vorige methode (stap 1 en 2). Let weer op dat je naar de juiste ComboBoxen en waardes verwijst. Inrichten formulier: ComboBoxen met gebruik van algemene methode Bij de laatste methodes heb je al kunnen zien dat de code heel erg op elkaar lijkt. Door deze code in een aparte methode te zetten, wordt het overzichtelijker, makkelijker te onderhouden en eenvoudiger te maken. 1

Eerst maken we de algemene methode. Voeg de volgende regels toe aan het formulier: private void pasLandItemsAan(LandItems inputVeld, String inputWaarde) { }

T

2 Voeg nu de volgende regels toe aan de nieuwe methode:

C

EP

cmbAfkorting2.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.Afkorting2); cmbAfkorting3.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.Afkorting3); cmbLandnaam.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.Naam); cmbVolledigeLandnaam.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.VolledigeNaam); cmbHoofdstad.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.Hoofdstad); cmbLandnummer.Text = Landen.getItem(inputVeld, inputWaarde, LandItems.Landnummer);

N

Zoals je ziet zijn die velden waar je steeds dezelfde waardes moest invullen vervangen door variabelen.

O

3 Ga naar het design-venster en dubbelklik op de ComboBox cmbHoofdstad. De volgende regels verschijnen:

C

private void cmbHoofdstad_SelectedIndexChanged(object sender, EventArgs e) { }

4 Voeg de volgende regel toe waarin je de gemaakte methode pasLandItemsAan() aanroept: pasLandItemsAan(LandItems.Hoofdstad, cmbHoofdstad.SelectedItem.ToString());

5 Pas de SelectedIndexChanged() methode van de ComboBox cmbLandnummer op dezelfde manier aan. 6 Voeg de volgende code toe aan het SelectedIndexChanged event van de ListBox lbxLanden: // Aanmaken nieuw land op basis van de waardes uit de ListBox Land land = new Land(lbxLanden.Text);


Hoofdstuk 1 Interne gegevens

223

// Toekennen van de waardes aan de diverse ComboBoxen cmbAfkorting2.Text = land.Afkorting2; cmbAfkorting3.Text = land.Afkorting3; cmbHoofdstad.Text = land.Hoofdstad; cmbLandnaam.Text = land.Naam; cmbLandnummer.Text = land.Landnummer; cmbVolledigeLandnaam.Text = land.VolledigeNaam;

Deze code zorgt ervoor dat als je een regel selecteert in de ListBox de bijbehorende waardes in de ComboBoxen worden opgezocht. 7 Test je programma.

Wat heb je nu geleerd?

C

•

T

• • •

Hoe je met behulp van een enum snel een switch-structuur kunt maken in Visual Studio Hoe je ComboBoxen kunt voorzien van lijstjes op basis van een class Hoe je input kunt controleren met behulp van een enum Dat als je een class goed opzet, en dat is best veel werk, je uiteindelijk iets krijgt wat je heel makkelijk vaak kunt toepassen Hoe je gebruik kunt maken van een static class

EP

•

Eenvoudige objecten (Structs)

Je moet een class instantiëren (aanmaken) met het keyword new.

O

1

N

In deel 1 heb je kennisgemaakt met objecten. Een object maak je met het keyword class. Een class biedt veel functionaliteit maar heeft drie nadelen:

C

2 Een class is altijd een referentietype. Je hebt, behalve de objecten zelf, altijd de verwijzingen (referenties) nodig. 3 En je hebt gezien dat het soms ook lastig programmeren is.

• •

Een eenvoudig object kun je ook maken met het keyword struct in plaats van class. De voordelen hiervan zijn: Je hoeft een struct niet te instantiëren, je kunt de struct direct gebruiken. Een struct is een waardetype en heeft een betere performance.

• •

Helaas heeft een struct ook wat nadelen: Een struct kan niet als basis dienen voor een andere struct. Een veld in een struct kun je geen standaardwaarde meegeven. Een constructor van een struct moet altijd tenminste één parameter bevatten. Daarom gebruik je structs alleen voor eenvoudige objecten.


224

Deel 2: Gegevens

Voorbeeld Struct 1: Postcode Een postcode is een eenvoudig stukje data dat eigenlijk uit twee soorten gegevens bestaat: vier cijfers, soms een spatie en twee letters. Het aardige is dat de letters altijd in hoofdletters worden geschreven, maar dat de invoer ook in kleine letters kan zijn. En de spatie? Het is niet nodig om deze te bewaren. Bij de uitvoer kun je de gebruiker de mogelijkheid geven om de spatie wel of niet weer te geven. En bij de invoer kun je regelen dat spaties genegeerd worden. Wat gaan we maken?

T

Een struct met de naam Postcode en de volgende velden: Cijfers (int) Letters (char)

1

EP

Daarnaast nog de methodes ToString() om het type Postcode om te zetten naar een string en Parse() om een string om te zetten naar het type Postcode. Voorbereiding Maak een nieuw project aan met de volgende naam: StructPostcode.

C

2 Hernoem het formulier naar frmStructPostcode.

Eigenschap

Waarde

N

3 Plaats de volgende controls op het formulier: Type

Naam

Label

label1

TextBox

txbPostcode

Postcode:

Text

O

Tabel 4: Controls postcode

Maken struct Ga in de menubalk naar Project en klik op Add Class (Add Struct bestaat niet!)

C 1

2 Vul als naam in Postcode. Je krijgt nu het volgende te zien: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StructPostcode { class Postcode { } }


Hoofdstuk 1 Interne gegevens

225

3 Vervang het woord class door het woord struct. De code ziet er nu als volgt uit: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace StructPostcode { struct Postcode { } }

EP

T

Aanmaken private variabelen Een verschil tussen een class en een struct is dat je de variabelen van een struct geen initiële waarde mee kunt geven. Dat doet het systeem voor je als je een parameterloze constructor hebt of je moet het zelf doen als je een constructor hebt met één of meerdere parameters. Een postcode kent twee onderdelen: cijfers en letters.

Voeg binnen de binnenste accolades de volgende commentaarregel toe:

C

1

// Private variabelen.

N

2 Voeg de volgende regels toe om de privévariabelen aan te maken: private string _Letters; private short _Cijfers;

O

Voor cijfers kiezen we het type short. Dit omdat cijfers nooit groter kan worden dan 9999.

C

Aanmaken eigenschappen (properties) Bij zowel een class als een struct kun je gebruikmaken van eigenschappen. Maar wat is nu eigenlijk een eigenschap en wat is het verschil met een variabele? Een eigenschap zegt iets over een object (een class of een struct). Via een eigenschap verander je de waarde van een privévariabele (set) of haal je de waarde van een privévariabele op (get).

Waarom doen we zo ingewikkeld? Omdat je binnen de eigenschappen alle controles maakt die ervoor zorgen dat alleen de toegestane waardes in een variabele kunnen komen. Deze werkwijze is gestandaardiseerd en dat maakt het programmeren eenvoudiger. 1

Voeg de volgende commentaarregel toe: // Public eigenschappen.


226

Deel 2: Gegevens

2 We voegen nu de property (eigenschap) Letters toe. Voeg de volgende regels toe: public string Letters { }

Je ziet: geen puntkomma’s en ronde haakjes tot dusver. 3 We zorgen ervoor dat de waarde van de privévariabele opgehaald wordt. Dit doen we door een zogenaamde getter te maken. Voeg de volgende regels toe tussen de accolades: get { return _Letters;

T

}

EP

Met get zorg je ervoor dat je met de volgende code de inhoud van _Letters op kunt halen: Postcode.Letters;

4 Nu moeten we het nog mogelijk maken om de _Letters te kunnen vullen. Dit doen we door een setter toe te voegen. Voeg de volgende regels toe:

C

set { }

O

N

5 Een postcode bestaat uit hoofdletters. De ingevoerde waarde kan een kleine letter zijn. We kunnen de gebruiker lastigvallen met foutmeldingen. Beter is om gewoon de ingevoerde waarde standaard om te zetten naar hoofdletters. Voeg de volgende twee regels toe in de setter: // Eerst de ingevoerde waarde omzetten naar hoofdletters. value = value.ToUpper();

C

Het keyword value staat voor de ingevoerde waarde.

6 Probleem is dat de gebruiker natuurlijk iets anders dan een letter kan invoeren, een uitroepteken, een punt of een cijfer. Dit checken we teken voor teken. Voeg de volgende regels toe: foreach (char letter in value) { }

Hiermee lopen we langs alle tekens. 7 Nu checken we of het teken een hoofdletter is. Hiervoor maken we later de methode isHoofdletter(). Voeg de volgende regels toe: if (isHoofdletter(letter)==false) { }


Hoofdstuk 1 Interne gegevens

227

8 Omdat een struct of een class niet in een formulier gebruikt wordt, hebben we niet de beschikking over zoiets als een MessageBox of een ErrorProvider. Wel kunnen we een foutmelding maken die je daar met een try-catch-blok kunt opvangen. En wat hoort er bij vangen? Juist: gooien (throw)! En dat doen we door de volgende regel toe te voegen tussen de accolades, achter if: throw new Exception(“Foutief teken:” + letter); throw breekt direct de uitvoering van dit gedeelte af!

9 Alle controles zijn uitgevoerd. Er moet nog één ding uitgevoerd worden: de interne variabele letters moet de waarde van value krijgen. Voeg de volgende regels toe voor de laatste accolade van set:

T

// Als alles goed is, omzetten naar de interne variabele. _Letters = value;

De totale code voor de eigenschap _Letters ziet er nu, als het goed is, als volgt uit:

C

O

N

C

EP

// Public eigenschappen. public string Letters { get { return _Letters; } set { // Eerst omzetten naar hoofdletters. value = value.ToUpper(); // Check op andere tekens dan hoofdletters. foreach (char letter in value) { if (isHoofdletter(letter)==false) { throw new Exception(“Foutief teken:” + letter); } } // Als alles goed is, omzetten naar de interne variabele. _Letters = value; } }

10 Nu maken we de eigenschap Cijfers. Voeg de volgende regels toe na de afsluitende accolade van de eigenschap Letters. public short Cijfers { // Publieke eigenschap omzetten naar privévariabelen en vice versa. get {return _Cijfers;} set { } }


228

Deel 2: Gegevens

11 Ook hier voegen we weer een controle voor de ingevoerde gegevens toe. Eerst controleren we of de ingevoerde waarde groter is dan 1000 (de kleinste postcode). Voeg achter de eerste accolade van set de volgende regels toe: // Omdat de laagste postcode 1000 is wordt hierop gecontroleerd. if (value < 1000) { throw new Exception(“De laagste postcode is 1000”); }

12 Ook hier geldt weer dat als het commando throw wordt uitgevoerd, de rest van het programma niet wordt uitgevoerd. Er is immers een foutsituatie. Vandaar dat we na de sluit-accolade van het if-statement de volgende regels moeten toevoegen om _Cijfers van de inhoud van value te voorzien:

Ondersteunende functies toevoegen De eigenschappen en variabelen zijn klaar. Nu moeten we nog de controlefunctie isHoofdletter maken. Voeg de volgende regels toe (nieuwe methode):

EP

1

T

_Cijfers = value;

C

// Private functies. private bool isHoofdletter(char letter) { }

N

2 We controleren of het ingevoerde teken een letter is, door de letter te vertalen naar een int. Dit levert de ASCII-waarde op van het teken. De ASCII-code voor hoofdletters loopt van 65 tot 90. We voegen de volgende regels toe tussen de accolades:

C

O

// Omdat de letters omgezet worden naar hoofdletters // moet alleen gecheckt worden of de waarde van die letter // tussen de ASCII-waardes voor hoofdletters liggen. if ((int)letter > 64 && (int)letter < 91) { return true; } else return false;

Hiermee is de functie isHoofdletter gereed voor gebruik. Override ToString() Het is handig dat je een postcode kunt omzetten naar een string. Standaard kent elke struct de ToString()-methode. Alleen levert deze standaard de naam van het object. Ook moet de gebruiker de keuze krijgen om wel of geen spatie tussen de cijfers en de letters op te nemen.

1

We maken eerst de functie waarin de parameter is opgenomen die aangeeft of er wel of geen spatie gebruikt moet worden. Voeg de volgende methode toe:


Hoofdstuk 1 Interne gegevens

229

public string ToString(bool spatieVerwijderen) { }

2 Nu voegen we de regels toe die ervoor zorgen dat er al dan niet een spatie tussen de cijfers en letters wordt weergegeven: // De ToString()-methode waarbij aangegeven kan worden // of er al dan niet een spatie moet worden toegevoegd. if (spatieVerwijderen == false) { return Cijfers.ToString() + “ “+ Letters; } return Cijfers.ToString() + Letters;

EP

T

3 De ToString(bool spatieVerwijderen)-methode met een parameter is klaar. Deze wordt nu gebruikt om de standaardmethode te vervangen. Dit doen we door een methode ToString() aan te maken, voorafgegaan door het keyword override. Maak een nieuwe methode ToString() met de volgende inhoud:

C

public override string ToString() { // Vervangen van de standaard ToString()-methode: // deze geeft een postcode terug met spatie. // Dit is de officiële schrijfwijze. return ToString(false); }

// Statische functies static public Postcode Parse(string postcode) { return new Postcode(postcode); }

C

1

O

N

Static Parse( )-functie Een Postcode kan nu omgezet worden naar een string. Nu moet er ook voor gezorgd worden dat een tekst omgezet wordt naar een Postcode. Hiervoor gebruiken we standaard een static functie met de naam Parse(). Voeg de volgende methode toe:

Impliciete conversie-operator De struct Postcode is bijna klaar. Een struct kun je gebruiken zonder het keyword new(). Ook is het makkelijk als je de volgende code kunt uitvoeren: txbPostcode.Text = postcode; txtPostcode is van het type string en postcode is van het type Postcode. We kun-

nen dit voor elkaar krijgen door de postcode met ToString() om te zetten naar een string. We kunnen echter ook een zogenaamde impliciete conversie toepassen. Dit betekent dat je de bovenstaande regel wel kunt toepassen zonder conversies zoals ToString().


230

Deel 2: Gegevens

Deze handige impliciete conversie voegen we toe met de volgende methode: static public implicit operator string(Postcode postcode) { return postcode.ToString(); }

1

Constructor toevoegen Voeg tenslotte de volgende regels aan je programma toe:

EP

T

// Constructor public Postcode(string postcode) { // Initialisatie, dit MOET gebeuren bij een struct // in de constructor. // Bij de standaard (lege) constructor worden alle // variabelen leeg aangemaakt. _Letters = “”; _Cijfers = 0;

C

O

N

C

// Controles op de ingevoerde waarde if (postcode.Length == 6 || postcode.Length == 7) { try { Letters = postcode.Substring(postcode.Length - 2); Cijfers = short.Parse(postcode.Substring(0, 4)); } catch (Exception exception) { throw exception; } } else if (postcode.Length > 0) { throw new Exception(“Een postcode moet 6, of 7 (met spatie) tekens lang zijn.”); }

}

Toepassen struct in formulier De struct is klaar. We kunnen deze nu eenvoudig toepassen binnen het formulier.

1

Ga naar het code-venster van frmStructPostcode.

2 Zoek de volgende regels op (boven in de class) public partial class frmStructPostcode: Form {

3 Declareer daar een variabele van het type Postcode en met de naam postcode: Postcode postcode;

4 Ga naar het design-venster.


Hoofdstuk 1 Interne gegevens

231

5 Voeg een errorprovider toe aan het formulier. 6 Dubbelklik op de TextBox met de naam txbPostcode. 7 Voeg een try-catch-blok toe aan het TextChanged event. Hiermee vangen we door de struct gegenereerde fouten af. 8 Voeg binnen het try-blok de volgende regels toe: // Omzetten van de inhoud van het tekstvak naar de variabele postcode: postcode = Postcode.Parse(txbPostcode.Text);

// Het leegmaken van foutmeldingen: errorProvider1.SetError(txbPostcode, “”);

T

// Terugzetten van de postcode naar het tekstvak // zodat die de juiste lay-out krijgt: txbPostcode.Text = postcode;

EP

9 Vang binnen het catch-blok de gegenereerde fouten af door daar de volgende regel toe te voegen: errorProvider1.SetError(txbPostcode, exception.Message);

10 Test je programma!

C

Opdracht Struct 1: 3D-punt

O

N

Maak een struct voor een 3D-punt. Een punt in de tweedimensionele ruimte kent een X- en een Y-coördinaat. Een punt in de driedimensionele ruimte heeft naast de X- en de Y-coördinaat een Z-coördinaat die de diepte aangeeft. De ToString()methode van een 3D-punt (0,1,2) geeft het volgende resultaat (X=0,Y=1, Z=2).

Opdracht Struct 2: Telefoonnummer

C

Maak een struct voor een telefoonnummer. Een telefoonnummer bestaat uit vier gedeelten:

1

De internationale toegangscode. In Nederland is dit 00, in de Verenigde Staten is dit 011. Je kunt het ook vervangen door een + teken. Het grootste nummer voor internationale toegang is 99588 (Finland, Globetel). 2 Het landnummer. 31 is Nederland, 32 is België en 42 is Duitsland. De hoogste nummers zijn 6723 (Norfolk) en 8817 (Iridium satelliettelefoon). 3 Het netnummer (kengetal). Dit begint met een 0 en duidt de stad of de regio aan. 020 is Amsterdam, 0546 is Almelo, enzovoort. Een netnummer in Nederland bestaat uit drie of vier cijfers. Na het landnummer vervalt de 0 van het netnummer. 4 Het abonneenummer. Dit is zes of zeven cijfers lang en begint nooit met een 0. Het netnummer en abonneenummer samen zijn in Nederland altijd tien posities.


232

Deel 2: Gegevens

Zorg ervoor dat je een telefoonnummer kunt omzetten naar een string. Hierbij moet je ervoor kunnen kiezen om het landnummer wel of niet te laten zien. Standaard staat het landnummer uit, maar als het landnummer áán staat, moet de 0 van het netnummer vervallen.

Opdracht Struct 3: Geografische coördinaten

T

Geografische coördinaten geven aan waar je je bevindt op de wereld. Je telefoon gebruikt ze om je positie te bepalen, je TomTom om uit te rekenen hoe ver je nog moet rijden en zo zijn er nog vele toepassingen van geografische coördinaten (ook wel Way Points genoemd). In plaats van een X- en Y-as zijn er twee andere assen: de evenaar en de nulmeridiaan. De nulmeridiaan is een lijn die via Greenwich, de Noordpool en de Zuidpool rond de aarde loopt. Geografische coördinaten bestaan uit de volgende elementen:

Eerst kijk je of een punt boven of onder de evenaar ligt: N(orth) of S(outh) dus.

EP

1

Vervolgens wordt de positie aangegeven in graden en minuten (een graad bestaat uit 60 minuten) en met drie decimalen achter de komma.

C

2 Daarna kijk je of het punt ten oosten, E(ast), of ten westen, W(est), van de nulmeridiaan ligt en geef je ook hier de positie aan in graden en minuten.

Dit zijn dus twee coördinaten: North 52 graden 19,947 minuten East 6 graden en 44,071 minuten

O

• •

N

Graden geef je aan met het gradenteken: ° (Alt+248) en minuten geef je aan door achter het cijfer een ’ te plaatsen. Een positie kan er dan als volgt uitzien: N 52°19’.947 E 6°44’.071

C

Maak een struct met de naam WayPoint die de hiervoor beschreven elementen bevat. De ToString()-methode moet een volledig coördinatenpaar weergeven zoals hierboven.

Opdracht Struct 4: Geografische coördinaten aanvulling * Breid Opdracht Struct 3 uit met een Parse()-functie waarbij je een string van het format N 52°19’.947 E 6°44’.071 omzet naar de struct WayPoint. Uiteraard met invoercontrole en foutmeldingen. Tip! Maak gebruik van enums.

* Opdrachten met een * zijn bedoeld als extra opdrachten (moeilijker of ter verdieping van de leerstof ). Ze zijn niet essentieel voor je leerproces.


Hoofdstuk 1 Interne gegevens

233

Wat heb je nu geleerd? Het werken met Structs voor eenvoudige datatypes Het maken van impliciete operators Het gebruik van override ToString() Het gebruik van static Parse

C

O

N

C

EP

T

• • • •


234

Hoofdstuk 2 Bestanden (externe gegevens)

Interne gegevens zijn gegevens die in het interne geheugen van de computer bewaard worden. Alleen als je de computer uitzet, wordt het interne geheugen leeggemaakt: geheugen leeg, gegevens weg. En dat willen we niet. We willen sommige gegevens permanent opslaan. En dat doen we door de gegevens weg te schrijven in bestanden.

EP

T

Voor verschillende soorten gegevens kennen we verschillende bestandsformaten. Deze bestanden zijn vaak herkenbaar aan de extensie (de laatste drie letters) van een bestandsnaam.

C

Let op! De extensie is niets anders dan een naam: je kunt .jpg zomaar veranderen in .exe of .txt. De structuur van het bestand wijzigt dan niet. Je brengt dan alleen het systeem in de war.

C

O

N

Escape sequences Escape sequences zijn speciale codes die ervoor zorgen dat je programmeertaal en gewone taal niet in de war raken. Soms wil je bijvoorbeeld een teken gebruiken, maar niet in de betekenis van C. Je kunt die betekenis overrulen met een escape sequence. Er zijn twee manieren om escape sequences te gebruiken: 1 regular (normaal) met slashes en symbolen: \r \n \\ enzovoort. 2 verbatim (letterlijk): door een @ voor een string te zetten krijg je de letterlijke tekst.

Tekstbestanden Een tekstbestand is de eenvoudigste vorm van een bestand. De extensie van een tekstbestand is .txt en je kunt er variabelen van het type string in opslaan. Er zijn in C drie methoden om teksten weg te schrijven:

1

System.IO.File.WriteAllText schrijft de hele waarde van een string-variabele naar

een bestand. 2

System.IO.File.WriteAllLines schrijft alle elementen van een string-Array naar een

3

System.IO.StreamWriter, hiermee schrijf je regel voor regel de waarde van een

bestand. string Array weg. Je kunt dus ook besluiten om iets niet of anders weg te schrijven.


Hoofdstuk 2 Bestanden (externe gegevens)

235

Het commando System.IO.File.WriteAllText(@”C:\Users\Public\WriteText.txt”, “Deze tekst schrijven we in een bestand”)

doet de volgende zaken: 1 Het schrijft de tekst Deze tekst schrijven we in een bestand 2 naar een bestand met de naam WriteText.txt 3 in de map c:\users\public In plaats van de bestandsnaam en de map hard te coderen (letterlijk in je code op te nemen) kun je deze beter variabel maken. Je krijgt dan de volgende code: string bestandsnaam = “WriteText.txt”; string pad = @”C:\Users\Public\”;

T

string tekst = “Deze tekst schrijven we in een bestand”;

EP

System.IO.File.WriteAllText(pad + bestandsnaam, tekst);

En die variabelen kun je weer laten invullen door een gebruiker.

C

Maar hoe weet je nu of een pad al bestaat en waar op je computer je het nieuwe bestand wilt opslaan? Gebruik de control FolderBrowseDialog. Deze werkt net zoals de ColorDialog uit deel 1, alleen kun je hier een map selecteren door te browsen.

Voorbeeld Tekstbestanden 1: String-variabele opslaan

Plaats de volgende controls op het formulier en pas de genoemde eigenschappen aan:

C

1

O

N

We maken een nieuw project met de naam TekstBestand. De bedoeling is dat je een programma maakt waarbij de gebruiker de plek en de naam kan aangeven van het bestand dat ingelezen dan wel weggeschreven moet worden. Uiteraard kun je ook de inhoud van het bestand wijzigen.

Type

Naam

Eigenschap

Waarde

Button

btnPad

Text

Pad:

TextBox

txbPad

Text

C:\Users\Public

Button

btnBestandsNaam

Text

Naam:

TextBox

txbBestandsnaam

Text

Tekstfile.txt

Text

Inhoud

MultiLine

True

Label TextBox

txbInhoud

Height

150

Button

btnOpslaan

Text

Opslaan

Button

btnInlezen

Text

Inlezen

Enabled

False

Tabel 5: Tekstbestand


236

Deel 2: Gegevens

De Button btnPad plaats je links voor de TextBox met de naam txbPad. De Button btnBestandsNaam plaats je links voor de TextBox met de naam txbBestandsNaam. 2 Sleep de FolderBrowseDialog vanuit de Toolbox op het formulier. Laat de control op een willekeurige plek los en er verschijnt een object met de naam FolderBrowseDialog1 onderaan je formulier. 3 Klik met je rechtermuisknop op het object FolderBrowseDialog1 en ga naar het properties (eigenschappen) venster en stel de volgende eigenschappen in: • Description: Geef de plek aan waar je het bestand wilt opslaan. (Hier geef je aan welke tekst er bij de FolderDialog getoond wordt aan de gebruiker.) • RootFolder: MyDocuments (Hier geef je aan vanaf welk punt je gaat zoeken in de tree van folders. Wij willen de gebruiker laten zoeken vanaf mijnDocumenten.)

EP

T

4 Sleep de OpenFileDialog vanuit de Toolbox op het formulier. Laat de control op een willekeurige plek los en er verschijnt een object met de naam OpenFileDialog1 onderaan je formulier. 5 Dubbelklik op het formulier zodat je in het Load event-venster terechtkomt. Hier voeg je de volgende regels toe:

C

// Deze instructie zet het standaardpad op MijnDocumenten. // Omdat dit op iedere pc anders kan zijn kun je dit beter niet hard coderen. txbPad.Text = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); GetFolderPath zet de naam van de folder MyDocuments om naar het volledige pad.

N

6 We voegen nu de code toe om het juiste pad te selecteren. Dubbelklik op de Button btnPad en voeg de volgende code toe:

C

O

// Alleen als er op OK gedrukt wordt in het FolderDialog venster // wordt de textbox txbPad gevuld met het gekozen pad. DialogResult = folderBrowserDialog1.ShowDialog(); if (DialogResult==DialogResult.OK) { txbPad.Text = folderBrowserDialog1.SelectedPath; }

7 Ga naar het code-venster en voeg bovenaan je programma aan het using-blok de volgende regel toe, omdat we gebruikmaken van de bibliotheek System.IO: using System.IO;

(IO staat voor In- en Output, dus in- en uitvoer van gegevens.) 8 Bij de Button btnInlezen staat de property Enabled standaard op false. Dit is gedaan omdat je alleen iets kunt inlezen als er aan twee voorwaarden is voldaan: • Er is een bestandsnaam ingevuld in de TextBox txbBestandsnaam. • Het bestand bestaat in het aangegeven pad.


Hoofdstuk 2 Bestanden (externe gegevens)

237

We maken twee functies: één om een bestand te kiezen en één om te controleren of een bestand wel bestaat. Ga naar het code-venster en maak de volgende functie aan:

C

EP

T

private void kiesBestandsNaam() { // Er wordt een ‘open file browser’ geopend zodat de gebruiker // een bestand kan kiezen. // Als de gebruiker akkoord is met het gekozen bestand wordt // de naam van het bestand in de textbox getoond. openFileDialog1.FileName = txbBestandsnaam.Text; DialogResult = openFileDialog1.ShowDialog(); if (DialogResult == DialogResult.OK) { // FileName geeft de bestandsnaam plus het volledige pad. // SafeFileName geeft alleen de bestandsnaam. txbBestandsnaam.Text = openFileDialog1.SafeFileName; txbPad.Text = openFileDialog1.InitialDirectory; btnInlezen.Enabled = true; } else { // Als de gebruiker de naam wil handhaven wil hij kennelijk // een nieuw bestand wegschrijven. Nu mag je de knop Inlezen niet // op Enabled zetten. Immers het bestand bestaat nog niet! // We zetten Enabled voor de zekerheid op false. btnInlezen.Enabled = false; } }

N

9 Maak nu de functie aan die controleert (valideert) of de ingevoerde bestandsnaam bestaat:

C

O

private void controleerBestandsNaam() { // Eerst controleren of de bestandsnaam bestaat in de gegeven directory. if (File.Exists(txbPad.Text + @”\” + txbBestandsnaam.Text)) { // Niets aan de hand: het bestand bestaat en de inleesknop kan aangezet // worden. btnInlezen.Enabled = true; } else { // Er bestaat geen bestand met de gegeven naam, er wordt een open file // browser geopend zodat de gebruiker een bestand kan kiezen. kiesBestandsNaam(); } }

10 Nu gebruiken we het Validating event van de TextBox txbBestandsnaam om te controleren of het bestand bestaat. Dit event treedt in werking (fires) voordat de control de focus verliest.


238

• • • •

Deel 2: Gegevens

Ga naar het properties-venster van txbBestandsnaam. Klik op het bliksemschichtje. Dubbelklik op Validating. Voeg de volgende code toe: controleerBestandsNaam();

11 Ook richten we de knop btnBestandsNaam zo in dat als je op die knop klikt er een OpenFileDialog geopend wordt. Dubbelklik op de Button btnBestandsNaam en voeg de volgende code toe: kiesBestandsNaam();

12 Nu schrijven we de inhoud van de TextBox txbInhoud weg naar een bestand.

EP

// Alleen voor overzicht. string bestandsnaam = txbBestandsnaam.Text; string pad = txbPad.Text; string tekst = txbInhoud.Text;

T

Dubbelklik op de Button met de naam btnOpslaan en voeg de volgende code toe:

// Het schrijven van het bestand. File.WriteAllText(pad + @”\” + bestandsnaam, tekst);

C

13 En tot slot laten we de inhoud van een bestand zien in de TextBox txbInhoud: Dubbelklik op de Button btnInlezen en voeg de volgende code toe:

N

// Lees het bestand als één string. txbInhoud.Text = File.ReadAllText(txbPad.Text + @”\” + txbBestandsnaam.Text);

O

14 Probeer je programma uit.

Opdracht Tekstbestanden 1: StringArray wegschrijven

C

Pas het programma van het voorbeeld als volgt aan: •

Voeg een string Array toe als globale variabele met de volgende inhoud:

Wintermaand Sprokkelmaand Lentemaand Grasmaand Bloeimaand Zomermaand Hooimaand Oogstmaand


Hoofdstuk 2 Bestanden (externe gegevens)

239

Herfstmaand Wijnmaand Slachtmaand Sneeuwmaand Tabel 6: Oud-Hollandse maanden

• •

Voeg een Button toe die de inhoud van de Array in zijn geheel wegschrijft naar een bestand met de naam maanden.txt. Voeg een Button toe die de inhoud van het bestand laat zien in de TextBox txbInhoud.

T

Hint: Gebruik een loop om door de Array te lopen en gebruik een regeleinde ‘\r’ om iedere maand op een volgende regel te plaatsen.

EP

Voorbeeld Tekstbestanden 2: Gedeelte van een string Array opslaan

C

Maar wat als je nu alleen regel 1, 2 en 3 wilt wegschrijven? Of je wilt de gebruiker laten kiezen? Dan kan dat niet met de commando’s System.IO.File.WriteAllText en System.IO.File.WriteAllLines. Dan moet je gebruikmaken van System.IO.StreamWriter.

1

N

We breiden Voorbeeld Tekstbestanden 1 uit met een aantal invoervelden en maken gebruik van de maanden Array die je net hebt aangemaakt. Voeg de volgende controls toe aan het formulier:

O

Naam

Button

btnMaanden

Label

nudVanaf

C

NumericUpDown

Label

NumericUpDown

nudTotEnMet

Eigenschap

Waarde

Text

Maanden:

Text

Vanaf:

Value

1

Minimum

1

Maximum

12

Text

Tot en met:

Value

12

Minimum

1

Maximum

12

Tabel 7: Tekstbestand, uitbreiding

2 Voeg vervolgens in het code-venster de volgende globale variabelen toe: int vanaf = 1; int totEnMet = 12;


240

Deel 2: Gegevens

3 Dubbelklik in het ontwerpvenster op de Numericupdown-control nudVanaf en voeg de volgende code toe: // Hier zet je de ingevoerde schermwaarde over naar de globale variabele. vanaf = (int)nudVanaf.Value;

4 Dubbelklik in het ontwerpvenster op de Numericupdown-control nudTotEnMet en voeg de volgende code toe: // Hier zet je de ingevoerde schermwaarde over naar de globale variabele. vanaf = (int)nudTotEnMet.Value;

5 Maak een nieuwe functie aan met de naam schrijfGeselecteerdeMaanden. Deze functie kent twee variabelen van het type int met de namen vanaf en totEnMet. Voeg de volgende code toe:

C

EP

T

private void schrijfGeselecteerdeMaanden(int vanaf, int totEnMet) { System.IO.StreamWriter file = new System.IO.StreamWriter(txbPad.Text +@”\”+ “maanden.txt”); // De Array kent 12 elementen, voor iedere maand 1. // Echter de Array begint bij 0 en eindigt bij 11! // Vandaar dat de variabele vanaf zo wordt aangepast // dat de gebruiker krijgt wat hij verwacht. Als hij 1 invoert // moet de Array beginnen bij 0. vanaf = vanaf -1;

N

// Nu gaan we de geselecteerde maanden wegschrijven. for (int i = vanaf; i < totEnMet; i++) { file.WriteLine(maanden[i]); }

O

// Je moet het bestand sluiten anders blijft het exclusief geopend voor // dit proces. Wil je dan nog iets schrijven dan krijg je een foutmelding. file.Close();

C

// En tot slot laat je de gebruiker even weten dat het klaar is. MessageBox.Show(“Maanden weggeschreven naar maanden.txt.”);

}

6 Vervolgens ga je weer naar het ontwerpvenster en dubbelklik je op de Button btnMaanden. Dan voeg je de volgende code toe die de functie schrijfGeselecteerdeMaanden aanroept: schrijfGeselecteerdeMaanden(vanaf, totEnMet);

7 Probeer je programma uit.

Opdracht Tekstbestanden 2: String Array meerdere selecties Pas het programma van Voorbeeld Tekstbestanden 2 zo aan dat je de volgende selecties kunt maken:


Hoofdstuk 2 Bestanden (externe gegevens)

• • •

241

Alleen de donkere maanden (maand 1 t/m 3 en 10 t/m 12) De vruchtbare maanden (maand 4 t/m 9) De seizoenen (winter, lente, zomer, herfst) In het bestand dat je wegschrijft komt als eerste regel de naam van de selectie te staan. Dus als de selectie Donkere maanden is, dan wordt de inhoud van het bestand maanden.txt:

T

Donkere maanden Wintermaand Sprokkelmaand Lentemaand Wijnmaand Slachtmaand Sneeuwmaand

O

• • •

C

•

Pas het programma van Opdracht Tekstbestanden 2 zo aan dat: De gebruiker de achtergrondkleur en de voorgrondkleur van het formulier kan wijzigen. Er een knop komt waarmee de gekozen kleuren in een tekstbestand worden opgeslagen met als naam instellingen.ini. De achtergrondkleur op regel 1 staat. De voorgrondkleur op regel 2 staat. Als het bestand instellingen.ini bestaat, dit bestand wordt ingelezen en de kleuren van het formulier worden aangepast. Je moet hiervoor code toevoegen aan het Form Load event.

N

•

EP

Opdracht Tekstbestanden 3: Bewaren gebruikersinstellingen *

Hint: Je kunt gebruiken:

System.Drawing.Color.Name(“Red”)

C

System.Drawing.Color.FromArgb(250, 250, 250)

Let op! Vang alle fouten af.

Wat heb je nu geleerd? • • • • • • • •

Tekstbestanden inlezen Tekstbestanden wegschrijven Arrays wegschrijven naar tekstbestanden Selectief Arrays wegschrijven naar tekstbestanden Gebruikersinstellingen bewaren in tekstbestanden Gebruik van de FolderDialog Gebruik van de OpenFileDialog Gebruik van escape sequences zoals \ en @


242

Deel 2: Gegevens

.csv-bestanden (Comma Separated Values) Een .csv-bestand (in het Nederlands: komma-gescheiden bestand) is het eenvoudigste en oudste databaseformat dat er bestaat. Het bestaat alleen uit tekstgegevens, waardoor je het eenvoudig kunt lezen en schrijven. De opbouw is eenvoudig. Op iedere regel staan een aantal waardes die bij elkaar horen, waarbij de waardes gescheiden worden door komma’s. Soms worden er andere scheidingstekens gebruikt zoals puntkomma’s. Dit kan voor vervelende problemen zorgen.

Bouwjaar

Merk

Type

Bijzonderheden

1996

Volkswagen

Golf IV

Elektrische kap

2004

Volvo

2017

Jaguar

2001

Peugeot

EP

T

.csv-bestanden kunnen door vrijwel elk programma gemaakt dan wel ingelezen worden.

V60

Elektrisch

105

800.00

3500.00

135000.00 500.50

C

Tabel 8: Overzicht auto’s

S type

Prijs

Deze tabel ziet er als .csv-bestand als volgt uit:

O

N

Bouwjaar,Merk,Type,Bijzonderheden,Prijs 1996,Volkswagen,Golf IV,Elektrische kap,800.00 2004,Volvo,V60,,3500.00 2017,Jaguar,S type,Elektrisch,135000.00 2001,Peugeot,105,,500.50

C

Let op! Als een waarde leeg is wordt er toch een komma neergezet. Kijk na Volvo V60 en na de 105 bij Peugeot. Omdat het een normaal tekstbestand is kunnen we voor een .csv-bestand gewoon alles toepassen wat we geleerd hebben over het schrijven en lezen van tekstbestanden. Alleen zijn er wat handige trucs die je met strings kunt uithalen waardoor het werken met .csv-bestanden veel makkelijker wordt: Een string in stukjes knippen op basis van een teken doe je met de functie Split(‘,’), waarbij als parameter het teken wordt opgegeven op basis waarvan je wilt splitsen. string commaSeparatedTekst = “1996,Volkswagen,Golf III,Elektrische kap,800.00”; string[] autoRecord = commaSeparatedTekst.Split(‘,’);


Hoofdstuk 2 Bestanden (externe gegevens)

243

Voorbeeld .csv-bestanden 1: .csv-bestand aanmaken vanuit formulier 1

.csv: voorbereiding Maak een nieuw project aan met de naam voorbeeldAutos.

2 Plaats op het formulier de volgende controls: Naam

Eigenschap

Waarde

DataGridView

dgvAutos

Width

520

Button

btnCsv

Text

--> .csv

BackColor

PaleTurquoise

Text

Exit

BackColor

LightCoral

Button

btnExit

Tabel 9: Voorbeeld auto’s

O

N

C

EP

3 Het formulier ziet er nu als volgt uit:

T

Type

Schermafbeelding 21: .csv Autos

C

.csv: Inrichten DataGridView Je hebt nu een DataGridView-control op het scherm geplaatst: een donkere grijze massa. Dit klopt. De DataGridView moet je nog inrichten met kolommen. Heb je dat eenmaal gedaan, dan kun je de DataGridView als een tabel met gegevens gebruiken.

1

Ga naar de properties van de DataGridView dgvAutos.

2 Klik op de link onderin beeld (onder het Properties-venster): Edit Columns. Het volgende scherm verschijnt:


Deel 2: Gegevens

Schermafbeelding 22: .csv Edit columns

T

244

C

O

N

C

EP

3 Klik op de Add... knop. Het volgende scherm verschijnt:

Schermafbeelding 23: .csv: Add column

4 Bij Name voeg je de naam in van de kolom: clmBouwjaar. Dit is de naam van de variabele die je in je programma gaat gebruiken. 5 Bij Header text vul je in: Bouwjaar. Deze tekst komt in de kop (header) van de kolom te staan.


Hoofdstuk 2 Bestanden (externe gegevens)

245

6 Voeg op dezelfde manieren kolommen toe voor: Kolomnaam

Kolomkop (Header)

clmMerk

Merk

clmType

Type

clmBijzonderheden

Bijzonderheden

clmPrijs

Prijs

Tabel 10: DataGridView toe te voegen kolommen

Al deze kolommen zijn van het type DataGridViewTextBoxColumn. Dit geeft aan dat de vakjes van de kolom zich zullen gedragen als een TextBox. Je kunt ze ook instellen als ComboBox, Button, Image, enzovoort.

O

N

C

EP

T

7 Klik op Close en het volgende scherm verschijnt:

C

Schermafbeelding 24: .csv Edit Columns gevuld

8 Nu moeten we nog de breedte van de kolommen instellen. Dit doe je door een kolom aan te klikken en in het rechterscherm naar beneden te scrollen naar de property Width. Stel de kolombreedtes in op de volgende waardes: Kolom

Width

Bouwjaar

60

Merk

70

Type

110

Bijzonderheden

170

Prijs

65

Tabel 11: DataGridView Column properties


246

Deel 2: Gegevens

EP

Schermafbeelding 25: .csv werkend programma 1

T

9 Je hebt nu je eerste programma gemaakt met een DataGridView. Als je op run klikt dan heb je een programma waarbij je in de tabel de waardes kunt invoeren zoals je die zometeen in het .csv-bestand gaat opslaan. Als je de eerste twee regels invoert ziet het programma er als volgt uit:

DataGridView omzetten naar .csv Nu maken we de methodes om de inhoud van de DataGridView om te zetten naar een List en die kunnen we vervolgens wegschrijven als een .csv-bestand.

• 1

C

N

•

We maken de volgende methodes: maakHeader. Deze methode zorgt ervoor dat de eerste regel van het .csv-bestand bestaat uit de veldnamen. maakRijen. Deze methode stelt de rijen samen op basis van de celwaardes van de DataGridView. schrijfBestand. Deze methode schrijft het resultaat van maakHeader() en maakRijen() weg naar een tekstbestand met de extensie .csv in de map Mijn Documenten.

O

•

Voeg aan het using-gedeelte de regel toe:

C

using System.IO;

2 Maak een methode aan met de naam maakHeader() en vul deze met de volgende code: private string maakHeader() { // In de eerste regel van een comma separated values (.csv)-bestand kun je // de veldnamen opslaan: string header = “”; // Columns is een Array die hoort bij een DataGridView. // Hierin wordt alle informatie over de kolommen opgeslagen. foreach (DataGridViewColumn kolom in dgvAutos.Columns) { header = header + kolom.HeaderText + “,”; }


Hoofdstuk 2 Bestanden (externe gegevens)

247

// Omdat er altijd een komma achter elke waarde wordt geplakt moet // de allerlaatste komma verwijderd worden. header = header.Substring(0,header.Length-1); // Teruggeven van de headerinformatie inclusief aan het eind // de code voor een nieuwe regel. return header + “\r\n”; }

3 Maak een methode aan met de naam maakRijen() en vul deze met de volgende code: private string maakRijen() { // Alle rijen die opgebouwd worden bij elkaar voegen. string rijen = “”;

T

// Opbouwen rijen met waardes uit de cellen van de DataGridview. string rij = “”;

EP

// Rows is een Array die hoort bij een DataGridView. // Hierin wordt alle informatie opgeslagen over de rijen. foreach (DataGridViewRow row in dgvAutos.Rows) { // Elke nieuwe rij begint leeg. rij = “”;

O

N

C

// In de Array Cells die hoort bij iedere row wordt // de informatie bijgehouden over de cellen. foreach (DataGridViewCell cell in row.Cells) { // Toevoegen van de waarde van de cel aan de rij // die we toevoegen aan de csvString. rij = rij + cell.Value + “,”; }

// Ook hier wordt de allerlaatste komma van de rij verwijderd. rij = rij.Substring(0, rij.Length - 1);

C

// Als er alleen voor iedere cel een komma is opgenomen // is de rij leeg en gaan we hem niet wegschrijven. // Er is tenminste één komma verwijderd dus zo kunnen // we checken of het een lege rij is of niet. if (row.Cells.Count < rij.Length-1) { // Toevoegen van een nieuwe regel code achter de rij. rijen = rijen + rij + “\r\n”; }

} return rijen; }


248

Deel 2: Gegevens

4 Maak als volgt een methode aan met de naam schrijfBestand: private void schrijfBestand(string inhoud) { try { // Aanmaken van het pad, we schrijven naar de MyDocuments map en // voegen een slash toe zodat de documentnaam toegevoegd kan worden. string pad = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); pad = pad + @”\”; // Aanmaken van de documentnaam. Omdat het een tekstbestand is van het // type comma separated value geven we het de algemeen bekende extensie // mee: .csv string documentNaam = “csvautos.csv”;

EP

T

// Wegschrijven van het bestand. // Standaard wordt een bestaand bestand zonder waarschuwing overschreven. File.WriteAllText(pad + documentNaam, inhoud); MessageBox.Show(“Het bestand: “ + documentNaam + “\r\n” + “is opgeslagen in de map: “ + pad, “csv Voorbeeld”, MessageBoxButtons.OK)

}

N

C

} catch (Exception exception) { MessageBox.Show(exception.Message, “Foutmelding csv Voorbeeld”, MessageBoxButtons.OK, MessageBoxIcon.Stop); }

O

5 Dubbelklik op de Button btnCsv en voeg de volgende code toe aan het Click event:

C

// In plaats van eerst een string-variabele aan te maken // en dan met maakHeader en maakRijen te vullen, // om vervolgens schrijfBestand aan te roepen met deze variabele, // kan het ook op de volgende manier: schrijfBestand(maakHeader() + maakRijen());

6 Dubbelklik op de Button btnExit en voeg de volgende code toe aan het Click event: this.Close();

7 Test je programma.

Opdracht .csv-bestanden 1: Flexibele opslag • • • •

Pas het Voorbeeld .csv-bestanden zo aan dat de gebruiker: Zelf de map kan aangeven waar hij het bestand wil opslaan. Zelf een naam aan het bestand kan geven, maar dat de extensie altijd .csv blijft. Kan kiezen of de eerste regel wel of geen headerinformatie moet bevatten. Een waarschuwing krijgt als het bestand al bestaat. Maak gebruik van File.Exists.


Hoofdstuk 2 Bestanden (externe gegevens)

249

Opdracht .csv-bestanden 2: Klassenlijst • • • • • • •

Maak een programma met een DataGridView waarin de volgende informatie betreffende de leerlingen in een schoolklas kan worden weergegeven: Voornaam Achternaam E-mailadres Telefoonnummer Klascode Mentor Klassenvertegenwoordiger

T

Zorg ervoor dat de ingevoerde informatie opgeslagen kan worden op een plek naar keuze met de vaste bestandsnaam klas + klascode + .csv. Het bestand van klas 23A wordt dan klas23a.csv.

EP

Voorbeeld .csv-bestanden 2: .csv-bestand inlezen

Je hebt nu bestanden weggeschreven. Maar het zou fijn zijn als je niet altijd alle gegevens weer opnieuw moet intypen. Met andere woorden: het is handig als de weggeschreven .csv-bestanden ook weer ingelezen kunnen worden.

C

.csv: inlezen We breiden het programma voorbeeldAutos uit met een inleesknop met de naam btnInlezen en de tekst Inlezen.

N

1

2 Dubbelklik op de Button en voeg de volgende code toe:

C

O

private void btnInlezen_Click(object sender, EventArgs e) { string pad = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments); pad = pad + @”\”; string documentNaam = “csvautos.csv”; // Lezen van het bestand. // Standaard wordt er een Array teruggegeven van strings. string[] inhoud = File.ReadAllLines(pad + documentNaam);

// Regel voor regel wegschrijven van de Array naar de DataGridView. // We moeten de eerste regel overslaan omdat daar de headerinformatie in zit. for (int i = 1; i < inhoud.Length; i++) { // Lege regels nemen we niet mee. if (inhoud[i].Length > 0) { // Om te zorgen dat iedere celwaarde in een aparte cel terecht komt // moeten we de rij weer omzetten naar een Array. string[] cellen = inhoud[i].Split(‘,’);


250

Deel 2: Gegevens

// Toevoegen van de rij aan de DataGridView op basis van de Array. dgvAutos.Rows.Add(cellen); } } }

3 Test het programma. Als het goed is kun je zo het eerder aangemaakte .csv-bestand inlezen.

Opdracht .csv-bestanden 3: Klassenlijst inlezen Breid het programma van Opdracht .csv-bestanden 2 uit met een knop waarmee je een dergelijk bestand weer kunt inlezen.

T

Opdracht .csv-bestanden 4: Klassenlijst inlezen Extra *

EP

• • •

Breid het programma van Opdracht .csv-bestanden 3 uit met de volgende controles bij het inlezen: Check of de header overeenkomt met de gebruikte veldnamen in je programma. Check of het aantal velden overeenkomt. Check of de volgorde van de velden overeenkomt.

C

Als er iets niet goed is, zorg dan voor een duidelijke foutboodschap. Voeg de mogelijkheid toe om velden in een andere volgorde in te lezen.

O

Het inlezen van .csv-bestanden Het schrijven van .csv-bestanden Het gebruik van een DataGridView Het met code aanmaken van kolommen in een DataGridView Het met code vullen van een DataGridView Het vullen van een .csv-bestand vanuit een DataGridView Het vullen van een DataGridView vanuit een .csv-bestand

C

• • • • • • •

N

Wat heb je nu geleerd?

Afbeeldingen Soms heb je afbeeldingen nodig om je programma mooier te maken. Dit kun je vaak eenvoudigweg doen door een plaatje in te stellen bij een property van een grafisch object. Het wordt anders als je een programma maakt dat iets met afbeeldingen doet, zoals het samenstellen van een fotoalbum, of het maken van een overzicht van je klasgenoten met hun foto’s erbij.


Hoofdstuk 2 Bestanden (externe gegevens)

251

Afbeeldingen zijn van het type Image. Er zijn vele soorten images: jpeg, ping, tiff, bitmap en nog veel meer. Voor verschillende soorten afbeeldingen zijn er in C aparte instanties (uitvoeringen) beschikbaar. Zo is er een type Bitmap voor bitmapafbeeldingen. Anders dan verwacht werkt de regel Image afbeelding = new Image(); niet. Het type Image moet je anders aanmaken en wel op de volgende manier: Image afbeelding = Image.FromFile(“plaatje.jpg”);

De volgende formats worden door .NET ondersteund: BMP, GIF, JPG, PNG, TIFF.

Voorbeeld Afbeeldingen 1: Inlezen afbeeldingen

EP

T

We maken een nieuw project met de naam Afbeeldingen. De bedoeling is dat je een programma maakt waarbij de gebruiker de plek en de naam kan aangeven van het bestand met de afbeelding die ingelezen dan wel weggeschreven moet worden. Uiteraard kun je ook de inhoud van het bestand wijzigen. Het gebruik van FolderDialogBoxen staat beschreven bij Voorbeeld Tekstbestand 1.

Type PictureBox

C

Voorbeeld Afbeelding en inrichten formulier Plaats de volgende controls op het formulier en pas de genoemde eigenschappen aan: Naam

Eigenschap

Waarde

pbInhoud

BackColor

ControlLight

N

1

O

StretchImage Bijna volledige breedte van de form De volgende knoppen worden rechts op de PictureBox geplaatst: Button btnSpiegelbeeld Text Spiegelbeeld PaleTurquoise BackColor Size 75; 45 Anchor Left, Right RadioButton rdbNormaal Text Normaal Anchor Left, Right Yes RightToLeft RadioButton rdbPlaatjeAanpassen Text Plaatje Aanpassen Anchor Left, Right Yes RightToLeft Checked True

C

SizeMode Width


252

Deel 2: Gegevens

Type

Naam

Panel

pnlKnoppen

Eigenschap

802;64 Bottom Pad: PaleTurquoise

C:\Users\Public Naam: PaleTurquoise

Plaatje.JPG True Opslaan PaleTurquoise

T

Size Anchor De volgende controls worden op het Panel geplaatst: Button btnPad Text BackColor TextBox txbPad Text Button btnBestandsNaam Text BackColor TextBox txbBestandsnaam Text ReadOnly Button btnOpslaan Text BackColor Button btnExit Text BackColor Enabled FolderBrowseDialog folderBrowseDialog1 RootFolder OpenFileDialog openFileDialog1 Filter SaveFileDialog saveFileDialog1 CheckFileExists

Waarde

EP

LightCoral

False MyPictures *.bmp|*.jpg|*.png|*.gif True

C

Tabel 12: Controls afbeelding

Inlezen

N

De Button btnPad plaats je links voor de TextBox met de naam txbPad. De Button btnBestandsNaam plaats je links voor de TextBox met de naam

O

txbBestandsNaam.

C

De eigenschap Filter van de OpenFileDialog zorgt ervoor dat in de DialogBox alleen bestanden met de extensies bmp, jpg, png en gif gezet kunnen worden. Het pipeteken (|) is de scheiding voor de verschillende soorten bestanden. De eigenschap CheckFileExists van de SaveFileDialog zorgt ervoor dat (als een bestand al bestaat) de gebruiker de vraag krijgt of het oorspronkelijke bestand overschreven moet worden. Als het goed is ziet je programma er nu als volgt uit:


253

T

Hoofdstuk 2 Bestanden (externe gegevens)

EP

Schermafbeelding 26: Afbeeldingen

Als je het programma schermvullend maakt, komt het Panel pnlKnoppen midden onder te staan en blijven de controls op de PictureBox pbInhoud rechtsboven staan.

1

N

C

Voorbeeld Afbeeldingen: liquid layout Een liquid layout is een scherm-layout die zich aanpast aan de grootte van het scherm. Als je het programma nu maximaliseert blijft de PictureBox net zo groot en ontstaat veel witruimte. We passen het zo aan dat de PictureBox altijd net iets minder breed is dan het formulier. Maak de volgende globale variabelen aan:

O

int rechterMarge = 0; int onderMarge = 0; Size groottePictureBox;

C

De variabele groottePictureBox gebruiken we om de afmetingen (breedte en hoogte van de PictureBox) in op te slaan. Soms wordt een PictureBox aangepast aan de grootte van de afbeelding en dan is het handig om de oorspronkelijke grootte nog ergens te hebben.

2 De rechtermarge van de PictureBox moet altijd gelijk blijven. Daarom rekenen we de rechtermarge uit. De linkermarge is gelijk aan de X-waarde van de locatie van de PictureBox pbInhoud. Als bij de linkermarge de breedte van de PictureBox pbInhoud wordt opgeteld, hebben we de plek waar de rechtermarge begint. Als we die waarde van de breedte van het formulier aftrekken houden we de breedte van de rechtermarge over. Voeg de volgende regels toe aan het Form Load event (frmAfbeeldingen_Load):


254

Deel 2: Gegevens

groottePictureBox = pbInhoud.Size; pbInhoud.Width = pbInhoud.Width + rdbPlaatjeAanpassen.Width + 20; rechterMarge = this.Width - pbInhoud.Location.X - pbInhoud.Width; onderMarge = pnlKnoppen.Height; pbInhoud.Height = this.Height - pbInhoud.Location.Y - pnlKnoppen.Height onderMarge; this verwijst naar de Form frmAfbeeldingen.

3 Ga naar het design-venster en ga naar de properties van het Form frmAfbeeldingen. 4 Klik op de bliksemschicht om de events-tab te openen. 5 Dubbelklik op resize.

T

Je komt nu in het Resize event van het formulier terecht. Deze code wordt iedere keer uitgevoerd als de gebruiker de afmetingen van het formulier wijzigt.

EP

private void frmAfbeeldingen_Resize(object sender, EventArgs e) { }

6 Voeg de volgende regels toe binnen het Resize event:

C

groottePictureBox.Width = this.Width - pbInhoud.Location.X - rechterMarge; pbInhoud.Width = groottePictureBox.Width;

N

Let op! De variabele groottePictureBox is vooral nuttig bij één van de opdrachten.

Voorbeeld Afbeeldingen: dialogs Standaard staat de map waarin gezocht wordt naar afbeeldingen op MyPictures (Mijn Afbeeldingen). Door op de knop btnPad (pad is een andere aanduiding voor map of directory) te klikken wordt de gebruiker in staat gesteld om een andere map te kiezen. Voeg de volgende regels toe aan het Click event van de knop btnPad:

C

1

O

7 Test het programma. Als je de grootte van het formulier verandert, moet de breedte van de PictureBox meeveranderen.

if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) { txbPad.Text = folderBrowserDialog1.SelectedPath; openFileDialog1.InitialDirectory = folderBrowserDialog1.SelectedPath; }

Als je in de DialogBox op OK klikt, wordt de code uitgevoerd. Anders niet. 2 Met de knop btnBestandsnaam kan de gebruiker de bestandsnaam van de afbeelding selecteren die getoond moet worden in de PictureBox pbInhoud. Alleen als de gebruiker in de DialogBox op OK klikt wordt de code uitgevoerd. Voeg de volgende regels toe aan het Click event van de knop btnBestandsnaam:


Hoofdstuk 2 Bestanden (externe gegevens)

255

if (openFileDialog1.ShowDialog()==DialogResult.OK) { txbBestandsnaam.Text = openFileDialog1.FileName; pbInhoud.Image = Image.FromFile(txbBestandsnaam.Text); }

Deze code is niet 100 crashbestendig. Als er nog geen plaatje is geselecteerd zal het programma crashen. In Opdracht Afbeeldingen 3 wordt je gevraagd te zorgen voor een sluitende foutafhandeling. 3 Ook bij het opslaan van het bestand wordt gebruik gemaakt van een DialogBox. Voeg de volgende code toe aan het Click event van de knop btnOpslaan: saveFileDialog1.ShowDialog();

1

EP

T

Voorbeeld Afbeeldingen: aanpassen image Je kunt allerlei bewerkingen uitvoeren op variabelen van het type Image. We voegen er een aantal toe aan het formulier. Voeg de volgende regels toe aan het Click event van de knop btnSpiegelbeeld: pbInhoud.Image.RotateFlip(RotateFlipType.RotateNoneFlipX); pbInhoud.Refresh();

C

FlipX zorgt ervoor dat alle X-waardes van links naar rechts worden omgewisseld. 2 Voeg de volgende regels toe aan het CheckedChanged event van de RadioButton rdbNormaal:

N

pbInhoud.Size = groottePictureBox; pbInhoud.SizeMode = PictureBoxSizeMode.Normal;

O

De grootte wordt standaard teruggezet naar de oorspronkelijke grootte, omdat soms de PictureBox wordt aangepast aan de grootte van de afbeelding.

C

3 Voeg de volgende regels toe aan het CheckedChanged event van de RadioButton rdbPlaatjeAanpassen: if (rdbPlaatjeAanpassen.Checked) { pbInhoud.Size = groottePictureBox; pbInhoud.SizeMode = PictureBoxSizeMode.StretchImage; }

4 Test het programma. Als je een afbeelding inleest ziet je programma er als volgt uit, uiteraard met de door jou gekozen afbeelding:


Deel 2: Gegevens

EP

T

256

Schermafbeelding 27: Afbeeldingen plus achtergrond

Opdracht Afbeeldingen 1: Liquid layout-form

N

C

Pas het programma van Voorbeeld Afbeeldingen 1 zo aan dat pbInhoud behalve in de breedte ook in de hoogte wordt aangepast als het formulier van grootte verandert. Hou hierbij rekening met de hoogte van het Panel pnlKnoppen onder de PictureBox pbInhoud.

Opdracht Afbeeldingen 2: Instellingen

O

C

• • •

Pas het programma van Opdracht Afbeeldingen 1 zo aan dat er behalve de twee RadioButtons (waarmee je de wijze van inpassen van de afbeelding in het formulier kunt regelen) ook RadioButtons komen voor: Zoom. Aanpassen van de PictureBox aan de afbeelding. Centreren van de afbeelding.

Opdracht Afbeeldingen 3: Crashbestendig maken • • • •

Pas het programma van Opdracht Afbeeldingen 2 zo aan dat het niet meer kan crashen: Voordat een bestand in de PictureBox pbInhoud geladen wordt, moet je controleren of het bestand bestaat. Zo niet, toon een foutmelding. Als een map niet bestaat, moet je een foutmelding tonen. Als je zelf de naam van een map invoert, moet gecontroleerd worden of deze bestaat. Zo niet dan wordt de folderBrowserDialog1 gestart. Als het bestand wel bestaat dan wordt het bestand gekoppeld aan de PictureBox.


Hoofdstuk 2 Bestanden (externe gegevens)

257

Let op! Maak geen gebruik van het TextChanged event. Het is handiger de controles uit te voeren als je de TextBox verlaat. Zoek het juiste event.

Opdracht Afbeeldingen 4: Draaien en flippen

EP

Opdracht Afbeeldingen 5: Kopiëren

T

• • • • • •

Pas het programma van Opdracht Afbeeldingen 3 zo aan dat de gebruiker een plaatje: 90° kan draaien 270° kan draaien 180° kan draaien ondersteboven kan neerzetten ondersteboven en in spiegelbeeld kan neerzetten terug kan zetten naar de oorspronkelijke instellingen

Maak een kopie van het programma van Opdracht Afbeeldingen 4. Pas deze kopie zo aan dat je in plaats van één PictureBox er twee krijgt: één aan de linkerkant van je scherm en één aan de rechterkant.

C

Beide PictureBoxen moeten van grootte veranderen als het formulier van grootte verandert.

N

De bewerkingsknoppen gelden alleen voor de rechter PictureBox. Als je een bestand inleest, moet dat alleen in de linker PictureBox terechtkomen.

C

O

Er moeten twee knoppen komen waarmee je de afbeeldingen kunt klonen (= kopiëren): één om van links naar rechts te kopiëren en één om van rechts naar links te kopiëren. Maak gebruik van Image.Clone(). Zorg ervoor dat je beide afbeeldingen kunt opslaan. De linkerafbeelding heeft altijd dezelfde naam als het originele bestand, een andere naam mag niet. De rechterafbeelding mag nooit dezelfde naam hebben als het origineel. Zorg voor een goede foutafhandeling.

Opdracht Afbeeldingen 6: Afbeeldingen en tekenen • • • •

Maak een programma dat de volgende functies kent: Je moet een afbeelding kunnen opzoeken en inlezen. De geselecteerde afbeelding moet je twee keer op het scherm laten zien. Op één van de plaatjes moet je ook lijnen kunnen trekken (zie ook deel 1, hoofdstuk 10). Uiteraard moet je het gewijzigde plaatje afzonderlijk kunnen opslaan, waarbij je de oorspronkelijke naam niet mag gebruiken.


258

• •

Deel 2: Gegevens

Je moet het zo maken dat, als je het programma opstart, automatisch de laatst gebruikte plaatjes weer getoond worden. Zorg ervoor dat de gebruiker één van de afbeeldingen kan spiegelen, roteren, op zijn kop kan zetten, enzovoort.

Opdracht Afbeeldingen 7: Thumbnails * Breid het programma van Opdracht Afbeeldingen 6 uit met een knop waarbij je alle plaatjes van de gekozen directory als thumbnails laat zien. Deze lijst met thumbnails wordt altijd onderaan het scherm getoond. Maak gebruik van de functie Image.GetThumbnailImage() en een loop.

EP

Het inlezen en gebruiken van afbeeldingen Roteren van een afbeelding Het spiegelen van een afbeelding (flip image) Het maken van een liquid layout Gebruik van thumbnails Gebruik van RadioButtons

C

• • • • • •

T

Wat heb je nu geleerd?

Video

C

O

N

Eerst spraken we en tekenden we wat in grotten, toen kwam het schrift, de boeken, eerst zonder en later met afbeeldingen, om uiteindelijk terecht te komen bij beeld zonder en later met geluid. Iets dergelijks heeft zich ook afgespeeld binnen de IT. Je hebt nu geleerd teksten en afbeeldingen in te lezen en op te slaan. Nu is het tijd voor beeld met geluid.

Voorbeeld Video Video: voorbereiding Eerst moeten we ervoor zorgen dat we de beschikking hebben over de Windows Mediaplayer in Visual Studio.

1

Maak een nieuw project met de naam VoorbeeldVideo.

2 Open de Toolbox. 3 Als de Windows Media Player niet voorkomt in het Components-gedeelte van de Toolbox dan moeten we de Windows Media Player toevoegen. 4 Klik met je rechtermuisknop in de Toolbox. 5 Klik op Choose Items.


Hoofdstuk 2 Bestanden (externe gegevens)

259

EP

T

De Customize Toolbox DialogBox verschijnt:

C

Schermafbeelding 28: Video COM components

6 Ga naar het tabblad COM Components.

N

7 Selecteer WindowsMediaPlayer.

Als WindowsMediaPlayer niet in de lijst voorkomt, klik dan op Browse, ga naar de folder Windows\Systems32 en open wmp.dll.

O

8 Klik op OK.

Nu kun je de Windows Media Player-control gebruiken vanuit de Toolbox.

C

9 Zet de volgende controls op het formulier: Type

Naam

Eigenschap

Waarde

Menustrip

mspMenu

Text

Menu

Plek op de pagina

Invoegen boven aan de pagina

Panel

pnlVideo

BackColor

ControlLight

Dock

Fill

VScrollBar

vsbVolume

Tabel 13: Components video

Plek op de pagina

Direct onder de menubalk

Minimum

1

Maximum

90

Plek op de pagina

Uiterst rechts over de hele hoogte


260

1

Deel 2: Gegevens

Video: instellen menu Klik in de menubalk Het volgende vakje verschijnt:

Schermafbeelding 29: Type Here

2 Typ in dit vakje File. Het volgende verschijnt:

T

Schermafbeelding 30: Type Here 2

EP

In het rechtervakje kun je een menu-item toevoegen. In het onderste vakje kun je een submenu-item toevoegen.

3 Voeg als menu-items naast File de volgende twee items toe: • Start • Stop

C

4 Voeg als submenu-items onder File de volgende twee items toe: • Open • Einde

C

O

N

Het scherm ziet er nu als volgt uit:

Schermafbeelding 31: Video


Hoofdstuk 2 Bestanden (externe gegevens)

261

Video: instellen van de OpenFileDialog met code We maken nu een OpenFileDialog aan met code: 1

Ga naar het code-venster en voeg de volgende globale variabele toe: string videoBestand = “”;

2 Voeg vervolgens de globale variabele toe voor de OpenFileDialog: OpenFileDialog openFileDialog = new OpenFileDialog();

3 Dubbelklik in het design-venster op het formulier (niet op het Panel of het ToolStripMenu!). Je komt nu in het Load event van het formulier:

T

private void frmVideo_Load(object sender, EventArgs e) { }

C

EP

4 Hier stellen we het filter in van de openFileDialog, zodat de openFiledialog meteen bij de start van het programma goed ingesteld is. De gebruiker moet kunnen kiezen uit: • mediabestanden • geluidsbestanden • videobestanden • alle bestanden Voeg de volgende regels toe aan het Load event:

O

N

openFileDialog.Filter = “Media bestanden|*.mpg;*.avi;*.wma;*.mov;*.wav;*.mp2; *.mp3;|” + “Muziek |*.wav;*.wma;*.mp2;*.mp3;|” + “Film |*.mpg;*.avi;*.mov;|” + “Afbeeldingen |*.jpg;*.bmp;*.png;*.gif;|” + “Alle bestanden|*.*” ;

C

Een filter stel je in door eerst de naam aan te geven, daarna een pipe-teken (|) en dan de verschillende formats gescheiden door een ;. Als je dan nog een reeks wilt opgeven moet je de reeks afsluiten met een pipe-teken.

5 Klik in het design-venster op het menu-item File om het submenu te openen.

6 Dubbelklik op het submenu-item Open. Je komt in het Click event van het openToolStripMenuItem terecht. Dit is de automatisch gegenereerde naam van het submenu-item. private void openToolStripMenuItem_Click(object sender, EventArgs e) { }

7 Voeg de volgende regels toe aan openToolStripMenuItem_Click waarmee je de openFileDialog opent en de geselecteerde bestandnaam toevoegt aan de variabele videoBestand:


262

Deel 2: Gegevens

// Opzoeken en openen media bestand. if (openFileDialog.ShowDialog()==DialogResult.OK) { videoBestand = openFileDialog.FileName; }

Video: WVM (Windows Video Media) Player aanmaken Je kunt eenvoudig een WindowsMediaPlayer-control vanuit de Toolbox op het formulier slepen. Je hebt dan een direct functionerende control, compleet met knoppen, submenu’s enzovoort. Het nadeel is dat sommige zaken niet goed werken. Daarom maken we deze control aan binnen de code. Door het toevoegen van de control aan de Toolbox zijn er wel een aantal bibliotheken toegevoegd aan Visual Studio. Ga naar het code-venster en voeg de volgende regel toe aan het using-blok:

T

1

EP

using WMPLib;

2 Maak de volgende globale variabele aan (de WindowsMediaPlayer). AxWMPLib.AxWindowsMediaPlayer windowsMediaPlayer = new AxWMPLib.AxWindowsMediaPlayer();

C

Je moet hier de naam van de AxWMPLib gebruiken omdat er anders een naamconflict ontstaat met WMPLIB (in beide bibliotheken zitten methodes met dezelfde naam...). 3 Ga naar de methode frmVideo_Load(object sender, EventArgs e).

N

4 Voeg de volgende regel toe aan de methode frmVideo_Load om de control aan te maken op het Panel pnlVideo.

O

windowsMediaPlayer.CreateControl();

Deze regel komt na de regels waarmee het openFileDialog.Filter wordt ingesteld.

C

Video: toevoegen event in code De WindowsMediaPlayer gaat op een niet-standaard manier om met foutmeldingen. In plaats van een foutmelding te genereren met een throw-commando maakt de WindowsMediaPlayer gebruik van een Error event. Omdat we de WindowsMediaPlayer aanmaken in code hebben we geen properties-venster waarin we events kunnen aanmaken. Nu moeten we dit zelf doen.

1

Maak in het code-venster de volgende methode aan die de foutmelding gaat afhandelen: private void windowsMediaPlayer_MediaError(object sender, AxWMPLib._WMPOCXEvents_MediaErrorEvent e) { }


Hoofdstuk 2 Bestanden (externe gegevens)

263

Deze code wordt aangeroepen door het event MediaError, dat we zo meteen aanmaken. 2 We voegen een try-catch-blok toe binnen de methode windowsMediaPlayer_MediaError. try { } catch (InvalidCastException invalidCastException) { }

T

3 Binnen het try-gedeelte voegen we de volgende regels toe die de WindowsMediaPlayer-foutmelding zullen omzetten naar een foutmelding die we kunnen gebruiken als foutboodschap:

EP

// Deze regel vertaalt de fout naar een bruikbaar foutobject met een cast // (codewoord as). Als dit mislukt, wordt er een fout gegenereerd. IWMPMedia2 errSource = e.pMediaObject as IWMPMedia2;

4 Nu voegen we de regels toe binnen het try-blok en na de vorige regels, om een fout-item aan te maken op basis van het foutobject errSource.

C

// Aanmaken errorItem op basis van het foutobject. IWMPErrorItem errorItem = errSource.Error;

5 De volgende regels genereren de foutboodschap en zetten deze in een MessageBox. Voeg deze als laatste regels toe aan het try-blok:

O

N

MessageBox.Show(“Error “ + errorItem.errorCode.ToString(“X”) + “ in “ + errSource.sourceURL + “\r\n \r\n” + errorItem.errorDescription);

C

6 Het try-blok is klaar. Als de cast mislukt gaat het programma naar het catch-blok. Dit richten we nu in. Zet tussen de accolades van het catch-blok de regel om de foutmelding te laten zien: MessageBox.Show(invalidCastException.Message);

1

Video: instellen van de control Voor het instellen van de control maken we een aparte methode met de naam instellenMediaPlayer();

Voeg de volgende regels toe: private void instellenMediaPlayer() { }

2 Eerst moet de WindowsMediaPlayer geïnitialiseerd worden. De intialisatie start je door de volgende regels toe te voegen als eerste regel binnen de methode instellenMediaPlayer:


264

Deel 2: Gegevens

// Initialisatie van de Windows Media Player. ((System.ComponentModel.ISupportInitialize)(windowsMediaPlayer)).BeginInit();

3 De mediaplayer moet het gehele Panel pnlVideo gaan vullen. Voeg de volgende regels toe: // Panel geheel vullen met Windows Media Player. windowsMediaPlayer.Dock = System.Windows.Forms.DockStyle.Fill;

4 De control heeft een naam en Windows weet hoe de control binnen een andere control geplaatst moet worden. Nu kan de control toegevoegd worden aan het Panel pnlVideo : // Toevoegen van de control aan het Panel. pnlVideo.Controls.Add(windowsMediaPlayer);

T

5 Het initialisatiegedeelte is hiermee klaar. De andere eigenschappen kunnen normaal ingesteld worden. Voeg de volgende regels toe om de initialisatie af te sluiten:

EP

((System.ComponentModel.ISupportInitialize)(windowsMediaPlayer)).EndInit();

6 We hebben de foutmelding net aangemaakt voor de WindowsMediaPlayer. Alleen moet er nog een koppeling gemaakt worden tussen de gebeurtenis (event) MediaError en de methode windowsMediaPlayer_MediaError(). Dit doen we door de volgende regels toe te voegen:

N

C

// Toevoegen MediaError event afhandeling. windowsMediaPlayer.MediaError += new AxWMPLib._WMPOCXEvents_MediaErrorEventHandler(windowsMediaPlayer_MediaError);

De combinatie += staat voor het toevoegen van een event aan een control.

O

7 Binnen de WindowsMediaPlayer kan de gebruiker door rechts te klikken een menu oproepen. Dit zetten we uit door de volgende regels toe te voegen:

C

// Als je rechtsklikt komt er wel of geen ondersteunend menu. windowsMediaPlayer.enableContextMenu = false;

8 Standaard komt de WindowsMediaPlayer met knoppen voor afspelen, volume, enzovoort. Deze knoppen willen we uitzetten. Voeg de volgende regels toe: // Geen knoppen zichtbaar. windowsMediaPlayer.uiMode = “none”;

9 Er zijn allerlei mogelijkheden om de WindowsMediaPlayer in te stellen. Wij stellen de player zo in dat de track automatisch steeds weer opnieuw speelt. Voeg de volgende regel toe: windowsMediaPlayer.settings.setMode(“loop”, true);

10 Voeg de volgende regel toe aan het Load event van het formulier: instellenMediaPlayer();

Hiermee is de instellingenmethode gereed.


Hoofdstuk 2 Bestanden (externe gegevens)

1

265

Video: menu-items koppelen aan de Windows Media Player Ga naar het design-venster.

2 Dubbelklik op het menu-item Start. Je ziet de volgende regels: private void startToolStripMenuItem_Click(object sender, EventArgs e) { }

3 Voeg de volgende regel toe om de geselecteerde track te starten: windowsMediaPlayer.URL = videoBestand;

4 Ga naar het design-venster. 5 Dubbelklik op het menu-item Stop. Je ziet de volgende regels:

EP

T

private void stopToolStripMenuItem_Click(object sender, EventArgs e) { }

6 Voeg de volgende regel toe om de track te stoppen: windowsMediaPlayer.Ctlcontrols.stop();

C

1

Video: Windows Media Player settings koppelen aan control Ga naar het design-venster.

2 Dubbelklik op de VScrollBar vsbVolume. Je komt nu in het Scroll event:

N

private void vsbVolume_Scroll(object sender, ScrollEventArgs e) { }

O

3 Voeg de volgende regel toe om de waarde (value) van vsbValue te koppelen aan het volume van de WindowsMediaPlayer:

C

windowsMediaPlayer.settings.volume = vsbVolume.Value;

Video: afsluiting

1

Voeg aan submenu-item Einde de code toe om het programma te beëindigen.

2 Test het programma.

Opdracht Video 1: Extra knoppen en bestandsinformatie • • •

Breid het Voorbeeld Video uit met de volgende items: Een tekstveld waarin de naam van de afgespeelde track wordt getoond. Een knop voor fast forward. Een knop voor pauze.


266

Deel 2: Gegevens

Opdracht Video 2: Extra instellingen toevoegen • • •

Breid het programma van Opdracht Video 1 uit met de volgende instellingsmogelijkheden: Standaardknoppen van de Windows Media Player zichtbaar of niet. Keuze uit de modi: autoRewind, loop, showFrame, shuffle. Een vinkje waarmee aangegeven kan worden of de gebruiker het contextmenu van de Windows Media Player wel of niet ter beschikking heeft. Bekijk de lijst met Properties en probeer de verschillende opties uit als je niet goed weet wat hierboven bedoeld wordt.

Opdracht Video 3: Meerdere bestanden selecteren en afspelen *

C

Het toevoegen van een control aan de Toolbox Het gebruik van een MenuStrip Het met code aanmaken van een OpenFileDialog Het met code aanmaken van een control (WindowsMediaPlayer) Het met code instellen van de WindowsMediaPlayer Het met code toevoegen van een event aan een control Het toevoegen van foutafhandeling aan de WindowsMediaPlayer

C

O

N

• • • • • • •

EP

Wat heb je nu geleerd?

T

Pas het programma van Opdracht Video 2 zo aan dat je meerdere bestanden kunt selecteren en toevoegen aan de playlist van de WindowsMediaPlayer.


267

Hoofdstuk 3 Systeembestanden (externe gegevens)

Windows registry

T

Bij het lezen en schrijven van tekstbestanden hebben we al gezien dat je .ini-bestanden kunt gebruiken om systeeminstellingen te bewaren, zodat een gebruiker niet alle instellingen van een programma steeds opnieuw hoeft in te voeren. Omdat alle programma’s de .ini-bestanden op allerlei plekken opsloegen, heeft Microsoft een systeem bedacht dat deze functionaliteit overneemt: de Windows registry.

EP

Windows registry is een hiërarchische database waarin zowel Windows als sommige applicaties hun instellingen bewaren. Denk hierbij aan de Windows Kernel, device drivers, services, Security Accounts Manager, user interfaces maar ook aan applicaties: programma’s zoals Word en spellen zoals Battle for Middle Earth.

C

De registry is bedoeld ter vervanging van de vele .ini-bestanden. Inmiddels wordt er ook gewerkt met .xml-bestanden en andere manieren om de instellingen van een applicatie vast te leggen.

N

Let op! Als je iets verandert in de registry waardoor de inhoud niet meer klopt, loopt je hele computersysteem vast en kunnen waardevolle gegevens verloren gaan.

O

Om de registry te kunnen gebruiken moet je eerst begrijpen hoe hij opgebouwd is. We werpen dus een blik op de registry.

C

Opbouw registry De informatie in de registry wordt opgeslagen in registry keys. Deze keys (sleutels) werken net zoals mappen en submappen binnen de Windows Verkenner. Je hebt dus ook keys en subkeys. In elke subkey wordt een stukje informatie opgeslagen van een programma zoals de ingestelde kleuren, de grootte en positie van het scherm, de bestandsextensie die bij het programma hoort, enzovoort.

• • • • • • •

De registry kent standaard de volgende indeling: CurrentUser De voorkeuren van de gebruiker. LocalMachine De instellingen van de lokale machine (computer). ClassesRoot Informatie over types, classes en eigenschappen. Users Informatie over de standaard gebruikersconfiguratie. PerformanceData Perfomance-informatie van software. CurrentConfig Hardware-informatie die niet met de gebruiker samenhangt. DynData Opslag van veranderende gegevens. In C kun je met behulp van de registry-class deze categorieën gebruiken.


268

Deel 2: Gegevens

Voorbeeld Registry: Opvragen gegevens We maken een programma dat enkele eenvoudige gegevens uit de registry haalt over de computer zelf.

1

Registry: voorbereiding Maak een project aan met de naam RegistryGegevens en voeg de volgende controls toe: Type

Naam

Eigenschap

Waarde

Button

btnProcessor

Text

Zoeken

GroupBox

gbProcessorInformatie

Text

Processorinformatie

label1

Text

Label

lblFabrikant

Text

label2

Label

lblModel

Label3

Label

lblType

ForeColor

Red

Text

Model:

Text

Model:

BackColor

LawnGreen

ForeColor

Red

Text

Type:

Text

Type: LawnGreen

ForeColor

Red

Label4

Text

Snelheid:

lblSnelheid

Text

Snelheid:

BackColor

LawnGreen

ForeColor

Red

C

Label

LawnGreen

BackColor

O

Label

N

Label

Fabrikant:

BackColor

C

Label

Fabrikant:

EP

Label

T

De volgende controls worden in de GroupBox gbProcessorInformatie geplaatst.

Tabel 14: Controls RegistryGegevens

2 Voeg eerst nog de bibliotheek Microsoft.Win32 toe: using Microsoft.Win32;

3 Het programma ziet er nu als volgt uit:


Hoofdstuk 3 Systeembestanden (externe gegevens)

269

Schermafbeelding 32: RegistryGegevens

1

EP

Van beide situaties laten we een voorbeeld zien.

T

• •

Registry: ophalen gegevens Je kunt de gegevens op verschillende manieren ophalen. Twee ervan zijn: Het hele pad invoeren. Elke subkey afzonderlijk benoemen.

We maken een methode om de processorfabrikant te achterhalen. Maak de volgende methode aan in het code-venster:

N

C

private string ProcessorFabrikant() { // Ophalen van gegevens, bijvoorbeeld: welke processor gebruik je? RegistryKey registryKey = Registry.LocalMachine; registryKey = registryKey.OpenSubKey(@”HARDWARE\DESCRIPTION\System\CentralProcessor\0”); object fabrikant = registryKey.GetValue(“VendorIdentifier”) return fabrikant.ToString(); }

O

Je ziet dat hier alle keys en subkeys als een pad worden opgegeven.

C

2 Nu maken we een methode aan om het processormodel te achterhalen. Deze methode benadert de subkeys één voor één. Maak de volgende methode aan binnen het code-venster: private string ProcessorModel() { // Toegang tot de informatie over de processor. RegistryKey registryKeyLocalMachine = Registry.LocalMachine; RegistryKey subKeyHardware = registryKeyLocalMachine.OpenSubKey(“HARDWARE”); RegistryKey subKeyDescription = subKeyHardware.OpenSubKey(“DESCRIPTION”); RegistryKey subKeySystem = subKeyDescription.OpenSubKey(“SYSTEM”); RegistryKey subKeyCPU = subKeySystem.OpenSubKey(“CENTRALPROCESSOR”); RegistryKey subKeyInfo = subKeyCPU.OpenSubKey(“0”); object model = subKeyInfo.GetValue(“Identifier”); return model.ToString(); }

3 Hier krijgt elke subkey zijn eigen variabele die weer van de vorige subkey wordt afgeleid.


270

Deel 2: Gegevens

Om voor alle op te vragen gegevens zo’n hele riedel te moeten invoeren is erg veel werk. Daarom maken we nog één algemene methode. Voeg de volgende methode toe binnen het code-venster: private string GetProcessorInfo(string onderdeel) { string processorInfo = “”; // Toegang tot de informatie over de processor. RegistryKey registryKeyLocalMachine = Registry.LocalMachine; RegistryKey subKeyHardware = registryKeyLocalMachine.OpenSubKey(“HARDWARE”); RegistryKey subKeyDescription = subKeyHardware.OpenSubKey(“DESCRIPTION”); RegistryKey subKeySystem = subKeyDescription.OpenSubKey(“SYSTEM”); RegistryKey subKeyCPU = subKeySystem.OpenSubKey(“CENTRALPROCESSOR”); RegistryKey subKeyInfo = subKeyCPU.OpenSubKey(“0”);

T

// Ophalen van de gewenste informatie. object info = subKeyInfo.GetValue(onderdeel);

EP

// Alleen als er informatie gevonden wordt, // kunnen we die toevoegen aan de variabele. if (info != null) { processorInfo = info.ToString(); } return processorInfo;

C

}

N

4 Nu het toevoegen van de methodes aan de Button btnProcessor. Ga naar het designvenster en dubbelklik op de Button btnProcessor. Het volgende verschijnt in het code-venster:

O

private void btnProcessor_Click(object sender, EventArgs e) { }

5 Voeg de volgende code toe om de Labels te vullen:

C

lblFabrikant.Text = ProcessorFabrikant(); lblModel.Text = GetProcessorInfo(“ProcessorNameString”); lblType.Text = ProcessorModel(); lblSnelheid.Text = GetProcessorInfo(“~Mhz”) + “ Mhz”;

6

Test het programma. Registry: Alleen gebruiken als het echt niet anders kan Vroeger gebruikte men de registry om gegevens over de programma’s in op te slaan – zaken als de laatst gebruikte map voor het opslaan van gegevens, de ingestelde kleuren en de locatie van de database. Tegenwoordig gebruiken we hier .xml-bestanden voor. Daarom zijn er hier ook geen opdrachten met betrekking tot de registry.


Hoofdstuk 3 Systeembestanden (externe gegevens)

271

Wat heb je nu geleerd? • • •

Een globaal overzicht van de structuur van Windows registry Twee manieren om gegevens uit de registry te halen Wanneer je de registry zelf moet gebruiken

XML-bestanden

EP

T

XML staat voor eXtensible Markup Language. Deze taal wordt veel gebruikt bij het uitwisselen van data. Word, Excel, grote databasesystemen, Visual Studio: ze kunnen allemaal met XML overweg. En dat niet alleen: deze programma’s maken allemaal zelf ook gebruik van XML. Voornamelijk omdat de taal leesbaar is voor zowel mensen als computers. XML lijkt erg veel op HTML. Net zoals HTML werkt XML met tags. HTML-code is beperkt tot een vooraf gedefinieede set met tags die alle gebruikers delen. Met XML bedenk je zelf de definities en deelt deze met jouw gebruikers.

C

Daar waar een .csv-bestand een header kent met de kolomnamen, wordt deze informatie in een XML-bestand opgeslagen in de tags. Daarnaast zijn er ook XSDbestanden waarin alleen de structuur, het schema, van een XML-bestand wordt opgeslagen.

N

Het leuke van XML-bestanden is dat er steeds meer informatie via het internet in XML beschikbaar komt. Denk hierbij aan zaken als weerberichten, financiële informatie van de Europese Bank en nog veel meer. In je programma kun je dan direct linken naar die informatie of zo’n site.

O

Hoe ziet nu zo’n XML-bestand eruit? We nemen weer het voorbeeld van het automerkenoverzicht: Merk

C

Bouwjaar

Type

Bijzonderheden Elektrische kap

1996

Volkswagen

Golf IV

2004

Volvo

V60

2017

Jaguar

S type

2001

Peugeot

105

Tabel 15: Overzicht Auto’s 2

In XML ziet dat er als volgt uit: <?xml version=”1.0” encoding=”utf-8”?> <autos> <auto> <bouwjaar>1996</bouwjaar> <merk>Volkswagen</merk> <type>Golf IV</type>

Prijs 800.00 3500.00

Elektrisch

135000.00 500.50


272

Deel 2: Gegevens

EP

T

<bijzonderheden>Elektrische kap</bijzonderheden> <prijs>800.00</prijs> </auto> <auto> <bouwjaar>2004</bouwjaar> <merk>Volvo</merk> <type>V60</type> <bijzonderheden></bijzonderheden> <prijs>3500.00</prijs> </auto> <auto> <bouwjaar>2017</bouwjaar> <merk>Jaguar</merk> <type>S type</type> <bijzonderheden>Elektrisch</bijzonderheden> <prijs>135000.00</prijs> </auto> <auto> <bouwjaar>2001</bouwjaar> <merk>Peugeot</merk> <type>105</type> <bijzonderheden></bijzonderheden> <prijs>500.50</prijs> </auto> </autos>

N

C

De eerste regel is een regel die aangeeft welke karakterset en welke versie van XML gebruikt worden. Binnen Visual Studio kun je XML-bestanden aanmaken. IntelliSense wordt dan ook ingesteld op de taalregels van XML. Wil je meer weten over XML? Op de site http://www.w3schools.com/xml/ kun je alles vinden over dit bestandsformaat.

C

O

In dit boek leer je hoe je deze bestanden kunt inlezen en hoe je er gebruik van kunt maken, niet wat de exacte regels zijn van XML.

Voorbeeld XML 1: Inlezen XML-bestand We maken een eenvoudig programma dat informatie ophaalt van www.buienradar. nl en dat gebruikmaakt van een XML-bestand met weerstationgegevens.

1

XML lezen: Voorbereiden Maak een nieuw project aan met de naam VoorbeeldXMLLezen.

2 Plaats de volgende controls op het formulier:


Hoofdstuk 3 Systeembestanden (externe gegevens)

273

Type

Naam

Eigenschap

Waarde

Button

btnWeer

Text

Weer

CheckBox

chbZoeken

Text

Zoeken

CheckState

Unchecked

Text

Regio:

Label

lblRegio

Visible

False

TextBox

txbRegio

Visible

False

Button

btnEinde

Text

Einde

ListBox

lbxWeer

BackColor

LightCoral

Anchor

Bottom

T

Tabel 16: Controls Voorbeeld XML Lezen

O

N

C

EP

3 Zorg ervoor dat het formulier er als volgt uitziet:

C

Schermafbeelding 33: Voorbeeld XML inlezen

1

XML lezen: Algemene controls instellen Zorg ervoor dat als je op de Button btnEinde klikt, het programma beëindigd wordt.

2 Als je de CheckBox chbZoeken aanvinkt, moeten het Label lblRegio en de TextBox txbRegio zichtbaar worden. Als er geen vinkje staat bij CheckBox chbZoeken moeten lblRegio en txbRegio verborgen worden. Dit doe je door de volgende regels toe te voegen aan het CheckedChanged event van chbZoeken: lblRegio.Visible = chbZoeken.Checked; txbRegio.Visible = chbZoeken.Checked;


274

1

Deel 2: Gegevens

XML lezen: Initialiseren en koppelen Ga naar het code-venster.

2 Voeg boven je programma een verwijzing (referentie) toe naar de XML-bibliotheek, door de volgende regel toe te voegen: using System.Xml;

3 Ga naar het design-venster en dubbelklik op de Button btnWeer. Je ziet de volgende regels in het code-venster: private void btnWeer_Click(object sender, EventArgs e) { }

EP

// Leegmaken ListBox lbxWeer. lbxWeer.Items.Clear();

T

4 We beginnen met de ListBox leeg te maken zodat deze klaar is om een nieuwe set met gegevens te ontvangen. Voeg de volgende regel toe:

5 Vervolgens voegen we de te gebruiken variabelen toe met de volgende regels:

C

// Is het vinkje ‘zoeken’ aangezet? ja of nee bool regioGevonden = false; // De string die opgebouwd wordt. string outputString = “”; // Scheidingsteken tussen de elementen. string scheiding = “, “;

N

6 Nu voegen we de variabele toe waarmee we verbinding leggen met de site en het XML-bestand ophalen. Voeg de volgende regels toe:

O

// Ophalen en inlezen XML bestand. XmlReader xmlReader = XmlReader.Create(“http://xml.buienradar.nl”);

C

7 Controlklik op de link http://xml.buienradar.nl, Visual Studio opent het XML bestand. Het ziet er als volgt uit: - <buienradarnl> - <weergegevens> <titel>www.buienradar.nl – actuele weersinformatie</titel> <link>http://www.buienradar.nl</link> <omschrijving>XML feed weersinformatie</omschrijving> <language>nl-nl</language> <copyright>(C)opyright 2005 – 2016 Buienradar B.V. Alle rechten voorbehouden</copyright> <gebruik>Deze XML feed mag vrij worden gebruikt onder voorwaarde van bronvermelding BuienRadar.NL inclusief een hyperlink naar http://www.buienradar. nl. Aan de feed kunnen door gebruikers of andere personen geen rechten worden ontleend.</gebruik> - <image> <titel>Buienradar.nl</titel> <link>http://www.buienradar.nl/</link> <url>http://www.buienradar.nl/include/images/header.jpg</url>


Hoofdstuk 3 Systeembestanden (externe gegevens)

275

O

N

C

EP

T

<width>950</width> <height>78</height> </image> - <actueel_weer> - <weerstations> - <weerstation id=”6391”> <stationcode>6391</stationcode> <stationnaam regio=”Venlo”>Meetstation Arcen</stationnaam> <lat>51.30</lat> <lon>6.12</lon> <datum>02/14/2016 16:20:00</datum> <luchtvochtigheid>97</luchtvochtigheid> <temperatuurGC>1.5</temperatuurGC> <windsnelheidMS>1.62</windsnelheidMS> <windsnelheidBF>2</windsnelheidBF> <windrichtingGR>293.9</windrichtingGR> <windrichting>WNW</windrichting> <luchtdruk>-</luchtdruk> <zichtmeters>-</zichtmeters> <windstotenMS>2.6</windstotenMS> <regenMMPU>0.318</regenMMPU> <icoonactueel zin=”bewolkt en regen” ID=”q”>http://xml.buienradar.nl/icons/q.gif</icoonactueel> <temperatuur10cm>1.7</temperatuur10cm> <url>http://www.buienradar.nl/weerstation/6391/Meetstation Arcen</url> <latGraden>51.50</latGraden> <lonGraden>6.20</lonGraden> </weerstation> - <weerstation id=”6275”> <stationcode>6275</stationcode> <stationnaam regio=”Arnhem”>Meetstation Arnhem</stationnaam> <lat>52.04</lat> <lon>5.53</lon>

XML lezen: Lezen en selecteren Het programma maakt gebruik van de geel gemarkeerde gegevens.

C

In de tags staan de elementen (zoals stationnaam en temperatuurGC), maar binnen een tag kunnen ook attributen gedefinieerd worden. Regio is een attribuut en Arnhem is de waarde van het attribuut dat bij het element stationnaam met de waarde Meetstation Arnhem hoort.

1

We kunnen het bestand regel voor regel door laten lezen met een while(read)statement. De XMLreader gaat automatisch naar de volgende regel, net zolang tot het einde van het bestand is bereikt. Voeg de volgende regels toe: while (xmlReader.Read()) { }

2 Het hele bestand wordt doorlopen. We kunnen ons programma elk onderdeel (dat een node wordt genoemd) laten inspecteren om vervolgens bepaalde gegevens te selecteren. Als het soort node een element (een tag) is en de waarde gelijk


276

Deel 2: Gegevens

is aan stationnaam dan willen we de waarde van het element toevoegen aan de outputString. Voeg de volgende regels toe binnen de while-lus: if ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == “stationnaam”)) { } else if { }

3 Als aan deze voorwaarde is voldaan, moeten we kijken of er attributen horen bij deze node. Dit om null-fouten te voorkomen. Voeg de volgende regels toe binnen het if-statement:

T

if (xmlReader.HasAttributes) { }

EP

4 Als de CheckBox chkZoeken geactiveerd is, moeten alleen die stations worden toegevoegd waarvan de regio gelijk is aan de inhoud van de TextBox txbRegio. Voeg daarom de volgende regels toe aan het laatst toegevoegde if-statement.

O

N

C

if (xmlReader.GetAttribute(“regio”) == txbRegio.Text || !chbZoeken.Checked) // De regio is gelijk aan de ingevoerde regio of zoeken staat uit: // in het ene geval moet alleen de overeenkomende regio toegevoegd // worden, in het andere geval worden alle regio’s toegevoegd. { regioGevonden = true; } else { // Als de regio niet gelijk is aan de ingevoerde regio // of chbZoeken is aangevinkt, dan wordt regioGevonden op false gezet. regioGevonden = false; }

C

5 Als de regio gevonden is, moeten we deze toevoegen aan de outputString. Voeg de volgende regels toe binnen het if-blok achter de regel regioGevonden = true;: // Opbouwen van de outputString: eerst het attribuut regio. outputString = “Regio: “ + xmlReader.GetAttribute(“regio”);

6 Omdat de regio altijd hoort bij het element station, moeten we ook de informatie van het element station aan de outputString toevoegen, waarbij de gegevens gescheiden worden door de variabele scheiding. Voeg de volgende regels toe aan het if-blok: // Vervolgens de waarde van het element toevoegen. outputString = outputString + scheiding + “Station: “ + xmlReader.ReadElementContentAsString();


Hoofdstuk 3 Systeembestanden (externe gegevens)

277

7 De regio en het station zijn geselecteerd. Dan rest ons nog het element temperatuur toe te voegen. We vullen nu het else if-gedeelte verder in. Voeg aan de regel else if het volgende toe: ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == “temperatuurGC”))

8 Als de node gelijk is aan temperatuurGC, dan kunnen we de outputString aanvullen met de temperatuur. Maar we hoeven dit alleen maar te doen als de variabele regioGevonden waar is. Voeg de volgende regels toe binnen het else if-blok: if (regioGevonden) { }

T

9 Nu kunnen we de temperatuur toevoegen aan de outputString. Voeg de volgende regels toe binnen het if-blok:

EP

// Toevoegen van de temperatuur aan de outputString. outputString = outputString + scheiding + “Temperatuur: “ + xmlReader.ReadElementContentAsString();

10 Tot slot voegen we de outputString toe aan de ListBox lbxWeer:

C

// Als de regio gevonden is, moet de outputString toegevoegd worden // aan de ListBox lbxWeer. lbxWeer.Items.Add(outputString);

Zoek een plaatje op met wolken, op het internet of uit je eigen verzameling.

O

1

N

Formulier: Achtergrond instellen Het programma gaat over weerstations, dus op het formulier zou een achtergrond met wolken mooi zijn.

2 Download het plaatje naar de map Mijn afbeeldingen met als naam cloud-01.jpg (als het een jpg-afbeelding is).

C

3 Ga naar het properties-venster van het formulier frmVoorbeeldXMLLezen.

4 Klik op de drie puntjes achter de property BackgroundImage. Je komt nu in het volgende scherm:


Deel 2: Gegevens

5 Klik op Local Resource.

EP

Schermafbeelding 34: Select Resource

T

278

6 Klik op Import. Je komt nu in een file browser. 7 Ga naar de map Mijn afbeeldingen.

C

8 Selecteer het bestand cloud-01.jpg (het bestand wat je daarnet hebt neergezet) en klik op OK.

N

9 Je hebt nu een afbeelding achter je formulier. Waarschijnlijk is de afbeelding te groot of te klein. Daarom passen we nog de eigenschap BackgroundImageLayout aan van frmVoorbeeldXMLLezen: BackgroundImageLayout = Stretch.

XML lezen: test je programma Start je programma op.

C

1

O

Let op: bij een grote afbeelding laadt het programma aanzienlijk langzamer.

2 Zet een vink bij de CheckBox chbZoeken.

3 Typ in de TextBox txbRegio: Noordzee.

4 Klik op de Button btnWeer. 5 Als het goed is, ziet je programma er nu als volgt uit (de achtergrond en de temperaturen kunnen natuurlijk verschillen):


EP

Schermafbeelding 35: Voorbeeld XML inlezen

279

T

Hoofdstuk 3 Systeembestanden (externe gegevens)

Let op! Omdat je met XML live gegevens inleest, kunnen de gegevens van minuut tot minuut veranderen.

C

Opdracht XML 1: ListBox vullen vanuit XML

N

Pas het programma van XML Voorbeeld 1 zo aan dat de TextBox txbRegio vervangen wordt door een ComboBox. Vul de ComboBox.Items vanuit het XML-bestand.

O

Opdracht XML 2: Extra selectieveld

C

Pas het programma van Opdracht XML 1 zo aan dat er twee invoervelden bij komen voor minimale windkracht en maximale windkracht. Als deze twee velden zijn ingevuld, moet het programma alleen die weerstations laten zien waarbij de windkracht zich tussen deze twee waardes bevindt.

Let op: Iedere keer dat je een ReadElement-opdracht geeft, leest de XML-reader een regel. Hierdoor kun je soms onbedoeld een regel overslaan.

Opdracht XML 3: DataGridView • • •

Pas het programma van Opdracht XML 2 zo aan dat de ListBox lbxWeer wordt vervangen door een DataGridView waarbij de volgende kolommen gevuld worden: Regio Stationnaam Stationcode


280

• • • • •

Deel 2: Gegevens

Temperatuur Windrichting WindsnelheidBF Windstoten RegenMMPU

Opdracht XML 4: Weerstation * Maak een programma dat alle gegevens uit het XML-bestand van buienradar.nl laat zien.

T

De bovenste helft van het scherm bestaat uit een DataGridView waarin de meest relevante gegevens van de weerstations getoond worden. Dit zijn de Stationcode, de Regio, de Stationnaam, de Breedtegraad, Lengtegraad, Breedtegraad in graden en Lengtegraad in graden.

EP

Op de onderste helft van het scherm toon je dan de andere gegevens van het geselecteerde station. Breng mogelijkheden aan om te filteren, net zoals in het voorbeeld is gedaan.

C

Voorbeeld XML 2: Schrijven naar een XML-bestand

N

We hebben nu gegevens opgehaald en laten zien in een programma. Inlezen is maar de helft van het verhaal. We willen natuurlijk ook onze eigen gegevens in een format kunnen publiceren dat iedereen kan begrijpen, in XML dus.

XML schrijven: voorbereiding Kopieer het programma voorbeeldAutos naar voorbeeldAutosXML.

C

1

O

We hebben al een programma gemaakt waarmee we autogegevens wegschrijven naar een .csv-bestand. Dit programma passen we aan voor gebruik met XML.

2 Voeg een Button toe met de naam btnXML en het opschrift  XML. 3 Voeg een bibliotheek toe met: using System.Xml;

XML schrijven 1

We voegen alleen code toe aan de Button btnXML. Dubbelklik op de Button btnXML. private void btnXML_Click(object sender, EventArgs e) { }

2 Eerst voegen we een aantal regels toe die de layout van het XML-bestand bepalen. Er worden bijvoorbeeld tabs en regeleindes toegevoegd. Doen we dit niet, dan bestaat het XML-bestand uit één lange regel. Voeg de volgende regels toe:


Hoofdstuk 3 Systeembestanden (externe gegevens)

281

// Opmaak van het XML-bestand. XmlWriterSettings xmlWriterSettings = new XmlWriterSettings(); xmlWriterSettings.NewLineOnAttributes = true; xmlWriterSettings.Indent = true;

3 Nu kunnen we een variabele aanmaken van het type XmlWriter. Voeg de volgende regels toe: using (XmlWriter writer = XmlWriter.Create(“autos.xml”,xmlWriterSettings)) { }

Je ziet: hier wordt gebruik gemaakt van de eerder aangemaakt variabele xmlWriterSettings.

EP

writer.WriteStartDocument();

T

4 Het document staat klaar. Nu kunnen we beginnen met schrijven. Alleen moet die opdracht wel gegeven worden. Voeg de volgende regel toe tussen de accolades van using XmlWriter:

5 Het document kan gevuld worden. In ons voorbeeld gaat het over auto’s. De rootnode wordt dus Autos. Voeg de volgende regel toe: writer.WriteStartElement(“Autos”);

C

6 Vervolgens moeten we rij voor rij door de dgvAutos om de rijen te kunnen lezen. Voeg de volgende regels toe:

N

foreach (DataGridViewRow row in dgvAutos.Rows) { }

O

7 Binnen de foreach-lus moeten we eerst checken of de regel een nieuwe lege regel is. Hier hebben we niets aan, want een dergelijke regel genereert een foutmelding. Voeg de volgende regels toe binnen de foreach-lus:

C

if (!row.IsNewRow) { }

8 Er is een nieuwe rij, dus we kunnen beginnen met een nieuw element auto. Voeg de volgende regel toe binnen het if-blok: writer.WriteStartElement(“Auto”);

9 Nu halen we cel voor cel de informatie op. for (int i = 0; i < dgvAutos.Columns.Count; i++) { }

10 Per cel wordt de kop van de kolom gebruikt om het element aan te maken. De waarde van de cel wordt de waarde van het element:


282

Deel 2: Gegevens

writer.WriteElementString(dgvAutos.Columns[i].HeaderText, row.Cells[i].Value.ToString());

11 Elk element met de naam auto moet ook netjes beëindigd worden. Voeg daarom direct na het if-blok de volgende regel toe: writer.WriteEndElement();

12 Het element autos moet ook beëindigd worden. Voeg daarom buiten de foreachloop de volgende regel toe: writer.WriteEndElement();

13 Tot slot moeten we het document sluiten. Voeg als laatste de volgende regel toe: writer.WriteEndDocument();

EP

Het XML-bestand ziet er dan als volgt uit:

T

14 Test je programma. Het XML-bestand wordt aangemaakt in de programma-directory van je programma (bin\debug of bin\release).

C

O

N

C

<?xml version=”1.0” encoding=”utf-8”?> <Autos> <Auto> <Bouwjaar>1962</Bouwjaar> <Merk>Volkswagen</Merk> <Type>Golf IV</Type> <Bijzonderheden>Elektrische kap</Bijzonderheden> <Prijs>800.00</Prijs> </Auto> <Auto> <Bouwjaar>2004</Bouwjaar> <Merk>Volvo</Merk> <Type>V50</Type> <Bijzonderheden /> <Prijs>3500.00</Prijs> </Auto> <Auto> <Bouwjaar>2017</Bouwjaar> <Merk>Jaguar</Merk> <Type>S type</Type> <Bijzonderheden>Elektrisch</Bijzonderheden> <Prijs>135000.00</Prijs> </Auto> <Auto> <Bouwjaar>2001</Bouwjaar> <Merk>Peugeot</Merk> <Type>105</Type> <Bijzonderheden /> <Prijs>500.50</Prijs> </Auto> </Autos>


Hoofdstuk 3 Systeembestanden (externe gegevens)

283

Opdracht XML 5: Landenlijst Maak een programma dat op basis van de eerder (in Voorbeeld Enum 2) gemaakte Landen class, deze class als een XML-bestand wegschrijft.

Wat heb je nu geleerd? Instellen van een achtergrondafbeelding op een formulier Kennisgemaakt met de XML-bestandsstructuur Inlezen van een XML-bestand Selecteren van elementen uit een XML-bestand Selecteren van attributen uit een XML-bestand Schrijven van een XML-bestand Uitlezen en wegschrijven van een DataGridView naar een XML-bestand

C

O

N

C

EP

T

• • • • • • •


284

Nawoord Deel 2

Je hebt nu geleerd om om te gaan met Arrays, List<T> en diverse bestandsformaten. Daarnaast heb je nader kennisgemaakt met enkele controls zoals de DataGridView en de ListBox. Met nadruk kennisgemaakt, want als je de lijst met properties en events van iedere control bekijkt, kan daar met gemak nog een boek mee gevuld worden.

T

Je bent nu in staat om programma’s te maken die gegevens opslaan, bewerken en ophalen. Met andere woorden: volwaardige programma’s.

EP

Het volgende deel gaat over de koppeling van C met databases. We gaan dan in op het veilig werken met grote hoeveelheden gegevens.

C

O

N

C

Dus vooralsnog: Happy coding!


C

O

N

C

EP

T

Deel 3: Databases


C EP

C

N

O T


287

Inleiding Deel 3: Databases

C# en databases In deel 1 van Leren programmeren in C heb je kennisgemaakt met C, in deel 2 heb je geleerd hoe je omgaat met gegevens in C. Je weet nu hoe je lijsten (Arrays en List<T>) combineert met Controls zoals een DataGridView en een ListView. Ook heb je geleerd hoe je werkt met externe gegevens zoals tekstbestanden, afbeeldingen, video’s en de registry.

EP

T

Heel veel gegevens worden echter opgeslagen in databases. Databases zijn programma’s die gemaakt zijn om met grote hoeveelheden data om te gaan. Ze zijn snel, veilig en robuust, tenminste als je er goed mee omgaat. In dit deel ligt de nadruk op het benaderen en verwerken van gegevens in databases met C.

C

Benodigde voorkennis

O

N

Voor dit deel is het noodzakelijk dat je de stof beheerst van de eerste twee delen: Introductie en Gegevens. Verder is het absoluut noodzakelijk dat je elementaire kennis hebt van relationele databases en dat je de basis van SQL snapt.

C

Ook is het noodzakelijk dat je weet hoe je tabellen en relaties aanmaakt met tenminste een van de volgende pakketten: – Microsoft Access – MySQL – Microsoft SQL Server Express

1 2 3 4 5

Samengevat heb je de volgende voorkennis nodig om met dit deel te kunnen werken: C Introductie C Gegevens basis relationele databases basis SQL basis Microsoft Access en/of MySQL en/of Microsoft SQL Server Express


288

Deel 3: Databases

Hoe werk je met dit boek? Ook bij dit deel is het de bedoeling dat je zowel de voorbeelden als de opdrachten maakt: probeer de voorbeelden stap voor stap uit en let op wat het programma doet. Sla iedere opdracht apart op in een bestandje. Je hebt de uitwerking van een opdracht soms voor een latere opdracht nog nodig. De verbinding met een database wordt vanuit een formulier gemaakt. Zo kun je je data goed bekijken. In de tweede helft van dit deel gaan we ook leren hoe je meerdere formulieren gebruikt en werken we ook verder in het Program.cs-bestand.

C

O

N

C

EP

T

Veel succes & plezier!


289

Hoofdstuk 1 Introductie databases

Wat is een database?

T

In Wikipedia staat: ‘Een database, gegevensbank of databank is een digitaal opgeslagen archief, ingericht met het oog op flexibele raadpleging en gebruik. Databases spelen een belangrijke rol voor het archiveren en actueel houden van gegevens bij onder meer de overheid, financiële instellingen en bedrijven, in de wetenschap, en worden op kleinere schaal ook privé gebruikt.’

EP

Voor de programmeur is een database iets is waarin gegevens worden opgeslagen met behulp van programma’s en waaruit je gegevens kunt halen met behulp van programma’s.

N

C

Er zijn verschillende soorten databases, maar wij beperken ons in dit boek tot de grootste groep databases: de relationele databases. Deze databases maken allemaal gebruik van de taal SQL (Structured Query Language).

Relationele databases

O

C

• • • • • •

Even ter opfrissing van de theorie, de volgende regels met betrekking tot relationele databases: Een relationele database bestaat uit tabellen. Een tabel bestaat uit rijen (regels) en kolommen. Iedere regel in een tabel heeft dezelfde opbouw. Iedere tabel heeft een sleutelveld. Tabellen worden gekoppeld op basis van sleutelvelden en kolommen. Voor een n-op-m-relatie heb je altijd een extra tabel nodig. Het volgende schema verduidelijkt het geheel:


290

Deel 3: Databases

Figuur 1 Basisstructuur tabellen

T

In dit geval bestaat de database uit drie tabellen: Games, Personages en GamesPersonages.

EP

De tabel Games kent drie kolommen: ID, Naam en Maker. Daarbij is ID (ai-die) het unieke identificatienummer dat ook wel sleutel wordt genoemd. Om deze kolommen te onderscheiden van gelijknamige kolommen van andere tabellen schrijven we in dit boek de namen van kolommen altijd met de tabelnaam ervoor, dus niet ID, maar Games.ID en Personages.ID.

C

Games.ID is de sleutelkolom voor de tabel Games en Personages.ID is de sleutelkolom voor de tabel Personages. De kolommen GamesPersonages.GameID en GamesPersonages.PersonageID vormen samen de sleutel van de tabel GamesPersonages.

N

Als een kolom sleutel is van de tabel betekent dit dat een waarde maar één keer mag voorkomen in deze kolom.

C

O

Waarom deze structuur? Een game, bijvoorbeeld Super Mario Galaxy, kent meerdere personages, zoals Mario, Luigi en Peach. Maar Mario komt in meerdere games voor, bijvoorbeeld ook in Super Mario Kart. Deze structuur zorgt ervoor dat je al deze informatie kunt opslaan in tabellen zonder een conflict te krijgen met de regels voor sleutels:

Figuur 2: Inhoud tabellen


Hoofdstuk 1 Introductie databases

291

Door de sleutels te combineren kun je erachter komen welk personage voorkomt in welk spel. In het spel met Games.ID 1 (Super Mario Galaxy) komen de personages voor met de Personages.ID’s 1, 2 en 3 (dus Mario, Luigi en Peach).

De databases in dit boek In dit boek leren we je werken met drie verschillende relationele databases.

Microsoft Access

EP

T

Access is een zogenaamde desktop database die deel uitmaakt van de Microsoft Office suite. Access kan goed ingezet worden voor prototyping (het snel in elkaar zetten van een applicatie als voorbeeld, om iets uit te proberen) en in kleine (desktop) omgevingen. Visual Studio werkt goed samen met Access.

MySQL

C

N

• •

MySQL is een vreemde eend in de bijt: geen Microsoft-product, niet standaard geïntegreerd in Visual Studio én een Open Source programma. Toch behandelen we ook deze database, en wel om de volgende redenen: Je leert binnen Visual Studio werken met niet-Microsoft-producten. Je leert werken met één van de grote (gratis) databases die veel gebruikt worden op het internet.

Microsoft SQL Server Express

C

O

Microsoft SQL Server Express is het kleine broertje van SQL Server (de grote database van Microsoft). Ook deze producten zijn goed geïntegreerd in Visual Studio. Omdat je leert echte programma’s te maken voor een bedrijfsmatige omgeving, is het belangrijk dat je ook weet hoe je met deze omgeving verbinding maakt.


292

Hoofdstuk 2 Hoe werkt het?

DataSets

T

In C werken we nooit rechtstreeks met een database. Een database bevindt zich, net zoals andere bronnen van data (DataSources of gegevensbronnen), altijd buiten het programma. Binnen het programma werken we met een DataSet, en die ziet er net zo uit als de tabellen van een relationele database.

EP

De DataSet onderhoudt de verbindingen met de DataSources. Een DataSource kan een database zijn maar ook een XML-bestand, een spreadsheet of een willekeurig ander bestand.

C

Hiermee is meteen duidelijk waarom men een DataSet bedacht heeft: je kunt op deze manier gegevens van allerlei DataSources eenvoudig in één programma combineren. Het programma maakt gebruik van de gegevens in de DataSet.

O

N

Een DataSet bestaat uit DataTables. Een DataTable werkt net zoals een tabel in een database. Alleen bevindt een tabel zich in een database en een DataTable staat in een DataSet.

C

Een DataSet bestaat alleen in het interne geheugen en dat betekent dat zodra je het programma beëindigt alle gegevens weg zijn. Je moet er zelf voor zorgen dat de gegevens vanuit de Dataset weer opgeslagen worden in de oorspronkelijke DataSources. Je leert eerst een DataSet aan te maken en te vullen met DataTables.

Daarna leer je hoe je de gegevens in een DataSet koppelt aan de Controls van een formulier. Heb je dat onder de knie, dan leer je (in hoofdstuk 3) hoe je een DataSet koppelt aan een database zoals MS Access en MySQL. Tegelijkertijd leer je dan hoe je de data synchroniseert tussen database en DataSet. Maar nu eerst het aanmaken van een DataSet en een DataTable.


Hoofdstuk 2 Hoe werkt het?

293

Voorbeeld 1: Nieuwe DataSet aanmaken met één DataTable Voorbereiding We maken een DataSet aan met één DataTable, de DataTable Personen. Deze DataTable gaan we koppelen (DataBinding) aan een aantal Controls zodat je kunt zien hoe het een en ander functioneert. 1 Maak een project aan met de naam voorbeeldDataSet. 2 Hernoem het formulier naar FrmPersonen en verander ook de naam van het formulier in de property Text van het formulier. 3 Plaats op het formulier de volgende Control rechtsonder op het venster: Naam

Eigenschap

Button

btnEinde

Text

Waarde Einde

BackColor

LightCoral

Anchor

Bottom, Right

T

Type

EP

Tabel 1: Control project voorbeeldDataSet

4 Dubbelklik op de Button btnEinde om in het code-venster te komen. Voeg de volgende regel toe aan het btnEinde_Click Event:

Aanmaken DataSet Blijf in het code-venster.

N

1

C

this.Close();

2 Maak de volgende globale variabele aan:

O

DataSet dataSetPersonen = new DataSet(“Personen”);

C

Je hebt nu een lege DataSet aangemaakt. In deze DataSet moeten DataTables aangemaakt worden, want daar wordt de informatie in opgeslagen. In dit voorbeeld maken we één tabel aan: de tabel Personen. We maken de tabel aan volgens de volgende data dictionary (beschrijving van tabellen en DataTables): DataTable-naam: dtPersonen Kolomnaam

Type

Lengte

Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

ID

int

-

PK

Ja

Ja

Nee

Roepnaam

string

15

-

-

-

-

Voorletters

string

10

-

-

-

-

Tussenvoegsels

string

7

-

-

-

-

Achternaam

string

25

-

-

-

-

Geboortedatum

DateTime

-

-

-

-

-

Tabel 2: Structuur DataTable dtPersonen


294

Deel 3: Databases

Aanmaken DataTable 3 Maak een methode aan van het type DataTable met de naam createDataTablePersonen(): private DataTable createDataTablePersonen()

4 Binnen deze methode (zorg voor accolades) maken we een tabel aan. Voeg de volgende regel toe aan de methode: DataTable dtPersonen = new DataTable(“Personen”); “Personen” is de naameigenschap van de nieuwe DataTable dtPersonen.

1

EP

T

Let op! In de naamgeving korten we hier dataTable af tot het voorvoegsel dt. Het wordt dus niet de naam dataTablePersonen maar de naam dtPersonen. Voor de duidelijkheid zullen we in dit boek vaak de typenaam toch voluit schrijven. Het is echter te doen gebruikelijk om twee- of drieletterige afkortingen te gebruiken als voorvoegsel bij namen (Hongaarse schrijfwijze).

Aanmaken DataColumns Nu maken we de sleutelkolom aan:

C

DataColumn ID = new DataColumn(“ID”, typeof(int));

N

Met het commando typeof kun je het type van de nieuw te maken kolom opgeven. Tussen de haakjes kun je elk C standaardtype invoeren.

O

2 Vervolgens passen we de eigenschappen aan van de kolom ID. Eerst de eigenschap om de kolom automatisch te laten ophogen (bij iedere nieuwe regel die wordt toegevoegd wordt de waarde voor ID automatisch berekend door bij de hoogste waarde voor ID één op te tellen). Voeg de volgende regel toe:

C

ID.AutoIncrement = true;

3 Het veld ID is uniek en moet altijd gevuld zijn. Het mag dus niet leeg zijn. Voeg de volgende regels toe: ID.AllowDBNull = false; ID.Unique = true;

4 We hebben nu een losse kolom. Deze moet toegevoegd worden aan de DataTable dtPersonen. Voeg de volgende regel toe: dtPersonen.Columns.Add(ID);

5 We kunnen kolommen ook gelijk aanmaken in de DataTable dtPersonen. Wel moeten we daarna nog de eigenschap MaxLength instellen voor de kolommen van het type string. Voeg de volgende regels toe: dtPersonen.Columns.Add(“Roepnaam”, typeof(string));


Hoofdstuk 2 Hoe werkt het?

295

dtPersonen.Columns[“Roepnaam”].MaxLength = 15; dtPersonen.Columns.Add(“Voorletters”, typeof(string)); dtPersonen.Columns[“Voorletters”].MaxLength = 10; dtPersonen.Columns.Add(“Tussenvoegsels”, typeof(string)); dtPersonen.Columns[“Tussenvoegsels”].MaxLength = 7; dtPersonen.Columns.Add(“Achternaam”, typeof(string)); dtPersonen.Columns[“Achternaam”].Unique = true; dtPersonen.Columns[“Achternaam”].MaxLength = 25; dtPersonen.Columns.Add(“Geboortedatum”, typeof(DateTime));

EP

// Instellen primary key. dtPersonen.PrimaryKey = new DataColumn[] {ID};

T

1

Aanmaken primary key / teruggeven DataTable Als de kolommen aangemaakt zijn moet de sleutel (primary key) van de tabel aangemaakt worden. Voeg de volgende regels toe:

Een primary key is een constraint, een beperking. Waarom een beperking? Een primaire sleutel moet uniek zijn: twee sleutels met dezelfde waarde mag dus niet. Vandaar dat een primaire sleutel tot de beperkingen (constraints) van een programma behoort.

N

return dtPersonen;

C

2 Tot slot geven we de gemaakte tabel terug aan de aanroepende methode. Voeg de volgende regel toe na InitializeComponent():

C

O

Initialiseren DataTable bij de start van het programma Bij de start van het programma moeten de DataSet en de DataTable aangemaakt worden. Dit kan op twee plekken: 1 Bij de constructor van het formulier (in dit geval de methode FrmPersonen()). 2 Bij het load event van het formulier (in dit geval de methode FrmPersonen_Load()). Microsoft beveelt aan om zoveel mogelijk de constructor te gebruiken. Het formulier is nog niet geladen en daardoor zal het laden sneller gaan.

1

Ga naar het code-venster en naar de constructor van het formulier (FrmPersonen()).

2 Voeg de volgende regels code toe na InitializeComponent(): // Aanmaken van de tabel in de DataSet. dataSetPersonen.Tables.Add(createDataTablePersonen());

Je ziet: hier wordt de methode createDataTablePersonen() aangeroepen met de Addmethode. De aangemaakte tabel wordt nu direct aan de DataSet toegevoegd.


296

Deel 3: Databases

Test je programma Hoewel je nog geen Controls aan je formulier hebt toegevoegd, kun je toch door het zetten van breaks je code controleren (debuggen).

Opdracht DataSet 1: Garage / tabel Auto’s Maak een nieuw project aan met de naam Garages. Maak binnen het formulier een DataSet aan met de naam dsGarage. Maak in de DataSet een DataTable aan volgens de volgende data dictionary: DataTable-naam: dtAutos Type

Lengte

Sleutel

Chassisnummer

string

-

PK

Kenteken

string

8

-

FabricageDatum

DateTime

DatumOpKenteken

DateTime

Merk

string

Type

string

Kleur

Color

Brandstof

string

AantalKilowatt

double

AantalPortieren

byte

Model

Moet uniek zijn

Mag leeg zijn Nee

-

Ja

-

EP

Ja

-

25

-

-

-

-

-

-

-

-

-

C

-

10

-

-

-

-

-

-

-

-

-

-

-

-

-

-

string

15

-

-

-

-

bool

-

-

-

-

-

-

-

-

-

-

-

-

-

25

-

-

-

-

N

-

O

Trekhaak

Automatisch ophogen

T

Kolomnaam

Tabel 3: Structuur DataTable dtAutos

C

Opdracht DataSet 2: Landen, tabellen en werelddelen

• •

Maak een nieuw project aan met de naam Landen. Maak binnen het formulier een DataSet aan met de naam dsLanden. Maak in de DataSet dsLanden twee DataTables aan: één met de naam dtLanden één met de naam dtWerelddelen volgens de volgende data dictionaries:


Hoofdstuk 2 Hoe werkt het?

297

DataTable-naam: dtLanden Mag leeg zijn

Moet uniek zijn

Type

Lengte

Sleutel

Automatisch ophogen

LandID

int

-

PK

Ja

Ja

Nee

KorteNaam

string

25

-

-

Ja

-

VolledigeNaam

string

100

-

-

Ja

-

AfkortingTweeLetters

string

2

-

-

Ja

-

AfkortingDrieLetters

string

3

-

-

Ja

-

Oppervlakte

double

-

-

-

-

-

OnafhankelijkheidsDatum

DateTime

-

-

-

-

-

Vlag

Image

-

-

-

-

-

Staatsvorm

string

20

-

-

-

-

Hoofdstad

string

35

-

Werelddeel

string

16

-

ErkendDoorVN

bool

-

-

T

Kolomnaam

Ja

-

-

-

-

-

-

EP

-

-

Tabel 4: Structuur DataTable dtLanden

WerelddeelID Naam

Lengte

Afbeelding

Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

int

-

PK

Ja

Ja

Nee

string

16

-

-

Ja

Nee

double

-

-

-

-

-

Image

-

-

-

-

-

O

Oppervlakte

Type

N

Kolomnaam

C

DataTable-naam: dtWerelddelen

C

Tabel 5: Structuur DataTable dtWerelddelen

Binding data aan controls

Er is nu een DataSet met een of meer DataTables. Echter, deze moet gebruikt worden door de Controls op het formulier. Dit doen we door gebruik te maken van een zogenaamde BindingSource. De BindingSource regelt de koppeling tussen Controls en de DataTable. De BindingSource houdt ook bij welke regel van de tabel geselecteerd is. Kortom: alle zaken die met de koppeling te maken hebben vind je in de BindingSource.


298

Deel 3: Databases

Voorbeeld 2: DataSet-koppeling (binding) met controls 1

Voorbereiding Open het programma van Voorbeeld 1: Nieuwe DataSet aanmaken met één DataTable.

2 Ga naar het design-venster. 3 Voeg de volgende Controls toe: Type

Naam

Label

Eigenschap

Waarde

Text

Roepnaam:

Text

Voorletters:

Text

Tussenvoegsels:

txbRoepnaam

TextBox Label

Label

Text

T

txbVoorletters

TextBox

Text

Geboortedatum:

Text

Zoeken

Achternaam:

EP

txbTussenvoegsels

TextBox Label

txbAchternaam

TextBox Label DateTimePicker

dtpGeboortedatum

GroupBox

grbZoeken

C

Plaats de volgende ComboBoxen en Labels in de GroupBox grbZoeken: Label ComboBox

ComboBox Label

Roepnaam:

Text

Achternaam:

Text

Geboortedatum:

cmbAchternaam

cmbGeboortedatum

O

ComboBox

N

Label

Text

cmbRoepnaam

Plaats de DatagridView onder de GroupBox grbZoeken:

C

DatagridView

dgvPersonen

Tabel 6: Toe te voegen Controls voorbeeld 2

Kijk voor de lay-out naar Schermafbeelding 1 op pagina 299.


299

EP

T

Hoofdstuk 2 Hoe werkt het?

C

Schermafbeelding 1: Voorbeeld Personen zonder databinding

N

Aanmaken DataSet De DataSet was al aangemaakt door de regel DataSet dataSetPersonen = new DataSet(“Personen”); toe te voegen. Ook had je de DataTable dtPersonen al toegevoegd aan de DataSet dataSetPersonen.

Aanmaken BindingSource Ga naar het code-venster.

C

1

O

Hier hoeven we dus niets te doen.

2 Voeg de volgende regel toe als globale variabele: BindingSource bindingSourcePersonen = new BindingSource();

Je hebt nu een BindingSource (verbinding), alleen moet die nog gekoppeld worden aan een DataSource (gegevensbron) zoals de DataTable Personen in de DataSet dataSetPersonen. 3 Ga naar de constructor (FrmPersonen()) en zoek de regel op waarin de DataTable Personen wordt aangemaakt: dataSetPersonen.Tables.Add(createDataTablePersonen());

4 Voeg na deze regel de volgende regels toe: // Koppelen van de BindingSource aan de DataTable Personen. bindingSourcePersonen.DataSource = dataSetPersonen.Tables[“Personen”];

Je hebt nu de DataTable gekoppeld aan de BindingSource bindingSourcePersonen.


300

Deel 3: Databases

Koppelen BindingSource met Controls Er is een DatatSet met een DataTable, er is een BindingSource die aan de DataTable gekoppeld is, nu kunnen de verschillende Controls aan één en dezelfde BindingSource gekoppeld worden. Het voordeel hiervan is dat alle Controls aan dezelfde regel van de DataTable gekoppeld worden. 1

Voeg direct na de regel waarin je de BindingSource aan de DataTable hebt gekoppeld de volgende regels toe om de BindingSource aan de TextBox txbRoepnaam te koppelen: // Koppelen van een TextBox aan een kolom. txbRoepnaam.DataBindings.Add(“Text”, bindingSourcePersonen, “Roepnaam”);

EP

T

De Add-methode van DataBindings kent drie parameters: – de eigenschap van de Control waaraan het veld gekoppeld wordt, in dit geval is dat de eigenschap Text; – de BindingSource die gebruikt wordt om te koppelen; – de kolomnaam van de DataTable waaraan gekoppeld wordt.

C

2 Nu gaan we de kolom Achternaam koppelen aan een ComboBox. Hiermee krijg je een uitklaplijstje met waarden uit de DataTable waar je uit kunt kiezen. Vervolgens zoekt de BindingSource die regel waar de gekozen waarde voorkomt en maakt van deze regel de actuele regel (CurrentRow). Voeg de volgende regels toe:

N

// Koppelen van een kolom aan een ComboBox. cmbAchternaam.DataSource = bindingSourcePersonen; cmbAchternaam.DisplayMember = “Achternaam”;

O

In dit geval moeten er twee eigenschappen ingesteld worden: – De DataSource die direct gekoppeld wordt aan de BindingSource. – De DisplayMember die gekoppeld wordt aan de kolom.

C

3 Bij de vorige twee voorbeelden hebben we steeds één kolom aan één veld gekoppeld. Nu gaan we de hele DataTable koppelen aan een Control: de DataGridView. Je zult zien dat deze automatisch gevuld wordt met kolommen die de gebruiker ook kan gebruiken om te sorteren. Voeg de volgende regels toe: // Koppelen van de hele tabel aan een DataGridView. dgvPersonen.DataSource = bindingSourcePersonen;

1

Test het programma Test het programma door in de DataGridView enkele personen toe te voegen. Als je verkeerde waardes invoert krijg je zeer uitgebreide foutmeldingen. Ook zijn er nog situaties waarin het programma zal crashen, maar het gaat hier om het leren van het principe.


Hoofdstuk 2 Hoe werkt het?

301

Let op! Test met data binnen de marges van je DateTimePicker, want als je een geboortedatum vóór 1753 invult, krijg je géén foutmelding, maar werkt je programma toch niet.

N

C

EP

T

2 Je programma zal er als volgt uitzien:

Schermafbeelding 2: Voorbeeld Personen met databinding

O

Omdat maar een paar Controls zijn gekoppeld, veranderen alleen die Controls mee als je een andere regel selecteert.

C

Opdracht Binding 1: Koppelen van controls Maak het voorbeeld compleet door alle niet gekoppelde TextBoxen en ComboBoxen te koppelen aan de juiste kolommen. Koppel de DateTimePicker dtpGeboortedatum op dezelfde manier als een TextBox.

Opdracht Binding 2: Garages Pas het programma van Opdracht DataSet 1: Garage / tabel Auto’s zo aan dat er een DataGridView gebruikt wordt waarin alle kolommen van de DataTable Autos getoond wordt.


302

Deel 3: Databases

Zorg dat alle kolommen in TextBoxen getoond worden. Maak voor de eigenschap kleur gebruik van een ColorDialogBox en Color.ToString (zie Programmeren in C deel 2: Gegevens) om een kleur te selecteren. Dat hoeft nog niet in de DataGridView. Maak gebruik van vier ComboBoxen om te kunnen zoeken op vier eigenschappen van een auto.

1-op-n master-detail-binding

EP

T

Tot nu toe hebben we steeds één kolom van één DataTable gekoppeld. In de praktijk komen we echter veel zogenaamde 1-op-n-koppelingen tegen. Dit wordt ook wel een ‘één-op-veel’ (one to many) relatie genoemd: één master is verbonden met véél details. Hierbij wordt een kolom eigenlijk aan de primaire sleutel van een andere kolom gekoppeld, zoals bijvoorbeeld: – een factuur kent meerdere factuurregels; – een rugbyteam kent een aantal spelers; – een werelddeel bestaat uit een aantal landen.

N

C

Ook dit kunnen we regelen met een BindingSource. Alleen moeten we daarvoor wel de relaties tussen de DataTables aanmaken. In zo’n relatie bepaal je op welke manier twee DataTables gekoppeld worden. In het voorbeeld wordt een aantal rugbyspelers gekoppeld aan een team. Het team (één) noemen we de ‘master’, de rugbyspelers (veel) zijn de ‘details’.

Voorbeeld 3: 1-op-n (1:n) koppeling Voorbereiding Maak een project aan met de naam voorbeeldRugbyteams.

O

1

2 Hernoem het formulier naar FrmRugby.

C

3 Geef het formulier de titel Rugby. 4 Plaats de volgende Controls op het formulier: Type

Naam

Label TextBox

Text

Trainer:

txbTrainer

Label Label

Waarde Teamnaam:

txbTeam

Label TextBox

Eigenschap Text

lblTeamID

Text

Team ID:

AutoSize

False

BackColor

ActiveBorder

ForeColor

ButtonHighlight

Size

36; 21


Hoofdstuk 2 Hoe werkt het?

Type

Button

Button

Naam

Eigenschap

btnVoegToe

btnVerwijder

Label ComboBox

303

cmbKlasse

Waarde

Text

(geen)

TextAlign

MiddleCenter

Text

+

BackColor

PaleTurquoise

Size

26; 23

Text

-

BackColor

LightCoral

Size

26; 23

Text

Klasse

Items (Collection)

Ereklasse Eerste klasse Tweede klasse

T

Derde klasse

Vierde klasse

ComboBox

Text cmbSoortRugby

Soort rugby:

EP

Label

Items (Collection)

Rugby Union

Rugby League Rugby Sevens Ten-a-Side

Touch Rugby

Button

btnEerste

btnVorige

O

Button

txbAantalSpelers

btnVolgende

C

Button

Button

Data-

btnLaatste

Aantal spelers:

ReadOnly

True

Size

36; 20

Text

<<

BackColor

PaleTurquoise

N

TextBox

Text

C

Label

Size

29; 23

Text

<

BackColor

PaleTurquoise

Size

26; 23

Text

>

BackColor

PaleTurquoise

Size

26; 23

Text

>>

BackColor

PaleTurquoise

Size

29; 23

dgvSpelers

Size

727; 389

btnEinde

Text

Einde

BackColor

LightCoral

GridView Button

Tabel 7: Controls project voorbeeldRugbyteams

5 Als het goed is ziet je programma er als volgt uit:


Deel 3: Databases

Schermafbeelding 3: Rugbyteams

Aanmaken DataSet en de DataBindingSources Ga naar het code-venster.

C

1

EP

T

304

2 Maak de volgende globale variabele aan:

O

N

DataSet dataSetRugby = new DataSet(“Rugby”); BindingSource masterBindingSourceRugbyteams = new BindingSource(); BindingSource detailBindingSourceSpelers = new BindingSource();

C

Aanmaken DataTables De lege DataSet is aangemaakt. Nu moeten de master-detail-DataTables dtRugbyteams en dtSpelers aangemaakt worden. Dit doen we zoals beschreven is bij Voorbeeld 1: Nieuwe DataSet aanmaken met één DataTable:

1

Maak een methode aan met de naam createDataTableRugbyteams() en maak binnen deze methode een DataTable aan volgens de volgende data dictionary:


Hoofdstuk 2 Hoe werkt het?

305

DataTable-naam: dtRugbyteams Kolomnaam

Type

Lengte

Sleutel

Automatisch Moet ophogen uniek zijn

Mag leeg zijn

ID

int

-

PK

Ja

Ja

Nee

Teamnaam

string

50

-

-

Ja

Nee

Trainer

string

50

-

-

-

-

Klasse

string

13

-

-

-

-

SoortRugby

string

12

-

-

-

-

AantalSpelers

byte

-

-

-

-

-

Vergeet niet de primary key (PK) aan te maken!

T

Tabel 8: Structuur DataTable dtRugbyteams

DataTable-naam: dtSpelers Type

ID

int

Roepnaam Voorletters

Lengte

Sleutel

Automatisch Moet ophogen uniek zijn

C

Kolomnaam

EP

2 Maak een methode aan met de naam createDataTableSpelers() en maak binnen deze methode een DataTable aan volgens de volgende data dictionary:

Mag leeg zijn

PK

Ja

Ja

Nee

string

15

-

-

-

-

string

10

-

-

-

-

Tussenvoegsels

string

7

-

-

-

-

Achternaam

-

-

-

Nee

N

-

25

DateTime

-

-

-

-

-

Rugnummer

byte

-

-

-

-

-

TeamID

int

-

FK

-

-

Nee

C

O

string

Geboortedatum

Tabel 9: Structuur DataTable dtSpelers

Je hebt nu twee BindingSources en twee DataTables. Alleen moet de relatie tussen die twee DataTables nog gemaakt worden. Deze relatie noemen we een foreign key (vreemde sleutel). In de tabel wordt dit aangegeven met de letters FK. Helaas: een computer moet je alles, maar dan ook alles ‘vertellen’.

1

Aanmaken DataRelation Maak een nieuwe methode met de naam createDataRelation() en van het type void. Een master-detail-relatie maak je door twee kolommen met soortgelijke informatie aan elkaar te koppelen. De kolom in de master-DataTable noemen we de


306

Deel 3: Databases

parent(ouder)-kolom, de kolom in de detail-DataTable is de child(kind)-kolom. Voeg aan de methode createDataRelation() de volgende regel toe: DataColumn dataColumnParent, dataColumnChild;

2 De nieuwe DataColumn-variabelen gaan we nu koppelen aan de desbetreffende kolom uit de twee DataTables. Voeg de volgende regels toe: dataColumnParent = dataSetRugby.Tables[“Rugbyteams”].Columns[“ID”]; dataColumnChild = dataSetRugby.Tables[“Spelers”].Columns[“TeamID”];

3 Nu kunnen we op basis van deze kolommen een relatie maken. De relatie krijgt een naam zodat je deze later in je programma kunt aanroepen:

T

DataRelation dataRelationTeamSpeler = new DataRelation(“relationTeamSpeler”, dataColumnParent, dataColumnChild);

EP

Let op! De datatypes van deze kolommen moeten gelijk zijn, anders krijg je een foutmelding. 4 De relatie moet nog wel toegevoegd worden aan de DataSet. Voeg de volgende regel toe:

C

dataSetRugby.Relations.Add(dataRelationTeamSpeler);

Voeg de volgende regel toe aan de methode createDataRelation():

C

1

O

N

Aanmaken foreign key relation Een foreign key relation zorgt ervoor dat de data-integriteit behouden blijft. Data wat? Integriteit. Dit betekent bijvoorbeeld dat als je een team wilt verwijderen, dit niet mogelijk is zolang er nog spelers zijn die in dat team spelen. Bij de theorie over databases wordt je precies uitgelegd welke mogelijkheden er allemaal zijn. Hier leer je hoe je deze regels (constraints) in C programmeert.

ForeignKeyConstraint foreignKeyConstraintTeamSpeler = dataRelationTeamSpeler.ChildKeyConstraint;

2 Je maakt een koppeling tussen de ChildKeyConstraint van de relatie die je net hebt aangemaakt. De regels die voor deze koppeling gelden, stellen we hieronder in. 3 Je mag geen team verwijderen als er nog spelers in dat team spelen. Voeg de volgende regel toe: foreignKeyConstraintTeamSpeler.DeleteRule = Rule.None;

4 Mocht de ID van het team veranderen dan moet het teamID van alle spelers die in dat team spelen meeveranderen. Voeg de volgende regel toe: foreignKeyConstraintTeamSpeler.UpdateRule = Rule.Cascade;


Hoofdstuk 2 Hoe werkt het?

307

5 Verder hoef je niets te doen. Doordat de ForeignKeyConstraint gekoppeld is aan de ChildKeyConstraint van de datarelatie worden de gemaakte instellingen automatisch doorgegeven. Toevoegen DataSet, DataTables en DataRelations aan het programma Het voorwerk is gedaan. Nu gaan we ervoor zorgen dat bij het openen van het formulier de DataSet ingericht wordt. 1

Ga naar de constructor van het formulier FrmRugby en voeg de volgende regels toe na de regel InitializeComponent(); om de tabellen aan te maken: dataSetRugby.Tables.Add(createDataTableRugbyteams()); dataSetRugby.Tables.Add(createDataTableSpelers());

T

2 Vervolgens maak je de relaties aan. Voeg de volgende regels toe binnen de constructor:

1

EP

// Aanmaken relaties. createDataRelation();

Toevoegen en instellen van BindingSources Eerst koppelen we de hoofd-DataTable (Rugbyteams) aan de master-BindingSource. Voeg de volgende regels toe:

C

// Bind de master-dataconnector aan de Rugbyteams-datatable. masterBindingSourceRugbyteams.DataSource = dataSetRugby.Tables[“Rugbyteams”];

N

2 Nu kunnen we alle Controls die horen bij de master-DataTable (dtRugbyteams) koppelen aan de BindingSource masterBindingSourceRugbyteams. Voeg de volgende regels toe:

O

// Koppelen van diverse Controls aan de master-DataTable.

C

txbAantalSpelers.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “AantalSpelers”); txbTeam.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “Teamnaam”); txbTrainer.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “Trainer”); lblTeamID.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “ID”); cmbKlasse.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “Klasse”); cmbSoortRugby.DataBindings.Add(“Text”, masterBindingSourceRugbyteams, “SoortRugby”);

3 De koppeling met de master-DataTable Rugbyteams is klaar. Nu moet ook de detail-DataTable gekoppeld worden. De DataSource-eigenschap van de detailBindingSourceSpelers wordt gekoppeld aan de masterBindingSourceRugbyteams! Want via de DataRelation relationTeamSpelers wordt de verbinding gemaakt met de detail-DataTable Spelers. Voeg de volgende regels toe:


308

Deel 3: Databases

// Bind de detail-dataconnector aan de master-dataconnector, // met behulp van de DataRelation-naam zodat de informatie in de detail// DataTable zo gefilterd wordt dat alleen die informatie getoond wordt // die hoort bij de huidige rij in de master-DataTable. detailBindingSourceSpelers.DataSource = masterBindingSourceRugbyteams; detailBindingSourceSpelers.DataMember = “relationTeamSpeler”;

4 Tot slot koppelen we de DataGridView dgvSpelers aan de detailBindingSourceSpelers. Voeg de volgende regel toe: dgvSpelers.DataSource = detailBindingSourceSpelers;

Koppelen BindingSource met Controls Rest ons nog code toe te voegen aan diverse knoppen. Voeg code toe aan de knop btnVoegToe zodat er een nieuw team gemaakt kan worden. Ga naar het design-venster en dubbelklik op de knop btnVoegToe.

T

1

EP

2 Voeg aan de methode btnVoegToe_Click() de volgende regel toe: masterBindingSourceRugbyteams.AddNew();

C

3 Ga naar het Enter event van dgvSpelers door de DataGridView te activeren en in het Properties-venster op het bliksemschicht-tabblad te klikken. Dubbelklik op het woord Enter en voeg in het code-venster de volgende regel toe aan de methode dgvSpelers_Enter: masterBindingSourceRugbyteams.EndEdit();

N

4 Ga naar het design-venster en dubbelklik op de knop btnVerwijder. 5 Voeg aan de methode btnVerwijder_Click() de volgende regels toe:

O

try {

C

masterBindingSourceRugbyteams.RemoveCurrent(); } catch (Exception exception) { MessageBox.Show(exception.Message, “Verwijderen mislukt”, MessageBoxButtons.OK, MessageBoxIcon.Stop); }

Er moet hier gebruik worden gemaakt van een try-catch-blok omdat er fouten kunnen ontstaan. Zo kan het gebeuren dat een team niet verwijderd mag worden omdat er nog spelers verbonden zijn met het team. 6 Ga naar het design-venster en dubbelklik op de knop btnEerste. 7 Voeg aan de methode btnEerste_Click() de volgende regel toe: masterBindingSourceRugbyteams.MoveFirst();


Hoofdstuk 2 Hoe werkt het?

309

8 Voeg op dezelfde manier ook de juiste code toe aan de Buttons btnVolgende, btnVorige en btnLaatste. Toevoegen ToolTips aan Controls In dit programma wordt gebruik gemaakt van eenvoudig vormgegeven knoppen. De knop btnVerwijder heeft slechts een – teken als opschrift. Om de gebruiker te helpen gaan we deze knop van een zogenaamde ToolTip voorzien. Als de gebruiker met de muis over de knop beweegt, verschijnt er een tekst naast de knop. 1

Ga naar het design-venster.

2 Zoek in de Toolbox het item ToolTip op en sleep dit op het formulier FrmRugby.

3 Ga naar het properties-venster van toolTip1.

EP

4 Stel de eigenschap initialDelay in op 100.

T

Onderaan het venster wordt het object toolTip1 toegevoegd.

5 Stel de eigenschap reshowDelay in op 100.

Met deze eigenschappen bepaal je hoe lang het duurt (in milliseconden) voordat een tekst verschijnt.

C

Ga naar de knop btnVerwijder en ga naar het properties-venster.

N

6 Ga naar de Events-tab en dubbelklik op MouseHover. Je komt nu in de btnVerwijder_MouseHover-methode. 7 Voeg de volgende regel toe:

O

toolTip1.SetToolTip(btnVerwijder, “Verwijder een team uit de DataSet.”);

C

Het ToolTip-object werkt net zoals de ErrorProvider. Je geeft de naam aan van de Control en de tekst van de boodschap die getoond moet worden. Vervolmaken van het programma Als je kiest voor een bepaalde soort rugby staat het aantal spelers van een team vast. Het is dus handig als het programma dit bepaalt op basis van de geselecteerde rugbysoort.

1

Ga naar het code-venster en maak een methode aan met de volgende handtekening: private byte getAantalSpelers(string soortRugby)

2 Voeg de volgende code toe tussen de accolades van de methode om op basis van de geselecteerde rugbysoort het aantal spelers te bepalen:


Deel 3: Databases

EP

N

C

byte aantalSpelers = 0; switch (soortRugby) { case “Rugby Union”: { aantalSpelers = 15; break; } case “Rugby League”: { aantalSpelers=13; break; } case “Rugby Sevens”: { aantalSpelers = 7; break; } case “Ten-a-Side”: { aantalSpelers = 10; break; } case “Touch Rugby”: { aantalSpelers = 6; break; } default: aantalSpelers = 0; break; } return aantalSpelers;

T

310

O

3 Ga naar het design-venster en dubbelklik op de ComboBox cmbSoortRugby. 4 Voeg de volgende regels toe:

C

if (cmbSoortRugby.SelectedItem.ToString() != null) { txbAantalSpelers.Text = getAantalSpelers(cmbSoortRugby.SelectedItem.ToString()).ToString(); }

• • • • • • •

Test het programma Test het programma op de volgende manier: Start het programma. Klik op de Button btnVoegToe. Vul alle velden in (dus de TextBoxen, ComboBoxen én de DataGridView). Klik op Volgende. Vul een aantal spelers in. Voer meerdere teams in en meerdere spelers. Probeer een team te verwijderen dat nog spelers heeft. Als het goed is krijg je de volgende foutmelding:


Hoofdstuk 2 Hoe werkt het?

311

Schermafbeelding 4: Foutmelding relatie

•

Ook in dit programma is niet alles perfect, maar ook hier gaat het om de basisprincipes.

T

Opdracht 1-op-n-koppeling 1: Facturen

• • • • • • • •

Op de factuurregels staan alle artikelen vermeld met de aantallen en prijzen: Regelnummer Artikelnummer Artikelnaam Artikelprijs Aantal Totaalprijs BTW-bedrag Totaal bedrag (Totaalprijs + BTW-bedrag)

C

O

N

C

EP

• • • • • • • •

Maak een programma dat de master-detail-relatie factuur-factuurregels laat zien. Een factuur kent de volgende onderdelen: Factuurnummer Factuurdatum Naam, adres, postcode en woonplaats van de klant Klantnummer E-mailadres Telefoonnummer Totaalbedrag Totaal BTW-bedrag

Een factuur kent meerdere factuurregels, iedere factuurregel hoort bij één factuur. Het regelnummer is het ID.

Opdracht 1-op-n-koppeling 2: Wielerploeg Maak een programma met een master-detail-relatie over wielrenners en wielerploegen. Een wielerploeg kent de volgende kenmerken: ploegleider, sponsor, naam. Van de wielrenners moeten alle persoonskenmerken vastgelegd worden.


312

Deel 3: Databases

Opdracht 1-op-n-koppeling 3: Provincies Maak een programma met een dubbele master-detail-relatie. Het programma moet de volgende informatie tonen: Provincies: Naam, provinciehoofdstad, aantal km2, aantal inwoners Gemeente: Naam, aantal inwoners, aantal km2, plaats waar het gemeentehuis staat Buurt/dorp: Naam, aantal inwoners, aantal km2 Een provincie is gekoppeld aan meerdere gemeentes, een gemeente aan meerdere dorpen/buurten. Het programma kent tenminste twee DataGridViews, één voor de gemeente en één voor de dorpen/buurten.

T

Look-up-tabellen in een DataGridView

C

EP

In het vorige onderdeel heb je geleerd hoe je een master-detail-koppeling tot stand brengt. Een ander soort koppeling is een look-up-tabel. Bij een master-detailkoppeling vul je eerst de hoofdgegevens in en daarna de details. Bij een look-up DataTable heb je een DataTable met waardes die je vaker gebruikt. Denk bijvoorbeeld aan een tabel met alle woonplaatsen van Nederland. Als je de geboorteplaats van een persoon invult, wil je namelijk graag dat die geboorteplaats bestaat en dat deze correct gespeld is. Dit doe je met een look-up DataTable.

Voorbeeld 4: Look-up DataTable

Voorbereiding Maak een kopie van het project van Opdracht Binding 1: Koppelen van controls.

N

1

O

2 Hernoem het kopieproject naar LookUpTable. 3 Pas de namen van de diverse onderdelen aan zoals je in deel 2 geleerd hebt.

C

4 Geef het formulier de volgende naam: FrmLookUpTable.

5 Voeg de volgende Control toe: Type

Naam

ComboBox

cmbGeboorteplaats

Eigenschap

Waarde

Tabel 10: Toe te voegen control look-up-tabel

6 Pas de breedte van het venster en de DataGridViewPersonen zo aan dat het geheel goed op het scherm past. Het resultaat ziet er dan als volgt uit:


313

EP

T

Hoofdstuk 2 Hoe werkt het?

Schermafbeelding 5: Look-up DataTable

1

Wijzigen createDataTablePersonen Voeg aan de methode createDataTablePersonen() de volgende twee regels toe:

C

dtPersonen.Columns.Add(“Geboorteplaats”, typeof(string)); dtPersonen.Columns[“Geboorteplaats”].MaxLength = 28;

Maak na de methode createDataTablePersonen() de methode createDataTablePlaatsen() aan van het type DataTable door de volgende regels toe te voegen:

C

1

O

N

Aanmaken look-up DataTable We maken een look-up DataTable aan met de naam DataTablePlaatsen waarin we de geboorteplaats kunnen opzoeken.

private DataTable createDataTablePlaatsen() { }

2 Maak een DataTable-variabele aan die teruggegeven kan worden aan het aanroepende programma door de volgende regel toe te voegen aan de methode createDataTablePlaatsen(): DataTable dtPlaatsen = new DataTable(“Plaatsen”);

3 Voeg de volgende regels toe voor de kolom met plaatsnamen die ook de primaire sleutel wordt: // Aanmaken Primary Key kolom. DataColumn Plaatsnaam = new System.Data.DataColumn(“Plaatsnaam”, typeof(string));


314

Deel 3: Databases

4 Omdat in de DataTable dtPlaatsen de kolom Plaatsnaam de primaire sleutel is, mag iedere plaatsnaam maar één keer voorkomen. Met andere woorden: de kolom Plaatsnaam is uniek. Voeg de volgende regel toe: Plaatsnaam.Unique = true;

5 Een primaire sleutel moet altijd gevuld zijn. Voeg daarom de volgende regel toe: Plaatsnaam.AllowDBNull = false;

6 De langste plaatsnaam in Nederland is Westerhaar-Vriezenveensewijk. Deze naam is inclusief het koppelteken 28 karakters lang. Voeg de volgende regel toe om de lengte van de kolom Plaatsnaam in te stellen:

T

Plaatsnaam.MaxLength = 28;

EP

7 Eerst voegen we de kolom Plaatsnaam toe aan de DataTable dtPlaatsen. Voeg de volgende regels toe: // Toevoegen van de kolom aan de tabel. dtPlaatsen.Columns.Add(Plaatsnaam);

C

8 Nu kunnen we de eigenschap PrimaryKey van de DataTable dtPlaatsen instellen. Voeg de volgende regels toe: // Aanmaken en toevoegen van de primaire sleutel regel (constraint). dtPlaatsen.PrimaryKey = new DataColumn[] { Plaatsnaam };

N

9 Tot slot wordt de DataTable dtPlaatsen teruggegeven aan het aanroepende programma.

O

Voeg de volgende regel toe: return dtPlaatsen;

C

Vullen van de look-up DataTable dtPlaatsen In dit voorbeeld wordt de DataTable dtPlaatsen gevuld met wat voorbeeldgegevens.

1

Maak een nieuwe methode aan met de naam fillPlaatsen().

private void fillPlaatsen() { }

2 We gaan tien plaatsnamen toevoegen aan de DataTable. Hiervoor gebruiken we de standaardmethode loadDataRow(). Als parameter moeten we een Array van objecten meegeven waarin we voor elke kolom een waarde moeten meegeven. Waarom een Array van objecten? Op deze manier kunnen we allerlei verschillende datatypes in één keer aangeven. Met de tweede parameter wordt aangegeven of de DataTable wijzigingen accepteert. Deze variabele zetten we op true.


Hoofdstuk 2 Hoe werkt het?

315

Voeg de volgende regels toe aan de methode fillPlaatsen(): // Toevoegen gegevens aan een DataTable. dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Aadorp”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Amsterdam”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Arnhem”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Assen”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Nieuwkoop”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Vlissingen”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Zundert”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Appelscha”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Lelystad”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{“Haarlem”}, true);

1

T

Toevoegen DataTable dtPlaatsen aan de DataSet dataSetPersonen Zoek de constructor op met de naam FrmLookUpTable(). Zoek de volgende regel op:

EP

dataSetPersonen.Tables.Add(createDataTablePersonen());

2 Voeg na deze regel de volgende regels toe:

Instellen BindingSource Voeg de volgende globale variabele toe na de initialisatie van de bindingSourcePersonen:

N

1

C

dataSetPersonen.Tables.Add(createDataTablePlaatsen()); // Vullen van de plaatsen-DataTable. fillPlaatsen();

BindingSource bindingSourcePlaatsen = new BindingSource();

O

2 Zoek binnen de constructor FrmLookUpTable de volgende regel op: bindingSourcePersonen.DataSource = dataSetPersonen.Tables[“Personen”];

C

en voeg de volgende regel direct na deze regel toe om de BindingSource bindingSourcePlaatsen te koppelen aan de DataTable Plaatsen: bindingSourcePlaatsen.DataSource = dataSetPersonen.Tables[“Plaatsen”];

Instellen ComboBox Een ComboBox is een Control waarbij de gebruiker kan kiezen uit een aantal waardes. De gekozen waarde wordt weergegeven in de eigenschap SelectedValue.

De te kiezen waarden staan in de DataTable Plaatsen, de geselecteerde waarde hoort bij de DataTable Personen. 1

Zoek de volgende regel op in de constructor FrmLookUpTable: cmbGeboorteDatum.DisplayMember = “GeboorteDatum”; en voeg de volgende regel toe

om de ComboBox cmbGeboorteplaats te binden aan de DataTable Plaatsen: cmbGeboorteplaats.DataSource = bindingSourcePlaatsen;


316

Deel 3: Databases

2 De DisplayMember is de kolom van de DataTable die je wilt laten zien aan de gebruiker. Omdat de DataTable Plaatsen slechts één kolom kent is het de kolom Plaatsnaam. Voeg de volgende regel toe: cmbGeboorteplaats.DisplayMember = “Plaatsnaam”;

3 De ValueMember is de kolom van de DataTable die uiteindelijk gekopieerd wordt naar de doel-DataTable, in dit geval de DataTable Personen. Omdat de DataTable maar één kolom kent voegen we ook hier de kolom Plaatsnaam toe. Voeg de volgende regel toe: cmbGeboorteplaats.ValueMember = “Plaatsnaam”;

T

4 Door de geselecteerde waarde (SelectedValue) te verbinden met de DataTable Personen zorg je ervoor dat de ValueMember als het ware gekopieerd wordt naar de aangegeven kolom. Voeg de volgende regel toe:

EP

cmbGeboorteplaats.DataBindings.Add(“SelectedValue”, bindingSourcePersonen, “Geboorteplaats”);

1

C

Instellen lay-out DataGridView Er is een ‘losse’ ComboBox toegevoegd. Omdat gegevens ook via de DataGridView kunnen worden toegevoegd, moet ook daar een ComboBox gebruikt worden. Standaard wordt echter van een tekstveld een soort TextBox gemaakt. We moeten dus zelf de dgvPersonen gaan opmaken. Maak een nieuwe methode aan met de naam initDataGridViewPersonen().

N

private void initDataGridViewPersonen() { }

O

2 Eerst zetten we het automatisch genereren van kolommen uit. Voeg de volgende regel toe aan de methode initDataGridViewPersonen():

C

dgvPersonen.AutoGenerateColumns = false;

3 Nu gaan we per kolom de opmaak en naamgeving regelen. Dit doen we op een nieuwe manier. Met het codewoord new wordt een nieuwe DataGridViewTextBoxColumn aangemaakt. Vervolgens maken we kennis met een nieuwe schrijfwijze. Tussen twee accolades kun je de eigenschappen van het object instellen. De eigenschappen worden van elkaar gescheiden door een komma, geen puntkomma. Voeg de volgende regels toe voor de opmaak van de kolom ID: dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “ID”, HeaderText = “ID”,


Hoofdstuk 2 Hoe werkt het?

317

ValueType = typeof(int), ReadOnly =true, Width = 30 });

De kolom ID zetten we op ReadOnly omdat deze automatisch gegenereerde waardes bevat. Per kolom stellen we de breedte in zodat we de hele lay-out naar onze hand zetten. Let op! De gebruikte namen moeten overeenkomen met de gebruikte namen van de DataTables. Anders crasht het programma.

T

4 Om de andere kolommen in te stellen moeten de volgende regels toegevoegd worden:

C

O

N

C

EP

dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “Roepnaam”, HeaderText = “Roepnaam”, ValueType = typeof(string), Width = 150 }); dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “Voorletters”, HeaderText = “Voorletters”, ValueType = typeof(string), Width = 75 }); dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “Tussenvoegsels”, HeaderText = “Tussenvoegsels”, ValueType = typeof(string), Width = 80 }); dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “Achternaam”, HeaderText = “Achternaam”, ValueType = typeof(string), Width = 250, AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill }); dgvPersonen.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = “Geboortedatum”, HeaderText = “Geboortedatum”, ValueType = typeof(DateTime), Width = 110 });


318

Deel 3: Databases

Als je het goed hebt gedaan is bij de kolom Achternaam de eigenschap AutoSizeMode ingesteld op DataGridViewAutoSizeColumnMode.Fill. Dit zorgt ervoor dat deze kolom automatisch alle eventueel overblijvende ruimte binnen de DataGridView gebruikt. Het heeft alleen zin om deze eigenschap bij één kolom in te stellen. Waarom? Instellen ComboBox in een DataGridView We moeten eerst een DataGridViewComboBoxColumn aanmaken, vervolgens instellen om hem tot slot toe te voegen aan de DataGridView. 1

Voeg de volgende regels toe om de DataGridViewComboBoxColumn aan te maken: // GeboorteplaatsComboBox toevoegen. DataGridViewComboBoxColumn geboorteplaatsComboBoxColumn = new DataGridViewComboBoxColumn();

EP

T

2 Voeg de volgende regel toe zodat er een koppeling komt tussen de DataGridViewComboBoxColumn en de kolom van de DataTable Personen die aan de DataGridView dgvPersonen is gebonden: geboorteplaatsComboBoxColumn.DataPropertyName = “Geboorteplaats”;

3 Voeg de volgende regel toe om de kop (header) van de DataGridView-kolom in te stellen:

C

geboorteplaatsComboBoxColumn.HeaderText = “Geboorteplaats”;

N

Het type van de DataGridView-kolom moet gelijk zijn aan het type van de kolom van de DataTable die aan de DataGridView is gekoppeld. Voeg de volgende regel toe: geboorteplaatsComboBoxColumn.ValueType = typeof(string);

O

4 De volgende drie stappen hebben betrekking op de koppeling met de look-up-tabel Plaatsen waarmee de DataGridViewComboBoxColumn wordt gekoppeld.

C

Eerst koppelen we DataGridViewComboBoxColumn aan de DataTable Plaatsen. Voeg de volgende regels toe: // Koppeling met de DataTable waarin de look-up-data staan. geboorteplaatsComboBoxColumn.DataSource = dataSetPersonen.Tables[“Plaatsen”];

5 Vervolgens geef je het item van de look-up-tabel aan dat je aan de gebruiker wilt laten zien. Voeg de volgende regels toe: // Het item van deze tabel dat je aan de gebruiker wilt laten zien. geboorteplaatsComboBoxColumn.DisplayMember = “Plaatsnaam”;

6 Je moet ook aangeven vanuit welke kolom van de look-up DataTable de waarde overgenomen moet worden in de hoofd-DataTable (in dit geval de DataTable Personen). Omdat de DataTable Plaatsen maar één kolom kent is deze dus dezelfde als bij stap 5. Voeg de volgende regels toe:


Hoofdstuk 2 Hoe werkt het?

319

// Het item waarvan je de waarde kopieert naar de DataTable van de DataGridView. geboorteplaatsComboBoxColumn.ValueMember = “Plaatsnaam”;

7 De koppeling is gereed. Nu moeten we de stijl nog instellen. Voeg de volgende regel toe: geboorteplaatsComboBoxColumn.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox;

8 Net zoals bij de vorige DataGridView-kolommen geven we ook hier de breedte aan. Voeg de volgende regel toe: geboorteplaatsComboBoxColumn.Width = 120;

9 Als laatste voegen we de nieuwe DataGridViewComboBox toe aan de DataGridView dgvPersonen. Voeg de volgende regel toe:

T

dgvPersonen.Columns.Add(geboorteplaatsComboBoxColumn);

1

C

EP

De laatste hand Normaal gesproken wordt de DataGridView automatisch opgemaakt op basis van de gekoppelde DataTable. Zodra de opmaak is ingesteld kan deze niet meer gewijzigd worden. Daarom moet de opmaak ingesteld worden, in ons geval door de methode initDataGridViewPersonen() uit te voeren, voordat de DataGridView dgvPersonen gekoppeld wordt aan de DataTable Personen. Open de constructor FrmLookUpTable() en zoek het volgende blok code op waarin de beide DataTables worden aangemaakt:

O

N

// Koppelen van de bindingSource aan de datatable Personen. bindingSourcePersonen.DataSource = dataSetPersonen.Tables[“Personen”]; bindingSourcePlaatsen.DataSource = dataSetPersonen.Tables[“Plaatsen”];

2 Voeg direct na deze regels de volgende regels toe:

C

// Initialiseren DataGridView dgvPersonen. initDataGridViewPersonen();

Let op! Deze regels moeten vóór de volgende regel worden uitgevoerd: dgvPersonen.DataSource = bindingSourcePersonen;

1

Test je programma Het formulier van je programma ziet er nu als volgt uit:


Deel 3: Databases

EP

T

320

Schermafbeelding 6: Look-up-tabel met ComboBox

2 Test het programma.

C

Opdracht look-up DataTable 1: Facturen

N

Pas het programma uit Opdracht 1-op-n-koppeling 1: Facturen zo aan dat de klantwoonplaats vervangen wordt door een ComboBox met daarin alle Europese hoofdsteden als keuzelijst.

Maak een programma waarin de volgende informatie over een auto getoond wordt in een DataGridView: Merk: een keuzelijst met alle automerken Uitvoering: een keuzelijst waarbij gekozen kan worden uit: sedan, cabriolet, stationwagen en MPV Kleur Type Brandstof: een keuzelijst waarbij gekozen kan worden uit benzine, diesel, gas, hybride en elektrisch Prijs

C

• •

O

Opdracht look-up DataTable 2: Auto’s

• • • •

Value & display members In het vorige voorbeeld heb je gezien hoe je een ComboBox koppelt aan een zogenaamde look-up DataTable. De look-up DataTable Plaatsen bestaat uit maar één


Hoofdstuk 2 Hoe werkt het?

321

kolom. Het gevolg hiervan is dat alle waardes van de DataTable Plaatsen ook in de DataTable Personen worden opgenomen. Iedere plaatsnaam is 28 tekens lang, gevuld of niet. Dit kost ruimte. Wat vaak gebeurt is dat de look-up DataTable, zoals de DataTable Plaatsen, uitgebreid wordt met een kolom ID waarin een uniek nummer als primaire sleutel wordt gebruikt om een plaats te vinden. En je laat vervolgens het programma de volledige, lange naam daarmee opzoeken als dat nodig is. In dit geval moet niet de plaatsnaam maar de sleutelwaarde uit de kolom ID opgenomen worden in de hoofdtabel.

Voorbeeld 5: Value & display members Voorbereiding Kopieer het project LookUpTable van Voorbeeld 4 naar een nieuw project met de naam ValueDisplayMembers.

1

Aanpassen van de DataTable Plaatsen Zoek in het code-venster de methode createDataTablePlaatsen() op en zoek de volgende regel op: DataTable dtPlaatsen = new DataTable(“Plaatsen”);

EP

T

1

2 Maak een nieuwe sleutelkolom door de volgende regels toe te voegen na deze regel:

N

C

// Aanmaken primaire sleutelkolom. DataColumn ID = new System.Data.DataColumn(“ID”, typeof(int)); ID.AutoIncrement = true; ID.AllowDBNull = false; dtPlaatsen.Columns.Add(ID);

O

3 Er is nu een nieuwe sleutelkolom aangemaakt. Nu moet de Primary eigenschap van de DataTable Plaatsen nog aangepast worden. Pas de regel waar de PrimaryKey gedefinieerd wordt zo aan dat daar het volgende komt te staan:

C

dtPlaatsen.PrimaryKey = new DataColumn[] { ID };

Aanpassen fillPlaatsen De DataTable Plaatsen is nu klaar. Nu moeten we de methode fillPlaatsen() aanpassen. Immers er wordt steeds een regel met één kolom toegevoegd, maar een regel van de DataTable Plaatsen bestaat uit twee kolommen: ID en Plaatsen.

1

Zoek de methode fillPlaatsen() op.

2 Pas de regels zo aan dat er voor de plaatsnaam een uniek nummer geplaatst wordt. De regels komen er dan als volgt uit te zien: dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{0,”Aadorp”},true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{1,”Amsterdam”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{2,”Arnhem”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{3,”Assen”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{4,”Nieuwkoop”}, true);


322

Deel 3: Databases

dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{5,”Vlissingen”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{6,”Zundert”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{7,”Appelscha”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{8,”Lelystad”}, true); dataSetPersonen.Tables[“Plaatsen”].LoadDataRow(new object[]{9,”Haarlem”}, true);

Aanpassen DataTable Personen Vervolgens passen we de DataTable Personen aan zodat daar alleen de ID van de plaatsnaam wordt opgeslagen. Hiervoor moeten we alleen het type van de kolom Geboorteplaats wijzigen van string in int. 1

Ga naar de methode createDataTablePersonen() en zoek de volgende regel op:

T

dtPersonen.Columns.Add(“Geboorteplaats”, typeof(string));

2 Vervang string door int. De regel ziet er nu als volgt uit:

3 Verwijder de volgende regel:

EP

dtPersonen.Columns.Add(“Geboorteplaats”, typeof(int));

dtPersonen.Columns[“Geboorteplaats”].MaxLength = 28;

Zoek in de methode initDataGridViewPersonen() naar de volgende regel:

N

1

C

Aanpassen van de DataGridView Personen De DataTables zijn aangepast. Wel moet nu nog de DataGridView aangepast worden.

geboorteplaatsComboBoxColumn.ValueMember = “Plaatsnaam”; en vervang in deze regel Plaatsnaam door ID. De regel ziet er nu als volgt uit:

Aanpassen van de ComboBox cmbGeboorteplaats De DataGridView is aangepast. Er is ook nog een ComboBox cmbGeboorteplaats die gekoppeld is aan de DataTable Personen, die aangepast moet worden.

C

1

O

geboortePlaatsComboBoxColumn.ValueMember = “ID”;

Ga naar de constructor FrmValueDisplayMembers en zoek de volgende regel op: cmbGeboorteplaats.ValueMember = “Plaatsnaam”;

Vervang Plaatsnaam door ID. De regel ziet er nu als volgt uit: cmbGeboorteplaats.ValueMember = “ID”;

1

Test je programma Als het goed is werkt je programma nog net zo als vóór de aanpassingen. Wat ben je er dan mee opgeschoten? Je hebt een veelvoorkomende techniek geleerd om grote bestanden op een efficiënte manier aan elkaar te koppelen.


Hoofdstuk 2 Hoe werkt het?

323

Opdracht Value- & DisplayMembers 1: Auto’s Wijzig Opdracht Look-up DataTable 2: Auto’s zo dat de merken-DataTable bestaat uit twee kolommen: ID en Merknaam.

Opdracht Value- & DisplayMembers 2: Eigenaren Pas Opdracht Value & DisplayMembers 1: Auto’s zo aan dat er een veld eigenaar wordt toegevoegd. Hiervoor gebruik je een ComboBox en/of een DataGridViewComboBox. In de DataTable Autos neem je de ID van Personen over. In de ComboBox laat je de achternaam van de eigenaar zien.

EP

T

Zoals je ziet wordt ook hier weer gebruik gemaakt van een personentabel. Als je een factuur hebt wil je de klant (= persoon) kunnen opzoeken. In dit programma wil je de eigenaar (= persoon) van een auto kunnen opzoeken. Wen er maar aan: in heel veel programma’s wordt gebruik gemaakt van een personentabel.

N-op-m (n:m) koppeling tussen DataTables

C

In het vorige onderdeel heb je geleerd hoe je een master-detail-koppeling tot stand brengt. Bij een master-detail-koppeling zoals een rugbyteam bestaat een rugbyteam uit één of meerdere spelers, maar is iedere rugbyspeler gekoppeld aan maximaal één team. Een speler kan dus niet uitkomen voor meerdere teams.

O

N

Als we echter een andere databank hebben, zoals de games-databank uit hoofdstuk 1, moet een personage aan meerdere games gekoppeld worden en een game aan meerdere personages. Dit noemen we een n-op-m (n:m) koppeling of relatie.

C

Hiervoor heb je altijd een extra DataTable nodig, een zogenaamde koppeltabel. De onderstaande afbeelding uit hoofdstuk 1 laat dit zien:

Figuur 3: Relaties n:m


324

Deel 3: Databases

Voorbeeld 6: n:m-koppeling 1

Voorbereiding Maak een nieuw project aan met de naam GamesEnPersonages.

2 Geef het formulier de volgende naam: FrmGamesEnPersonages. 3 Voeg de volgende Controls toe: Type

Naam

Eigenschap

Waarde

DataGridView

dgvGames

Width

622

Height

255

dgvPersonages

Width

485

Height

255

DataGridView

dgvGamesPersonages

Width

397

Height

105

Button

btnEinde

Text

Einde

LightCoral

EP

BackColor

T

DataGridView

Tabel 11: Controls project GamesEnPersonages

4 Uiteraard voeg je aan de Button btnEinde code toe om het formulier te sluiten.

C

O

N

C

5 Het formulier FrmGamesEnPersonages ziet er nu als volgt uit:

Schermafbeelding 7: Games en personages zonder binding

Initialisatie globale variabelen Maak de volgende globale variabelen aan door de volgende regels toe te voegen: DataSet dataSetGames = new DataSet(“Games”); BindingSource masterBindingSourceGames = new BindingSource(); BindingSource masterBindingSourcePersonages = new BindingSource(); BindingSource detailBindingSourceGames_GamesPersonages = new BindingSource(); BindingSource detailBindingSourcePersonages_GamesPersonages = new BindingSource();

Je ziet dat er voor de twee master-DataTables twee BindingSources worden aangemaakt. Ze delen echter één detail-DataTable: dtGamesPersonages. De BindingSources worden beurtelings, afhankelijk van de situatie, toegepast.


Hoofdstuk 2 Hoe werkt het?

325

Aanmaken DataTables Voor dit project hebben we, zoals je kunt zien in Figuur 3, drie DataTables nodig: • • •

Games

1

Maak een methode aan met de naam createDataTableGames(). Het type van deze methode is DataTable. Volg de stappen van Voorbeeld 1 op pagina 293-295.

Personages GamesPersonages

2 Voeg aan deze methode de code toe om een DataTable aan te maken die voldoet aan de volgende beschrijving: DataTable-naam: dtGames Moet uniek zijn

Mag leeg zijn

Ja

Ja

Nee

-

-

Ja

Nee

-

-

-

-

-

-

-

-

-

-

-

-

Type

Lengte

Sleutel

Automatisch ophogen

ID

int

-

PK

GameNaam

string

50

GameFabrikant

string

50

ReleaseDatum

DateTime

-

Beschrijving

string

256

EP

T

Kolomnaam

C

Tabel 12: Structuur DataTable dtGames

N

3 Maak een methode aan met de naam createDataTablePersonages(). Het type van deze methode is DataTable.

O

4 Voeg aan deze methode code toe om een tabel aan te maken die voldoet aan de volgende beschrijving: DataTable-naam: dtPersonages Type

C

Kolomnaam

Lengte Sleutel

Automatisch Moet ophogen uniek zijn

Mag leeg zijn

ID

int

-

PK

Ja

Ja

Nee

PersonageNaam

string

50

-

-

Ja

Nee

Soort

string

50

-

-

-

-

Afbeelding

Image

-

-

-

-

-

Beschrijving

string

256

-

-

-

-

Tabel 13: Structuur DataTable dtPersonages

5 Maak een methode aan met de naam createDataTableGamesPersonages(). Het type van deze methode is DataTable. Dit is de zogenaamde koppeltabel waarmee de DataTables dtGames en dtPersonages gekoppeld worden.


326

Deel 3: Databases

6 Voeg aan deze methode code toe om een tabel aan te maken die voldoet aan de volgende beschrijving: DataTable-naam: dtGamesPersonages Kolomnaam

Type

Lengte

Sleutel*

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

GameID

int

-

PK / FK

-

-

Nee

PersonageID

int

-

PK / FK

-

-

Nee

*) GameID en PersonageID vormen samen de primary key en zijn sámen uniek. Tabel 14: Structuur DataTable dtGamesPersonages

T

Let op! Een primary key over twee velden stel je in door in plaats van één veld twee velden op te nemen, gescheiden door een komma.

EP

Er zijn twee Foreign Keys (FK): één voor GameID naar de DataTable dtGames en één voor PersonageID naar de DataTable dtPersonages. Deze worden buiten deze methode aangemaakt met behulp van de DataRelations. Dit is de volgende stap.

Maak een nieuwe methode met de naam createDataRelationGames_GamesPersonages(). Het type van deze methode is void: je hoeft geen gegevens terug te geven omdat er alleen een datarelatie gevormd wordt.

N

1

C

Aanmaken DataRelations (Foreign Keys) Eerst maken we de DataRelation aan tussen dtGames en dtGamesPersonages:

O

2 We maken twee variabelen aan om de kolommen te kunnen instellen. Voeg de volgende regel toe aan createDataRelationGames_GamesPersonages(): DataColumn dataColumnParent, dataColumnChild;

C

3 We koppelen de desbetreffende kolom uit de twee tabellen aan de juiste variabele. Voeg de volgende regels toe: dataColumnParent = dataSetGames.Tables[“Games”].Columns[“ID”]; dataColumnChild = dataSetGames.Tables[“GamesPersonages”].Columns[“GameID”];

4 Aanmaken van de relatie. Voeg de volgende regels toe: DataRelation dataRelationGames_GamesPersonages = new DataRelation( “relationGames_GamesPersonages”, dataColumnParent, dataColumnChild);

5 Voeg de DataRelation toe aan de DataSet door de volgende regels toe te voegen: dataSetGames.Relations.Add(dataRelationGames_GamesPersonages);

6 Tot slot breiden we de DataRelation uit met de foreign-key-beperkingen met de volgende regels:


Hoofdstuk 2 Hoe werkt het?

327

ForeignKeyConstraint foreignKeyConstraintGames_GamesPersonages = dataRelationGames_GamesPersonages.ChildKeyConstraint; foreignKeyConstraintGames_GamesPersonages.DeleteRule = Rule.None; foreignKeyConstraintGames_GamesPersonages.UpdateRule = Rule.Cascade;

7 Vervolgens maken we een methode aan om een DataRelation te leggen tussen dtPersonages en dtGamesPersonages. Voeg de methode createDataRelationPersonages_GamesPersonages() toe door de volgende regels over te nemen:

EP

T

private void createDataRelationPersonages_GamesPersonages() { DataColumn dataColumnParent, dataColumnChild; dataColumnParent = dataSetGames.Tables[“Personages”].Columns[“ID”]; dataColumnChild = dataSetGames.Tables[“GamesPersonages”]. Columns[“PersonageID”]; DataRelation dataRelationPersonages_GamesPersonages = new DataRelation( “relationPersonages_GamesPersonages”, dataColumnParent, dataColumnChild); dataSetGames.Relations.Add(dataRelationPersonages_GamesPersonages);

C

ForeignKeyConstraint foreignKeyConstraintPersonages_GamesPersonages = dataRelationPersonages_GamesPersonages.ChildKeyConstraint; foreignKeyConstraintPersonages_GamesPersonages.DeleteRule = Rule.None; foreignKeyConstraintPersonages_GamesPersonages.UpdateRule = Rule.Cascade;

N

}

1

O

Opmaak DataGridViews Zoals je gezien hebt bij het aanmaken van de DataTables zijn niet alle velden even lang. Daarom is het netjes om de DataGridViews voor de DataTables Games en Personages een zelfde indeling (lay-out) te geven. Maak de methode private void initDataGridViewGames() aan.

C

2 Voeg code toe, zie Voorbeeld 4: Look-up DataTable onder het kopje Instellen lay-out DataGridView (pagina 312-314), die de volgende waardes instelt: DataGridViewTextBoxColumn Column ID DataPropertyName

"ID"

HeaderText

"ID"

ValueType

typeof(int)

ReadOnly

true

Width

30


328

Deel 3: Databases

Column GameNaam DataPropertyName

"GameNaam"

HeaderText

"Naam"

ValueType

typeof(string)

Width

150

Column GameFabrikant DataPropertyName

"GameFabrikant"

HeaderText

"Fabrikant"

ValueType

typeof(string)

Width

75

DataPropertyName

"ReleaseDatum"

HeaderText

"Release datum" typeof(DateTime)

Width

80

Column Beschrijving

EP

ValueType

T

Column ReleaseDatum

DataPropertyName

"Beschrijving"

HeaderText

"Beschrijving"

ValueType

typeof(string) 250

AutoSizeMode

DataGridViewAutoSizeColumnMode.Fill

C

Width

N

Tabel 15: Lay-out DataGridView Games

O

Let op! Vergeet niet om de DataGridView-eigenschap AutoGenerateColumns op false in te stellen!

C

3 Maak de methode private void initDataGridViewPersonages() aan. Hiermee wordt de DataGridView dgvPersonages opgemaakt. 4 Voeg code toe die de volgende waardes instelt: DataGridViewTextBoxColumn Column ID DataPropertyName

"ID"

HeaderText

"ID"

ValueType

typeof(int)

ReadOnly

true

Width

30


Hoofdstuk 2 Hoe werkt het?

329

Column PersonageNaam DataPropertyName

"PersonageNaam"

HeaderText

"Naam personage"

ValueType

typeof(string)

Width

75

Column Soort DataPropertyName

"Soort"

HeaderText

"Soort"

ValueType

typeof(string)

Width

75

DataPropertyName

"Afbeelding"

HeaderText

"Afbeelding"

ValueType

typeof(Image)

Width

80

EP

Column Beschrijving

T

Column Afbeelding

DataPropertyName

"Beschrijving"

HeaderText

"Beschrijving" typeof(string)

Width

250

AutoSizeMode

DataGridViewAutoSizeColumnMode.Fill

C

ValueType

N

Tabel 16: Lay-out DataGridView Personages

O

5 Maak de methode private void initDataGridViewGamesPersonages() aan. Hiermee wordt de DataGridView dgvGamesPersonages opgemaakt.

C

6 Deze DataGridView wijkt af van de vorige twee omdat het hier de koppel-DataTable betreft. Deze DataGridView krijgt twee ComboBoxen die verwijzen naar de twee andere DataTables. Volg de stappen op pagina 318 en 319. Voeg code toe die de volgende waardes instelt: DataGridViewComboBoxColumn Column GameID DataPropertyName

"GameID"

HeaderText

"Game"

ValueType

typeof(int)

DataSource

"Games"

ValueMember

"ID"

DisplayMember

"GameNaam"

DisplayStyle

DataGridViewComboBoxDisplayStyle.ComboBox

Width

120


330

Deel 3: Databases

Column PersonageID DataPropertyName

"PersonageID"

HeaderText

"Personage"

ValueType

typeof(int)

DataSource

"Personages"

ValueMember

"ID"

DisplayMember

"PersonageNaam"

DisplayStyle

DataGridViewComboBoxDisplayStyle.ComboBox

Width

120

Tabel 17: Lay-out DataGridView GamesPersonages

C

N

• • • • • • •

EP

T

Initialisatie programma De hiervoor aangemaakte methodes moeten natuurlijk ergens aangeroepen worden. Dit doen we bij de initialisatie van het programma. Om het geheel overzichtelijk te houden maken we een aparte methode waarin we alles combineren wat met de databinding te maken heeft. Deze methode geven we een duidelijke opbouw, namelijk: Aanmaken en toevoegen van de DataTables aan de DataSet. Eventueel vullen van de DataTables met gegevens. Aanmaken van de relaties (DataRelations). Koppelen van de DataTables aan de master-BindingSources. Koppelen van de DataTables aan de detail-BindingSources. Opmaken van de DataGridViews voordat deze gebonden wordt aan een DataTable. Binden van de Controls aan de BindingSources.

1

O

Deze structuur voegen we als commentaar toe aan de code. Maak een private void-methode aan met de naam InitialisatieDataBindings().

C

2 Voeg het volgende commentaar toe aan deze methode. Dit commentaar gaan we gebruiken als kopregels. Als je in de toekomst vaker dit soort methodes gaat maken kun je dit stramien (deze sjabloon) aanhouden: // 1.Aanmaken en toevoegen van de DataTables aan de DataSet. // 2.Eventueel vullen van de DataTables met gegevens. // 3.Aanmaken van de relaties (DataRelations). // 4.Koppelen van de DataTables aan de master-BindingSources. // 5.Koppelen van de DataTables aan de detail-BindingSources. // 6.Opmaken van de DataGridViews voordat deze gebonden wordt aan een DataTable. // 7.Binden van de Controls aan de BindingSources.

Nu voegen we onder elke kop de juiste programmaregels toe: 1

// 1.Aanmaken en toevoegen van de DataTables aan de DataSet. dataSetGames.Tables.Add(createDataTableGames()); dataSetGames.Tables.Add(createDataTableGamesPersonages()); dataSetGames.Tables.Add(createDataTablePersonages());


Hoofdstuk 2 Hoe werkt het?

2

331

// 2.Eventueel vullen van de DataTables met gegevens.

In dit programma vult de gebruiker de DataTable met gegevens. Daarom worden er geen programmaregels toegevoegd na commentaarregel 2. 3

// 3.Aanmaken van de relaties (DataRelations). createDataRelationGames_GamesPersonages(); createDataRelationPersonages_GamesPersonages();

4

// 4.Koppelen van de DataTables aan de master-BindingSources. masterBindingSourceGames.DataSource = dataSetGames.Tables[“Games”]; masterBindingSourcePersonages.DataSource = dataSetGames.Tables[“Personages”];

5

// 5.Koppelen van de DataTables aan de detail-BindingSources.

T

// Master-detail-verbinding tussen Games en GamesPersonages detailBindingSourceGames_GamesPersonages.DataSource = masterBindingSourceGames; detailBindingSourceGames_GamesPersonages.DataMember = “relationGames_GamesPersonages”;

EP

// Master-detail-verbinding tussen Personages en GamesPersonages detailBindingSourcePersonages_GamesPersonages.DataSource = masterBindingSourcePersonages; detailBindingSourcePersonages_GamesPersonages.DataMember = “relationPersonages_GamesPersonages”; // 6.Opmaken van de DataGridViews voordat deze gebonden wordt aan een DataTable. initDataGridViewGames(); initDataGridViewPersonages(); initDataGridViewGamesPersonages();

7

// 7.Binden van de Controls aan de BindingSources. dgvGames.DataSource = masterBindingSourceGames; dgvPersonages.DataSource = masterBindingSourcePersonages;

O

N

C

6

C

8 De methode InitialisatieDataBindings() is klaar. Nu moet je er voor zorgen dat deze methode bij het opstarten van het programma wordt uitgevoerd. Voeg deze methode toe aan de constructor van het formulier FrmGamesEnPersonages zodat er komt te staan: public frmGamesEnPersonages() { InitializeComponent(); InitialisatieDataBindings(); }

Verzamelen filtergegevens Als je de BindingSources goed instelt, toont de detail-DataTable alle rijen die horen bij één rij van de master-DataTable. We hebben hier alleen het probleem dat er één detail-DataTable is (dtGamesPersonages) en twee master-DataTables: dtGames en dtPersonages. En je kunt maar aan één master-DataTable tegelijkertijd koppelen.


332

Deel 3: Databases

Een ander probleem is dat, als bijvoorbeeld dtGames de master-DataTable is in dtGamesPersonages, de ID’s worden getoond van alle personages die gekoppeld zijn aan een game. Maar je wilt eigenlijk alle gegevens van die personages zien. Sterker nog: je wilt alleen alle gegevens zien van de personages die bij de geselecteerde game horen. Daarom gaan we een methode maken die op basis van de in de DataTable geselecteerde games en personages een filter maakt. We maken een algemeen toepasbare methode waarbij je de DataGridView en het kolomnummer van de kolom waar je op wilt filteren meegeeft. 1

Maak de volgende methode aan:

T

private string getFilterDataDataGridView(DataGridView dataGridView, int kolomNummer)

EP

Deze methode geeft als string een rij waardes terug waarop gefilterd kan gaan worden, gescheiden door komma’s, bijvoorbeeld: “1,3,6,7”. 2 Eerst maken we een variabele aan waarin we de gegevens opslaan waarop zometeen gefilterd gaat worden. Voeg de volgende regel toe: string teFilterenGegevens = “”;

C

3 In de DataGridView staan alleen die rijen die bij een bepaalde game horen. We hebben dus alle rijen nodig. Voeg de volgende regels toe:

N

foreach (DataGridViewRow rij in dataGridView.Rows) { }

O

4 De laatste rij is een lege rij zodat je waardes kunt toevoegen. Alleen hebben we niets aan lege waardes, deze laten het programma crashen. Voeg de volgende regels toe binnen de foreach-loop om dit te voorkomen:

C

if (rij.Cells[1].Value != null) { }

5 De eerste keer krijgt de variabele teFilterenGegevens de waarde van de kolom. De DataGridView GamesPersonages bestaat uit twee kolommen. Kolom 0 verwijst naar de Games-DataTable en kolom 1 verwijst naar de Personages-DataTable. Het kolomnummer staat in de variabele kolomNummer. Voeg de volgende regels toe binnen het if-statement: if (teFilterenGegevens.Length == 0) { teFilterenGegevens = rij.Cells[kolomNummer].Value.ToString(); }


Hoofdstuk 2 Hoe werkt het?

333

6 De volgende waardes moeten, gescheiden door een komma, toegevoegd worden aan de variabele teFilterenGegevens. Voeg het volgende else-statement toe: else { teFilterenGegevens = teFilterenGegevens + “,” + rij.Cells[kolomNummer].Value.ToString(); }

7 Tot slot voegen we aan het eind van de methode de regel toe om de string met gegevens terug te geven aan de aanroepende methode: return teFilterenGegevens;

Maak een methode aan met de volgende handtekening:

EP

1

T

Maken BindingSource-filter De class DataGridView kent geen methode waarmee gefilterd kan worden. Wel kunnen we de achterliggende BindingSource filteren.

private void filterBindingSource(BindingSource bindingSource, string filterColumn, string filter)

C

Deze methode kent drie parameters: de BindingSource die gefilterd gaat worden, de kolomnaam waarop gefilterd gaat worden en de waardes die gebruikt worden in het filter.

N

2 Eerst verwijderen we eventuele bestaande filters door de volgende regel toe te voegen: bindingSource.RemoveFilter();

O

3 Als de variabele filter leeg is, zijn er kennelijk geen rijen geselecteerd die getoond moeten worden. Dit bereiken we door het filter de waarde -1 mee te geven. Voeg de volgende regels toe:

C

if (filter.Length == 0) { filter = “-1”;

}

Nu kan het filter volgens het volgende format* opgebouwd en toegevoegd worden aan de bindingSource “ID IN (0,2,3,4)”: bindingSource.Filter = filterColumn + “ “ + “IN (“ + filter + “)”;

* Dit is eigenlijk een stukje SQL


334

Deel 3: Databases

Activeren filters Het filter moet geactiveerd worden op het moment dat er een nieuw lijstje, een nieuwe binding, getoond wordt in de DataGridView dgvGamesPersonages. Hiervoor is een event beschikbaar: DataBindingComplete. Ga naar het design-venster en activeer de DataGridView dgvGamesPersonages 1

Dubbelklik op het woord DataBindingComplete in de lijst met events die gekoppeld zijn aan dgvGamesPersonages. De volgende code verschijnt in het code-venster: private void dgvGamesPersonages_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { }

if (dgvGames.Focused)

EP

Voeg de volgende if ... else-statements toe:

T

2 Afhankelijk van welk DataGridView de focus heeft, waar we dus mee bezig zijn, moet er een filter op de andere DataGridView geplaatst worden.

C

{ } else if (dgvPersonages.Focused) { }

N

3 Voeg binnen het if-statement de volgende regels toe om het filter aan te maken en om het filter toe te passen op de masterBindingSourcePersonages.

O

string filter = getFilterDataDataGridView(dgvGamesPersonages, 1); filterBindingSource(masterBindingSourcePersonages, “ID”, filter);

4 Voeg binnen het else-statement de volgende regels toe om het filter aan te maken en om het filter toe te passen op de masterBindingSourceGames:

C

string filter = getFilterDataDataGridView(dgvGamesPersonages, 0); filterBindingSource(masterBindingSourceGames, “ID”, filter);

Wijzigen van de kleuren Omdat het in dit programma handig is om te kunnen zien welk onderdeel de focus heeft voegen we code toe die kleuren verschillend instelt:

1

Maak een methode aan met de volgende handtekening: private void setColorsFocusDgvGames()

2 Voeg de volgende code toe om de kleuren van de drie DataGridViews in te stellen: dgvGamesPersonages.BackgroundColor = Color.Aquamarine; dgvGames.BackgroundColor = Color.Aqua; dgvPersonages.BackgroundColor = Color.AliceBlue;


Hoofdstuk 2 Hoe werkt het?

335

Maak een methode aan met de volgende handtekening: private void setColorsFocusDgvPersonages()

3 Voeg de volgende code toe om de kleuren van de drie DataGridViews in te stellen: dgvGamesPersonages.BackgroundColor = Color.Aquamarine; dgvPersonages.BackgroundColor = Color.Aqua; dgvGames.BackgroundColor = Color.AliceBlue;

4 Maak een methode aan met de volgende handtekening: private void setColorsFocusDgvGamesPersonages()

5 Voeg de volgende code toe om de kleuren van de drie DataGridViews in te stellen:

T

dgvGamesPersonages.BackgroundColor = Color.Aqua; dgvPersonages.BackgroundColor = Color.Aquamarine; dgvGames.BackgroundColor = Color.Aquamarine;

Ga naar het event Enter van de DataGridView dgvGames. Je ziet dan de volgende code:

C

1

EP

Wijzigingen van de DataRelations Bij dit programma veranderen de DataRelations zodra de focus wijzigt. Als de gebruiker bezig is met het invoeren of bekijken van games, dan moeten er in de GamesPersonages-DataTable de bij de game behorende personages getoond worden. Dit geldt dan ook voor de Personages-DataTable.

N

private void dgvGames_Enter(object sender, EventArgs e) } }

O

2 Verwijder eventuele filters die betrekking hebben op games. Je wilt immers alle games zien als je met games bezig bent. Voeg de volgende regel toe: masterBindingSourceGames.RemoveFilter();

C

3 Vervolgens zorg je dat er een master-detail-DataRelation gelegd wordt tussen dtGames en dtGamesPersonages door de volgende regel toe te voegen: dgvGamesPersonages.DataSource = detailBindingSourceGames_GamesPersonages;

4 Stel de kleuren in door de volgende regel toe te voegen: setColorsFocusDgvGames();

Het omgekeerde geldt als de gebruiker bezig is met de personages. Dan moeten er in de Games-DataTable alleen die games getoond worden waar een bepaald personage in voorkomt. 1

Ga naar het event Enter van de DataGridView dgvPersonages. Je ziet dan de volgende code: private void dgvPersonages_Enter(object sender, EventArgs e) } }


336

Deel 3: Databases

2 Verwijder eventuele filters die betrekking hebben op Personen. De gebruiker wil immers alle personen zien als de focus ligt op de DataGridView dtPersonages. Voeg de volgende regel toe: masterBindingSourcePersonages.RemoveFilter();

3 Vervolgens zorg je dat er een master-detail-relatie gelegd wordt tussen dtPersonages en dtGamesPersonages door de volgende regel toe te voegen: dgvGamesPersonages.DataSource = detailBindingSourcePersonages_GamesPersonages;

5 Stel de kleuren in door de volgende regel toe te voegen: setColorsFocusDgvPersonages();

Ga naar het event Enter van de DataGridView dgvGamesPersonages. Je ziet dan de volgende code:

EP

1

T

En als de gebruiker bezig is met de koppeltabel dtGamesPersonages, dan wil hij de beschikking hebben over alle games en alle personages, want hij moet ze kunnen koppelen.

private void dgvGamesPersonages_Enter(object sender, EventArgs e) { }

C

2 Verwijder eventuele filters die betrekking hebben op Games en Personages. Voeg de volgende regels toe:

N

masterBindingSourcePersonages.RemoveFilter(); masterBindingSourceGames.RemoveFilter();

3 Stel de kleuren in door de volgende regel toe te voegen:

Test je programma Je programma moet er nu, als je wat games en personages toevoegt, als volgt uitzien zodra de focus op de DataGridView dgvGames ligt. Je ziet alle games en per game de personages die erin voorkomen.

C

1

O

setColorsFocusDgvGamesPersonages();

Schermafbeelding 8: Games en personages met de focus op Games


Hoofdstuk 2 Hoe werkt het?

337

2 Verleg je focus naar de DataGridView dgvPersonages en het scherm zal alle personages laten zien met de aan de personage gekoppelde games.

T

Schermafbeelding 9: Games en personages met de focus op Personages

EP

Opdracht n:m-koppeling 1: Auto’s en eigenaren

Maak een programma waarbij je meerdere auto’s kunt koppelen aan één persoon (eigenaar) en meerdere personen aan één auto. De drie DataTables hebben de volgende indeling: DataTable-naam: dtAutos Type

ID

int

Lengte

Sleutel

Automatisch Moet ophogen uniek zijn

C

Kolomnaam

Mag leeg zijn

PK

Ja

Ja

Nee

8

-

-

Ja

-

Chassisnummer

string

50

-

-

Ja

-

Merk

string

30

-

-

-

-

Type

string

30

-

-

-

-

Kleur

Color

-

-

-

-

-

-

-

-

C

O

N

-

string

Kenteken

Brandstof

string

12

Tabel 18: Structuur DataTable dtAutos

DataTable-naam: dtPersonen Kolomnaam

Type

Lengte Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

ID

int

-

PK

Ja

Ja

Nee

Voorletters

string

8

-

-

-

-

Tussenvoegsels

string

10

-

-

-

-

Achternaam

string

40

-

-

-

-

Tabel 19: Structuur DataTable dtPersonen


338

Deel 3: Databases

DataTable-naam: dtEigenaren Kolomnaam

Type

Lengte

Sleutel

Automatisch Moet ophogen uniek zijn

Mag leeg zijn

AutoID

int

-

PK / FK

-

-

Nee

PersonenID

int

PK / FK

-

-

Nee

Begindatum

DateTime

-

-

Ja

-

Einddatum

DateTime

-

-

-

-

Tabel 20: Structuur DataTable dtEigenaren

T

Zorg voor ComboBoxen om Merk, Type en Brandstof op te kunnen zoeken, maak gebruik van DataTables om de standaardwaardes in op te slaan.

•

Pas het programma uit Voorbeeld 6: n:m-koppeling zo aan dat: je binnen de DataGridView dgvGames met behulp van een ComboBox kunt kiezen uit een aantal game-leveranciers. je binnen de DataGridView dgvPersonages met behulp van een ComboBox kunt kiezen uit de soorten mens, trol, monster, beest, kabouter, dwerg, elf, tovenaar, heks, fee en overig.

C

•

EP

Opdracht n:m-koppeling 2: Aanvullen voorbeeld 6 n:m-koppeling

N

Opdracht n:m-koppeling 3: Klassen en leerlingen *

C

O

Meestal wordt de werkelijkheid zo voorgesteld dat één klas een groep is van een aantal leerlingen en dat iedere leerling tot één klas behoort. Dit is zo als je naar één bepaalde datum kijkt. Maar als je over een periode van drie jaar kijkt zie je dat leerlingen onderdeel uitmaken van meerdere klassen. Een leerling begon zijn schoolcarrière in 1H, ging toen naar 2C en 3C, om vervolgens de school te verlaten vanuit 4Q, om maar eens een aantal willekeurige codes te nemen. Maak een programma waarmee een leerling aan een klas toegevoegd kan worden, verwijderd kan worden uit een klas en waarmee een leerling van klas kan wisselen. Tegelijkertijd moet het ook mogelijk zijn om alle leerlingen van een bepaalde periode en klas op te vragen. Gebruik de klascodes die op je eigen school gangbaar zijn.

Wat heb je geleerd? • • • • •

Aanmaken en gebruiken van DataSets en DataTables. Binding DataTables aan Controls. Gebruik van en instellen van een DatagridView. Filteren van DataTables. Het instellen van 1:n-relaties.


Hoofdstuk 2 Hoe werkt het?

Instellen n:m-relaties. Instellen van een ToolTip. Het verschil tussen DisplayMembers en ValueMembers. Het maken van een look-up DataTable in combinatie met een ComboBox.

C

O

N

C

EP

T

• • • •

339


340

Hoofdstuk 3 Database versus Dataset

Inleiding

EP

T

Tot dusver hebben we allerlei gegevens opgeslagen in DataTables, rijen toegevoegd en verwijderd. Alleen: zodra het programma afgesloten wordt, verdwijnt alle ingevoerde informatie. Dit komt omdat een DataSet met daarin de DataTables, alleen in het interne geheugen bestaat. Er wordt niets opgeslagen, niet in een tekstbestand, niet in een XML-file, niet in een database, nergens, omdat je die opdracht niet hebt gegeven. Je moet in je programma zelf vertellen welke informatie waar wordt bijgehouden.

C

Voor gegevens, zoals die in een DataSet worden opgeslagen, zijn databases een goed medium om je informatie te bewaren, omdat databases vrijwel dezelfde structuur hebben als DataTables. Dat past dus goed.

Desktop-database: Microsoft Access

N

In dit hoofdstuk gaan we leren hoe je een koppeling tussen een Microsoft Accessdatabase en een DataSet tot stand kunt brengen.

C

O

Let op! In dit boek gaan we ervan uit dat je in staat bent om tabellen en relaties aan te maken in Microsoft Access.

Algemene voorbereiding koppeling Access & Visual Studio Het kan zijn dat je de volgende foutmelding krijgt als je (bijvoorbeeld op pagina 345) een verbinding met een Access-database wilt maken:

Schermafbeelding 10: Unable to add data connection


Hoofdstuk 3 Database versus Dataset

341

De meest voor de hand liggende oorzaak is dat je de verbinding tussen Access en alle niet-Office-programma’s, waaronder Visual Studio, nog moet installeren. Deze verbinding wordt namelijk niet meer standaard geïnstalleerd. Je vindt instructies over het maken van deze verbinding op het internet onder de naam: Microsoft Access Database Engine 2010 Redistributable.

Voorbeeld 7: Microsoft Access 1

Voorbereiding: database aanmaken Maak in Microsoft Access een database aan met de naam Games. Onthoud de map waarin je de database hebt aangemaakt.

T

2 Maak in deze database de drie tabellen aan zoals beschreven bij Voorbeeld 6: n:mkoppeling (dit zijn de tabellen Games, Personages en GamesPersonages).

C

O

N

C

EP

3 Maak de verschillende relaties aan tussen de drie tabellen. Zorg ervoor dat er een vinkje staat bij het item Referentiële integriteit afdwingen. In Access ziet het er dan, afhankelijk van de versie van Access, ongeveer zo uit:

Schermafbeelding 11: Access relaties

1

Voorbereiding: project aanmaken Kopieer het voorbeeld GamesEnPersonages naar een nieuw project met de naam GamesEnPersonagesMetDatabase.

2 Wijzig de titel van het formulier FrmGamesEnPersonages in Games & Personages met database. 3 Wijzig de eigenschap BackColor van het formulier FrmGamesEnPersonages in Honeydew.


342

Deel 3: Databases

4 Als je bij de projectinstellingen de default Namespace van je project gewijzigd hebt, moet je dit ook doen in het code-venster van het formulier FrmGamesEnPersonages. Toevoegen DataSource Let op! Het kan zijn dat het venster om DataSources te bewerken in andere versies van Visual Studio op een andere plek in het programma te vinden is. 1

We gaan nu een externe DataSource toevoegen aan ons project. Ga naar de menuoptie View (bovenin de menubalk) en klik hierop.

2 Klik op de menukeuze Other Windows.

N

C

EP

T

3 Klik op de menukeuze Data Sources (je kunt ook de toetsencombinatie <Shift><Alt><d> gebruiken). Je komt nu in het volgende venster:

O

Schermafbeelding 12: Data Sources-venster

C

4 Klik op Add New Data Source. Het volgende venster verschijnt:


343

EP

T

Hoofdstuk 3 Database versus Dataset

Schermafbeelding 13: Choose Data Source Type

C

O

N

C

5 Selecteer Database en klik op Next. Het volgende venster verschijnt:

Schermafbeelding 14: Choose a Database Model

6 In dit venster selecteer je Dataset en klik je op Next. Het volgende venster verschijnt:


Deel 3: Databases

EP

T

344

Schermafbeelding 15: Choose Your Data Connection

N

C

7 Nu komen we in het gedeelte waar we daadwerkelijk een verbinding met de database gaan maken. Alle informatie over zo’n verbinding, de soort database, hoe er verbinding wordt gelegd, waar we de bestanden kunnen vinden, enzovoort wordt achter elkaar opgeschreven in een string-variabele. Zo’n string-variabele met informatie over de verbinding (in het Engels connection) noemen we een connection-string.

C

O

Klik op New Connection en je komt in het venster waar je de juiste database kunt kiezen:

Schermafbeelding 16: Add Connection


Hoofdstuk 3 Database versus Dataset

345

Schermafbeelding 17: Change Data Source

T

8 Klik op Change en kies in het volgende venster voor Microsoft Access Database File. Klik vervolgens op OK of Continue.

C

O

N

C

EP

9 Je komt nu terug in het Add-Connection-venster. Browse hier naar de map waarin je de Games-database hebt aangemaakt.

Schermafbeelding 18: Browse Database file

10 Klik op Test Connection om de verbinding te testen. Als het goed is krijg je nu de volgende melding te zien:

Schermafbeelding 19: Test connection succeeded


346

Deel 3: Databases

Zo niet, volg dan het advies aan het begin van dit hoofdstuk. 11 Klik vervolgens net zolang op OK tot je weer in het choose-your-connection-venster terechtkomt.

N

C

EP

T

12 Klik daar op het plusteken voor Connection string that you will save in the application. Als het goed is, ziet je venster er dan ongeveer als volgt uit, uiteraard met daarin de exacte plek van jouw map:

O

Schermafbeelding 20: Choose Your Data Connection plus Connection string

C

13 Klik op Next. Je krijgt dan de volgende waarschuwing te zien:

Schermafbeelding 21: Local file waarschuwing

14 Klik op Nee. Als je op Ja klikt dan wordt je database iedere keer dat de applicatie gerund wordt gekopieerd naar je projectmap. Dit kan handig zijn. Het houdt wel in dat je database iedere keer weer dezelfde beginsituatie heeft. Wij hebben gekozen voor Nee.


Hoofdstuk 3 Database versus Dataset

347

EP

T

Je komt nu in het volgende venster:

C

Schermafbeelding 22: Save the Connection String

C

O

N

15 Klik in dit venster op Next. Je komt nu in het volgende venster:

Schermafbeelding 23: Choose Your Database Objects


348

Deel 3: Databases

16 Zet een vinkje bij Tables, want we willen alle tabellen uit de Games-database gebruiken.

C

EP

T

17 Wijzig de DataSet-name in DataSetGames (omdat we al gebruik maken van een DataSet met de naam dataSetGames). Je venster ziet er nu als volgt uit:

N

Schermafbeelding 24: Choose Your Database Objects ingevuld

C

O

18 Klik op Finish. Als alles goed gegaan is ziet je data-sources-venster er nu als volgt uit:

Schermafbeelding 25: Data Sources ingevuld

Als je op de driehoekjes voor de tabel namen klikt, komt onder de tabelnaam een overzicht van alle kolommen. Getypeerde (typed) DataSet In het project staat nog allerlei code die overbodig is geworden omdat we net een DataSet hebben aangemaakt die gekoppeld is met een database. De DataTables en DataRelations zijn automatisch aangemaakt. Nog mooier is dat dit een zogenaamde typed DataSet is.


Hoofdstuk 3 Database versus Dataset

349

Een typed DataSet is een DataSet die, samen met alle onderdelen, als een aparte klasse is gedefinieerd. Dit houdt in dat C de structuur en naamgeving van jouw tabellen kent. Tot dusver benaderde je een DataTable als volgt: masterBindingSourceGames.DataSource = dataSetGames.Tables[“Games”];

Hier is de DataTable-naam een string-variabele. Als je een typfout maakt, crasht het programma. Bij een typed DataSet ziet dezelfde regel er als volgt uit: masterBindingSourceGames.DataSource = dataSetGames.Games;

Verwijderen overbodige methodes Ga naar het code-venster.

EP

1

T

Het plezierige is dat je gebruik kunt maken van alle ingebouwde hulpmiddelen van Visual Studio, zoals IntelliSense.

2 Verwijder de methode createDataTableGames().

3 Verwijder de methode createDataTablePersonages().

4 Verwijder de methode createDataTableGamesPersonages().

C

5 Verwijder de methode createDataRelationGames_GamesPersonages(). 6 Verwijder de methode createDataRelationPersonages_GamesPersonages(). Opschonen code Ga naar de methode InitialisatieDataBindings().

N

1

O

2 Verwijder de volgende drie regels die de DataTables aanmaken en toevoegen aan de DataSet dataSetGames:

C

dataSetGames.Tables.Add(createDataTableGames()); dataSetGames.Tables.Add(createDataTableGamesPersonages()); dataSetGames.Tables.Add(createDataTablePersonages());

3 Verwijder vervolgens de twee regels die de DataRelations aanmaken. Deze regels zien er als volgt uit: createDataRelationGames_GamesPersonages(); createDataRelationPersonages_GamesPersonages();

Aanpassen dataSetGames De DataSet dataSetGames moet veranderd worden van de ongetypeerde, zeg maar algemene DataSet naar een getypeerde DataSet. Dit doen we door het type DataSet te wijzigen in DataSetGames. 1

Zoek de volgende regel op bij de globale variabelen: DataSet dataSetGames = new DataSet(“Games”);


350

Deel 3: Databases

2 Wijzig deze regel zo, dat de regel er als volgt uitziet: DataSetGames dataSetGames;

3 Maak een nieuwe methode aan met de volgende naam en handtekening: private void InitialisatieDataSet()

4 Voeg de volgende regels toe aan de methode InitialisatieDataSet die de DataSet aanmaken en initialiseren: dataSetGames = new DataSetGames(); dataSetGames.DataSetName = “DataSetGames”; dataSetGames.SchemaSerializationMode = SchemaSerializationMode.IncludeSchema;

Voeg bij de globale variabelen de volgende drie regels toe:

C

1

EP

T

Aanmaken TableAdapters De TableAdapter is dé verbinding tussen de database-tabel en de DataTable. De TableAdapter zorgt ervoor dat de data in de database gesynchroniseerd worden met de data in de DataTable. De TableAdapters moeten wij nog steeds zelf aanmaken. Door het aanmaken van de getypeerde DataSet wordt er wel een datatype TableAdapter, in dit geval DataSetGamesTableAdapter, door Visual Studio voor ons aangemaakt. We moeten alleen nog een aantal variabelen van dit type aanmaken om ze te kunnen gebruiken:

N

DataSetGamesTableAdapters.GamesTableAdapter gamesTableAdapter; DataSetGamesTableAdapters.GamesPersonagesTableAdapter gamesPersonagesTableAdapter; DataSetGamesTableAdapters.PersonagesTableAdapter personagesTableAdapter;

O

2 Behalve een TableAdapter is er ook een TableAdapterManager, een soort regelneef voor TableAdapters. Ook deze moeten we toevoegen bij de globale variabelen: DataSetGamesTableAdapters.TableAdapterManager tableAdapterManager;

C

3 Uiteraard moeten ook deze variabelen geïnitialiseerd worden. Maak de volgende methode aan waarin we dit gaan doen: private void InitialisatieTableAdapters()

4 Behalve dat we de variabelen gaan initialiseren wordt ook de eigenschap ClearBeforeFill op true gezet. Dit houdt in dat, voordat een DataTable vanuit de database gevuld wordt, deze eerst leeg wordt gemaakt. Voeg aan de methode InitialisatieTableAdapters() de volgende regels toe: // // gamesTableAdapter gamesTableAdapter = new DataSetGamesTableAdapters.GamesTableAdapter(); gamesTableAdapter.ClearBeforeFill = true; // // personagesTableAdapter personagesTableAdapter = new DataSetGamesTableAdapters.PersonagesTableAdapter(); personagesTableAdapter.ClearBeforeFill = true;


Hoofdstuk 3 Database versus Dataset

351

// // gamesPersonagesTableAdapter gamesPersonagesTableAdapter = new DataSetGamesTableAdapters.GamesPersonagesTableAdapter(); gamesPersonagesTableAdapter.ClearBeforeFill = true;

5 Ook de TableAdapterManager moet ingesteld worden. Maak de volgende methode aan: private void InitialisatieTableAdapterManager() en voeg aan deze methode de volgende regels toe:

EP

T

// // instellen tableAdapterManager // tableAdapterManager = new DataSetGamesTableAdapters.TableAdapterManager(); tableAdapterManager.BackupDataSetBeforeUpdate = false; tableAdapterManager.UpdateOrder = DataSetGamesTableAdapters.TableAdapterManager. UpdateOrderOption.InsertUpdateDelete; // // tableAdapterManager koppelen aan de diverse tableAdapters // tableAdapterManager.GamesPersonagesTableAdapter = gamesPersonagesTableAdapter; tableAdapterManager.GamesTableAdapter = gamesTableAdapter; tableAdapterManager.PersonagesTableAdapter = personagesTableAdapter;

C

De hiervoor aangemaakte methodes voegen we toe aan de constructor van het formulier FrmGamesEnPersonages, direct na de regel InitializeComponent();. De constructor ziet er dan als volgt uit:

O

N

InitializeComponent(); InitialisatieDataSet(); InitialisatieTableAdapters(); InitialisatieTableAdapterManager(); InitialisatieDataBindings();

C

Aanpassen InitialisatieDataBindings In de methode InitialisatieDataBindings() staat nog een aantal ‘oude’ verwijzingen, die gaan we hier aanpassen.

1

Zoek de volgende regel op:

masterBindingSourceGames.DataSource = dataSetGames.Tables[“Games”];

Omdat we nu te maken hebben met een getypeerde DataSet veranderen we deze regel zo dat er dit staat: masterBindingSourceGames.DataSource = dataSetGames.Games;

2 Zoek nu de volgende regel op: masterBindingSourceGames.DataSource = dataSetGames.Tables[“Personages”];

Pas deze regel zo aan dat dit er komt te staan: masterBindingSourcePersonages.DataSource = dataSetGames.Personages;


352

Deel 3: Databases

3 Helaas zijn de namen van de relaties iets afwijkend en moeten we die ook nog aanpassen. Zoek de volgende regel op: detailBindingSourceGames_GamesPersonages.DataMember = “relationGames_GamesPersonages”;

Vervang in deze regel “relationGames_GamesPersonages” door “GamesGamesPersonages”. 4 Zoek de volgende regel op: detailBindingSourcePersonages_GamesPersonages.DataMember = “relationPersonages_GamesPersonages”;

T

Vervang in deze regel “relationPersonages_GamesPersonages” door “PersonagesGamesPersonages”.

Maak een Load event aan bij het formulier FrmGamesEnPersonagesMetDatabase.

N

1

C

EP

Vullen DataSet vanuit de database We hebben nu een DataSet met koppelingen naar een database, TableAdapters, die van alles regelen tussen de DataTables en de tabellen in de database. Alleen heeft niemand ooit de opdracht gegeven om de gegevens op te halen uit de database en daarmee de DataSet te vullen. Je kunt het zo zien: de rails zijn aangelegd, de stations zijn er, er zijn treinen, maar nu moeten we met die treinen gaan rijden en passagiers gaan vervoeren, anders heeft de hele infrastructuur geen zin.

2 Plaats de volgende regels in FrmGamesEnPersonagesMetDatabase_Load:

O

gamesTableAdapter.Fill(dataSetGames.Games); personagesTableAdapter.Fill(dataSetGames.Personages); gamesPersonagesTableAdapter.Fill(dataSetGames.GamesPersonages);

C

Hier worden de TableAdapters voor gebruikt: voor het vullen van de DataTables. Let op! De volgorde is hier erg belangrijk. Eerst moet je de look-up DataTables vullen, daarna de hoofd-DataTables en daarna de zogenaamde koppel-DataTables. Doe je dit niet dan krijg je problemen met de referentiële integriteit. Toevoegen rijen Als een nieuwe rij toegevoegd wordt, willen we dat deze rij ook direct in de database wordt opgeslagen. Vervolgens moeten alle door de database gegenereerde waardes ook weer getoond worden in de DataGridView. 1

We gaan een variabele gebruiken om vast te stellen of er een nieuwe rij aangemaakt is.


Hoofdstuk 3 Database versus Dataset

353

Voeg de volgende globale variabele toe: bool nieuweRij = false;

2 Voeg een UserAddedRow event toe aan de DataGridView dgvGames. 3 Als dit event afgaat, dan betekent dit dat de gebruiker bezig is met het toevoegen van een nieuwe rij. Daarom voegen we de volgende regel code toe aan de methode dgvGames_UserAddedRow(): nieuweRij = true;

T

4 Als een gebruiker naar een volgende regel gaat en deze wordt gecontroleerd (gevalideerd) door het systeem dan gaat het event Validated af. Dit event gebruiken we om de gegevens bij te werken in de database en om de gegevens vanuit de database opnieuw te laden (zodat we direct de gegenereerde ID’s te zien krijgen). Voeg een Validated event toe aan de DataGridView dgvGames.

EP

5 Voeg de volgende regels code toe aan de methode dgvGames_Validated():

C

if (nieuweRij) { nieuweRij = false; this.Validate(); masterBindingSourceGames.EndEdit(); tableAdapterManager.UpdateAll(dataSetGames); gamesTableAdapter.Fill(dataSetGames.Games); }

// Algehele controle. // Stop met wijzigen. // Update van de database. // Bijwerken van de DataTable.

N

6 Voeg dezelfde twee events ook toe aan de DataGridView Personages.

O

7 Pas de code zo aan dat de juiste DataTables en TableAdapters gebruikt worden. De variabele nieuweRij kan onveranderd door alle methodes gebruikt worden. Weet je waarom?

C

8 Nu blijft de DataGridView GamesPersonages over. Maar voor de DataGridView GamesPersonages is er geen masterBindingSource, er zijn wel twee detailBindingSources, die afhankelijk van de situatie toegepast moeten worden. Neem de volgende code over in het Validated event van dgvGamesPersonages: if (nieuweRij) { nieuweRij = false; this.Validate(); // Omdat de DataGridView GamesPersonages afhankelijk van of de focus op Games // of Personages een andere DataSource gebruikt wordt hier gekeken aan welke // DataSource de DataGridView gebonden is zodat dezelfde DataSource gebruikt // wordt om de oorspronkelijke situatie te herstellen. if (dgvGamesPersonages.DataSource == detailBindingSourceGames_GamesPersonages.DataSource)


354

Deel 3: Databases

{ detailBindingSourceGames_GamesPersonages.EndEdit(); } else detailBindingSourcePersonages_GamesPersonages.EndEdit(); tableAdapterManager.UpdateAll(dataSetGames); gamesPersonagesTableAdapter.Fill(dataSetGames.GamesPersonages); }

Verwijderen van een rij Ook bij het verwijderen moet de database bijgewerkt worden. Alleen kan het hier erg mis gaan. Je mag namelijk geen rij verwijderen als er nog gerelateerde rijen zijn. Je mag een game niet verwijderen als er nog rijen in GamesPersonages zitten die bij die game horen.

T

Dit is wat er moet gebeuren:

Eerst wordt een rij verwijderd. Dan wordt er een fout geconstateerd: de rij mag niet verwijderd worden. Alleen is de rij al uit de DataGridView verdwenen. We moeten dus de DataTable en daarmee de DataGridView opnieuw vullen. En als we het netjes doen moet de DataGridView ook weer naar de rij scrollen waar we mee bezig waren.

1

We beginnen met het laatste punt: scrollen naar een bepaalde rij. Hiervoor maken we een methode aan met de volgende naam en handtekening:

C

EP

• • • • •

N

void moveToRow(BindingSource bindingSource, DataGridView dgv, int rijnummer)

O

De bindingSource hebben we nodig om naar de volgende rij te kunnen gaan, de DataGridView omdat we daar het huidige rijnummer van de DataGridView vandaan halen en het rijnummer is het nummer van de rij waar we naartoe moeten.

C

2 Als het rijnummer niet gelijk is aan de huidige rij, schuiven we door naar de volgende. Dit doen we door de volgende regels toe te voegen: if (rijnummer != dgv.CurrentRow.Index) { bindingSource.MoveNext(); }

Als we bij de volgende rij zijn aangekomen, moeten we weer controleren of het rijnummer overeenkomt met de huidige rij. Zo niet dan moet er weer doorgeschoven worden. Maar hier hebben we net code voor geschreven! Deze kunnen we dus weer aanroepen. Dit noemen we met een mooi woord recursief programmeren: een methode roept zichzelf aan. Voeg als laatste regel aan het if-statement de volgende regel toe: moveToRow(bindingSource, dgv, rijnummer);


Hoofdstuk 3 Database versus Dataset

355

3 En als de rij gevonden is? Dan stopt het hele circus vanzelf. We hoeven dus geen extra code toe te voegen. 4 Wel moeten we nog de andere punten verwerken in ons programma. Voeg een event UserDeletedRow toe aan de DataGridView dgvGames. 5 We willen weten wat het rijnummer is van de te verwijderen rij zodat we ernaar kunnen terugkeren als er iets mis gaat. Voeg de volgende regel toe aan de methode dgvGames_UserDeletedRow(): int rijnummer = dgvGames.CurrentRow.Index;

EP

try { } catch (Exception exception) { MessageBox.Show(exception.Message); }

T

6 Voeg daarna een standaard try-catch-blok toe met een MessageBox om een gegenereerde foutmelding te kunnen laten zien:

7 Voeg in het try-blok de volgende regel toe:

C

gamesTableAdapter.Update(dataSetGames.Games);

Hiermee wordt geprobeerd om de rij uit de database te verwijderen.

N

8 Als het mis gaat moet het één en ander hersteld worden. Voeg in het catch-blok na de regel met de MessageBox de volgende code toe:

C

O

// Herstellen van de oorspronkelijke situatie. // Opnieuw vullen van de DataTable. gamesTableAdapter.Fill(dataSetGames.Games); // Scrollen naar de oorspronkelijke rij, // bij het vullen staat de cursor weer bovenaan. moveToRow(masterBindingSourceGames, dgvGames, rijnummer); // Selecteren van de actuele rij: de rij die verwijderd moest worden. dgvGames.Rows[rijnummer].Selected = true;

9 Voeg hetzelfde event toe aan DataGridView Personen. 10 Zorg ervoor dat in dit event alle verwijzingen naar de Games-DataTable, -BindingSource en -TableAdapter gewijzigd worden naar de Personages-DataTable, enz. 11 Voeg hetzelfde event toe aan de DataGridView GamesPersonages. 12 Vervang de laatste regel (waar je opnieuw naar de regel gaat die verwijderd had moeten worden) door de volgende regels. Het commentaar vertelt genoeg: // Omdat de DataGridView GamesPersonages afhankelijk van óf de focus op Games óf // op Personages een andere DataSource gebruikt, wordt hier gekeken aan welke // DataSource de DataGridView gebonden is, zodat dezelfde DataSource gebruikt // wordt om de oorspronkelijke situatie te herstellen.


356

Deel 3: Databases

if (dgvGamesPersonages.DataSource == detailBindingSourceGames_GamesPersonages.DataSource) { moveToRow(detailBindingSourceGames_GamesPersonages, dgvGamesPersonages, rijnummer); } else { moveToRow(detailBindingSourcePersonages_GamesPersonages, dgvGamesPersonages, rijnummer); }

13 Kortom: zorg ervoor dat GamesPersonages en Personages net zo werken als Games.

N

C

EP

T

Test de applicatie! Als het goed is zie je weinig verschil met Voorbeeld 6: n:m-koppeling. Het scherm zal er nog steeds als volgt uitzien (als je de database met de volgende informatie vult):

Schermafbeelding 26: Games en personages met database

O

Je zult alleen zien dat de ingevoerde gegevens er de volgende keer nog steeds zijn, en dat de waarden bij de ID’s steeds verder oplopen.

C

Opdracht Access 1: Rugbyteams Zet Voorbeeld 3: 1-op-n (1:n) koppeling om naar een Access-database.

Opdracht Access 2: Auto’s en eigenaren Bouw Opdracht n:m-koppeling 1: Auto’s en eigenaren om naar een programma dat een Access-database gebruikt.

Opdracht Access 3: Klassen en leerlingen * Bouw Opdracht n:m-koppeling 3: Klassen en leerlingen om naar een programma dat een Access-database gebruikt.


Hoofdstuk 3 Database versus Dataset

357

Opdracht Access 4: Politici en partijen Maak een programma waarin je vastlegt welke politicus bij welke partij hoort. Maak gebruik van een Access-database. De tabellen zien er als volgt uit: Tabelnaam: Politici Type

Lengte

Sleutel

Automatisch Moet uniek Mag leeg ophogen zijn zijn

ID

int

-

PK

Ja

Ja

Nee

Voornaam

string

15

-

-

-

-

Voorletters

string

8

-

-

-

-

Tussenvoegsels

string

30

-

-

-

-

Achternaam

string

30

-

-

-

Nee

Titel

string

5

-

-

-

-

Geslacht

string

1

-

-

-

-

Partij

int

-

FK

-

-

Nee

EP

Tabel 21: Structuur tabel Politici

Tabelnaam: Partijen Type

ID

int

Naam

Automatisch Moet ophogen uniek zijn

Mag leeg zijn

PK

Ja

Ja

Nee -

Sleutel

string

8

-

-

Ja

string

40

-

-

Ja

Nee

string

30

-

-

-

-

-

-

-

-

-

-

-

-

O

Logo

-

N

Afkorting

Lengte

C

Kolomnaam

T

Kolomnaam

DateTime

Slogan

string

250

C

JaarVanOprichting

Tabel 22: Structuur tabel Partijen

Let op! Een plaatje sla je vaak niet op in de database. In de database sla je de plek en de bestandsnaam op van het plaatje. In je programma maak je dan code die op basis van deze gegevens het plaatje ophaalt en laat zien (zie Deel 2: Gegevens).


358

Hoofdstuk 4 Drag & Drop

Inleiding Je hebt nu geleerd hoe je in C DataSets aanmaakt met DataTables, hoe je een Connection string vult, hoe je een koppeling tot stand brengt tussen een database en

C

O

N

C

EP

T

een DataTable met een TableAdapter en hoe je een koppeling legt tussen een DataSet en een Control met een DataBinding. Schematisch ziet dat er zo uit:

Schema: 1 Database DataSet Controls

In het algemeen is dit best veel werk. Het kan ook eenvoudiger met behulp van het data-sources-venster en een beetje drag & drop (of op zijn Hollands ‘sleur & pleur’). Alleen kun je de ingewikkelde zaken, zoals een n-op-m-koppeling, dan niet zo maken als wij dat gedaan hebben.


Hoofdstuk 4 Drag & Drop

359

Voorbeeld 8: Drag & Drop 1

Voorbereiding Maak een nieuw project aan met de naam Vakantiewoningen.

2 Maak met een database naar keuze – in het voorbeeld is gebruik gemaakt van Access – een database aan met de naam Vakantiewoningen. 3 Maak binnen de database Vakantiewoningen de volgende twee tabellen aan: Tabelnaam: Streek Type

Lengte

Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

ID

int

-

PK

-

Ja

Nee

Streeknaam

string

60

-

-

Ja

Nee

Land

string

40

-

-

Omschrijving

string

250

-

-

Bergen

bool

-

-

-

Zee

bool

-

-

Zon

bool

-

-

-

-

-

-

-

-

-

-

-

-

-

EP

-

C

Tabel 23: Structuur tabel Streek

T

Kolomnaam

Tabelnaam: Vakantiewoningen Type

Lengte Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

WoningCode

string

8

PK

-

Ja

Nee

string

40

-

-

Ja

Nee

Streek

int

-

FK

-

-

Nee

Beschrijving

string

250

-

-

-

-

Foto

string

250

-

-

Ja

-

Huisdieren

bool

-

-

-

-

-

AantalKamers

int

-

-

-

-

-

WiFi

bool

-

-

-

-

-

PrijsPerWeek

double

-

-

-

-

-

C

O

Plaats

N

Kolomnaam

Tabel 24: Structuur tabel Vakantiewoningen

4 Maak de relatie aan in de database tussen deze twee tabellen. In Access ziet de database er dan als volgt uit:


Deel 3: Databases

EP

T

360

Schermafbeelding 27: Structuur Vakantiewoningen-database

Aanmaken DataSet Open de Data Source Configuration Wizard, maak een connectie met de database Vakantiewoningen, selecteer alle tabellen. Laat bij DataSet de standaardnaam staan.

C

1

C

O

N

2 Klik alle items open. Het data-sources-venster ziet er nu als volgt uit:


361

C

EP

T

Hoofdstuk 4 Drag & Drop

Schermafbeelding 28: Data Source Vakantiewoningen

Slepen en plakken Open het formulier-design-venster.

C

1

O

N

Wat opvalt is dat de tabel Vakantiewoningen twee keer voorkomt: één keer als zelfstandige tabel en één keer als onderdeel van de tabel Streek. Deze laatste zorgt voor de master-detail-koppeling. Je kunt deze tabel dan ook met of zonder de koppeling gebruiken.

2 Ga terug naar het data-sources-venster en klik op het driehoekje achter Streek. De volgende pop-up verschijnt:

Schermafbeelding 29: Pop-up Data Sources


362

Deel 3: Databases

3 Klik in dit pop-up-venster op Details.

EP

T

Klik nogmaals op Streek, en sleep het op het formulier FrmVakantiewoningen. De volgende Controls verschijnen op je scherm:

Schermafbeelding 30: Resultaat slepen en plakken

C

Alles wat je tot dusver met de hand zelf hebt gecodeerd, wordt nu voor je aangemaakt.

N

Sleep nu het item Vakantiewoningen dat deel uitmaakt van Streek op het formulier. Je verandert niets aan de standaardinstellingen. Wat gebeurt er? Je krijgt een volledige DataGridView inclusief de relaties cadeau.

O

Test je programma Als het goed is ziet je scherm er als volgt uit:

C

1


363

EP

T

Hoofdstuk 4 Drag & Drop

C

Schermafbeelding 31: Eindresultaat Vakantiewoningen

2 Je ziet dat er ook een navigatiebalk is toegevoegd en die werkt ook nog!

N

Opdracht Drag & Drop 1: Vakantiewoningen

O

Pas het voorbeeld van de Vakantiewoningen zo aan dat overal nette teksten bij staan en dat de DataGridView alles op een nette manier toont, zoals bijvoorbeeld de juiste kolombreedte.

C

Opdracht Drag & Drop 2: Kastelen en pachters * Maak een database aan met de naam KastelenEnPachters. Voeg aan de database de volgende drie tabellen, inclusief de bijbehorende relaties toe en maak met behulp van de hiervoor geleerde techniek een applicatie voor KastelenEnPachters.


364

Deel 3: Databases

Tabelnaam: Kastelen Kolomnaam

Type

Lengte Sleutel

Automatisch Moet uniek Mag ophogen zijn leeg zijn

ID

int

-

PK

Ja

Ja

Nee

Kasteelnaam

string

15

-

-

Ja

-

Locatie

string

20

-

-

-

-

Streek

int

-

FK

-

-

Nee

Land

string

30

-

-

-

-

OppervlakLandgoed

double

-

-

-

-

-

Tabel 25: Structuur tabel Kastelen

T

Tabelnaam: Pachters Type

Lengte Sleutel Automatisch Moet uniek Mag leeg ophogen zijn zijn

ID

int

-

PK

Ja

Ja

Nee

Pachtboerderij

string

30

-

-

Ja

-

NaamPachter

string

40

-

-

-

-

Kasteel

int

-

FK

-

-

Nee

OppervlakBoerderij

double

-

-

-

-

-

Lengte Sleutel

Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

C

EP

Kolomnaam

N

Tabel26: Structuur tabel Pachters

Tabelnaam: Streek

O

Kolomnaam Type int

-

PK

-

Ja

Nee

Streeknaam

string

60

-

-

Ja

-

Land

string

40

-

-

-

-

Omschrijving

string

250

-

-

-

-

Bergen

bool

-

-

-

-

-

Zee

bool

-

-

-

-

-

Zon

bool

-

-

-

-

-

C

ID

Tabel 27: Structuur tabel Streek

Wat heb je geleerd? Hoe je met behulp van slepen en plakken razendsnel een werkende applicatie in elkaar kunt zetten.


365

Hoofdstuk 5 Multiformulierapplicatie

Eén applicatie, meerdere formulieren

T

EP

• •

Tot nu toe heb je alleen maar programma’s gemaakt die uit één formulier bestaan. Veel programma’s bestaan uit meerdere formulieren. Er zijn twee soorten multiformulierprogramma’s: Programma’s die uit een aantal gelijkwaardige formulieren bestaan. Programma’s die één hoofdformulier kennen en een aantal afhankelijke formulieren. Deze worden niet in dit boek behandeld. Je kunt meer informatie hierover vinden bij Google als je zoekt op docking en forms. Soms moet je gegevens van het ene formulier overdragen naar het volgende. Dit kun je doen door een public variabele op te nemen in het formulier dat aangeroepen wordt.

Voorbereiding Maak een nieuw project aan met de naam VoorbeeldLanden.

N

1

C

Voorbeeld 9: Gegevensoverdracht tussen formulieren

2 Hernoem het formulier Form1 naar FrmVoorbeeldLanden.

O

3 Maak een public Class aan met de naam Land.

C

4 Maak binnen de Class Land de volgende public eigenschappen aan met een getter en een setter: public string Afkorting2 { get; set; } public string Afkorting3 { get; set; } public string Naam { get; set; } public string VolledigeNaam { get; set; } public string Hoofdstad { get; set; } public string Landnummer { get; set; }

Let op! Je kunt ook de in deel 2 bij Voorbeeld enum 2 aangemaakte Class Land gebruiken. Alleen moet je dan nog een lege constructor opnemen, een constructor zonder variabelen. 5 Om de toegevoegde Class bekend te maken aan Visual Studio moet het programma nu eerst opnieuw samengesteld worden. Klik op de menu-optie Build. 6 Klik op Build Solution.


366

Deel 3: Databases

Toevoegen DataSource Let op! Het kan zijn dat het venster om Data Sources te bewerken in andere versies van Visual Studio op een andere plek in het programma te vinden is. 1

We gaan nu weer een DataSource toevoegen aan ons project. Ga naar het data-sources-venster en klik op Add New DataSource.

2 We kunnen nu een object kiezen zoals de door ons gemaakte Class Land.

N

C

EP

T

Kies voor Object. Het volgende venster verschijnt:

Schermafbeelding 32: Select Data Objects

C

O

3 Klik op het driehoekje voor VoorbeeldLanden. Het volgende venster verschijnt:


367

EP

T

Hoofdstuk 5 Multiformulierapplicatie

Schermafbeelding 33: Select Data Objects uitgeklapt 1

C

O

N

C

4 Klik vervolgens op het driehoekje voor het item { } VoorbeeldLanden. Het volgende venster wordt zichtbaar:

Schermafbeelding 34: Select Data Objects uitgeklapt 2

5 Zet een vinkje bij Land en klik op Finish.


368

Deel 3: Databases

Schermafbeelding 35: Data Sources Land

Inrichten FrmVoorbeeldLanden Open het formulier FrmVoorbeeldLanden.

EP

1

T

Je hebt nu een verbinding gemaakt met een intern object van het type Land. Als je de applicatie afsluit zal alle ingevoerde informatie verloren gaan omdat er geen verbinding met een bestand is gemaakt. Om de werking van het overdragen van gegevens tussen formulieren te begrijpen is dit nu niet van belang. Het data-sourcesvenster ziet er als volgt uit:

2 Sleep Land naar het formulier FrmVoorbeeldLanden. Er verschijnt een DataGridView landDataGridView.

C

Voeg een Button toe met het opschrift Einde om de applicatie te kunnen sluiten.

Aanpassen DataGridView Zet de eigenschap (property) AllowUserToAddRows van de DataGridView landDataGridView op false.

O

1

N

Voeg de code om de applicatie te sluiten toe aan de Button btnEinde.

C

2 Zet de eigenschap AddNewItem van de Control landBindingNavigator (de balk met knopjes boven aan het scherm) op None. 3 Zoals je bij de andere eigenschappen, zoals DeleteItem, kunt zien wordt hier rechtstreeks verwezen naar functies van de BindingSource. We gaan de gegenereerde Controls zo aanpassen dat er niet vanzelf nieuwe regels worden toegevoegd. Dit gaan we doen met behulp van een nieuw formulier.

1

Maken van het tweede formulier Maak een formulier aan met de naam FrmNieuwLand.

2 Ga naar het data-sources-venster. Als het goed is, is achter Land in het data-sources-venster een v (pijltje) verschenen:


Hoofdstuk 5 Multiformulierapplicatie

369

Schermafbeelding 36: Data Sources Land met v

EP

T

3 Klik op de v en een uitklapmenu verschijnt.

Schermafbeelding 37: Data Source Land details

4 Kies hier voor Details.

5 Sleep Land op het formulier FrmNieuwLand.

C

6 Verwijder in het formulier FrmNieuwLand de Control landBindingNavigator door deze te selecteren en op Delete te klikken.

C

O

N

7 Voeg twee Buttons toe, een OK Button en een Cancel Button. Geef ze een kleur en een passende naam en je venster ziet er nu als volgt uit:

Schermafbeelding 38: FrmNieuwLand

1

Initialisatie FrmNieuwLand We willen de ingevoerde waarden van het formulier FrmNieuwLand toevoegen aan de DataGridView LandDataGridView van het formulier FrmVoorbeeldLanden.

2 Dit doe je door in het formulier FrmNieuwLand een publieke variabele toe te voegen van het type Land. Voeg de volgende regel toe in het code-venster van het formulier FrmNieuwLand:


370

Deel 3: Databases

public Land land;

Als het formulier FrmNieuwLand wordt geopend moet er direct een nieuwe regel toegevoegd worden aan de DataSource van dit formulier. Ga naar de constructor en voeg daar de volgende regel toe: landBindingSource.AddNew();

1

Instellen DialogResult Als dit formulier aangeroepen is (door op het plusteken te klikken) en vervolgens afgesloten wordt met OK, moet het de ingevoerde gegevens teruggeven aan het formulier FrmVoorbeeldLanden. Hiervoor gebruiken we de eigenschap DialogResult. Open het Click event van de Button btnOK. Je ziet de volgende regels:

T

private void btnOK_Click(object sender, EventArgs e) { }

EP

Voeg de volgende regel toe om de eigenschap DialogResult in te stellen: this.DialogResult = DialogResult.OK;

2 De public variabele land moet de waarde krijgen van de net ingevoerde BindingSource-regel. Voeg de volgende regel toe:

C

land = (Land)landBindingSource.Current;

3 En het formulier moet gesloten worden. Voeg de volgende regel toe:

N

this.Close();

O

4 Als de gebruiker op de Cancel Button klikt moet de DialogResult op Abort ingesteld worden en het formulier gesloten worden. Voeg de volgende regels toe aan het Click event van de Button btnCancel:

C

this.DialogResult = DialogResult.Abort; this.Close();

Instellen FrmVoorbeeldLanden Het formulier FrmNieuwLand is klaar voor gebruik. Nu moet het formulier FrmVoorbeeldLanden nog aangepast worden door de Add Button van de Control landBindingNavigator in te stellen.

1

Ga naar het design-venster van het formulier FrmVoorbeeldLanden.

2 Dubbelklik in de Control landBindingNavigator op het oranje-gele plusteken: de Add Button. Je ziet nu in het code-venster de volgende regels: private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e) { }


Hoofdstuk 5 Multiformulierapplicatie

371

3 We willen het formulier FrmNieuwLand gebruiken. Hiervoor maken we een lokale variabele aan door de volgende regel toe te voegen: FrmNieuwLand frmNieuwLand = new FrmNieuwLand();

4 De volgende regels voegen we toe zodat we kunnen controleren welke waarde wordt teruggegeven: DialogResult.OK of DialogResult.Abort: if (frmNieuwLand.ShowDialog()== DialogResult.OK) { } else { }

EP

T

5 Als het resultaat DialogResult.OK is dan is de public variabele land van het formulier FrmNieuwLand gevuld met de ingevoerde waardes en moeten we die toevoegen aan de BindingSource van FrmVoorbeeldLanden. Voeg aan het if-blok de volgende regel toe: landBindingSource.Add(frmNieuwLand.land);

6 Als het resultaat iets anders is, moet er een foutboodschap verschijnen met daarin de naam van het formulier (FrmNieuwLand) verwerkt. Voeg de volgende regels toe aan het else-blok:

O

Test het programma Je programma moet er ongeveer als volgt uitzien als je op het plusteken geklikt hebt:

C

1

N

C

MessageBox.Show(“Het toevoegen van een land vanuit het formulier:” + frmNieuwLand.Name + “ is door de gebruiker afgebroken.”); frmNieuwland.Dispose();

Schermafbeelding 39: Voorbeeld Landen eindresultaat


372

Deel 3: Databases

Opdracht Multiformulier 1: Rugbyteams Maak op basis van Opdracht Access 1: Rugbyteams een multiformulierapplicatie waarbij twee formulieren gemaakt worden, één om de teams in te voeren en één om de spelers in te voeren. Vanuit de teams moet je een speler kunnen selecteren, maar ook een nieuwe speler kunnen toevoegen.

Opdracht Multiformulier 2: Auto’s en eigenaren

T

Maak een programma op basis van Opdracht n:m-koppeling 1: Auto’s en eigenaren waarbij er drie formulieren worden gebruikt: één om de auto’s in te voeren, één om de personen in te voeren en één waarbij de koppeling tussen beide gelegd wordt (eigenaren). Uiteraard moeten de gegevens van alle formulieren altijd up-to-date zijn.

EP

Meerdere formulieren bij start applicatie

C

Bij de start van een programma wordt op enig moment een variabele aangemaakt van het type van het formulier dat je wilt gaan gebruiken om vervolgens dit formulier te openen. Op deze manier kun je ook eerst het ene formulier openen, de login-gevens laten invoeren, deze laten controleren, het formulier weer afsluiten en daarna, tenminste als het login-proces goed verlopen is, het daadwerkelijke programma opstarten.

N

Doe je dit niet, dan heeft de potentiële hacker al één voet tussen de deur.

Voorbereiding Maak een nieuw project aan met de naam VoorbeeldLogin.

C

1

O

Voorbeeld 10: Login-formulier

2 Hernoem Form1 naar FrmVoorbeeldLogin. 3 Stel de volgende eigenschappen van het formulier FrmVoorbeeldLogin als volgt in: • Text: Login • MaximizeBox: False • MinimizeBox: False • Opacity: 90 4 Plaats de volgende Controls op het formulier FrmVoorbeeldLogin (zie pagina 365 voor de lay-out op het formulier):


Hoofdstuk 5 Multiformulierapplicatie

373

Type

Naam

Eigenschap

Waarde

Panel

pnlLogin

BackColor

Cornsilk

Op dit Panel plaats je de volgende Controls: Label

label1

Text

Gebruikersnaam

TextBox

txbGebruikersnaam

TabIndex

1

Label

label2

Text

Wachtwoord

TextBox

txbWachtwoord

TabIndex

2

PassWordChar

*

Onder het Panel plaats je de volgende Buttons:

Button

btnOK

Text

btnCancel

OK

BackColor

LightGreen

Text

Cancel

BackColor

Tomato

EP

Tabel 28: Controls FrmVoorbeeldLogin

T

Button

De eigenschap PassWordChar zorgt ervoor dat er, in dit geval, een sterretje verschijnt als je een wachtwoord invoert.

N

C

Lay-out-check gebruikersnaam Een gebruikersnaam moet minimaal zes tekens lang zijn en mag geen spaties bevatten. Dit zijn regels die voor dit programma gelden en die we gaan invoeren. Als de invoer niet aan deze eisen voldoet dan is de gebruikersnaam per definitie fout en moeten we het systeem afsluiten.

Maak twee globale variabelen aan door de volgende regels toe te voegen:

C

1

O

Let op! Uiteraard vertel je er niet bij waarom de gebruiker precies geen toegang krijgt. Doe je dat wel, dan is de hacker weer een stap dichter bij zijn doel: ongeautoriseerde toegang tot jouw systeem.

string gebruikersnaam = “”; int minimaleLengteGebruikersnaam = 6;

2 Maak voor de TextBox txbGebruikersnaam een TextChanged event aan. 3 Voeg de volgende regels code toe aan dit event dat checkt of de ingevoerde tekst geen spaties bevat en tenminste de minimaal benodigde lengte heeft: if (!txbGebruikersnaam.Text.Contains(‘ ‘) && txbGebruikersnaam.Text.Length > minimaleLengteGebruikersnaam ) { gebruikersnaam = txbGebruikersnaam.Text; }


374

• • • • •

Deel 3: Databases

Lay-out check wachtwoord Ook de lay-out van het wachtwoord moet meestal aan een aantal gestelde regels voldoen. In dit voorbeeld zijn dat: Een wachtwoord bestaat uit tenminste 8 tekens moet tenminste één hoofdletter bevatten moet tenminste één leesteken bevatten uit de reeks ! @    ^ & * ( ) < > ? { } [ ] , . _moet tenminste één cijfer bevatten mag geen =-teken bevatten Hiervoor gaan we een aantal methodes maken. Eerst maken we een methode die checkt of een teken een cijfer is of niet. Maak een methode met de volgende naam en handtekening:

EP

private bool isCijfer(char teken)

T

1

2 Voeg de volgende code toe aan de methode:

O

N

C

switch (teken) { case ‘0’: return true; case ‘1’: return true; case ‘2’: return true; case ‘3’: return true; case ‘4’: return true; case ‘5’: return true; case ‘6’: return true; case ‘7’: return true; case ‘8’: return true; case ‘9’: return true; default: return false; }

C

Bij deze switch gebruik je geen break-commando. Dit is niet nodig omdat het commando return automatisch de methode afbreekt.

3 De volgende methode checkt of een teken één van de voorgeschreven leestekens is. Maak een methode aan met de volgende naam en handtekening: private bool isLeesteken(char teken)

4 Voeg de volgende code toe aan de methode isLeesteken() om te checken of het ingevoerde teken gelijk is aan één van de voorgeschreven leestekens: switch (teken) { case ‘!’: return true; case ‘@’: return true; case ‘#’: return true; case ‘$’: return true; case ‘%’: return true;


Hoofdstuk 5 Multiformulierapplicatie

375

T

case ‘^’: return true; case ‘&’: return true; case ‘*’: return true; case ‘(‘: return true; case ‘)’: return true; case ‘<’: return true; case ‘>’: return true; case ‘?’: return true; case ‘{‘: return true; case ‘}’: return true; case ‘[‘: return true; case ‘]’: return true; case ‘,’: return true; case ‘.’: return true; case ‘ ‘: return true; case ‘_’: return true; case ‘-’: return true; default: return false; }

EP

5 Vervolgens hebben we een methode nodig die checkt of een teken een hoofdletter is of niet. Maak een methode aan met de volgende naam en handtekening: private bool isHoofdletter(char teken)

C

6 De code om te controleren of een teken een hoofdletter is, is vrij eenvoudig. De hoofdletters bevinden zich in de ASCII-tabel op positie 65 tot positie 90. Dus als een teken zich in die range bevindt is het een hoofdletter. Voeg de volgende code toe aan de methode isHoofdletter:

O

N

if ((int)teken >= 65 && (int)teken <= 90) { return true; } else return false;

C

7 Nu voegen we een methode toe die op niet-toegestane-tekens, in dit geval het is gelijk teken (=) controleert. Maak een methode aan met de volgende handtekening en naam: private bool isToegestaan(char teken)

8 Voeg de volgende code toe die checkt of het teken toegestaan is of niet. Er is gebruik gemaakt van een switch zodat de code eenvoudig met andere tekens is uit te breiden. switch (teken) { case ‘=’: return false; default: return true; }

9 Tot slot voegen we de vorige methodes samen in één methode die de ingevoerde tekens controleert. Maak een methode aan met de volgende naam en handtekening:


376

Deel 3: Databases

private bool checkVerplichteTekensAanwezig(string wachtwoord)

10 Binnen deze methode maken we gebruik van drie lokale variabelen waarin we vastleggen of aan één van de criteria is voldaan. Voeg de volgende regels code toe om deze variabelen aan te maken binnen de methode checkVerplichteTekensAanwezig(): bool cijferAanwezig = false; bool leestekenAanwezig = false; bool hoofdletterAanwezig = false;

11 Vervolgens moeten we ieder teken in het wachtwoord gaan bekijken. Voeg de volgende regels toe:

T

foreach (char teken in wachtwoord.ToArray()) { }

EP

We gebruiken hier de ToArray()-functie zodat we teken (char) voor teken (char) door de string-variabele kunnen lopen. 12 Eerst checken we of het teken toegestaan is. Zo niet, dan kan de hele methode afgebroken worden met als resultaat false. Voeg de volgende regels toe binnen het foreach-blok:

return false;

N

}

C

if (!isToegestaan(teken)) {

Het uitroepteken zorgt voor de ontkenning.

O

13 Als er al een cijfer in het wachtwoord aanwezig is, zal de variabele cijferAanwezig op waar staan. En dan is het niet meer nodig om te kijken of een teken een cijfer is. Dit doen we door de volgende regels toe te voegen:

C

if (!cijferAanwezig) { }

14 Als er nog geen cijfer is gevonden kunnen we eenvoudig de variabele cijferAanwezig de waarde laten krijgen van de methode isCijfer. Voeg de volgende regel toe binnen het if-statement: cijferAanwezig = isCijfer(teken);

15 Bij de leesteken-check doen we in feite precies hetzelfde als bij de cijfercontrole. Voeg de volgende regels toe: if (!leestekenAanwezig) { leestekenAanwezig = isLeesteken(teken); }


Hoofdstuk 5 Multiformulierapplicatie

377

16 En bij de hoofdlettercheck is het al niet anders dan bij de cijfer- en leesteken-check. Voeg de volgende regels toe: if (!hoofdletterAanwezig) { hoofdletterAanwezig = isHoofdletter(teken); }

17 Nadat alle tekens gecontroleerd zijn, moet, als aan alle drie de voorwaarden is voldaan, de waarde true teruggegeven worden. Dit kun je natuurlijk doen met behulp van if-statements. Maar je kunt dit ook doen door logische beweringen samen te voegen: drie beweringen zijn samen alleen waar als ze allemaal waar zijn. Als één van de beweringen niet waar is dan zal het resultaat automatisch niet waar zijn. Voeg de volgende regel toe:

T

return cijferAanwezig & leestekenAanwezig & hoofdletterAanwezig;

EP

18 Ook voor het wachtwoord maken we gebruik van twee globale variabelen. Voeg de volgende twee globale variabelen toe: string wachtwoord = “”; int minimaleLengteWachtwoord = 8;

C

19 Ook hier wordt direct bij de invoer het wachtwoord gecontroleerd. Voldoet het niet aan de eis dan blijft de variabele wachtwoord leeg. Voeg de volgende code toe aan het TextChanged event van de TextBox txbWachtwoord:

O

N

if (checkVerplichteTekensAanwezig(txbWachtwoord.Text) && txbWachtwoord.Text.Length > minimaleLengteWachtwoord) { wachtwoord = txbWachtwoord.Text; }

C

Het uiteindelijke resultaat is dat als een wachtwoord en/of een gebruikersnaam niet aan de verplichte lay-out voldoen er niet eens gekeken wordt of deze combinatie van gebruikersnaam en wachtwoord voorkomt: ze mogen niet voorkomen omdat ze niet aan de regels voldoen.

Aanmaak DataSet en DataTable Als de lay-out van zowel gebruikersnaam als wachtwoord goed is, moeten we natuurlijk ook nog kijken of deze ergens bestaan. We hebben dus een systeem nodig waarin deze gegevens opgeslagen liggen. In dit voorbeeld doen we dit nog hard gecodeerd. Maar wel op zo’n manier dat dit eenvoudig omgebouwd kan worden naar het gebruik met een database. We maken ook hier weer gebruik van een DataSet en een DataTable.

1

Voeg de volgende globale variabelen toe: DataSet dsLogin = new DataSet(“Login”); DataTable dtAutorisatie;


378

Deel 3: Databases

In plaats van dataSetLogin schrijven we dsLogin, dit is een goed gebruik. Het is qua schrijfwijze korter maar geeft nog steeds duidelijk aan waar we mee bezig zijn. 2 Maak een methode aan met de naam createDataTableAutorisatie die een DataTable aanmaakt met de volgende structuur: DataTable-naam: dtAutorisatie Kolomnaam

Type

Lengte Sleutel Automatisch ophogen

Moet uniek zijn

Mag leeg zijn

ID

int

-

PK

Ja

Ja

Nee

Gebruikersnaam

string

50

-

-

Ja

-

Wachtwoord

string

50

-

-

-

-

T

Tabel 29: DataTable dtAutorisatie

EP

3 Maak een methode aan om een DataTable te vullen met standaardwaarden, met de naam fillAutorisatie. In de DataTable dtAutorisatie moeten de volgende regels opgenomen worden: Gebruikersnaam MackJoJo met wachtwoord 1A!oliebol en ID 0, Gebruikersnaam HansK met wachtwoord appelFlap3? En ID 1,

C

Gebruikersnaam FransVanZet met wachtwoord 2#$!Appelstroop en ID 2.

4 Voeg de volgende regels toe aan de constructor van het formulier FrmVoorbeeldLogin zodat de DataSet en de DataTable aangemaakt en gevuld worden:

O

N

dtAutorisatie = createAutorisatie(); dsLogin.Tables.Add(dtAutorisatie); fillAutorisatie();

C

Er wordt hier gebruik gemaakt van een variabele met de naam dtAutorisatie, hierdoor is het eenvoudiger om met deze DataTable te werken. Check login-gegevens De lay-out van de login-gegevens is gecontroleerd, er is een DataTable waarin logingegevens zijn opgeslagen, nu moet er alleen gecontroleerd worden of de ingevoerde gebruikersnaam-en-wachtwoord-combinatie voorkomt in de DataTable.

1

Maak een methode aan met de volgende naam en handtekening: private bool loginExists(string gebruikersnaam, string wachtwoord)

2 Vervolgens maken we een filter aan dat gebruikt kan worden op de DataTable dtAutorisatie door de volgende regel toe te voegen: string filter = “Gebruikersnaam = ‘” + gebruikersnaam + “’ AND wachtwoord = ‘” + wachtwoord + “’”;

Let op! Er staan hier geen tiepvauten. Alle aanhalingstekens, zowel de enkele als de dubbele, moeten exact overgenomen worden!


Hoofdstuk 5 Multiformulierapplicatie

379

3 Op basis van dit filter worden rijen uit de DataTable dtAutorisatie geselecteerd. Als er geen rijen gevonden worden, wordt er geen toegang verleend. Als er meer dan één rij gevonden wordt ook niet, want op basis van het filter kan er maximaal één rij gevonden worden. Voeg de volgende code toe aan de methode loginExists(): if (dtAutorisatie.Select(filter).Count()== 1) { return true; } else return false;

EP

T

1

Gebruik DialogResult We gaan dit formulier gebruiken om te bepalen of iemand toegang krijgt tot een ander formulier. Dit kun je doen met behulp van de eigenschap DialogResult van een formulier. Dit is een enum met een klein aantal waarden, waaronder DialogResult.Abort en DialogResult.OK. We hebben dus een kleine methode nodig die de boolean waarde van loginExists() omzet naar een DialogResult waarde.

2 Maak een methode aan met de volgende naam en handtekening: private DialogResult boolNaarDialogResult(bool check)

C

3 Voeg de volgende regels code toe die ervoor zorgen dat de waarde true omgezet wordt naar DialogResult.Ok en false omgezet wordt naar DialogResult.Cancel:

N

if (check) { return DialogResult.OK; } return DialogResult.Cancel;

C

O

4 Door de volgende regels code toe te voegen aan het Click event van de Button btnOK worden de ingevoerde waardes gecontroleerd en omgezet naar een DialogResult en sluiten we het formulier: this.DialogResult = boolNaarDialogResult(loginExists(gebruikersnaam, wachtwoord)); this.Close();

5 Behalve de Button btnOk is er ook nog een Button met de naam btnCancel. Hier wordt de login afgebroken door de gebruiker. We maken de naam en het wachtwoord leeg, de eigenschap DialogResult krijgt de waarde DialogResult.Abort en het formulier wordt gesloten door de volgende regels toe te voegen aan het Click event van de Button btnCancel: txbGebruikersnaam.Text = “”; txbWachtwoord.Text = “”; this.DialogResult = DialogResult.Abort; this.Close();


380

Deel 3: Databases

Aanmaken tweede formulier Hiermee is het login-formulier klaar voor gebruik. Wat we nog nodig hebben is een ander formulier dat geopend wordt zodra de login succesvol verlopen is. 1

Ga in de menubalk naar de optie Project en klik deze aan.

2 Klik de optie Add Windows Form aan. 3 Er wordt gevraagd om het formulier een naam te geven. Geef het nieuwe formulier de volgende naam: FrmHoofdprogramma. Er wordt een nieuw leeg formulier aangemaakt en getoond in het design-venster.

T

4 Je mag hier zoveel Controls opzetten als je wilt. Voor het voorbeeld is alleen belangrijk dat er een ander formulier geopend wordt.

1

Open de Solution Explorer.

2 Klik op program.cs.

C

Je ziet nu de volgende code:

EP

Toegang regelen tot het formulier Normaal start een Windows-applicatie direct het formulier. De code hiervan vind je in de Solution Explorer in het bestand met de naam program.cs.

N

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms;

C

O

namespace VoorbeeldLogin { static class Program { // <summary> // The main entry point for the application. // </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new FrmVoorbeeldLogin()); } } }

Deze code start het formulier FrmVoorbeedLogin op. We willen echter het formulier FrmHoofdprogramma starten, maar alleen als de eigenschap DialogResult van het formulier FrmVoorbeeldLogin DialogResult.OK is.


Hoofdstuk 5 Multiformulierapplicatie

381

3 Voeg in Main na de regel Application.SetCompatibleTextRenderingDefault(false); een regel toe om een nieuwe variabele aan te maken van het type FrmVoorbeeldLogin: Form frmVoorbeeldlogin = new FrmVoorbeeldLogin();

4 Nu kunnen we het formulier gebruiken net zoals we dat met bijvoorbeeld een ColorDialogBox doen. Voeg de volgende regels toe: if (frmVoorbeeldlogin.ShowDialog() == DialogResult.OK) { } else { }

EP

5 Voeg bij het if-statement de volgende regel toe:

T

We voegen twee MessageBoxen toe die het resultaat van de login-procedure aan de gebruiker meedelen.

MessageBox.Show(“Welkom in ons systeem.”);

6 Voeg bij het else-statement de volgende regel toe:

MessageBox.Show(“U verlaat ons systeem zonder in te loggen.”);

C

7 Verplaats de regel Application.Run(new FrmVoorbeeldLogin()); naar het if-statement zodat het hoofdformulier alleen na een succesvolle login wordt opgestart.

N

8 Natuurlijk willen we het formulier frmVoorbeeldLogin niet nogmaals opstarten maar moet het formulier FrmHoofdprogramma opgestart worden. Vervang daarom in de zojuist verplaatste regel FrmVoorbeeldLogin door FrmHoofdprogramma.

O

Test je programma Als het goed is zie je eerst een login-venster verschijnen:

C

1

Schermafbeelding 40: Login-venster

(Doordat de opacity (ondoorschijnendheid) van het login-formulier op 90 is ingesteld kun je vaag de onderliggende vensters zien.) 2 Als je een goede combinatie van gebruikersnaam en wachtwoord invult, bijvoorbeeld MackJoJo en 1A!oliebol krijg je een welkomstboodschap te zien:


382

Deel 3: Databases

Schermafbeelding 41: Welkomstboodschap

3 Vervolgens kom je in je hoofdscherm terecht.

Opdracht Multiformulier 3: Login

N

C

Werken met meerdere formulieren Het doorgeven van gegevens tussen formulieren Een eenvoudig login-systeem Het instellen van de opacity van een formulier Het gebruik van een formulier met een DialogResult Het combineren van logische beweringen Kennisgemaakt met de opstart-file program.cs

C

O

• • • • • • •

EP

Wat heb je geleerd?

T

Voorzie het programma uit Voorbeeld 6: n:m-koppeling van een login-systeem en -formulier.


383

Hoofdstuk 6 Online databases

MySQL

EP

T

Microsoft Access is een desktop-database. Dat betekent dat Access meestal door één of enkele gebruikers tegelijkertijd gebruikt wordt. Als programmeur wil je natuurlijk ook werken met de grote databases waar duizenden mensen tegelijkertijd mee werkten en waar miljoenen gegevens in op zijn geslagen. MySQL is zo’n database. Het is een zogenaamde Open Source database. Dit betekent dat MySQL gratis te downloaden is.

Visual Studio uitbreiden met MySQL

Op de site van MySQL (www.MySQL.com) staat een installer voor Windows die volledige integratie met Visual Studio belooft. Op het moment van het schrijven van dit boek was deze download beschikbaar op: http://dev.mysql.com/tech-resources/articles/mysql-installer-for-windows.html

C

1

N

2 De MySQL community heeft ook enkele voorbeeld-databases beschikbaar die vrij te downloaden zijn. Wij maken hier gebruik van de voorbeeld-database Classic Models. Deze database is te downloaden via http://www.mysqltutorial.org/ mysql-sample-database.aspx

O

3 Installeer de database Classic Models op je MySQL server.

C

Voorbeeld 11: MySQL 1

Voorbereiding MySQL Zorg dat je MySQL server draait en benaderbaar is.

2 Zorg dat je bij de database Classic Models kunt. 3 Zorg dat je de volgende informatie beschikbaar hebt: • De host-name van je server • Een gebruikersnaam met administrator-rechten op de database Classic Models • Het wachtwoord van deze gebruiker • De poort waarop MySQL draait (vaak 3306 of 3307)

1

Voorbereiding project Maak een Visual Studio-project aan met de naam VoorbeeldMySqlClassicModels.

2 Geef het formulier de naam FrmVoorbeeldMySqlClassicModels.


384

1

Deel 3: Databases

Aanmaken DataSource Ga naar het data-sources-venster en kies voor Database.

2 Kies Dataset. 3 Klik in het choose-your-data-connection-venster voor New Connection.

EP

T

Het volgende venster verschijnt:

C

Schermafbeelding 42: MySQL Server Connection

N

4 Geef bij Server name de naam van de server in. Als je gebruik maakt van een server die draait op de Local Host dan is de servernaam gelijk aan de naam van je PC. De naam van je PC kun je vinden bij systeeminfo.

O

5 Voer bij User name de gebruikersnaam in van een gebruiker van de MySQL-database.

C

6 Voer bij Password het wachtwoord in van deze gebruiker. 7 Klik op de Advanced-knop. Je komt nu in het volgende venster:


385

8 Zoek het item Port op.

EP

Schermafbeelding 43: MySQL Advanced

T

Hoofdstuk 6 Online databases

9 Vul hier het poortnummer in van je MySQL server.

10 Klik op OK en je komt terug in het Add connection-venster.

O

N

C

11 Klik op Test Connection. Als het goed is krijg je de volgende boodschap:

C

Schermafbeelding 44: Test connection succeeded

12 Klik op OK.

13 Kies in het Database name-veld voor Classic Models.


Deel 3: Databases

Schermafbeelding 45: Add Connection

T

386

C

O

N

C

EP

14 Klik op OK en je komt in het volgende venster:

Schermafbeelding 46: Connection string

15 In dit venster zet je een vinkje bij No, exclude sensitive data from the connection string. I will set this information in my application code, want dat is precies wat we gaan doen. 16 Klik twee keer op Next. Je komt dan in het volgende venster:


387

EP

T

Hoofdstuk 6 Online databases

Schermafbeelding 47: Classic Models DataSet 1

C

O

N

C

17 Zet in dit venster in alle vakjes vinkjes en klik op Finish. Je komt nu weer in het Data Sources venster:

Schermafbeelding 48: Classic Models DataSet 2

1

Inrichten formulier FrmVoorbeeldMySqlClassicModels Open het formulier FrmVoorbeeldMySqlClassicModels in het design-venster.

2 Ga naar het Data Sources venster. 3 Klik Productlines open.


388

Deel 3: Databases

T

Schermafbeelding 49: Productlines

EP

4 Sleep de drie velden productLine, textDescription en htmlDescription op het formulier FrmVoorbeeldMySqlClassicModels. 5 Sleep de tabel products op het formulier FrmVoorbeeldMySqlClassicModels. 6 Plaats nog een Button btnEinde rechtsonder op het formulier om het formulier af te sluiten en je formulier is gereed.

Voeg een inlog-scherm toe zoals je hebt geleerd in Voorbeeld 10: Login-formulier.

N

1

C

Inlog-scherm Het formulier is gereed, maar zal onherroepelijk vastlopen omdat er geen toegang is tot de database.

Ga naar het code-venster van het formulier FrmLogin.

C

1

O

Wijzigen MySQL Connection string Omdat we nu met een MySQL-database gaan werken moeten we de MySQL-connection aanpassen. Hiertoe gaan we de code van het login-formulier wijzigen.

2 Voeg bovenaan je programma de volgende code toe om de MySQL-bibliotheken te kunnen gebruiken: using MySql.Data.MySqlClient;

Het login-formulier moet als resultaat ook een connection string genereren met een wachtwoord en een gebruikersnaam. Hiertoe nemen we een publieke globale variabele op van het type MySqlConnection: public MySqlConnection mySqlConnection;

We willen de ingevoerde gebruikersnaam en het wachtwoord toe kunnen voegen aan de connection string. Maak de volgende methode aan:


Hoofdstuk 6 Online databases

389

private MySqlConnection makeMySQLConnection(string userID, string password)

3 We gebruiken de klasse MySqlConnectionStringBuilder om een ConnectionString op te bouwen. Dit heeft alles met veiligheid te maken: als je velden zomaar achter elkaar plakt ben je kwetsbaar voor zogenaamde SQL-injections. De MySqlConnectionStringBuilder helpt dit soort bedreigingen te voorkomen. Voeg de volgende regel toe aan de methode makeMySQLConnection: MySqlConnectionStringBuilder connectionString = new MySqlConnectionStringBuilder();

De ConnectionString bouwen we op door de verschillende parameters toe te voegen. Voeg de volgende regels code toe:

EP

T

connectionString.Server = “MICHIEL-PC”; // De naam van je eigen PC / server! connectionString.UserID = userID; connectionString.Password = passWord; connectionString.Database = “ClassicModels”; connectionString.Port = 3307;

Geef de opgebouwde ConnectionString terug door de volgende regel toe te voegen:

Instellen OK Button We gaan nu het Click event van de Button btnOK inhoud geven. Eerst voegen we de volgende regel toe om een ConnectionString op te bouwen:

N

1

C

return new MySqlConnection(connectionString.ConnectionString);

mySqlConnection = makeMySQLConnection(txbGebruiker.Text, txbWachtwoord.Text);

O

2 Vervolgens gaan we testen of we met deze ConnectionString toegang krijgen of niet. Voeg een try-catch-blok toe.

C

3 Voeg binnen het try-gedeelte de volgende code toe om de verbinding te openen, het DialogResult in te stellen, de verbinding weer te sluiten en het login-formulier af te sluiten: mySqlConnection.Open(); this.DialogResult = DialogResult.OK; mySqlConnection.Close(); this.Close();

4 En binnen het catch-gedeelte laat je de boodschap zien die bij de foutmelding hoort. Dit mag geen probleem meer zijn. Instellen Close Button Het formulier sluiten… dat kun je nu echt wel zelf bedenken. Het login-formulier is gereed.


390

1

Deel 3: Databases

Aanpassen program.cs Vervang de bestaande code onder static void Main() door de volgende regels: Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); FrmLogin frmLogin = new FrmLogin(); if (frmLogin.ShowDialog() == DialogResult.OK) { Application.Run(new FrmVoorbeeldMySqlClassicModels (frmLogin.mySqlConnection)); }

2 Het grote verschil met het vorige login-formulier is dat hier nu als parameter de publieke variabele mySQLConnection van het formulier frmLogin wordt meegegeven in de constructor van het formulier en dat dat een foutmelding oplevert.

T

Deze fout lossen we eenvoudig op door het formulier aan te passen.

EP

1

Aanpassen FrmVoorbeeldMySqlClassicModels Ga naar het code-venster van het formulier FrmVoorbeeldMySQLClassicModels.

2 Ga naar de constructor van het formulier.

3 Wijzig de eerste regel zo dat er het volgende komt te staan:

C

public frmVoorbeeldMySqlClassicModels(MySql.Data.MySqlClient.MySqlConnection mySqlConnection)

N

4 Nu gaan we de connection gebruiken om de gegevens op te halen. Voeg de volgende regel toe aan de constuctor als laatste regel:

1

O

loadData(mySqlConnection);

Aanmaken loadData Maak een methode aan met de naam loadData():

C

private void loadData(MySql.Data.MySqlClient.MySqlConnection mySqlConnection)

2 Voeg aan deze methode een try-catch-blok toe.

3 Voeg binnen het try-blok de volgende code toe om de twee DataTables te koppelen aan de juiste tabellen in de database: productsTableAdapter.Connection = mySqlConnection; productlinesTableAdapter.Connection = mySqlConnection;

4 Voeg na deze regels de volgende code toe om de tabellen te vullen met gegevens: productsTableAdapter.Fill(this.classicModelsDataSet.products); productlinesTableAdapter.Fill(this.classicModelsDataSet.productlines);

5 Uiteraard voeg je een foutboodschap toe en zorg je voor een fatsoenlijke exit-knop.


Hoofdstuk 6 Online databases

1

391

Test je programma Test je programma. Als het goed is ziet het er, nadat je ingelogd hebt, als volgt uit:

T

Schermafbeelding 50: Eindresultaat MySQL ClassicModels

EP

Let op! Log niet in met ‘root’. Deze gebruiker is niet geschikt om databases als eindgebruiker te benaderen. Maak een aparte gebruiker aan met alle rechten op de database Classic Models en gebruik die om in te loggen.

C

Opdracht MySQL 1: Adressenlijst

N

Maak een MySQL-database met daarin één tabel waarin je de namen, adressen, e-mail en telefoongegevens van mensen kunt opslaan. Maak ook een C-programma waarmee je deze gegevens kunt aanpassen.

O

Opdracht MySQL 2: Meerdere adressen

C

Maak een MySQL-database met daarin de mogelijkheid om meerdere adressen per persoon op te nemen. Maak ook het bijbehorende C-programma.

Opdracht MySQL 3: Voetbaluitslagen Maak een programma in combinatie met een MySQL-database waarin per gespeelde wedstrijd van de voetbalcompetitie de volgende gegevens worden bijgehouden: Naam voetbalteams Samenstelling voetbalteams Datum en tijdstip van de wedstrijd De naam van de scheidsrechter De namen van de grensrechters, en uiteraard De uitslag


392

Deel 3: Databases

Opdracht MySQL 4: Classic Models compleet * Maak een compleet werkend systeem voor de database Classic Models. Pas hier al het geleerde in toe: look-up DataTables, ComboBoxen, DataGridViews, n:m-vensters, ToolTips, kortom alles wat je tot nu toe geleerd hebt. Zorg er in ieder geval voor dat je alle tabellen kunt benaderen en wijzigen.

Microsoft SQL Server Express

T

Je hebt nu van twee programma’s, MS Access en MySQL, gezien hoe je er verbinding mee maakt. In principe weet je nu hoe je verbinding kunt maken met elke database. Daarom mag je de volgende verbinding zelf uitpuzzelen, waarbij je je verworven kennis op dit gebied toepast.

EP

Zoals elke database is ook Microsoft SQL Server bedoeld voor het opslaan van grote hoeveelheden data ergens in de wereld in een datacenter op een server. Je kunt echter ook de light versie, SQL Server Express, lokaal installeren op je PC om software te ontwikkelen. Hoe je dit doet valt buiten het bestek van dit boek. Op het internet kun je uitgebreide handleidingen vinden over hoe je Microsoft SQL Server Express installeert.

N

C

Wat blijft is de connectie met de database, of deze nu lokaal staat of ergens in een datacenter: je moet de DataSources configureren zoals je hiervoor ook hebt gedaan. Op het internet (zoektermen: datasource c instellingen sql server express) kun je vinden welke parameters je moet instellen en hoe je ze moet instellen.

O

Bij het maken van onderstaande opdracht zul je merken dat het eigenlijk niet zoveel uitmaakt of je nu een MySQL-, SQL-, Oracle- of welke database server dan ook hebt.

C

Opdracht SQL Server Express 1 * Maak in je eigen lokale MySQL Server Express een database aan volgens Opdracht Access 2: Auto’s en eigenaren. Maak een nieuw project aan met een connectie naar deze database en natuurlijk de formulieren om de data in te vullen.

Wat heb je geleerd? Om te gaan met: • • • •

TableAdapters DataSources ConnectionStrings

Recursief programmeren


Hoofdstuk 6 Online databases

Connectie met Microsoft Access Connectie met MySQL Connectie met een willekeurige andere database

C

O

N

C

EP

T

• • •

393


394

Nawoord Deel 3

In het eerste deel van Leren programmeren in C maakte je kennis met C. In het tweede deel heb je kennisgemaakt met allerlei gegevensstructuren binnen en buiten C. In dit deel heb je geleerd hoe je met databases en DataSets om moet gaan. In feite kun je nu complete programma’s bouwen.

T

Nu je de basiskennis hebt van de taal wordt het tijd om te kijken of je ook onderhoudbare en herbruikbare programma’s kunt gaan maken. Dat gaan we doen in deel 4: Objectgeoriënteerd programmeren.

C

O

N

C

EP

Keep coding!


C

O

N

C

EP

T

Deel 4: Objectoriëntatie


C EP

C

N

O T


397

Inleiding Deel 4: Objectoriëntatie

C# OO & UML OO, object wat? Objectoriëntatie of afgekort OO. In veel personeelsadvertenties, op het internet en in computertijdschriften kom je de kreet tegen. Het heeft iets met programmeren te maken, maar wat dan? Dat ga je in dit deel leren.

C

EP

T

Na drie delen Leren programmeren in C doorgewerkt te hebben, heb je al enige programmeerervaring. Je kunt inmiddels al werkende programma’s maken. Maar om iets te maken dat je nog een keer in een ander project kunt gebruiken, of nog beter: een stuk code dat een ander kan gebruiken, is iets heel anders. Vaak ben je eindeloos lang bezig om uit te vinden hoe het zou moeten werken en dan ben je weer eindeloos lang bezig om de bestaande code aan te passen. Ondertussen wordt je werk voor een ander vaak steeds moeilijker te lezen. En dan hebben we het nog niet over een programma dat je zelf lang geleden gemaakt hebt en waar je vandaag een aanpassing in moet aanbrengen, dat is vaak de grootste puzzel: je hebt het programma zelf gemaakt, maar hoe werkt het ook alweer?

C

O

N

Hier komt OO om de hoek kijken. OO is een manier van coderen, maar er zijn ook enkele specifieke functies die een programmeertaal meer of minder geschikt maken om objectgeoriënteerd te programmeren. C is ontworpen om objectgeoriënteerd te programmeren. Objectoriëntatie gaat over structureren, ontwerpen en coderen. De zaak is om eerst na te denken voor je begint. Bezint eer gij begint, zoals ze vroeger zeiden. Of, de wat uitgebreidere versie van deze uitdrukking: Denkt aleer gij doende zijt, doende denkt dan nog. Je moet dus niet alleen van tevoren nadenken, maar ook steeds blijven bijsturen! Je kunt voor je gaat coderen eerst je programma schematisch weergeven. Een veelgebruikte schematechniek hiervoor is UML (Unified Modeling Language). Je maakt eerst met behulp van allerlei schema’s een ontwerp voor je programma. Daarna zet je deze schema’s om naar een werkend programma. Dit deel gaat niet over UML. Wel wordt een aantal UML-schema’s uitgelegd en gebruikt om OO te verduidelijken.


398

Deel 4: Objectoriëntatie

Benodigde voorkennis Om met dit deel te kunnen werken wordt ervan uitgegaan dat je basiskennis hebt van C en van programmeren. Dit houdt in dat je tenminste kunt werken met variabelen, loops, selecties en Arrays. Deze onderwerpen staan allemaal in Deel 1: Introductie. Daarnaast worden ook de onderwerpen uit Deel 2: Gegevens als bekend verondersteld. Deel 3: Databases staat meer op zichzelf. Wel is het zo dat als je dat deel hebt doorgewerkt, de stof van dit deel wellicht eenvoudiger te begrijpen is.

Opmerkingen bij deel 4

EP

T

Het is ook bij dit deel de bedoeling dat je zowel de voorbeelden als de opdrachten maakt: probeer de voorbeelden stap voor stap uit en let op wat het programma doet. Begin elke nieuwe opdracht en elk voorbeeld met het kopiëren van de hele projectmap. Zo heb je altijd nog een back-up (eerdere versie) van je programma. Je hebt de uitwerking van een opdracht voor latere opdrachten nodig.

N

C

Voor een aantal methoden en eigenschappen is gekozen voor een Engelstalige benaming, ook worden hier en daar Engelse en Nederlandse benamingen door elkaar gebruikt. De reden hiervoor is dat binnen OO ook in Nederland de Engelstalige termen vaak gebruikt worden. Veel programmeurs hebben het bijvoorbeeld over properties in plaats van eigenschappen. Bovendien sijpelen de Engelse benamingen je programma binnen zodra je van de standaardobjecten van C functionaliteit gaat overnemen (erven). In bepaalde gevallen wordt hierop geanticipeerd door een methode eenzelfde (Engelse) naam te geven.

C

O

Veel succes & plezier!


399

Hoofdstuk 1 Voorbereiding

Objectoriëntatie Zoals al in de inleiding is gezegd is objectoriëntatie een manier van gestructureerd coderen waardoor je code eenvoudig opnieuw te gebruiken is door jezelf en anderen in andere projecten.

EP

T

Bij OO probeer je de wereld in objecten op te delen. Een object is iets met een duidelijk omschreven functie: een ding waarvan duidelijk is wat ermee moet gebeuren. Een broodmes is bijvoorbeeld zo’n ding: lang, een handvat en grove kartels, uitstekend geschikt om de broden van de warme bakker mee te snijden. Een ander object is dan een vleesmes: lang, smal en vlijmscherp, uitstekend geschikt voor het snijden van een karbonaadje.

N

C

Daarentegen is een Zwitsers zakmes een onding. Allemaal mesjes, maar ook een schaar, kurkentrekker, nagelknipper en nog veel meer, verenigd in één mes. Bij objectgeoriënteerd programmeren maken we zo min mogelijk Zwitserse zakmessen. Veel liever maken we een goede schaar, een goede kurkentrekker en uitstekende messen.

O

En dat is ook geen probleem, want software weegt niets en kost weinig ruimte. ‘Zwitserse zakmessen’ zijn hier helemaal niet handig.

C

Kenmerken

Een object moet aan de volgende eisen voldoen: – Een object heeft een duidelijk doel/taak. – De werking van het object is verborgen voor de buitenwereld (encapsulation).

UML UML (Unified Modeling Language) is, zoals al gezegd in de inleiding, niets anders dan een uitgebreide set van diagrammen (modellen) die je kunt gebruiken om een programma te ontwerpen. Sommige modellen, zoals de Use Case, worden gebruikt binnen een functioneel ontwerp (FO) en andere worden gebruikt voor het technisch ontwerp (TO). Dit boek gaat niet over het ontwerpen van software maar over het bouwen van software op basis van OO.


400

Deel 4: Objectoriëntatie

Wel wordt verteld hoe een UML-klassendiagram getekend wordt en in elkaar zit omdat dit diagram inzicht kan geven in hoe zaken als overerving en encapsulation (deze begrippen worden later uitgelegd) werken. Van iedere klasse in dit boek wordt ook een UML-diagram gegeven. Een voorbeeld kun je zien op pagina 408.

Voorbereiding voorbeeldprogramma

N

2 3 4 5

Maak een nieuw Windows Forms project aan met de naam NatSim (NatuurSimulatie). Hernoem het formulier naar FrmNatSim. Geef het formulier de naam Natuursimulatie. De afmetingen van het formulier worden 614 bij 430 pixels. Stel de achtergrondkleur in op PaleGoldenrod.

C

1

EP

Aanmaken project NatSim

T

In tegenstelling tot de vorige delen ga je nu werken met maar één voorbeeldprogramma. We gaan een soort natuursimulatie maken: een eenvoudige, niet volledige simulatie waarbij planten voorkomen die opgegeten kunnen worden door dieren en waarbij planteneters door vleeseters en alleseters worden opgegeten. Ook de opdrachten voer je binnen dit voorbeeldprogramma uit. Als voorbereiding maak je daarom nu het project aan waarbinnen dit allemaal plaatsvindt.

Plaatsen GroupBox Dieren

O

1 Plaats een GroupBox met de naam grbDieren linksboven op het formulier FrmNatSim. 2 Stel de GroupBox grbDieren als volgt in: Naam

Eigenschap

Waarde

GroupBox

grbDieren

Anchor

Top, Left

BackColor

BurlyWood

Location

12; 12

C

Type

Tabel 1 GroupBox grbDieren

Size

119; 192

TabIndex

0

Text

Dieren


Hoofdstuk 1 Voorbereiding

401

3 Plaats binnen de GroupBox grbDieren de volgende RadioButtons: Type

Naam

Eigenschap

RadioButton

rdbKonijn

AutoSize

true

BackColor

BurlyWood

Checked

true

Location

6; 19

TabIndex

1

TabStop

true

Text

Konijn

rdbBeer

C

O

RadioButton

true

BackColor

BurlyWood false

Location

6; 42

TabIndex

2

TabStop

true

T

Checked

EP

rdbLynx

AutoSize

Text

Koe

AutoSize

true

BackColor

BurlyWood

Checked

false

Location

6; 65

TabIndex

3

TabStop

true

Text

Lynx

C

RadioButton

rdbKoe

AutoSize

true

BackColor

BurlyWood

Checked

false

Location

6; 88

TabIndex

4

TabStop

true

Text

Beer

N

RadioButton

Waarde

Tabel 2 RadioButtons GroupBox grbDieren

Plaatsen GroupBox Planten 1

Plaats een GroupBox met de naam grbPlanten linksonder op het formulier FrmNatSim.

2 Stel deze als volgt in:


402

Deel 4: Objectoriëntatie

Type

Naam

Eigenschap

Waarde

GroupBox

grbPlanten

Anchor

Bottom, Left

BackColor

PaleGreen

Location

12; 210

Size

119; 169

Text

Planten

Tabel 3 GroupBox grbPlanten

4 Plaats binnen de GroupBox grbPlanten de volgende Controls: Naam

Eigenschap

RadioButton

rdbGras

AutoSize

true

BackColor

PaleGreen

Checked

true

rdbVenijnboom

TabIndex

5

TabStop

true

Text

Gras

AutoSize

true

BackColor

PaleGreen

Checked

false

Location

6; 42

TabIndex

7

TabStop

true

Text

Venijnboom

AutoSize

true

rdbVingerhoedskruid

C

O

RadioButton

N

C

RadioButton

CheckBox

6; 19

EP

Location

Waarde

T

Type

chbZaai

Tabel 4 RadioButtons GroupBox grbPlanten

BackColor

PaleGreen

Checked

false

Location

6; 65

TabIndex

8

TabStop

true

Text

Vingerhoedskruid

AutoSize

true

BackColor

PaleGreen

Checked

false

Location

62; 20

TabIndex

6

TabStop

true

Text

Zaai


Hoofdstuk 1 Voorbereiding

403

Plaatsen Labels en een PictureBox Plaats de volgende Controls op het formulier FrmNatSim en stel deze als volgt in: Type

Naam

Eigenschap

Waarde

Label

Label1

Anchor

Top,Right

Label

lblInformatie

AutoSize

true

BackColor

PaleGoldenrod

Font Bold

true

Location

505; 12

TabIndex

0

Text

Natuurinfo

Anchor

Top, Right

AutoSize

false

BackColor

Khaki

T

1

Location

483; 31

103; 173

EP

Size

TabIndex

0

Text

pbWereld

BackColor

PaleGoldenrod

BorderStyle

Fixed3D

Location

137; 12

C

PictureBox

Size

340; 367

N

Tabel 5 Label- en PictureBox-instellingen NatSim

Plaats een Panel op het formulier FrmNatSim en stel dit als volgt in: Type

Naam

Eigenschap

Panel

pnlKnoppen

Anchor

Bottom,Right

BackColor

PaleGoldenrod

Location

486; 210

C

1

O

Plaatsen en instellen KnoppenPaneel

Waarde

Size

100; 169

TabIndex

0

Tabel 6 Instellingen Panel pnlKnoppen

2 Alle knoppen worden op een Panel geplaatst zodat de knoppen bij het resizen van het formulier in één keer op een nieuwe plaats gezet kunnen worden. Plaats op het Panel pnlKnoppen de volgende Controls en stel deze als volgt in:


404

Deel 4: Objectoriëntatie

Type

Naam

Eigenschap

Waarde

Label

Label2

AutoSize

true

BackColor

PaleGoldenrod

Location

19; 117

TabIndex

0

Text

Snelheid

Location

22; 130

Minimum

1

Maximum

100

btnEinde

Size

75; 10

TabIndex

9

Value

1

BackColor

LightCoral

Location

22; 143

Size

75; 23

TabIndex

10

Text

Einde

EP

Button

hsbSnelheid

T

HScrollBar

Tabel 7 Controls op het Panel pnlKnoppen

C

O

N

C

Als je alles goed hebt ingesteld en geplaatst ziet je formulier er als volgt uit:

Schermafbeelding 1 FrmNatSim

Instellen globale variabelen / initialisatie 1

Voeg als globale variabele de volgende regels toe:


Hoofdstuk 1 Voorbereiding

405

// // Initialisatie // Graphics papier;

Voeg aan de constructor van het Form FrmNatSim de volgende regel toe na de initialisatieregel van het Form: papier = pbWereld.CreateGraphics();

Resize NatSim

1

T

Als het formulier schermvullend wordt gebruikt moeten de PictureBox pbWereld en het Label lblinformatie zo aangepast worden dat het scherm optimaal gebruikt wordt: Voeg de volgende code toe aan het formulier; deze methode past de grootte van de

EP

PictureBox pbWereld aan op basis van de grootte van het formulier.

N

C

private void ResizePictureBox() { // Deze methode zorgt ervoor dat, zodra de grootte van het // formulier wijzigt, de grootte van de PictureBox pbWereld zo // wordt aangepast dat het formulier optimaal gevuld wordt. int margeBreedte = 40; int margeHoogte = 64; pbWereld.Width = this.Width - grbDieren.Width - grbPlanten.Width - margeBreedte; pbWereld.Height = this.Height - margeHoogte; papier = pbWereld.CreateGraphics(); }

O

2 Voeg de volgende code toe aan het formulier; deze methode past de grootte van het Label lbInformatie aan op basis van de grootte van het formulier.

C

private void ResizeLblInformatie() { // Deze methode zorgt ervoor dat, zodra de grootte van het // formulier wijzigt, de hoogte van het informatielabel lblInfo zo // wordt aangepast dat het formulier optimaal gebruikt wordt. int margeHoogte = 88; lblInformatie.Height = this.Height - margeHoogte - pnlKnoppen.Height; }

3 Activeer het Event Resize en voeg code toe zodat de volgende methode ontstaat: private void FrmNatSim_Resize(object sender, EventArgs e) { // Het event Resize dat afgevuurd (getriggerd) wordt: ResizePictureBox(); ResizeLblInformatie(); }


406

Deel 4: Objectoriëntatie

EP

T

Als je nu het formulier maximaliseert zal het er, afhankelijk van je computer, ongeveer als volgt uitzien:

Schermafbeelding 2 FrmNatSim gemaximaliseerd

C

Je bent nu klaar om de voorbeelden uit te voeren.

Wat heb je geleerd?

C

O

N

– De kenmerken van objectgeoriënteerd werken. – Wat UML is.


407

Hoofdstuk 2 Klassen (Classes)

Beschrijving In het objectgeoriënteerde programmeren is een klasse (class) een template met de beschrijving van eigenschappen en methoden die bij een bepaald type object horen.

Inhoud (instantie)

EP

T

Het object zelf wordt gemaakt op basis van het template en bevat bepaalde waarden.

C

De definitie is mooi maar wat bepaalt nu of ik iets als een aparte klasse moet opnemen in mijn programma? Helaas is daar geen vast omlijnde procedure voor. Het hangt er namelijk van af wat het doel is van een programma.

N

Als je een programma maakt voor een computerbedrijf, is het wellicht voldoende als je in de klasse van werknemers het kenteken opneemt van de leaseauto waarin de werknemer rijdt. Je maakt dan geen aparte klasse van voertuigen.

C

O

Het wordt heel anders als je een programma maakt voor een leasemaatschappij: die moet heel veel weten over de verhuurde voertuigen. De leasemaatschappij heeft aan de andere kant wellicht alleen maar behoefte aan de naam van de bestuurder als eigenschap bij een voertuig.

UML

Binnen UML wordt een klasse aangegeven als een titelbalk met daarin gecentreerd de naam van de klasse en daaronder twee vakken (attributes en operations) waarin: 1 Alle variabelen en eigenschappen worden opgenomen met het type en eventueel de standaardwaarde. 2 Alle methoden met return-type en handtekening* worden opgenomen en namen van events worden beschreven.

* In Deel 1 is uitgelegd dat de handtekening of signatuur van een methode bestaat uit het type, de naam en de parameterlijst van de methode. Het is als het ware de ‘buitenkant’ van de methode.


408

Deel 4: Objectoriëntatie

Figuur 1 UML-klassendiagram klant

T

– –

EP

– – –

In dit voorbeeld zijn: _klantnummer, Naam en Woonplaats variabelen en eigenschappen. PlaatsOrder en ZendFactuur zijn methoden. OnDelete(), OnVerhuis() en OnNietBetaald() zijn methoden die gebruikt worden bij events. Een - staat voor private. Een + staat voor public.

Vuistregels

C

Een UML-klassendiagram kan ook bestaan uit alleen één vak, dan zijn er (nog) geen methoden, eigenschappen of events bekend.

O

N

Een klasse moet: – voor één doel gemaakt zijn, – helder omschreven zijn en – eenvoudig in het gebruik zijn.

C

In het algemeen wordt een klasse gemaakt om – één soort object te verwerken, zoals bijvoorbeeld een e-mail of een ISBN, – een platte datastructuur weer te geven en – de interactie tussen verschillende klassen te regelen. Je kunt ervan uitgaan dat als in je klassennaam het woord ‘en’ voorkomt je deze klasse moet splitsen in twee klassen.

Soorten klassen Er zijn verschillende soorten klassen. In dit boek behandelen we de volgende soorten: – de standaardklasse (dit hoofdstuk), – structs (hoofdstuk 6) en – de abstracte klasse (hoofdstuk 9).


Hoofdstuk 2 Klassen (Classes)

409

Naamgeving – – – –

Gebruik een zelfstandig naamwoord. Gebruik PascalCase. Gebruik zo min mogelijk afkortingen. Gebruik geen type-aanduidingen als prefix, zoals C voor Class, vóór de naam van een klasse. – Gebruik geen underscore ( _, laag streepje).

Voorbeeld 2.1: Aanmaken klasse Konijn We maken een klasse voor het dier Konijn.

EP

T

1 Open het project NatSim (NatuurSimulatie). 2 Ga naar Project en Add Class… 3 Maak een public klasse aan met de naam Konijn. Je ziet nu de volgende code (nadat je public vóór class hebt toegevoegd):

C

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

}

O

}

N

namespace NatSim { public class Konijn {

C

4 Omdat we gebruik willen gaan maken van de types Size en Point moeten we de bibliotheek Drawing toevoegen aan onze klasse. Voeg de volgende regel toe aan het eind van het using-blok: using System.Drawing;

5 Behalve van de bibliotheek Drawing gaan we in al deze klassen ook gebruikmaken van Timers. Er zijn drie verschillende soorten Timers beschikbaar binnen C: System.Windows.Forms.Timer System.Timers.Timer System.Threading.Timer

Het voert te ver om hier uit te leggen welke Timer je precies waarvoor kunt gebruiken. Het internet biedt ruim voldoende mogelijkheden om dit te achterhalen. Binnen NatSim maken we gebruik van de Timer uit System.Timers. Voeg daarom de volgende regel toe aan het einde van het using gedeelte: using System.Timers;


410

Deel 4: Objectoriëntatie

Opdrachten Klassen Opdracht 2.1: Aanmaken klasse Gras Maak binnen het project NatSim naast de public klasse Konijn de public klasse Gras aan die gebruikmaakt van System.Drawing en System.Timers.

Opdracht 2.2: Aanmaken klasse Venijnboom Maak binnen het project NatSim naast de andere klassen de public klasse Venijnboom aan die gebruikmaakt van System.Drawing en System.Timers.

T

Opdracht 2.3: Aanmaken klasse Vingerhoedskruid

EP

Maak binnen het project NatSim naast de andere klassen de public klasse Vingerhoedskruid aan die gebruikmaakt van System.Drawing en System.Timers.

Opdracht 2.4: Aanmaken klasse Koe

C

Maak binnen het project NatSim naast de andere klassen de public klasse Koe aan die gebruikmaakt van System.Drawing en System.Timers.

Opdracht 2.5: Aanmaken klasse Lynx

O

N

Maak binnen het project NatSim naast de andere klassen de public klasse Lynx aan die gebruikmaakt van System.Drawing en System.Timers.

Opdracht 2.6: Aanmaken klasse Beer

C

Maak binnen het project NatSim naast de andere klassen de public klasse Beer aan die gebruikmaakt van System.Drawing en System.Timers.

Wat heb je geleerd? – – – – –

De kenmerken van een klasse. De naamgeving van een klasse. Het verschil tussen klasse en instantie. Hoe een UML-klassendiagram eruit ziet. Hoe je een klasse maakt in Visual Studio.


411

Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

Encapsulation

EP

T

Van een auto hoef je doorgaans niet te weten wat het ontstekingstijdstip is, of het blokkeringsmoment van de wielen. Nee, je wilt rijden en daarom moet de motor draaien en als je een noodstop moet maken dan moet het ABS (antiblokkeersysteem) er maar voor zorgen dat de wielen niet permanent blokkeren (dan draait je wiel niet meer, gaat de auto glijden en wordt vervolgens onbestuurbaar). Maar deze voorzieningen moeten wel in je auto zitten! Ze zijn alleen voor de gebruiker aan het gezicht onttrokken, ze zijn als het ware ingekapseld.

C

Het grote voordeel hiervan is dat de gebruiker, de bestuurder, niet afgeleid wordt door allerlei, voor hem, nodeloze zaken, en dat de technicus er over het algemeen van uit kan gaan dat de gebruiker niet heeft geprutst met ABS en ontstekingstijdstip. En dat vergemakkelijkt het onderhoud.

N

Dit is een belangrijk punt bij het objectgeoriënteerd programmeren. In het Engels heet dit encapsulation. Omdat deze term overal binnen de softwarewereld wordt gebruikt, gebruiken wij deze term ook.

O

Alle variabelen van een klasse worden verborgen voor de buitenwereld. Dit doen we door ze als private aan te merken.

C

De toegang tot de private variabelen wordt geregeld door middel van zogenaamde assessoren: get om variabelen uit te lezen en set om een waarde aan een variabele toe te kennen. Deze manier van werken kent twee voordelen:

1

Het is een standaardmanier van werken, niet alleen binnen C maar binnen veel objectgeoriënteerde talen. Je kunt dus makkelijker je weg vinden binnen vreemde code. 2 Het biedt een vaste plek binnen je code om allerlei controles uit te voeren. Je beschermt als het ware je variabelen.


412

Deel 4: Objectoriëntatie

Naamgeving variabelen én eigenschappen Algemeen Voor de naamgeving van variabelen en eigenschappen worden zelfstandige naamwoorden of een combinatie van zelfstandige naamwoorden gebruikt. Daarnaast kennen we het verschil in private en public variabelen, en eigenschappen en variabelen die gebruikt worden in de handtekening van een methode.

Private

_naam, _rijbewijs, _minimaleGrootte enz.

EP

Public

T

Interne, private variabelen of eigenschappen geven we namen die beginnen met een underscore en daarna worden de woorden in camelCase geschreven:

Een publieke variabele of eigenschap wordt geschreven in PascalCase. Deze beginnen dus allemaal met een hoofdletter:

Parameterlijst

C

Voornaam, Voedsel, MaximaleGrootte enz.

N

Variabelen die binnen de handtekening van een methode (zie ook hoofdstuk 4 Methoden) gebruikt worden krijgen namen zonder underscore en worden in camelCase geschreven:

O

postcode, voertuig, maagGevuld enz.

C

Variabelen

Van ieder konijn willen we een aantal items vastleggen, de methoden laten we even buiten beschouwing. De items groeperen we als volgt: – Alle informatie over de konijn als soort. – De informatie over één specifiek konijn. – De informatie die te maken heeft met het konijn als grafisch object binnen het programma.

– – – – –

Informatie over de soort De Latijnse naam van de soort: Oryctolagus cuniculus. De Nederlandse naam: konijn in dit geval. Het maximale gewicht voor een konijn (5 kg). De maximale levensduur van een konijn (9 jaar). Een lijstje met de namen van planten die giftig zijn voor een konijn.


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

Informatie over het konijn als grafisch object binnen je programma De afmetingen van Flappie op het scherm. De kleur van Flappie zowel in het echt als op het scherm. De locatie van Flappie op het scherm. Met welke snelheid Flappie in beweging is en welke kant Flappie op gaat. Een variabele die aangeeft of het object Flappie de status verwijderd (gestorven) heeft.

T

– – – – –

Informatie over één specifiek konijn De naam van het konijn, bijvoorbeeld Flappie. Het gewicht van Flappie. In hoeverre de maag van Flappie gevuld is. De voedingswaarde van Flappie voor een vleeseter. De leeftijd van Flappie. Een Timer die ervoor zorgt dat Flappie ouder wordt. De geboortedatum van Flappie. En als Flappie gestorven is, de sterfdatum van Flappie.

EP

– – – – – – – –

413

UML

N

C

De voorgaande informatie vertalen we naar een UML-klassendiagram. Zoals gezegd: binnen een UML-klassendiagram staat een + (plus) voor public en - (min) voor private. In dit diagram staan alleen de private variabelen vermeld. Vandaar alle mintekens.

O

Achter het type van de eigenschap kan het teken = staan met een waarde. Dit is dan de standaard (default) waarde van die eigenschap.

C

Naar UML vertaald ziet de klasse Konijn er als volgt uit:

Figuur 2 UML-klassendiagram Konijn met alleen de private variabelen


414

Deel 4: Objectoriëntatie

Voorbeeld 3.1: Interne variabelen Voeg de volgende regels, inclusief commentaarregels, toe aan de klasse Konijn zodat de klasse Konijn de functionaliteit krijgt zoals beschreven is in het UMLdiagram. // Private variabelen // // Algemene gegevens // private string _latijnseNaam = “Oryctolagus cuniculus”; // Latijnse naam van de // soort private string _nederlandseNaam = “Konijn”; // Naam van de soort private double _gewichtMaximaal = 5; // Maximaal gewicht van // een konijn in kg private int _levensduur = 9; // De maximale levensduur // van een konijn // in jaren private List<string> _wordtVergiftigdDoor = new List<string>(){“Venijnboom,Vingerhoedskruid”}; // Een lijst met // planten die giftig // zijn voor een konijn // // Objectspecifieke gegevens // private string _naam = “”; // De naam van een konijn, bijvoorbeeld // Flappie private double _gewicht = 3; // Het gewicht van Flappie. private int _maagGevuld = 0; // Voor hoeveel procent is de maag gevuld? private int _voedingswaarde = 0; // Hoeveel voedingswaarde heeft Flappie? private Timer _verouder = new Timer(1000); // Flappie wordt ouder: // 1000 ticks = 1 sec = 1 jaar. private int _leeftijd = 0; // De leeftijd van Flappie. private DateTime _geboortedatum; // De geboortedatum van Flappie. private DateTime _sterfdatum; // De datum dat Flappie gestorven is. // // Grafische informatie // private Size _afmetingen = new Size(10, 10); // Hoe groot is Flappie op het scherm? private bool _verwijderd = false; // Is Flappie verwijderd? private Color _kleur = Color.Brown; // De kleur van Flappie. private Point _locatie = new Point(0,0); // Waar bevindt Flappie zich? private int _richtingX = 0; // In welke X-asrichting beweegt // Flappie zich met welke snelheid? private int _richtingY = 0; // In welke Y-asrichting beweegt // Flappie zich met welke snelheid?

C

O

N

C

EP

T

1


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

415

Visual Studio klassendiagram

C

EP

T

De tot nu toe gemaakt klasse ziet er in een Visual Studio klassendiagram – en dat is geen UML! – als volgt uit:

Schermafbeelding 3 Klassendiagram Konijn met alleen de velden

N

Voorbeeld 3.2: Visual Studio klassendiagram genereren

1

O

Je kunt binnen Visual Studio bovenstaand klassendiagram automatisch genereren op de volgende wijze: Ga in de menubalk naar de optie View.

C

2 Klik op Class View.

3 Het volgende venster wordt nu rechtsboven zichtbaar (klik eventueel op het pijltje om het menu uit te vouwen):


Deel 4: Objectoriëntatie

EP

Schermafbeelding 4 Class View NatSim

T

416

O

N

C

4 Klik met je rechtermuisknop op de klasse waarvan je een diagram wilt zien. In dit geval op de klasse Konijn. Het volgende venster verschijnt:

Schermafbeelding 5 Class View Opties

C

5 Nu klik je op View Class Diagram en het hiervoor getoonde klassendiagram van de klasse Konijn wordt zichtbaar (Schermafbeelding 3 Klassendiagram Konijn). Zoals je ziet worden de velden automatisch alfabetisch gerangschikt.

Eigenschappen (properties) Zoals al eerder gezegd en gedaan zijn alle variabelen afgeschermd voor de buitenwereld. Dit hebben we gedaan door ze te markeren als private. Om gebruik te maken van deze variabelen gaan we eigenschappen (properties) aanmaken met een get- en een set-methode.


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

417

Vaste structuur van eigenschappen Iedere eigenschap in C heeft dezelfde basisstructuur, de eigenschap Naam ziet er dan als volgt uit: public string Naam { get { return _naam; } set { _naam = value; } }

Voorbeeld 3.3: Eigenschappen

// // Eigenschappen //

EP

Ten eerste wat commentaarregels die aangeven dat hier de eigenschappen gedefinieerd worden. Voeg binnen de klasse Konijn na de laatste variabele de volgende regels toe:

C

1

T

Doordat er zowel achter get (het ophalen van de informatie) als bij set (het wegschrijven van gegevens) een codeblokaanduiding staat (twee accolades) kun je hier zoveel regels code toevoegen als je wilt. Hier kun je allerlei aanvullende logica kwijt.

N

2 Nu gaan we regels toevoegen om een eigenschap te definiëren. Ook hier moeten de gebruikelijke onderdelen als public, type en naam aangegeven worden:

}

O

public string Naam {

C

Zoals je ziet komen er direct na de naam van de eigenschap twee accolades. Hierbinnen gaan we de code definiëren om de interne variabelen weer te geven of te vullen.

3 Voeg tussen de twee accolades de volgende code toe om de get-methode aan te maken: get { return _naam; }

Zoals je ziet is het enige wat de get-methode doet, de waarde van de interne variabele _naam teruggeven. 4 Als je zo meteen een object met bijvoorbeeld de naam Konijn1 hebt van het type Konijn dan wordt met het commando konijn1 op de achtergrond de get-functie geactiveerd en de waarde van de interne variabele _naam opgehaald.


418

5

Deel 4: Objectoriëntatie

Je kunt nu een waarde ophalen. Vervolgens hebben we code nodig om de waarde van de interne variabele _naam te kunnen wijzigen. Dit doen we door direct na de regel get {return _naam;} de volgende regel toe te voegen: set { _naam = value; }

Als je nu de regel konijn1.Naam = “Flappie” in zou voeren, dan zou _naam de waarde Flappie krijgen. (Dit hoef je nu niet te doen.) Binnen de accolades van de set-methode kun je zoveel controles toevoegen als je wilt om de consistentie van de interne variabele zeker te stellen.

EP

public int Leeftijd public double Gewicht public int MaagGevuld public int Voedingswaarde public DateTime Geboortedatum public DateTime Sterfdatum

T

6 Een naam van een eigenschap komt in dit geval overeen met de naam van de interne variabele, de private variabele _leeftijd hoort bij de public eigenschap Leeftijd. Maak de volgende properties aan op dezelfde wijze:

C

Ooit gaan we een konijn op het beeldscherm zetten en dan hebben we een aantal specifieke eigenschappen nodig. We beginnen met wat commentaarregels toe te voegen:

N

// // Grafische eigenschappen //

O

7 Daarna voegen we de volgende eigenschappen toe die gebruikt gaan worden bij het konijn als grafisch object. Ook hier moeten we per eigenschap een getter en een setter definiëren.

C

Maak voor de volgende vijf eigenschappen aan: public Size Afmetingen public Color Kleur public Point Locatie public int RichtingX public int RichtingY

Voorbeeld 3.4: ReadOnly-eigenschappen Sommige eigenschappen zijn onveranderlijk. Zo is de NederlandseNaam van een konijn altijd Konijn en zal de LatijnseNaam ook niet veranderen. Dit kunnen we eenvoudig voor elkaar krijgen door alleen het get-gedeelte te implementeren.


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

1

419

Voeg de volgende regels toe om het ReadOnly-gedeelte aan te geven: // // ReadOnly-eigenschappen specifiek voor konijnen //

2 Voeg de volgende regels toe om de eigenschap NederlandseNaam aan te maken: public string NederlandseNaam { get { return _nederlandseNaam;} }

EP

T

Doordat er geen set-gedeelte is opgenomen kan de waarde van de interne variabele niet meer veranderen door de regel konijn1.NederlandseNaam = “Olifant”;. Deze regel zal nu een de volgende fout veroorzaken:

Schermafbeelding 6 Foutmelding ReadOnly Property

3 Maak de volgende ReadOnly-eigenschappen aan:

N

C

public string LatijnseNaam public double GewichtMaximaal public int Levensduur public bool Verwijderd

Voorbeeld 3.5: Eigenschappen met een complex type toevoegen

C

O

Het veld _wordtVergiftigdDoor is een lijst van het type List<string>. De bijbehorende eigenschap wordt op precies dezelfde wijze gemaakt als alle voorgaande eigenschappen die je hebt aangemaakt. Uiteraard is ook deze eigenschap public en ReadOnly.

1

Voeg de volgende regels toe:

public List<string> WordtVergiftigdDoor { get { return _wordtVergiftigdDoor; } }

2 Voeg op dezelfde wijze de volgende eigenschap toe: public Timer Verouder

Als het goed is ziet het klassendiagram er als volgt uit, als je de Fields inklapt:


Deel 4: Objectoriëntatie

EP

T

420

Schermafbeelding 7 Klassendiagram Konijn met velden en eigenschappen

C

Opdrachten Encapsulation, variabelen en eigenschappen Opdracht 3.1: Voorbereiding klasse Gras

N

Binnen de klasse Gras wordt gebruikgemaakt van de enum Bloeiwijze. Deze ziet er als volgt uit:

C

O

public enum Bloeiwijze { none, hoofdje, bloemkoek, bloemkluwen, aar, aartje, katje, bloeikolf, tros, schermvormigeTros, bundel, scherm, schijnkrans, eentakkigBijscherm, schroef, sikkel, schicht, waaier, samengesteldScherm,


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

421

samengesteldGevorktScherm, samengesteldeAar, pluim, dichasialePluim, tuil, dichasialeTuil, thyrsus, kegel }

N

C

EP

T

Maak de enum Bloeiwijze buiten de klassen maar binnen de namespace NatSim aan. Op deze manier kunnen ook andere klassen eenvoudig gebruikmaken van deze enum. Ook een enum kun je weergeven in UML. De enum voor bloeiwijze wordt als volgt in UML weergegeven:

O

Figuur 3 UML-klassendiagram enumeration Bloeiwijze

C

Opdracht 3.2: De klasse Gras implementeren Met implementeren bedoelen we dat een klasse gevuld wordt met variabelen, eigenschappen, methoden en/of events.

Vul binnen het project NatSim de klasse Gras aan zodat deze voldoet aan het volgende UML-klassendiagram:


422

Deel 4: Objectoriëntatie

Figuur 4 UML-klassendiagram Gras

T

Verkorte schrijfwijze eigenschappen

EP

Zoals je gemerkt hebt is dit erg veel werk. De verleiding is groot om de velden (variabelen) gewoon als public te definiëren en de eigenschappen overboord te zetten. De functionaliteit zal exact hetzelfde blijven. Je voldoet alleen niet aan een internationale standaard. Je code zal wel een stuk sneller klaar zijn omdat je gewoon veel minder hoeft te typen.

C

Alleen wordt je code ook minder toegankelijk voor derden, juist omdat je je niet aan die standaard houdt.

O

N

Visual Studio kent echter ook een verkorte schrijfwijze waarbij de private variabelen automatisch, buiten beeld, op de achtergrond worden aangemaakt. Je specificeert dan alleen nog private variabelen in specifieke omstandigheden zoals eigenschappen die ReadOnly zijn en eigenschappen waarbij een stuk code uitgevoerd moet worden zoals bijvoorbeeld een controle op de invoer.

C

Notatie binnen UML

Omdat UML slechts variabelen kent en geen onderscheid maakt tussen eigenschappen en variabelen wordt er binnen dit boek de volgende afspraak gehanteerd: Alleen die variabelen die je binnen de code zelf moet aanmaken, worden opgenomen in het UML-klassendiagram. Automatisch gegenereerde variabelen laten we buiten beschouwing. Alle publieke variabelen worden, tenzij nadrukkelijk anders vermeld, geïmplementeerd als eigenschappen met de daarbij behorende getter en setter. Bij ReadOnly-eigenschappen wordt dit vermeld achter de eigenschap. Binnen het programma, en dus niet in het UML-diagram, wordt een overeenkomstige private variabele gedefinieerd (dezelfde naam maar dan beginnend met een underscore).


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

423

Voorbeeld 3.6: Verkorte schrijfwijze eigenschappen

Figuur 5 UML-klassendiagram Koe

EP

T

We gaan de klasse Koe in NatSim implementeren volgens het volgende UML-diagram:

N

Voorbereiding

C

Bij de eigenschap WordtVergiftigdDoor staan twee standaardwaardes omdat het hier om een lijst gaat, daar kun je dus meerdere waardes als standaard meegeven.

O

1 Open het project NatSim. 2 Open de klasse met de naam Koe. 3 Je code ziet er als het goed is als volgt uit:

C

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; using System.Timers; namespace NatSim { public class Koe { } }


424

Deel 4: Objectoriëntatie

Aanmaken eigenschappen (properties) 1

We gaan de eigenschap Naam toevoegen (dus niet de private variabele _naam), door de volgende regel binnen de klasse te typen: // // Eigenschappen // public string Naam { get; set; }

Dit is alles. Visual Studio maakt zelf de private variabelen aan. Binnen Visual Studio worden deze aangeduid als: ‘Auto-Implemented properties for trivial get and set.’

N

C

EP

public int AantalLitersMelk { get; set; } public DateTime Geboortedatum { get; set; } public double Gewicht { get; set; } public int Leeftijd { get; set; } public int MaagGevuld { get; set; } public DateTime Sterfdatum { get; set; } // // Grafische eigenschappen // public Color Kleur { get; set; } public Point Locatie { get; set; } public int RichtingX { get; set; } public int RichtingY { get; set; }

T

2 Voeg de volgende regels toe om de klasse te completeren:

ReadOnly-eigenschappen

O

Maar ook hier hebben we te maken met een aantal ReadOnly-eigenschappen. Dit zijn alle eigenschappen vanaf de eigenschap Afmetingen. Eerst geven we aan dat het volgende programmagedeelte over de ReadOnly-eigenschappen gaat. Voeg de volgende commentaarregels toe:

C

1

// // ReadOnly-eigenschappen specifiek voor de klasse Koe //

Helaas kun je de ReadOnly-eigenschap niet eenvoudigweg aangeven door alleen de getter te specificeren. Als je dat wel doet krijg je de volgende foutmelding:

Schermafbeelding 8 Foutmelding ReadOnly Getter


Hoofdstuk 3 Encapsulation, variabelen en eigenschappen

425

2 In geval van een ReadOnly-eigenschap moet je ook de onderliggende private variabele specificeren. Ook wel logisch want dan kun je deze variabelen direct initialiseren. Daarom definiëren we eerst de private variabelen met standaardwaardes door de volgende regels toe te voegen: private Size _afmetingen = new Size(80, 40); private double _gewichtMaximaal= 450; private List<string> _wordtVergiftigdDoor = new List<string>(){“Venijnboom”,”Vingerhoedskruid”}; private string _latijnseNaam = “Bos taurus”; private int _levensduur = 25; private string _nederlandseNaam = “Koe”; private Timer _verouder = new Timer(); private bool _verwijderd = false;

T

3 Daarna kunnen we de ReadOnly-eigenschappen definiëren door van de bijbehorende eigenschappen alleen de getters te definiëren. Voeg de volgende regels toe:

C

EP

public Size Afmetingen {get {return _afmetingen;} } public double GewichtMaximaal { get { return _gewichtMaximaal;} } public List<string> WordtVergiftigdDoor { get { return _wordtVergiftigdDoor;} } public string LatijnseNaam { get { return _latijnseNaam;} } public int Levensduur { get { return _levensduur;} } public string NederlandseNaam { get { return _nederlandseNaam;} } public Timer Verouder { get { return _verouder; } } public bool Verwijderd { get { return _verwijderd;} }

N

Opdracht Verkorte schrijfwijze

O

Opdracht 3.2: Verkorte schrijfwijze: de klasse Venijnboom

C

Vul de klasse Venijnboom binnen het project NatSim aan met eigenschappen volgens het volgende UML-klassendiagram. Gebruik daar waar het kan de verkorte schrijfwijze. Alleen de eigenschappen AantalKubiekeMetersHout, Leeftijd en Locatie zijn niet ReadOnly.

Figuur 6 UML-klassendiagram Venijnboom


426

Deel 4: Objectoriëntatie

De andere klassen worden verderop in het boek beschreven, want het kan nog korter en nog sneller.

Wat heb je geleerd? Wat het begrip encapsulation inhoudt. Hoe je een Visual Studio klassendiagram maakt (geen UML). Hoe je variabelen en eigenschappen toevoegt aan een klasse. Wat de afspraken zijn rondom de naamgeving van eigenschappen en variabelen. Hoe variabelen en eigenschappen in een UML-diagram weergegeven worden.

C

O

N

C

EP

T

– – – – –


427

Hoofdstuk 4 Methoden

Beschrijving

T

Tot nu toe hebben we het over de eigenschappen van een klasse gehad. De eigenschappen zijn zichtbaar en tastbaar. Eigenschappen zijn de status van een object: de toestand waarin een object zich bevindt. Een eigenschap wordt, net zoals een klasse, meestal aangeduid met een zelfstandig naamwoord. Niet geheel toevallig, want een klasse kan zelf ook weer uit klassen bestaan.

EP

Een methode daarentegen wordt aangeduid met een werkwoord. Dat werkwoord geeft aan wat er met dat object uit die klasse kan gebeuren. De methode Delete() verwijdert iets. De methode Beweeg() laat een object bewegen, enzovoort.

C

Eisen

N

Een methode is gewoon dezelfde methode die we al uit Deel 1 t/m 3 kennen. Een methode voldoet dus aan de volgende eisen.

O

Iedere methode heeft een handtekening die bestaat uit: – het return-type – de naam van de methode – de lijst met parameters

C

Deze handtekening moet uniek zijn binnen een klasse! Van iedere methode wordt het beschermingsniveau aangegeven. Op dit moment ken je twee niveaus: private en public. Geef je niets aan dan is de methode automatisch private.

Overloaden Zoals we gezien hebben moet de handtekening van een methode uniek zijn. Dat betekent dat je wel eenzelfde methodenaam en -type mag gebruiken, zolang ze maar een verschillende parameterlijst gebruiken. Hierbij is het type van de parameter bepalend voor de uniciteit, niet de interne naam van de parameter.


428

Deel 4: Objectoriëntatie

Goed: public void koopEenIJsje(string smaak, bool hoorntje); public void koopEenIJsje(bool slagroom, string smaak);

Omdat de volgorde van de variabelen verschillend is worden deze twee methoden gezien als twee verschillende methoden. Fout: private void koopEenIJsje(string smaak, bool hoorntje); private void koopEenIJsje(string smaak, bool slagroom);

EP

T

Omdat de volgorde en het type van de variabelen identiek zijn kan C geen onderscheid maken tussen deze twee methoden en zal C met de volgende foutmelding komen:

C

Schermafbeelding 9 Foutmeldingmethode met dezelfde handtekening

N

UML

Zoals we al eerder zagen is het tweede vak binnen het UML-klassendiagram gereserveerd voor de methoden.

C

O

In het diagram plaats je alleen de handtekening van de methode en het beschermingsniveau. Het UML-klassendiagram voor Konijn ziet er nu als volgt uit:


429

EP

T

Hoofdstuk 4 Methoden

Figuur 7 UML-klassendiagram Konijn met methoden

Voorbeeld 4.1: Methoden toevoegen aan de klasse Konijn

C

N

Verwijder( ) Een object moet verwijderd kunnen worden. Objecten vormen een onderdeel van een programma, een groter geheel, en er zijn binnen een objectgeoriënteerd programma altijd lijstjes die van alles bijhouden over onderliggende objecten. Binnen C verwijder je een object door het de status verwijderd te geven. Vervolgens kan het object verwijderd worden uit de lijstjes. Als een object nergens meer geregistreerd staat, wordt het ook verwijderd uit het geheugen.

C

– – –

O

–

Een object van de klasse Konijn moet in ons voorbeeld een aantal dingen kunnen doen. Een Konijn moet: verwijderd kunnen worden als: - de maximale levensduur is bereikt. - een plant wordt gegeten die op de lijst WordtVergiftigdDoor voorkomt. een plant kunnen eten. getekend kunnen worden op een grafisch object. ouder kunnen worden.

Voor de klasse kan het belangrijk zijn dat er een public variabele bestaat die aangeeft of het object verwijderd moet worden of niet. Daarnaast is het in het gebruik prettig als er een methode is die deze actie uitvoert. Het lijkt dubbelop, maar ga bij het maken van een klasse uit van het gebruikersgemak: intern mag je klasse complex zijn, maar aan de buitenkant moet de klasse zo helder en duidelijk mogelijk zijn.


430

1 2 3 4

Deel 4: Objectoriëntatie

Ga binnen het project NatSim naar de klasse Konijn. Scroll naar het einde van de eigenschappen. Sla een regel over. Voeg de volgende commentaarregels toe: // // Methoden //

5 Voeg de volgende regels toe die samen de methode Verwijder() vormen: public void Verwijder() { _verwijderd = true; }

C

EP

T

Het enige wat deze functie doet is de interne private variabele _verwijderd op true zetten. Omdat de eigenschap ReadOnly is kunnen we de variabele niet via de eigenschap veranderen. Dit is zo gedaan om een gebruikersinterface te krijgen die intuïtief is qua gebruik. Flappie.Verwijderd = true; werkt ook maar is toch weer wat meer werk en leest minder ‘soepel’. Je ziet dat er vaak meerdere manieren zijn om hetzelfde te bereiken. Het (vage) onderscheid tussen een eigenschap en een functie is dat een functie een activiteit weergeeft en een eigenschap de toestand van een object. Je ziet het, de voorwaarden wanneer een Konijn verwijderd moet worden zijn nog niet geprogrammeerd. Dat komt later aan de orde.

N

6 Voeg dezelfde methode Verwijder() met dezelfde inhoud toe aan de klassen Gras en Venijnboom.

C

O

Eet( ) en ondersteunende methoden De methode Eet() verwerkt een plant als aan de volgende condities is voldaan: – Het Konijn is niet verzadigd en de plant komt niet voor op de lijst WordtVergiftigdDoor. – Het Konijn heeft honger en eet daarom ook giftige planten. – Je moet dus bepalen of het Konijn honger heeft en je moet ervoor zorgen dat het Konijn planten kan eten. Wij beperken ons hier tot het Gras en de Venijnboom. Waarom? Omdat dit eigenlijk helemaal niet zo snugger is. Je moet namelijk voor elke plant een nieuwe methode maken. Later leer je hoe je dit veel beter kunt aanpakken. 1

Ga binnen het project NatSim naar de klasse Konijn. Voeg na de methode Verwijder() de volgende regels toe: // private methoden private bool honger() { return (MaagGevuld < 25); }


Hoofdstuk 4 Methoden

431

De methode honger() (met een kleine letter omdat het een private methode is) bekijkt of het Konijn hongerig is. Een Konijn heeft honger als de waarde van de eigenschap MaagGevuld kleiner is dan 25. Vervolgens hebben we een methode nodig waarmee een Konijn Gras kan eten. Ook dit wordt weer een private methode. Voeg de volgende regels toe: private void eetGras(Gras voedsel) { }

De methode eetGras() verwerkt een variabele van het type Gras.

EP

T

2 Je weet niet of Gras in de variabele WordtVergiftigdDoor voorkomt, dat moet eerst gecheckt worden. Door met een lijst met voor een Konijn giftige planten te werken voorkom je als programmeur dat je om de haverklap je code moet aanpassen. De lijst aanpassen is vaak voldoende en dat kun je door een gebruiker laten doen. Voeg de volgende regels toe binnen de methode eetGras():

C

if (WordtVergiftigdDoor.Contains(voedsel.NederlandseNaam)) { if (honger()) { Verwijder(); } }

N

Als het Konijn honger heeft eet het alles, ook de giftige Planten. Vervolgens wordt het Konijn verwijderd.

O

3 Als het voedsel niet giftig is wordt er alleen gegeten zolang de maag niet voor 100 gevuld is. Het verwerken van de Plant betekent dat de eigenschap MaagGevuld met een aantal eenheden Voedingswaarde van de Plant wordt opgehoogd. De Plant wordt vervolgens verwijderd.

C

Voeg de volgende regels toe: else if (MaagGevuld < 100) { // De maag wordt aangevuld met de voedingswaarde van het voedsel MaagGevuld = MaagGevuld + voedsel.Voedingswaarde; // Een maag kan nooit meer dan 100% gevuld zijn if (MaagGevuld > 100) { MaagGevuld = 100; } // Uiteraard wordt het voedsel na de maaltijd verwijderd uit de simulatie: voedsel.Verwijder(); }

Als je Verwijder() nog niet toegevoegd hebt aan Gras krijg je nu een foutmelding.


432

Deel 4: Objectoriëntatie

4 Behalve eetGras() maken we ook een methode eetVenijnboom() voor als het Konijn echt honger heeft. Deze methode is, op de handtekening na, exact hetzelfde als de methode eetGras(). Later zullen we daar profijt van hebben. Voeg de volgende regels toe:

T

private void eetVenijnBoom(Venijnboom voedsel) { if (WordtVergiftigdDoor.Contains(voedsel.NederlandseNaam)) { if (honger()) { Verwijder(); } } else if (MaagGevuld < 100) { // De maag wordt aangevuld met de voedingswaarde van het voedsel: MaagGevuld = MaagGevuld + voedsel.Voedingswaarde;

EP

// De maag kan nooit meer dan 100% gevuld zijn: if (MaagGevuld > 100) { MaagGevuld = 100; }

C

// Uiteraard wordt het voedsel na de maaltijd verwijderd: voedsel.Verwijder(); } }

O

N

5 De ondersteunde functies honger(), eetGras() en eetVenijnboom() zijn klaar, nu kunnen we de methode Eet() maken. Deze methode onderzoekt van welk type het eten is en afhankelijk van het type wordt de bijbehorende methode aangeroepen. Voeg de volgende regels toe:

C

public void Eet(object voedsel) { // Bepalen wat voor voedsel het is, gras of venijnboom if (voedsel.GetType() == typeof(Gras)) { eetGras((Gras)voedsel); } else if (voedsel.GetType() == typeof(Venijnboom)) { eetVenijnBoom((Venijnboom)voedsel); } }

Door als parameter een variabele van het type object te gebruiken kan in de methode Eet() bepaald worden van welk type het object voedsel is. Vervolgens kan door middel van type casting het algemene object voedsel omgezet worden naar het type Gras, dus (Gras)voedsel.


Hoofdstuk 4 Methoden

433

Teken( ) Uiteindelijk moeten de diverse objecten ook getekend kunnen worden. Weliswaar in eerste instantie als eenvoudige rechthoekjes met verschillende kleuren, maar er moet toch een methode voor gemaakt worden. Voeg de volgende regels toe die de methode Teken() vormen:

EP

T

public void Teken(Graphics graphics) { // Het tekenen van een kader op de locatie van het object met de afmetingen // van het object. Pen pen = new Pen(Color.Black, 2); // Omdat Windows de hoogte van boven naar beneden berekent en een rechthoek // ook van boven naar beneden wordt getekend, maar het voor de gebruiker // natuurlijker aanvoelt om van beneden naar boven te tekenen gebruiken we een // variabele die een nieuwe starthoogte berekent om vandaar de rechthoek te // tekenen. int startHoogte = Locatie.Y - Afmetingen.Height; // Tekenen van de rechthoek. graphics.DrawRectangle(pen, Locatie.X, startHoogte, Afmetingen.Width, Afmetingen.Height); // Met dispose verwijder je de variabele direct uit het geheugen en // maak je ruimte vrij. pen.Dispose();

}

N

C

// Het vullen van de rechthoek op de locatie van het object met de afmetingen // van het object. SolidBrush kwast = new SolidBrush(Kleur); graphics.FillRectangle(kwast, Locatie.X, startHoogte, Afmetingen.Width, Afmetingen.Height); // Het direct vrijgeven van het geheugen van een gebruikt object. kwast.Dispose();

C

O

Er wordt gekozen om een parameter van het type Graphics mee te geven. Zo kan er op verschillende objecten getekend worden zonder al deze objecten in de klasse op te moeten nemen. Je hoeft niet bang te zijn dat door het gebruik van using een hele bibliotheek in je code wordt opgenomen. Visual Studio gebruikt alleen die gedeeltes die je aangeroepen hebt. Roep je niets aan? Dan wordt er ook niets in je executable opgenomen.

1

Verouder( ) Deze methode gaan we maken in het hoofdstuk over events. Je ziet het, een methode in een klasse is hetzelfde als alle methoden die je tot nu toe gebruikt hebt. Er is dan ook geen enkel verschil. Alles wat je geleerd hebt over methoden kun je ook toepassen op methoden in een klasse. Je hebt natuurlijk al die tijd gewoon in klassen gewerkt. Kijk maar eens naar de code van FrmNatSim, die begint ook met de regels:


434

Deel 4: Objectoriëntatie

namespace NatSim { public partial class FrmNatSim : Form {

Opdrachten Methoden Opdracht 4.1: Methoden toevoegen aan de klasse Koe Voeg aan de klasse Koe dezelfde methoden toe die je aan Konijn hebt toegevoegd.

T

Opdracht 4.2: Methoden toevoegen aan de klassen Gras en Venijnboom

EP

Voeg aan de klassen Venijnboom en Gras (alleen) de methode Teken() toe. Deze is identiek aan de methode Teken() uit de klassen Konijn en Koe.

Reflectie 1

C

Het feit dat ergens exact dezelfde code staat geeft twee dingen aan: De code is redundant (= overbodig), dit wil zeggen dat dezelfde code meerdere keren voorkomt binnen een programma.

O

N

2 Redundante code is vaak slechte code. Dit moet dus verbeterd worden en dat gaan we doen in hoofdstuk 8 Overerving. Waarom slecht? Omdat het risico groot is dat je bij onderhoud een van de gekopieerde stukken vergeet. Redundante code leidt vaak tot inconsistente (onbetrouwbare) code.

C

Wat heb je geleerd?

– – – – –

Hoe een methode opgebouwd is. Hoe je een methode binnen een klasse gebruikt. Wat het begrip overloaden inhoudt. Hoe een methode in een UML-klassendiagram wordt weergegeven. Wat redundantie inhoudt en waarom je redundantie moet vermijden.


435

Hoofdstuk 5 Constructor

Beschrijving

T

Zoals we al in Deel 1 hebben besproken, is een klasse slechts een beschrijving. Pas als we een instantie van een klasse aanmaken met een specifieke naam kunnen we gebruik gaan maken van alles wat we gemaakt hebben in de klasse. Dit doen we normaal gesproken met het keyword new.

EP

Als we iets met de klasse Konijn willen doen dan zullen we eerst een variabele van het type Konijn moeten aanmaken en wel op de volgende wijze: Konijn konijn01 = new Konijn();

C

We kunnen niets anders omdat we nog geen constructor hebben aangemaakt. Een constructor is de methode die aangeroepen wordt als je het keyword new gebruikt. Standaard heeft elk object een constructor zonder parameters.

O

N

Zodra je een constructor met parameters maakt vervalt de standaard-constructor. Heb je ook een constructor nodig zonder parameters dan moet je deze zelf maken. Een constructor kun je immers overloaden op exact dezelfde manier als bij een (andere) methode.

Eisen constructor

C

Een constructor – heeft altijd exact dezelfde naam als de class waartoe de constructor behoort, – heeft geen type! – is altijd public.

UML In UML wordt een constructor binnen het vak met methoden opgenomen. De methode heeft alleen geen return type. De klasse Konijn ziet er met constructors en de bijbehorende methoden als volgt uit:


Deel 4: Objectoriëntatie

EP

T

436

C

Figuur 8 UML-klassendiagram Konijn met constructors

Voorbeeld 5.1: Constructors voor Konijn

1

O

N

Een Konijn moet ergens in het formulier FrmNatSim zichtbaar worden, het liefst daar waar geklikt wordt op de PictureBox pbWereld. Daarnaast moet er een Timer gaan lopen die loopt totdat de maximale levensduur bereikt is. Omdat we met meerdere constructors gaan werken maken we één methode waarin alle logica zit die we meerdere keren gaan aanroepen. Open het project NatSim en open de klasse Konijn.

C

2 Voeg de volgende commentaarregels toe als eerste regels van de klasse Konijn: // // Initialisatie //

3 Maak een private methode aan met de naam initClass door de volgende regels daaronder toe te voegen: private void initClass(Point locatie, string naam, Color kleur) { Locatie = locatie; Naam = naam; Kleur = kleur; Verouder.Start(); }


Hoofdstuk 5 Constructor

437

4 Nu gaan we de diverse constructors maken. Eerst de constructor zonder parameters: public Konijn() { initClass(new Point(0, 0), “”, Kleur); }

Ook voor de Kleur wordt gewoon de standaardwaarde opgehaald zoals die initieel al is aangebracht. Waarom dan toch initClass opgestart? Omdat in die methode ook de Timer Verouder wordt gestart. En dat kun je doen door alleen die regel op te nemen, maar door initClass te starten gebruik je één plek waar alle initialisatielogica samenkomt.

EP

public Konijn(Point locatie) { initClass(locatie, “”, Kleur); }

T

5 De volgende constructor kent slechts één parameter: de locatie waar eventueel het Konijn getekend moet worden, althans het bruine vierkantje. Voeg de volgende regels toe aan de klasse Konijn:

C

6 En maak tot slot een constructor waar je alle gedefinieerde parameters kunt doorgeven.

O

N

public Konijn(Point locatie, string naam, Color kleur) { initClass(locatie, naam, kleur); }

Opdrachten Constructor

C

Opdracht 5.1: Constructors voor Koe Voeg aan de klasse Koe dezelfde soort constructors toe als aan de klasse Konijn.

Opdracht 5.2: Constructors voor Gras en Venijnboom Voeg aan de klassen Gras en Venijnboom dezelfde soort constructors toe als aan de klasse Konijn. Planten kennen de eigenschap Naam niet. Die neem je dan ook niet op in je constructor. Bij Planten is de eigenschap Kleur ReadOnly; ook deze eigenschap neem je dan niet als parameter mee in je constructor.


438

Deel 4: Objectoriëntatie

Voorbeeld 5.2: Konijn tekenen In het programma NatSim moet een Konijn, of in ons geval een bruin rechthoekje, getekend worden in de PictureBox pbWereld als er in deze PictureBox geklikt wordt, op de plek waar geklikt is. 1

Open het project NatSim.

2 Open het formulier FrmNatSim. 3 Selecteer de PictureBox pbWereld. 4 Voeg het event MouseClick toe zodat de volgende code gegenereerd wordt: private void pbWereld_MouseClick(object sender, MouseEventArgs e) {

T

}

EP

5 Als er op de linkermuisknop geklikt wordt moet het Dier getekend worden dat geselecteerd is binnen de GroupBox pbDieren. Voeg de volgende code toe aan de pbWereld_MouseClick()-methode:

C

if (e.Button== MouseButtons.Left) { TekenDier(e.Location); }

N

6 Uiteraard moet nu ook de methode TekenDier() aangemaakt worden. Binnen deze methode wordt onderzocht welke RadioButton actief is en wordt het desbetreffende dier getekend. Voeg de volgende regels toe:

C

O

private void TekenDier(Point positie) { if (rdbKonijn.Checked == true) { Konijn Konijn01 = new Konijn(positie, “Flappie”, Color.Brown); Konijn01.Teken(pbWereld.CreateGraphics()); } }

7 Test het programma NatSim. Je moet nu, als je de RadioButton rdbKonijn hebt geselecteerd, een bruin rechthoekje zien verschijnen binnen de PictureBox pbWereld op de plek waar je geklikt hebt. Na een aantal keer klikken ziet je scherm er dan als volgt uit:


439

Opdrachten Tekenen

EP

Schermafbeelding 10 NatSim met Konijnen

T

Hoofdstuk 5 Constructor

C

Opdracht 5.3: Tekenen van Koe

N

Breid het programma NatSim uit met de mogelijkheid om, zodra je op de linkermuisknop klikt, behalve een Konijn ook een Koe te kunnen tekenen. Je tekent het dier dat aangevinkt is.

O

Opdracht 5.4: Tekenen van Gras en Venijnboom

C

Verder moet er Gras of een Venijnboom getekend worden zodra er op de rechtermuisknop geklikt wordt. Je tekent dan die Plant die aangevinkt is. Het resultaat kan er dan als volgt uitzien:


Deel 4: Objectoriëntatie

T

440

EP

Schermafbeelding 11 NatSim met Konijnen, Koeien, Gras en Venijnbomen

C

Een heleboel zaken zijn nog niet geïmplementeerd. De diverse Dieren en Planten leven onbeperkt, er wordt niets gegeten en er wordt geen informatie over een specifiek object getoond. En zoals we het nu opgezet hebben, is het nog heel veel werk om dat allemaal voor elkaar te krijgen.

N

Daarom gaan we het ontwerp in het volgende hoofdstukken verbeteren.

Wat heb je geleerd?

C

O

– Wat een constructor is. – Hoe je een constructor gebruikt. – Hoe een constructor in een UML-klassendiagram wordt weergegeven.


441

Hoofdstuk 6 Structs

Beschrijving Een struct is een eenvoudige klasse die bedoeld is voor simpele datastructuren die weinig (16 bytes) geheugen in beslag neemt.

T

Binnen de computer kennen we twee soorten geheugen: de stack en de heap.

EP

Stack

C

In het stack-geheugen worden alle programmaonderdelen geplaatst zodra ze aangeroepen worden. Ze worden daar als het ware op elkaar gestapeld. Wil je bij een onderliggend onderdeel? Dan moeten de bovenliggende onderdelen eerst verwijderd worden. Het stack-geheugen is redelijk beperkt maar wel snel. Zo moet vooraf bekend zijn wat de grootte van het programmaonderdeel is dat op de stack uitgevoerd wordt.

C

O

N

De stack kun je voorstellen als een stapel papier:

Afbeelding 1 Stapel papier

Als je bij het onderste blaadje wilt komen, moet je eerst de rest verwijderen.

Heap Daarnaast kennen we de heap. Binnen de heap is de beperking alleen de hoeveelheid hardware van de computer. Programmaonderdelen op de heap kunnen gewijzigd worden, kunnen groter of kleiner gemaakt worden. Kortom, allemaal erg flexibel. Al die flexibiliteit heeft een prijs: administratie en snelheid. De stack is vele malen sneller dan de heap.


442

Deel 4: Objectoriëntatie

Een struct maakt gebruik van het stack-geheugen, een class gebruikt het heapgeheugen. Heel veel donkerblauwe items in Visual Studio ‘leven’ op de stack, de lichtblauwe items ‘leven’ meestal op de heap.

T

De heap kun je je voorstellen als een ladenkast:

EP

Afbeelding 2 Ladenkast

Als je een blaadje nodig hebt open je gewoon het juiste laatje.

C

Eigenschappen struct Een struct

N

Een struct onderscheidt zich van een class op de volgende manieren: Een class

is klein, liever niet groter dan 16 bytes

kent geen limiet qua grootte

is snel

is relatief traag leeft op de heap

mag geen parameterloze constructors bevatten

mag allerlei soorten constructors bevatten

moet variabelen initialiseren binnen de

kan variabelen overal initialiseren

C

O

leeft op de stack

constructor

kan variabelen buiten de constructor niet voorzien van een default waarde

kan variabelen overal voorzien van default waardes

kan niet erven

kan erven

kan niet van geërfd worden

kan van geërfd worden

UML Voor UML is een struct niets anders dan een klasse. Boven de naam van de klasse kun je dan aangeven of het een struct is:


Hoofdstuk 6 Structs

443

Figuur 9 UML-klassendiagram struct

Voorbeeld 6.1: NatSimII struct Snelheid We gaan eerst NatSim ombouwen tot een echt OO-project in NatSimII. In NatSimII zullen onze dieren door de wereld gaan bewegen. We gaan ze als objecten programmeren, zodat ieder individueel dier een eigen leven kan gaan leiden.

1

Kopieer het project NatSim naar NatSimII.

T

Ombouwen NatSim tot NatSimII

EP

2 Hernoem binnen NatSimII het formulier FrmNatSim naar FrmNatSimII.

3 Zorg dat je de namespace ook hernoemt.* Let erop dat bij nieuwe klassen ook steeds de namespace correct is.

C

4 We gaan nu nieuwe klassen toevoegen, maar ook oude klassen veranderen. In NatSim maakten we gebruik van een timer uit System.Timers, maar in NatSimII wordt dat een timer uit System.Windows.Forms. Controleer dus steeds of zowel using System. Drawing als using System.Windows.Forms toegevoegd zijn aan de lijst met bibliotheken.

O

N

Vanaf Visual Studio 15 kun je zien welke bibliotheken gebruikt worden omdat die vet gedrukt zijn. Als je wilt, kun je de ongebruikte bibliotheken uit de lijst verwijderen, maar je kunt ze ook voor de zekerheid laten staan; de snelheid van je programma verandert er niet door.

C

Aanmaken ondersteunende struct Snelheid Snelheden zijn iets met een waarde en een richting. Als een dier een rand tegenkomt moet het omkeren en dus wijzigen de x-waarde en de y-waarde. We maken een aparte struct Snelheid volgens het volgende UML-klassendiagram:

* Hoe je een project hernoemt, is uitgebreid beschreven in Deel 2: Gegevens, bij het Voorbeeld Array 2: Smoothies met uitgebreide ingrediëntenlijst.


444

Deel 4: Objectoriëntatie

Figuur 10 UML-klassendiagram Snelheid

1

Maak een public struct aan met de naam Snelheid. Weet je nog? Je klikt bij Project op Add Class en verandert dan het woord class in struct. Vergeet using System. Drawing niet.

EP

// Eigenschappen public int X { get; set; } public int Y { get; set; }

T

2 Voeg de volgende eigenschappen toe aan de struct Snelheid:

3 Voeg de volgende code toe binnen de public struct Snelheid om de constructor aan te maken:

N

C

// Initialisatie public Snelheid(int x, int y) : this() { X = x; Y = y; }

C

O

Achter de constructor van de struct moet je eerst de standaard-constructor uitvoeren. Dit doe je met het keyword this(). Deze standaard parameterloze constructor van een struct zorgt ervoor dat de eigenschappen een initiële waarde krijgen. Doe je dit niet dan krijg je de volgende foutmeldingen:

Schermafbeelding 12 Foutmelding struct

4 De methode Keerom() zorgt ervoor dat het object omkeert. Voeg de volgende regels toe: // Methoden // Keerom: het object gaat dezelfde weg terug. public Snelheid Keerom() { X = -1 * X; Y = -1 * Y; return new Snelheid(X, Y); }


Hoofdstuk 6 Structs

445

Bij de ondersteunende struct Snelheid hebben we iets nodig waarin we kunnen meegeven of er een horizontaal vlak, een verticaal vlak of een hoek geraakt is zodat we weten hoe het object van richting moet veranderen. 5 Maak binnen de namespace NatSimII een enum aan door de volgende regels toe te voegen: public enum Vlak { Horizontaal, Verticaal, Hoek, Geen }

T

6 De methode Stuiter() zorgt ervoor dat een object stuitert. Voeg de volgende regels toe aan de struct Snelheid:

C

O

N

C

EP

public Snelheid Stuiter(Vlak vlak) { // Stuiter: Als een object tegen een horizontaal vlak, boven of beneden, // botst, wordt de y-component van de snelheid omgedraaid. // Als een object tegen een verticaal vlak, links of rechts, // botst, wordt de x-component van de snelheid omgedraaid. // Bij een botsing precies in een hoek, worden beide componenten omgedraaid. switch (vlak) { case Vlak.Horizontaal: Y = -1 * Y; break; case Vlak.Verticaal: X = -1 * X; break; case Vlak.Hoek: X = -1 * X; Y = -1 * Y; break; } return new Snelheid(X, Y); }

Let op! Deze variabelen krijgen geen initiële waarde. Dit kan niet bij een struct. Dit moet je binnen de constructor regelen. Properties zijn ook altijd ReadOnly.

Wat heb je geleerd? – – – – – –

Wat een struct is. Hoe je een struct gebruikt. Wat de heap is. Wat de stack is. Het verschil tussen een struct en een class. Hoe een struct in een UML-klassendiagram wordt weergegeven.


446

Hoofdstuk 7 Compositie

Beschrijving

T

Je weet dat een klasse is opgebouwd uit een aantal andere klassen. Een auto bestaat uit onder andere vier wielen, een motor, een stuur, een paar pedalen, een carrosserie en een versnellingsbak. En een wiel is bij elke auto grofweg hetzelfde: een rond ding van rubber dat om een velg zit. De klasse Auto zou dus bestaan uit onder andere de klassen Wiel, Motor, Stuur, Pedaal, Carrosserie en Versnellingsbak.

EP

In dit boek heb je inmiddels al vele keren zo’n samengestelde klasse gemaakt. Voor de compositie (samenstelling of opbouw) van zo’n klasse maak je gewoon een eigenschap aan binnen een andere klasse, bijvoorbeeld: Pen myPen = new Pen(Color.Red);

C

myPen is een object van het type Pen, en tegelijkertijd onderdeel van het object (de

N

klasse) waarbinne myPen gebruikt wordt. Net zoals een Michelin-band een band, en tegelijkertijd een onderdeel is van mijn auto.

Klasse Rechthoek

O

Binnen de simulatie moet op de een of andere manier bepaald worden of een Konijn een object van het type Gras tegenkomt, of dat een Lynx een Konijn tegenkomt, enzovoort.

C

Al deze objecten worden voorgesteld door rechthoekjes. Daarom maken we een klasse Rechthoek die binnen de andere klassen gebruikt gaat worden. Met behulp van de klasse Rechthoek gaan we vaststellen of een object de grenzen van de PictureBox pbWereld overschrijdt en of twee objecten elkaar raken. Dit proces heet collision detection en wordt veel gebruikt binnen games en simulaties. In deze simulatie wordt gebruikgemaakt van een zeer eenvoudige vorm van collision detection die we in hoofdstuk 13 gaan programmeren. Een static methode, een methode die je kunt gebruiken zonder eerst een instantie van de klasse te maken, wordt binnen UML aangegeven door de methode te onderstrepen. Het UML-klassendiagram van de klasse Rechthoek ziet er als volgt uit:


Hoofdstuk 7 Compositie

447

Figuur 11 UML-klassendiagram Rechthoek

Voorbeeld 7.1: Compositie klasse Rechthoek Maak een nieuwe klasse aan binnen NatSimII met de naam Rechthoek.

T

1

2 Voeg aan de klasse Rechthoek de volgende regel toe:

EP

using System.Drawing;

3 Voeg het volgende commentaar, constructor en eigenschappen toe:

O

N

C

// Een meetkundige rechthoek bestaat uit 4 punten: A, B, C en D // // A(10,4---------------B(30,4) // | | // | | // C(10,26)-------------D(30,26) // // waarbij: // - de X-waarde van A gelijk is aan de X-waarde van C (10) // - de Y-waarde van A gelijk is aan de Y-waarde van B (4) // - de X-waarde van D gelijk is aan de X-waarde van B (30) // - de Y-waarde van D gelijk is aan de Y-waarde van C (26)

C

// Het punt A is gelijk aan de locatie waar geklikt wordt. // De andere punten worden berekend op basis van de gegeven afmetingen // (breedte en hoogte) // Deze klasse heeft methoden die bepalen of de ene rechthoek // een andere rechthoek raakt.

// Constructor public Rechthoek() { } public Rechthoek(Point locatie, Size afmetingen) { Locatie = locatie; Afmetingen = afmetingen; } public Size Afmetingen { get; set; } public Point A { get; set; }

4 Punt A wordt bij het bij het aanmaken van de klasse ingesteld. De eigenschap Locatie is als het ware een alias voor punt A. De overige punten worden berekend


448

Deel 4: Objectoriëntatie

aan de hand van punt A en de afmetingen van de Rechthoek. Voeg hiertoe de volgende regels toe aan de klasse Rechthoek: public Point B { get { return new Point(A.X + Breedte, A.Y); } } public Point C { get { return new Point(A.X, A.Y + Hoogte); } } public Point D { get { return new Point(B.X, C.Y); } }

C

EP

T

// De Locatie vanwaar getekend wordt, is gelijk aan punt A. public Point Locatie { get { return A; } set { A = value; } } public int Breedte { get { return Afmetingen.Width; } } public int Hoogte { get { return Afmetingen.Height; } }

N

5 De klasse Rechthoek kent twee methoden die bepalen of een object van het type Rechthoek, dat geheel binnen een andere Rechthoek valt, de grens van dat object, bijvoorbeeld een PictureBox, bereikt heeft.

O

Voeg de statische methode toe die dit uitvoert voor twee objecten van het type Rechthoek door de volgende regels toe te voegen:

C

// Statische methode om te onderzoeken of twee objecten van het type rechthoek // elkaar raken. Deze methode kan statisch zijn omdat alle parameters worden // meegegeven aan de methode. public static Vlak GrensBereikt(Rechthoek rechthoek1, Rechthoek rechthoek2) { Vlak vlak = Vlak.Geen; // overschrijden linker- of rechterkant if (rechthoek1.A.X <= rechthoek2.A.X || rechthoek1.B.X >= rechthoek2.B.X) { vlak = Vlak.Verticaal; } // overschrijden onder- of bovenrand if (rechthoek1.A.Y <= rechthoek2.A.Y || rechthoek1.C.Y >= rechthoek2.C.Y)


Hoofdstuk 7 Compositie

449

{ if (vlak == Vlak.Verticaal) { vlak = Vlak.Hoek; } else { vlak = Vlak.Horizontaal; } } return vlak; }

T

6 Vervolgens maken we een methode aan die gebruikmaakt van de static methode GrensBereikt() om een instantie van een Rechthoek te vergelijken met een andere Rechthoek. Voeg de volgende regels toe:

C

EP

// De volgende methode onderzoekt of een andere rechthoek de huidige instantie // van Rechthoek raakt. // Omdat gebruikgemaakt wordt van de variabelen van de huidige instantie // Rechthoek kan deze methode niet statisch zijn. public Vlak GrensBereikt(Rechthoek Rechthoek2) { // Rechthoek1 is de huidige instantie van de klasse Rechthoek en deze wordt // aangeroepen met het woord this. return GrensBereikt(this, Rechthoek2); }

N

7 Behalve dat er een methode nodig is voor een rechthoek die volledig binnen een andere rechthoek valt, zoals een konijn altijd geheel binnen de rechthoek van de PictureBox valt, heb je een andere methode nodig voor het geval twee rechthoeken botsen. Maak de volgende methode aan:

C

O

// Methode waarmee bepaald wordt of twee rechthoeken elkaar overlappen: public bool Overlap(Rechthoek rechthoek) { return ((rechthoek.D.X >= A.X && rechthoek.A.X <= D.X) && (rechthoek.D.Y >= A.Y && rechthoek.A.Y <= D.Y)); }

8 De laatste methode van de klasse Rechthoek zet een object van het type Rechthoek om naar het C-type Rectangle. Voeg de volgende regels toe: // Omzetten van het type Rechthoek naar het VisualStudio-type Rectangle: public Rectangle ToRectangle() { return new Rectangle(Locatie, Afmetingen); }

9 Maar ja, wat is een Rechthoek zonder oppervlak en omtrek? Een valkuil van veel programmeurs is dat ze niet-gespecificeerde, voor de hand liggende, items er even bij maken. Maar dat is precies wat we hier wel gaan doen. Dan kun je deze klasse nog vaker gebruiken. Voeg de volgende regels toe:


450

Deel 4: Objectoriëntatie

public int Oppervlak() { return Breedte * Hoogte; } public int Omtrek() { return 2 * ( Breedte + Hoogte); }

Wat heb je geleerd?

C

O

N

C

EP

T

– Wat het begrip compositie inhoudt. – Hoe je een compositie gebruikt. – Hoe een compositie in een UML-klassendiagram wordt weergegeven.


451

Hoofdstuk 8 Overerving

Beschrijving

EP

T

Het kunnen erven van een klasse is een van de pijlers van het objectgeoriënteerde programmeren. Het principe is vrij eenvoudig. Als er een klasse is gedefinieerd met alle kenmerken van een voertuig zoals een identificatie, vermogen, snelheid en aantal personen dan kan ik andere klassen definiëren die alle eigenschappen, events en methoden erven van deze klasse. Zo kun je een klasse auto definiëren die erft van voertuig, een klasse boot die erft van voertuig en zo verder. Je kunt de klasse voertuig zo vaak gebruiken als je wilt. Je kunt echter maar van één klasse erven.

Voordelen

O

UML

N

C

Het moge duidelijk zijn. Op deze manier kun je veel code hergebruiken, je hoeft maar één keer iets te maken en vele klassen kunnen er gebruik van maken. Ook qua onderhoud is dit handig. Je hoeft je code maar op één plek aan te passen en alle klassen maken er gebruik van.

C

Zoals je gemerkt hebt bij het maken van het project NatSim zit er nogal wat overlap tussen de verschillende klassen. Vandaar dat we het project NatSimII wat robuuster gaan maken. We zetten eerst de vier UML-klassendiagrammen voor Konijn, Venijnboom, Gras en Koe op een rij:


452

Deel 4: Objectoriëntatie

.RH

.RQLMQ

$WWULEXWHV

$WWULEXWHV

$DQWDO/LWHUV0HON GRXEOH $IPHWLQJHQ 6L]H ^UHDG2QO\` *HERRUWHGDWXP 'DWH7LPH *HZLFKW GRXEOH *HZLFKW0D[LDPDDO GRXEOH ^UHDG2QO\` .OHXU &RORU %ODFN /DWLMQVH1DDP VWULQJ %RV WDXUXV^UHDG2QO\` /HHIWLMG LQW /HYHQVGXXU LQW ^UHDG2QO\` /RFDWLH 3RLQW 0DDJ*HYXOG LQW 1DDP VWULQJ 1HGHUODQGVH1DDP VWULQJ .RH^UHDG2QO\` 5LFKWLQJ; LQW 5LFKWLQJ< LQW 6WHUI'DWXP 'DWH7LPH 9HURXGHU 7LPHU^UHDG2QO\` 9HUZLMGHUG ERRO IDOVH^UHDG2QO\` 9RHGLQJVZDDUGH LQW :RUGW9HUJLIWLJG'RRU /LVW VWULQJ! 9HQLMQERRP 9LQJHUKRHGVNUXLG^UHDG2QO\`

$IPHWLQJHQ 6L]H ^UHDG2QO\` *HERRUWHGDWXP 'DWH7LPH *HZLFKW GRXEOH *HZLFKW0D[LDPDDO GRXEOH ^UHDG2QO\` .OHXU &RORU %URZQ /DWLMQVH1DDP VWULQJ 2U\FWRODJXV FXQLFXOXV^UHDG2QO\` /HHIWLMG LQW /HYHQVGXXU LQW ^UHDG2QO\` /RFDWLH 3RLQW 0DDJ*HYXOG LQW 1DDP VWULQJ 1HGHUODQGVH1DDP VWULQJ .RQLMQ^UHDG2QO\` 5LFKWLQJ; LQW 5LFKWLQJ< LQW 6WHUI'DWXP 'DWH7LPH 9HURXGHU 7LPHU^UHDG2QO\` 9HUZLMGHUG ERRO IDOVH^UHDG2QO\` 9RHGLQJVZDDUGH LQW :RUGW9HUJLIWLJG'RRU /LVW VWULQJ! 9HQLMQERRP 9LQJHUKRHGVNUXLG^UHDG2QO\`

9HQLMQERRP

(HW YRHGVHO REMHFW .RQLMQ .RQLMQ ORFDWLH 3RLQW .RQLMQ ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU 7HNHQ SDSLHU *UDSKLFV 9HUZLMGHU HHW*UDV YRHGVHO *UDV HHW9HQLMQERRP YRHGVHO 9HQLMQERRP KRQJHU ERRO LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

EP

(HW YRHGVHO REMHFW .RH .RH ORFDWLH 3RLQW .RH ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU 7HNHQ SDSLHU *UDSKLFV 9HUZLMGHU HHW*UDV YRHGVHO *UDV HHW9HQLMQERRP YRHGVHO 9HQLMQERRP KRQJHU ERRO LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

2SHUDWLRQV

T

2SHUDWLRQV

*UDV

$WWULEXWHV

$WWULEXWHV

$IPHWLQJHQ 6L]H ^UHDG2QO\` %ORHLZLM]H3ODQW %ORHLZLM]H %ORHLZLM]H DDU^UHDG2QO\` .OHXU &RORU /DZQ*UHHQ /DWLMQVH1DDP VWULQJ *UDPLQHDH^UHDG2QO\` /HHIWLMG LQW /HYHQVGXXU LQW ^UHDG2QO\` /RFDWLH 3RLQW 1HGHUODQGVH1DDP VWULQJ *UDV^UHDG2QO\` 9HURXGHU 7LPHU^UHDG2QO\` 9HUZLMGHUG ERRO IDOVH^UHDG2QO\` 9RHGLQJVZDDUGH LQW

2SHUDWLRQV

O

N

C

$DQWDO.XELHNH0HWHUV+RXW GRXEOH $IPHWLQJHQ 6L]H ^UHDG2QO\` %ORHLZLM]H3ODQW %ORHLZLM]H %ORHLZLM]H NHJHO^UHDG2QO\` *HQHHVPLGGHO9RRU /LVW VWULQJ! ORQJNDQNHU ERUVWNDQNHU^UHDG2QO\` .OHXU &RORU )RUHVW*UHHQ /DWLMQVH1DDP VWULQJ 7D[XV EDFDWWD^UHDG2QO\` /HHIWLMG LQW /HYHQVGXXU LQW ^UHDG2QO\` /RFDWLH 3RLQW 0D[LPDOH*URRWWH LQW ^UHDG2QO\` 1HGHUODQGVH1DDP VWULQJ 9HQLMQERRP^UHDG2QO\` 9HURXGHU 7LPHU^UHDG2QO\` 9HUZLMGHUG ERRO IDOVH^UHDG2QO\` 9RHGLQJVZDDUGH LQW

*UDV *UDV ORFDWLH 3RLQW 7HNHQ SDSLHU *UDSKLFV 9HUZLMGHU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

C

7HNHQ SDSLHU *UDSKLFV 9HQLMQERRP 9HQLMQERRP ORFDWLH 3RLQW 9HUZLMGHU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

2SHUDWLRQV

Figuur 12 UML-klassendiagram NatSim

– – – – –

Als je goed kijkt naar de UML-klassendiagrammen van Figuur 12 NatSim is er een aantal overlappende onderdelen die in alle klassen terugkomen: Eigenschappen die te maken hebben met het tekenen van de klasse. Eigenschappen die met het levend zijn te maken hebben. Eigenschappen die met dieren te maken hebben. Eigenschappen die specifiek zijn voor planten. Verschillende eetmethoden voor planteneters, vleeseters en alleseters. We kijken eerst naar de eigenschappen. De bijbehorende private variabelen moet je natuurlijk wel meenemen maar zijn niet van belang in het selectieproces. Als je


Hoofdstuk 8 Overerving

453

de eigenschappen Locatie en RichtingX in een aparte klasse onderbrengt moet je in die klasse uiteraard ook de bijbehorende private variabelen _locatie en _richtingX onderbrengen.

NatSimII GrafischObject De volgende eigenschappen en methoden hebben te maken met het tekenen van de objecten bij alle klassen. We kunnen daarom een klasse GrafischObject gaan maken waarin we deze eigenschappen en methoden onderbrengen, en die gebruikt kan worden door alle planten en dieren in NatSimII.

T

Verwijderd Locatie Afmetingen

EP

– – – – – –

Kleur Verwijder() Teken()

Leven

N

C

Bij de klassen Konijn en Koe komen ook de eigenschappen RichtingY en RichtingX voor die te maken hebben met een bewegend object. Deze horen niet thuis in de klasse GrafischObject omdat planten daar niets mee van doen hebben. De oplossing hiervoor vind je in hoofdstuk 11 Interfaces.

NederlandseNaam LatijnseNaam

C

– – – – – – –

O

Elk van de klassen heeft een aantal eigenschappen die te maken heeft met het fenomeen leven:

Levensduur

VerhoudingTicksJaren Voedingswaarde Leeftijd Verouder

Dier De twee klassen Koe en Konijn komen voor een groot gedeelte overeen. Een aantal eigenschappen is nu ondergebracht in de klassen Leven en GrafischObject. De volgende eigenschappen en methoden komen voor bij zowel Koe als Konijn en alle andere dieren:


454

– – – – – – –

Deel 4: Objectoriëntatie

GewichtMaximaal Giftig GeboorteDatum SterfDatum RichtingX RichtingY EetVoedsel(object voedsel)

PlantenEter, AllesEter en VleesEter

T

De klasse Dier kent de methode EetVoedsel(object voedsel). Alleen is deze methode voor de verschillende groepen dieren anders. Een vleeseter eet geen planten, een planteneter eet geen vlees en een alleseter eet alles. Daarom gaan we ook de volgende klassen maken: Planteneter, Alleseter en Vleeseter.

EP

Plant

Tenslotte houden we een groep over die één eigenschap gemeen heeft, namelijk de BloeiwijzePlant. Dit wordt de klasse Plant.

C

Erven

C

O

N

Het leuke is dat als je eenmaal de klasse Leven hebt gedefinieerd, deze dan alle eigenschappen en methoden van GrafischObject kan erven. Deze eigenschappen en methoden hoef je niet nogmaals te definiëren. De klasse Dieren erft alle eigenschappen en methoden van Leven, en dus indirect ook van GrafischObject. Op deze manier kun je een hele stamboom opzetten van klassen die van elkaar erven. Ook hier geldt het Duitse spreekwoord: In die Beschränkung zeigt sich den Meister. Ofwel: maak de structuur niet te complex. Als de hiërarchie van objecten die van elkaar erven meer dan drie niveaus diep is, moet je jezelf al afvragen of je ontwerp niet te ingewikkeld wordt. Hieronder is een nieuwe indeling gemaakt van het project NatSim: NatSimII.


455

EP

T

Hoofdstuk 8 Overerving

Figuur 13 UML-klassendiagram NatSimII

N

Afspraken

C

De pijlen betekenen hier ‘erft van’. Koe erft dus van Planteneter, Planteneter erft van Dier, Dier erft van Leven en Leven erft van GrafischObject.

C

O

– Een klasse kan een aantal kenmerken, zoals variabelen en methoden, overnemen van een andere klasse. Dit noemen we erven (inherit). Zo’n klasse noemen we een subklasse. De klasse waarvan geërfd wordt noemen we een superklasse. – Binnen een UML-klassendiagram wordt door middel van een pijl aangegeven van welke superklasse een subklasse erft. – Binnen een subklasse kunnen ook eigen methoden en variabelen gedefinieerd worden die niet in de superklasse voorkomen. – De structuur van superklassen en subklassen wordt de klassenhiërarchie genoemd. – Binnen C kan een subklasse slechts van één superklasse erven.

Voorbeeld 8.1: Overerving Aanmaken en implementeren klasse GrafischObject 1

Maak een nieuwe klasse aan binnen NatSimII met de naam GrafischObject

2 Voeg aan de klasse GrafischObject de volgende regel toe: using System.Drawing;

3 We gaan het volgende UML-klassendiagram implementeren:


456

Deel 4: Objectoriëntatie

Figuur 14 UML-klassendiagram GrafischObject

T

Zoals je ziet worden er twee klassen getoond: Rechthoek en GrafischObject. Dit is omdat GrafischObject twee eigenschappen heeft die van het type Rechthoek zijn.

EP

Als je goed opgelet hebt, zie je dat de eigenschappen van GrafischObject afwijken van de eigenschappen zoals die in NatSim gebruikt werden. Er is geen eigenschap Afmetingen en er zijn twee eigenschappen bijgekomen: GraphicsVenster en Tekengebied, beide van het type Rechthoek.

C

GraphicsVenster wordt gebruikt om te bepalen of een object de grens van een ven-

ster bereikt heeft en dus moet omkeren.

Tekengebied is het rechthoekje dat het Dier of de Plant symboliseert (voorstelt) op

N

het scherm.

O

De klasse GrafischObject wordt voorzien van een constructor die de protected methode initClass aanroept die een aantal standaardwaardes instelt, waaronder een nieuwe lege Rechthoek.

C

protected betekent dat deze methode wel aangeroepen kan worden door een andere klasse zolang dit programma maar behoort tot dezelfde namespace. Een protected klasse herken je in UML aan het teken  (hash).

4 Voeg de volgende regels code toe aan de klasse GrafischObject: public class GrafischObject { // // Constructor // public GrafischObject() { initClass(); } // // Eigenschappen //


Hoofdstuk 8 Overerving

457

C

O

N

C

EP

T

public Color Kleur { get; set; } public Color KaderKleur { get; set; } public Point Locatie { // Deze eigenschap is eigenlijk een doorgeefluik // naar de bijbehorende instantie van Rechthoek. // Door deze eigenschap hier op te nemen kan de klasse // Konijn makkelijker gebruikt worden. get { return Tekengebied.Locatie; } set { Tekengebied.Locatie = value; } } public Graphics Papier { get { return _papier; } set { _papier = value; // Om te bepalen hoe groot het tekengebied is van de control waarop // getekend wordt, kun je deze van het grafisch object krijgen. // Addertje onder het gras is dat de VisibleClipBounds het gebied // aangeven wat je ziet. Het totale grafische object is veel groter. // Omdat in uitzonderlijke gevallen een null-waarde kan ontstaan // wordt hierop gecheckt om foutmeldingen te voorkomen. if (value != null) { int breedteVenster = (int)Papier.VisibleClipBounds.Width; int hoogteVenster = (int)Papier.VisibleClipBounds.Height; _graphicsVenster = new Rechthoek(new Point(0, 0), new Size(breedteVenster, hoogteVenster)); } } } public Rechthoek Tekengebied { get; set; } public Color Wiskleur { get; set; } // // ReadOnly-eigenschappen // public Rechthoek GraphicsVenster { get { return _graphicsVenster; } } public Guid ID { get { return _id; } } // // Variabelen // private Guid _id; private bool _verwijderd = false; private Graphics _papier; private Rechthoek _graphicsVenster; // // Methoden // // De methode initClass stelt de standaardwaarde in voor enkele // eigenschappen. De methode is private omdat deze methode alleen binnen deze // klasse gebruikt wordt. Deze methode wordt altijd door één of meerdere // constructors gebruikt.


458

Deel 4: Objectoriëntatie

private void initClass() { KaderKleur = Color.Black; Kleur = Color.WhiteSmoke; Wiskleur = Color.PaleGoldenrod; // Grafische instellingen: Tekengebied = new Rechthoek(); // Instellen van de guid. Een guid of uuid is een acroniem voor // ‘Globally Unique Identifier’ (of ‘Universally Unique Identifier’). // Het is een 128-bits integer die gebruikt wordt om objecten te // identificeren: _id = Guid.NewGuid(); }

N

C

EP

T

public void Verwijder() { _verwijderd = true; Wis(); } public void Wis() { // Door het object opnieuw te tekenen met de Wiskleur, de kleur van // het object waarop getekend wordt, wordt een object onzichtbaar: Color oudeKleur = Kleur; Color oudeKaderkleur = KaderKleur; Kleur = Wiskleur; KaderKleur = Wiskleur; Teken(); KaderKleur = oudeKaderkleur; Kleur = oudeKleur; }

O

}

C

5 Tot slot moet de methode Teken() toegevoegd worden aan de klasse GrafischObject. Het kan alleen wel voorkomen dat het grafisch object Papier nog niet is ingesteld. Om dat te voorkomen, wordt een controle ingebouwd. Voeg de volgende regels toe aan de klasse GrafischObject:

public void Teken(Graphics papier) { // Door het GraphicsObject waarop getekend wordt te bewaren kunnen we een // getekend object ook altijd weer uitvegen: Papier = papier; // Het tekenen van een kader op de locatie van het object met de afmetingen // van het object: Pen pen = new Pen(KaderKleur, 2); // Het tekenen van de rechthoek }


Hoofdstuk 8 Overerving

459

6 Bij het tekenen van de Rechthoek moet er dus eerst gecontroleerd wordt of de eigenschap Papier leeg is. Dit doen we door het volgende if-statement toe te voegen na de regel // Het tekenen van de rechthoek: if (Papier != null) { Papier.DrawRectangle(pen, Tekengebied.ToRectangle()); }

We doen dit met een if-statement omdat dit het systeem veel minder belast dan een try ... catch-blok.

T

7 Voeg de volgende regels toe aan de klasse GrafischObject binnen het if-statement. Er wordt alleen getekend als er een grafisch object is.

EP

// Het direct vrijgeven van het geheugen van een gebruikt object: pen.Dispose();

C

// Het vullen van de rechthoek op de locatie van het object met de afmetingen // van het object: SolidBrush kwast = new SolidBrush(Kleur); Papier.FillRectangle(kwast, Tekengebied.ToRectangle()); // Het direct vrijgeven van het geheugen van een gebruikt object: kwast.Dispose();

N

8 Als het object verwijderd wordt, gebruiken we de eigenschap Papier die gevuld is bij het tekenen van het object om het object ook weer ‘uit te gummen’ (nogmaals te tekenen met als kleur de achtergrondkleur). Voeg de volgende code toe om een parameterloze methode Teken() te maken:

C

O

public void Teken() { Teken(Papier); }

Aanmaken en implementeren van de klasse Leven

GrafischObject is een klasse die je kunt gebruiken om ergens een object te tekenen en weer te verwijderen. De klasse Leven erft van deze klasse alle objecten, methoden en eigenschappen. Deze hoeven we dus niet nog een keer te maken. Hierdoor kan elk object dat van de klasse Leven afstamt getekend en weer uitgewist worden.

Omdat Leven erft van GrafischObject tekenen we in het UML-diagram beide klassen. De pijl geeft aan dat Leven erft van GrafischObject.


Deel 4: Objectoriëntatie

EP

T

460

Figuur 15 UML-klassendiagram leven

Voeg de klasse Leven toe aan het project NatSimII. Door middel van de dubbele punt achter de klassennaam, in dit geval Leven, geef je aan dat de klasse Leven alle methoden en eigenschappen erft van de klasse die achter de dubbele punt genoemd wordt; in dit geval de klasse GrafischObject.

C

1

N

2 Voeg de volgende verwijzing toe: using System.Windows.Forms;

O

3 Voeg de volgende code toe aan het project NatSimII om de klasse Leven te maken:

C

public class Leven: GrafischObject { }

3 De klasse Leven krijgt twee constructors. Een lege, want soms heb je alleen maar een lijstje nodig van giftige planten en dan is een beperkte set aan gegevens voldoende. En een constructor waarbij de nodige ReadOnly-eigenschappen zoals LatijnseNaam en Levensduur ingesteld worden. Voeg aan de klasse Leven de volgende regels toe om de constructors te maken: // Initialisatie public Leven() { // In dit geval wordt iets van het type klasse aangemaakt zonder // parameters. // Omdat er ook een timer wordt geïnstalleerd die het object automatisch // weer laat verdwijnen, stellen we de levensduur in op // de grootste waarde.


Hoofdstuk 8 Overerving

461

initClass(1,””,int.MaxValue); } public Leven(int verhoudingTicksJaren, string latijnseNaam, int levensduur) { initClass(verhoudingTicksJaren, latijnseNaam, levensduur); }

5 Behalve de constructor moet ook de methode initClass nog aangemaakt worden. Voeg de volgende regels toe:

EP

T

private void initClass(int verhoudingTicksJaren, string latijnseNaam, int levensduur) { VerhoudingTicksJaren = verhoudingTicksJaren; // Instellen ReadOnly-variabelen _latijnseNaam = latijnseNaam; _levensduur = levensduur; _verouder = new Timer(); _verouder.Interval = _aantalTicksPerSeconde * VerhoudingTicksJaren; }

6 Voeg de volgende code toe om de klasse Leven te completeren:

C

// Interne variabelen private const int _aantalTicksPerSeconde = 1000; private string _latijnseNaam; private double _levensduur; private Timer _verouder;

N

// Eigenschappen public int Leeftijd { get; set; } public int VerhoudingTicksJaren { get; set; } public int Voedingswaarde { get; set; }

C

O

// ReadOnly-eigenschappen public string LatijnseNaam { get { return _latijnseNaam; } } public double Levensduur { get { return _levensduur; } } public string NederlandseNaam { get { return this.ToString().Split(‘.’). Last(); } } public Timer Verouder { get { return _verouder; } } // Methoden public void Sterf() { Verwijder(); }

In de klasse Leven zijn drie bijzondere elementen: 1

Er wordt gebruikgemaakt van een constante, namelijk _aantalTicksPerSeconde. Dit is een onveranderlijk gegeven: 1 seconde duurt 1000 ticks.

2 De eigenschap NederlandseNaam is ReadOnly maar maakt geen gebruik van een interne variabele. Er is een stuk code gemaakt die de naam van de klasse destilleert uit de volledig gekwalificeerde naam van de klasse. Als je de code this.ToString() ge-


462

Deel 4: Objectoriëntatie

bruikt krijg je in het geval van de klasse Leven het volgende te zien: NatSimII.Leven. maar we willen alleen Leven zien zodat bij de klasse Konijn als NederlandseNaam Konijn wordt weergegeven. Dat doet dit stukje code. 3 Een van de kenmerken van Leven is dat het ooit sterft. Vandaar de methode Sterf(). Later zal daar meer code aan toegevoegd worden. Op dit moment zorgt de code ervoor dat de grafische representatie van een object dat erft van GrafischObject wordt verwijderd. Verwijder() is een methode van GrafischObject die je zo kunt gebruiken, omdat je

deze erft.

Toevoegen van de klasse Plant

C

O

N

C

EP

T

De klasse Plant erft van de klasse Leven. In de klasse Plant worden alle eigenschappen en methoden ondergebracht die relevant zijn voor alle planten. Het UMLklassendiagram van Plant ziet er als volgt uit:

Figuur 16 UML-klassendiagram Plant

1

Maak een klasse aan met de naam Plant, vul het lijstje bibliotheken aan met using System.Drawing en voeg de volgende code toe: public class Plant: Leven { // // Initialisatie //


Hoofdstuk 8 Overerving

463

public Plant(){} public Plant(int verhoudingTicksJaren, string latijnseNaam, int levensduur , Bloeiwijze bloeiwijzePlant) : base(verhoudingTicksJaren, latijnseNaam, levensduur) { BloeiwijzePlant = bloeiwijzePlant; } // // Eigenschappen // public Bloeiwijze BloeiwijzePlant { get; set; } }

EP

T

In de klasse Leven zie je twee constructors. Omdat de klasse Leven een constructor kent met de parameter verhoudingTicksJaren die de snelheid van het leven bepaalt en we deze functionaliteit ook binnen de klasse Plant willen implementeren (aanbrengen) moeten we de klasse Plant ook uitrusten met twee constructors: – Een lege, die automatisch de parameterloze constructors van de basisklassen (baseklassen) aanroept. De base-klasse (of superklasse) is de klasse waarvan geërfd wordt. – En een constructor waar je de benodigde parameters verhoudingTicksJaren, latijnseNaam en levensduur doorgeeft aan de klasse Leven. Dit doe je door de regel

C

: base(verhoudingTicksJaren, latijnseNaam, levensduur){}

toe te voegen achter de constructor-naam.

N

Met het woord : base() zeg je dat je de constructor van de basisklasse start.

O

Doordat Plant erft van Leven en Leven erft van GrafischObject is het merendeel van de vereiste functionaliteit voor deze klasse al geregeld.

C

Je zult zien hoe eenvoudig het kan zijn. Met een paar regels code heb je een nieuwe klasse aangemaakt met veel functionaliteit.

Toevoegen van de klasse Gras De klasse Gras erft van de klasse Plant. De oude klasse Gras kent een methode Zaaien(). De methode Zaaien() voegt over een bepaald oppervlak Gras objecten toe aan het model. Deze methode gaan we later in een andere klasse implementeren.

Het nieuwe UML-klassendiagram voor Gras ziet er als volgt uit; uiteraard erft Plant nog steeds van Leven en Leven van GrafischObject:


Deel 4: Objectoriëntatie

C

EP

T

464

Figuur 17 UML-klassendiagram Gras

Open de klasse Gras.

N

1

O

2 Gooi alle al aangemaakte code binnen de klasse Gras weg zodat je alleen de volgende code overhoudt:

C

using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NatSimII { class Gras { } enum Bloeiwijze ...

3 Ook de regel using System.Timers kan weg. De timer (van System.Windows.Forms) wordt immers nu aangeroepen in de klasse Leven waarvan Gras via Plant gaat erven. Let op! De inhoud van enum Bloeiwijze blijft gewoon bestaan.


Hoofdstuk 8 Overerving

465

4 Zorg dat de klasse Gras van Plant erft door : Plant achter public class Gras toe te voegen. 5 Maak de twee constructors aan door de volgende regels toe te voegen: // // Initialisatie //

T

public Gras() :base(1,”Gramineae”,4, Bloeiwijze.aar) { initClass(new Point(0,0)); } public Gras(Point locatie): base(1,”Gramineae”,4,Bloeiwijze.aar) { initClass(locatie); }

EP

6 Zoals je ziet wordt bij beide constructors de base-klasse aangeroepen om de snelheid van de simulatie in te stellen: de LatijnseNaam en de Levensduur van het Gras. 7 Bij de initialisatie hoort ook de methode initClass. Voeg de volgende regels toe aan de klasse Gras:

Gras kent de methode Zaaien() om tegelijkertijd meerdere Gras objecten te kunnen aanmaken. Voeg de volgende regels toe aan de klasse Gras:

O

8

N

C

private void initClass(Point locatie) { Locatie = locatie; Tekengebied.Afmetingen = new Size(2,10); Kleur = Color.LawnGreen; Voedingswaarde = 1; }

C

public static void Zaaien(Point locatie, Graphics papier, int lengte, int breedte, int zaaiAfstand) { // Berekenen van het punt vanaf waar gezaaid gaat worden // (de locatie ligt midden in het gebied): int puntX = locatie.X - lengte / 2; int puntY = locatie.Y - breedte / 2; // Variabelen die de uitgangsposities bewaren zodat // deze hersteld en opnieuw gebruikt kunnen worden: Point oorspronkelijkeLocatie = locatie; int startpuntY = puntY; // Bepalen tot welke locatie er gezaaid gaat worden: lengte = puntX + lengte; breedte = puntY + breedte;


466

Deel 4: Objectoriëntatie

while (puntX < lengte) { while (puntY < breedte) { locatie = new Point(puntX, puntY); Gras gras = new Gras(locatie); gras.Teken(papier); puntY = puntY + zaaiAfstand; } puntY = startpuntY; puntX = puntX + zaaiAfstand; } }

T

9 Voor het standaard gebruik maken we tenslotte een overloaded methode Zaaien() aan die voorziet in een standaard lengte, breedte en zaaiafstand. Deze hebben we later nodig. Voeg de volgende regels toe:

EP

public static void Zaaien(Point locatie, Graphics papier, Plant plant) { Zaaien(locatie, papier, 150, 46, 15); }

Wijzigen klasse Venijnboom

C

O

N

C

De klasse Venijnboom kent ten opzichte van Plant enige extra eigenschappen zoals het volgende UML-klassendiagram laat zien:

Figuur 18 UML-klassendiagram Venijnboom

1

Open de klasse Venijnboom en verwijder alle code zodat je de volgende regels overhoudt met betrekking tot de klasse Venijnboom:


Hoofdstuk 8 Overerving

467

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; namespace NatSimII { public class Venijnboom { } }

2 Voeg : Plant toe achter public class Venijnboom om ervoor te zorgen dat Venijnboom van Plant gaat erven.

T

3 Implementeer het initialisatiegedeelte (constructors plus ondersteunende methoden) door de volgende regels toe te voegen aan de klasse Venijnboom:

N

C

EP

// // Initialisatie // public Venijnboom() : base(1,”Taxus baccata”,2000, Bloeiwijze.kegel) { initClass(new Point(0,0)); } public Venijnboom(Point locatie) : base(1,”Taxus baccata”,2000, Bloeiwijze.kegel) { initClass(locatie); }

C

O

private void initClass(Point locatie) { Locatie = locatie; Tekengebied.Afmetingen = new Size(10, 400); Kleur = Color.ForestGreen; }

4 Voor het coderen van de ReadOnly-eigenschappen voeg je de volgende regels toe aan de klasse Venijnboom: // // Variabelen // private List<string> _geneesmiddelVoor = new List<string> { “Longkanker”, “Borstkanker”}; private int _maximaleGrootte = 20000; // // ReadOnly-eigenschappen // public List<string> GeneesmiddelVoor { get { return _geneesmiddelVoor; } } public int MaximaleGrootte { get { return _maximaleGrootte; } }


468

Deel 4: Objectoriëntatie

5 Voeg de volgende regels toe om de eigenschap AantalKubiekeMetersHout te implementeren: // // Eigenschappen // public double AantalKubiekeMetersHout { get; set; }

Opdracht Overerving Opdracht 8.1: Klasse Vingerhoedskruid

O

N

C

EP

T

Maak de klasse Vingerhoedskruid aan volgens het volgende UML-diagram. Zoals je ziet, verschilt de klasse Vingerhoedskruid nauwelijks van de klasse Gras. Alleen de constructors en de waardes van de eigenschappen zijn verschillend.

C

Figuur 19 UML-klassendiagram Vingerhoedskruid

Let op: Misschien kun je je programma nu niet testen, omdat we nog niet alles hebben aangepast. Raadpleeg bij twijfel je docent.

Wat heb je geleerd? – – – –

Wat het begrip overerving inhoudt. Hoe je gebruikmaakt van overerving binnen C. Hoe overerving in een UML-klassendiagram wordt weergegeven. De voordelen van het werken met overerving.


469

Hoofdstuk 9 Abstracte klasse

Beschrijving

EP

T

Van sommige klassen heb je geen instantie nodig. Integendeel, van bijvoorbeeld de klassen Leven en Plant zul je geen instanties gaan aanmaken. Nee, je maakt een object aan van het type Gras, Venijnboom of Vingerhoedskruid. Om te voorkomen dat de klasse oneigenlijk gebruikt wordt kun je een klasse als abstract definiëren. Dit houdt in dat andere klassen van deze klasse kunnen erven maar dat je geen objecten van dat type kunt aanmaken. Maar hoe doe je dit? Heel eenvoudig door voor het woord class een spatie en het woord abstract te typen.

C

UML

C

O

N

Een abstracte klasse geef je in UML aan door de naam van de klasse met een cursief (scheef ) lettertype te schrijven. Het UML-klassendiagram van de klasse Leven komt er dan als volgt uit te zien:

Figuur 20 UML-klassendiagram abstracte klasse Leven

Voorbeeld 9.1: Abstract maken van de klasse GrafischObject 1

Open de klasse GrafischObject

2 Zoek de volgende regel op: Public class GrafischObject


470

Deel 4: Objectoriëntatie

3 Voeg voor het woord class het woord abstract toe zodat de volgende regel ontstaat: public abstract class GrafischObject

Nu is de klasse GrafischObject omgezet naar een abstracte klasse.

Opdrachten Abstracte klassen Opdracht 9.1: Abstract maken van de klasse Leven

T

Maak de klasse Leven abstract.

Opdracht 9.2: Abstract maken van de klasse Plant

Wat heb je geleerd?

N

C

Wat een abstract class is. Hoe je een abstract class gebruikt. Waarom je een abstract class gebruikt. Hoe een abstract class in een UML-klassendiagram wordt weergegeven.

C

O

– – – –

EP

Maak de klasse Plant abstract.


471

Hoofdstuk 10 Oefening, uitbouwen van het project

Werk aan de winkel. Om de volgende hoofdstukken goed te kunnen uitvoeren is het belangrijk dat de voorbeelden en opdrachten uit dit hoofdstuk allemaal worden uitgevoerd!

Voorbeeld 10.1: Klasse Dier

EP

T

Het leven bestaat uit Planten en Dieren. De klasse Dier is belangrijk voor de volgende hoofdstukken.

Aanmaken Dier

Maak de abstracte klasse Dier aan volgens het volgende UML-klassendiagram met de code hieronder. Typ ook de regel using System.Drawing weer in de lijst van benodigde bibliotheken.:

C

O

N

C

1

Figuur 21 UML-klassendiagram Dier


472

Deel 4: Objectoriëntatie

public abstract class Dier : Leven {

EP

// // Private variabelen // // Maximaal gewicht van een dier in kg private double _gewichtMaximaal = 0;

T

// // Constructor // public Dier(int verhoudingTicksJaren, string latijnseNaam, int levensduur, double gewichtMaximaal) : base(verhoudingTicksJaren,latijnseNaam,levensduur) { initDier(gewichtMaximaal); } private void initDier(double gewichtMaximaal) { _gewichtMaximaal = gewichtMaximaal; WordtVergiftigdDoor = new List<string>(); }

C

O

N

C

// // Eigenschappen voor alle dieren // // Van 0 tot 100% (honger tot 75%) public int MaagGevuld { get; set; } // Aantal ticks dat het duurt voordat een maaltijd verteerd is public int SpijsverteringsDuur { get; set; } // Maximaal gewicht van een dier in kg public double GewichtMaximaal { get { return _gewichtMaximaal; } } public List<string> WordtVergiftigdDoor { get; set; } public Snelheid SnelheidObject { get; set; }

// // Methoden // // Een kenmerk van dieren is dat ze eten. Op dit niveau weten we nog niet wat. // Een konijn zal geen lynx eten, andersom wel en een lynx zal geen gras eten. public abstract void Eet(Leven leven); // Dieren zullen wel, net als in NatSim, alleen eten als ze honger hebben: public bool Honger() { return (MaagGevuld < 25); }

}


Hoofdstuk 10 Oefening, uitbouwen van het project

473

Types vergelijken Een Planteneter eet Planten en een Vleeseter eet Dieren. Je moet dus bepalen van wat voor soort type het Leven is. 1

Voeg de volgende eigenschap toe aan de klasse Leven:

Opdracht 10.1: IsDier( )

EP

Opdracht Typering object

T

public bool IsPlant { // Deze code kijkt of het type van het object erft van Plant // en geeft het resultaat terug. // Met GetType bepaal je het type van het object. // Met IsSubclassOf bekijk je of deze klasse erft van, in dit geval Plant: get { return this.GetType().IsSubclassOf(typeof(Plant)); } }

C

Voeg aan de klasse Leven een eigenschap toe die bepaalt of het object van het type Dier is of niet.

Voorbeeld 10.2: Abstracte methode implementeren

C

O

N

We gaan hieronder de klasse Planteneter implementeren volgens het volgende UML-klassendiagram:

Figuur 22 UML-klassendiagram Planteneter

Binnen de klasse Dier is de abstracte methode Eet(Leven leven){ } gedefinieerd. Dit houdt in dat alle klassen die erven van Dier deze methode moeten implementeren. Doe je dit niet, dan krijg je de volgende foutmelding:

Schermafbeelding 13 Does not implement inherited abstract member

1

Maak een nieuwe abstracte klasse Planteneter aan die erft van Dier.

2 Typ daarin de volgende code: public override void Eet(


474

Deel 4: Objectoriëntatie

3 Niet schrikken! Omdat de methode al gedefinieerd is binnen Dier, kan Visual Studio de rest van de code, inclusief een standaardfoutmelding voor als je zelf geen code schrijft, aanmaken. Je ziet nu de volgende code: public override void Eet(Leven leven) { throw new NotImplementedException(); }

4 Vervang de regel throw new NotImplementedException(); door de volgende code die ervoor zorgt dat alleen Planten worden opgegeten:

N

C

EP

T

if (leven.IsPlant) { // Wat doet een planteneter met giftige planten? if (WordtVergiftigdDoor.Contains(leven.NederlandseNaam)) { // Normaal eet de planteneter geen giftige planten. Behalve als de // planteneter honger heeft. Alleen gaat de planteneter dan wel dood. // De planteneter eet een giftige plant en wordt verwijderd. if (Honger()) { this.Sterf(); leven.Sterf(); } else { // Als de plant niet eetbaar is, keer dan om. SnelheidObject = SnelheidObject.Keerom(); }

C

O

} // Alles wat niet giftig is wordt gewoon opgegeten als er nog plek is else if (MaagGevuld < 100) { MaagGevuld = MaagGevuld + leven.Voedingswaarde; leven.Sterf(); }

} // Als het leven geen plant is keert de planteneter om. else { SnelheidObject = SnelheidObject.Keerom(); }

5 Als je een PlantenEter aanmaakt, dan erft de Planteneter van Dier. Dus dan wil je ook dat de constructor van Dier uitgevoerd wordt. Dit kunnen we doen door de volgende constructor toe te voegen: public Planteneter(int verhoudingTicksJaren, string latijnseNaam, int levensduur, double gewichtMaximaal) : base(verhoudingTicksJaren,latijnseNaam,levensduur, gewichtMaximaal){}


Hoofdstuk 10 Oefening, uitbouwen van het project

475

Opdrachten Uitbouwen van het project Opdracht 10.2: Vleeseter Maak de klasse Vleeseter aan die erft van de klasse Dier volgens het volgende UMLklassendiagram.

Opdracht 10.3: Methode Eet( ) bij Vleeseter

T

Figuur 23 UML-klassendiagram Vleeseter

C

Opdracht 10.4: Alleseter

EP

Breid de klasse Vleeseter uit met een methode Eet(Leven leven) die ervoor zorgt dat alleen Dieren die kleiner zijn dan de Vleeseter opgegeten worden. Maak gebruik van een typecast.

O

N

Maak de klasse Alleseter aan die erft van de klasse Dier volgens het volgende UMLklassendiagram:

C

Figuur 24 UML-klassendiagram Alleseter

Opdracht 10.5: Methode Eet( ) bij Alleseter

– – – –

Zorg ervoor dat binnen de klasse Alleseter de methode Eet(Leven leven) het volgende doet: Alleen Dieren die kleiner zijn worden opgegeten. Eetbare Planten worden gegeten. Giftige Planten worden alleen opgegeten als de Alleseter honger heeft. Als de Alleseter een giftige Plant gegeten heeft, gaan zowel de Alleseter als de Plant dood.


476

Deel 4: Objectoriëntatie

Voorbeeld 10.3: Aanpassen van de klasse Konijn

EP

T

De klasse Konijn moet aangepast worden zodat deze erft van Planteneter. Tegelijkertijd moet de klasse zo aangepast worden dat alles wat al gedefinieerd is binnen Leven, Dier en Planteneter verwijderd wordt. Het nieuwe UML-klassendiagram van Konijn ziet er als volgt uit.

Figuur 25 UML-klassendiagram Konijn

1

Open de klasse Konijn.

2 Verwijder alles.

C

3 Voeg de volgende code toe:

O

N

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing;

C

namespace NatSimII { public class Konijn : Planteneter { // // Initialisatie // public Konijn() : base(_verhoudingTicksJaren, _latijnseNaam, _leeftijd, _gewichtMaximaal) { initClass(new Point(0, 0), “”, Color.Brown); } public Konijn(Point locatie) : base(_verhoudingTicksJaren, _latijnseNaam, _leeftijd, _gewichtMaximaal) { initClass(locatie, “”, Color.Brown); }


Hoofdstuk 10 Oefening, uitbouwen van het project

477

EP

T

public Konijn(Point locatie, string naam, Color kleur) : base(_verhoudingTicksJaren, _latijnseNaam, _leeftijd , _gewichtMaximaal) { initClass(locatie, naam, kleur); } // // Hier stel je de kenmerken van een konijn in. // private void initClass(Point locatie, string naam, Color kleur) { Locatie = locatie; Naam = naam; Kleur = kleur; Tekengebied.Afmetingen = new Size(10, 10); WordtVergiftigdDoor.Add(“Vingerhoedskruid”); WordtVergiftigdDoor.Add(“Venijnboom”); Gewicht = 5; Voedingswaarde = 1; }

N

C

// Voor ieder konijn gelden de volgende waardes. Vandaar dat // deze in een constante worden opgeslagen: het zijn onveranderlijke // variabelen. // Deze constanten worden weer doorgegeven aan de klassen waarvan geërfd // wordt. private const string _latijnseNaam = “Oryctolagus cuniculus”; private const int _leeftijd = 9; private const int _verhoudingTicksJaren = 1; private const double _gewichtMaximaal = 10;

O

// // Eigenschappen // public string Naam { get; set; } public double Gewicht { get; set;} public DateTime Geboortedatum { get; set; } public DateTime Sterfdatum { get; set;}

C

}

}

Je ziet het: veel minder code dan bij het eerste NatSim-programma. Bovendien wordt veel code ook binnen andere klassen gebruikt.

Opdracht Vernieuwde klasse Opdracht 10.6: Nieuwe klasse Koe Vervang de klasse Koe door een nieuwe klasse Koe die erft van Planteneter en die voldoet aan het volgende UML-klassendiagram:


478

Deel 4: Objectoriëntatie

C

O

N

C

EP

T

Figuur 26 UML-klassendiagram Koe


479

Hoofdstuk 11 Interfaces

Beschrijving

T

We hebben een klasse Leven die erft van GrafischObject. De klasse Dier verschilt onder andere van de klasse Plant in dat Dieren zich verplaatsen op het scherm. Dieren hebben een snelheid, een richting en ze kunnen botsen, net zoals alle andere bewegende objecten.

EP

Omdat we ook andere programmatuur kunnen gaan maken met bewegende objecten is het handig om hier een aparte klasse van te maken. Waarom? Omdat als we deze functionaliteit toevoegen aan de klasse GrafischObject ook alle objecten die niet moeten kunnen bewegen deze functionaliteit krijgen.

C

De oplossing zou kunnen zijn om een klasse BewegendObject te maken. Probleem is dan dat de klasse Dier moet erven van Leven én van BewegendObject, maar de volgende regel maakt dit onmogelijk:

N

Onthoud: Iedere klasse kan maar van één klasse erven!

O

De oplossing hiervoor is om een interface te maken. Een klasse kan van maximaal één klasse erven maar je kunt zoveel interfaces implementeren als je maar wilt en daarmee kun je ook op een bepaalde manier gegevens ‘erven’. Maar wat is nu een interface?

C

Een interface is een lege klasse waarin alleen de namen van de variabelen en de methoden zijn opgenomen. Door aan te geven dat een klasse gebruikmaakt van een interface geef je aan dat de genoemde variabelen en methoden aangemaakt moeten worden in de klasse.

Dit lijkt dubbel werk. Je maakt een interface aan, benoemt wat variabelen en methoden en dan moet je ze alsnog aanmaken binnen de klasse. Het grote voordeel hiervan is dat als een interface gebruikt is, je deze kunt gebruiken in een methode zonder dat je al weet welke klasse er uiteindelijk precies gebruikt wordt. Als er een interface BewegendObject geïmplementeerd is, dan moet de programmeur ervoor zorgen dat er bijvoorbeeld een methode Stap() is. Wat die methode doet, dat is de zaak van de programmeur. Voordeel is dat je alle objecten waar de interface BewegendObject geïmplementeerd is als volgt kunt aanspreken:


480

Deel 4: Objectoriëntatie

Je kunt nu een Array maken van het type BewegendObject. Alle objecten van het type Konijn, Beer, Koe of Lynx kun je nu toevoegen, en je zou ze als volgt kunnen benaderen: Koe koe = new Koe(); Konijn konijn = new Konijn(); Beer beer = new Beer(); Lynx lynx = new Lynx(); BewegendObject[] bewegendeObjecten = { konijn, koe, beer, lynx }; foreach (BewegendObject bewegendObject in bewegendeObjecten) { bewegendObject.Stap(); }

(Deze code werkt uiteraard nog niet, de klassen moeten we eerst nog aanpassen.)

EP

T

Komt er in de toekomst een object bij van bijvoorbeeld het type Aap die ook de interface BewegendObject implementeert (gebruikt), dan kan deze zo toegevoegd worden. Als je geen interface implementeert, heb je veel meer code nodig.

UML

O

N

C

Een interface is eigenlijk een apart soort klasse, net zoals de abstracte klasse. Een interface geef je als volgt aan door boven de naam van de klasse het woord interface tussen speciale haakjes (guillemets) te zetten:

Figuur 27 UML-klassendiagram interface

C

Voorbeeld 11.1: Interface IBewegendObject Aanmaken interface We gaan de interface IBewegendObject maken volgens het volgende UML-diagram:


481

EP

Figuur 28 UML-interface IBewegendObject

T

Hoofdstuk 11 Interfaces

De naming convention voor interfaces is dat de naam van elke interface begint met de hoofdletter i (I). 1

Klik binnen Visual Studio op Project.

C

O

N

C

2 Klik op Add Class. Het volgende venster wordt zichtbaar:

Schermafbeelding 14 Add New Item

3 Klik op Interface. 4 Voer bij Name IBewegendObject.cs in. Als het goed is krijg je de volgende code te zien:


482

Deel 4: Objectoriëntatie

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NatSimII { public interface IBewegendObject { } }

T

5 Voeg using System.Drawing toe en using System.Windows.Forms. Deze laatste bibliotheek hebben we nodig voor de timer die we gaan gebruiken.

Inhoud geven aan de interface IBewegendObject

Nu is het zaak om de benodigde eigenschappen te definiëren: Voeg de volgende regels toe aan de interface IBewegendObject:

EP

1

// Eigenschappen Snelheid SnelheidObject { get; set; }

C

Timer Klok { get; set; }

// De verplaatsing over de x- en de y-as // per tijdseenheid. // Hoe vaak wordt het scherm ververst?

N

2 Behalve eigenschappen kent IBewegendObject een aantal methoden die aangeroepen kunnen worden. Binnen de interface specificeren we alleen de handtekening van de methoden. Binnen het object waar de interface geïmplementeerd (toegepast) wordt, wordt de eigenlijke code van de methode toegevoegd. Voeg de volgende regels toe aan IBewegendObject:

C

O

// Methoden Point Stap(); Point Stap(Snelheid snelheidObject);

Implementatie IBewegendObject in de klasse Dier Er is nu een interface IBewegendObject. Het kenmerk van alle dieren is dat ze bewegen. Nu moet de interface IBewegendObject nog toegevoegd worden aan de klasse Dier.

Dit doen we door achter de naam van de klasse (en de klasse waarvan geërfd wordt) een komma en de naam van de interface toe te voegen. 1

Open de klasse Dier.

2 Zoek de volgende regel op: public abstract class Dier : Leven


Hoofdstuk 11 Interfaces

483

3 Voeg na het woord Leven het volgende aan deze regel toe: , IBewegendObject

4 Het resultaat is nu: public abstract class Dier : Leven, IBewegendObject

Dit betekent dat de klasse Dier erft van Leven en dat de interface IBewegendObject geïmplementeerd is. Alleen hebben we nog geen code toegevoegd…

EP

T

Je ziet al wel de volgende foutmeldingen:

C

Schermafbeelding 15 Dier does not implement interface

Implementatie interface-members

De interface IBewegendObject kent twee methoden met de naam Stap(). Deze methoden berekenen op basis van de snelheid de volgende coördinaten (Point) waar het object getekend gaat worden.

N

1

O

Voeg de volgende code toe aan de klasse Dier om de methoden Stap() te maken:

C

// Methoden voor IBewegendObject public Point Stap() { return Stap(this.SnelheidObject); } public Point Stap(Snelheid snelheidObject) { this.SnelheidObject = snelheidObject; // Waar komt het object terecht bij de voorgenomen stap? int berekendeX = Locatie.X + (SnelheidObject.X); int berekendeY = Locatie.Y + (SnelheidObject.Y); // Bepalen van het nieuwe Tekengebied waar het object getekend gaat worden: Rechthoek nieuwTekengebied = new Rechthoek(new Point(berekendeX, berekendeY), Tekengebied.Afmetingen); // Berekenen van een nieuwe richting naar aanleiding van een // eventuele overschrijding van een grens. Vlak vlak = Rechthoek.GrensBereikt(nieuwTekengebied, GraphicsVenster);


484

Deel 4: Objectoriëntatie

// Berekenen van nieuwe richting als het object een rand tegenkomt. SnelheidObject = SnelheidObject.Stuiter(vlak); // Opnieuw berekenen van de snelheid zodat een wijziging // van de richting meegenomen wordt. berekendeX = Locatie.X + (SnelheidObject.X); berekendeY = Locatie.Y + (SnelheidObject.Y); return new Point(berekendeX, berekendeY); }

Opdracht 11.1: Klok toevoegen

T

Opdracht Interfaces

EP

Voeg de eigenschap klok van de interface IBewegendObject met een ‘getter’ en een ‘setter’ toe aan de klasse Dier.

UML-klassendiagram Dier

C

O

N

C

Het UML-klassendiagram voor Dier met interface ziet er nu als volgt uit:

Figuur 29 UML-implementatie interface IBewegendObject


Hoofdstuk 11 Interfaces

485

Wat heb je geleerd? Wat een interface binnen C is. Hoe je een interface gebruikt. Waarom je een interface gebruikt. Hoe een interface in een UML-klassendiagram wordt weergegeven.

C

O

N

C

EP

T

– – – –


486

Hoofdstuk 12 Delegates

Voorafje

T

Een delegate is een lastig onderdeel van C om onder de knie te krijgen. Daarom vallen de voorbeelden uit dit hoofdstuk buiten de simulatie en ga je een apart programmaatje maken: een uitstapje. In de volgende hoofdstukken wordt wel gebruikgemaakt van de kennis die je in dit hoofdstuk hebt verkregen.

EP

Beschrijving

C

Een interface, zo hebben we gezien, is een sjabloon waarin vastgelegd wordt welke eigenschappen en methoden een klasse moet implementeren en waardoor de programmeur niet van tevoren een klasse hoeft te kennen om ermee te kunnen werken. De enige vraag is of de desbetreffende interface geïmplementeerd is. Bij methoden bestaat ook zo’n systeem en dat noemen we een delegate.

C

O

N

In een methode kun je één of meer parameters van verschillende datatypes gebruiken, maar kun je ook een functie gebruiken als parameter? Kun je ook een methode maken waarbij je niet als parameter een variabele van het type string of bool meegeeft maar een paramater van het type methode of functie? Ja, dat kan. Dit doe je met het type delegate.

UML

Binnen UML geef je een delegate als volgt aan:

Figuur 30 UML-delegate


Hoofdstuk 12 Delegates

487

Aanmaken delegate Een delegate is een reference-type en is een verwijzing naar een methode die ergens anders beschreven is. Een delegate maak je aan door het delegate-keyword te gebruiken en een handtekening van een methode aan te maken. Alleen de handtekening, niet de body. Een delegate is immers een verwijzing naar een methode die ergens anders gedefinieerd is! Een delegate maak je net zo aan als een methode, alleen wordt het woord delegate toegevoegd aan de handtekening. Iedere delegate is opgebouwd uit de volgende elementen: delegate

T

public, private, protected string, int, void, enzovoort delegateNaam

EP

– <Toegangsniveau> – Reserved word – <return type> – De naam van de delegate – Tussen haakjes de parameterlijst

(parameterlijst)

Op de volgende regel zie je een voorbeeld van de declaratie van een delegate:

C

public delegate void SchrijfDelegate(int value);

Voorbeeld 12.1: Delegate definiëren, aanmaken en gebruiken

N

In dit voorbeeld wordt een getal op drie manieren weergegeven: als getal met punten tussen de duizendtallen, als valuta en met voorloopnullen. En dat allemaal door dezelfde delegate, SchrijfDelegate te gebruiken.

C

O

Het doel van dit voorbeeld is om je te leren hoe je een delegate aanmaakt en gebruikt. In het daaropvolgende voorbeeld wordt een nuttige toepassing van een delegate uitgelegd.

Voorbereiding

1

Maak een nieuw project aan met de naam VoorbeeldDelegate.

2 Geef het formulier de naam FrmVoorbeelDelegate. 3 De afmetingen van het formulier worden 259; 275. 4 Stel de achtergrondkleur in op Color.Moccasin. 5 Stel MaximizeBox en MinimizeBox in op false. 6 Plaats de volgende controls op het formulier:


488

Deel 4: Objectoriëntatie

Type

Naam

Eigenschap

Waarde

Panel

pnlDelegateAanmaken

BackColor

BlanchedAlmond

Location

15;8

Size

211;105

Op het Panel pnlDelegateAanmaken plaats je de volgende controls: Button

TextBox

btnSchrijfwijzes

txbUitvoerGetallen

BackColor

MediumAquamarine

Location

6;40

Text

Schrijfwijzes

Location

87;4

Multiline

True

Size

121;98

T

Tabel 8 Controls VoorbeeldDelegate 1

N

C

EP

7 Als het goed is ziet je formulier er nu als volgt uit:

O

Schermafbeelding 16 FrmVoorbeeldDelegate 1

Definiëren delegate

Ga naar het codevenster.

C

1

2 Voeg de volgende regel toe tussen de namespace VoorbeeldDelegate en de class FrmVoorbeeldDelegate: public delegate void SchrijfDelegate(int value);

Hiermee is een delegate gedefinieerd die je overal binnen dit project kunt gebruiken.

Aanmaken methoden die voldoen aan de delegate SchrijfDelegate 1

Er zijn drie methoden nodig, voor iedere gewenste schrijfwijze één die voldoet aan de handtekening van de delegate met de naam SchrijfDelegate. Voeg de methode SchrijfGetal toe aan de class FrmVoorbeeldDelegate:


Hoofdstuk 12 Delegates

489

public void SchrijfGetal(int getal) { txbUitvoerGetallen.Text = txbUitvoerGetallen.Text + String.Format(“Getal: {0,-12:N0}”, getal) + “\r\n”; }

2 Voeg de methode SchrijfGeld toe aan de class FrmVoorbeeldDelegate: public void SchrijfGeld(int geld) { txbUitvoerGetallen.Text = txbUitvoerGetallen.Text + String.Format(“Geld: {0:C}”, geld) + “\r\n”; }

3 Voeg de methode SchrijfVoorloopnullen toe aan de class FrmVoorbeeldDelegate:

EP

T

public void SchrijfVoorloopnullen(int getal) { txbUitvoerGetallen.Text = txbUitvoerGetallen.Text + String.Format(“Voorloopnullen: {0:D6}”, getal) + “\r\n”; }

C

De drie methoden hebben allemaal dezelfde handtekening, maar zoals je ziet kunnen de namen van de parameters binnen de parameterlijst wel verschillen, zolang de types maar gelijk zijn.

Gebruik van de delegate

Ga naar het designvenster en dubbelklik op de Button btnSchrijfwijzes.

N

1

2 Voeg de volgende regels toe aan het btnSchrijfwijzes_Click event:

O

// De delegate schrijfDelegate verwijst naar de methode SchrijfGetal SchrijfDelegate schrijfDelegate = SchrijfGetal;

C

Zoals je ziet gebruik je alleen de naam van de methode en geen haakjes. Op deze manier verwijst schrijfDelegate naar SchrijfGetal, schrijfDelegate zal dan ook precies zo werken als SchrijfGetal. Nu de delegate schrijfDelegate gekoppeld is aan een methode kunnen we de

delegate net zo gebruiken als een methode. Voeg de volgende regels toe om de delegate te gebruiken: schrijfDelegate(10000); schrijfDelegate(200);

3 Test je programma. Als het goed is krijg je twee getallen te zien in de TextBox txbUitvoerGetallen:


490

Deel 4: Objectoriëntatie

Schermafbeelding 17 Uitkomst delegate voorbeeld 1a

T

4 Vervolgens koppelen we de delegate schrijfDelegate aan de methode schrijfGeld door de volgende regel toe te voegen:

EP

// De delegate schrijfDelegate verwijst naar de methode SchrijfGeld schrijfDelegate = SchrijfGeld;

5 Als we nu exact dezelfde twee regels nog een keer uitvoeren zul je zien dat nu de methode SchrijfGeld wordt uitgevoerd. Voeg de volgende regels toe: schrijfDelegate(10000); schrijfDelegate(200);

C

O

N

C

6 Test je programma nogmaals. Als het goed is krijg je het volgende te zien:

Schermafbeelding 18 Uitkomst voorbeeld delegate 1b

Doordat de delegate gekoppeld is aan een andere methode krijg je nu ook een andere uitkomst.

Invoke Je hebt de delegate schrijfDelegate uitgevoerd met de regel schrijfDelegate(10000); Je kunt de delegate ook uitvoeren door de methode Invoke() aan te roepen van de delegate. Voeg de volgende regels toe aan de methode btnSchrijfwijzes_Click():


Hoofdstuk 12 Delegates

491

// De delegate schrijfDelegate verwijst naar de // methode SchrijfVoorloopnullen schrijfDelegate = SchrijfVoorloopnullen; schrijfDelegate.Invoke(10000); schrijfDelegate.Invoke(200);

Invoke is het Engelse woord voor aanroepen. Deze term kom je vaak tegen in allerlei documentatie. Deze regels doen precies hetzelfde als de regels: schrijfDelegate(10000); schrijfDelegate(200);

Test het programma. Als je het goed hebt gedaan ziet het programma er als volgt uit:

C

EP

T

1

N

Schermafbeelding 19 Uitkomst voorbeeld delegate 1c

Waarom delegates?

O

Zoals je gezien hebt in de voorbeelden is het aardig dat je hetzelfde commando kunt gebruiken om verschillende methoden aan te roepen. Niet echt een voorbeeld van helder en overzichtelijk programmeren.

C

Toch zijn er twee gebieden waar delegates onmisbaar zijn: 1 Het toepassen van events. Dit wordt uitgelegd in hoofdstuk 13. 2 Het toepassen van een callback-functie. Dit wordt hieronder uitgelegd.

Callback-functies Stel je hebt twee klassen. De klasse frmVoorbeeldDelegate en de klasse Berekeningen. In de klasse Berekeningen is een methode ondergebracht die behoorlijk lang duurt met de naam langeBerekening. Als je deze methode opstart blokkeert je scherm en krijg je geen enkele informatie. Als je probeert iets vanuit de klasse Berekenen naar het scherm, dus de klasse frmVoorbeeldDelegate, weg te schrijven dan lukt dat niet. Je hebt geen instantie van


492

Deel 4: Objectoriëntatie

het scherm binnen de klasse Berekeningen dus het gaat gewoon niet. De pc is bezig met de methode langeBerekening en dat is het. Met behulp van een delegate kun je echter wél de andere klasse bereiken.

Hoe werkt het? Uiteraard definieer je weer een delegate binnen de namespace VoorbeeldDelegate. Binnen de klasse frmVoorbeeldDelegate definieer je een methode die voldoet aan de handtekening van de delegate.

T

Binnen de parameterlijst van de methode langeBerekening neem je een parameter op van het type delegate. Je maakt op dat moment een verwijzing naar een methode die in een andere klasse thuishoort.

EP

Als je vervolgens de delegate aanroept wordt de methode uit de andere klasse uitgevoerd en kun je bijvoorbeeld tussenresultaten op een scherm laten verschijnen. Deze procedure noemen we een callback-functie. En dit is exact wat we gaan doen in het volgende voorbeeld.

Voorbereiding

Breid het formulier frmVoorbeeldDelegate uit met de volgende controls: Type

Naam

Eigenschap

Waarde

pnlDelegateToepassing

BackColor

BlanchedAlmond

Location

18;130

Size

208;94

O

Panel

N

1

C

Voorbeeld 12.2: Callback delegate

C

Op het Panel pnlDelegateToepassing plaats je de volgende controls: Button

Label

btnTerugMelding

lblTerugMelding

lblTussenresultaat

BackColor

MediumAquamarine

Location

3;3

Size

75;49

Text

Langdurige berekening

Location

3;55

Text

Percentage:

Location

3;74

Text

Tussenresultaat:

Tabel 9 Controls voorbeeld delegate 2

2 Het formulier ziet er nu als volgt uit:


Hoofdstuk 12 Delegates

493

Schermafbeelding 20 Voorbeeld delegate 2

Definieer een nieuwe delegate binnen de namespace FrmVoorbeeldDelegate en buiten de bestaande klassen door de volgende regels (bijvoorbeeld boven de klasse FrmVoorbeeldDelegate) toe te voegen:

EP

1

T

Definiëren van de delegate

C

// // Delegate met de handtekening van de callback-functie (terugmelding): // public delegate void TerugMelding(string boodschap, long tussenresultaat);

N

Deze delegate kent twee parameters, een boodschap van het type string en een tussenresultaat van het type long.

Aanmaken van een methode TerugMelding Maak binnen de klasse frmVoorbeeldDelegate een methode aan met de naam Melding die voldoet aan de handtekening van de delegate TerugMelding. Dus return type void en een parameterlijst die bestaat uit een string-variabele en een long variabele. Voeg de volgende regels toe:

C

O

1

// Dit is de methode die meegegeven wordt als variabele. Deze methode moet dus // dezelfde handtekening hebben als de delegate. // Omdat deze methode behoort tot de klasse FrmVoorbeeldDelegate kan binnen deze // methode gebruikgemaakt worden van alle variabelen binnen de klasse // FrmVoorbeeldDelegate. void Melding(string meldingTekst, long totaal) { }

2 De bedoeling is dat deze methode twee Labels vult, het ene met het percentage dat al gereed is na de berekening, het andere met een tussenresultaat. Voeg de volgende regels toe aan de methode Melding: lblTerugMelding.text = meldingTekst; lblTussenResultaat.Text = “Tussenresultaat:” + totaal.ToString(); // Iedere keer voor het beëindigen van de methode moet het scherm ververst // worden, anders zie je nog steeds geen resultaat: this.Refresh();


494

Deel 4: Objectoriëntatie

Aanmaken van de klasse Berekeningen 1

Maak een nieuwe klasse aan met de naam Berekeningen. Dat mag binnen hetzelfde bestand, of in een nieuwe klasse als .cs-file. // // Klasse met daarin een methode die een langdurige berekening uitvoert. // public class Berekeningen { }

2 Maak binnen deze klasse een publieke methode aan met de naam LangeBerekening:

T

public static long LangeBerekening(TerugMelding terugMelding) { }

EP

Het wordt een static methode omdat er dan niet eerst een instantie van Berekeningen gemaakt hoeft te worden. Dit is echter geen voorwaarde om een delegate te kunnen gebruiken! 3 Vul de body van de methode met een loop in een loop. De reden hiervoor is dat het programma op deze manier lekker lang bezig is.

C

long totaal = 0; int grootGetal = 10000000; int eenProcent = grootGetal / 100; int percentage = 1;

C

O

N

for (int i = 0; i <= grootGetal; i++) { if (i % eenProcent == 0 ) { // De extra loop dient alleen om vooral veel tijd te gebruiken: // elke stap duurt hierdoor de hoeveelheid grootGetal langer. for (int index = 0; index < grootGetal; index++) { totaal = totaal + index; } // Hier wordt de delegate als het ware gevuld: terugMelding(percentage.ToString() + “ procent bereikt”, totaal); percentage++;

} } return totaal;

Het geaccentueerde gedeelte is de plek waar via de delegate de methode Melding() van de klasse frmVoorbeeldDelegate wordt aangeroepen. Dit gebeurt dus via de delegate-terugmelding van het type TerugMelding.


Hoofdstuk 12 Delegates

495

Afmaken formulier 1

Voeg tenslotte de volgende code toe aan het btnTerugMelding_Click event om de berekening uit te voeren: private void btnTerugMelding_Click(object sender, EventArgs e) { lblTussenresultaat.Text = “Totaal: “ + Berekeningen.langeberekening(Melding); }

Opdrachten Delegate

T

Opdracht 12.1: De ProgressBar

EP

Voeg aan het voorbeeldproject VoorbeeldDelegate een ProgessBar toe waarin de voortgang van de berekening wordt aangegeven. Je moet daarvoor de delegate TerugMelding uitbreiden met een variabele van het type int om het percentage in aan te kunnen geven.

C

Je moet de methode Melding() dan eveneens uitbreiden met een variabele van het type int en je moet ervoor zorgen dat binnen de methode Melding() de waarde van de ProgressBar gelijk wordt aan het percentage.

N

Opdracht 12.2: Spreektijd

C

O

Maak een programma waarbij je de hoeveelheid spreektijd van een politicus kunt instellen. Bijvoorbeeld 10 minuten. Er moet een startknop komen waarmee je aangeeft dat de politicus start met spreken. Tegelijkertijd moet er een ProgressBar getoond worden die aangeeft hoeveel procent van de spreektijd verbruikt is. Maak hierbij gebruik van een delegate.

Opdracht 12.3: Spreektijd met eindtijd waarschuwing * Breid het programma van Opdracht 12.2 Spreektijd uit met een voorziening waarbij, zodra 80 van de spreektijd verstreken is, de achtergrond van het scherm oranje wordt en zodra 90 van de spreektijd bereikt is de achtergrond van het scherm rood wordt.

Multithreading Als je Opdracht 12.1 gemaakt hebt, dan zie je bij de langdurige berekening een ProgessBar in beeld komen die keurig netjes de voortgang toont. Helaas is en blijft de rest van je programma geblokkeerd. Soms vind je dat prettig maar vaak is dit alleen maar irritant.


496

Deel 4: Objectoriëntatie

Dit komt omdat je programma in principe alle regels code achter elkaar uitvoert en pas na afloop weer terugkeert naar het scherm en daar gebruikersinvoer afwacht. Zo’n rij opdrachten die door de processor wordt uitgevoerd, heet een thread. Nu kun jij, als programmeur, van meerdere threads gebruikmaken. Hierdoor kan een gebruiker de computer als het ware meerdere taken tegelijkertijd uit laten voeren. Als threads gebruikmaken van elkaars resultaten kan dit wel onvoorspelbare resultaten opleveren. Het is dus belangrijk dat je precies weet wat je wilt! Het gebruikmaken van meerdere threads noemen we multithreading. Helaas valt multithreading buiten het bestek van dit boek.

EP

Wat een delegate is. Hoe je een delegate gebruikt. Hoe een delegate in een UML-klassendiagram wordt weergegeven. Wat invoke inhoudt. Wat een callback-functie inhoudt. Hoe je een callback-functie maakt. Het begrip multithreading.

C

O

N

C

– – – – – – –

T

Wat heb je geleerd?


497

Hoofdstuk 13 Events

Beschrijving

T

Binnen het programmeren kunnen we niet meer zonder events (gebeurtenissen). Als iets verandert binnen het programma dan spreken we van een event. Als je ergens op klikt, een scherm resizet of als er in een database een trigger afgaat, het zijn allemaal events.

C

EP

Een event (gebeurtenis) zoals we dat hier programmeren bestaat uit twee gedeeltes. – Het gedeelte waar het event afgaat, getriggerd wordt. Dit gedeelte noemen we de uitgever (publisher) of de verzender. Er wordt namelijk een bericht gestuurd naar een ander deel, dat het event heeft plaatsgevonden. – Het gedeelte dat iets doet met de wetenschap dat het event (de gebeurtenis) heeft plaatsgevonden. Dit gedeelte noemen we ook wel de abonnee (subscriber) van het bericht of de ontvanger van het bericht. Deze gedeeltes zijn strikt gescheiden. In de code is geen directe verwijzing opgenomen van de publisher naar de subscriber of van de subscriber naar de publisher.

O

N

Hierdoor kun je een klasse maken met een aantal events zoals bijvoorbeeld de klasse Button met het OnClick event. Het OnClick event vertoont wat standaardgedrag, het uiterlijk van de Button wijzigt als je erop klikt, maar verder gebeurt er helemaal niets.

C

De gebruiker van de klasse Button kan een instantie van een Button in een willekeurig programma gebruiken en in de verdere afhandeling van het OnClick event in de eigen code opnemen. Als je dit niet zo zou doen, moet je iedere keer de klasse Button aanpassen. Stel je eens voor: een klasse Button met daarin alle code van alle Buttons die er ooit gebruikt worden…

Publishers Een event ontstaat binnen een klasse als een vooraf bepaalde conditie wordt bereikt. Een event is eigenlijk niets meer en niets minder dan een bericht. Waarbij de publisher/provider (uitgever op zijn Nederlands) niets anders doet dan deze berichten versturen naar de abonnees.


498

Deel 4: Objectoriëntatie

Subscribers Een andere klasse, die niets hoeft te weten van hoe de publisher werkt of hoe het komt dat een event gepubliceerd wordt, reageert op de gebeurtenis door een stuk code uit te voeren. Doordat meerdere klassen zich kunnen abonneren op een event kan het ook zijn dat meerdere klassen reageren op hetzelfde event.

Gestandaardiseerd berichtenverkeer Events maken gebruik van gestandaardiseerd berichtenverkeer. Er wordt gebruikgemaakt van standaard-delegates. De subscriber werkt met een standaardmethode waarvan vooraf alleen de handtekening bekend is. De standaard-delegate die je kunt gebruiken voor events is de EventHandler.

T

De declaratie van de delegate EventHandler ziet er als volgt uit:

EP

public delegate void EventHandler(object sender,EventArgs e);

Je ziet dat de EventHandler een delegate is. Dit betekent eenrichtingverkeer. De methode is van het type void, er wordt niets teruggegeven aan de zender. En dat moet ook niet, omdat de zendende klasse niets af hoeft te weten van de ontvangende klasse.

C

De methode kent twee parameters: sender van het type object en e van het type EventArgs.

N

De variabele sender bevat informatie over het object waar de gebeurtenis plaatsvond. Door gebruik te maken van typecast kun je alle eigenschappen en methoden van dit object gebruiken.

C

O

De variabele e kan specifieke informatie bevatten die betrekking heeft op het event. Hiervoor moet je dan wel zelf een klasse aanmaken die erft van de standaardklasse EventArgs. Dit klinkt ingewikkelder dan het is. Je maakt gewoon een klasse aan met daarin de eigenschappen die je wilt doorgeven/gebruiken met een constructor waarin je al deze eigenschappen moet invoeren en die erft van EventArgs.

UML Binnen een UML-klassendiagram wordt een event aangegeven door voor de eigenschap die betrekking heeft op het event het woord event aan te geven tussen guillemets (Franse aanhalingstekens, zie figuur 31).


Hoofdstuk 13 Events

499

Figuur 31 UML-voorbeeldnotatie event

Het teken  staat voor een protected methode. Deze methoden zijn zichtbaar binnen de gebruikte namespace maar niet daarbuiten.

Voorbeeld 13.1: Timer event

EP

T

Binnen NatSimII wordt gebruikgemaakt van twee Timers. Een om de simulatie te laten bewegen, en een die ervoor zorgt dat als de maximale leeftijd van een levend object bereikt is dit object verwijderd wordt uit de simulatie. We beginnen met de Timer voor de levensduur.

Voorbereiding

C

De Timer klasse maakt gebruik van het event Tick. Iedere keer dat er een Tick voorbij is kun je een zelfbedachte methode starten. Die methode moet dan wel aan de eisen van de standaard-delegate voor een event voldoen.

N

Het grootste gedeelte is al gebeurd. Er is al een eigenschap Verouder met een daaraan gekoppelde private variabele _Verouder, beide van het type Timer.

O

Het Timer-event

C

Ook zijn er binnen de methode InitClass twee regels opgenomen die de Timer initialiseren. Achter deze regels voegen we de code toe om met de Timer te kunnen werken.

1

Zoek binnen de klasse Leven in de methode InitClass de volgende twee regels op:

_verouder = new Timer(); _verouder.Interval =_aantalTicksPerSeconde * VerhoudingTicksJaren;

2 Voeg de volgende regel toe na deze twee regels om de Timer te starten: _verouder.Start();

3 We spreken af dat in de simulatie iedere keer als er een Tick verlopen is alles wat erft van de klasse Leven één jaar ouder wordt. Hiervoor maken we gebruik van het Timer event Tick. Voeg de volgende code toe: _verouder.Tick +=


500

Deel 4: Objectoriëntatie

Je ziet, zodra je dit getypt hebt, de volgende boodschap op het scherm verschijnen:

Schermafbeelding 21 _verouder_Tick

Bij het werken met delegates heb je geleerd dat een methode moet voldoen aan de handtekening van de delegate als je die methode wilt gebruiken samen met de delegate.

T

Het event Tick is niets anders dan de uitgeverij die een abonnement verstrekt op de melding dat er weer een Tick (jaar) voorbij is. Met += kun je je abonneren op die boodschap. Maar dan moet je dat wel doen met een methode die voldoet aan de delegate die bij Tick hoort.

EP

Door op Tab te klikken wordt de standaardnaam voor deze methode ingevoegd. 4 Klik op Tab (werkt alleen als de boodschap in beeld is). De volgende boodschap verschijnt:

C

Schermafbeelding 22 Generate handler _verouder_Tick

N

5 Inderdaad, je hebt nu wel een naam van een methode die aan de standaard Microsoft Naming Convention voor delegates en events voldoet, maar je hebt de methode zelf nog niet.

C

O

Door nogmaals op Tab te klikken (of, afhankelijk van je versie van Visual Studio, door op Apply te klikken) wordt een lege methode aangemaakt die aan alle eisen voldoet. Klik op Tab (ook hier werkt het alleen als de boodschap in beeld is) en de volgende code wordt voor je aangemaakt:

private void _verouder_Tick(object sender, EventArgs e) { throw new NotImplementedException(); }

6 De regel throw new NotImplementedException(); genereert een Exception; dus vervang je deze regel door de volgende regels: // Een jaar bestaat uit x ticks. // Na een aantal jaren is de ‘eindeleeftijd’ bereikt en // houdt het object op te bestaan. if (Leeftijd < Levensduur) { Leeftijd++; }


Hoofdstuk 13 Events

501

7 Elk object dat erft van Leven wordt nu bij iedere Tick een jaartje ouder. Als de maximale levensduur bereikt is moet het object verwijderd worden. Dat regelen we met de methode Sterf(). Voeg de regels toe na het if-statement om deze methode te activeren: else { _verouder.Stop(); Sterf(); }

8 Om te laten zien dat er iets gebeurt laten we even een boodschap verschijnen vanuit de methode Sterf(). Voeg de volgende regel toe aan de methode Sterf():

T

MessageBox.Show(this.NederlandseNaam + “ wordt verwijderd.”);

EP

Doordat de eigenschap NederlandseNaam voorkomt in de klasse Leven kunnen we de boodschap op deze wijze laten zien. Ongeacht of het een Konijn, Koe of wat dan ook is, zolang het maar erft van de klasse Leven.

Test het programma

C

Als je een Konijn of wat Gras aanmaakt zul je zien dat deze vanzelf weer van het scherm verdwijnen na verloop van tijd. Verwijder na de test de regel die de boodschap over verwijderen op het scherm zet.

C

O

N

Het aangepaste UML-diagram van Leven ziet er nu als volgt uit:

Figuur 32 UML Leven met methode voor delegate

Voorbeeld 13.2: Beweging De Dieren blijven tot op dit moment statisch op het scherm staan. Om beweging te simuleren wis je de oude tekening en laat je het programma een klein stukje verder het object opnieuw tekenen. Dit gaan we nu implementeren.


502

Deel 4: Objectoriëntatie

Tot nu toe tekenden we een object en verloren we in wezen de controle over het object: aan het einde van de methode wordt het object feitelijk verwijderd. Alleen als er nog een Timer actief is bestaat het object namelijk nog ergens in het geheugen.

C

EP

T

Als je ergens klikt gaan we nu niet alleen het object aanmaken maar het object ook toevoegen aan een List van het type Leven. Hiervoor maken we een aparte klasse aan die erft van het type List<Leven>. Op deze manier kunnen we bestaande objecten verplaatsen en informatie over ieder object laten zien. Kortom: het wordt een stuk toegankelijker. Het UML-klassendiagram van deze klasse, die we Natuur gaan noemen, ziet er als volgt uit:

Figuur 33 UML-klasse Natuur die erft van List<String>

Maak een nieuwe public klasse aan met de naam Natuur en laat deze erven van List<Leven>.

O

1

N

De klasse Natuur

2 Voeg in het using-gedeelte de volgende twee bibliotheken toe:

C

using System.Windows.Forms; using System.Drawing;

3 Als het goed is krijg je het volgende resultaat: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing; namespace NatSimII { public class Natuur : List<Leven> { } }


Hoofdstuk 13 Events

503

4 Voeg tussen de accolade na namespace en voor de class-definitie de volgende enum toe volgens het onderstaande UML-klassendiagram:

EP

public enum SoortLeven { Gras, Vingerhoedskruid, Venijnboom, Koe, Konijn, Beer, Lynx }

T

Figuur 34 UML-klassendiagram SoortLeven

C

5 Voeg een constructor toe aan de klasse Natuur door de volgende regels toe te voegen:

O

}

N

// // Constructors // public Natuur() : base() {

C

De toevoeging : base() betekent dat eerst de constructor van List<Leven> wordt gestart voordat de code binnen de constructor van Natuur wordt uitgevoerd.

Het ritme van het leven Om objecten te kunnen laten bewegen moeten ze op gezette tijd een stukje verplaatst worden. Dit gaan we doen met behulp van een Timer.

1

Voeg aan de klasse Natuur de private variabele _LevensKlok toe van het type Timer door de volgende regels toe te voegen: // // Private variabelen // Timer _levensKlok = new Timer();


504

Deel 4: Objectoriëntatie

2 Stel de Timer in en start deze binnen de constructor van de klasse Natuur door de volgende regels toe te voegen: _levensKlok.Interval = 10; _levensKlok.Start();

3 Voeg het Tick event toe door de volgende code toe te voegen gevolgd door tweemaal Tab: _levensKlok.Tick +=

4 De volgende code ontstaat binnen de constructor: _levensKlok.Tick += levensKlok_Tick;

En de volgende methode wordt automatisch aangemaakt:

EP

T

void _levensKlok_Tick(object sender, EventArgs e) { throw new NotImplementedException(); }

C

5 De regel throw new NotImplementedException(); vervangen we door de volgende code die ervoor zorgt dat de Dieren gaan bewegen. Eerst wordt er gekeken of het Leven in de lijst van het type Dier is. Vervolgens wordt het Dier verplaatst met de snelheid die bij dat Dier hoort.

O

N

foreach (Leven leven in this) { if (leven.IsDier) { ((Dier)leven).Beweeg(); } }

6 De methode Beweeg() moet nog toegevoegd worden aan de klasse Dier:

C

// Dieren bewegen, dit maken we mogelijk met de methode Beweeg(): public void Beweeg() { Verwijder(); Locatie = Stap(); Teken(); }

Toevoegen van een levensvorm 1

We gaan weer terug naar de klasse Natuur. De standaardmethode Add() van een List biedt te weinig functionaliteit. De Add()-methode van List mogen we niet overschrijven (override). Je mag wel een nieuwe maken. En dat gaan we nu doen door de volgende regels toe te voegen:


Hoofdstuk 13 Events

505

// // Publieke methoden // public new void Add(Leven leven) { // In het geval het object een dier is moet de snelheid meegegeven worden: if (leven.IsDier == true) { Random random = new Random(); Snelheid snelheid = new Snelheid(random.Next(-4, 4), random.Next(-4, 4)); ((Dier)leven).SnelheidObject = snelheid; } // Toevoegen van het leven aan de lijst (List). Hiervoor gebruiken we de // methode die bij de base-klasse hoort waarvan geërfd is: base.Add(leven);

T

}

1

EP

Inregelen van het formulier

Ga naar het codevenster van het Form FrmNatSimII.

2 Met de volgende variabelen bepaal je welk soort Leven opgenomen moet worden in de List.

C

Maak twee globale variabelen aan waarin je de selectie voor Dier of Plant kunt vastleggen door de volgende regels toe te voegen, bovenaan, vóór de initialisatie:

N

SoortLeven soortPlant = SoortLeven.Gras; SoortLeven soortDier = SoortLeven.Konijn;

O

3 Maak een globale variabele aan waarin je alle levensvormen onder kunt brengen door de volgende regel toe te voegen: Natuur natuur = new Natuur();

C

Hierdoor wordt automatisch de Timer van Natuur gestart.

Vullen van de lijst

Als ergens op de PictureBox pbWereld geklikt wordt moet een nieuw object toegevoegd worden aan de lijst. 1

Ga naar de methode die hoort bij het MouseClick event van de PictureBox pbWereld (pbWereld_MouseClick).

2 Verwijder alle code. 3 Eerst wordt bepaald welk soort leven er toegevoegd moet worden. Voeg de volgende regels toe:


506

Deel 4: Objectoriëntatie

SoortLeven soortLeven = SoortLeven.Gras; if (e.Button== MouseButtons.Left) { soortLeven = soortDier; } else if (e.Button == MouseButtons.Right) { soortLeven = soortPlant; }

4 De toe te voegen soort is bepaald. Nu kan deze toegevoegd worden aan de lijst natuur. Voeg de volgende code toe:

C

O

N

C

EP

T

switch (soortLeven) { case SoortLeven.Gras: natuur.Add(new Gras(e.Location)); break; case SoortLeven.Vingerhoedskruid: natuur.Add(new Vingerhoedskruid(e.Location)); break; case SoortLeven.Venijnboom: natuur.Add(new Venijnboom(e.Location)); break; case SoortLeven.Koe: natuur.Add(new Koe(e.Location)); break; case SoortLeven.Konijn: natuur.Add(new Konijn(e.Location)); break; case SoortLeven.Beer: // Zodra er een klasse Beer gemaakt is moet onderstaande regel geactiveerd // worden: // natuur.Add(new Beer(e.Location); break; case SoortLeven.Lynx: // Zodra er een klasse Lynx gemaakt is moet onderstaande regel geactiveerd // worden: // natuur.Add(new Lynx(e.Location); break; default: break; }

Tot slot moet het object nog getekend worden door de volgende regels toe te voegen: // Het als laatste aan de lijst natuur toegevoegde element wordt getekend: natuur.Last().Teken(papier);

We zullen later zien dat ook dit met behulp van een event uitgevoerd kan worden.


Hoofdstuk 13 Events

507

Instellen RadioButtons De lijst is klaar maar nu moeten de RadioButtons nog zo ingesteld worden dat de juiste levenssoorten worden geselecteerd: 1

Voeg de volgende code toe aan de RadioButton rdbKonijn: private void rdbKonijn_CheckedChanged(object sender, EventArgs e) { if (rdbKonijn.Checked) soortDier = SoortLeven.Konijn; }

2 Voeg de volgende code toe aan de RadioButton rdbGras:

EP

T

private void rdbGras_CheckedChanged(object sender, EventArgs e) { if (rdbGras.Checked) soortPlant = SoortLeven.Gras; }

Opdrachten Completeren formulier Opdracht 13.1: Toevoegen code

C

Pas de overige RadioButtons zo aan dat iedere knop de juiste waarde toekent aan de variabelen soortDier en soortPlant.

N

Opdracht 13.2: Test het programma

C

O

Als het goed is blijven de Planten staan en bewegen de Dieren redelijk willekeurig door elkaar heen. Vervelend is wel dat de Dieren niet meer verdwijnen. Ze staan namelijk in de List natuur en zolang ze daar niet verdwijnen worden ze steeds weer opnieuw getekend. Ook vliegen de Dieren dwars door elkaar heen. Deze zaken gaan we aanpakken binnen het volgende voorbeeld.

Voorbeeld 13.3: Het eerste zelfbouwevent Op dit moment is het binnen de simulatie zo dat als een Dier sterft, het Dier in eerste instantie wel gewist wordt van het scherm, maar steeds weer opnieuw getekend wordt. Het Dier bestaat immers nog steeds binnen de List natuur.

Het lastige is dat een object niet zomaar iets kan wijzigen binnen een andere klasse zonder daar zelf eerst een instantie van te maken. Nog vervelender is dat zodra een instantie van bijvoorbeeld het formulier binnen de klasse Dier aangemaakt wordt dit weer een nieuwe instantie is en deze instantie dus niets te maken heeft met de instantie waarbinnen de List natuur actief is en waaruit het object verwijderd moet worden…


508

Deel 4: Objectoriëntatie

Dit klinkt ingewikkeld, dat is het ook en het werkt bovendien niet. Om voor elkaar te krijgen dat, als een object uit de List natuur verwijderd is, de bijbehorende tekening gewist wordt van het formulier, hebben we een event en een delegate nodig. Binnen de klasse Leven maken we een event dat afgaat zodra het Leven beëindigd wordt.

C

EP

T

Van de List natuur maken we een klasse die zich abonneert op het event Einde van Leven. Op deze manier kunnen we een gestorven Dier ook verwijderen uit de List natuur en zo voorkomen we dat het object opnieuw getekend wordt. Het nieuwe UML-klassendiagram voor de klasse Leven ziet er als volgt uit:

N

Figuur 35 UML-klassendiagram Leven met implementatie event

1

O

Aanmaken event Einde Open de klasse Leven.

C

2 Voeg het event Einde toe aan de klasse Leven door de volgende regel toe te voegen: public event EventHandler<EventArgs> Einde;

Het event Einde maakt gebruik van de standaard-delegate EventHandler.

Aanmaken methode OnEinde( ) 1

Vervolgens maken we een protected (alleen zichtbaar binnen de namespace) en virtual (kan overschreven worden door een klasse die deze methode erft) methode aan die checkt of er een klasse is die zich geabonneerd heeft op het event en die vervolgens het event activeert. Voeg de volgende regels toe aan de klasse Leven:


Hoofdstuk 13 Events

509

protected virtual void OnEinde() { // Check of er abonnees zijn op het event Einde. if (Einde != null) { // Activeren van het event Einde. Einde(this, EventArgs.Empty); } }

2 Binnen de methode Sterf(), die aangeroepen wordt zodra de maximale levensduur bereikt is, of zodra er iets anders voorvalt waardoor het object ophoudt te bestaan, wordt de methode OnEinde() aangeroepen. Pas de methode Sterf() zo aan dat deze er als volgt komt uit te zien:

EP

T

public void Sterf() { Verwijder(); OnEinde(); }

Hiermee is het event Einde klaar voor gebruik.

C

Gebruik van het event Einde binnen de klasse Natuur

C

O

N

De klasse Natuur wordt uitgebreid volgens het onderstaande UML-klassendiagram:

Figuur 36 UML-klassendiagram Natuur met implementatie event

1

Open de klasse Natuur.

2 Ga naar de methode Add. 3 Ga naar de regel // In het geval het object een dier is moet de snelheid meegegeven worden

4 Voeg hiervoor de volgende regels toe: // Abonneren op het bericht dat het object gestorven is. leven.Einde += leven_Einde;


510

Deel 4: Objectoriëntatie

Nu we de methode leven_Einde() willen toevoegen aan het event Einde moet deze methode nog wel gemaakt worden. Deze methode moet voldoen aan de handtekening van de delegate EventHandler. Voeg de volgende methode toe aan de klasse Natuur: private void leven_Einde(object sender, EventArgs e) { // Als een leven beëindigd is wordt het uit de lijst // natuur verwijderd: this.Remove((Leven)sender); ; }

Test het programma

EP

T

Zoals je al gemerkt hebt, kent de standaard EventHandler een parameterlijst met twee variabelen: object sender en EventArgs e. De variabele sender wordt gebruikt om het hele object dat het event activeert mee te geven. De variabele e van het type EventArgs wordt gebruikt om specifieke informatie over het event mee te geven. Omdat het Leven dat beëindigd wordt het event leven_Einde activeert, bestaat het object met de naam sender uit alle informatie over het beëindigde Leven.

C

Je zult zien dat aangemaakte objecten nu na verloop van tijd ook weer zullen verdwijnen.

Voorbeeld 13.4: Event arguments

O

N

Op dit moment wordt er getekend door binnen de methode die volgt op het MouseClick event van pbWereld nieuwLeven de regel natuur.Last().Teken(papier); uit te voeren. Dit kan ook met een event. Verwijder deze regel dus nu uit de methode. We gaan het event toevoegen.

C

Er is wel een probleem. Vanuit de klasse FrmNatSimII wordt alleen de klasse Natuur gebruikt. Hierdoor kun je niet rechtstreeks gebruikmaken van events die geactiveerd worden door Leven. Al het Leven ontstaat en ‘leeft’ binnen de klasse Natuur. Daarom zullen we daar ook een event maken dat bijhoudt of er nieuw Leven ontstaat. Alleen hebben we dan niet de beschikking over informatie over dat Leven. Dat moet meegegeven worden. Hiervoor moeten we een klasse aanmaken die erft van de klasse EventArgs. Deze klasse heeft eigenschappen die gevuld worden via de constructor. In dit geval zal er een variabele doorgegeven worden van het type Leven. De naming convention is dat deze klasse de naam krijgt van het event met daarachter de tekst EventArgs. Het UML-klassendiagram van NieuwLevenEventArgs ziet er als volgt uit:


Hoofdstuk 13 Events

511

Figuur 37 UML-klassendiagram NieuwLevenEventArgs

1

Open de klasse Natuur.

2 Ga naar de een na laatste accolade.

T

Aanmaken van NieuwLevenEventArgs

Voeg de volgende regels toe:

EP

3 Buiten de klasse Natuur, maar binnen de namespace NatSimII maken we nu een nieuwe klasse aan.

O

N

C

// // De klassen die zorgen voor extra informatie bij een event NieuwLeven // public class NieuwLevenEventArgs : EventArgs { // Constructor public NieuwLevenEventArgs(Leven leven) { NieuwLeven = leven; } // Eigenschappen public Leven NieuwLeven { get; set; }

C

}

Aanpassen van de klasse Natuur

1

We maken een extra event aan in de klasse Natuur dat gebruikmaakt van de nieuwe klasse NieuwLevenEventArgs. Voeg de volgende regel toe aan de klasse Natuur: public event EventHandler<NieuwLevenEventArgs> NieuwLeven;

We zoeken in de methode Add de volgende regel op: base.Add(leven);

Nadat het Leven daadwerkelijk aan de klasse Natuur is toegevoegd activeren we het event NieuwLeven. Dit doen we door direct na deze regel de volgende regels toe te voegen:


512

Deel 4: Objectoriëntatie

// Het activeren van het event NieuwLeven als er geabonneerd wordt op // het event. if (NieuwLeven != null) { NieuwLeven(this, new NieuwLevenEventArgs(leven)); }

We moeten hier wel gebruikmaken van een variabele om het nieuwe Leven door te geven omdat this hier slaat op de List natuur.

EP

T

Het UML-diagram voor de klasse Natuur ziet er dan als volgt uit:

Figuur 38 UML-klassendiagram Natuur met event NieuwLeven

C

Aanpassen van het Form FrmNatSimII

N

Het Form FrmNatSimII moet zich abonneren op het event NieuwLeven. Voeg de volgende regels toe aan de constructor van FrmNatSimII:

O

// Abonneren op de diverse events van de lijst Natuur: natuur.NieuwLeven += natuur_NieuwLeven;

En natuurlijk de methode die uitgevoerd wordt als het event geactiveerd wordt. Voeg de volgende regels toe:

C

// De methoden die uitgevoerd worden in het geval er een event // van de lijst Natuur afgaat. Ontstaat een nieuw leven, dan // wordt dit getekend. void natuur_NieuwLeven(object sender, NieuwLevenEventArgs e) { e.NieuwLeven.Teken(papier); }

De variabele e kent één eigenschap met de naam NieuwLeven. Wil je meer informatie doorgeven dan moet je deze aanmaken binnen de klasse NieuwLevenEventArgs en vullen vanuit de klasse Natuur. Let op! De parameter sender kun je hier minder goed gebruiken om de gegevens van het te tekenen object op te vragen. Omdat het object dat het event activeert het object Natuur is moet je dan het laatste toegevoegde item gebruiken.


Hoofdstuk 13 Events

513

Test het programma Als het goed is is er niets veranderd. Het programma werkt nog net zoals tevoren. Wat ben je er dan mee opgeschoten? Alle logica is terechtgekomen in de klasse Natuur. Op deze manier is programmatuur makkelijker herbruikbaar en minder foutgevoelig.

Voorbeeld 13.5: Keten van events

T

Soms kan het voorkomen dat een klasse bestaat uit elementen van een andere klasse, zoals Natuur een lijst is van objecten van het type Leven. De klasse die gebruikmaakt van Natuur kan zich niet abonneren op de afzonderlijke elementen van Natuur zonder deze precies aan te roepen. Dat is lastig. Makkelijker is dat events van Leven opgevangen worden door een event binnen de klasse Natuur waarop de klasse die Natuur gebruikt zich kan abonneren. Dit gaan we nu doen.

EP

We gaan de functie maken waarmee, als je de muis over een object van het type Leven beweegt, er in het Label lblInformatie informatie over het object getoond wordt zoals de LatijnseNaam, de NederlandseNaam, de Locatie en voor Planten de Bloeiwijze.

C

Voorbereiding

N

We hebben een ToString()-functie nodig die een nette tekst met de gevraagde informatie oplevert. We beginnen met de ToString()-methode van Plant. Voeg de volgende regels toe aan Plant:

C

O

public override string ToString() { return “Latijnse naam: “ + LatijnseNaam + Environment.NewLine + Environment.NewLine + “Nederlandse naam: “ + NederlandseNaam + Environment.NewLine + Environment.NewLine + “Levensduur: “ + Levensduur + Environment.NewLine + Environment.NewLine + “Bloeiwijze: “ + BloeiwijzePlant + Environment.NewLine + Environment.NewLine + “Locatie: “ + Locatie.ToString(); }

Helaas levert deze code (na lang wachten) de volgende foutmelding op:


514

Deel 4: Objectoriëntatie

Schermafbeelding 23 Stack Overflow

EP

T

En nog vervelender is dat de foutmelding naar de verkeerde eigenschap wijst. De fout ontstaat namelijk bij de eigenschap NederlandseNaam. Deze wil de standaard ToString()-methode van het object aanroepen om uit de objectnaam de NederlandseNaam af te leiden, zoals je kunt zien in de volgende regel uit de klasse Leven: public string NederlandseNaam { get { return this.ToString().Split(‘.’).Last(); } }

De oplossing is om binnen de eigenschap van Leven de standaardmethode voor ToString() aan te roepen. Zoek de eigenschap NederlandseNaam op binnen de klasse Leven:

N

1

C

Maar in de nieuwe ToString()-methode wordt de eigenschap NederlandseNaam aangeroepen en zo hebben we een eindeloze loop gecreëerd.

O

public string NederlandseNaam { get { return this.ToString().Split(‘.’).Last(); } }

2 Vervang het woord this binnen deze regel door base. Je krijgt dan de volgende regel:

C

public string NederlandseNaam { get { return base.ToString().Split(‘.’).Last(); } }

Aanpassen van de klasse GrafischObject Binnen de klasse GrafischObject, waar alle andere klassen binnen Leven gebruik van maken, kun je het beste bepalen of een punt zich op, of juist niet op het object bevindt. Mocht je ooit iets anders gaan maken dat gebruikmaakt van GrafischObject dan kun je het event ook daar gebruiken. Het UML-klassendiagram van GrafischObject komt er als volgt uit te zien:


Hoofdstuk 13 Events

515

Open de klasse GrafischObject.

EP

1

T

Figuur 39 UML-klassendiagram GrafischObject

2 Maak een event aan met de naam OpObject door de volgende regel toe te voegen: public event EventHandler<EventArgs> OpObject;

C

3 Maak de bijbehorende methode OnOpObject aan door de volgende regels toe te voegen: protected virtual void OnOpObject(object sender) {

}

O

N

if (OpObject != null) { // Activeren van het event OnObject OpObject(this, EventArgs.Empty); }

C

4 Nu maken we ook nog een methode aan die bepaalt of een punt zich op een object (rechthoek) bevindt of niet. Voeg de volgende methode toe aan GrafischObject: public void IsOpObject(Point punt) { if (punt.X >= Tekengebied.A.X && punt.X <= Tekengebied.B.X && punt.Y >= Tekengebied.A.Y && punt.Y <= Tekengebied.D.Y) { OnOpObject(this); } }


516

Deel 4: Objectoriëntatie

Aanmaken ondersteunende EventArgs-klasse Vanuit Natuur maken we een event dat geactiveerd wordt zodra een punt zich op een onderliggend object van het type Leven bevindt. Aan dit event willen we behalve alle informatie over het object ook informatie over het tijdstip dat het object getroffen is meegeven. Het UML-diagram voor de klasse GetroffenEventArgs ziet er als volgt uit:

EP

T

1

Figuur 40 UML-klassendiagram GetroffenEventArgs

Voeg de volgende klasse toe na de klasse Natuur:

O

N

C

public class GetroffenEventArgs : EventArgs { // Constructor public GetroffenEventArgs(Leven leven) { Getroffen = leven; GeraaktOp = DateTime.Now; }

C

// Eigenschappen public Leven Getroffen { get; set; } public DateTime GeraaktOp { get; set; }

}

Implementeren van het event Getroffen binnen de klasse Natuur Het nieuwe UML-diagram voor de klasse Natuur ziet er als volgt uit. Voor het gemak hebben we de klasse List<Leven> waarvan Natuur erft hier buiten beschouwing gelaten:


Hoofdstuk 13 Events

517

Figuur 41 UML-klassendiagram Natuur met event Getroffen

1

Zoek de volgende regel op binnen de klasse Natuur: public event EventHandler<NieuwLevenEventArgs> NieuwLeven;

T

2 Voeg de volgende regel toe direct na deze regel om het event aan te maken: public event EventHandler<GetroffenEventArgs> Getroffen;

EP

3 We hebben een methode nodig die onderzoekt of een punt zich op een object uit de onderliggende List bevindt. Deze methode roept ieder object aan in de List en start de methode IsOpObject() op. Bevindt een punt zich op het object dan wordt het event OpObject geactiveerd. Voeg de volgende regels toe om de methode LevenGeraakt() te maken:

O

N

C

public void LevenGeraakt(Point locatie) { foreach (Leven leven in this) { leven.IsOpObject(locatie); } }

C

4 Op het GrafischObject is een event geplaatst dat geactiveerd wordt zodra een punt zich op een GrafischObject bevindt. De klasse Natuur moet zich hier nog op abonneren. Dit doen we binnen de methode Add(). Zo abonneert de klasse Natuur zich op het event OpObject van ieder object dat toegevoegd wordt. Zoek binnen de methode Add() de volgende regels op:

// Abonneren op het bericht dat het object gestorven is. leven.Einde += leven_Einde;

5 Voeg de volgende regel toe direct achter deze regels: // Abonneren als er op het object geklikt is.

6 Typ de volgende code direct gevolgd door tweemaal de Tab-toets: leven.OpObject +=

De regel wordt aangevuld zodat er het volgende staat: leven.OpObject += leven_OpObject;


518

Deel 4: Objectoriëntatie

Ook wordt de volgende methode voor je aangemaakt (scroll zo nodig even naar beneden: hij kan ergens onderaan terechtkomen): void leven_OpObject(object sender, EventArgs e) { throw new NotImplementedException(); }

7 Vervang de regel throw new NotImplementedException(); door de volgende regels:

EP

T

// Als iemand zich geabonneerd heeft op Getroffen dan wordt // als het ware het onderliggende event dat is afgegaan doorgegeven. // Zo kun je een ketting van events maken. if (Getroffen != null) { // Als een object uit de lijst geactiveerd wordt // dan activeert Natuur het Getroffen event. Getroffen(this, new GetroffenEventArgs((Leven)sender)); }

Implementeren van het event Getroffen binnen FrmNatSimII 1

Open het codevenster van FrmNatSimII.

2 Ga naar de constructor FrmNatSimII.

C

3 Zoek de volgende regel op:

natuur.NieuwLeven += natuur_NieuwLeven;

N

4 Voeg de volgende regel toe:

natuur.Getroffen += natuur_Getroffen;

O

5 Vul de methode natuur_Getroffen door de volgende regels over te nemen:

C

void natuur_Getroffen(object sender, GetroffenEventArgs e) { lblInformatie.Text = e.GeraaktOp.ToString(“hh:mm:ss tt”) + Environment.NewLine + Environment.NewLine + e.Getroffen.ToString(); }

6 De events zijn aangemaakt met de bijbehorende methoden, alleen moeten de events nog aan het pbWereld_MouseMove event gekoppeld worden. Maak de methode pbWereld_MouseMove() in het formulier FrmNatSimII door op het MouseMove event van pbWereld te dubbelklikken vanuit het formulier (Design-venster). Voeg de volgende code toe: private void pbWereld_MouseMove(object sender, MouseEventArgs e) { if (chbZaai.Checked && rdbGras.Checked) { Gras.Zaaien(e.Location, pbWereld.CreateGraphics()); }


Hoofdstuk 13 Events

519

else { // Bij het bewegen van de muis wordt gekeken of er een levend object getroffen // wordt. Zo ja, dan wordt het event geactiveerd dat aangeeft dat het object // getroffen is: lblInformatie.Text = “”; natuur.LevenGeraakt(e.Location); } }

Test het programma

O

N

C

EP

T

Als het goed is wordt er, zodra je een object van het type Plant aanmaakt en je de muis over de plant beweegt, de volgende informatie over die Plant in het Label lblInformatie getoond.

C

Schermafbeelding 24 Natuurinfo plant

NatSimII tot nu toe Als je de muis over een Dier beweegt krijg je het volgende scherm te zien:


Deel 4: Objectoriëntatie

EP

Schermafbeelding 25 Natuurinfo dier

T

520

Wat opvalt is dat alleen de naam van de namespace en de klasse worden getoond. Dit klopt want alleen de ToSTring()-methode van Plant is aangepast.

C

Opdrachten Events

N

In de bijlage Cookbook Events staat een stappenplan voor het maken en toepassen van events.

O

Opdracht 13.3: Extra informatie tonen

C

Pas het programma zo aan dat van een Konijn en een Koe ook de LatijnseNaam, de NederlandseNaam, de Locatie waar geklikt is en de Levensduur van het Dier getoond wordt.

Opdracht 13.4: Nog meer informatie tonen * Pas het programma zo aan dat als je met de muis over een Dier beweegt ook wordt getoond of het Dier een Planteneter, een Vleeseter of een Alleseter is.

Collision detection Een belangrijk onderdeel van veel games is de zogenaamde collision detection. Hoe bepaal je of twee objecten elkaar raken? Dit is lastige materie. Zoals je al gezien hebt is hetgeen op het scherm getoond wordt alleen maar een verzameling pixels. Wij herkennen het wel als dingen, maar de computer kan hier helemaal niets mee.


Hoofdstuk 13 Events

521

Een object ‘bestaat’ pas als het ergens wordt opgeslagen. In onze simulatie is dat in het object Natuur. De methode Zaaien() van Gras zorgt er op dit moment nog steeds voor dat er alleen Gras op het scherm getekend wordt. Er wordt geen Gras toegevoegd aan de lijst Natuur. Maar dit is van later zorg. Eerst de botsingen (collisions)! Wil je weten of een object botst met een ander object dan zul je bij iedere nieuw berekende positie deze positie moeten vergelijken met de positie en omvang van de andere objecten.

EP

Voorbeeld 13.6: Collision detection

T

Binnen de klasse GrafischObject hebben we al methoden gedefinieerd die bepalen of het ene object het andere object raakt. Binnen de klasse BewegendObject zijn al methoden gemaakt waarmee je de richting van een object kunt veranderen.

Klasse Leven ondersteunende functies

C

O

N

C

Botsingen kunnen plaatsvinden tussen alle soorten Leven. Vandaar dat we in de klasse Leven de nodige zaken gaan aanpassen zodat de code elders eenvoudiger wordt. Het nieuwe UML-klassendiagram voor Leven komt er als volgt uit te zien:

Figuur 42 UML-klassendiagram Leven met conversiemethoden

1

Het is handig als we een object van het type klasse kunnen omzetten naar een object van het type Dier. Dit kan altijd met een cast maar door dit in een methode te zetten wordt je code eenvoudiger. Voeg de volgende methode toe aan de klasse Leven:


522

Deel 4: Objectoriëntatie

public Dier ToDier() { // Check of het Leven van het type Dier is. // Zo niet wordt er een leeg object teruggegeven: if (IsDier) return (Dier)this; else return null; }

2 Hetzelfde doen we om Leven om te zetten naar Plant. Voeg de volgende methode toe: public Plant ToPlant() { if (IsPlant) return (Plant)this; else return null; }

EP

T

3 Ook kan het handig zijn om van een object dat erft van Leven te bepalen tot welke enum SoortLeven dit object behoort. Voeg de volgende methode toe aan de klasse Leven:

O

N

C

public SoortLeven ToSoort() { switch (NederlandseNaam.ToLower()) { case “beer”: return SoortLeven.Beer; case “gras”: return SoortLeven.Gras; case “koe”: return SoortLeven.Koe; case “konijn”: return SoortLeven.Konijn; case “lynx”: return SoortLeven.Lynx; case “venijnboom”: return SoortLeven.Venijnboom; case “vingerhoedskruid”: return SoortLeven.Vingerhoedskruid; default: return SoortLeven.Gras; } }

C

Aanpassen klasse Natuur In de klasse Natuur wordt de List bijgehouden van alle Planten en Dieren binnen de simulatie. Deze klasse is dus de plek waar we kunnen vaststellen of twee objecten elkaar raken.

Binnen de klasse GrafischObject kun je niet vaststellen of een GrafischObject botst met een ander GrafischObject want ieder GrafischObject heeft alleen maar informatie over dat ene GrafischObject! Let op! Deze simulatie is zeer primitief en zeker niet 100 waterdicht! Wat moet er gebeuren? Van ieder object moet vastgesteld worden of ze elkaar raken. Zo ja, dan kan de een de ander opeten of de objecten keren om.


Hoofdstuk 13 Events

523

De eenvoudigste oplossing is een brute-force-oplossing. Deze zullen we hier implementeren. Andere methoden zijn bijvoorbeeld quadtrees, R-trees en spatial hashmaps.

N

C

EP

T

De brute-force-methode is heel simpel. Je vergelijkt ieder object uit de List Natuur met ieder ander object uit dezelfde List. Hiervoor zou je tweemaal een foreachloop kunnen gebruiken. Echter zodra een object verwijderd wordt uit de List (Gras wordt opgegeten door een Konijn) krijg je de volgende foutmelding te zien:

Schermafbeelding 26 Foreach-foutmelding

Een for-loop is al gemaakt binnen de methode private void levensKlok_Tick(object sender, EventArgs e). Deze methode, die bij iedere Tick van de levensKlok uitgevoerd wordt, verplaatst ieder object een stukje. We gaan nu bij iedere Tick een methode opstarten die checkt of er een botsing plaats gaat vinden. Voeg de methode CollisionDetection() toe:

C

1

O

Je mag namelijk bij een foreach-loop de List Items niet veranderen en al helemaal niet verwijderen. Vandaar dat we werken we met een for-loop.

public void CollisionDetection(Dier dier) { // Alleen Dieren bewegen en kunnen dus ergens op botsen. for (int i = 0; i < this.Count; i++) { // We slaan het Dier zelf over, je botst altijd met jezelf. if (dier.ID != this[i].ID) { dier.IsBotsing(this[i]); } } }


524

Deel 4: Objectoriëntatie

2 Nu gaan we de methode private void levensKlok_Tick(object sender, EventArgs e) aanpassen. Binnen de klasse Leven hebben we net een methode ToDier() gemaakt die een object van het type Leven kan omzetten naar een object van het type Dier of een null-waarde teruggeeft. Vervang de body van de methode levensKlok_Tick() door de volgende regels: for (int i = 0; i < this.Count; i++) { Dier dier = this[i].ToDier(); if (dier != null) { dier.Beweeg(); CollisionDetection(dier); } }

T

3 Binnen de klasse Dier maken we tenslotte een methode IsBotsing().

N

C

EP

public bool IsBotsing(Leven leven) { if (this.Tekengebied.Overlap(leven.Tekengebied)) { // Acties op de objecten Dier dier = leven.ToDier(); if (dier != null) { this.SnelheidObject = this.SnelheidObject.Keerom(); dier.SnelheidObject = dier.SnelheidObject.Keerom(); } Eet(leven);

}

O

return true; } return false;

C

4 Soms moet je een object van het type Leven omzetten naar een specifieker object zoals bijvoorbeeld Gras. Voeg de onderstaande code toe aan de klasse Leven: public Gras ToGras() { if (this.GetType() == typeof(Gras)) { return (Gras)this; } else { return null; } }


Hoofdstuk 13 Events

525

Test het programma Als het goed is keren de Koeien en Konijnen om zodra ze elkaar raken en eten ze grassprietjes op. En wat gebeurt er als ze een Venijnboom opeten? Precies: ze verdwijnen allebei.

Opdrachten Collision detection Opdracht 13.5: Red de Venijnboom

EP

Opdracht 13.6: Vleeseter, Jaguar

T

Dat is natuurlijk niet erg waarschijnlijk. De Venijnboom is giftig, dus de dieren gaan er van dood, maar de Venijnboom blijft leven. Pas je programma aan zodat alleen het Dier verdwijnt als het eet van een Venijnboom.

Een Jaguar is een Vleeseter. Elk ander dier is een mogelijke prooi. De kleur van de Jaguar is geel en de afmetingen op het scherm 17 bij 6 pixels.

C

O

N

C

Voeg de klasse Jaguar toe volgens het volgende UML-klassendiagram, zorg dat hij volwaardig mee kan doen in NatSimII en test je programma:

Figuur 43 UML-klassendiagram Jaguar

Opdracht 13.7: Vleeseter, Lynx Een Lynx is ook een Vleeseter. Een Vleeseter eet alleen andere Dieren. Als een Lynx een andere Lynx tegenkomt moeten beide Lynxen omkeren. De kleur van de Lynx is grijs en de afmetingen op het scherm 7 bij 4 pixels.


526

Deel 4: Objectoriëntatie

EP

T

Voeg de klasse Lynx toe volgens het volgende UML-klassendiagram en test je programma:

Figuur 44 UML-klassendiagram Lynx

Opdracht 13.8: Alleseter, Beer *

N

C

Een Beer is een Alleseter. Als een Beer honger heeft eet ook hij giftige planten, met het bekende gevolg: zowel de Beer als de Plant worden verwijderd. Een Beer eet ook Beren die kleiner zijn dan de Beer zelf. De kleur van de Beer is wit. De afmetingen zijn 20 bij 12 pixels.

C

O

Voeg de klasse Beer toe volgens het volgende UML-klassendiagram en test je programma.

Figuur 45 UML-klassendiagram Beer


Hoofdstuk 13 Events

527

Opdracht 13.9: Red de Venijnboom opnieuw Verander je programma zodat alleen de Beer verdwijnt, maar de Venijnboom blijft leven.

Opdracht 13.10: Conversie van Leven naar Venijnboom Voeg aan de klasse Leven de methode ToVenijnboom() toe die een object van het type Leven kan omzetten naar een Venijnboom.

Opdracht 13.11: Conversie van Leven naar Vingerhoedskruid

T

Voeg aan de klasse Leven de methode ToVingerhoedskruid() toe die objecten van het type Leven omzet naar het type Vingerhoedskruid.

Voorbeeld 13.7: Zaaien

Verplaats de methode Zaaien() van de klasse Gras naar de klasse Natuur en pas deze zo aan dat je het volgende resultaat krijgt:

O

1

N

Aanpassen Zaaien

C

EP

In het programma is in de klasse Gras een methode opgenomen voor Zaaien(). Om een aantal redenen is dit niet handig: – Soms wil je ook andere Planten kunnen zaaien. – De gezaaide Planten moeten opgenomen worden in de List Natuur. – Het ligt dus voor de hand om deze methode te verplaatsen naar de klasse Natuur en de methode daar aan te passen.

C

public void Zaaien(Point locatie, Graphics papier, int lengte, int breedte, int zaaiAfstand, Plant plant) { // Berekenen van het punt vanaf waar gezaaid gaat worden // (de locatie ligt midden in het zaaigebied): int puntX = locatie.X - lengte / 2; int puntY = locatie.Y - breedte / 2; // Variabelen die de uitgangsposities bewaren zodat // deze hersteld en opnieuw gebruikt kunnen worden: Point oorspronkelijkeLocatie = locatie; int startpuntY = puntY; // Bepalen tot welke locatie er gezaaid gaat worden: lengte = puntX + lengte; breedte = puntY + breedte; while (puntX < lengte) { while (puntY < breedte) { locatie = new Point(puntX, puntY);


528

Deel 4: Objectoriëntatie

// Bepalen welke plant toegevoegd moet worden: if (plant.ToGras() != null) { Gras gras = new Gras(locatie); Add(gras); } // gras.Teken(papier); is niet nodig, dat is nu gekoppeld aan het event: puntY = puntY + zaaiAfstand; } puntY = startpuntY; puntX = puntX + zaaiAfstand; ; } }

T

De belangrijkste verandering is het vervangen van Gras door een variabele van het type Plant en het vervangen van de regel waar getekend wordt door de regel waarbij de Plant wordt toegevoegd aan de List (Add).

EP

Binnen Add wordt het event nieuwLeven geactiveerd en dat zorgt er weer voor dat er binnen het formulier een methode wordt gestart die de Plant tekent. 3 Er is ook een overloaded methode voor Zaaien(). Ook deze moet verplaatst worden naar Natuur en zo aangepast worden dat je het volgende resultaat krijgt:

N

C

public void Zaaien(Point locatie, Graphics papier, Plant plant) { Zaaien(locatie, papier, 150, 46, 15, plant); }

Test de simulatie

C

O

Inderdaad, de simulatie is niet perfect en ook nog (lang?) niet ‘af ’. Belangrijk is dat je de achterliggende principes begrijpt. De diverse objecten zullen verdwijnen (opgegeten worden, spontaan verdwijnen) of van richting veranderen. Voel je vrij om de simulatie uit te breiden en te verbeteren.

Wat heb je geleerd?

– – – – – –

Wat een event is. Hoe je een event gebruikt. Hoe een event in een UML-klassendiagram wordt weergegeven. Wat collision detection inhoudt. Hoe je collision detection implementeert. Het gebruik van EventArgs.


529

Hoofdstuk 14 Oefening baart kunst

Dit hoofdstuk bestaat uit een aantal opdrachten waarmee je kunt oefenen en waarmee je de simulatie kunt uitbreiden.

T

Opdrachten NatSimII Opdracht 14.1: Venijnboom

EP

Implementeer de volledige klasse Venijnboom. Zorg ervoor dat in het informatieLabel behalve de standaardinformatie over de naam en de positie ook de ziektes komen te staan waarbij de Venijnboom als geneesmiddel kan werken.

C

Opdracht 14.2: Vingerhoedskruid

Implementeer de volledige klasse Vingerhoedskruid.

N

Opdracht 14.3: Zaaien

Pas het programma zo aan dat je behalve Gras ook Vingerhoedskruid kunt zaaien.

O

Opdracht 14.4: Over gras lopen *

C

Pas het programma zo aan dat een Dier over Planten kan lopen die kleiner zijn dan zijn eigen grootte. Het lastige is dat de Planten opnieuw getekend moeten worden nadat het Dier voorbijgegaan is.

Opdracht 14.5: Voortplanting Breid de klasse Dier uit met de eigenschap Geslacht. Dit kan mannelijk zijn of vrouwelijk. Voeg ook de eigenschap AantalNakomelingen toe. In deze eigenschap wordt het aantal nakomelingen per geboorte vermeld. Zo kan een Koe vier kalveren krijgen, een Konijn acht nakomelingen en een Beer maar twee nakomelingen per ontmoeting. Zorg ervoor dat als twee Dieren van dezelfde soort botsen en ze van verschillende geslachten zijn, dat er een aantal nieuwe Dieren gemaakt wordt.


530

Deel 4: Objectoriëntatie

Opdracht 14.6: Voortplanten Planten * Wijzig het programma zo dat een Plant zich voortplant als er tenminste nog tien Planten in leven zijn en de Plant zich op driekwart van zijn levensduur bevindt.

Opdracht 14.7: Shooting game * Pas het programma zo aan dat je door met je muis te bewegen en op de toets K (van Kill) te drukken Dieren kunt afschieten. Het kleinste dier levert je de meeste punten op, het grootste Dier de minste. Uiteraard worden de behaalde punten ergens op het scherm getoond.

Opdracht 14.8: Snelheid wijzigen *

Opdracht 14.9: Plaatjes *

EP

T

Op het formulier staat een schuifbalk met het opschrift ‘snelheid’. Pas het programma zo aan dat je met deze schuifbalk de snelheid van de simulatie kunt instellen.

O

N

C

Een bruin vierkantje een Konijn? Een zwarte rechthoek als Koe? Het is mooier als je ervoor zorgt dat er op die plek een afbeelding getoond wordt. Pas de simulatie zo aan dat er betere afbeeldingen getoond worden van diverse objecten. Breid het programma zo uit dat ieder levend object voorzien kan worden van een afbeelding. De afbeelding krijgt dezelfde naam als de klasse van het object. Deze afbeeldingen moeten in één map bij elkaar geplaatst worden. Zorg ervoor dat er een mogelijkheid komt om de afbeeldingen te beheren. Met andere woorden: de gebruiker moet in staat zijn om een ander plaatje te kiezen voor bijvoorbeeld een Konijn.

Opdracht 14.10: Geluid *

C

Tot dusverre is de simulatie een geluidloze aangelegenheid. Pas de simulatie zo aan dat de volgende gebeurtenissen (events) ook een geluid laten afspelen voor: het bewegen van een object (voetstappen) het opeten van een object (smakgeluiden) het botsen op eenzelfde object (een knal) het verwijderen van een object (een kreet)

– – – –

Opdracht 14.11: Collision detection * Zoals je ziet is de collision detection van deze simulatie niet perfect. Soms vliegen objecten nog door elkaar heen, bij het aanmaken van een object kunnen objecten elkaar ook overlappen en zo loopt het niet overal even ‘smooth’. Verbeter de objecten/code die betrokken is bij de collision detection.


Hoofdstuk 14 Oefening baart kunst

531

Opdracht 14.12: Evenwicht *

C

O

N

C

EP

T

Pas de simulatie zo aan dat je door middel van het instellen van het aantal nakomelingen, levensduur, enzovoort, een evenwicht bereikt in de simulatie. Dit betekent dat er geen Planten of Dieren uitsterven en de simulatie in feite eeuwig doorgaat.


532

Nawoord Deel 4

Dit was het vierde en voorlopig laatste deel van het boek Leren programmeren in C. Je hebt kennisgemaakt met de basisstructuren van het programmeren, de taal C, met lijsten, bestanden en databases. In dit laatste deel heb je niet zozeer nieuwe commando’s geleerd als wel een nieuwe manier van programmeren, een andere manier van een programma ontwerpen.

T

Ben je nu volleerd? Gelukkig niet, want software-ontwikkeling is een vak waar je altijd geconfronteerd wordt met nieuwe dingen. Of je bedenkt ze zelf, of een ander bedenkt wel wat.

EP

Kortom: een vak voor nieuwsgierige en creatieve mensen. Een software-ontwikkelaar wordt door sommigen dan ook meer als een artiest beschouwd dan als een technicus.

Happy coding!

C

O

N

Voorjaar 2021

C

Met het geleerde heb je je basisuitrusting gereed om nu veilig op ontdekking te gaan in de uitdagende wereld van de software-ontwikkeling.


C

O

N

C

EP

T

Bijlagen


C EP

C

N

O T


ASCII-tabel deel 1: Besturingscodes

535

ASCII-tabel deel 1: Besturingscodes

EP

T

Omschrijving Null Start of Header Start of Tekst End of Tekst End of Transmission Enquiry Acknowledgment Bell Backspace Horizontal Tab Line Feed Vertical Tab Form Feed Carriage Return Shift Out Shift In Data Link Escape Device Control  (XON) Device Control 2 Device Control 3 (XOFF) Device Control 4 Negative Acknowledgement Synchronous Idle End of Transmission Block Cancel End of Medium Substitute Escape File Separator Group Separator Record Separator Unit Separator

C

Code NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI DLE DC DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US

N

Binair                                

C

O

DEC                                

Tabel 5: ASCII besturingscodes


536

Bijlagen

ASCII-tabel deel 2: Tekens

N

O

C

Teken P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ DEL

T

Binair                                                

EP

Dec                                                

C

Dec Binair Teken   space   !   "            &   ‘   (   )   *   +   ,     .   /                                 :   ;   <   =   >   ?   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O Tabel 6: ASCII Tekens


Begrippen

537

Begrippen

C

O

N

C

EP

T

App Afkorting van applicatie (letterlijk: toepassing). We gebruiken in dit boek de woorden app, applicatie en programma voor hetzelfde: regels code die de computer een reeks taken laat uitvoeren voor de gebruiker. Breakpoint Een plek in de code waarbij het programma stopt met de uitvoering zodat de programmeur kan onderzoeken welke waardes de diverse variabelen op dat moment hebben. camelCase In principe gelijk aan PascalCase, maar de eerste letter van het eerste woord is altijd een kleine letter. Voorbeeld: gekookte eieren eten wordt in camelCase: gekookteEierenEten. Complexiteit Hoe ingewikkeld een programma is. Het gaat hier niet alleen over het aantal regels maar ook hoeveel geneste structuren er gebruikt zijn. Meerdere if...elsestatements of loops binnen selecties. Consistent Consistent betekent met elkaar kloppend: op een logische manier samenhangend. Met andere woorden: je gebruikt overal dezelfde manier van coderen. Console (app) Een programma dat werkt met een tekstvenster voor in- en output. Wordt nog vaak gebruikt bij servers en technische apparaten. Ook hackers maken vaak gebruik van console-apps. Expliciet Je geeft exact aan wat je verwacht van je programma. Expliciet is het tegenovergestelde van impliciet. Forms (app) Een applicatie speciaal geschreven voor de grafische interface van Windows forms. Deze applicaties maken vaak gebruik van de muis. Hongaarse notatie De naam van een variabele begint met een drieletterige afkorting van het type van de variabele. Voorbeeld: int getal; wordt in Hongaarse notatie: int intGetal; Impliciet Een waarde of een handeling wordt afgeleid uit hetgeen voorhanden is. Bijvoorbeeld als een string-variabele samen wordt gevoegd met een getal dan gaat C ervan uit dat het getal omgezet moet worden naar een string. Inconsistent Onderdelen zijn met elkaar in tegenspraak. Tegengesteld aan consistent. Parameter Een of meerdere variabelen die tussen de haakjes achter de methodenaam aan een methode worden meegegeven.


538

Bijlagen

C

O

N

C

EP

T

PascalCase Eén woord maken van afzonderlijke woorden die met een hoofdletter beginnen. Voorbeeld: gekookte eieren eten wordt in PascalCase GekookteEierenEten. Referentiële integriteit Dit zijn de regels die ervoor zorgen dat de gegevens in de database consistent blijven. Zo kun je bijvoorbeeld voorkomen dat in een database een klant verwijderd wordt zolang er nog facturen van deze klant zijn. Screaming Caps Een stuk tekst of de naam van een variabele die alleen in hoofdletters is gespeld. Dit is niet gebruikelijk, want het komt over alsof er geschreeuwd wordt. Voorbeeld: RAPPORTCIJFER Syntax De regels waaraan een taal zich houdt. Nederlands heeft andere regels dan Duits. Zo heeft iedere programmeertaal ook eigen regels.


Bronvermelding

539

Bronvermelding Gebruikte software – Visual Studio 2013 Professional (codering) – Visual Studio 2015 Ultimate (UML) – Microsoft Word 2016

Informatieve sites

EP

T

www.google.com www.codeproject.com www.c-sharpcorner.com http://stackoverflow.com https://msdn.microsoft.com www.tutorialspoint www.wikipedia.org http://lcinformatica.amsterdams.info/index.htm www.intertech.com/Blog/c-sharp-tutorial-understanding-c-events/ http://www.excelsoftware.com/download/csharp_models.pdf

C

O

N

C

– – – – – – – – – –


540

Bijlagen

Cookbook Events Een cookbook is een korte algemene handleiding, een soort kookboek. Hieronder de handleiding met voorbeeldregels voor hoe je een event kunt implementeren.

T

Uitgangspunten: – Alle events in .NET zijn gebaseerd op delegates. – Wanneer je een event wilt publiceren (triggeren, afvuren, fire in programmeertaal), moet je dit event ook definiëren. – Je kunt een event alleen publiceren als er tenminste één abonnee is. Dit check je eenvoudig door te kijken of een event ongelijk is aan null. – Naming convention: De namen van events beginnen allemaal met het Engelse On. Dus OnMouseClick en OnVerwijderd. Maar van deze regel wordt regelmatig afgeweken.

De eenvoudige publisher

Bij deze events wordt er verder geen informatie verstrekt over het event. Alleen de gegevens van de zendende klasse zijn beschikbaar.

EP

1

Maak een event aan op basis van de standaard-delegate EventHandler. Deze methode geeft een variabele terug van het type EventArgs.

C

public event EventHandler<EventArgs> OnNaamVanHetEvent;

2 Bepaal onder welke voorwaarden het event gepubliceerd moet worden:

O

N

if ((voorwaarde==true) && (OnNaamVanHetEvent != null)) { // De klasse die het event publiceert wordt meegegeven als argument, // verder wordt er geen informatie over het event zelf meegegeven. OnNaamVanHetEvent(this, EventArgs.Empty); }

C

Het gedeelte (OnNaamVanHetEvent != null) zorgt ervoor dat een event alleen gepubliceerd wordt als er tenminste één abonnee is.

De eenvoudige abonnee

1

Je maakt een variabele aan van de klasse waar je het event wilt gebruiken: JouwKlasse jouwKlasse = new JouwKlasse();

2 Je maakt een methode aan waarin je de regels opneemt die uitgevoerd moeten worden als het event zich voordoet. De naam van deze methode bouw je als volgt op: De naam van de klasse waar het event bij hoort, een underscore en de naam van het event zonder het voorvoegsel On. In dit geval wordt de naam van het event: klasse_NaamVanHetEvent.


Cookbook Events

541

private void jouwKlasse_NaamVanHetEvent(object sender, EventArgs e) { // Hier schrijf je de code die uitgevoerd moet worden als het event // gepubliceerd wordt. }

Belangrijk is dat deze methode voldoet aan de handtekening van de delegate EventHandler! 3 Vervolgens kun je je eenvoudig abonneren op het event. Dit doe je door de net aangemaakte methode, in dit geval de methode jouwKlasse_NaamVanHetEvent, toe te voegen aan het event. Dit doe je met de operator +=. jouwKlasse.OnNaamVanHetEvent += jouwKlasse_NaamVanHetEvent;

T

Let op! Links staat eigenlijk het event, rechts staat de methode die dan uitgevoerd moet worden.

EP

Hiermee heb je een klasse met een event en een methode uit een andere klasse die daarop kan reageren.

C

Let op! Gebruik je = in plaats van += dan wordt de bestaande lijst met methoden vervangen door de ene methode die je als laatste toevoegt. En dat is meestal het laatste wat je wilt…

Een publisher met extra informatie

Maak een klasse aan waarin de extra informatie doorgegeven kan worden:

C

1

O

N

Stel je wilt extra informatie over het event delen met je abonnees. Die móéten er geen gebruik van maken, nee, ze kúnnen er gebruik van maken. Dit doe je door een aparte klasse aan te maken waar je die informatie in stopt. Er is één maar: die klasse moet erven van EventArgs. Hierdoor kan deze klasse bij elk event gebruikt worden.

public class NaamVanHetEventEventArgs : EventArgs { public NaamVanHEtEventEventArgs(string mijnVariabele) { MijnVariabele = mijnVariabele; } public string MijnVariabele { get; set; } }

Deze klasse bestaat dus uit een constructor en een aantal eigenschappen. 2 Nu moet je ook nog het gedeelte aanpassen waarin je het event publiceert. Daar kun je de verschillende parameters een waarde meegeven: OnMeervoudVanVijfBereikt(this, new NaamVanHetEventEventArgs(“Extra informatie”));


542

Bijlagen

De publisher met extra informatie 1

Je maakt nu een EventHandler waarbij je gebruikmaakt van de net aangemaakte klasse. Dit kan omdat de methode aangemaakt wordt op basis van een delegate die gebruikmaakt van een variabele van het type EventArgs. De net aangemaakte klasse erft van EventArgs. public event EventHandler< NaamVanHetEventEventArgs > OnNaamVanHetEvent;

Gebruik van de extra informatie door de abonnee 1

Het event moet nu zo gewijzigd worden dat deze geen parameter van het type EventArgs accepteert maar een parameter van het type NaamVanHetEventEventArgs.

T

Het event wordt dan als volgt gedefinieerd: public event EventHandler< NaamVanHetEventEventArgs > OnNaamVanHetEvent;

EP

2 Vervolgens moet ook de aanroep van het event aangepast worden:

C

O

N

C

OnNaamVanHetEvent(this, new NaamvanHetEventArgs(“Mee te geven variabele”));


Kleurcodering Visual Studio 2019 C editor

543

Kleurcodering Visual Studio 2019 C# editor Als je de instelling Wijzigingen bijhouden hebt aangevinkt in Visual Studio 2019 dan worden de wijzigingen als volgt geaccentueerd met behulp van een gekleurde balk aan de linkerzijde van je code: – geel De regel is gewijzigd maar nog niet opgeslagen. – groen De regel is gewijzigd én opgeslagen. Behalve in de linkermarge worden er ook allerlei gekleurde balken, lijnen en blokjes in de rechtermarge getoond:

EP

T

– Kleine vierkante blokjes in het midden van de marge: breakpoint – Kleine vierkante blokjes rechts in de marge: syntaxfout

– Verticaal grijs blok: het gedeelte van de code dat nu op het beeldscherm zichtbaar is.

C

–

N

–

O

– Doorgetrokken horizontale blauwe lijn: de huidige positie van de cursor

C

Als je deze accenten niet ziet dan kun je dit als volgt instellen: 1 Tools – Options – TextEditor 2 Check de Track Changes-optie.


544

Bijlagen

Naamgeving klasse

– –

C

O

N

C

EP

–

T

– – – –

De volgende regels voor de naamgeving van klassen worden aanbevolen door Microsoft: Gebruik een zelfstandig naamwoord. Gebruik PascalCase. Gebruik zo min mogelijk afkortingen. Gebruik geen type-aanduidingen als prefix (zoals C voor Class) voor de naam van een klasse. Gebruik geen underscore (laag streepje). Gebruik alleen een I als eerste letter als de I een onderdeel vormt van een woord dat onderdeel is van de class-naam zoals bijvoorbeeld Identiteitsbewijs. De hoofdletter I wordt namelijk gebruikt bij de naamgeving van interfaces (zie hoofdstuk 11). Gebruik voor een afgeleide klasse, een klasse die erft van een basisklasse, een samengesteld woord, maar doe dit alleen als het de leesbaarheid van je code ten goede komt. Het tweede gedeelte van het woord is dan de naam van de basisklasse. ApplicationException is een geschikte naam voor een klasse die afgeleid is van een klasse met de naam Exception, omdat ApplicationException een soort Exception is. Gebruik vooral je gezonde verstand. Button is afgeleid van de klasse Control, maar de naam ButtonControl is onnodig lang en voegt niets toe aan je code.


Naming conventions C

545

Naming conventions C#* Gebruik PascalCase voor de namen van klassen en methoden.

public class ClientActivity { public void ClearStatistics() { //... } public void CalculateStatistics() { //... } }

T

GOED

GOED

EP

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en goed voor de leesbaarheid. Gebruik camelCase voor de parameters van methoden en lokale variabelen en voor de instantie van classes.

O

N

C

public class UserLog { public void Add(LogEvent logEvent) { int itemCount = logEvent.Items.Count; // ... } }

C

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en goed voor de leesbaarheid.

FOUT

Gebruik geen Screaming Caps voor constanten of read only variabelen.

// Goed public static const string ShippingType = “DropShip”; // Fout public static const string SHIPPINGTYPE = “DropShip”;

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework. Hoofdletters trekken te veel aandacht en leiden af van de hoofdzaak.

* http://www.dofactory.com/reference/csharp-coding-standards


546

FOUT

Bijlagen

Gebruik geen Hongaarse notatie voor de typeaanduiding in de naam van variabelen of methoden.

// Goed int counter; string name; // Fout int intCounter; string strName;

het gebruik van afkortingen. Uitzonderingen: afkortingen die gebruikt worden als zelfstandige naam zoals: Id, Xml, Ftp, Uri.

EP

VERMIJD

T

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en het is in Visual Studio IDE gemakkelijk om het type te achterhalen (via tooltip). In het algemeen geldt: gebruik nooit een typeaanduiding in welke naam dan ook, behalve in de naam van controls.

// Correct UserGroup userGroup; Assignment employeeAssignment;

C

// Vermijd UserGroup usrGrp; Assignment empAssignment;

O

N

// Uitzonderingen CustomerId customerId; XmlDocument xmlDocument; FtpHelper ftpHelper; UriPart uriPart;

C

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en het voorkomt inconsistente afkortingen.

GOED

Gebruik PascalCase voor afkortingen van drie letters of meer (bij twee letters gebruik je twee hoofdletters).

HtmlHelper htmlHelper; FtpTransfer ftpTransfer; UIControl uiControl;

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework. Hoofdletters trekken te veel aandacht en leiden af van de hoofdzaak.


Naming conventions C

FOUT

547

Gebruik géén underscore (laag streepje) in namen. Uitzondering: Je mag een underscore gebruiken als prefix van statische variabelen.

// Correct public DateTime clientAppointment; public TimeSpan timeLeft; // Fout public DateTime client_Appointment; public TimeSpan time_Left; // Uitzonderingen private DateTime _registrationDate;

// Goed string firstName; int lastIndex; bool isSaved;

N

// Fout String firstName; Int32 lastIndex; Boolean isSaved;

EP

Gebruik door Microsoft gedefinieerde typenamen (bijvoorbeeld string, int, bool enzovoort) in plaats van de system-typenamen zoals Int16, Single, UInt64 enzovoort.

C

GOED

T

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en het maakt de code leesbaarder (zonder ‘rommel’). Bovendien voorkomt het de problemen in die gevallen dat de onderstreping niet zichtbaar is.

O

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en goed voor de leesbaarheid.

C

GOED

Gebruik zelfstandige naamwoorden of zelfstandig-naamwoordzinnen voor de naam van een klasse.

public class Employee { } public class BusinessLocation { } public class DocumentCollection { }

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework en eenvoudig te onthouden.


548

GOED

Bijlagen

Geef de source-file dezelfde naam als de belangrijkste klasse in die file. Uitzondering: Bestandsnamen met deelklassen verwijzen naar het doel waarvoor de klasse is ontworpen. Bijvoorbeeld: designer, generated enzovoort.

// Located in Task.cs public partial class Task { //... } // Located in Task.generated.cs public partial class Task { //... }

GOED

EP

T

Waarom? Dit is volgens de richtlijnen van het Microsoft .NET Framework. Bestanden worden alfabetisch gesorteerd en deelklassen zijn zo eenvoudiger te vinden. Lijn alle accolades verticaal uit: zet ze netjes onder elkaar.

N

C

// Goed class Program { static void Main(string[] args) { } }

O

Waarom? Alhoewel Microsoft een andere standaard hanteert, heeft het leeuwendeel van alle programmeurs een voorkeur voor verticaal uitgelijnde accolades. Declareer alle variabelen bovenin de klasse.

C

GOED

// Goed public class Account { public string Number {get; set;} public DateTime DateOpened {get; set;} public DateTime DateClosed {get; set;} public decimal Balance {get; set;} // Constructor public Account() { // ... }

}


Naming conventions C

549

C

O

N

C

EP

T

Waarom? Dit is een algemeen geaccepteerd gebruik dat voorkomt dat je op zoek moet gaan naar variabelen die ergens binnen een klasse worden gedeclareerd.


550

Bijlagen

UML-diagram NatSimII

FG 80/&ODVV'LDJUDP

(YHQW$UJV

$WWULEXWHV *UDSKLFV9HQVWHU 5HFKWKRHN^UHDG2QO\` ,' *XLG^UHDG2QO\` .DGHU./HXU &RORU %ODFN .OHXU &RORU :KLWH6PRNH

$WWULEXWHV

1LHXZ/HYHQ(YHQW$UJV

/RFDWLH 3RLQW 2S2EMHFW (YHQW+DQGOHU (YHQW$UJV! 3DSLHU *UDSKLFV 7HNHQJHELHG 5HFKWKRHN :LVNOHXU &RORU 3DOH*ROGHQ5RG BYHUZLMGHUG ERRO IDOVH

1LHXZ/HYHQ /HYHQ

2SHUDWLRQV

1LHXZ/HYHQ(YHQW$UJV

2SHUDWLRQV

1LHXZ/HYHQ(YHQW$UJV

*UDILVFK2EMHFW

*UDILVFK2EMHFW ,V2S2EMHFW SXQW 3RLQW 7HNHQ 7HNHQ SDSLHU *UDSKLFV 9HUZLMGHU :LV LQLW&ODVV

1LHXZ/HYHQ(YHQW$UJV OHYHQ /HYHQ

T

2Q2S2EMHFW VHQGHU REMHFW

/HYHQ

/LVW /HYHQ! ©HQXPHUDWLRQª

%ORHLZLM]H

$WWULEXWHV

(LQGH (YHQW+DQGOHU /DWLMQVH1DDP VWULQJ^UHDG2QO\` /HHIWLMG LQW /HYHQVGXXU GRXEOH^UHDG2QO\` 1HGHUODQGVH1DDP VWULQJ^UHDG2QO\` 9HUKRXGLQJ7LFNV-DUHQ LQW 9HURXGHU 7LPHU^UHDG2QO\` 9RHGLQJVZDDUGH LQW BDDQWDO7LFNV3HU6HFRQGH LQW ^UHDG2QO\`

EP

/LWHUDOV 1DWXXU

$WWULEXWHV

*HWURIIHQ (YHQW+DQGOHU *HWURIIHQ(YHQW$UJV! 1LHXZ/HYHQ (YHQW+DQGOHU 1LHXZ/HYHQ(YHQW+DQGOHU! BOHYHQV.ORN 7LPHU

2SHUDWLRQV /HYHQ /HYHQ YHUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ /HYHQVGXXU LQW 6WHUI 7R'LHU 'LHU 7R3ODQW 3ODQW 7R6RRUW 6RRUW/HYHQ LQLW&ODVV 9HUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ OHYHQVGXXU LQW BYHURXGHUB7LFN VHQGHU REMHFW H (YHQW$UJV 2Q(LQGH VHQGHU REMHFW

2SHUDWLRQV

$GG OHYHQ /HYHQ /HYHQ*HUDDNW ORFDWLH 3RLQW 1DWXXU OHYHQB(LQGH VHQGHU REMHFW H (YHQW$UJV OHYHQB2S2EMHFW VHQGHU REMHFW H (YHQW$UJV BOHYHQV.ORNB7LFN VHQGHU 2EMHFW H (YHQW$UJV

C

1DWXXU

3ODQW

$WWULEXWHV %ORHLZLM]H3ODQW %ORHLZLM]H

N

DDU DDUWMH EORHPNOXZHQ EORHPNRHN EORHPNROI EXQGHO GLFKDVLDOH3OXLP GLFKDVLDOH7XLO HHQWDNNLJ%LMVFKHUP KRRIGMH NDWMH NHJHO QRQH SOXLP VDPHQJHVWHOGH$DU VDPHQJHVWHOG*HYRUNW6FKHUP VDPHQJHVWHOG6FKHUP VFKHUP VFKHUPYRUPLJH7URV VFKLFKW VFKLMQNUDQV VFKURHI VLNNHO WK\UXV %ORHLZLM]H3ODQW WXLO ZDDLHU

/HYHQ

/LVW /HYHQ!

2SHUDWLRQV

O

*UDV

9HQLMQERRP

9LQJHUKRHGVNUXLG

$WWULEXWHV

$WWULEXWHV

$WWULEXWHV

BEORHLZLM]H3ODQW %ORHLZLM]H %ORHLZLM]H DDU^UHDG2QO\` BODWLMQVH1DDP VWULQJ *UDPLQHDH^UHDG2QO\` BOHHIWLMG LQW ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

$DQWDO.XELHNH0HWHUV+RXW GRXEOH *HQHHVPLGGHO9RRU /LVW VWULQJ! /RQJNDQNHU %RUVWNDQNHU^UHDG2QO\` 0D[LPDOH*URRWWH LQW ^UHDG2QO\` BDIPHWLQJHQ 6L]H ^UHDG2QO\` BEORHLZLM]H3ODQW %ORHLZLM]H %ORHLZLM]H NHJHO^UHDG2QO\` BODWLMQVH1DDP VWULQJ 7D[XV EDFFDWD^UHDG2QO\` BOHHIWLMG LQW ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

BEORHLZLM]H3ODQW %ORHLZLM]H %ORHLZLM]H WURV^UHDG2QO\` BODWLMQVH1DDP VWULQJ 'LJLWDOLV 3XUSXUHD^UHDG2QO\` BOHHIWLMG LQW ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

2SHUDWLRQV

C

*UDV *UDV ORFDWLH 3RLQW =DDLHQ ORFDWLH 3RLQW SDSLHU *UDSKLFV OHQJWH LQW« LQLW&ODVV ORFDWLH 3RLQW

2SHUDWLRQV 9LQJHUKRHGVNUXLG 9LQJHUKRHGVNUXLG ORFDWLH 3RLQW LQLW&ODVV ORFDWLH 3RLQW

2SHUDWLRQV 9HQLMQERRP 9HQLMQERRP ORFDWLH 3RLQW LQLW&ODVV ORFDWLH 3RLQW

$WWULEXWHV 2SHUDWLRQV (HW OHYHQ /HYHQ 3ODQWHQHWHU YHUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ OHYHQVGXXU LQW JHZLFKW0D[LPDDO GRXEOH

.RQLMQ

.RH

$WWULEXWHV

$WWULEXWHV

*HERRUWH'DWXP 'DWH7LPH *HZLFKW GRXEOH 1DDP VWULQJ 6WHUI'DWXP 'DWH7LPH BJHZLFKW0D[LPDDO LQW ^UHDG2QO\` BODWLMQVH1DDP VWULQJ 2U\FWRODJXV FXQLFXOXV^UHDG2QO\`

$DQWDO/LWHUV0HON LQW *HERRUWH'DWXP 'DWH7LPH *HZLFKW GRXEOH 1DDP VWULQJ 6WHUI'DWXP 'DWH7LPH BJHZLFKW0D[LPDDO LQW ^UHDG2QO\`

BOHHIWLMG ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

BODWLMQVH1DDP VWULQJ %RV WDXUXV^UHDG2QO\` BOHHIWLMG ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

2SHUDWLRQV .RQLMQ .RQLMQ ORFDWLH 3RLQW .RQLMQ ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

2SHUDWLRQV .RH .RH ORFDWLH 3RLQW .RH ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU


UML-diagram NatSimII

551

©HQXPHUDWLRQª

9ODN /LWHUDOV 5HFKWKRHN

5HFKWKRHN

9ODN

$WWULEXWHV

*HHQ +RHN +RUL]RQWDDO 9HUWLFDDO

$ 3RLQW $IPHWLQJ 6L]H % 3RLQW %UHHGWH LQW & 3RLQW ' 3RLQW +RRJWH LQW /RFDWLH 3RLQW

5HFKWKRHN

9ODN

6QHOKHLG

2SHUDWLRQV *UHQVEHUHLNW UHFKWKRHN 5HFKWKRHN UHFKWKRHN 5HFKWKRHN 9ODN *UHQVEHUHLNW UHFKWKRHN 5HFKWKRHN 9ODN 2PWUHN LQW 2SSHUYODN LQW

6QHOKHLG

2YHUODS UHFKWKRHN 5HFKWKRHN ERRO 5HFKWKRHN /RFDWLH 3RLQW $IPHWLQJ 6L]H

; LQW < LQW 2SHUDWLRQV .HHURP 6QHOKHLG 6QHOKHLG [ LQW \ LQW 6WXLWHU YODN 9ODN

EP

6QHOKHLG

T

$WWULEXWHV

5HFKWKRHN 7R5HFWDQJOH

,%HZHJHQG2EMHFW

©LQWHUIDFHª

,%HZHJHQG2EMHFW

$WWULEXWHV

.ORN 7LPHU 6QHOKHLG2EMHFW 6QHOKHLG

$WWULEXWHV *HZLFKW0D[LPDDO GRXEOH^UHDG2QO\` 0DDJ*HYXOG LQW 6QHOKHLG2EMHFW 6QHOKHLG 6SLMVYHUWHULQJVGXXU LQW :RUGW9HUJLIWLJG'RRU /LVW VWULQJ! 2SHUDWLRQV

N

C

2SHUDWLRQV

C

O

'LHU YHUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ OHYHQVGXXU LQW JHZLFKW0D[LPDDO GRXEOH 6WDS 3RLQW 6WDS VQHOKHLG2EMHFW 6QHOKHLG LQLW'LHU

$WWULEXWHV

$WWULEXWHV

2SHUDWLRQV

2SHUDWLRQV

(HW OHYHQ /HYHQ 9OHHVHWHU YHUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ OHYHQVGXXU LQW JHZLFKW0D[LPDDO GRXEOH

$OOHVHWHU YHUKRXGLQJ7LFNV-DUHQ LQW ODWLMQVH1DDP VWULQJ OHYHQVGXXU LQW JHZLFKW0D[LPDDO GRXEOH (HW OHYHQ /HYHQ

-DJXDU

/\Q[

%HHU

$WWULEXWHV

$WWULEXWHV

$WWULEXWHV

BJHZLFKW0D[LPDDO LQW BODWLMQVH1DDP VWULQJ 3DQWKHUD RQFD^UHDG2QO\` BOHHIWLMG ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

3URRLGLHUHQ /LVW VWULQJ! +DDV .RQLMQ 0XLV 9RJHOV BJHZLFKW0D[LPDDO LQW BODWLMQVH1DDP VWULQJ /\Q[^UHDG2QO\` BOHHIWLMG ^UHDG2QO\`

BJHZLFKW0D[LPDDO LQW BODWLMQVH1DDP VWULQJ 8UVLGDH^UHDG2QO\` BOHHIWLMG ^UHDG2QO\` BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

2SHUDWLRQV

BYHUKRXGLQJ7LFNV-DUHQ LQW ^UHDG2QO\`

2SHUDWLRQV

-DJXDU -DJXDU ORFDWLH 3RLQW -DJXDU ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

2SHUDWLRQV

%HHU %HHU ORFDWLH 3RLQW %HHU ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU

/\Q[ /\Q[ ORFDWLH 3RLQW /\Q[ ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU LQLW&ODVV ORFDWLH 3RLQW QDDP VWULQJ NOHXU &RORU


C EP

C

N

O T


Turn static files into dynamic content formats.

Create a flipbook
CONCEPT_c-sharp-Geheel-2025 by Boom beroepsonderwijs - Issuu