Having little difficulty with adding frames to organize my program - python

I am using this code to navigate between frames in my Tkinter text editor app. And when I want to organize the pageone with multiple frames in order to collect all the buttons in the same frame and other widgets in some other possible frames I got an error: _tkinter.TclError: cannot use geometry manager grid inside . which already has slaves managed by pack. if you could help me, ty.
here is the code I used
import tkinter as tk
from tkinter import ttk
LARGE_FONT=("Verdana",12)
def popupmsg(msg):
popup=tk.Tk()
popup.wm_title("!")
label=ttk.Label(popup,text=msg,font=LARGE_FONT)
label.pack()
b1=ttk.Button(popup,text="OKAY",command=popup.destroy)
b1.pack()
popup.mainloop()
class MainWindow(tk.Tk):
def __init__(self,*arg,**kwargs):
tk.Tk.__init__(self,*arg,**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)
menubar=tk.Menu(container)
filemenu=tk.Menu(menubar,tearoff=0)
filemenu.add_command(label="Save settings", command=lambda:popupmsg("Not supported yet!"))
filemenu.add_command(label="Exit",command=quit)
menubar.add_cascade(label="File", menu=filemenu)
tk.Tk.config(self,menu=menubar)
self.frames={}
for F in(StartPage,PageOne):
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)
label_startpage=ttk.Label(self,text="Start Page",font=LARGE_FONT)
label_startpage.pack(padx=10,pady=10)
button_to_pageONE=ttk.Button(self,text="Go to Page ONE",command= lambda:
controller.show_frame(PageOne)).pack()
class PageOne(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.rowconfigure(0, minsize=200, weight=1)
self.columnconfigure(1, minsize=200, weight=1)
txt_edit = tk.Text(self).grid(row=0, column=1, sticky="nsew")
button_frame = tk.Frame(self,bg="lightblue").grid(row=0, column=0, sticky="ns")
btn_open = ttk.Button(button_frame, text="Open").grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save = ttk.Button(button_frame, text="Save As...").grid(row=1, column=0, sticky="ew", padx=5)
button_to_startpage = ttk.Button(button_frame, text="Back to Start Page",
command=lambda:
controller.show_frame(StartPage)).grid(row=2, column=0,
sticky="ew", padx=5,
pady=5)
app=MainWindow()
app.geometry("1280x720")
app.mainloop()

You cannot use pack as well as grid on container
container=tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
pack and grid just don't work well together.
See this entry for more info

You're making a very common mistake on this line:
button_frame = tk.Frame(self,bg="lightblue").grid(row=0, column=0, sticky="ns")
Because you are doing tk.Frame(...).grid(...), button_frame is being set to the result of .grid(...). That makes button_frame None. When you do btn_open = ttk.Button(button_frame, ...), that places the button as a child of the root window rather than a child of button_frame. You're using pack in the root window, so you can't use grid for any other widgets in the root window.
The solution is to properly create button_frame by separating widget creation from widget layout:
button_frame = tk.Frame(...)
button_frame.grid(...)

Related

How can I create this layout with Python3 using Tkinter with the grid geometry manager?

I am trying to create the following layout:
This is my code:
from . import FlowPane,JsonPane,PropertiesPane,ToolbarPane
import tkinter as tk
class ConflixEditor(tk.Tk):
def __init__(self, args):
super().__init__()
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black')
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue')
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green')
# Widget Layout
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.S+tk.W)
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)
The constructors for FlowPane, JsonPane, PropertiesPane, ToolbarPane all take two parameters: the parent widget and the background color.
Instead of getting the desired result above, I am getting the following result:
What am I doing wrong? How can I create the desired layout? Note that the background colors are just temporary to confirm that each widget is using the correct amount of space. This is eventually going to be an application for designing and building Netflix Conductor workflows. I want to have a toolbar with menus and buttons in the black area, a Canvas in the red area for displaying the flow-chart elements that represent tasks in the workflows, a Treeview for viewing properties in the blue area, and a Text Editor in the green area at the bottom for viewing/editing the raw JSON.
You need to:
specify height option for toolbarPane and jsonPane;
specify width option for propertiesPane;
add tk.E to sticky option for flowPane;
use grid_rowconfigure() and grid_columnconfigure() for self and self.frame as well
Below is an updated code:
class ConflixEditor(tk.Tk):
def __init__(self, *args):
super().__init__()
#self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# make self.frame use all the space of root window
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black', height=100) # added height option
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue', width=250) # added width option
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green', height=200) # added height option
# Widget Layout
self.frame.grid_columnconfigure(0, weight=1) # used self.frame instead of self
self.frame.grid_rowconfigure(2, weight=1) # used self.frame instead of self
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.E+tk.S+tk.W) # added tk.E
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)

*.grid() layout manager does not work as I expect it to behave

So I try to create my from Tk.LabelFrame derived class and use the grid() layout manager.
But somehow it doesn't work as expected. It really annoys me, is there anything I dont see?
Every other class I used and created with this method works as expected, but not this one...
This is my minimal working example (which doesnt "work", lol):
# ==[ Import ]============================================================
import tkinter as Tk
from tkinter import ttk
# ==[ Class definition ]==================================================
class FileHandlingFrame(Tk.LabelFrame):
# --< Initialization >------------------------------------------------
# Constructor
def __init__(self, container, *args, **kwargs):
Tk.LabelFrame.__init__(self, container, *args, **kwargs)
self.configure_self()
self.create_widgets()
pass
def configure_self(self):
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(2, weight=1)
self.grid_columnconfigure(3, weight=1)
self.grid_columnconfigure(4, weight=1)
self.grid_columnconfigure(5, weight=1)
self.grid_columnconfigure(6, weight=1)
self.grid_columnconfigure(7, weight=1)
self.pack()
pass
def create_widgets(self):
# Create ui components
self.importButton = ttk.Button(master=self, text="Import")
self.exportButton = ttk.Button(master=self, text="Export")
self.currentLabel = ttk.Label(master=self, text="Current")
self.createButton = ttk.Button(master=self, text="Create")
self.configurationButton = ttk.Button(master=self, text="s")
# Grid components
self.importButton.grid(row=0, column=0, columnspan=4, sticky="w")
self.exportButton.grid(row=0, column=4, columnspan=4, sticky="e")
self.currentLabel.grid(row=1, column=0, columnspan=8, sticky="w")
self.createButton.grid(row=2, column=0, columnspan=7)
self.configurationButton.grid(row=2, column=7, columnspan=1)
pass
# ==[ Start of main application ]=========================================
if __name__ == "__main__":
root = Tk.Tk()
root.resizable(width=False, height=False)
root.title("MWE")
mainframe = Tk.Frame(master=root)
mainframe.pack()
application = FileHandlingFrame(container=mainframe)
application.mainloop()
This is what I expect:
But this is what I get:
Anyone has any idea? I really am fighting this for hours already...
P.S.: I know I should do:
self.importButton.grid(row=0, column=0, columnspan=4, sticky="w")
self.exportButton.grid(row=0, column=4, columnspan=4, sticky="e")
Even though you've given a weight to every column, columns without anything in them will be empty and thus have no size. weight only affects how extra space is allocated, and doesn't affect the minimum or default size.
If you want all of the columns to be the same size, you can set the uniform option of every column to the same value. It doesn't matter what the value is as long as it is the same for every column.
The other problem is that you aren't using sticky properly to get the result from your drawing.
By the way, you can pass more than one column to columnconfigure:
self.grid_columnconfigure((0,1,2,3,4,5,6,7), weight=1, uniform="x")
...
self.importButton.grid(row=0, column=0, columnspan=4, sticky="ew")
self.exportButton.grid(row=0, column=4, columnspan=4, sticky="ew")
self.currentFileLabel.grid(row=1, column=0, columnspan=8, sticky="ew")
self.createHeatmapButton.grid(row=2, column=0, columnspan=7, sticky="ew")
self.heatmapConfigurationButton.grid(row=2, column=7, columnspan=1, sticky="ew")

Trouble adding new elements inside a new window executed through a button

I made a button inside a class and that calls a define function which creates a new window, but whenever I try to put something inside the window like a Label, nothing appears. What is it am I doing wrong? What am I supposed to call when creating the Label because it seems like I'm not calling the right thing
from tkinter import *
class mainTitle(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
myTitle = Label(self, text="Arithmetic & Geometric Sequence", font=("bold", 15))
myTitle.grid(row=1, column=1, pady=50)
opLabel = Label(self, text="Chooose either Arithmetic & Geometric series to calculate")
opLabel.grid(row=2, column=1, pady=10)
self.ariButton = Button(self, text="Arithmetic Sequence", command=self.ariClick)
self.ariButton.grid(row=3, column=1)
self.geoButton = Button(self, text="Geometric Sequence", command=self.geoClick)
self.geoButton.grid(row=4, column=1, pady=10)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(5, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
def ariClick(Frame):
ariWindow = Toplevel()
ariWindow.geometry("300x300")
ariWindow.title("Arithmetic Sequence")
aLbl = Label(Frame, text="a")
aLbl.place(anchor=CENTER) #This is not appearing in the new window
def geoClick(Frame):
geoWindow = Toplevel()
geoWindow.geometry("300x300")
geoWindow.title("Geometric Sequence")
def qProgram(Frame):
root.destroy()
if __name__ == "__main__":
root = Tk()
root.geometry("450x350")
mainTitle(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
this should work
class mainTitle(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
myTitle = Label(self, text="Arithmetic & Geometric Sequence", font=("bold", 15))
myTitle.grid(row=1, column=1, pady=50)
opLabel = Label(self, text="Chooose either Arithmetic & Geometric series to calculate")
opLabel.grid(row=2, column=1, pady=10)
self.ariButton = Button(self, text="Arithmetic Sequence", command=self.ariClick)
self.ariButton.grid(row=3, column=1)
self.geoButton = Button(self, text="Geometric Sequence", command=self.geoClick)
self.geoButton.grid(row=4, column=1, pady=10)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(5, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
def ariClick(Frame):
ariWindow = Toplevel()
ariWindow.geometry("300x300")
ariWindow.title("Arithmetic Sequence")
#i called as parent the toplevel() instance ariwindow
aLbl = Label(ariWindow, text="aqwertyui")
aLbl.place(anchor=CENTER) # This is not appearing in the new window
def geoClick(Frame):
geoWindow = Toplevel()
geoWindow.geometry("300x300")
geoWindow.title("Geometric Sequence")
def qProgram(Frame):
root.destroy()
if __name__ == "__main__":
root = Tk()
root.geometry("450x350")
mainTitle(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
vote the answer if it runs.

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

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")

Is it possible to manage geometry modularly?

I have a code that I'm trying to handle the geometry of a button in a frame and entry in another frame. But it doesn't seem to work independently of the main window they're both children of.
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class NumPad(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.button = tk.Button(text=0)
self.button.grid(row=1, column=0, sticky='nsew')
class CalcFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.entry = tk.Entry(self)
self.entry.pack()
if __name__ == '__main__':
root = tk.Tk()
frame1 = CalcFrame(master=root)
frame2 = NumPad(master=root)
frame1.grid(row=0, column=0)
frame2.grid(row=1, column=0, sticky='nsew')
root.mainloop()
In the above code if I replace:
self.button.grid(row=0, column=0, sticky='nsew')
with:
self.button.grid(row=1, column=0, sticky='nsew')
the widget in frame2 overlaps the widget on frame1. How can I have an inner grid per widget basis? Right now it seems like there's only one top-level grid.
As Bryan Oakley pointed out in the comments above, when you declare the Button widget on this line...
self.button = tk.Button(text=0)
You aren't assigning it a parent meaning that it just dumps itself into the Tk() window by default.
On a side note, you have variables which by their name suggest that they are Frame widgets (namely frame1 and frame2) but actually appear to be references to classes which don't ever use Frame widgets.
Frame widgets are very powerful and can be used to easily separate sets of widgets in the same window. An example of using Frames can be found below:
from tkinter import *
root = Tk()
frame1 = Frame(root, borderwidth=1, relief="solid")
frame2 = Frame(root, borderwidth=1, relief="solid")
frame1.pack(side="left", fill="both", expand=True, padx=10, pady=10)
frame2.pack(side="right", fill="both", expand=True, padx=10, pady=10)
label1 = Label(frame1, text="I'm inside a frame")
label2 = Label(frame2, text="I'm inside a different frame")
label1.pack()
label2.pack()
root.mainloop()
This shows that you can have widgets using a different geometry manager to their parents:
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame2 = Frame(root)
frame1.pack(side="left")
frame2.pack(side="right")
label1 = Label(frame1, text="I'm grid")
label2 = Label(frame1, text="I'm grid")
label3 = Label(frame2, text="I'm pack")
label4 = Label(frame2, text="I'm pack")
label1.grid(row=0, column=0)
label2.grid(row=0, column=1)
label3.pack()
label4.pack()
root.mainloop()

Categories