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.
Related
I'm writing a program, where I want a thread to create a Label and add it to a window in tkinter. Below is an abstracted version of my code. The Label does not show in the window. The thing that has me confused though, is that if if I have something like self.window.geometry("900x900") in the render function, the window will be updated. Why can the shape of the window be updated but not a Label? I've also tried using the update() function in render but to no avail. Thanks in advance.
import tkinter as tk
import threading
class Game(threading.Thread):
def __init__(self):
super().__init__()
self.gui = GUI()
def run():
while True:
# some code
self.gui.render()
class GUI:
def __init__(self, game_board):
self.window = tk.Tk()
self.window.geometry("500x500")
def render():
img = tk.PhotoImage(file="image.png")
label = tk.Label(self.window, image=img)
label.pack()
if __name__ == '__main__':
g = Game()
g.start()
g.gui.window.mainloop()
My plotting library needs to be able to show multiple plots at the same time, each of which is represented as a PIL image, and each of which should show up as its own window. The windows should be independent, so closing any one of them should not affect the others, but when all of them have been closed the main loop should exit. This behavior was easy to achieve in qt and wx, but in qt it's proving difficult so far.
Here's the closest I've come so far:
from six.moves import tkinter
from PIL import ImageTk
class Window:
def __init__(self, img):
self.window = tkinter.Toplevel()
self.window.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self.window, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
windows = []
for img in imgs:
windows.append(Window(img))
if len(windows) > 0: windows[0].window.mainloop()
This displays an image in each window, and each of those windows can be closed independently. But it also displays an empty root window which needs to be closed for the main loop to exit, and which will cause all windows to close when closed, which is not the behavior I want.
If I replace tkinter.Toplevel() with tkinter.Tk(), then create_image fails for the second window with an obscure "pyimageX does not exist" error message, where X is an incrementing integer.
Will I have to make an invisible root window, and then manually count how many child windows have closed and trigger destruction of the invisible root window when all of them have closed in order to get the behavior I'm looking for? Or is there a simple way to achieve this?
Edit: Just to clarify: My program is not mainly a Tk app. It spends almost all its time doing other stuff, and only temporarily uses Tk in a single function to display some plots. That's why it's important that the main loop exits after the plots have been closed, to the program can resume its normal operation. Think about how show() in matplotlib works for an example of this scenario.
Here is an example of how you might want to do this. This example uses the root window to house a button that will open up all images at the top level.
Make sure you change self.path to your image folder.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open Images", command=self.open_images).pack()
self.path = ".\RGB"
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
print(filename)
top = tk.Toplevel(self)
img = tk.PhotoImage(file="{}\{}".format(self.path, filename))
lbl = tk.Label(top, image=img)
lbl.image = img
lbl.pack()
if __name__ == '__main__':
app = App()
app.mainloop()
Here is my 2nd example where you can hide the root window and when the last top level window is closed the tkinter instance is also destroyed. This is maned with a simple tracking variable.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.top_level_count = 0
self.path = ".\RGB"
self.open_images()
self.withdraw()
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
self.top_level_count += 1
image_top(self, self.path, filename)
def check_top_count(self):
print(self.top_level_count)
if self.top_level_count <= 0:
self.destroy()
class image_top(tk.Toplevel):
def __init__(self, controller, path, filename):
tk.Toplevel.__init__(self, controller)
self.controller = controller
self.protocol("WM_DELETE_WINDOW", self.handle_close)
img = tk.PhotoImage(file="{}\{}".format(path, filename))
lbl = tk.Label(self, image=img)
lbl.image = img
lbl.pack()
def handle_close(self):
self.controller.top_level_count -= 1
self.destroy()
self.controller.check_top_count()
if __name__ == '__main__':
app = App()
app.mainloop()
Ok so here's a couple of classes I came up with to solve this problem:
class ImgRoot(tkinter.Tk):
def __init__(self, imgs):
super(ImgRoot, self).__init__()
for i in imgs:
Window(self, i)
self.withdraw()
self.open=True
self.tick()
def tick(self):
if not self.open:
self.destroy()
self.open=False
self.after(100, self.tick)
def checkin(self):
self.open=True
class Window(tkinter.Toplevel):
def __init__(self, root, img):
super(Window, self).__init__()
self.root=root
self.tick()
self.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
def tick(self):
self.root.checkin()
self.after(100, self.tick)
The idea here is to create a main class (ImgRoot) which handles the whole thing. Then, every 0.1 seconds (100 miliseconds), it will check if any of the image windows have told it that they are still alive, and, if not, close. The image windows (Windows) do this by setting the ImgRoot's open attribute to True every 0.1 seconds that they are alive. Here is an example usage:
import tkinter
#above classes go here
ImgRoot(imgs) #imgs is a list as defined in your question
tkinter.mainloop()
print('done') #or whatever you want to do next
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 have made a small application with tkinter and Python 3 which has four buttons on the top of the window to form a menu. It works fine but I want to know how to make the buttons appear along the window over a period of time starting from a single button in the center when first started rather than being statically placed in the center.
Here is my script so far:
import tkinter as tk
class utilities(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.window()
def window(self):
self.pluginrun = tk.Button(self)
self.pluginrun["text"] = "Run Existing Plugin"
self.pluginrun["command"] = self.run_plugin
self.pluginrun.pack(side="left")
self.owning = tk.Button(self)
self.owning["text"] = "Add A New Plugin"
self.owning["command"] = self.plugin
self.owning.pack(side="left")
self.webpage = tk.Button(self)
self.webpage["text"] = "Webpage"
self.webpage["command"] = self.web
self.webpage.pack(side="left")
self.more_info = tk.Button(self)
self.more_info["text"] = "More"
self.more_info["command"] = self.more
self.more_info.pack(side="left")
def run_plugin(self):
print('Running Plugin')
def plugin(self):
print('Available Extensions')
def web(self):
print("Opening Webpage To Python.org")
def more(self):
print('Made Entirely In Python')
root = tk.Tk()
root.geometry('500x500')
show = utilities(master=root)
show.mainloop()
Which gives this result:
When first opened I would like it to look like this:
and over a period of time for more buttons to appear alongside one at a time until it looks like the first image.
How can this be done?
You can add all your buttons to a list and then use a repeating timed method to pack each button in the list one at a time at a set interval.
I created a counter that we can use to keep track of what button is going to be packed next from the list.
I also created a new list to store all the buttons in.
Then I modified your window() method to add each button to the list instead.
The last thing was to create a timed method that would use the self.counter attribute I created to keep track of what button is to be packed next.
In tkinter the best method to use to keep a timed loop or set a timer for anything is to use after(). Using sleep() or wait() in tkinter will only cause the entire tkinter app to freeze.
Take a look at the below code.
import tkinter as tk
class utilities(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.list_of_buttons = []
self.counter = 0
self.window()
def window(self):
for count in range(4):
self.list_of_buttons.append(tk.Button(self))
pluginrun = self.list_of_buttons[0]
pluginrun["text"] = "Run Existing Plugin"
pluginrun["command"] = self.run_plugin
owning = self.list_of_buttons[1]
owning["text"] = "Add A New Plugin"
owning["command"] = self.plugin
webpage = self.list_of_buttons[2]
webpage["text"] = "Webpage"
webpage["command"] = self.web
more_info = self.list_of_buttons[3]
more_info["text"] = "More"
more_info["command"] = self.more
self.timed_buttons()
def timed_buttons(self):
if self.counter != len(self.list_of_buttons):
self.list_of_buttons[self.counter].pack(side ="left")
self.counter +=1
root.after(1500, self.timed_buttons)
def run_plugin(self):
print('Running Plugin')
def plugin(self):
print('Available Extensions')
def web(self):
print("Opening Webpage To Python.org")
def more(self):
print('Made Entirely In Python')
root = tk.Tk()
root.geometry('500x500')
show = utilities(master=root)
show.mainloop()
Add the Buttons inside a Frame, which you centre, and then as you add more Buttons, the Frame should centre them. If not, you may need to call root.update(), to re-centre the Frame.
In my application, I have a call to an external module which spawns some threads, does some stuff, then returns a value. I'm trying to get a QMessageBox to show before and a QLabel to update after this is complete, but I'm stumped. The code goes something like this (called from QObject.connect on a button):
def _process(self):
self._message_box.show()
for i in range(3):
rv = external_module_function_with_threads() // blocking function call
label = getattr(self, "label%d" % (i + 1))
label.setText(rv)
When I click the button and the function is called, the message box only shows after the loop completes. The labels only update after the loop completes as well. I tried calling label.repaint() in the loop, but all that seems to do is make the message box show up earlier (but with no text in it).
I know I'm not violating the "GUI operations from outside the main thread" rule (...right?), so is there a way to force an update?
For your message box use self._message_box.exec_(). From my understanding of your question, I think this will do what you want.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QVBoxLayout(self)
button = QPushButton("Press me")
self.label = QLabel("Run #")
map(layout.addWidget, [button, self.label])
button.pressed.connect(self.buttonPressed)
self.messageBox = QMessageBox()
def buttonPressed(self):
self.messageBox.exec_()
Thread().run(self.label)
class Thread(QThread):
def run(self, label):
for x in range(5):
self.updateLabel(label)
app.processEvents()
time.sleep(.5)
def updateLabel(self, label):
try:
number = int(label.text().split(" ")[-1])
number += 1
except ValueError:
number = 0
label.setText("Run %i" % number)
app = QApplication([])
main = Main()
main.show()
sys.exit(app.exec_())