My base program imports it's GUI interface from a script GUI.py
old_stdout = sys.stdout
root = Tk.Tk()
root.title('Coursera-dl')
root.geometry("345x230")
app = GUI.Interface(root)
app.mainloop()
if app.button_press() == True and app.return_data():
data = app.return_data()
main(data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],data[8])
sys.stdout = old_stdout
In my GUI.py :
class Interface(ttk.Frame):
def __init__(self,parent=None):
ttk.Frame.__init__(self,parent)
self.parent = parent
self.New_Window()
def New_Window(self):
self.newWindow = Tk.Toplevel(self.parent)
self.app = CoreGUI(self.newWindow)
class StdoutRedirector(object):
def __init__(self,text_widget):
self.text_space = text_widget
def write(self,string):
self.text_space.insert('end', string)
self.text_space.see('end')
class CoreGUI(object):
def __init__(self,parent):
self.parent = parent
self.InitUI()
def InitUI(self):
self.text_box = Tk.Text(self.parent, wrap='word', height = 11, width=50)
self.text_box.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
sys.stdout = StdoutRedirector(self.text_box)
But what It does is it opens two windows and the first window (the toplevel one) works as expected and the second is idle , This is what is expected until I click a certain button which after pressing prints data continuously and the data printed should appear in the second window's text widget however this doesn't happen and there is no response from the program and when I close the Toplevel window an error message appears
"TclError: invalid command name "".33328904.33329104"""
So How can I print the data in Text Widget rather than in the console?
EDIT:
Inorder to help ya'll if you struggling with this, I've made a script to redirect stdout to a Tkinter Text widget, see it in action here :-)
The problem is that when you call app.mainloop(), the thread is busy executing the Tkinter mainloop, so the statements before it are not executed until you exit the loop. But once you exit the mainloop, you try to use the Text widget but it is already destroyed.
I recommend you to move the call to main to the callback of a Tkinter widget (I suppose you are already trying to do that with app.button_press()), so the Text object can be used to display the text.
class CoreGUI(object):
def __init__(self,parent):
self.parent = parent
self.InitUI()
button = Button(self.parent, text="Start", command=self.main)
button.grid(column=0, row=1, columnspan=2)
def main(self):
print('whatever')
def InitUI(self):
self.text_box = Text(self.parent, wrap='word', height = 11, width=50)
self.text_box.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
sys.stdout = StdoutRedirector(self.text_box)
root = Tk()
gui = CoreGUI(root)
root.mainloop()
Related
I am writing a python application using Tkinter.
The purpose of the application is to allow the user to pick a date from a calendar.
The user have to click on a button to spawn a new window,
The user then select the date and updates the value in the mainframe.
I have coded it:
import tkinter as tk
class App(tk.Tk):
""" Allows user to pick a date """
def __init__(self):
super().__init__()
self.label_a01 = tk.Label(text = 'Selected date is:')
self.var_selected_date = tk.StringVar(value = "Noting selected")
self.label_selected_date = tk.Label(textvariable = self.var_selected_date)
self.button_a01 = tk.Button(self,
text="Select the first day", command = self.select_a_date)
self.button_a02 = tk.Button(self,
text="Select the last day", command = self.select_a_date)
self.label_a01.pack()
self.label_selected_date.pack()
self.button_a01.pack(pady = 5)
self.button_a02.pack(pady = 5)
def select_a_date(self):
self.window01 = tk.Toplevel(self)
self.label_b01 = tk.Label(self.window01, text = 'Please select a date from the calendar')
self.button_b01 = tk.Button(self.window01, text="01 Jan 2022", command = lambda: self.update_date_to_mainframe(self.button_a01, "01 Jan 2022"))
self.button_b02 = tk.Button(self.window01, text="31 Dec 2022", command = lambda: self.update_date_to_mainframe(self.button_a02, "31 Dec 2022"))
self.label_b01.pack(pady = 5)
self.button_b01.pack(pady = 5)
self.button_b02.pack(pady = 5)
def update_date_to_mainframe(self, widget, text_to_update):
widget.config(text = text_to_update)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
However, if the user click the button multiple times, it will open many new windows.
Is there a way to limit only 1 new Toplevel window that can be opened from the button press?
Thanks to acw1668 for suggesting modal dialogs.
Found an answer somewhere on reddit:
(credits to socal_nerdtastic)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try: #python3 imports
import tkinter as tk
except ImportError: #python3 failed, try python2 imports
import Tkinter as tk
class Main(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the main frame")
lbl.pack()
btn = tk.Button(self, text='click me', command=self.open_popup)
btn.pack()
def open_popup(self):
print("runs before the popup")
Popup(self)
print("runs after the popup closes")
class Popup(tk.Toplevel):
"""modal window requires a master"""
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
lbl = tk.Label(self, text="this is the popup")
lbl.pack()
btn = tk.Button(self, text="OK", command=self.destroy)
btn.pack()
# The following commands keep the popup on top.
# Remove these if you want a program with 2 responding windows.
# These commands must be at the end of __init__
self.transient(master) # set to be on top of the main window
self.grab_set() # hijack all commands from the master (clicks on the main window are ignored)
master.wait_window(self) # pause anything on the main window until this one closes
def main():
root = tk.Tk()
window = Main(root)
window.pack()
root.mainloop()
if __name__ == '__main__':
main()
Sorry for any stupidity or ignorance on my part but I am new to Python, and coding in general. I have been trying to get a UI working for a game I'm going to make as a way of teaching myself python I have a main window and a few button widgets in this.
The Play game button opens a second window (for the game itself) and hides the root window with the .withdraw method. This works perfectly. Inside the game window I have another button which I would like to destroy the game window and bring the user back to the root menu window that I have withdrawn. This seems to work except every time I call it,it creates an new, duplicate set of widgets in the window so I end up with multiple sets.
I'll post my full code at the bottom but here are what I believe are the relevant parts.
A tkinter Button calls this inside the parent class (the main window). This works fine.
def playGame(self): #Start the main game window
self.master.withdraw()
gameWindow()
I'm using the bellow method inside of the child class to destroy the game window and then call a method in the parent class to bring back the earlier withdrawn window
def exitMenu(self):
self.g.destroy()
UI(root).showMenu()
this works except it duplicates the widgets each time resulting in this being shown:
screen capture of result
Bellow is all my code, thank you so much for any help.
import tkinter as tk
import PIL
from Config import *
root = tk.Tk()
class UI(): #Main Menu
def __init__(self, master):
#Create Main Menu Window
self.master = master
self.master.title("Monopoly")
self.master.wm_iconbitmap('icons\Monopoly-Icon.ico')
self.master.geometry((resolution))
#Menu Buttons
self.label = tk.Label(master, text= 'Welcome to Monopoly! PLACEHOLDER')
self.playButton = tk.Button(master, text= 'Play Game', command= self.playGame)
self.settingsButton = tk.Button(master, text= 'settings', command= self.settings)
self.exitButton = tk.Button(master, text= 'Exit', command= self.exitGame)
self.label.grid(columnspan=2)
self.playButton.grid(column=0)
self.settingsButton.grid(column=0)
self.exitButton.grid(column=0)
def settings(self): #Opens Settings Window
s = tk.Toplevel()
s.title('Settings')
s.wm_iconbitmap('icons\Monopoly-Icon.ico')
s.geometry((resolution))
self.master.withdraw()
resLabel = tk.Label(s, text= 'Resolution')
resOption = tk.OptionMenu(s, resolution, *resList)
resLabel.grid(column=0,row=0)
resOption.grid(column=0, row=4)
def showMenu(self): #Bring back menu windwow
self.master.deiconify()
def exitGame(self): #Exit Game Method
root.destroy()
def playGame(self): #Start the main game window
self.master.withdraw()
gameWindow()
class gameWindow(UI):
def __init__(self):
self.g = tk.Toplevel()
self.g.title('Monopoly')
self.g.wm_iconbitmap('icons\Monopoly-Icon.ico')
self.g.geometry((resolution))
self.menuButton = tk.Button(self.g, text= 'Main Menu', command= self.exitMenu)
self.menuButton.grid(column=0,row=0)
def exitMenu(self):
self.g.destroy()
UI(root).showMenu()
mainMenu = UI(root)
root.mainloop()
You should pass the parent class, UI, to the child class gameWindow, then you can use the showMenu() method of the parent class in the child class.
Code for showing the main window from the child window will be:
self.mainUI.showMenu() instead of UI(root).showMenu()
Child class becomes:
class gameWindow(UI):
def __init__(self, mainUI): # Make the main window a parameter/attribute
self.mainUI = mainUI # Make the main window a parameter/attribute
self.g = tk.Toplevel()
self.g.title('Monopoly')
self.g.wm_iconbitmap('icons\Monopoly-Icon.ico')
self.g.geometry((resolution))
self.menuButton = tk.Button(self.g, text= 'Main Menu', command= self.exitMenu)
self.menuButton.grid(column=0,row=0)
def exitMenu(self):
self.g.destroy()
self.mainUI.showMenu()
gameWindow now requires one argument, so you pass it the main UI as self
def playGame(self): #Start the main game window
self.master.withdraw()
gameWindow(self)
You can read more about hiding/showing window here
Here is the full code:
import tkinter as tk
import PIL
from Config import *
root = tk.Tk()
class UI(): #Main Menu
def __init__(self, master):
#Create Main Menu Window
self.master = master
self.master.title("Monopoly")
self.master.wm_iconbitmap('icons\Monopoly-Icon.ico')
self.master.geometry((resolution))
#Menu Buttons
self.label = tk.Label(master, text= 'Welcome to Monopoly! PLACEHOLDER')
self.playButton = tk.Button(master, text= 'Play Game', command= self.playGame)
self.settingsButton = tk.Button(master, text= 'settings', command= self.settings)
self.exitButton = tk.Button(master, text= 'Exit', command= self.exitGame)
self.label.grid(columnspan=2)
self.playButton.grid(column=0)
self.settingsButton.grid(column=0)
self.exitButton.grid(column=0)
def settings(self): #Opens Settings Window
s = tk.Toplevel()
s.title('Settings')
s.wm_iconbitmap('icons\Monopoly-Icon.ico')
s.geometry((resolution))
self.master.withdraw()
resLabel = tk.Label(s, text= 'Resolution')
resOption = tk.OptionMenu(s, resolution, *resList)
resLabel.grid(column=0,row=0)
resOption.grid(column=0, row=4)
def showMenu(self): #Bring back menu windwow
self.master.deiconify()
def exitGame(self): #Exit Game Method
root.destroy()
def playGame(self): #Start the main game window
self.master.withdraw()
gameWindow(self)
class gameWindow(UI):
def __init__(self, mainUI): # Make the main window a parameter/attribute
self.mainUI = mainUI # Make the main window a parameter/attribute
self.g = tk.Toplevel()
self.g.title('Monopoly')
self.g.wm_iconbitmap('icons\Monopoly-Icon.ico')
self.g.geometry((resolution))
self.menuButton = tk.Button(self.g, text= 'Main Menu', command= self.exitMenu)
self.menuButton.grid(column=0,row=0)
def exitMenu(self):
self.g.destroy()
self.mainUI.showMenu()
mainMenu = UI(root)
root.mainloop()
I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered
so I am making an application that takes notes(similar to Windows Sticky Notes). Since I need to display multiple notes simultaneously, I have used a class which inherits from Thread and also creates a tkinter window. The problem is that my windows do not open simultaneously. The second opens up after the first is closed. Here is the code. What am I doing wrong? Is there another method that I can use? [For now I am just displaying notes I have hard-coded.]
from tkinter import *
from threading import Thread
class Note(Thread):
nid = 0
title = ""
message = ""
def __init__(self, nid, title, message):
Thread.__init__(self)
self.nid = nid
self.title = title
self.message = message
def display_note_gui(self):
'''Tkinter to create a note gui window with parameters '''
window = Tk()
window.title(self.title)
window.geometry("200x200")
window.configure(background="#BAD0EF")
title = Entry(relief=FLAT, bg="#BAD0EF", bd=0)
title.pack(side=TOP)
scrollBar = Scrollbar(window, takefocus=0, width=20)
textArea = Text(window, height=4, width=1000, bg="#BAD0EF", font=("Times", "14"))
scrollBar.pack(side=RIGHT, fill=Y)
textArea.pack(side=LEFT, fill=Y)
scrollBar.config(command=textArea.yview)
textArea.config(yscrollcommand=scrollBar.set)
textArea.insert(END, self.message)
window.mainloop()
def run(self):
self.display_note_gui()
new_note1 = Note(0, "Hello", "Hi, how are you?")
new_note1.start()
new_note1.join()
new_note2 = Note(1, "2", "How's everyone else?")
new_note2.start()
new_note2.join()
If all you need is multiple note windows then you definitely don't need threads. Tkinter is quite capable of managing dozens or hundreds of open windows.
Just create instances of Toplevel for every window except the root window. Here's a somewhat over-engineered example:
import Tkinter as tk
class Notepad(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self, wrap="word")
self.vsb = tk.Scrollbar(self, orient="vertical", comman=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
def main():
root = tk.Tk()
Notepad(root).pack(fill="both", expand=True)
for i in range(5):
top = tk.Toplevel(root)
Notepad(top).pack(fill="both", expand=True)
root.mainloop()
if __name__ == "__main__":
main()
Instead of subclassing Thread just subclass Toplevel, a top level in tkinter is a separate window in the same application which sounds like exactly what you are trying to accomplish:
from tkinter import *
#from threading import Thread #no longer needed
class Note(Toplevel):
nid = 0
#title = "" #this would block the method to override the current title
message = ""
def __init__(self, master, nid, title, message):
Toplevel.__init__(self,master)
self.nid = nid
self.title(title) #since toplevel widgets define a method called title you can't store it as an attribute
self.message = message
self.display_note_gui() #maybe just leave that code part of the __init__?
def display_note_gui(self):
'''Tkinter to create a note gui window with parameters '''
#no window, just self
self.geometry("200x200")
self.configure(background="#BAD0EF")
#pass self as the parent to all the child widgets instead of window
title = Entry(self,relief=FLAT, bg="#BAD0EF", bd=0)
title.pack(side=TOP)
scrollBar = Scrollbar(self, takefocus=0, width=20)
textArea = Text(self, height=4, width=1000, bg="#BAD0EF", font=("Times", "14"))
scrollBar.pack(side=RIGHT, fill=Y)
textArea.pack(side=LEFT, fill=Y)
scrollBar.config(command=textArea.yview)
textArea.config(yscrollcommand=scrollBar.set)
textArea.insert(END, self.message)
#self.mainloop() #leave this to the root window
def run(self):
self.display_note_gui()
root = Tk()
root.withdraw() #hide the root so that only the notes will be visible
new_note1 = Note(root, 0, "Hello", "Hi, how are you?")
#new_note1.start()
#new_note1.join()
new_note2 = Note(root, 1, "2", "How's everyone else?")
#new_note2.start()
#new_note2.join()
root.mainloop() #still call mainloop on the root
note that instead of storing the title as an attribute you can call self.title() to get the current title of the window and self.title("new title") to change it.
I found this example of code here on stackoverflow and I would like to make the first window close when a new one is opened.
So what I would like is when a new window is opened, the main one should be closed automatically.
#!/usr/bin/env python
import Tkinter as tk
from Tkinter import *
class windowclass():
def __init__(self,master):
self.master = master
self.frame = tk.Frame(master)
self.lbl = Label(master , text = "Label")
self.lbl.pack()
self.btn = Button(master , text = "Button" , command = self.command )
self.btn.pack()
self.frame.pack()
def command(self):
print 'Button is pressed!'
self.newWindow = tk.Toplevel(self.master)
self.app = windowclass1(self.newWindow)
class windowclass1():
def __init__(self , master):
self.master = master
self.frame = tk.Frame(master)
master.title("a")
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25 , command = self.close_window)
self.quitButton.pack()
self.frame.pack()
def close_window(self):
self.master.destroy()
root = Tk()
root.title("window")
root.geometry("350x50")
cls = windowclass(root)
root.mainloop()
You would withdraw the main window, but you have no way to close the program after the button click in the Toplevel, when the main window is still open but doesn't show Also pick one or the other of (but don't use both)
import Tkinter as tk
from Tkinter import *
This opens a 2nd Toplevel which allows you to exit the program
import Tkinter as tk
class windowclass():
def __init__(self,master):
self.master = master
##self.frame = tk.Frame(master) not used
self.lbl = tk.Label(master , text = "Label")
self.lbl.pack()
self.btn = tk.Button(master , text = "Button" , command = self.command )
self.btn.pack()
##self.frame.pack() not used
def command(self):
print 'Button is pressed!'
self.master.withdraw()
toplevel=tk.Toplevel(self.master)
tk.Button(toplevel, text="Exit the program",
command=self.master.quit).pack()
self.newWindow = tk.Toplevel(self.master)
self.app = windowclass1(self.newWindow)
class windowclass1():
def __init__(self , master):
""" note that "master" here refers to the TopLevel
"""
self.master = master
self.frame = tk.Frame(master)
master.title("a")
self.quitButton = tk.Button(self.frame,
text = 'Quit this TopLevel',
width = 25 , command = self.close_window)
self.quitButton.pack()
self.frame.pack()
def close_window(self):
self.master.destroy() ## closes this TopLevel only
root = tk.Tk()
root.title("window")
root.geometry("350x50")
cls = windowclass(root)
root.mainloop()
In your code:
self.newWindow = tk.Toplevel(self.master)
You are not creating a new window independent completely from your root (or master) but rather a child of the Toplevel (master in your case), of course this new child toplevel will act independent of the master until the master gets detroyed where the child toplevel will be destroyed as well,
To make it completely seperate, create a new instance of the Tk object and have it close the windowclass window (destroy its object):
self.newWindow = Tk()
you have two options here:
1 - Either you need to specify in the windowclass1.close_window(), that you want to destroy the cls object when you create the windowclass1() object, this way:
def close_window(self):
cls.master.destroy()
2 - Which is the preferred one for generality, is to destroy the cls after you create windowclass1 object in the windowclass.command() method, like this:
def command(self):
print 'Button is pressed!'
self.newWindow = Tk()
self.app = windowclass1(self.newWindow)
self.master.destroy()
and make the quitButton in the __init__() of windowclass1 like this:
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.master.quit)
to quit completely your program