Modifying treeview column width after creation - python

I want to use the same tree widget for different reports. To make this work I need to modify the header every time before inserting new data. But I can't get the widget to behave the way I want it to: whenever I change the header/width, an empty column will come out of nowhere. Is there something I can do to prevent that or I must destroy and recreate a new treeview everytime?
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
tree = ttk.Treeview(root, selectmode='browse')
tree.grid(row=0,column=0)
tree_header = ("One", "Two", "Three", "Four", "Five")
tree_width = (100, 100, 100, 100, 100)
tree["columns"] = tree_header
tree['show'] = 'headings'
for i in range(len(tree_header)):
tree.column(tree_header[i],width=tree_width[i], anchor="w", stretch = False)
tree.heading(tree_header[i], text=tree_header[i], anchor='w')
tree.insert("",tk.END,text="",value=(1,2,3,4,5))
def click_me():
tree.delete(*tree.get_children())
new_header = ("Six","Seven","Eight","Nine","Ten")
new_width = (120, 80, 120, 80, 100)
tree["columns"] = new_header
tree['show'] = 'headings'
for i in range(len(new_header)):
tree.column(new_header[i],width=new_width[i],anchor="w", stretch = False)
tree.heading(new_header[i],text=new_header[i],anchor="w")
a_button.config(command=click_me_again)
tree.insert("", tk.END, text="", value=(6, 7, 8, 9, 10))
def click_me_again():
tree.delete(*tree.get_children())
tree["columns"] = tree_header
tree['show'] = 'headings'
for i in range(len(tree_header)):
tree.column(tree_header[i],width=tree_width[i], anchor="w", stretch = False)
tree.heading(tree_header[i], text=tree_header[i], anchor='w')
a_button.config(command=click_me)
tree.insert("", tk.END, text="", value=(1, 2, 3, 4, 5))
a_button = tk.Button(root,text="Click me",command=click_me)
a_button.grid(row=1,column=0)
root.mainloop()

This example changes yours a little bit. I think it's a
benefit for the user if the Treeview expands with its
container.
But that change doesn't solve the problem.
I found that shrinking the column widths
still leaves the Treeview widget with the
original width, hence the white space.
Then if I resize the window using
the right border, and it passes in the right
direction over the last column right border, it
will catch the header and the headers and window
will resize together again...
What I do below is to set the container (root window)
width to the calculated width of all the columns,
after a width change. I account also
for the column separator pixels
(otherwise a window width resize will have to
catch again as described above):
You can still detach the last header from the
window border manually, opening whitespace,
But you can also force a minwidth. And rewriting
headers fixes it. I think you can also capture
that event and force the window width to follow,
if that matters.
I don't know it there is a simpler way to fix this
behaviour, and I don't know also if it is expected
or more like a bug. If I have a pre-determined number
of columns and I am on 'headings' not 'tree headings' why the
extra whitespace?
import tkinter as tk
from tkinter import ttk
def change_headers():
global headers
headers = (headers[0]+'A', headers[1]+'B', headers[2]+'C')
widths = (50, 50, 50)
tree['columns'] = headers
for i, header in enumerate(headers):
tree.heading(header, text=header)
tree.column(header, width=widths[i])
w = sum(width for width in widths) + len(widths)-1
h = root.winfo_reqheight()
root.wm_geometry('{}x{}'.format(w,h))
root.update()
root = tk.Tk()
tree = ttk.Treeview(root)
button = ttk.Button(root, text='Change Headers', command=change_headers)
tree.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
headers = ('A', 'B', 'C')
widths = (150, 150, 150)
tree['show'] = 'headings'
tree['columns'] = headers
for i, header in enumerate(headers):
tree.heading(header, text=header)
tree.column(header, width=widths[i])
root.mainloop()

Related

Making a rectangle border around text in Textbox python tkinter

I want to have a rectangle border around certain text that is added to the text box from the end and will be placed at the center.
For example:
Unfortunately, I can't find a way to do that, because I
don't know how to place texts at the center of the line in the text box, and don't know how to surround a text with a rectangle.
You can wrap a Label with border between a space and newline with justify='center' in a Text widget.
Below is an example:
import tkinter as tk
root = tk.Tk()
textbox = tk.Text(root, width=30, height=10)
textbox.pack()
textbox.tag_config('center', justify='center')
def center_label(textbox, **kwargs):
textbox.insert('end', ' ', 'center')
lbl = tk.Label(textbox, bd=3, relief='solid', **kwargs)
textbox.window_create('end', window=lbl)
textbox.insert('end', '\n\n')
center_label(textbox, text='hello', width=10, font='Arial 12 bold')
center_label(textbox, text='............', width=20)
textbox.insert('end', '\nhello\n')
root.mainloop()
Result:
Try putting the text box into it's own frame.
Some thing like this:
from Tkinter import *
root = Tk()
labelframe = LabelFrame(root, text="LabelFrame")
labelframe.pack()
text = Label(labelframe, text="Text inside labelframe")
text.pack()
root.mainloop()
You can add the border to the Entry using relief = "solid", centre the text with outline and you can use grid to align the widgets the way you want.
import tkinter as tk
root = tk.Tk()
root.geometry("400x200")
root.grid_columnconfigure(0, weight = 1)
ent1 = tk.Entry(root, relief = "solid", justify = "center")
ent1.insert(0, "hello")
ent1.grid(row = 0, column = 0, pady = 10)
ent2 = tk.Entry(root, relief = "solid", justify = "center")
ent2.insert(0, ".......")
ent2.grid(row = 1, column = 0, pady = 10)
lab1 = tk.Label(root, text = "hello")
lab1.grid(row = 2, column = 0, sticky = "w")
lab2 = tk.Label(root, text = "hello")
lab2.grid(row = 3, column = 0, sticky = "w")
root.mainloop()
Most of this is straightforward, the root.grid_columnconfigure line makes the grid take up the full width of the root window by giving the first column a weight of 1. The result is very similar to your example:
You could create an Entry widget in the textbox using text.window_create(). You could customize the border of the Entry widget, and you would be able to type text inside it. To make it look more like part of the textbox, you should register events so when the user presses Right and the caret is one character left of the Entry, give the Entry focus using focus_set. You could do the same with the Left.

How to make a floating Frame in tkinter

For my tkinter app I want to make a frame that would on top of other widgets, not taking any space (like html position fixed).
The Frame will have to contain widgets, if Frame is not possible labels or buttons will do.
I have no idea how to do it so haven't tried anything yet. Please help!
Here is a demonstration of place manager.
Remarks in the code explain behaviour.
import tkinter as tk
root = tk.Tk()
root.geometry("868x131")
button = tk.Button(root, text = "A Button")
button.place(x = 2, y = 2)
Frame = tk.LabelFrame(root, text = "A LabelFrame using place", bg="cyan")
# Frame using place with x, y, width, height in absolute coordinates
Frame.place(x = 250, y = 20, width = 600, height = 70)
ButtonInFrame = tk.Button(Frame, text = "A Button IN LabelFrame", bg="white")
# Note: place x,y is referenced to the container (Frame)
# Note: place uses just x,y and allows Button to determine width and height
ButtonInFrame.place(x = 2, y = 2)
Label = tk.Label(root, text = "A Label on LabelFrame\nWith multiple lines\nOf Text.", bg="light green")
# Label sits on top of buttonFrame and Frame
# Note place uses just x,y and allows Label to determine width and height
Label.place(x = 330, y = 60)
root.mainloop()

tkinter useing grid on a frame doesn't seem to work

Im trying to get a tkinter gui that lets the user sign in im really new to tkinter. I made a frame and when i use grid to put another frame widget inside of the frame it does it based off of the root and not the inner_frame thats what I think is happening. In the code I made a grey box to demonstrate and I dont understand why it is below the blue frame and not inside the yellow frame under the "sign in" text. Thanks for the help.
from tkinter import *
root = Tk()
root.title("sighn in test")
#colors
background = "#273E47"
accent = "#d8973c"
red = "#bb4430"
white = "#edf2f4"
#this creates and places the background frame
main_buttons_frame = Frame(root, height = 500, width = 400, bg = background).grid(row = 0, column = 0)
#this creates and places the inner frame
inner_frame = Frame(main_buttons_frame, height = 450, width = 300, bg = accent).grid(row = 0, column = 0)
#this creates and places the "sighn in text"
top_text = Label(inner_frame, text = "sign in", font = ("helvitica", 30, "bold"), bg = accent, fg =
background).grid(row = 0, column = 0)
#this is a test to demonstrate
test_frame = Frame(inner_frame, bg = "grey", height = 100, width = 100).grid(row = 1, column = 0)
root.mainloop()
You have very common mistake of beginners
inner_frame = Frame(...).grid...()
It assigns None to inner_frame because grid()/pack()/place() gives None.
So later Frame(inner_frame, ..) means Frame(None, ..) and it adds to root
You have to do it in two steps
inner_frame = Frame(...)
inner_frame.grid(...)
And now you have Frame assigned to inner_frame
EDIT:
With correctly assigned widgets I get
and now gray box is inside yellow frame but image shows different problem - grid()/pack() automatically calculate position and size and external frame automatically change size to fit to child's size.
Using .grid_propagate(False) you can stop it
But it shows other problem - cells don't use full size of parent so yellow frame is moved to left top corner, not centered :) Empty cells have width = 0 and heigh = 0 so moving all to next row and next column will not change it - it will be still in left top corner :)
You need to assign weight to column and/or row which decide how to use free space. All columns/rows has weight=0 and when you set weight=1 then this row and/or column will use all free space - (this would need better explanation) - and then element inside cell will be centered.
main_buttons_frame.grid_columnconfigure(0, weight=1)
main_buttons_frame.grid_rowconfigure(0, weight=1)
import tkinter as tk # PEP8: `import *` is not preferred
# --- colors ---
background = "#273E47"
accent = "#d8973c"
red = "#bb4430"
white = "#edf2f4"
# --- main ---
root = tk.Tk()
root.title("sighn in test")
main_buttons_frame = tk.Frame(root, height=500, width=400, bg=background) # PEP8: without spaces around `=` inside `( )`
main_buttons_frame.grid(row=0, column=0)
#main_buttons_frame = None
main_buttons_frame.grid_propagate(False)
main_buttons_frame.grid_columnconfigure(0, weight=1)
main_buttons_frame.grid_rowconfigure(0, weight=1)
inner_frame = tk.Frame(main_buttons_frame, height=450, width=300, bg=accent)
inner_frame.grid(row=0, column=0)
#inner_frame = None
inner_frame.grid_propagate(False)
inner_frame.grid_columnconfigure(0, weight=1)
inner_frame.grid_rowconfigure(0, weight=1)
top_text = tk.Label(inner_frame, text="sign in", font=("helvitica", 30, "bold"), bg=accent, fg=background)
top_text.grid(row=0, column=0,)
#top_text = None
#top_text.grid_propagate(False)
#top_text.grid_columnconfigure(0, weight=1)
#top_text.grid_rowconfigure(0, weight=1)
test_frame = tk.Frame(inner_frame, bg="grey", height=100, width=100)
test_frame.grid(row=1, column=0)
#test_frame = None
#test_frame.grid_propagate(False)
#test_frame.grid_columnconfigure(1, weight=1)
#test_frame.grid_rowconfigure(0, weight=1)
root.mainloop()
BTW: PEP 8 -- Style Guide for Python Code

Why are my Tkinter column widths misaligned?

I made an Excel-like grid for entering data that will be exported to another program. The top row is "frozen" so that it remains visible when you scroll down. I'm using a list ColWidths = [15, 15, 40] to define the widths of the widgets in the top row, and the widgets in the scroll-able rows beneath.
I read that if you don't give a unit, width defaults to pixels. Even so, I made sure to use the same font size in the top row and all the other rows.
Oddly the columns still appear to be slightly different widths. Any idea how to fix this?
MCVE CODE:
import tkinter as tk
from tkinter import simpledialog,filedialog,colorchooser,messagebox,Frame,Button
from PIL import ImageTk, Image
import textwrap
root = tk.Tk()
canv_1 = tk.Canvas(root, bg="blue")
canv_1.pack(side="top", fill="both")#, fill="both", expand=True)
canv_2 = tk.Canvas(root, bg="gray")
canv_2.pack(fill="both", expand=True)
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
frame = tk.Frame(canv_2)
# create, bind & position scrollbar
vsb = tk.Scrollbar(canv_2, orient="vertical", command=canv_2.yview)
canv_2.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canv_2.pack(side="left", fill="both", expand=True)
canv_2.create_window((0,0), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canv_2=canv_2: onFrameConfigure(canv_2))
labels = ["Chapter Title", "Slide", "Instructions"]
ColWidths = [15, 15, 40]
root.label_wid = []
font1 = ("arial", 15, "bold")
load1 = Image.open("StartingImage.jpg")
root.render1 = ImageTk.PhotoImage(load1)
ch_text = []
for i in range(len(labels)):
root.label_wid.append(tk.Label(canv_1,
font=font1,
relief="raised",
text=labels[i],
width=ColWidths[i],
).grid(row=0, column=i, sticky="we"))
c1 = "#a9d08e"
c2 = "#8dd1bf"
Title_col = [
tk.Entry(
frame,
bg = c1,
width = ColWidths[0],
font = font1)
for y in range(0, 5)
]
Slide_col = [
tk.Entry(
frame,
bg = c2,
width = ColWidths[1],
font = font1)
for y in range(0, 5)
]
instruction_col = [
tk.Text(
frame,
bg="white",
wrap="word",
font=font1, width = ColWidths[2], height=10)
for y in range(0, 5)
]
for y in range(0, 5):
Title_col[y].grid(row=y + 1, column=0, sticky='news')
Slide_col[y].grid(row=y + 1, column=1, sticky='news')
instruction_col[y].grid(row=y+1, column=2, sticky='news')
bt1 = tk.Button(canv_1, text="Export", font=font1, bg="#f5b942")
bt1.grid(row=0, column=4)
load2 = Image.open("scroll-up-gray.png")
root.render2 = ImageTk.PhotoImage(load2)
load3 = Image.open("scroll-down.png")
root.render3 = ImageTk.PhotoImage(load3)
root.mainloop()
GUI SCREENSNIP:
Remove bold from your font or figure out how much to add to the image column (your original screen grab) to compensate. Seriously though, just remove bold from your font and watch it all magically work.
bold is adding extra pixels to all of your labels, and it isn't adding anything in all of those empty boxes. It took me like 2 hours to figure this out (lol)! I even rewrote your entire example much better and still couldn't get it to work ... until I realized that the instruction label was being forced out of the window bounds. Some kind of way that triggered the font format in my head. I simply removed bold and everything worked. I then re-copy/pasted your example and removed bold. It still worked.
I understand that you ultimately want to keep bold. You will have to use line-metrics or something more reliable than simply telling every column to be the same size. The main point is, at least now you know the culprit and why.
edit:
I also got it to work by doing the below instead. Unfortunately, I can't explain why this works. Obviously, I know that we are just adding an inner padding to each cell, but I don't understand why dividing the cell width by 2 works in every case. This may even cause problems later. I simply don't know, but in this example case it seems to work almost perfect.
for y in range(0, 5):
Title_col[y].grid(row=y + 1, column=0, sticky='news', ipadx=ColWidths[0]/2)
Slide_col[y].grid(row=y + 1, column=1, sticky='news', ipadx=ColWidths[1]/2)
instruction_col[y].grid(row=y+1, column=2, sticky='news', ipadx=ColWidths[2]/2)
edit 2:
If you want the results of the above code to appear absolutely perfect in line with the header columns, specify highlightthickness=0 in your canvas instances. This will remove the canvas borders, which are creating a tiny offset.
canv_1 = tk.Canvas(root, bg="blue", highlightthickness=0)
canv_1.pack(side="top", fill="both")#, fill="both", expand=True)
canv_2 = tk.Canvas(root, bg="gray", highlightthickness=0)
canv_2.pack(fill="both", expand=True)

How do I keep a tkinter canvas within a window?

When I run my code, my grid of entry fields does not fit within the window, meaning I have to expand the window in order to access the lower entry fields and the buttons. I want to be able to get to them by scrolling instead.
I have tried various combinations of frames and canvasses, including putting the entry fields directly on the canvas, but at no point have I been able to create a canvas the size of the window(and therefore smaller than the grid of entries contained within it).
def __init__(self, window):
# parameter entry fields below
column_headers = ["Duration (ns)", "SPDT state", "SP4T state", "RF relative wave"]
row_number=50
self.entries = []
canvas = tk.Canvas(window, width=700, height=600)
frame=tk.Frame(canvas)
frame.grid(row=0, column=0)
canvas.configure(scrollregion=frame.bbox("all"))
for col_num, col_name in enumerate(column_headers):
tk.Label(frame, text = col_name).grid(row = 0, column = col_num)
for row_num in range(row_number): # Creates grid of entry fields and stores locations in a list of lists.
self.entries.append([]) # Entry field contents accessed by self.entries[row_num][col_num].get() (both starting at 0)
for col_num, col_name in enumerate(column_headers):
self.entries[row_num].append(tk.StringVar())
self.entries[row_num][col_num] = tk.Entry(frame)
self.entries[row_num][col_num].grid(row = row_num+1, column = col_num)
tk.Button(frame, text = "Update Parameters", command=self.get_params).grid(row = row_number+4)
tk.Button(frame, text = "Run Sweep", command= run_expt).grid(row = row_number+4, column = 1)
tk.Button(frame, text = "Abort", command = abort).grid(row = row_number+4, column = 2)
# data storage stuff below
tk.Label(frame, text="File Name").grid(sticky='W', row=row_number+3, column=0)
self.fileNameEntry = tk.StringVar()
self.fileNameEntry = tk.Entry(frame)
self.fileNameEntry.grid(row=row_number+3, column=1)
vbar = tk.Scrollbar(window, orient=tk.VERTICAL, command=canvas.yview)
canvas.configure(yscrollcommand=vbar.set)
vbar.pack(side=tk.RIGHT, fill=tk.Y)
canvas.pack(fill=tk.BOTH)
window=tk.Tk()
window.geometry("700x600")
EPR=EPRGUI(window)
window.mainloop()
Just so no one suggests this, I'd like to point out that I do have functions for all the buttons in my code, but have omitted them from this question to make it a bit quicker to read.

Categories