Reputation: 101
I'm trying to change a text field's font using a listbox with fonts from tkinter. It works fine except for the first time the listbox get selected.
On first click on the listbox I would get this error message IndexError: tuple index out of range
and the tuple with the listbox selection would be ()
. However, the next selection would work totally fine. Then I would get a tuple like this (number,)
What's the cause of this? If I bind <Double-Button-1>
instead for <Button-1>
then the first selection (double click on listbox) works fine.
import tkinter
from tkinter import font
class EnkelTeksteditor:
def __init__(self):
self.hovedvindu = tkinter.Tk()
self.tekstomraadet = tkinter.Text(self.hovedvindu, height=10, width=30)
self.tekstomraadet.grid(column=0, row=0)
self.scrollbar = tkinter.Scrollbar(self.hovedvindu, orient=tkinter.VERTICAL,
command=self.tekstomraadet.yview)
self.scrollbar.grid(column=1, row=0, sticky=(tkinter.N, tkinter.S))
self.tekstomraadet.config(yscrollcommand=self.scrollbar.set)
self.hovedvindu.title("Enkel teksteditor")
self.fontlistbox = tkinter.Listbox(self.hovedvindu, selectmode=tkinter.SINGLE)
self.fontene = font.families()
for fonten in self.fontene:
self.fontlistbox.insert(tkinter.END, fonten)
self.fontlistbox.grid(column=3, row=0, sticky=(tkinter.N, tkinter.S))
self.fontscroller = tkinter.Scrollbar(self.hovedvindu, orient=tkinter.VERTICAL,
command=self.fontlistbox.yview)
self.fontlistbox.config(yscrollcommand=self.fontscroller.set)
self.fontscroller.grid(column=4, row=0, sticky=(tkinter.N, tkinter.S))
self.fontlistbox.bind("<Button-1>", self.endre_font_listbox)
self.fontlistbox.bind("<Key-Return>", self.endre_font_listbox)
tkinter.mainloop()
def endre_font_listbox(self, hendelse):
valgte_indekser = self.fontlistbox.curselection()
print(valgte_indekser)
if valgte_indekser:
font_tekst = self.fontene[valgte_indekser[0]]
ny_font = font.Font(size=10, weight=bold_tekst, slant=italic_tekst, family=font_tekst)
self.tekstomraadet.config(font=ny_font)
if __name__ == "__main__":
gui = EnkelTeksteditor()
Upvotes: 0
Views: 3328
Reputation: 385890
The listbox generates a special virtual event when the selection changes. You should be using it instead of binding to a key. Because you are binding to a key press, your binding fires before the default bindings. It is the default bindings that causes the selection to change.
Here's how you bind to the virtual event:
self.fontlistbox.bind("<<ListboxSelect>>", self.endre_font_listbox)
In addition to solving the problem of the order in which event callbacks are called, this has the added advantage that it will also work if the user uses the keyboard to change the selection.
Upvotes: 3
Reputation: 7680
When you bind to <Button-1>
, the binded function fires before the listbox has a chance to deal with the event. This is why you can return "break"
from a binded function to stop an event.
Look at this code here:
import tkinter as tk
def return_break(event):
return "break"
root = tk.Tk()
text = tk.Text(root)
text.pack()
text.bind("<Key>", return_break)
root.mainloop()
As soon as you press a button tkinter calls your function return_break
. That function returns "break"
which tells tkinter to stop the event. That is why the event never reaches the text
widget.
Similarly when you bind to <Button-1>
, your function gets called before the listbox learns about the mouse press. When you change it to <ButtonRelease-1>
, it works because the function is called when you are releasing the mouse button and the listbox had time to deal with the <Button-1>
event.
Another way of fixing the problem is by changing your code to:
def endre_font_listbox(self, hendelse):
self.fontlistbox.after(1, self._endre_font_listbox)
def _endre_font_listbox(self):
valgte_indekser = self.fontlistbox.curselection()
print(valgte_indekser)
...
That tells tkinter to wait 1 ms (which is enough time for the listbox to deal with the event) before calling your function (_endre_font_listbox
).
Always remember that your function gets called before the widget's handler.
Upvotes: 1