manipulate dynamically generated tk-inter widgets without global variables - python

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.

Related

how can i use the Entry function in tkinter in an if argument?

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()

return self.func(*args) TypeError: menu() missing 1 required positional argument: 'self'

I keep getting this error and I cant seem to fix it, if anybody could help me I would really appreciate it. I have been looking at it for quite a while now and I can't seem to get my head around it, I am still quite new to programming in an Object Oriented Way.
Thanks & Merry Christmas
Welcome Page
from External_Menu import *
from tkinter import *
class Welcome(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.welcome_button()
self.pack()
def welcome_button(self):
self.welcome = Button(self, text="Welcome!", command=ExternalMenu.menu)
self.welcome.pack()
self.pack()
if __name__ == "__main__":
root = Tk()
main = Welcome(root)
main.mainloop()
External Menu
from tkinter import *
class ExternalMenu(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.menu()
self.pack()
def menu(self):
self.external_menu_lbl = Label(self, text="External Menu", font=("", 26))
self.external_menu_lbl.pack()
self.sign_in_button = Button(self, text="Sign In")
self.sign_in_button.pack()
self.sign_up_button = Button(self, text="Sign Up")
self.sign_up_button.pack()
self.pack()
Your menu is a method so it needs an object it can manipulate in order to work. But you're trying to call it without an object created for it, essentially you're calling it like a function. First you need to create an object of the class that method is defined on:
an_ex_men = ExternalMenu(root)
and then you are able to call menu method on an_ex_men:
an_ex_men.menu()
But since you already call menu under your ExternalMenu's __int__ method it is called as soon as an object instance for that class is created. Shortly, you creating an ExternalMenu object is enough to achieve that as in, even without putting it to a variable to refer later:
ExternalMenu(root)
Since you want to swap between two windows you need an additional method to either hide or completely destroy the other window. Let's say you want to destroy the other window, and for that you could use a method defined for your button that does those 2 actions:
def welcome(self):
self.destroy()
ExternalMenu(root)
As in:
...
def welcome_button(self):
self.welcome = Button(self, text="Welcome!", command=self.welcome)
self.welcome.pack()
def welcome(self):
self.destroy()
ExternalMenu(root)
...
Below example does exactly what you expect, first I created a parent class App as according to you the two frames you have are in the same level of hierarchy and having a parent for them is in my opinion better structured:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.welcome_frame = Welcome(self)
#I believe it's better to call geometry managers as parent
self.welcome_frame.pack()
#assigning parent method as button command as it affects siblings
self.welcome_frame.button['command'] = self.go_ex_men
def go_ex_men(self):
self.welcome_frame.destroy()
self.ex_men = ExternalMenu(self)
self.ex_men.pack()
class Welcome(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.button = tk.Button(self, text="Welcome!")
self.button.pack()
class ExternalMenu(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.external_menu_lbl = tk.Label(self, text="External Menu", font=("", 26))
self.external_menu_lbl.pack()
self.sign_in_button = tk.Button(self, text="Sign In")
self.sign_in_button.pack()
self.sign_up_button = tk.Button(self, text="Sign Up")
self.sign_up_button.pack()
if __name__ == "__main__":
root = App()
root.mainloop()

tkinter variable in another class

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.

access to top level widget in Tkinter

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()

Tkinter Global Binding

Is it possible to bind all widgets to one command, with a single line? It would be nice if I could type in one line as opposed to doing each widget individually.
You would use the bind_all method on the root window. This will then apply to all widgets (unless you remove the bindtag "all" from some widgets). Note that these bindings fire last, so you can still override the application-wide binding on specific widgets if you wish.
Here's a contrived example:
import Tkinter as tk
class App:
def __init__(self):
root = tk.Tk()
root.bind_all("<1>", self.woot)
label1 = tk.Label(text="Label 1", name="label1")
label2 = tk.Label(text="Label 2", name="label2")
entry1 = tk.Entry(name="entry1")
entry2 = tk.Entry(name="entry2")
label1.pack()
label2.pack()
entry1.pack()
entry2.pack()
root.mainloop()
def woot(self, event):
print "woot!", event.widget
app=App()
You might also be interested in my answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget? where I talk a little more about bindtags.
If you have a list that contains all your widgets, you could iterate over them and assign the events.
You mean something like this code which handles all mouse events handled with single function?
from Tkinter import *
class ButtonHandler:
def __init__(self):
self.root = Tk()
self.root.geometry('600x500+200+200')
self.mousedown = False
self.label = Label(self.root, text=str(self.mousedown))
self.can = Canvas(self.root, width='500', height='400', bg='white')
self.can.bind("<Motion>",lambda x:self.handler(x,'motion'))
self.can.bind("<Button-1>",lambda x:self.handler(x,'press'))
self.can.bind("<ButtonRelease-1>",lambda x:self.handler(x,'release'))
self.label.pack()
self.can.pack()
self.root.mainloop()
def handler(self,event,button_event):
print('Handler %s' % button_event)
if button_event == 'press':
self.mousedown = True
elif button_event == 'release':
self.mousedown = False
elif button_event == 'motion':
if self.mousedown:
r = 5
self.can.create_oval(event.x-r, event.y-r, event.x+r, event.y+r, fill="orange")
self.label.config(text=str(self.mousedown))
button_event = ButtonHandler()
You could also just define a function that calls on all your widgets, and call that function. Or better yet create a class that call on your widgets in init and import the class...

Categories