15. Nov 2022
Lesedauer 11 Min.
Zur Auswahl
Grafische Benutzeroberflächen für Python-Programme (Teil 6)
Mit Hilfe des GUI-Toolkits tkinter kann man komfortabel Listboxen in Python-Applikationen integrieren.

Nach den Radiobuttons und den Checkbuttons schließen wir hier die Auswahlfelder mit den Listboxen ab. Listboxen von tkinter besitzen ein eigenes Erscheinungsbild, sie sind jedoch äußerst flexibel und sie lassen sich in vielerlei Hinsicht anpassen. Anders als Radiobuttons, die nur eine Einfachauswahl erlauben und Checkbuttons, die speziell für eine Mehrfachauswahl vorgesehen sind, können Sie Listboxen ohne Mehraufwand für Einfach- als auch für Mehrfachauswahlen einsetzen.
Listboxen integrieren
Radiobuttons und Checkboxen haben Sie bereits kennengelernt. Ein weiteres Auswahlfeld ist die Listbox. Die Listbox selbst wird mit der gleichnamigen Klasse erstellt. Die gewünschten Einträge können Sie mit der Methode insert() hinzufügen. Als Beispiel soll die Browser-Auswahl dienen, die wir in Teil 4 mit Radiobuttons umgesetzt hatten.
Mit der Klasse Listboxumgesetzte Auswahlliste, in der genau ein Element ausgewählt werden kann(Bild 1)
Saumweber
Im folgenden Listing wird diese Auswahl mit einer Listbox umgesetzt (Bild 1), ansonsten ändert sich im Code nicht allzu viel:
<span class="hljs-keyword">from</span> tkinter import *
<span class="hljs-keyword">from</span> tkinter.font import *
<span class="hljs-keyword">class</span> App(Frame):
def __init__(<span class="hljs-keyword">self</span>, parent):
super().__init__(parent)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">1</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">2</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.myfont = Font(family=<span class="hljs-string">'Calibri'</span>, size=<span class="hljs-number">12</span>)
<span class="hljs-keyword">self</span>.browser = [<span class="hljs-string">'Firefox'</span>, <span class="hljs-string">'Google Chrome'</span>, <span class="hljs-string">'Microsoft Edge'</span>, <span class="hljs-string">'Opera'</span>, <span class="hljs-string">'Safari'</span>]
<span class="hljs-keyword">self</span>.label1 = Label(<span class="hljs-keyword">self</span>, text=<span class="hljs-string">'Welcher Browser ist Ihrer Meinung nach der beste?'</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label1.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
<span class="hljs-keyword">self</span>.listbox = Listbox(<span class="hljs-keyword">self</span>, height=<span class="hljs-number">5</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.listbox.grid(row=<span class="hljs-number">1</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>, pady=<span class="hljs-number">3</span>)
# Einträge zur Listbox hinzufügen
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.browser:
<span class="hljs-keyword">self</span>.listbox.insert(<span class="hljs-keyword">END</span>, item)
<span class="hljs-keyword">self</span>.label2 = Label(<span class="hljs-keyword">self</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label2.grid(row=<span class="hljs-number">2</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.listbox.bind(<span class="hljs-string">'<<ListboxSelect>>'</span>, <span class="hljs-keyword">self</span>.listb_sel)
def listb_sel(<span class="hljs-keyword">self</span>, <span class="hljs-keyword">event</span>):
<span class="hljs-keyword">index</span> = <span class="hljs-keyword">self</span>.listbox.curselection()
browser = <span class="hljs-keyword">self</span>.listbox.get(<span class="hljs-keyword">index</span>)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = f<span class="hljs-string">'Ausgewählt: {browser}'</span>
def main():
root = Tk()
root.title(<span class="hljs-string">'Listbox'</span>)
root.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
root.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
app = App(root)
app.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
root.mainloop()
main()
Die Browser, die zur Auswahl stehen, sind wiederum in einer Liste (self.browser) gespeichert. Allerdings genügt hier eine Liste von Strings, da wir im Weiteren allein die Browsernamen für die Listeneinträge benötigen (im genannten Beispiel aus Teil 4 hatten wir zusätzlich einen Integerwert für den row-Index des Grid-Managers gespeichert; während in einer Gruppe von Radiobuttons jeder Radiobutton ein eigenes Element darstellt und somit separat platziert werden muss, fungiert die Listbox mit ihren Auswahlpunkten als ein einziges Element). Die Browserliste wird in einer for-Schleife durchlaufen, wobei jedes Element am jeweiligen Ende der Listeneinträge hinzugefügt wird. Dies bewirkt die Konstante END für den ersten Parameter der insert()-Methode. Ansonsten können Sie einen neuen Listeneintrag durch Angabe eines Index an die gewünschte Stelle platzieren, mit self.listbox.insert(0, 'Listeneintrag') zum Beispiel an den Anfang der Liste (eine entsprechende Konstante wie zum Beispiel START als Pendant zu END gibt es hierfür nicht). Weitere Parameter außer Indexposition und Listeneintrag besitzt die insert()-Methode nicht. Einen bestimmten Font, mit dem Sie die Listeneinträge versehen wollen, notieren Sie wie im Beispiel beim Erstellen des Listbox-Objekts als Parameter der __init__()-Methode.
padx, pady
Mit den Parametern padx und pady der grid()-Methode können Sie den freien Platz rechts und links (padx-Wert) bzw. ober- und unterhalb (pady-Wert) des zu platzierenden Widgets festlegen. Der Wert 3 für pady vergrößert im obigen Beispiel den Abstand zwischen der Listbox und den beiden Label-Elementen um ein kleines bisschen – self.listbox.grid(…, pady=3). Die pack()-Methode stellt die gleichen Parameter zur Verfügung.
Die Methode curselection() des Listbox-Objekts liefert ein Tupel mit den Indizes der ausgewählten Einträge. Im Beispiel ist das genau einer, da in einer Listbox standardmäßig nur ein Eintrag ausgewählt werden kann. Mit der get()-Methode erhalten Sie schließlich den Eintrag selbst. In der Ereignisbehandlungsmethode self.listb_sel() des obigen Beispiels haben wir wegen der besseren Lesbarkeit des Codes mit der Anweisung index = self.listbox.curselection() zunächst den von curselection() zurückgegebenen Index gespeichert und anschließend in der Variablen browser den ausgewählten Listeneintrag, das heißt den ausgewählten Browser – browser = self.listbox.get(index).
Zwei Schritte in einer Anweisung
In einer Anweisung lassen sich die beiden Schritte wie folgt zusammen fassen: browser = self.listbox.get(self.listbox.curselection()) – der Rückgabewert der curselection()-Methode wird sofort der get()-Methode übergeben. Die Ereignisbehandlungsmethode self.listb_sel() reagiert auf das ListboxSelect-Ereignis, das mit der bind()-Methode des Listbox-Objekts an dieses gebunden wird:
self<span class="hljs-selector-class">.listbox</span><span class="hljs-selector-class">.bind</span>(<span class="hljs-string">'<<ListboxSelect>>'</span>, self.listb_sel)
Eine besondere Bedeutung kommt dem Listbox-Parameter height zu. Mit dem Wert für diesen Parameter können Sie die Anzahl der Zeilen der Listbox festlegen und damit die Anzahl der Listeneinträge, die in der Listbox sofort zu sehen sind. So gesehen bestimmt height zwar ebenfalls wie bei anderen Widgets die Höhe des Elements, nur wird diese bei der Listbox nicht in Pixeln, sondern eben in Zeilen angegeben. Beachten Sie, dass leere Zeilen in einer Listbox nicht ausgewählt werden können. Dementsprechend ist ihnen auch kein Index zugeordnet. Standardmäßig wird height auf 10 gesetzt, das heißt wenn Sie den Parameter nicht explizit angeben, ist der sichtbare Bereich der Listbox so groß, dass zehn Einträge darin Platz haben. Da im Beispiel fünf Browser zur Auswahl stehen, bietet sich der Wert 5 für height an. Mit diesem Wert sind alle Browser sofort sichtbar und die Listbox enthält keine leeren, sprich überflüssigen Zeilen.Das Gleiche hätten wir im Übrigen auch mit dem height-Wert 0 erreichen können. Die Einstellung height=0 bedeutet so viel wie »Mache die Listbox genau so groß, wie Einträge vorhanden sind«.Elemente, die eine Listbox von Anfang an besitzen soll, lassen sich auch noch auf andere Weise hinzufügen als wie gezeigt über die insert()-Methode, nämlich indem man ein Variable-Objekt mit den gewünschten Listeneinträgen generiert und dieses der listvariable-Eigenschaft des Listbox-Objekts zuweist. Dementsprechend könnte man im obigen Listing statt der for-Schleife mit dem gleichen Ergebnis die folgende Anweisung notieren:
self.browser = [<span class="hljs-string">'Firefox'</span>, <span class="hljs-string">'Google Chrome'</span>, <span class="hljs-string">'Microsoft Edge'</span>, <span class="hljs-string">'Opera'</span>, <span class="hljs-string">'Safari'</span>]
…
# Einträ<span class="hljs-keyword">ge</span> zur Listbox hinzufügen
self.listbox[<span class="hljs-string">'listvariable'</span>] =
<span class="hljs-keyword">Variable</span>(value=self.browser)
Alle anderen Codeteile bleiben gleich (in der Datei Beispielcode.txt finden Sie dennoch das komplette geänderte Listing). Die Zuweisung der Liste self.browser an die Eigenschaft value des Variable-Objekts (value=self.browser) verbindet dieses mit der Browserliste bzw. mit den entsprechenden Einträgen. Wenn es darum geht, Listeneinträge dynamisch hinzuzufügen, dann sind Sie jedoch allein auf die insert()-Methode angewiesen (siehe dazu weiter unten den Abschnitt »Listbox-Einträge dynamisch hinzufügen«).
Scrollbalken hinzufügen
Wenn Sie eine Listbox ähnlich wie das von vielen Anwendungsprogrammen her bekannte Listenfeld darstellen wollen, dann setzen Sie height auf den Wert 1. Im Prinzip bräuchten Sie nichts weiter zu tun, da ein Benutzer in der tkinter-Listbox immer alle Einträge mit den Pfeiltasten auswählen kann, unabhängig davon, wie viele anfänglich sichtbar sind. Das Scrollen mit dem Mausrad funktioniert jedoch, wenn überhaupt, nur theoretisch; in der Praxis werden die Einträge dabei so schnell übersprungen, dass man es gar nicht mitbekommt. Um Ihren Benutzern die Bedienung von Listboxen einfacher bzw. komfortabler zu machen, ist es daher sinnvoll, Scrollbalken zur Verfügung zu stellen. Von den genannten typischen Listenfeldern ist man Scrollbalken sowieso gewohnt, der Benutzer erwartet sie geradezu, und praktisch sind sie in jedem Fall, egal, wie viele Einträge eine Listbox hat und wie viele davon »versteckt« sind.In tkinter haben Widgets grundsätzlich keine Scrollbalken. Diese sind eigenständige Steuerelemente vom Typ Scrollbar, die sich aber praktisch jedem anderen Widget hinzufügen lassen, also auch einer Listbox. Vom Ergebnis her kommt es also auf dasselbe heraus. Durch die Trennung sind tkinter-Scrollbars sogar flexibler, da sie prinzipiell an jeder beliebigen Stelle platziert werden können. Üblicherweise wird man das auf der rechten Seite des Widgets tun, eine Scrollbar kann aber genauso gut auch auf der linken Seite, oberhalb oder unterhalb eines Widgets – und theoretisch auch an einer Stelle, die von dem Widget, auf das sie sich bezieht, weiter entfernt ist – platziert werden.Vertikaler oder horizontaler Scrollbalken
Beim Erstellen eines Scrollbar-Objekts geben Sie an, ob der Skrollbalken vertikal oder horizontal verlaufen soll, indem Sie dem Parameter orient eine der Konstanten VERTICAL oder HORIZONTAL zuweisen. Damit die Scrollbar von dem Widget, zu dem sie funktional gehört, »weiß«, muss dem command-Parameter der Scrollbar die Methode yview (bei vertikalen Scrollbalken) bzw. die Methode xview (bei horizontalen Scrollbalken) des Widgets zugewiesen werden. Umgekehrt muss dem Parameter yscrollcommand (bei vertikalen Scrollbalken) bzw. dem Parameter xscrollcommand (bei horizontalen Scrollbalken) des Widgets die Methode set der Scrollbar zugewiesen werden.
Die einzeilige Listboxenthält nach wie vor fünf Browser zur Auswahl. Der Benutzer kann scrollen oder, wenn das Feld den Fokus hat, mit den Pfeiltasten navigieren(Bild 2)
Saumweber
Schließlich muss die Scrollbar entsprechend reagieren, wenn ein Benutzer innerhalb der Listbox mit den Pfeiltasten navigiert, und ebenso muss sich die Anzeige der Listbox anpassen, wenn ein Benutzer die Scrollbar betätigt, wenn er also nach unten oder nach oben scrollt. Um mehr brauchen Sie sich aber nicht zu kümmern – es reicht, wenn Sie die beiden genannten Methodennamen wechselseitig zuweisen. Zu beachten ist lediglich die Reihenfolge, in Abhängigkeit davon, welches Widget zuerst erstellt wird. Im folgenden Beispiel wird zuerst die Scrollbar und danach die Listbox erstellt, folglich muss die Zuweisung von yview an den command-Parameter der Scrollbar nachträglich erfolgen. Das folgende Listing ergänzt die Browser-Listbox auf der rechten Seite um eine Scrollbar (Bild 2):
<span class="hljs-keyword">from</span> tkinter import *
<span class="hljs-keyword">from</span> tkinter.font import *
<span class="hljs-keyword">class</span> App(Frame):
def __init__(<span class="hljs-keyword">self</span>, parent):
super().__init__(parent)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">1</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">2</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.myfont = Font(family=<span class="hljs-string">'Calibri'</span>, size=<span class="hljs-number">12</span>)
<span class="hljs-keyword">self</span>.browser = [<span class="hljs-string">'Firefox'</span>, <span class="hljs-string">'Google Chrome'</span>, <span class="hljs-string">'Microsoft Edge'</span>, <span class="hljs-string">'Opera'</span>, <span class="hljs-string">'Safari'</span>]
<span class="hljs-keyword">self</span>.label1 = Label(<span class="hljs-keyword">self</span>, text=<span class="hljs-string">'Welcher Browser ist Ihrer Meinung nach der beste?'</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label1.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
<span class="hljs-keyword">self</span>.frame_listbox = Frame(<span class="hljs-keyword">self</span>)
<span class="hljs-keyword">self</span>.scrollbar = Scrollbar(<span class="hljs-keyword">self</span>.frame_listbox, orient=VERTICAL)
<span class="hljs-keyword">self</span>.listbox = Listbox(<span class="hljs-keyword">self</span>.frame_listbox, height=<span class="hljs-number">1</span>, font=<span class="hljs-keyword">self</span>.myfont, yscrollcommand=<span class="hljs-keyword">self</span>.scrollbar.set)
<span class="hljs-keyword">self</span>.scrollbar[<span class="hljs-string">'command'</span>] = <span class="hljs-keyword">self</span>.listbox.yview
<span class="hljs-keyword">self</span>.listbox.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>, pady=<span class="hljs-number">3</span>)
<span class="hljs-keyword">self</span>.scrollbar.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">1</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.frame_listbox.grid(row=<span class="hljs-number">1</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
# Einträge zur Listbox hinzufügen
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.browser:
<span class="hljs-keyword">self</span>.listbox.insert(<span class="hljs-keyword">END</span>, item)
<span class="hljs-keyword">self</span>.label2 = Label(<span class="hljs-keyword">self</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label2.grid(row=<span class="hljs-number">2</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.listbox.bind(<span class="hljs-string">'<<ListboxSelect>>'</span>, <span class="hljs-keyword">self</span>.listb_sel)
def listb_sel(<span class="hljs-keyword">self</span>, <span class="hljs-keyword">event</span>):
<span class="hljs-keyword">index</span> = <span class="hljs-keyword">self</span>.listbox.curselection()
browser = <span class="hljs-keyword">self</span>.listbox.get(<span class="hljs-keyword">index</span>)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = f<span class="hljs-string">'Ausgewählt: {browser}'</span>
def main():
root = Tk()
root.title(<span class="hljs-string">'Listbox'</span>)
root.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
root.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
app = App(root)
app.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
root.mainloop()
main()
Damit der Scrollbalken unmittelbar an die Listbox angrenzt, ist es hier notwendig, beide in einen Frame zu integrieren. Mit der Anweisung self.frame_listbox.grid(row=1, column=0, sticky='W') wird der Frame dann als Ganzes in der zweiten Zeile platziert. Würde man die Listbox und die Scrollbar separat platzieren, also in den Spalten column=0 und column=1 der zweiten Zeile, dann würde der Scrollbalken sehr weit rechts erscheinen, da die zweite Spalte erst am Ende des Textes »Welcher Browser ist Ihrer Meinung nach der beste?« beginnt.Eine Scrollbar erscheint ausgegraut und bleibt ohne Funktion, wenn die Listbox so groß ist, dass alle auszuwählenden Elemente sichtbar sind. In diesem Fall sollte man logischerweise auf eine Scrollbar verzichten, es sei denn, Listeneinträge können während der Programmausführung dynamisch hinzugefügt werden.Standardmäßig kann in einer Listbox immer nur ein einziges Element ausgewählt sein. Dies entspricht der Einstellung selectmode=BROWSE. Gleichzeitig kann ein Benutzer unter dieser Einstellung die Markierung mit der Maus per Drag-and-drop nach oben oder nach unten ziehen, um ein anderes Element auszuwählen – die Auswahl folgt gewissermaßen der Maus. Verwenden Sie die Einstellung selectmode=SINGLE, wenn Sie diesen Effekt nicht wünschen. Der Benutzer kann die Auswahl im SINGLE-Modus allerdings auch nicht mehr mit den Pfeiltasten, sondern nur noch per Mausklick durchführen. Auswählen kann er auch in diesem Modus nur jeweils ein Element. Für eine Mehrfachauswahl weisen Sie selectmode eine der beiden Konstanten EXTENDED oder MULTIPLE zu.
activestyle='none'
Ausgewählte Listeneinträge werden in tkinter-Listbox-Elementen standardmäßig unterstrichen dargestellt, was der Einstellung activestyle='underline' entspricht. Das ist optisch durchaus ansprechend. Von den üblichen Listenfeldern anderer Programme ist man diesen Effekt jedoch nicht gewohnt. Übergeben Sie der __init__()-Methode der Listbox-Klasse activestyle='none', falls Sie keine Unterstreichungen wünschen.
Auch diese unterscheiden sich bezüglich des Mausverhaltens. Unter der Einstellung EXTENDED kann der Benutzer die Maus über beieinanderliegende Elemente ziehen, um sie gemeinsam auszuwählen. Außerdem kann er die Elemente auswählen, indem er sie bei gedrückter Strg-Taste nacheinander anklickt. Die Einstellung selectmode=MULTIPLE deaktiviert beides, das Aufziehen mit der Maus sowie die Strg-Taste. Letzten Endes gestaltet sich die MULTIPLE-Auswahl aber genauer und bequemer: Der Benutzer klickt die Element nacheinander an, um sie auszuwählen. Er braucht dabei keine Taste zu drücken, die bereits selektierten Elemente bleiben in diesem Zustand. Um ein Element wieder abzuwählen, klickt man es einfach erneut an. Probieren Sie die verschiedenen Einstellungen am besten selbst einmal aus.Das folgende Listing setzt auf Basis des Einführungsbeispiels eine Mehrfachauswahl um:
<span class="hljs-keyword">from</span> tkinter import *
<span class="hljs-keyword">from</span> tkinter.font import *
<span class="hljs-keyword">class</span> App(Frame):
def __init__(<span class="hljs-keyword">self</span>, parent):
super().__init__(parent)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">1</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">2</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.myfont = Font(family=<span class="hljs-string">'Calibri'</span>, size=<span class="hljs-number">12</span>)
<span class="hljs-keyword">self</span>.browser = [<span class="hljs-string">'Firefox'</span>, <span class="hljs-string">'Google Chrome'</span>, <span class="hljs-string">'Microsoft Edge'</span>, <span class="hljs-string">'Opera'</span>, <span class="hljs-string">'Safari'</span>]
<span class="hljs-keyword">self</span>.label1 = Label(<span class="hljs-keyword">self</span>, text=<span class="hljs-string">'Welche der folgenden Browser verwenden Sie?'</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label1.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
<span class="hljs-keyword">self</span>.listbox = Listbox(<span class="hljs-keyword">self</span>, height=<span class="hljs-number">5</span>, font=<span class="hljs-keyword">self</span>.myfont, activestyle=<span class="hljs-string">'none'</span>)
<span class="hljs-keyword">self</span>.listbox[<span class="hljs-string">'selectmode'</span>] = MULTIPLE
<span class="hljs-keyword">self</span>.listbox.grid(row=<span class="hljs-number">1</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>, pady=<span class="hljs-number">3</span>)
# Einträge zur Listbox hinzufügen
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.browser:
<span class="hljs-keyword">self</span>.listbox.insert(<span class="hljs-keyword">END</span>, item)
<span class="hljs-keyword">self</span>.label2 = Label(<span class="hljs-keyword">self</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label2.grid(row=<span class="hljs-number">2</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.listbox.bind(<span class="hljs-string">'<<ListboxSelect>>'</span>, <span class="hljs-keyword">self</span>.listb_sel)
def listb_sel(<span class="hljs-keyword">self</span>, <span class="hljs-keyword">event</span>):
indexes = <span class="hljs-keyword">self</span>.listbox.curselection()
li = []
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> indexes:
li.append(<span class="hljs-keyword">self</span>.listbox.get(i))
br = <span class="hljs-string">', '</span>.join(li)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = f<span class="hljs-string">'Ausgewählt: {br}'</span>
def main():
root = Tk()
root.title(<span class="hljs-string">'Listbox'</span>)
root.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
root.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
app = App(root)
app.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
root.mainloop()
main()
Der Listbox-Eigenschaft selectmode wird die Konstante MULTIPLE zugewiesen. Wesentliche Veränderungen ergeben sich in der Ereignisbehandlungsmethode listb_sel(), da nun damit zu rechnen ist, dass die Methode curselection() des Listbox-Objekts mehrere Indizes zurückliefert. Das Tupel mit den Indizes wird mit einer for-Schleife durchlaufen und die Listeneinträge, das heißt die ausgewählten Browser, werden in einer Liste gespeichert. Diese Liste wird anschließend für die Ausgabe im Label self.label2 verwendet (Bild 3).

Mit der Listbox-Einstellung MULTIPLEfür selectmode können mehrere Elemente gleichzeitig ausgewählt werden(Bild 3)
Saumweber
Wir generieren jetzt ein Szenario, in dem neue Listeneinträge hinzugefügt und bestehende gelöscht werden können. Dabei lernen Sie auch den Steuerelement-Typ Entry für Eingabefelder genauer kennen (in Teil 3 hatten wir ihn ja schon einmal verwendet). Neben einem Eingabefeld für neue Listeneinträge fügen wir noch einen Button für das Hinzufügen sowie einen Button für das Löschen von Listeneinträgen hinzu (Bild 4).

Listbox mit der Möglichkeit,neue Einträge hinzuzufügen oder bestehende zu entfernen(Bild 4)
Saumweber
Hier die wichtigsten Erläuterungen zum Code: Das Löschen von Listeneinträgen ist schnell erledigt. Es ist nur erforderlich, mit curselection() den Index des selektierten Eintrags zu ermitteln. Dieser wird anschließend der Methode delete() des Listbox-Objekts übergeben. Damit die Methode bt_delete_click(), in der das geschieht, per Klick auf den Löschen-Button zur Ausführung gelangt, wird sie dem command-Parameter des self.bt_delete_item-Objekts zugewiesen: self.bt_delete_item['command'] = self.bt_delete_click.Ein Entry-Feld ist ein einzeiliges Eingabefeld. Im Beispiel wird das Entry-Objekt mit der Anweisung self.new_item = Entry(self.frame_new_item, width=27, font=self.myfont) erstellt. Der Wert für den width-Parameter ist so gewählt, dass die Breite des Feldes in etwa der der Listbox entspricht. Mit der Methode get() des Entry-Objekts können Sie jederzeit den Wert, das heißt den im Feld eingegebenen Text, abfragen. Dies geschieht im folgenden Listing in der if-Bedingung der Methode bt_add_click(). Schließlich kann der Listbox nur dann ein Eintrag hinzugefügt werden, wenn er zuvor vom Benutzer in das Feld eingegeben wurde. Ist das Feld leer, bleibt ein Klick auf den Button Hinzufügen ohne Wirkung. Nach dem Hinzufügen des neuen Leisteneintrags wird das Entry-Feld gleich wieder geleert. Die delete()-Methode hat zwei Pflichtparameter, das erste und das letzte zu löschende Zeichen. Letzteres kann mit der Konstante END festgelegt werden:
<span class="hljs-keyword">from</span> tkinter import *
<span class="hljs-keyword">from</span> tkinter.font import *
<span class="hljs-keyword">class</span> App(Frame):
def __init__(<span class="hljs-keyword">self</span>, parent):
super().__init__(parent)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">1</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">2</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">3</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.myfont = Font(family=<span class="hljs-string">'Calibri'</span>, size=<span class="hljs-number">12</span>)
<span class="hljs-keyword">self</span>.obst = [<span class="hljs-string">'Äpfel'</span>, <span class="hljs-string">'Birnen'</span>, <span class="hljs-string">'Bananen'</span>, <span class="hljs-string">'Aprikosen'</span>, <span class="hljs-string">'Kirschen'</span>]
<span class="hljs-keyword">self</span>.label1 = Label(<span class="hljs-keyword">self</span>, text=<span class="hljs-string">'Welches Obst essen Sie am liebsten?'</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label1.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
# Frame mit Listbox, Scrollbar, Löschen-Button
<span class="hljs-keyword">self</span>.frame_listbox = Frame(<span class="hljs-keyword">self</span>)
<span class="hljs-keyword">self</span>.scrollbar = Scrollbar(<span class="hljs-keyword">self</span>.frame_listbox, orient=VERTICAL)
<span class="hljs-keyword">self</span>.listbox = Listbox(<span class="hljs-keyword">self</span>.frame_listbox, height=<span class="hljs-number">5</span>, font=<span class="hljs-keyword">self</span>.myfont, yscrollcommand=<span class="hljs-keyword">self</span>.scrollbar.set)
<span class="hljs-keyword">self</span>.scrollbar[<span class="hljs-string">'command'</span>] = <span class="hljs-keyword">self</span>.listbox.yview
<span class="hljs-keyword">self</span>.listbox.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>, pady=<span class="hljs-number">3</span>)
<span class="hljs-keyword">self</span>.scrollbar.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">1</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.bt_delete_item = Button(<span class="hljs-keyword">self</span>.frame_listbox, text=<span class="hljs-string">'Löschen'</span>)
<span class="hljs-keyword">self</span>.bt_delete_item[<span class="hljs-string">'command'</span>] = <span class="hljs-keyword">self</span>.bt_delete_click
<span class="hljs-keyword">self</span>.bt_delete_item.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">2</span>, padx=<span class="hljs-number">10</span>)
<span class="hljs-keyword">self</span>.frame_listbox.grid(row=<span class="hljs-number">1</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
# Einträge zur Listbox hinzufügen
<span class="hljs-keyword">self</span>.listbox[<span class="hljs-string">'listvariable'</span>] = Variable(value=<span class="hljs-keyword">self</span>.obst)
<span class="hljs-keyword">self</span>.label2 = Label(<span class="hljs-keyword">self</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = <span class="hljs-string">'Noch nichts ausgewählt'</span>
<span class="hljs-keyword">self</span>.label2.grid(row=<span class="hljs-number">2</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
# Frame mit Eingabefeld und Hinzufügen-Button
<span class="hljs-keyword">self</span>.frame_new_item = Frame(<span class="hljs-keyword">self</span>)
<span class="hljs-keyword">self</span>.new_item = Entry(<span class="hljs-keyword">self</span>.frame_new_item, width=<span class="hljs-number">27</span>, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.new_item.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
<span class="hljs-keyword">self</span>.bt_add_item = Button(<span class="hljs-keyword">self</span>.frame_new_item, text=<span class="hljs-string">'Hinzufügen'</span>)
<span class="hljs-keyword">self</span>.bt_add_item[<span class="hljs-string">'command'</span>] = <span class="hljs-keyword">self</span>.bt_add_click
<span class="hljs-keyword">self</span>.bt_add_item.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">1</span>, padx=<span class="hljs-number">15</span>)
<span class="hljs-keyword">self</span>.frame_new_item.grid(row=<span class="hljs-number">3</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'W'</span>)
<span class="hljs-keyword">self</span>.listbox.bind(<span class="hljs-string">'<<ListboxSelect>>'</span>, <span class="hljs-keyword">self</span>.listb_sel)
def listb_sel(<span class="hljs-keyword">self</span>, <span class="hljs-keyword">event</span>):
<span class="hljs-keyword">index</span> = <span class="hljs-keyword">self</span>.listbox.curselection()
obst = <span class="hljs-keyword">self</span>.listbox.get(<span class="hljs-keyword">index</span>)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = f<span class="hljs-string">'Ausgewählt: {obst}'</span>
def bt_delete_click(<span class="hljs-keyword">self</span>):
<span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.listbox.curselection():
<span class="hljs-keyword">index</span> = <span class="hljs-keyword">self</span>.listbox.curselection()[<span class="hljs-number">0</span>]
<span class="hljs-keyword">self</span>.listbox.delete(<span class="hljs-keyword">index</span>)
def bt_add_click(<span class="hljs-keyword">self</span>):
<span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.new_item.get():
<span class="hljs-keyword">self</span>.listbox.insert(<span class="hljs-keyword">END</span>, <span class="hljs-keyword">self</span>.new_item.get())
<span class="hljs-keyword">self</span>.new_item.delete(<span class="hljs-number">0</span>, <span class="hljs-keyword">END</span>)
def main():
root = Tk()
root.title(<span class="hljs-string">'Listbox'</span>)
root.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
root.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
app = App(root)
app.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
root.mainloop()
main()
Auch im Zusammenhang mit Entry-Feldern können Kontrollvariablen eine wichtige Rolle spielen. Setzen Sie diese zum Beispiel ein, um Werte respektive Berechnungen in anderen Widgets automatisch zu aktualisieren. Um die Vorgehensweise zu demonstrieren, greifen wir wiederum auf den Kilojoule-Kilokalorien-Umrechner zurück. Die Umrechnung soll nun automatisch erfolgen, sobald der Benutzer einen Kilojoule-Wert in das Feld eingibt oder diesen ändert. Die GUI benötigte also keine Schaltfläche, um die Berechnung zu starten. Nach Eingabe des Kilojoule-Wertes zeigt das Label sofort den berechneten Kilokalorien-Wert an (Bild 5).

Die Anzeige des Umrechnungsergebnissesim unteren Label aktualisiert sich automatisch, sobald ein Benutzer den Wert im Feld ändert(Bild 5)
Saumweber
Um das zu erreichen, wird das Entry-Feld mit einer Kontrollvariablen verbunden und diese mit der trace()-Methode überwacht:
from tkinter import *
from tkinter.font import *
class App(Frame):
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span></span>(<span class="hljs-keyword">self</span>, parent):
super().__init_<span class="hljs-number">_</span>(parent)
<span class="hljs-keyword">self</span>.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
<span class="hljs-keyword">self</span>.myfont = Font(family=<span class="hljs-string">'Calibri'</span>, size=<span class="hljs-number">12</span>)
<span class="hljs-keyword">self</span>.frame = Frame(<span class="hljs-keyword">self</span>)
<span class="hljs-keyword">self</span>.kilojoule = StringVar()
<span class="hljs-keyword">self</span>.kilojoule.trace_add(<span class="hljs-string">'write'</span>, <span class="hljs-keyword">self</span>.calculate_kcal)
label1 = Label(<span class="hljs-keyword">self</span>.frame, text=<span class="hljs-string">'Kilojoule:'</span>, font=<span class="hljs-keyword">self</span>.myfont)
label1.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'NESW'</span>)
<span class="hljs-keyword">self</span>.entry = Entry(<span class="hljs-keyword">self</span>.frame, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.entry[<span class="hljs-string">'textvariable'</span>] = <span class="hljs-keyword">self</span>.kilojoule
<span class="hljs-keyword">self</span>.entry.grid(row=<span class="hljs-number">1</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'NESW'</span>)
<span class="hljs-keyword">self</span>.label2 = Label(<span class="hljs-keyword">self</span>.frame, font=<span class="hljs-keyword">self</span>.myfont)
<span class="hljs-keyword">self</span>.label2.grid(row=<span class="hljs-number">2</span>, column=<span class="hljs-number">0</span>, sticky=<span class="hljs-string">'NESW'</span>)
<span class="hljs-keyword">self</span>.frame.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculate_kcal</span></span>(<span class="hljs-keyword">self</span>, *args):
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = <span class="hljs-string">''</span>
if <span class="hljs-keyword">self</span>.kilojoule.get().isnumeric():
kcal = round((float(<span class="hljs-keyword">self</span>.kilojoule.get()) * <span class="hljs-number">0</span>.<span class="hljs-number">24</span>), <span class="hljs-number">2</span>)
<span class="hljs-keyword">self</span>.label2[<span class="hljs-string">'text'</span>] = str(kcal) + <span class="hljs-string">' Kilokalorien'</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span></span>():
root = Tk()
root.title(<span class="hljs-string">'Kilojoule in Kilokalorien umrechnen'</span>)
root.geometry(<span class="hljs-string">'370x120'</span>)
root.minsize(<span class="hljs-number">370</span>, <span class="hljs-number">120</span>)
root.maxsize(<span class="hljs-number">450</span>, <span class="hljs-number">240</span>)
root.grid_rowconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
root.grid_columnconfigure(<span class="hljs-number">0</span>, weight=<span class="hljs-number">1</span>)
app = App(root)
app.grid(row=<span class="hljs-number">0</span>, column=<span class="hljs-number">0</span>)
root.bind(<span class="hljs-string">'<Return>'</span>, app.calculate_kcal)
root.mainloop()
main()
Entscheidend ist die Anweisung self.kilojoule.trace_add('write', self.calculate_kcal). Jedes Mal, wenn sich der Wert der Kontrollvariablen self.kilojoule ändert, wird die Funktion self.calculate_kcal() ausgeführt (zur Thematik siehe auch die Abschnitte »Kontrollvariablen verwenden« und »Kontrollvariablen überwachen« in Teil 4).
ListboxSelect-Ereignis
Beachten Sie, dass das <i>ListboxSelect</i>-Ereignis immer dann eintritt, wenn ein Benutzer in der Listbox etwas auswählt. Das geht zwar meistens mit einer Änderung der Auswahl und damit des Indexwertes einher, es muss aber nicht so sein. Wenn der Benutzer noch einmal auf das bereits selektierte Listenelement klickt, wird das <i>ListboxSelect</i>-Ereignis ebenfalls ausgelöst.
Beachten Sie auch, dass im Zusammenhang mit Entry-Feldern in der Regel eine mit StringVar() erzeugte Kontrollvariable die richtige Wahl sein wird, da jede Benutzereingabe ja erst einmal als String interpretiert wird. Im Beispiel wird der eingegebene Wert für die Berechnung mit der float()-Funktion in einen Double-Wert konvertiert – float(self.kilojoule.get(). Zuvor prüft die Bedingung if self.kilojoule.get().isnumeric():, ob die Konvertierung möglich ist.Falls ein Benutzer etwas eingibt, das nicht als Kilojoule-Wert interpretiert werden kann, oder eine vorhandene Eingabe im Feld löscht, passiert nichts, und es stellt sich auch kein Fehler ein.