Tkinter GUI for CRUD app #3

Below I have described the code of the NewCarWindow class describing a new child window, created when the add_car_button button is clicked:

import tkinter as tk
from sql_helper import Helper


class NewCarWindow():
    def __init__(self, root, parent):
        self.top_level = tk.Toplevel(root)
        self.helper = Helper()
        self.parent = parent
        x = self.top_level.winfo_screenwidth()
        y = self.top_level.winfo_screenheight()
        geometry = '+{}+{}'.format(int((x / 2) - 100),
                                   int((y / 2) - 100))
        size = self.top_level.geometry(geometry)

First, I import the required classes, then the init () method sets the child window’s position relative to the main window.

        self.top_level.title('Add car')
        self.top_level.grab_set()
        label_make = tk.Label(self.top_level, text='Make: ',
                              font=12, padx=20, pady=10).grid(row=0, column=0)
        label_model = tk.Label(self.top_level, text='Model: ',
                               font=12, padx=20, pady=10).grid(row=1, column=0)
        label_year = tk.Label(self.top_level, text='Year: ',
                              font=12, padx=20, pady=10).grid(row=2, column=0)
        label_vrn = tk.Label(self.top_level, text='VRN: ',
                             font=12, padx=20, pady=10).grid(row=3, column=0)
        label_vin = tk.Label(self.top_level, text='VIN: ',
                             font=12, padx=20, pady=10).grid(row=4, column=0)

Then the title of the child window is set. The grab_set () method makes the main window inactive – e.g. you cannot press any button, etc.

The next step is to set the text labels describing the input fields, which I define below, together with variables of the StringVar type that store the values ​​entered into these fields:

    self.make_sv = tk.StringVar()
    self.model_sv = tk.StringVar()
    self.year_sv = tk.StringVar()
    self.vrn_sv = tk.StringVar()
    self.vin_sv = tk.StringVar()
    self.info_sv = tk.StringVar()

    entry_make = tk.Entry(
            self.top_level, text=self.make_sv)
    entry_make.focus_set()
    entry_make.grid(row=0, column=1)
    entry_model = tk.Entry(
            self.top_level, text=self.model_sv).grid(row=1, column=1)
    entry_year = tk.Entry(
            self.top_level, text=self.year_sv).grid(row=2, column=1)
    entry_vrn = tk.Entry(
            self.top_level, text=self.vrn_sv).grid(row=3, column=1)
    entry_vin = tk.Entry(
            self.top_level, text=self.vin_sv).grid(row=4, column=1)

    info_label = tk.Label(self.top_level, textvariable=self.info_sv,
                              font=12, padx=10, pady=10, fg='red').grid(row=5, column=0, columnspan=2)

The info_label label is used to display a message in the case of not filling all the required fields to create a new car.

        save_button = tk.Button(self.top_level, text='Save',
                                command=self.save_new_car)
        save_button.bind('<Return>', self.save_new_car)
        save_button.grid(row=6, column=1, sticky='W', padx=10)
        cancel_button = tk.Button(self.top_level, text='Cancel',
                                  command=self.top_level.destroy)
        cancel_button.grid(row=6, column=1, sticky='W', padx=70)
        cancel_button.bind('<Return>', self.top_level.destroy)

At the bottom of the window, the Save and Cancel buttons are displayed to save the data about the new car in the database or close the ‘Add car’ window.

def save_new_car(self, event=None):
    if self.make_sv.get() and self.model_sv.get() and self.year_sv.get() and self.vrn_sv.get() and self.vin_sv.get():
        self.helper.add_car(self.make_sv.get(),
                            self.model_sv.get(),
                            self.year_sv.get(),
                            self.vrn_sv.get(),
                            self.vin_sv.get())
        self.top_level.destroy()
        self.parent.show_cars()
    else:
        self.info_sv.set('Please fill in all entry fields')

The code for the save_new_car() method is activated if the Save button is pressed. If all the values ​​needed to create a new car have been filled, the add_car () method of the Helper class is run, then the child window is closed and the entries about cars in the main window are updated using the show_cars() method of the CarManager class.

The ‘Add car’ child window. The message is displayed when all input fields are not filled in. Focus is set on the first element.

***

The code of the RepairsWindow class, activated after pressing the main button repair_button:

import tkinter as tk
from tkinter import ttk
from sql_helper import Helper
from date_picker import DatePicker


class RepairsWindow():
    def __init__(self, root, car):
        self.top_level = tk.Toplevel(root)
        self.root = root
        self.top_level.title('Repairs')
        self.top_level.grab_set()
        self.car = car
        self.helper = Helper()

        x = self.top_level.winfo_screenwidth()
        y = self.top_level.winfo_screenheight()
        geometry = '+{}+{}'.format(int((x / 2) - 150),
                                   int((y / 2) - 150))
        size = self.top_level.geometry(geometry)

First, I import all the necessary classes. In the init () method, a new window is created – an instance of the TopLevel class, and the window title is set to ‘Repairs’. Then, using the grab_set () method, the possibility of starting widgets from the main window is blocked. The self.car and self.helper variables are set and the child window is positioned relative to the main window.

        toolbox_frame = tk.Frame(self.top_level)
        toolbox_frame.grid(column=0, row=0, sticky='W')
        self.add_repair_img = tk.PhotoImage(file='Resources/add_repair.gif')
        add_repair_button = tk.Button(
            toolbox_frame, image=self.add_repair_img, command=self.add_repair)
        add_repair_button.grid(column=0, row=0, sticky='W')
        add_repair_button.bind('<Return>', self.add_repair)
        add_repair_button.bind('<KP_Enter>', self.add_repair)
        if car.sold:
            add_repair_button.config(state='disabled')

A toolbox_frame is created in the child window, in which the add_repair_button button is placed. The add_repair () method is bound to the created button – activated with the mouse or keyboard after pressing Enter on the numeric or main keyboard. Then it is checked if the car is not marked as ‘sold’, then the button for adding new repairs is inactive.

        col_headers = ('No', 'Date', 'Description')
        self.repairs_tv = ttk.Treeview(self.top_level, columns=col_headers,
                                       show='headings', selectmode='none')
        self.repairs_tv.tag_configure('c1', background='ivory2')
        self.repairs_tv.tag_configure('c2', background='ivory3')
        for i, col in enumerate(col_headers):
            self.repairs_tv.heading(col, text=col)
            self.repairs_tv.column(col, anchor='center')
            if i == 0:
                self.repairs_tv.column(col, width=50, stretch='NO')
        self.repairs_tv.grid(column=0, row=2,  sticky='NSWE')

        scrollbar = tk.Scrollbar(self.top_level, command=self.repairs_tv.yview)
        scrollbar.grid(column=1, row=2, sticky='NS')
        
        self.show_repairs()

The next code snippet describes creating the Treeview widget that will display repair data. To improve readability, I created two tags that set the background colors of the entered widget elements. The init () method ends by calling the show_repairs () method, which adds repairs to the Treeview widget.

    def show_repairs(self):
        repairs = self.helper.show_repairs(self.car)
        self.repairs_tv.delete(*self.repairs_tv.get_children())
        for i, repair in enumerate(repairs, start=1):
            repair = (i, repair[0], repair[2])
            if i % 2:
                self.repairs_tv.insert('', 'end', values=repair, tag='c1')
            else:
                self.repairs_tv.insert('', 'end', values=repair, tag='c2')

The show_repairs () method calls the appropriate Helper method to retrieve the repair data from the database. Then the repairs_tv items are deleted and the updated data is set. To improve readability, the following lines are alternately colored, i.e.

Repairs window.

Code of the add_repair method, which is activated when the add_repair_button button is pressed with the mouse or the keyboard:

    def add_repair(self, event=None):
        self.add_repair_frame = tk.Frame(self.top_level)
        self.add_repair_frame.grid(
            column=0, row=1, pady=20, sticky='WE')

        date_label = tk.Label(self.add_repair_frame,
                              text='Date:').grid(column=0, row=2)
        self.date_sv = tk.StringVar()
        self.date_entry = tk.Entry(self.add_repair_frame,
                                   text=self.date_sv)
        self.date_entry.focus_set()
        self.date_entry.grid(column=1, row=2, sticky='W')

        self.cal_img = tk.PhotoImage(file='Resources/calendar.gif')
        show_cal_btn = tk.Button(self.add_repair_frame, image=self.cal_img,
                                 command=self.show_cal, relief='flat').grid(column=1, row=2, sticky='W', padx=170)

        description_label = tk.Label(self.add_repair_frame,
                                     text='Description:').grid(column=0, row=3)
        self.description_sv = tk.StringVar()
        self.description_entry = tk.Entry(self.add_repair_frame,
                                          text=self.description_sv)
        self.description_entry.grid(column=1, row=3, ipadx=200)
        save_button = tk.Button(self.add_repair_frame, text='Save',
                                command=self.save_repair)
        save_button.grid(column=1, row=4, pady=10, sticky='E')
        save_button.bind('<Return>', self.save_repair)
        save_button.bind('<KP_Enter>', self.save_repair)
        cancel_button = tk.Button(self.add_repair_frame, text='Cancel',
                                  command=self.cancel_repair)
        cancel_button.grid(column=1, row=4, sticky='E', padx=60)
        cancel_button.bind('<Return>', self.cancel_repair)
        cancel_button.bind('<KP_Enter>', self.cancel_repair)
Repairs window after pressing the add_repair_button button.

Code of the cancel_repair () method that hides the repair_frame:

    def cancel_repair(self, event=None):
        self.add_repair_frame.grid_remove()

Save_repair () method code:

def save_repair(self, event=None):
        if self.date_sv.get() and self.description_sv.get():
            self.helper.add_repair(self.car, self.date_sv.get(),
                                   self.description_sv.get())
            self.show_repairs()
            self.add_repair_frame.grid_remove()

In the save_repair () method, basic form validation is performed, ie it is checked if all required fields to create a repair entry are filled in.

If so, the repair note is saved in the database (add_repair method of the Helper class), and then the entries about the repairs are updated and the repair_frame is hidden.

The remaining show_cal () method, which is activated after pressing the show_cal_btn button:

    def show_cal(self, event=None):
        date_picker = DatePicker(self.top_level, self.date_entry, '%d-%m-%Y')
        self.description_entry.focus_set()

The show_cal () method creates an instance of the DatePicker class that I created to get the date and insert it into the given Entry field. As an argument, this class allows you to insert the selected date formatting, so that, for example, in the application settings the user could choose how the date should be displayed.

You can find the code of the DatePicker class in this post.

Selecting a date using my DatePicker class

***

P.S. If the main window is to run as maximized, but with the resize option, then in the init () method of the CarManager class, add:

root.attributes('-zoomed', True)

P.P.S. If entries about cars are to be displayed with row formatting, the show_cars () method can be modified by adding tags that define row colors, i.e.

    def show_cars(self):
        all_cars = self.helper.show_all_cars()
        self.car_tview.delete(*self.car_tview.get_children())
        for i, car in enumerate(all_cars, start=1):
            car = list(car)
            car.insert(0, i)
            if i % 2:
                self.car_tview.insert('', 'end', values=car, tag='c2')
            else:
                self.car_tview.insert('', 'end', values=car, tag='c1')
            self.car_tview.tag_configure('c1', background='ivory3')
            self.car_tview.tag_configure('c2', background='ivory2')
Treeview widget with formatting tags

Leave a Reply

Your email address will not be published. Required fields are marked *