Python Forum
[Tkinter] Button click problem using OOP - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: [Tkinter] Button click problem using OOP (/thread-30379.html)



Button click problem using OOP - JohnB - Oct-18-2020

Can anyone help me with a problem illustrated in the code below. I am instantiating three tkinter buttons and saving the objects in a list which is I think a fairly standard way of doing things. The buttons can then be managed, colour change, text etc by referencing them via their index in the list. But if a button is clicked it seems impossible to detect which button it was. The only button object that is "active", if you want to call it that, is the last one that was instantiated in the For loop. Which ever button is clicked, it is that object that handles the click event. Is there a way of detecting which button is clicked when they have been instantiated this way. Thank you for sharing your expertise.

JohnB

import tkinter as tk
root=tk.Tk()

class Butman(tk.Frame):
    def __init__(self,MyNumber):
        self.MyNumber = MyNumber
        tk.Frame.__init__(self)
        
    def SetCol(self,col):
        self.but.config(bg = col)
        
    def SetText(self,Text):
        self.but.config(text = Text)



def DoSomething():
    print ("something done")
    
        
frame2 = tk.Frame(bg="grey", height = 720 ,width = 1280)
frame2.pack()
ButObj = []
for x in range(0,3):
    B = Butman(x)
    B.but=tk.Button(frame2, state="normal", text="sometext",compound= tk.CENTER,font=('comicsans', 
    16),relief=tk.FLAT,command = lambda: DoSomething())
    B.but.place(relx=x/4, rely=x/4, relwidth=0.18, relheight=0.18)
    ButObj.append(B)
    
ButObj[1].SetCol("green")
B.SetCol("red")



RE: Button click problem using OOP - deanhystad - Oct-19-2020

Why not pass the button as an argument to the function?
import tkinter as tk
root = tk.Tk()

def button_pressed(button):
    label.configure(text=button['text'])
     
         
frame = tk.Frame()
frame.pack()

b1 = tk.Button(frame, text='Hello')
b1.configure(command = lambda b = b1: button_pressed(b))
b1.grid(row=0, column=0)

b2 = tk.Button(frame, text='Goodbye')
b2.configure(command = lambda b = b2: button_pressed(b))
b2.grid(row=1, column=0)

label = tk.Label(frame, text = 'Button Text')
label.grid(row=2, column=0)



RE: Button click problem using OOP - joe_momma - Oct-19-2020

I think you kind of over thought it, the class isn't needed
Quote: Is there a way of detecting which button is clicked when they have been instantiated this way.
Yes by indexing a list and using functool -> partial.
here's an example:
import tkinter as tk
from functools import partial


root= tk.Tk()

def do_something(index):
    button_list[index].config(bg='green',fg='white')


frame= tk.Frame(bg='gray60', width= 1280, height= 720)
frame.pack()
button_list= []
for x in range(0,3):
    text_='Button {0}'.format(x)
    button_list.append(tk.Button(frame,text=text_,compound='center',
                       font=('comicsans',16,'italic'),
                       command= partial(do_something,x),
                       relief= tk.FLAT))
    button_list[-1].place(relx=x/4, rely=x/4, relwidth=0.18, relheight=0.18)

root.mainloop()



RE: Button click problem using OOP - JohnB - Oct-20-2020

Thank you for your replies. I have learnt something from both of them.
I could pass the button as an argument as you suggest deanhistad but I need to configure up to 20 buttons, so I would prefer them to be configured in a loop.

Which using partial functions does. I've never come across partial functions or the Functools module before. Having researched it a bit I can see you need to generate a new function of the partial function, and call the new function supplying any if needed additional arguments. Can't quite see how a new function of the partial(do_something,x) is made/called when a button is clicked.
Another puzzle your solution has produced is why command = lambda:do_something(x) does not work. Why do you need to use a partial function. You would think that x, the list index basically, would be passed to the do_something function and the correct button would be actioned. What happens is the same as the Class scenario where the last button made is always actioned which ever button is clicked.

Anyway, having got all that clear in my mind, it's great when solutions provide more questions, that's how we learn.


RE: Button click problem using OOP - deanhystad - Oct-20-2020

command = lambda:do_something(x) does not work because lambdas are evaluated when called. IN this example I make three buttons and all are set to call DoSomething and passing the argument I.
import tkinter as tk

buttons = []

def DoSomething(args):
    print(args)

root = tk.Tk()
f = tk.Frame()
f.pack()

for i in range(3):
    b = tk.Button(f, command = lambda : DoSomething(i), text=str(i))
    b.grid(row=0, column=i)
    buttons.append(b)
When I press any button the program prints "2". Why is that?

How about answer this. What is the value of i? In my Python console I type i and it responds "2". I press a button, the button executes the lambda. The lambda calls DoSomething and passes the value of i.

To lock in the "i" argument in the callback you can use a partial function. The partial function arguments are frozen when the partial function is created. You are not actually setting the "command" to DoSomething(i), you are setting it to DoSomething(0) or DoSomething(1). You can do something similar with a lambda.
import tkinter as tk

buttons = []

def DoSomething(args):
    print(args)

root = tk.Tk()
f = tk.Frame()
f.pack()

for i in range(3):
    b = tk.Button(f, command = lambda x = i: DoSomething(x), text=str(i))
    b.grid(row=0, column=i)
    buttons.append(b)



RE: Button click problem using OOP - JohnB - Oct-21-2020

Thanks for the great explanation deanhystad. Yes I asked the console to print i, and yes it equalled 2. I like the lambda solution, giving arguments before the colon which are evaluated at run rather than at execution. In fact this is a solution to my first post where I make objects of the buttons.

All's good Thanks