I'm facing some trouble aligning tkinter widgets when located in different frames, as shown
I have 3 frames: main_frame - colored in blue, containing 3 subframes: buttons_frame,timers_frame,switches_frame, which all centered to main_frame, one on top of other.
Design requires: all widgets in all subframes will be centered inside its frame. As shown in attached pic, and code - middle sub-frame, timers_frame is streched OK to max size using tk.E+tk.w, BUT widgets inside positioned at row=0, column=0 and not aligning to center of streched frame. Using tk.E+tk.W didn't solve it.
Solving it without using sub-frames, works OK.
Relevant portion of code:
class CoreButton(ttk.Frame):
def __init__(self, master, nickname='CoreBut.inc', hw_in=[], hw_out=[], ip_in='', \
ip_out='', sched_vector=[], num_buts=1):
ttk.Frame.__init__(self, master)
# Styles
self.style = ttk.Style()
self.style.configure("Azure.TFrame", background='azure4')
self.style.configure("Blue.TFrame", background='blue')
self.style.configure("Blue2.TFrame", background='light steel blue')
self.style.configure("Red.TButton", foreground='red')
# Frames
# Buttons&Indicators
py, px = 4, 4
self.main_frame = ttk.Frame(self, style="Blue2.TFrame", relief=tk.RIDGE)
self.main_frame.grid()
self.buttons_frame = ttk.Frame(self.main_frame, relief=tk.RIDGE, style="Azure.TFrame")
self.buttons_frame.grid(row=0, column=0, pady=py, padx=px)
# Counters
self.timers_frame = ttk.Frame(self.main_frame, relief=tk.RIDGE, border=5, style="Azure.TFrame")
self.timers_frame.grid(row=1, column=0, pady=py, padx=px, sticky=tk.E+tk.W)
# Extra GUI
self.switches_frame = ttk.Frame(self.main_frame, relief=tk.RIDGE, border=5, style="Azure.TFrame")
self.switches_frame.grid(row=2, column=0, pady=py)
# Run Gui
# self.build_gui()
self.extras_gui()
def extras_gui(self):
ck1 = tk.Checkbutton(self.switches_frame, text='On/Off', variable=self.on_off_var, \
indicatoron=1, command=self.disable_but)
ck1.grid(row=0, column=0)
ck2 = tk.Checkbutton(self.switches_frame, text='Schedule', variable=self.enable_disable_sched_var, \
indicatoron=1,
command=lambda: self.disable_sched_task(s=self.enable_disable_sched_var.get()))
ck2.grid(row=1, column=0)
You have to give the column your timers are in weight!
self.timers_frame.columnconfigure(0, weight=1)
Otherwise the column has exactly the width of your timers and not the width of your timers_frame.
Related
I am trying to get my scrollable canvas to work. It works when I pack the elements using .pack, however when I insert the elements via .place, the scrollbar stops working. Here is a minimal reproducable example of my code.
startup.py file:
import frame as f
import placeWidgetsOnFrame as p
p.populate3()
f.window.mainloop()
frame.py file:
#Creates widnow
window = customtkinter.CTk()
window.geometry("1900x980")
customtkinter.set_appearance_mode("dark")
window.resizable(False, False)
#Creates Frame for GUI
mainFrame = customtkinter.CTkFrame(window, width=1900, height=980, corner_radius=0)
mainFrame.pack(expand=True, fill=tk.BOTH)
mainFrame.pack_propagate(False)
topFrame = customtkinter.CTkFrame(master=mainFrame, width=1865, height=140, corner_radius=10)
topFrame.grid(columnspan=2, padx=15, pady=15)
topFrame.pack_propagate(0)
leftFrame = customtkinter.CTkFrame(master=mainFrame, width=380, height=530, corner_radius=10)
leftFrame.grid(row=1, column=0, padx=15, pady=10)
leftFrame.pack_propagate(False)
rightFrame = customtkinter.CTkFrame(master=mainFrame, width=1450, height=775, corner_radius=10)
rightFrame.grid(row=1, column=1, padx=15, pady=10, rowspan=2)
rightFrame.pack_propagate(False)
bottomLeftFrame = customtkinter.CTkFrame(mainFrame, width=380, height=220, corner_radius=10)
bottomLeftFrame.grid(row=2, column=0, padx=15, pady=10)
bottomLeftFrame.pack_propagate(False)
#Creates Scrollbar for right Frame
#Creates a canvas for the right Frame
canvas2=tk.Canvas(rightFrame, bg="#000000", highlightthickness=0, relief="flat")
canvas2.pack(side="left", fill="both", expand=True)
#Creates a scroll bar for the right Frame
scrollbar = customtkinter.CTkScrollbar(master=rightFrame, orientation="vertical", command=canvas2.yview, corner_radius=10)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
#Configures scrollbar to canvas
canvas2.configure(yscrollcommand=scrollbar.set)
canvas2.bind("<Configure>", lambda *args, **kwargs: canvas2.configure(scrollregion=canvas2.bbox("all")))
#Creates a scrollable frame to place widgets on
scrollableFrame = customtkinter.CTkFrame(canvas2, fg_color=("#C0C2C5", "#343638"), corner_radius=10)
canvasFrame = canvas2.create_window((0,0), window=scrollableFrame, anchor="nw", tags=("cf"))
#TO DO - resize canvas and to fit all widgets
def handleResize(event):
c = event.widget
cFrame = c.nametowidget(c.itemcget("cf", "window"))
minWidth = cFrame.winfo_reqwidth()
minHeight = cFrame.winfo_reqheight()
print (event.width)
print (event.height)
if minWidth < event.width:
c.itemconfigure("cf", width=event.width)
if minHeight < event.height:
c.itemconfigure("cf", height=event.height)
print (event.width)
print (event.height)
c.configure(scrollregion=c.bbox("all"))
canvas2.bind('<Configure>', handleResize)
def onMousewheel(event):
canvas2.yview_scroll(-1 * round(event.delta / 120), "units")
canvas2.bind_all("<MouseWheel>", onMousewheel)
canvas2.bind("<Destroy>", lambda *args, **kwargs: canvas2.unbind_all("<MouseWheel>"))
placeWidgetsOnFrame.py file:
import tkinter
import customtkinter
import frame as f
rightFrame = f.scrollableFrame
def populate2():
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.pack(padx=10, pady=10)
def populate3():
x=50
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.place(x=40, y=x)
x=x+50
Here is the output when populate3() is run:
Here
Here is the output when populate2() is run
Here
Does anyone know why this is? I can always go back and change the way I insert widgets to .pack rather than .place, however I would rather use .place as I find it easier to place widgets where I want to.
The reason is because pack by default will cause the containing frame to grow or shrink to fit all of the child widgets, but place does not. If your frame starts out as 1x1 and you use place to add widgets to it, the size will remain 1x1. When you use place, it is your responsibility to make the containing widget large enough to contain its children.
This single feature is one of the most compelling reasons to choose grid or pack over place - these other geometry managers do a lot of work for you so that you can think about the layout logically without getting bogged down in the details of the layout.
I'm tring to build a scrollable GUI window containing ttk.Entry and ttk.Label.
The only way doing so (as noted in many questions here )- is by creating a Canvas, with a frame that contains all that widgets.
So- my goal is to make such class- that gets as a parameter a frame containing all needed widgets, and display it in a window with horizontal and vertical scroll bars ( since I need it in many displays inside my code ).
After coding successfully - I tried to make a class, but it shows only empty green canvas.
Any ideas what am I doing wrong?
import tkinter as tk
from tkinter import ttk
class CanvasWidgets(ttk.Frame):
def __init__(self, master, frame_in, width=100, height=100):
ttk.Frame.__init__(self, master)
self.master = master
self.frame = frame_in
self.width, self.height = width, height
self.build_gui()
def build_gui(self):
self.canvas = tk.Canvas(self.master, self.width, self.height, bg='light green')
# self.frame = ttk.Frame(self.frame_in)
self.frame.bind("<Configure>", self.onFrameConfigure)
self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.canvas.yview)
self.hsb = tk.Scrollbar(self.frame, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=self.vsb.set, xscrollcommand=self.hsb.set)
self.vsb.grid(row=0, column=1, sticky=tk.N + tk.S + tk.W)
self.hsb.grid(row=1, column=0, sticky=tk.W + tk.N + tk.E)
self.canvas.create_window((4, 4), window=self.frame, anchor="nw")
self.canvas.grid(row=0, column=0)
def onFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root = tk.Tk()
frame = ttk.Frame(root)
rows, cols = 5, 5
for row in range(rows):
for col in range(cols):
ttk.Label(frame, text=[row, col], relief=tk.SUNKEN, width=5).grid(row=row, column=col, sticky=tk.E)
a = CanvasWidgets(root, frame)
a.grid()
root.mainloop()
The first problem is that you are placing the canvas in master when it needs to be in self. Think of the instance of CanvasWindow as a box in which you are going to put everything else.
The second problem is that, because the frame was created before the canvas, the frame has a lower stacking order than the canvas. You need to call lift on the frame to get it to be above the canvas.
The third problem is that you're putting the scrollbars in frame. You can't put them in the inner frame because they control the inner frame. Instead, they also need to be in self. Both the scrollbar and the canvas need to share a common parent.
The fourth problem is that the frame isn't a child of the canvas, so it won't be clipped by the borders of the canvas. It would be better if the CanvasWidgets created the frame, and then the caller can get the frame and add widgets to it.
For example:
a = CanvasWidgets(root)
rows, cols = 5, 5
for row in range(rows):
for col in range(cols):
label = ttk.Label(a.frame, text=[row, col], relief=tk.SUNKEN, width=5)
label.grid(row=row, column=col, sticky=tk.E)
I am creating a table using Labels laid out as a grid within a Frame. I'd like to embed this Frame in a scrollable canvas, and I'd like the Labels to expand to fill the horizontal space while maintaining vertical scrolling capability. To see what I am trying to do, here is my code:
class ScrollableTable(Frame):
def __init__(self, parent, rows=4, columns=5):
self.rows = rows
self.columns = columns
Frame.__init__(self, parent, borderwidth=0, background='green')
self.canvas = Canvas(self, borderwidth=0)
self.frame = Frame(self.canvas, borderwidth=0, background='black')
self.vsb = Scrollbar(parent, orient=VERTICAL, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.canvas.pack(side=LEFT, fill=X, expand=TRUE)
# This is where I fix the height I want this window to take up
self.canvas.configure(yscrollcommand=self.vsb.set, width=720, height=80)
self.height = self.canvas.winfo_reqheight()
self.width = self.canvas.winfo_reqwidth()
self._widgets = []
for row in range(rows):
current_row = []
for column in range(columns):
label = Label(self.frame, text="N/A",
borderwidth=0, width=5, anchor=W, justify=LEFT)
label.grid(row=row, column=column, sticky='NESW', padx=0.5, pady=0.5)
current_row.append(label)
self._widgets.append(current_row)
self.canvas.create_window((0,0), window=self.frame, anchor='w', tags=self.frame)
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.canvas.bind("<Configure>", self.onResize)
#self.frame.pack(side=LEFT, fill=X, expand=True)
self.vsb.pack(side=RIGHT, fill=Y, expand=True)
In the above code, the 2nd to last line is commented. When it is commented out, I get a table that doesn't expand to fill the space, but the scrollbar works. However, if I uncomment that lines and use the frame.pack approach, the table fills the horizontal space, but I lose scrolling capability. I'm having trouble wrapping my head around why I'm losing the ability to scroll - any pointers?
You need to not call pack on the frame, and because of that you'll need to manage the width of the frame manually. Typically you do this by binding to the <Configure> event on the canvas. In the bound function, get the width of the canvas, and then use the itemconfigure method to set the width of the window object.
I want to display 2 frames and one canvas such that:
The red frame must be on top of the window and spans over the canvas and the blue frame.
The blue frame and the canvas are on the bottom of the window. The canvas is on the right.
I am not getting the expected results. Here is my code:
from Tkinter import *
class Mine(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.frameh()
self.framev()
self.thecanvas()
# horizontal frame on top spanning over 2 columns
def frameh(self):
self.fh=Frame(root,width=400,height=40,bg="red")
self.fh.grid(sticky=N,row=0,columnspan=2)
# vertical frame on bottom
def framev(self):
self.fv=Frame(root,height=200,bg="blue")
self.fv.grid(sticky=S+W,row=1,column=0)
def thecanvas(self):
self.c=Canvas(root,width=500,height=200,bg="black")
self.c.grid(sticky=S+E,row=1,column=1)
if __name__=="__main__":
root=Tk()
root.wm_title("mine")
m=Mine(root)
root.mainloop()
I think you want to set columnspan=2 for frameh, so that it spans both the framev and thecanvas columns (plus extra sticky to make it fill the full width), and define a non-zero minimum width for framev:
...
def frameh(self):
self.fh=Frame(root, width=400, height=40, bg="red")
self.fh.grid(sticky=N+E+W, row=0, columnspan=2)
def framev(self):
self.fv=Frame(root, height=200, width=100, bg="blue")
self.fv.grid(sticky=S+W, row=1, column=0)
...
This gives me:
I've am new to Tkinter and have written a program to open a file and parse binary messages.
I am struggling on how best to display the results. My parsing class will have 300+ entries and I want something similar to a table.
var1Label : var1Val
var2Label : var2Val
I have played around with these widgets but have not gotten anything that I can be proud of: Label, Text, Message and probably others.
So I'd like the Labels to be justify Right, and the Var's to be justify left or anything else that would that would be a good idea on how to make this an attractive display, like having all the ':' aligned. The size of the Var's will be between 0-15 characters.
I'm using python 2.7.2 on windows.
Here's the grid method I was trying with dummy variables
self.lbVar1 = Label(self.pnDetails1, text="Var Desc:", justify=RIGHT, bd=1)
self.lbVar1.grid(sticky=N+W)
self.sVar1 = StringVar( value = self.binaryParseClass.Var1 )
self.Var1 = Label(self.pnDetails1, textvariable=self.sVar1)
self.Var1.grid(row=0, column=1, sticky=N+E)
The ttk.Treeview widget lets you create a list of objects with multiple columns. It will probably be the easiest thing for you to use.
Since you specifically asked about a grid of labels, here is a quick and dirty example showing how to create 300 items in a scrollable grid:
import Tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# create a canvas to act as a scrollable container for
# the widgets
self.container = tk.Canvas(self)
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.container.yview)
self.container.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.container.pack(side="left", fill="both", expand=True)
# the frame will contain the grid of labels and values
self.frame = tk.Frame(self)
self.container.create_window(0,0, anchor="nw", window=self.frame)
self.vars = []
for i in range(1,301):
self.vars.append(tk.StringVar(value="This is the value for item %s" % i))
label = tk.Label(self.frame, text="Item %s:" % i, width=12, anchor="e")
value = tk.Label(self.frame, textvariable=self.vars[-1], anchor="w")
label.grid(row=i, column=0, sticky="e")
value.grid(row=i, column=1, sticky="ew")
# have the second column expand to take any extra width
self.frame.grid_columnconfigure(1, weight=1)
# Let the display draw itself, the configure the scroll region
# so that the scrollbars are the proper height
self.update_idletasks()
self.container.configure(scrollregion=self.container.bbox("all"))
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()