I'm a complete newbie. I started last week with python in which I'm trying to create a functional GUI. This gui will make use of multiple tab's using ttk notebook. However, I'm experiencing a problem regarding to adding multiple listboxes in my programm. I do have a working listbox, in "tab11". But, whenever I try to add a simple listbox it won't render when I run the program. The shell however supplies me with an error, I don't have any clue what the problem is.
The error I get is:
Traceback (most recent call last):
File "/Users/nielsschreuders/Documents/01_TUe_B32_Feb13_Jun13/Project/Concept/prototype/python/RaspberryBackup/B32Project/PythonExperiments/multipleListboxTest.py", line 133, in <module>
listbox2 = tk.Listbox(tab12, width=50, height=-1)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/tkinter/__init__.py", line 2608, in __init__
Widget.__init__(self, master, 'listbox', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/tkinter/__init__.py", line 2075, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: can't invoke "listbox" command: application has been destroyed
This is a simplefied version of the code:
import tkinter as tk
#import RPi.GPIO as gpio
import sys
import os
from tkinter import ttk
mGui = tk.Tk()
mGui.geometry('800x480')
mGui.title("Apps on Wheels")
nb = ttk.Notebook(mGui, name="notebook")
nb.pack(fill=tk.BOTH, expand=tk.Y, padx=2, pady=3)
dispColor="Yellow"
#create tabs
tab1 = ttk.Frame(nb, name="audioTab")
#assign tabs to notebook children
nb.add(tab1, text="Audio")
nbAS = ttk.Notebook(tab1, name="audioSource")
nbAS.pack(pady=100)
#nbAS.pack(fill=tk.BOTH, expand=tk.Y, padx=2, pady=3)
tab11 = ttk.Frame(nbAS, name="radioTab")
tab12 = ttk.Frame(nbAS, name="mp3Tab")
nbAS.add(tab11, text="Radio")
nbAS.add(tab12, text="MP3")
##################Radio Controller###################
def get_list(event):
#get selected line index
index = listbox1.curselection()[0]
#get the line's text
seltext=listbox1.get(index)
#delete previous text in enter1
enter1.delete(0, 50)
#display selected text
enter1.insert(0, "You are listening to: " +seltext)
intIndex = int(index)
intRadioFreq = intIndex +1
radioFreq = str(intRadioFreq)
os.system("mpc play " + radioFreq)
def set_list(event):
try:
index = listbox1.curselection()[0]
#delete old listbox line
listbox1.delete(index)
except IndexError:
index = tk.END
#insert edited item back into listbox1 at index
listbox1.insert(index, enter1.get())
def sort_list():
temp_list = list(listbox1.get(0, tk.END))
temp_list.sort(key=str.lower)
#delete contents of present listbox
listbox1.delete(0, tk.END)
for item in temp_list:
listbox1.insert(tk.END, item)
def save_list():
#get a list of listbox lines
temp_list = list(listbox1.get(0, tk.END))
#add a trailing newline char to each line
temp_list=[radio + '\n' for radio in temp_list]
#give the file new name
fout = open("radio_data2.txt", "w")
fout.writelines(temp_list)
fout.close()
#create the data file
str1 = """Radio 1
Radio 2
3FM Serious Radio
538 Radio
Radio 6 Soul & Jazz
"""
fout = open("radio_data.txt", "w")
fout.write(str1)
fout.close()
fin = open("radio_data.txt", "r")
radio_list= fin.readlines()
fin.close()
radio_list= [radio.rstrip() for radio in radio_list]
RadioFrequencies = tab11
#creat listbox
listbox1 = tk.Listbox(RadioFrequencies , width=50, height=-1)
listbox1.grid(row=0, column=0)
#use entry widget to display/edit selection
enter1 = tk.Entry(RadioFrequencies , width=44, bg=dispColor, fg="black", justify=tk.CENTER, font=("Arial" ,16, "bold"))
#enter1.insert(0, "Choose your radio station")
enter1.grid(row=1, column=0)
for item in radio_list:
listbox1.insert(tk.END, item)
listbox1.bind("<ButtonRelease-1>", get_list)
RadioFrequencies.mainloop()
listbox2 = tk.Listbox(tab12, width=50, height=-1)
listbox2.place(x=100, y=150)
listbox2.insert(tk.END, "entry here")
for item2 in ["one", "two", "three", "four"]:
listbox2.insert(tk.END, item2)
mGui.mainloop()
Your program calls mainloop() in two places:
RadioFrequencies.mainloop()
...
mGui.mainloop()
Generally speaking, a Tkinter app should call mainloop only once, and it is usually the tk.Tk() instance that makes the call. Once the call is made, the program hands over the flow of control to the Tkinter event loop which manages all the GUI events.
Usually the call to mainloop() would therefore be the last call in the script. When the GUI is exited, the script exits as well.
So remove the line
RadioFrequencies.mainloop()
The reason you are getting the error
_tkinter.TclError: can't invoke "listbox" command: application has been destroyed
is because after you close the GUI, Python returns from
RadioFrequencies.mainloop()
and then tries to call
listbox2 = tk.Listbox(tab12, width=50, height=-1)
but by this point the application has already ended.
Related
I'm trying to create a button which has the same effect as the button from the version test from tkinter when using the command
py -m tkinter
in CMD. The button is supposed to display the text "Button", then with every press a set of brackets will be added on either side of the word. So on the first press, the text will read "[Button]", then the second press "[[Button]]", and so on.
Here is what I have:
from tkinter import *
from tkinter import ttk
def changeText():
textName = "[" + textName + "]"
root = Tk()
root.geometry('400x150')
root.title('Hit the button')
textName = "button"
frame = ttk.Frame(root, padding = 10).grid()
btn = ttk.Button(frame, text = textName, command = changeText).grid(column = 0, row = 0)
root.mainloop()
When I run my code, the window and button pops up correctly, however when the button is pressed, I get the error:
"X:\Pycharm Environments\venv\Scripts\python.exe" "X:/Pycharm Environments/Learning/tkinterPractie.py"
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\jared\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1885, in __call__
return self.func(*args)
File "X:\Pycharm Environments\Learning\tkinterPractie.py", line 5, in changeText
textName = "[" + textName + "]"
UnboundLocalError: local variable 'textName' referenced before assignment
I'm not really sure what I'm doing wrong here. Thanks for any help!
When you call .grid() on your button, the return value is None, so you have already lost the direct reference to your button. There are other ways of accessing it, but it would be much easier to just create the button and then put it in the layout in 2 steps. Also you need to set the button['text'] when changing the text. textName is just a variable holding a string and updating it only updates the value of the variable.
from tkinter import *
from tkinter import ttk
def changeText():
btn['text'] = "[" + btn['text'] + "]" # change to changing the buttons text
root = Tk()
root.geometry('400x150')
root.title('Hit the button')
textName = "button"
frame = ttk.Frame(root, padding = 10).grid()
btn = ttk.Button(frame, text = textName, command = changeText) # return the instance
btn.grid(column = 0, row = 0) # then input into the layout
root.mainloop()
I am learning python.I follow the book example code. This code make SumGrid class in order to be attachable to containers where other widgets are being gridded or packed. The author said that it leaves its own geometry management ambiguous and requires callers to pack or grid its instances. It's OK for containers to pick either scheme for their own children because they effectively seal off the pack-or-grid choice. But attachable component classes that aim to be reused under both geometry managers cannot manage themselves because they cannot predict their parent's policy.
from tkinter import *
from tkinter.filedialog import askopenfilename
from PP4E.Gui.Tour.quitter import Quitter # reuse, pack, and grid
class SumGrid(Frame):
def __init__(self, parent=None, numrow=5, numcol=5):
Frame.__init__(self, parent)
self.numrow = numrow # I am a frame container
self.numcol = numcol # caller packs or grids me
self.makeWidgets(numrow, numcol) # else only usable one way
def makeWidgets(self, numrow, numcol):
self.rows = []
for i in range(numrow):
cols = []
for j in range(numcol):
ent = Entry(self, relief=RIDGE)
ent.grid(row=i+1, column=j, sticky=NSEW)
ent.insert(END, '%d.%d' % (i, j))
cols.append(ent)
self.rows.append(cols)
self.sums = []
for i in range(numcol):
lab = Label(self, text='?', relief=SUNKEN)
lab.grid(row=numrow+1, column=i, sticky=NSEW)
self.sums.append(lab)
Button(self, text='Sum', command=self.onSum).grid(row=0, column=0)
Button(self, text='Print', command=self.onPrint).grid(row=0, column=1)
Button(self, text='Clear', command=self.onClear).grid(row=0, column=2)
Button(self, text='Load', command=self.onLoad).grid(row=0, column=3)
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
def onPrint(self):
for row in self.rows:
for col in row:
print(col.get(), end=' ')
print()
print()
def onSum(self):
tots = [0] * self.numcol
for i in range(self.numcol):
for j in range(self.numrow):
tots[i] += eval(self.rows[j][i].get()) # sum current data
for i in range(self.numcol):
self.sums[i].config(text=str(tots[i]))
def onClear(self):
for row in self.rows:
for col in row:
col.delete('0', END) # delete content
col.insert(END, '0.0') # preserve display
for sum in self.sums:
sum.config(text='?')
def onLoad(self):
file = askopenfilename()
if file:
for row in self.rows:
for col in row: col.grid_forget() # erase current gui
for sum in self.sums:
sum.grid_forget()
filelines = open(file, 'r').readlines() # load file data
self.numrow = len(filelines) # resize to data
self.numcol = len(filelines[0].split())
self.makeWidgets(self.numrow, self.numcol)
for (row, line) in enumerate(filelines): # load into gui
fields = line.split()
for col in range(self.numcol):
self.rows[row][col].delete('0', END)
self.rows[row][col].insert(END, fields[col])
if __name__ == '__main__':
import sys
root = Tk()
root.title('Summer Grid')
if len(sys.argv) != 3:
SumGrid(root).pack() # .grid() works here too
else:
rows, cols = eval(sys.argv[1]), eval(sys.argv[2])
SumGrid(root, rows, cols).pack()
root.mainloop()
When I run this .py file in my window shell. It can't work. The err message like below. I think SumGrid is a Frame. The caller can decide to pack or grid this Frame. But it can't work on my computer. Why? Thank you.
Traceback (most recent call last):
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 84, in <module>
SumGrid(root).pack() # .grid() works here too
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 12, in __init__
self.makeWidgets(numrow, numcol) # else only usable one way
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\Grid\grid5c.py", line 35, in makeWidgets
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
File "C:\Users\hp\PP4E-Examples-1.4\Examples\PP4E\Gui\Tour\quitter.py", line 12, in __init__
self.pack()
File "C:\Users\hp\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2425, in pack_configure
self.tk.call(
_tkinter.TclError: cannot use geometry manager pack inside .!sumgrid which already has slaves managed by grid
Like the error says, you can't use both grid and pack on widgets that have the same parent.
Let's look at these two lines of code:
Button(self, text='Load', command=self.onLoad).grid(row=0, column=3)
Quitter(self).grid(row=0, column=4) # fails: Quitter(self).pack()
Notice how both Quitter and Button are using self as the parent. That means that you must only use one of grid or pack for both widgets (and the other widgets that have self as the parent). If you use grid for the button, you cannot use pack for Quitter, which is exactly what the error is telling you.
I am constructing a simple hex editor in Python using Tkinter. I want to be able to indicate selection of a value (the "0000"s) in the GUI by changing the colors of a pressed button.
To implement this, I instantiated each button as a class inside a for loop and put each instance into a list to reference them later in the code. When I attempt to alter the Button API properties of the classes in the while loop, the IDLE prints an error when the hex editor is closed:
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/Files/Python/hex editor.py", line 64, in <module>
ml.button.configure(bg='#00FF00', fg='#000000')
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1479, in configure
return self._configure('configure', cnf, kw)
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1470, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".!frame.!frame.!button"
The intended behavior of the button is that when any "0000" is clicked, the selected button will become green and remain so until another button is pressed. (i.e the pressed button will turn green and the previously selected button will turn black)
Source Code:
from tkinter import *
selected_i = 0
selected_j = 0
data_w = 16
address_w = 8
class mem_location:
def __init__(self, row, column, data):
self.row = row
self.column = column
self.data = data
self.button = Button(subframe,
text=self.data,
font=('Consolas',9),
bd=0,
command=self.select)
self.button.pack(side=LEFT)
self.button.configure(bg='#000000', fg='#00FF00')
def select(self):
selected_i = self.row
selected_j = self.column
root = Tk()
root.configure(bg="black")
root.title('Hex Editor')
frame = Frame(root,
padx=30,
pady=10,
bg='black')
frame.pack()
ml_list = []
for i in range((1<<address_w)>>4):
subframe = Frame(frame,
padx=10,
bg='black')
subframe.pack()
addr_label = Label(subframe,
text=hex(i<<4)+" ",
bg='black',
fg='#00FF00',
width=5)
addr_label.pack(side = LEFT)
for j in range(16):
ml_list.append(mem_location(i, j, '0000'))
root.mainloop()
while True:
for ml in ml_list:
if selected_i == ml.row and selected_j == ml.column:
ml.button.configure(bg='#00FF00', fg='#000000')
else:
ml.button.configure(bg='#000000', fg='#00FF00')
I am currently in the process of learning about Tkinter. Why can't I modify the class's Button configuration and how can this be fixed?
The code after root.mainloop will not run until the window is not closed, so you are trying to modify the buttons after the window has been closed.
Instead, you could move the code in the submit method like this:
from tkinter import *
# You don’t need selected_i and selected_j anymore
data_w = 16
address_w = 8
class MemLocation:
def __init__(self, data):
# Same code without self.row and self.column
def select(self):
for ml in [ml_ for ml_ in ml_list if ml_ is not self]: # All elements except this one
ml.button.config(bg='#000', fg='#0F0')
self.button.config(bg='#0F0', fg='#000')
# Same code here
root.mainloop()
#END
Note that I renamed the class according to PEP 8 but there are many other improvement possible like making MemLocation inherit from Frame or Button, or adding event listeners to make the code cleaner.
I am trying to bind this function self.copyTextToClipboard(self,t) to multiple different trees to make it more flexible (please see binding below).
from tkinter.ttk import Treeview
from tkinter import *
class App:
def __init__(self, master):
self.master = master
frame = Frame(master)
master.geometry("{}x{}".format(master.winfo_screenwidth() - 100, master.winfo_screenheight() - 100))
master.resizable(False, False)
self.leftFrame = Frame(master, bg="#DADADA", width=375, relief=SUNKEN)
self.leftFrame.pack_propagate(0)
self.leftFrame.pack(side=LEFT, fill=Y, padx=1)
# This table (TreeView) will display the partitions in the tab
self.partitionsOpenDiskTree = Treeview(self.leftFrame, columns=("#"), show="headings", selectmode="browse", height=23)
yscrollB = Scrollbar(self.leftFrame)
yscrollB.pack(side=RIGHT, fill=Y)
self.partitionsOpenDiskTree.column("#", width=50)
self.partitionsOpenDiskTree.heading("#", text="#")
self.partitionsOpenDiskTree.configure(yscrollcommand=yscrollB.set)
# Bind left click on text widget to copy_text_to_clipboard() function
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda t=self.partitionsOpenDiskTree: self.copyTextToClipboard(self,t))
# Adding the entries to the TreeView
for i in range(3):
self.partitionsOpenDiskTree.insert("", "end", i, values=(i), tags=str(i))
self.partitionsOpenDiskTree.pack(anchor=NW, fill=Y)
#todo: figure out where this is getting called and put in tree
def copyTextToClipboard(self, tree, event=None):
print(type(tree))
# triggered off left button click on text_field
root.clipboard_clear() # clear clipboard contents
textList = tree.item(tree.focus())["values"]
line = ""
for text in textList:
if line != "":
line += ", " + str(text)
else:
line += str(text)
root.clipboard_append(line) # append new value to clipbaord
root = Tk()
app = App(root)
root.mainloop()
However, I am unable to bind it to a TreeView object it seems; when I run the code, I get:
Exception in Tkinter callback
<class '__main__.App'>
Traceback (most recent call last):
File "C:\Users\user1\Anaconda3\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:/Users/user1/main_merged.py", line 56, in <lambda>
lambda t=self.partitionsOpenDiskTree: self.copyTextToClipboard(self,t))
File "C:/Users/user1/main_merged.py", line 70, in copyTextToClipboard
textList = tree.item(tree.focus())["values"]
AttributeError: 'App' object has no attribute 'item'
If I try to print out tree type, I get that it's a not a TreeView object. Any ideas on how I can get a TreeView object, so that I can figure out which item was selected?
Thanks!
-FF
So, apparently, taking out the self call seemed to work:
from tkinter.ttk import Treeview
from tkinter import *
class App:
def __init__(self, master):
self.master = master
frame = Frame(master)
master.geometry("{}x{}".format(master.winfo_screenwidth() - 100, master.winfo_screenheight() - 100))
master.resizable(False, False)
self.leftFrame = Frame(master, bg="#DADADA", width=375, relief=SUNKEN)
self.leftFrame.pack_propagate(0)
self.leftFrame.pack(side=LEFT, fill=Y, padx=1)
# This table (TreeView) will display the partitions in the tab
self.partitionsOpenDiskTree = Treeview(self.leftFrame, columns=("#"), show="headings", selectmode="browse", height=23)
yscrollB = Scrollbar(self.leftFrame)
yscrollB.pack(side=RIGHT, fill=Y)
self.partitionsOpenDiskTree.column("#", width=50)
self.partitionsOpenDiskTree.heading("#", text="#")
self.partitionsOpenDiskTree.configure(yscrollcommand=yscrollB.set)
# Bind left click on text widget to copy_text_to_clipboard() function
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda event, t=self.partitionsOpenDiskTree: self.copyTextToClipboard(t))
# Adding the entries to the TreeView
for i in range(3):
self.partitionsOpenDiskTree.insert("", "end", i, values=(i), tags=str(i))
self.partitionsOpenDiskTree.pack(anchor=NW, fill=Y)
#todo: figure out where this is getting called and put in tree
def copyTextToClipboard(self, tree, event=None):
print(type(tree))
# print(type(tree.partitionsOpenDiskTree))
# triggered off left button click on text_field
root.clipboard_clear() # clear clipboard contents
textList = tree.item(tree.focus())["values"]
line = ""
for text in textList:
if line != "":
line += ", " + str(text)
else:
line += str(text)
root.clipboard_append(line) # append new value to clipbaord
print(line)
root = Tk()
app = App(root)
root.mainloop()
Output:
0
When you use bind, the callback function must have an event as its first argument, custom arguments should be put after. But as your callback does not need the event parameters, you may mask it with your lambda. So you have to change both the binding and the def of your callback:
self.partitionsOpenDiskTree.bind("<ButtonRelease-1>", lambda event, t=self.partitionsOpenDiskTree: self.copyTextToClipboard(t))
...
def copyTextToClipboard(self, tree):
should solve the problem
I'm trying to write a program that is a recipe book that lets you add recipes etc. I'm fairly new with Python and Tkinter though.
#New Recipe Screen
def click(key):
new_recipe = Tk()
new_recipe.title("New Recipe")
itemtext = Label(new_recipe, text="Item").grid(row=0, column=0)
input_item = Entry(new_recipe).grid(row=0, column=1)
quantitytext = Label(new_recipe, text="Quantity").grid(row=1, column=0)
input_quantity =Entry(new_recipe).grid(row=1, column=1)
unittext = Label(new_recipe, text="Unit").grid(row=2, column=0)
input_unit = Entry(new_recipe).grid(row=2, column=1)
fin_btn_text = "Finish"
def write(x=fin_btn_text):
click(x)
dataFile = open("StoredRecipes.txt", "w")
dataFile.write(str(input_item, ) + "\n")
new_recipe.destroy
finish_btn = Button(new_recipe, text=fin_btn_text, command=write).grid(row=3, column=0)
Two problems here:
You are not closing the file when you are done with it. Some systems require you to do this in order to commit the changes. Either call dataFile.close() at the end of your write function or use a with-statement to open the file (which will automatically close it when you are done):
def write(x=fin_btn_text):
click(x)
with open("StoredRecipes.txt", "w") as dataFile:
dataFile.write(str(input_item, ) + "\n")
new_recipe.destroy() # Remember to call this
As #Kevin noted in a comment, you cannot call .grid on the same line as you create a widget. The .grid method works in-place and always returns None. Thus, it should be called on its own line after you create the widget:
itemtext = Label(new_recipe, text="Item")
itemtext.grid(row=0, column=0)