Issue with adding images to button in tkinter application - python

I am helping with a small project where we want to add and take items away from a store. The code is below:
from tkinter import *
import tkinter
####################
# Variables
eggs = 0
milk = 0
butter = 0
lemon = 0
guiSize = "800x1280"
def newItemGUI():
main.withdraw()
def addEgg():
global eggs
eggs += 1
updateLabels()
def menu():
global eggs
update(eggs)
itemWindow.destroy()
main.deiconify()
def updateLabels():
eggLabel = Label(itemWindow, text = eggs)
eggLabel.grid(row = 3,column = 0)
itemWindow = Toplevel()
itemWindow.geometry(guiSize)
itemWindow.title("Add a new item")
itemWindow.configure(background = "#a1dbcd")
heading = Label(itemWindow, text="Add new items", font=("Arial",20),background = "#a1dbcd")
heading.grid(row=0, columnspan = 3)
egg = PhotoImage(file ="images/egg.gif")
eggButton = Button(itemWindow, image = egg, command = addEgg)
eggButton.grid(row = 2, column = 0,padx = 10, pady = 10)
eggLabel = Label(itemWindow, text = eggs).grid(row = 3,column = 0)
back = Button(itemWindow, text = "Main Menu", command = menu, width = 15)
back.grid(row = 4, column = 0, padx = 20, pady = 20)
def update(eggs):
items.delete("1.0",END)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
main=tkinter.Tk()
main.geometry(guiSize)
bgColour = "#DDA0DD"
main.configure(background = bgColour)
button1 = Button(main, text="Input new products", width = 20, height = 5, command = newItemGUI)
button1.grid(column = 1, row =2, padx = 20, pady = 20)
label2 = Label(main,text = "Items in the fridge :", font =("Arial,20"), background = bgColour)
label2.grid(row=4, columnspan = 2)
items = Text(main, width = 60, height = 10)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
items.grid(row=5, columnspan = 4)
main.mainloop()
When you click on the input new products button, this takes you to a new screen. On the screen should be a photo of an egg with a count underneath. For some reason the image of the egg is not showing and the button will not click.
If I change the eggButton from an image to:
eggButton = Button(itemWindow, text = "egg", command = addEgg)
this seems to allow me to click and the variable and it increases. Any idea as to what/where we have gone wrong? I know the path is correct as I can get the button to display a picture of an egg in a Label.

The problem is because the PhotoImage is being stored in a variable named egg which is local to the newItemGUI() function, so it (and associated image object) are being deleted when the function returns. This is a fairly common problem, so your question is likely a duplicate of another (and may get closed).
This PhotoImage documentation mentions avoiding this potential issue the way shown below in the Note near the end.
Regardless, to prevent that from happening, you can store the value somewhere else such as in an attribute of the Toplevel window. I would also recommend changing its name to something more descriptive like egg_image.
Here are changes to your code showing what how it could be done:
itemWindow.egg_image = PhotoImage(file="images/egg.gif")
eggButton = Button(itemWindow, image=itemWindow.egg_image, command = addEgg)

Related

how to switch from a frame to another frame

I'd like to switch from the rating_frame to the summary_frame. How would I do this? Would I destroy the rating_frame? I want to go onto the rating_frame by clicking the 'Show' button.
I have a search frame that is staying there. I only want the rating frame to change.
I have not yet made a start on the summary_frame as I don't know how to change from the rating_frame or where I would write it. Could you give me a good foundation?
Here is my Wireframe:
Here is my code:
from tkinter import *
class Movie:
def __init__(self, movie):
self.movie = movie
self.ratings = "No Rating"
class MovieRaterGUI:
def __init__(self, parent):
self.counter = 0
self.index = 0
#variable set up
self.v = StringVar()
self.v.set("No Rating")
#frames used so you can easily switch between rating frame and summary frame - keeping the search frame
rating_frame = Frame(root)
search_frame = Frame(root)
summary_frame = Frame(root)
rating_frame.pack(side="top", expand=True)
search_frame.pack(side="bottom", expand=True)
summary_frame.pack(side="top", expand = True)
#rating frame
#list of ratings for movies
self.movies = [
Movie("The Hobbit"),
Movie("Coraline"),
Movie("Love, Rosie")]
#used to display the ratings
self.ratings = ["No Rating", "Forget it", "2", "3", "4", "Must See"]
#labels
self.movie_label = Label(rating_frame, text = "Please Rate:", borderwidth = 10)
self.current_movie = Label(rating_frame, text = self.movies[self.counter].movie, borderwidth = 10)
self.rating_label = Label(rating_frame, text = "Your rating:", borderwidth = 10)
self.movie_label.grid(row = 0, column = 0, sticky = W)
self.current_movie.grid(row = 0, column = 1, sticky = W)
self.rating_label.grid(row = 1, column = 0, sticky = W)
#making radio buttons
self.radiobutton = []
self.num_choices = self.ratings
for i in range(len(self.ratings)):
self.option = Radiobutton(rating_frame, variable = self.v, value = self.ratings[i], text = self.ratings[i], borderwidth = 10, command = self.update_rating)
self.radiobutton.append(self.option)
self.option.grid(row = i+1, column = 1, sticky = W)
next_btn = Button(rating_frame, text = "Next", borderwidth = 10, command = self.next_movie)
previous_btn = Button(rating_frame, text = "Previous", borderwidth = 10, command = self.previous_movie)
next_btn.grid(row = 7, column = 1, sticky = W)
previous_btn.grid(row = 7, column = 0, sticky = W)
#search frame
self.search_label = Label(search_frame, text = "Search for movies with a rating of:", borderwidth = 10)
self.search_label.grid(row=0, column=0, columnspan=len(self.num_choices))
for i in range(len(self.num_choices)):
option = Radiobutton(search_frame, variable = self.v, value = i, text = self.num_choices[i])
option.grid(row = 1, column = i, sticky = W)
show_btn = Button(search_frame, text = "Show", borderwidth = 10, command = self.summary_frame)
show_btn.grid(row = 3, column = 0, columnspan = len(self.num_choices))
def next_movie(self):
self.counter +=1
self.current_movie.configure(text = self.movies[self.counter].movie)
#used so each radio button the user chooses will be saved
for i in range(len(self.radiobutton)):
self.radiobutton[i].configure(variable = self.v, text = self.ratings[i], value = self.ratings[i])
#the default movie rating is no rating for every movie
self.v.set("No Rating")
def previous_movie(self):
self.counter -=1
self.current_movie.configure(text = self.movies[self.counter].movie)
#the default movie rating is no rating for every movie
self.v.set("No Rating")
def update_rating(self):
self.movies[self.counter].ratings = self.v.get()
for element in self.movies:
print(element.ratings)
print()
print('*'*20)
print()
if __name__ == "__main__":
root = Tk()
root.title("Movie Ratings")
radiobuttons = MovieRaterGUI(root)
root.mainloop()
If you use destroy then you destroy also data which you have in this frame - and you couldn't use them. You would have to get data from frame before you destroy it.
But it may be better to remove frame without destroying (pack_forget, grid_forget) and then you have access to data in frame and you can always display this frame again.
With grid it can be simpler to put new element in the same place.
Where put this code?
Usually programs have buttons << Previous,Next >> or << Details, Summary >> to change frames/steps - but it seems you forgot these buttons.
Eventually you can use Notebook to have frames in tabs.
Applications for smartphones usually can slide frames (using fingers) but desktop programs rather don't use this method. And tkinter doesn't have special methods for this. It would need much more code with events or drag'&'drop. Buttons are much simpler to create and simpler to use by users.

Tkinter won't update image file to one in a new directory

I'm trying to create a button with an image icon on top of it which should switch icons to an image specified in a function the button calls when its clicked. The button however just stays as it is with the original image when hit with no error messages, is there something I'm missing out on?
from tkinter import *
sampleW = Tk()
sampleW.geometry("250x250")
sampleW.title("god help me")
def imageSwitch():
icon1Directory == PhotoImage(file = r"C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\bread man.png") # new image directory
print("button has been pressed")
icon1Directory = PhotoImage(file = r"C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\plus_black.png") # original image directory
icon1_photoImage = icon1Directory.subsample(7, 7)
button = Button(sampleW, relief = "ridge", padx = 70, pady = 5,image = icon1_photoImage, command = imageSwitch)
button.grid(column = 0, row = 0)
sampleW.mainloop()
first of all, in your code, this part causes errors:
icon1Directory == PhotoImage(file = r"C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\bread man.png") # new image directory
the == operation is for comparison.
then about your function. after you create a button(or something else in tkinter), you should use .config to change some properties of it.
you can code this to change the icon:
from tkinter import *
sampleW = Tk()
sampleW.geometry("250x250")
sampleW.title("god help me")
def imageSwitch():
icon2 = PhotoImage(file=r'C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\bread man.png')
button.config(image=icon2)
button.image = icon2
icon = PhotoImage(file=r'C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\plus_black.png')
button = Button(sampleW, relief = "ridge", padx = 70, pady = 5,image = icon, command = imageSwitch)
button.grid(column = 0, row = 0)
sampleW.mainloop()
I think you should change this line in the function:
icon1Directory == PhotoImage(file = r"C:\Users\txvpa\OneDrive\Desktop\hentai\Atom Projects\The Bread Exchange\bread man.png")
You wrote == but you should write =.
Your syntax means that you are making a statement with False return and not a variable deceleration.

Python Tkinter: Is there a way prevent 'Label' text from pushing columns to the side?

I'm pretty new to Python's Tkinter library tool. Currently I'm trying to output text on a screen when a user enters proper login credentials to a server node. I'm using grid instead of pack when I organize things around this GUI.
Is there a way to prevent Label text from pushing columns to the side?
This is what the current GUI looks like:
... and here's what happens when I "login" to the server node with some confirmation text on the screen:
... and this is what I'm wishing would happen instead (photoshopped):
... and using #Bryan Oakley's feedback (small difference):
I could move the text on the second column, column=1, but there are things to the right of textbox entry modules (that I won't show in this question). My initial thought that perhaps there is some layer priority with GUIs so maybe I could set the text at the bottom layers so that it wouldn't affect the textboxes but I haven't found anything with Tkinter's official documentation on how to do this.
Here's some of my code:
import json
import requests
import sys
import os
import time
import threading
import getpass
import warnings
from tkinter import *
from tkinter import ttk
nodeIPLabel = Label(text = 'Node IP/Hostname : ')
nodeIPLabel.grid(column = 0, row = 0, sticky='W')
nodeUsernameLabel = Label(text = 'Node username : ')
nodeUsernameLabel.grid(column = 0, row = 1, sticky='W')
nodePasswordLabel = Label(text = 'Node passwword : ')
nodePasswordLabel.grid(column = 0, row = 2, sticky='W')
# Enter Login Credentials
nodeIP = StringVar()
nodeUsername = StringVar()
nodePassword = StringVar()
nodeIPEntry = Entry(textvariable = nodeIP, width = 30)
nodeIPEntry.grid(column = 1, row = 0, sticky='W')
nodeUsernameEntry = Entry(textvariable = nodeUsername, width = 30)
nodeUsernameEntry.grid(column = 1, row = 1, sticky='W')
nodePasswordEntry = Entry(textvariable = nodePassword, width = 30, show = '*')
nodePasswordEntry.grid(column = 1, row = 2, sticky='W')
def nodeLogin():
global nodeIP
nodeIP = nodeIP.get()
global nodeUsername
nodeUsername = nodeUsername.get()
global nodePassword
nodePassword = nodePassword.get()
global nodeType
nodeType = nodeType.get()
nodeURL = 'https://' + nodeIP
try:
listOfStuff = '%s/redfish/v1/Chassis/1' % nodeURL
response = requests.get(listOfStuff, auth=(nodeUsername, nodePassword), verify=False)
if response.status_code == 200:
print('Connection successful!')
except requests.exceptions.ConnectionError:
connectionStatus = Label(text='Connection failure: Incorrect or nonexisting IP/Hostname. Hit "RESET GUI" to restart.', fg = 'red')
connectionStatus.grid(column = 0, row = 7, sticky='N')
except requests.exceptions.InvalidURL:
connectionStatus = Label(text='Connection failure: Nothing was typed in. Hit "RESET GUI" to restart.', fg = 'red')
connectionStatus.grid(column = 0, row = 7, sticky='N')
except KeyError:
connectionStatus = Label(text='Connection failure: Wrong login credentials. Hit "RESET GUI" to restart.', fg = 'red')
connectionStatus.grid(column = 0, row = 7, sticky='N')
except json.decoder.JSONDecodeError:
connectionStatus = Label(text='Connection failure: Unknown error... Hit "RESET GUI" to restart.', fg = 'red')
connectionStatus.grid(column = 0, row = 7, sticky='N')
def resetGUI():
os.startfile(__file__)
sys.exit()
# Login button
nodeLoginButton = Button(text = 'LOGIN', command = nodeLogin, bg = 'green', width = 20)
nodeLoginButton.grid(column = 0, row = 4, sticky='W')
# Reset script button
resetButton = Button(text = 'RESET GUI', command = resetGUI, bg = 'yellow', width = 20)
resetButton.grid(column = 0, row = 5, sticky = 'W')
# Stop script button
stopButton = Button(text = 'EMERGENCY STOP', command = window.destroy, bg = 'red', width = 20)
stopButton.grid(column = 0, row = 6, sticky = 'W')
window.mainloop()
The simplest solution in this specific case is to have that bottom label span more than one column.
connectionStatus.grid(column = 0, row = 7, sticky='N', columnspan=2)
Of course, that assumes you're only using 2 columns. If you're using more, adjust columnspan to fit your overall design.
However, even that's not enough if the text is literally too long to fit in the window. In that case, you need to give the widget a fixed size. When you do that, the contents of the label won't cause the label to change size.
Typically what I do for a widget like this is give it a size of one, have the label span all of the columns you are using, and then use the sticky attribute to have the widget "stick" to the sides of the space allocated to it. That will cause the label to grow to fit the space, but because you've given an explicit requested size, changing the text won't change the size of the label.
That being said, an even simpler approach is to divide your window into two parts: the top part would be a frame with all of the widgets, and the bottom part with just the label. You can then use pack to arrange the top and bottom parts easily, and then you can use grid to lay out the widgets in the frame, but because the label is outside the frame it won't affect anything.

How to avoid global variables in command function call

I'm still working on a code that moves around two planets and shows the gravitational force between them. I tried to make it easier to use by just showing two buttons that allow to select the planet you want to move. Then you can click in the canvas and the selected planet will move where you've clicked.
The programm works, but I'd like to know if there is a better way to write it then using the chngB and chngO functions with global statements.
I still can't believe that in Python you are forced to use a function without parameters when assigning it to the command parameter of a button.
Basically I'd like to know if it's just possible to write something like command = (a=1)
(I know that this doesn't work, but you get the idea.)
Also, there probably just is another way of doing it than having to use a variable to know which planet is selected (which button has been pressed last).
I use Python 3.
from tkinter import *
import math
x, y = 135, 135
a = 0
def gravitation (obj1,obj2):#showing gravitational force between planets
a, b, c, d = can.coords (obj1)
e, f, g, h = can.coords (obj2)
dist = math.sqrt ((((a+c)/2)-((e+g)/2))**2+(((b+d)/2)-((f+h)/2))**2)
if dist != 0:
grav = 6.67384/dist
else:
grav = "Infinite"
str(grav)
return grav
def chngB ():#Telling Blue planet is selected
global a
a = 1
def chngO ():#Telling Orange planet is selected
global a
a = 0
def updt ():#Updating gravitation label
lbl.configure (text = gravitation(oval1, oval2))
def moveBlue (event):#Placing blue planet where mouse click on canv
coo = [event.x-15, event.y-15, event.x+15, event.y+15]
can.coords(oval1, *coo)
updt()
def moveOrange (event):#Placing orange planet where mouse click on canv
coo = [event.x-15, event.y-15, event.x+15, event.y+15]
can.coords(oval2, *coo)
updt()
def choice (event):#Function binded to can, move the selected planet (blue = 1, prange = 0)
if a == 0:
moveOrange(event)
else :
moveBlue(event)
##########MAIN############
wind = Tk() # Window and canvas
wind.title ("Move Da Ball")
can = Canvas (wind, width = 300, height = 300, bg = "light blue")
can.grid(row=0, column=0, sticky =W, padx = 5, pady = 5, rowspan =3)
can.bind ("<Button-1>", choice)
Button(wind, text = 'Quit', command=wind.destroy).grid(row=2, column=1, sticky =W, padx = 5, pady = 5)
oval1 = can.create_oval(x,y,x+30,y+30,width=2,fill='blue') #Planet 1 moving etc
buttonBlue = Button(wind, text = 'Blue Planet', command = chngB)
buttonBlue.grid(row=1, column=1, sticky =W, padx = 5, pady = 5)
oval2 = can.create_oval(x+50,y+50,x+80,y+80,width=2,fill='orange') #Planet 2 moving etc
buttonOrange = Button(wind, text = 'Orange Planet', command = chngO)
buttonOrange.grid(row=0, column=1, sticky =W, padx = 5, pady = 5)
lbl = Label(wind, bg = 'white')#label
lbl.grid(row=4, column=1, sticky =W, padx = 5, pady = 5, columnspan = 3)
gravitation (oval1, oval2)
wind.mainloop()
Normally, if you want a button to toggle a variable between one of N values, you would use a set of radiobuttons. When you use a radiobutton you can associate it with a variable so that whenever you click the button, the variable is automatically selected.
For example:
planet = IntVar()
planet.set(0)
buttonBlue = Radiobutton(wind, text="Blue Planet", variable=planet, value=1)
buttonOrange = Radiobutton(wind, text="Orange Planet", variable=planet, value=0)
...
def choice (event):#Function binded to can, move the selected planet (blue = 1, prange = 0)
if planet.get() == 0:
moveOrange(event)
else :
moveBlue(event)
If you really want to use a regular button, you can do that with a single callback rather than two, and then use lambda or functools.partial to pass in the new value.
For example:
buttonBlue = Button(wind, text = 'Blue Planet', command = lambda: change(1))
buttonOrange = Button(wind, text = 'Blue Planet', command = lambda: change(0))
def change(newValue):
global a
a = newValue
Unfortunately, you cannot have a lambda expression with a statement (assignment) in it.
But you can easily have just one setter function which returns a closure that you can pass to your Button constructor. Here's an example of creating and using closures:
a = 0
def set_a (v):
def closure ():
global a
a = v
return closure
command = set_a(42)
command()
print a # prints 42
command = set_a(17)
command()
print a # prints 17

Thread designed to delete Tkinter Text Widget Content does not operate.

Sorry if this is a rather specific problem, but let me try and explain it well.
I've got a text box that a user can add text to by typing in a box and hitting 'Send'.
The problem is that I need to clean out the textbox after fifteen lines have been used.
Here is what I tried to do:
Whenever the user hits send, to add the text to the textbox, it add's a number to a Variable called textlimiter. Because every send button creates a new line as well. I can use a variable to record the number of times something is 'sent' to the textbox.
When the variable exceeds 15, a thread that is watching the variable will wipe the textbox clean.
Thing is, It never seems to work. I've tried asking for help and I got a little from an awesome guy who suggested I rewrite everything into 'frames', so that I can class all my functions, widgets, and variables into one class.
It appears I've got a conflict ion with variable class's. So I tried to set the variable to 'global' inside my class's, and functions using the variable. But that didn't seem to work either.
I can go and rewrite it all into frames I suppose, but I was just going to ask if anyone has a quick fix they can think of here. (I also don't know when the guy can help me once I'm done and if issues arise)
If my question is inappropriate, or somehow not allowed, Tell me and I'll take it down tomorrow when I get up. Sorry if it's bad.
I've got the code here:
# [Importing Modules]
from tkinter import *
import os
import threading
#_________________| Setting Variables
global textlimiter
textlimiter = 0
#____________| Defining Functions
def CONVERT():
chatbox.insert(INSERT,"You: "+USER_ENTRY.get()+ "\n")
INPUT_BOX.set("")
global textlimiter
textlimiter += 1
#_______| Creating Window |
chat = Tk()
chat.title("Net Send Client [0.4]")
chat.geometry('550x500+200+200')
#________| Title |
title = StringVar()
title.set("Net Send Chat\n Type in box to send.")
title_widget = Label(chat,textvariable = title, height = 4)
title_widget.pack()
#_______________________| User Input Box|
INPUT_BOX = StringVar()
USER_ENTRY = Entry(chat,textvariable = INPUT_BOX)
USER_ENTRY.pack(side=TOP, padx = 10, pady = 20)
#___________________________________________________________________| Send Button|
send_button = Button(chat, text="Send",width = 20, command = CONVERT)
send_button.pack(padx = 10, pady = 10)
#_______________________________________________________| Text Box Widget |
chatbox = Text(width = 60, height = 15, relief = SUNKEN)
left = Frame(chatbox)
right = Frame(chatbox)
s_start = Scrollbar(right)
s_start.pack(side=RIGHT)
chatbox.grid(row = 0, column = 0, columnspan = 3)
chatbox.pack()
#__________________________________________| Chat Wizard Checks Text Limit|
class Chatwizard(threading.Thread):
def Chatmonitor():
global textlimiter
if textlimiter >= 15:
chatbox.set(None)
chatbox.insert(INSERT,"Console: Limit Reached, Chat Wiped" + "\n")
Chatwizard.start
chat.mainloop()
As you can see I've tried setting my variables to Global, but nothing seems to change.
If you try running the program, remember you need to enter and send something 15 times for it to supposedly wipe the chatbox.
Thanks for taking a look if you do. I'm really a little stumped right now.
You definitely do not need a thread to monitor the Text widget
(chatbox), because the chatbox itself can tell you how many lines
of text it contains:
numlines = len(chatbox.get("1.0",END).splitlines())
Moreover, Tkinter is not threadsafe. All GUI widgets should be
touched by one and only one thread.
from Tkinter import *
import os
import threading
def CONVERT():
numlines = len(chatbox.get("1.0",END).splitlines())
if numlines > 2:
chatbox.delete("1.0",END)
chatbox.insert(INSERT,"Console: Limit Reached, Chat Wiped" + "\n")
chatbox.insert(INSERT,"You: "+USER_ENTRY.get()+ "\n")
INPUT_BOX.set("")
chat = Tk()
chat.title("Net Send Client [0.4]")
chat.geometry('550x500+200+200')
title = StringVar()
title.set("Net Send Chat\n Type in box to send.")
title_widget = Label(chat,textvariable = title, height = 4)
title_widget.pack()
INPUT_BOX = StringVar()
USER_ENTRY = Entry(chat,textvariable = INPUT_BOX)
USER_ENTRY.pack(side=TOP, padx = 10, pady = 20)
send_button = Button(chat, text="Send",width = 20, command = CONVERT)
send_button.pack(padx = 10, pady = 10)
chatbox = Text(width = 60, height = 15, relief = SUNKEN)
left = Frame(chatbox)
right = Frame(chatbox)
s_start = Scrollbar(right)
s_start.pack(side=RIGHT)
chatbox.grid(row = 0, column = 0, columnspan = 3)
chatbox.pack()
chat.mainloop()

Categories

Resources