I am trying to create a class, which will help me to optimize/organize creation of another Tkinter buttons. I am aware that an inheritance should have been used. I've been trying to do something like this:
from tkinter import *
window = Tk()
window.title("Button class app")
upper_frame = Frame(window)
upper_frame.pack(fill="both", side=TOP, expand=1)
lower_frame = Frame(window)
lower_frame.pack(fill="both", side=BOTTOM, expand=1)
function1 = None
function2 = None
class Buttons(Button):
def __init__(self, master, text, command):
Button().__init__(self) # or super(). method
self.master=master
self.text=text
self.command=command
self.font=("Comic Sans", 30)
self.fg="#7df9ff",
self.bg="#FFFF00",
self.activeforeground="#7df9ff",
self.activebackground="#FFFF00",
self.state=ACTIVE,
self.compound='bottom',
button1 = Buttons(upper_frame, "Some text 1", function1)
button1.pack(fill="x")
button2 = Buttons(lower_frame, "Some text 2", function2)
button2.pack(fill="x")
I've been trying to use super() method instead of Button().__ init __(), but I am not sure what arguments should I pass on to/what should I really inherit. Using super().
method gives me error: 'Buttons' object has no attribute 'tk'.
Somehow, also with Button(). __ init __ I could also get access to i.e.: self["text"]=text, but not with self.text=text as usual (why?)
I've been also trying to make a class for main Tkinter window, and then for frames - to make inheritance for a Tkinter Button Class object - but I think this is not the way it should work - I think I should make an inheritance from predefined Tkinter Button/tk.Button class.
I will be very grateful for any help or explanations.
In other GUIs you could use self.text but tkinter uses
self['text'] = text
self.config(text=text)
And the same with other values (maybe except master)
It allow also
self.config(text=text, command=command)
self.config({"text":text, "command":command})
setting = {'text': 'other', 'bg': 'red'}
self.config(setting)
setting = {'text': 'other', 'bg': 'red'}
self.config(**setting) # with `**` to unpack dictionary
Full working code.
import * is not preferred - using import tkinter as tk I can create class with name Button (without s) and use three different classes at the same time: tk.Button, ttk.Button and my Button.
In __init__ I use **kwargs so I can send other parameters to class.
import tkinter as tk # PEP8: `import *` is not preferred
# --- classes --- # PEP8: all classes after imports
class Button(tk.Button):
def __init__(self, master, text, command, **kwargs):
super().__init__(master, text=text, command=command)
#self.master = master
#self['text'] = text # PEP8: spaces around `=`
#self['command'] = command
self['font'] = ("Comic Sans", 30)
self['fg'] = "#7df9ff"
self['bg'] = "#FFFF00"
self['activeforeground'] = "#7df9ff"
self['activebackground'] = "#FFFF00"
self['state'] = 'active'
self['compound'] = 'bottom'
self.config(**kwargs)
# --- functions --- # PEP8: all functions after classes (before main code)
def function1():
print('function1')
def function2():
print('function2')
# --- main ---
window = tk.Tk()
window.title("Button class app")
upper_frame = tk.Frame(window)
upper_frame.pack(fill="both", side=TOP, expand=1)
lower_frame = tk.Frame(window)
lower_frame.pack(fill="both", side=BOTTOM, expand=1)
button1 = Button(upper_frame, "Some text 1", function1, bg='red', activebackground='blue')
button1.pack(fill="x")
button2 = Button(lower_frame, "Some text 2", function2, bg='green', activebackground='blue')
button2.pack(fill="x")
window.mainloop()
PEP 8 -- Style Guide for Python Code
Related
Creates two windows and gridding is not correct. Some additional comments in the code initiation.
I have used this approach, without the super init with no problem, many times.
Advice appreciated.
Thanks
# timhockswender#gmail.com
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self):
super(constants_page, self).__init__() # from stackoverflow
# if not used error = 'constants_page' object has no attribute 'tk'
# if used, another tiny window is opened
# in addtion to the constants_page
self.constants_page = tk.Tk()
self.constants_page.geometry("1000x500") #width*Length
self.constants_page.title("Owen's Unit Conversion App")
self.constants_page.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self.constants_page,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
# Problem: not gridding properly
self.title_label = ttk.Label(self.constants_page, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.constants_page.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.constants_page.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = constants_page()
program.mainloop()
if __name__ == "__main__":
Show_Page()
The super call expects you to provide a root window (an instance of tk.Tk()). If you don't provide one it defaults to the first root window opened, and if none has been opened yet then it helpfully opens one for you. A few lines later you open a second one yourself.
The easy fix is to remove the self.constants_page = tk.Tk() line. The proper fix is to make the Tk() instance outside of the class and pass it in. This allows you to use the Frame class itself to lay out widgets (use self instead of self.constants_page). Try this:
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
master.geometry("1000x500") #width*Length
master.title("Owen's Unit Conversion App")
self.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
self.title_label = ttk.Label(self, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = tk.Tk()
win = constants_page(program)
win.pack()
program.mainloop()
if __name__ == "__main__":
Show_Page()
I'm trying to set up a list of checkbuttons from top to bottom in the GUI and add up the associated "onvalues" for each of the checkbuttons that are on.
My problem now is that for some reason my 'command' attribute in my 'calcbutton' is giving me a "Name 'calc_cost' is not defined" error.
I've added a bunch of imports that you see at the top of the code hoping that would solve the problem, to not much avail.
import tkinter as tk
from tkinter import *
from tkinter import Button
servicelist = ("Oil change","Lube job","Radiator flush","Transmission flush","Inspection","Muffler replacement","Tire rotation")
servicecost = (30,20,40,100,35,200,20)
a = 0
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def calc_cost(self):
print(a)
def init_window(self):
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
for i in range(len(servicelist)):
serviceButton = Checkbutton(self, text=servicelist[i], onvalue = servicecost[i], var = a)
serviceButton.place(x=0, rely = i*.1)
calcButton = tk.Button(self, text = "Calculate Cost", fg = "black", bg = "green", command = calc_cost)
calcButton.pack(side = "bottom")
root = Tk()
#size of the window
root.geometry("400x300")
app = Window(root)
root.mainloop()
The checkbuttons pop up and the GUI works for the most part besides the displaying of the 'calcbutton' as well as getting the "NameError: name 'calc_cost' is not defined"
Change command = calc_cost to command = self.calc_cost
self represents the instance of the class. By using the self keyword we can access the attributes and methods of the class in python.
It will give you this output
MainTicTacToe.py
import tkinter as tk
import MenubarCommand as mbc
class Game(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parnt=parent
# self.parnt.geometry('500x300')
self.parnt.title("Tic Tac Toe")
# self.pack()
menubar=tk.Menu(parent)
# 'settings' menu
settingsOption=tk.Menu(menubar, tearoff=0)
settingsOption.add_command(label='Player Settings', command=self.doit)
settingsOption.add_command(label='Board Settins', command=self.doit)
menubar.add_cascade(label='Setings', menu=settingsOption)
# without using this method, menubar isn't shown in Frame
self.parnt.config(menu=menubar)
def doit(self):
root=self.win()
set=mbc.playerSettings(root)
# print(set.p1name)
root.mainloop()
def win(self):
return tk.Tk()
def main():
root=tk.Tk()
Game(root)
root.mainloop()
main()
MenubarCommand.py
import tkinter as tk
class playerSettings(tk.Frame):
def __init__(self,parent=tk.Frame):
tk.Frame.__init__(self,parent)
self.parnt=parent
self.parnt.title("Player Setting")
self.p1name='Player 1'
self.p2name='Player 2'
self.p1symbol='x'
self.p2symbol='o'
# **********************************#
self.p1NameLabel = tk.Label(parent, text='Player 1: Name ')
self.p1NameLabel.grid(row=0, column=0)
self.p1NameEntry = tk.Entry(parent)
self.p1NameEntry.insert(0,self.p1name)
self.p1NameEntry.bind('<FocusIn>', lambda event:self.p1NameEntry.delete(0,'end'))
self.p1NameEntry.grid(row=0, column=1)
apply=tk.Button(parent, text="Apply Settings", fg='white', bg='gray', command=self.saveStat)
apply.grid(row=2, rowspan=1, columnspan=4)
def saveStat(self):
print('Settings Window Destroyed')
self.p1name=self.p1NameEntry.get()
print(self.p1name)
self.parnt.destroy()
I want to change the value of attribute of an instance in one file from the instance of another class in another file already created.
When I change default Player name in MenubarComman.py file, I want to access the changed name from MainTicTacToe.py class. How can I do this?
I'm new new in Python.
Thanks in Advance.
You problems stem from 2 instances of Tk(), and sloppy programming, i.e. sometimes you use parent, and sometimes self.parnt which is a bad habit to get into, so everything is changed to self.top so an error will pop up if any of those two remains.. You also have to have a way to signal when PlayerSetting() is finished. The way the program is structured, the only way that I know of is to check for "alive" using recursion. You could also have PlayerSettings call a function in Game() when finished, which would print the value. I have cleaned up your code and it works as I understand it should.. Note that the 2 classes are in the same file to make it easier to test and post here.
import tkinter as tk
##import MenubarCommand as mbc
class Game():
def __init__(self,parent):
self.parnt=parent
# self.parnt.geometry('500x300')
self.parnt.title("Tic Tac Toe")
# self.pack()
menubar=tk.Menu(parent)
# 'settings' menu
settingsOption=tk.Menu(menubar, tearoff=0, bg="yellow")
settingsOption.add_command(label='Player Settings', command=self.doit)
settingsOption.add_command(label='Board Settins', command=self.doit)
menubar.add_cascade(label='Setings', menu=settingsOption)
# without using this method, menubar isn't shown in Frame
self.parnt.config(menu=menubar)
def doit(self):
## do not open more than one Tk() instance
##root=self.win()
self.top=tk.Toplevel(self.parnt)
self.set_it=PlayerSettings(self.top)
self.get_variable()
##root.mainloop()
def get_variable(self):
""" check continuously if PlayerSettings has exited and
if so, get the Entry's value
"""
if self.set_it:
self.parnt.after(1000, self.get_variable)
else:
print("from Game", self.set_it.p1name)
def win(self):
return tk.Tk()
class PlayerSettings():
def __init__(self, parent):
self.top=parent
self.p1name='Player 1'
self.p2name='Player 2'
self.p1symbol='x'
self.p2symbol='o'
# **********************************#
self.p1NameLabel = tk.Label(self.top, text='Player 1: Name ', bg="lightblue")
self.p1NameLabel.grid(row=0, column=0)
self.p1NameEntry = tk.Entry(self.top)
self.p1NameEntry.focus_set()
self.p1NameEntry.insert(0,self.p1name)
##self.p1NameEntry.bind('<FocusIn>', lambda event:self.p1NameEntry.delete(0,'end'))
self.p1NameEntry.grid(row=0, column=1, sticky="nsew")
apply=tk.Button(self.top, text="Apply Settings", fg='white', bg='gray', command=self.saveStat)
apply.grid(row=2, rowspan=1, columnspan=4)
def saveStat(self):
self.p1name=self.p1NameEntry.get()
print(self.p1name)
print('Settings Window Destroyed')
self.top.destroy()
root=tk.Tk()
Game(root)
root.mainloop()
I am trying to use Tkinter to create a GUI, the code is:
from tkinter import *
class LoginFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
# initialize the login screen UI
def initUI(self):
self.parent.title("Login Screen")
# create a menu bar
menubar = Menu(top)
# create a help menu
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=about)
menubar.add_cascade(label="Help", menu=helpmenu)
# display the menu
self.parent.config(menu=menubar)
#----------------------------------------------------------------------
def about():
"""about info"""
print("This is a Tkinter demo")
# create a button
#----------------------------------------------------------------------
def make_button(parent, command, caption=NONE, side=top, width=0, **options): # name error 'top' is not defined
"""make a button"""
btn = Button(parent, text=caption, command=command)
if side != top:
btn.pack(side=side)
else:
btn.pack()
return btn
def main():
top = Tk()
# Set up login frame properties
top.title("Login Screen")
# create a login button
login_btn = make_button(top, about, "Login")
top.mainloop()
if __name__ == '__main__':
main()
I tried to run the code, python gave me the following error:
builtins.NameError: name 'top' is not defined
You only define top in main, not at global scope, and even if it was at global scope, you defined it after make_button; default arguments in Python are evaluated once, at definition time, not looked up at call time.
The best approach would probably be to make most of your functions into class methods, and have the class itself create a top attribute.
But for the time being, you could do a minimalist change:
# Use None as a default at definition time, since top doesn't exist yet
def make_button(parent, command, caption=NONE, side=None, width=0, **options):
"""make a button"""
if side is None: # Convert None to top at call time
side = top
btn = Button(parent, text=caption, command=command)
if side is not top: # Minor tweak: Use identity test over equality
btn.pack(side=side)
else:
btn.pack()
return btn
def main():
global top # Make top a global then define it
top = Tk()
... rest of main ...
Note that this is still not great code; without main being executed, there is no top global defined, so your code is only usable as the main program, not as an importable module without a lot of hackery.
You are referring to top in the make_button parameter list - where you say side=top, but haven't actually defined top before that function. There is no global called top.
You can't set it as the default for a parameter until it is defined.
I also got same error, but I realized, I need to use upper case for "TOP" not "Top", after I used uppercase, it worked for me.
frame = Frame(root)
frame.pack()
root.title("Calcu_Displayframe")
num_1=StringVar()
topframe = Frame(root)
topframe.pack(side=TOP)
txtDisplay=Entry(frame, textvariable=num_1, bd=20, insertwidth=1, font=30)
txtDisplay.pack(side=TOP)
root.mainloop()
if you import tkinter like this : import tkinter as tk
then the pack gonna be test.pack(tk.TOP)
if you import tkinter like this : from tkinter import *
then the gonna be test.pack(TOP)
I have the app with Tkinter, for example:
from Tkinter import *
from ttk import *
class MyMenu(Menu):
....
class MyNotebook(Notebook):
....
tk=Tk()
f1=Frame(master=tk)
f2=Frame(master=tk)
menu=MyMenu(master=f1)
notebook=MyNotebook(master=f2)
I want to add command in menu, which will add new tab in notebook. How can i do this?
P.S. f1 != f2 It's important!
P.P.S. functions, that used as commands in menu may be in another file
One of the frames is not necessary for the menu, since it should be configured with the window and not placed with the geometry manager. Something similar to this can do the job:
# ...
def add_tab():
text = "Tab {}".format(len(notebook.tabs()))
frame = Frame(notebook, width=100, height=100)
notebook.add(frame, text=text)
menu=MyMenu()
menu.add_command(label="Add tab", command=add_tab)
tk.config(menu=menu)
However, I recommend you to: a) Define a class instead of using global variables; and b) Don't use import * since Tkinter an ttk uses the same name for different classes. It will be not only more organized, but also easier to read:
import Tkinter as tk
import ttk
class MyMenu(tk.Menu):
pass
class MyNotebook(ttk.Notebook):
pass
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.frame = ttk.Frame(self)
self.notebook = MyNotebook(self.frame)
self.frame.pack()
self.notebook.pack()
self.add_tab()
menu = MyMenu()
menu.add_command(label="Add tab", command=self.add_tab)
self.config(menu=menu)
def add_tab(self):
text = "Tab {}".format(len(self.notebook.tabs()))
frame = ttk.Frame(self.notebook, width=100, height=100)
self.notebook.add(frame, text=text)
app = App()
app.mainloop()
The solution is simple: for an instance of class A to interact with an instance of class B, class A needs a reference to the instance of class B. That means you need to either pass it in to the constructor, or set if after creation. For example:
class MyMenu(Menu):
def __init__(self, notebook):
...
self.add_command("New page", command=notebook.add(...))
...
notebook = Notebook(...)
menu = MyMenu(notebook)
Another way -- which I think is better -- is to pass what is sometimes called a controller -- a class that knows about all the widgets, or provides an interface to the widgets. For example, you could implement your app as a class and use an instance of that as your controller:
class MyMenu(Menu)
def __init__(self, app=None):
...
self.add_command(..., command=app.add_tab)
class App(Tk):
def __init__(self):
...
self.menu = MyMenu(self, controller=self)
self.notebook = Notebook(...)
...
def add_tab(self, label):
frame = Frame(self)
self.notebook.add(frame, text=label)
app = App()
app.mainloop()