Informatica

Jurgen Vandendriessche - jurgen.vandendriessche@vub.be

Thibaut Vandervelden- thvdveld@vub.be

Inleiding

Hieronder vinden jullie de oefeningen voor de WPO's informatica 1. De oefeningen zijn onderverdeeld in 4 categorieën: A, E en X. A zijn de algemene basisoefeningen en zijn het minimum dat verwacht wordt. De oefeningen van de E reeks zijn het niveau dat verwacht wordt op het examen. De X-oefeningen (Xtreme) zijn voor de echte durvers. Na elke les worden de opgeloste oefeningen via Canvas ingestuurd.

Tijdens de lessen zijn er 2 aangekondigde testen (elk 10%) gepland. Tijdens deze testen ondervragen wij de aangeleerde topics! Alle testen en examens worden hierbij gesloten boek afgenomen. Internet, theorieboek en opgeloste oefgeningen op papier of via een digitaal medium zullen niet toegelaten worden.

Tijdens de oenfeningensessies wordt er van je verwacht dat je actief meewerkt. Is dit niet het geval en verstoor je de groep of kom je te laat binnen, dan kan de assistent je de toegang tot die sessies weigeren.

Oefening baart kunst! Iedereen kan leren programmeren. Programmeren kan je alleen door veel te oefenen!

Visual Studio

Als technologie(ver)kenner zal je regelmatig nieuwe stukken code schrijven. In je opleiding zal je je eerste programmeerstappen in Visual Studio zetten (C#). Deze omgeving wordt door veel ervaren en professionele programmeurs gebruikt en wordt heel actief door Microsoft onderhouden. Visual Studio kan je als student gratis bekomen via deze link. Zorg ervoor dat je de Community Edition downloadt en installeert. Deze is zeker voldoende om de lessen informatica te kunnen bijwonen. De minimale versie tijdens de lessen is Visual Studio 2019. Oudere versies (2017) zijn zeker OK. Instructies over de installatie van visual studio kan je hier vinden.

Oplossing Test 2

Zowel de opgave als de oplossingen blijven beschikbaar tot het examen. Op de dag van het examen woden deze even van de pagina gehaald, net zoals de screensavers.

Opgave A

Opgave B

Oplossing

Opgaven WPF (C#)

WPO 1

Oefeningen werkcollege 1

In dit werkcollege worden de beginselen van het programmeren in C# aangeleerd. Hierbij wordt het gebruik van variabelen, de grafische interface en enkele bijhorende elementen aangetoond. Hiernaast wordt ook het gebruik van Visual Studio aangetoond. De variabelen die we zullen gebruiken kunnen onderverdeeld worden in 4 grote groepen:

  • de gehele getallen of integers,
  • de kommagetallen: hieronder vallen double en float,
  • de strings (string) en karakters (char): deze worden gebruikt om tekst weer te geven,
  • de booleaanse waarden (true of false).

Het gebruik van elk type variabele wordt hier geïllustreerd.

// declareer eerst de variabelen
// dit gebeurt door de naam van de variabele te laten voorafgaan 
// door het type
double value_double;
float value_float;
int value_int;
char value_char;
string value_string;
bool value_bool;
// zorg ervoor dat de variabelen en waarde bevatten
value_double = 5.0;
// merk de f: deze duidt aan dat het getal als float gezien moet worden
value_float = 5.0f; 
value_int = 5; // geen komma!
value_char = 'a'; // enkele aanhalingstekens
value_string = "dit is een string"; // dubbele aanhalingstekens
// voer enkele bewerkingen uit
value_int = 5+8; // levert 13 op
value_double = 5.6+3.9; // = 9.5
value_string = "test" + "bla" // = "testbla"

Visual Studio (C#) biedt de mogelijkheid aan om waarden van een formulier uit te lezen. Waarden naar een formulier schrijven is eveneens mogelijk. De gebruikelijke controls (textbox, label, enz.) handelen de in- en uitvoer van waarden af als tekenreeksen (string). Om de waarden van een formulier (control) te gebruiken, moet men eerst de conversie van een string het gepaste type uitvoeren.

// inlezen van een control (.Text) naar een variabele (double)
// txtInput.Text is van het type string
value_double = double.Parse(txtInput.Text);
// equivalent
value_double = Convert.ToDouble(txtInput.Text);
// inlezen van een control (.Text) naar een variabele (float)
value_float = float.Parse(txtInput.Text);
// equivalent
value_float = Convert.ToSingle(txtInput.Text);
// inlezen van een control (.Text) naar een variabele (int)
value_int = int.Parse(txtInput.Text);
// equivalent
value_int = Convert.ToInt32(txtInput.Text);
// inlezen van een string gebeurt zonder conversie 
// string naar string heeft geen conversie nodig
value_string = txtInput.Text;

Omgekeerd wordt een waarde steeds eerst naar een string omgezet voordat deze op het scherm weergegeven kan worden. Dit wordt geïllustreerd in onderstaand codefragment.

// wegschrijven van een variabele naar een control (.Text)
// txtOutput.Text is van het type string
txtOutput.Text = value_double.ToString();
// wegschrijven van een variabele naar een control (.Text)
txtOutput.Text = value_float.ToString();
// wegschrijven van een variabele naar een control (.Text)
txtOutput.Text = value_int.ToString();
// wegschrijven van een string 
// string naar string heeft geen conversie nodig
txtOutput.Text = value_string;

Tijdens de lessen worden ook verkorte notatie's gebruikt. Deze werken wel enkel voor enkelvoudige datatypes.

txtOutput.Text  = "" + value_double;
txtOutput.Text  = "" + value_float;
txtOutput.Text  = "" + value_int;

WPO 2

Oefeningen werkcollege 2

Tijdens dit werkcollege worden de principes van het debuggen en de conditionele if-structuren behandeld. Ook worden de eerste stappen van het debuggen tijdens dit WPO behandeld. Debuggen is van essentieel belang om goede software te kunnen schrijven! Zorg ervoor dat je deze technieken dus goed onder de knie hebt!
De conditionele if-structuren - keuzestructuren - vormen een belangrijke mijlpaal tijdens het programmeren. De keuzestructuren laten toe om volgens opgelegde voorwaarden een bepaald stukje code al dan niet uit te voeren. De meest eenvoudige if-structuur is de volgende:

if (condition)
{
	//some code
}

De voorwaarde (condition) kan om het even wat zijn, zolang het resultaat een boolean is. Voorbeelden van dergelijke voorwaarden worden hieronder opgelijst:

  • if (5>3)
  • if (a>3)
  • if ((a>3)&&(a<10)) => en a is groter dan 3, en a is kleiner dan 10
  • if ((a>3)||(a<10)) => of a is groter dan 3, of a is kleiner dan 10

Het blokje code dat uitgevoerd wordt als aan de condities voldaan is, bevindt zich tussen het daaropvolgende openend accolade en het bijbehorende sluitende accolade. Indien er verschillende condities met bijbehorende resultaten vereist zijn, kan men de if-structuur met een else if- en else-blok (zie hieronder) combineren.

if (condition1)
{
	//some code for condition 1
}
else if (condition2)
{
	//some code for condition 2
}
else
{
	//some code for else
}

De uitleg is vrij gelijkaardig als hierboven. Als regel ga je ervan uit dat de condities met hoogste prioriteit of met hoogste eisen als eerste worden behandeld (condition1, dan condition2, enz.). Geneste if-structuren zijn eveneens mogelijk (zoals hieronder weergegeven). De werking ervan is gelijkaardig aan een enkelvoudige if-structuur.

if (condition)
{
	if (subcondition1)
	{
		//some code
	}
}

Ook hier zijn de varianten met else if en else mogelijk. Varianten op de gewone vergelijkende if-structuren bestaan en worden in de opgaves zelf vermeld (checkboxen en radiobuttons).

WPO 3

Oefeningen werkcollege 3

Oefeningen werkcollege 3 (extra)

Tijdens dit WPO wordt een eerste vorm van loopstructuren behandeld: de for loop. Loops bestaan uit 2 blokken: de code waarover de loop effect heeft, en de conditie(s) waaronder de loop plaatsvindt. De basis van de for-loop wordt hieronder weergegeven:

for (start;end;increment)
{
	//some code	
}

Over het algmeen wordt er in de for-loop met een integer (index, vandaar i) gewerkt. In dat geval wordt de for-loop:

for (i=0;i<max;i++)
{
	//some code	
}

Voor de start en eind conditie kunnen willekeurige waarden opgegeven worden. De increment (hier i++ of i=i+1) kan ook vrij bepaald worden (bv. i=i-1; i=i+5,...). De loop kan dus ook van hoog naar laag gaan. De grootste bron van fouten bij het opstellen van dergelijke loops is de combinatie van het verkeerd kiezen van de start en eind conditie, tesamen met een verkeerde increment. Het is dus belangrijk om de correctheid van de code na te gaan met de debugger.
Naast enkelvoudige for-loops is het ook mogelijk om for-loops te nesten, een if-structuur binnen een loop te schrijven, of omgekeerd.

WPO 4

Oefeningen werkcollege 4

In WPO 4 wordt het tekenen van allerhande geometrische figuren behandeld. Sommige elementen binnen de toolbox van Visual Studio (zoals een canvas) laten toe om grafische constructies zelf uit te tekenen. De voorbeelden die hieronder aangehaald worden, zijn gebaseerd op de canvas als tekenveld. Het tekenen van objecten verloopt steeds in stappen: het aanmeken van het object met alle eigenschappen en het achteraf toevoegen van dit object aan de canvas.

Hieronder wordt het aanmaken van een aantal object geïllustreerd. Als canvas wordt hier "cvs" gebruikt.

Tekenen van een lijn

// create a line object
Line ln = new Line();
// give the line the red color -> we use stroke for lines and contours
ln.Stroke = new SolidColorBrush(Colors.Red);
// give the line a given width of 5 pixels
ln.StrokeThickness = 5;
// set X and Y coordinates to the line 
ln.X1 = 0;
ln.X2 = cvs.Width;
ln.Y1 = cvs.Height;
ln.Y2 = cvs.Height;

Tekenen van een rechthoek

// create a rectangle object
Rectangle rect = new Rectangle();
// set contour width and color
rect.Stroke = new SolidColorBrush(Colors.GreenYellow);
rect.StrokeThickness = 1;
// position the rectangle against a canvas: 
// here 10,10 pixels from the upper left corner of the canvas
Canvas.SetLeft(rect, 10);
Canvas.SetTop(rect, 10);
// set width and height of our rectangle
rect.Height = 100;
rect.Width = 100;

Tekenen van een cirkel (ellips)

// create an ellips object
Ellipse el = new Ellipse();
// set contour color and width
el.Stroke = new SolidColorBrush(Color.FromArgb(255, 0, 200, 0));
el.StrokeThickness = 5;
// set the ellips width and height, if both are equal we obtain a circle
el.Width = 25;
el.Height = 25;
// position the rectangle against a canvas: 
// here 10,10 pixels from the upper left corner of the canvas
Canvas.SetTop(el, 10);
Canvas.SetLeft(el, 10);

Tekenen van een balk (gevulde rechthoek)

// create a rectangle object
Rectangle rect = new Rectangle();
// set area color
rect.Fill = new SolidColorBrush(Colors.GreenYellow);
// position the rectangle against a canvas: 
// here 10,10 pixels from the upper left corner of the canvas
Canvas.SetLeft(rect, 10);
Canvas.SetTop(rect, 10);
// set width and height of our rectangle
rect.Height = 100;
rect.Width = 100;

Tekenen van een schijf (gevulde ellips)

// create an ellips object
Ellipse el = new Ellipse();
// set area color
el.Fill = new SolidColorBrush(Color.FromArgb(255, 0, 200, 0));
// set the ellips width and height, if both are equal we obtain a circle
el.Width = 25;
el.Height = 25;
// position the rectangle against a canvas: 
// here 10,10 pixels from the upper left corner of the canvas
Canvas.SetTop(el, 10);
Canvas.SetLeft(el, 10);
Nadat deze getekend zijn kunnen die toegevoegd worden aan de canvas. Het toevoegen gebeurt zoals hieronder weergegeven.

Toevoegen van de getekende objecten aan de tekencanvas ("cvs")

// add line to canvas
cvs.Children.Add(ln);
// add ellips to canvas
cvs.Children.Add(el);
// add rectangle to canvas
cvs.Children.Add(rect);

De canvas verwijderen kan met een eenvoudige regel code

cvs.Children.Clear();

Assenstelsel van de canvas

Merk op dat alle objecten (met uitzondering van de lijn) steeds vanuit het linkerhoekpunt van het object getekend worden. Indien het object een hoogte en breedte heeft, dan zal het object vanuit dat punt naar links en naar onder getekend worden. Het assenstelsel van de canvas loopt zoals in de meeste andere programmeeromgevingen van links naar rechts en van boven naar onder toe.

Kleuren

In WPF wordt (toch in de oefeningen) er bijna uitsluitend met een brush gewerkt. In bovenstaande voorbeelden is aangetoond hoe men kleur kan toewijzen aan een object. Het toewijzen van kleuren aan objecten kan op verschillende manieren:
// get a color from 4 gradients 
// Alpha = 255, Red = 0, Green = 255, Blue = 255 -> gives a color between blue and green
// note that the first gradient specefies the transparancy: 
// 0 = fully transparant, 255 = fully opaque
Color clr = Color.FromArgb(255,0,255,255);
// we can also do it without transparancy
clr = Color.FromRgb(0,255,255);
// we can also choose a predefined color from WPF
// note the difference between Color and Colors
clr = Colors.Black;
// assigning the color
rect.Fill = new SolidColorBrush(clr);
el.Fill = new SolidColorBrush(clr);

Het is dus volledig mogelijk om zelf een kleur samen te stellen uit 3 of 4 waarden. Indit geval geeft de eerste waarde de doorzichtigheid (alfa) gevolgd door de rode, groene en blauwe kleurcomponent. Alle componenten zijn hierbij begrensd tussen 0 en 255 (inclusief). Het datatype van de componenten is steeds een byte. Het is dus noodzakelijk om de gewenste typecast te doen indien men vanuit een integer of ander type vertrekt. Het is echter ook mogelijk om vanuit de vooropgestelde kleure van WPF te vertrekken.

WPO 5

Oefeningen werkcollege 5

De while-loop onderscheidt zich van de for-loop in die zin dat er enkel aan een set van voorwaarden moet voldoen worden om een stukje code herhaaldelijk uit te voeren. De algemene vorm van de while-loop ziet er als volgt uit:

while (condition)
{
	//some code	
}

De conditie kan om het even wat zijn, zolang het eindresultaat een boolean is. De (syntax)regels voor de conditie zijn dezelfde als voor een gewone if-structuur. Een bijzondere while-loop is de oneindige loop, en wordt gevormd door:

while (true) //-- true is always true, so loop forever
{
	//some code	
}

Een for-loop kan altijd naar een while-loop omgevormd worden. De for-loop is nl. een gespecialiseerde vorm van de while-loop. Hierbij moet men echter zelf de index (i) van de for-loop in een while-loop bijhouden. Omgekeerd geldt dat een while loop soms ook herschreven kan worden als een for-loop. Dit is echter niet altijd het geval. Een voorbeeld van een omzetting van een for-loop naar een while-loop wordt hieronder weergegeven.

// write 10 number ins a label
for (int i=0;i<10;i++)
{
	lblOutput.Content = lblOutput.Content + " " + i.ToString();
}
// do the same with a while loop now
int i=0;
while(i<10)
{
	lblOutput.Content = lblOutput.Content + " " + i.ToString();
	// we need to do the increment of i ourselves now
	i++;
}

Naast de while loop bestaat er ook de do-while loop. De do-while loop begint eerst met do en eindigt met de while conditie. De code tussen de do en de while zal herhaaldelijk uitgeoverd worden zolang de conditie geldig is. In onderstaand codefragment wordt de do-while gedemonstreerd.

do 
{
	//some code	
}
while(condition==true);

Merk op dat een do-while loop altijd eindigt met een punt-komma. Het grootste verschil met een while-loop is echter het tijdstip wanneer de conditie nagekeken wordt. Bij een do-while wordt de conditie voor het eerst negekeken wanneer de loop minstens éénmaal al uitgevoerd is geweest. Bij een while-loop wordt eerst de conditie nagekeken alvorens de loop 1 keer is uitgevoerd. Het voorbeeldje van hierboven kan in dit geval ook omgevormd worden tot een do-while loop. Deze code zal altijd minstens 1 maal uitgevoerd worden.

int i=0;
do 
{
	lblOutput.Content = lblOutput.Content + " " + i.ToString();	
	i++;
}
while(i<10);

Uiteraard is het mogelijk om, net zoals in een for-loop, een while-loop in een while-loop te nesten. Ook een for-loop in een while-loop, een if-structuur in een while-loop nesten is ook mogelijk. Omgekeerd kan een while-loop op zijn beurt ook genest worden in de andere structuren.

WPO 6

Oefeningen werkcollege 6

Naast de klassieke iteratiemethoden die we tot nu toe hebben gezien kunnen we in C# (WPF) ook gebruik maken van timers. Een timer is een object die op regelmatige tijdsintervallen een voorafbepaalde functie zal oproepen. Hoewel er nergens in de code een loop gedeclareerd hoeft te worden, zal de code die in die bepaalde functie geschreven staat regelmatig uitgevoerd worden. In WPF worden timers aangemaakt via de DispatcherTimer. Het aanmaken en gebruik van de timer wordt hieronder weergegeven. Merk op dat er een speciaal directief bovenaan de code toegevoegd moet worden. Dit wordt eveneens hieronder vermeld.

// put this after the last using... statement above in the code
using System.Windows.Threading;
public partial class MainWindow : Window
{
	// declare our timer as a global variable
	DispatcherTimer timer = new DispatcherTimer();
	public MainWindow()
	{
		InitializeComponent();		
	}
	//-- event handler of a button on the form
	private void btnStart_click(object sender, RoutedEventArgs e)
	{		
		// first clean our label of any content
		lblShow.Content = "";
		// set function to call to our timer
		timer.Tick += timer_Tick;
		// give the interval at which the timer must call our function
		// in this case: 0 days, 0 hours, 0 minutes, 0 seconds and 200 milliseconds
		// frequency is thus 5Hz
		timer.Interval = new TimeSpan(0, 0, 0, 0, 200);
		// start our timer after the cleaning
		timer.Start();		
	}
	//-- the function which will be called by the timer
	//-- code inside this function will be repeated periodically
	//-- (here every 200ms)
	//-- until the timer is stopped:
	//-- timer.Stop();
	private void timer_Tick(object sender, EventArgs e)
	{
		lblShow.Content = lblShow.Content + "X";		
	}
}

Naast de timers zullen we vandaag een nieuwe beslissingsstructuur ontdekken: de switch case. Switch cases zijn naast de if-structuren een manier om het verloop van het programma te bepalen. De switch case structuur is een specialisatie op de if structuur. Bij de switch case is het enkel mogelijk om op gelijkheid te evalueren. Een tweede beperking op de if-structuur wordt gevormd doordat er enkel met voorafbepaalde waarden vergeleken kan worden! Ondanks deze beperkingen wordt de switch case gebruikt om de code meer leesbaar te maken (net zoals de for-loop). M.a.w. het is altijd mogelijk om een switch case om te vormen naar een if-structuur. Omgekeerd kan alleen als aan de beperkingen voldaan wordt. Een voorbeeld van een switch case wordt hieronder weergegeven.

//-- usage of a switch case structure
//-- first decalre necessary variables
int value = 10;
//-- do not forget the brackets!
switch (value)
{
	//-- use "case" + value + ":"
	//-- note that a function can not be used
	//-- to compare against!
	case 0:
		//-- do something when value equals 0
		//-- can be a statement of function
		//-- or else
		//-- end with the break statement
		break;
	case 5:
		//-- do something when value equals 5
		//-- can be a statement of function
		//-- or else
		//-- end with the break statement
		break;
	case 10:
		//-- do something when value equals 10
		//-- can be a statement of function
		//-- or else
		//-- end with the break statement
		break;
	default:
		//-- do something when previous values ar not met
		//-- can be a statement of function
		//-- or else
		//-- end with the break statement
		break;
}

De variabele dat hier gebruikt is, is van het type integer. De programmeur kan hier in principe zelf het (enkelvoudige) type kiezen, maar de integer, string, char en enums worden het vaakst gebruikt. Functies kunnen niet gebruikt worden om mee te vergelijken binnen de switch case (dus case function), enkel een voorafbepaalde waarde!

Als laatste zal een introductie gegeven worden tot de combobox. Een combobox is een dropdown element dat toelaat om een bepaalde selectie te kiezen. Het gebruik van een combobox gebeurt door deze eerst in te vullen. nadien kan men, indien een waarde geselecteerd is, de waarde bepalen via de selected index eigenschap. Het geheel wordt hieronder weergegeven.

// Fill in the combobox with a few values (string only). 
cmbBox.Items.Add("Print in volgorde af");
cmbBox.Items.Add("Print in omgekeerde volgorde af");
cmbBox.Items.Add("Print randomwaarden af");

De juiste waarde uit de combobox opvissen kan als volgt:

// Code that resides in a button 
int index = cmbBox.SelectedIndex;
switch (index)
{
	case -1: // when no value has been selected (no mouse event on the combobox
		MessageBox.Show("Ongeldige keuze");
		break;
	case 0: // first element of the itmes in a combobox
		MessageBox.Show("Print in volgorde af");
		break;
	case 1:
		MessageBox.Show("Print in omgekeerde volgorde af");
		break;
	case 2:
		MessageBox.Show("Print randomwaarden af");
		break;
	default: // out ofrange for the combobox
		MessageBox.Show("Geen geldige waarde gekozen");
		break;
			
} 

De index van het geselecteerde item van de combobox kan ook gebruikt worden elementen uit een array/lijst aan te spreken (zie later).

WPO 7

Oefeningen werkcollege 7

Tijdens dit WPO worden de beginselen van meervoudige variabelen gezien. Collecties vormen hierbij een belangrijke constructie om een reeks bij elkaar horende waarden onder dezelfde naam in te kapselen. Hoewel het lijkt dat het om een gewone variabele gaat, bevat deze variabele een bepaald aantal waarden. Onder de noemer van collecties zullen we tijdens de WPO's 2 structuren zien: de arrays en de lijsten.

  • Arrays: bevatten een vast aantal elementen. Het aantal elementen wordt vastgelegd tijdens het declareren van de array.
  • Lijsten: bevatten een veranderlijk aantal elementen. Een lijst wordt leeg gedeclareerd en het aantal elementen kan veranderen tijdens de uitvoering van het programma.

Arrays kunnen zowel 1D, 2D en hoger dimensionaal zijn. Tijdens dit WPO zullen we ons enkel focussen op 1D arrays. Het gebruik van lijsten en arrays wordt in onderstaande codefragmenten weergegeven. Merk op dat net zoals gewone variabelen, arrays en lijsten van om het even welk datatype aangemaakt kunnen worden. Bij arrays en lijsten geldt dat de elementen aangesproken worden a.d.h.v. een index. Deze index start altijd bij 0 en eindigt bij het aantal elementen -1 (met een loop doorgaan zolang de index kleiner is dan de lengte dus).

// start with declaring an array.
// make an array of 10 floating point values
float[] float_array = new float[10];

// assign values to each element of the array
// the index i goes from 0 to 9, having thus 10 elements
// the value equals here i at position i, but can be anything else!
for (int i=0;i<10;i++)
{
	float_array[i] = i;
}
// read the values and print them in a textbox
for (int i=0;i<float_array.Length;i++)
{
	txtoutput.Text = txtoutput.Text + " " + float_array[i];
}
// Start with declaring a list of a given type.
// when declaring and initializing, the type 
// is given between the brackets. Do not forget the 
// round brackets at the end before ';' 
List<float> float_list = new List<float>();

// Add the values into the list
// the index i goes from 0 to 9, having thus 10 elements
for (int i=0;i<10;i++)
{
	float_list.Add(i);
}
// read the values and print them in a textbox
// the values can be read as if the list were an array
// go from zero up to the amount of elements in the list -1!
for (int i=0;i<float_list.Count;i++)
{
	txtoutput.Text = txtoutput.Text + " " + float_list[i];
}

WPO 8

Oefeningen werkcollege 8

In dit WPO zullen we de basis leren van gestructureerd programmeren. Tot hier toe werd alle code in de event-handler van een button geplaatst. Echter is deze techniek niet geschikt om grotere programma's te schrijven. In wat volgt zullen we de code opdelen in kleinere fragementen die we in procedures en functies zullen onderbrengen. In WPO 8 zullen we de procedures zien. In WPO 9 volgen de functies.

Procedures zijn methoden die toelaten om veelgebruikte code onder te brengen onder eenzelfde naam (bv. tekenRechthoek). Procedures bestaan uit 4 grote delen:

  • De naam van de procedure: dit is de naam die net de ronde haken voorafgaat.
  • De argumenten van de procedure: dit zijn de waarden die binnen de ronde haken worden gegeven. Deze kunnen bekeken worden als lokale variabelen die reeds ingevuld zijn. Elk argument wordt voorafgegaan door een datatype.
  • Een procedure geeft nooit een waarde terug. Daarom begint een procedure altijd met "void" (letterlijk vertaald: niets).
  • Hiernaast bevat een procedure ook een aantal commando's. Die staan altijd tussen het daaropvolgende paar accoladen. Dit is de code die op verschillende andere plaatsen in de het programma opgeroepen zal worden.

Hieronder wordt een voorbeeld van een procedure weergegeven.

// start a procedure with void
// than after a space follows the name of the procedure
// open the round brackets and write the arguments
private void tekenDriehoek(int x1, int y1, int x2, int y2, int x3, int y3,Color kleur)
{
	Line ln = new Line();
	ln.Stroke = new SolidColorBrush(kleur);
	ln.StrokeThickness = 1;
	ln.X1 = x1; ln.Y1 = y1; ln.X2 = x2; ln.Y2 = y2;
	cvsDraw.Children.Add(ln);

	ln = new Line();
	ln.Stroke = new SolidColorBrush(kleur);
	ln.StrokeThickness = 1;
	ln.X1 = x2; ln.Y1 = y2; ln.X2 = x3; ln.Y2 = y3;
	cvsDraw.Children.Add(ln);
	
	ln = new Line();
	ln.Stroke = new SolidColorBrush(kleur);
	ln.StrokeThickness = 1;
	ln.X1 = x3; ln.Y1 = y3; ln.X2 = x1; ln.Y2 = y1;
	cvsDraw.Children.Add(ln);	            
}

Bovenstaande procedure kan aangeroepen worden zoals afgebeeld in onderstaand codefragment.

// call the procedure as follows (draw 3 triangles)
tekenDriehoek(0,0,100,100,0,100,Colors.Black);
tekenDriehoek(200,200,250,200,100,100,Colors.Red);
tekenDriehoek(500,600,250,250,400,400,Colors.Yellow);

Een procedure laat dus toe om gelijkaardige code te parametriseren en onder te brengen onder 1 naam (naam van de procedure). Procedures worden daarna vanop verschillende plaatsen in het programma opnieuw opgeroepen. Deze techniek laat toe om codeduplicatie te vermijden. Bovendien wordt op die manier gekende en geverifieerde code beschikbaar gemaakt in de rest van een programma terwijl dat fouten t.g.v. codeduplicatie vermeden worden.

WPO 9

Oefeningen werkcollege 9

In dit WPO worden de functies behandeld. Functies lijken enorm hard op procedures en om deze reden is de uitleg ervan grotendeels gelijklopend. Het grote verschil met procedures is dat een functie altijd een return statement heeft. Functies bestaan in tegenstelling tot procedures uit 5 grote delen:

  • De naam van de functies: dit is de naam die net de ronde haken voorafgaat.
  • De argumenten van de functie: dit zijn de waarden die binnen de ronde haken worden gegeven. Deze kunnen bekeken worden als lokale variabelen die reeds ingevuld zijn. Elk argument wordt voorafgegaan door een datatype.
  • Een functie geeft altijd een waarde terug. Daarom begint een procedure altijd met een datatype. Dit kan een integer, float, double, enz. zijn.
  • Hiernaast bevat een functie ook een aantal commando's. Die staan altijd tussen het daaropvolgende paar accoladen. Dit is de code die op verschillende andere plaatsen in de het programma opgeroepen zal worden.
  • Als laatste moet de functie ook een waarde teruggeven. Dit wordt aangeduid met de statement "return value". Het datatype van value komt overeen met de datatype die vlak voor de naam van de functie staat. Een return-statement beëindigt altijd het verloop van de functie. Dit geldt ook voor procedures waar er na de return-statement geen waarde staat! Alle code die na een return-statement staat wordt nooit uitegevoerd.

Hieronder wordt een voorbeeld gegeven van een functie die de som van 2 getallen retourneert.

// write a function to sum two values
private int Sum(int value1, int value2)
{
	int s = 0;
	s = value1 + value2;
	// return the value, function will exit here even if 
	// there is code behind the return statement
	return s;
}

De functie kan ook aangeroepen worden. Merk op dat een functie een waarde teruggeeft. Dit betekent dus dat we deze waarde eerst in een variabele moeten opslaan. Dit gebeurt op dezelfde manier als het toewijzen van een waarde naar een variabele. Het verschil is echter dat de functie nu rechts van het gelijkheidsteken staat. De datatype van de variabele moet overeenkomen met de return-statement.

// first declare a variable for both input values
int val1 = 5;
int val2 = 10;
// declare a variable for the return-statement
int ret;
ret = Sum(val1,val2); // gives 15

WPO 10

Oefeningen werkcollege 10

Als laatste zullen we meerdimensionale arrays behandelen. We zullen ons in dit WPO beperken tot 2D arrays. het gebruik van hogere dimensies verloopt op dezelde manier. In tegenstelling tot lineaire arrays, ttz. arrays met 1 enkele index, hebben meerdimensionale arrays verschillende indices. Hierbij is het dus heel belangrijk bij te houden welke dimensie welke informatie bijhoudt. Over het algemeen worden meerdimensionale arrays met meer dan 3 dimensies niet gebruikt.

Een voorbeeld van meerdimensionale arrays zijn de afbeeldingen in bitmap-formaat. In een afbeelding wordt de informatie in een 3D array bijgehouden. Dankzij de eerste 2 indices kan men de een pixel op een bepaalde x- en y-positie aanspreken. De 3de dimensie van de array geeft de kleur van die ene pixel weer (R,G,B).

Het gebruik van een meerdimensionale array verschilt in weze niet veel van die van een lineaire array. Het enige verschil is het kunnen aanpreken van de verschillende dimensies. Dit wordt weergegeven in onderstaand codefragment.

// create a 2D array of 100 elements, note the ',' inside the brackets
double[,] arr = new double[10,10];
// fill our array with some values
for (int i=0;i<arr.GetLength(0);i++)
{
	for (int j=0;j<arr.GetLength(1);j++)
	{
		arr[i,j] = i+j;
	}
}

Merk op dat we voor elke dimensie een aparte for loop gebruiken. Omdat we over een meerdimensionale array itereren, moeten we voor elke dimensie de lengte opvragen. Dit doen we door de methode "GetLength(int dimensie)" op te vragen. Deze methode retourneert hoeveel cellen er voor die dimensie aanwezig zijn. Om elke dimensie aan te spreken volstaat het om de indices te scheiden door komma's. Elke komma wijst op een extra dimensie.

Het doorgeven van van meerdimensionale arrays aan functies verloopt op een heel gelijkaardige manier als het doorgeven van een lineaire. Dit wordt in onderstaande codefragment geïllustreerd.

//------------------------------------------
private void processArray(double[,] arr)
{
	...
}
//------------------------------------------
private double[,] returnArray()
{
	double[,] arr = new double[10,10];
	...
	return arr;
}
//------------------------------------------

Meerdimensionale arrays kunnen uiteraard aangemaakt worden van eender welk datatype.

Een laatste stap dat we hier willen behandelen zijn de null-waarden. Arrays kunnen in het algemeen ook gelijkgesteld worden aan null. Null in C# komt overeen met "het bestaat niet". Dit kan een handig mechanisme zijn indien men foutieve waarden uitkomt in een functie en dus niets wilt/kan retourneren. Een eenvoudige test op null laat toe om na te gaan of de array effectief geldige data bevat. Dit wordt hieronder weergegeven.

//------------------------------------------
private double[,] returnArray()
{
	double[,] arr = new double[10,10];
	// an error occured during calculations, so make the array null
	arr = null;
	return arr;
}
//------------------------------------------
private void callerFunction()
{
	double[,] arr = returnArray();
	if (arr!=null)
	{
		// do something with the array
	}
	else
	{
		MessageBox.Show("The array does not exist!");
	}
}

WPO 11

Oefeningen werkcollege 11

Een aantal elementaire concepten zijn intussen aan bod gekomen in de voorgaande WPO's. Om de basis van het programmeren te vervolledigen zullen we een aantal concepten introduceren die het leven van een (beginnende) programmeur soms kunnen vereenvoudigen:

  • Doorgeven van argumenten "by reference".
  • Doorgeven van argumenten "by out".
  • Arrays doorgeven als "by reference".
  • Recursie.
  • Het opdelen van code in verschillende codebestanden.

De eerste 2 elementen worden in heel veel programmeertalen toegelaten. Vaak is er hier extra syntax nodig om dit te bewerkstelligen. Het concept achter het doorgeven van argumenten "by reference"/"by out" is dat men op die manier virtueel over meerdere return-statements kan beschikken. Een gewone functie retourneert maximaal 1 enkel waarde via de return-statement. Door argumeten bidirectioneel te maken kan men meer dan 1 return waarde bekomen. Het gebruik van by reference moet echter met de grootste voorzorg gedaan worden om zodoende ongewenste fouten te vermijden. Gebruik deze constructies dus enkel waar ze echt nodig zijn. Tussen "by reference" en "by out" is er een subtiel verschil. In het eerste geval moet het argument al een waarde toegewezen hebben gekregen voordat deze aan de functie meegegeven wordt. In het 2de geval is dit niet nodig (out van output). Het gebruik van "by reference"/"by out" wordt in onderstaande codefragment aangetoond.

//------------------------------------------
private void changevalue(ref int value)
{
	// multiply our value by 2
	value = value*2; 
	// no return needed -> pass by reference
}
//------------------------------------------
private void callerFunction()
{
	// we first need declare our variable and assign a value to it
	int val = 5;
	changevalue(ref val);
	MessageBox.Show(val.ToString());
}
//------------------------------------------
private void setValue(out int value)
{
	value = 10; 
	// no return needed -> pass by reference
}
//------------------------------------------
private void callerFunction()
{
	// we first need declare our variable;
	// do not assign a value to it
	int val;
	changevalue(out val);
	MessageBox.Show(val.ToString());
}

In beide gevallen moeten de variabelen gedeclareerd zijn voordat de functie opgeroepen wordt. Het argument wordt steeds voorafgegaan door het woordje out of ref, en dit zelfs voordat de datatype geschreven wordt. Ook in het geval dat de functie opgeroepen wordt, moeten de woorden out of ref gebruikt worden zoals de voorbeelden het aangeven.

Indien men een enkelvoudige variabele "by reference" of "by out" wilt doorgeven, dan is men verplicht om de extra sleutelwoorden te gebruiken. Ook arrays en lijsten kunnen "by reference" doorgegeven worden. Hier moet men echter niets extra voor schrijven. Arrays en lijsten worden impliciet "by reference" doorgegeven.

//------------------------------------------
private void changeArray(int[] arr)
{
	// arrays are implicitely passed by reference
	for (int i = 0; i < arr.Length; i++)
	{
		arr[i] = 2 * arr[i];
	}
}
//------------------------------------------
private void callerFunction()
{
	// we first need declare our array and assign values to it;
	int[] values = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	changeArray(values);
}
//------------------------------------------
private void changeList(List<int> arr)
{
	for (int i = 0; i < arr.Count; i++)
	{
		arr[i] = 2 * arr[i];
	}
}
//------------------------------------------
private void callerFunction()
{
	//first declare and populate our list!!
	List<int> values = new List<int>();
	for (int i = 0; i < 11; i++) values.Add(i);
	// now we can change the existing values in this list
	changeList(values);
}

Het doorgeven van een array of lijst "by out" kan, maar wordt veel minder gebruikt. Dit wordt hier niet verder toegelicht.

Naast het doorgeven van parameters "by reference"/"by out" kan het soms nuttig zijn om een algoritme op recursieve wijze te implementeren. Recursie wordt bereikt doordat een functie zichzelf oproept. Dit oproepen kan rechtstreeks als onrechtstreeks gebeuren. IN het eerste geval roept de functie zichzelf als functie terug op. In het 2de geval roept de functie een andere functie op, die op zijn beurt de originele functie oproept. Een voorbeeld van recursie wordt hieronder weergegeven.

//------------------------------------------
private int factorial(int value)
{
	if (value>=2) 
		return value*factorial(value-1); // continue recursion here
	else
		return 1; // stop recursion here
}
//------------------------------------------
private void RunFactorial()
{
	MessageBox.Show("Factorial of 10 = " + factorial(10));
}

Als laatste willen we nog de beginselen van het opdelen van code in verschillende codebestanden meegeven. Het zal de aandachtige student al zijn opgevallen dat de code die we schrijven al snel 100 lijnen of meer in beslag neemt. Ondanks de pogingen om de code in aparte functies onder te brengen zal dit alsnog tot een warboel uitgroeien. Om dit tegen te gaan kan men bij elkaar horende blokken code onderbrengen in een apart bestand (klasse). Men kan dan vanuit de hoofdcode de code uit die bestanden aanspreken via functies om zodoende het gewenste resultaat te bekomen. De functies die we hier zullen aanspreken zullen we voortaan methoden noemen. Voor de eenvoud worden hier enkel static-methoden gebruikt. Andere methoden bestaand ook, maar komen in de cursus van objectgeoriënteerd programmeren aan bod. Het gebruik van een dergelijke constructie wordt in onderstaande codefragmenten aangetoond met een aantal functies die het tekenen vergemakkelijken.

// write here a class with static methods which can be access from the main code 
// this code does reside in another file than the code of the main window
public class Drawings
{
	//---------------------------------------------------------------------------------------------------
	public static void DrawLine(double x1, double y1, double x2, double y2, Color clr, Canvas cvsDraw)
	{
		// put here the code to draw a line on the canvas 
	}
	//---------------------------------------------------------------------------------------------------
	public static void DrawRectangle(double x1, double y1, double w, double h, Color clr, Canvas cvsDraw)
	{
		// put here the code to draw a rectangle on the canvas 
	}
	//---------------------------------------------------------------------------------------------------
	public static void DrawEllips(double x1, double y1, double w, double h, Color clr, Canvas cvsDraw)
	{
		// put here the code to draw an ellips on the canvas 
	}
	//---------------------------------------------------------------------------------------------------
}

Het oproepen van deze functies vanuit een knop op de mainwindow geschiedt als volgt:

// this code is in the function of a button
Drawings.DrawLine(10,10,100,100,Colors.Red,cvsDraw);
Drawings.DrawRectangle(100,100,150,150,Colors.Blue,cvsDraw);

Merk op dat de methode-oproepen nu voorafgegaan worden door de naam van de klasse, nl. Drawings. Om de methoden aanspreekbaar te maken van buiten de klasse Drawings moeten deze public static staan. In andere gevallen zullen de methoden mogelijks niet aanspreekbaar (en dus niet te gebruiken) zijn.

WPO 12

Oefeningen werkcollege 12

In dit WPO zullen we de basis leggen van complexe datatypes. Complexe datatypes laten toe om verschillende variabelen die bij elkaar horen onder te brengen in eenzelfde datacontainer. Een voorbeeld hiervan is een punt in 2D die uit een x- en een y-component bestaat.

De declaratie van een complex datatype (struct) voor een 2D punt wordt in onderstaand codefragment getoond.

public struct Point2D
{
	public double x;
	public double y;	
}

Van hieruit kan een nieuwe variabele aangemaakt worden. Dit gebeurt als volgt:

// declare a point from the struct and make it new (think of an object)
Point2D p = new Point2D();

Waarden aan de subvariabelen toekennen verloopt op een gelijkaardige wijze als het toekennen van waarden aan gewone variabelen. Het enige verschil is dat men eerst de container moet aanspreken voordat men de subvariabele aanspreekt. Zie onderstaand codefragment.

p.x = 15;
p.y = 150;

De waarden uit deze variabele halen gebeurt op een omgekeerde werkwijze, nl.:

double xpos = p.x;
double ypos = p.y;

Merk op dat de variabele p hier steeds gedaclareerd en geïnstantieerd moet worden volgens het type van de struct. Alle velden (subvariabelen) van de struct worden ook public gedeclareerd zodat men er rechtstreeks via de code aankan.

WPO 13

Oefeningen werkcollege 13

Bijlagen werkcollege 13

Als laatste zullen we het gebruik van bestanden bespreken. In onderstaande codefragmenten wordt aangehaald hoe de verschillende operaties (read,write en append) tewerk gaan. De append-methode is een variant op het gewone schrijven, met als voornaamste verschil dat de append automatisch data achteraan het bestand toevoegt, terwijl het gewoon schrijven naar een bestand eerst het bestand volledig wist, en dan pas data in het bestand schrijft. De append-methode komt overeen met een += operatie bij een string.

private void btnRead_Click(object sender, EventArgs e)
{
	// Open the file to read from.
	try
	{
		// generate string for the current file name
		// note the '@' at the beginning
		string path = @"E:\test.txt";
		using (StreamReader sr = File.OpenText(path))
		{
			string s = "";
			// read line by line until the end of file (s==null) 
			// has been reached
			while ((s = sr.ReadLine()) != null)
			{
				// write the current line in a textbox.
				txtOutput.Text += s + "\r\n";
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}
// write data to a file
private void btnWrite_Click(object sender, EventArgs e)
{
	try
	{
		string path = @"E:\test.txt";
		using (StreamWriter sw = File.CreateText(path))
		{
			sw.Write(txtOutput.Text);                    
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}
// append data to a file
private void btnAppend_Click(object sender, EventArgs e)
{
	try
	{
		string path = @"E:\test.txt";
		using (StreamWriter sw = File.AppendText(path))
		{
			sw.Write(txtOutput.Text);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

Merk op dat de strings die gebruikt worden als paden voor het filesysteem altijd voorafgegaan worden door een apenstaart. Paden worden in Windows vaak met het teken "\" aangeduid. Dit teken is echter ook een stringcommando in C# (\n, \r, \t). Om het stringcommando uit te schakelen wordt de string voorafgegaan door dit apenstaart. Een andere manier is om echter die commando uit te schakelen door een dubbele slash te gebruiken ("\\"). In dat geval wordt het apenstaart vooraan weggelaten. Hieronder wordt dezelfde string gebruikt op de 2 manieren.

// first way of decalring a string for path
string path = @"E:\Bijlagen_WPO13\test.txt";
// second way to do it
string path = "E:\\Bijlagen_WPO13\\test.txt";

CSV-formaat

Lezen, schrijven en data toevoegen zijn de basisbehandelingen die men kan uitvoeren op een bestand. Indien men veel data met een regelmatig patroon wenst op te slaan, kan het handig worden om een bepaalde opslagconventie te hanteren. Een voorbeeld van zo'n conventie is het CSV-formaat. Het CSV-formaat ("comma separated values") wordt gekenmerkt door het opslaan van data die gescheiden zijn door komma's. Door op elke regel maar een bepaalde set van waardes op te slaan, en dit zo op verschillende regels te hanteren, kan men grote datasets aanmaken. Zo bijvoorbeeld kunnen meetresultaten opgeslagen worden. Het CSV-formaat wordt als volgt toegepast: de eerste rij bevat de namen van de kolommen, waarij de namen gescheiden zijn door komma's. De volgende rijen bevatten de data (numerieke data, tekst, enz.), eveneens gescheiden door komma's.

Het lezen uit zo'n bestand verschilt niet veel van het lezen uit een random tekstbestand. Lees eerst regel per regel in. Als dit lukt, is de volgende stap het afzonderen van de data van de komma's. Hiervoor kan je de methode "Stringsplit" toepassen (met de komma als delimiter). Deze methode retourneert een aantal strings terug, waarbij het aantal overeenkomt met het aantal data per regel. Nu is het de moment om al deze afzonderlijke strings naar het juiste datatype om te zetten (int, float, double, enz.). De volgorde van de data (plaats in de regel) is hier zeer belangrijk, en wordt bepaald door de kolomnaam ("Caption").

Het schrijven naar een CSV-bestand kan op een eenvoudige wijze plaatsvinden. Voor elke regel maak je een lege string aan. Deze vul je op door alternerend de juiste waarde van van huidige kolom erin te schrijven, tesamen met een komma. Elke regel (string) sluit je af met een newline! Deze newline wordt bij het lezen gebruikt als einde voor de "Readln" methode, en duidt ook aan dat een gegeven dataset afgesloten is. Via de StreamWriter kan je de string toevoegen aan het bestand. Vergeet niet: de eerste regel bevat de kolomnamen!

Als voorbeeld wordt het uitlezen van een CSV-bestand gedemonstreerd. De code berekent het gemiddelde van alle waarden op eenzelfde lijn die door een kommas gescheiden worden.

// Open the file to read from.
try
{
	// generate string for the current file name
	// note the '@' at the beginning
	string path = @"E:\Bijlagen_WPO13\test.txt";
	using (StreamReader sr = File.OpenText(path))
	{
		string s = "";
		int linenumber = 1;
		// read line by line until the end of file (s==null) 
		// has been reached
		if ((s = sr.ReadLine())!=null)
		{
			// read caption here
		}
		while ((s = sr.ReadLine()) != null)
		{
			// split the values by ','
			csv_values = s.Split(',');
			int sum=0;
			for (int i=0;i<csv_values.Length;i++)
			{
				int value = int.Parse(csv_values[i]);
				sum+=value;
			}
			int average = sum/csv_values.Length;
			txtOutput.Text += "Average on line " + linenumber + " = " + average.ToString() + "\n";
			linenumber++;
		}
	}
}
catch (Exception ex)
{
	MessageBox.Show(ex.Message);
}

WPO 14

Tijdens deze les wordt een finale demo getoond waarin duidelijk wordt gemaakt hoe de samenhang van de verschillende aangeleerde programmatorische onderdelen tot een finaal programma kan leiden.

Daarom wordt tijdens deze les "The matrix" als screensaver-programma gedemonstreerd.

De finale oplossing (Visual Studio solution) kan hier gedownload worden. De map bevat de Visual Studio solution tesamen met de gepaste font ("matrix.ttf"). Het volstaat om de font te installeren in Windows om het effect in de screensaver te zien. In de submap "the_matrix\the_matrix\bin\Debug" kan het uivoerbaar bestand "the_matrix.exe" gevonden worden. Dit bestand kan ingesteld worden als echte Windows screensaver door de .exe extensie eerst te veranderen naar .scr. Een rechtermuisklik op dit bestand gevolgd door een install laat toe om dit ook effectief te doen. Via deze link kan meer informatie gevonden worden.

De screensaver die gemaakt werd in 2019 (vuurwerk) kan je hier vinden