Can't get tkinter frame to centre when switching from mutiple frames - python

I am fairly new to tkinter and I'm using the code from the top comment in this post:Switch between two frames in tkinter with some changes to be able to switch between two frames. The problem is that I can't get my second one to centre or any other frame that isn't the first one when I add it for that matter. Apologises if I'm making any obvious mistakes as I said I'm still getting to know tkinter and python and I don't really understand how the code from this post works. Here is my code:
import tkinter as tk
class MainView(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["LoginFrame"] = LoginFrame(parent=container, controller=self)
self.frames["RegisterFrame"] = RegisterFrame(parent=container, controller=self)
self.frames["LoginFrame"].grid(row=0, column=0, sticky="NESW")
self.frames["RegisterFrame"].grid(row=0, column=0, sticky="NESW")
self.ShowFrame("LoginFrame")
def ShowFrame(self, PageName):
frame = self.frames[PageName]
frame.tkraise()
class LoginFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
WelcomeLabel = tk.Label(self, text="Welcome to Detention Organiser!",font=(None,20) ).grid(columnspan=2)
UsernameLabel = tk.Label(self, text="Username",font=(None,15) ).grid(row=1, sticky="E")
PasswordLabel = tk.Label(self, text="Password",font=(None,15) ).grid(row=2, sticky="E")
UsernameEntry = tk.Entry(self).grid(row=1, column=1, sticky="W")
PasswordEntry = tk.Entry(self, show="*").grid(row=2, column=1, sticky="W")
LoginButton = tk.Button(self, text="Login").grid(columnspan=2)
RegisterButton = tk.Button(self, text="Sign Up",command=lambda: controller.ShowFrame("RegisterFrame")).grid(columnspan=2)
class RegisterFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.Variable = tk.StringVar()
self.Variable.set("7A")
RegisterLabel = tk.Label(self, text="Register",font=(None,20)).grid(columnspan=2)
UsernameLabel = tk.Label(self, text="Username",font=(None,15)).grid(row=1, sticky="E")
PasswordLabel = tk.Label(self, text="Password",font=(None,15)).grid(row=2, sticky="E")
FormGroupLabel = tk.Label(self, text="Form Group",font=(None,15) ).grid(row=3, sticky="E")
UsernameEntry = tk.Entry(self).grid(row=1, column=1, sticky="W")
PasswordEntry = tk.Entry(self, show="*").grid(row=2, column=1, sticky="W")
FormGroupDrop = tk.OptionMenu(self,self.Variable,"7A","7B","8A","8B").grid(row=3, column=1, sticky="W")
RegisterButton = tk.Button(self, text="Register",command=lambda: controller.ShowFrame("RegisterFrame"))
RegisterButton.grid(columnspan=2)
BackButton = tk.Button(self, text="Back",command=lambda: controller.ShowFrame("LoginFrame")).grid(columnspan=2)
if __name__ == "__main__":
app = MainView()
app.geometry("640x360")
app.mainloop()

First thing, in general in your program, avoid doing something like:
UsernameEntry = tk.Entry(self).grid(row=1, column=1, sticky="W")
because the variable UsernameEntry is not a tk.Entry as you would expect, and will definitely cause a bug sometimes. Instead, use one of the following:
# If you need the variable later
UsernameEntry = tk.Entry(self)
UsernameEntry.grid(row=1, column=1, sticky="W")
# Otherwise
tk.Entry(self).grid(row=1, column=1, sticky="W")
Now, back to your problem. What is missing is that you don't tell how the program should allocate the remaining space (The space that is not necessary to contain your widgets). By default, the grid will place your widgets as close as possible to the top/left of the container. To change this behaviour, you will need to use grid_columnconfigure and/or grid_rowconfigure. For example,
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
tells the program that the remaining space should be equally split between column 0 and column 1. The weight can be any non-negative integer value:
self.grid_columnconfigure(0, weight=3)
self.grid_columnconfigure(1, weight=0)
self.grid_columnconfigure(2, weight=1)
This tells the program to allocate 3/4 of remaining space to column 0, None to column 1 and 1/4 to column 2. By default, all weights are 0.
Then, your classes LoginFrame and RegisterFrame might look like
class LoginFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid_rowconfigure(5, weight=1) # Fills vertical space below the last row
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
tk.Label(self, text="Welcome to Detention Organiser!",font=(None,20) ).grid(columnspan=2)
tk.Label(self, text="Username",font=(None,15)).grid(row=1, sticky="E")
tk.Label(self, text="Password",font=(None,15)).grid(row=2, sticky="E")
tk.Entry(self).grid(row=1, column=1, sticky="W")
tk.Entry(self, show="*").grid(row=2, column=1, sticky="W")
tk.Button(self, text="Login").grid(row=3, columnspan=2)
tk.Button(self, text="Sign Up",command=lambda: controller.ShowFrame("RegisterFrame")).grid(row=4, columnspan=2)
class RegisterFrame(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.Variable = tk.StringVar()
self.Variable.set("7A")
self.grid_rowconfigure(5, weight=1) # Fills vertical space below the last row
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=1)
tk.Label(self, text="Register",font=(None,20)).grid(columnspan=2)
tk.Label(self, text="Username",font=(None,15)).grid(row=1, sticky="E")
tk.Label(self, text="Password",font=(None,15)).grid(row=2, sticky="E")
tk.Label(self, text="Form Group",font=(None,15) ).grid(row=3, sticky="E")
tk.Entry(self).grid(row=1, column=1, sticky="W")
tk.Entry(self, show="*").grid(row=2, column=1, sticky="W")
tk.OptionMenu(self,self.Variable,"7A","7B","8A","8B").grid(row=3, column=1, sticky="W")
RegisterButton = tk.Button(self, text="Register",command=lambda: controller.ShowFrame("RegisterFrame"))
RegisterButton.grid(columnspan=2)
tk.Button(self, text="Back",command=lambda: controller.ShowFrame("LoginFrame")).grid(columnspan=2)
Finally, tkinter has many many different options that you need to test to really understand how they work. As you do some tests, I advise you to extensively use the option bg="COLOR", that will change the background of a widget and tell you precisely its boundaries. For instance,
tk.Frame.__init__(self, parent, bg="RED")

Related

How to call onto the class that is in the tkinter mainloop?

I've been trying to call the class that is in my mainloop, but whenever I properly call it makes it that the program doesn't even launch anymore. I've been attempting to call it in order to get a method, which would return the current frame. I am aware that my code uses controller and parent to communicate between classes, but I haven't managed to fully grasp an understanding of these. If I call the mainlooped class, with "test = Database_project()", then the program won't run anymore. Can someone explain this to me? I'm trying to get the scrollbar feature to work on specific frames, and I haven't managed to figure out just yet. I'm trying to call the mainlooped class in the "CreatePage" class. Thank you in advance!
from tkinter import *
import tkinter as tk
class Database_Project(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
stack_frame_container = tk.Frame(self)
stack_frame_container.grid_columnconfigure(0, weight=1)
stack_frame_container.grid_rowconfigure(0, weight=1)
stack_frame_container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (MainPage, CreatePage):
frame_occurrence = frame.__name__
active_frame = frame(parent=stack_frame_container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
#self.frameslist["CreatePage"].dbproject = self.frameslist["Datanase_Project"]
self.current_frame("MainPage")
print(self.frameslist)
def current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
def get_current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
return active_frame
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label_create = tk.Label(self, text="Create and insert data").grid(row=0, column=0, padx=50, pady=(50,0))
create_button = tk.Button(self, text="CREATE", command=lambda: controller.current_frame("CreatePage")).grid(row=1, column=0)
label_read = tk.Label(self, text="Query over data").grid(row=0, column=1, padx=50, pady=(50,0))
read_button = tk.Button(self, text="READ").grid(row=1, column=1)
label_update = tk.Label(self, text="Modify existing data").grid(row=2, column=0, padx=50, pady=(50,0))
update_button = tk.Button(self, text="UPDATE").grid(row=3, column=0, pady=(0,50))
label_delete = tk.Label(self, text="Remove data").grid(row=2, column=1, padx=50, pady=(50,0))
delete_button = tk.Button(self, text="DELETE").grid(row=3, column=1, pady=(0,50))
class CreatePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.inputlist = []
self.newinputlist = []
Test = Database_Project()
#active_frame = Test.get_current_frame("CreatePage")
#scrollbar = tk.Scrollbar(active_frame, orient="vertical")
#scrollbar.grid(row=0, column=2, stick="ns", columnspan=10)
#text["yscrollcommand"] =
labels = [tk.Label(self, text="Enter unique field"), tk.Label(self, text="Enter corresponding the value/s")]
self.inputlist.append(labels[:])
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
first_entries = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.newinputlist.append(first_entries[:])
self.inputlist.append(first_entries[:])
for x in range(0, len(self.newinputlist) + 1):
self.newinputlist[0][x].grid(row=1, column=x, padx=10, pady=5)
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK", command=lambda: controller.current_frame("MainPage"))]
self.inputlist.append(button_input_1[:])
button_input_2 = [tk.Button(self, text="IMPORT FILE"), tk.Button(self, text="SUBMIT DATA", command=self.submit_data)]
self.inputlist.append(button_input_2[:])
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
add_input = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.inputlist.insert(-2, add_input)
self.newinputlist.append(add_input)
for widget in self.children.values():
widget.grid_forget()
for index, widgets in enumerate(self.inputlist):
print(widgets)
widget_one = widgets[0]
widget_two = widgets[1]
print(str(index), widget_one, widget_two)
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
def submit_data(self):
for index, entries in enumerate(self.newinputlist):
my_label = Label(self, text=str(entries[0].get()) + str(entries[1].get("1.0", END)))
my_label.grid(row=len(self.inputlist) + index, column=0)
if __name__ == "__main__":
NoSQL_Project = Database_Project()
NoSQL_Project.title("NoSQL Database Project")
NoSQL_Project.maxsize(500, 500)
NoSQL_Project.mainloop()

Is it possible to resize an input box (entry) when clicked with tkinter?

Reading through other stackoverflow questions, and other sources I do see that bind can be used to call a function. Currently I'm working on a program that will communicate with a database (most likely mongodb), and so far I've set up a frame that has 2 inputs per row (key-value). I haven't completely decided whether I want one row per document, or one row per field. Right now, if a user has a lot to type then it wouldn't be ideal for them because you can't see everything you write. So what I was thinking is that, if the user clicks on the entry widget, then the box would become bigger and show them everything they have written. My current line of thinking is that maybe I could create another frame for it and somehow pass onto the information to that?
This is what it currently looks like
Then what I'd ideally want it to look like
Here's the code if interested how I made it (Images are from the "CreatePage" section):
from tkinter import *
import tkinter as tk
class Database_Project(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
stack_frame_container = tk.Frame(self)
stack_frame_container.grid_columnconfigure(0, weight=1)
stack_frame_container.grid_rowconfigure(0, weight=1)
stack_frame_container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (MainPage, CreatePage):
frame_occurrence = frame.__name__
active_frame = frame(parent=stack_frame_container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.current_frame("MainPage")
def current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label_create = tk.Label(self, text="Create and insert data").grid(row=0, column=0, padx=50, pady=(50,0))
create_button = tk.Button(self, text="CREATE", command=lambda: controller.current_frame("CreatePage")).grid(row=1, column=0)
label_read = tk.Label(self, text="Query over data").grid(row=0, column=1, padx=50, pady=(50,0))
read_button = tk.Button(self, text="READ").grid(row=1, column=1)
label_update = tk.Label(self, text="Modify existing data").grid(row=2, column=0, padx=50, pady=(50,0))
update_button = tk.Button(self, text="UPDATE").grid(row=3, column=0, pady=(0,50))
label_delete = tk.Label(self, text="Remove data").grid(row=2, column=1, padx=50, pady=(50,0))
delete_button = tk.Button(self, text="DELETE").grid(row=3, column=1, pady=(0,50))
class CreatePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.inputlist = []
self.newinputlist = []
labels = [tk.Label(self, text="Enter unique field"), tk.Label(self, text="Enter corresponding the value/s")]
self.inputlist.append(labels[:])
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
for entries in range(2):
for entrynum in range(0, 1):
print("column:", entries)
print("row", entrynum)
self.newinputlist.append(tk.Entry(self, borderwidth=5))
for x in range(len(self.newinputlist)):
self.newinputlist[x].grid(row=1, column=x, padx=10, pady=5)
self.inputlist.append(self.newinputlist[:])
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK", command=lambda: controller.current_frame("MainPage"))]
self.inputlist.append(button_input_1[:])
button_input_2 = [tk.Button(self, text="IMPORT FILE"), tk.Button(self, text="SUBMIT DATA")]
self.inputlist.append(button_input_2[:])
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
add_input = [tk.Entry(self, borderwidth=5), tk.Entry(self, borderwidth=5)]
self.inputlist.insert(-2, add_input)
self.newinputlist.append(add_input)
for widget in self.children.values():
widget.grid_forget()
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
print(str(index), widget_one, widget_two)
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
if __name__ == "__main__":
NoSQL_Project = Database_Project()
NoSQL_Project.title("NoSQL Database Project")
NoSQL_Project.mainloop()
It's pointless to resize an Entry widget since they can only ever hold a single line. I'll give an example using the Text widget instead, though the technique works with any widget.
There's really no trick, just bind to <FocusIn> and <FocusOut>. In the following example I've created two Text widgets that have this resize behavior:
import tkinter as tk
def resizer(event):
if event.widget == event.widget.focus_get():
event.widget.configure(height=8)
else:
event.widget.configure(height=1)
root = tk.Tk()
root.geometry("400x200")
text1 = tk.Text(root, height=1, width=20)
text2 = tk.Text(root, height=1, width=20)
text1.pack(side="left")
text2.pack(side="right")
for widget in (text1, text2):
widget.bind("<FocusIn>", resizer)
widget.bind("<FocusOut>", resizer)
root.mainloop()
The actual behavior depends on how you've laid out your widget. This could cause widgets to jump around or the window resize, but every app will be different so it's hard to give a solution that works everywhere.

(python tkinter) pages with class, variable passed to other page, cant use as label : PY_VAR0

I had a problem with passing a variable from one page to the other, its works now, but if I want to use it on a label, it writes PA_VAR0. I read that ".get()" should be used in that case, but it still doesn't work that way. (with .get() it don't even passes the variable). I tried to set a new variable with tk.StringVar() function, but it still didn't work
import tkinter as tk
from tkinter import ttk
class example(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.shared_data = {
"variable": tk.StringVar()
}
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 (page1, page2):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(page1)
def get_page(self, page_class):
return self.frames[page_class]
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = ttk.Label(self, text="first page")
label.grid(row=0, column=4, padx=10, pady=10)
button1 = ttk.Button(self, text="turn page",
command=lambda: self.pageturn())
button1.grid(row=3, column=1, padx=10, pady=10)
def pageturn(self):
self.controller.shared_data["variable"] = 'string i wanna pass'
print("variable set here: ", self.controller.shared_data["variable"])
self.controller.show_frame(page2)
class page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = ttk.Label(self, text=self.controller.shared_data["variable"])
label.grid(row=0, column=4, padx=10, pady=10)
label = ttk.Label(self, text=self.controller.shared_data["variable"].get())
label.grid(row=1, column=4, padx=10, pady=10)
button3 = ttk.Button(self, text=self.controller.shared_data["variable"],
command=lambda: print(self.controller.shared_data["variable"]))
button3.grid(row=8, column=10, padx=10, pady=10)
button2 = ttk.Button(self, text="if fuction sees variable",
command=lambda: self.ifok())
button2.grid(row=9, column=10, padx=10, pady=10)
def ifok(self):
if self.controller.shared_data["variable"] == 'string i wanna pass':
print("ok")
app = example()
app.mainloop()
You overwrite self.controller.shared_data["variable"] by a string inside pageturn():
self.controller.shared_data["variable"] = 'string i wanna pass'
You should use .set() instead:
self.controller.shared_data["variable"].set('string i wanna pass')
Full updated code:
import tkinter as tk
from tkinter import ttk
class example(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.shared_data = {
"variable": tk.StringVar()
}
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 (page1, page2):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(page1)
def get_page(self, page_class):
return self.frames[page_class]
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = ttk.Label(self, text="first page")
label.grid(row=0, column=4, padx=10, pady=10)
button1 = ttk.Button(self, text="turn page",
command=lambda: self.pageturn())
button1.grid(row=3, column=1, padx=10, pady=10)
def pageturn(self):
self.controller.shared_data["variable"].set('string i wanna pass') ### changed = to .set()
print("variable set here: ", self.controller.shared_data["variable"].get()) ### called .get()
self.controller.show_frame(page2)
class page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = ttk.Label(self, textvariable=self.controller.shared_data["variable"]) ### changed text to textvariable
label.grid(row=0, column=4, padx=10, pady=10)
label = ttk.Label(self, text=self.controller.shared_data["variable"].get())
label.grid(row=1, column=4, padx=10, pady=10)
button3 = ttk.Button(self, textvariable=self.controller.shared_data["variable"], ### changed text to textvariable
command=lambda: print(self.controller.shared_data["variable"].get()))
button3.grid(row=8, column=10, padx=10, pady=10)
button2 = ttk.Button(self, text="if fuction sees variable",
command=lambda: self.ifok())
button2.grid(row=9, column=10, padx=10, pady=10)
def ifok(self):
if self.controller.shared_data["variable"].get() == 'string i wanna pass': ### called .get()
print("ok")
app = example()
app.mainloop()

Python, Tkinter entry get to function (error was with function call from button)

The entry value is just simply doesn't get passed into the function, whatever I do.
class Registration(tk.Frame):
def __init__(self, parent, controller):
name_var = tk.StringVar()
name_entry = ttk.Entry(self, textvariable=name_var)
name_entry.grid(row=1, column=1)
button1 = ttk.Button(self, text="Registration", command=self.RegFunction(name_var.get()))
button1.grid(row=4, column=4)
def RegFunction(self, name)
print(name)
Edit:
I just realized, if I add a variable to the function, it gets called as soon as I run the program, and it doesn't care about the button; but if I don't give it a variable, it works as it should, only when I push the button.
Here is the whole code
import tkinter as tk
from tkinter import ttk
class ToDoList(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 (LogIn, Registration):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(LogIn)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class LogIn(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# label of frame Layout 2
label = ttk.Label(self, text="Log In")
label.grid(row=0, column=4, padx=10, pady=10)
username_label = ttk.Label(self, text="Username :")
username_label.grid(row=1, column=1)
password_label = ttk.Label(self, text="Password:")
password_label.grid(row=2, column=1)
usrname_entry = ttk.Entry(self)
usrname_entry.grid(row=1, column=2)
password_entry = ttk.Entry(self)
password_entry.grid(row=2, column=2)
button2 = ttk.Button(self, text="Registration",
command=lambda: controller.show_frame(Registration))
button2.grid(row=4, column=1, padx=10, pady=10)
class Registration(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = ttk.Label(self, text="Registration")
label.grid(row=0, column=4, padx=10, pady=10)
name_label = ttk.Label(self, text="Username:")
name_label.grid(row=1, column=0)
pass1_label = ttk.Label(self, text="Password:")
pass1_label.grid(row=2, column=0)
pass2_label = ttk.Label(self, text="Password again:")
pass2_label.grid(row=3, column=0)
name_var = tk.StringVar()
name_entry = ttk.Entry(self, textvariable=name_var)
name_entry.grid(row=1, column=1)
pass1_entry = ttk.Entry(self)
pass1_entry.grid(row=2, column=1)
pass2_entry = ttk.Entry(self)
pass2_entry.grid(row=3, column=1)
k = 12
button1 = ttk.Button(self, text="Registration", command=self.RegFunction(k))
button1.grid(row=4, column=4, padx=10, pady=10)
button2 = ttk.Button(self, text="Back to Login",
command=lambda: controller.show_frame(LogIn))
button2.grid(row=4, column=0, padx=10, pady=10)
def RegFunction(self, x):
print("Something", x)
app = ToDoList()
app.mainloop()
There's a difference between calling a function and passing in a function's name so it can be called later on. The ttk.Button command= expects a function name (or reference) to be passed in, so that the named or referenced function can be called later. You are calling the function rather than passing in its name, so things go awry.
Change:
button1 = ttk.Button(self, text="Registration", command=self.RegFunction(name_var.get()))
to:
button1 = ttk.Button(self, text="Registration", command=lambda: self.RegFunction(name_var.get()))
and you'll be closer to your goal. The lambda tells Python not to call the function but rather just return a reference to it that can be used to call it later.
Once you do that, you'll see that you have a typo in your function definition -- you're missing a colon at the end of the def statement. So, change:
def RegFunction(self, name)
to:
def RegFunction(self, name): # colon added at end
command=self.RegFunction(name_var.get()) will execute self.RegFunction() immediately. You need to use lambda:
class Registration(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
name_var = tk.StringVar()
name_entry = ttk.Entry(self, textvariable=name_var)
name_entry.grid(row=1, column=1)
button1 = ttk.Button(self, text="Registration", command=lambda: self.RegFunction(name_var.get()))
button1.grid(row=4, column=4)
def RegFunction(self, name):
print(name)
Note that your code did not call super().__init__(parent) inside __init__().
You are not calling the entry function, but the outcome of the function. Try it without the brackets:
button1 = ttk.Button(self, text="Registration", command=self.RegFunction(name_var))
Also, the RegFunction misses the colon:
def RegFunction(self, name):
print(name)

How to properly access a StringVar() of a class from another class - Python - tkinter [duplicate]

This question already has answers here:
How to access variables from different classes in tkinter?
(2 answers)
Closed 4 years ago.
(I'm using mac 10.8.5 and Python3 with PyCharm)
I have a tkinter GUI TestMain() class plus one PageOne() class and a PageTwo() class.
I need PageOne() and PageTwo() to be different GUI windows cause they will handle different data.
I minimized the code in order to set it as readable as possible.
After many tests I tried to place the tk.StringVar() and a simple function in the global scope as you can see below, but there's still a problem.
import tkinter as tk
page1_label = tk.StringVar()
page2_entry = tk.StringVar()
def set_ebdata():
data = page2_entry.get()
page1_label.set(data)
class TestMain(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, 'TEST GUI')
container = tk.Frame(self)
container.pack(side='top')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.configure(background='lightgrey')
frame.grid(row=0, column=0, sticky='nswe')
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
frame_eb_data = tk.Frame(self, width=100, height=100, bg="orange", colormap="new")
frame_eb_data.grid(row=0, column=0, sticky='w', padx=5, pady=5)
frame_but_right = tk.Frame(self, width=240, height=60, bg="yellow", colormap="new")
frame_but_right.grid(row=0, column=1, padx=5, pady=5, rowspan=2)
lab_eb_data = tk.Label(frame_eb_data, background='#DDD4EF', textvariable=page1_label)
lab_eb_data.grid(row=0, column=0, sticky='n')
b_ebdata = tk.Button(frame_but_right, text="Page 2", width=10, height=2, command=lambda: controller.show_frame(PageTwo))
b_ebdata.grid(row=3, column=0)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
frame_buttons = tk.Frame(self, width=455, bg="#DDD4EF", colormap="new")
frame_buttons.grid(row=0, column=0, padx=5, pady=5, sticky='e')
frame_up_left = tk.Frame(self, width=485, height=260, bg="#89E3FA", colormap="new")
frame_up_left.grid(row=1, column=0, sticky='w', padx=5, pady=5)
b_data = tk.Label(frame_buttons, text='Example GUI', font='TrebuchetMS 30 bold', background="#DDD4EF")
b_data.grid(row=0, column=0, padx=13, pady=5, sticky='w')
b5 = tk.Button(frame_buttons, text='Set Text', command=lambda: set_ebdata)
b5.grid(row=0, column=2, padx=5, pady=5, sticky='e')
b6 = tk.Button(frame_buttons, text='Page 1', command=lambda: controller.show_frame(PageOne))
b6.grid(row=0, column=3, padx=5, pady=5, sticky='e')
label_2 = tk.Label(frame_up_left, text="Name:", font=("bold", 14))
label_2.grid(row=1, column=0, sticky='e')
entry_nombre_fld = tk.Entry(frame_up_left, width=40, textvariable=page2_entry)
entry_nombre_fld.grid(row=1, column=1, columnspan=3, sticky='w')
app = TestMain()
app.mainloop()
When you run the program a window with a "Page 2" button (b_ebdata) appears, by clicking it you enter Page 2 window which has a "Set Text" button (b5), a "Page 1" button (b6) and an entry field (entry_nombre_fld).
I'd like to set the text I'll enter in the entry field (entry_nombre_fld) in the Page 1 label (lab_eb_data) by clicking the "Set Text" button (b5).
Could a solution be to put page1_label = tk.StringVar() into PageOne() class and page2_entry = tk.StringVar() into PageTwo() class and make those accessible by each other?
Any other suggestion ?
Thx in advance for your help!
I had to change a few things but for the most part the major solution is to move your StringVar()'s into the main class. Then next we can use the controller argument in the other 2 classes to manipulate the data.
I added a function on page 2 to deal with updating the the label StringVar.
Because of this I deleted the other function you had for this.
I had to change your entry field to a class attribute so we can use its content in the new method. I also had to create a class attribute for the controller in page 2 so we can use the controller in the method as well.
Now there might be an easier way but this is what I managed with your code.
import tkinter as tk
class TestMain(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title('TEST GUI')
# Moved StringVar()'s to the main class
self.page1_label = tk.StringVar()
self.page2_entry = tk.StringVar()
container = tk.Frame(self)
container.pack(side='top')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.configure(background='lightgrey')
frame.grid(row=0, column=0, sticky='nswe')
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# Deleted this function
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
frame_eb_data = tk.Frame(self, width=100, height=100, bg="orange")
frame_eb_data.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
frame_but_right = tk.Frame(self, width=240, height=60, bg="yellow")
frame_but_right.grid(row=1, column=0, padx=5, pady=5, sticky='nsew')
lab_eb_data = tk.Label(frame_eb_data, background='#DDD4EF', textvariable=controller.page1_label)
lab_eb_data.grid(row=0, column=0)
b_ebdata = tk.Button(frame_but_right, text="Page 2", width=10, height=2, command=lambda: controller.show_frame(PageTwo))
b_ebdata.grid(row=0, column=0)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# Added the self.controller so the method below can use it.
self.controller = controller
frame_buttons = tk.Frame(self, width=455, bg="#DDD4EF", colormap="new")
frame_buttons.grid(row=0, column=0, padx=5, pady=5, sticky='e')
frame_up_left = tk.Frame(self, width=485, height=260, bg="#89E3FA", colormap="new")
frame_up_left.grid(row=1, column=0, sticky='w', padx=5, pady=5)
b_data = tk.Label(frame_buttons, text='Example GUI', font='TrebuchetMS 30 bold', background="#DDD4EF")
b_data.grid(row=0, column=0, padx=13, pady=5, sticky='w')
b5 = tk.Button(frame_buttons, text='Set Text', command= self.update_p2_label)
b5.grid(row=0, column=2, padx=5, pady=5, sticky='e')
b6 = tk.Button(frame_buttons, text='Page 1', command=lambda: controller.show_frame(PageOne))
b6.grid(row=0, column=3, padx=5, pady=5, sticky='e')
self.entry_nombre_fld = tk.Entry(frame_up_left, width=40)
self.entry_nombre_fld.grid(row=1, column=1, columnspan=3, sticky='w')
label_2 = tk.Label(frame_up_left, text="Name:", font=("bold", 14))
label_2.grid(row=1, column=0, sticky='e')
# Added this function to update the page1_label StringVar.
def update_p2_label(self):
self.controller.page1_label.set(self.entry_nombre_fld.get())
app = TestMain()
app.mainloop()

Categories