unable to eliminate a global variable - python

I have a fairly lengthy GUI image viewing program, the "globals" started to get out of hand and I have eliminated them all but one. The simplified code below shows the global variable art. with it in place the image is displayed, without it - a grey screen. I would appreciate any help in understanding what is going on
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
def image():
global art
path="c:/Google Drive/Art Images/0030#Van Tromp, going about to please his Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.grid()
label.configure(image=art)
label=Label(root,bg="grey")
image()
root.mainloop()

Your issue is that when creating a Photoimage, you need to keep a reference to that image, meaning you need to assign that image a variable name that can be accessed elsewhere. In your case, the variable art, without using a global, stays in the function. This means that when the function finished, art is destroyed. An easy fix is to return the variable art, making the output of the function art, and thus bringing the variable into the main code. This is what effbot has to say about that:
When you add a PhotoImage or other Image object to a Tkinter widget, you must keep your own reference to the image object. If you don’t, the image won’t always show up.
The problem is that the Tkinter/Tk interface doesn’t handle references to Image
objects properly; the Tk widget will hold a reference to the internal object, but Tkinter does not. When Python’s garbage collector discards the Tkinter object, Tkinter tells Tk to release the image. But since the image is in use by a widget, Tk doesn’t destroy it. Not completely. It just blanks the image, making it completely transparent
To fix this, you could use a global variable (which can be accessed elsewhere in the main code, thus becoming a 'reference'), or you could do something like this:
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
def image():
label.grid()
path="c:/Google Drive/Art Images/0030#Van Tromp, going about to please his Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.configure(image=art)
return art
label=Label(root,bg="grey")
image()
root.mainloop()
In that code, your image is returned by the function and assigned a variable in the main code, making is a 'reference' without globals.
You can find more info here

Your function, image(), is being used to output your image, and to do that, you're assigning it to the variable 'art'. An image object is created, but when your function ends, since you aren't returning anything, that object ceases to exist:
y = 5
def getx():
x = 3
getx()
a = y + x
This code won't work, because x is local to the function getx(). Since I'm not doing anything with it, when the function ends, so does the variable. What I can do, though, is pass data out of the function, like this:
y = 5
def getx():
x = 3
return x
x = getx()
a = y + x
In your code, tkinter needs for the image object to exist when it runs, but while you created it within your function, you let the function end without doing anything with it. Naming art a global variable effectively allows your function to return your object to a variable outside of the function. Another way to do it, though would be:
root = Tk()
def image():
path="c:/Google Drive/Art Images/0030#Van Tromp, going about Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.grid()
label.configure(image=art)
return art
label=Label(root,bg="grey")
art = image()
root.mainloop()

Related

There is a way to wait on a user's answer in tkinter?

I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.

_tkinter.TclError: image "pyimage1" doesn't exist

Why is my Tkinter image not working? It is in the same directory, all commands are right but I get the error:
_tkinter.TclError: image "pyimage1" doesn't exist.
What's wrong?
fertig=tkinter.Tk()
fertig.title("Window")
text=tkinter.Label(fertig,text="Success")
text.pack()
w = tkinter.PhotoImage(file="/Users/Hannes/Desktop/Spambot/successful.gif ")
w = tkinter.label(fertig,image=w)
w.pack()
knapp=tkinter.Button(fertig,text="Ok",command=lambda:close())
knapp.pack()
knapp.mainloop()
The following works for me on my Windows system. I had to fix and add a few (unrelated) things to get the code in your question to work, but after doing so I found the real reason the image doesn't display.
So, the error that relates most directly to the that problem is because you're overwriting the variable w: After you assign a tkinter.PhotoImage() value to it, you immediately assign a another value (the tkinter.Label) to using its current value (image=w). The second assignment causes the tkinter.PhotoImage() object that was in it to be lost. Since there are no more references to to, it will be garbage-collected at some point.
To fix that, I simply assign the PhotoImage object to a separate variable img.
Note, too, that (apparently) having the trailing space character in the filename isn't a problem (at least not on Windows).
Here's some "official" documentation specifically about the PhotoImage class that discusses the need for keeping a reference around to the original—see the NOTE: at the end—when using it with other tkinter widgets (like a Label).
import tkinter
def close(): # just a placeholder implementation.
print('close() called')
fertig=tkinter.Tk()
fertig.title("Window")
text=tkinter.Label(fertig, text="Success")
text.pack()
#w = tkinter.PhotoImage(file="/Users/Hannes/Desktop/Spambot/successful.gif ")
img = tkinter.PhotoImage(file=r"C:\vols\Files\PythonLib\Stack Overflow\successful.gif ")
w = tkinter.Label(fertig, image=img)
w.pack()
knapp=tkinter.Button(fertig, text="Ok", command=lambda: close())
knapp.pack()
knapp.mainloop()
Here's what it looks like (using an image of my own).

Modifying a variable in a function has no result

I have a Tkinter application, so I have the mainloop which is typical of Tkinter, and various functions which handle mouse clicks and all that garbage.
In one of the functions, I generate a string and I want to store that string SOMEWHERE in the program, so that I can use it later on if some other function is called or maybe if I want to print it from the main loop.
import this and that, from here and there etc etc
#blah blah global declarations
fruit = ''
def somefunction(event):
blahblahblah;
fruit = 'apples'
return fruit
mainwin = Tk()
#blah blah blah tkinter junk
#my code is here
#its super long so I don't want to upload it all
#all of this works, no errors or problems
#however
button = Button( blahblahblha)
button.bind("<button-1", somefunction)
print fruit
#yields nothing
mainwin.mainloop()
This is an abridged example. Everything else in the program works fine, I can track my variable throughout the program, but when it's time for it to be saved for later use, it gets erased.
For example, I can print the variable as I pass it along from one function to another as an argument, and it will be fine. It is always preserved, and prints. The instant I try to get it back into the loop or store it for later use, it gets lost or overwritten (I'm not quite sure which).
I am really unfamiliar with Python, so I bet it's something simple that I've missed. I am assuming this is supposed to work like every other language, where you save a string to a global variable, and it will stay there UNTIL you or something resets it or overwrites it.
My current workaround is to create a text file and save the string in it until I need it.
I am using Python 2.7.11 on Windows 7, but have had the same issue on Ubuntu.
When you do fruit = 'anything' inside the function, it assigns it as a local variable. When the function ends, that local variable disappears. If you want to reassign to a global variable, you need to indicate that you'd like to do so with the global keyword.
def somefunction(event):
global fruit # add this
blahblahblah
fruit = 'apples'
return fruit
Note that functions can access global variables without this line, but if you want an assignment to the same name to apply to the global one you have to include it.
Also, "<button-1" should be "<button-1>".
Also, instead of binding to a Button, you should just add a command to it:
button = Button(mainwin, text='blahblah', command=somefunction)
And Button widgets, when clicked, don't send an event object to the function they're bound to, so define somefunction as def somefunction():.
Also, the print fruit is executed exactly once. If you want to change fruit and then see the new value, you'll have to print it as some point after you've done the reassignment. Using return to send a value to a Button doesn't do anything, as the widget can't do anything with it. This is why Tkinter apps are commonly created as object-oriented (OO) programs, so you can easily save instance variables without having to use global.
Learn classes and your problems disappear. Almost any of these cover classes https://wiki.python.org/moin/BeginnersGuide/Programmers Also a Tkinter reference so you can fix your typos http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class StoreVariable():
def __init__(self, root):
self.fruit = ''
button = tk.Button(root, bg="lightblue")
button.grid()
button.bind("<Button-1>", self.somefunction)
tk.Button(root, text="Exit", bg="orange", command=root.quit).grid(row=1)
def print_fruit(self):
print self.fruit
def somefunction(self, event):
self.fruit = 'apples'
print "fruit changed"
mainwin = tk.Tk()
SV=StoreVariable(mainwin)
mainwin.mainloop()
## assume that once Tkinter exits there is something stored
## note that you have exited Tkinter but the class instance still exists
print SV.fruit
## call the class's built in function
SV.print_fruit()
Basing on your abridged function, here are some things that might caused your problems:
You might not saved fruit to a variable inside the main loop/program. Values saved inside a function will be erased once that function finishes. Unless you saved it inside a class variable using self.variable_name (applicable if you are using classes). If you don't like classes, just save it within a variable inside the main loop/function like:
fruit = somefunction()
other stuff
print fruit #the time where you access fruit again
where this statement is inside the main loop/program where you would accesss it again with print.
You might be changing the value of fruit with other statements/functions. Not definite since you haven't posted your whole code.

Using instance attributes to create images in tkinter

I'm working on a simple tkinter program, and am trying to create a callback function that will take an image (imported using PIL and stored as a class attribute) and draw it on the canvas when the corresponding button is clicked. Each click should create a new Bacteria object, reflected by the creation of a new image on the canvas (in the actual program, the new objects are also appended to an array and used later in the program's execution--the code here is something of a simplification).
The following runs without errors (except for a file-not-found error stemming from the fake file name used for posting) but, unfortunately, no images draw on the canvas when the button is clicked. The code works as intended when the image is imported and stored as an attribute of the MainWindow class--it only seems to fail when imported/stored as an attribute of the Bacteria class.
import tkinter as tk
import random
from PIL import Image
from PIL import ImageTk
class MainWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, background = "white")
self.pack()
self.canvas_height = 500
self.canvas_width = 1000
self.canvas = tk.Canvas(self, height = self.canvas_height, width = self.canvas_width)
self.canvas.grid(row = 0, column = 0)
self.launch_button = tk.Button(self, text = "Haz clic!", width = 25, command = self.callback)
self.launch_button.grid(row = 1, column = 0, sticky = "W")
def callback(self):
test_bact = Bacteria()
x_pos = random.randint(0,1000)
y_pos = random.randint(0,500)
self.canvas.create_image(x_pos, y_pos, image = test_bact.imageTk)
class Bacteria:
def __init__(self):
self.image = Image.open('testBacterium.png')
self.imageTk = ImageTk.PhotoImage(image=self.image)
root = tk.Tk()
app = MainWindow(root)
app.mainloop()
I'm sort of stumped on this. Can anyone offer any insight on what's going wrong?
From documentation of canvas.create_image() -
create_image(position, **options) [#] Draws an image on the canvas.
image= The image object. This should be a PhotoImage or BitmapImage,
or a compatible object (such as the PIL PhotoImage). The application
must keep a reference to the image object.
(Emphasis mine)
So most probably, either canvas object does not keep a reference to the image itself, or it keeps a weak reference . In either case, in a nutshell what is happening is that after to call canvas.create_image() and the callback() method ends, you are no longer holding any reference to the image object (used in the create_image() method) , and hence its not showing up.
In your case, when you are keeping the reference to the image in Bacteria class , what is happening is -
You are creating the Bacteria object and loading the image and storing there. You are just creating the Bacteria object - test_bact - as a local variable for callback() method.
Then you are using test_bact.imageTk for the image argument to canvas.create_image() method.
Now, the callback() ends, and hence there is no longer any reference to test_bact and so it gets garbage collected. Also , since the only reference to imageTk object was in the Bacteria object, that also gets garbage collected, and hence there are no more references to the image object in your application.
From what you want to achieve, it seems like you should store the bacteria object as an instance variable of MainWindows class.

Stopping label replacement

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.

Categories

Resources