For fun, I'm creating a crappy helicopter/flappybird clone using tkinter and I've run into some really bizarre behavior with regards images apparently not showing up.
(btw, using python3)
So I started with the following code just to see if I could start getting things to draw:
from tkinter import *
from PIL import ImageTk
class Bird(object):
def __init__(self, canvas, x=0, y=0):
self.canvas = canvas
photo = ImageTk.PhotoImage(file="flappy.gif")
self.bird = self.canvas.create_image(x,y,image=photo)
class Environment(Canvas):
def __init__(self, master, width=500, height=500):
super(Environment, self).__init__(master, width=width, height=height)
self.pack()
self.master = master
self.bird = Bird(self)
if __name__=="__main__":
r = Tk()
env = Environment(r)
env.pack()
r.mainloop()
Image didn't appear, all I had was a blank canvas. I thought this was odd, so I started playing around to see why that might be the case. My next step was to test that I knew how to create images, so I just made my file a basic image create:
if __name__=="__main__":
r,c=get_canv()
c.pack()
img = ImageTk.PhotoImage(file="flappy.gif")
c.create_image(100,100,image=img)
r.mainloop()
And this, predictably, works fine. So, my syntax in the prior code seemed to be correct. This is when I stumbled on something a little confusing:
if __name__=="__main__":
r,c=get_canv()
c.pack()
c.create_image(100,100,image=ImageTk.PhotoImage(file="flappy.gif"))
r.mainloop()
This didn't draw. I'm left with a blank canvas again. This is what made me suspect that maybe there was some weird threading issue going on behind the scenes. Does Anyone know why the second snippet worked and the third snippet failed?
I've seen this a number of times already. The problem is that the PhotoImage is garbage collected even though it is used in the Label! To fix the problem, just bind it to a member variable of the GUI itself:
self.photo = ImageTk.PhotoImage(file="flappy.gif")
self.bird = self.canvas.create_image(x,y,image=self.photo)
The reason it works in your second example is that the img variable exists until after the mainloop method has finished, while in your third example, it exists only during the creation of the Label.
Related
I'm working on a program using tkinter for python. It is an MIDI file editor. I inserted some gif files into it, but I need the code for them to turn transparent when touching the mousepointer.
I've already asked a similar question, and I got a very helpful result, but it isn't exactly working as expected.
def on_mouse_enter(event):
print("enter...", event.widget)
def on_mouse_leave(event):
print("leave...", event.widget)
root = tk.Tk()
for i in range(10):
label = tk.Label(root, text="Item #{}".format(i), name='label-{}'.format(i))
label.pack()
label.bind("<Enter>", on_mouse_enter)
label.bind("<Leave>", on_mouse_leave)
tk.mainloop()
I tried replacing tk.label with tk.PhotoImage but it resulted into an attribute error.
I am creating a revision program for myself however whenever the fun1 function is called it prints out underneath the previously executed function. e.g the label will print out underneath the last one instead of replacing it, any ideas? Any help would be appreciated!!
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
#Sets list of facts
def t():
print("hi")
facts = ['fact one','true', 'fact two','abc']
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl = Label(gui,text=facts[r]).pack()
btnt = Button(text="True", command=t).pack()
btnf = Button(text="False", command=t).pack()
gui.after(5000, fun1)
gui.after(5000, fun1)
mainloop()
Overview
The best way to write this sort of program is to create the label or button only once, and then use the configure method to change the text of the the label.
Using a procedural style
Here's an example based off of your original code:
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
def t():
print("hi")
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl.configure(text=facts[r])
gui.after(5000, fun1)
#Sets list of facts
facts = ['fact one','true', 'fact two','abc']
# create the widgets
lbl = Label(gui,text="")
btnt = Button(text="True", command=t)
btnf = Button(text="False", command=t)
# lay out the widgets
lbl.pack(side="top", fill="x", expand=True)
btnt.pack(side="left", fill="x")
btnf.pack(side="right", fill="y")
# show the first fact; it will cause other
# facts to show up every N seconds
fun1()
mainloop()
Using an object-oriented style
Since you're just getting started, let me suggest a better way to organize your code. Python is object-oriented in nature, so it makes sense to make your application an object. Even though you may not be familiar with classes an objects, if you start with this pattern and follow a couple of simple rules, you can get all the benefits of object orientation without much effort.
The only thing you need to remember is that in your main class, all functions need to have self as the first parameter, and when calling a function you use self.function and omit self as an argument (python does that for you). Other than that, you can pretty much code as normal inside a class.
Here's an example:
import tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.facts = ['fact one','true', 'fact two','abc', 'a long fact\non two lines']
self.label = tk.Label(self,text="", width = 40, height=4)
self.true_button = tk.Button(self, text="True", command=self.t)
self.false_button = tk.Button(self, text="False", command=self.t)
self.label.pack(side="top", fill="both", expand=True, pady=40)
self.true_button.pack(side="left", fill="x")
self.false_button.pack(side="right", fill="x")
self.fun1()
def t(self):
print("hi")
def fun1(self):
r = random.randrange(len(self.facts))
self.label.configure(text=self.facts[r])
self.after(5000, self.fun1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Here are a few things to notice:
import tkinter as tk this requires a tiny bit more typing, but it makes your program more robust. The side effect of this (which is a good side effect IMO) is that all tkinter commands now need to be prefixed with tk (eg: tk.Button). In my opinion this is a much better way than just blindly importing everything from tkinter as a bunch of global variables and functions. I know must tutorials show from tkinter import *, but they are misguided.
The main part of your logic is a class. This makes it easy to avoid using global variables. As a rule of thumb when programming, you should avoid global variables.
Notice how self is an argument to t, fun1 and __init__ -- this is a requirement for python classes. It's just how you do it. Also notice we call them like self.fun1 rather than just fun1. This lets python know that you want to call the function associated with the current object. Once your program gets bigger, this makes it easier to know where fun1 is defined.
I removed the code that forces the size of the GUI. Tkinter is really good at calculating what the size should be, so let it do it's job. Also, if you take care when laying out your widgets, they will grow and shrink properly when the window is resized. This means you don't need to force a min or max size to a window. Forcing a size gives a bad user experience -- users should always be able to resize windows to fit their needs.
I separated the creation of the widgets from the layout of the widgets. For one, you have to separate them if you want to keep references to widgets. This is because this: lbl=label(...).pack() sets lbl to None. That's just how python works -- the last function is what gets saved to a variable, and both pack and grid always return none. The second reason is simply that it makes your code easier to write and maintain. All of the code that organizes your widgets is in one place, making it easier to see the big picture.
I am building an Interface with TKinter and I have the problem, that whenever create a new window the standardized tkinter window of 200x200 pixel with nothing in it flashes up for a fraction of a second and AFTER that all my modifications (widgets ect.) are made. This happens before AND after calling the mainloop.
The Main-Interface is created.
Mainloop stats
Flashing window
Main-Interface appears
Also after Mainloop was called this happens with newly created windows.
Main-Interface appears--> Push a button, that creates a new window
Flashing window
new window appears
Sadly I cannot give you a sample code... If I try to do a minimal example, this doesn't happen. Maybe the standard window is created, but it is changed so fast, that it doesn't appear on screen. I don't even know what to look up in this case... searching "tkinter flashing window" yields nothing.
EDIT: I found the source of the problem. It seems to be caused by wm_iconbitmap, FigureCanvasTkAgg and tkinter.Toplevel. If you remove the the icon out of the code, it works fine, no flashing. But if I use it together with one of the other, the window flashes when created. Try it out with the code below. You have to put the icon in the working directory of course.
Here is a code sample and the link to the icon I am using, but I suppose any icon will do.
# coding=utf-8
import numpy as np
import matplotlib as mpl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import os
class INTERFACE(object):
def __init__(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.EXIT)
self.root.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
self.root.geometry("1024x768")
canvas = FigureCanvasTkAgg(self.testfigure(), master=self.root) #<---- !!!!!!
canvas.get_tk_widget().grid(sticky=tk.N+tk.W+tk.E+tk.S)
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def testfigure(self):
x=np.linspace(0, 2*np.pi,100)
y=np.sin(x)
fig = mpl.figure.Figure()
sub = fig.add_subplot(111)
sub.plot(x,y)
return fig
def EXIT(self):
Top = tk.Toplevel(master=self.root)
Top.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
Top.transient(self.root)
Top.resizable(width=False, height=False)
Top.title("Exit")
tk.Message(Top,text="Do you really want to quit?", justify=tk.CENTER, width=300).grid(row=0,columnspan=3)
tk.Button(Top,text="YES",command=self.root.destroy).grid(row=1,column=0)
tk.Button(Top,text="No",command=self.root.destroy).grid(row=1,column=1)
tk.Button(Top,text="Maybe",command=self.root.destroy).grid(row=1,column=2)
def start(self):
self.root.mainloop()
if __name__ == '__main__':
INTERFACE().start()
I know this is an old question, but I've experienced a similar situation and have found a solution.
In my case, I've isolated the issue to the use of iconbitmap. I've managed to solve it by calling iconbitmap with the after method just before calling root.mainloop().
Example:
from tkinter import *
root = Tk()
w = Label(root, text="Hello, world!")
w.pack()
root.geometry('300x300+500+500')
root.after(50, root.iconbitmap('icon.ico'))
root.mainloop()
This method has worked on Toplevel() windows with icons as well.
Tested on Win 8.1 with Python 3.5.0.
Edit: Upon further inspection I've noticed that the behavior changes relative to root.geometry's presence as well. My initial example didn't have it and I only noticed after a few tries that it still had the same issue. The time delay in the after method doesn't seem to change anything.
Moving root.geometry below the after method yields the same issue for some reason.
Most likely, somewhere in your initialization code you're calling update or update_idletasks, which causes the current state of the GUI to be drawn on the screen.
Another possible source of the problem is if you're creating multiple instances of Tk rather than Toplevel.
Without seeing your code, though, all we can do is guess.
Your best route to solving this problem is to create a small example that has the same behavior. Not because we need it to help you, but because the effort you put into recreating the bug will likely teach you what is causing the bug.
This should work but it requires win32gui
import win32gui
def FlashMyWindow(title):
ID = win32gui.FindWindow(None, title)
win32gui.FlashWindow(ID,True)
I'm in the middle of rewriting the code for my first tkinter application, in which I'd avoided using classes. That was a dead end and I have to finally learn class programming in python. I've encountered a very weird error and I have no idea how to fix it. I've tried, but to no effect. What I'm trying to do is specify a font for two labels in my app. It worked well in my previous, class-free code but now it gives me an error:
(...) line 56, in create_widgets
TimeFont = font.Font(family='Palatino', size=88, weight='bold')
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/tkinter/font.py", line 71, in __init__
root = tkinter._default_root
AttributeError: 'module' object has no attribute '_default_root'
Here's the function I'm using for creating widgets:
def create_widgets(self):
self.set_timer = ttk.Entry(self, textvariable=self.timer)
self.start = ttk.Button(self, text='Start', command=self.start)
TimeFont = font.Font(family='Palatino', size=88, weight='bold') #the infamous line 56
self.display1 = ttk.Label(self, textvariable=self.player1, font=TimeFont)
self.display2 = ttk.Label(self, textvariable=self.player2, font=TimeFont)
And some more code "from above" in case its relevant:
from decimal import *
from tkinter import *
from tkinter import ttk
from tkinter import font
import time, _thread, subprocess
class Chclock(ttk.Frame):
#classmethod
def main(cls):
NoDefaultRoot()
root = Tk()
app = cls(root)
app.grid(sticky=NSEW)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.resizable(True, False)
root.mainloop()
def __init__(self, root):
super().__init__(root)
root.bind('q', self.player1move)
root.bind('p', self.player2move)
root.bind('b', self.pause)
root.bind('a', self.undo)
root.bind('l', self.undo)
self.create_variables()
self.create_widgets() #here I call the function containing the error
self.grid_widgets()
self.grid_columnconfigure(0, weight=1)
It's probably something silly but I just can't understand what's causing this problem. It used to work fine...
Thanks!
Perhaps the code "NoDefaultRoot()" and the error message "object has no attribute '_default_root'" might have something to do with each other? Notice a correlation? First rule of debugging is to assume the error message is telling you something useful.
The problem is that you are creating a font object without telling that object what window it belongs to. Since you aren't telling it, it chooses to use the default root window. However, you've explicitly requested no default root window.
This is a somewhat strange way to structure your Tkinter program. I recommend reading the answers in the question Python Tkinter Program Structure
Well I've managed to find it. Since some kind person gave this question an upvote I'll post the solution: I've deleted the NoDefaultRoot() line. I'm not sure why it didn't work, and why it works now, but it does... Can someone explain what's happened in the comments? I'm really new to this stuff and the line I'd deleted came with a template.
Sorry if I made a mess.
I'm trying to make a script which will enable me to dynamically update an image object and then post the updated image to a Tkinter Canvas widget. The code here is prototype code, just to get the basics down. The aim here is to put a blue pixel on the image being displayed by the canvas, at the click location.
Something very strange is going on here. I'm using the Wing IDE, and if I run this code through the debugger, with a breakpoint at any line in the woohoo function, and then continue execution after hitting the breakpoint, the code works exactly as expected- putting a blue pixel on the image. If I run the code normally, or through the debugger with no breakpoints, the image is never updated. This leads me to the conclusion that there is some internal wizardry going on which I haven't got much hope of understanding without aid.
I'd really like to know the best way to go about this (or any way, I guess), and if someone could explain to me what's going on under the hood that'd be really cool. Thanks.
from Tkinter import *
from PIL import Image, ImageTk
def woohoo(event):
original.putpixel((event.x,event.y),(0,0,255))
newpic = ImageTk.PhotoImage(original)
c.create_image((0,0),image=newpic, anchor="nw")
main = Tk()
c = Canvas(main, width=300, height=300)
main.geometry("300x300+0+0")
c.pack()
original = Image.open("asc.bmp")
picture = ImageTk.PhotoImage(original)
c.create_image((0,0),image=picture, anchor="nw")
c.bind("<Button-1>", woohoo)
main.mainloop()
My guess is, you're creating a new image in a function. The reference to the image is a local variable. When the function exits, the reference is garbage collected which causes the new image to be destroyed. Most likely, running interactively causes the garbage collector to run differently (perhaps more lazily?)
Changed a little of the other post to work with Python 3+ :
from tkinter import *
def stuff(event):
global picture3
picture3 = PhotoImage(file='picture2.png')
c.itemconfigure(picture2, image = picture3)
main = Tk()
c = Canvas(main, width=300, height=300)
c.pack()
picture = PhotoImage(file='picture1.png')
picture2 = c.create_image(150,150,image=picture)
c.bind("<Button-1>", stuff)
main.mainloop()
try it like this:
from Tkinter import *
from PIL import Image, ImageTk
def woohoo(event):
global picture #
original.putpixel((event.x,event.y),(0,0,255))
picture = ImageTk.PhotoImage(original)#
c.itemconfigure(myimg, image=picture)#
main = Tk()
c = Canvas(main, width=300, height=300)
main.geometry("300x300+0+0")
c.pack()
original = Image.open("asc.bmp")
picture = ImageTk.PhotoImage(original)
myimg = c.create_image((0,0),image=picture, anchor="nw")#
c.bind("<Button-1>", woohoo)
main.mainloop()