7. Grafische gebruikersinterfaces met C# en VS.NET
7.1. De basisprincipes van grafische gebruikersinterfaces
7.1.1. Een eerste project
Laten we een eerste project van het type "Windows-toepassing" bouwen:
![]() |
- [1]: een nieuw project aanmaken
- [2]: van het type Windows-toepassing
- [3]: de naam van het project doet er op dit moment niet toe
- [4]: het project is aangemaakt
![]() |
- [5]: de huidige oplossing opslaan
- [6]: projectnaam
- [7]: map van de oplossing
- [8]: naam van de oplossing
- [9]: er wordt een map aangemaakt voor de oplossing [Chap5]. De projecten daarvan komen in submappen te staan.
![]() |
- [10]: het project [01] in de oplossing [Chap5]:
- [Program.cs] is de hoofdklasse van het project
- [Form1.cs] is het bronbestand dat het gedrag van het venster regelt [11]
- [Form1.Designer.cs] is het bronbestand dat de informatie over de componenten van het venster [11] zal inkapselen
- [11]: het bestand [Form1.cs] in de ontwerpmodus (design)
- [12]: de gegenereerde toepassing kan worden uitgevoerd met (Ctrl-F5). Het venster [Form1] wordt weergegeven. Dit venster kan worden verplaatst, vergroot of verkleind en gesloten. We beschikken dus over de basiselementen van een grafisch venster.
De hoofdklasse [Program.cs] ziet er als volgt uit:
using System;
using System.Windows.Forms;
namespace Chap5 {
static class Program {
/// <summary>
/// Het belangrijkste toegangspunt voor de toepassing.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
- regel 2: applicaties met formulieren gebruiken de naamruimte System.Windows.Forms.
- regel 4: de oorspronkelijke naamruimte is hernoemd naar Chap5.
- regel 10: bij het uitvoeren van het project (Ctrl-F5) wordt de methode [Main] uitgevoerd.
- regels 11-13: de klasse Application behoort tot de naamruimte System.Windows.Forms. Deze bevat statische methoden om grafische Windows-toepassingen te starten en te stoppen.
- regel 11: optioneel – maakt het mogelijk om verschillende visuele stijlen toe te kennen aan de besturingselementen die op een formulier zijn geplaatst
- regel 12: optioneel – stelt de weergave-engine voor de tekst van de besturingselementen in: GDI+ (true), GDI (false)
- regel 13: de enige onmisbare regel van de methode [Main]: maakt een instantie aan van de klasse [Form1], de klasse van het formulier, en vraagt deze om uit te voeren.
Het bronbestand [Form1.cs] ziet er als volgt uit:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
}
}
- regel 5: de klasse Form1 is afgeleid van de klasse [System.Windows.Forms.Form], de bovenliggende klasse van alle vensters. Het trefwoord partial geeft aan dat de klasse gedeeltelijk is en kan worden aangevuld met andere bronbestanden. Dat is hier het geval: de klasse Form1 is verdeeld over twee bestanden:
- [Form1.cs]: waarin het gedrag van het formulier is vastgelegd, met name de gebeurtenishandlers
- [Form1.Designer.cs]: waarin de formuliercomponenten en hun eigenschappen zijn opgenomen. Dit bestand wordt telkens opnieuw gegenereerd wanneer de gebruiker het venster in de modus [conception] wijzigt.
- regels 6-8: de constructor van de klasse Form1
- regel 7: roept de methode InitializeComponent aan. We zien dat deze methode niet aanwezig is in [Form1.cs]. Deze is te vinden in [Form1.Designer.cs].
Het bronbestand [Form1.Designer.cs] ziet er als volgt uit:
namespace Chap5 {
partial class Form1 {
/// <summary>
/// Vereiste ontwerpvariabele.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Ruim alle gebruikte bronnen op.
/// </summary>
/// <param name="disposing">true als beheerde bronnen moeten worden vrijgegeven; anders false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#door Windows Form Designer gegenereerde code
/// <summary>
/// Vereiste methode voor Designer-ondersteuning – niet wijzigen
/// de inhoud van deze methode niet wijzigen met de code-editor.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(196, 98);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
}
}
- regel 2: het gaat nog steeds om de klasse Form1. Merk op dat het niet langer nodig is te herhalen dat deze is afgeleid van de klasse Form.
- regels 25-37: de methode InitializeComponent die wordt aangeroepen door de constructor van de klasse [Form1]. Deze methode zal alle componenten van het formulier aanmaken en initialiseren. Ze wordt bij elke wijziging van het formulier opnieuw gegenereerd in de modus [conception]. Er wordt een sectie, genaamd région, aangemaakt om het formulier af te bakenen (regels 19-39). De ontwikkelaar mag geen code toevoegen in dit gebied: deze wordt bij de volgende regeneratie overschreven.
Het is in eerste instantie eenvoudiger om geen aandacht te besteden aan de code van [Form1.Designer.cs]. Deze wordt automatisch gegenereerd en is de vertaling in C# van de keuzes die de ontwikkelaar maakt in de modus [conception]. Laten we een eerste voorbeeld bekijken:
![]() |
- [1]: selecteer de modus [conception] door te dubbelklikken op het bestand [Form1.cs]
- [2]: klik met de rechtermuisknop op het formulier en kies [Properties]
- [3]: het eigenschappenvenster van [Form1]
- [4]: de eigenschap [Text] vertegenwoordigt de titel van het venster
- [5]: de wijziging van de eigenschap [Text] wordt zowel in de modus [conception] als in de broncode meegenomen [Form1.Designer.cs]:
private void InitializeComponent() {
this.SuspendLayout();
...
this.Text = "Mon 1er formulaire";
...
}
7.1.2. Een tweede project
7.1.2.1. Het formulier
We starten een nieuw project met de naam 02. Hiervoor volgen we de eerder beschreven procedure voor het aanmaken van een project. Het te maken venster is als volgt:
![]() |
Het formulier bestaat uit de volgende onderdelen:
nr. | naam | type | rol |
1 | labelSaisie | Label | een omschrijving |
2 | textBoxSaisie | TextBox | een invoerveld |
3 | buttonAfficher | Knop | om de inhoud van het invoerveld in een dialoogvenster weer te geven textBoxSaisie |
Dit venster kan als volgt worden opgebouwd:
![]() |
- [1]: klik met de rechtermuisknop op het formulier buiten een component en kies de optie [Properties]
- [2]: het eigenschappenvenster verschijnt in de rechterbenedenhoek van Visual Studio
Enkele opmerkelijke eigenschappen van het formulier:
om de achtergrondkleur van het venster in te stellen | |
om de kleur van afbeeldingen of tekst in het venster in te stellen | |
om een menu aan het venster te koppelen | |
om het venster een titel te geven | |
om het type venster vast te stellen | |
om het lettertype van de tekst in het venster in te stellen | |
om de naam van het venster vast te leggen |
Hier stellen we de eigenschappen Text en Name in:
Invoervelden en knoppen - 1 | |
frmSaisiesBoutons |
![]() |
- [1]: kies de toolkit [Common Controls] uit de toolkits die door Visual Studio worden aangeboden
- [2, 3, 4]: dubbelklik achtereenvolgens op de componenten [Label], [Button] en [TextBox]
- [5]: de drie componenten staan op het formulier
Om de componenten correct uit te lijnen en op maat te maken, kunt u de elementen van de werkbalk gebruiken:
![]() | |||||||||
Het principe van het opmaken is als volgt:
- selecteer de verschillende componenten die u samen wilt opmaken (houd de Ctrl-toets ingedrukt terwijl u op de verschillende componenten klikt om ze te selecteren)
- kies het gewenste opmaaktype:
- (vervolg)
- met de opties Align kunt u componenten uitlijnen aan de boven-, onder-, linker- of rechterkant, of in het midden
- met de opties ‘Make Same Size’ kunnen componenten dezelfde hoogte of dezelfde breedte krijgen
- met de optie Horizontal Spacing kunt u componenten horizontaal uitlijnen met tussenruimtes van gelijke breedte. Hetzelfde geldt voor de optie Vertical Spacing om componenten verticaal uit te lijnen.
- Met de optie Center kunt u een component horizontaal (Horizontally) of verticaal (Vertically) in het venster centreren
Zodra de componenten zijn geplaatst, stellen we hun eigenschappen in. Klik hiervoor met de rechtermuisknop op de component en kies de optie Properties:
![]() |
- [1]: selecteer de component om het eigenschappenvenster te openen. Wijzig daarin de volgende eigenschappen: name: labelSaisie, text: Saisie
- [2]: ga op dezelfde manier te werk: name: textBoxSaisie, text: laat leeg
- [3]: name: buttonAfficher, text: Afficher
- [4]: het venster zelf: naam: frmSaisiesBoutons, tekst: Invoervelden en knoppen - 1
- [5]: voer het project uit (Ctrl-F5) om een eerste voorbeeld van het venster in werking te zien.
Wat in de modus [conception] is gedaan, is vertaald naar de code van [Form1.Designer.cs]:
namespace Chap5 {
partial class frmSaisiesBoutons {
...
private System.ComponentModel.IContainer components = null;
...
private void InitializeComponent() {
this.labelSaisie = new System.Windows.Forms.Label();
this.buttonAfficher = new System.Windows.Forms.Button();
this.textBoxSaisie = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// labelSaisie
//
this.labelSaisie.AutoSize = true;
this.labelSaisie.Location = new System.Drawing.Point(12, 19);
this.labelSaisie.Name = "labelSaisie";
this.labelSaisie.Size = new System.Drawing.Size(35, 13);
this.labelSaisie.TabIndex = 0;
this.labelSaisie.Text = "Saisie";
//
// buttonAfficher
//
this.buttonAfficher.Location = new System.Drawing.Point(80, 49);
this.buttonAfficher.Name = "buttonAfficher";
this.buttonAfficher.Size = new System.Drawing.Size(75, 23);
this.buttonAfficher.TabIndex = 1;
this.buttonAfficher.Text = "Afficher";
this.buttonAfficher.UseVisualStyleBackColor = true;
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
//
// textBoxSaisie
//
this.textBoxSaisie.Location = new System.Drawing.Point(80, 19);
this.textBoxSaisie.Name = "textBoxSaisie";
this.textBoxSaisie.Size = new System.Drawing.Size(100, 20);
this.textBoxSaisie.TabIndex = 2;
//
// frmSaisiesBoutons
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 118);
this.Controls.Add(this.textBoxSaisie);
this.Controls.Add(this.buttonAfficher);
this.Controls.Add(this.labelSaisie);
this.Name = "frmSaisiesBoutons";
this.Text = "Saisies et boutons - 1";
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.Label labelSaisie;
private System.Windows.Forms.Button buttonAfficher;
private System.Windows.Forms.TextBox textBoxSaisie;
}
}
- regels 53-55: de drie componenten hebben geleid tot drie privévelden van de klasse [Form1]. Merk op dat de namen van deze velden overeenkomen met de namen die aan de componenten zijn gegeven in de modus [conception]. Dit geldt ook voor het formulier op regel 2, dat de klasse zelf is.
- regels 7-9: de drie objecten van het type [Label], [TextBox] en [Button] worden aangemaakt. Via deze objecten worden de visuele componenten beheerd.
- regels 14-19: configuratie van het label labelSaisie
- regels 23-29: configuratie van de knop buttonAfficher
- regels 33-36: configuratie van het invoerveld textBoxSaisie
- regels 40-47: configuratie van het formulier frmSaisiesBoutons. Let in de regels 43-45 op de manier waarop componenten aan het formulier worden toegevoegd.
Deze code is begrijpelijk. Zo is het mogelijk om formulieren via code te bouwen zonder de modus [conception] te gebruiken. In de MSDN-documentatie van Visual Studio worden talrijke voorbeelden hiervan gegeven. Als u deze code onder de knie hebt, kunt u formulieren tijdens de uitvoering maken: bijvoorbeeld ter plekke een formulier aanmaken waarmee een databasetabel kan worden bijgewerkt, waarbij de structuur van deze tabel pas tijdens de uitvoering wordt ontdekt.
Nu moeten we nog de procedure schrijven voor het afhandelen van een klik op de knop Afficher. Selecteer de knop om het eigenschappenvenster te openen. Dit venster heeft verschillende tabbladen:
![]() |
- [1]: lijst met eigenschappen in alfabetische volgorde
- [2]: gebeurtenissen gerelateerd aan het besturingselement
De eigenschappen en gebeurtenissen van een besturingselement zijn toegankelijk per categorie of in alfabetische volgorde:
- [3]: Eigenschappen of gebeurtenissen per categorie
- [4]: Eigenschappen of gebeurtenissen in alfabetische volgorde
Het tabblad Events in de modus Catégories voor de knop buttonAfficher ziet er als volgt uit:
![]() |
- [1]: in de linkerkolom van het venster staan de mogelijke gebeurtenissen voor de knop vermeld. Een klik op een knop komt overeen met de gebeurtenis Click.
- [2]: de rechterkolom bevat de naam van de procedure die wordt aangeroepen wanneer de bijbehorende gebeurtenis plaatsvindt.
- [3]: als je dubbelklikt op de cel van de gebeurtenis Click, ga je automatisch naar het codevenster om de gebeurtenishandler Click voor de knop buttonAfficher te schrijven:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class frmSaisiesBoutons : Form {
public frmSaisiesBoutons() {
InitializeComponent();
}
private void buttonAfficher_Click(object sender, EventArgs e) {
}
}
}
- regels 10-12: het raamwerk van de gebeurtenishandler Click voor de knop met de naam buttonAfficher. Let op de volgende punten:
- de methode is volgens het schema nomDuComposant_NomEvénement benoemd
- de methode is privé. Ze ontvangt twee parameters:
- sender: dit is het object dat de gebeurtenis heeft veroorzaakt. Als de procedure wordt uitgevoerd na een klik op de knop buttonAfficher, zal sender gelijk zijn aan buttonAfficher. Men kan zich voorstellen dat de procedure buttonAfficher_Click vanuit een andere procedure wordt uitgevoerd. Deze zou dan alle vrijheid hebben om als eerste parameter het object sender naar keuze in te stellen.
- EventArgs: een object dat informatie over de gebeurtenis bevat. Voor een gebeurtenis Click bevat het niets. Voor een gebeurtenis die betrekking heeft op muisbewegingen, bevat het de (X,Y)-coördinaten van de muis.
- We zullen hier geen van deze parameters gebruiken.
Het schrijven van een gebeurtenishandler bestaat uit het aanvullen van het bovenstaande codekader. Hier willen we een dialoogvenster weergeven met daarin de inhoud van het veld textBoxSaisie als dit niet leeg is ([1]), en anders een foutmelding ([2]):
![]() |
De code om dit te realiseren zou als volgt kunnen zijn:
private void buttonAfficher_Click(object sender, EventArgs e) {
// de tekst die is ingevoerd in het veld wordt weergegeven TextBox textboxSaisie
string texte = textBoxSaisie.Text.Trim();
if (texte.Length != 0) {
MessageBox.Show("Texte saisi= " + texte, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information);
} else {
MessageBox.Show("Saissez un texte...", "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
De klasse MessageBox wordt gebruikt om berichten in een venster weer te geven. We hebben hier de volgende methode Show gebruikt:
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
met
het weer te geven bericht | |
de titel van het venster | |
de knoppen in het venster | |
het pictogram in het venster |
De parameter buttons kan de volgende constante waarden aannemen (voorafgegaan door MessageBoxButtons, zoals weergegeven in regel 7) hierboven:
knoppen | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
De parameter icon kan de volgende constante waarden aannemen (voorafgegaan door MessageBoxIcon, zoals weergegeven in regel 10) hierboven:
![]() | idem Stop | ||
idem Waarschuwing | ![]() | ||
idem Asterisk | ![]() | ||
![]() | idem Hand | ||
![]() |
De methode Show is een statische methode die een resultaat van het type [System.Windows.Forms.DialogResult] retourneert, wat een opsomming is:

Om te weten op welke knop de gebruiker heeft gedrukt om het venster van het type MessageBox te sluiten, schrijven we:
7.1.2.2. De code voor het beheer van gebeurtenissen
Naast de functie buttonAfficher_Click die we hebben geschreven, heeft Visual Studio in de methode InitializeComponents van [Form1.Designer.cs], die de formuliercomponenten aanmaakt en initialiseert, de volgende regel gegenereerd:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Click is een gebeurtenis van de klasse Button [1, 2, 3]:
![]() |
- [5]: de declaratie van de gebeurtenis [Control.Click] [4]. Zo zien we dat de gebeurtenis Click niet eigen is aan de klasse [Button]. Deze behoort tot de klasse [Control], de bovenliggende klasse van de klasse [Button].
- EventHandler is een prototype (een model) van een methode, een zogenaamde delegate. We komen hier later op terug.
- event is een sleutelwoord dat de functionaliteit van delegate en EventHandler beperkt: een object delegate heeft meer functionaliteiten dan een object event.
De delegate EventHandler is als volgt gedefinieerd:
![]() |
Het object delegate EventHandler verwijst naar een methodemodel:
- met als eerste parameter een Object-type
- met als tweede parameter het type EventArgs
- die geen resultaat oplevert
Dit is het geval bij de methode voor het afhandelen van een klik op de knop buttonAfficher, die door Visual Studio is gegenereerd:
private void buttonAfficher_Click(object sender, EventArgs e);
De methode buttonAfficher_Click komt dus overeen met het prototype dat is gedefinieerd door het type EventHandler. Om een object van het type EventHandler aan te maken, gaat men als volgt te werk:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Aangezien de methode buttonAfficher_Click overeenkomt met het prototype dat is gedefinieerd door het type EventHandler, kan men schrijven:
Een variabele van het type delegate is in feite een lijst met verwijzingen naar methoden van het type delegate. Om een nieuwe methode M toe te voegen aan de bovenstaande variabele evtHandler, gebruiken we de volgende syntaxis:
De notatie += kan worden gebruikt, zelfs als evtHandler een lege lijst is.
Laten we teruggaan naar de regel met [InitializeComponent], die een gebeurtenishandler toevoegt aan de gebeurtenis Click van het object buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Deze instructie voegt een methode van het type EventHandler toe aan de lijst met methoden van het veld buttonAfficher.Click. Deze methoden worden aangeroepen telkens wanneer de gebeurtenis Click op de component buttonAfficher wordt gedetecteerd. Vaak is er maar één. Deze wordt de „gebeurtenishandler” genoemd.
Laten we nog eens kijken naar de handtekening van EventHandler:
private delegate void EventHandler(object sender, EventArgs e);
De tweede parameter van delegate is een object van het type EventArgs of van een daarvan afgeleide klasse. Het type EventArgs is zeer algemeen en geeft in feite geen informatie over de gebeurtenis die heeft plaatsgevonden. Voor een klik op een knop is dit voldoende. Voor een muisbeweging over een formulier zouden we een gebeurtenis MouseMove hebben van de klasse [Form], gedefinieerd door:
delegate MouseEventHandler is gedefinieerd als:
![]() |
Dit is een gedelegeerde functie (delegate) met de handtekening void f (object, MouseEventArgs). De klasse MouseEventArgs wordt gedefinieerd door:
![]() |
De klasse MouseEventArgs biedt meer mogelijkheden dan de klasse EventArgs. Zo kunnen bijvoorbeeld de X- en Y-coördinaten van de muis worden bepaald op het moment dat de gebeurtenis plaatsvindt.
7.1.2.3. Conclusion
Uit de twee bestudeerde projecten kunnen we concluderen dat, zodra de grafische interface met Visual Studio is gebouwd, het werk van de ontwikkelaar voornamelijk bestaat uit het schrijven van de gebeurtenishandlers die hij voor deze grafische interface wil beheren. Er wordt automatisch code gegenereerd door Visual Studio. Deze code, die complex kan zijn, kan in eerste instantie worden genegeerd. Later kan het bestuderen ervan leiden tot een beter begrip van het maken en beheren van formulieren.
7.2. De basiscomponenten
We presenteren nu verschillende toepassingen waarin de meest gangbare componenten worden gebruikt, om de belangrijkste methoden en eigenschappen ervan te ontdekken. Voor elke toepassing presenteren we de grafische interface en de relevante code, voornamelijk die van de gebeurtenishandlers.
7.2.1. Formulier Form
We beginnen met de onmisbare component: het formulier waarop componenten worden geplaatst. We hebben al enkele basis-eigenschappen ervan besproken. Hier gaan we dieper in op enkele belangrijke gebeurtenissen van een formulier.
het formulier wordt geladen | |
het formulier wordt gesloten | |
het formulier is gesloten |
De gebeurtenis Load vindt plaats nog voordat het formulier wordt weergegeven. De gebeurtenis Closing vindt plaats wanneer het formulier wordt gesloten. Dit sluiten kan nog programmatisch worden gestopt.
We maken een formulier met de naam Form1 zonder componenten:
![]() |
- [1]: het formulier
- [2]: de drie verwerkte gebeurtenissen
De code van [Form1.cs] is als volgt:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// het formulier wordt voor het eerst geladen
MessageBox.Show("Evt Load", "Load");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
// het formulier wordt gesloten
MessageBox.Show("Evt FormClosing", "FormClosing");
// er wordt om bevestiging gevraagd
DialogResult réponse = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (réponse == DialogResult.No)
e.Cancel = true;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
// het formulier wordt gesloten
MessageBox.Show("Evt FormClosed", "FormClosed");
}
}
}
We gebruiken de functie MessageBox om op de hoogte te worden gebracht van verschillende gebeurtenissen.
regel 10: De gebeurtenis Load vindt plaats bij het opstarten van de applicatie, nog voordat het formulier wordt weergegeven: | ![]() |
regel 15: De gebeurtenis FormClosing vindt plaats wanneer de gebruiker het venster sluit. | ![]() |
regel 19: We vragen hem dan of hij de de applicatie wil verlaten: | ![]() |
regel 20: Als hij ‘Nee’ antwoordt, stellen we de eigenschap Cancel van de gebeurtenis CancelEventArgs die de methode als parameter heeft ontvangen. Als we deze eigenschap instellen op False, wordt het sluiten van het venster wordt afgebroken, anders gaat het door. De gebeurtenis FormClosed zal dan plaatsvinden: | ![]() |
7.2.2. Labels en invoervelden TextBox
We zijn deze twee componenten al tegengekomen. Label is een tekstcomponent en TextBox een invoerveldcomponent. Hun belangrijkste eigenschap is Text, die ofwel de inhoud van het invoerveld ofwel de tekst van het label aangeeft. Deze eigenschap is lees- en schrijfbaar.
De gebeurtenis die doorgaans voor TextBox wordt gebruikt, is TextChanged, die aangeeft dat de gebruiker het invoerveld heeft gewijzigd. Hier volgt een voorbeeld waarin de gebeurtenis TextChanged wordt gebruikt om de wijzigingen in een invoerveld bij te houden:
![]() |
nr. | type | naam | rol |
1 | TextBox | textBoxSaisie | invoerveld |
2 | Label | labelControle | geeft de tekst van 1 in realtime weer AutoSize=False, Text=(niets) |
3 | Knop | buttonEffacer | om de velden 1 en 2 te wissen |
4 | Knop | buttonQuitter | om de applicatie te verlaten |
De code van deze applicatie is als volgt:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// de inhoud van TextBox is gewijzigd – deze wordt gekopieerd naar het label labelControle
labelControle.Text = textBoxSaisie.Text;
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// de inhoud van het invoerveld wordt gewist
textBoxSaisie.Text = "";
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
// klik op de knop Afsluiten – de applicatie wordt afgesloten
Application.Exit();
}
private void Form1_Shown(object sender, System.EventArgs e) {
// zet de focus op het invoerveld
textBoxSaisie.Focus();
}
}
}
- regel 24: de gebeurtenis [Form].Shown vindt plaats wanneer het formulier wordt weergegeven
- regel 26: vervolgens wordt de focus (voor invoer) op de component textBoxSaisie geplaatst.
- regel 9: de gebeurtenis [TextBox].TextChanged vindt plaats telkens wanneer de inhoud van een component TextBox verandert
- regel 11: de inhoud van de component [TextBox] wordt gekopieerd naar de component [Label]
- regel 14: verwerkt de klik op de knop [Effacer]
- regel 16: de lege tekenreeks wordt in de component [TextBox] geplaatst
- regel 19: verwerkt de klik op de knop [Quitter]
- regel 21: om de actieve toepassing te stoppen. We herinneren ons dat het object Application dient om de toepassing te starten in de methode [Main] van [Form1.cs]:
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
In het volgende voorbeeld wordt een TextBox met meerdere regels gebruikt:
![]() |
De lijst met controles is als volgt:
nr. | type | naam | rol |
1 | TextBox | textBoxLignes | meerregelig invoerveld Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True |
2 | TextBox | textBoxLigne | invoerveld met één regel |
3 | Knop | buttonAjouter | Voegt de inhoud van 2 naar 1 toe |
Om een TextBox meerregelig te maken, stelt u de volgende eigenschappen van het besturingselement in:
om meerdere regels tekst toe te staan | |
om aan te geven of het besturingselement scrollbalken moet hebben (Horizontal, Vertical, Both) of niet (None) | |
indien gelijk aan true, zorgt de Enter-toets ervoor dat er een nieuwe regel wordt ingevoegd | |
indien gelijk aan true, zal de Tab-toets een tabstop in de tekst genereren |
Met de applicatie kunt u regels rechtstreeks in [1] invoeren of toevoegen via [2] en [3].
De code van de applicatie is als volgt:
using System.Windows.Forms;
using System;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// de inhoud van textBoxLigne wordt toegevoegd aan die van textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// de focus wordt op het invoerveld geplaatst
textBoxLigne.Focus();
}
}
}
- regel 18: wanneer het formulier wordt weergegeven (eventueel Shown), wordt de focus op het invoerveld textBoxLigne geplaatst
- regel 10: verwerkt de klik op de knop [Ajouter]
- regel 12: de tekst van het invoerveld textBoxLigne wordt toegevoegd aan de tekst van het invoerveld textBoxLignes, gevolgd door een regeleinde.
- regel 13: het invoerveld textBoxLigne wordt gewist
7.2.3. Keuzelijsten ComboBox
We maken het volgende formulier aan:
![]() |
nr. | type | naam | rol |
1 | ComboBox | comboNombres | bevat tekenreeksen DropDownStyle=DropDownList |
Een ComboBox-component is een vervolgkeuzelijst in combinatie met een invoerveld: de gebruiker kan ofwel een item selecteren in (2) of tekst invoeren in (1). Er zijn drie soorten ComboBox die worden bepaald door de eigenschap DropDownStyle:
niet-uitklapbare lijst met bewerkingsveld | |
uitklaplijst met invoerveld | |
uitklaplijst zonder invoerveld |
Standaard is het type van een ComboBox DropDown.
De klasse ComboBox heeft één constructor:
maakt een lege keuzelijst aan |
De elementen van de ComboBox zijn beschikbaar in de eigenschap Items:
Dit is een geïndexeerde eigenschap, waarbij Items[i] verwijst naar element i van de keuzelijst. Deze eigenschap is alleen-lezen.
Stel dat C een combobox is en C.Items de lijst met elementen ervan. We hebben de volgende eigenschappen:
aantal elementen van de combobox | |
element i van de combobox | |
voegt het object o toe als laatste element van de combobox | |
voegt een array met objecten toe aan het einde van de combo | |
voegt het object o toe op positie i van de combobox | |
verwijdert element i uit de combobox | |
verwijdert het object o uit de combobox | |
verwijdert alle elementen uit de combo | |
geeft de positie i van het object o in de keuzelijst weer | |
index van het geselecteerde element | |
geselecteerd element | |
weergegeven tekst van het geselecteerde element | |
weergegeven tekst van het geselecteerde element |
Het is misschien verrassend dat een combo-box objecten kan bevatten, terwijl deze visueel tekenreeksen weergeeft. Als een ComboBox een object obj bevat, wordt de tekenreeks obj.ToString() weergegeven. We herinneren ons dat elk object een methode ToString heeft, geërfd van de klasse object, die een tekenreeks retourneert die „representatief” is voor het object.
Het element Item dat in de keuzelijst C is geselecteerd, is C.SelectedItem of C.Items[C.SelectedIndex], waarbij C.SelectedIndex het nummer van het geselecteerde element, waarbij dit nummer begint bij nul voor het eerste element. De geselecteerde tekst kan op verschillende manieren worden verkregen: C.SelectedItem.Text, C.Text
Bij het selecteren van een element in de vervolgkeuzelijst vindt de gebeurtenis SelectedIndexChanged plaats, die vervolgens kan worden gebruikt om op de hoogte te worden gesteld van de wijziging in de selectie in de vervolgkeuzelijst. In de volgende toepassing gebruiken we deze gebeurtenis om het element weer te geven dat in de lijst is geselecteerd.
![]() |
De code van de toepassing is als volgt:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
private int previousSelectedIndex=0;
public Form1() {
InitializeComponent();
// combo invullen
comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
// element nr. 0 geselecteerd
comboBoxNombres.SelectedIndex = 0;
}
private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
int newSelectedIndex = comboBoxNombres.SelectedIndex;
if (newSelectedIndex != previousSelectedIndex) {
// het geselecteerde element is gewijzigd – dit wordt weergegeven
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// de nieuwe index wordt genoteerd
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- regel 5: previousSelectedIndex slaat de laatst geselecteerde index in de keuzelijst op
- regel 10: de keuzelijst wordt gevuld met een array van tekenreeksen
- regel 12: het eerste element wordt geselecteerd
- regel 15: de methode die wordt uitgevoerd telkens wanneer de gebruiker een item uit de keuzelijst selecteert. In tegenstelling tot wat de naam van de gebeurtenis zou doen vermoeden, vindt deze plaats zelfs als het geselecteerde item hetzelfde is als het vorige.
- regel 16: de index van het geselecteerde element wordt genoteerd
- regel 17: als deze verschilt van de vorige
- regel 19: het nummer en de tekst van het geselecteerde element worden weergegeven
- regel 21: de nieuwe index wordt opgeslagen
7.2.4. Component ListBox
We stellen voor om de volgende interface te bouwen:
![]() |
Dit venster bevat de volgende componenten:
nr. | type | naam | rol/eigenschappen |
0 | Form | Form1 | formulier FormBorderStyle=FixedSingle (raam waarvan de grootte niet kan worden aangepast) |
1 | TextBox | textBoxSaisie | invoerveld |
2 | Knop | buttonAjouter | knop waarmee de inhoud van het invoerveld [1] aan de lijst [3] kan worden toegevoegd |
3 | ListBox | listBox1 | lijst 1 SelectionMode=MultiExtended: |
4 | ListBox | listBox2 | lijst 2 SelectionMode=MultiSimple: |
5 | Knop | button1naar2 | verplaatst de geselecteerde items van lijst 1 naar lijst 2 |
6 | Knop | button2naar1 | doet het omgekeerde |
7 | Knop | buttonEffacer1 | maakt lijst 1 leeg |
8 | Knop | buttonEffacer2 | lijst 2 leegmaken |
De componenten ListBox hebben een selectiemodus voor hun elementen die wordt bepaald door hun eigenschap SelectionMode:
er kan slechts één element worden geselecteerd | |
meerdere selecties mogelijk: houd de toets SHIFT ingedrukt en klik op een element om de selectie van het eerder geselecteerde element uit te breiden naar het huidige element. | |
meerdere selecties mogelijk: een element wordt geselecteerd/gedeselecteerd door er met de muis op te klikken of door op de spatiebalk te drukken. |
- De gebruiker typt tekst in veld 1. Hij voegt deze toe aan lijst 1 met de knop Ajouter (2). Het invoerveld (1) wordt vervolgens leeggemaakt en de gebruiker kan een nieuw element toevoegen.
- Hij kan elementen van de ene lijst naar de andere verplaatsen door het te verplaatsen element in een van de lijsten te selecteren en de juiste verplaatsknop 5 of 6 te kiezen. Het verplaatste element wordt aan het einde van de doellijst toegevoegd en uit de bronlijst verwijderd.
- Hij kan dubbelklikken op een item in lijst 1. Dit item wordt dan naar het invoerveld verplaatst om te worden bewerkt en uit lijst 1 verwijderd.
De knoppen zijn ingeschakeld of uitgeschakeld volgens de volgende regels:
- de knop Ajouter is alleen verlicht als er niet-lege tekst in het invoerveld staat
- de knop [5] voor het overzetten van lijst 1 naar lijst 2 is alleen actief als er een item is geselecteerd in lijst 1
- de knop [6] voor het overzetten van lijst 2 naar lijst 1 is alleen verlicht als er een item is geselecteerd in lijst 2
- De knoppen [7] en [8] voor het wissen van lijst 1 en lijst 2 zijn alleen verlicht als de te wissen lijst items bevat.
Onder de bovenstaande voorwaarden moeten alle knoppen bij het opstarten van de applicatie uitgeschakeld zijn. De eigenschap Enabled van de knoppen moet dan worden ingesteld op false. Dit kan tijdens het ontwerpen worden gedaan, waardoor de bijbehorende code in de methode InitializeComponent wordt gegenereerd, of we kunnen dit zelf in de constructor doen, zoals hieronder:
public Form1() {
InitializeComponent();
// --- aanvullende initialisaties ---
// een aantal knoppen wordt uitgeschakeld
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
De status van de knop Ajouter wordt bepaald door de inhoud van het invoerveld. Met de gebeurtenis TextChanged kunnen we de wijzigingen in deze inhoud volgen:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// de inhoud van textBoxSaisie is gewijzigd
// de knop Toevoegen is alleen actief als het invoerveld niet leeg is
buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
}
De status van de overdrachtsknoppen hangt af van het feit of er al dan niet een item is geselecteerd in de lijst die ze beheren:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
// er is een element geselecteerd
// de knop voor overdracht van 1 naar 2 wordt geactiveerd
button1vers2.Enabled = true;
}
private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
// er is een element geselecteerd
// de knop 'Overzetten van 2 naar 1' wordt geactiveerd
button2vers1.Enabled = true;
}
De code die hoort bij het klikken op de knop Ajouter is als volgt:
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// een nieuw element is toegevoegd aan lijst 1
listBox1.Items.Add(textBoxSaisie.Text.Trim());
// de invoer wordt gewist
textBoxSaisie.Text = "";
// Lijst 1 is niet leeg
buttonEffacer1.Enabled = true;
// focus terug naar het invoerveld
textBoxSaisie.Focus();
}
Let op de methode Focus, waarmee de „focus” op een formulierveld kan worden geplaatst. De code die hoort bij het klikken op de knoppen Effacer:
private void buttonEffacer1_Click(object sender, System.EventArgs e) {
// lijst 1 wordt gewist
listBox1.Items.Clear();
// Knop Wissen
buttonEffacer1.Enabled = false;
}
private void buttonEffacer2_Click(object sender, System.EventArgs e) {
// lijst 2 wordt gewist
listBox2.Items.Clear();
// knop Wissen
buttonEffacer2.Enabled = false;
}
De code om de geselecteerde elementen van de ene lijst naar de andere over te zetten:
private void button1vers2_Click(object sender, System.EventArgs e) {
// het geselecteerde item uit lijst 1 overbrengen naar lijst 2
transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
}
private void button2vers1_Click(object sender, System.EventArgs e) {
// het geselecteerde item uit Lijst 2 verplaatsen naar Lijst 1
transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
}
De twee bovenstaande methoden delegeren het overzetten van geselecteerde elementen van de ene lijst naar de andere aan één en dezelfde privémethode, genaamd ‘transfert’:
// overzetten
private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
// overzetten van de geselecteerde elementen uit lijst l1 naar lijst l2
for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
// index van het geselecteerde element
int index = l1.SelectedIndices[i];
// toevoegen aan l2
l2.Items.Add(l1.Items[index]);
// verwijdering uit l1
l1.Items.RemoveAt(index);
}
// knoppen Wissen
buttonEffacer2.Enabled = l2.Items.Count != 0;
buttonEffacer1.Enabled = l1.Items.Count != 0;
// overzetknoppen
button1vers2.Enabled = false;
}
- regel b: de methode ‘transfert’ ontvangt zes parameters:
- een verwijzing naar de lijst met de geselecteerde elementen, hier l1 genoemd. Tijdens de uitvoering van de applicatie is l1 ofwel listBox1 ofwel listBox2. Voorbeelden van aanroepen zijn te zien in de regels 3 en 8 van de overdrachtsprocedures buttonXversY_Click.
- een verwijzing naar de overdrachtsknop die aan lijst l1 is gekoppeld. Als l1 bijvoorbeeld listBox2 is, is dit button2vers1 (zie aanroep op regel 8)
- een verwijzing naar de knop voor het wissen van lijst l1. Als l1 bijvoorbeeld listBox1 is, wordt dit buttonEffacer1 (zie aanroep op regel 3)
- de drie andere referenties zijn vergelijkbaar, maar verwijzen naar lijst l2.
- regel d: de verzameling [ListBox].SelectedIndices vertegenwoordigt de indices van de geselecteerde elementen in de component [ListBox]. Dit is een verzameling:
- [ListBox].SelectedIndices.Count is het aantal elementen in deze verzameling
- [ListBox].SelectedIndices[i] is element nr. i van deze verzameling
We doorlopen de verzameling in omgekeerde volgorde: we beginnen bij het einde van de verzameling en eindigen bij het begin. We zullen uitleggen waarom.
- regel f: index van een geselecteerd element uit de lijst l1
- regel h: dit element wordt toegevoegd aan de lijst l2
- regel j: en verwijderd uit de lijst l1. Omdat het is verwijderd, is het niet langer geselecteerd. De verzameling l1.SelectedIndices van regel d wordt opnieuw berekend. Het element dat zojuist is verwijderd, gaat hieruit verloren. Voor alle elementen die hierna komen, verandert het nummer van n naar n-1.
- Als de lus van regel (d) oplopend is en zojuist element nr. 0 heeft verwerkt, zal deze vervolgens element nr. 1 verwerken. Het element dat vóór het verwijderen van element nr. 0 het nummer 1 had, krijgt nu echter het nummer 0. Het wordt dan door de lus overgeslagen.
- Als de lus in regel (d) aflopend is en zojuist element nr. n heeft verwerkt, zal deze vervolgens element nr. n-1 verwerken. Na het verwijderen van element nr. n verandert het nummer van element nr. n-1 niet. Het wordt dus in de volgende lusronde verwerkt.
- regels m-n: de status van de knoppen [Effacer] hangt af van de aanwezigheid of afwezigheid van elementen in de bijbehorende lijsten
- regel p: er zijn geen geselecteerde elementen meer in lijst l2: de overdrachtsknop wordt uitgeschakeld.
7.2.5. Selectievakjes CheckBox, keuzerondjes ButtonRadio
We stellen voor om de volgende applicatie te schrijven:
![]() |
De componenten van het venster zijn als volgt:
nr. | type | naam | functie |
1 | GroupBox zie [6] | groupBox1 | een container voor componenten. Hierin kunnen andere componenten worden geplaatst. Text=Keuzerondjes |
2 | RadioButton | radioButton1 radioButton2 radioButton3 | 3 keuzerondjes - radioButton1 heeft de eigenschap Checked=True en de eigenschap Text=1 - radioButton2 heeft de eigenschap Text=2 - radioButton3 heeft de eigenschap Text=3 Keuzerondjes die zich in dezelfde container bevinden, in dit geval de GroupBox, sluiten elkaar uit: slechts één ervan is geselecteerd. |
3 | GroupBox | groupBox2 | |
4 | CheckBox | checkBox1 checkBox2 checkBox3 | 3 selectievakjes. chechBox1 heeft de eigenschap Checked=True en de eigenschap Text=A - chechBox2 heeft de eigenschap Text=B - chechBox3 heeft de eigenschap Text=C |
5 | ListBox | listBoxValeurs | een lijst die de waarden van de keuzerondjes en selectievakjes weergeeft zodra er een wijziging plaatsvindt. |
6 | laat zien waar de container GroupBox te vinden is |
De gebeurtenis die voor deze zes besturingselementen van belang is, is de gebeurtenis CheckChanged, die aangeeft dat de status van het selectievakje of de keuzeknop is gewijzigd. Deze status wordt in beide gevallen weergegeven door de booleaanse eigenschap Checked, die, wanneer deze op ‘waar’ staat, betekent dat het besturingselement is aangevinkt. We zullen hier slechts één methode gebruiken om de zes gebeurtenissen CheckChanged te verwerken, namelijk de methode affiche. Om ervoor te zorgen dat de zes gebeurtenissen CheckChanged door dezelfde methode affiche worden afgehandeld, kunnen we als volgt te werk gaan:
Selecteer de component radioButton1 en klik er met de rechtermuisknop op om de eigenschappen te openen:
![]() |
Op het tabblad événements [1] koppelen we de methode affiche [2] aan de gebeurtenis CheckChanged. Dit betekent dat men wil dat het klikken op de optie A1 wordt afgehandeld door een methode met de naam affiche. Visual Studio genereert automatisch de methode affiche in het codevenster:
private void affiche(object sender, EventArgs e) {
}
De methode affiche is een methode van het type EventHandler.
Voor de vijf andere componenten gaat men op dezelfde manier te werk. Laten we bijvoorbeeld de optie CheckBox1 en de bijbehorende gebeurtenissen [3] selecteren. Naast de gebeurtenis Click staat een vervolgkeuzelijst [4] met daarin de bestaande methoden die deze gebeurtenis kunnen verwerken. Hier is alleen de methode affiche. beschikbaar. We selecteren deze. We herhalen dit proces voor alle andere componenten.
In de methode InitializeComponent is code gegenereerd. De methode affiche is als handler voor de zes gebeurtenissen CheckedChanged op de volgende manier gedeclareerd:
this.radioButton1.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton2.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton3.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox1.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox2.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox3.CheckedChanged += new System.EventHandler(this.affiche);
De methode affiche is als volgt aangevuld:
private void affiche(object sender, System.EventArgs e) {
// geeft de status van de keuzeknop of het selectievakje weer
// is dit een selectievakje?
if (sender is CheckBox) {
CheckBox chk = (CheckBox)sender;
listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
}
// is dit een keuzerondje?
if (sender is RadioButton) {
RadioButton rdb = (RadioButton)sender;
listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
}
}
De syntaxis
if (sender is CheckBox) {
maakt het mogelijk te controleren of het object sender van het type CheckBox is. Dit stelt ons vervolgens in staat om een typeconversie uit te voeren naar het exacte type van sender. De methode affiche schrijft in de lijst listBoxValeurs de naam van de component die de gebeurtenis heeft veroorzaakt en de waarde van de eigenschap Checked. Bij de uitvoering van [7] zien we dat een klik op een keuzerondje twee gebeurtenissen CheckChanged veroorzaakt: één op het oude aangevinkte keuzerondje, dat nu "niet aangevinkt" wordt, en één op het nieuwe keuzerondje, dat nu "aangevinkt" wordt.
7.2.6. Regelaars ScrollBar
Er zijn verschillende soorten variatoren: de horizontale variator (HscrollBar), de verticale variator (VscrollBar), de incrementator (NumericUpDown). |
Laten we de volgende toepassing maken:
![]() |
nr. | type | naam | rol |
1 | hScrollBar | hScrollBar1 | een horizontale variator |
2 | hScrollBar | hScrollBar2 | een horizontale variator die de variaties van variator 1 volgt |
3 | Label | labelValeurHS1 | geeft de waarde van de horizontale variator weer |
4 | NumericUpDown | numericUpDown2 | maakt het mogelijk de waarde van regelaar 2 vast te stellen |
Met een schuifregelaar van het type ScrollBar kan de gebruiker een waarde kiezen binnen een bereik van gehele getallen, gesymboliseerd door de "balk" van de schuifregelaar waarover een cursor beweegt. De waarde van de schuifregelaar is beschikbaar in de eigenschap Value.
- Bij een horizontale schuifregelaar vertegenwoordigt het linkeruiteinde de minimumwaarde van het bereik, het rechteruiteinde de maximumwaarde en de schuifknop de huidige gekozen waarde. Bij een verticale schuifregelaar wordt het minimum vertegenwoordigd door het bovenste uiteinde en het maximum door het onderste uiteinde. Deze waarden worden weergegeven door de eigenschappen Minimum en Maximum en zijn standaard ingesteld op 0 en 100.
- Een klik op de uiteinden van de schuifregelaar verandert de waarde met één stap (positief of negatief), afhankelijk van het aangeklikte uiteinde, genaamd SmallChange, dat standaard de waarde 1 heeft.
- Een klik aan weerszijden van de schuifregelaar verandert de waarde met één stap (positief of negatief), afhankelijk van het aangeklikte uiteinde, genaamd LargeChange, dat standaard de waarde 10 heeft.
- Wanneer je op het bovenste uiteinde van een verticale schuifregelaar klikt, neemt de waarde ervan af. Dit kan de gemiddelde gebruiker verrassen, die normaal gesproken verwacht dat de waarde „stijgt“. Dit probleem los je op door de eigenschappen SmallChange en LargeChange een negatieve waarde te geven.
- Deze vijf eigenschappen (Value, Minimum, Maximum, SmallChange, LargeChange) zijn zowel leesbaar als schrijfbaar.
- De belangrijkste gebeurtenis van de regelaar is die welke een waardeverandering signaleert: de Scroll-gebeurtenis.
Een component NumericUpDown bevindt zich dicht bij de regelaar: ook deze heeft de eigenschappen Minimum, Maximum en Value, standaard ingesteld op 0, 100, 0. Maar hier wordt de eigenschap Value weergegeven in een invoerveld dat integraal deel uitmaakt van het besturingselement. De gebruiker kan deze waarde zelf wijzigen, tenzij de eigenschap ReadOnly van het besturingselement op ‘waar’ is ingesteld. De waarde van de stapgrootte wordt bepaald door de eigenschap Increment, standaard 1. De belangrijkste gebeurtenis van de component NumericUpDown is die welke een waardeverandering signaleert: de gebeurtenis ValueChanged
De applicatiecode is als volgt:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// we stellen de kenmerken van regelaar 1 in
hScrollBar1.Value = 7;
hScrollBar1.Minimum = 1;
hScrollBar1.Maximum = 130;
hScrollBar1.LargeChange = 11;
hScrollBar1.SmallChange = 1;
// we geven regelaar 2 dezelfde kenmerken als regelaar 1
hScrollBar2.Value = hScrollBar1.Value;
hScrollBar2.Minimum = hScrollBar1.Minimum;
hScrollBar2.Maximum = hScrollBar1.Maximum;
hScrollBar2.LargeChange = hScrollBar1.LargeChange;
hScrollBar2.SmallChange = hScrollBar1.SmallChange;
// idem voor de incrementator
numericUpDown2.Value = hScrollBar1.Value;
numericUpDown2.Minimum = hScrollBar1.Minimum;
numericUpDown2.Maximum = hScrollBar1.Maximum;
numericUpDown2.Increment = hScrollBar1.SmallChange;
// het label krijgt de waarde van regelaar 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// wijziging van de waarde van de frequentieregelaar 1
// de waarde wordt doorgegeven aan regelaar 2 en aan het label
hScrollBar2.Value = hScrollBar1.Value;
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
// de incrementator heeft van waarde veranderd
// de waarde van regelaar 2 wordt vastgelegd
hScrollBar2.Value = (int)numericUpDown2.Value;
}
}
}
7.3. Muisgebeurtenissen
Wanneer er in een container wordt getekend, is het belangrijk om de positie van de muis te kennen om bijvoorbeeld bij een klik een punt weer te geven. Muisbewegingen veroorzaken gebeurtenissen in de container waarin de muis zich verplaatst.
![]() |
- [1]: gebeurtenissen die plaatsvinden wanneer de muis over het formulier of een besturingselement wordt bewogen
- [2]: gebeurtenissen die plaatsvinden bij slepen en neerzetten (Drag'nDrop)
de muis is zojuist het gebied van het besturingselement binnengekomen | |
de muis heeft zojuist het gebied van het besturingselement verlaten | |
de muis beweegt binnen het besturingsgebied | |
De linkermuisknop is ingedrukt | |
De linkermuisknop loslaten | |
de gebruiker laat een object op het besturingselement los | |
de gebruiker komt in het gebied van het besturingselement door een object te slepen | |
de gebruiker verlaat het gebied van het besturingselement door een object te verslepen | |
de gebruiker beweegt met een object over het besturingsgebied |
Hier is een applicatie waarmee u beter kunt begrijpen wanneer de verschillende muisgebeurtenissen plaatsvinden:
![]() |
nr. | type | naam | rol |
1 | Label | lblPositionSouris | om de positie van de muis in formulier 1, lijst 2 of knop 3 weer te geven |
2 | ListBox | listBoxEvts | om andere muisgebeurtenissen weer te geven dan MouseMove |
3 | Knop | buttonEffacer | om de inhoud van 2 te wissen |
Om de muisbewegingen op de drie besturingselementen te volgen, schrijven we slechts één handler, namelijk de handler affiche:
![]() |
De code van de procedure affiche is als volgt:
private void affiche(object sender, MouseEventArgs e) {
// muisbeweging – de coördinaten (X,Y) van de muis worden weergegeven
labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}
Telkens wanneer de muis het gebied van een besturingselement binnengaat, verandert het coördinatensysteem ervan. De oorsprong (0,0) is de linkerbovenhoek van het besturingselement waarop de muis zich bevindt. Tijdens de uitvoering, wanneer men de muis van het formulier naar de knop verplaatst, is de verandering in coördinaten dus duidelijk zichtbaar. Om deze veranderingen in het bereik van de muis beter te kunnen zien, kun je de eigenschap Cursor [1] van de besturingselementen gebruiken:
![]() |
Met deze eigenschap kun je de vorm van de muiscursor instellen wanneer deze het gebied van het besturingselement binnenkomt. In ons voorbeeld hebben we de cursor dus ingesteld op ‘Default’ voor het formulier zelf ([2]), op ‘Hand’ voor lijst 2 ([3]) en op ‘Cross’ voor knop 3 ([4]).
Om bovendien te detecteren wanneer de muis lijst 2 binnenkomt en verlaat, verwerken we de gebeurtenissen MouseEnter en MouseLeave van diezelfde lijst:
private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
// de gebeurtenis wordt gemeld
listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
}
private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
// de gebeurtenis wordt gemeld
listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}
Om klikken op het formulier te verwerken, verwerken we de gebeurtenissen MouseDown en MouseUp:
private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
// de gebeurtenis wordt gemeld
listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
}
private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
// de gebeurtenis wordt gemeld
listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
- regels 3 en 8: de berichten worden op de eerste plaats in ListBox geplaatst, zodat de meest recente gebeurtenissen bovenaan de lijst staan.
![]() |
Tot slot de code voor de klikhandler op de knop Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. Een venster met menu maken
Laten we nu eens kijken hoe we een venster met een menu kunnen maken. We gaan het volgende venster maken:
![]() |
Om een menu te maken, kiezen we de component "MenuStrip" in de balk "Menus & Tollbars":
![]() |
- [1]: keuze van de component [MenuStrip]
- [2]: er verschijnt nu een menu op het formulier met lege velden met de titel "Type Here". U hoeft alleen maar de verschillende menuopties in te vullen.
- [3]: de tekst „Options A” is ingevoerd. We gaan verder met de tekst [4].
- [5]: de teksten van de opties A zijn ingevoerd. We gaan verder met de tekst [6]
![]() |
- [6]: de eerste opties B
- [7]: onder B1 wordt een scheidingsteken geplaatst. Dit is beschikbaar in een keuzelijst met de tekst "Type Here"
- [8]: om een submenu te maken, gebruik je de pijl [8] en typ je het submenu in [9]
Nu moeten de verschillende onderdelen van het formulier nog een naam krijgen:
![]() |
nr. | type | naam(en) | rol |
1 | Label | labelStatut | om de tekst van de aangeklikte menuoptie weer te geven |
2 | toolStripMenuItem | toolStripMenuItemOptionsA toolStripMenuItemA1 toolStripMenuItemA2 toolStripMenuItemA3 | menuopties onder de hoofdoptie "Opties A" |
3 | toolStripMenuItem | toolStripMenuItemOptionsB toolStripMenuItemB1 toolStripMenuItemB2 toolStripMenuItemB3 | menuopties onder de hoofdoptie "Opties B" |
4 | toolStripMenuItem | toolStripMenuItemB31 toolStripMenuItemB32 | menuopties onder de hoofdoptie "B3" |
Menuopties zijn besturingselementen, net als andere visuele componenten, en hebben eigenschappen en gebeurtenissen. De eigenschappen van de menuoptie A1 zijn bijvoorbeeld als volgt:
![]() |
In ons voorbeeld worden twee eigenschappen gebruikt:
de naam van het menubesturingselement | |
de tekst van de menuoptie |
Selecteer in de menustructuur de optie A1 en klik met de rechtermuisknop om de eigenschappen van het besturingselement te openen:
![]() |
Op het tabblad événements [1] koppelen we de methode affiche [2] aan de gebeurtenis Click. Dit betekent dat men wil dat het klikken op de optie A1 wordt afgehandeld door een methode met de naam affiche. Visual Studio genereert automatisch de methode affiche in het codevenster:
private void affiche(object sender, EventArgs e) {
}
In deze methode zullen we ons beperken tot het weergeven van de eigenschap Text van de aangeklikte menuoptie in het label labelStatut:
private void affiche(object sender, EventArgs e) {
// geeft in TextBox de naam van het geselecteerde submenu weer
labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}
De bron van de gebeurtenis sender is van het type object. De menuopties zijn van het type ToolStripMenuItem, dus moeten we een typeconversie uitvoeren van object naar ToolStripMenuItem.
Voor alle menuopties stellen we de klikhandler in op de methode affiche [3,4].
Laten we de applicatie uitvoeren en een menu-item selecteren:
![]() | ![]() |
7.5. Niet-visuele componenten
We gaan nu kijken naar een aantal niet-visuele componenten: deze worden gebruikt tijdens het ontwerpen, maar zijn niet zichtbaar tijdens de uitvoering.
7.5.1. Dialoogvensters OpenFileDialog en SaveFileDialog
We gaan de volgende toepassing bouwen:
![]() |
De besturingselementen zijn als volgt:
Nr. | type | naam | rol |
1 | TextBox | TextBoxLignes | door de gebruiker ingevoerde tekst of tekst die uit een bestand is geladen MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True |
2 | Knop | buttonSauvegarder | maakt het mogelijk de tekst van [1] op te slaan in een tekstbestand |
3 | Knop | buttonCharger | hiermee kun je de inhoud van een tekstbestand in [1] laden |
4 | Knop | buttonEffacer | wist de inhoud van [1] |
5 | SaveFileDialog | saveFileDialog1 | component waarmee de naam en de locatie van het back-upbestand van [1] kunnen worden gekozen. Deze component wordt uit de werkbalk [7] gehaald en eenvoudig op het formulier geplaatst. Hij wordt dan opgeslagen, maar neemt geen ruimte in op het formulier. Het is een niet-visuele component. |
6 | OpenFileDialog | openFileDialog1 | component waarmee het bestand kan worden geselecteerd dat in [1] moet worden geladen. |
De code die bij de knop Effacer hoort, is eenvoudig:
private void buttonEffacer_Click(object sender, EventArgs e) {
// er wordt een lege tekenreeks in TexBox geplaatst
textBoxLignes.Text = "";
}
We zullen de volgende eigenschappen en methoden van de klasse SaveFileDialog gebruiken:
Veld | Type | Rol |
Propriété | de bestandstypen die worden aangeboden in de vervolgkeuzelijst met bestandstypen in het dialoogvenster | |
Propriété | het nummer van het standaard aangeboden bestandstype in de bovenstaande lijst. Begint bij 0. | |
Propriété | de map die aanvankelijk werd weergegeven voor het opslaan van het bestand | |
Propriété | de door de gebruiker opgegeven naam van het back-upbestand | |
Méthode | methode die het dialoogvenster voor het opslaan weergeeft. Geeft een resultaat van het type DialogResult terug. |
De methode ShowDialog toont een dialoogvenster dat er ongeveer zo uitziet:
![]() |
uitklaplijst samengesteld op basis van de eigenschap Filter. Het standaard voorgestelde bestandstype wordt bepaald door FilterIndex | |
huidige map, vastgelegd door InitialDirectory indien deze eigenschap is ingevuld | |
de bestandsnaam die door de gebruiker is gekozen of rechtstreeks is ingevoerd. Deze is beschikbaar in de eigenschap FileName | |
knoppen Opslaan/Annuleren. Als de knop Enregistrer wordt gebruikt, levert de functie ShowDialog het resultaat DialogResult.OK op |
De opslagprocedure kan als volgt worden geschreven:
private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
// het invoerveld wordt opgeslagen in een tekstbestand
// het dialoogvenster savefileDialog1 wordt geconfigureerd
saveFileDialog1.InitialDirectory = Application.ExecutablePath;
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
// we geven het dialoogvenster weer en halen het resultaat op
if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
// de bestandsnaam wordt opgehaald
string nomFichier = saveFileDialog1.FileName;
StreamWriter fichier = null;
try {
// het bestand wordt geopend in schrijfmodus
fichier = new StreamWriter(nomFichier);
// de tekst erin schrijven
fichier.Write(textBoxLignes.Text);
} catch (Exception ex) {
// probleem
MessageBox.Show("Problème à l'écriture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// het bestand wordt gesloten
if (fichier != null) {
fichier.Dispose();
}
}
}
}
- regel 4: de bronmap (InitialDirectory) wordt ingesteld op de map (Application.ExecutablePath) die het uitvoerbare bestand van de applicatie bevat.
- regel 5: de weer te geven bestandstypen worden vastgelegd. Let op de syntaxis van de filters: filtre1|filtre2|..|filtren met filteri= Tekst|bestandssjabloon. Hier heeft de gebruiker de keuze tussen de bestanden *.txt en *.*.
- regel 6: hier wordt bepaald welk bestandstype als eerste aan de gebruiker wordt getoond. Hier verwijst index 0 naar *.txt-bestanden.
- regel 8: het dialoogvenster wordt weergegeven en het resultaat ervan wordt opgehaald. Terwijl het dialoogvenster wordt weergegeven, heeft de gebruiker geen toegang meer tot het hoofdformulier (dit wordt een ‘modaal’ dialoogvenster genoemd). De gebruiker stelt de naam van het op te slaan bestand in en verlaat het dialoogvenster via de knop Enregistrer, de knop Annuler, of door het dialoogvenster te sluiten. Het resultaat van de methode ShowDialog is alleen DialogResult.OK als de gebruiker de knop Enregistrer heeft gebruikt om het dialoogvenster te sluiten.
- Zodra dit is gebeurd, staat de naam van het aan te maken bestand nu in de eigenschap FileName van het object saveFileDialog1. Vervolgens keert men terug naar het klassieke aanmaken van een tekstbestand. Hierin wordt de inhoud van TextBox: textBoxLignes.Text geschreven, waarbij eventuele uitzonderingen worden afgehandeld.
De klasse OpenFileDialog lijkt sterk op de klasse SaveFileDialog. We gebruiken dezelfde methoden en eigenschappen als eerder. De methode ShowDialog geeft een dialoogvenster weer dat lijkt op het volgende:
![]() |
een vervolgkeuzelijst die is samengesteld op basis van de eigenschap Filter. Het standaard aangeboden bestandstype wordt bepaald door FilterIndex | |
huidige map, vastgelegd door InitialDirectory indien deze eigenschap is ingevuld | |
de bestandsnaam die door de gebruiker is gekozen of rechtstreeks is ingevoerd. Deze is beschikbaar in de eigenschap FileName | |
knoppen Openen/Annuleren. Als de knop Ouvrir wordt gebruikt, levert de functie ShowDialog het resultaat DialogResult.OK op |
De procedure voor het laden van het tekstbestand kan als volgt worden geschreven:
private void buttonCharger_Click(object sender, EventArgs e) {
// we laden een tekstbestand in het invoerveld
// het dialoogvenster wordt geconfigureerd openfileDialog1
openFileDialog1.InitialDirectory = Application.ExecutablePath;
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
openFileDialog1.FilterIndex = 0;
// het dialoogvenster wordt weergegeven en het resultaat wordt opgehaald
if (openFileDialog1.ShowDialog() == DialogResult.OK) {
// de bestandsnaam wordt opgehaald
string nomFichier = openFileDialog1.FileName;
StreamReader fichier = null;
try {
// het bestand wordt geopend in leesmodus
fichier = new StreamReader(nomFichier);
// het hele bestand wordt gelezen en opgeslagen in het TextBox
textBoxLignes.Text = fichier.ReadToEnd();
} catch (Exception ex) {
// probleem
MessageBox.Show("Problème à la lecture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// het bestand wordt gesloten
if (fichier != null) {
fichier.Dispose();
}
}//eindelijk
}//als
}
- regel 4: de startmap (InitialDirectory) wordt ingesteld op de map (Application.ExecutablePath) die het uitvoerbare bestand van de toepassing bevat.
- regel 5: de weer te geven bestandstypen worden vastgelegd. Let op de syntaxis van de filters: filtre1|filtre2|..|filtren met filteri= Tekst|bestandssjabloon. Hier heeft de gebruiker de keuze tussen de bestanden *.txt en *.*.
- regel 6: hier wordt bepaald welk bestandstype als eerste aan de gebruiker wordt getoond. Hier verwijst index 0 naar *.txt-bestanden.
- regel 8: het dialoogvenster wordt weergegeven en het resultaat ervan wordt opgehaald. Terwijl het dialoogvenster wordt weergegeven, heeft de gebruiker geen toegang meer tot het hoofdformulier (een zogenaamd modaal dialoogvenster). De gebruiker stelt de naam van het op te slaan bestand in en verlaat het dialoogvenster via de knop Ouvrir, de knop Annuler, of door het dialoogvenster te sluiten. Het resultaat van de methode ShowDialog is alleen DialogResult.OK als de gebruiker de knop Enregistrer heeft gebruikt om het dialoogvenster te sluiten.
- Zodra dit is gebeurd, staat de naam van het aan te maken bestand nu in de eigenschap FileName van het object openFileDialog1. We keren dan terug naar het klassieke lezen van een tekstbestand. Let op, in regel 16, de methode waarmee het volledige bestand kan worden gelezen.
7.5.2. Dialoogvensters FontColor en ColorDialog
We gaan verder met het vorige voorbeeld en voegen er twee nieuwe knoppen en twee nieuwe niet-visuele besturingselementen aan toe:
![]() |
6
7
Nr. | type | naam | rol |
1 | Knop | buttonCouleur | om de kleur van de tekens van de TextBox vast te stellen |
2 | Knop | buttonPolice | om het lettertype van TextBox in te stellen |
3 | ColorDialog | colorDialog1 | het onderdeel waarmee een kleur kan worden geselecteerd – afkomstig uit de toolbox [5]. |
4 | FontDialog | colorDialog1 | de component waarmee een lettertype kan worden geselecteerd – uit de toolbox [5]. |
De klassen FontDialog en ColorDialog hebben een methode ShowDialog die analoog is aan de methode ShowDialog van de klassen OpenFileDialog en SaveFileDialog.
![]() |
Met de methode ShowDialog van de klasse ColorDialog kan een kleur [1] worden gekozen. Met de methode van de klasse FontDialog kunt u een lettertype [2] kiezen:
- [1]: als de gebruiker het dialoogvenster sluit met de knop OK, is het resultaat van de methode ShowDialog DialogResult.OK en staat de gekozen kleur in de eigenschap Color van het gebruikte object ColorDialog.
- [2]: als de gebruiker het dialoogvenster sluit met de knop OK, is het resultaat van de methode ShowDialog DialogResult.OK en staat het gekozen lettertype in de eigenschap Font van het gebruikte object FontDialog.
We beschikken nu over de elementen om klikken op de knoppen Couleur en Police te verwerken:
private void buttonCouleur_Click(object sender, EventArgs e) {// een tekstkleur kiezen
if (colorDialog1.ShowDialog() == DialogResult.OK) {
// de eigenschap Forecolor van TextBox wordt gewijzigd
textBoxLignes.ForeColor = colorDialog1.Color;
}//if
}
private void buttonPolice_Click(object sender, EventArgs e) {
// een lettertype kiezen
if (fontDialog1.ShowDialog() == DialogResult.OK) {
// de eigenschap Font van TextBox wijzigen
textBoxLignes.Font = fontDialog1.Font;
}
- regel [4]: de eigenschap [ForeColor] van een component TextBox geeft de kleur van het type [Color] aan voor de tekens van de TextBox. In dit geval is deze kleur de kleur die de gebruiker heeft gekozen in het dialoogvenster van het type [ColorDialog].
- regel [12]: de eigenschap [Font] van een component van het type TextBox verwijst naar het lettertype van het type [Font] voor de tekens van het type TextBox. In dit geval is dit het lettertype dat de gebruiker heeft gekozen in het dialoogvenster van het type [FontDialog].
7.5.3. Timer
We stellen hier voor om de volgende toepassing te schrijven:
![]() |
nr. | Type | Naam | Rol |
1 | Label | labelChrono | toont een stopwatch |
2 | Knop | buttonArretMarche | knop Stop/Start van de stopwatch |
3 | Timer | timer1 | component die hier elke seconde een gebeurtenis uitzendt |
In [4] zien we de stopwatch lopen, in [5] is de stopwatch gestopt.
Om de inhoud van het label LabelChrono elke seconde te wijzigen, hebben we een component nodig die elke seconde een gebeurtenis genereert; deze gebeurtenis kunnen we onderscheppen om de weergave van de timer bij te werken. Deze component is de Timer [1], beschikbaar in de toolkit Components [2]:
![]() |
De eigenschappen van de hier gebruikte Timer-component zijn als volgt:
aantal milliseconden waarna een gebeurtenis Tick wordt gegenereerd. | |
de gebeurtenis die wordt gegenereerd na Interval milliseconden | |
maakt de timer actief (true) of inactief (false) |
In ons voorbeeld heet de timer timer1 en is timer1.Interval ingesteld op 1000 ms (1 s). De gebeurtenis Tick zal dus elke seconde plaatsvinden. Een klik op de knop Stop/Start wordt verwerkt door de volgende procedure buttonArretMarche_Click:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
// instantievariabele
private DateTime début = DateTime.Now;
...
private void buttonArretMarche_Click(object sender, EventArgs e) {
// uit of aan?
if (buttonArretMarche.Text == "Marche") {
// de starttijd noteren
début = DateTime.Now;
// we geven deze weer
labelChrono.Text = "00:00:00";
// de timer wordt gestart
timer1.Enabled = true;
// de tekst van de knop wordt gewijzigd
buttonArretMarche.Text = "Arrêt";
// einde
return;
}//
if (buttonArretMarche.Text == "Arrêt") {
// de timer stoppen
timer1.Enabled = false;
// de tekst van de knop wordt gewijzigd
buttonArretMarche.Text = "Marche";
// einde
return;
}
}
}
}
- regel 13: de procedure die de klik op de knop Aan/Uit verwerkt.
- regel 15: de tekst op de knop Aan/Uit is ofwel „Uit” ofwel „Aan”. We moeten dus een controle uitvoeren op deze tekst om te weten wat we moeten doen.
- regel 17: in het geval van „Aan“ wordt de starttijd opgeslagen in een variabele début, een globale variabele (regel 11) van het formulierobject
- regel 19: initialiseert de inhoud van het label LabelChrono
- regel 21: de timer wordt gestart (Enabled=true)
- regel 23: de tekst van de knop verandert in "Stop".
- regel 27: in het geval van "Stoppen"
- regel 29: wordt de timer gestopt (Enabled=false)
- regel 31: de tekst van de knop wordt gewijzigd in "Start".
Nu moeten we nog de gebeurtenis Tick op het object timer1 afhandelen, een gebeurtenis die elke seconde plaatsvindt:
private void timer1_Tick(object sender, EventArgs e) {
// er is één seconde verstreken
DateTime maintenant = DateTime.Now;
TimeSpan durée = maintenant - début;
// de timer wordt bijgewerkt
labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
}
- regel 3: we noteren de huidige tijd
- regel 4: we berekenen de verstreken tijd sinds het moment waarop de stopwatch is gestart. Dit levert een object van het type TimeSpan op, dat een tijdsduur vertegenwoordigt.
- regel 6: deze moet in de stopwatch worden weergegeven in de vorm hh:mm:ss. Hiervoor gebruiken we de eigenschappen Hours, Minutes, Seconds van het object TimeSPan, die respectievelijk de uren, minuten en seconden van de tijdsduur vertegenwoordigen die we weergeven in het formaat ToString("d2") om een weergave met 2 cijfers te krijgen.
7.6. Voorbeeldtoepassing - versie 6
We nemen de voorbeeldtoepassing IMPOTS opnieuw onder de loep. De laatste versie is besproken in paragraaf 6.4. Het betrof de volgende drielaagse toepassing:
![]() |
- de lagen [metier] en [dao] waren ingekapseld in DLL
- de laag [ui] was een laag [console]
- het instantiëren van de lagen en hun integratie in de applicatie werd verzorgd door Spring.
In deze nieuwe versie wordt de laag [ui] verzorgd door de volgende grafische interface:
![]() |
7.6.1. De Visual Studio-oplossing
De Visual Studio-oplossing bestaat uit de volgende onderdelen:
![]() |
- [1]: het project bestaat uit de volgende onderdelen:
- [Program.cs]: de klasse die de toepassing start
- [Form1.cs]: de klasse van een eerste formulier
- [Form2]: de klasse van een tweede formulier
- [lib], gedetailleerd beschreven in [2]: hierin zijn alle DLL opgenomen die nodig zijn voor het project:
- [ImpotsV5-dao.dll]: de DLL van de laag [dao] die in paragraaf 6.4.3 is gegenereerd;
- [ImpotsV5-metier.dll]: de DLL van de laag [dao] die in paragraaf 6.4.4 is gegenereerd;
- [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: de DLL van Spring die al in de vorige versie werden gebruikt (zie paragraaf 6.4.6).
- [references], gedetailleerd beschreven in [3]: de projectreferenties. Er is een referentie toegevoegd voor elk van de DLL-bestanden in de map [lib]
- [App.config]: het configuratiebestand van het project. Dit is identiek aan dat van de vorige versie, zoals beschreven in paragraaf 6.4.6;
- [DataImpot.txt]: het bestand met de belastingschijven, dat zo is geconfigureerd dat het automatisch wordt gekopieerd naar de uitvoermap van het project [4]
Het formulier [Form1] is het invoerformulier voor de parameters van de belastingberekening [A], dat hierboven al is besproken. Het formulier [Form2] [B] dient om een foutmelding weer te geven:
![]() |
7.6.2. De klasse [Program.cs]
De klasse [Program.cs] start de applicatie. De code ervan is als volgt:
using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
namespace Chap5 {
static class Program {
/// <summary>
/// Het belangrijkste toegangspunt voor de applicatie.
/// </summary>
[STAThread]
static void Main() {
// door Vs gegenereerde code
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// --------------- Code van de ontwikkelaar
// instanties van de lagen [metier] en [dao]
IApplicationContext ctx = null;
Exception ex = null;
IImpotMetier metier = null;
try {
// Spring-context
ctx = ContextRegistry.GetContext();
// er wordt een verwijzing aangevraagd naar de laag [metier]
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
// opslag van uitzondering
ex = e1;
}
// formulier weergeven
Form form = null;
// is er een uitzondering opgetreden?
if (ex != null) {
// ja – er wordt een foutmelding aangemaakt om weer te geven
StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
Exception e = ex;
while (e != null) {
msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
e = e.InnerException;
}
// foutvenster aanmaken waaraan de weer te geven foutmelding wordt doorgegeven
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// dit wordt het venster dat moet worden weergegeven
form = form2;
} else {
// alles is goed verlopen
// aanmaak van de grafische interface [Form1] waaraan de referentie op de laag [metier] wordt doorgegeven
Form1 form1 = new Form1();
form1.Metier = metier;
// dit wordt het venster dat moet worden weergegeven
form = form1;
}
// weergave van het venster
Application.Run(form);
}
}
}
De door Visual Studio gegenereerde code is vanaf regel 19 aangevuld. De applicatie maakt gebruik van het volgende [App.config]- -bestand:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
<constructor-arg index="0" value="DataImpot.txt"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
- regels 24-32: gebruik van het vorige bestand [App.config] om de lagen [metier] en [dao] te instantiëren
- regel 26: verwerking van het bestand [App.config]
- regel 28: ophalen van een referentie naar de laag [metier]
- regel 31: opslaan van een eventuele uitzondering
- regel 34: de referentie form geeft aan welk formulier moet worden weergegeven (form1 of form2)
- regels 36-50: als er een uitzondering is opgetreden, wordt er voorbereiding getroffen om een formulier van het type [Form2] weer te geven
- regels 38-44: de weer te geven foutmelding wordt samengesteld. Deze bestaat uit de aaneenschakeling van de foutmeldingen van de verschillende uitzonderingen in de uitzonderingsketen.
- regel 46: er wordt een formulier van het type [Form2] aangemaakt.
- regel 47: zoals we later zullen zien, heeft dit formulier een openbare eigenschap MsgErreur, die het weer te geven foutbericht is:
public string MsgErreur { private get; set; }
Deze eigenschap wordt ingevuld.
- regel 49: de referentie form, die het weer te geven venster aanduidt, wordt geïnitialiseerd. Let op het polymorfisme dat hier aan het werk is. form2 is niet van het type [Form], maar van het type [Form2], een type dat is afgeleid van [Form].
- regels 50-57: er is geen uitzondering opgetreden. We bereiden ons voor op het weergeven van een formulier van het type [Form1].
- regel 53: er wordt een formulier van het type [Form1] aangemaakt.
- regel 54: zoals we later zullen zien, heeft dit formulier een openbare eigenschap Metier, die verwijst naar de laag [metier]:
public IImpotMetier Metier { private get; set; }
We vullen deze eigenschap in.
- regel 56: de referentie form, die het weer te geven venster aanduidt, wordt geïnitialiseerd. We zien hier opnieuw dat polymorfisme aan het werk is. form1 is niet van het type [Form], maar van het type [Form1], een type dat is afgeleid van [Form].
- regel 59: het venster waarnaar form verwijst, wordt weergegeven.
7.6.3. Het formulier [Form1]
In de modus [conception] ziet het formulier [Form1] er als volgt uit:
![]() |
De besturingselementen zijn als volgt
nr. | type | naam | rol |
0 | GroupBox | groupBox1 | Text=Bent u getrouwd? |
1 | RadioButton | radioButtonOui | aangevinkt indien gehuwd |
2 | RadioButton | radioButtonNon | aangevinkt indien niet gehuwd Checked=True |
3 | NumericUpDown | numericUpDownEnfants | aantal kinderen van de belastingplichtige Minimum=0, Maximum=20, Stap=1 |
4 | TextBox | textSalaire | jaarsalaris van de belastingplichtige in euro's |
5 | Label | labelImpot | te betalen belastingbedrag BorderStyle=Fixed3D |
6 | Knop | buttonCalculer | start de belastingberekening |
7 | Knop | buttonEffacer | zet het formulier terug in de staat waarin het zich bevond bij het laden |
8 | Knop | buttonQuitter | om de applicatie te verlaten |
Werkingsregels van het formulier
- de knop Calculer blijft uitgeschakeld zolang het salarisveld leeg is
- als bij het starten van de berekening blijkt dat het salaris onjuist is, wordt de fout gemeld [9]
De klassecode is als volgt:
using System.Windows.Forms;
using Metier;
using System;
namespace Chap5 {
public partial class Form1 : Form {
// laag [métier]
public IImpotMetier Metier { private get; set; }
public Form1() {
InitializeComponent();
}
private void buttonCalculer_Click(object sender, System.EventArgs e) {
// is het salaris correct
int salaire;
bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
if (! ok || salaire < 0) {
// foutmelding
MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
// terug naar het foutieve veld
textSalaire.Focus();
// tekst in het invoerveld selecteren
textSalaire.SelectAll();
// terug naar de invoerinterface
return;
}
// het salaris is correct – de belasting kan worden berekend
labelImpot.Text = Metier.CalculerImpot(radioButtonOui.Checked, (int)numericUpDownEnfants.Value, salaire).ToString();
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
Environment.Exit(0);
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// formulier leegmaken
labelImpot.Text = "";
numericUpDownEnfants.Value = 0;
textSalaire.Text = "";
radioButtonNon.Checked = true;
}
private void textSalaire_TextChanged(object sender, EventArgs e) {
// status knop [Calculer]
buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
}
}
}
We geven alleen uitleg bij de belangrijke onderdelen:
- regel [8]: de openbare eigenschap Metier waarmee de startklasse [Program.cs] in [Form1] een verwijzing naar de laag [metier] kan invoegen.
- regel [14]: de procedure voor de berekening van de belasting
- regels 15-27: controle van de geldigheid van het salaris (een geheel getal >=0).
- regel 29: berekening van de belasting met behulp van de methode [CalculerImpot] van de laag [metier]. Opvallend is de eenvoud van deze bewerking, die wordt bereikt door de inkapseling van de laag [metier] in een DLL.
7.6.4. Het formulier [Form2]
In de modus [conception] ziet het formulier [Form2] er als volgt uit:
![]() |
De controles zijn als volgt
nr. | type | naam | rol |
1 | TextBox | textBoxErreur | Multiline=True, Scrollbars=Both |
De code van de klasse is als volgt:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form2 : Form {
// foutmelding
public string MsgErreur { private get; set; }
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, System.EventArgs e) {
// de foutmelding wordt weergegeven
textBoxErreur.Text = MsgErreur;
// alle tekst wordt gedeselecteerd
textBoxErreur.Select(0, 0);
}
}
}
- regel 6: de openbare eigenschap MsgErreur waarmee de startklasse [Program.cs] het weer te geven foutbericht in [Form2] kan invoegen. Dit bericht wordt weergegeven tijdens de verwerking van de gebeurtenis Load, regels 12-16.
- regel 14: de foutmelding wordt in TextBox geplaatst
- regel 16: de selectie die bij de vorige bewerking is gemaakt, wordt ongedaan gemaakt. [TextBox].Select(begin,lengte) selecteert (markeert) longueur tekens vanaf teken nr. début. [TextBox].Select(0,0) komt neer op het deselecteren van alle tekst.
7.6.5. Conclusie
Laten we nog eens terugkomen op de gebruikte drielaagse architectuur:
![]() |
Dankzij deze architectuur konden we de console-implementatie van de bestaande laag [ui] vervangen door een grafische implementatie, zonder iets te wijzigen aan de lagen [metier] en [dao]. We konden ons concentreren op de laag [ui] zonder ons zorgen te maken over mogelijke gevolgen voor de andere lagen. Dat is het belangrijkste voordeel van drielaagse architecturen. We zullen hier later nog een ander voorbeeld van zien, wanneer de laag [dao], die momenteel gegevens uit een tekstbestand verwerkt, wordt vervangen door een laag [dao] die gegevens uit een database verwerkt. We zullen zien dat dit gebeurt zonder gevolgen voor de lagen [ui] en [metier].


































































