Currently I am creating an app in Tkinter that creates, displays, and saves sticky notes. I am new to python so I keep trying to rewrite my classes so I have my code more organized but every time I try to create a separate class that inherits from my root class, it quits working properly and won't let me get variables from the inherited class. This is the working code, let me know if this looks terrible or if I just need to better understand how to structure this. It seems far too complicated. I've watched videos on OOP and classes and have been reading Programming Python for some help but I can't seem to impliment classes correctly in Tkinter although I have done general examples that work. The code if functioning fine right now but I am having a hard time adding functionality (allowing user to change font and default colors) with the way it is written.
import tkinter as tk
from tkinter import *
from tkinter.filedialog import asksaveasfile
tiny_font = ('Lucida Console', 8)
small_font = ('CalibriLight', 10)
medium_font = ('CalibriLight', 12)
large_font = ('Lucida Bright', 20)
class MyApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title(string="Stickies")
self.attributes('-alpha', 0.95)
self.configure(bg="#ffffff")
def makecolor(self):
orange = self.orange.get()
blue = self.blue.get()
purple = self.purple.get()
green = self.green.get()
yellow = self.yellow.get()
pink = self.pink.get()
if orange == '1':
return '#f7d9c4'
if blue == "1":
return '#c6def1'
if purple == "1":
return '#dbcdf0'
if green == "1":
return '#d0f4de'
if yellow == "1":
return '#fcf6bd'
if pink == "1":
return '#f2c6de'
else:
return '#FFFFFF'
def openSettings(self, *args):
settingsWindow = tk.Toplevel(self)
settingsWindow.wm_title('Settings')
Settingsframe = LabelFrame(
settingsWindow,
bg= '#ffffff',
text='Settings',
font=medium_font,
)
Filetypelabel = Label(
Settingsframe,
bg = '#ffffff',
text= 'Set default file save type:'
)
defaulttype = StringVar
Filetypeentry = Entry(
Settingsframe,
bg = '#ffffff',
textvariable = defaulttype,
)
Filetypelabel.pack(padx=10, pady=10)
Filetypeentry.pack(padx=10, pady=10)
saveSettings = Button(Settingsframe,text='Save Settings')
saveSettings.pack(padx=10, pady=10)
Settingsframe.pack(fill=BOTH, padx=10, pady=10)
def getscreensize(self):
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
screen_size = [screen_width, screen_height]
return screen_size
# function invoked when you click the create button ont the main frame
def create_sticky(self, *args):
newWindow = tk.Toplevel(self)
nameentry = self.nameentry.get()
if nameentry == "":
nameentry = 'Untitled'
newWindow.wm_title(nameentry)
Topnoteframe = LabelFrame(
newWindow,
bg= '#ffffff',
font=small_font,
)
saveButton = Button(Topnoteframe, text='Save', command=lambda: save_file())
saveButton.pack(pady=5, expand=True, side=LEFT)
hideButton = Button(Topnoteframe, text='Hide', command=lambda: hide_sticky())
hideButton.pack(pady=5, expand=True, side=LEFT)
showButton = Button(Topnoteframe, text='Show', command=lambda: show_sticky())
showButton.pack(pady=5, expand=True, side=LEFT)
Topnoteframe.pack(fill=BOTH, side=BOTTOM)
newwindowscreenwidth = (getscreensize(newWindow))[1] * 0.3
newwindowscreenheight = (getscreensize(newWindow))[1] * 0.3
stickydims = str(int(newwindowscreenwidth)) + "x" + str(int(newwindowscreenheight))
newWindow.geometry(stickydims)
T = Text(newWindow, height=20, width=40, bg=makecolor(self))
T.pack()
T.configure(font='Calibri')
def save_file():
txtcontent = T.get("1.0", 'end-1c')
f = asksaveasfile(initialfile=(nameentry), defaultextension=".txt",
filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")])
f.write(txtcontent)
newWindow.destroy()
def show_sticky():
myscreenwidth = (getscreensize(newWindow))[1] * 0.3
myscreenheight = (getscreensize(newWindow))[1] * 0.3
stickydims = str(int(myscreenwidth)) + "x" + str(int(myscreenheight))
newWindow.geometry(stickydims)
def hide_sticky():
stickydims = str(int(0)) + "x" + str(int(50))
newWindow.geometry(stickydims)
myscreenwidth = (getscreensize(self))[1]
myscreenheight = (getscreensize(self))[1]
myscreendims = str(int(myscreenwidth * 0.3)) + "x" + str(int(myscreenheight* 0.5))
myscreenposition = str("+" + str((myscreenwidth+70)) + "+" + str(10))
self.geometry(myscreendims + myscreenposition)
MyMainFrame = frame = LabelFrame(
self,
text='Stickies',
bg='#ffffff',
font=medium_font
)
MyNameFrame = frame_name = LabelFrame(
frame,
text='Name your note',
bg='#ffffff',
font=medium_font
)
MyColorFrame = frame_colors = LabelFrame(
frame,
text='Colors',
bg='#ffffff',
font=medium_font
)
self.nameentry = StringVar()
MyNameEntry = Entry(
frame_name,
textvariable = self.nameentry,
font=small_font,
bg='#f1f1f1'
)
self.purple = StringVar()
self.blue = StringVar()
self.orange = StringVar()
self.green = StringVar()
self.yellow = StringVar()
self.pink = StringVar()
color1 = Checkbutton(
frame_colors,
text = 'Purple',
bg = "#dbcdf0",
variable = self.purple,
font=small_font,
)
color1.deselect()
color2 = Checkbutton(
frame_colors,
text = 'Green',
bg = "#d0f4de",
variable = self.green,
font=small_font,
)
color2.deselect()
color3 = Checkbutton(
frame_colors,
text = 'Blue',
bg = "#c6def1",
variable = self.blue,
font=small_font,
)
color3.deselect()
color4 = Checkbutton(
frame_colors,
text = 'Pink',
bg = "#f2c6de",
variable = self.pink,
font = small_font,
)
color4.deselect()
color5 = Checkbutton(
frame_colors,
text = 'Orange',
bg = "#f7d9c4",
variable = self.orange,
font=small_font,
)
color5.deselect()
color6 = Checkbutton(
frame_colors,
text = 'Yellow',
bg= "#fcf6bd",
variable = self.yellow,
font=small_font,
)
color6.deselect()
# --PACK ELEMENTs----------------------------------------------------------------
color1.pack(padx=5, pady=5)
color2.pack(padx=5, pady=5)
color3.pack(padx=5, pady=5)
color4.pack(padx=5, pady=5)
color5.pack(padx=5, pady=5)
color6.pack(padx=5, pady=5)
MyNameFrame.pack(padx=10, pady=10, fill=BOTH)
MyNameEntry.pack(padx=5, pady=5)
MyMainFrame.pack(expand=False, fill=BOTH, padx=10, pady=10)
MyColorFrame.pack(expand=False, fill=BOTH, padx=10, pady=10)
# Tells the program to return to the create sticky function on click
myCreateButton = Button(
frame,
text='Create',
command=lambda: create_sticky(self),
font = small_font,
)
myCreateButton.pack(padx=5, pady=5)
settingsbutton = Button(
frame,
text= 'Settings',
font = small_font,
command= lambda: openSettings(self),
)
settingsbutton.pack(padx=5, pady=10, side=BOTTOM)
help(MyApp)
if __name__ == '__main__':
w = MyApp()
w.mainloop()
1 Answer 1
Overall this is a great start, basically functional, and looks cool.
Your doing this:
import tkinter as tk
is a good idea - so you should then avoid the next line:
from tkinter import *
as it pollutes your namespace. Using tk.LabelFrame
etc. uniformly will help.
tk.Tk.__init__(self, *args, **kwargs)
should use super()
rather than tk.Tk
.
The indentation issue is a case of Python being characterized as friendly to beginners, but actually shooting beginners right in both feet. makecolor
, openSettings
, and so on should actually be de-indented by one level so that rather than being locally-defined functions, they become actual class methods. Note that the lines starting with:
myscreenwidth = (getscreensize(self))[1]
are actually just part of __init__
- which is fine-ish, but the function definitions above that should all be moved down and de-tabbed.
You using PyCharm will help - it makes a bunch of suggestions that you would benefit from observing, including PEP8-standard spacing around =
operators, etc.
This:
myscreenwidth = (getscreensize(self))[1]
myscreenheight = (getscreensize(self))[1]
myscreendims = str(int(myscreenwidth * 0.3)) + "x" + str(int(myscreenheight* 0.5))
myscreenposition = str("+" + str((myscreenwidth+70)) + "+" + str(10))
has a few issues and opportunities for simplification:
- By PEP8,
myscreenwidth
would bemy_screen_width
- Seeing
[1]
twice is likely a bug and you probably want the first one to be[0]
- Rather than calling
getscreensize(self)
, once you properly de-tab your methods, it will look likeself.getscreensize()
- Tuple unpacking will simplify your return assignment, i.e.
myscreenwidth, myscreenheight = self.getscreensize()
Your repeated str
and +
invocations can be simplified using f-strings.
When you alias a variable like this:
MyMainFrame = frame =
I'm not sure why. The second variable name seems reasonable and the first does not.
Your lambda references like
lambda: openSettings(self)
should be rewritten as
self.open_settings
Your call to help
should probably be dropped.
str(int(0)) + "x" + str(int(50))
should just be '0x50'
.
initialfile=(nameentry)
is a bug and should be initialfile=nameentry.get()
.
Your file type entries should probably be re-expressed as a tuple ()
instead of a list []
, and put the *.*
entry last:
filetypes=(
('Text Documents', '*.txt'),
('All Files', '*.*'),
),
There's another bug: if you cancel your save dialogue, then currently the application will crash. Adding an if not None
will fix this.
You should also replace your check-boxes with a radio button group so that your colour selection can be mutually exclusive.
Consider separating out your sticky and settings windows into separate classes.
You should migrate MyApp
from "is-a Tk window" to "has-a Tk window", to enforce better encapsulation.
Suggested
Addressing some of the above,
import tkinter as tk
from tkinter.filedialog import asksaveasfile
from typing import Tuple, Callable
TINY_FONT = ('Lucida Console', 8)
SMALL_FONT = ('CalibriLight', 10)
MEDIUM_FONT = ('CalibriLight', 12)
LARGE_FONT = ('Lucida Bright', 20)
class StickyWindow:
def __init__(
self,
name: str,
colour: str,
parent: tk.Tk,
get_size: Callable[[], Tuple[int, int]],
) -> None:
# function invoked when you click the create button ont the main frame
self.new_window = tk.Toplevel(parent)
if name == '':
name = 'Untitled'
self.name = name
self.new_window.wm_title(name)
top_note_frame = tk.LabelFrame(
self.new_window, bg='#ffffff', font=SMALL_FONT,
)
save_button = tk.Button(top_note_frame, text='Save', command=self.save_file)
save_button.pack(pady=5, expand=True, side=tk.LEFT)
hide_button = tk.Button(top_note_frame, text='Hide', command=self.hide_sticky)
hide_button.pack(pady=5, expand=True, side=tk.LEFT)
show_button = tk.Button(top_note_frame, text='Show', command=self.show_sticky)
show_button.pack(pady=5, expand=True, side=tk.LEFT)
top_note_frame.pack(fill=tk.BOTH, side=tk.BOTTOM)
self.get_size = get_size
width, height = get_size()
self.new_window.geometry(f'{width}x{height}')
self.T = T = tk.Text(self.new_window, height=20, width=40, bg=colour)
T.pack()
T.configure(font='Calibri')
def save_file(self) -> None:
txt_content = self.T.get('1.0', 'end-1c')
f = asksaveasfile(
initialfile=self.name,
defaultextension='.txt',
filetypes=(
('Text Documents', '*.txt'),
('All Files', '*.*'),
),
)
if f is not None:
f.write(txt_content)
self.new_window.destroy()
def show_sticky(self) -> None:
width, height = self.get_size()
self.new_window.geometry(f'{width}x{height}')
def hide_sticky(self) -> None:
self.new_window.geometry('0x50')
class SettingsWindow:
def __init__(self, parent: tk.Tk):
self.window = tk.Toplevel(parent)
self.window.wm_title('Settings')
settings_frame = tk.LabelFrame(
self.window, bg='#ffffff', text='Settings', font=MEDIUM_FONT,
)
file_type_label = tk.Label(
settings_frame, bg='#ffffff', text='Set default file save type:'
)
default_type = tk.StringVar
file_type_entry = tk.Entry(
settings_frame, bg='#ffffff', textvariable=default_type,
)
file_type_label.pack(padx=10, pady=10)
file_type_entry.pack(padx=10, pady=10)
save_settings = tk.Button(settings_frame, text='Save Settings')
save_settings.pack(padx=10, pady=10)
settings_frame.pack(fill=tk.BOTH, padx=10, pady=10)
class MyApp:
def __init__(self) -> None:
self.window = tk.Tk()
self.window.title(string='Stickies')
self.window.attributes('-alpha', 0.95)
self.window.configure(bg='#ffffff')
self.mainloop = self.window.mainloop
width, height = self.getscreensize_fraction(0.3, 0.5)
self.window.geometry(f'{width}x{height}+{width+70}+10')
frame = tk.LabelFrame(
self.window, text='Stickies', bg='#ffffff', font=MEDIUM_FONT,
)
frame_name = tk.LabelFrame(
frame, text='Name your note', bg='#ffffff', font=MEDIUM_FONT,
)
frame_colors = tk.LabelFrame(
frame, text='Colors', bg='#ffffff', font=MEDIUM_FONT,
)
self.name_entry = tk.StringVar()
name_entry = tk.Entry(
frame_name, textvariable=self.name_entry, font=SMALL_FONT, bg='#f1f1f1',
)
self.purple = tk.StringVar()
self.blue = tk.StringVar()
self.orange = tk.StringVar()
self.green = tk.StringVar()
self.yellow = tk.StringVar()
self.pink = tk.StringVar()
color1 = tk.Checkbutton(
frame_colors, text='Purple', bg='#dbcdf0', variable=self.purple, font=SMALL_FONT,
)
color1.deselect()
color2 = tk.Checkbutton(
frame_colors, text='Green', bg='#d0f4de', variable=self.green, font=SMALL_FONT,
)
color2.deselect()
color3 = tk.Checkbutton(
frame_colors, text='Blue', bg='#c6def1', variable=self.blue, font=SMALL_FONT,
)
color3.deselect()
color4 = tk.Checkbutton(
frame_colors, text='Pink', bg='#f2c6de', variable=self.pink, font=SMALL_FONT,
)
color4.deselect()
color5 = tk.Checkbutton(
frame_colors, text='Orange', bg='#f7d9c4', variable=self.orange, font=SMALL_FONT,
)
color5.deselect()
color6 = tk.Checkbutton(
frame_colors, text='Yellow', bg='#fcf6bd', variable=self.yellow, font=SMALL_FONT,
)
color6.deselect()
# --PACK ELEMENTs----------------------------------------------------------------
color1.pack(padx=5, pady=5)
color2.pack(padx=5, pady=5)
color3.pack(padx=5, pady=5)
color4.pack(padx=5, pady=5)
color5.pack(padx=5, pady=5)
color6.pack(padx=5, pady=5)
frame_name.pack(padx=10, pady=10, fill=tk.BOTH)
name_entry.pack(padx=5, pady=5)
frame.pack(expand=False, fill=tk.BOTH, padx=10, pady=10)
frame_colors.pack(expand=False, fill=tk.BOTH, padx=10, pady=10)
# Tells the program to return to the create sticky function on click
my_create_button = tk.Button(
frame, text='Create', command=self.create_sticky, font=SMALL_FONT,
)
my_create_button.pack(padx=5, pady=5)
settings_button = tk.Button(
frame, text='Settings', font=SMALL_FONT, command=self.open_settings,
)
settings_button.pack(padx=5, pady=10, side=tk.BOTTOM)
@property
def colour(self) -> str:
if self.orange.get() == '1':
return '#f7d9c4'
if self.blue.get() == '1':
return '#c6def1'
if self.purple.get() == '1':
return '#dbcdf0'
if self.green.get() == '1':
return '#d0f4de'
if self.yellow.get() == '1':
return '#fcf6bd'
if self.pink.get() == '1':
return '#f2c6de'
return '#FFFFFF'
def open_settings(self) -> None:
SettingsWindow(parent=self.window)
def getscreensize_fraction(self, fx: float = 0.3, fy: float = 0.3) -> Tuple[int, int]:
return (
int(fx*self.window.winfo_screenwidth()),
int(fy*self.window.winfo_screenheight()),
)
def create_sticky(self) -> None:
StickyWindow(
name=self.name_entry.get(), colour=self.colour, parent=self.window,
get_size=self.getscreensize_fraction,
)
if __name__ == '__main__':
MyApp().mainloop()
-
2\$\begingroup\$ I could cry this is so helpful. Thank you so much. I have been struggling with the Python syntax since all my experience programming is in scripting languages that don't evaluate spaces or indentation. \$\endgroup\$Anna Smith– Anna Smith2021年08月05日 14:34:37 +00:00Commented Aug 5, 2021 at 14:34
-
1\$\begingroup\$ np - you're in the right place; keep the questions coming :) \$\endgroup\$Reinderien– Reinderien2021年08月05日 14:57:26 +00:00Commented Aug 5, 2021 at 14:57
Explore related questions
See similar questions with these tags.
makecolor
and onward. However, by some miracle this all runs as-is, so uh. It's reviewable, but there are going to be some changes needed. \$\endgroup\$