I have created a custom widget for tkinter that lays out 5 buttons. The widget works beautifully for the most part. The problem is that I cannot figure out how to pass the button that the user presses in the widget to the main application. The custom widget stores the last button pressed in a variable, but I cannot figure out how to make the main application see that it has been changed without resorting to binding a button release event to root. I would like to try to build out this custom widget further, and I want it to work without having to do some messy hacks. Ideally, in the example below, when a button is pressed, the label should change to reflect the button pressed. For example, if the user clicks the "2" button, the label should change to "2 X 2 = 4". How can I pass the text on the button directly to the main application for use? Hopefully, I am making it clear enough. I want to be able to get the value from the widget just like any other tkinter widget using a .get() method. Here is the code that I am using:
import tkinter as tk
from tkinter import ttk
class ButtonBar(tk.Frame):
def __init__(self, parent, width=5, btnLabels=''):
tk.Frame.__init__(self, parent)
self.btnLabels = []
self.btnNames = []
self.setLabels(btnLabels)
self.selButton = None
self.display()
def getPressedBtn(self,t):
"""
This method will return the text on the button.
"""
self.selButton = t
print(t)
def createBtnNames(self):
"""
This method will create the button names for each button. The button
name will be returned when getPressedBtn() is called.
"""
for i in range(0,5):
self.btnNames.append(self.btnLabels[i])
def display(self):
"""
This method is called after all options have been set. It will display
the ButtonBar instance.
"""
self.clear()
for i in range(len(self.btnLabels)):
self.btn = ttk.Button(self, text=self.btnLabels[i], command=lambda t=self.btnNames[i]: self.getPressedBtn(t))
self.btn.grid(row=0, column=i)
def setLabels(self, labelList):
if labelList == '':
self.btnLabels = ['1', '2', '3', '4', '5']
self.createBtnNames()
else:
btnLabelStr = list(map(str, labelList))
labelsLen = len(btnLabelStr)
def clear(self):
"""
This method clears the ButtonBar of its data.
"""
for item in self.winfo_children():
item.destroy()
root = tk.Tk()
def getButtonClicked(event):
global selBtn
print(event)
if example.winfo_exists():
selBtn = example.selButton
answer = int(selBtn) * 2
myLabel.config(text='2 X ' selBtn ' = ' str(answer))
tabLayout = ttk.Notebook(root)
tabLayout.pack(fill='both')
vmTab = tk.Frame(tabLayout)
myLabel = tk.Label(vmTab, text='2 X 0 = 0', width=50, height=10)
myLabel.pack()
vmTab.pack(fill='both')
tabLayout.add(vmTab, text='Volume Movers')
# Create the ButtonBar.
example = ButtonBar(vmTab)
selBtn = None
example.pack()
lbl = tk.Label(root, text='')
root.mainloop()
I have looked at some other posts on stackoverflow. This one creating a custom widget in tkinter was very helpful, but it didn't address the button issue. I though this Subclassing with Tkinter might help. I didn't understand the If I bind the event using root.bind("<ButtonRelease-1>", getButtonClicked)
, then the widget works fine. Is there any other way to do it though?
CodePudding user response:
I'd say that you have made the code more complex than it should be, you really just need to create the buttons and give them some callback that is passed as an argument. And that callback should take at least one argument which would be the text that would be on the button which will be also passed to that callback.
import tkinter as tk
from tkinter import ttk
class ButtonBar(tk.Frame):
def __init__(self, master, values: list, command=None):
tk.Frame.__init__(self, master)
for col, text in enumerate(values):
btn = ttk.Button(self, text=text)
if command is not None:
btn.config(command=lambda t=text: command(t))
btn.grid(row=0, column=col, sticky='news')
def change_label(val):
res = 2 * int(val)
new_text = f'2 X {val} = {res}'
my_label.config(text=new_text)
root = tk.Tk()
my_label = tk.Label(root, text='2 X 0 = 0')
my_label.pack(pady=100)
texts = ['1', '2', '3', '4', '5']
example = ButtonBar(root, values=texts, command=change_label)
example.pack()
root.mainloop()
You can also base the buttons on a list of values so that you can specify any values and it will create buttons that have that text on them and pressing them will call the given function with an argument of their text. That way you can use it as really any other widget, it would require the master, some values (text) and a command. Then you would just create that callback, which will take that one argument and then change the label accordingly. (I also removed all the notebook stuff, but I am just showing how you can achieve what you asked for)
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case
, class names in CapitalCase
. Have two blank lines around function and class declarations. Object method definitions have one blank line around them.