Python Tkinter, modify Text from outside the class - python

I am trying to access the Text widget defined in class FirstPage from outside of the class.
I tried to solve this problem by creating a new instance of FirstPage, but could not find the right arguments to use. Also tried to use instance of GUI to gain the access, but unsuccessfully.
My problem is solved when I can use text.insert(0.0, t) from outside of the classes. It would help me modify the text displayed with Tkinter by functions that are not directly related with the GUI.
The origin of the code I am trying to use is found: Switch between two frames in tkinter
Also I removed lines that were not necessary for this question..
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frame = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
text = tk.Text(self , height=25, width=80)
text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.mainloop()
EDIT:
Here is the working code:
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frame = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
page_name = FirstPage.__name__
self.frames[page_name] = frame
def get_page(self, page_name):
return self.frames[page_name]
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self , height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")
app.mainloop()

There's nothing special you need to do. As with any python object, you simply need a reference to the object in order to manipulate it.
The concept in the code you started with is to have a "controller" that controls access to all of the pages, since that object is where the pages are created. You can add a function in the controller that gives you a reference to a page, and then you can use that to call a function on that page.
Here's the changes you need to make to the controller:
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
...
page_name = FirstPage.__name__
self.frames[page_name] = frame
...
def get_page(self, page_name):
return self.frames[page_name]
You also need to modify FirstPage to keep a reference to the widget so that you can access it later:
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
...
self.text = tk.Text(...)
...
From within any other code you can now access the text widget via get_page (but your pages must save a reference to the controller for this to work).
class AnotherPage(tk.Frame):
def __init__(self, parent, controller):
...
self.controller = controller
...
def some_function(self):
...
first_page = self.controller.get_page("FirstPage")
text = first_page.text.get("1.0", "end-1c")
...
first_page.text.insert("end", "some new text\n")
Note that this technique works outside of any GUI pages. In your code, app is the controller, so you can do something like this:
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")

Related

How to access methods from a parent class which enables the frame swapping?

This is the window provides the container and methods which allow frame swapping:
class Login_Window(ctk.CTk):
def __init__(self, *args, **kwargs):
super().__init__()
self.geometry('400x400')
self.title('Music Mayhem')
self.resizable(False, False)
container = ctk.CTkFrame(master=self)
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames={}
for F in (LoginFrame, RegEmailFrame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky= 'nsew')
frame.grid_columnconfigure(0,weight=1)
frame.grid_rowconfigure(0,weight=1)
self.show_frame(LoginFrame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
In order to swap the frames, a button has to be created in the frame that is going to be swapped. How would i go about creating an instance of another class within these frames which will also call the show_frame method? Here is the code for the frames- this could be ran as long as you have tkinter and custom tkinter installed. The only aspect that should supposedly not work are the buttons in the menu frame.
Yes, in this situation the menu frame is not needed but this is just a simple example because the actual code is way too long to be included here.
I have tried adding the menu frame into the list of frames to be swapped (in the class above) and giving it the same parent and controller attributes as the other frame but that required a parent and controller argument to be passed through when it is called in the Login and Register frames.
Is there a way to get round this or a simpler method that could be implemented instead?
class LoginFrame (tk.Frame):
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.loginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.loginBtn.grid(row=1, column=0)
class RegEmailFrame(tk.Frame):
def __init__(self, parent, controller,header_name="Register Email"):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.emailLabel = ctk.CtKLabel(master=self,width=100, height=20 text='Frame swapped')
self.emailLabel.grid(row=1, column=0)
class Menu(tk.Frame):
def __init__(self, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(*args, width=width, height=height, **kwargs)
self.menuloginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(LoginFrame)
self.menuloginBtn.grid(row=0, column=0)
self.menuRegBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.menuRegBtn.grid(row=1, column=0)
In the current implementation, the Menu class does not have access to the controller object that is used to switch between frames in the Login_Window class. One way to fix this would be to pass the controller object to the Menu class during instantiation.
You can do this by adding a parameter called controller in the Menu class constructor and then passing it as an argument when creating an instance of the Menu class in the LoginFrame and RegEmailFrame classes.
For example, in the LoginFrame class:
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self, controller)
self.menu.grid(row=0, column=0)
And in the Menu class constructor:
def __init__(self, parent, controller, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(parent, *args, width=width, height=height, **kwargs)
self.controller = controller
With this changes, the Menu class now has access to the controller object and can use it to switch between frames using the show_frame method.
You should also make the same changes in the RegEmailFrame class and in the constructor of the Menu class.
Hope this helps!

How do you change the variable in a function from a different class and call it back?

I am making a game with levels and in each level, I will need to be using different operators and/or different ranges. My problem is that I don't know how to change the variables in a function from a different class. I would like to do this so I don't need to copy and paste my code making it lengthy. I'd like to use self.Answer and self.strQuestion for mulitple scope.
The code below is just to make the classes functional.
from tkinter import *
import tkinter as tk
import random
from Tkinter import messagebox
class BattleMaths(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, levelone, leveltwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
lvl1_button = Button(self, text="LEVEL 1", command=lambda: controller.show_frame(levelone))
lvl1_button.place(relx=0.5, rely=0.5, anchor='center')
I want to put the questions def into class leveltwo while changing it to self.Answer = int(numOne) * int(numTwo) and self.strQuestion = "{} x {}".format(str(numOne), str(numTwo))
class levelone(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def widgets(self):
#widgets here
def question(self):
self.UserAnswer = ''
numOne = random.randrange(1,10)
numTwo = random.randrange(1,10)
self.Answer = int(numOne) + int(numTwo) #change this
self.strQuestion = "{} + {}".format(str(numOne), str(numTwo)) #and change this
def answer(self):
#answer checker
class leveltwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#question def here
root = BattleMaths()
root.title("Battle Maths")
root.geometry("400x250")
root.resizable(0,0)
root.mainloop()
Create the variables you want in the main class (BattleMaths), then you can alter them in the child classes via controller.my_variable.
Example: self.Answer created in BattleMaths and accessed in levelone via controller.Answer

Python - Global not working as expected

I'm currently working on a tkinter project. The code is shown below:
import tkinter as tk
from tkinter import font as tkfont
background_colour = "#F0F0F0"
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("CCNA Command Learner")
self.geometry("500x500")
self.iconbitmap("favicon.ico")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (IntroPage, MainPage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("IntroPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class IntroPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.image = tk.PhotoImage(file="IntroImage.gif")
self.head_img = tk.Label(self, image=self.image, background=background_colour)
self.head_img.pack()
self.label = tk.Label(self, bg=background_colour,
text="\nLearn essential commands for the CCNA exam\n\nPlease enter your name")
self.label.pack()
self.head_entry = tk.Entry(self)
self.head_entry.pack()
self.submit = tk.Button(self, text="Submit", command=self.submit_button)
self.submit.pack()
def submit_button(self):
global var_username
self.controller.show_frame("MainPage")
var_username = self.head_entry.get()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.introduction = tk.Label(self, text="Hello" + var_username)
self.introduction.pack()
if __name__ == "__main__":
app = Application()
app.mainloop()
My problem is that in the submit_button method of my IntroPage class, I am trying to set a global variable so that my MainPage class can then access it. However, despite declaring it and setting it, I am still getting a NameError when accessing it in my MainPage class?
That's correct, as you seem to use global incorrectly. It can make existing variable out of your scope visible and available for modification, like this:
a = 'test'
def f():
global a
a = 'edited'
f()
print(a)
Output:
>>> edited
You don't seem to declare variable with this name, hence the NameError. And again - global is not for creating global variables, it is for making them visible.
By the way, think twice before using globals in python, that usually indicates huge design problems. It's surely possible to refactor your code in a way that you won't need global.
You simply need create it before MainPage in call stack.
Create it outside the IntroPage class, and initializing can stay where it is.

Passing zipped tuple into function [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have a Class hierarchy where I am trying to switch tkinter windows while retaining specific properties (such as window name, dimensions, resizable, etc.). I'm having some issues with the resizable part since it takes in two values:
import tkinter as tk
from tkinter import font as tkfont
class Manifold(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self) #container = stack of frames; one on top is visible
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F,geometry,title in zip((StartPage,PageOne,PageTwo,PageThree),
("532x279","532x279","254x279","299x620"),
("","Experimental Data","Orientation Distribution","Manifold Embedding"),
((False,False),(False,False),(True,True),(True,True))):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = (frame,geometry,title,(option1,option2)) #puts all pages in stacked order
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name): #show a frame for the given page name
frame, geometry, title = self.frames[page_name]
self.update_idletasks()
self.geometry(geometry) #changes between window sizes
tk.Tk.wm_title(self, title) #window heading
###UNKNOWN:
self.resizable(*options)
###########
frame.tkraise() #raises window to top
if __name__ == "__main__":
app = Manifold()
app.mainloop()
Any advice would be greatly appreciated.
Close. Try this:
self.frames = {}
for F,geometry,title,options in zip((StartPage,PageOne,PageTwo,PageThree),
("532x279","532x279","254x279","299x620"),
("","Experimental Data","Orientation Distribution","Manifold Embedding"),
((False,False),(False,False),(True,True),(True,True))):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = (frame,geometry,title,options) #puts all pages in stacked order
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name): #show a frame for the given page name
frame, geometry, title,options = self.frames[page_name]
self.geometry(geometry) #changes between window sizes
self.title(title) #window heading
self.resizable(*options)
###########
frame.tkraise() #raises window to top
Assuming this is a subclass of Tk().
update_idletasks should not be needed (it's very rarely used).
It would be a lot neater to put those options in each Frame's tkraise() method. In order to do that you would need a hook to the root (the Tk() instance). Since you've obfuscated that somewhat with your "container" Frame (why?) you need to be sure to pass the root instance along. A simple example:
import tkinter as tk
class BigWindow(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Button(self, text='go to\nsmall window', command=lambda: master.show_frame(SmallWindow)).pack()
def tkraise(self):
self.master.title('Big Window')
self.master.geometry('600x600')
self.master.resizable(True, True)
tk.Frame.tkraise(self) # raise this Frame
class SmallWindow(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
tk.Button(self, text='go to\nbig window', command=lambda: master.show_frame(BigWindow)).pack()
def tkraise(self):
self.master.title('Small Window')
self.master.geometry('200x200')
self.master.resizable(False, False)
tk.Frame.tkraise(self)
class Manifold(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frames = {}
for F in (SmallWindow, BigWindow):
frame = F(self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(SmallWindow)
def show_frame(self, page): #show a frame for the given page
self.frames[page].tkraise()
if __name__ == "__main__":
app = Manifold()
app.mainloop()
You could make this even cleaner by making a base class for all your other frames to inherit from, and then just setting some variables:
import tkinter as tk
class AutoSizeFrame(tk.Frame):
def tkraise(self):
self.master.title(self.title)
self.master.geometry(self.geometry)
self.master.resizable(*self.resizemodes)
tk.Frame.tkraise(self) # raise this Frame
class BigWindow(AutoSizeFrame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.title = 'Big Window'
self.geometry = '600x600'
self.resizemodes = (True, True)
tk.Button(self, text='go to\nsmall window', command=lambda: master.show_frame(SmallWindow)).pack()
class SmallWindow(AutoSizeFrame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.title = 'Small Window'
self.geometry = '200x200'
self.resizemodes = (False, False)
tk.Button(self, text='go to\nbig window', command=lambda: master.show_frame(BigWindow)).pack()
class Manifold(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frames = {}
for F in (SmallWindow, BigWindow):
frame = F(self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(SmallWindow)
def show_frame(self, page): #show a frame for the given page
self.frames[page].tkraise()
if __name__ == "__main__":
app = Manifold()
app.mainloop()

tkinter wm_grid() got an unexpected keyword argument 'row'

In line 11 I tried to use the grid(), but it keeps giving me
"wm_grid() got an unexpected keyword argument 'row'" error
Can anyone take a look at this?
import tkinter as tk
class MainPage(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
# pass frames as dictionaries
self.frames = {}
frame = StartPage(container, self)
self.frames[StartPage] = frame
# problem here****************************************************
frame.grid(row=1, column=1)
# problem here****************************************************
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
class StartPage(tk.Tk):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="StartPage")
label.pack(pady=10, padx=10)
root = MainPage()
root.mainloop()
I guess the issue for you comes because you are subclassing StartPage from tk.Tk , you cannot use row/column keyword arguments for grid in that case.
But I believe that you really did not intend it to be a subclass of tk.Tk , since you seem to be trying to initialize tk.Frame inside it. I believe the fix in your case would be to inherit the class from tk.Frame instead as -
class StartPage(tk.Frame):

Categories

Resources