How to define different styles/themes with tkinter - python

I am learning tkinter and have noticed something with using styles when trying to have different windows or frames with styles.
In the code example I provided, if I change the style attributes on frame2, they also show in frame1. If I change the order of execution then the reverse happens.
How can I define styles that are specific to each frame or window?
Thanks
Dan
from tkinter import *
import tkinter as tk
from tkinter import ttk
def showframe1():
f1_columns = ('#1', '#2')
f1_tree = ttk.Treeview(frame1, columns=f1_columns, show='headings')
f1_style = ttk.Style()
f1_style.theme_use("default")
f1_style.configure("Treeview",
background="white",
foreground="black",
rowheight=25 # Height of each row in tree
)
f1_tree.heading('#1', text='First Name')
f1_tree.heading('#2', text='Last Name')
f1_tree.column("#1", width=150)
f1_tree.column("#2", width=150)
# generate sample data
contacts = []
for n in range(1, 100):
contacts.append((f'first {n}', f'last {n}'))
# adding data to the frame1 tree
for contact in contacts:
f1_tree.insert('', tk.END, values=contact)
f1_tree.grid(row=0, column=0, sticky='nsew')
def showframe2():
f2_columns = ('#1', '#2', '#3')
f2_tree = ttk.Treeview(frame2, columns=f2_columns, show='headings')
f2_style = ttk.Style()
f2_style.theme_use("default")
f2_style.configure("Treeview",
background="lightblue",
foreground="black",
rowheight=25 # Height of each row in tree
)
f2_tree.heading('#1', text='First Name')
f2_tree.heading('#2', text='Last Name')
f2_tree.heading('#3', text='Email')
f2_tree.column("#1", width=150)
f2_tree.column("#2", width=150)
f2_tree.column("#3", width=250)
# generate sample data
contacts = []
for n in range(1, 100):
contacts.append((f'first {n}', f'last {n}', f'email{n}#example.com', f'email{n}#example.com'))
# adding data to the frame2 tree
for contact in contacts:
f2_tree.insert('', tk.END, values=contact)
f2_tree.grid(row=0, column=0, sticky='nsew')
def exit_root(event):
root.destroy()
root = tk.Tk()
root.title('Frame Styles Example')
root.geometry('600x800')
frame1 = Frame(root, bd=5, relief='sunken')
frame1.grid(row=0, column=0, padx=20, pady=10, sticky=N + W)
frame2 = Frame(root, bd=5, relief='sunken')
frame2.grid(row=1, column=0, padx=20, pady=10, sticky=N + W)
showframe1()
showframe2()
root.bind('<Return>', exit_root)
# run the app
root.mainloop()

I found a good tutorial at https://www.pythontutorial.net/tkinter/ttk-style/
I needed to create a custom Treeview style for the additional frame(s). I just created two custom styles.
f1_style.configure("f1.Treeview",....)
f2_style.configure("f2.Treeview",....)
Then assign the style to each Treeview definition.
ttk.Treeview(frame1, columns=f1_columns, show='headings',style='f1.Treeview')
ttk.Treeview(frame2, columns=f2_columns, show='headings',style='f2.Treeview')
With this, I can control the style of each frame.

Related

How to add tkinter Scrollbar with Grid?

I am new to Python programming. I started learning the language a few weeks ago. I am creating a "Temperature Converter" application using "tkinter". I am trying to add a scroll bar to the application but unable to do so. Please forgive me if I have not written something correctly. Please do let me know my mistake and I will definitely rectify it. I won't repeat the mistake in the future.
Thank you very much for your time. Following is my code:
import tkinter as tk
from tkinter.messagebox import showerror
from tkinter import *
window = tk.Tk()
window.title("Temperature Converter")
window.geometry('420x400')
window.option_add('*Font', '25')
window.option_add('*Font', 'Tahoma')
window.configure(bg='#f8f8f8')
window.resizable(0, 0)
def fahrenheit_to_celsius():
fahrenheit = fahrenheit_to_celsius_entry_temperature.get()
if fahrenheit == '':
showerror(title='Empty Field Error', message="Field is empty. Please input number.")
else:
try:
celsius = (5 / 9) * (float(fahrenheit) - 32)
fahrenheit_to_celsius_label_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"
except:
showerror(title='Input Error', message="Invalid input. Please input only number.")
fahrenheit_to_celsius_label_heading = tk.Label(text="Fahrenheit to Celsius:", bg='#f8f8f8', fg='Black')
# Create the Fahrenheit entry frame with an Entry widget and label in it
fahrenheit_to_celsius_frame_entry = tk.Frame(master=window)
fahrenheit_to_celsius_entry_temperature = tk.Entry(master=fahrenheit_to_celsius_frame_entry, width=10)
fahrenheit_to_celsius_label_temperature = tk.Label(master=fahrenheit_to_celsius_frame_entry, text="\N{DEGREE FAHRENHEIT}")
def fahrenheit_to_celsius_clear():
fahrenheit_to_celsius_entry_temperature.delete(0, tk.END)
fahrenheit_to_celsius_label_result.config(text="\N{DEGREE CELSIUS}")
fahrenheit_to_celsius_button_clear = tk.Button(window, text="Clear", command=fahrenheit_to_celsius_clear, bg='#c82333', fg='White')
fahrenheit_to_celsius_label_heading.grid(row=0, column=0, columnspan=3, sticky="w")
fahrenheit_to_celsius_entry_temperature.grid(row=1, column=0, sticky="ew")
fahrenheit_to_celsius_label_temperature.grid(row=1, column=1, sticky="ew")
fahrenheit_to_celsius_button_clear.grid(row=1, column=2, sticky="w")
fahrenheit_to_celsius_button_convert = tk.Button(master=window, text="Convert", command=fahrenheit_to_celsius, bg='#218838', fg='White')
fahrenheit_to_celsius_label_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
fahrenheit_to_celsius_label_heading.grid(row=0, padx=10, pady=10)
fahrenheit_to_celsius_frame_entry.grid(row=1, column=0, padx=12, sticky="ew")
fahrenheit_to_celsius_button_convert.grid(row=1, column=1, padx=0, sticky="ew")
fahrenheit_to_celsius_button_clear.grid(row=1, column=2, padx=(5,0), sticky="ew")
fahrenheit_to_celsius_label_result.grid(row=1, column=3, padx=0, sticky="w")
Adding a scrollbar
Scrollbars can't be added for a tkinter frame, but you can add them for a canvas.
procedure
Put all your widgets in a tkinter canvas
Configure canvas scrollregion
Add a scrollbar for the canvas
Example
base = tk.Canvas(window)
base.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
# change scrollregion as changes are made to the canvas
base.bind('<Configure>', lambda e: base.configure(scrollregion=base.bbox(tk.ALL)))
# add scrollbar for the canvas
scroll = tk.Scrollbar(window, command=base.yview)
scroll.pack(side=tk.LEFT, fill=tk.Y)
base.config(yscrollcommand=scroll.set)
# sample widgets
for i in range(2, 20):
btn = tk.Button(base, text=f"Button {i}")
base.create_window(200, i*30, window=btn)
For your code, you need to :
Create a scrollable canvas following above instructions
Instead of directly using grid, use canvas.create_window(x, y, window) for each widget

Drop down list dependent from another drop down tkinter

I have one list with car brands in it and a second list with model names from these brands. I want to have two dropdown lists. First you select the brand and in the second dropdown you can select the model. But just models from the selected brand. I got the following code.
import tkinter as tk
brands = ["Bugatti","VW","Opel","Porsche"]
models = [["Veyron","Chiron"],
["Golf","Passat","Polo","Caddy"],
["Insignia","Corsa","Astra"],
["Taycan","Cayenne","911"]]
root = tk.Tk()
canvas = tk.Canvas(root, height=500, width= 500, bg="white")
canvas.pack()
tkvar = tk.StringVar(root)
tkvar.set('Choose')
popupMenu1 = OptionMenu(canvas, tkvar, *brands)
popupMenu1.pack()
def change_dropdown(*args):
print("Chosen brand " + tkvar.get())
for i in range(len(brands)):
if tkvar.get() == brands[i]:
print(models[i])
tkvar.trace('w', change_dropdown)
root.mainloop()
How do i now create a second dropdown with the information from the list models. Thanks for your help.
You can use Combobox to make dependent dropdown list
import tkinter
from tkinter import ttk
root = tkinter.Tk()
'''
widgets are added here
'''
brands = ["Bugatti","VW","Opel","Porsche"]
models = [["Veyron","Chiron"],
["Golf","Passat","Polo","Caddy"],
["Insignia","Corsa","Astra"],
["Taycan","Cayenne","911"]]
car_brand = ttk.Combobox(root, width=37, value=(brands))
car_brand.grid(row=3, column=1, columnspan=2, padx=10, pady=2, sticky='w')
def callback(eventObject):
abc = eventObject.widget.get()
car = car_brand.get()
index=brands.index(car)
car_model.config(values=models[index])
car_model = ttk.Combobox(root, width=37)
car_model.grid(row=4, column=1, columnspan=2, padx=10, pady=2, sticky='w')
car_model.bind('<Button-1>', callback)
root.mainloop()
import tkinter as tk
brands = ["Bugatti","VW","Opel","Porsche"]
models = [["Veyron","Chiron"],
["Golf","Passat","Polo","Caddy"],
["Insignia","Corsa","Astra"],
["Taycan","Cayenne","911"]]
root = tk.Tk()
canvas = tk.Canvas(root, height=500, width= 500, bg="white")
canvas.pack()
tkvar = tk.StringVar(root)
tkvar.set('Choose')
tkvar2 = tk.StringVar(root)
tkvar2.set('Model')
popupMenu1 = tk.OptionMenu(canvas, tkvar, *brands)
popupMenu1.pack()
popupMenu2 = tk.OptionMenu(canvas, tkvar2, [])
popupMenu2.pack()
def change_dropdown(*args):
print("Chosen brand " + tkvar.get())
for i in range(len(brands)):
if tkvar.get() == brands[i]:
popupMenu2["menu"].delete(0, "end")
for item in models[i]:
popupMenu2['menu'].add_command(label=item, command=tk._setit(tkvar2, item))
tkvar.trace('w', change_dropdown)
root.mainloop()

How to set a specified positions for RadioBox and Slider steps with tkinter

I am new with tkinter.
I am trying to make GUI and for the moment I made 3 RadioBoxe and a slider. and this what I got:
The problem that I want the 3 RadioBox to be in the same line and bellow them the slider. Is it possibe?
and I want the slider to go through odd numbers only (1,3,5,7,9 ...)
This is my code so far:
window = Tk()
window.title("Welcome to LikeGeeks app")
panelA = None
menu = Menu(window)
new_item = Menu(menu)
new_item.add_command(label='Open File', command=open_file)
new_item.add_separator()
new_item.add_command(label='Save File')
new_item.add_separator()
new_item.add_command(label='Exit')
menu.add_cascade(label='File', menu=new_item)
window.config(menu=menu)
label_blur = Label(window, text="Blurring")
label_blur.pack(anchor="w", padx=10, pady=10)
blur_rad1 = Radiobutton(window, text='Average', value=0)
blur_rad2 = Radiobutton(window, text='Gaussian', value=1)
blur_rad3 = Radiobutton(window, text='Median', value=2)
blur_rad1.pack(anchor="w", padx=10, pady=0)
blur_rad2.pack(anchor="w", padx=10, pady=0)
blur_rad3.pack(anchor="w", padx=10, pady=0)
blur_slide = Scale(window, from_=1, to=31, length=600,tickinterval=5, orient=HORIZONTAL)
blur_slide.pack(anchor="w", padx=10, pady=10)
window.mainloop()
There are two simple solutions: use grid so that you can specify the row and columns, or continue to use pack, but place the radiobuttons in a separate frame.
Since you're already using pack, it's easiest to use the second method.
Create a frame for the buttons, and use pack to add it to the window where you want it to appear:
button_frame = Frame(window)
button_frame.pack(side="top", fill="x")
Make the buttons children of the frame:
blur_rad1 = Radiobutton(button_frame, ...)
blur_rad2 = Radiobutton(button_frame, ...)
blur_rad3 = Radiobutton(button_frame, ...)
Use pack to lay out the buttons left-to-right:
blur_rad1.pack(side="left", expand=True)
blur_rad2.pack(side="left", expand=True)
blur_rad3.pack(side="left", expand=True)

Tkinter how to size fonts and keep alignment

I'm writing an app that needs to be zoomable. Using the default system fonts, "TkDefaultFont and TkTextFont" I increase their sizes and I get the results I want sort of. The problem is after sizing the alignment is thrown off between the field label and field widget. I have tried applying update_idletasks() but it does nothing. Moving the mouse over the widget fixes the problem as see in the video. If you run the example below go the the size spinbox and change the size to view the issue.
My dev system is Linux, Desktop KDE, I don't know if this is a OS issue.
A link to a short video of the issue.
Video of Alignment Issue.
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
root = tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(99, weight=1)
frame = ttk.Frame(root)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
cbo = ttk.Combobox(frame)
cbo.config(
values=('Test 1', 'Test 2', 'Test 3')
)
cbo.set(value='Test 1')
ent_var = tk.StringVar()
ent = ttk.Entry(frame, textvariable=ent_var)
ent_var.set('Test')
lb_size = ttk.Label(frame, text='size')
spn = ttk.Spinbox(frame, values=tuple(range(1, 101)))
font1 = tkfont.nametofont('TkDefaultFont')
font2 = tkfont.nametofont('TkTextFont')
lbl_field_name = tk.Label(frame, text='Field Name')
lbl_field_name.grid()
def size(e):
cfg = font1.actual()
cfg['size'] = e.widget.get()
font1.configure(**cfg)
font2.configure(**cfg)
spn.bind('<<Increment>>', size)
spn.bind('<<Decrement>>', size)
cbo.grid(row=0, column=1, sticky=tk.NSEW)
ent.grid(row=0, column=2, sticky=tk.NSEW)
lb_size.grid(row=0, column=3, sticky=tk.NSEW)
spn.grid(row=0, column=4, sticky=tk.NSEW)
frame.grid(sticky=tk.NSEW)
root.mainloop()
The only way I have found to avoid this issue is to change the font from each entry's configure() method. I guess the easier way will be to put all the entries in a list, see code below.
I have also noticed that size() uses the previous value of the spinbox not the one after the increment/decrement. To avoid that, I suggest you to use the command option of the spinbox instead of the bindings to <<Increment>> and <<Decrement>> (see code below).
Finally, you do not need to retrieve the whole font settings to update the size, you can simply do font1.configure(size=<newsize>).
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
root = tk.Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(99, weight=1)
frame = ttk.Frame(root)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
entries = [] # list of all entries
cbo = ttk.Combobox(frame)
cbo.config(
values=('Test 1', 'Test 2', 'Test 3')
)
cbo.set(value='Test 1')
entries.append(cbo)
ent_var = tk.StringVar()
ent = ttk.Entry(frame, textvariable=ent_var)
ent_var.set('Test')
entries.append(ent)
def size():
size = spn.get() # get current spinbox's value
font1.configure(size=size)
font2.configure(size=size)
for e in entries: # update font in all entries
e.configure(font=font2)
lb_size = ttk.Label(frame, text='size')
# use the command option to update the font size
spn = ttk.Spinbox(frame, command=size, values=tuple(range(1, 101)))
font1 = tkfont.nametofont('TkDefaultFont')
font2 = tkfont.nametofont('TkTextFont')
lbl_field_name = ttk.Label(frame, text='Field Name')
lbl_field_name.grid()
cbo.grid(row=0, column=1, sticky=tk.NSEW)
ent.grid(row=0, column=2, sticky=tk.NSEW)
lb_size.grid(row=0, column=3, sticky=tk.NSEW)
spn.grid(row=0, column=4, sticky=tk.NSEW)
frame.grid(sticky=tk.NSEW)
root.mainloop()

Secret tkinter widget?

I got this problem. when I write secretframe() I get a empty box any ideas to fix this? Also how can I put a little space between the 2 frames.
I'm new to tkinter and any tips is gladly appreciated.
import tkinter as tk
import time
import math
##----------Functions
root = tk.Tk()
def Pi():
pi = math.pi
x = list(str(pi))
map(int,x)
print(math.pi)
def Unfinished():
print("This Button is not finished")
def secretframe(frame):
secrett = tk.Toplevel()
sframe = tk.Frame(secrett, height="300", width="300", bg="PeachPuff")
sLabel = tk.Label(sframe, text="Secret")
sframe.grid
sLabel.grid
##Defining
##Frames
frame = tk.Frame(root, height="300", width="300", bg="Black")
Mframe = tk.Frame(root, height="300", width="300", bg="White")
##WIdgets
Label = tk.Label(frame, text="R1p windows", bg="Black", fg="White")
Label2 = tk.Label(Mframe, text="Math magic", bg="White", fg="Black")
Knapp = tk.Button(frame, text="ok(ADMIN)", fg="White", bg="Black", font="monospace")
PIKnapp = tk.Button(Mframe, text="Pi(WIP)(UNFINISHED)", bg="White", fg="Black", font="Times")
##Config and bindings
Knapp.config(command=Unfinished)
PIKnapp.config(command=Pi)
##Packing
## Frame 1
Label.grid()
frame.grid()
Knapp.grid()
## Frame 2
Label2.grid()
Mframe.grid()
PIKnapp.grid()
You've forgotten the brackets. Try:
sframe.grid ()
sLabel.grid ()

Categories

Resources