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.
Related
I'd like to position one of my python tkinter Frames (ButtonWindow) within my other Frame (MainWindow), so that when I run the app the widgets in ButtonWindow are present in MainWindow along with the MainWindow widget.
In the code below the Buttons from ButtonWindow are present along with the MainWindow Label, but the ButtonWindow Label is missing.
I looked at the answers in Frame inside another frame In python Tkinter and tried to set the background to purple to understand where the borders of ButtonWindow actually are, but I can't see any purple?
Thanks for any help!
import tkinter as tk
class ButtonWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.bd = 5
self.bg = "purple"
self.label = tk.Label(text="Button window", font=12)
for i in range(3):
self.button = ttk.Button(text="button", command= lambda: button_fun())
self.button.pack(side = tk.LEFT)
def button_fun(self):
pass
class MainWindow(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.label = tk.Label(text="Main Window", font=12)
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow()
self.button_window.pack()
app = MainWindow()
app.mainloop()
It is better to specify the parent of widgets when creating them, otherwise they will be children of root window.
Also you have never called any layout function on the "Button window" label so it is not visible.
self.bd = 5 and self.bg = "purple" will not change the border width and the background color. Use self.config(bd=5, bg="purple") instead.
import tkinter as tk
from tkinter import ttk
class ButtonWindow(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.config(bd=5, bg="purple") # replace self.bd = 5 and self.bg = "purple"
self.label = tk.Label(self, text="Button window", font=12, fg='white', bg='purple') # specify parent
self.label.pack() # pack the label, otherwise it is not visible
for i in range(3):
self.button = ttk.Button(self, text="button", command=self.button_fun) # specify parent
self.button.pack(side=tk.LEFT)
def button_fun(self):
pass
class MainWindow(tk.Frame):
def __init__(self, master=None, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.label = tk.Label(self, text="Main Window", font=12) # specify parent
self.label.pack(pady=10, padx=10)
self.button_window = ButtonWindow(self) # specify parent
self.button_window.pack()
root = tk.Tk() # create root window explicitly
MainWindow(root).pack()
root.mainloop()
Also I have changed command=lambda: button_fun() to command=self.button_fun. The former one will raise exception when any of the buttons is clicked.
If I understood correctly what you want is create a Main Frame with another frame inside it which include 3 buttons.
In this case, I changed a little bit your code to do that. One of the changes was replace the tk.Label for the tk.LabelFrame (This corrects the Label that was missing in the ButtonWindow as you stated).
The second change that I suggest is to pass the MainFrame to the ButtonWindow as a parent frame. To do this I created the myCoreFrame inside the MainWindowclass. Also, for all widgets I've set a parent frame.
import tkinter as tk
class ButtonWindow():
def __init__(self, Frame, *args, **kwargs):
self.label = tk.LabelFrame(Frame, text="Button window", font=12, bg = "purple")
self.label.pack()
for i in range(3):
self.button = tk.Button(self.label, text="button", command= lambda: button_fun())
self.button.pack(side = tk.LEFT)
def button_fun(self):
pass
class MainWindow():
def __init__(self, window, *args, **kwargs):
myCoreFrame = tk.Frame(window)
myCoreFrame.pack()
self.label = tk.LabelFrame(myCoreFrame, text="Main Window", font=12, bg = "red")
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow(self.label)
root = tk.Tk()
app = MainWindow(root)
root.mainloop()
The problem is that you aren't putting the labels and buttons inside frames. You need to explicitly set the frame as the parent of the button and label. If you don't, the widgets become children of the root window.
class ButtonWindow(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.label = tk.Label(self, text="Button window", font=12)
# ^^^^^^
for i in range(3):
self.button = ttk.Button(self, text="button", command= lambda: button_fun())
# ^^^^^^
...
class MainWindow(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.label = tk.Label(self, text="Main Window", font=12)
# ^^^^^
self.label.pack(pady=10,padx=10)
self.button_window = ButtonWindow(self)
# ^^^^
...
If I create a Toplevel popup window in a tkinter application, when I close the main root window all sub windows are closed.
With the following code, if I open up two successive Toplevel windows, I can close "Window One" but "Window Two" stays open.
#!/usr/bin/env python3
import tkinter as tk
import tkinter.ttk as ttk
def main():
root = tk.Tk()
root.geometry("600x300")
root.title("Main Window")
app = Application(root)
app.mainloop()
def window_one():
window_one = WindowOne()
window_one.geometry("450x200")
window_one.title('Window One')
def window_two():
window_two = WindowTwo()
window_two.geometry("200x100")
window_two.title('Window Two')
class Application(ttk.Frame):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.pack()
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = window_one)
button1.pack()
class WindowOne(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = window_two)
button1.pack()
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
if __name__ == "__main__":
main()
How can I code this to make Window Two dependent on Window One so that if I close "Window One", "Window Two" also closes, mimicking the behaviour of the main root window?
You can make Window One the parent of Window Two:
def window_two(parent):
window_two = WindowTwo(parent) # pass parent to WindowTwo
window_two.geometry("200x100")
window_two.title('Window Two')
...
class WindowOne(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
def create_widgets(self):
button1 = ttk.Button(self, text = "Click Here", command = lambda: window_two(self)) # pass self to `window_two()`
button1.pack()
Update: if you want to close Window One when Window Two is closed, you can use self.protocol("WM_DELETE_WINDOW", ...):
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
self.protocol("WM_DELETE_WINDOW", self.on_destroy)
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
def on_destroy(self):
self.master.destroy() # close parent window
self.destroy() # close itself
or bind <Destroy> event to a callback to close the parent window:
class WindowTwo(tk.Toplevel):
def __init__(self, master= None, *args, **kwargs):
super().__init__(master = master)
self.create_widgets()
self.bind("<Destroy>", self.on_destroy)
def create_widgets(self):
button1 = ttk.Button(self, text = "$$$")
button1.pack()
def on_destroy(self, event):
self.master.destroy() # close parent window
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)
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()
I'm having a trouble when i open a secondary window. Now I'm just creating a toplevel window with a button and I need to open the same secondary window If i click the button (not generate a new instance).
Which is the better way to generate single secondary window and not generating a new window instance?
I leave the code that I'm actually working on:
import tkinter
class LogWindow():
def __init__(self, parent):
self.parent = parent
self.frame = tkinter.Frame(self.parent)
class MainWindow(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent = parent
frame = tkinter.Frame(self, borderwidth=1)
frame.pack(fill=tkinter.BOTH, expand=True, padx=5, pady=5)
self.LogButton = tkinter.Button(frame, text="Log Viewer", command= self.openLogWindow)
self.LogButton.grid(sticky=tkinter.E+tkinter.W)
self.pack(fill=tkinter.BOTH,expand=True)
def openLogWindow(self):
self.logWindow = tkinter.Toplevel(self.parent)
self.app = LogWindow(self.logWindow)
def main():
global app, stopRead
root = tkinter.Tk()
root.geometry("300x300")
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
Maybe i need to have a single instance of a Toplevel class and call show and close to show or hide the secondary window.
Finally after some tests I've found how to solve that, thanks to the #furas response and some investigation about the Tkinter events with the protocol function.
I've got that working with:
import tkinter
logWindowExists = False
class LogWindow():
def __init__(self, parent):
global logWindowExists, root
logWindowExists = True
self.parent = parent
self.frame = tkinter.Frame(self.parent)
def on_closing(self):
global logWindowExists
logWindowExists = False
self.parent.destroy()
class MainWindow(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent = parent
frame = tkinter.Frame(self, borderwidth=1)
frame.pack(fill=tkinter.BOTH, expand=True, padx=5, pady=5)
self.LogButton = tkinter.Button(frame, text="Log Viewer", command= self.openLogWindow)
self.LogButton.grid(sticky=tkinter.E+tkinter.W)
self.pack(fill=tkinter.BOTH,expand=True)
def openLogWindow(self):
if not logWindowExists:
self.logWindow = tkinter.Toplevel(self.parent)
self.app = LogWindow(self.logWindow)
self.logWindow.protocol("WM_DELETE_WINDOW", self.app.on_closing)
else:
self.logWindow.deiconify()
def main():
global app, stopRead, root
root = tkinter.Tk()
root.geometry("300x300")
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
Using a boolean to know if the window exists or not i can handle when the window it's opened or not and just show the existing window or creating a new one.