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()
Related
Python beginner. I placed a scrollbar widget in window and that works, but no matter what I do I can't get the scrollbox widget to change size. Could go with a larger scrollbox or for it to resize when the window resizes, but can't figure out how to force either to happen. Tried lots of different solutions, but feels like the grid and canvas are defaulting to a size and can't figure out how to change that. Help would be appreciated. Code is below:
import tkinter as tk
from tkinter import ttk
import os
import subprocess
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=True)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
root = tk.Tk()
root.title("application")
root.geometry('750x800')
dbEnvs = ['a','b']
x = 1
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack(side = "top")
body.pack()
footer.pack(side = "top")
#setup Environment selection
envLabel = tk.Label(header, text="Environment:")
envLabel.grid(row=0,column=0,sticky='nw')
dbselection = tk.StringVar()
scrollable_body = Scrollable(body, width=20)
x = 1
for row in range(50):
checkboxVar = tk.IntVar()
checkbox = ttk.Checkbutton(scrollable_body, text=row, variable=checkboxVar)
checkbox.var = checkboxVar # SAVE VARIABLE
checkbox.grid(row=x, column=1, sticky='w')
x += 1
scrollable_body.update()
#setup buttons on the bottom
pullBtn = tk.Button(footer, text='Pull')
pullBtn.grid(row=x, column=2, sticky='ew')
buildBtn = tk.Button(footer, text='Build')
buildBtn.grid(row=x, column=3, sticky='ew')
compBtn = tk.Button(footer, text='Compare')
compBtn.grid(row=x, column=4, sticky='ew')
root.mainloop()
have tried anchoring, changing the window base size and multiple other things (8 or 19 different items, plus reading lots of posts), but they normally involve packing and since I used grids that normally and ends with more frustration and nothing changed.
If you want the whole scrollbox to expand to fill the body frame, you must instruct pack to do that using the expand and fill options:
body.pack(side="top", fill="both", expand=True)
Another problem is that you're setting expand to True for the scrollbar. That's probably not something you want to do since it means the skinny scrollbar will be allocated more space than is needed. So, remove that attribute or set it to False.
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
tip: when debugging layout problems, the problems are easier to visualize when you temporarily give each widget a unique color. For example, set the canvas to one color, body to another, the instance of Scrollable to another, etc. This will let you see which parts are visible, which are growing or shrinking, which are inside the borders of others, etc.
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 pretty new to Tkinter and I build a little window with different widgets.
My Code looks like this:
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.master.geometry("800x600")
self.master.title("Tkinter Sandbox")
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
self._create_left_frame()
self._create_button_bar()
self._create_label_frame()
def _create_left_frame(self):
frame = tk.Frame(self.master, bg="red")
tree_view = ttk.Treeview(frame)
tree_view.column("#0", stretch=tk.NO)
tree_view.heading("#0", text="Treeview")
tree_view.pack(fill=tk.Y, expand=1)
frame.grid(row=0, column=0, rowspan=2, sticky=tk.N + tk.S)
def _create_button_bar(self):
frame = tk.Frame(self.master, bg="blue")
button_run_single = tk.Button(frame, text="Button 1")
button_run_all = tk.Button(frame, text="Button 2")
button_details = tk.Button(frame, text="Button 3")
button_run_single.grid(row=0, column=0)
button_run_all.grid(row=0, column=1, padx=(35, 35))
button_details.grid(row=0, column=2)
frame.grid(row=0, column=1, sticky=tk.N)
def _create_label_frame(self):
frame = tk.Frame(self.master, bg="blue")
name_label = tk.Label(frame, text="Label 1")
performance_label = tk.Label(frame, text="Label 2")
name_entry = tk.Entry(frame)
performance_entry = tk.Entry(frame)
name_label.grid(row=0, column=0)
name_entry.grid(row=0, column=1)
performance_label.grid(row=1, column=0)
performance_entry.grid(row=1, column=1)
frame.grid(row=1, column=1)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
app.mainloop()
Between the three buttons and the label + entry frame is a huge space. I want the button and label + entry frame right under each other, without the huge space but the treeview should also expand vertically over the whole application window.
I think the problem might be my row and column configuration but I don't know how to solve this problem.
The way you've structured your code makes it hard to see the problem. As a good general rule of thumb, all calls to grid or pack for widgets within a single parent should be in one place. Otherwise, you create dependencies between functions that are hard to see and understand.
I recommend having each of your helper functions return the frame rather than calling grid on the frame. That way you give control to Application.__init__ for the layout of the main sections of the window.
For example:
left_frame = self._create_left_frame()
button_bar = self._create_button_bar()
label_frame = self._create_label_frame()
left_frame.pack(side="left", fill="y")
button_bar.pack(side="top", fill="x")
label_frame.pack(side="top", fill="both", expand=True)
I used pack here because it requires less code than grid for this type of layout. However, if you choose to switch to grid, or wish to add more widgets to the root window later, you only have to modify this one function rather than modify the grid calls in multiple functions.
Note: this requires that your functions each do return frame to pass the frame back to the __init__ method. You also need to remove frame.grid from each of your helper functions.
With just that simple change you end up with the button bar and label/entry combinations at the top of the section on the right. In the following screenshot I changed the background of the button_bar to green so you can see that it fills the top of the right side of the UI.
You need to change line
self.master.grid_rowconfigure(0, weight=1)
to
self.master.grid_rowconfigure(1, weight=1)
so that the second row takes all the space. Then you need to stick widgets from the label frame to its top by adding sticky parameter to the grid call in _create_label_frame:
frame.grid(row=1, column=1, sticky=tk.N)
I prefer to use the Pack Function since it gives a more open window - its easy to configure. When you use Pack() you can use labels with no text and just spaces to create a spacer, by doing this you won't run into the problem your facing.
First of all, I'm sorry for my English and lack of knowledge about tkinter. I have been trying to fix this this whole week, unsuccessfully.
So I am making my first GUI app and I have chosen tkinter to do this job. Right now I am completing my program's feature that shows all records from database (SQLite) and I am feeding these records into labels. The List is already scrollable and stuff, but my problem is that items inside frame in canvas are not responsive so they kinda underflow my frame.
I have already tried experimenting with weights, but nothing has changed the situation for better. I have also tried to reconfigure the frame to width of canvas, but it hasn't changed so I suppose that the canvas by itself "overflows" the root window. So I have tried giving it fixed width of parent by Configure but that just made thing worse.
class List(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
createBackground(self)
def populate(object, list): # function to feed text from database to the grid
iterate = 0
for row in list:
count = 0
for element in row:
tk.Label(object, text=element,borderwidth=1, relief="solid", bg="yellow", fg="black", width=20).grid(row=iterate,column=count,sticky="NWES",ipady=8)
count += 1
iterate +=+ 1
def onFrameConfigure(event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
self.basic_frame = tk.Frame(self, bg="white")
self.basic_frame.grid(row=1,column=1, padx=30, pady=30, sticky="NWES")
# shanenigens with tkinter so I could scroll down
self.canvas = tk.Canvas(self.basic_frame, background="red", borderwidth=0)
self.frame = tk.Frame(self.canvas, background="blue")
self.scrollbar = tk.Scrollbar(self.basic_frame, orient=tk.VERTICAL, bg="white")
self.scrollbar.config(command=self.canvas.yview)
self.canvas.config(yscrollcommand=self.scrollbar.set, scrollregion=self.canvas.bbox(tk.ALL))
self.canvas.grid(row=1,column=0, columnspan=9, sticky="NWES")
self.scrollbar.grid(row=0,column=10, rowspan=2,sticky="NWES")
self.grid_rowconfigure(1,weight=1)
self.grid_columnconfigure(1,weight=1)
self.basic_frame.grid_rowconfigure(1,weight=1)
self.frame.grid_rowconfigure(1,weight=1)
self.frame.grid_columnconfigure(1,weight=1)
for x in range(9): # setting grid weights
self.basic_frame.grid_columnconfigure(x,weight=1)
self.canvas.grid_columnconfigure(x,weight=1)
label_array = ["Name","Author","Pages","Genre","ISBN","Language","Read","Owned","Added"] # making labels in List
for element in label_array:
label_name = tk.Label(self.basic_frame, text=element, bg="#0AB300",fg="black",borderwidth=1, relief="solid",width=20)
label_name.grid(row=0, column=label_array.index(element),sticky="NWES",ipady=8)
self.canvas.create_window((0,0), window=self.frame,anchor="nw",tags="feed_list")
self.frame.bind("<Configure>", onFrameConfigure)
books_list = select_all() # getting every element from database
populate(self.frame, books_list)
Here is a link for screenshot:
https://ctrlv.cz/qX7N
I'm quite new to python and tkinter so try to keep the answers simple please!
I've got the hang of setting up the window and adding in buttons, labels and photos etc, but I've noticed that whenever I use .pack() it just adds it to the middle of the window underneath the last thing I packed.
I was wondering if there is a way to pack the items in the same row as the last item I packed.
Thanks!
pack has options that let you control which side of the containing widget to use, the padding, and other things. For example, to pack widgets from left to right you would use widget.pack(side='left'). All of these options are well documented in many places.
Here's a quick example showing a row of buttons across the top, and a column of labels on the right:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.toolbar = tk.Frame(self, borderwidth=1, relief="raised")
self.labels = tk.Frame(self, borderwidth=1, relief="raised")
self.text = tk.Text(self, width=80, height=20)
self.toolbar.pack(side="top", fill="x")
self.labels.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
for i in range(5):
button = tk.Button(self.toolbar, text="Button %d" %i)
button.pack(side="left")
for i in range(5):
label = tk.Label(self.labels, text="Label %d" %i)
label.pack(side="top", fill="x", padx=8)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
A nice tutorial of pack, including a list of all of the options, is here: http://effbot.org/tkinterbook/pack.htm
A complete description of how the pack algorithm works is here: http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26