4
\$\begingroup\$

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()
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Your indentation doesn't make much sense starting at 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\$
    – Reinderien
    Aug 4, 2021 at 22:26
  • \$\begingroup\$ I've been writing in Pycharm so I've just been fixing indents as it tells me \$\endgroup\$
    – Anna Smith
    Aug 4, 2021 at 23:44

1 Answer 1

2
\$\begingroup\$

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 be my_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 like self.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()
\$\endgroup\$
2
  • 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
    Aug 5, 2021 at 14:34
  • 1
    \$\begingroup\$ np - you're in the right place; keep the questions coming :) \$\endgroup\$
    – Reinderien
    Aug 5, 2021 at 14:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.