I need to set some variables using tkinter callbacks on a tkinter Button. So I could say (inside of a class definition)
Button1 = Button(parentframe, text="set", command = self.setvar)
def setvar(self):
self.myvar = 7
Is there some way to do that with an inline (lambda) function, rather than cluttering things up with silly little callbacks?
command = lambda *args: something?
You could do this:
command = lambda: setattr(self, 'myvar', 7)
You could do something like this using Tkinter's integer variable class and a lambda function as you suspected. A variable of type IntVar has .get() and .set() methods that will allow you to, unsurprisingly, get and set its value. Take the following for example:
from Tkinter import *
class App(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
# initialise Integer Variables
self.example1 = IntVar()
self.example1.set(2)
self.text1 = Entry(root, textvariable=self.example1, state=DISABLED)
self.text1.grid(row=0, column=0)
self.button1 = Button(root, text="Double", command=lambda: self.double_value(self.example1))
self.button1.grid(row=1, column=0)
def double_value(self, var):
var.set(var.get() * 2)
root = Tk()
app = App(root)
root.mainloop()
This gives you a bit of flexibility, and could be expanded to apply the same feature to multiple buttons relating to different Entry boxes and IntVar variables.
Related
This code of a minimal example is functional:
from tkinter import *
textbox =str()
def openpopup():
popupwindow = Toplevel(root)
global textbox
textbox = Text(popupwindow, height=20, width=40,font="Courier")
textbox.pack()
textbox.delete(1.0, END)
textbox.insert(1.0,"start")
Button(popupwindow, text="do it", command=changepopup).pack()
def changepopup():
global textbox
textbox.delete(1.0, END)
textbox.insert(1.0,"changed text")
root = Tk()
Button(root, text="open", command=openpopup).pack()
mainloop()
my goal is to open a popup dynamically on userinput and then have various gui elements interact.
I managed to do this using global. I've read using global variables should be avoided.
What is the recommended way of going about this? Can I avoid using globals? I am aware that this is an issue of scoping, this is how I came up with this "solution". I am not so familiar with OOP but I have a hunch this might be a solution here.
Question: Can I avoid using globals?
Yes, consider this OOP solution without any global.
Reference:
- 9.5. Inheritance
- class-and-instance-variables
- Dialog Windows
import tkinter as tk
from tkinter import tkSimpleDialog
class Popup(tkSimpleDialog.Dialog):
# def buttonbox(self):
# override if you don't want the standard buttons
def body(self, master):
self.text_content = ''
self.text = tk.Text(self)
self.text.pack()
return self.text # initial focus
def apply(self):
self.text_content = self.text.get(1.0, tk.END)
class App(tk.Tk):
def __init__(self):
super().__init__()
btn = tk.Button(self, text='Popup', command=self.on_popup)
btn.pack()
def on_popup(self):
# The widget `Popup(Dialog)`, waits to be destroyed.
popup = Popup(self, title='MyPopup')
print(popup.text_content)
if __name__ == '__main__':
App().mainloop()
The object-oriented way would be to create a class representing "popup" objects. The class' initializer method, __init__(), can create the popup's widgets as well as act as a storage area for the contents of the Text widget. This avoids needing a global variable because methods of class all has an first argument usually call self the is instance of the class.
Any data needed can be stored as attributes of self and can easily be "shared" all the methods of the class.
The other primary way to avoid global variables is by explicitly passing them as arguments to other callables — like main() does in the sample code below.
Here's an example based on the code in your question:
from tkinter import *
class Popup:
def __init__(self, parent):
popup_window = Toplevel(parent)
self.textbox = Text(popup_window, height=20, width=40, font="Courier")
self.textbox.pack()
self.textbox.insert(1.0, "start")
btn_frame = Frame(popup_window)
Button(btn_frame, text="Do it", command=self.do_it).pack(side=LEFT)
Button(btn_frame, text="Close", command=popup_window.destroy).pack(side=LEFT)
btn_frame.pack()
def do_it(self):
self.clear()
self.textbox.insert(1.0, "changed text")
def clear(self):
self.textbox.delete(1.0, END)
def main():
root = Tk()
Button(root, text="Open", command=lambda: Popup(root)).pack()
root.mainloop()
if __name__ == '__main__':
main()
The global you created, textbox is unnecessary. You can simply remove it from your program. and still get the same behavior
# textbox = str()
I hope my answer was helpful.
So i am making a password organisator in python, and i don't know how i can get user input from an Entry and use it in an if argument?
text1 = StringVar()
def but():
text1.get()
print(text1.get())
knapp2 = Button(root, command="but").pack()
entry1 = Entry(root, textvariable=text1).place(x=270, y=100)
You can call the .get() function on on the Entry widget too to get the text.
import tkinter
from tkinter import Tk, Button, Entry
mw = Tk()
entry = Entry(mw)
entry.pack()
def but():
text = entry.get()
print(text)
button.config(text='Button Clicked')
button = Button(mw, command=but, text='Test')
button.pack()
mw.mainloop()
This code does work but will become complicated with larger code. You will have to define the function before creating a widget that calls that function. In the above example if you created the button widget before the function you would get an exception. You could create the widget, then create the function, then change the configuration of the button to call that function when clicked but that's still pretty complicated and will be confusing in large programs.
I would recommend putting everything in a class. It makes it easy to reference widgets in functions.
import tkinter
from tkinter import Tk, Button, Entry
class Main:
def __init__(self, master):
self.master = master
self.entry = Entry(self.master)
self.entry.pack()
self.button = Button(self.master, text='Test', command=self.But)
self.button.pack()
def But(self):
print(self.entry.get())
self.button.config(text='Button Clicked.')
mw = Tk()
main = Main(mw)
mw.mainloop()
Why in this code when clicking a button when a new window opens, all the radio buttons are selected?
class CodeButton:
def __init__(self, root):
self.btn = Button(root, text="Code",width=20, height=1,bg="white", fg="black")
self.btn.bind("<Button-1>", make_code_window)
self.btn.pack()
def make_code_window(event):
new_root = Toplevel()
new_root.minsize(width=300, height=300)
var = IntVar()
var.set(0)
for i in range(8):
Radiobutton(new_root, text=str(i), variable=var, value=i).pack()
def main():
root = Tk()
root.minsize(width=400, height=250)
CodeButton(root)
root.mainloop()
It's got something to do with storing the IntVar in a local variable in the function that will be discarded as soon as the make_code_window() function returns. You can fix the problem by making the IntVar an attribute of the new_root window widget, so it will exist at least as long as the widget using it does.
The code in your example isn't very realistic in the sense that typically one would want to use the current value of the IntVar for something somewhere else in the Python code, but that wouldn't be possible since it's only stored temporarily in local variable which exists only during the execution of the function that created it.
try:
from tkinter import *
except ImportError: # Python 2
from Tkinter import *
class CodeButton:
def __init__(self, root):
self.btn = Button(root, text="Code",width=20, height=1,bg="white", fg="black")
self.btn.bind("<Button-1>", make_code_window)
self.btn.pack()
def make_code_window(event):
new_root = Toplevel()
new_root.minsize(width=300, height=300)
var = new_root.var = IntVar() # changed
var.set(0)
for i in range(8):
Radiobutton(new_root, text=str(i), variable=var, value=i).pack()
def main():
root = Tk()
root.minsize(width=400, height=250)
CodeButton(root)
root.mainloop()
main()
(Following-up on the discussion we were having in the comments section of my other answer.)
Yes, passing the IntVar as an argument to the event handler function is a little tricky—in fact it's sometimes called The extra arguments trick. ;-)
Here's an example of applying it to your code:
try:
from tkinter import *
except ImportError: # Python 2
from Tkinter import *
class CodeButton:
def __init__(self, root):
self.btn = Button(root, text="Code",width=20, height=1,bg="white", fg="black")
self.btn.bind("<Button-1>",
# Extra Arguments Trick
lambda event, var=root.var: make_code_window(event, var))
self.btn.pack()
def make_code_window(event, var): # note added "var" argument
new_root = Toplevel()
new_root.minsize(width=300, height=300)
var.set(-99) # deselect by using value not associated with any RadioButtons
for i in range(8):
Radiobutton(new_root, text=str(i), variable=var, value=i).pack()
def main():
root = Tk()
root.minsize(width=400, height=250)
root.var = IntVar() # create it here to give access to it in the rest of your code
CodeButton(root)
root.mainloop()
main()
Python 3.1, tkinter/ttk
I wrote something very simple to try and understand how tkinter's variables linked to widgets can be stored in a different class to the widget. Code below.
Questions:
1) why doesn't pressing the button change the label?
2) do I need quite so many selfs? Can the variables within each method manage without self. on the start?
Hopefully the answer will be a useful learning exercise for other tkinter newbies...
from tkinter import *
from tkinter.ttk import *
root = Tk()
class Store:
def __init__(self):
self.v = IntVar()
self.v.set(0)
def set(self, v):
self.v.set(v)
class Main:
def __init__(self):
self.counter = 0
self.label = Label(root, textvariable = a.v)
self.label.pack()
self.button = Button(root, command = self.counter, text = '+1')
self.button.pack()
def counter(self):
self.counter = self.counter + 1
a.set(self.counter)
a = Store()
b = Main()
root.mainloop()
Your problem is that you have both a method and a variable named counter. When you click the button, your function isn't being called so the variable isn't being set. At the time you create the button, tkinter thinks that self.counter is a variable rather than a command.
The solution to this particular problem is to rename either the function or the variable. It then should work as you expect.
To answer the question about "too many selfs": you need to use self so that python knows that the object you are referring to should be available everywhere within a specific instance of the object. So, yes, you need all those self's, if you want to refer to variables outside of the function they are defined in.
As for self in the definition of a method, those too are necessary. When you do object.method(), python will automatically send a reference to the object as the first argument to a method, so that the method knows specifically which method is being acted upon.
If you want to know more about the use of "self", there's a specific question related to self here: What is the purpose of self?
If I were you, I will do this:
import tkinter
root = tkinter.Tk()
var = tkinter.IntVar()
label = tkinter.Label(root, textvariable=var)
button = tkinter.Button(root, command=lambda: var.set(var.get() + 1), text='+1')
label.pack()
button.pack()
root.mainloop()
UPDATE:
But, if you insist doing this with two classes (where the first class is only a somewhat pointless interface), then I would do this:
import tkinter
class Store:
def __init__(self):
self.variable = tkinter.IntVar()
def add(self, value):
var = self.variable
var.set(var.get() + value)
return var.get()
class Main(tkinter.Tk):
def __init__(self, *args, **kwargs):
tkinter.Tk.__init__(self, *args, **kwargs)
var = Store()
self.label = tkinter.Label(self, textvariable=var.variable)
self.button = tkinter.Button(self, command=lambda: var.add(1), text='+1')
self.label.pack()
self.button.pack()
root = Main()
root.mainloop()
As you may notice, I used in both times the get() and set() methods of the IntVar (in your version I do this thru the Store interface we made), therefore you don't need to use a new variable (like self.counter) because the variable we instantiated is storing the data.
The other thing I used is a lambda expression instead of a full-blown function definition, since we only want to get the current value, add 1 to it, and store it as the new value.
And in my version, the Main class is a subclass of the original tkinter.Tk class -- that's why I call it's __init__ method inside the Main's __init__ method.
How i can access to top level widget from function, which used as command for button or menu? There is way to add params in this command function by using command=lambda: f(params), but i think it may be easier.
You could use a lambda, like you mentioned:
command=lambda: f(params)
or you could make a closure:
def make_callback(params):
def callback():
print(params)
return callback
params = 1,2,3
button = tk.Button(master, text='Boink', command=make_callback(params))
or, you could use a class and pass a bound method. The attributes of self can contain the information that you would otherwise have had to pass as parameters.
import Tkinter as tk
class SimpleApp(object):
def __init__(self, master, **kwargs):
self.master = master
self.params = (1,2,3)
self.button = tk.Button(master, text='Boink', command=self.boink)
self.button.pack()
def boink(self):
print(self.params)
root = tk.Tk()
app = SimpleApp(root)
root.mainloop()