tkinter get widget that event got called by - python

Minimal reproducable Example
from tkinter import *
def test(event):
print(event.widget)
window = Tk()
window.geometry("600x600")
window.bind("<Motion>", test)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Motion>", test)
window.mainloop()
I want to call the function "test" from different widgets when i move over them. Instead of that, when i hover over the frame, both the window and the frame print that im hovering over the frame, which is not the behaviour i need. Can someone help me achieve the right behaviour?

I think your confusion is in the moment of interpreting when the mouse pointer entered the widget or left the widget. Instead of Motion let's use for example Leave and Enter events to better understand what happens.
I have taken the liberty of including some labels that show which widget we enter and left at each moment.
from tkinter import *
def test_enter(event):
enter.set(event.widget)
def test_left(event):
left.set(event.widget)
window = Tk()
window.geometry("300x300")
window.bind("<Enter>", test_enter)
window.bind("<Leave>", test_left)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Enter>", test_enter)
frame.bind("<Leave>", test_left)
label1 = Label(frame, text="Enter")
label1.place(relx=0.1, rely=0.4, x= 0.2, y=0.2)
enter = StringVar()
label_enter = Label(frame, textvariable=enter)
label_enter.place(relx=0.3, rely=0.4, x= 0.6, y=0.2)
label2 = Label(frame, text="Leave")
label2.place(relx=0.1, rely=0.6, x= 0.2, y=0.2)
left = StringVar()
label_left = Label(frame, textvariable=left)
label_left.place(relx=0.3, rely=0.6, x= 0.6, y=0.2)
window.mainloop()
Remember that the Frame is inside the Window. I mean that when you enter the Window you have not left root Window, you will continue to be in Window. Or if you enter inside a Label you have not left the Frame.

You could use lambda to pass the widget when the function is run
def test(event, widget):
print(widget)
window = Tk()
window.geometry("600x600")
window.bind("<Motion>", test)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Motion>", lambda: test(frame))
window.mainloop()
You just need to change two lines
def test(event, widget):
print(widget)
and
frame.bind("<Motion>", lambda: test(frame))

Related

Explanation on the tkinter layout settings to use to position to buttons side by side properly

For the first time, I need to use tkinter for a very small and simple GUI. I am trying to create a window with 2 buttons placed side by side. If I was to use matplotlib widgets, I would do:
from matplotlib import pyplot as plt
from matplotlib.widgets import Button
fig = plt.figure(figsize=(5, 2))
# Start button
start_button_ax = fig.add_axes([0.1, 0.2, 0.3, 0.6])
start_button = Button(start_button_ax, 'Start MML', color='lightgoldenrodyellow', hovercolor='0.975')
def start_button_on_clicked(mouse_event):
pass
start_button.on_clicked(start_button_on_clicked)
# Stop button
stop_button_ax = fig.add_axes([0.6, 0.2, 0.3, 0.6])
stop_button = Button(stop_button_ax, 'Stop MML', color='lightgoldenrodyellow', hovercolor='0.975')
def stop_button_on_clicked(mouse_event):
pass
stop_button.on_clicked(stop_button_on_clicked)
Result:
Very simple stuff. With tkinter, I have been struggling for the past hour trying to move around and resize the 2 buttons. Here is my current code:
import tkinter as tk
def start():
global running
running = True
def stop():
global running
running = False
windowWidth = 200
windowHeight = 100
root = tk.Tk()
root.title("Minimum Masking Level")
root.geometry(f'{windowWidth}x{windowHeight}+{int(root.winfo_screenwidth()/2 - windowWidth/2)}+{int(root.winfo_screenheight()/3 - windowHeight/2)}')
start = tk.Button(root, text="Start counting", command=start)
start.place(x=20, y=20)
stop = tk.Button(root, text="Stop counting", command=stop)
stop.place(x=110, y=20)
root.mainloop()
I tried using .config(width=, height=) to resize the button.. but that was clearly not the way to go. Could someone help me produce the desired output and explain a bit how the Tkinter coordinate system/positioning/resizing of widgets work?
Edit following comments: Trying to add width and height at the creation of the button:
start = tk.Button(root, text="Start counting", command=start, width=70, height=60)
start.place(x=20, y=20)
Output:
Yet the window should have a width of 200 and height of 100. 20 + 70 should easily fit.
Look at this:
import tkinter as tk
def start():
global running
running = True
def stop():
global running
running = False
windowWidth = 200
windowHeight = 100
root = tk.Tk()
root.title("Minimum Masking Level")
root.geometry(f'{windowWidth}x{windowHeight}+{int(root.winfo_screenwidth()/2 - windowWidth/2)}+{int(root.winfo_screenheight()/3 - windowHeight/2)}')
start = tk.Button(root, text="Start counting", command=start)
start.pack(expand=True, fill="both", padx=5, pady=10, side="left")
stop = tk.Button(root, text="Stop counting", command=stop, width=50, height=2)
stop.pack(expand=True, fill="both", padx=5, pady=10, side="right")
root.mainloop()
Also try changing the values for padx/pady. The documentation is here. You can change the size of the text inside the button by putting font=("", <size as in>) inside the button definition.
Take a look here, because using grid() is more accurate, because if you have more than one widget, it will get hard to understand what to do. With grid its just rows and columns:
start_b = tk.Button(root, text="Start counting",command=start)
start_b.grid(row=0,column=0,padx=20)
stop_b = tk.Button(root, text="Stop counting", command=stop)
stop_b.grid(row=0,column=1,padx=20)
If you are wondering why some of the button is cut out, it is because you are setting your own geometry to window and it is smaller than that of the buttons. Anyway notice that I renamed your buttons to not be as same as the function name, as it might cause issues later. padx adds some space to both the sides along x axis to the widget. Similary, there is pady too. Also there is ipady and ipadx which is to give space inside the widget or make it grow in size.

Python tkinter how to zoom in widgets

My code:
import tkinter as tk
root = tk.Tk()
for i in range(50):
for j in range(50):
tk.Button(height=1, width=2, bg='Blue').grid(row=j, column=i)
root.mainloop()
I can not see all of the buttons in the screen even when I maxmize the window. so I want to add an option to zoom out (all of the widgets will be smaller) so I can see all of them. How do I do that?
Example code:
import tkinter as tk
root = tk.Tk()
root.state('zoomed')
widgets_to_zoom_list = []
DEFAULT_SIZE = 50
def zoom(widget):
for every_widget in widgets_to_zoom_list:
every_widget.config(width=widget.get(), height=widget.get())
def main():
canvas = tk.Canvas(root)
frame = tk.Frame(canvas)
zoom_scale = tk.Scale(root, orient='vertical', from_=1, to=100)
zoom_scale.config(command=lambda args: zoom(zoom_scale))
zoom_scale.set(DEFAULT_SIZE)
pixel = tk.PhotoImage(width=1, height=1)
for i in range(50):
btn = tk.Button(frame, text=str(i + 1), bg='Blue', image=pixel, width=DEFAULT_SIZE, height=DEFAULT_SIZE, compound="c")
btn.grid(row=0, column=i)
widgets_to_zoom_list.append(btn)
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scroll region
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'))
canvas.pack(fill='both', side='left', expand=True)
zoom_scale.pack(fill='y', side='right')
root.mainloop()
if __name__ == '__main__':
main()
From what i have been able to understand from your question, i think you want the window to be resizable in tkinter.
To allow a window to be resized by the user, we use the resizable function -:
root.resizable(height = True, width = True)
In this case the two args are height and width which help you customize if you only want the widget to be vertically resizable or only want it to be horizontally resizable.
Your code with this function added should look like this -:
import tkinter as tk
root = tk.Tk()
root.resizable(True, True) #Assuming you want both vertically and horizontally resizable window.
for i in range(50):
for j in range(50):
tk.Button(height=1, width=2, bg='Blue').grid(row=j, column=i)
root.mainloop()
I hope this will solve your problem.
And I also hope you are safe in this time of an ongoing pandemic.

Why is Tkinter not displaying Button?

import Tkinter as ass
root = ass.Tk()
frame = ass.Frame(root)
button1 = ass.Button(frame, command=button1(), text='Kushagra', width=50, height=40)
button1.pack(side=ass.LEFT)
root.mainloop()
button1() is a function I made which i don't think is relevant. After I run this all I get is a blank window. How do I fix this?
You didn't pack the frame widget after creating the frame. The below is the edited code
import tkinter as ass
root = ass.Tk()
frame = ass.Frame(root)
frame.pack()
button1 = ass.Button(frame, text="QUIT")
button1.pack(side=ass.LEFT)
root.mainloop()
You have to pack the frame if you want it to be displayed. This will let the button to be shown but the function button1() won't work as you want because it would be called when it is given to the Button as a command, As you have used parenthesis () after giving it to the button as a command. You just need to remove those parentheses.
Like this:
import tkinter as ass
def button1():
return
root = ass.Tk()
frame = ass.Frame(root)
frame.pack()
button1 = ass.Button(frame, command=button1, text='Kushagra', width=50, height=40)
button1.pack(side=ass.LEFT)
root.mainloop()
And if you need to pass arguments to any function then you should use lambda before giving it as a command to any Button.
Like this:
import tkinter as ass
def test(a):
print(a)
root = ass.Tk()
frame = ass.Frame(root)
frame.pack()
button1 = ass.Button(frame, command=lambda: test(1), text='Kushagra', width=50, height=40)
button1.pack(side=ass.LEFT)
root.mainloop()

Adding buttons in TkInter on pressing button

I am having button and on pressing it I want to create new Button and new Label.
Label must have random color and must change it on pressing this button to another random color.
My code even can not add buttons correctly, there is problems with placing new(sizes are strange).
How can I improve this? And how can I later create func for new buttons which will change their label's colours, cause I dont have label's names.
import random
from tkinter import *
def color(*args):
pass
def dump( *args):
global count
Butt = Button(root, text="color ", command=color)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=count)
Txt = Label(root, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))
Txt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Txt.grid(row=1, column=count)
count+=1
root.mainloop()
count=2
TKroot = Tk()
TKroot.title("Hello")
root = Frame(TKroot)
root.place(relx=0, rely=0, relheight=1, relwidth=1)
root.columnconfigure(0, weight=10)
root.columnconfigure(1, weight=10)
root.rowconfigure(0, weight=10)
root.rowconfigure(1, weight=10)
Butt = Button(root, text="Butt ON")
Butt.bind('<Button-1>', dump)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=0)
Exit = Button(root, text="Quit!", command=root.quit)
Exit.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Exit.grid(row=0, column=1)
Txt = Label(root, text="This is a label", bg="PeachPuff")
Txt.grid(row=1, column=1, columnspan=1)
TKroot.mainloop()
print("Done")
I see a few issues with your code.
1st is you are using place for your frame.
This is going to cause issues when adding new buttons as it will not allow the window to resize correctly with the new layout.
2nd is how you are writing your code. You name your frame root and use the quit method on the frame and not on your actually root window. The way you are writing things makes it harder to follow so consider following PEP8 guidelines when writing your code.
3rd you are trying to apply mainloop to your frame in the dump function. You only ever need 1 instance of mainloop and this applies to the actual root window (Tk()).
To address your question on how to change the label color later on I would use a list to store your buttons and labels. This way we can reference their index values and apply your random color code to the labels on button click.
I have re-written most of your code to follow PEP8 and done some general clean up.
Let me know if you have any questions.
import tkinter as tk
import random
def color(ndex):
button_label_list[ndex][1].config(bg="#%06x" % random.randint(0, 16777215))
def dump():
global count, button_label_list
button_label_list.append([tk.Button(frame, text="color", command=lambda x=count: color(x)),
tk.Label(frame, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))])
button_label_list[-1][0].grid(row=0, column=count, sticky='nsew')
button_label_list[-1][1].grid(row=1, column=count, sticky='nsew')
frame.columnconfigure(count, weight=1)
count += 1
root = tk.Tk()
count = 0
button_label_list = []
root.title("Hello")
root.rowconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
frame = tk.Frame(root)
frame.rowconfigure(1, weight=1)
frame.grid(row=0, column=2, sticky='nsew', rowspan=2)
tk.Button(root, text="butt ON", command=dump).grid(row=0, column=0, sticky='nsew')
tk.Button(root, text="Quit!", command=root.quit).grid(row=0, column=1, sticky='nsew')
tk.Label(root, text="This is a label", bg="PeachPuff").grid(row=1, column=1, columnspan=1, sticky='nsew')
root.mainloop()
Results:
A window that can add new buttons and be able to change colors on each label. The main 2 buttons the window starts with are static in that they cannot be pushed out of the window like in you code example and will remain on the left anchored in place.
below an object oriented version.
Every time you press on Color button, you create a new label and a new button
and put label reference in a dictionary.
The color of the label is randomly generate.
After creation if we click on a new button we change the relative label color.
The coolest part of the script is:
command=lambda which=self.count: self.change_color(which)
lambda funcion it's used to keep a reference to the button and label just
create when we call the change_color function.
import tkinter as tk
import random
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.count = 0
self.labels = {}
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Button(w, text="Color", command=self.callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def callback(self):
text_label = "I'm the {} label".format(self.count)
text_button = "I'm the {} button".format(self.count)
color = "#" + ("%06x" % random.randint(0, 16777215))
obj = tk.Label(self.f, text=text_label, bg=color)
obj.pack()
self.labels[self.count]=obj
tk.Button(self.f,
text=text_button,
command=lambda which=self.count: self.change_color(which)).pack()
self.count +=1
def change_color(self,which):
color = "#" + ("%06x" % random.randint(0, 16777215))
self.labels[which].config(bg=color)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

How to make a button open a new window?

I am making a simple GUI that starts with a main menu them the user can click a button to proceed to a new window which has a picture of a keyboard and the user can press key on their keyboard to play the paino. Right now I cant figure out how to make a button that when pressed closes the main menu (labeled mainMenu()) and open the game menu (playGame).
import tkinter
from tkinter import *
class mainMenu:
def _init_(self, master):
frame = Frame(master)
frame.pack()
self.quitButton = Button(frame, text = "Quit", command = frame.quit)
self.quitButton.pack(side = LEFT)
self.proceedButton = Button(frame, text = "Play", command = playGame)
self.proceedButton.pack(side = LEFT)
def playGame(self):
frame.quit
gameMenu()
def gameMenu(self):
root = Tk()
b = mainMenu(root)
topFrame = Frame(root)
topFrame.pack()
bottomFrame = Frame(root)
bottomeFrame.pack(side = BOTTOM)
photo = PhotoImage(file = "piano.png")
label = Label(root, image = photo)
label.pack()
root.mainloop()
You'll have to forgive me for removing your class but I've never personally worked with classes in python before. However I seem to have you code working to some degree.
import tkinter
from tkinter import *
def playGame():
frame.quit
gameMenu()
def gameMenu():
b = mainMenu(root)
topFrame = Frame(root)
topFrame.pack()
bottomFrame = Frame(root)
bottomFrame.pack(side = BOTTOM)
photo = PhotoImage(file = "piano.png")
label = Label(root, image = photo)
label.pack()
root=Tk()
frame = Frame(root)
frame.pack()
quitButton = Button(frame, text = "Quit", command = frame.quit)
quitButton.pack(side = LEFT)
proceedButton = Button(frame, text = "Play", command = playGame)
proceedButton.pack(side = LEFT)
root.mainloop()
The main problem you had was that you were using both root and master. When declaring the main window in tkinter you normally use either root = Tk() or master = Tk() either one is acceptable, personally I use master. This variable contains the main window that everything else is placed into. You also hadn't put Tk() into any variable, meaning that when you hit root.mainloop() there was nothing to enter the main loop, this was because you were trying to declare root = Tk() inside gameMenu, which wasn't getting called in your program.
If you want to open windows within tkinter it's probably easier to write something like this:
from tkinter import *
master = Tk() #Declaring of main window
def ProceedButtonCommand(mainframe, master): #Command to attach to proceed button
mainframe.destroy()
DrawSecondScreen(master) #This line is what lets the command tied to the button call up the second screen
def QuitButtonCommand(master):
master.destroy()
def DrawFirstScreen(master):
mainframe = Frame(master) #This is a way to semi-cheat when drawing new screens, destroying a frame below master frame clears everything from the screen without having to redraw the window, giving the illusion of one seamless transition
ProceedButton = Button(mainframe, text="Proceed", command=lambda: ProceedButtonCommand(mainframe, master)) #Lambda just allows you to pass variables with the command
QuitButton = Button(mainframe, text = "Quit", command=lambda: QuitButtonCommand(master))
mainframe.pack()
ProceedButton.pack()
QuitButton.pack()
def DrawSecondScreen(master):
mainframe = Frame(master)
Label1 = Label(mainframe, text="Temp")
mainframe.pack()
Label1.pack()
DrawFirstScreen(master)
master.mainloop() #The mainloop handles all the events that occur in a tkinter window, from button pressing to the commands that a button runs, very important
This little script just draws a screen with two buttons, one draws a new screen with the text "temp" on it and the other button closes the master window.
In the future it's probably a better idea to ask a friend who is experienced in programming to help with this kind of stuff. Get talking on some computing forums, I'm sure you'll find a group of sharing and fixing code quickly.

Categories