How can i nest a function inside an outer function? - python

I'm trying to design a function that will display the coordinates of the canvas so i wrote this function inside another function which is the canvas for the solar system simulation. Here is my code:
def solar_system():
top2 = Toplevel()
top2.title("Solar system simulation") #window title
canvas = tk.Canvas(top2, width=1500, height=900, bg = "black") #making the canvas
canvas.pack()
def display_xy_coordinates(event): #function for displaying x and y
widgetxy["text"]=f"x,{event.x} y,{event.y}"
display_xy_coordinates(event)
widgetxy = Label(canvas, font = "Calibri 10", fg = "white", bg = "black") #colour and font for label showing xy coordinates
canvas.bind("<Button-1>", display_xy_coordinates) #Buton-1 = left click on the mouse. This calls the display xy coordinates function
widgetxy.pack() #this label needs to be packed in order to show
canvas.create_window(1267, 34, window=widgetxy) #this is where the label will be placed (top right of canvas)
I have tried every possible way of writing this function inside the main function but i always get this error:
display_xy_coordinates(event)
^^^^^
NameError: name 'event' is not defined

The error is with this line: display_xy_coordinates(event). Like the error says, you haven't defined event so you can't pass this non-existing variable to the function.
The simple solution is to remove that line of code. That function appears to be designed to work only when being called from a binding which automatically passes the event parameter, so it makes no sense to call it outside of that context. After all, it makes no sense to print the x,y coordinate of a click if the user hasn't clicked anywhere.

Related

How to make an object appear in the canvas when you left click in python?

So what I want is to get an oval onto the canvas of my python work. ( Which is a photo editor project ). "c" refers to the canvas I've made and shaped into a tkinter program in python. How do I make the following code to make an oval pop up in the canvas of my program?( Also if you know how to do a mouse down event please change the "<Button-1>" to the appropriate tag ):
def PaintBrushWorking():
blueBlob = c.create_oval(20, 30, 40, 60, fill = "blue")
blueBlob.pack()
c.bind_all("<Button-1>", PaintBrushWorking)
You just need to remove the call to pack, and then make your function accept an event argument. And finally, you probably want to use bind rather than bind_all, unless you really want it to draw the oval even if you click on some other widget such as a button or scrollbar.
import Tkinter as tk
def PaintBrushWorking(event):
blueBlob = c.create_oval(20, 30, 40, 60, fill="blue")
root = tk.Tk()
c = tk.Canvas()
c.pack(fill="both", expand=True)
c.bind("<Button-1>", PaintBrushWorking)
root.mainloop()

Find a widget in a window by coordinates

I make a window with tkinter and I want to check if a Label is at the coordinates relx = 0.3 and rely = 0.63. Is there any function which I could use?
I already tried to use the nametowidget function, but there I have to give every widget I have a name.
So, the widget I try to get is moving in the window and if it reaches the coordinates I want to move it in another way, but I dont know any function which I could use
You can use winfo_x/y to get the label coordinates and winfo_width/height to get the dimension of the window to compute relative coordinates. I made a little example where the goal is to resize the window to get the right relative coordinates:
import tkinter as tk
def check_label_relpos():
relx = l.winfo_x()/parent.winfo_width()
rely = l.winfo_y()/parent.winfo_height()
if (abs(relx - 0.3) < 0.02) and (abs(rely - 0.63) < 0.02):
print("Ok")
else:
print(relx, rely, "Try again")
parent = tk.Tk()
l = tk.Label(parent, text="Label text", bg="red")
l.place(x=50, y=160)
tk.Button(parent, text="Check label relpos", command=check_label_relpos).place(relx=0.5, rely=1, anchor="s")
parent.mainloop()

Python Tkinter Label not updating in new window

So I've got a function that opens a new window. In this window I am trying to update a Label, when I use textvariable it doesn't update and the label always stays blank. With just text, the label will show the text.
My textvariable's work in my main window but not in this one and I have no idea why.
def Manage():
PropsP1 = Tk()
area = Canvas(PropsP1, width = 920, height = 970)
area.pack()
MedCR = StringVar()
MedO = 1
count = 1
MedR = 4
if MedO == count:
MedCRLabel = Label(PropsP1, textvariable=MedCR, bg = "White")
MedCRLabel.place(x = 15, y = 65)
MedCR.set("Current Rent: "+str(MedR))
This is the function, I've tried making multiple Labels and none display anything with textvariable. I can see a white square for the label so I know it is showing up but there is no text.
The problem is that you are creating a new instance of Tk. A tkinter application should only ever create a single instance of Tk, and call mainloop exactly once. To create a popup window, create an instance of Toplevel.

How to change the text of the last clicked button in tkinter python

I have dynamically created a buttons by looping through a list:
AuxName = Button(GUI_1, text=List_Aux[x], font=("Arial", 10))
tk_rgb = "#%02x%02x%02x" % (0,0,0)
AuxName["fg"] = tk_rgb
tk_rgb = "#%02x%02x%02x" % (255,255,255)
AuxName["bg"] = tk_rgb
AuxName.place(x=BUTTON_LEFT_AUX1, y=BUTTON_TOP_AUX1 , height=BUTTON_HEIGTH_AUX1, width=BUTTON_WIDTH_AUX1)
AuxName["command"] = ButtonClick
Each button has a different AuxName value (1btn1, 1btn2, 1btn3, etc) numbers being the column and the row where each button is located.
The problem I'm having is that now I want the function ButtonClick to get the name of the button being clicked.
The last thing I tried was:
def ButtonClick():
1btn1["text"] = "Goodbye"
But python throws a syntax error (and points just before the opening bracket).
How could I get the last clicked button name, and then change its text through the function ButtonClick()?
-------------------- Edit to the first answer, almost working perfectly now ------------
Thank you for the answer, I appreciate it very much, and makes it work almost perfect.
There are two flaws, first of them is it will throw an error because of the command ButtonClick being sent with no argument, which is the default way to send commands of the button binds, so I dont really know how to fix that...
And the second is, after using the code you provided me, buttons text will change as you especified, but they wont respect the position and size previously defined by me.
BUTTON_TOP_AUX1 = 20
BUTTON_WIDTH_AUX1 = 400
BUTTON_LEFT_AUX1 = 5
BUTTON_HEIGTH_AUX1 = 20
Buttons_Function = "DesiredFunctionOne"
BUTTON_COLUMN_AUX1 = 1
List_Size_Aux = len(List_Aux)
for x in range ( List_Size_Aux ):
AuxName = str(BUTTON_COLUMN_AUX1)+'btn'+str(x)
AuxName = Button(GUI_1, text=List_Aux[x], font=("Arial", 10))
tk_rgb = "#%02x%02x%02x" % (0,0,0)
AuxName["fg"] = tk_rgb
tk_rgb = "#%02x%02x%02x" % (255,255,255)
AuxName["bg"] = tk_rgb
AuxName.place(x=BUTTON_LEFT_AUX1, y=BUTTON_TOP_AUX1 , height=BUTTON_HEIGTH_AUX1, width=BUTTON_WIDTH_AUX1)
AuxName["command"] = ButtonClick
BUTTON_TOP_AUX1 = BUTTON_HEIGTH_AUX1+BUTTON_TOP_AUX1
AuxName.bind("<Button-1>", ButtonClick)
AuxName.pack()
Could you please tell me how to fix those?
Greatly appreciated :)
First you have to bind the function to an event, instead of using the command attribute. For a left click on a widget, remove the line
And use the event <Button-1> instead:
AuxName.bind("<Button-1>", ButtonClick)
Then in the event handler you receive an event argument. It has a widget field, so you can get the widget which triggered the event:
def ButtonClick(event):
event.widget['text'] = 'Goodbye'
Remembar that you have to remove the AuxName['command'] = ButtonClick, because if you don't do so, ButtonClick will be executed two times (one of them without any argument).
When you change the text of the Button, it will modify also its width since the new text may have a different lenght. One problem you have in respect to the position of the widgets is that you are using both place and pack as geometry managers:
AuxName.place(...)
# ...
AuxName.pack()
In Tkinter, you should use consistently one geometry manager. In this case, I suggest you to use the grid geometry manager, which is the easiest to use:
AuxName.grid(row=x, column=y, ...)

How do I create a Tiling layout / Flow layout in TkInter?

I want to to fill my window with, say, labels and I want them to wrap once the column would be bigger than the current window (or rather parent frame) size.
I've tried using the grid layout, but then I have to calculate the size of the content of each row myself, to know when to put the next element in the next row.
The reason I ask, is because I want to create some sort of tiled file icons.
Or asked differently, is there something like Swing's FlowLayout for TkInter?
What I do when I want something like this is use the text widget for a container. The text widget can have embedded widgets, and they wrap just like text. As long as your widgets are all the same height the effect is pretty nice.
For example (cut and pasted from the question at the author's request):
textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
Here is a way to make flow behavior inside a frame.
I wrote a function that will do this. Basically you pass a frame to the function (not root or top level) and the function will look at all the children of the frame, go through them measure their sizes and place them in the frame.
Here is the placement procedure
Place the first widget, and move x over an amount equal to its width.
Measure the next widget.
If placing the next widget would cause it to goes past the frame width, bump its x value to 0 and bump it down a y value equal to the largest widget in the current row (start a new row).
Reset the value of the largest widget since you are starting a new row.
Keep repeating until all widgets are placed.
Bind that procedure to the resizing of the frame event.
I used 3 functions to make this work:
The function that runs the procedure.
The function that binds the resizing of the frame to the function.
The function that unbinds the resizing of the frame.
Here are the functions:
from tkinter import *
def _reorganizeWidgetsWithPlace(frame):
widgetsFrame = frame
widgetDictionary = widgetsFrame.children
widgetKeys = [] # keys in key value pairs of the childwidgets
for key in widgetDictionary:
widgetKeys.append(key)
# initialization/priming loop
width = 0
i = 0
x = 0
y = 0
height = 0
maxheight = 0
# loop/algorithm for sorting
while i < len(widgetDictionary):
height = widgetDictionary[widgetKeys[i]].winfo_height()
if height > maxheight:
maxheight = height
width = width + widgetDictionary[widgetKeys[i]].winfo_width()
# always place first widget at 0,0
if i == 0:
x = 0
y = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
# if after adding width, this exceeds the frame width, bump
# widget down. Use maximimum height so far to bump down
# set x at 0 and start over with new row, reset maxheight
elif width > widgetsFrame.winfo_width():
y = y + maxheight
x = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
maxheight = height
# if after adding width, the widget row length does not exceed
# frame with, add the widget at the start of last widget's
# x value
else:
x = width-widgetDictionary[widgetKeys[i]].winfo_width()
# place the widget at the determined x value
widgetDictionary[widgetKeys[i]].place(x=x, y=y)
i += 1
widgetsFrame.update()
def organizeWidgetsWithPlace(frame):
_reorganizeWidgetsWithPlace(frame)
frame.bind("<Configure>", lambda event: _reorganizeWidgetsWithPlace(frame))
_reorganizeWidgetsWithPlace(frame)
def stopOrganizingWidgetsWithPlace(frame):
frame.unbind("<Configure>")
And here is an example of them in use:
def main():
root = Tk()
root.geometry("250x250")
myframe = Frame(root)
# make sure frame expands to fill parent window
myframe.pack(fill="both", expand=1)
buttonOrganize = Button(myframe, text='start organizing',
command=lambda: organizeWidgetsWithPlace(myframe))
buttonOrganize.pack()
buttonStopOrganize = Button(myframe, text='stop organizing',
command=lambda: stopOrganizingWidgetsWithPlace(myframe))
buttonStopOrganize.pack()
##### a bunch of widgets #####
button = Button(myframe, text="---a random Button---")
canvas = Canvas(myframe, width=80, height=20, bg="orange")
checkbutton = Checkbutton(myframe, text="---checkbutton----")
entry = Entry(myframe, text="entry")
label = Label(myframe, text="Label", height=4, width=20)
listbox = Listbox(myframe, height=3, width=20)
message = Message(myframe, text="hello from Message")
radioButton = Radiobutton(myframe, text="radio button")
scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
scrollbar = Scrollbar(myframe)
textbox = Text(myframe, width=3, height=2)
textbox.insert(END, "Text Widget")
spinbox = Spinbox(myframe, from_=0, to=10)
root.mainloop()
main()
Notice:
That you do not need to grid, pack or place them. As long as you specify the frame, that will all be done at once when the function is called. So that is very convenient. And it can be annoying if you grid a widget, then try to pack another, then try to place another and you get that error that you can only use one geometry manager. I believe this will simply overwrite the previous choices and place them. I believe you can just drop this function in and it will take over management. So far that has always worked for me, but I think you should really not try to mix and match geometry managers.
Notice that initially the buttons are packed, but after pressing the button, they are placed.
I have added the "WithPlace" naming to the functions because I have a similar set of functions that do something very similar with the grid manager.

Categories

Resources