events for the GUI •een event is iedere voor het programma van buiten komende gebeurtenis muis knop in, nog steeds in, los, bewogen ......
datastructuren college 13
•het GUI-systeem doet veel werk voor je er is een klasse GUI die het basiswerk doet
•maak zelf een subklasse van de klasse GUI •jij moet handlers (functies/methoden) schrijven die de events afhandelen •het GUI-systeem zorgt voor koppeling events aan handlers
GUI deel 2
programma hoeft zich dus nooit af te vragen of er ‘n event is en welke handler dan gebruikt moet worden
1
2
de GUI_kernel
ontwerp GUI_kernel
•waarom een GUI_Kernel
•de class GUI regelt het gedrag van de GUI
er zijn heelveel details te regelen in een GUI de ervaren programmeur wil dit kunnen er is daarom een ingewikkeld interface
de event handlers zijn methoden binnen deze class
•een specifieke GUI is een subclass van GUI definieert handlers (opnieuw) dynamic binding van methoden
als beginner wil je niet alle details de GUI_kernel biedt een simpel interface lang niet alles kan, maar wat kan gaat simpel(er)
•beperkingen er is maar een window
3
4
1
de GUI klasse
het canvas
class GUI { public:
•al het tekenen binnen het window op het canvas
void void
GUI Run Run
virtual virtual virtual virtual
void void void void
( GSIZE, char *title ); ( WINARGS winArgs ); ( Menu& menu, WINARGS winArgs ); Window Mouse Keyboard Timer
( ( ( (
friend class Canvas; ... private: ...
const const const const
RECT& area ) {} MOUSEINFO& mouse_info){} KEYINFO& key_info {} int dt ) {}
de handlers
5
my first GUI program
( ( ( ( ( ( ( ( ( ( ( ( (
GUI &gui ); RGBCOLOUR ); GPOINT ); ); ); GPOINT ); GPOINT, GPOINT ); const char *s ); GPOINT, GPOINT ); GPOINT points [], int size ); GPOINT, GPOINT ); GPOINT, GPOINT ); GPOINT points [], int size ); 6
•hoe maken we een GUI object maak gui-object binnen de try om fouten te vangen dus geen globaal object gebruik functie met static lokaal object schrijf bij gebruik theGUI( ) i.p.v. theGUI
de subklasse van GUI
class myGUI : public GUI stuk wat je { public: moet tekenen myGUI () : GUI(GSIZE(SreenWidth,ScreenHight),"my first GUI") {} void Window ( const RECT& area ); };
myGUI& theGUI() { static myGUI mg; return mg; }
void myGUI :: Window ( const RECT& area ) { Canvas canvas ( *this ); canvas . setPenColour ( RedRGB ); canvas . setPenPos ( GPOINT ( 30, 30 )); canvas . drawText ( "Hallo Nijmegen" ); }
zorg dat er maar één cavas is
my first GUI program 2
#include "gui_kernel.h" const int SreenWidth = 300; const int ScreenHight = 200;
class Canvas { friend class GUI; public: Canvas void setPenColour void setPenPos void toggleXORmode void drawPOINT void drawLineTo void drawRectangle void drawText void drawOval void drawPolygon void fillRectangle void fillOval void fillPolygon private:
de windowevent handler 7
zo gedraagt dit zich als globale, maar wordt pas binnen try gemaakt 8
2
my first GUI program slot
exceptions gooien
•gebruik exceptions om fouten af te handelen
•bedoeld om op een nette manier fouten af te vangen •als je fout opmerkt gooi je een exception
try en catch zorgen hiervoor gebruik copy/paste programming WinMain
char deelFout [] = "Delen door 0!";
i.p.v. main
int deel ( int t, int n ) { if ( n==0 ) throw deelFout; else return t/n; }
int WINAPI WinMain (HINSTANCE hi , HINSTANCE hpi, PSTR cmdLine, int cmdShow) { try { theGUI() . Run (WINARGS(hi, hpi, cmdLine, cmdShow)); } catch ( const GUIException& ge ) { ge.report (); alleen gebruikt als er } binnen de try een return 0; exception komt }
•je kunt dingen van ieder type gooien •vaak maak je een nieuwe class voor de fouten 9
exceptions vangen
10
een simpele key handler
•als exception gegooid wordt binnen een try, wordt die gevangen door de binnenste try met het goede type
void myGUI :: Keyboard ( const KEYINFO& key ) { if ( key . isASCII ) switch ( key . keyCode ) { case 'q': theGUI ( ) . startTimer ( ticks ) ; return; case 's': theGUI ( ) . Stop ( ) ; return; default: return; } }
int main ( int argc, char *argv[] ) { try { cout << deel (2,0) << endl; } catch ( int n ) vangt niets, verkeerde type { cout << n << " gevangen\n"; } catch ( char *s ) vangt onze fout { cout << s << endl; } vangt alles catch (...) { cout << "Onbekende exception gevangen!\n"; } system ( "PAUSE" ); return EXIT_SUCCESS; }
•geeft dus: Delen door 0!
11
12
3
muis handler
timer handler
•meestal lang •hier willen we lijnen tekenen •mouse down
void myGUI :: Timer ( const int dt ) { int tpm = 60 * ticks ; // aantal tikken per minuut tikken = ( tikken + dt ) % tpm; GPOINT eind ( mx + r * sin ( pi * 2 * tikken / tpm ), my – r * cos ( pi * 2 * tikken / tpm ));
moet dus ook lijn tekenen
begint lijn
•still down
gebruik XOR trucje
wis oude lijn; teken nieuwe
Canvas canvas ( *this ); canvas . setPenColour ( RedRGB ); canvas . fillOval ( lo, rb );
•up wis oude lijn; teken definitieve lijn
canvas . setPenColour ( WhiteRGB ); drawLine ( centrum, eind, canvas ); } 13
14
de GUI valkuil
slepen met objecten
•met een GUI zijn er 2 waarheden: wat de gebruiker ziet op het scherm de inwendige toestand van het programma
•het is de taak van de programmeur om dit te laten sporen bij de voorbeelden wordt alleen de eerste tekst getekend by een window event ! voor een echt programma moet je alle getekende lijnen onthouden je wil ook lijnen kunnen selecteren en weggooien
•we willen verschillende objecten binnen window •die objecten zijn te verplaatsen •maak één (abstracte) klasse Object: nieuwe basis klasse, géén subklasse GUI
class Object { public: GPOINT pos;
// de positie van het object // public, iedereen kan object verplaatsen Object ( GPOINT p ) : pos ( p ) { }; constructors Object ( ) : pos ( GPOINT ( 100, 100 )) { };
virtual bool inside ( GPOINT p ) { return false; }; virtual void draw ( Canvas& canvas ) { }; virtual void drag ( Canvas& canvas) { }; }; 15
slepen
herdefinieerbare methoden
16
4
cirkel methoden
subklasse cirkel
bool Circle :: inside ( GPOINT p ) { return pow ( p.x-pos.x, 2 ) + pow ( p.y-pos.y, 2 ) <= r*r; }
class Circle : public Object speciaal object, { nieuwe subklasse protected: int r; // de straal, object bevat middelpunt RGBCOLOUR color; methoden worden public: geherdefinieerd bool inside ( GPOINT p ); void draw ( Canvas& canvas ); void drag ( Canvas& canvas ); Circle ( GPOINT p, int d, RGBCOLOUR c ) : Object ( p ), r ( d ), color ( c ) { } };
pos
r
void Circle :: draw ( Canvas& canvas ) p { canvas . setPenColour ( color ); canvas . fillOval ( GPOINT ( pos.x-r, pos.y+r ), GPOINT ( pos.x+r, pos.y-r )); }
17
object-subklasse vierkant
void Circle :: drag ( Canvas& canvas ) // slepen { canvas . setPenColour ( color ); canvas . toggleXORmode ( ); // teken in XOR-mode canvas . drawOval ( GPOINT ( pos.x-r, pos.y+r ), GPOINT ( pos.x+r, pos.y-r )); canvas . toggleXORmode ( ); // XOR-mode weer uit }
18
de GUI bevat een rij van objecten
class Square : public Object { protected: int s; // de afmetingen, object bevat links-boven-hoek RGBCOLOUR color; public: bool inside( GPOINT p ); void draw ( Canvas& canvas ); void drag ( Canvas& canvas ); Square ( GPOINT p, int d, RGBCOLOUR c ) : Object ( p ), s ( d ), color ( c ) { } };
•implementatie methoden als bij cirkel 19
class myGUI : public GUI { Object *objects [ MaxObjects ];// de objecten van achter naar voor int nObjects; // het aantal objecten in de rij GPOINT mousePos; // de laatst bekende muis-positie public: myGUI ( ) : GUI(GSIZE(SreenWidth,ScreenHight),"Slepen"), nObjects(0) { objects[nObjects++]= new Circle(GPOINT(10,10),5,RedRGB); objects[nObjects++]= new ... vaste set objecten } void Window ( const RECT& area ); void Keyboard ( const KEYINFO& key ); void Mouse ( const MOUSEINFO& mouse ); void nieuwObject ( Object *obj ); void deleteObject ( ); void drawAll ( int n, Canvas& canvas ); void drawAl ( Canvas& canvas ); 20 };
5
GUI methoden: tekenen
GUI methoden: toevoegen
void myGUI :: drawAll ( int n, Canvas& canvas ) oudste n objecten { canvas.setPenColour ( WhiteRGB ); // poets alles weg: canvas.fillRectangle ( GPOINT(0,0), GPOINT ( SreenWidth, ScreenHight )); for ( int i = 0; i < n; i += 1 ) // teken alle objecten objects [ i ] -> draw ( ); }
void myGUI :: nieuwObject ( Object *obj ) { Canvas canvas( *this ); if ( nObjects < MaxObjects ) { objects [ nObjects++ ] = obj; obj -> draw ( canvas ); } else actieve object: Beep ( 10 ); } achteraan in rij
void myGUI :: drawAll ( Canvas& canvas ) { drawAll ( nObjects, canvas ); } void myGUI :: Window ( const RECT& area ) { Canvas canvas( *this ); drawAll ( canvas ); } 21
GUI methoden: weggooien void myGUI :: deleteObject ( ) { if ( nObjects > 0 ) { Canvas canvas( *this ); delete objects [ nObjects ]; nObjects -= 1; drawAll ( canvas ); } else Beep ( 10 ); }
22
keyboard handler
actieve object: achteraan in rij
23
void myGUI :: Keyboard ( const KEYINFO& key ) { if ( key . isASCII ) switch ( key . keyCode ) { case 'q': stop ( ); return; case 'n': nieuw ( ); return; default: return; } else switch ( key . keyCode ) { case WinBackSpKey: case WinDelKey: deleteObject ( ); return; default: return; } }
24
6
button down
mouse handler
Canvas canvas ( *this ); GPOINT p = mouse.mousePos; switch ( mouse . mouseState ) {case MouseDown: Object *touched = NULL; int i = nObjects-1; // begin bij laatste object
Gewenst gedrag muis: •button down: als boven object: object wordt bovenste (achteraan in rij), gepoetst, en getekend met drag
for ( ; i >= 0 ; i-=1 ) if (objects [ i ] -> inside ( p )) // muis binnen object ? { touched = objects [ i ]; break; } if ( touched != NULL) // object gevonden ? { for ( ; i < nObjects-1; i+=1) objects [ i ] = objects[ i+1 ]; // rest een naar voren objects [ i ] = touched; // touched boven aan drawAll ( nObjects-1, canvas ); // herteken rest objects [ nObjects-1 ] -> drag ( canvas ); } // drag actieve object mousePos = p; // bewaar muis positie break;
•mouse track: als oude binnen object: poets oude uit en teken nieuwe (met drag)
•button up: als oude binnen object: poets oude uit en teken nieuwe (met draw) 25
26
tracking (drag)
button up
case MouseTrack: if ( ( p != mousePos ) && // muis bewogen ? objects [ nObjects - 1 ] -> inside ( mousePos )) // object geselecteerd { is inside ( p ) ook goed? Object *o = objects [ nObjects – 1 ]; o->drag ( canvas ); // wis oude tekening // bereken nieuwe plek o->pos = GPOINT( o -> pos.x + p.x - mousePos.x, o -> pos.y + p.y - mousePos.y); o->drag ( canvas ); // teken op nieuwe plek mousePos = p; // bewaar muis positie
case MouseUp: if ( objects[ nObjects-1 ] -> inside ( mousePos )) {
static was handig geweest Object *o = objects [ nObjects-1 ]; o->drag ( canvas );
// wis oude tekening
o->pos = GPOINT( o -> pos.x + p.x - mousePos.x, o -> pos.y + p.y - mousePos.y ); o->draw ( canvas );
// teken definitief
mousePos = p; break; }
break; } 27
28
7
dialogen
dialogen
•twee mogelijke aanpakken
• twee aparte delen 1. layout: maken met resource editor (MS VC++) 2. methoden: als ander methoden beetje code nodig om koppeling te maken • als voorbeeld een simpele dialoog om nieuwe objecten te maken
programmeer waar alle knopjes, velden etc moeten komen teken met een speciale editor de dialoog, plak de bijbehorende code er in je programma aan
•voor statische dialogen is tweede methode handiger we doen een simpel voorbeeldje pas op: je hebt een nieuwe versie van de gui_kernel nodig !!
29
interface van class Dialog class Dialog { friend class GUI; public: Dialog Dialog& addButtonCallBack Dialog& addCheckCallBack Dialog& addRadioButtonCallBack
30
call-back functies •worden aangeroepen als dialog item gekozen is •krijgen altijd hun nummertje en de diakog mee
( ( ( (
DialogId, GUI& ); ControlId, ButtonCallBack ); ControlId, CheckCallBack ); ControlId, RadioButtonCallBack );
void
closeDialog
( );
void bool bool
getEditValue getRadioValue getCheckValue
( ControlId, char *, int ); ( ControlId ); ( ControlId);
void void void void
setEditValue setRadioValue setCheckValue setStaticValue
( ( ( (
typedef void ( *ButtonCallBack ) ( ControlId, Dialog& ); typedef void ( *CheckCallBack ) ( ControlId, bool, Dialog& ); typedef void ( *RadioButtonCallBack )( ControlId, Dialog& );
•in C++ is iedere functie die je meegeeft een pointer •b.v. de cancel call-back functie:
ControlId, const char * ); ControlId ); ControlId, bool ); ControlId, const char * );
void cancelCB ( ControlId button, Dialog& d ) { d . closeDialog ( ); }
private: .. 31
32
8
koppelen van call-backs aan dialoog #include "resource.h" // voor namen van dialoog en buttons void nieuw ( ) { Dialog d ( IDD_DIALOG1, theGUI ( ) ); // dialoog met vaste vorm // default waarden invullen en call-back functies eraan hangen d . setRadioValue ( RADIOBLAUW ); // set default d . setEditValue ( EDITGROOTTE, "10" ); // set default d . addButtonCallBack ( IDNIETS, nietsCB ); // handler d . addButtonCallBack ( IDCIRKEL, cirkelCB ); d . addButtonCallBack ( IDVIERKANT, vierkantCB ); }
•maken van dialog, b.v. via menu item, toets, .. int WINAPI WinMain ( ... { Menu menu ( "File" ); menu.add ( "New...", nieuw ) ...
button call-back functies •handler voor niets-knop (cancel) void nietsCB ( ControlId button, Dialog& d ) { d . closeDialog ( ); }
•vertaling van string naar int bool stringToInt ( char string [], int size, int& n ) { n = 0; for ( int i=0; i<size && isdigit(string[i]); i+=1 ) n = n*10+(string[i]-'0'); return n>0; }
33
cirkel call-back functie void cirkelCB ( ControlId button, Dialog& d ) { const int size = 40; int r; char string [ size ]; RGBCOLOUR color = BlackRGB; d . getEditValue( EDITGROOTTE, string, size ); if ( stringToInt ( string, size, r ) && r>0 && r<1000) { if ( d.getRadioValue ( RADIOROOD )) color = RedRGB; else if ( d.getRadioValue ( RADIOBLAUW )) color = BlueRGB; else if ( d.getRadioValue ( RADIOGROEN )) color = GreenRGB; else if ( d.getRadioValue ( RADIOGEEL )) color = YellowRGB; theGUI ( ). nieuwObject ( new Circle ( GPOINT ( 100, 100 ), r, color )); d.closeDialog ( ); } } 35
34
vierkant call-back functie •op één regel na gelijk aan die voor cirkel dat moet beter kunnen ! gebruik één object-call-back functie void nieuw ( ) { Dialog d ( IDD_DIALOG1, theGUI ( )); d.setRadioValue ( RADIOBLAUW ); d.setEditValue ( EDITGROOTTE, "10" ); d.addButtonCallBack ( IDNIETS, nietsCB ); d.addButtonCallBack ( IDCIRKEL, objectCB ); d.addButtonCallBack ( IDVIERKANT, objectCB ); }
één handler: abstractie 36
9
object call-back functie
GUI-functies
void objectCB ( ControlId button, Dialog& d ) { const int size = 10; int r; char string [ size ]; RGBCOLOUR color = BlackRGB; d . getEditValue( EDITGROOTTE, string, size ); if ( stringToInt ( string, size, r ) && r>0 && r<1000) { if ( d.getRadioValue ( RADIOROOD )) color = RedRGB; else ...
•handlers voor events, horen bij één window methoden van class GUI •liggen vast in subtype van GUI
handlers voor: window, keyboard, muis, timer
•call-back-functies voor geen methoden van GUI ! menu functies •vast leggen bij opstarten GUI
if (button == IDCIRKEL) theGUI().nieuwObject(new Circle(GPOINT(100,100),r,color)); else theGUI().nieuwObject(new Square(GPOINT(100,100),r,color)); d.closeDialog();
dialog items •dialogen worden dynamisch gemaakt als dat nodig is •definitie ligt meestal vast •eenvoudig te maken met resource editor
} }
•zorg steeds dat toestand en scherm spoort !! 37
Wat hebben we gedaan
het vervolg
• •
•extra opgave over lijsten/bomen op jullie verzoek inleveren optioneel, slechtste beoordeling telt niet mee
dictaat: H 15, bekijk voorbeelden abstractie: laat details weg één klasse object als basis voor cirkels en vierkanten
•
•toetsresultaten komen zo snel mogelijk •volgende week pinksteren geen college, wel practicum •week daarna: overzichtscollege/samenvatting •vragenuur ?
•
GUI is gebaseerd op objecten programmeur schrijft handlers voor events systeem zorgt voor aanroepen handlers systeem zorgt voor standaard dingen
•wat kan er beter aan dit vak?
•
wat moet zo blijven? 'verplicht' aanwezig?
39
extra figuur toevoegen is nu heel eenvoudig
één callback voor cirkels en vierkanten is altijd een goed idee
je kunt altijd langskomen/mailen met vragen
•tentamen 3 juli, 10:30, HG00 307
38
menus tekenen, window resizen, buttons, filteren events
zorg dat er steeds maar één canvas per handler is ! 40
10