1

With the below code Button-1 click called relief change works only when it is already clicked once (only double click works 1st time, 2nd time it is ok, but if an other widget is selected, the same happens):

def selected(event):
    event.widget.config(relief=SUNKEN if event.widget.cget("relief") == "raised" else RAISED)    

B1 = Button(root, text ='BUTTON1', font='-size 8', relief=RAISED)
B1.bind("<Button>", selected)
B1.grid(row = 1, column = 2, sticky = N+E+S+W)

B2 = Button(root, text ='BUTTON2', font='-size 8', relief=RAISED)
B2.bind("<Button>", selected)
B2.grid(row = 2, column = 2, sticky = N+E+S+W)

With Button-2 and Button-3 it is working properly,

What is the reason?

15
  • first use print() to check what you get event.widget.cget("relief") when you click it. Second: create minimal working code with your problem which we can test. Commented Aug 6, 2020 at 7:32
  • 1
    Try adding return 'break' in select() function. Commented Aug 6, 2020 at 7:35
  • 2
    Actually you can use Checkbutton with indicator=False which has the same effect. Commented Aug 6, 2020 at 7:48
  • 2
    Actually I just think that the default mouse button release event handler may mess up the synchronisation of the relief state. But why it works on second click, and then it does not work after clicking another button, I don't know the reason. return "break" just skip the default event handlers. Once again, I prefer using Checkbutton with indicator=False. Commented Aug 6, 2020 at 8:27
  • 1
    you have to assign button to arg= - you can't put it directly in selected() - button['command'] = lambda arg=button:selected(arg) All problem is that it doesn't copy value from button to selected(button) but only reference to variable button which is changed in loop so finally all selected use reference to the same value - last value in for-loop. Using arg=button it creates new variable in every loop and it copies value from button to arg - so every selected() use differen arg with different value. Commented Aug 6, 2020 at 10:29

2 Answers 2

2

Problem is that tkinter can bind many functions to event and there is already binded default function which changed relief when you click button. You can use return "break" in your function to inform tkinter that you processed this event and it will skip other functions.

Or you could use standard command= to assing function and then it will skip other function.

import tkinter as tk  # PEP8: `import *` is not preferred
 
# --- functions ---

def selected1(event):
    event.widget.config(relief='sunken' if event.widget.cget('relief') == 'raised' else 'raised')    
    return "break"

def selected2():
    B2.config(relief='sunken' if B2.cget("relief") == 'raised' else 'raised')    

# --- main ---

root = tk.Tk()

B1 = tk.Button(root, text='BUTTON1', relief='raised')
B1.bind("<Button>", selected1)
B1.grid(row=1, column=2, sticky='news')

B2 = tk.Button(root, text='BUTTON2', relief='raised', command=selected2)
B2.grid(row=2, column=2, sticky='news')

root.mainloop()  

The same using for-loop and lambda.

Normally it use only reference to button which valeu is changed in loop so finally all functions use reference to the same value - which is last value assigned in loop.

Using lambda arg=button it create new variable in every loop and copy value from button to arg so every widget use different arg with differen value.

import tkinter as tk  # PEP8: `import *` is not preferred
 
# --- functions ---

def selected(widget):
    widget.config(relief='sunken' if widget.cget('relief') == 'raised' else 'raised')    

# --- main ---

root = tk.Tk()

for x in range(1, 6):
    button = tk.Button(root, text=f'LOOP BUTTON {x}', relief='raised')
    #button.config(command=lambda arg=button:selected3(arg))
    button['command'] = lambda arg=button:selected(arg)
    button.grid(row=x, column=2, sticky='news')

root.mainloop()  

As @acw1668 mentioned in comment you can also uses Checkbutton(..., indicator=False) to get the same effect without extra function

import tkinter as tk  # PEP8: `import *` is not preferred
 
# --- main ---

root = tk.Tk()

for x in range(1, 6):
    button = tk.Checkbutton(root, text=f'LOOP BUTTON {x}', indicator=False, padx=10, pady=5)
    button.grid(row=x, column=2, sticky='news')

root.mainloop()  
Sign up to request clarification or add additional context in comments.

2 Comments

This way the for loop generated buttons work too. Thanks.
mark this as the correct ans, if it helped u solve the issue :D
-1

It seems that you just have to tweak a single thing in order to single-click on buttons to activate the function. Try changing the bind to this:

def selected(event):
    event.widget.config(relief=SUNKEN if event.widget.cget("relief") == "raised" else RAISED)    

B1 = Button(root, text ='BUTTON1', font='-size 8', relief=RAISED)
B1.bind("<Button-1>", selected)
B1.grid(row = 1, column = 2, sticky = N+E+S+W)

B2 = Button(root, text ='BUTTON2', font='-size 8', relief=RAISED)
B2.bind("<Button-1>", selected)
B2.grid(row = 2, column = 2, sticky = N+E+S+W)

Just change "Button" to "Button-1" and please tell me if there are errors

4 Comments

It doesn't work with <Button-1>, I tried and copied here with <Button>, because it shows that with middle and right button it works properly.
Try using ButtonPress instead of Button=1
It does the same.
it is the same <Button> , <Button-1>, <ButtonReleased>, <ButtonReleased-1> and almost the same as <ButtonPressed> and <ButtonPressed-1>. Problem is that tkinter can bind many functions to the same event and there is already binded (default) function which has to be stoped/skiped - and return "break" skips this second function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.