After taking advice from Reinderien, I have completely rewritten the code for my Window()
subclass.
EDIT *reason for posting was unclear:
I would like for someone to suggest improvements or other features to add to this subclass. Either that, or give a general review.
EDIT *compatibility bug:
In root.config()
, I changed root.title
to root.title_string
because it replaced the title()
function, which is used by other tkinter widget functions (eg. tkinter.simpledialog.askstring()
)
Features:
settings-file interaction for persistent application settings
fullscreen functionality using F11, can be persistent or defaulted
removes the need for multiple
root.configure()
statementsremoves the need for
root.title()
androot.row/columnconfigure()
You can adjust the default window by calling it using the Window()
class, and then you can use two methods to adjust the settings file (which will be named settings.ini
by default).
root.set()
configures all of the software options found insettings.ini
in realtime.root.get()
returns a list with the settings you want to reference fromsettings.ini
you can supply multiple arguments to both of these methods
One thing that this subclass fixes is the need for multiple root.configure()
statements. It uses a new method, root.config()
, that loops through each keyword argument supplied and runs the appropriate configurations.
- this method also handles the
title()
androw/columnconfigure()
functions in this format:
root = Window()
root.config(bg = 'black', title = 'My Window', row = (0, 1), col = (0, 1))
root.mainloop()
This creates a basic window with a black background, a title ("My Window"), and row 0 and column 0 are set to weight = 1.
Changes:
comment convention has been improved
Exceptions are printed if present
files are handled in a cleaner fashion
default
settings.ini
contents are now a dictionaryroot.get()
no longer requires pointing to a dictionary key, as it now returns a liststring-booleans are fixed as a result of the above change
less return functions are used
new
shortHand()
function to prevent ridiculous copy/pasting inroot.set()
args
inroot.get()
are now handled via a dictionaryno
except: pass
new options:
instaquit=False
andfullscreenable=True
The code for this Window()
class is below, along with an example setup. You can simply copy/paste this into your editor and it'll run as a standalone.
import tkinter as tk
from tkinter import messagebox
import os
class Window(tk.Tk):
def __init__(self, settings_file = './settings.ini'):
self.window = super(); self.window.__init__()
self.window.protocol('WM_DELETE_WINDOW', self.close)
# the settings file is where your window properties are held
# you can change the file name and type when you create a Window()
self.file = settings_file
# create your default settings file here
# give section markers a value of None
default_settings = {
'; ux settings': None,
'instaquit': False,
'; display settings': None,
'fullscreenable': True,
'fullscreen': False,
'resolution': '720x480',
'screenres': f'{self.winfo_screenwidth()}'
f'x{self.winfo_screenheight()}',
'resizex': False,
'resizey': False,
'; font settings': None,
'fontfamily': 'TkDefaultFont',
'fontsize': 12
}
# checking if settings file exists
with open(self.file, 'a+') as f:
def writeDefaults():
# write/format default settings from dictionary
for key, value in default_settings.items():
# if a value exists, get it as a string
if value != None: value = str(value)
# if value is None, section off with new lines
else: value = '\n'; key = '\n' + key
# case a: no value - surround in new lines
# case b: value - add equals sign and write pair
formatted_content = (
f'''{key}{
'=' if not
value.isspace()
else ''
}{value}'''
+ '\n'
)
# add formatted settings to self.settings and file
self.settings = [].append(formatted_content)
f.write(formatted_content)
# if the file is empty, write the default contents
if not bool(f.tell()):
writeDefaults()
# read file contents
f.seek(0)
self.settings = f.readlines()
#print('settings loaded:\n', ''.join(self.settings))
self.update()
# this function refreshes the window and its properties
def update(self):
with open(self.file) as f:
self.settings = f.readlines()
self.font = (self.get('fontf'),
self.get('fonts'))
self.window.resizable(self.get('rx'),
self.get('ry'))
self.window.geometry(self.get('res'))
self.window.overrideredirect(0)
# check if the window is fullscreen
if self.get('fs'):
self.window.geometry(self.get('screenres'))
self.toggleFullscreen(None)
# check if the window is allowed to switch modes
if self.get('fsable'):
self.window.bind('<F11>', self.toggleFullscreen)
else:
self.window.bind('<F11>', None)
# this function toggles the window to fullscreen or normal
def toggleFullscreen(self, event=None):
# if the user pressed F11
if event:
if self.get('fs'):
# set fullscreen to False
self.set(fs = False)
self.window.overrideredirect(0)
else:
# set fullscreen to True
self.set(fs = True)
self.window.overrideredirect(1)
self.window.geometry(self.get('screenres') + '+0+0')
# if the toggle was hard-coded
else:
if self.get('fs'):
self.window.overrideredirect(1)
self.window.geometry(self.get('screenres') + '+0+0')
else:
self.window.overrideredirect(0)
self.window.update()
# last-minute cleanup before closing the window
def close(self, event=None):
# instaquit not recommended for most programs, but the option is there
if not self.get('iquit'):
if messagebox.askokcancel('Exit Program',
'Are you sure you want to exit?'):
self.window.destroy()
self.window.destroy()
#
# set the values in the settings file
def set(self, **kwargs):
# shorthands for the set command
def shortHand(short_name, long_name):
if short_name in kwargs:
kwargs[long_name] = kwargs.pop(short_name)
shortHand('iquit', 'instaquit'); shortHand('fsable', 'fullscreenable')
shortHand('fs', 'fullscreen'); shortHand('res', 'resolution')
shortHand('rx', 'resizex'); shortHand('ry', 'resizey')
for i in kwargs:
for n, j in enumerate(self.settings):
if j.lower()[:j.find('=')] == i:
j = j.replace(
j[j.find('=')+1:],
str(kwargs[i]) + '\n'
)
self.settings[n] = j
with open(self.file, 'w') as f:
for i in self.settings:
f.write(i)
self.update()
# search through settings and pull chosen values
def get(self, *args):
# shorthands for the get command
shorthands = {
'iquit': 'instaquit',
'fsable': 'fullscreenable',
'fs': 'fullscreen',
'res': 'resolution',
'rx': 'resizex',
'ry': 'resizey',
'fontf': 'fontfamily',
'fonts': 'fontsize'
}
args = [shorthands.get(x, x) for x in args]
results = []
for value in args:
for i, j in enumerate(self.settings):
# check for matching text before equals sign
if j[:j.find('=')] == value:
# append value of match
results.append(j[j.find('=')+1:].strip())
if len(results) == 1:
return (results[0] if results[0]
not in ['True', 'False']
else eval(results[0]))
return results
def config(self, **kwargs):
# title the window
if 'title' in kwargs:
self.title_string = kwargs['title']
self.window.title(kwargs.pop('title'))
# row configure
if 'row' in kwargs:
self.window.rowconfigure(kwargs['row'][0],
weight = kwargs['row'][1])
kwargs.pop('row')
# column configure
if 'column' in kwargs or 'col' in kwargs:
try:
kwargs['column'] = kwargs.pop('col')
except Exception as e: print(e)
self.window.columnconfigure(kwargs['column'][0],
weight = kwargs['column'][1])
kwargs.pop('column')
self.window.configure(**kwargs)
# the following is an example to show how this class is used
root = Window()
root.set(fs=False, res='240x160',
rx=False, ry=False,
fsable=True, iquit=True,
fonts=32)
root.config(bg='black',
bd=12,
relief=tk.SUNKEN,
title='Window')
label = tk.Label(root, text='test', fg='white',
bg=root['bg'], font=root.font)
label.pack(fill = tk.BOTH, expand=True)
root.mainloop()
So many changes were made today that I didn't pick up on much that I seek to immediately improve, but you may find something I've missed, so please let me know!
As I said in my last post, I wanted to make this as a way of speeding up the rudimentary GUI creation process, and hopefully it can be used to get the ugly stuff out of the way faster.