Is it possible to have a standard style for a widget? - python

I want all of the buttons in my gui to have the same style. Right now I manually write in the attributes I want but it takes up so much space. Also, if I wanted to change the style I would have to go to every single button. Is it possible to have a style i define once and then reference it when making all buttons?
Something like the following:
basic_style =
{'background': 'blue',
'foreground':'white',
'font':'Helvetica 8 bold'}
self.btn = tk.Button(text = 'Hello', style = basic_style)
I know its possible to do something like this:
self.btn['text'] = 'Bye'
but that still doesn't help me much.

There are at least four ways I can think of to do this: using the option database, passing dictionaries of options, using ttk, and creating custom classes.
Using the option database
There are several ways to accomplish what you want. The original support for this is based on the X11 windowing system option database. Every widget has a class, and every class can have it's options set.
You do this through the option_set method of the root window (as well as option_readfile), specifying a pattern, and the default value.
Example:
import tkinter as tk
root = tk.Tk()
root.option_add("*Font", "Helvetica 8 bold")
root.option_add("*Background", "blue")
root.option_add("*Foreground", "white")
button1 = tk.Button(root, text="Hello", command=lambda: print("Hello"))
button2 = tk.Button(root, text="World", command=lambda: print("world"))
button1.pack()
button2.pack()
root.mainloop()
Note: you must do this after creating the root window but before creating any widgets you want to be affected by this.
How to use the option database can be a bit complex, though it does make it possible to compute entire themes if you're willing to take the time to do it. One of the best -- though oldest -- descriptions comes straight from one of the core developers of tcl/tk here: http://www.cs.man.ac.uk/~fellowsd/tcl/option-tutorial.html. This requires a little bit of mental gymnastics to translate the tcl/tk code to python/tkinter, but the python docs describe how to do that (see Mapping basic tk into tkinter
Using dictionaries.
You can indeed pass in a dictionary of options, with a couple of small caveats. This dictionary must be the first argument after the parent, and it must be before any other keyword arguments.
Example:
import tkinter as tk
basic_style = {'background': 'blue', 'foreground': 'white', 'font': 'Helvetica 8 bold'}
root = tk.Tk()
button1 = tk.Button(root, basic_style, text="Hello", command=lambda: print("Hello"))
button2 = tk.Button(root, basic_style, text="World", command=lambda: print("world"))
button1.pack()
button2.pack()
root.mainloop()
I don't if this is documented anywhere, but you can see that it's supported by looking at the actual tkinter code.
Using the ttk widgets
"ttk" stands for "themed tk". The whole idea was to re-implement tk with the ability to apply themes. ttk has most of the same widgets as tkinter, plus a few that it doesn't have (for example, the treeview widget).
Example:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.configure('Custom.TButton',
background="blue", foreground="white",
font='Helvetica 8 bold')
button1 = ttk.Button(root, text='Hello', style='Custom.TButton')
button2 = ttk.Button(root, text='Hello', style='Custom.TButton')
button1.pack()
button2.pack()
root.mainloop()
Unfortunately, there's almost no good documentation on how to configure the themes. It's a bit confusing and non-standard, but the results can be quite polished if you're willing to put in the effort.
The best resource for learning how to create themes is in the Styles and Themes section of tkdocs.com
Creating custom classes
Finally, a fourth option is to create custom classes. Your classes can inherit from the built-in classes and force options to be set if not provided.
Example:
import tkinter as tk
class CustomButton(tk.Button):
def __init__(self, *args, **kwargs):
kwargs.setdefault("background", "blue")
kwargs.setdefault("foreground", "white")
kwargs.setdefault("font", "Helvetica 8 bold")
super().__init__(*args, **kwargs)
root = tk.Tk()
button1 = CustomButton(root, text="Hello", command=lambda: print("Hello"))
button2 = CustomButton(root, text="World", command=lambda: print("world"))
button1.pack()
button2.pack()
root.mainloop()

Yes, it's even easier than you imagine. Just use dictionary unpacking:
basic_style =
{'background': 'blue',
'foreground':'white',
'font':'Helvetica 8 bold'}
self.btn = tk.Button(text = 'Hello', **basic_style)
Another popular option is to make a subclass with the style you want, and perhaps other features such as tooltips or style dependant on values or anything else.
class Justine(tk.Button):
def __init__(self, master=None, **kwargs):
tk.Button.__init__(self, master,
background = 'blue',
foreground = 'white',
font = 'Helvetica 8 bold',
**kwargs)
self.btn = Justine(text = 'Hello')

Related

How to separate data from GUI when using Tkinter

I'm a bit stumped with the proper way to design with Tkinter. I've read dozens of Tkinter examples but since most of them describe simple and specific cases I'm having trouble with finding the right global design for my script. I'm trying to separate as much as possible the GUI code from the data model (I'm working on a very simple card game).
My main problem is how to access Tkinter elements globally, from different functions and classes. Currently I am building the entire interface inside a large gui_build() function, right after the definitions for my data classes. This function is called from a main() function along with all the data initialisation stuff. Problem is, even if I return the Tk root object from gui_build() so that main() can pass it around, I'm having trouble accessing Tk elements when needed.
For example, I have several callback functions for different Tk buttons which are in the global scope. If one of those needs to check the state of a radio button, it's apparently impossible to find it in the Tk root hierarchy by name, and passing references to radio buttons all over the place seems very inelegant.
I can sort of make it work by declaring radio buttons globally but this defeats the purpose of keeping all the GUI code in the same place.
Wondering what could be considered as a "best practice" before trying to reinvent the wheel.
Thank you.
I have been making Tkinter interfaces for 2 years, now. I was facing the same problem initially. From my experience I would suggest that you define a class which inherits Tk root widget and then assign you element widgets as attributes of that class, also the callback functions as functions of that class. That will make accessing global widgets(button) and functions easy. These widgets and functions are global inside the class definition. It makes them easy to access. You can follow following template
from tkinter import *
class Interface(Tk):
def __init__(self, title):
Tk.__init__(self)
self.title(title)
self.build()
def build(self):
self.UserNameLabel = Label(self, text="User Name")
self.UserNameLabel.grid(row=0, column=0, sticky=E, pady=10)
self.UserNameEntry = Entry(self)
self.UserNameEntry.grid(row=0, column=1, sticky=W, pady=10)
self.PassWordLabel = Label(self, text="Password")
self.PassWordLabel.grid(row=1, column=0, sticky=E, pady=10)
self.PassWordEntry = Entry(self, show='*')
self.PassWordEntry.grid(row=1, column=1, sticky=W, pady=10)
self.status = Label(self, text='Please enter User Name and Password')
self.status.grid(row=2, column=0, columnspan=2, pady=10)
self.LoginButton = Button(self, text='Login', width=20, command=self.checkCreadentials)
self.LoginButton.grid(row=3, column=0, columnspan=2, pady=10)
def checkCreadentials(self):
if (self.UserNameEntry.get().strip() == 'username') and (self.PassWordEntry.get()=='password'):
self.status['text']= 'Welcome !'
else:
self.status['text']= 'Invalid User Name or Password!!!'
self.bell()
Interface('Login').mainloop()

Prevent scrolledtext from taking up entire parent window disallowing other widgets from showing up

So I am actually writing a simple GUI program which makes use of ScrolledText widget from tkinter.scrolledtext module.
The problem is this ScrolledText widget seems to take up the complete space available in the parent window. It disallows me from putting in any other widget in the same parent window. I have tried using both grid and pack GeoManagers (i know place isn't very useful in all cases), but the other widgets won't show up (neither above the scrolledtext widget nor below it).
HERE IS THE CODE--
import tkinter as tk
import tkinter.scrolledtext as sct
win2 = tk.Tk()
win2.geometry('1150x680')
win2.wm_geometry('+80+20')
txtbox = sct.ScrolledText(win2, width=500, height=350, bg='#fff', fg='#00f')
txtbox.grid(row=0, column=0)
txt = '<ABOUT 60 Lines TEXT HERE>'
txtbox.insert(1.0, txt)
txtbox.configure(state=tk.DISABLED)
tk.Button(win2, text='Got It', command=win2.destroy).grid(row=1, column=0)
This code is actually a part of a static method (i don't think makes a difference). When this is run the only thing visible on the screen is the scrolledtext widget with those 60 lines (i have tried it with 2 lines as well - still doesn't work).
The same happens when using pack().
To my surprise the only thing i could find in documentation is this::
ScrolledText Documentation
I don't know what I am missing here so please suggest me a way around this.
Thanks You :)
Solution with grid
The problem is the configuration of the grid: by default, the grid cells expand to fit the content. In your case the text widget is so big that the button in the row below is out of the screen. To fix that, you need to configure the first row and column to stretch with the GUI:
win2.rowconfigure(0, weight=1)
win2.columnconfigure(0, weight=1)
and make the text widget fill the cell, using the sticky option:
txtbox.grid(row=0, column=0, sticky='ewns')
This way the text widget will adapt to the window size and not the other way around.
Full code:
import tkinter as tk
import tkinter.scrolledtext as sct
win2 = tk.Tk()
win2.geometry('1150x680')
win2.wm_geometry('+80+20')
win2.rowconfigure(0, weight=1)
win2.columnconfigure(0, weight=1)
txtbox = sct.ScrolledText(win2, width=500, height=350, bg='#fff', fg='#00f')
txtbox.grid(row=0, column=0, sticky='ewns')
txt = '<ABOUT 60 Lines TEXT HERE>'
txtbox.insert(1.0, txt)
txtbox.configure(state=tk.DISABLED)
tk.Button(win2, text='Got It', command=win2.destroy).grid(row=1, column=0)
Alternative method, using pack
You can use pack with the options fill='both' and expand=True to achieve the same result as with grid. In this case, the additional trick is to pack the button first to ensure that it has enough space to show in the window. Code:
import tkinter as tk
import tkinter.scrolledtext as sct
win2 = tk.Tk()
win2.geometry('1150x680')
win2.wm_geometry('+80+20')
tk.Button(win2, text='Got It', command=win2.destroy).pack(side='bottom')
txtbox = sct.ScrolledText(win2, width=500, height=350, bg='#fff', fg='#00f')
txtbox.pack(fill='both', expand=True)
txt = '<ABOUT 60 Lines TEXT HERE>'
txtbox.insert(1.0, txt)
txtbox.configure(state=tk.DISABLED)

Python - is there any way to expand the resolution ratio for existing Tkinter Tk() and Toplevel() widget? (zooming-in)

I just made an app using python and tkinter widgets.
There are Labels, Frames, Buttons, etc in the Tk and Toplevel widgets.
However, it includes thousands of codes and its really annoying to resize every widgets when I support multiple resolutions.
Is there any way to expand the resolution ratio for existing Tkinter Tk() and Toplevel() widget and their child widgets? (zooming-in)
If not, what would be the best approach to support multiple resolutions of a python app with the same ratio?
Any help would be much appreciated, sorry for bad English.
Yes, this is possible however it depends on the geometry manager you have used in your program.
For the .pack() method (which is arguably the simplest geometry method for "intelligent" GUI designs) you can use a range of attributes on when you declare .pack() on the widget. These attributes include (but are not limited to) fill, expand, anchor, padx, pady, etc.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.pack(fill="both", expand=True)
btn2.pack(fill="both", expand=True)
btn3.pack(fill="both", expand=True)
root.mainloop()
For the .grid() method you will need to make use of the functions Grid.columnconfigure() and Grid.rowconfigure. Both of these have the attribute weight which determines which rows and columns should be given priority for assignment of extra space if more becomes available in the window. Setting all rows and columns to have a weight of 1 means they will all be given space equally. You will also need to use the sticky attribute when declaring .grid() on the widgets.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
for column in range(3):
Grid.columnconfigure(root, column, weight=1)
for row in range(1):
Grid.rowconfigure(root, row, weight=1)
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.grid(column=0, row=0, sticky=N+S+E+W)
btn2.grid(column=1, row=0, sticky=N+S+E+W)
btn3.grid(column=2, row=0, sticky=N+S+E+W)
root.mainloop()
Using .place() would be a lot more difficult, you would need to have a function setup which would trigger on every window resize event which would calculate the size that the buttons need to expand to.
This would look something like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.button = Button(self.root, text="Button")
self.button.place(relx=0.5, rely=0.5, anchor="center")
self.root.bind("<Configure>", self.resize)
def resize(self, *args):
self.button.configure(width=self.root.winfo_width(), height=self.root.winfo_height())
root = Tk()
App(root)
root.mainloop()
Subjectively speaking, .pack() tends to be easier, however this all comes down to how much effort you're willing to put in to implement this with your current program.
Can't comment so I add a short tip to the detailed Ethan answer. You can design most of the GUIs in tkinter with either pack, grid or a combination of both (by placing frames on a window with one of them, and using either grid or pack inside of each frame, to "place" the widgets). You can tune the configurations for proper location and size when the window resizes. Keep placer use for special cases (like overlaying some widget on the top of others)

Making themed TK button have a border

With the default theme on Linux, I can't get the button to have a border. Here's my attempt:
from tkinter import *
from tkinter.ttk import *
tk = Tk()
style = Style()
style.configure('TButton.border', background='red')
button = Button(tk, text='Hello')
button.pack()
mainloop()
I have found theming very difficult because it is not clear what I can change. Sometimes changes seem to do nothing.
It depends on what theme you're using; not all themes associate all widgets with all the elements they might have, and some (e.g., the OSX native theme, aqua) really very tightly control what the look and feel of many elements are (since the theme engine in that case delegates to the native drawing code). It's entirely possible that the theme you're using just doesn't permit red borders at all.
Try switching to a different themeā€¦
ttk.Style().theme_use('clam')
Simple proposal ... if you really want a border. Understand it should not be satisfactory.
import Tkinter as tk
root = tk.Tk()
def nothing(event=None):
print "click"
bgbutton= tk.Button(root, )
bgbutton.pack()
bgbutton.configure(relief=tk.GROOVE, borderwidth=5, background="#2244FF", activebackground="#FF0000", highlightcolor="#00FF00")
button= tk.Button(bgbutton, text="Glance at my border", command=nothing)
button.pack()
button.configure(relief=tk.GROOVE, borderwidth=2)
root.mainloop()

How to copy data from one Tkinter Text widget to another?

from Tkinter import *
root = Tk()
root.title("Whois Tool")
text = Text()
text1 = Text()
text1.config(width=15, height=1)
text1.pack()
def button1():
text.insert(END, text1)
b = Button(root, text="Enter", width=10, height=2, command=button1)
b.pack()
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
text.config(width=60, height=15)
text.pack(side=LEFT, fill=Y)
scrollbar.config(command=text.yview)
text.config(yscrollcommand=scrollbar.set)
root.mainloop()
How can I add the data from a text widget to another text widget?
For example, I'm trying to insert the data in text1 to text, but it is not working.
You are trying to insert a Text reference at the end of another Text widget (does not make much sense), but what you actually want to do is to copy the contents of a Text widget to another:
def button1():
text.insert(INSERT, text1.get("1.0", "end-1c"))
Not an intuitive way to do it in my opinion. "1.0" means line 1, column 0. Yes, the lines are 1-indexed and the columns are 0-indexed.
Note that you may not want to import the entire Tkinter package, using from Tkinter import *. It will likely lead to confusion down the road. I would recommend using:
import Tkinter
text = Tkinter.Text()
Another option is:
import Tkinter as tk
text = tk.Text()
You can choose a short name (like "tk") of your choice. Regardless, you should stick to one import mechanism for the library.

Categories

Resources