How to changes fonts using ttk themed widgets in windows - python

On OS X, ttk.Style().configure('TLabelframe.label', font='helvetica 14 bold') works to change the font used by the ttk.LabelFrame widget. On Windows, ttk.Style().configure('TLabelframe.label', font='arial 14 bold') has no effect other than returning the same font info to ttk.Style().lookup('TLabelframe.label','font').
I've tried different font names and formats, creating a derived style, using TkDefaultFont and just changing the size, and different widgets (TButton.label, TCheckbutton.label). So far, no matter what I've tried, it always appears to use TkDefaultFont in the default size.
Changing the font setting in python27/tcl/tk8.5/ttk/xpTheme.tcl (the default theme on windows) does change the font being displayed. Removing the -font TkDefaultFont setting from the theme settings does not change what is displayed.
Any suggestions as to how this actually works?
Edit: I hadn't tried changing the font for the Label widget before, and that one actually works.

I believe the code in this area is buggy and will open a ticket. Using 'TLableframe.Label' (note uppercase 'L' in 'Label' works. 'TButton.label' and 'TButton.Label' don't work, but just 'TButton' does; 'TCheckbutton' is the same. I was unable to change the fonts for 'TEntry' with any combination, including adding 'textarea.'

It looks like for ttk.LabelFrame, you have to create a separate ttk.Label widget, and then assign it to the LabelFrame using the labelwidget= operand. You can set whatever font/style on the Label widget that you desire and that will be reflected in the LabelFrame. Note, you don't call the geometry manager for the Label widget. Just instantiate it, then assign it to the LabelFrame.
This also means that you can assign almost any widget you want, such as a ttk.Checkbutton, if you wanted to control the state of child controls within the LabelFrame. You'd have to write the code for this, but visually, it'd enable/disable the child controls based on the state of the Checkbutton.
Source: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/ttk-LabelFrame.html

Related

Tkinter/Ttkbootstrap Font Size After Theme Change

I'm probably just missing something obvious here but I've tried everything I know of. In my application, the user can change the theme by selecting it from a Combobox, and the theme changes. The unwanted behavior is that the font size also changes back to the default. I've tried resetting it after the theme change but the only way I can find to do it would be to destroy the entire instance and reload everything which is not preferable. Any suggestions?
I also noticed that the font size does go back to what it was set to when the theme is switched to the originally set theme. I.e. if I had it set to font size 12 and the theme as darkly when the application was first started, then the font size will go to the default when the theme is changed to anything else but will go to 12 when darkly is selected again.
Here's a MWE:
import tkinter as tk
import ttkbootstrap as tb
def change_theme(event):
t = combo.get()
style.theme_use(t)
app = tb.Window(title='Font Weirdness',
themename='darkly')
style = app.style
style.configure('.', font=('TkDefaultFont', 20))
button = tb.Button(app, text='Hello')
button.pack(pady=20)
combo = tb.Combobox(app, values=tb.Style().theme_names())
combo.pack(pady=20)
combo.bind('<<ComboboxSelected>>', change_theme)
app.position_center()
app.mainloop()
Thanks for any help!
The problem is that you're giving the current style a custom font that only applies to that style. When you do font=('TkDefaultFont', 20) you are creating a new font based on the family "TkDefaultFont". That's not a valid font family so the custom font will end up being based on the default font. The actual default font remains unchanged.
If you want to define the font once and have it be the same for all styles, you need to modify the actual default font. You can use nametofont to get the actual font object. You can then change the size of that font object with configure:
from tkinter.font import nametofont
...
default_font = nametofont("TkDefaultFont")
default_font.configure(size=30)
...
With that, you can remove your style.configure statement and all styles that use TkDefaultFont properly will use your new definition.

Python Tkinter : How to change underground / background color of Treeview, Entry and other widgets

By using wrong keyword I've stupidly struggled to change what we could call the first level of color of ttk widgets. It's not underground or underlayer but fieldbackground as most of you already know.
See below...
A little reminder :
background color is the color under the text in the widget
foreground color is the color of the text
fieldground color is the color of the place where the text will appear
This can be manage with Style:
self.MainTk = tkinter.Tk()
self.style = ttk.Style( self.MainTk )
self.style.configure("Treeview", fieldbackground = 'grey65')
self.style.configure("TEntry", fieldbackground = 'grey65')
Note that some widgets need a T before the name in the configure phase.
see https://www.pythontutorial.net/tkinter/ttk-style/ for more infos.
In Treeview, it manage the Tree mode appearence when you have a single parent collapsed.
From digging old posts it seems that this may not work with some themes under some configuration. You'll find out.
WARNING : Since the error is often made even if it's logic, the widgets must be ttk not tkinter so you must use
self.MyEntry = ttk.Entry(MainTk)
instead of
self.MyEntry = tkinter.Entry(MainTk)
or
self.MyEntry = tk.Entry(MainTk)
if you have import tkinter as tk. Here lie the most common mistake I think.

How to change font to bold/underline/italics in Python Tkinter listbox?

How do I change the font style of an item in a listbox? I assumed it would use listbox[i].itemconfig(), but THIS implies font style is not an option.
I also read THIS, which implies you have to first get the font of the item, then change it and set it to the new font. But how do I do this with a single item in a listbox?
I am trying to have a button to set the selected item in a listbox as a 'master' (not important what that means), which for these purposes just needs to be highlighted in some way (preferably not with foreground/background/whatever colors). Below is my attempt, which does not at all work, because it is incomplete. I am unsure how to interact with the fonts of specific items in a listbox. Please feel free to offer better approaches.
I apologize for not providing more or better example code. I do not know how to attempt this. I am also using THIS as a reference. (I am having a hard time understanding tkFont as a package, and why/how it should be used over tk.font...)
def set_master(self):
the_selection = self.the_listbox.curselection()
for the_index, the_item in enumerate(self.the_listbox):
f = tkFont.Font(font=the_item.cget("font"))
if int(the_selection) == int(the_index):
f.configure(underline=True, weight='bold', slant='italic')
else:
f.configure(underline=False, weight='normal', slant='roman')
#the_item.set_font(f) <--This is pseudocode
return
You cannot change the font of an individual item in a listbox.
If you need something that works like a listbox but which offers the ability to change the font of individual items, you can use the ttk.Treeview widget.

ttk.Separator appearing as dot "." when using .pack() layout manager

My question is similar to this one, but I'm using the layout manager pack rather than grid so the answer in the alternate thread doesn't work for me.
Code:
iconLabelImage = ttk.Label(labelFrame)
self.iconImage = PhotoImage(file='images\icon.png')
iconLabelImage['image'] = self.iconImage
iconLabelImage.pack(anchor='w')
sep = ttk.Separator(parameterFrame, orient=VERTICAL)
sep.pack(side="right", fill="y")
The LabelFrame is a child of the parameterFrame.
It doesn't matter what parameters I change I can't seem to get the separator to extend more than a pixel even though it exists in a larger frame.
Any ideas?
Actually the idea is same with the question you have provided above. That means:
The expand option tells the manager to assign additional space to the widget box. If the parent widget is made larger than necessary to hold all packed widgets, any exceeding space will be distributed among all widgets that have the expand option set to a non-zero value.
-effbot
The point here you should focus on is: non-zero value/weight.
So to solve this problem using pack method add expand=True option.

Create resizable/multiline Tkinter/ttk Labels with word wrap

Is it possible to create a multi-line label with word wrap that resizes in sync with the width of its parent? In other words the wordwrap behavior of Notepad as you change the width of the NotePad window.
The use case is a dialog that needs to present a block of multi-line text (instructions) in its entirety without having the text clipped or resorting to scrollbars. The parent container will have enough vertical space to accomodate narrow widths.
I've been experimenting with Tkinter Label and Message widgets and the ttk Label widget without success. It seems that I need to hard code a pixel wraplength value vs. have these controls auto wordwrap when their text reaches the right edge of their containers. Certainly Tkinters geometry managers can help me auto-resize my labels and update their wraplength values accordingly?
Should I be looking at the Text widget instead? If so, is it possible to hide the border of a Text widget so I can use it as a multi-line label with wordwrap?
Here's a prototype of how one might do what I described above. It was inspired by Bryan Oakley's tip to use the Text widget and the following post on Stackoverflow:
In python's tkinter, how can I make a Label such that you can select the text with the mouse?
from Tkinter import *
master = Tk()
text = """
If tkinter is 8.5 or above you'll want the selection background to appear like it does when the widget is activated. Comment this out for older versions of Tkinter.
This is even more text.
The final line of our auto-wrapping label that supports clipboard copy.
""".strip()
frameLabel = Frame( master, padx=20, pady=20 )
frameLabel.pack()
w = Text( frameLabel, wrap='word', font='Arial 12 italic' )
w.insert( 1.0, text )
w.pack()
# - have selection background appear like it does when the widget is activated (Tkinter 8.5+)
# - have label background color match its parent background color via .cget('bg')
# - set relief='flat' to hide Text control borders
# - set state='disabled' to block changes to text (while still allowing selection/clipboard copy)
w.configure( bg=master.cget('bg'), relief='flat', state='disabled' )
mainloop()
Use Message widget:
The Message widget is a variant of the Label, designed to display multiline messages. The message widget can wrap text, and adjust its width to maintain a given aspect ratio.
No, there is no feature built-in to Tk to auto-word-wrap labels. However, it's doable by binding to the <Configure> event of the label and adjusting the wrap length then. This binding will fire every time the label widget is resized.
The other option, as you suggest, is to use a text widget. It is possible to entirely turn off the border if you so desire. This has always been my choice when I want word-wrapped instructional text.
Here is the code:
entry = Label(self, text=text,
anchor=NW, justify=LEFT,
relief=RIDGE, bd=2)
def y(event, entry=entry):
# FIXME: make this a global method, to prevent function object creation
# for every label.
pad = 0
pad += int(str(entry['bd']))
pad += int(str(entry['padx']))
pad *= 2
entry.configure(wraplength = event.width - pad)
entry.bind("<Configure>", y )
The tkinter.Message widget suggested by some people does NOT use TTK styling, which means that it's gonna look like garbage inside a TTK (themed) interface.
You could manually apply the background and foreground colors from your TTK theme to the tkinter.Message (by instantiating ttk.Style() and requesting the active themes' TLabel foreground and background colors from that style object), but it's not worth it... because the ancient Message widget has ZERO advantages over TTK's regular ttk.Label.
The tkinter.Message widget has an "aspect ratio" property that defines how many pixels until it wraps.
The ttk.Label instead has a wraplength= property which determines how many pixels until the words wrap. You should also use its anchor= and justify= properties to customize it to your exact desires. With these properties you can make your Label behave as the old Message widget did.
Example: ttk.Label(root, text="foo", wraplength=220, anchor=tkinter.NW, justify=tkinter.LEFT). Creates a beautifully styled label which permanently wraps its text after 220 pixels wide.
As for automatically updating the wraplength? Well, you should attach to the <Configure> event as people have said... However, if you have a completely fluid window (which resizes itself to fit all content), or a grid/frame that is fluid and contains the label, then you can't automatically calculate it that way, because the parent WINDOW/CONTAINER itself will EXPAND whenever the label grows too wide. Which means that the label will always resize itself to the maximum width it would need to fit all text. So, updating wraplength automatically is only possible if the label itself has some constraints on how wide it can grow (either via its parent container being a fixed size/maxsize, or itself being a fixed size/maxsize). In that case, sure, you can use configure to calculate new wrapping numbers to make sure the text always wraps... However, the example code by t7ko is broken and not valid anymore, just fyi.

Categories