Drag button inside a field to a set boundary - python

It's been a number of years since I asked for help. I'm creating a number of drag and drop buttons inside a frame. When the button is released It triggers that works fine. I have set up a boundary inside the button_drag().The problem I'm having is if the button is dragged right up to the boundary the button fails to trigger. Of course code is worth a thousand words so I made up a small code which shows the approach i'm taking using just one button. I have tried placing the button inside it's own canvas and fiddled around with mouse Info. I hope someone can help. thanks
root = Tk()
root.geometry("300x300")
frame = Frame(root,bg="grey40")
frame.config(width=200,height = 200)
frame.pack()
frame.place(x=150, y=150, anchor=CENTER)
def button_push():
print("Button Pressed")
def button_drag(event):
global Yevent
x = event.x + event.widget.winfo_x()
y = event.y + event.widget.winfo_y()
Yevent = y
if y > 180:
y = 180
event.widget.place(x=x, y=y, anchor="s")
button = Button(frame, text="button1", font='TkDefaultFont', fg="slate grey",command=lambda: button_push())
button.place(x=50,y=50)
button.bind("<B1-Motion>",button_drag)
root.mainloop()

The normal behavior of buttons is to not call their command if you release the mouse button when the cursor is not over the button. This allows the user to prevent the button from being clicked if they change their mind after pressing the button.
If you want the button to be triggered no matter where the cursor is when the user releases the button, you'll have to add your own binding.
You'll want to have your binding return the string "break" so that the default behavior doesn't also cause the button to trigger.
button.bind("<ButtonRelease-1>", lambda event: event.widget.invoke() and "break")

Related

Want to change color of 100 buttons on hover in tkinter

import tkinter as tk
def on_enter(e):
year_btn.config(background="orange",foreground="white")
def on_leave(e):
year_btn.config(background="white", foreground="black")
window = tk.Tk()
yearnumber=1
for i in range(10):
window.rowconfigure(i,weight=1,minsize=40)
window.columnconfigure(i,weight=1,minsize=40)
for j in range(10):
frame = tk.Frame(padx=5,pady=5)
frame.grid(row=i,column=j,sticky="nsew")
year_btn = tk.Button(text=f"{yearnumber}", master=frame, activebackground="red", activeforeground="white")
year_btn.pack(padx=1, pady=1,fill="both",expand="true")
#year_btn.grid(sticky="nsew")
yearnumber+=1
year_btn.bind('<Enter>', on_enter)
year_btn.bind('<Leave>',on_leave)
window.mainloop()
So, I created hundred buttons over here and wanted them to change color when the mouse hovers over them, I did this as per the internet to create events and bind them with the buttons.
My problem is I created hundred buttons using for-loop, so I added the binding code in the loop. The result of this was that if I hover the mouse over any Button only the 100th hover changes color. I also placed the hovering code outside the loop but that does nothing
How do I change color of button over hover for each button in this case.
Thank you
The event object that is passed to the bound function has a reference to the widget that received the event, under the attribute widget. You can use that to change the attribute of the button.
def on_enter(e):
e.widget.config(background="orange",foreground="white")
#^^^^^^^
def on_leave(e):
e.widget.config(background="white", foreground="black")
#^^^^^^^

Can't type text into tkinter entry widget (python)

I'm using tkinter to get a file path to open and then ask the user two questions, name via a text entry widget and course via some radiobuttons. The code for the radio buttons I got from the answer to this thread and it worked great:
How do I display a dialog that asks the user multi-choice question using tkInter?
Then I decided to add the Entry widget to get the answer to the name question and have been struggling. I've got it working, but with bugs:
The text entry field comes pre-populated and when clicked it clears (it's meant to do this) but the cursor doesn't appear and I can't enter new text in the entry. If I click off the tkinter window and then back onto it the cursor appears and I can enter text as intended
The top radio button appears pre-selected, except it isn't. The validate function says no radio button is selected. I can select another option and it works, but I can't select the first option, even if I click off the first option and then back on to it. EDIT: This problem was caused by my validate function, which discarded 0 values i.e. the top radio button. Thanks #acw1668 for the clue to solve this one. I've now set the starting value to -1 (so nothing is selected) and the validate function checks for -1
I can't figure out where to go from here. I've tried using e1.focus() but it made no difference. I've tried running the questions window before the fileopen dialog but that really confused it. I've tried commenting out the clear entry function in case that was causing the problem but the behaviour was basically the same. EDIT: I'm using Spyder from Anaconda 3 as my IDE, I'm wondering if this is causing problem 1 as #acw1668 couldn't replicate it.
Here is my code:
root=tk.Tk()
csvfp=tk.filedialog.askopenfilename()
SName, ProgSel=ask_multiple_choice_question(root, LPrograms)
def ask_multiple_choice_question(root, options):
#https://stackoverflow.com/questions/42581016/how-do-i-display-a-dialog-that-asks-the-user-multi-choice-question-using-tkinter
#Define a function to clear the contents of the textbox when it is clicked
def click(event):
e1.configure(state=tk.NORMAL)
e1.delete(0,tk.END)
e1.unbind('<Button-1>',clicked)
#checks entries are valid when submit button is clicked, then closes window
def validate():
if v.get() == 0 or v2.get() == "" or v2.get()==starttext:
return None
root.destroy()
frame1=Frame(root)
frame1.pack(padx=20,pady=10)
frame2=Frame(root)
frame2.pack()
frame3=Frame(root)
frame3.pack(pady=10)
Label(frame1, text="Student name?").pack()
v2=tk.StringVar()
e1 = Entry(frame1, width=30, textvariable=v2)
starttext="Surname, Firstname (ID)"
e1.insert(0, starttext)
e1.pack()
#bind e1 with mouse button to clear contents on click
clicked=e1.bind('<Button-1>',click)
Label(frame2, text="Program?").pack()
v = IntVar()
for i, option in enumerate(options):
Radiobutton(frame2, text=option, variable=v, value=i).pack(anchor="w")
Button(frame3, text="Submit", command=validate).pack()
root.mainloop()
return v2.get(), options[v.get()]
Thanks for any clues!
For issue #1, you need to show the file dialog after the main window is ready and visible. You can call root.wait_visibility() after root = tk.Tk():
...
root = tk.Tk()
root.wait_visibility()
...
For issue #2, since the value option of the first radio button is 0 and the initial value of its tkinter variable v is default 0, so the first radio button will be selected. Set the initial value of v to -1 will fix it:
...
def validate():
# check -1 instead of 0
if v.get() == -1 or v2.get() == "" or v2.get() == starttext:
return None
root.destroy()
...
v = tk.IntVar(value=-1)
...

Tkinter scale gets stuck each time a function is called

I have a problem where the tkinter Scale widget seems to get stuck whenever I run a seemingly big function.
This is the code:
from tkinter import Tk, Button, Frame, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
frame = Frame(root)
frame.pack()
num = 0
def buttons():
for widget in frame.winfo_children():
widget.destroy()
for i in range(50):
Button(frame, text='Button' + str(i)).pack()
def basic():
global num
slider.set(num)
num += 1
print(num)
if num <= 100:
slider.after(100, basic)
if __name__ == '__main__':
buttons()
basic()
root.bind('<space>', lambda x: buttons())
root.mainloop()
What I want my program to do is update the slider normally even when I press 'Space' (meaning calling the buttons() function)
If you watch closely each time you press Space the slider will get stuck a little.
Since I'm using the slider for an Mp3 player in order to show time elapsed, this loss of time is extremely important for example for audio files of 10 or so seconds since the slider falls behind a lot making it seem as if it's working wrong \
I'd also like to point out that destroying the buttons and then repacking them is necessary for me.
I suspect that this happens because the program has to go over the buttons() function something that takes time since it's creating 50 buttons. Or am I mistaken?
Can I avoid that lag?
PS: As I mentioned in my comment:
I normally have a button that renames a (button) which is a song and in order for them to alphabetically ordered after renaming i need to recall the function that draws them. If I only configure tha name of the button (and not redraw them), it will stay in place and not move down or up depending on its name, while on the actual directory the order will change leading to inappropriate behavior such as playing the same song
Here are some images for better understanding:
Thanks in advance!
Look at this code:
import tkinter as tk
def config_buttons():
# Get the `text` of the first button
starting_value = int(buttons[0].cget("text")) + 1
# Iterate over all of the buttons
for i, button in enumerate(buttons, start=starting_value):
# Change the button's `text` and `command` atributes
button.config(text=i, command=lambda i=i:print("Clicked %i"%i))
root = tk.Tk()
buttons = []
add_button = tk.Button(root, text="+1 on all buttons", command=config_buttons)
add_button.pack()
for i in range(50):
button = tk.Button(root, text=i, command=lambda i=i:print("Clicked %i"%i))
button.pack()
buttons.append(button)
root.mainloop()
When the add_button buttons is pressed, I iterate over all of the buttons and change their text and command attributes. As I am not creating new buttons, the function runs very fast.
You can implement something similar in your code. Basically, avoid creating new buttons and just update the ones you already have on the screen.

having trouble with bind function in python gui tkinter

This code is a tutorial from youtube. The given code is supposed to print right on the right click of the mouse on the console and same for the left click but it is not doing so. I think the problem is with the bind function.
I am using python 3.7 which already have tkinter package in it, what can I do to make it work, thank you very much.
from tkinter import *
root = Tk()
def leftclick(event):
print("left")
def rightclick(event):
print("right")
frame = Frame(root, width=300, height=300)
frame.bind("button-1", leftclick)
frame.bind("button-2", rightclick)
frame.pack()
root.mainloop()
I expect the program to print 'left' in the console on the left click of the mouse inside the tk window and same for right click
First, as already noted in comments, the mouse button events need <...>. About the rightclick not working: This is because the even for the right mouse button is actually <Button-3>, whereas <Button-2> is the middle mouse button (or pressing down on the mouse wheel).
frame.bind("<Button-1>", leftclick)
frame.bind("<Button-3>", rightclick)
This may be a bit unintuitive if you think of the right mouse button as the "secondary" button, but makes sense if you just enumerate the buttons from left to right. This is AFAIK also consistent with all (most?) other UI frameworks and languages.

Simple bind function doesn't work in python

I'm currently creating an adventure game and I want to bind alt+a to my callback. It doesn't do what I want, so I have two questions:
Is it possible to bind a function to a Label, too?
Why does this (simplyfied) code doesn't work?
Here is the code:
import tkinter as tk
dw = tk.Tk()
dw.title('Hearts')
def play(event):
print('This is the test.')
areal = tk.Frame(master=dw, width=1200, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw))
areal.bind("<Alt-A>", play)
dw.mainloop()
It doesn't give me an error, but it doesn't do anything when I click the Frame and afterwards press alt+a. What is wrong here?
EDIT:
import tkinter as tk
def go_fwd(event):
areal.focus_set()
print(event.x, event.y)
dw = tk.Tk()
dw.title('Adventure')
areal = tk.Frame(master=dw, width=20000, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw)-100)
areal.focus_set()
dw.bind("<Alt-A>", go_fwd)
enter = tk.Frame(master=dw, width=20000, height=100, bg='cyan')
enter.pack(fill=tk.X)
enterentry = tk.Text(master=enter, width=100, height=4, bg='white')
enterentry.pack()
enterbutton = tk.Button(master=enter, text='Senden', bg='red')
enterbutton.pack()
dw.mainloop()
Here is the complete code.
Is it possible to bind a function to a Label, too?
You can bind to any widget you want. However, if you bind key events, the bindings will only work if the widget has focus. By default, most widgets other than Entry and Text don't get focus unless you explicitly set the focus to them.
Note: only one widget can have keyboard focus at a time.
You can also set a binding to the root window, which will cause it to fire no matter what widget has focus.
For a more thorough explanation of how key bindings are processed, see this answer: https://stackoverflow.com/a/11542200/7432
Why does this (simplyfied) code doesn't work?
It doesn't work the way you expect because the binding is on a Frame widget, but that widget doesn't have the keyboard focus. You could give it focus with something like this:
areal.focus_set()
Or, you could only give it focus after you click on the frame, by creating a binding on a mouse click:
areal.bind("<1>", lambda event: areal.focus_set())
Note: you are binding to a capital "A", so make sure when you test that you're pressing control-alt-a
You need to bind to dw instead of your frame.
So, you can do dw.bind("<Alt-A>", play).
A minor note, Alt-A will bind to the uppercase A as expected, so you'd have to click Alt+Shift+A on your keyboard. Doing Alt+A on your keyboard won't work, you'd have to bind to Alt-a for this to work.
The main window has keyboard focus. Or, alternatively you can leave the bind on the frame and just do areal.focus_set() to set the focus to the frame.

Categories