1
Inhoud
Klassen & objecten, overerving, abstracte klassen, debuggen, interfaces, formulieren, polymorfie, statische methoden, event-handlers
2
Geluidsbronnen simulator, deel 2 • Inleiding • De weergave versnellen en een eigen usercontrol aanmaken • Interfaces & polymorfie • Event handlers • Formulieren
2.1
Inleiding
In het voorgaande WPO hebben we de verschillende onderdelen van de geluidsimulator behandeld. Het is nu mogelijk om verschillende geluidsbronnen weer te geven en de aard van een geluidsbron te programmeren. Het is weliswaar nog niet mogelijk om geluidsbronnen dynamisch toe te voegen. Bovendien maakt voorgaande implementatie nog steeds gebruik van een panel, waardoor we relatief beperkt zijn in de weergavesnelheid van C#. Tijdens dit WPO zullen we trachten om deze beperkingen te verhelpen.
2.2
De weergave versnellen en een eigen usercontrol aanmaken
De eindgebruiker verwacht een grafische interface dat snel en comfortabel is. Het algoritme dat we tot hiertoe behandeld hebben, tekent rechtreeks op de panel via het graphics object. Doordat hierdoor rechtreeks op het scherm getekend wordt, gaat er veel verwerkingstijd verloren aan het telkens updaten van het scherm na het tekenen van ´e´en enkele pixel. Om het tekenen aanzienlijk te versnellen, zullen we eerst het tekenen bufferen via het gebruik van een Bitmap. Ook voor een bitmap kan een graphics aangemaakt worden (zoek zelf uit hoe dit kan). Een pixel tekenen kan via de setPixel methode. Eens alle pixels geupdated zijn, kan de bitmap op de panel weergegeven worden (ook via een graphics object, maar van de panel zelf). Het gebruik van een panel beperkt ons in die mate omdat we het probleem van flikkerende beelden niet kunnen oplossen. Bovendien is het niet zo effici¨ent om via een panel o.a. muisgebeurtenissen (Eng. mouse events) af te handelen. Het gebruik van een panel verplicht ons ook om afhankelijk te zijn van een extern object. Om enerzijds de tekenbeperkingen als de functionele beperkingen te verhelpen, zullen we de bestaande eigenschappen van de UserControl overerven. Anderzijds is de UserControl vergelijkbaar met een klassieke button, label, enz. Het gebruik ervan laat ons dus toe om het weergegeven van de geluidsbronnen in een aparte entiteit te plaatsen. Bovendien kan een usercontrol ook gebruikt worden als een control binnen Visual Studio. 1
Enkele punten dienen hier weliswaar in rekening gehouden te worden. Doordat we nu rechtreeks van een usercontrol overerven, moeten we zelf de afmetingen (breedte en hoogte) van de control bijhouden. Hiervoor is het noodzakelijk om de methode OnResize te overriden. Het bijhouden van de afmetingen is noodzakelijk om de juiste grootte van de map/bitmap te bepalen. Doublebuffering laat ons toe om het flikkeren van het scherm te vermijden. Dit kan toegepast worden via een combinatie van verschillende stukken code. Eerst wordt in de constructor van de klasse volgende regel code toegevoegd. this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true); Nadien wordt al het tekenwerk verplaatst naar de event-handler OnPaint. Event handlers laten toe om wanneer een bepaalde gebeurtenis zich voordeed, een gepaste methode ervoor aan te roepen. Merk op dat de OnPaint event-handler noodzakelijk is om het flikkeren te vermijden. Aangezien dit event aangeroepen wordt vanuit C# zelf, moeten we een mechanisme voorzien dat deze methode aanroept. Dit kan door het huidig object te refreshen: this.Refresh(); Roep deze regel dus steeds aan wanneer het gepast is om te tekenen (wanneer de OnResize methode aangeroepen wordt, of wanneer men wenst te tekenen). Nice to have: Via Tools ->Options ->Windows Form Designer ->General ->AutoToolBoxPopuplate = true is het mogelijk om de eigen control in de toolbox van Visual Studio weer te geven. Nota: Het is mogelijk om het tekenen nog te versnellen. Dit valt echter buiten het bestek van dit vak. Meer informatie kan gevonden worden op : http://bobpowell.net/lockingbits.aspx.
2.3
Interfaces & polymorfie
De geluidsbronnen bestaan in verschillende vormen (sinuso¨ıdaal, blokgolf, zaagtand, randomgolven, enz.). Om te vermijden dat de geluidsmap voor elk type geluidsbron een aparte lijst moet bijhouden, aparte methodes en aparte tekenfuncties moet implementeren, worden de verschillende type geluidsbronnen door een interface bepaald. Deze interface wordt ge¨ımplementeerd door de verschillende type geluidsbronnen. De interface beschrijft de publieke methoden van elke geluidsbron dat deze implementeert. Alle klassen die de interface overerven, moeten dus de interface volledig implementeren. Dit zorgt ervoor dat de geluidsmap op voorhand weet welke methoden aanwezig zullen zijn bij elk type geluidsbron. M.a.w. kan een interface gezien worden als een “contract” tussen verschillende delen van een programma. Aangezien interfaces enkel een beschrijving zijn, zijn deze dus nooit instantieerbaar. Zorg ervoor dat via ´e´en enkele interface alle geluidsbronnen gekenmerkt kunnen worden. Ook de abstracte klasse voor regelmatige geluidsbronnen moet deze overerven!
2
2.4
Event handlers
Buttons, comboboxen, tekstvelden en andere controls beschikken over events die getriggered worden wanneer een bepaalde gebeurtenis plaatsvindt. Zo wordt het event “OnClick” getriggered wanneer een gebruiker op een knop klikt. Dit mechanisme laat toe om een externe functie/methode te koppelen aan een interne gebeurtenis. Op die manier blijven de functionaliteiten van een knop volledig gescheiden van de eigenschappen van de aan te roepen methode. Dit mechanisme bestaat uit 2 delen: het event, en de aan te roepen methode. Het eerste gedeelte bestaat uit een prototype declaratie (delegate) en het event als dusdanig. Beiden worden hieronder weergegeven. public delegate void onMouseClick_t(int x,int y); public event onMouseClick_t onMouseClick; Via de eerste regel wordt het prototype van het event gedeclareerd. Merk op dat de methode start met de returnwaarde “void”, en dat de argumenten al reeds gedefinieerd worden. De tweede regel zorgt voor de instantie van het event. Beide regels worden in de klasse zelf geschreven en het aanroepen (Eng. to fire) van dit event wordt binnen de klasse zelf verwezenlijkt via: if (onMouseClick != null) { onMouseClick(0,5); } Merk op dat we eerst nagaan of dat het event weldeglijk bestaat (dus verschillend van null ). Als alles goed gaat is via de Intellisense van Visual Studio een bliksemschichtje te zien wanneer men een methode aan dit event wenst te koppelen. Om dit event bruikbaar te maken, dient er in een andere klasse een methode aangemaakt te worden, waarbij het functieprototype exact overeenkomt met het prototype aangegeven door de declaratie van het event. Zo’n methode ziet er dus als volgt uit: private void onMapMouseClick(int x, int y) { MessageBox.Show("Mouse click detected at x = " + x + ", y = " + y); } Merk hierbij op dat de returnwaarde als de argumenten exact overeenkomen met wat de declaratie oplegt. Enkel de namen van de functie en de variabelen mag hierbij afwijken, niet het type. Als laatste dienen beide delen aan elkaar gekoppeld te worden. Dit gebeurt in de klasse die bovenstaande methode implementeerd, en het object bevat van waaruit het event afgevuurd wordt. Het koppelen vindt plaats op volgende wijze: soundmap.onMouseClick += this.onMapMouseClick; Waarbij soundmap de instantie is van het object dat het event zal afvuren. Merk op dat het toevoegen van een methode aan de event-list plaatsvindt met “+=”. Het verwijderen van de methode gebeurt via “-=”. 3
Schrijf zelf de gepaste delegates, event-handlers en opvangmethoden om o.a. een muisklikevent af te handelen. Hierbij dien je in de geluidsmap eerst de methode OnMouseClick te overriden. In deze methode wordt het event afgevuurd. Schrijf een methode in GUI dat toelaat om dit event af te vangen. Later zullen we aan dit event een extra scherm koppelen dat ons toelaat om nieuwe geluidsbronnen aan te maken.
2.5
Formulieren
In het laatste gedeelte van de applicatie zullen we een extra formulier aanmaken dat ons toelaat om de gegevens van een nieuwe geluidsbron in te geven. Het aanmaken gebeurt op dezelfde manier als een nieuwe klasse toevoegen. Het enige verschil nu is dat men voor een “Windows Form” dient te kiezen i.p.v. een klasse. Een nieuw tekengebied verschijnt, en het is mogelijk om alle controls er op te plaatsen dat men wenst. De code van dit formulier wordt in de klasse van dit formulier geschreven, en verloopt op dezelfde manier als voor het hoofdscherm. Het laten weergeven van dit formulier kan op twee manieren: via “Show()” of “ShowDialog()”. Het eerste zorgt ervoor dat het formulier enkel wordt weergegeven, en er geen rechtreekse band is tussen de aanroepcode en het formulier. De tweede methode laat ons toe om het gemaakte formulier te benaderen als een dialoog (Eng. dialog). We zullen de tweede methode hanteren aangezien deze iets transparanter is. Het venster kan aangeroepen worden gebruik makend van een aantal regels code (zie hieronder). // Veronderstel dat het formulier door de klasse NewSoundSourceForm omschreven wordt // De instantie wordt net zoals een ander object aangemaakt via de constructor NewSoundSourceForm soundsourcedialog = new NewSoundSourceForm(); // Het weergeven van het formulier (dialoog) kan gebeuren via de ShowDialog() methode. DialogResult res = soundsourcedialog.ShowDialog(); // In het andere geval volstond .Show(); Merk op dat de code stopt op de regel dat het formulier aanroept (enkel voor ShowDialog()). De enige manier om de code verder te laten lopen is door het nieuwe venster af te sluiten. Een dialoogvenster bestaat vaak uit een “OK” en een “Cancel” knop. Deze kunnen respectievelijk gekoppeld worden aan “DialogResult.OK” en “DialogResult.Cancel”, wat ook het returntype is van een dialoogvenster. Door in de aanroepcode onderstaand fragment te schrijven, is het mogelijk om een succes (of omgekeerd) op te vangen, en de gepaste handelingen uit te voeren. if (res == DialogResult.OK) { MessageBox.Show("OK clicked"); } In het dialoogformulier worden, om het opvangen mogelijk te maken, de knoppen “OK” en “Cancel” geplaatst, waarbij volgende code geschreven wordt: 4
private void btnOK_Click(object sender, EventArgs e) { this.DialogResult = DialogResult.OK; this.Close(); } /*----------------------------------------*/ private void btnCancel_Click(object sender, EventArgs e) { this.DialogResult = DialogResult.Cancel; this.Close(); } Merk op dat nadat het resultaat gezet wordt, ook het formulier afgesloten wordt. Data uitwisselen tussen de aanroepcode en het formulier is mogelijk, en vindt plaats op dezelfde wijze plaats als tussen de aanroepcode en een aangeroepen object. Dit kan via properties, getters en setters. Bij succes kan het resultaat via de gepaste methoden of properties opgevangen worden. Opgave: Zorg ervoor dat wanneer de gebruiker op de geluidsmap klikt, een dialoogvenster geopend wordt waarin de gebruiker het type geluidsbron, de co¨ordinaten en andere eigenschappen kan instellen. Via dit formulier moet het mogelijk zijn om alle type geluidsbronnen aan te maken. Indien het formulier een succes retourneert, wordt de nieuwe geluidsbron aan de map toegevoegd. Opgave: Zorg ervoor dat het hoofscherm beter oogt. Een lijst wordt naast de geluidsmap op het scherm weergegeven. Indien de gebruiker op een item uit de lijst klikt, wordt een scherm weergegeven waarin de gebruiker de eigenschappen van de geluidsbron kan aanpassen. Merk op dat er hier een geluidsbron wordt aangepast en niet aangemaakt! Opgave: Zorg ervoor dat de gebruiker het kleurenpalet kan aanpassen. De bestaande methoden moeten dus beschikbaar zijn binnen de GUI zodat de gebruiker tussen de aangeleverde paletten kan kiezen (eventueel via een combobox). Pas hierbij de geluidsmap aan zodat het palet ingesteld kan worden.
5