Tkinter PhotoImage in function doesn't appear [duplicate] - python

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 2 years ago.
I'm creating a little program to show if something is ok, but I have a problem with the images. It was working until I create a function to generate my page
Without images
def genTout(matInput):
#images
vert_off = PhotoImage(file=r".\asset\vert_off.png")
rouge_off = PhotoImage(file=r".\asset\rouge_off.png")
vert_on = PhotoImage(file=r".\asset\vert_on.png")
rouge_on = PhotoImage(file=r".\asset\rouge_on.png")
#frame
rightFrame = Frame(highlightbackground="black",highlightthickness=5 ,bg="grey")
buttonControl = Frame(rightFrame,highlightbackground="black",highlightthickness=5 ,bg="grey")
for i in range(0,4):
Label(buttonControl,text=i+1).grid(row=0,column=i+2)
Label(buttonControl,image=(vert_on if matInput[i*2] == 0 else vert_off)).grid(row=1,column=i+2)
Label(buttonControl,image=(rouge_on if matInput[i*2+1] == 0 else rouge_off)).grid(row=2,column=i+2)
return frame
When i take my code on the main it's working but if I put the code inside a function no images appear
Here is my main where I get the return
root = Tk()
PanelView = PanedWindow(width=100, bd=5,relief="raised",bg="grey")
PanelView.pack(fill=BOTH,expand=1)
#my side bar code
...
rightFrame = genTout(matInput)
PanelView.add(rightFrame)

I saw that problem often already here : Your PhotoImage objects are getting garbage collected when the function finishes / returns. Tkinter somehow doesn't like that (for example Button's can get garbage collected, Tkinter somehow deals with it, but that's not the case for PhotoImage's). You must somehow save these images, for example by creating the frame first thing in your function and then making the images attributes of the frame, like this:
def genTout(matInput):
#frame
rightFrame = Frame(highlightbackground="black", highlightthickness=5 , bg="grey")
buttonControl = Frame(rightFrame, highlightbackground="black", highlightthickness=5, bg="grey")
#images
rightFrame.vert_off = PhotoImage(file=r".\asset\vert_off.png")
rightFrame.rouge_off = PhotoImage(file=r".\asset\rouge_off.png")
rightFrame.vert_on = PhotoImage(file=r".\asset\vert_on.png")
rightFrame.rouge_on = PhotoImage(file=r".\asset\rouge_on.png")
for i in range(0, 4):
Label(buttonControl, text=i + 1).grid(row=0, column=i + 2)
Label(buttonControl, image=(rightFrame.vert_on if matInput[i * 2] == 0 else rightFrame.vert_off)).grid(row=1, column=i+2)
Label(buttonControl, image=(rightFrame.rouge_on if matInput[i * 2 + 1] == 0 else rightFrame.rouge_off)).grid(row=2, column=i + 2)
return frame

Related

creating dynamic widget reference

I have a program using GUIZero for my display interface. On this display, I have 30 pushbuttons created. I am trying to find a way to dynamically reference all 30 buttons. Here is the code I "think" I need, but obviously it is not working. Quick background on GUIZero, I create the pushbutton, and then the pushbutton has parameters I can adjust. I am trying to adjust the background of the pushbutton. That looks like {widget}.{parameter} = {value}. I am trying to make the {widget} portion dynamic.
Here is a shortened version of what works:
def Clearapp():
button_0.bg = "light grey"
button_1.bg = "light grey"
button_2.bg = "light grey"
.
.
.
button_29.bg = "light grey"
This is what I "think" it should be, but is not working.
bit0 = "button_"
def Clearapp():
global bit0
for x in range(30):
btn = bit0+str(x) # this makes "button_" + {value of x}
btn.bg = "light grey"
I know I have bit0 set as a global variable, and I am doing that because of the structure of what I am doing, I use that string in a lot of different places, and it makes it easier to use and keep my name and syntax correct.
So, the long and short, I am trying to figure out how to combine 2 strings together and then use that string as a reference or pointer to a specific item. As shown above I tried both code versions. The long version works fine. The short version results with the following errors:
File "/home/pi/Desktop/qc_box_221129a.py", line 50, in Clearapp btn.bg = "light grey" AttributeError: 'str' object has no attribute 'bg'
To add some additional clarity:
I want to use the string value in "btn" (the combination of bit0 and x) as a pointer to go to a specific widget.
So in my bottom code, this is the order of operations:
`For x in range(30):
btn = "button_" + x
btn.bg = {value}
Step 1: x=0
btn = button_ + 0
{use string to address widget} -> insert value of btn into command "btn.bg = {value}"
button_0.bg = {value}
Step 2: x = 1
btn = button_ + 1
button_1.bg = {value}
Step 3: x = 2
btn = button_ + 2
button_2.bg = {value}
.
.
.
Step 30: x = 29
btn = button_ + 29
button_29.bg = {value}`

I am trying to populate a Tkinter grid from a JSON and limit the number of rows to 5, so when it reaches the fifth it'd continue into another column

What I am trying to achieve is something like this:
But instead, what I am getting is this:
This is the structure:
The file that's getting the JSON data, called bitmex.py
import requests
def get_contracts():
response_object = requests.get('https://www.bitmex.com/api/v1/instrument/active')
contracts = []
for contract in response_object.json():
contracts.append(contract['symbol'])
return contracts
print(get_contracts())
And this is the main.py that is populating the data into a Grid:
import tkinter as tk
from bitmex import get_contracts
if __name__ == '__main__':
bitmex_contracts = get_contracts()
root = tk.Tk()
root.title("Bitmex Contracts")
x = 0
y = 0
for contract in bitmex_contracts:
label_widget = tk.Label(root, text=contract, borderwidth=1, relief=tk.SOLID, width=13).grid(row=x, column=y, sticky='ew')
if x >= 4:
y += 1
x = 0
else:
x += 1
root.geometry('800x600')
root.mainloop()
As you'll see the problem I am facing is that the data is getting one underneath each other but not on a grid but superimposed instead.
The version of Python I am using is v.3.9.7 and
Tkinter v.8.6

UnboundLocalError: local variable 'qn' referenced before assignment

I'm getting the error "UnboundLocalError: local variable 'qn' referenced before assignment" on running the code. Why is that? How can I correct it? I'm new to tkinter so please try to keep it simple. This is part of the code for a game I was writing. It would be a great help if I could get an answer soon
from tkinter import *
from tkinter import messagebox
from io import StringIO
root = Tk()
root.title("Captain!")
root.geometry("660x560")
qn = '''1$who are you?$char1$i am joe$3$i am ben$2
2$what are you?$char2$i am a person$1$i am nobody$3
3$how are you?$char3$i am fine$2$i'm alright$1'''
var = '''1$10$-35$20$15$-20
2$9$7$30$-5$-15
3$10$-25$-15$10$5'''
class Game :
def __init__(self):
self.m_cur = {1:["Military",50]}
self.c_cur = {1:["People's",50]}
self.r_cur = {1:["Research",50]}
self.i_cur = {1:["Industrial",50]}
self.p_cur = {1:["Research",50]}
#function to clear all widgets on screen when called
def clear(self):
for widget in root.winfo_children():
widget.destroy()
#function to quit the window
def exit(self):
msg = messagebox.askquestion("Thank you for playing","Are you sure you want to exit?")
if msg == "yes" :
root.destroy()
else:
Game.main(self)
#start function
def start(self):
Label(root,text="Hello, what should we call you?",font=("segoe print",20)).grid(row=0,column=0)
name = Entry(root,width=20)
name.grid(row=1,column=0)
Button(root,text="Enter",font=("segoe print",20),command=lambda: Game.main(self)).grid(row=1,column=1)
self.name=name.get()
#main function
def main(self):
Game.clear(self)
Label(root,text="Welcome to the game",font=("segoe print",20)).grid(row=0,column=0)
Label(root,text='What do you want to do?',font=("segoe print",20)).grid(row=1,column=0)
Button(root,text="Start Game",font=("segoe print",20),command=lambda: Game.qn_func(self,1)).grid(row=2,column=0)
Button(root,text="Exit Game",font=("segoe print",20),command=lambda: Game.exit(self)).grid(row=3,column=0)
#function to check variables and display game over
def game_over(self,x_cur):
if x_cur[1][1]<=0 or x_cur[1][1]>=100 : #condition to check game over
Game.clear(self)
Label(root,text=x_cur)
Label(root,text="GAME OVER",font=("ariel",20)).place(relx=0.5,rely=0.5,anchor=CENTER)
Button(root,text="Continue",font=("segoe print",20),command=lambda: Game.main(self)).place(relx=0.5,rely=0.6)
#function to display question and variables
def qn_func(self,qn_num) :
Game.clear(self)
#accessing the questions
q_file = StringIO(qn)
#reading the question, options, next qn numbers and the character name from the file
qn_list = q_file.readlines()
qn = qn_list[qn_num-1].strip().split("$")[1]
char_name = qn_list[qn_num-1].strip().split("$")[2]
qn1 = qn_list[qn_num-1].strip().split("$")[3]
qn2 = qn_list[qn_num-1].strip().split("$")[5]
n_qn1 = int(qn_list[qn_num-1].strip().split("$")[4])
n_qn2 = int(qn_list[qn_num-1].strip().split("$")[6])
#displaying the character name and the question as a label frame widget with character name as parent
label_frame = LabelFrame(root,text = char_name,font = ("segoe print",20))
label = Label(label_frame,text = qn,font = ("segoe print",20))
label_frame.place(relx=0.5,rely=0.5,anchor=CENTER)
label.pack()
q_file.close()
#accessing variables
v_file = StringIO(var)
#reading values of variables from file
v_list = v_file.readlines()
self.r_cur[1][1] += int(v_list[qn_num-1].strip().split("$")[1])
self.c_cur[1][1] += int(v_list[qn_num-1].strip().split("$")[2])
self.i_cur[1][1] += int(v_list[qn_num-1].strip().split("$")[3])
self.m_cur[1][1] += int(v_list[qn_num-1].strip().split("$")[4])
self.p_cur[1][1] += int(v_list[qn_num-1].strip().split("$")[5])
#running each variable through game_over to see if you are dead
Game.game_over(self,self.r_cur)
Game.game_over(self,self.c_cur)
Game.game_over(self,self.i_cur)
Game.game_over(self,self.m_cur)
Game.game_over(self,self.p_cur)
#defining the Doublevar variables
s_var1 = DoubleVar()
s_var2 = DoubleVar()
s_var3 = DoubleVar()
s_var4 = DoubleVar()
s_var5 = DoubleVar()
#setting the values in the scales
s_var1.set(self.r_cur[1][1])
s_var2.set(self.c_cur[1][1])
s_var3.set(self.i_cur[1][1])
s_var4.set(self.m_cur[1][1])
s_var5.set(self.p_cur[1][1])
#variables as scale widgets
scale1 = Scale(root,from_=100,to=0,orient=VERTICAL,sliderlength=10,variable=s_var1)
scale2 = Scale(root,from_=100,to=0,orient=VERTICAL,sliderlength=10,variable=s_var2)
scale3 = Scale(root,from_=100,to=0,orient=VERTICAL,sliderlength=10,variable=s_var3)
scale4 = Scale(root,from_=100,to=0,orient=VERTICAL,sliderlength=10,variable=s_var4)
scale5 = Scale(root,from_=100,to=0,orient=VERTICAL,sliderlength=10,variable=s_var5)
#displaying the scale widgets on the screen
scale1.grid(row=0,column=0)
scale2.grid(row=0,column=1)
scale3.grid(row=0,column=2)
scale4.grid(row=0,column=3)
scale5.grid(row=0,column=4)
#disabling the scales
scale1.config(state=DISABLED)
scale2.config(state=DISABLED)
scale3.config(state=DISABLED)
scale4.config(state=DISABLED)
scale5.config(state=DISABLED)
v_file.close()
#displaying the buttons on the screen
Button(root,text=qn1,command=lambda: Game.qn_func(self,n_qn1)).place(relx=0.2,rely=0.7,anchor=W,width=200,height=50)
Button(root,text=qn2,command=lambda: Game.qn_func(self,n_qn2)).place(relx=0.8,rely=0.7,anchor=E,width=200,height=50)
game = Game()
game.start()
root.mainloop()
You can see in this particular section that you have called on 'qn' before it was even defined:
#function to display question and variables
def qn_func(self,qn_num) :
Game.clear(self)
#accessing the questions
q_file = StringIO(qn)
#reading the question, options, next qn numbers and the character name from the file
qn_list = q_file.readlines()
qn = qn_list[qn_num-1].strip().split("$")[1]
The variable needs to be assigned a value before being used. Here, you call q_file = StringIO(qn) before you have defined qn = qn_list....

Automatically update label from variable with formula

I'm trying to create a label which automatically shows the result from an inputed variable.
Basically I'm trying to combine these two programs :
from tkinter import *
root = Tk()
var = StringVar()
var.set('hello')
l = Label(root, textvariable = var)
l.pack()
t = Entry(root, textvariable = var)
t.pack()
root.mainloop() # the window is now displayed
this one (source : Update Tkinter Label from variable) automatically updates the label, however it can only update it to what was inputed by the user.
and this one :
from tkinter import *
myWindow = Tk()
def MyCalculateFunction():
pressure, temprature = float(box_pressure.get()), float(box_temprature.get())
result = pressure + temperature
label_result.config(text="%f + %f = %f" % (pressure, temprature, result))
box_pressure = Entry(myWindow)
box_pressure.pack()
box_temprature = Entry(myWindow)
box_temprature.pack()
button_calculate = Button(myWindow, text="Calcuate", command=MyCalculateFunction)
button_calculate.pack()
label_result = Label(myWindow)
label_result.pack()
the problem I have with this one it that if the user changes the pressure or temperature, the result doesn't automatically change. (source : How to get value from entry (Tkinter), use it in formula and print the result it in label)
How can I make it so that when a user changes any variable, Python automatically calculates the new result and changes the label on its own?
Just a few things you missed out.
Tkinters widgets need variables to hold the values inputed into them, which you missed out on creating in your temperature and pressure widgets.
You are better served calculating your values and then set the widgets variable.
Hopefully this helps.
from tkinter import *
myWindow = Tk()
def MyCalculateFunction():
label_variable=StringVar()
label_result= Label(myWindow, textvariable=label_variable)
label_result.pack()
pressure, temperature = float(pressure_variable.get()), float(temperature_variable.get())
result = pressure + temperature
label_variable.set("%f + %f = %f" % (pressure, temperature, result))
pressure_variable=StringVar()
box_pressure = Entry(myWindow, textvariable=pressure_variable)
box_pressure.pack()
temperature_variable=StringVar()
box_temprature = Entry(myWindow, textvariable=temperature_variable)
box_temprature.pack()
button_calculate = Button(myWindow, text="Calcuate", command=MyCalculateFunction)
button_calculate.pack()

Creating a mouse over text box in tkinter

I'm trying to implement system where when the user points to an object, a text box appears with certain information which I haven't implemented yet, then disappears when they move their mouse away. I'm trying to do that by binding the < Enter > and < Leave > commands, but nothing happens when I run the following code, except that in the terminal it says that destroy requires two arguments, so I know it is calling the functions.
from tkinter import *
xhig, yhig = 425,325
bkgnclr = '#070707'
currentmouseoverevent = ''
c = Canvas(master, width=xhig*2, height=yhig*2, bg=bkgnclr, cursor = 'crosshair',)
def mouseovertext(event):
mouseover = "Jack"
currentmouseoverevent = event
c.create_rectangle(bbox=(event.x,event.y, (event.x + 5), (event.y +len(mouseover)*5)),outline="white", fill=bkgnclr, width= len(mouseover))
c.create_text(position=(event.x,event.y),text=mouseover, fill="white", currentmouseoverevent=event)
def closemouseover(x):
c.destroy(currentmouseoverevent)
c.bind("<Enter>", mouseovertext)
c.bind("<Leave>", closemouseover)
What arguments does destroy take, and why is the rectangle not being created?
A bounding box (bbox) in tkinter is a 4-tuple which stores the bounds of the rectangle. You are only passing in the mouse location, which is a 2-tuple.
Also, you are never actually assigning to the variable "currentmouseoverevent" before using it in the code you show, so your closemouseover function will fail.
The corrected code is as follows.
It turns out I was calling bbox wrong. Instead of passing the coords as a tuple, I should have passed them as the first four agrguments of create_rectangle. c.destroy is only for objects like canvas, entry or textbox, instead I used c.delete for deleting items, and used the event number returned by c.create_rectangle and c.create_text.
from tkinter import *
xhig, yhig = 425,325
bkgnclr = '#070707'
currentmouseoverevent = ['','']
c = Canvas(master, width=xhig*2, height=yhig*2, bg=bkgnclr, cursor = 'crosshair',)
def mouseovertext(event):
mouseover = "Jack"
if currentmouseoverevent[0] != '':
closemouseover()
currentmouseoverevent[0]=''
return
currentmouseoverevent[0] = c.create_rectangle(event.x,event.y, (event.x + 5), (event.y +len(mouseover)*5),outline="white", fill=bkgnclr, width= len(mouseover))
currentmouseoverevent[1] = c.create_text(event.x,event.y,text=mouseover, fill="white", currentmouseoverevent=event,anchor=NW)
def closemouseover(x):
c.delete(currentmouseoverevent[0])
c.delete(currentmouseoverevent[1])
c.bind("<Button-3", mouseovertext)

Categories

Resources