I've been trying to get to grips with OOP and tkinter in python 3. I'd really like to have sub-windows pop up during use, either for data, output, etc. However, I cannot figure out how to assign the title in my Windows class, depending on what sort of window is being made. Please find a simplified example of what I have done so far.
from tkinter import *
from tkinter import messagebox
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("ProgramName")
self.pack(fill = BOTH, expand = 1)
menu = Menu(self.master)
self.master.config(menu=menu)
prog_help = Menu(menu)
prog_help.add_command(label='Help', command=self.help_popup)
prog_help.add_command(label='About', command=self.version_popup)
menu.add_cascade(label='Help', menu=prog_help)
#Method 1: Using message.box
def version_popup(self):
messagebox.showinfo("About program", "Version 0.1")
return
#Method 2: Using another window
def help_popup(self):
helpwindow()
return
def helpwindow():
hwindow = Toplevel()
hwindow.geometry("100x100")
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
I think that I should be doing something in the definition of helpwindow() which alters a variable in self.master.title(x), but I cannot figure out what.
Many thanks for your time.
In your case, the help window isn't a subclass of anything. All you need to do is call the title method of the Toplevel
def helpwindow():
hwindow = Toplevel()
hwindow.title("I am the help window")
...
Related
I am trying to write a gui which as a class which is the main application. A single instance of that class is created in the main root. At the same time I want a submit button to be clicked where some values are verified before a further submission to write the data. I am trying to do this by creating a new class for the Toplevel pop up window. But I am not sure how best to structure this. Ideally an instance of the pop up window class would be created each time the button is selected. It seems like with the way I have structured it another instance of the main application class has been created. I am a little confused how to correctly do this using OOP.
Below is some sample code to illustrate the problem.
import tkinter as tk
from tkinter import ttk
class Window(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.title = "TITLE"
self.master = master
self.submit = ttk.Button(self, text = 'SUBMIT', command = self.click_submit_button)
self.submit.grid(row = 0, column = 2, padx = 20, pady = 20)
def click_submit_button(self):
self.submit_pop_up = submit_button(self.master)
print('New PopUp')
class submit_button(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.master = master
self.title = 'TITLE'
if __name__ == "__main__":
root = tk.Tk()
app = Window(root)
app.pack()
root.mainloop()
There is something missing from my understanding of the best approach to using OOP to structure a program like this.
Solution
Just make the submit_button class inherit the Window class and instantiate only the submiit_button class. With this, you don't need to instantiate the Window class. This allows for a special trick so that you can access the attributes of the submit_class class in the Window class without creating an instance. Just use self since it is actually an instance of the submit_class passed on to the Window class. Here is your code with that. There are many other suggestions and to know, see the code.
Suggestions
In the first place, why are you making submit_button a separate class? You could include it as a method of Window class. If you have good reason, it is ok but otherwise make it a method.
And also, why are you creating a root window and making the window class save it as an attribute? Just make the Window class inherit tk.Tk instead of tk.Frame. You can then create frame inside the __init__ function. Here is the code working code with these rectifications:
Code
import tkinter as tk
from tkinter import ttk
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.title("TITLE")
self.submit = ttk.Button(self, text = 'SUBMIT', command = self.click_submit_button)
self.submit.grid(row = 0, column = 2, padx = 20, pady = 20)
class submit_button(Window):
def __init__(self):
super().__init__()
self.submit_pop_up = tk.Toplevel(self)
self.submit_pop_up.withdraw()
print(self.submit_pop_up)
def click_submit_button(self):
self.submit_pop_up.deiconify()
print('New PopUp')
if __name__ == "__main__":
app = submit_button()
app.mainloop()
Is that what you want? You should used self.master for all widgets.
import tkinter as tk
from tkinter import ttk
class Window(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.title( "TITLE")
self.submit = ttk.Button(self.master, text='SUBMIT', command=self.click_submit_button)
self.submit.grid(row=0, column=2, padx=20, pady=20)
def click_submit_button(self):
self.submit_pop_up = submit_button(self.master)
print('New PopUp')
class submit_button(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.master = master
self.master.title('TITLE')
if __name__ == "__main__":
root = tk.Tk()
app = Window(root)
#app.pack()
root.mainloop()
Result:
I found the following in the tk docs:
The wm manage and wm forget commands may be used to perform undocking
and docking of windows.
So I tried wm_manage and wm_forget in this code:
import tkinter as tk
root = tk.Tk()
class MyFigure(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master = master
self.bc = tk.Button(self, text='confi',
command=lambda:self.configure(bg='red')
)
self.bmanage = tk.Button(self, text='manage',
command = lambda:self.master.wm_manage(self)
)
self.bforget = tk.Button(self, text='forget',
command = lambda:self.master.wm_forget(self)
)
self.bmanage.pack(side='left')
self.bc.pack(side='left')
self.bforget.pack(side='left')
mf = MyFigure(root)
mf.pack()
root.mainloop()
But it dosen't worked out. So I readed more and there is no way I can missunderstand this:
A toplevel widget may be used as a frame and managed with any of the
other geometry managers after using the wm forget command.
So I tried to do something like that:
def _manage(self):
top = self.master.wm_manage(self)
print(top)
def _forget(self):
frame = self.master.wm_forget(self)
print(frame)
But both return None. Am I something missing here? What am I doing wrong?
In order to make wm_forget correctly work, you should pass a toplevel window as argument. For instance, if you add the following lines in the constructor of the class:
self.top = tk.Toplevel()
self.top.title("Top level")
You can then call the method as follows:
self.master.wm_forget(self.top)
Regarding the wm_manage, you should pass as argument the widget you want to convert to a stand alone top-level window. Please keep in mind that you can only use this command with frame, labelframe and toplevel widgets. If you apply this command to your main window Tk, nothing will happen.
A full example converting a frame to a toplevel (pressing button manage) and converting it back to frame (pressing button forget):
import tkinter as tk
root = tk.Tk()
class MyFigure(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master = master
self.bc = tk.Button(self, text='confi',
command=lambda:self.configure(bg='red')
)
self.bmanage = tk.Button(self, text='manage',
command = lambda:self._manage()
)
self.bforget = tk.Button(self, text='forget',
command = lambda:self._forget()
)
self.bmanage.pack(side='left')
self.bc.pack(side='left')
self.bforget.pack(side='left')
self.frame = tk.Frame(self.master, bg="red", height=100)
self.label=tk.Label(self.frame, text="hi")
self.frame.pack()
self.label.pack(expand=True, fill=tk.BOTH)
def _manage(self):
test=self.master.wm_manage(self.frame)
def _forget(self):
self.master.wm_forget(self.frame)
self.frame.pack()
mf = MyFigure(root)
mf.pack()
root.mainloop()
I am having issues with a Tkinter GUI. I need to create a large application. I need to use classes for that to manage all modules together. For a unit check and getting help here, I have tried to provide a sample which is close to exact problem ( with a small example) here:
I am creating a 1st window with a Button labelled as "Test". What I want is that when I click the button "Test", a new second window will pop up with a text "Enter Value" and entry space, where I can enter the value. I have provided the code below. What is happening is that, I am able to get the new window, but the text "Enter Value" and entry Space is generated in the first window instead of the second and the second window remains blank. I am not understanding where I am making the wrong logic call. Help will be very much appreciated.
I know we do not need classes for GUI applications, however to manage my large application ( not shown here), I need to have classes and I will very much appreciate, if some Tkinter Guru can help me with the bug in my code.
gui view File (gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=1,rowspan=2)
#Entry Space
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=2,column=0,rowspan=2)
### gui Controller File (gui_controller.py)
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.newWin = tk.Toplevel(self.root)
self.newWin.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew = gui_view.MySecondGUI(self.newWin)
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
if __name__ == "__main__":
c = MainControl()
#
ADDING A MODIFIED CODE WITH NEW PROBLEM.
I have now been able to generate a code which pops up a new window with entries, when I click the button "Test" in the first Window. However, I am having problems creating buttons in the scond window. The way I have it now, it pops an error to me saying "'MySecondGUI' object has no attribute 'buttonPressed2"
Help will be very much appreciated.
I have pasted my updated code below:
GUI_VIEW FILE ( gui_view.py)
import tkinter as tk
from tkinter import Tk
class MyMainGUI(tk.Frame):
def __init__(self, controller):
tk.Frame.__init__(self)
self.pack()
self.controller = controller
self.Button1=tk.Button(self)
self.Button1["text"]= "Test"
self.Button1["command"]=self.controller.buttonPressed1
self.Button1.grid(row=2,column=0,rowspan=2)
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)enter code here
GUI MAIN CONTROLLER FILE
import tkinter as tk
import gui_view # the VIEW file
class MainControl:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('550x200')
self.view = gui_view_temp.MyMainGUI(self)
self.view.mainloop()
def newWindow(self):
self.viewNew = gui_view.MySecondGUI()
self.viewNew.geometry('300x400')
self.newDisplay = tk.Label(self.newWin, text='Test Mode')
self.viewNew.mainloop()
self.newDisplay.pack()
def buttonPressed1(self):
self.newWindow()
def buttonPressed2(self):
pass
if name == "main":
c = MainControl()
in MySecondGUI(tk.Frame):
def __init__(self, controller):
#Attach your frame to "secondGUI" (which is your second window)
tk.Frame.__init__(self, controller)
in class MainControl:
#I assume you're passing your parent window/frame as "controller"?
self.viewNew = MySecondGUI(self.newWin)
according to Python Tkinter Docs
https://docs.python.org/3.5/library/tkinter.html#mapping-basic-tk-into-tkinter
You should specify your parent window if you're not attach your widget to main window.
#Main window
root = tk.Tk()
#Second Window
newWin = tk.Toplevel(root)
#Attach to main window
tk.Label(text="This label attached to root").pack()
tk.Button(text="This button attached to root").pack()
#Second Window
tk.Label(newWin, text="This label attached to second window").pack()
tk.Button(newWin, text="This button attached to second window").pack()
also,
self.viewNew.mainloop()
#This will fail because you have to set everything up before mainloop()
#_tkinter.TclError: can't invoke "pack" command: application has been destroyed
self.newDisplay.pack()
Edit for update
You should put your
def buttonPressed2(self):
in class MySecondGUI, not in class MainControl.
class MySecondGUI(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.outputLabel2 = tk.Label(self)
self.outputLabel2["text"] = ("Enter Value")
self.outputLabel2.grid(row=5,rowspan=2)
self.entrySpace2 = tk.Entry(self)
self.entrySpace2.grid(row=8,column=0,rowspan=2)
self.Button2=tk.Button(self)
self.Button2["text"]= "Try Me"
#self means "MySecondGUI" not "MainControl" here
self.Button2["command"] = self.buttonPressed2
self.Button2.grid(row=14,column=0,rowspan=2)
def buttonPressed2(self):
pass
I am kind of new to classes and tkinter in general. This is my code, I have been trying to a simple interface in which I can choose the starting date and end date in order to identify an interval.
import tkinter as tk
import Calendar as cal
class Application(tk.Frame):
def __init__(self, master = None):
super().__init__(master)
self.grid()
self.pack()
self.create_widgets()
def create_widgets(self):
self.winfo_toplevel().title("Choose date")
self.sd_button = tk.Label(self, text="Start date")
self.sd_button.pack()
self.ed_button = tk.Label(self, text="End date")
self.ed_button.pack()
self.sd_date_button = cal.Control(root)
self.sd_date_button.pack()
self.ed_date_button = cal.Control(root)
self.ed_date_button.pack()
root = tk.Tk()
my_gui = Application(root)
root.mainloop()
I want to implement a button next to "start date" and "end date" that allows me to choose the date like a pop up.
I found an external code widget from this link that might do the job: https://python-forum.io/Thread-Tkinter-tkinter-calendar-widget
So I capied the code in the link and put in into a "Calendar.py" file.
Unfortunately I have been struggling on how to make it appear effectively in my main code.
Can anyone help me out/give me a hint?
Thanks a lot in advance!
There were several problems.
You use both pack and grid in __init__.
Since you import Calendar.py it's not in your namesapce and you must compensate for that. The name gets to be Calendar.Calendar as both the package and class are named Calendar.
Use buttons instead of labels, and associate them with a command to open the calendar.
Save a reference to master so you can call it.
I also bound SPACE to the printout function.
import tkinter as tk
import Calendar
class Application(tk.Frame):
def __init__(self, master = None):
super().__init__(master)
self.master = master
self.grid()
self.data = {}
self.create_widgets()
def create_widgets(self):
self.winfo_toplevel().title("Choose date")
self.sd_button = tk.Button(self, text="Start date", command=self.popup)
self.sd_button.grid()
self.ed_button = tk.Button(self, text="End date", command=self.popup)
self.ed_button.grid()
self.master.bind('<space>', self.print_selected_date)
def popup(self):
child = tk.Toplevel()
cal = Calendar.Calendar(child, self.data)
def print_selected_date(self, event):
print(self.data)
root = tk.Tk()
my_gui = Application(root)
root.mainloop()
The class Control is not part of the calendar but is included as an example of how to use it. This is a common way of building packages. Look for the line if __name__ == '__main__': and there the example will be.
I'm trying to get this right but so far no luck. Would appreciate it if someone can help me with it.
import tkinter
class MyGUI:
def __init__(self):
self.main_window = tkinter.Tk()
self.button1 = tkinter.Button(self.main_window,text='Average',command=self.average)
self.button1.pack()
tkinter.mainloop()
def average(self):
self.mini_window = tkinter.Tk()
self.avg_mess = tkinter.Label(self.mini_window,text='Results:')
self.avg_result_var = tkinter.StringVar()
self.avg_result_display = tkinter.Label(self.mini_window,textvariable=self.avg_result_var)
self.avg_mess.pack()
self.avg_result_display.pack()
self.button2 = tkinter.Button(self.mini_window,text='Calculate',command=self.avg_calc)
self.button2.pack()
def avg_calc(self):
self.avg_result = (100+300+80)/3
self.avg_result_var.set(self.avg_result)
gui = MyGUI()
The problem occurs when the Calculate button is clicked but the avg_result_var does not change its value. And hence the avg.result_display remains blank. I suspect there is something wrong with the function call when the button is pressed. I'm using Python 3.x. Thanks.
You're almost doing it correctly, but there are a couple of problems
First, the result never changes because you use the same numbers each time you do a calculation. The result is always the same so it appears that it is not changing.
The second problem is that you're creating two instances of Tk. Tkinter isn't designed to work like that, and it causes problems such as the one you are observing. If you need additional pop-up windows, use Toplevel rather than Tk.
Here's a modified version of your program, though I've added a random number in the computation so you can see it change each time.
import Tkinter as tkinter
import random
class MyGUI:
def __init__(self):
self.main_window = tkinter.Tk()
self.button1 = tkinter.Button(self.main_window,text='Average',command=self.average)
self.button1.pack()
tkinter.mainloop()
def average(self):
self.mini_window = tkinter.Toplevel()
self.avg_mess = tkinter.Label(self.mini_window,text='Results:')
self.avg_result_var = tkinter.StringVar()
self.avg_result_display = tkinter.Label(self.mini_window,textvariable=self.avg_result_var)
self.avg_mess.pack(fill="both")
self.avg_result_display.pack()
self.button2 = tkinter.Button(self.mini_window,text='Calculate',command=self.avg_calc)
self.button2.pack()
def avg_calc(self):
x = random.randint(100,200)
self.avg_result = (100+300+x)/3
print "result:", self.avg_result
self.avg_result_var.set(self.avg_result)
gui = MyGUI()