My problem is simple, but i really don't know what the issue is.
I'm trying to open more than one window almost at the same time, but if i do it like that:
from tkinter import *
import threading
import time
class new_window:
def build(self, killtime):
self.w = Tk()
self.w.update()
time.sleep(killtime)
self.w.destroy()
def __init__(self, killtime):
threading.Thread(target=self.build(killtime)).start()
a = new_window(2)
time.sleep(2)
b = new_window(2)
it doesn't behave like: "open, wait, open"
but instead like: "open, wait until killed, wait, open"
What i mean is that the delay starts after the first window is closed, not after the window started. I thought a Thread would help me out, but it didn't.
Hopefully one of you knows how to fix that.
You don't really need to use the threading module.
You can use the .after() method to open a new window a little after.
Here is your code:
from tkinter import *
window = Tk()
window.title("window1")
def open_new_window():
window2 = Toplevel()
window2.title("Window2")
window.after(1000, open_new_window)
window.mainloop()
Hope this helps!
Edit: The code above opens one window, then stops doing anything. If you want new windows to keep opening with a small delay in between, you can use the below code:
from tkinter import *
window = Tk()
window.title("window1")
def open_new_window():
window2 = Toplevel()
window2.title("Window2")
window.after(1000, open_new_window)
open_new_window()
window.mainloop()
Hope this helps!
I did it like this now. But i still marked the Answer of #TheMaker as the Solution, cause its more compact and easier to understand.
from tkinter import *
import threading
import time
from random import *
root = Tk()
root.configure(background='black')
root.attributes("-fullscreen", True)
class new_window:
def build(self, delay, x, y):
self.w = Tk()
self.w.title("")
self.ws = root.winfo_screenwidth() # width of the screen
self.hs = root.winfo_screenheight() # height of the screen
self.w.geometry(f"250x100+{int(round(self.ws/100*x))}+{int(round(self.hs/100*y))}")
self.w.update()
time.sleep(delay)
def __init__(self, delay, x, y):
threading.Thread(target=self.build(delay, x, y)).start()
def rdm(delay, count):
i = 1
while i<=count:
new_window(delay, randint(1, 100), randint(1, 100))
i+=1
rdm(0.02, 100)
root.mainloop()
Related
I have already tried to find an answer to my question, but I haven't been successful.
I do my first try with an GUI with tkinter on Python 3.6
I create a small example for you:
import tkinter as tk
import PyQt5.QtWidgets
RateFenstergrossh=0.75
RateFenstergrossb=0.75
app = PyQt5.QtWidgets.QApplication([]) # the bug doesn't occur without this line
screen_width = app.desktop().screenGeometry().width()
screen_height = app.desktop().screenGeometry().height()
def vp_start_gui():
global root
root = tk.Tk()
top = Toplevel1(root)
root.mainloop()
class Toplevel1:
def __init__(self, top=None):
top.geometry("+%d+%d"%(screen_width/2 - screen_width*RateFenstergrossb/2, screen_height/2 - screen_height*RateFenstergrossh/2))
self.Entry = tk.Entry(top)
self.Entry.pack(padx=20,pady=20)
if __name__ == '__main__':
vp_start_gui()
If you type some characters and try to delete them with backspace a small rectangle appears in the entry line. If you print this character the program prints <0x08>. If I do not use the package import PyQt5.QtWidgets and the funktion PyQt5.QtWidgets.QApplication([]) the bug doesn't occur.
I do not really understand this bug. Maybe somebody can help me with this.
Thanks to the two comments. I have learned never mix tkinter and PyQt. My solution is now.
import tkinter as tk
RateFenstergrossh=0.75
RateFenstergrossb=0.75
def vp_start_gui():
global root
root = tk.Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
top = Toplevel1(root)
root.mainloop()
class Toplevel1:
def __init__(self, top=None):
top.geometry("+%d+%d"%(screen_width/2 - screen_width*RateFenstergrossb/2, screen_height/2 - screen_height*RateFenstergrossh/2))
self.Entry = tk.Entry(top)
self.Entry.pack(padx=20,pady=20)
if __name__ == '__main__':
vp_start_gui()
Solution: I have to read the screensize with
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
The purpose is that input fn takes string input and pass it into GUI fn which runs the condition and ammend tkinter window accordingly.
#*********************************** IMPORTING MODULES*****************
import tkinter
from tkinter import*
import tkinter.messagebox
import sqlite3
import os
import threading
from time import sleep
from input import*
conn = sqlite3.connect('portal.db')
c = conn.cursor()
global a
#*************** TKINTER GUI CODE******************
def gui(a):
window = tkinter.Tk()
window.title("Smart Notice Board")
#********************** FRAMES OF MAIN WINDOW(HOME)******************
top = Canvas(window,width=1024,height=184)
top.pack(fill=X)
middle = Canvas(window, width=1024, height=450, bg='steelblue')
middle.pack(fill=X)
main_left = Canvas(middle, width=275, height=450, bg='lightgreen')
main_left.pack(side=LEFT)
main_right = Canvas(middle, width=800, height=450, bg='steelblue')
main_right.pack(side=RIGHT)
bottom = Canvas(window, width=1024, height=70, bg='black')
bottom.pack(fill=X)
#************************** IMAGES********************
i_top = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
top.create_image(0,10, anchor=tkinter.NW,image = i_top)
i_right = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\aus1.png')
main_right.create_image(0,0, anchor=tkinter.NW,image = i_right)
#i_left = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\widget1.png')
#main_left.create_image(0,0, anchor=tkinter.NW,image = i_left)
t1 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\first.png')
t2 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
#***************** TIMETABLE IMAGE VIEWING FN***********************
def home():
main_right.create_image(0,0, anchor=tkinter.NW,image = t2)
#*********************** TIMETABLE BUTTON PRESS FN*************************
def timetable():
main_right.create_image(0,0, anchor=tkinter.NW,image = t1)
#******************************* CONDITIONS**********************
if a == "NULL":
timetable()
if a == "HOME":
home()
#*********************** MAIN MENU BUTTONS****************
button_1 = Button(text = ' HOME', anchor = 'w', height = 2, width = 8,activebackground = '#33B5e5',bg = 'brown',fg = 'white',command = home)
top.create_window(2,150,anchor = 'nw', window = button_1)
button_2 = Button(text='TIMETABLE', height = 2, width=12, activebackground = '#33B5e5', bg = 'brown', fg = 'white',command = timetable)
top.create_window(75,150, anchor='nw', window = button_2)
window.mainloop()
#************************** MAIN LOOP************************
if __name__ == "__main__":
#print(valuea())
a=valuea()
gui(a)
Now what I want is continuously run that thing and update Tkinter window; but the 2btn fn only takes it one time and pass it into GUI fn which runs tkinter and it stucks on window.mainloop as tkinter is infinity loop.
Please suggest me a solution also u can run this code by only setting pictures from your computer
The standard method to run code regularly in the mainloop is to register a timeout function using the after method of the root window.
But, whatever you do in such a timeout function (and indeed in all other callbacks), it should not block, because that would lock up the mainloop! So you cannot use input. But you could read from sys.stdin, which is a io.TextIOWrapper instance.
You could use input in a second thread. But since Tkinter isn't thread-safe, that second thread should not use Tkinter functions or methods. So you should not simply update a label from the second thread. You could save/append the input to a global variable, but you'd have to protect that with a lock or mutex. And you'd need to use a timeout function in the main Tkinter thread to test if the lock or mutex is released by the second thread so the Tkinter thread can claim it and access the data. As you can see this is really complicated. So mixing Tkinter and threads is generally not recommended.
In order to ammend tkinter window and show the text and open picture.I did slight change in the input.py file;and coded as below
def vala():
a=speech()
if a == "HOME":
home()
if a == "NULL":
timetable()
window.after(1000,vala)
what I have done is I convert it into recursive fn that call itself after 1000ms. In this way, it can be done.
Basically, I have done it with Pocketsphinx as Input (i.e. my project takes speech input and open file/image in tkinter screen and it runs continuously)
I have a Tkinter program which I want to pause for 3 seconds.
time.sleep doesn't work and the after method doesn't do exactly what I want to.
here is an example code:
from Tkinter import *
def waithere():
print "waiting..."
root = Tk()
print "1"
root.after(3000,waithere)
print "2"
root.mainloop()
output:
1
2
*3 seconds*
waiting...
the output i want to have:
1
waiting...
*3 seconds*
2
thanks.
Normally it's a very bad idea to have a GUI wait for something. That's imply not how event-based programs work. Or more accurately, GUIs are already in a perpetual wait state, and you don't want to block that with your own waiting.
That being said, tkinter has a way to wait until certain things happen. For example, you can use one of the "wait" functions, such as wait_variable, wait_window, or wait_visibility.
Assuming that you wanted waithere to do the waiting, you could use wait_variable to do the waiting, and after to set the variable after a given amount of time.
Here's the solution based on your original code:
from Tkinter import *
def waithere():
var = IntVar()
root.after(3000, var.set, 1)
print("waiting...")
root.wait_variable(var)
root = Tk()
print "1"
waithere()
print "2"
root.mainloop()
The advantage to using these methods is that your code is still able to respond to events while it is waiting.
I found a way like that, i hope it helps you:
from tkinter import *
def waitToShow():
index = 1
while index < 11:
l1.config(text=index)
l1.after(1000)
l1.update()
index += 1
win = Tk()
l1 = Label(win)
l1.pack()
waitToShow()
win.mainloop()
Just for future reference, refrain from using long or infinite loops in Tkinter; they will prevent the UI from responding to user events (AKA freezing). The method I was taught was to periodically update the field using the after() function.
The after() function creates an alarm-callback meaning when called (with the right parameters) it will queue a call to the target method (in the example below def update(self) with our entered delay. You can use a boolean in the class to exit the loop. Create on on __init__ and then when set to False don't call after() anymore.
Here is an example creating a class inheriting Tkinter.Frame to inherit the functionality.
try:
import tkinter as tk
except:
import Tkinter as tk
import datetime
class DelayedUpdateUI(tk.Frame):
def __init__(self, master=None, **kw):
# Create widgets, if any.
tk.Frame.__init__(self, master=master, **kw)
self.timeStr = tk.StringVar()
self.lblTime = tk.Label(self, textvariable=self.timeStr)
self.lblTime.grid()
# Call update to begin our recursive loop.
self.update()
def update(self):
self.timeStr.set(datetime.datetime.now())
# We use after( milliseconds, method_target ) to call our update
# method again after our entered delay. :)
self.after(1000, self.update)
if __name__ == '__main__':
root = tk.Tk()
DelayedUpdateUI(root).grid()
root.mainloop()
A suggestion based on Bryan's answer:
I understand the recommended way from an event-based perspective, but it does not feel very intuitive to me. I have to look up the trick every time I need it. Therefore, I have created a small mixin class that makes usage a bit more intuitive:
import tkinter as tk
class TkWaitMixin:
"""Simple wait timer that makes Tk waiting functionality
more intiutive. Applies the recommended way according to
https://stackoverflow.com/a/51770561/12646289.
"""
def start_wait_timer(self, milliseconds):
self.resume = tk.BooleanVar(value=False)
self.master.after(milliseconds, self.resume.set, True)
# Assume master attribute is available:
# https://stackoverflow.com/a/53595036/12646289
def wait_on_timer(self):
self.master.wait_variable(self.resume)
Example usage:
import tkinter as tk
class MyWindow(tk.Tk, TkWaitMixin):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
self.start_wait_timer(milliseconds)
self.message_label['text'] = message
self.wait_on_timer()
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
Obviously, this will only be of use if you use classes in your tkinter code. Also note that the master attribute should be available in the main class to which the mixin is added.
Edit
Alternatively, usage can be made even easier with a context manager:
import tkinter as tk
class TkWait:
def __init__(self, master, milliseconds):
self.duration = milliseconds
self.master = master
def __enter__(self):
self.resume = tk.BooleanVar(value=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.master.after(self.duration, self.resume.set, True)
self.master.wait_variable(self.resume)
Note that the waiting starts when the context manager is exited.
Example usage:
import tkinter as tk
class MyWindow(tk.Tk):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
with TkWait(self.master, milliseconds):
self.message_label['text'] = message
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
You forgot to do the () at root.after(3000,waithere <<<<-)
from tkinter import *
def waithere():
print("waiting...")
root = Tk()
print("1")
root.after(3000,waithere())
print("2")
root.mainloop()
I apologize if this is a redundant post, but I have spent a few hours sniffing out solutions around StackOverflow and other sources, this is where I ended up. AFAIK it should work.
Put simply, I can't fathom why my variable doesn't update more than once. When I run this program, it starts at '0.0' and updates to a realistic value (after time.sleep(5)), but only once. Theoretically it should update in real-time...
I realize you can't recreate my dev environment with my exact Modbus slave device, etc etc, but flow_unpack is a "good" variable because it prints to the Tkinter window once. If it was bad, it would return a ValueError or something. The problem is: why isn't it updating?
import tkinter as tk
import time
import minimalmodbus
import serial
import struct
root = tk.Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500, 500))
i = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
i.serial.baudrate = 9600
i.serial.bytesize = 8
i.serial.parity = serial.PARITY_ODD
i.serial.stopbits = 1
i.serial.timeout = 1
i.debug = False
var = tk.DoubleVar()
label = tk.Label(root, textvariable=var)
label.pack()
mass_flow_rate = i.read_registers(registeraddress=246, numberOfRegisters=2,
functioncode=3)
flow_ = [mass_flow_rate[0], mass_flow_rate[1]]
flow_pack = struct.pack('HH', flow_[0], flow_[1])
flow_unpack = round(struct.unpack('f', flow_pack)[0], 4)
def function():
global flow_unpack
var.set(flow_unpack)
root.update()
time.sleep(5)
root.after(2000, function)
root.mainloop()
Change this in your code
def function():
global flow_unpack
var.set(flow_unpack)
root.after(2000, function) # just add this for further calls
# root.update() -> not needed, see Brian comment.
# time.sleep(5) -> do you really need this?
# you will block the tkinter mainloop and your UI.
I have a program written in IDLE3.3 and tkinter where I don't know where to place the mainloop(). The program creates a systray icon that creates a little note if you click on "new note" in the context menu. If there is the line "self.root.mainloop()" at the end of Note.init(), the note is shown, but only one note. if I create a second one, the first note is dead and nothing further happens.
But if I don't call the mainloop() in the init-method, i see that there a several notes created because it is printed in the shell.
So the question is, where should I place the mainloop so that every newly created notw is shown and works? Sorry for that possibly stupid question but I can't figure it out.
from tkinter import *
import sys
from PyQt4.QtGui import *
import threading
class Note():
yellow=["#e7e37c","#d9d574"]
def __init__(self,noteset=None, properties=None):
self.root=Tk()
self.noteset=noteset
self.properties=properties
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
print("No initial properties to load => creating new note")
self.notecolor=self.yellow[0]
self.gripcolor=self.yellow[1]
self.root.overrideredirect(1)
self.text=""
self.font="arial"
self.fontsize=10
self.sizeX=250
self.sizeY=200
self.posX=int(self.screen_width/2 - self.sizeX/2)
self.posY=int(self.screen_height/2 - self.sizeY/2)
self.root.wm_geometry("%sx%s+%s+%s" %(self.sizeX, self.sizeY, self.posX, self.posY) )
self.root.wm_attributes("-topmost",1)
self.GUI()
self.bindings()
self.root.mainloop()
def bindings(self):
self.frmGRIP.bind("<ButtonPress-1>", self.StartMove)
self.frmGRIP.bind("<ButtonRelease-1>", self.StopMove)
self.frmGRIP.bind("<B1-Motion>", self.OnMotion)
def StartMove(self, event):
self.startx = event.x
self.starty = event.y
def OnMotion(self, event):
mousex,mousey=self.root.winfo_pointerxy()
self.root.geometry("+%s+%s" % (mousex-self.startx, mousey-self.starty))
def StopMove(self, event):
self.posX = self.root.winfo_x()
self.posY = self.root.winfo_y()
def GUI(self):
self.frmTOP=Frame(master=self.root,height=15)
self.frmBOTTOM=Frame(master=self.root,width=300,height=300)
self.frmGRIP=Frame(self.frmTOP,bg=self.gripcolor,height=15)
self.frmRESIZE=Frame(self.frmBOTTOM,width=300,height=10)
self.frmTEXT=Frame(self.frmBOTTOM,bg=self.notecolor,width=300,height=300)
self.frmRESIZE_empty=Frame(self.frmRESIZE,bg=self.notecolor,height=10)
self.frmRESIZE_grip=Frame(self.frmRESIZE,bg=self.gripcolor,width=10,height=10)
self.frmTOP.pack(fill=X,expand=NO)
self.frmBOTTOM.pack(side=BOTTOM,fill=BOTH,expand=YES)
self.frmGRIP.pack(side=LEFT,fill=X,expand=YES)
self.frmRESIZE.pack(side=BOTTOM,fill=X)
self.frmTEXT.pack(side=BOTTOM,fill=BOTH,expand=YES)
self.frmRESIZE_empty.pack(side=LEFT,fill=X,expand=YES)
self.frmRESIZE_grip.pack(side=LEFT,expand=NO)
self.T=Text(self.frmTEXT,
height=6,width=30,
bd=0,wrap=WORD,pady=3,padx=5,
bg=self.notecolor,undo=1,
font=(self.font,self.fontsize)
)
self.T.insert(END,self.text)
self.T.pack(fill=BOTH,expand=YES)
class Noteset():
def __init__(self):
self.notes = []
def newNote(self):
note=Note(noteset=self)
self.notes.append(note)
print(self.notes)
return note
class Main():
def __init__(self):
self.N=Noteset()
app = QApplication(sys.argv)
trayIcon = QSystemTrayIcon(QIcon("J:\\python\\SimpleNotes.ico"), app)
menu = QMenu()
ActionNewNote = menu.addAction("new Note")
ActionNewNote.triggered.connect(self.newNote)
trayIcon.setContextMenu(menu)
trayIcon.show()
app.exec()
def newNote(self):
self.N.newNote()
Main()
You cannot successfully use Qt and Tkinter together. Also, if you remove Qt from the above you have the additional problem that you should not create more than once instance of Tk.
To answer your specific question, mainloop is normally the very last line of code that you execute. Since it is an infinite loop, any code after you call mainloop won't execute until the main window is destroyed.
The normal structure for a Tkinter application goes something like this:
import Tkinter as tk
class MyApp(...):
def __init__(self, root, ...):
...
...
root = tk.Tk()
myApp(root)
root.mainloop()
Its not a good idea to mix gui frameworks as each of the mainloops block each other, your better off to code the whole thing in one or the other.