I am attempting to create a GUI using Python classes. Because I'm new to Python, I'm still learning to troubleshoot my errors. Below, I wish to create a class called Plot_Seismo, and create a GUI that has a listbox, a quit button, and a "Plot" button. I am having trouble with the "Plot_button" within the class. What I want this button to do is read in the seismogram and then plot the selected seismogram from the listbox. I have a feeling my syntax is incorrect (due to my naivete). I can get this to work when this function isn't in a class, but when I put it in a class as a method, I get slightly confused. The error message is shown below:
#!/usr/bin/env python
from Tkinter import *
from obspy.core import read
import math
class Plot_Seismo:
def __init__(self, parent):
self.master = parent
top = Frame(parent, width=500, height=300)
top.pack(side='top')
# create frame to hold the first widget row:
hwframe = Frame(top)
# this frame (row) is packed from top to bottom (in the top frame):
hwframe.pack(side='top')
# create label in the frame:
font = 'times 18 bold'
hwtext = Label(hwframe, text='Seismogram Reader GUI', font=('Arial',18,'bold'), fg="red")
hwtext.pack(side='top')
### ListBox
List1 = Listbox(root, width=50, height= 10)
List1.insert(1,"trace1.BHZ")
List1.insert(2,"trace2.BHZ")
List1.pack(padx=20, pady=20)
plot_button = Button(top, text='Plot Seismogram', command=self.plot_seis)
plot_button.pack(side='top', anchor='w', padx=45, pady=20)
self.event = read(List1.get(List1.curselection()[0]))
# finally, make a quit button and a binding of q to quit:
quit_button = Button(top, text='Quit Seismo-Reader GUI', command=self.quit)
quick_button.pack(side='top', anchor='w', padx=20, pady=20)
self.master.bind('<q>', self.quit)
def quit(self, event=None):
self.master.quit()
def plot_seis(self, event=None):
self.event.plot()
root = Tk()
Plot_Seismo = Plot_Seismo(root)
root.mainloop()
Error Message:
Traceback (most recent call last):
File "plot_seismogram.py", line 46, in <module>
Plot_Seismo = Plot_Seismo(root)
File "plot_seismogram.py", line 31, in __init__
self.event = read(List1.get(List1.curselection()[0]))
IndexError: tuple index out of range
Since I do not have obspy module installed, I had to shrink down your code a bit, but you should get the point.
Since I only have Python3 running on my machine, I rewrote your code to Python3 which is not a big deal. The only differences should be (tkinter instead of Tkinter and print() instead of print).
I changed some parts of your code: The listbox is no populated using a list, which make this a bit easier and it became a class attribute to access it in plot_seis.
Since .curselection() returns a tuple with the listboxes entry index, we have to get the according text entry as described in the docs and in this answer.
Buttons and maybe the listbox provide some event handling features which are making the listbox an class attribute by using self. kind of ugly somehow, but it does the job:
#!/usr/bin/env python3
# coding: utf-8
from tkinter import *
# from obspy.core import read
import math
class Plot_Seismo:
def __init__(self, parent):
self.master = parent
top = Frame(parent, width=500, height=300)
top.pack(side='top')
# create frame to hold the first widget row:
hwframe = Frame(top)
# this frame (row) is packed from top to bottom (in the top frame):
hwframe.pack(side='top')
# create label in the frame:
font = 'times 18 bold'
hwtext = Label(hwframe, text='Seismogram Reader GUI', font=('Arial',18,'bold'), fg="red")
hwtext.pack(side='top')
### ListBox
self.List1 = Listbox(root, width=50, height= 10)
# populate listbox using a list with desired entries
self.list_entries = ["trace1.BHZ", "trace2.BHZ"]
for i, element in enumerate(self.list_entries):
self.List1.insert(i, element)
self.List1.pack(padx=20, pady=20)
plot_button = Button(top, text='Plot Seismogram', command=self.plot_seis)
plot_button.pack(side='top', anchor='w', padx=45, pady=20)
# finally, make a quit button and a binding of q to quit:
quit_button = Button(top, text='Quit Seismo-Reader GUI', command=self.quit)
quit_button.pack(side='top', anchor='w', padx=20, pady=20)
self.master.bind('<q>', self.quit)
def quit(self, event=None):
self.master.quit()
def plot_seis(self, event=None):
selection_index = self.List1.curselection()[0]
selection_text = self.List1.get(selection_index)
print(selection_text)
# do something with `read` from the `obspy.core` module
# read(selection_text)
root = Tk()
Plot_Seismo = Plot_Seismo(root)
root.mainloop()
Related
I'm trying to create a basic counting app so that I can go in the field and count multiple items at once. I created the majority of the widgets with a for loop and I am trying to get the value of the label self.count and add to it.
Is there a way to retrieve the values with the references I have stored?
Since the values are in different memory locations and I don't know how to tell which button is pressed based on the below code.
(Note: the buttons should only interact with the values directly above them.)
import tkinter as tk
from tkinter import ttk
from dataclasses import dataclass
#dataclass
class fwidgets:
frame: str
framenum: int
widnum: int
widgets: list
ref: list
class App:
def addcount(self):
pass
def resetcount(self):
pass
def main(self):
win = tk.Tk()
largedoublefrm = ttk.Frame(win)
largesinglefrm = ttk.Frame(win)
smalldoublefrm = ttk.Frame(win)
smallsinglefrm = ttk.Frame(win)
combofrm = ttk.Frame(win)
largedoublefrm.grid(column=2, row=0)
largesinglefrm.grid(column=0, row=0)
smalldoublefrm.grid(column=3, row=0)
smallsinglefrm.grid(column=1, row=0)
combofrm.grid(column=4, row=0)
mainframe = fwidgets('win', 200, 5, ('largesinglefrm', 'smallsinglefrm',
'largedoublefrm', 'smalldoublefrm', 'combofrm'),
(largesinglefrm, smallsinglefrm, largedoublefrm,
smalldoublefrm, combofrm))
self.refframe = []
x=0
for wid in mainframe.ref:
ttk.Label(wid, text=mainframe.widgets[0]).pack()
self.count = ttk.Label(wid, text='0', font=("Arial", 20))
add = tk.Button(wid, text='ADD', height=5, width=15,
command=self.addcount)
reset = tk.Button(wid, text='RESET', height=5, width=15,
command=self.resetcount)
self.count.pack()
add.pack()
reset.pack()
frame = fwidgets(mainframe.widgets[x], x, 3, ('count', 'add', 'reset'),
(self.count, add, reset))
self.refframe.append(frame)
x += 1
print(self.refframe)
win.mainloop()
if __name__ == '__main__':
app = App()
app.main()
I'm not sure where to go from here. Any help would be appreciated.
To find the relevant objects first remove win.mainloop(), since only one
mainloop is permitted per tk.tk instance.
Then modify your code like this.
def addcount(self, i):
print(i.winfo_children())
def resetcount(self, i):
print(i.winfo_children())
add = tk.Button(wid, text='ADD', height=5, width=15,
command = lambda w = wid: self.addcount(w))
reset = tk.Button(wid, text='RESET', height=5, width=15,
command = lambda w = wid: self.resetcount(w))
# win.mainloop()
Now when you click a button a list of relevant objects will be displayed.
Most of the topics I came across deals with how to not shrink the Frame with contents, but I'm interested in shrinking it back after the destruction of said contents. Here's an example:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
So far I should see this in my window:
Hello!
My name is Foo
Bye!
That's great, but I want to keep the middle layer interchangeable and hidden based on needs. So if I destroy the lbl2 inside:
lbl2.destroy()
I want to see:
Hello!
Bye!
But what I see instead:
Hello!
███████
Bye!
I want to shrink frm back to basically non-existence because I want to keep the order of my main widgets intact. Ideally, I want to run frm.pack(fill=tk.BOTH, expand=True) so that my widgets inside can scale accordingly. However if this interferes with the shrinking, I can live without fill/expand.
I've tried the following:
pack_propagate(0): This actually doesn't expand the frame at all past pack().
Re-run frm.pack(): but this ruins the order of my main widgets.
.geometry(''): This only works on the root window - doesn't exist for Frames.
frm.config(height=0): Oddly, this doesn't seem to change anything at all.
frm.pack_forget(): From this answer, however it doesn't bring it back.
The only option it leaves me is using a grid manager, which works I suppose, but not exactly what I'm looking for... so I'm interested to know if there's another way to achieve this.
When you destroy the last widget within a frame, the frame size is no longer managed by pack or grid. Therefore, neither pack nor grid knows it is supposed to shrink the frame.
A simple workaround is to add a small 1 pixel by 1 pixel window in the frame so that pack still thinks it is responsible for the size of the frame.
Here's an example based off of the code in the question:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
def delete_the_label():
lbl2.destroy()
if len(frm.winfo_children()) == 0:
tmp = tk.Frame(frm, width=1, height=1, borderwidth=0, highlightthickness=0)
tmp.pack()
root.update_idletasks()
tmp.destroy()
button = tk.Button(root, text="Delete the label", command=delete_the_label)
button.pack()
root.mainloop()
Question: Shrink a Frame after removing the last widget?
Bind to the <'Expose'> event and .configure(height=1) if no children.
Reference:
Expose
An Expose event is generated whenever all or part of a widget should be redrawn
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
tk.Label(self, text='Hello!').pack()
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
tk.Label(self, text='Bye!').pack()
tk.Label(frm, text='My name is Foo').pack()
self.menubar = tk.Menu()
self.config(menu=self.menubar)
self.menubar.add_command(label='delete', command=self.do_destroy)
self.menubar.add_command(label='add', command=self.do_add)
frm.bind('<Expose>', self.on_expose)
def do_add(self):
tk.Label(self.frm, text='My name is Foo').pack()
def do_destroy(self):
w = self.frm
if w.children:
child = list(w.children).pop(0)
w.children[child].destroy()
def on_expose(self, event):
w = event.widget
if not w.children:
w.configure(height=1)
if __name__ == "__main__":
App().mainloop()
Question: Re-run frm.pack(): but this ruins the order of my main widgets.
frm.pack_forget(), however it doesn't bring it back.
Pack has the options before= and after. This allows to pack a widget relative to other widgets.
Reference:
-before
Use its master as the master for the slaves, and insert the slaves just before other in the packing order.
Example using before= and self.lbl3 as anchor. The Frame are removed using .pack_forget() if no children and get repacked at the same place in the packing order.
Note: I show only the relevant parts!
class App(tk.Tk):
def __init__(self):
...
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
self.lbl3 = tk.Label(self, text='Bye!')
self.lbl3.pack()
...
def on_add(self):
try:
self.frm.pack_info()
except:
self.frm.pack(before=self.lbl3, fill=tk.BOTH, expand=True)
tk.Label(self.frm, text='My name is Foo').pack()
def on_expose(self, event):
w = event.widget
if not w.children:
w.pack_forget()
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6
I'm going to create a program that resembles the image below. The interface uses one text entry for a name, one button, and two labels. The button should have the text Say hello and when the user clicks the button, the bottom label should display the name with Hi in front of it (see image below)
Here's what I've got
from tkinter import *
from tkinter.ttk import *
def say_hello():
name_var.set(name_entry.get())
def main():
global window, name_var, name_entry
window = Tk()
top_label = Label(window, text='Enter a name below')
top_label.grid(row=0, column=0)
name_var = StringVar()
name_entry = Entry(window, textvariable=name_var)
name_entry.grid(row=1, column=0)
hello_button = Button(window, text='Say hello', command=say_hello)
hello_button.grid(row=2, column=0)
bottom_label = Label(window, text='Hi ' + name_var)
bottom_label.grid(row=3, column=0)
window.mainloop()
main()
When I try to run it I get this error:
Traceback (most recent call last): File "C:\Program Files (x86)\Wing IDE 101 5.1\src\debug\tserver_sandbox.py", line 29, in <module> File "C:\Program Files (x86)\Wing IDE 101 5.1\src\debug\tserver_sandbox.py", line 24, in main builtins.TypeError: Can't convert 'StringVar' object to str implicitly
Everything works GUI wise, I'm just not sure how to get the last label that says "Hi Jack" to come up after pressing the button — i.e what my command should be in the hello_button line.
Here's your offensive code:
bottom_label = Label(window, text='Hi ' + name_var)
You can't really add a string and an instance of a class. A Tkinter StringVar isn't actually a string, but like a special thing for the gui to hold a string. That's why it can update automatically and stuff like that. Solution is simple:
bottom_label = Label(window, text = 'Hi ' + name_var.get())
Here's how I did it:
#!/usr/bin/env python2.7
import Tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
self.name_var = tk.StringVar()
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.top_label = tk.Label(self, text='Enter a name below')
self.top_label.grid(row=0, column=0)
self.name_entry = tk.Entry(self)
self.name_entry.grid(row=1, column=0)
self.hello_button = tk.Button(self, text='Say hello', command=self.say_hello)
self.hello_button.grid(row=2, column=0)
self.output = tk.Label(self, textvariable=self.name_var)
self.output.grid(row=3, column=0)
def say_hello(self):
self.name_var.set("Hi {}".format(self.name_entry.get()))
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Ultimately it was very similar to your code. The only thing you were missing was how to use Tkinter.StringVar() correctly. You need to set the bottom label's textvariable to name_var when you create it, and then you should be good to go.
This simple class should do what you want:
from tkinter import Button, Tk, Entry,StringVar,Label
class App():
def __init__(self, **kw):
self.root = Tk()
# hold value for our output Label
self.s = StringVar()
# label above our Entry widget
self.l = Label(self.root, text="Enter name here").pack()
# will take user input
self.e = Entry(self.root)
self.e.pack()
self.b = Button(self.root, text="Say Hello",command=self.on_click).pack()
# textvariable points to our StringVar
self.l2 = Label(self.root, textvariable=self.s).pack()
self.root.mainloop()
# every time the Button is pressed we get here
# an update the StringVar with the text entered in the Entry box
def on_click(self):
self.s.set(self.e.get())
App()
You just need to create a couple of Labels, and Entry widget to take the name and a callback function to update the StringVar value so the label/name value gets updated.
I'd like to know how I can add or delete widgets from within an imported module. I fail to access them correctly. I know, using OOP would make it easier, but I tried to grasp OOP and while the principles are easy I can't get my head around the details, so since I lack a proper teacher, I need a procedural solution.
This is the main script:
#!/usr/bin/python
try:
# Python2
import Tkinter as tk
except ImportError:
# Python3
import tkinter as tk
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
import target
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=300,height=200)
def test():
target.secondWindow()
root = tk.Tk()
root.geometry("600x350+30+50")
myframe = tk.Frame(root,relief="groove",bd=1)
myframe.place(x=20, y=30, width=560, height=200 )
canvas = tk.Canvas(myframe)
frame = tk.Frame(canvas)
myscrollbar=tk.Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right", fill="y")
canvas.pack(side="left")
canvas.create_window((0,0), window=frame, anchor='nw')
allMissions = {
"1":{"name":"go"},
"2":{"name":"see"},
"3":{"name":"win"},
"4":{"name":"party"}} # this would be a text file
for a in allMissions.keys():
mn = allMissions[a]["name"]
tk.Label(frame, text=mn, justify="left").grid(row=int(a), column=0)
# what's bind really doing?
frame.bind("<Configure>", myfunction)
test = tk.Button(root, command=test, text="TEST")
test.place(x = 20, y = 250, width=580, height=40)
tk.mainloop()
and this is the imported module: target.py
try:
# Python2
import Tkinter as tk
except ImportError:
# Python3
import tkinter as tk
def changeMainWindow():
# here's where I'm stuck
print("What do I have to do to add a new")
print("label in the main window from here?")
print("Or to delete it?")
def secondWindow():
amWin = tk.Toplevel()
amWin.geometry("300x200+720+50")
button = tk.Button(amWin, text="OK", command=changeMainWindow)
button.place(x = 20, y = 80, width=260, height=30)
#amWin.mainloop() comment noticed (:
You do it by passing the memory address of whatever widget to the second program. There is no reason to import Tkinter again as you can just pass a pointer to the existing instance. If you are going to be doing anything more than simple experimenting with Tkinter, then it is well worth the time to learn classes first at one of the online sites like this one http://www.greenteapress.com/thinkpython/html/thinkpython016.html More here https://wiki.python.org/moin/BeginnersGuide/NonProgrammers
You aren't going to get many answers with the way the program is structured because most programmers use the class structure AFAIK, so do not know how to pound the code into a non-class environment, so will not have any answers. If the first program below used classes then the second program's class could be inherited, and the functions would become part of the first program's class and could be accessed in the same way as the existing classes, so no passing of pointers, or any other hack, would be necessary.
## I deleted some code for simplicity
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=300,height=200)
def test():
TG.secondWindow()
root = tk.Tk()
root.geometry("600x350+30+50")
myframe = tk.Frame(root,relief="groove",bd=1)
myframe.place(x=20, y=30, width=560, height=200 )
canvas = tk.Canvas(myframe)
frame = tk.Frame(canvas)
myscrollbar=tk.Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right", fill="y")
canvas.pack(side="left")
canvas.create_window((0,0), window=frame, anchor='nw')
# what's bind really doing?
frame.bind("<Configure>", myfunction)
test = tk.Button(root, command=test, text="TEST", bg="lightblue")
test.place(x = 20, y = 250, width=580, height=40)
tk.Button(root, text="Quit All", command=root.quit,
bg="orange").place(x=20, y=300)
""" instance of the class in the imported program
a pointer to the root window and the Tk instance are passed
"""
TG=target.Target(tk, root)
tk.mainloop()
And target.py. Notice there are no imports.
class Target():
def __init__(self, tk, root):
self.tk=tk
self.root=root
def changeMainWindow(self):
# here's where I'm stuck
self.tk.Label(self.amWin, bg="yellow", text =""""What do I have to do to add a new
label in the main window from here?
Or to delete it?""").place(x=50,y=20)
def secondWindow(self):
self.amWin = self.tk.Toplevel(self.root)
self.amWin.geometry("300x200+720+50")
button = self.tk.Button(self.amWin, text="Add Label",
command=self.changeMainWindow)
button.place(x = 20, y = 90, width=260, height=30).
I am developing a GUI on Python and I have the following problem: I want to have the picture at the top of the window and buttons right underneath it. I am using the Tkinter module and whatever geometry I use (place, pack or grid) the buttons don't move. The are only displayed if I move the image using grid to row 1 (which is the second row), otherwise they don't appear at all. Here is the code I am using for now. For reference the picture has dimensions of 291x87 pixels.
import Tkinter
from Tkinter import *
def main():
window =Tk()
window.geometry("300x300")
window.title("Dienes Blocks Application")
window.iconbitmap(default='favicon.ico')
app = HomeScreen(window)
window.mainloop()
class HomeScreen(Frame):
def __init__(self, master):
Frame.__init__(self,master)
self.create_buttons()
self.sparx_head()
def sparx_head(self):
self.grid()
photo = Tkinter.PhotoImage(file="logosmall.gif")
sparx_header = Label(image=photo)
sparx_header.image = photo # keep a reference!
sparx_header.grid(column=0, row=0, columnspan=2, rowspan=2, sticky='NSEW')
def create_buttons(self):
self.grid()
#teacher button
teacher_button = Tkinter.Button(self, text="Teacher")
teacher_button.grid(column=0, row=10)
# student button
student_button = Tkinter.Button(self, text="Student")
student_button.grid(column=2, row=10)
# prototype button
prototype_button = Tkinter.Button(self, text="Prototype")
prototype_button.grid(column=1, row=10)
if __name__ == "__main__":
main()
You just need to use self in the image label:
sparx_header = Label(self,image=photo)
They were not having the same parent & that's why you were having this problem