The following is the simplest code required to illustrate my question:
def function_one():
pass
def function_two():
pass
def button_clicked(function_name_as_string):
# do stuff common to all button clicks
# here...
# then respond to specific button click by converting the
# supplied function name string into an actual function which runs.
eval(function_name_as_string + "()")
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked("function_one"))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked("function_two"))
This is an attempt to make my tkinter code as compact as possible. Here, there are multiple buttons. But rather than write a function to handle each button click, I wanted a single function to handle all button clicks. In particular, that's because there are some operations I want to execute in response to any button click, before responding to the specific button click. At the same time, I wanted to eliminate any lengthy if/elif statement in the button_clicked function, which would be required for the function to know which button had been clicked, and take appropriate action.
In my solution above, when a button is clicked, the command keyword calls my button_clicked function, but also uses lambda to send the name of the function I want to run in response to the button click. The button_clicked function executes the code common to all button clicks, then uses eval to convert the supplied function name (passed as a string) into an actual function that now runs.
So the button_clicked function is not aware of which button was clicked. Rather, it is aware of what function ought to run in response to the specific button click. And this code runs perfectly.
I have never used lambda before, nor have I used eval. I came across both during a long search. But because both are new to me, I am a bit wary. My question is simply this: is the above approach correct and sound? Or are there hidden pitfalls that I may not be aware of? Thanks in advance.
I would argue it's better to pass the actual function than the function name as a string. eval rarely adds any value over other solutions, and mostly just makes the code harder to understand.
def function_one():
pass
def function_two():
pass
def button_clicked(function):
# do stuff common to all button clicks
# here...
# then respond to specific button click by calling
# the specified function
function()
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked(function_one))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked(function_two))
Related
I'm trying to overwrite an existing keyboard function on the enter key with a custom hotkey. The problem is, I cannot stop the default action from occurring also. Worse yet, it occurs after the custom action, so I don't have the chance to retroactively correct it as well.
Here are the relevant parts of the code:
from tkinter import *
from tkinter import ttk
from keyboard import *
root = Tk()
root.geometry('400x300')
'''This is the problematic function called by the hotkey'''
def entr_key(text):
'''Enter key results in printing of the line to the right of cursor'''
curs_pos = text.index(INSERT)
right_hand = text.get(curs_pos,END)
right_hand = right_hand.split('\n')[:1] #lines -> strs in a list, selects the first
print (right_hand)
return
'''THIS IS THE MAIN WIDGET FOR THIS FRAME'''
text_view = ttk.Frame(root, padding=10)
text_view.grid()
text_box = Text(text_view, width=45, wrap=WORD)
text_box.grid(column=0, row=1)
text_box.insert('1.0', 'This is a Text widget demo,\nThis text is aimed at testing the enter key function')
add_hotkey("enter", lambda: entr_key(text_box))
root.mainloop()
(I've changed up some variable names to be more understandable, apologies if I missed any but it's not the source of the problem!)
I've also (unsuccesfully) tried other ways of doing this, eg:
while True:
if is_pressed('enter'):
entr_key(text_box)
'''AND ALSO'''
on_press_key("enter", lambda x=None: entr_key(text_box))
Just to be clear, I don't want the default action of enter key moving the text to a new line.
I need either a way to "break" the key event so that only the custom action takes place, or a way for the custom action to occur after default action, so I can retroactively edit it out
EDIT!!!
I've found a workaround: at the start of entr_key() I call time.sleep(0.01). During this, the default action of the enter key occurs first, and I can retroactively edit it out when the custom function resumes. The delay is slight enough to not be noticeable at all.
But still, if anyone knows how to prevent the default action from occurring completely, I would really appreciate it.
i have a question when using the Button widget in tkinter. I am new to this.
I noticed that when we use the command in the Button widget, sometimes we call a simple function just like that and sometimes we use lambda function and then we call it. What is the difference?
For example: tk.Button(window, text = "Click Me!", command = myfunction)
tk.Button(win,text="Result",command=lambda: result(en1.get())
Cant we just use it without lambda?
THank you.
Use of lambda:
The parentheses are the main reason that the function gets executed when given as command to a Button without lambda. If the function(which you are passing to the Button as a command) has no parameters(to be passed to itself), then you can simply pass it as a command avoiding the parentheses(). And hence you don't need to use lambda in this case. Like in this Example:command=func.
So using lambda is only necessary when the function has its own parameters(to be passed to itself).Like in this Example:command=lambda:func(a,b,c)
What lambda Does:
When you have to pass arguments to the function itself you have cannot avoid parentheses().
So in the case of buttons, lambda basically delays the execution of the function until the user clicks the button, by creating another function on the spot, which does not get called until the button is actually clicked. Hence the function does not get executed, where it is given as command to the Button.
Any Questions will be answered.
I am making a small program (first actually) with Tkinter. This program is supposed to have the logos for websites and when pressed opens the website.
google_url = 'https://www.google.com'
def OpenUrl():
webbrowser.open_new(google_url)
button1 = tk.Button(root, command=OpenUrl)
This code functions (after packing/importing everything)
Now this was the code before:
button1 = tk.Button(root, command=webbrowser.open_new('https://www.google.com'))
I am new to Python so I really want to understand why this change worked. Was it Python interpreter that did something? Now, this isn't all of the code that I wrote so if you think I should show that then ask me but this was the difference that allowed the code to work. (Open Google when the button was pressed. Before, when the code was ran, it opened google but didn't open tkinter)
Hooray for your first project!
Your first attempt didn't work because the command arg must be function. webbrowser.open_new('https://www.google.com') is a function that has already been called and thusly evaluated. It now equals open_new()s return value (which is nothing).
The contents of OpenUrl are not evaluated until the buttons is clicked, as a function only runs when called. A lambda (an unnamed, in-line function) would work as well:
button1 = tk.Button(root, command= lambda: webbrowser.open_new('https://www.google.com'))
Happy coding!
I am trying to figure out how to pass an instance of a button into it's own command. Essentially what I am trying to do is if the events of the command being called are successful, I want to then disable the button.
Here is my button code which calls a function called 'runcommand':
btn_id = Button(tab_id, text=label, anchor=W)
btn_id.configure(command=lambda command=command, logtext=logtext, btn_id=btn_id: runcommand(command, logtext, btn_id))
Once called, if conditions are met, I am trying to disable it in the 'runcommand' function:
btn_id.configure(state=DISABLED)
I'm not sure if this is possible or if I'm just going about it the wrong way.
You are already passing in a reference to the button, so
def runcommand(command, text, btn_id):
...
if (should_disable_the_button):
btn_id.configure(state=DISABLED)
Ok, this is where I fully admit I am a dumbass. I was implementing the function correctly. I have an uber complex series of 'if' conditions in the runcommand function and the disable code wasn't getting called. I designated a boolean flag to determine if the button should be disabled now. Works fine. DU...MASS!
I have the following problem when using tkinter to create a very simple window containing a matrix of buttons: When one of the buttons is clicked, the event handler changes the text of that button using the configure method on the button widget. This works. But I also want to change the text in one of the other buttons, which does not work. The method I use is that on creating the button, I store the object returned by the Button method before I use the grid geometry manager to place it. This object looks like ".123456789L" when printed and seems to be a pointer to the widget. I also use configure on this to change the button text. But somehow it seems to be wrong, because it works sometimes, and most of the times not. There's unfortunately no error message, just nothing happens when calling configure. I checked and it seems to be the correct pointer to the widget. Do I have to use a special way to affect a widget other that the one that called the event handler? These are the relevant parts of the code:
# CREATING THE BUTTONS:
buttons={} # global
for i in range(3):
for j in range(3):
button = Tkinter.Button(self,text='foo')
buttons[button]=(i,j)
button.grid(column=j,row=i)
button.bind( "<Button-1>", self.OnButtonClick )
# CHANGING BUTTONS:
def find_button(i,j):
"""Return the pointer to the other button to be changed when a button has been clicked."""
for button,key in buttons.items():
if key==(i,j): return button
def OnButtonClick(self,event):
print "You clicked the button",buttons[event.widget]
i,j=buttons[event.widget]
old_button=find_button(i,j) # This is simplified, I don't actually pass i,j, but other values. But I checked this and it returns the reference to the correct button. But this simplified version works the same way, just assume a different button that the one pressed would be returned.
old_button.configure(text = 'blabla') # THIS DOES NOT WORK
event.widget.configure(text = 'something') # THIS WORKS
I have the same problem and i solve it with:
buttons[button]=(i,j,button)
and in the function OnButtonClicK:
i,j,old_button=buttons[event.widget]
old_button.configure(text = 'blabla') # THIS DOES NOT WORK