Python3 class function definition confusion about NameError - python-3.x

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

Related

Tkinter Label class not appearing when used within a class

I am creating a basic GUI with multiple, but similar, label structures. However, when I created a class to help minimize the text, and placed it within a label frame, the label frame does not appear. This only happens when I use the class within a class, and if I use the regular label and label frame classes everything works out well. I'm trying to figure out as to why this is the case.
My code:
main.py
from tkinter import *
def main():
main_window = Tk()
app = First(main_window)
main_window.mainloop()
class GPULabel(Label):
def __init__(self, master, varText):
varText = varText
super().__init__()
self["text"] = varText
self["anchor"] = "w"
self["width"] = 25
class First:
def __init__(self, root):
self.root = root
self.root.title('First Window')
self.myFrame = LabelFrame(self.root, text="frame")
self.myFrame.pack()
label1 = GPULabel(self.myFrame, "label")
lable1.pack()
if __name__ == '__main__'
main()
This opens a window but it is completely empty. However, if I swap to a regular Label(self.myFrame...) then the window pops up correctly. Why is that? And is there a way to make my original method work?

How can I create a multiple buttons in tkinter that change the background image when hovering over them?

I am coding a GUI for a Mathematics Formula Calculator. I want to create multiple buttons that change the background image when hovering over them, and I don't really know how to go about doing that...
I have already tried creating a class for the button itself so that I can modify the behaviour of it, it did not work...
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
self.defaultbackground = tk.PhotoImage(file = "GeometricBackground.png")
self.currentbackground = tk.PhotoImage(file = "")
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, currentbackground):
image = tk.Label(self, image = currentbackground)
image.pack()
def on_leave(self):
image = tk.Label(self, image = self.defaultbackground)
image.pack()
root = tk.Tk()
classButton = HoverButton(root, currentbackground = "MainMenu.png")
classButton.grid()
root.mainloop()
I was really hoping this would cut it, but I got this error message when it executed:
_tkinter.TclError: unknown option "-currentbackground"
Any help would be appreciated :)
There are several issues with your code:
The error you get is because you are trying to pass the currentbackground option to your HoverButton but given the way your class is defined:
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
currentbackground ends into the kw dictionary you pass in argument to the standard tkinter Button class which has no currentbackground option, hence the unknown option error. To fix it, you can define the options specific to your class like
def __init__(self, master, defaultbackground="", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
so that defaultbackground and currentbackground won't end in the kw dictionary.
When an event occurs, the function you bound to this event is executed with one argument, the "event" object that contains information about the event (like the pointer coordinates, the widget in which the event happened ...) so you need to add this argument when you define on_enter() and on_leave().
You are creating a label to put the image inside then packing this label in the button. This is overly complicated (and probably will result in the button not reacting to click events). The button class has an image option to set the background image of the button, so you can change the image with button.configure(image=<image>).
Inserting all those changes in the code gives
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, defaultbackground="GeometricBackground.png", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultbackground = tk.PhotoImage(file=defaultbackground)
self.currentbackground = tk.PhotoImage(file=currentbackground)
self.configure(image=self.defaultbackground)
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.configure(image=self.currentbackground)
def on_leave(self, event):
self.configure(image=self.defaultbackground)
root = tk.Tk()
classButton = HoverButton(root, currentbackground="MainMenu.png")
classButton.grid()
root.mainloop()

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.

Can not move widgets in Tkinter

I am trying to learn tkinter, but I got a problem and I can`t move forward from this point. I wanted to make just a simple GUI with one button, unfortunately, I am not able to move that button ( being always displayed in the most left upper corner).
This is the code that I used :
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid()
self.master.title('GUI')
quitbttn = Button(self, text='quit')
quitbttn.grid(row=3, column=5)
root = Tk()
app = App(root)
app.mainloop()
Although, I found this snippet of code on the iternet, and it is working perfectly, the only difference being that pack() is used instead of grid() :
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quit_button = Button(self, text='quit')
quit_button.pack(side=BOTTOM)
root = Tk()
app = Window(root)
root.mainloop()
I would like to be able to use grid as well.
Any advice is being apreciated. Thank you!
The reason you cannot move the button is because you only have one element.
quitbttn.grid(row=3, column=5)
This part of the script basically says that the button should be placed a third row down and in the fifth space along. Since you have no other elements in the window it does not move the button at all. This is because all the 2 rows and 4 columns are all equal to 0 so the first place it packs is in the top left corner.
Using the .pack() function allows you move the button without the need of any other button in the window.
If you added another button you would then be able to move around the first button in three different places.
Note that you cannot use the .pack() and .grid() functions in the same window.

In tkinter button is not showing

I don't know why but button is not showing - probably some simple mistake but I don't see it. Please help
I use python 3 if it helps
from tkinter import Tk, Label, Button, Entry, IntVar, END, W, E, filedialog, BOTH, Frame, LEFT
class Resizer(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.master = master
master.title("Resizer")
def askdir():
self.dir_opt = options = {}
options['initialdir'] = '~/'
options['mustexist'] = False
options['parent'] = root
options['title'] = 'This is a title'
filedialog.askdirectory(**self.dir_opt)
Button(self, text='askopenfile', command=askdir).pack()
root = Tk()
my_gui = Resizer(root)
root.mainloop()
The button isn't visible because it's parent (my_gui) isn't visible. You need to use pack, place or grid on my_gui to make it visible. For example:
my_gui = Resizer(root)
my_gui.pack(fill="both", expand=True)

Resources