Creating a Label from a thread in Tkinter - python

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()

Related

Tkinter Get Text entry in Notebook page

I have created a notebook and added a frame to it:
nb = ttk.Notebook(root, style="TNotebook")
page1 = ttk.Frame(nb, style='Frame1.TFrame')
layout1(page1)
nb.add(page1, text='Welcome')
So i have a function layout1, the first page of the notebook,
i added to it a Text:
def layout1(page):
entry = Text(page, width=20)
entry.place(relx=0.03, rely=0.1, height=400)
Button(page, text='EXECUTE', command=import_entry).place(relx=0.5, rely=0.6)
And next i have my import_entry function:
def import_entry():
result = entry.get()
print(result)
I can't get the entry because of accessibilty of variables in function. So, how can i get it?
Here is an example of how you should structure your app with a class:
import tkinter
import tkinter.ttk as ttk
class App(tkinter.Tk):
def __init__(self):
super().__init__()
# assign on_closing method to window close event
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.title("Example App")
self.geometry("600x500")
self.button_1 = tkinter.Button(master=self, text="Test", command=self.button_event)
self.button_1.pack(pady=10)
# create more widgets ...
def button_event(self, event):
print("button pressed")
def on_closing(self):
# code that needs to happen when gets closed
self.destroy() # controlled closing of window with .destroy()
if __name__ == "__main__":
app = App()
app.mainloop()

simple window with an enter field

i'm a new programmer and there are certainly several errors but this shouldn't be difficult to spot. I need to create a simple window with a field named "Concorrente 1:" and an entry field displayed by function named lacopertina(). I don't understand where is the error:
import tkinter as tk
from tkinter import *
from tkinter.ttk import *
from tkinter import ttk
class schermoiniziale(tk.Frame):
def lacopertina():
print(gio1)
#return (tot1)
def __init__(self):
global gio1
#tot1=0
#schermo1=Tk()
self.gio1=tk.StringVar()
lab1=ttk.Label(self, text="Concorrente 1:")
lab1.pack()
ent1=ttk.Entry(self, textvariable=self.gio1)
ent1.pack()
pulsante = ttk.Button(self, text="Inizio", textvariable=self.gio1, command=self.lacopertina)
pulsante.pack()
def main():
schermoiniziale().mainloop()
if __name__== "__main__":
main()
I would suggest you to go through some tutorials on Python OOP.
I have modified your code as below with some comment:
# avoid using wildcard import
import tkinter as tk
from tkinter import ttk
class schermoiniziale(tk.Frame):
def __init__(self, master, **kw):
# need to call __init__() of inherited class
super().__init__(master, **kw)
self.gio1 = tk.StringVar()
lab1 = ttk.Label(self, text="Concorrente 1:")
lab1.pack()
ent1 = ttk.Entry(self, textvariable=self.gio1)
ent1.pack()
# removed textvariable=self.gio1 as I think you actually don't need it
pulsante = ttk.Button(self, text="Inizio", command=self.lacopertina)
pulsante.pack()
def lacopertina(self):
# use .get() to get the content of a StringVar
print(self.gio1.get())
def main():
# need to create the root window before creating other widget
root = tk.Tk()
# pass root window as the parent of the widget
frame = schermoiniziale(root)
frame.pack()
# start the tkinter mainloop
root.mainloop()
if __name__== "__main__":
main()

Displaying multiple independent windows with images in tkinter, and having the main loop exit when they have all been closed

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

Where should i place mainloop?

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.

why do I get a blank tkinter window?

so when i run this code and click the button:
from Tkinter import *
import thread
class App:
def __init__(self, master):
print master
def creatnew():
admin=Tk()
lab=Label(admin,text='Workes')
lab.pack()
admin.minsize(width=250, height=250)
admin.maxsize(width=250, height=250)
admin.configure(bg='light green')
admin.mainloop()
def other():
la=Label(master,text='other')
la.pack()
bu=Button(master,text='clicks',command=lambda: thread.start_new_thread(creatnew,()))
bu.pack()
other()
Admin = Tk()
Admin.minsize(width=650, height=500)
Admin.maxsize(width=650, height=500)
app = App(Admin)
Admin.mainloop()
i get a second tkinter window but its a white blank screen that makes both programs not respond.
any ideas
Don't use threads. It's confusing the Tkinter mainloop. For a second window create a Toplevel window.
Your code with minimal modifications:
from Tkinter import *
# import thread # not needed
class App:
def __init__(self, master):
print master
def creatnew(): # recommend making this an instance method
admin=Toplevel() # changed Tk to Toplevel
lab=Label(admin,text='Workes')
lab.pack()
admin.minsize(width=250, height=250)
admin.maxsize(width=250, height=250)
admin.configure(bg='light green')
# admin.mainloop() # only call mainloop once for the entire app!
def other(): # you don't need define this as a function
la=Label(master,text='other')
la.pack()
bu=Button(master,text='clicks',command=creatnew) # removed lambda+thread
bu.pack()
other() # won't need this if code is not placed in function
Admin = Tk()
Admin.minsize(width=650, height=500)
Admin.maxsize(width=650, height=500)
app = App(Admin)
Admin.mainloop()

Categories

Resources