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 customFrame
subclass with the desired layout. But that means changing the "client" code in many places. :-/
CodePudding user response:
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()
CodePudding user response:
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 defaultNW
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 |
------------- ----------------
| ... |