i tried to execute this code:
import Tkinter as tk
import tkFont
import functools
import math
import random
import time
class Pong(tk.Canvas):
DEFAULTS = dict(width=640, height=480,background='black',highlightthickness=0)
def main(cls):
root = tk.Tk()
root.title('Pong')
root.resizable(False, False)
root.bind_all('<Escape>', lambda event: root.destroy())
game = cls(tkFont.Font(family='Book Antiqua', size=15, weight='bold'), 5, 100,background='black', width=640, height=480)
game.grid()
root.mainloop()
but i had those errors:
Traceback (most recent call last):
line 413, in <module>
pong.main()
, line 17, in main
game = cls(tkFont.Font(family='Book Antiqua', size=15, weight='bold'), 5, 100,background='black', width=640, height=480)
AttributeError: Pong instance has no __call__ method
I can see one main problem so far
cls coming in as a variable
def main(cls):
Then you are using it like a function
game = cls(tkFont.Font(family='Book Antiqua', size=15, weight='bold'), 5, 100,background='black', width=640, height=480)
As you mentioned in the comments, this is how you are creating pong and calling main:
if __name__ == '__main__':
pong = Pong()
pong.main()
At the time where you call Pong() you are creating an instance. As such the main method you are calling is an instance method where the first implicit parameter is the instance itself. Usually this parameter is named self for instance methods—not cls as you did.
So if we rename it, it’s clear what’s failing afterwards:
game = self(tkFont.Font(…), 5, 100, background='black', width=640, height=480)
Keep in mind that self is an object, and instance of Pong. So it’s not a function and not a constructor you can call; you are actually trying to call the instance which won’t work here.
I think what you actually want to do instead is have the main as a “static” method, being the entry point to your application. In that case, you should add the #classmethod decorator in front of the method declaration:
#classmethod
def main(cls):
root = tk.Tk()
# …
In that case, cls will be indeed the type Pong, which you can call later to create a Pong object.
You should change your __name__ == '__main__ part to just Pong.main() though, as you are no longer calling main for an instance but the whole type.
if __name__ == '__main__':
Pong.main()
You are doing a very strange thing -- you are creating a subclass of tk.Canvas, yet you don't initialize tk until after you create this object. That's not the right way to be using tkinter. You should initialize Tk outside of the scope of this class, or don't inherit from tk.Canvas.
I recommend you restructure your code so that you initialize tkinter first, and then create the instance of your game.
For example:
import Tkinter as tk
class Pong(tk.Canvas):
def __init__(self, root):
tk.Canvas.__init__(self, root)
<put other "main" code here if you want>
if __name__ == "__main__":
root = tk.Tk()
pong = Pong(root)
pong.pack(fill="both", expand=True)
root.mainloop()
Related
I have been wrestling for a very long time with the issue of creating a Tkinter gui in modular fashion using classes. While there are many examples on this site - and believe me, I have read them all - they have all been too complex for me to understand. In particular, I could not work out how the imported modules could 'talk to' functions in the main application. I have finally had a eureka moment - I created a class in a module that defines a root window and a button. Then, I wrote a main python file that imports the root window / button module, and tests interactions between the imported module and the main app. All of those tests were successful, which is huge progress for me, as far as it goes. Here is the module code, saved as 'fmod.py':
import tkinter as tk
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = tk.Button(self, text='Click Me')
self.button.pack()
Here is the python file written to import the module, and test all the interactions I was interested in understanding:
import tkinter as tk
# import module
import fmod
# create gui root window as an instance of imported MainWindow class
MainWin = fmod.MainWindow()
# define a local function for testing purposes
def ButtonClicked():
print("Button clicked")
# alter attributes of imported root/button from within this file
MainWin.geometry("700x400")
MainWin.config(bg = "yellow")
MainWin.button.config(bg="lightblue")
# add a new widget to root from within this file
NewLabel = tk.Label(MainWin,text="Label")
NewLabel.pack()
# connect an imported widget to the above local function
MainWin.button.config(command=ButtonClicked)
# mainloop
MainWin.mainloop()
As mentioned, all of the tests in the above code worked successfully. The question is this: I would rather have the MainWindow class define the root window and nothing else. So rather than including the button in that code, I'd like to write another entirely separate class that simply defines a button, which could be imported into my app separately. Would anyone be kind enough to help me write that code? I tried copying code from the MainWindow class, and it worked, but it opened an entirely new window (probably because of the init / super init code, which I do not fully understand, and don't really need to understand at the moment - it works, and I'm fine with that). I want code for a simple button that I could import into the main app, as a widget, and that I could place in the MainWin window inside the app.
You can define the class in a separate module and then import it, but you will still want to initiate the button inside of your main window like you are doing now. After all the button does belong on the window.
To create a button class it would be similar to how you created the MainWindow...
import tkinter as tk
class MyButton(tk.Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
... do something
def buttonClicked(self, *args):
print("Button Clicked")
Then you could just leave you mainwindow the way it is except switch out the class for your class. I also suggest moving a lot of your logic that is in the global scope inside of your MainWindow, and minimizing your use of the global scope as much as possible. For example:
import tkinter as tk
from mybuttonmodule import MyButton
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = MyButton(self, text='Click Me')
self.button.pack()
self.button.config(command=self.button.buttonClicked)
self.newlabel = tk.Label(self, text="Label")
self.newlabel.pack()
self.geometry("700x400")
self.config(bg = "yellow")
self.button.config(bg="lightblue")
if __name__ == "__main__":
window = MainWindow()
window.mainloop()
My program starts with a 'Loading_UI' class that shows a simple loading bar and creates an object that contains data fetched from .csv files.
When the loading (= data fetching) is completed, the loading bar destroys itself (root.destroy()) and then I want to pass on the object that I created to another class that will show the main UI of the program. To keep things neat, I want to call the main UI from its own .py file and own Main_UI class.
Using the idea above, my question is: how can I pass the object on from the Loading_UI class to the Main_UI class?
(Run the program from RUN_loading.py)
(DataBase.py) Simple object creation:
class DataBase:
def __init__(self, name):
self.name = name
def show_content(self):
print(self.name)
And in the other .py file with class Loading_UI, something like this:
(RUN_Loading.py) UI with loading bar simulation:
from tkinter import *
from DataBase import DataBase
class Loading_UI:
def __init__(self, master):
self.master = master
self.DataBase = DataBase("This is our object.")
self.load_pct = DoubleVar()
self.load_pct.set(0)
loading_bar = Label(textvariable = self.load_pct)
loading_bar.grid()
self.handle_loading_progress()
def handle_loading_progress(self):
if self.load_pct.get() < 10:
self.load_pct.set(self.load_pct.get() + 1)
self.master.after(200, self.handle_loading_progress)
else:
self.show_main()
def show_main(self):
root.destroy()
from UI_Main import Main_UI
Main = Main_UI(self.DataBase)
Main.main()
root = Tk()
root.geometry("100x100+300+300")
app = Loading_UI(root)
root.mainloop()
And the main UI which would need to process the object would look like this.
(UI_Main.py) Main UI Window that pops up after loading is complete.
from tkinter import *
class Main_UI:
def __init__(self, database_object):
self.DataBase = database_object
# Run this function to check that the
# data object is properly received.
self.DataBase.show_content()
root = Tk()
app = Main_UI(root, database_object)
root.mainloop()
if __name__ == '__main__':
main()
The above is obviously wrong, but I just want to present the idea what I am after. Running the code above gives:
NameError: name 'database_object' is not defined
EDIT: Edited the code with a practical (simplified) example.
The best way to do this might be to create a Main_UI object in Loading_UI file.
Loading_UI.py:
import Main_UI
# Create your progress bar
data = getDataFromCSV('file.csv')
# Delete your progress bar
main_ui = Main_Ui(data)
I'm no expert in tkinter but I've researched its documentation a little bit and this is how I'd do it.
Tkinter(as pygtk for that matter) has the concept of events and signals that are fired upon them. You can bind an event in any tkinter widget (such as a progressbar) using the bind function and have a callback function that will act on this. Apparently there's a predefined list of tkinter events that are available and in your case I think the <Destroy> A widget is being destroyed. will be of use.
So in your loading_ui.py file:
widget = ttk.Progressbar(parent, option=value, ...) # create your progressbar
#Bind it to the destroy event. Once destroyed call the proceed_with_main function and pass your object
widget.bind('<Destroy> ', lambda event, db=self.DataBase:
self.proceed_with_main(db))
def proceed_with_main(db):
from UI_Main import Main_UI
Main = Main_UI(db)
Main.main()
Ok multiple things are not done right here. Corrections are posted in the code directly.
(Run_loading.py)
from tkinter import *
from DataBase import DataBase
class Loading_UI:
def __init__(self, master):
self.master = master
self.DataBase = DataBase("This is our object.")
self.load_pct = DoubleVar()
self.load_pct.set(0)
loading_bar = Label(textvariable = self.load_pct)
loading_bar.grid()
self.handle_loading_progress()
def handle_loading_progress(self):
if self.load_pct.get() < 10:
self.load_pct.set(self.load_pct.get() + 1)
self.master.after(200, self.handle_loading_progress)
else:
self.show_main()
def show_main(self):
root.destroy()
from UI_Main import Main_UI
Main = Main_UI(self.DataBase)
# Main.main() This line is wrong, your object Main_UI() does not have a defined 'main()' function
root = Tk()
root.geometry("100x100+300+300")
app = Loading_UI(root)
root.mainloop()
(UI_Main.py)
from tkinter import *
class Main_UI:
def __init__(self, database_object):
self.DataBase = database_object
# Run this function to check that the
# data object is properly received.
self.DataBase.show_content()
self.tkWindow = Tk() # if you want to keep an new window you could keep this
self.tkWindow.mainloop()
""" You shouldn't do things outside of the __init__() function of this class.
root = Tk() lines to delete
app = Main_UI(root, database_object) lines to delete
root.mainloop() lines to delete """
""" This part is not necessary
if __name__ == '__main__':
main() """
I'm trying to make a little program by using classes. So far, I've made two classes, in which the first one will run the next one. When I run this, I get an error message. I don't understand what's wrong, but it looks like it has something do to about that I define the name Menu1 before it's been read. I'm going to create a new function after these classes, that'll first run MainWindow, and then Menu1. I would appreciate help.
Code:
class MainWindow:
app = Tk()
app.title("MyApp")
window = Frame(app, width=1050, height=550)
app.minsize(width=1050, height=550)
window.pack()
menu = Menu1()
menu.makeMenu()
app.mainloop()
class Menu1:
def makeMenu(self):
app.config(menu=menu)
menu.add_cascade(label="Settings", menu=subMenu)
subMenu.add_command(label="Settings", command=settings1)
def settings1():
print("Open new window")
if __name__ == "__main__":
MainWindow()
Error message:
Traceback (most recent call last):
File "", line 7, in <module>
class MainWindow:
File "", line 13, in MainWindow
menu = Menu1()
NameError: name 'Menu1' is not defined
Process finished with exit code 1
Everything under class MainWindow is run immediately. It is not in a method. At that point class Menu1 has not yet been executed and no class by that name exists yet.
It looks like you really only wanted MainWindow to be a function instead:
def main_window():
app = Tk()
app.title("MyApp")
window = Frame(app, width=1050, height=550)
app.minsize(width=1050, height=550)
window.pack()
menu = Menu1()
menu.makeMenu()
app.mainloop()
(I used a lowercase letter this time, as the Python style guide reserves camel-case names for classes).
Your next problem is that Menu1.makeMenu() has no access to the app local variable in main_window(); you would need to pass that in:
menu = Menu1()
menu.makeMenu(app)
and
class Menu1:
def makeMenu(self, app):
app.config(menu=self)
menu.add_cascade(label="Settings", menu=subMenu)
subMenu.add_command(label="Settings", command=setting1)
Note that I changed menu to self there, menu was another local name in main_window.
The code still won't work because you haven't defined the name subMenu anywhere, but this is at least a step or 2 closer.
I want to create some frames in Tkinter, that should be updated periodically.
Here is the code for on of them:
from Tkinter import *
import time
import random
class KopfFrame:
def __init__(self,master):
frame = Frame(master,bg="tan")
frame.pack(side=TOP,expand=YES, fill=BOTH)
self.ZeitLabel = Label(frame)
self.ZeitLabel.pack(side=RIGHT, expand=NO,ipadx=2, ipady=2)
self.refresh()
def refresh(self):
self.ZeitLabel.configure(text=time.strftime("%H:%M:%S"))
# call this function again in 5 seconds
#print(self)
self.after(5000, self.refresh)
root = Tk()
K = KopfFrame(root)
root.mainloop()
But, when I run it, I have the error:
AttributeError: KopfFrame instance has no attribute 'after'
I am pretty sure, that the way of calling is the problem. So if someone could help me, thaf I would either be thankful for a hint to a good tutorial for functions, classes an how to call an use them.
You are trying to call self.after(), with self being an instance of your KopfFrame class. That class does not have such a method defined.
Perhaps you wanted to call the Frame.after() method here. You'll need to store a reference to the Frame object you pass in first, then call after on that:
class KopfFrame:
def __init__(self,master):
# make frame an attribute on self
self.frame = Frame(master, bg="tan")
self.frame.pack(side=TOP,expand=YES, fill=BOTH)
self.ZeitLabel = Label(self.frame)
self.ZeitLabel.pack(side=RIGHT, expand=NO,ipadx=2, ipady=2)
self.refresh()
def refresh(self):
self.ZeitLabel.configure(text=time.strftime("%H:%M:%S"))
# now you can reach the frame and call after on that:
self.frame.after(5000, self.refresh)
What I changed:
Instead of making frame only a local in the KopfFrame.__init__ method, make it an attribute on self.
Use the self.frame reference to the frame in KopfFrame.refresh() to access the Frame.after() method.
I have a tkinter GUI python code that creates a gui interface to my code, in the code later snack sound toolkit is used (which also uses Tk and creates an instance using root = Tk()). As, mainloop of the previously GUI application is already running to everytime snack function is called a new empty default tk window pops up. As this happens quite a lot, there are hundreds of empty tk windows on screen when this code executes. I have tried to close them using numerous methods root.destroy,root.withdraw, WM_DELETE_WINDOW etc. but to no solution.
Is there any way this can be done in tkinter?
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1 = thread.start_new_thread(run, (PATH_TO_WAVE_FILE,))
def run(path):
for k in range(10):
PITCH_VALUES = snack_work(path)
print PITCH_VALUES
def snack_work(INPUT_WAVE_FILE):
# initializing the snack tool
root = tk.Tk()
tkSnack.initializeSnack(root)
# root.withdraw()
mysound = tkSnack.Sound()
# processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES = mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()
Make run() and snack_work() into instance methods of your app object, so that they can easily access that object's attributes. To work with a more minimal MCVE that doesn't rely on external libraries or files, I tested the following with simple print() (I'm on Python 3) and after() calls rather than snack stuff, as all I wanted to check was that the other functions could access a tkinter object.
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1=thread.start_new_thread(self.run,(PATH_TO_WAVE_FILE,))
def run(self, path):
for k in range(10):
PITCH_VALUES = self.snack_work(path)
print PITCH_VALUES
def snack_work(self, INPUT_WAVE_FILE):
## initializing the snack tool
tkSnack.initializeSnack(self) # use self instead of the separate root object
# self.withdraw()
mysound=tkSnack.Sound()
## processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES= mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()