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.
Related
This is what I tried
GUI.py
from tkinter import *
class Search:
def __init__(self, root):
self.query = None
self.root = root
# Create and draw container
self.frame = Frame(self.root)
self.frame.pack()
# Create widgets
self.search_bar = Entry(self.frame)
self.search_button = Button(self.frame, command = self.get_query, text = 'Search')
# Draw widgets
self.search_bar.pack()
self.search_button.pack()
def get_query(self):
self.query = self.search_bar.get()
def create_gui_root(): # Should I be doing this in a function in GUI.py?
root = Tk()
root.geometry('300x300')
return root
main.py
from GUI import *
def use_query_to_do_stuff(query):
print(query)
def main():
# Create GUI root instance
root = create_gui_root()
# Initialize search page
search_page = Search(root)
# I know this part is wrong, just not sure where to go from here
# It runs the search_submit method on start instead of waiting for the button to be pressed
query = search_page.get_query()
use_query_to_do_stuff(query)
root.mainloop() # Also is this in the right place?
main()
I want the program to wait until I press the button, then assign the text inside the Entry widget to a variable.
I started off with a fully working text-based program in main.py and I'm trying to add a GUI.
I'm trying to learn how to split the logic and GUI into separate Python files so I import the Search class into the main.py file and I need GUI.py to return the search query back to main.py when the button is pressed.
I'm also unsure if I'm going about using classes and functions and separating logic from GUI the right way. Should I be using a bunch of functions instead of a class? Or am I using the class wrong?
Are the root = Tk() and root.mainloop() lines even in the right place?
I thought of importing main.py into GUI.py, creating a method in Search() to call the function use_query_to_do_stuff() from main.py while passing self.search_bar.get() as a parameter directly, then binding that method to the self.search_button command. However, I feel like cross-importing your main.py file into your GUI.py is counter-intuitive, I kind of feel like all roads should 'lead back' to the main.py file, if that makes sense. Then again, maybe I'm completely lost.
I made main.py first and it ran as intended, but now that I'm trying to implement a GUI I feel like I'm going about everything slightly incorrectly.
Add Label in GUI.
Add label.configure in get_query(self) function
.
Snippet code:
# Create widgets
self.search_bar = Entry(self.frame)
self.search_button = Button(self.frame, command = self.get_query, text = 'Search')
self.label = Label(self.frame)
# Draw widgets
self.search_bar.pack()
self.search_button.pack()
self.label.pack(padx=15, pady=35)
def get_query(self):
self.query = self.search_bar.get()
self.label.configure(text=self.query)
self.search_bar.delete(0, END)
print(self.query)
Screenshot before:
Screenshot after:
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 a new member to Stack Overflow.
I found this thread, but was not allowed to comment or ask questions on it, so I thought I'd just reference it here: How can I make a in interactive list in Python's Tkinter complete with buttons that can edit those listings?
from tkinter import *
import os
import easygui as eg
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
# character box
Label(frame, text = "Characters Editor").grid(row = 0, column = 0, rowspan = 1, columnspan = 2)
charbox = Listbox(frame)
for chars in []:
charbox.insert(END, chars)
charbox.grid(row = 1, column = 0, rowspan = 5)
charadd = Button(frame, text = " Add ", command = self.addchar).grid(row = 1, column = 1)
charremove = Button(frame, text = "Remove", command = self.removechar).grid(row = 2, column = 1)
charedit = Button(frame, text = " Edit ", command = self.editchar).grid(row = 3, column = 1)
def addchar(self):
print("not implemented yet")
def removechar(self):
print("not implemented yet")
def editchar(self):
print("not implemented yet")
root = Tk()
root.wm_title("IA Development Kit")
app = App(root)
root.mainloop()
Could somebody explain to me why the very last line is root.mainloop()?
Being a novice with Python, and coming from a background that's procedural-oriented with no object-orient experience, I would have thought it would have been app.mainloop().
In fact app = App(root) , app is never used again in the rest of the code! I'm having trouble understanding why root.mainloop() still works.
I'm not sure if you'll find this answer satisfying, but you call root.mainloop() because root is the object that has the mainloop method. In the code you've given, App has no mainloop function.
In simpler terms, this is just how tkinter works -- you always end your script by calling the mainloop method of the root window. When that method returns, your program will exit.
Let's start at the beginning. The simplest, non-OO Tkinter program is going to look like the following example. Note that this is a python 2.x example, and I do not use a global import since I think global imports are bad.
import Tkinter as tk
root = tk.Tk()
<your widgets go here>
root.mainloop()
Many people find that a pure procedural style is not an effective way to write code, so they might choose to write this in an object-oriented style. It's natural to think of "the app" as a singleton object. There are many ways to do this -- the one in your question is, unfortunately, not one of the clearer ways to do it.
A slightly better way, IMO, would be to structure the code like this:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
<your widgets go here>
app = App()
app.mainloop()
In this case, mainloop is still being called, though now it's a method of App since App inherits from Tk. It is conceptually the same as root.mainloop() since in this case, app is the root window even though it goes by a different name.
So, in both cases, mainloop is a method that belongs to the root window. And in both cases, it must be called for the GUI to function properly.
There is a third variation which is what the code you picked is using. And with this variation, there are several ways to implement it. The variation is your question uses a class to define the GUI, but does not inherit from Tk. This is perfectly fine, but you still must call mainloop at some point. Since you don't create (or inherit) a mainloop function in your class, you must call the one associated with the root window. The variations I speak of are how and where the instance of App is added to the root window, and how mainloop is ultimately called.
Personally I prefer that App inherits from Frame, and that you pack the app outside the definition of the app. The template I use looks like this:
class App(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
<your widgets go here>
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
app.pack(fill="both", expand=True)
root.mainloop()
In this final example, app and root are two completely different objects. app represents a frame that exists inside the root window. Frames are commonly used this way, as a container for groups of other widgets.
So, in all cases, mainloop must be called. where it is called, and how, depends a bit on your coding style. Some people prefer to inherit from the root window, some don't. In either case, you must call the mainloop function of the root window.
I tested both like you see:
One is written with "app." + ".pack()" and one calls "mainframe." + ".grid()"
#-*- coding: utf-8 -*-
#THIS IS THE "MAINFRAME." - PART
from Tkinter import *
import ttk
def show():
p = password.get() #get password from entry
print(p)
root = Tk()
root.title("Ingos first program")
mainframe = ttk.Frame(root, padding="30 30 60 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
password = StringVar() #Password variable
passEntry = Entry(mainframe, textvariable=password, show='*').grid(column=3, row=3, sticky=S)
submit = Button(mainframe, text='Show Console',command=show).grid(column=3, row=4, sticky=S)
root.mainloop()
def show():
p = password.get() #get password from entry
print(p)
#THIS IS THE "APP."-PART. BOTH WORKS FINE.
app = Tk()
app.title("Ingos first program")
password = StringVar() #Password variable
passEntry = Entry(app, textvariable=password, show='#').pack()
submit = Button(app, text='Show Console',command=show).pack()
app.mainloop()
This instance works fine with python 2.7. In that test even app. can handle "mainloop()"
That script opens 2 windows, one after another (if you close the first one) and the first programm is formatted, didn't tryed to write the colum=3... stuff in the pack() clamps.
I still started the Tkinter so don't fight me, just trying.. Hope I could help to answer your question.
All the best, Ingo
The App object is just your app code, and the reason you call App(root) is to make an instance with your class, which then has access to your root window.
It receives this reference in the __init__ method:
def __init__(self, master):
# master refers to the root window now
...
You can see the entire definition of the App object (given by the block beginning with class App:), and it doesn't even have a mainloop method, so to start the main Tkinter loop, you have to call it on the root window.
In the example in the Python2 documentation, they do call it as you suspected should be done, but note that their example class subclasses the Tk object Frame. In your example code, App is an old-style class that doesn't inherit anything.
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()