Create Derived Underline Text Style Using ttk Styling - python

Summary
How do I create a derived style with underlined text using ttk styling?
Details
I'm attempting to create a ttk style derived from the built-in TLabel style. The only difference between the built-in and the derived style should be that the text is underlined; it should inherit all other characteristics from the built-in TLabel style (i.e, if the TLabel font changes later, so should the Underline.TLabel).
I know that the basic way (not using ttk styling) is to create a new underlined font. However, as you can see from the sample code (below), the Underline.TLabel style displays in the correct (default) font, but the font-size is larger. I'm certain I'm missing something obvious, but haven't been able to find it through any Google searches, etc.
import tkinter as tk
from tkinter import font
from tkinter import ttk
def main():
root = tk.Tk()
style = ttk.Style(root)
f = font.Font(underline=1)
style.configure('Underline.TLabel', font=f)
lbl0 = ttk.Label(root, text='Label 0', style='TLabel')
lbl0.pack()
lbl1 = ttk.Label(root, text='Label 1', style='Underline.TLabel')
lbl1.pack()
root.mainloop()
if __name__ == '__main__':
main()

The problem is that you are assuming that font.Font(underline=1) returns the exact same font that is used by TLabel but with the underline bit turned on. This may or may not be true depending on the platform and how it's configured.
If you need the custom font to be based off of the font used by TLabel, you should first make a copy of the font instead of relying on defaults.
For example:
original_font = font.nametofont(style.lookup("TLabel", "font"))
f = font.Font(**original_font.configure())
f.configure(underline=1)

Related

How to configure map for all ttk widgets except TButton?

I am creating a GUI with Tkinter and ttk, and I'm trying to create a custom map to make ttk widgets blue on hover. I could apply that to all widgets with passing "." as the first argument to ttk.Style().map(...).
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
style = ttk.Style()
style.map(".", background=[("active", "blue")])
button = ttk.Button(root, text="An example button")
button.pack()
scrollbar = ttk.Scrollbar(root)
scrollbar.pack()
root.mainloop()
But now I want to exclude TButton from this query. That is, I need to make all widgets but TButton blue on hover. How can I do that?
Passing ".!TButton" as well as "!TButton" instead of "." has no effect.
There is a root style whose name is '.'. To change some feature's default appearance for every widget, you can configure this style. For example, let's suppose that you want all text to be 12-point Helvetica (unless overriden by another style or font option).
So we can override it by simply adding another style:
style.map('TButton',background=[('active','#f0f0f0')])
Keep a note that you might want to do this for every style that you wish to set to default too.
Take a read here for more info.

Can we use more than 1 style for ttk.Notebook (tkinter), such as "tab.TNotebook" and "background.TNotebook"?

I have tried using both separately and they work, but when I combine them under the same variable neither of them work. Is there a method of combining them both together?
Here is the bit of my code which I am talking about:
sideTab = ttk.Style(screen)
sideTab.configure("tab.TNotebook", tabposition='wn')
tabColour = ttk.Style(screen)
tabColour.configure("colour.TNotebook", background="black")
newStyle = ("tab.TNotebook", "colour.TNotebook")
tabList = ttk.Notebook(screen, style=newStyle)
This code wouldn't change the style as both are being used in the 'style'
Does anyone have any ideas how I could get them both to work?
It is usually enough to create a single ttk.Style instance and configure the styles of all the widgets with it.
To combine styles, just choose one name, e.g. "custom.TNotebook" and configure the same options as in "colour.TNotebook" and "tab.TNotebook":
style.configure("custom.TNotebook", background="black", tabposition="nw")
and use this style for your notebook.
Full example:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style(root)
style.configure("custom.TNotebook", background="black", tabposition='wn')
tabList = ttk.Notebook(root, style="custom.TNotebook")
for i in range(4):
tabList.add(ttk.Label(tabList, text=f"Content {i}"), text=f"Tab {i}")
tabList.pack(fill="both", expand=True)
root.mainloop()

Why can't I define (and save) Tkinter fonts inside a function?

Defining a font inside a function and in the main body of the script seems to behave differently, and I can't seem to figure out how it's supposed to work.
For example, the Label in this example ends up being in a larger font, as expected:
from Tkinter import *
from ttk import *
import tkFont
root = Tk()
default = tkFont.Font(root=root, name="TkTextFont", exists=True)
large = default.copy()
large.config(size=36)
style = Style(root)
style.configure("Large.TLabel", font=large)
root.title("Font Test")
main_frame = Frame(root)
Label(main_frame, text="Large Font", style="Large.TLabel").pack()
main_frame.pack()
root.mainloop()
However, if I try to define styles inside a function, it seems like the font gets deleted or garbage collected and is not available by the time the widget needs to use it:
from Tkinter import *
from ttk import *
import tkFont
def define_styles(root):
default = tkFont.Font(root=root, name="TkTextFont", exists=True)
large = default.copy()
large.config(size=36)
style = Style(root)
style.configure("Large.TLabel", font=large)
root = Tk()
root.title("Font Test")
define_styles(root)
main_frame = Frame(root)
Label(main_frame, text="Large Font", style="Large.TLabel").grid(row=0, column=0)
main_frame.pack()
root.mainloop()
Printing out tkFont.names() in the first version just before the main_frame.pack() lists the custom font as font<id>, but printing the same in the second version does not list the custom font outside the define_styles function. Do I have to do something special to save them?
Why can't I put that code in a function? Am I fundamentally misunderstanding something about how Fonts are supposed to be used? tkFont seems to have some kind of font registry, why aren't mine sticking around?
I have no evidence to back this up, but I believe that your large Font object is being garbage collected by Python once define_styles ends. This is because no pure Python objects have any references to it, even though the underlying Tcl implementation is still using it. This is a problem that afflicts Tkinter's PhotoImage class, as well.
The workaround is to keep the object alive by making a long-lived reference to it. Just assign it to any old attribute on the root object, for example.
def define_styles(root):
default = tkFont.Font(root=root, name="TkTextFont", exists=True)
large = default.copy()
large.config(size=36)
style = Style(root)
style.configure("Large.TLabel", font=large)
root.myfont = large
Result:

ttk.ComboBox Styling does not get set properly

I have created a python GUI application. It works great, and I've styled everything to my liking, save for the ComboBox. Styling on the ttk.Combobox doesn't seem to work.
That should give an idea of the material style I'm going for. Here's the styling block I have for the combobox.
globalStyle = ttk.Style()
globalStyle.configure('TCombobox', foreground=textColor, background=backgroundColor, fieldbackground=selectColor, fieldforeground=textColor, font='Verdana')
The only thing I have been able to successfully change is the text and the foreground color. I am looking to edit the following attributes:
Text color
Field background
Dropdown text color
Dropdown background
EDIT: I should mention that the color variables used are all valid hex color codes.
selectColor = '#333333'
backgroundColor = '#444444'
foregroundColor = '#555555'
textColor = '#999999'
So I ran in to the same issue but found most of the solution here. All you have to do is add the following to your code:
option add *TCombobox*Listbox.background color
option add *TCombobox*Listbox.font font
option add *TCombobox*Listbox.foreground color
option add *TCombobox*Listbox.selectBackground color
option add *TCombobox*Listbox.selectForeground color
Then to change the font inside the box (when the drop down isn't present) add font='font_style' to your code.
So in my case I had:
class CreateProfile(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg='dodgerblue4')
label = tk.Label(self, text="Create Profile", font=large_font, bg='dodgerblue4', fg='deepskyblue')
label.grid(columnspan=10, row=0, column=0, pady=5, padx=5)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
self.option_add("*TCombobox*Listbox*Background", "dodgerblue")
self.option_add("*TCombobox*Listbox*Font", "pirulen")
self.list_c = ttk.Combobox(self, values=("1", "2", "3", "4"), font='pirulen')
self.list_c.grid(row=1, column=1, pady=5, padx=5)
Make sure you also have the following imports:
import tkinter as tk
import tkinter.ttk as ttk
My issue is I'm only able to change the background color of the actual box (when the drop down isn't present). I'm still trying to figure out how to change the font color (foreground doesn't work for me), and the color of the box itself. So if anybody could add to this answer that would be great!
I know that this question is half a year old, but I had a similar issue and managed to solve it. For changing the color of a ttk Combobox popdown frame you can use the following code:
# these imports are indeed only valid for python 3.x
import tkinter as tk
import tkinter.ttk as ttk
# for python 2.x the following import statements should work:
# import Tkinter as tk
# import ttk
root = tk.Tk()
# adding the options to the root elements
# (all comboboxes will receive this options)
root.option_add("*background", "#444444"),
root.option_add("*foreground", "#999999"),
# create a combobox
ttk.Combobox(root, values=["Banana", "Coconut", "Strawberry"]).pack()
root.mainloop()
I'm not sure whether I understand the styling mechanisms of tk correctly.
At least the above code works for me on Python 3.2 and Python 3.4

How can I connect a StringVar to a Text widget in Python/Tkinter?

Basically, I want the body of a Text widget to change when a StringVar does.
Short version is, you can't. At least, not without doing extra work. The text widget doesn't directly support a variable option.
If you want to do all the work yourself it's possible to set up a trace on a variable so that it keeps the text widget up to date, and you can add bindings to the text widget to keep the variable up to date, but there's nothing built directly into Tkinter to do that automatically.
The main reason this isn't directly supported is that the text widget can have more than just ascii text -- it can have different fonts and colors, embedded widgets and images, and tags.
I thought of this while working on a project. It's actually really simple:
import tkinter as tk
from tkinter import *
from tkinter.scrolledtext import ScrolledText
def get_stringvar(event):
SV.set(ST1.get("1.0", END))
ST2.replace("1.0", END, SV.get())
root = tk.Tk()
SV = StringVar()
ST1 = ScrolledText(root)
ST1.pack()
ST1.bind('<KeyRelease>', get_stringvar)
ST2 = ScrolledText(root)
ST2.pack()
root.mainloop()

Categories