Self or not Self in Widgets [closed] - object

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 months ago.
Improve this question
I run the Codes "With Self" and "Without Self" and the result is the same. Most developers prefer the "With Self" Why? What is the difference? Thanks!
# Without Self
import tkinter as tk
class MyApp():
def __init__(self, root):
w = tk.Label(root, text="Hello!", font = "Arial 36", bg='yellow')
w.pack()
root = tk.Tk()
myapp = MyApp(root)
root.mainloop()
# With self
import tkinter as tk
class MyApp():
def __init__(self, root):
self.root = root
self.w = tk.Label(self.root, text="Hello!", font = "Arial 36", bg='yellow')
self.w.pack()
root = tk.Tk()
myapp = MyApp(root)
root.mainloop()

Self or not Self?
Classes or no classes? Functions or no functions?
There is in my opinion no really simple, short and straightforward answer to your question. It is a matter of preferences and a matter of what one considers to be a good programming style.
Usage of classes is not strictly necessary for programming tkinter applications and I personally prefer whenever it makes sense to avoid the overhead required for coding classes. There is a good reason behind why stackoverflow tries to eliminate questions asking for opinions or preferences. This helps to avoid unnecessary vivid discussions and Style Wars.
The Python code below demonstrates that it is possible to configure a tkinter.Label without prior saving a reference to it in a class self. variable. This is possible because tkinter itself stores all of the information about created and still existing widgets and objects. The comments Try accessing the label inside another function of your class (a method) [without self] are maybe a tiny bit misleading as they assume that this is not possible and make the difference between self or not self.
In the code below along with self or not self are two more cases of programming style covered: no function or class at all, and no class but functions. Label modification after its creation is there only to help understanding tkinter and classes:
import tkinter as tk
# No class or function.
# Down to FIVE (5) lines of code:
root = tk.Tk()
tk.Label(root, text="Hello!", font = "Arial 36", bg='yellow').pack()
root.geometry('800x128')
root.winfo_children()[0].config(text="No class/function: 5 lines")
root.mainloop()
# With a class, but without self.
# There are TWELVE (12) lines of code:
class MyApp_withoutSelf():
def __init__(self):
tk.Label(root, text="Hello!", font = "Arial 36", bg='yellow').pack()
self.setWindowGeometry()
self.changeLabelText()
def setWindowGeometry(self):
root.geometry('640x128')
def changeLabelText(self):
root.winfo_children()[0].config(text="Class no self: 12 lines")
root = tk.Tk()
MyApp_withoutSelf()
root.mainloop()
# With a class and with self
# There are FOURTEEN (14) lines of code:
class MyApp_withSelf():
def __init__(self, root):
self.root = root
self.w = tk.Label(self.root, text="Hello!", font = "Arial 36", bg='yellow')
self.w.pack()
self.setWindowGeometry()
self.changeLabelText()
def changeLabelText(self):
self.w.config(text="Class with self 14 lines")
def setWindowGeometry(self):
root.geometry('640x128')
root = tk.Tk()
myapp = MyApp_withSelf(root)
root.mainloop()
# No class but functions.
# There are ELEVEN (11) lines of code:
root = tk.Tk()
def setup():
tk.Label(root, text="Hello!", font = "Arial 36", bg='yellow').pack()
setWindowGeometry()
changeLabelText()
def setWindowGeometry():
root.geometry('920x128')
def changeLabelText():
root.winfo_children()[0].config(text="No class but functions 11 lines.")
setup()
root.mainloop()

Related

Python scrollable frame and canvas window sizing

I have been trying to find a way to size a frame inside of a canvas window for quite a while to no avail. I finally came across some posts that helped me begin to understand the problem, and eventually dug up a post that gave the solution below:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
root=tk.Tk()
canvas = tk.Canvas(root, background="blue")
frame = tk.Frame(canvas, background="red")
canvas.pack(expand=True, fill="both")
canvas.create_window((0,0), window=frame, anchor="nw", tags="frame")
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
This completely solves my problem....if I don't have the GUI in a function, which I need to. I have multiple different GUI's that would need to implement this solution. I have come across other solutions that use OOP, but I haven't yet wrapped my head around OOP. I've also found a way to make the above code work inside of a program myself:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
def test():
window=tk.Tk()
global canvas
canvas = tk.Canvas(master=window)
frame=tk.Frame(master=canvas, background='red')
canvas.pack(expand=True, fill=tk.BOTH)
canvas.create_window((0,0), window=frame, anchor=tk.NW, tags = 'frame')
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
test()
However, this requires the use of a global variable, which I would rather avoid. Is there anything I'm missing that would help me resize the frame inside of the canvas window? If you have any pointers to where I might even find this information that would also be helpful.
The event object that is passed in has a reference to the widget that received the event. So, just replace your global canvas with e.widget, or initialize a local variable named canvas:
def onCanvasConfigure(e):
canvas = e.widget
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
If it helps, here's an object-oriented version of your application code. Other than the implementation differences, it should behave the same way as the functional version.
import tkinter as tk
class App(tk.Tk): # create a base app class that inherits from Tk
def __init__(self):
super().__init__() # initialize Tk
self.canvas = tk.Canvas(master=self)
self.frame = tk.Frame(master=self.canvas, background='red')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.canvas.create_window(
(0,0),
window=self.frame,
anchor=tk.NW,
tags='frame',
)
self.canvas.bind('<Configure>', self.on_canvas_configure)
def on_canvas_configure(self, event):
self.canvas.itemconfig(
'frame',
height=self.canvas.winfo_height(),
width=self.canvas.winfo_width(),
)
if __name__ == '__main__':
root = App() # instantiate the App class
root.mainloop() # start the app
Since everything here is contained within the App class, you can avoid globals (thanks to self!)

tkinter: Difference between calling super().__init__(self) and tk.Frame(self, self.master)

I'm trying to write a simple tkinter app and would like to follow OOP best practices glimpsed from here:
Best way to structure a tkinter application?
and here:
https://www.begueradj.com/tkinter-best-practices/
and here:
The app should be a simple game for children to learn the alphabet, showing one big letter and then three buttons with animal pictures to chose from.
Now I have started with this code:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master) # or super().__init__(self.master)?
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.title('Example')
self.master.geometry('500x500')
self.master.minsize(100, 100)
self.master.columnconfigure([0, 1], minsize=50, weight=1)
self.master.rowconfigure([0, 1], minsize=50, weight=1)
# self.master.resizable(False, False)
def create_widgets(self):
# Frame that will contain the big letter
self.letterbox = tk.Frame(self.master, bg='red', width=200, height=200)
self.letterbox.grid(row=0, column=0, sticky='nsew')
...
# Frame that contains the 3 animal pictures
self.animalbox = tk.Frame(self.master, bg='yellow')
self.animalbox.rowconfigure(0, weight=1)
self.animalbox.columnconfigure([0, 1, 2], weight=1)
self.animalbox.grid(row=1, column=0, sticky='nsew')
self.animalbox_left = tk.Button(self.animalbox, text='left animal')
self.animalbox_left.grid(row=0, column=0, padx=10, pady=10, ipady=10)
self.animalbox_middle = tk.Button(self.animalbox, text='middle animal')
self.animalbox_middle.grid(row=0, column=1, padx=10, pady=10, ipady=10)
self.animalbox_right = tk.Button(self.animalbox, text='right animal')
self.animalbox_right.grid(row=0, column=2, padx=10, pady=10, ipady=10)
# Frame that contains the score and options
self.scorebox = tk.Frame(self.master, bg='blue')
self.scorebox.grid(row=0, column=1, rowspan=2, sticky='nsew')
def main():
root = tk.Tk()
MainApplication(root)
root.mainloop()
if __name__ == '__main__':
main()
I thought about splitting the questions but since they all adress the same short piece of code and are maybe rather basic, I chose to keep them in one post.
Questions:
Why does it matter, whether I pass two arguments to tk.Frame.init(self, self.master) but only one to argument (self.master) to the super().__init__self(self.master)?
If I pass two arguments to super()... it doesn't work.
Edit: I've found the answer here:
What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__()
In the end it's just an implementation detail of super, if I understand correctly.
In the code examples from Brian Oakley and for example here https://www.pythontutorial.net/tkinter/tkinter-object-oriented-frame/ only "self" is passed as master to the widget, while my code needs "self.master" to work. Why?
Edit2: In the meantime I have found out, that if you initialize the MainApplication inheriting from tk.Frame, then it's possible to use "self.pack() in the MainApplication init-function:
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.pack()
This will pack the just initialized frame into the tk.Tk() root window and then allows other widgets to pack themselves into this frame, thus allowing to reference them like:
self.letterbox = tk.Frame(self, bg='red', width=200, height=200)
...instead of:
self.letterbox = tk.Frame(self.master, bg='red', width=200, height=200)
...which directplay packs the widget into the root window.
Does it matter, whether I assign MainApplication(root) to a variable or not in the main() function definition?
def main():
root = tk.Tk()
MainApplication(root)
root.mainloop()
Edit3: I don't think it matters.
Thanks a lot for your insight. This is my first question on stackoverflow and I tried to make it nice.
Best regards!
Why does it matter, whether I pass two arguments to
tk.Frame.init(self, self.master) but only one to argument
(self.master) to the super().__init__self(self.master)? If I pass two
arguments to super()... it doesn't work.
In fact it doesn't really matter if you are using super() or calling the parent or sibling directly in your example, in another environment it could. It's more than an implementation detail that should be said. See Reymond Hettinger's answer the author of super() considered super! for a quick overview. In python 3 there was a change to new super, syntactic sugar that let you forget about that self parameter.
I think, I have explained self well here. To boiler it down, self is the pointer to the entity of you identified type. To make another example, you can refer to a dog as an undefined individual but a defined type "normally a dog has 4 legs" and you can refer to doggo the dog has 4 legs.
class Dog:
#classmethod
def has_four_legs(cls):
return True
print(Dog.has_four_legs())
doggo = Dog()
print(doggo.has_four_legs())
To come to tkinter, tkinter is conceptual hierarchically and everything ends up in the root, the instance of tkinter.Tk. The master parameter, the only positional argument and the first one you SHOULD define, is the default value for in_ in the chosen geometry method. So every constructor (e.g. tk.Label(master,..) except for the root window MUST have a master, this becomes crucial when it comes to scrollable frames, but that is a different story.
Enough context so far. You other two question can be answered together. Look at your code here:
def main():
root = tk.Tk()
MainApplication(root)
root.mainloop()
if __name__ == '__main__':
main()
You should bind your instances/entities to a variable. Because if you won't address your instance, it does usually not make any sense to initialize it in the first place. For you, you need root as the master parameter, that is why you have it bound to a variable. You should do the same for MainApplication. mainframe = MainApplication(root) then you can do mainframe.pack(). Using self.pack() is effectively the same, but it is a bad practice and you will notice that if your layout becomes more complex and you are searching through all your classes to find the methods that messing up your layout.
So I hope all questions are answered with enough context and further reading. Happy coding !

Python Tkinter GUI File Menu Not Displaying though GUI is operational

I'm relatively new to Python and I'm sure this is an error with the structure of my code, but I cannot seem to get the filemenu to display in my GUI. Can someone tell me what errors I have made with the filemenu inclusion? Also, I am sorry, but the spacing after copying and pasting is a little off. The class indentation level is proper on my side. I am using Python 3.71
Any other comments on better or more Pythonic ways to accomplish what I have here are also welcome and thank you for your help in advance!
from tkinter import *
from tkinter import ttk
import tkinter.scrolledtext as tkst
import os
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class UserGui(tk.Tk):
def __init__(self,parent):
self.parent=parent
self.widgets()
def widgets(self):
self.parent.configure(bg='white')
self.frame1_style = ttk.Style()
self.frame1_style.configure('My.TFrame', background='white')
self.frame2_style = ttk.Style()
self.frame2_style.configure('My2.TFrame',background='white')
self.parent.title("TGUI")
self.frame1 = ttk.Frame(self.parent, style='My.TFrame') #Creating Total Window Frame 1
self.frame1.grid(row=0, column=0, sticky=(N, S, E, W))
self.frame2 = ttk.Frame(self.parent, width=100, height=20, style='My2.TFrame')
self.frame2.grid(row=0, column=6, padx=20, pady=5)
#Menu Creation
self.menu1 = tk.Menu(self.parent, tearoff=0)
self.parent.config(menu=self.menu1)
self.fileMenu = tk.Menu(self.menu1, tearoff=0)
self.fileMenu.add_command(label="Open", command=self.donothing)
self.fileMenu.add_command(label="Save", command=self.donothing)
self.fileMenu.add_separator()
self.fileMenu.add_command(label="Exit", command=self.parent.quit)
self.fileMenu.add_cascade(label="File", menu=self.menu1)
self.editMenu = tk.Menu(self.menu1, tearoff=0)
self.editMenu.add_command(label="Cut", command=self.donothing)
self.editMenu.add_command(label="Copy", command=self.donothing)
self.editMenu.add_command(label="Paste", command=self.donothing)
self.editMenu.add_cascade(label="Edit", menu=self.menu1)
def donothing(self):
filewin = Toplevel(self.parent)
button = Button(filewin, text="Do nothing button")
button.pack()
def main():
root=tk.Tk()
ug=UserGui(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit 1,2,3: I have corrected the add_cascade option for menu with menu=self.menu1 and I still do not have a file menu displaying.
EDIT: I'm sorry I didn't notice the Python-3 tag in time, it's all the same except when inherriting you would call super().__init__ instead of the Frame.__init__ directly. That would make it more Py3-like. Even so, this should still work.
Weirdly, pushing the menu.config down to the run function worked for me - even though it looks like it should work the way you did it.
def main():
root=tk.Tk()
ug=UserGui(root)
root.config(menu=ug.fileMenu)
root.mainloop()
if __name__ == '__main__':
main()
Oterwise there are some things you can work on to make it more OOP like and readable. THis is how I usually handle making GUIs. The idea is to split the GUI's into Frames that then do simmilar things. I.e. your app could have left and right Frame where the RightFrame would hold the textbox ad the left Frame would actually have 2 sub frames - one for the names and dropdowns and the other for the buttons. That way each individual functionality is handled by the Frames themselves and it's not all in one giant class, the elements in those Frames are placed relative to the Frame's grid itself, while all the Frames are placed in the MainFrame's grid. This lets you split a lot of code into modules as well and helps with maintainability.
The sub-frames emit "global" events (events bothering other frames) by propagating them through the MainFrame, that's why they all carry a self.parent - their parent frame, and a self.root - the MainFrame. The MainFrame is also the Frame in which I like to put something like self.data which itself is a class on its own (outside Tkinter) that handles all the data input/output and logic so that you don't clutter the GUI code logic with data calculations and logic. Ideally the Data class would handle data errors and GUI would only then have to handle any errors in logic (such as selecting two impossible-to-combine options from the dropdown menus.
from tkinter import *
from tkinter import ttk
class SubFrame(Frame):
def __init__(self, parent, text="Top Right"):
Frame.__init__(self)
self.pack()
self.parent = parent
self.root = parent.root
self.label=Label(self, text=text).pack()
class RightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=RIGHT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Right Frame").pack()
class LeftFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=LEFT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Left Frame").pack()
#again Frames which would have a parent class RightFrame and root MainFrame
self.subFrame1 = SubFrame(self)
self.subFrame2 = SubFrame(self, text="Top Right SubFrame 2")
class MainFrame(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry("1100x600")
self.title("Working Example")
self.leftFrame = LeftFrame(self)
self.rightFrame = RightFrame(self)
#self.data = MagicalDataHandlingClass()
def run():
app = MainFrame()
app.mainloop()
EDIT answer to comments that are too long to fit
The call to Frame.__init__(...) is made because the class definition looks like class LeftFrame(Frame). Usually to declare a class what you would write is just class LeftFrame. When you add the bit in the () what is happening is called inheritance. When you inherit from a class (called parent), your class (called child) inherits all of the methods and attributes of parent. But much like you have to initialize your class to get an object, i.e. lf = LeftFrame(...) the parent class has to be initialized too. In Python this initialization is done by calling the special dunder __init__(...) function. So that call to Frame.__init__(...) happens because you need to tell the parent class what are all the values it needs to work properly. In Python 3 however it is recommended that instead of instantiating the parent by name like that you use the super function like super().__init__(....). This happens for a lot of complicated reasons most of which you probably don't have to worry about for a while yet (such as what if you inherit from multiple classes at the same time, what if you inherit from a class that inherited from a different one, etc...). I wouldn't try to feel overwhelmed by understanding the complete power of super() if you're just starting because 99% of the time in Python 3 just doing super().__init__(...) will do exactly what you want even if you don't understand. If you feel like getting in over your head Raymond Hettinger has a good writeup of Super is Super and why exactly it's so much better than old way.
I will post this answer for completeness considering #JasonHarper has not copied it to an answer format and I want others to be able to benefit from the post.
The key was the object that I was calling the add_cascade on the child Menu widget object instead of the main Menu widget object called self.menu1. The key was changing:
self.fileMenu.add_cascade(label="File", menu=self.menu1)
to :
self.menu1.add_cascade(label="File", menu=self.fileMenu)
This was the proper way of adding the fileMenu Menu object to the total Menu widget object of self.menu1.

Scheduling order of events with tkinter OOP

I am using tkinter in python 3.6 to display a blue square in the middle of a window. At each click the blue square should disappear and reappear after 2 seconds on a different random location. When running the following code, the blue square (referred as stimulus) does not disappear. Everything else seem to work properly.
This is the code:
import tkinter as TK
import random as RAN
class THR:
def __init__(self, root):
self.root = root
self.root.config(background='black')
self.screenYpixels = 600
self.screenXpixels = 1024
self.ITI = 2000
self.background = TK.Canvas(root, width=1024, height=600, bg='black',
bd=0, highlightthickness=0, relief='ridge')
self.background.pack()
self.newtrial()
def newtrial(self):
self.xpos = RAN.randrange(200, 1000)
self.ypos = RAN.randrange(100, 500)
self.stimulus = TK.Canvas(root,width=100,height=100,bg='blue', bd=0,
highlightthickness=0, relief='ridge')
self.stimulus.place(x=self.xpos, y=self.ypos, anchor="c")
self.stimulus.bind("<Button-1>", self.response)
self.exitbutton()
def response(self, event):
self.stimulus.place_forget()
self.intertrialinterval()
def intertrialinterval(self, *args):
self.root.after(self.ITI,self.newtrial())
def exitbutton(self):
self.exitButton = TK.Button(self.root, bg="green")
self.exitButton.place(relx=0.99, rely=0.01, anchor="c")
self.exitButton.bind("<Button-1>", self.exitprogram)
def exitprogram(self, root):
self.root.quit()
root = TK.Tk()
THR(root)
root.mainloop()
Here a list of things I tried but that did not work
Using time.sleep instead of root.after
Changing the way a newtrial() is called
Putting the sleep or after in different places
The last couple of hours searching the web for similar problems / solutions did not really help. I would like to fix this and at the same time understand what am I doing wrong.
As it turns out the list of links on the right of this webpage provided me with the solution just 3 minutes after posting the question.
This is the original thread Python Tkinter after event OOPS implementation
and here I write the solution:
dropping the parenthesis in the function called with the after method. So this self.root.after(self.ITI,self.newtrial()) is self.root.after(self.ITI,self.newtrial)
Unfortunately I still do not understand how that fixed the problem..

Python3 class function definition confusion about NameError

I'm new to programming and this is my first post on the site. I'm sure I'm making a dumb mistake, but I'd really appreciate a push in the right direction. I'm trying to make a calculator, and want to make a function that produces a Button object for numbers. When I try to run this I get the error:
'NameError: name 'num_but_gen' is not defined'
Here is the code:
from tkinter import *
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
self.Button(text='{}'.format(disp),height=hei, width=wid)
self.place(x=xloc, y=yloc)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=BOTH, expand=1)
Button1 = num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()
Any help would be greatly appreciated! Also bonus points to anyone with suggestions on how to better phrase my question titles in future posts.
jasonharper is right, you need to add self in front of num_but_gen, but there are other problems in your code.
In num_but_gen:
your window class does not have a Button attribute, so you need to remove self. in front of Button
it is not the Window instance but the button that you want to place
you don't need to use text='{}'.format(disp), text=disp does the same.
In init_window:
you store the result of num_but_gen in a variable, but this function returns nothing so that's useless (and capitalized names should not be used for variables, but for class names only)
the width option of a button displaying text is in letters, not in pixels and its height option is in text lines, so wid=40, hei=40 will create a very big button. If you want to set the button size in pixels, you can do it through the place method instead.
Here is the corresponding code:
import tkinter as tk
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
button = tk.Button(self, text=disp)
button.place(x=xloc, y=yloc, height=hei, width=wid)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=tk.BOTH, expand=1)
self.num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = tk.Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()

Resources