Tkinter: Addressing Label widget created by for loop - python

The following is my script. Basically, it will ask the user to input a number into the Entry box. Once the user enter a number and click OK, it will give you combination of Labels+Buttons depends on the number that user typed in to the Entry box.
from Tkinter import *
root=Tk()
sizex = 600
sizey = 400
posx = 0
posy = 0
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
def myClick():
myframe=Frame(root,width=400,height=300,bd=2,relief=GROOVE)
myframe.place(x=10,y=10)
x=myvalue.get()
value=int(x)
for i in range(value):
Mylabel=Label(myframe,text=" mytext "+str(i)).place(x=10,y=10+(30*i))
Button(myframe,text="Accept").place(x=70,y=10+(30*i))
mybutton=Button(root,text="OK",command=myClick)
mybutton.place(x=420,y=10)
myvalue=Entry(root)
myvalue.place(x=450,y=10)
root.mainloop()
Normally, when i create a label widget, i would do something like this
mylabel=Label(root,text='mylabel')
mylabel.pack()
So when i want to change the text of my label later on i can just simply do this
mylabel.config(text='new text')
But now, since i am using for loop to create all labels at once, is there anyway to address the individual labels after the labels has been created?
For example, the user typed in '5' into the entry box and the program will give me 5 lables + 5 buttons. Is there anyway for me to change the properties (ie, label.config(..)) of the individual labels?

Sure! Just make a list of labels, call place on each one, and then you can reference them later and change their values. Like so:
from Tkinter import *
root=Tk()
sizex = 600
sizey = 400
posx = 0
posy = 0
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
labels = []
def myClick():
del labels[:] # remove any previous labels from if the callback was called before
myframe=Frame(root,width=400,height=300,bd=2,relief=GROOVE)
myframe.place(x=10,y=10)
x=myvalue.get()
value=int(x)
for i in range(value):
labels.append(Label(myframe,text=" mytext "+str(i)))
labels[i].place(x=10,y=10+(30*i))
Button(myframe,text="Accept").place(x=70,y=10+(30*i))
def myClick2():
if len(labels) > 0:
labels[0].config(text="Click2!")
if len(labels) > 1:
labels[1].config(text="Click2!!")
mybutton=Button(root,text="OK",command=myClick)
mybutton.place(x=420,y=10)
mybutton2=Button(root,text="Change",command=myClick2)
mybutton2.place(x=420,y=80)
myvalue=Entry(root)
myvalue.place(x=450,y=10)
root.mainloop()
Also note! In the assignment Mylabel=Label(myframe,text=" mytext "+str(i)).place(x=10,y=10+(30*i)) in the original code, that call sets Mylabel to None, since the place method returns None. You want to separate the place call into its own line, like in the code above.

Related

Tkinter widgets created in an Update function run by tk.after function do not create untill the aforementioned Update ends

I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()

Text Widget: Update and keep idle after loop when no input

root = tk.Tk()
text = tk.Text(root, width = 40, height = 1, wrap = 'word')
text.pack(pady = 50)
after_id = None
# Now i want to increase the height after wraping
def update():
line_length = text.cget('width')
lines = int(len(text.get(1.0, 'end'))/line_length) + 1 # This is to get the current lines.
text.config(height = lines) # this will update the height of the text widget.
after_id = text.after(600, update)
update()
root.mainloop()
Hi I'm making a text widget and I want to update it when some input the passed else keep it idle, Right now I'm using this code. but I don't know how to keep it idle when no input is passed or no button is pressed.
I know there is a better way to do this operation but didn't found yet. Please Help!!!
Hi after reading some documentation and article i got a solution of my problem. In this we can use KeyPress event and we can bind over update method with this event.
Here is the code....
import tkinter as tk
root = tk.Tk()
text = tk.Text(root, width = 40, height = 1, wrap = 'word')
text.pack(pady = 50)
# first of all we need to get the width of the Text box, width are equl to number of char.
line_length = text.cget('width')
Init_line = 1 # to compare the lines.
# Now we want to increase the height after wraping of text in the text box
# For that we will use event handlers, we will use KeyPress event
# whenever the key is pressed then the update will be called
def Update_TextHeight(event):
# Now in this we need to get the current number of char in the text box
# for the we will use .get() method.
text_length = len(text.get(1.0, tk.END))
# Now after this we need to get the total number of lines int the textbox
bline = int(text_length/line_length) + 1
# bline will be current lines in the text box
# text_length is the total number of char in the box
# Since we have line_length number of char in one line so by doing
# text_length//line_length we will get the totol line of numbers.
# 1 is added since initially it has one line in text box
# Now we need to update the length
if event.char != 'Return':
global Init_line
if Init_line +1 == bline:
text.config(height = bline)
text.update()
Init_line += 1
# Nowe we will bind the KeyPress event with our update method.
text.bind("<KeyPress>",Update_TextHeight)
root.mainloop()

Avoid multiple windows of the same kind

My goal is to have an interactive bar chart. I want to open a window containing a text. This shall happen when the user clicks on one of the bars. I started playing around a bit. In the following example, if you click on the left bar a window containing a text pops up. The problem I have, I only want to have the window opening once. So if you click a second time on the left bar, I don't want to open a second window. Therefore my question, how can I check if the window already exists and avoid multiple windows of the same kind. I already found a post regarding this topic, but I do not understand the solution, which was only explained poorly.
Thank you very much for your help.
def on_press(event):
cont,att = rect[0].contains(event)
if cont == True:
win = tk.Tk()
label1 = ttk.Label(win, text ="Test1").grid(column=0,row=0)
label2 = ttk.Label(win, text ="Test2").grid(column=0,row=1)
label3 = ttk.Label(win, text ="Test3").grid(column=0,row=2)
fig = plt.figure()
ax = fig.add_subplot(111)
x = [1,2,3]
y = [10,20,5]
rect = ax.bar(x,y)
test = rect[0].figure.canvas.mpl_connect('button_press_event', on_press)
plt.show()
Meanwhile I found a solution for my problem. Like MediaEU said, I use a variable which I set to true or false. However, one important "trick" is to use the method state().
If a main window is open, state() returns 'normal'. If a main window is closed state() throws an exception. Took me - as a starter - quite some time to figure it out.
Here is my suggestion:
class info_window:
def __init__(self, rect): # has an argument rect, for a specific bar in your plot
self.rect = rect
self.win_open = False
self.state = 'normal'
self.temp = self.rect.figure.canvas.mpl_connect('button_press_event', self)
def label(self):
self.label1 = ttk.Label(self.win, text ="Test1").grid(column=0,row=0)
self.label2 = ttk.Label(self.win, text ="Test2").grid(column=0,row=1)
self.label3 = ttk.Label(self.win, text ="Test3").grid(column=0,row=2)
def __call__(self, event):
cont, att = self.rect.contains(event)
try: # here is the "trick", in case window was closed self.win.state() throws an exception
self.win.state()
except: # setting win_open to False ensures that a window can be opened again
self.win_open = False
if cont == True:
if self.win_open == False: # if window does not exist, create it
self.win = tk.Tk()
self.label()
self.win_open = True # as long as window is open, you can't create it again

generating animation in tkinter

I wrote a small program using tkinter to generate a figure based on parameters I'm giving the program (through gui interface). I am drawing the figure directly with tkinter using Canvas. Now I want to generate a series of figures and put them in animated loop. I can generate a gif animation and load it into the figure but the question is if I can do this directly in tkinter. I can put everything in loop, but then I'll lose the event handler. Any other option?
Edit
Here is a simple script:
#!/usr/bin/python
from Tkinter import *
import ttk
import numpy as np
colors = {1:'#F00',
2:'#0F0',
3:'#00F',
4:'#FF0',
5:'#0FF'}
root = Tk()
canvas = Canvas(root,width=400,height=400)
label = Label(root,text='seed')
values = StringVar()
combo = ttk.Combobox(root)
def painter(event):
seed = int(combo.get())
np.random.seed(seed)
nrows = 10
ncols = 10
Y = 0
dx = 400/nrows
dy = 400/ncols
for i in range(nrows):
X = 0
for j in range(ncols):
n = np.random.randint(1,5)
col = colors[n]
canvas.create_rectangle(X,Y,X+dx,Y+dy,width=1,fill=col)
X += dx
Y += dy
canvas.grid(column=0,row=0,rowspan=2)
combo.grid(row=1,column=1)
label.grid(row=0,column=1)
combo['values'] = [1,2,3,4]
combo.bind('<<ComboboxSelected>>',painter)
combo.current(0)
painter(None)
root.mainloop()
I could change the painter function to include an infinite loop but this way I never reach the mainloop(), so I need to call this only after the main loop is created, but how?
a simple example of some animation using tkinter canvas:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Canvas animation example")
self.c = tk.Canvas(self, width=400, height=400)
self.c.pack()
self.f_index = 0 # index so we know which frame to draw next
# array to hold our frame data,
# you'll probably need this to hold more than
# just a set of coordinates to draw a line...
self.f_data = []
for num in range(0, 400, 5): # make up a set of fake data
self.f_data.append([num, num, num+10, num+10])
def next_frame(self):
data = self.f_data[self.f_index] # fetch frame data
self.c.delete('all') # clear canvas
self.c.create_line(*data) # draw new frame data
self.f_index += 1 # increment frame index
if (self.f_index >= len(self.f_data)): # check and wrap if at end of sequence
self.f_index = 0
self.c.after(50, self.next_frame) # call again after 50ms
if __name__ == "__main__":
app = App()
app.next_frame() # called manually once to start animation
# could be started with 'after' instead if desired
app.mainloop()
note that the more items you want to delete and redraw the slower this code will perform. you need to have realistic expectations, drawing a few 10's of items will be fine, a few 100's might be ok on a decent pc, but 1000's of items would be very poorly performing.

Tkinter: Identifying button by row and column

I want to be able to select a button based on what row and column it is in on a grid and the button and control its Text and Relief. I haven't been able to find anything on widgets or cells used in this manner.
Edit:
I changed where root is placed and now it says that I can't use a tuple that I recieved for 'relief' which makes sense, I need to access the widget itself. Any reccomendations
import tkinter
import functools
import random
from time import sleep
width = input('Enter the grid width. ')
height = input('Enter the grid height. ')
numb = input('Enter the number of bombs. ')
Matrix = [[0 for lp in range(int(width))] for fg in range(int(height))]
def ranintx():
return random.randint(0,int(width))
def raninty():
return random.randint(0,int(height))
def placemines():
y = ranintx()
x = raninty()
for ranintformine in range(int(numb)):
x = ranintx()
y = raninty()
Matrix[y-1][x-1] = 1
placemines()
def sunken(event, self, x, y):
button = event.widget
button['relief'] = 'sunken'
if x - 1 < 0 :
return
if x > int(width) + 1 :
return
if y - 1 < 0 :
return
if y > int(height) + 1 :
return
if Matrix[x][y] == 1 :
top = tkinter.Toplevel()
top.title("About this application...")
msg = tkinter.Message(top, text="You Lose")
msg.pack()
button = tkinter.Button(top, text="Dismiss", command=top.destroy)
button.pack()
print('Column = {}\nRow = {}'.format(x, y))
else:
n1 = x - 1
n2 = y - 1
for lp in range(3):
for lp2 in range(3):
abutton = root.grid_location(n1, n2)
abutton['relief'] = ['sunken']
# I want to be able to change and select the button here. This was one of my poor attempt
n2 =+ 1
n1 =+ 1
def push(event, self, x, y):
button = event.widget
if Matrix[x][y] == 1 :
print('Column = {}\nRow = {}'.format(x, y))
class MineSweep(tkinter.Frame):
#classmethod
def main(cls, width, height):
window = cls(root, width, height)
'''placemine()'''
root.mainloop()
def __init__(self, master, width, height):
super().__init__(master)
self.__width = width
self.__height = height
self.__build_buttons()
self.grid()
#def sunken(event):
# button = event.widget
# button['relief'] = 'sunken'
def __build_buttons(self):
self.__buttons = []
for y in range(self.__height):
row = []
for x in range(self.__width):
button = tkinter.Button(self, state='disabled')
button.grid(column=x, row=y)
button['text'] = ' '
print(grid.slaves)
self.checked = True
#button['command'] = functools.partial(self.__push, x, y)
button.bind("<Button-3>",
lambda event, arg=x, brg=y: push(event, self, arg, brg))
button['relief'] = 'raised'
button.bind("<Button-1>",
lambda event, arg=x, brg=y: sunken(event, self, arg, brg))
#button['command'] = sunken
row.append(button)
self.__buttons.append(row)
root = tkinter.Tk()
if __name__ == '__main__':
MineSweep.main(int(width), int(height))
You have a few things wrong with your program. First, sunken should be a method on the class. It's very weird to have it outside the class, and then you pass in self as some other argument. It works, but it makes the code very confusing.
That being said, you're actually very close to making this work. You're already saving a reference to each button in a list of lists, so you should be able to get the widget with self.__buttons[y][x]. However, because sunken is not part of the class, and because you named the variable with two underscores, the variable is not accessible to the sunken function.
If you change the variable to have a single underscore instead of a double, your code should work more-or-less exactly as it is (once you fix the syntax and indentation errors). The other solution is to make sunken a method on the class and fix how you call it (remove the self argument, call it as self.sunken), it will work with two underscores.
Frankly, using two underscores has zero practical benefit. Avoid the temptation to use it. At the very least, don't use it until you have your basic logic working, then you can go back and hide attributes you don't want to be exposed.

Categories