functools.partial doesn't pass Tkinter argument - python

I am creating an application that requires input from the user. The code has an entry widget, and there is a button that calls a function with the input from one of the entry widgets as an argument. However, whenever I print the argument (the content of the entry widget) I get an empty list instead of what I entered.
#!/usr/bin/env python
import grammar
import shelve
from functools import partial
from Tkinter import *
def call_response(text):
print text
grammar.analyze.analyze(text)
class MainWindow(Tk):
def new_object(self):
main_frame = Frame(self)
bottom_frame = Frame(self)
main_frame.pack()
bottom_frame.pack(side=BOTTOM)
output = Text(main_frame)
output.pack()
input_entry = Entry(bottom_frame, width=50)
input_entry.grid(row=1, column=1)
send_input_button = Button(bottom_frame, text='Chat!', command=partial(
call_response, input_entry.get().split()))
send_input_button.grid(row=1, column=2)
mainloop()
root = MainWindow()
root.new_object()
Does anyone know what could be causing this to happen, or what might be wrong with my code?

You fetch the entry once, when creating the button; a partial() does not execute the expressions you used to create the arguments when it itself is called; the input_entry.get().split() expression is executed first, and the result is passed to the partial() object being created.
Use a lambda here to have the entry.get() executed when the button is clicked:
send_input_button = Button(bottom_frame, text='Chat!', command=lambda:
call_response(input_entry.get().split()))

call_response should be part of the class IMHO which eliminates the problems.
self.input_entry = Entry(bottom_frame, width=50)
send_input_button = Button(bottom_frame, text='Chat!', command=self.call_response)
def call_response(self):
text=self.input_entry.get().split()
grammar.analyze.analyze(text)

or just change it to
def call_response(text_fn):
text = text_fn().split()
print text
grammar.analyze.analyze(text)
....
send_input_button = Button(bottom_frame, text='Chat!', command=partial(
call_response, input_entry.get))
as an alternative if you really want to avoid lambda ... but lambda is fine #AlexMarteli has valid criticism of it ... but for something simple like this they work fine

Related

How to pass value to a global variable through a Button command in Tkinter? [duplicate]

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

how to write a string which is in a variable in a txt file [duplicate]

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

How to pass parameters to functions in python

I have this simple program that I wrote so I could better understand the 'return' function and how to pass a value from one function to another. All this program does is to pass the value of buttontwo=2 to the function button_one_function,so if button two is pressed first then button one does nothing.I thought that I could do this without using a global statement - is there a way of writing the code below without using global? I have tried doing this by putting the value of buttontwo in to the button_one_function parentheses but this didnt work. Thanks for any tips
from tkinter import *
my_window = Tk()
my_frame = Frame(my_window, height=500, width=500, bd='4')
my_frame.grid(row=0, column=0)
def button_one_function():
if button_two == 2:
print('do nothing')
else:
label_one = Label(my_frame, text='label one')
label_one.grid(row=1, column=0, sticky='n')
def button_two_function():
global button_two
button_two = 2
label_two = Label(my_frame, text='label two')
label_two.grid(row=1, column=1, sticky='n')
return button_two
button_one = Button(my_frame, text='button1', command=button_one_function)
button_one.grid(row=0, column=0)
button_two = Button(my_frame, text='button2', command=button_two_function)
button_two.grid(row=0, column=1)
my_window.mainloop()
If I've understood corectly, you are interested in sth. like this:
from tkinter import *
root = Tk()
def click(a):
print(a)
Button(root, text='1', command=lambda: click('1')).pack()
Button(root, text='2', command=lambda: click('2')).pack()
root.mainloop()
What is happening is I'm not passing a full click function to a button, but a so called lambda function, which is essentially a one-line function. Example: if I did p = lambda: print('Hi') then each time I do p() I would see a little Hi pop up. Also, if I did k = lambda a,b: a*b then k(4,5) would return "20". More info about lambdas here.
Hope that's helpful!
def function(a,b):
print("a is :",a)
print("b is :",b)
function(10,20)
You can definitely do this without globals. You could extend the tk.button class to hold a variable like self.status = pressed.
There are a few ways you can go about this with classes. You could create either one or two classes. Maybe even have child classes for each button.
But you can just dump both your functions in one class and pass self as its first argument.
Whenever I feel the need for a global variable, I usually make a class.

Python3 Print Tuple in another class [duplicate]

I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)

mutliple entry widgets and one button widget in tkinter

I am trying to make two entry boxes and an 'OK' button that processes both entries:
I don't understand the init() function or the reason 'self' has to be include in doing this.
I want to access the entries outside this class and mainloop. The entries should be string type.
from tkinter import *
root = Tk()
root.geometry("550x145+500+300")
class myDose:
def __init__(self):
l1 = Label(text="Enter the prescription dose to three (3) decimal places [cGy]").pack()
e1 = Entry(root)
e1.pack()
l2 = Label(text="Save new excel file as...").pack()
e2 = Entry(root)
e2.pack()
l3 = Label(text="click 'OK', and then close window.").pack()
b = Button(root, text="OK", command=submit)
b.pack()
def submit(self):
print(e1.get())
print(e2.get())
D = myDose()
root.mainloop()
#I want to access the entries, e1 and e2, outside the mainloop
print(D.e1)
print(D.e2)
The problem is that mainloop doesn't exit until the root window is destroyed. Once the root window is destroyed you can no longer access the widgets inside the root window. You can, however, access non-widget attributes of the object.
If that is what you want -- to destroy the window and have access to the values in the widget -- you need to save the values before destroying the window.
For example, in your submit you could save the values like this:
def submit(self):
self.data = {"e1": self.e1.get(), "e2": self.e2.get()}
Once the window is destroyed, you still have a reference to the application object, so you can still access the non-widget attributes of the class:
...
D = myDose(root)
root.mainloop()
print D.data["e1"]
print D.data["e2"]
Based on comments to the original question, you mention that later in your code you'll need to use askopenfilename. if that is the case, you should reconsider the need to run code after mainloop. Tkinter is designed to have a root window created exactly once, and for the program to exit immediately after mainloop exits.
You can write programs however you want, but I think your code will be easier to maintain and modify over the long run if you stick to normal design patterns. Move your "real" code into a method of the app class and that will eliminate all of the problems associated with widgets being destroyed.
Your approach is fundamentally flawed. mainloop() does what it says: starts a loop. The lines that follow it will not be executed until that loop is over - meaning, until your application has closed. Since you already have a class with Tkinter stuff in it, just finish the move to an OO approach:
from tkinter import *
class myDose:
def __init__(self, root):
root.geometry("550x145+500+300")
self.l1 = Label(root, text="Enter the prescription dose to three (3) decimal places [cGy]")
self.l1.pack()
self.e1 = Entry(root)
self.e1.pack()
self.l2 = Label(root, text="Save new excel file as...")
self.l2.pack()
self.e2 = Entry(root)
self.e2.pack()
self.l3 = Label(root, text="click 'OK', and then close window.")
self.l3.pack()
self.b = Button(root, text="OK", command=self.submit)
self.b.pack()
def submit(self):
print(self.e1.get())
print(self.e2.get())
root = Tk()
D = myDose(root)
root.mainloop()
I recommend looking more thoroughly into how fundamental concepts like objects and classes work before creating a full GUI application.
Here's a quick fix.
The problem with both your version and TigerhawkT3's modified version is that when you close the window the Entry widgets are no longer valid, so you need to save the data before closing the window. One way to do that is to attach Tkinter StringVars to the Entry widgets. The values in the StringVars will be available after the window is closed.
import tkinter as tk
class myDose:
def __init__(self, root):
root.geometry("550x145+500+300")
l = tk.Label(root, text="Enter the prescription dose to three (3) decimal places [cGy]")
l.pack()
self.dosage = tk.StringVar()
e = tk.Entry(root, textvariable=self.dosage)
e.pack()
l = tk.Label(root, text="Save new excel file as...")
l.pack()
self.savename = tk.StringVar()
e = tk.Entry(root, textvariable=self.savename)
e.pack()
l = tk.Label(root, text="Enter the data and then close window.")
l.pack()
root.mainloop()
D = myDose(tk.Tk())
print(D.dosage.get())
print(D.savename.get())
Note that I've changed the import statement. It's much cleaner to not do
from tkinter import *
as that clutters your namespace with over 100 names, leading to potential name collisions.
The __init__() function is a method of the myDose class. A class's __init__() method is called when an instance of that class is created. In your code that happens when you do
D = myDose()
In my version it's when this line is executed:
D = myDose(tk.Tk())
which first creates the Tkinter root window which is then passed to myDose; that root window is then available to my __init__() as its root argument.
The name self is used inside the class to refer to the current class instance. Outside the class we need to use the actual name we assigned the instance to, which in this case is D.

Categories