I have a problem regarding the layout in tkinter. This is the
Layout I want to have and this is whatI have right now.
I am not understanding why this is happening. I thought when I specify the width and height for a specific frame it would take all this place but this is not happening looking at self.GameStatFrame. It would be nice if someone could explain to me why code does what it does and what my mistakes were.
Here is the code:
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, parent)
#self.update_idletasks()
self.createWidgets()
def createWidgets(self):
#new frame for everything not in the canvas
#self.Frame = tk.Frame(self.parent)
self.MlGameStatFrame = tk.Frame(self.parent, width=600, height=300,
bg='blue')
self.GameStatFrame = tk.Frame(self.MlGameStatFrame, bg='white',
width=300, height=300)
self.MlFrame = tk.Frame(self.MlGameStatFrame, bg='white',
width=300, height=300)
#self.Frame.pack()
self.createGraphWidget()
self.MlGameStatFrame.pack()
self.GameStatFrame.pack(side=tk.LEFT, anchor=tk.W, fill=tk.BOTH)
self.MlFrame.pack(side=tk.BOTTOM, fill=tk.BOTH)
self.createGameStats(self.GameStatFrame)
self.createMlStats(self.MlFrame)
#self.createLog()
def createGraphWidget(self):
self.graph = tk.Canvas(self.parent, background ='white',
width=200,height=300)
self.graph.create_rectangle(0,20,40,50)
self.graph.pack(side=tk.TOP,fill=tk.X)
# needs to get a frame because side by side with Ml stats
def createGameStats(self,GameFrame):
Frame1 = tk.Frame(GameFrame, bg='red', width=300)
tk.Label(Frame1, text="Status: ").pack(side=tk.LEFT, expand=tk.YES)
#initialize with certain value for now
self.statusChange = tk.Label(Frame1,
text="Learning").pack(side=tk.LEFT, expand=tk.YES)
Frame2=tk.Frame(GameFrame, bg='green')
tk.Label(Frame2, text="Fitness").pack(side=tk.LEFT, expand=tk.YES,
anchor=tk.W) #could get changed
self.fitnessChange = tk.Label(Frame2, text="6").pack(side=tk.LEFT,
expand=tk.YES)
Frame1.pack(side=tk.TOP, anchor=tk.W)
Frame2.pack(side=tk.TOP,fill=tk.X, anchor=tk.W)
def createMlStats(self, MlFrame):
Frame1 = tk.Frame(MlFrame)
tk.Label(Frame1, text="Status: ").pack(side=tk.LEFT, expand=tk.YES)
# initialize with certain value for now
self.statusChange = tk.Label(Frame1,
text="Learning").pack(side=tk.LEFT, fill=tk.X, expand=tk.YES)
Frame2=tk.Frame(MlFrame)
tk.Label(Frame2, text="Fitness").pack(expand=tk.YES, anchor=tk.W)
self.fitnessChange = tk.Label(Frame2, text="6").pack(side=tk.LEFT,
fill=tk.X, expand=tk.YES)
Frame1.pack(side=tk.TOP, fill=tk.BOTH,anchor=tk.W)
Frame2.pack(side=tk.TOP,fill=tk.BOTH)
def main():
root = tk.Tk()
root.geometry('600x900-0+0') #120* 50 ppixels in top right corner of desktop
app = Application(root)
app.master.title('Sample application')
app.mainloop()
if __name__ == '__main__':
main()
The .grid layout manager is much more flexible:
class Application(Frame):
def __init__(self, parent):
self.parent = parent
Frame.__init__(self, parent)
self.create_widgets()
def create_widgets(self):
graph = Canvas(self.parent, width=200, height=300, bg="white")
graph.create_rectangle(0, 20, 40, 50)
mlgamestat_frame = Frame(self.parent, width=600, height=300, bg="blue")
gamestat_frame = Frame(mlgamestat_frame, width=300, height=300, bg="yellow")
ml_frame = Frame(mlgamestat_frame, width=300, height=300, bg="green")
graph.grid(row=0, column=0)
mlgamestat_frame.grid(row=1, column=0)
gamestat_frame.grid(row=0, column=0)
ml_frame.grid(row=0, column=1)
You can place widgets as if they were in a table. The code above makes your intended layout.
Related
After several tests and help received, I managed to get the code for a scroll with several checkboxes inside. My current problem is that the scroll is much larger than the space it needs and in general I can't change its size to my liking.
This is my code:
import tkinter as tk
class CheckboxList(tk.Frame):
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.checkbuttons = []
self.vars = []
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
self.yscroll = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.frame, anchor='nw')
self.canvas.configure(yscrollcommand=self.yscroll.set)
self.canvas.grid(row=0,column=0, sticky='nsew')
self.yscroll.grid(row=0, column=1, sticky='nse')
for i in range(20):
var = tk.IntVar(value=0)
cb = tk.Checkbutton(self.frame, text=f"checkbutton #{i}", variable=var, onvalue=1, offvalue=0)
cb.grid(row=i, column=0, sticky='w')
self.checkbuttons.append(cb)
self.vars.append(var)
self.frame.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox("all"))
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
root = tk.Tk()
cl = CheckboxList(root, width=20, height=10)
cl.grid(row=0,column=0,sticky='nsew')
root.mainloop()
I have made several tests by changing the values of "grid" but I can not. I wish I could have more control over the size.
As you can see from the image, there is a lot of white space left and I would like to be able to change the overall height as well
EDIT:
Working code:
class CheckboxList(tk.Frame):
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.checkbuttons = []
self.vars = []
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
#self.canvas = tk.Canvas(self, bg='white', bd=0,width=115, highlightthickness=0)
self.yscroll = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.frame, anchor='nw')
self.canvas.configure(yscrollcommand=self.yscroll.set)
self.canvas.grid(row=0,column=0, sticky='nsew')
self.yscroll.grid(row=0, column=1, sticky='nse')
self.frame.bind("<Configure>", lambda e: self.canvas.config(width=e.width, scrollregion=self.canvas.bbox("all")))
for i in range(20):
var = tk.IntVar(value=0)
cb = tk.Checkbutton(self.frame, text=f"checkbutton #{i}", variable=var, onvalue=1, offvalue=0)
cb.grid(row=i, column=0, sticky='w')
self.checkbuttons.append(cb)
self.vars.append(var)
self.frame.update_idletasks()
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
Add width in line 8.
Change this:
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
to:
self.canvas = tk.Canvas(self, bg='white', bd=0,width=115, highlightthickness=0)
Screenshot:
You can set the width of self.canvas same as self.frame whenever self.frame is resized via callback of event <Configure> on self.frame. Note also that you need to update scrollregion of the canvas when the size of self.frame is changed.
...
self.frame = tk.Frame(self.canvas)
self.frame.bind("<Configure>", lambda e: self.canvas.config(width=e.width, scrollregion=self.canvas.bbox("all")))
...
# scrollregion will be updated via the above event binding on self.frame, so below line is not necessary
#self.canvas.config(scrollregion=self.canvas.bbox("all"))
...
I am intended to make one class use another class as widget, for example: a Label A, within this will be Label B, but apparently no object can be used as an attribute
AttributeError: 'Label_A' object has no attribute 'parent'
code that I am trying to use:
import tkinter as tk
win = tk.Tk()
win.title("Test")
win.geometry("640x480")
class Label_A(tk.Label):
def __init__(self, parent):
# make a Label
def label_a():
self.label_a = tk.Label(self.parent, text="LABEL A", bg="red", fg="white", font=("Arial", 20), width=300, height=300)
self.label_a.place(x=0, y=0)
label_a()
class Label_B(tk.Label):
def __init__(self, parent):
# make a Label
def label_b():
self.label_b = tk.Label(self.parent, text="LABEL B", bg="blue", fg="white", font=("Arial", 20), width=200, height=200)
self.label_b.place(x=0, y=0)
label_b()
Label_B(Label_A(win))
win.mainloop()
Try to use derived class of tkinter.Frame, so it can be frame in frame.
import tkinter as tk
class Frame_A(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.place(x=0, y=0, width=300, height=200)
self.label_a = self.label()
def label(self):
label = tk.Label(self, text="LABEL A", bg="red", fg="white", font=("Arial", 20))
label.place(x=0, y=0, width=300, height=200)
return label
class Frame_B(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.place(x=0, y=0, width=200, height=100)
self.label_b = self.label()
def label(self):
label = tk.Label(self, text="LABEL B", bg="blue", fg="white", font=("Arial", 20))
label.place(x=0, y=0, width=200, height=100)
return label
win = tk.Tk()
win.title("Test")
win.geometry("400x300")
Frame_B(Frame_A(win))
win.mainloop()
import tkinter as tk
from tkinter import *
from tkinter import ttk
LARGE_FONT = ("Verdana", 12)
class pages(tk.Tk):
#starts us off in the login page
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "ScanNET")
tk.Tk.wm_minsize(self, 800, 800)
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 (loginpage, GUI):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky=N+E+S+W)
self.show_frame(loginpage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class loginpage(tk.Frame):
#login page content
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
loginlabel = tk.Label(self, text="login page", font=LARGE_FONT)
loginlabel.pack(padx=10, pady=10)
#button moves you to gui
loginbutton1 = tk.Button(self, text= "Go to GUI", command=lambda: controller.show_frame(GUI))
loginbutton1.pack()
class GUI(tk.Frame):
def __init__(self, parent, controller):
#all widths and heights aren't official, most likely change
tk.Frame.__init__(self, parent)
self.root = tk.Tk()
#the tabs
my_notebook = ttk.Notebook(self.root)
my_notebook.pack()
devicestab = Frame(my_notebook, width=800, height=600)
reportstab = Frame(my_notebook, width=800, height=600)
devicestab.pack(fill=BOTH, expand=1)
reportstab.pack(fill=BOTH, expand=1)
my_notebook.add(devicestab, text="Devices")
my_notebook.add(reportstab, text="Reports")
#contents for devices tab
devicesleft = LabelFrame(devicestab, text="Devices found: ", padx=5, pady=5, width=500, height=600)
devicesleft.grid(row=0, column=0)
devicesright = LabelFrame(devicestab, text="Activity Feed: ", padx=5, pady=5, width=300 , height=600)
devicesright.grid(row=0, column=1)
#contents for reports tab
reportsleft = LabelFrame(reportstab, text="Report Summaries: ", padx=5, pady=5, width=400 , height=600)
reportsleft.grid(row=0, column=0)
reportsright= LabelFrame(reportstab, text="Charts and Diagrams: ", padx=5, pady=5, width=400 , height=600)
reportsright.grid(row=0, column=1)
app = pages()
app.mainloop()
When I run this, both the loginpage and GUI windows open. Correct me if I'm wrong, but I think the problem is probably around the
tk.Frame.__init__(self, parent)
self.root = tk.Tk()
my_notebook = ttk.Notebook(self.root)
part in the GUI class. I've searched everywhere and I can't seem to find a way to have a first page as a login page which will move to a second page that has tabs using notebook. I feel as if something else has to be in the ttk.Notebook() part, and perhaps remove self.root = tk.Tk() after. I'd love to hear what y'all think.
I am assuming you want the notebook in the same widget of the rest, so you should not use tk.Tk() and then you place the notebook in the parent which is already your root. Check the code in the end of my answer. Also, since there was a lot of problems with your code I made some changes and comments that will help you to write better codes in tkinter. Please read it carefully. You may also want to study the effbot web page.
import tkinter as tk
# from tkinter import * # just don't do this
from tkinter import ttk
LARGE_FONT = ("Verdana", 12)
# class pages(tk.Tk):
class Pages(tk.Tk): # class names should start with upper case
#starts us off in the login page
# def __init__(self, *args, **kwargs):
def __init__(self):
# tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.__init__(self)
# tk.Tk.wm_title(self, "ScanNET")
self.winfo_toplevel().title("ScanNET")
# tk.Tk.wm_minsize(self, 800, 800)
self.wm_minsize(800, 800) # since you defined tk.Tk as pages parent you can call Tk methods directly
container = tk.Frame(self)
# container.pack(side=TOP, fill=BOTH, expand=True)
# container.grid_rowconfigure(0, weight=1)
# container.grid_columnconfigure(0, weight=1)
container.grid(row=0, column = 0) # don't use pack if you want to use grid
self.frames = {}
for F in (loginpage, GUI):
frame = F(container, self)
self.frames[F] = frame
# frame.grid(row=0, column=0, sticky=N+E+S+W)
frame.grid(row=0, column=0, sticky='NESW') #since we are not importing all we are not importing tk.W but you can use string instead
self.show_frame(loginpage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class loginpage(tk.Frame):
#login page content
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
loginlabel = tk.Label(self, text="login page", font=LARGE_FONT)
loginlabel.pack(padx=10, pady=10)
#button moves you to gui
loginbutton1 = tk.Button(self, text= "Go to GUI", command=lambda: controller.show_frame(GUI))
loginbutton1.pack()
class GUI(tk.Frame):
def __init__(self, parent, controller):
#all widths and heights aren't official, most likely change
tk.Frame.__init__(self, parent)
# self.root = tk.Tk() # don't create new Tk objects, you just need one. The others should be Toplevel objects
### self.root = tk.Toplevel() ### this would be the correct way of creating a new window but you don't want to do that here your root is your parent
#the tabs
# my_notebook = ttk.Notebook(self.root)
my_notebook = ttk.Notebook(self) # this is how you place the notebook in the Frame widget and not in a new one
# my_notebook.pack()
my_notebook.grid() # we are now using grid so it will not accept pack anymore
# devicestab = Frame(my_notebook, width=800, height=600)
devicestab = tk.Frame(my_notebook, width=800, height=600) # again, since we are not importing al we have to use tk. before tkinter methods
# reportstab = Frame(my_notebook, width=800, height=600)
reportstab = tk.Frame(my_notebook, width=800, height=600)
# devicestab.pack(fill=BOTH, expand=1)
devicestab.pack(fill="both", expand=1) # instead of tk.BOTH we can use "both"
reportstab.pack(fill="both", expand=1)
my_notebook.add(devicestab, text="Devices")
my_notebook.add(reportstab, text="Reports")
#contents for devices tab
devicesleft = tk.LabelFrame(devicestab, text="Devices found: ", padx=5, pady=5, width=500, height=600)
devicesleft.grid(row=0, column=0)
devicesright = tk.LabelFrame(devicestab, text="Activity Feed: ", padx=5, pady=5, width=300 , height=600)
devicesright.grid(row=0, column=1)
#contents for reports tab
reportsleft = tk.LabelFrame(reportstab, text="Report Summaries: ", padx=5, pady=5, width=400 , height=600)
reportsleft.grid(row=0, column=0)
reportsright= tk.LabelFrame(reportstab, text="Charts and Diagrams: ", padx=5, pady=5, width=400 , height=600)
reportsright.grid(row=0, column=1)
app = Pages()
app.mainloop()
I am trying to change the Label's text, but I have no idea how to access it. I have added the label and a button and I want to change the label's text on button click. However, I cannot access the label from the function - see the line with the question marks. What should I change it to?
When I try the code as is I get "Example instance has no attribute 'frame2'"
I am using Python 2.7
============== update =========
changed frame2 to self.frame2, but it did not solve the problem
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.all = []
self.path = ""
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("SomeName")
self.style = Style()
self.style.theme_use("default")
self.frame2 = Frame(self, relief=FLAT, borderwidth=2)
self.frame2.pack(side=TOP, fill=BOTH, expand=False)
# this is my label
usrLable = Label(self.frame2, text="Username: ")
usrLable.pack(side=LEFT, padx=5, pady=1)
frame6 = Frame(self, relief=FLAT, borderwidth=2)
frame6.pack(fill=BOTH, expand=True)
# this is my button
stopButton = Button(frame6, text="Stop", command=self.stopButtonClick)
stopButton.pack(side=LEFT)
def stopButtonClick(self):
try:
self.frame2.usrLable.configure(text="hello") # ?????????????
except Exception,e:
print str(e)
return
You need to replace the following line:
frame2 = Frame(self, relief=FLAT, borderwidth=2)
frame2.pack(side=TOP, fill=BOTH, expand=False)
with:
self.frame2 = Frame(self, relief=FLAT, borderwidth=2)
self.frame2.pack(side=TOP, fill=BOTH, expand=False)
to make frame2 an instance attribute instead of local variable.
And also change the references to frame2 to self.frame2 accordingly.
Same for the usrLable.
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.all = []
self.path = ""
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("SomeName")
self.style = Style()
self.style.theme_use("default")
self.frame2 = Frame(self, relief=FLAT, borderwidth=2)
self.frame2.pack(side=TOP, fill=BOTH, expand=False)
self.usrLable = Label(self.frame2, text="Username: ")
self.usrLable.pack(side=LEFT, padx=5, pady=1)
self.frame6 = Frame(self, relief=FLAT, borderwidth=2)
self.frame6.pack(fill=BOTH, expand=True)
stopButton = Button(self.frame6, text="Stop", command=self.stopButtonClick)
stopButton.pack(side=LEFT)
def stopButtonClick(self):
self.usrLable.configure(text="hello")
I'm added canvas and a scroll bar to one of the frames in my script.
However somethings wrong cause the scroll bar is off (lower bottom is not visible) and the text I drew is off. Could anyone please tell me whats the problem ? I want the canvas to fill the whole frame (obviously without the scroll bar)
import sys
import os
if sys.version_info[0] < 3:
import Tkinter as tk
import ttk as ttk
else:
import tkinter as tk
import tkinter.ttk as ttk
#
# LeftMiddle
#
class LeftMiddle(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
self.draw_text()
def __create_layout(self):
self.canvas = tk.Canvas(self, bg="green", relief=tk.SUNKEN)
self.canvas.config(width=20, height=10)
self.canvas.config(highlightthickness=0)
self.sbar = tk.Scrollbar(self, orient=tk.VERTICAL)
self.sbar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, expand="YES", fill=tk.BOTH)
def draw_text(self):
self.canvas.create_text(0, 0, text='1234567890', fill='red')
self.canvas.create_text(0, 25, text='ABCDEFGH', fill='blue')
#
# MainWindow
#
class MainWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
def __create_layout(self):
self.frame1 = tk.Frame(self, bg="yellow")
self.frame2 = tk.Frame(self, bg="blue")
self.frame3 = LeftMiddle(self) # tk.Frame(self, bg="green")
self.frame4 = tk.Frame(self, bg="brown")
self.frame5 = tk.Frame(self, bg="pink")
self.frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))
for r in range(6):
self.rowconfigure(r, weight=1)
for c in range(10):
self.columnconfigure(c, weight=1)
#
# MAIN
#
def main():
root = tk.Tk()
root.title("Frames")
root.geometry("550x300+525+300")
root.configure(background="#808080")
root.option_add("*font", ("Courier New", 9, "normal"))
window = MainWindow(master=root)
window.pack(side="top", fill="both", expand=True)
root.mainloop()
if __name__ == '__main__':
main()
You have overlapping frames. Both self.frame3 and self.frame4 are in row 4 with a rowspan of 2, meaning they occupy rows 4 and 5. self.frame5 is also in row 5. So, self.frame5 is obscuring the bottom half of self.frame3, the frame that contains the canvas.
I don't understand why you have so many rowspans, they seem completely unnecessary unless you have some specific reason why you want multiple rows and columns but only single frames that span these rows and columns. Looking at the screenshot I see the need for only three rows.
The reason the text seems off is that by default the text is centered over the coordinate you give. You might want to look at the anchor option for the create_text method.