© Harry Broeders en Reinier Tetteroo.
Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft.
Met behulp van Visual C++ kun je twee soorten windows applicaties ontwikkelen:
Tot nu toe hebben we bij dit practicum console applicaties gemaakt. Ik zal jullie op deze pagina laten zien hoe eenvoudig het is om een Windows applicatie (inclusief menu, knoppen enz) in elkaar te zetten met behulp van Microsoft Visual C++ 2008 Express Edition.
Voordat we kunnen beginnen moet ik jullie eerst enkele dingen vertellen over het Windows OS (operating systeem). Zoals je wel weet is het operating systeem verantwoordelijk voor alle beheerszaken in de PC. Het operating systeem is onder andere verantwoordelijk voor:
Een applicatie kan (moet) dus voor al deze zaken gebruik maken van het operating systeem. De vraag is nu hoe communiceert jouw Windows-applicatie met het Windows OS. Dit gaat door middel van functie-aanroepen. Alle functies die aan te roepen zijn in het Windows operating systeem vormen een zogenaamde API (Application Programmers Interface). Als we gebruik maken van de zogenoemde Windows API dan draaien onze applicaties op WindowsXP, Windows Vista en Windows 7. Het aantal functies in deze API is heel groot en het gebruik van deze API is zeker niet eenvoudig. Behalve dat onze applicatie met het Windows OS moet communiceren moet het Windows OS ook events (gebeurtenissen) aan onze applicatie kunnen doorgeven. Denk daarbij aan dingen zoals:
Als z'n event optreedt dan stuurt het Windows OS een message naar het Window waarop dit event betrekking heeft. Een applicatie kan bestaan uit meerdere windows. Het OS ziet een knop of een scrollbar overigens ook als een apart "window". Elk window moet daarom bij het opstarten het adres van een zogenaamde messagehandler opgeven aan het OS. Als er dan een event optreedt dan zal het Windows OS de messagehandler van het betreffende window aanroepen. Als argument geeft Windows dan een message datastructuur mee. Deze message bevat het message-id en enkele parameters. De messagehandler van een window bestaat dus uit een groot switch statement waarin bepaald wordt welke message het OS naar dit window stuurt en welke actie het window daarop moet ondernemen.
Samengevat kunnen we dus zeggen:
De meeste windows applicaties zullen nadat ze opgestart zijn niets anders doen dan wachten op messages van het OS en afhankelijk van deze message's bepaalde acties uitvoeren. Omdat de events die optreden het gedrag van z'n programma bepalen noemt men zulke programma's event-driven.
Het Windows operating systeem heeft dus geen object georiënteerde interface maar een procedurele interface. Het ligt natuurlijk voor de hand om een groep met classes te ontwikkelen die de complexiteit van de win32 API voor ons verbergen. Een verzameling bij elkaar behorende classes die gebruikt kunnen worden om bepaalde applicaties te ontwikkelen (in dit geval GUI = Graphical User Interface applicaties) wordt een framework genoemd. Er zijn meerdere bekende frameworks voor het ontwikkelen van Windows applicaties:
Behalve de frameworks bieden zowel Microsoft, Nokia als Borland ontwikkeltools die het gebruik van deze frameworks aanzienlijk vereenvoudigen. Microsoft noemt deze hulptools Wizards. Zo heeft de Microsoft C++ ontwikkelomgeving een ClassWizard en een ApplicationWizard. De C++ Builder ontwikkelomgeving en de Qt ontwikkelomgeving hebben vergelijkbare tools.
Als je een applicatie ontwikkelt met behulp van het MFC framework maak je (bijna zonder dat je er erg in hebt) gebruik van overerving. Om een GUI applicatie te kunnen maken moet je het volgende doen:
Form1
in de editor.System::Windows::Forms::Form
. Het keyword
ref
is een
Microsoft
specifieke uitbreiding van C++. De class Form
heeft heel
veel properties en memberfuncties die Form1
dus allemaal overerft.
Properties
zijn een specifieke Microsoft uitbreiding van C++. Op dit moment kun je net
doen of properties membervariabelen zijn. Deze properties kun je eenvoudig
aanpassen in de Property Grid dat verschijnt als je op de tab Form1.h [Design]
klikt.Text
op Een window met een kruis
. Deze tekst
komt in de titelbalk van het Window.
Size
op 275; 275
. Het eerste getal staat voor
de breedte, het tweede voor de hoogte.
BackColor
op Window
.
WM_PAINT
) naar het window. We kunnen reageren op dit event
door met behulp van de Property Grid een eventhandler te definiëren.
Klik op de tab Events (het bliksemicoontje) en selecteer de regel
Paint
en dubbelklik op het invoerveld. De memberfunctie
Form1_Paint
wordt nu automatisch aan de class Form1
toegevoegd en de code voor deze memberfunctie wordt in de editor geopend.![]() |
![]() |
-
Het ^
teken is een specifieke Microsoft uitbreiding van C++
en wordt gebruikt om een zogenoemde
handle
(soort pointer) naar een managed object mee te definiëren. Een
managed object wordt gemaakt met
gcnew
(ook weer een specifieke Microsoft uitbreiding van C++) en wordt automatisch
opgeruimd (deleted) als het niet meer nodig is. Dit automatisch opruimen
wordt garbage collection genoemd.
Form1_Paint
de volgende code in:
e->Graphics->SmoothingMode = Drawing2D::SmoothingMode::AntiAlias;
Pen^ pen = gcnew Pen(Color::Gray, 2);
e->Graphics->DrawLine(pen, 0, 0, this->ClientSize.Width, this->ClientSize.Height);
e->Graphics->DrawLine(pen, 0, this->ClientSize.Height, this->ClientSize.Width, 0);
System::Drawing::Font^ font = gcnew System::Drawing::Font("Arial", 20);
e->Graphics->DrawString("Hallo!", font, Brushes::Blue, 10, 10);
Form1
is afgeleid van Form
en deze class
bevat een groot aantal properties en memberfuncties (methods). Zet in de
editor de cursor op het woord Form
en druk op F1 om uit te vinden
welke properties en memberfuncties beschikbaar zijn.Form
klikt, dan zie je de gegevens van de
klasse zelf. Als je dan naar beneden scrollt, kom je bij Inheritance Hierarchy.
Daar zie je dat een Form
is afgeleid van
ContainerControl
die is afgeleid van
ScrollableControl
die is afgeleid van Control
die
is afgeleid van Component
die is afgeleid van
MarhalByRefObject
die (tot slot) is afgeleid van
Object
. Ben je er nog?BackColor
en Text
(overgeërfd van Control
) die we al
eerder met behulp van de Property Grid hadden aangepast. Paint
event dat we hebben
afgevangen ook in deze lijst.
Form1_Paint
. Zet de
cursor op het woord PaintEventArgs
en druk op F1. In de lijst
met properties kun je vinden dat de variabele (property)
Graphics
die we in de memberfunctie Form1_Paint
hebben gebruikt een pointer naar een object van het type
Graphics
is. Klik erop om die property te bekijken. De eigenschappen
van deze class kun je in de helpfile vinden (klik op Graphics onder Property
Value):Graphics
een ding
waarop je kunt schrijven of tekenen. De class Graphics
bevat
veel properties en memberfuncties die je kunt gebruiken om te tekenen/schrijven.
Laten we de volgende 3 regels van de memberfunctie Form1_Paint
nog eens wat beter bekijken.
Pen^ pen = gcnew Pen(Color::Gray, 2);
e->Graphics->DrawLine(pen, 0, 0, this->ClientSize.Width, this->ClientSize.Height);
e->Graphics->DrawLine(pen, 0, this->ClientSize.Height, this->ClientSize.Width, 0);
In de eerste regel heb ik een nieuw object van het type Pen
gemaakt. Deze pen kan je in het Graphics
object gebruiken om
mee te tekenen. Zoals je in de help kunt vinden bevat een Pen
een kleur en een dikte. In de constructor heb ik deze eigenschappen opgegeven.
De kleur is een constante van de enumeratie Color
. Als kleur
heb ik Color::Gray
gebruikt, en de dikte van de pen heb ik op
2 pixels ingesteld.
Op de tweede regel gebruik ik de memberfunctie
DrawLine
van
Graphics
om met de pen een lijn
te tekenen van de positie 0,0 (linker bovenhoek) naar de positie
ClientSize
in het venster. ClientSize
is een property
(een soort member variabele) van Form1
(overgeërfd van
Form
) die het formaat van de binnenkant van het venster
aangeeft. ClientSize
is van het type Size
dat een
Width
en een Height
bevat. De positie
ClientSize.Width,ClientSize.Height
komt dus overeen met de rechter
benedenhoek van het window.
De derde regel is in principe hetzelfde als de tweede regel, alleen de
y-coördinaten zijn omgedraaid. Deze regel tekent dus een grijze lijn
van 2 pixels breed van linksboven naar rechtsonder in het window. Deze 3
regels samen tekenen een grijs kruis in het window.
Opdracht A.
Probeer nu zelf met behulp van de helpfiles van Visual C++ de rest van de
|
Opdracht B.Pas het programma nu zodanig aan dat het kruis met een groene stippellijn die slechts 1 pixel dik is wordt getekend. Zorg er tevens voor dat de tekst veel groter en in een rode kleur wordt weergegeven. |
Laten we het programma nu eens zodanig aanpassen dat dit programma reageert op bepaalde events die veroorzaakt worden door de gebruiker. Bijvoorbeeld op een muisklik. We willen het volgende:
Om dit mogelijk te maken moeten we een eventhandler schrijven de juiste acties uitvoert als op een muisknop wordt gedrukt:
Form1
moet een private variabele van het type
Color
worden opgenomen genaamd penKleur
. Deze variabele
wordt in de constructor op Color::Gray
(grijs) gezet en in de
Form1_Paint
memberfunctie gebruikt om de kleur van de pen te
kiezen.
Form1
ziet er nu als volgt uit:
private:
/// <summary>
/// Required designer variable.
/// </summary>
System::ComponentModel::Container ^components;Color penKleur;
Form1(void)
{
InitializeComponent();
penKleur = Color::Gray;
}
Form1_Paint
wordt nu:e->Graphics->SmoothingMode = Drawing2D::SmoothingMode::AntiAlias;
Pen^ pen = gcnew Pen(penKleur, 2);
e->Graphics->DrawLine(pen, 0, 0, this->ClientSize.Width, this->ClientSize.Height);
e->Graphics->DrawLine(pen, 0, this->ClientSize.Height, this->ClientSize.Width, 0);
System::Drawing::Font^ font = gcnew System::Drawing::Font("Arial", 20);
e->Graphics->DrawString("Hallo!", font, Brushes::Blue, 10, 10);
MouseDown
afvangen. De benodigde code ziet er als volgt uit: private: System::Void Form1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e)
{
switch(e->Button) {
case System::Windows::Forms::MouseButtons::Right: penKleur = Color::Red; break;
case System::Windows::Forms::MouseButtons::Left: penKleur = Color::Green; break;
}
Invalidate();
}
De memberfunctie die het MouseDown
event
afhandeld Form1_MouseDown
heeft parameters
e
dat van het type MouseEventArgs
is . Deze bevat
onder andere de property Button
die aangeeft welke muisknop
(links, midden of rechts) is ingedrukt.
Ook bevat MouseEventArgs
de properties X
en
Y
. Deze properties bevatten de positie in het window waar de
muiscursor zich bevond toen op de muisknop werd gedrukt.
De MouseEventArgs
bevat echter geen informatie over welke
combinatietoets eventueel is ingedrukt. Hiervoor kun je onderstaande code
gebruiken:
bool
shift = (this->ModifierKeys & Keys::Shift) == Keys::Shift;
bool control = (this->ModifierKeys & Keys::Control) == Keys::Control;
bool alt = (this->ModifierKeys & Keys::Alt) == Keys::Alt;
In de FormMouseDown
memberfunctie kennen we de gewenste kleur
toe aan de variabele penKleur
en roepen we de memberfunctie
Invalidate
van de baseclass Control
aan. Deze aanroep
zal een win32 API functie aanroepen waarmee de applicatie aan het Windows
OS vraagt of die het window opnieuw wil tekenen. Als het Windows OS hier
tijd voor heeft dan stuurt hij dit window de message WM_PAINT
.
Deze message zal het aanroepen van de memberfunctie Form1_Paint
tot gevolg hebben waarin het window opnieuw wordt getekend. We kunnen ook
zelf meteen de memberfunctie Form1_Paint
aanroepen vanuit de
Form1_MouseDown
functie. Als we Invalidate
aanroepen
wordt het window echter eerst gewist en dat gebeurt niet als we rechtstreeks
Form1_Paint
aanroepen.
Opdracht C.
Als je de grootte van het window aanpast door aan de randen van het window
te trekken dan wordt het kruis niet opnieuw getekend. Dit geeft
een slordig beeld. Als een window van grootte veranderd stuurt het windows
OS de message |
Opdracht D.
Pas het programma aan zodat de kleur van het kruis ook met het toetsenbord
aangepast kan worden. Als je op een G drukt moet de kleur groen worden, als
je op een R drukt rood en als je op een B drukt blauw. Om dit voor elkaar
te krijgen moet je de windows message |
Visual C++ bevat een groot aantal kant en klare componenten. Laten we eens kijken hoe je deze componenten kunt gebruiken om je programma van een menubalk en een knoppenbalk te voorzien. Deze componenten kunnen we gebruiken om commando's naar het programma te sturen.
Bepaalde commando's zullen zowel in de knoppenbalk als in de menubalk voorkomen. Verderop staat beschreven hoe je geen dubbele code hoeft te schrijven.
Hieronder volgt een overzicht van de commando's die we willen toevoegen:
Commando | Menu | In knoppenbalk? | Beschrijving |
---|---|---|---|
Afsluiten |
- |
Nee |
Sluit de applicatie. |
Blauw |
Kleur |
Ja |
Kleur het kruis blauw. |
Groen |
Kleur |
Ja |
Kleur het kruis groen. |
Rood |
Kleur |
Ja |
Kleur het kruis rood. |
Voordat we deze commando's gaan toevoegen gaan we eerst de plaatjes voor de knoppen tekenen:
We beginnen met het toevoegen van de menubalk:
MenuStrip
component in de Toolbox. De Toolbox
kun je zichtbaar maken via de tab aan de rechterkant of door op Ctrl+Alt+x
te drukken.MenuStrip
te benaderen.MenuStrip
worden
toegevoegd:
menuStrip1
. Klik nu op Type
Here
.
&Afsluiten
. Een & voor een letter zorgt
ervoor dat deze letter in het menu onderstreept wordt als de ALT toets wordt
ingedrukt. Je kunt dan die letter samen met de ALT toets indrukken om de
betreffende menuoptie te selecteren.
Type Here
rechts van Afsluiten.
&Kleur
.
Type Here
onder Afsluiten.
&Blauw
.
afsluitenToolStripMenuItem_Click
wordt nu aangemaakt en in de
editor geopend. Vul hier de volgende code in: Close();
blauwToolStripMenuItem_Click
wordt nu aangemaakt en in de editor geopend. Vul hier de volgende code in:
penKleur = Color::Blue;
Invalidate();
Als het commando Blauw
wordt gekozen wordt nu dus de penKleur
op Color::Blue
gezet en wordt het window opnieuw getekend.
We kunnen nu de knoppenbalk als volgt toevoegen:
ToolStrip
in de Toolbox.GripStyle
op Hidden
.
Padding
op 10;4;0;4
.
ToolStrip
worden
toegevoegd:
ToolStrip
op het icoontje Add
ToolStripButton
. Er wordt een nieuwe button aangemaakt, en gelijk
geselecteerd.
White
.
Blauw
.
blauwToolStripMenuItem_Click
event. Zo kan
je de eerder gemaakte code hergebruiken.ShowItemToolTips
van de ToolStrip
op
false
te zetten.
Helaas staan de knoppenbalk en de statusbalk over het kruis heen getekend.
Dit kunnen we oplossen door het kruis niet rechtstreeks op de
Form
te tekenen maar gebruik te maken van het Panel
component:
Panel
component in de Property Grid (onder
Containers).Dock
op Fill
(het middelste blokje). Dit zorgt
ervoor dat het Panel
alle (nog vrije) ruimte in de
TForm1
opvult.
Form1
en zet
Size
op 275;356
.
Panel
en gebruik de Property Grid om de event
Paint
af te vangen.
panel1_Paint
de volgende code in:
e->Graphics->SmoothingMode = Drawing2D::SmoothingMode::AntiAlias;
Pen^ pen = gcnew Pen(penKleur, 2);
e->Graphics->DrawLine(pen, 0, 0, panel1->Width, panel1->Height);
e->Graphics->DrawLine(pen, 0, panel1->Height, panel1->Width, 0);
System::Drawing::Font^ font = gcnew System::Drawing::Font("Arial", 20);
e->Graphics->DrawString("Hallo!", font, Brushes::Blue, 10, 10);
Form1_Paint
.
Windows Form Designer generated
code
). Het gaat om de volgende regel:this->Paint += gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::Form1_Paint);
Panel
en gebruik de Object Inspector om de event
MouseDown
af te vangen.
panel1_MouseDown
de volgende code in:
switch
(e->Button) {
case System::Windows::Forms::MouseButtons::Right: roodToolStripMenuItem_Click(this, EventArgs::Empty); break;
case System::Windows::Forms::MouseButtons::Left: groenToolStripMenuItem_Click(this, EventArgs::Empty); break;
}
Invalidate();
Form1_MouseDown
(ook uit de declaratie).
Form1_KeyPress
gewijzigd worden in:
switch(e->KeyChar) {
case 'r': case 'R': roodToolStripMenuItem_Click(this, EventArgs::Empty); break;
case 'g': case 'G': groenToolStripMenuItem_Click(this, EventArgs::Empty); break;
case 'b': case 'B': blauwToolStripMenuItem_Click(this, EventArgs::Empty); break;
}
Invalidate();
panel1->Invalidate();
Het aanpassen van de kleur door op de muisknoppen te klikken is nu een beetje overbodig.
panel1_MouseDown
(ook uit de declaratie).
Opdracht E.
Breidt het programma uit met een zogenaamd "context menu" (een popup menu
dat verschijnt als je op de rechtermuisknop drukt) waarmee je de kleur van
het kruis kunt kiezen. Dit kun je doen door een
|
Windows kent een aantal zogenaamde "common dialogs". Dit zijn standaard in Windows beschikbare dialogs. Laten we ons programma eens uitbreiden met de standaard dialog om een kleur te kiezen.
ColorDialog
component onder Dialogs in de
Toolbox.ColorDialog
te benaderen.Form1_KeyPress
als volgt uit:
switch(e->KeyChar) {
case 'r': case 'R': roodToolStripMenuItem_Click(this, EventArgs::Empty); break;
case 'g': case 'G': groenToolStripMenuItem_Click(this, EventArgs::Empty); break;
case 'b': case 'B': blauwToolStripMenuItem_Click(this, EventArgs::Empty); break; case 'k': case 'K':
if (colorDialog1->ShowDialog() == System::Windows::Forms::DialogResult::OK) {
penKleur = colorDialog1->Color;
panel1->Invalidate();
}
break;
}
K
toets te drukken.Opdracht F.
Zorg ervoor dat het commando kiezen ook in de menubalk, in de knoppenbalk
en in het popupmenu wordt opgenomen. Maak weer een Click methode aan voor
het commando in het menu, en roep die aan in |
Het is ook mogelijk om zelf dialog schermen te ontwerpen.
KleurDialoog
.
Opdracht G.
Zorg ervoor dat KleurDialoog er als volgt uitziet: |
Color GetKleur() {
if (radioButtonBlauw->Checked)
return Color::Blue;
else if (radioButtonGroen->Checked)
return Color::Green;
else if (radioButtonRood->Checked)
return Color::Red;
return Color::Empty;
}
Opdracht H.
Voeg de code toe die het dialoog sluit wanneer men op
|
Opdracht I.KleurDialoog moet met een commando vanuit Form1 worden geopend:
|
KleurDialoog
bij het openen meteen de
juiste radiobutton zou selecteren als het kruis al blauw, groen of rood is.
Dit kun je voor elkaar krijgen door een Set-methode voor de kleur te schrijven,
onder GetKleur()
:
void SetKleur(Color nieuweKleur) {
if (nieuweKleur == Color::Blue)
radioButtonBlauw->Checked = true;
else if (nieuweKleur == Color::Green)
radioButtonGroen->Checked = true;
else if (nieuweKleur == Color::Red)
radioButtonRood->Checked = true;
}
Form1
. Pas de
code als volgt aan:
kleurDialoog = gcnew KleurDialoog();
kleurDialoog->SetKleur(penKleur); 
kleurDialoog->ShowDialog();
penKleur = kleurDialoog->GetKleur();
panel1->Invalidate();
Opdracht J.
Ontwerp en implementeer zelf een dialog waarmee de tekst (inhoud en
lettertype) die in het |
Bij het gebruik van Visual C++ zijn er een aantal dingen die heel handig zijn om te weten:
Op de volgende plaatsen vind je meer informatie over het gebruik van Microsoft Visual C++ Express Edition.