# Generating PNG barcodes through a GUI

This is a continuation of my earlier question, now updated to use a basic GUI. This is my first time using tkinter so I suspect there are a fair few things I'm missing! I've also added the ability to generate a single barcode without having to first save it in a CSV. I'm specifically interested in feedback on how I have implemented the GUI, since that's the bit I'm least confident about, but comments on anything else are very welcome too!

import csv
import barcode
from barcode.writer import ImageWriter
from pathlib import Path
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
from tkinter import ttk

FORBIDDEN_CHARS = str.maketrans({char: "" for char in ':;<>\'\"\\/?*|.'})

def remove_forbidden(string_in: str) -> str:
"""Removes characters forbidden from windows file names from a string"""
return string_in.translate(FORBIDDEN_CHARS)

def get_code_png(isbn: str) -> barcode.ean.EuropeanArticleNumber13:
"""Creates a barcode, given an ISBN"""
isbn13 = barcode.get_barcode_class('ean13')
return isbn13(isbn, writer=ImageWriter())

def save_code_png(isbn: str, title: str, save_folder_path: Path):
"""Saves one PNG barcode in a given folder"""
code = get_code_png(isbn)
code.save(save_folder_path.joinpath(isbn + " " + remove_forbidden(title)))

def codes_from_csv(list_path: Path, save_folder_path: Path):
"""Creates PNG Barcodes from a CSV, placing them in a given folder"""
with open(list_path, newline='') as csvfile:
save_code_png(row[0], row[1], save_folder_path)

def gui():
def submit_lone():
"""Creates a single barcode from a button"""
try:
save_code_png(txt_isbn_lone.get(), txt_title_lone.get(),
Path(txt_save_path_lone.get()))
except:
messagebox.showerror('Error', 'Unexpected error:',
sys.exc_info()[0])
else:
messagebox.showinfo('Success!', 'Code Created')

def submit_csv():
"""Creates barcodes from a CSV from a button"""
try:
codes_from_csv(Path(txt_csv_path.get()),
Path(txt_save_path_csv.get()))
except:
messagebox.showerror('Error', 'Unexpected error:',
sys.exc_info()[0])
else:
messagebox.showinfo('Success!', 'Codes Created')

def save_path(txt_field: Entry):
"""Finds a folder and saves the path in a tkinter Entry widget"""
txt_field.delete(0, END)

def csv_path():
"""Finds a csv file and saves the path in a tkinter Entry widget"""
files = (("Comma Separated Variable", "*.csv"), ("All Files", "*.*"))
txt_csv_path.delete(0, END)

#   Setting up the window
window = Tk()
window.geometry("400x180")
window.title("Book Barcodes")
tab_control = ttk.Notebook(window)
lone = ttk.Frame(tab_control)
csv = ttk.Frame(tab_control)
tab_control.pack(expand=1, fill='both')
#   Tab 1: Lone Code
lbl_isbn_lone = Label(lone, text="ISBN:")
txt_isbn_lone = Entry(lone, width=30)
lbl_title_lone = Label(lone, text="Title:")
txt_title_lone = Entry(lone, width=30)
lbl_save_path_lone = Label(lone, text="Save Folder:")
txt_save_path_lone = Entry(lone, width=30)
btn_save_path_lone = Button(lone, text="Find Folder",
command=lambda: save_path(txt_save_path_lone))
btn_submit_lone = Button(lone, text="Generate Code", command=submit_lone)
#   Tab 2: Codes from CSV
instructions = Label(csv, text="""Please select a CSV file with ISBNs in the first
column and titles in the second.""")
lbl_csv_path = Label(csv, text="CSV Location:")
txt_csv_path = Entry(csv, width=30)
btn_list_csv = Button(csv, text="Find File", command=csv_path)
lbl_save_path_csv = Label(csv, text="Save Folder:")
txt_save_path_csv = Entry(csv, width=30)
btn_save_path_csv = Button(csv, text="Find Folder",
command=lambda: save_path(txt_save_path_csv))
btn_submit_csv = Button(csv, text="Generate Codes", command=submit_csv)

window.mainloop()

if __name__ == "__main__":
gui()



## Don't use wildcard imports

Instead of from tkinter import *, use import tkinter as tk. Then, wherever you reference a tkinter class or value, prefix it with tk. (eg: tk.Label, tk.Button, etc).

This makes your code easier to understand, and helps keep the number of objects in the global namespace low. Plus, it keeps it consistent with PEP8 guidelines.

## Move the GUI code to a class

Moving the GUI code to a class helps organize your code. It makes it clear which parts of your code are related to the GUI and which aren't. An example of how to do this can be found on stackoverflow at this question: Best way to structure a tkinter application?

## Separate widget creation from widget layout

I consider the pattern where you create a widget, call grid, create a widget, call grid, ... to be an antipattern. It makes it difficult to visualize the layout and makes it more difficult to change the layout. I find that the layout code can change frequently during development as you add new widgets or improve the layout.

lbl_isbn_lone = Label(lone, text="ISBN:")
txt_isbn_lone = Entry(lone, width=30)
lbl_title_lone = Label(lone, text="Title:")
txt_title_lone = Entry(lone, width=30)
...


... do it like this:

lbl_isbn_lone = Label(lone, text="ISBN:")
txt_isbn_lone = Entry(lone, width=30)
lbl_title_lone = Label(lone, text="Title:")
txt_title_lone = Entry(lone, width=30)
...


## Use more whitespace

Right now you have a giant wall of code for creating widgets and laying them out. There's no way to tell if there's any sort of organization to your code, it's all just one giant block of code. You have some comments, but comments alone don't do a good job of helping our eye organize what we see.

Specifically, add a blank line before # Tab 1: Lone Code and before # Tab 2: Codes from CSV

## Don't make complex function calls.

save_code_png(txt_isbn_lone.get(), txt_title_lone.get(),
Path(txt_save_path_lone.get()))


Do this:

isbn = txt_isbn_lone.get()
title = txt_title_lone.get()
path = Path(txt_save_path_lone.get())

save_code_png(isbn, title, path)
`

I think it makes the code easier to read, and it makes the code easier to debug since it's easier to examine the values before passing them to the function.