I'm aware of how to create a basic Tkinter menu bar, but I'm not sure how to implement it such that the menu appears on every frame of a multi-frame GUI.
I will be using the menu bar to switch between frames. Therefore, I need to run the controller.show_frame command within the menu commands. I am currently using buttons to do this.
I'm unable to find a way to do this, as (as far as I am aware) the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the function.
Here is the code:
""" Messing about with tkinter """
import tkinter as tk
LARGE_FONT = ("Verdana", 12)
class Window(tk.Tk):
""" Main class """
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 frame in (Main, Checker):
current_frame = frame(container, self)
self.frames[frame] = current_frame
current_frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Main)
def show_frame(self, cont):
""" Raises a particular frame, bringing it into view """
frame = self.frames[cont]
frame.tkraise()
def qprint(quick_print):
""" Function to print a string """
print(quick_print)
class Main(tk.Frame):
""" Main frame of program """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=LARGE_FONT)
label.pack(pady=10, padx=10)
class Checker(tk.Frame):
""" Password Strength Checker """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Password Checker", font=LARGE_FONT)
label.pack(pady=10, padx=10)
APP = Window()
APP.geometry("350x200")
APP.mainloop()
"the menu must be created in the frame class rather than the tk.Tk class, in order to allow me to run the
function."
I don't think that's true, see below example that creates Menu for a Toplevel widget:
import tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
toplevel = tk.Toplevel(root)
# create a toplevel menu
menubar = tk.Menu(toplevel)
menubar.add_command(label="Hello!")
menubar.add_command(label="Quit!", command=root.quit)
# display the menu
toplevel.config(menu=menubar)
root.mainloop()
Alternatively, you can create menu's in frames for their parents with the condition that their parent is Toplevel-like.
In below example when a menu item is selected Root's menu jumps between the Root's menu and its children FrameWithMenu object's menu:
import tkinter as tk
class Root(tk.Tk):
def __init__(self):
super().__init__()
self.title("The Root class with menu")
self.a_frame = FrameWithMenu(self)
self.create_menu()
def create_menu(self):
self.menubar = tk.Menu(self)
self.menubar.add_command(label="Root", command=self.a_frame.replace_menu)
self['menu'] = self.menubar
class FrameWithMenu(tk.Frame):
def __init__(self, master):
super().__init__(master)
def replace_menu(self):
""" Overwrite parent's menu if parent's class name is in _valid_cls_names.
"""
_parent_cls_name = type(self.master).__name__
_valid_cls_names = ("Tk", "Toplevel", "Root")
if _parent_cls_name in _valid_cls_names:
self.menubar = tk.Menu(self)
self.menubar.add_command(label="Frame", command=self.master.create_menu)
self.master['menu'] = self.menubar
if __name__ == '__main__':
root = Root()
root.mainloop()
I found an answer to my own question. All I needed to do was create the menu using the controller parameter, which references the tk.Tk class.
class Main(tk.Frame):
""" Main frame of program """
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=LARGE_FONT)
label.pack(pady=10, padx=10)
menubar = tk.Menu(controller)
menubar.add_command(label="Checker", command=lambda: controller.show_frame(Checker))
controller.config(menu=menubar)
Related
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!
Segmentation fault: 11 - not sure what it means, why it has happened. I thought it was an issue with Python on my machine by all other files run fine. I have, of course, tried restarting and re-installing Python but didn't help.
I'm just trying to implement frame switching via a menu bar with tkinter.
Any help greatly appreciated.
# import tkinter modules
from tkinter import *
from tkinter import ttk
import tkinter.font as tkFont
from PIL import ImageTk, Image
from tkcalendar import *
# import modules for restart functionality
import os
import sys
import time
# define self
class tkinterApp(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
# creating a container
container = Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
# initialising frames to an empty array
self.frames = {}
menu_bar = Menu(container)
menu_bar.add_cascade(label="Main Menu", menu=menu_bar)
menu_bar.add_command(label="Welcome page", command=lambda: self.show_frame(welcome_frame))
menu_bar.add_command(label="Book a vehicle", command=lambda: self.show_frame(booking_frame))
menu_bar.add_command(label="Register as new user", command=lambda: self.show_frame(register_frame))
Tk.config(self, menu=menu_bar)
for F in (welcome_frame, register_frame, booking_frame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(welcome_frame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class welcome_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
# welcome_frame = Frame(self, width=1000, height=800)
# welcome_frame.grid()
welcome = Label(welcome_frame, text="Hello, please use the menu above to navigate the interface")
welcome.grid(row=0, column=4, padx=10, pady=10)
class register_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
welcome = Label(self, text="New user - enter your details below to use the Collyer's car park.")
welcome.grid(row=0, column=4, padx=10, pady=10)
class booking_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
app = tkinterApp()
app.geometry("1000x800")
app.mainloop()
You are trying to make a cascade menu where the cascaded menu is the same menu:
menu_bar.add_cascade(label="Main Menu", menu=menu_bar)
The menu option needs to point to a new menu menu.
main_menu = Menu(menu_bar)
menu_bar.add_cascade(label="Main Menu", menu=main_menu)
I'm guessing you also want to put the menu commands on that menu, too
main_menu.add_command(label="Book a vehicle", command=lambda: self.show_frame(booking_frame))
main_menu.add_command(label="Register as new user", command=lambda: self.show_frame(register_frame))
Unrelated to the question, this code is also wrong:
welcome = Label(welcome_frame, text="Hello, please use the menu above to navigate the interface")
You are trying to use a class as the parent/master of the Label widget. You can't do that. The first parameter needs to be a widget. In this case, it should be self.
You also need to make sure that show_frame is indented the same as the __init__ method of the tkinterApp class.
I have a Tkinter app in which I would like to include some buttons in a frame, and then place this frame in the main window.
However running the code returns just an empty window. So I guess I miss completely how to build a Tkinter app with modular classes.. The atomic code is:
import Tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.navbar = NavBar(self)
self.navbar.grid(row=0, column=0)
class NavBar(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.fetchDataBtn = tk.Button(self, text='Fetch data')
self.filterDataBtn = tk.Button(self, text='Filter data')
self.fetchDataBtn.pack(padx=5, pady=10, side=tk.LEFT)
self.filterDataBtn.pack(padx=5, pady=20, side=tk.LEFT)
def main():
root = tk.Tk()
app = MainApplication(root)
root.mainloop()
if __name__ == '__main__':
main()
I thus wonder what I miss. I searched but cannot find duplicates ..(if they are some, you can point out and I will close the topic).
NB: I am using Python 2.7.10
The problem is that you don't pack (or grid or place) your MainApplication instance.
Since your MainApplication extends the tk.Frame class, its instances are widgets, and thus need to be packed into their master.
def main():
root = tk.Tk()
app = MainApplication(root) <--- here: where does it go in the root?
root.mainloop()
Pack it and it will work:
app.pack()
You must put the navbar in the parent frame, using pack, or grid:
import Tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.navbar = NavBar(self)
self.navbar.grid(row=0, column=0)
self.pack() # <-- here ---------
class NavBar(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.fetchDataBtn = tk.Button(self, text='Fetch data')
self.filterDataBtn = tk.Button(self, text='Filter data')
self.fetchDataBtn.pack(padx=5, pady=10, side=tk.LEFT)
self.filterDataBtn.pack(padx=5, pady=20, side=tk.LEFT)
def main():
root = tk.Tk()
app = MainApplication(root)
# app.pack() # <-- or here for a better control of the placement of several instances
root.mainloop()
if __name__ == '__main__':
main()
Credits to #RightLeg for pointing out an initial mistake.
I've looked at other question with a similar title and have read the answers, however nothing has worked for me. I am trying to make a simple app with a listbox + scroll bar with two buttons below it all within a group box. I've used pyqt but this is my first time using tkinter:
import tkinter as tk
class InputWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
# Group box to contain the widgets
self.input = tk.LabelFrame(self, text="Input Files")
# Listbox with scrollbar to the side
self.listbox = tk.Listbox(self.input)
self.scrollbar = tk.Scrollbar(self.listbox, orient=tk.VERTICAL)
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.listbox.grid(row=0, column=0, columnspan=2)
self.add_btn = tk.Button(self.input, text="Add...")
self.add_btn.grid(row=1, column=0)
self.remove_btn = tk.Button(self.input, text="Remove")
self.remove_btn.grid(row=1, column=1)
if __name__ == "__main__":
root = tk.Tk()
app = InputWindow(root)
root.mainloop()
This is more or less what I want but in tkinter:
What am I doing wrong/how can this be done?
You're forgetting two things:
To pack (or grid or place) app
To pack (or grid or place) input
You're program with the required statements:
import tkinter as tk
class InputWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initialize()
def initialize(self):
# Group box to contain the widgets
self.input = tk.LabelFrame(self, text="Input Files")
# Listbox with scrollbar to the side
self.listbox = tk.Listbox(self.input)
self.scrollbar = tk.Scrollbar(self.listbox, orient=tk.VERTICAL)
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.listbox.grid(row=0, column=0, columnspan=2)
self.add_btn = tk.Button(self.input, text="Add...")
self.add_btn.grid(row=1, column=0)
self.remove_btn = tk.Button(self.input, text="Remove")
self.remove_btn.grid(row=1, column=1)
self.input.pack(expand=1, fill="both") # Do not forget to pack!
if __name__ == "__main__":
root = tk.Tk()
app = InputWindow(root)
app.pack(expand=1, fill="both") # packing!
root.mainloop()
How would I create a new window when the user clicks a button (still needs creating)? I have took some code out to make this shorter. I need a button creating and when they hit that button, a new window opens. I haven't created the button because the button has to be linked to the new window. Please help
My imports...
class App:
def __init__(self, master):
self.master = master
# call start to initialize to create the UI elemets
self.start()
def start(self):
self.master.title("E-mail Extranalyser")
self.now = datetime.datetime.now()
tkinter.Label(
self.master, text=label01).grid(row=0, column=0, sticky=tkinter.W)
# CREATE A TEXTBOX
self.filelocation = tkinter.Entry(self.master)
self.filelocation["width"] = 60
self.filelocation.focus_set()
self.filelocation.grid(row=0, column=1)
# CREATE A BUTTON WITH "ASK TO OPEN A FILE"
# see: def browse_file(self)
self.open_file = tkinter.Button(
self.master, text="Browse...", command=self.browse_file)
# put it beside the filelocation textbox
self.open_file.grid(row=0, column=2)
# now for a button
self.submit = tkinter.Button(
self.master, text="Execute!", command=self.start_processing,
fg="red")
self.submit.grid(row=13, column=1, sticky=tkinter.W)
def start_processing(self):
#code here
def browse_file(self):
# put the result in self.filename
self.filename = filedialog.askopenfilename(title="Open a file...")
# this will set the text of the self.filelocation
self.filelocation.insert(0, self.filename)
root = tkinter.Tk()
app = App(root)
root.mainloop()
Use a Toplevel to open a new one. Modify your code as shown below.
self.NewWindow = tkinter.Button(self.master,
text="New Window",
command=self.CreateNewWindow)
def CreateNewWindow(self):
self.top = tkinter.Toplevel()
self.top.title("title")
Take a look at https://www.youtube.com/watch?v=jBUpjijYtCk. Working through this tutorial would probably help you but this specific video shows how to work with multiple pages.
Something like this:
from tkinter import *
class Sample(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (MainPage, OtherPage):
frame=F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MainPage)
def show_frame(self, page):
frame = self.frames[page]
frame.tkraise()
class MainPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Start Page").pack()
Button(self, text="other page?", command=lambda:controller.show_frame(OtherPage)).pack()
class OtherPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Next Page").pack()
Button(self, text="back", command=lambda:controller.show_frame(MainPage)).pack()
app = Sample()
app.mainloop()