How to create new Tkinter window after mainloop()? - python

I want to dynamically create Tkinter windows on my screen. I understand that I should only have one mainloop(). I use the threading module to make mainloop execute in a seperate thread, so it will not block the script.
How do I create more Tkinter windows after I executed mainloop?
Please take a look at my code:
from Tkinter import *
import threading
import time
class box:
def __init__(self, pos):
self.master = Tk()
self.master.geometry(pos)
self.canvas = Canvas(self.master, width=50, height=50, highlightthickness=0 )
self.canvas.pack()
self.rect = self.canvas.create_rectangle(0, 0, 50, 50, fill="red", outline="red")
self.text = self.canvas.create_text(25, 24, text="99",fill="white", font=("calibri", 24, "bold"))
def changeFill(self, color):
self.canvas.itemconfig(self.rect, fill=color, outline=color) # change color
class box_manager(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.boxes = {}
self.add_box(1, "50x50+300+300")
self.add_box(2, "50x50+100+100")
def add_box(self, num, pos):
self.boxes[num] = box(pos)
def run(self):
mainloop()
tk = box_manager()
tk.start()
# How do I dynamically add new tkinter windows? the line below makes python.exe crash.
tk.add_box(3, "50x50+200+200")
Update after Joel's comment, still doesn't work:
from Tkinter import *
import threading
import time
class MyCustomWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
#setup goes here
self.geometry("50x50+100+100")
self.canvas = Canvas(self, width=50, height=50, highlightthickness=0 )
self.canvas.pack()
class App(Tk):
def CreateFirst(self):
self.anotherWindow = MyCustomWindow()
def CreateSecond(self):
self.secondWindow = MyCustomWindow()
class SecondWindow(threading.Thread):
#after 2 seconds create a second window, python.exe crashes
def run(self):
time.sleep(2)
tk.CreateSecond()
SecondWindow().start()
tk = App()
tk.CreateFirst()
mainloop()

How do I create more Tkinter windows after I executed mainloop?
You don't. That's not how Tkinter is designed to work. You should always call mainloop exactly once, and from the main thread.

Additional (non-root) windows are simply Toplevel widgets. You would simply subclass Toplevel, and call it from within your main class:
class MyCustomWindow(tkinter.Toplevel):
def __init__(self):
tkinter.Toplevel.__init__(self)
#setup goes here
class App(tkinter.Tk):
def someCallback(self):
self.anotherWindow = MyCustomWindow()
EDIT
You don't have to subclass Toplevel of course, you can use it directly.

Related

How do I show, hide and show tkinter canvas after a period of time?

I'm trying to show and hide and show the canvas. The canvas does not even appear when I run the program and seems to get stuck in a loop.
import tkinter as tk
import time
class Test():
def __init__(self):
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, bg="black", width=550, height=820)
def main(self):
time.sleep(2)
self.canvas.pack()
time.sleep(2)
self.canvas.pack_forget()
time.sleep(2)
self.canvas.pack()
self.root.mainloop()
a = Test()
a.main()
I see some problems here
You should use mainloop method inside __init__ function
You should update your elements (.update()) to make it work
U simply cannot occour events like that
Dont use sleep function Using .after method is slightly better in tkinter
I've modified your code to something like this:
import tkinter as tk
class Test():
def __init__(self):
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, bg='black', width=550, height=820)
self.btn = tk.Button(self.root,text='main func',command=self.main)
self.btn.pack()
self.root.mainloop()
def main(self):
self.root.after(2000)
self.canvas.pack()
self.canvas.update()
self.root.after(2000)
self.canvas.pack_forget()
self.canvas.update()
self.root.after(2000)
self.canvas.pack()
self.canvas.update()
a = Test()
As u can see I've added a button to occur the event for me.
Try to find the moment in your program where the event can be occured. For example: I click the button to generate some strings. Its also runs main funcion.
I hope i helped you enough

Open a new window with the button

I am building a real time monitoring project where information is given in the first window and that's keep on updating in the second window. I am trying to monitor the updated information in parallel from a different window using the same code, but as I pressed the new button and given the new information it is updating in the previous window also but I wanted monitor window to be completely different, so that I can monitor the different information in parallel using the same code. Please have a look at the sample code and help me with the ideas.
The sample code:
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
from tkinter import *
class Demo1:
data=[]
def __init__(self, master):#Python scrollbar on text widget
self.master = master
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.grid(row=1, column=1)
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.grid(row=2,column=1)
def new_window(self):
self.inputValue=self.t.get("1.0",'end-1c')
Demo1.data1=self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Toplevel() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.mainloop()
class Demo2:
t1 = []
s1 = True
display = []
display1 = []
i=0
kas = True
num=0
j1=0
def __init__(self, master):
self.master = master
self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
cols = ('aa','bb')
self.listBox = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.listBox.heading(col, text=col)
self.listBox.column(col,minwidth=0,width=170)
self.listBox.grid(row=1, column=0)
self.a()
def a(self):
self._img=tkinter.PhotoImage(file="green1.gif")
a=Demo1.data1
for i,(a) in enumerate(a): #here I have some function which runs repeatedlly
self.listBox.insert('', 'end',image=self._img, value=(a))
threading.Timer(1.0, self.a).start()
def new(self):
main()
def main():
root = tkinter.Toplevel()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
I have given the pppp information to monitor but as a pressed new button and added the xx information its updating in the previous window also. Please help me with the idea so that the link between these window will be vanished.
Output:
You have some major issues with your program. Including how you are trying to use your classes. The Toplevel() object was giving me issue, so I used Tk(). This should show you how to properly use the classes with the window. Most importantly your second window needs to be created from global not the first window. Also Demo1.data is a reference to your class definition not the actual data you loaded. I hope this example is helpful.
from tkinter import *
# your second window should be created in global
def create_demo2():
global app, app2
root2 = Tk()
app2 = Demo2(root2, app)
class Demo1:
def __init__(self, window):
self.window = window
self.data = ""
self.button = Button(self.window, text="New Window",
command=create_demo2)
self.button.pack()
def set_data(self):
self.data = "data"
class Demo2:
# you could just use app from global scope, but I like to pass so that it is explicit.
def __init__(self, window, app1):
self.window = window
self.button_set = Button(self.window, text="Set", command=app1.set_data)
self.button_use = Button(self.window, text="Use", command=self.use_data)
self.app = app1
self.label = Label(self.window)
self.button_set.pack()
self.button_use.pack()
self.label.pack()
def use_data(self):
self.label.configure(text=self.app.data)
root = Tk()
app = Demo1(root)
app2 = None
root.mainloop()

Want to display two string value in two different tkinter label

I make code for displaying two serial read string in two different label using tkinter GUI..
My code is running and window also popped up but values are not displaying..
Help me to solve this issue..
Here is my code
import serial
import tkinter
from tkinter import *
import time
class SerialViewer(Tk):
def _init_(self):
self.win = Tk()
self.ser=serial.Serial('/dev/ttyS0',9600)
def makewindow (self):
frame1 =Frame(self.win)
frame1.pack()
self.v=StringVar()
self.v.set=('default')
label=Label(frame1,textvariable=self.v,relief=RAISED)
label.pack(side=LEFT)
frame2 = Frame(self.win)
frame2.pack()
self.d=StringVar()
self.d.set=('default')
label=Label(frame2,textvariable=self.d,relief=RAISED)
label.pack(side=RIGHT)
def update(self):
print(self.ser.write("*01T%"))
data=self.ser.readline(self.ser.inWaiting())
self.v.set(data)
time.sleep(2)
print(self.ser.write('*00T%'))
data1=self.ser.readline(self.ser.inWaiting())
self.d.set(data1)
self.win.after(100,self.update)
def run (self):
self.makewindow()
self.update()
self.win.mainloop()
app=SerialViewer()
app.mainloop()
As said in comment that the method _init_ needs to be changed to __init__ and the recursion error is coming because the __init__ method of parent class wasn't called. Please add that call like below:
class SerialViewer(Tk):
def _init_(self):
super(SerialViewer, self).__init__() # Call the initializer method of parent class
self.win = Tk()
self.ser=serial.Serial('/dev/ttyS0',9600)
Hope this helps!!

import a python file that create a window when main window button clicks

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

Interacting with Tkinter window during a long process

I have a basic python class that creates a window using the standard Tkinter library:
import Tkinter
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def lock_func(self):
while 1==1:
print "blah"
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=self.lock_func)
self.processBtn.pack()
app = GUI(None)
app.mainloop()
when I hit the Process button, the window doesn't respond.
I want to be able to close the program (using the x button) whene the lock_func is runing.
You could use a generator to hold the state within the loop, and use yield to relinquish control back to the main loop. Then use self.after to repeatedly call the generator's next method to simulate the effect of while True -- but doing it in a way which is friendly to Tkinter's main loop.
import Tkinter as tk
class App(object):
def __init__(self, master):
self.master = master
self.initialize()
def lock_func(self):
def step():
while True:
print("blah")
self.nextstep_id = self.master.after(1, nextstep)
yield
nextstep = step().next
self.nextstep_id = self.master.after(1, nextstep)
def stop(self):
self.master.after_cancel(self.nextstep_id)
print("stopped")
def initialize(self):
self.nextstep_id = 0
self.process_button = tk.Button(self.master, text="Process",
command=self.lock_func)
self.stop_button = tk.Button(self.master, text="Stop",
command=self.stop)
self.process_button.pack()
self.stop_button.pack(expand='yes', fill='x')
root = tk.Tk()
app = App(root)
root.mainloop()
You can use the window.update() method too keep your GUI active and functional after every time you change something on it. During the roots mainloop, this happens automatically but if you're prolonging the mainloop it's probably a good idea to do it manually your self. Put the window.update() in the loop that is taking a while. Note: window is a Tk() object
One way is to use threading:
import Tkinter
import thread
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def lock_func(self):
while 1==1:
print "blah"
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
self.processBtn.pack()
app = GUI(None)
app.mainloop()
However, it will keep printing until you close the Python console.
To stop it, you can use another button that changes a variable:
import Tkinter
import thread
class GUI(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.shouldPrint = True
self.initialize()
def lock_func(self):
while self.shouldPrint:
print "blah"
def setShouldPrint(self, value):
self.shouldPrint = value
def initialize(self):
self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
self.stopBtn = Tkinter.Button(self, text = "Stop", command = lambda: self.setShouldPrint(False))
self.processBtn.grid(row = 1)
self.stopBtn.grid(row = 2)
app = GUI(None)
app.mainloop()

Categories