tkinter widget call back to identify details about the widget - python-3.x

I was reading http://infohost.nmt.edu/tcc/help/pubs/tkinter/tkinter.pdf
and started playing around with "54.7. The extra arguments trick" located towards the end of the document. If I understand it correctly, I can create widgets in a list, so that when the widget is clicked on, the callback should be able to display information stored in the instantiated class, I get the widget to display, but the callback is not displaying the info that I expected. IE the attributes of the object created - Hopefully someone can help. thanks in advance
here is the code
from tkinter import *
class Component(object):
def __init__(self, image=None, Number=None, Name=None):
self.image=image
self.Number=Number
self.Name=Name
ComponentList = [] #array of componenents
def FeederCB(event):
print(event.widget.Number, event.widget.Name)
root = Tk()
test=Frame(root, bg='white')
test.grid()
for x in range(0, 2):
print(x)
ComponentList.append(Component(None, str(x),"Poly 23"))
Temp=Label(test, text=ComponentList[-1].Name)
Temp.configure(bg='white', font='times 12')
Temp.grid(row=0, column=x, sticky=S)
ComponentList[-1].image = (Label(test, text='test'))
ComponentList[-1].image.configure(bg='white')
ComponentList[-1].image.bind("<Button-1>",FeederCB)
ComponentList[-1].image.grid(row=1, column=x)
print('Lenght of Component List ', len(ComponentList))
root.mainloop()

I think I got it
from tkinter import *
class Component(object):
def __init__(self, image=None, Number=None, Name=None):
self.image=image
self.Number=Number
self.Name=Name
ComponentList = [] #array of componenents
#ComponentList.append(Class object,class attibutes)
#example ComponentList.append(Virgin("1", "materialtype", "Name", "Setpoint"))
#print(ComponentList[0].Name)
def __FeederCB(event, x):
print(ComponentList[x].Number, ComponentList[x].Name)
root = Tk()
test=Frame(root, bg='white')
test.grid()
for x in range(0, 2):
print(x)
ComponentList.append(Component(None, str(x),"Poly 23"))
Temp=Label(test, text=ComponentList[-1].Name)
Temp.configure(bg='white', font='times 12')
Temp.grid(row=0, column=x, sticky=S)
ComponentList[-1].image = (Label(test, text='test'))
ComponentList[-1].image.configure(bg='white')
def FeederCB(event, x=x):
return __FeederCB(event, x)
ComponentList[-1].image.bind("<Button-1>",FeederCB)
ComponentList[-1].image.grid(row=1, column=x)
print('Lenght of Component List ', len(ComponentList))
root.mainloop()

Related

How is the list index out of range?

I'm playing around a bit in Tkinter python when I get this error for what I think is no reason whatsoever.
print(self.entries[x])
IndexError: list index out of range
My code:
from tkinter import *
class Application(Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.submit()
def submit(self):
for x in range(2):
self.entries = []
self.buttons = []
e = Entry()
self.entries.append(e)
self.entries[x].grid(row=x, column=0)
b = Button(text='SUBMIT', command=lambda x=x: print(self.entries[x].get()))
self.buttons.append(b)
self.buttons[x].grid(row=x, column=1)
root = Tk()
app = Application(root)
app.mainloop()
The goal is to make multiple rows of entries and submit buttons with this single loop. I have tried to remove all the self in front of everything in the function, but to no avail.
Everything works if the range() in the for loop has a 1, but not for any other number. Can someone please explain? My 1 year course in high school didn't set me up for this kind of stuff.
You keep resetting self.entries and self.buttons each time the for loop runs. You need to move the self.entries = [] and self.buttons = [] before the for loop like this:
from tkinter import *
class Application(Frame):
def __init__(self, master):
super().__init__(master)
# self.master = master # Useless
self.submit()
def submit(self):
self.entries = []
self.buttons = []
for x in range(2):
e = Entry(self)
e.grid(row=x, column=0)
self.entries.append(e)
b = Button(self, text="SUBMIT", command=lambda x=x: print(self.entries[x].get()))
b.grid(row=x, column=1)
self.buttons.append(b)
root = Tk()
app = Application(root)
app.pack()
app.mainloop()
Also another few things:
Instead of self.entries[x].grid(...), you can use e.grid(...)
You never passed in anything for the master argument when creating the entries and buttons.
You inherited from tk.Frame but never put anything inside it and you didn't even call app.pack(...)/app.grid(...)
Also please you import tkinter as tk instead of from tkinter import *.

Drag Drop List in Tkinter

from tkinter import *
import tkinter as tk
root = tk.Tk()
def add_many_songs():
# Loop thru to fill list box
for song in range(11):
playlist_box.insert(END, song)
playlist_box =tk.Listbox(root,bg="black", fg="green", width=60, selectbackground="green", selectforeground='black',font = 20)
playlist_box.grid(row=0, column=0)
add_many_songs()
class DragDropListbox(tk.Listbox):
""" A Tkinter listbox with drag'n'drop reordering of entries. """
def __init__(self, master, **kw):
kw['selectmode'] = tk.SINGLE
tk.Listbox.__init__(self, master, kw)
self.bind('<Button-1>', self.setCurrent)
self.bind('<B1-Motion>', self.shiftSelection)
self.curIndex = None
def setCurrent(self, event):
self.curIndex = self.nearest(event.y)
def shiftSelection(self, event):
i = self.nearest(event.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
elif i > self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
##I found this code that does drag and drop features within tkinter list. I got it to work with the example code. However, I am not able to get it to work within the attached code. I am still learning Python.
You should use the class DragDropListbox instead of tk.Listbox when creating playlist_box:
import tkinter as tk
def add_many_songs():
# Loop thru to fill list box
for song in range(11):
playlist_box.insert(tk.END, song)
class DragDropListbox(tk.Listbox):
""" A Tkinter listbox with drag'n'drop reordering of entries. """
def __init__(self, master, **kw):
kw['selectmode'] = tk.SINGLE
tk.Listbox.__init__(self, master, kw)
self.bind('<Button-1>', self.setCurrent)
self.bind('<B1-Motion>', self.shiftSelection)
self.curIndex = None
def setCurrent(self, event):
self.curIndex = self.nearest(event.y)
def shiftSelection(self, event):
i = self.nearest(event.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
elif i > self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
root = tk.Tk()
playlist_box = DragDropListbox(root,bg="black", fg="green", width=60, selectbackground="green", selectforeground='black',font = 20)
playlist_box.grid(row=0, column=0)
add_many_songs()
root.mainloop()
Note that it is not recommended to import tkinter like below:
from tkinter import *
import tkinter as tk
Just use import tkinter as tk.

Tkinter error: bad window path name when deleting frames dynamically

Im trying to recreate a little version of trello in tkinter. Right now im stuck I have a problem when I want to delete frames in a different order. For example: I click on the button and a new frame is generated if I delete that everything works. If I create 3 frames I have to remove them in the same order as I have created them. So I think my problems lies in the pop function but I dont know how to access them manually. When i change the pop function to (1) then I have to delete the second creation first instead of the first.
Here is the code:
from tkinter import *
class Window:
def __init__(self, width, height):
self.root = Tk()
self.width = width
self.height = height
self.root.geometry(width + "x" + height)
class Frames:
def __init__(self):
self.l = Frame(window.root, bg="red", height=300, width=300, relief="sunken")
self.l.place(relwidth=0.3, relheight=0.3)
self.deleteB = Button(self.l, text="X", command=self.delete_frame, bg="blue")
self.deleteB.place(rely=0, relx=0.92)
self.addB = Button(self.l, text="Add", command=self.add_note, bg="blue")
self.addB.place(rely=0, relx=0.65)
def delete_frame(self):
self.l.pack()
self.l.pack_forget()
self.l.destroy()
frames.pop()
def add_note(self):
self.note_Label = Label(self.l, text="Clean the room")
self.note_Label.pack(padx=20, pady=10)
self.delete_Note = Button(self.note_Label, text="X", command=self.del_Note)
self.delete_Note.pack(padx=5, pady=5)
def del_Note(self):
self.note_Label.pack_forget()
self.note_Label.destroy()
class Note:
def __init__(self):
pass
class DragNDrop:
def __init__(self):
pass
def make_draggable(self, widget):
widget.bind("<Button-1>", self.on_drag_start)
widget.bind("<B1-Motion>", self.on_drag_motion)
def on_drag_start(self, event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(self, event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
class Buttons:
def __init__(self):
self.button = Button(window.root, width=20, height=20, bg="blue", command=self.add_frames)
self.button.pack()
def add_frames(self):
frames.append(Frames())
print(frames)
window = Window("800", "600")
frames = []
drag = DragNDrop()
button = Buttons()
while True:
for i in frames:
drag.make_draggable(i.l)
window.root.update()
If someone has an Idea or workaround that would be nice to know.
Also I have another Idea instead of destroying them I could just hide them but in the end that makes the programm really slow at some point.
Here is the error: _tkinter.TclError: bad window path name ".!frame2"
Your code needs to remove the frame from the list. Instead, you're calling pop which always removes the last item. That causes you to lose the reference to the last window, and one of the references in frames now points to a window that has been deleted (which is the root cause of the error)
Instead, call remove:
def delete_frame(self):
self.l.destroy()
frames.remove(self)

Delivering data between instances of different classes in tkinter

Beginner to tkinder and the version of python is 3.6.
I'm trying to make a python GUI for data processing.
There're several instances of different classes designed for different jobs.
When the data is processed with function click_emd in EMDFrame class, it should be drawn in PreviewFrame class which uses matplotlib to show the data. However i have no idea how to pass the data between two classes.
I've searched similar questions but they didn't work.
Now I'm thinking about two possible solutions.
One is to find a way to pass data_processed to previewframe.cplot in emdframe.click_emd.
Another is to make full use MainPage class. It can acquire data_processed from one class and call the cplot in another class to drawn. But how can MainPage get noticed once the data_processed is generated?
Got confused and don't know what to do. Really appreciate for your patience.
There are two py code file.
Mainframe.py :
import tkinter as tk
from view import * # initiate the subframes
class MainPage():
def __init__(self, master=None):
self.root = master
root.geometry('%dx%d' % (800, 600))
self.createPage()
def createPage(self):
self.emdPage = EMDFrame(root) # subframe
self.emdPage.place(x=20, y=300)
self.preview = PreviewFrame(root)
self.preview.place(x=340, y=20)
self.aboutPage = AboutFrame(root)
menubar = Menu(root)
menubar.add_command(label='EMD', command=self.emd)
menubar.add_command(label='About', command=self.aboutDisp)
root['menu'] = menubar # set menu
def emd(self):
self.emdPage.place(x=20, y=300)
self.preview.place(x=340, y=20)
self.aboutPage.place_forget()
def aboutDisp(self):
self.emdPage.place_forget()
self.preview.place_forget()
self.aboutPage.place(x=20, y=300)
def plot_update(self, data, t):
self.preview.cplot(data, t)
root = tk.Tk()
root.title("Demo")
app = MainPage(root)
root.mainloop()
view.py :
from tkinter import *
from pyhht.emd import EMD
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
data = []
class EMDFrame(LabelFrame):
def __init__(self, master=None):
LabelFrame.__init__(self, master, width=300, height=250, text='EMD')
self.root = master
self.createPage()
def createPage(self):
b = Button(self, text='EMD', width=20, height=2, command=self.click_emd)
b.place(x=40, y=160, anchor='nw', width=80, height=40)
def click_emd(self):
global data
decomposer = EMD(data)
data_processed = decomposer.decompose()
# Deliever data_processed to PreviewFrame's cplot function
class PreviewFrame(LabelFrame):
def __init__(self, master=None):
LabelFrame.__init__(self, master, width=440, height=530, text='Preview')
self.root = master
self.createPage()
def cplot(self, data, t):
f = Figure(figsize=(4, 4.8), dpi=100)
a = f.add_subplot(111)
a.plot(t, data)
canvas = FigureCanvasTkAgg(f, master=self)
canvas.draw()
canvas.get_tk_widget().place(x=20, y=0)
def createPage(self):
t = np.arange(0.0, 3, 0.01)
s = np.sin(2 * np.pi * t)
self.cplot(s,t)
class AboutFrame(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.root = master
self.createPage()
def createPage(self):
Label(self, text='About').pack()
You aren't passing data between classes, you are passing data between two instances of classes.
In order for one instance to send data to another instance of a class, it needs to know about it. So in MainFrame.py we pass the reference of the preview class to the emdPage.
def createPage(self):
self.preview = PreviewFrame(root)
self.preview.place(x=340, y=20)
self.emdPage = EMDFrame(root,self.preview) # subframe
self.emdPage.place(x=20, y=300)
self.aboutPage = AboutFrame(root)
Then in the other view.py, update the class to keep track of the preview reference and then use it once the data has been processed.
class EMDFrame(LabelFrame):
def __init__(self, preview, master=None):
LabelFrame.__init__(self, master, width=300, height=250, text='EMD')
self.preview = preview
self.root = master
self.createPage()
def createPage(self):
b = Button(self, text='EMD', width=20, height=2, command=self.click_emd)
b.place(x=40, y=160, anchor='nw', width=80, height=40)
def click_emd(self):
global data
decomposer = EMD(data)
data_processed = decomposer.decompose()
# Deliever data_processed to PreviewFrame's cplot function
self.preview.cplot(.....)
(Code example is untested and incomplete but should show the principle)
(There is probably a better way to do it thought the MainPage class but this would involve a bit more code.)

NameError: name 'Menu' is not defined

I am trying to make a menubar for my GUI. I get this error:
`NameError: name 'Menu' is not defined`
I am not sure why this is happening, could someone please help me? My apologies, I am a beginner at python and tkinter.
from tkinter import Tk, BOTH
from tkinter.ttk import Frame, Button, Style
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("xcal file.ics")
self.pack(fill=BOTH, expand=1)
self.centerWindow()
self.createMenuBar()
def centerWindow(self):
w = 500
h = 500
sw = self.parent.winfo_screenwidth()
sh = self.parent.winfo_screenheight()
x = (sw - w)/2
y = (sh - h)/2
self.parent.geometry('%dx%d+%d+%d' % (w, h, x, y))
def createMenuBar(self):
menubar = Menu(Frame) #ERROR
menubar.add_command(label="Hello!", command=hello)
Frame.config(menu=menubar)
def main():
root = Tk()
ex = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
Menu is in the module tkinter, so you need to import it:
from tkinter import Tk, BOTH, Menu

Resources