I'm writing a tkinter app that has 3 pages in three different .py files.
I've reworked the code to allow me to create each frame by running a main overarching app which is self.controller in all the subsequent pages (thanks to some excellent users on this site). The reason I have done this is that I want to be able to pass a user name (tk.StringVar()) from the first Frame to a tk.Label in the second Frame.
As I've said above I've rewritten this code a few times but still when I try to actually call a variable or a function from either of the other pages I get the error shown below.
The other page is called FrontPage and it's stored in front_page.py and when I run that page through the main tk.Tk it works perfectly so I know I have definied self.name_entry properly there.
The (minimum) code I'm using for the GamePage is
import tkinter as tk
from tkinter import SUNKEN
import front_page
from front_page import FrontPage
from character import Character
from dice_roll import Die
class GamePage(tk.Frame):
"""The overall class for the app"""
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page")
label.pack(side="top", fill="x", pady=10)
self.features = {}
#This is where I try to call the text from the other page
self.character_name = front_page.FrontPage.name_entry.get()
self.name_label = tk.Label(
self.mainframe,
text= self.character_name,
font=("Courier", 20),
bd = 1,
relief = SUNKEN
)
self.name_label.pack()
When I try to actually call a the text from a tk.Entry on the FrontPage it doesn't work. All the functions from my other classes (which are imported up top) work fine.
Traceback (most recent call last):
File "/Users/kevinomalley/Desktop/python_work/rapid_rpg/app_GUI.py",
line 47, in <module>
app = GUI()
File "/Users/kevinomalley/Desktop/python_work/rapid_rpg/app_GUI.py",
line 20, in __init__
frame = F(parent=container, controller=self)
File "/Users/kevinomalley/Desktop/python_work/rapid_rpg/main_page.py",
line 23, in __init__
self.character_name = front_page.FrontPage.name_entry.get()
AttributeError: type object 'FrontPage' has no attribute 'name_entry'
Now I'm 90% sure this is because I'm not using the self.controller properly
I've seen loads of answers referencing it but no clear explanation about how to use it or effectively call it.
If anyone could free me from 5 days of beating my head against a wall it'd lift my poor little newbie heart.
Thanks
The controller is a way to control access between the pages. You haven't shown all your code, but if you're wanting your pages to be able to access other pages, the first think you need to do is create a function that can return a reference to another page.
For example:
class YourApp(...):
...
def get_page(self, page_class):
return self.frames[page_class]
Now, from any page, you can call this function to get a reference to any other page:
game_page = self.controller.get_page(GamePage)
With that reference, you can now use any of its attributes:. For example:
self.character_name = game_page.name_entry.get()
Note: these examples may not be 100% correct. I don't know how you've implemented the rest of your code. However, the concept is what is important:
add a method to a controller to return a page
call that method to get a reference to a page
use that reference to get attributes of that page
This is all explained in more detail in this answer: https://stackoverflow.com/a/33650527/7432
Related
I am restructuring this question: I felt the original questions were long winded and not particularly helpful to anyone else.
I have a main app (run as__main__) that runs directly.
I have a module (simple_module.py) that I only wish to use as an import.
I realise that I can run it standalone if required (via if__name__) and have including that in the modules code, for demonstration only.
When user presses main.py's 'start' button, it should open a new toplevel window with all the classes and widgets from the simple_module which are all in a single class called Page. (all whilst the main app window remains open.)
I want the module to be re-imported (or equivalent) every time the button is pressed. When the modules 'close' button or X is pressed I want it to close that window. main.py's window remains open throughout this and the button press needs to re-open that module window, infinitely, like a loop.
I have added if name == 'main' just to highlight that I understand what this does (it is normally within all my main.py apps) and why I still cannot get the result I want. As far as I can see, it does not change anything, I am now only importing the class but 'new' is still not recognised. Which is the same issue as in the prior example.
I have main.py
import tkinter as tk
# audio module works as expected
import audio_module as am
# I want this window to open and close on command
import simple_module as sm
class GUI(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
#self.new = tk.Toplevel(self) # auto loads a second, unwanted window
self.session_counter = 0
self.start_btn = tk.Button(root, text="start", command=self.start)
self.start_btn.grid(row=4,column=0,sticky="nsew",pady=30, padx=30, ipady=18)
def start(self):
am.spell() # these audio imports work like a charm, every btn press - single functions call OK
self.session_counter += 1
print(self.session_counter)
#import simple_module - if used here, my usual 'illegal' import style (works great, once only,
# unless in same script as __main__ in which case all re-imports work fine)
# Import attempts
#import simple_module as sm
#page = Page(new) # Page not defined
#sm.Page() #missing parent arg (new)
# error: 'new' not defined
#sm.Page(new)
if __name__ == '__main__':
print('running as __main__')
root = tk.Tk()
#sm.Page = tk.Toplevel(new) # a desperate attempt NO
#page = sm.Page(tk.TopLevel) NO
# qualify and USE module here! sm is not required if you use 'from simple_module import Page' !!
page = sm.Page(root)
#page.pack(fill='both', expand=True)
page.grid(row=0,column=0,sticky='nsew')
main = GUI(root)
root.mainloop()
Finally, we have simple_module.py:
import tkinter as tk
import audio_module as am
# this module works exactly as expected IF run directly...
class Page(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
# super().__init__(*args, **kwargs)
self.back_btn = tk.Button(parent, text="close", command=self.back)
self.back_btn.grid(row=4,column=0,sticky="nsew",pady=30, padx=30, ipady=18)
def back(self):
am.click()
# close this page BUT have it ready to re-open IF user re-presses button.
new.destroy()
if __name__ == "__main__":
print('running as __main__ directly')
new = tk.Tk()
#new = tk.Toplevel() # this loads an unwanted additional blank window. IF run directly.
page = Page(new)
# the missing line to self contain module!
#page.pack(fill='both', expand=True)
page.grid(row=0,column=0,sticky='nsew')
new.mainloop()
else:
print('running as import with __name__ ==',__name__)
Thanks for your patience and responses. I have re-studied the if main guide you provided a link to, it re-affirms what I already believe I knew about it. A useful example in there for when I want to open a single frame only and switch between them, but in this case I want the main window to remain open whilst calling the modules window.
The problem you're facing is that your Page class isn't written to be reusable. It's relying on global variables and knowledge about the code that is calling it. To be reusable, the Page class needs to be self-contained.
In short, that means that every widget created by Page must be inside the Page class and not in the root window. Also, the code that creates an instance of Page needs to be responsible for calling pack, place, or grid on the instance.
So, the first step is to modify Page so that it can be reusable. The key to that is to require that the caller pass in the parent widget. You're doing that, but you aren't using the passed-in value.
Page should look like the following code. Notice that it explicitly declares parent as the first positional argument, and it passes that on to tk.Frame.__init__:
class Page(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.back_btn = tk.Button(parent, text="close", command=self.back)
...
Notice how the back button is a child of parent rather than the child of some global variable. This is the important step that you are missing, and this is what allows Page to be self-contained and not tightly coupled to the code that creates it.
Once Page is properly self-contained, it is the responsibility of the code that creates an instance of Page to call pack, place, or grid on the instance.
For example, simple_module.py might have this block at the end:
if __name__ == "__main__":
new = tk.Tk()
page = Page(new)
page.pack(fill="both", expand=True)
new.mainloop()
In main.py, since you are importing simple_module as a whole, you need to fully qualify the use of Page:
import simple_module as sm
...
root = tk.Tk()
page = sm.Page(root)
page.pack(fill="both", expand=True)
root.mainloop()
Alternately, you could just import Page and omit the sm.:
from simple_module import Page
...
page = Page(root)
...
Notice how one file can use root and one can use new, but your code will work in either case because it doesn't rely on a global variable. Inside of page it will always be parent no matter what the caller called it.
As an aside, you don't have to import simple_module inside start -- you only need to import it once at the start of the program.
i am quite new to inheritance and overriding methods and i am not quite sure how to override the __init__ method of a class, more specifically, adding another parameter to it.
Basically i want to override the __init__ method of the tkinter class simpledialog.Dialog.
From the documentation the init method of simpledialog.Dialog has the Parameters __init__(self,parent,title=None) and i want it to be __init__(self,parent,labeltitle,title=None)
Ok guys, so for context i also changed some of the other methods of the class, i will give you the code so that people know why i want labeltitle in the init method. I found a solution on how to solve my problem, if anyone has an idea or sees a problem with this let me know.
import tkinter
from tkinter import simpledialog
class MyDialog(simpledialog.Dialog):
def __init__(self,parent,labeltitle,title):
self.labeltitle=labeltitle
super().__init__(parent,title)
def body(self, master):
self.geometry("400x300")
tkinter.Label(master, text=self.labeltitle).grid(row=0)
self.e1 = tkinter.Entry(master)
self.e1.grid(row=0, column=1,pady=30)
return self.e1 # initial focus
def apply(self):
first = self.e1.get()
self.result = first
so now when i create an Object of MyDialog like this:
asd = MyDialog(root,"title_of_the_label","title_of_the_Dialogwindow")
i get the Dialogwindow with these titles. Now i can create multiple objects with different windowtitles and labeltitles, since the simpledialog.Dialog class only allowed to change the title of the Dialogwindow, not the labeltitle.
I am working on a project that involves creating many instances of Tkinter Labels and Entry widgets that will always be aligned next to one another. To try and save myself time, I created a custom class that I am showing below:
class labelEntry(Label,Entry):
def __init__(self,parent,label,row,column,bg_color):
Label.__init__(self,parent)
self['text']=label
self['justify']='right'
self['bg']=bg_color
self.grid(row=row,column=column, sticky=E)
Entry.__init__(self,parent)
self['width']="10"
self.grid(row=row,column=column+1)
This creates the configuration I want and is easy enough to arrange (I have them stored in a frame). The problem is I don't know how to access the Entry widgets that I have created as they are part of this new class.
I have a desire to read and delete the entries from the entry widgets. My best guess at clearing them was with this button that was being fed into the same frame:
class clearAllEntry(Button):
def clearAll(self,targetFrame):
targetFrame.labelEntry.Entry.delete(0,END)
def __init__(self,parent,targetFrame):
Button.__init__(self,parent,text='Clear All Entries',bg='black',fg='white')
self['command']= "clearAll(targetFrame)"
I have also looked at grid_slave as an approach but am having the same issue.
Any advice/help would be greatly appreciated.
First off, if you're creating a new class that contains two objects of different classes, you should not be using inheritance. Instead, use composition.
Second, to be able to access the entry widget, save it to an instance variable.
For example:
class LabelEntry():
def __init__(self, parent, label, row, column, bg_color):
self.label = Label(parent, text=label, justify='right', bg=bg_color)
self.entry = Entry(parent, width=10)
self.label.grid(row=row, column=column, sticky="e")
self.grid(row=row,column=column+1)
Later, you can reference these attributes like you can any other attribute:
le1 = LabelEntry(root)
...
print(le1.entry.get())
I have a small problem with my code.
There are two classes. First one creates a window with a Options button. Upon clicking the button, the second class is called and creates another window with an Ok button. Let's say there is also a checkbox, which changes the background color to black or something like that. After clicking the button, whatever changes were made in the options are stored into a file and the second window is closed.
All of this works fine. My problem is that now I need to call method update_init from the first class that will apply those changes to the MainWindow. The code below shows my first solution to this problem, but from what I understand, by using second mainloop I create second thread, which should be avoided.
class MainWindow:
def __init__(self, master):
self.master = master
self.options_btn = tk.Button(self.master, text="Options", command=self.open_options)
self.options_btn.pack()
self.options_window = None
def open_options(self):
options_master = tk.Toplevel()
self.options_window = OptionsWindow(options_master)
options_master.mainloop()
lst = meta_load() # loads changes from a file
self.update_init(lst)
def update_init(self, lst):
#code
class OptionsWindow:
def __init__(self, master):
self.master = master
self.ok_btn = tk.Button(self.master, text="OK", command=self.update_meta)
self.ok_btn.pack()
def update_meta(self):
meta_save(12) # saves changes into a file
self.master.destroy()
main_master = tk.Tk()
main_master.minsize(width=1280, height=720)
b = MainWindow(main_master)
main_master.mainloop()
My second solution was to just put both classes into one, but the code is quite messy if I do so.
Can I somehow call the method update_init (which is in the MainWindow class) from the OptionsWindow class without initializing new MainWindow class window? Or is there any other way to deal with this? I would appreciate any help.
I am sorry if this is too specific, I've tried to make it as general as possible, but it's a very specific problem and I couldn't find much information about it anywhere on the internet.
In general you can call a class method from anywhere you want and pass anything to it without initialisation of that class's instance, thanks to objective nature of python, but beware of self dependencies! Although, I don't think that's a good practice.
class A:
def __init__(self):
self.foo = 'foo'
def return_foo(self):
return self.foo
class B:
def __init__(self):
self.bar = 'bar'
print('Ha-ha Im inited!')
def return_bar(self):
try:
return self.bar
except AttributeError:
return 'bar'
def test():
a = A()
# b = B()
return_bar = getattr(B, 'return_bar', None)
if callable(return_bar):
print('%s%s' % (a.return_foo(), return_bar(None)))
test()
Links:
getattr
callable
I'm learning Python & Tkinter and following a tutorial. I'm making a program to do what I'm used to do in scripting languages such as Powershell, get a user value, and then do something with it, for starter.
I can't find any example code that looks like mine. So I'm asking for your help.
Basically, this program creates a frame with a Quit Button, an Entry widget where the user enters a value, & a OK button. Then, he presses the OK button, and the value should be printed in the python terminal.
I'm getting an error but I don't understand why. I'm following a tutorial so I'm doing it the way they show, by defining a class to create the frame, putting the buttons & text box inside it.
The function to get the user input text from the entry widget is set below, where I would put my other functions, if let's say, I would like to add functions for buttons to do a print. I managed to make the print work if the value to print is set before, but I can't get the value from the Entry Widget.
from tkinter import *
class Window:
def __init__(self, master):
self.master = master
master.title("Get a value and print it")
self.info = Label(master, text="Please write something then click ENTER")
self.info.pack()
self.ebox= Entry()
self.ebox.pack()
self.viewnumber = Button(master, text="OK", activebackground="red", command=get_int)
self.viewnumber.pack()
self.quit = Button(master, text="Quit", activebackground="blue", command=quit)
self.quit.pack()
def get_int():
intnum = ebox.get
print(intnum)
def quit():
root.quit()
root = Tk()
root.geometry("400x400")
my_gui = Window(root)
root.mainloop()
I am getting this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\USERNAME\AppData\Local\Programs\Python\Python35-32\lib\tkint
er\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\USERNAME\Desktop\pyth222184.py", line 20, in get_int
intnum = ebox.get
NameError: name 'ebox' is not defined
Thank you for your help.
Actually the problem is that ebox can't be accessed from the function. You need to use self.ebox, so that it actually references the instance variable ebox.
To see more about that, visit here.
Also, you are using self.ebox.get instead of self.ebox.get(). self.ebox.get on its own would return the raw function object of self.entry.get. To actually call the function and get the contents, use self.ebox.get().
But in order for that to work, self has to be defined. To do this, add self as the first argument in every method. When you call instance.method() Python will automatically pass instance in as self. Essentially, self lets you access the current instance.
That's right. The get_int in Class is a method, and you can not call that like a function. You'd have to call that like a method something like this
elf.viewnumber = Button(master, text="OK",bg="red",command=lambda:my_gui.get_int() )
self.viewnumber.pack()
This is an example of code that would work.