Tkinter label font size interfere with the frame structure below - python-3.x

I have a window with two main frames. One frame in row=0 called frame-A is used for a Title. The other frame in row=1 called frame-B is structured in several sub-frames with data. This frame-B has a label at the top (row = 0). It contains also several sub-frames in rows 1-3. If I use a font size of 15 for the label on frame-B, there are no problems. If I increase the font size= 20, the sub-frames in the frame-B become separated. I am trying to understand how the font size is creating problems with the frames in rows 1-3.
Here is my code:
import tkinter as tk
window = tk.Tk()
window.geometry("1200x1200")
#1-Main text
fr_A = tk.Frame(window,width=50, height=50, bd= 1,highlightbackground="green", highlightcolor="green", highlightthickness=1)
tk.Label(fr_A,text="My title",font=("Courier", 30,"bold")).grid(row=0)
fr_A.grid(row=0)
#2-initial configuration
fr_B = tk.Frame(window,width=300, height=300, bd= 1,highlightbackground="red",highlightcolor="red", highlightthickness=1)
fr_B.grid(row=1,column=0,ipady=80)
tk.Label(fr_B,text="Init data",font="helvetica 20",height=2).grid(row=0,column=0) #>>>>>>>font size problem
fr_list = []
for cr in ((1,0),(1,1),(1,2),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2)):
frame_in=tk.Frame(fr_B, highlightbackground="black", highlightcolor="black", highlightthickness=1,bd= 1)
frame_in.grid(row=cr[0],column=cr[1])
fr_list.append(frame_in)
cnt = -1
for fr in fr_list:
for cr in ((0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)):
cnt += 1
tk.Label(fr,text=cnt,width=3,height =1).grid(row=cr[0],column=cr[1],sticky="nsew")
window.mainloop()

You need to add columnspan to the title label.
tk.Label(fr_B, text="Init data",font="helvetica 20",height=2).grid(row=0,column=0, columnspan=3) #>>>>>>>font size problem

Related

ttk, How to position heading inside Labelframe instead of on the border?

I am trying to create an own ttk Theme based on my company's CI. I took the Sun Valley theme as starting point and swapped out graphics, fonts and colors.
However I am stuck on the Label frame. I am trying to position the Label within the frame, kind of like a heading. I.e. there should be some margin between top edge and label, and appropriate top-padding for the content (child widgets).
Now:
+-- Label ------
| ...
Desired:
+---------------
| Label
| ...
I tried to set the padding option:
within the Layout
on TLabelframe itself
on TLabelframe.Label
but the label did not move a pixel. How to achieve this?
Generally I am very confused about what identifiers and options are legal within ttk:style layout, ttk:style element and ttk:style configure, because documentation is hazy and scattered all over the 'net, and there are no error messages whatsoever. Any helpful tips?
Edit: What I found out since posting:
The Labelframe label is a separate widget altogether, with the class TLabelframe.Label.
It is possible to override its layout and add a spacer on top, shifting the text down.
However, the label widget is v-centered on the frame line. If its height increases, it pushes "upward" as much as downward. I found no way to alter the alignment w.r.t. to the actual frame.
It might be possible to replace Labelframe altogether with a custom Frame subclass with the desired layout. But that means changing the "client" code in many places. :-/
This can be done by changing the layout definitions so that the text element is held by the Labelframe layout and the Layoutframe.Label no longer draws the text element. Adding a bit of padding ensures the contained widgets leave the label clear.
Example code:
import sys
import tkinter as tk
import tkinter.ttk as ttk
class CustomLabelframe(ttk.Labelframe):
def __init__(self, master, **kwargs):
"""Initialize the widget with the custom style."""
kwargs["style"] = "Custom.Labelframe"
super(CustomLabelframe, self).__init__(master, **kwargs)
#staticmethod
def register(master):
style = ttk.Style(master)
layout = CustomLabelframe.modify_layout(style.layout("TLabelframe"), "Custom")
style.layout('Custom.Labelframe.Label', [
('Custom.Label.fill', {'sticky': 'nswe'})])
style.layout('Custom.Labelframe', [
('Custom.Labelframe.border', {'sticky': 'nswe', 'children': [
('Custom.Labelframe.text', {'side': 'top'}),
('Custom.Labelframe.padding', {'side': 'top', 'expand': True})
]})
])
if (style.configure('TLabelframe')):
style.configure("Custom.Labelframe", **style.configure("TLabelframe"))
# Add space to the top to prevent child widgets overwriting the label.
style.configure("Custom.Labelframe", padding=(0,12,0,0))
style.map("Custom.Labelframe", **style.map("TLabelframe"))
master.bind("<<ThemeChanged>>", lambda ev: CustomLabelframe.register(ev.widget))
#staticmethod
def modify_layout(layout, prefix):
"""Copy a style layout and rename the elements with a prefix."""
result = []
for item in layout:
element,desc = item
if "children" in desc:
desc["children"] = HistoryCombobox.modify_layout(desc["children"], prefix)
result.append((f"{prefix}.{element}",desc))
return result
class App(ttk.Frame):
"""Test application for the custom widget."""
def __init__(self, master, **kwargs):
super(App, self).__init__(master, **kwargs)
self.master.wm_geometry("640x480")
frame = self.create_themesframe()
frame.pack(side=tk.TOP, fill=tk.BOTH)
for count in range(3):
frame = CustomLabelframe(self, text=f"Frame {count}", width=160, height=80)
frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
button = ttk.Button(frame, text="Test")
button.pack(side=tk.LEFT)
self.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
def create_themesframe(self):
frame = ttk.Frame(self)
label = ttk.Label(frame, text="Theme: ")
themes = ttk.Combobox(frame, values=style.theme_names(), state="readonly")
themes.current(themes.cget("values").index(style.theme_use()))
themes.bind("<<ComboboxSelected>>", lambda ev: style.theme_use(ev.widget.get()))
label.pack(side=tk.LEFT)
themes.pack(side=tk.LEFT)
return frame
def main(args=None):
global root, app, style
root = tk.Tk()
style = ttk.Style(root)
CustomLabelframe.register(root)
app = App(root)
try:
import idlelib.pyshell
sys.argv = [sys.argv[0], "-n"]
root.bind("<Control-i>", lambda ev: idlelib.pyshell.main())
except Exception as e:
print(e)
root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
It is relatively easy to place ttk.Labelframe text below, on or above the relief graphic. This example uses the text attribute but labelwidget can also be used.
In order for the relief to be visible the background color of Labelframe.Label must be set to "".
import tkinter as tk
from tkinter import font
from tkinter import ttk
message = "Hello World"
master = tk.Tk()
style = ttk.Style(master)
style.theme_use(themename = "default")
actualFont = font.Font(
family = "Courier New", size = 20, weight = "bold")
style.configure(
"TLabelframe.Label", background = "", font = actualFont)
frame = ttk.LabelFrame(
master, labelanchor = "n", text = message)
frame.grid(sticky = tk.NSEW)
frame.rowconfigure(0, weight = 1)
frame.columnconfigure(0, weight = 1)
def change_heading():
if frame["text"][0] == "\n":
frame["text"] = f"{message}\n"
else:
frame["text"] = f"\n{message}"
button = tk.Button(
frame, text = "Change", command = change_heading)
button.grid(sticky = "nsew")
master.mainloop()
Looking through the source of the Labelframe widget, I found that:
The label is either placed vertically-centered on the frame's border, or flush above it, depending on the -labeloutside config option. (for default NW anchor)
i.e. by adding whitespace on top of the text by any means, the label box will extend upwards the same amount as downwards, creating a "dead space" above the frame.
There might still be a way to get it "inside" by increasing the border width, but I couldn't get it to work.
I now used the labeloutside option to make a "tab-like" heading.
# ... (define $images array much earlier) ...
ttk::style element create Labelframe.border image $images(card2) \
-border 6 -padding 6 -sticky nsew
ttk::style configure TLabelframe -padding {8 8 8 8} -labeloutside 1 -labelmargins {2 2 2 0}
ttk::style element create Label.fill image $images(header2) -height 31 -padding {8 0 16 0} -border 1
With suitable images, this is nearly what I was aiming for, only that the header does not stretch across the full frame width. Tkinter elements use a "9-patch"-like subdivision strategy for images, so you can make stretchable frames using the -border argument for element create.
Result is approximately this:
+-------------+
| Heading |
+-------------+----------------+
| ... |

Tkinter Button Placement to stretch vertically

I have a tkinter pop-up with four objects and a button.
I need them to be placed in this way (showing using excel):
I am placing the labels like this:
lb1.grid(row=1,column=1)
lb3.grid(row=2,column=1)
lb3.grid(row=3,column=1,sticky='w')
lb4.grid(row=3,column=1,sticky='e')
However, when I put button using following, it doesn't stretch across all rows. Instead it stays as a row 4 item.
bt.grid(rowspan=3,column=2) #have even tried adding sticky = 'ns' to this but doesn't stretch
Basically, I want the button to be a thin vertical line on the right border of the widget. I will also want to remove the bevel of the button, text will be blank, and color will be same as label background to make it difficult to see the button. But it should be clickable across the whole east side border of the widget across all the labels.
Can we do that?
Your problem is that you are placing the lb4 label on the lb3 label. You also should specify a columnspan = 2 for lb1 and lb2 as they span 2 columns (column 1, where lb3 sits, and column 2 where lb4 sits). bt will be in column 3 starting from row 1 with a rowspan = 3 therefor reaching till row = 3; Code:
from tkinter import *
root = Tk()
root.geometry("400x400")
lb1 = Label(root, text="Obj1")
lb2 = Label(root, text="Obj2")
lb3 = Label(root, text="Obj3")
lb4 = Label(root, text="Obj4")
lb1.grid(row=1,column=1, columnspan=2)
lb2.grid(row=2,column=1, columnspan=2)
lb3.grid(row=3,column=1,sticky='w')
lb4.grid(row=3,column=2,sticky='e')
bt = Button(root, text="Button")
bt.grid(rowspan=3, row=1, column=3, sticky="ns")
root.mainloop()
also note that rows and columns actually start at 0 (like most things in python), but because i wasn't sure if you already had some widgets in row = 0 / column = 0, i used your column numbers / row numbers.

Multiple tkinter labels in notebook tab are not expanding to full length of window

I'm trying to equally distribute three objects/widgets across one row in a ttk notebook tab, but the three objects only expand half of the window.
I'm not sure what controls the number of columns within a tab since columnsspan in tab.grid(row=0, columnspan=3) doesn't appear to change anything. I've also tried various values for rows and columns for every object with .grid. This is only an issue for notebook tabs, rather than a single window.
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
root = Tk()
root.title('Title')
root.resizable(width=FALSE, height=FALSE)
root.geometry('{}x{}'.format(750, 750))
nb = ttk.Notebook(root)
nb.grid(row=0, column=0)
# Add first tab
tab1 = ttk.Frame(nb)
#tab1.grid(row=0, column=0)
nb.add(tab1, text='Setup')
# Add row label
lb1 = ttk.Label(tab1, text = 'Parent Directory:')
lb1.grid(row = 1, column = 1)
# Add text entry
txt1 = ttk.Entry(tab1)
txt1.grid(row = 1, column = 2)
# Add selection button
btn1 = ttk.Button(tab1, text="Select")
btn1.grid(row=1, column=3)
root.mainloop()
I'm expecting the columns to span the full length of the window, instead of half the length of the window.
In order to do this using grid you need to use the Frame.columnconfigure([column#], minsize=[minsize]) function.
If you want the text box and button to stretch to fill the space, use the sticky option. (Sticky doesn't really do anything with the label)
Code:
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
root = Tk()
root.title('Title')
root.resizable(width=FALSE, height=FALSE)
root.geometry('{}x{}'.format(750, 750))
nb = ttk.Notebook(root, width=750)
nb.grid(row=0, column=0)
# Add first tab
tab1 = ttk.Frame(nb)
#tab1.grid(row=0, column=0)
nb.add(tab1, text='Setup')
# Change the sizes of the columns equally
tab1.columnconfigure(1, minsize=250)
tab1.columnconfigure(2, minsize=250)
tab1.columnconfigure(3, minsize=250)
# Add row label
lb1 = ttk.Label(tab1, text = 'Parent Directory:')
lb1.grid(row = 1, column = 1,sticky=(E,W))
# Add text entry
txt1 = ttk.Entry(tab1)
txt1.grid(row = 1, column = 2,sticky=(E,W))
# Add selection button
btn1 = ttk.Button(tab1, text="Select")
btn1.grid(row=1, column=3,sticky=(E,W))
root.mainloop()
Image of result

tkinter resize label width (grid_propagate not working)

I'm having problem with adjusting the width of the label to reflect current width of the window. When the window size changes I'd like label to fill the rest of the width that is left after other widgets in row consume width they need.
Putting the label in a Frame and using grid_propagate(False) does not seem to work.
Consider following code:
import tkinter as tk
import tkinter.ttk as ttk
class PixelLabel(ttk.Frame):
def __init__(self,master, w, h=20, *args, **kwargs):
'''
creates label inside frame,
then frame is set NOT to adjust to child(label) size
and the label keeps extending inside frame to fill it all,
whatever long text inside it is
'''
ttk.Frame.__init__(self, master, width=w, height=h,borderwidth=1)
#self.config(highlightbackground="blue")
self.grid_propagate(False) # don't shrink
self.label = ttk.Label(*args, **kwargs)
self.label.grid(sticky='nswe')
def resize(self,parent,*other_lenghts):
'''
resizes label to take rest of the width from parent
that other childs are not using
'''
parent.update()
new_width = parent.winfo_width()
print(new_width)
for lenght in other_lenghts:
new_width -= lenght
print(new_width)
self.configure(width = new_width)
root = tk.Tk()
master = ttk.Frame(root)
master.grid()
label = ttk.Label(master,text='aaa',borderwidth=1, relief='sunken')
label.grid(row=0,column=0)
label1_width = 7
label1 = ttk.Label(master,text='bbbb',borderwidth=1, relief='sunken',width=label1_width)
label1.grid(row=0,column=1)
label2 = ttk.Label(master,text='ccccccccccccccccccccccccccccccccccccc',borderwidth=1, relief='sunken')
label2.grid(row=0,column=2)
label3_width = 9
label2 = ttk.Label(master,text='ddddd',borderwidth=1, relief='sunken',width=label2_width)
label2.grid(row=0,column=3)
label4 = ttk.Label(master,text='ee',borderwidth=1, relief='sunken')
label4.grid(row=1,column=0)
label5 = ttk.Label(master,text='f',borderwidth=1, relief='sunken')
label5.grid(row=1,column=1,sticky='we')
nest_frame = ttk.Frame(master)
nest_frame.grid(row=2,columnspan=4)
label8_width = 9
label8 = ttk.Label(nest_frame,text='xxxxx',borderwidth=1, relief='sunken',width=label8_width)
label8.grid(row=0,column=0)
label9 = PixelLabel(nest_frame, 5, text='should be next to xxxxx but is not?',borderwidth=1, relief='sunken')
label9.grid(row=0,column=1)
label9.resize(root,label2_width)
root.mainloop()
Why label9 does not appear next to label8
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
It's not clear why you are using a label in a frame. I suspect this is an XY problem. You can get labels to consume extra space without resorting to putting labels inside frames. However, since you posted some very specific code with very specific questions, that's what I'll address.
Why label9 does not appear next to label8
Because you are creating the label as a child of the root window rather than a child of the frame. You need to create the label as a child of self inside PixelLabel:
class PixelLabel(...):
def __init__(...):
...
self.label = ttk.Label(self, ...)
...
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
There are a couple more problems. First, you need to give column zero of the frame inside PixelFrame a non-zero weight so that it uses all available space (or, switch to pack).
class PixelLabel(...):
def __init__(...):
...
self.grid_columnconfigure(0, weight=1)
...
Second, you need to use the sticky attribute when placing nest_frame in the window so that it expands to fill its space:
nest_frame.grid(..., sticky="ew")

tkinter textbox resizes instead of wrapping text to fill box

def red():
frame3.output_display.config(fg = 'red', font=root.customFont1)
def blue():
frame3.output_display.config(fg = 'darkblue', font=root.customFont2)
def green():
frame3.output_display.config(fg = 'darkgreen',font=root.customFont3)
def black():
frame3.output_display.config(fg = 'black',font=root.customFont4)
from tkinter import *
from tkinter import ttk
import tkinter.font
from tkinter.scrolledtext import ScrolledText
root = Tk()
root.title("Change Text")
root.geometry('700x500')
# change font size and family: not used currently because of resizing issue
root.customFont1 = tkinter.font.Font(family="Handwriting-Dakota", size=12)
root.customFont2 = tkinter.font.Font(family="Comic sans MS", size=14)
root.customFont3 = tkinter.font.Font(family="Script MT", size=16)
root.customFont4 = tkinter.font.Font(family="Courier", size=10)
# FRAME 3
frame3 = LabelFrame(root, background = '#EBFFFF', borderwidth = 2, text = 'text entry and display frame', fg = 'purple',bd = 2, relief = FLAT, width = 75, height = 40)
frame3.grid(column = 2, row = 0, columnspan = 3, rowspan = 6, sticky = N+S+E+W)
#frame3.grid_rowconfigure(0, weight=0)
#frame3.grid_columnconfigure(0, weight=0)
frame3.grid_propagate(True)
frame3.output_display = ScrolledText(frame3, wrap = WORD)
frame3.output_display.pack( side = TOP, fill = BOTH, expand = True )
frame3.output_display.insert('1.0', 'the text should appear here and should wrap at character forty five', END)
#frame3.output_display.config(state=DISABLED) # could be used to prevent modification to text (but also prevents load new file)
# draws all of the buttons,
ttk.Style().configure("TButton", padding=6, relief="flat",background="#A52A2A", foreground='#660066')
names_colour=(('Red',red),('Blue',blue),('Green',green),('Black',black))
root.button=[]
for i,(name, colour) in enumerate(names_colour):
root.button.append(ttk.Button(root, text=name, command = colour))
row,col=divmod(i,4)
root.button[i].grid(sticky=N+S+E+W, row=6, column=col, padx=1, pady=1)
root.mainloop()
In the GUI when the text font face and font size is changed, the textbox resizes and obscures the buttons. In my naivety I thought that the textbox would remain the same size and the text would simply wrap within the constraints of the textbox. At least taht is what I would like to achieve. Obviously there is some concept in font size or in textbox , tkinter that I do not understand
Thanks
The width of the text widget is defined in units of character widths rather than pixels, and it tries to use its configured width as its minimum width whenever possible. The widget will be wider for wider fonts, and narrower for narrow fonts. Thus, if you give it a wide font it will try to make itself wider to remain X characters wide.
So, how do you solve this?
One solution is to set the width and height to something small. For example, if you set the width and height to 1 (one), the widget will only ever try to force itself to be one character wide and tall. Unless you're using absolutely huge fonts, you'll barely see the widget grow as you enlarge the font.
Then you will need to rely on the pack, grid, or place algorithm to stretch the widget to the desired dimensions. If you're using grid, this usually means you need to make sure that column and row weights are set appropriately, along with setting the sticky attribute.
The downside to this is that you have to make sure your GUI has the right size, rather than depending on it just magically happening based on the preferred size of each widget.
As a quick hack, you can see this in your program by adding these lines after the widgets have been created:
frame3.output_display.configure(width=1, height=1)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(2, weight=1)
When I run your code with the above additional lines, the text widget remains a fixed size and the text wraps at different places with each font.

Resources