← RU Pythoncursus

Extra: een grafische gebruikersinterface

Deze opdracht is erg veel werk. Indien je geen zin hebt in het programmeren van een grafische gebruikersinterface mag je in plaats van deze opdracht ook werken aan opdrachten die je nog niet af hebt.

In deze opdracht gaan we een simpel grafisch programma in elkaar zetten. Het maken van grafische programma's is erg veel werk. Daarom beperken we ons hier tot een simpele rekenmachine om je een beetje een idee te geven van hoe dit in zijn werk gaat, zodat je eventueel na afloop van deze cursus zelf uitgebreidere grafische programma's kunt maken, mocht je daar interesse in hebben.

Graphical User Interfaces (GUI's) maken zeer intensief gebruik van objecten, klassen en methoden. Voor grotere grafische programma's is het daarom van belang dat je goed weet hoe dit werkt (zie hoofdstuk 14 uit CS Circles). Voor deze opdracht proberen we het simpel te houden. Waarschijnlijk is deze opdracht goed te maken zonder dat je precies hoeft te weten hoe objecten werken.

Om een grafisch programma te maken heb je een GUI-library nodig (die je met import importeert). We gebruiken voor deze opdracht de library QT. Deze library bevat allerlei handige functies om een GUI in elkaar te zetten. QT ondersteunt naast Python ook andere talen/platformen, zoals C++, Android of Java. Alternatieve GUI-libraries zijn GTK, TCL/TK of wxWidgets. Voor een uitgebreide introductie van QT in Python kun je hier kijken.

In het eerste deel van de deze opdracht gaan we een venster maken. Voor dit venster maken we een klasse IntroGUI. Dit object stoppen we in de variabele app en starten we met de code gui = IntroGUI(). Zodra je gui-code aanroept, wordt de code in de functie __init__() aangeroepen. Binnen de init-functie gebruiken we voor 'ingebouwde' GUI-methodes die over het venster zelf gaan het keyword self.

  1. We gaan nu een voorbeeld bekijken. Download het bestand intro.py, plaats het in PyCharm en voer het uit. Probeer te snappen wat er precies gebeurt. Indien je niet snapt wat er precies gebeurt, moet je even om hulp vragen omdat anders de rest van deze opgave erg lastig wordt. Het pictogram aap.png moet je zetten in dezelfde map als het intro.py bestand.

Een leeg venster is natuurlijk een beetje saai. We gaan daarom nu de benodigde knoppen voor een rekenmachine toevoegen. Om de knoppen in het venster netjes te ordenen, hebben we een layout nodig. Er zijn verschillende layouts mogelijk, waar wij hier gebruik maken van een gridlayout. Met een gridlayout kunnen we widgets in een 'grid' neerzetten: we kunnen een coördinatenstelsel gebruiken om de de widgets (in dit geval dus knoppen/buttons) op de goede plaats neer te zetten.

Buttons zelf worden gemaakt met een QPushButton. Als argument heeft dit widget een string nodig, dat de titel van je knopje wordt. Als we een button gemaakt hebben kunnen we hem aan de layout toevoegen.

Een voorbeeld van een enkele knop in een venster is te vinden in button.py

  1. Breid de voorbeeldcode uit met knoppen voor de getallen 0 tot en met 9 en de +, -, /, * en de =. Doe dit zo slim mogelijk: maak gebruik van loops en functies om de buttons te maken en aan de gridlayout toe te voegen.

Nu we de buttons allemaal aangemaakt hebben, moeten we ze gaan 'verbinden' met een 'listener'. Een listener is een methode die wordt uitgevoerd zodra er op een knopje geklikt wordt. Hierdoor moet je in de IntroGUI klasse een methode aanmaken, en deze methode verbinden met je knop. In deze methode kun je bijvoorbeeld een print(...) stoppen, zodat er wat in de console geprint wordt.

Een voorbeeld van hoe je een knop met een listener verbindt is te vinden in button-listener.py. Als je de voorbeeldcode begrepen hebt kun je nu listeners toevoegen aan al je knoppen. Je kunt voor iedere knop een aparte listener-functie maken, maar je kunt waarschijnlijk af met één listener en een slim if-statement.

  1. Koppel nu listeners aan de knoppen in de rekenmachine. Zorg ervoor dat de gebruiker op willekeurige knoppen kan drukken, om zo een som uit kunnen rekenen. Print het resultaat van je berekening (nu nog in de console) zodra de gebruiker op de knop = drukt. Voordat hij op = drukt moet je dus bijhouden op welke andere knoppen er gedrukt is. De rekenvolgorde kan nog lastig zijn: $2 + 3*5 = 17$, en niet $25$. Dit is een erg lastig probleem: het is mooi als je het opgelost krijgt, maar je mag het ook laten zitten en verder gaan.

De rekenmachine zou nu moeten werken, maar de uitvoer komt helaas nog steeds op de console te staan en niet in het venster met de rekenknoppen. Daar gaan we nu aan werken. Naast de standaard buttons, zijn er ook nog andere soorten widgets zoals tekstlabels en invoervelden.

Op dit moment maken we gebruik van een gridlayout om de knoppen netjes te rangschikken. Een gridlayout is hier ook zeer geschikt voor. Het uitvoerveldje van de rekenmachine past echter niet in de 'grid', maar hoort erboven te staan. We zullen daarom gebruik gaan maken van een vboxlayout, waarmee je widgets (of groepen van widgets - dus layouts) onder elkaar kunt plaatsen.

Om tekst te tonen gebruiken we de widget QLabel. De tekst van dit widget kan met de methode setText() gewijzigd worden.

Een voorbeeld van een QLabel in combinatie met een grid en vboxlayout is te vinden in qlabel.py.

  1. Vervang de print(...) in je programma door een QLabel en voeg een vboxlayout aan je rekenmachine toe. Als het goed is moet je rekenmachine nu volledig werken zonder console.

In plaats van een QLabel kun je ook een QLineEdit gebruiken. Hiermee kun je de tekst ook aanpassen. Zodra de tekst aangepast wordt kun je daar weer een melding in een listener van krijgen. Een voorbeeld is te vinden in qlineedit.py.

  1. Vervang de QLabel door een QLineEdit. Zorg er ook voor dat de gebruiker in plaats van op de knoppen te drukken een som in het QlineEdit tekstvakje in kan voeren en deze uit kan rekenen door op de knop = te drukken.

Naast knoppen kunnen we ook menubalken of toolbars toevoegen. Hiervoor moeten we een QMainWindow in plaats van een QWidget gebruiken. Dit zorgt ervoor dat een layout aan je venster toevoegen wat lastiger wordt. Een voorbeeld van een menubalk is te vinden in menubar.py. Een voorbeeld van een toolbar is te vinden in toolbar.py.

  1. Mocht je nog niet genoeg gekregen hebben van GUI's, dan kun je nu een menubalk en/of een toolbar aan je programma toevoegen met behulp van bovengenoemde voorbeeldcode.