Plantasia is a python GUI aimed to organize plants and muchrooms. Please check the First Part of this project to get the overall idea.
In the last post, both add and remove options were still unavailable. The later options, and several corrections were implemented and the GUI is now finished. However, some minor optimizations can be applied depending on the user's needs.
Let's start by the option to add a new specie.
For this, I made three main functions inside the App class. The first is create_specie()
, this one is called by clicking New
in the top menu bar, and it is mainly to structure and design the page.
def create_specie(self):
self.mainFrame.destroy()
self.create_mainFrame()
self.label_new_specie = tk.Label(self.mainFrame, text='New Specie', foreground='#5A8A29', bg='#FFFEC7')
self.label_new_specie['font'] = Fonts().font0
self.label_new_specie.grid(sticky='n', pady = 20, column = 0, row = 0)
self.sub_frame = tk.Frame(self.mainFrame, bg='#FFFEC7')
self.sub_frame.grid(sticky = 'nw', column = 0, row = 1)
self.frame_new_specie = tk.LabelFrame(self.sub_frame, bg='#FFFEC7', text= "Details", relief= tk.RIDGE)
self.frame_new_specie.grid(sticky = 'nw', column = 0, row = 0, pady = 5, padx = 5)
self.frame_right = tk.Frame(self.sub_frame, bg='#FFFEC7')
self.frame_right.grid(sticky = 'n', column = 1, row = 0, pady = 5, padx = 20)
self.frame_description = tk.LabelFrame(self.frame_right, bg='#FFFEC7', text= "Description", relief= tk.RIDGE)
self.frame_description.grid(sticky = 'nw', column = 0, row = 0, pady = 5, padx = 20)
self.frame_image = tk.LabelFrame(self.frame_right, bg='#FFFEC7', text= "Upload an Image", relief= tk.RIDGE)
self.frame_image.grid(sticky = 'nw', column = 0, row = 1, pady = 15, padx = 20)
#name
self.specie_name_label = tk.Label(self.frame_new_specie, text="Name:", bg='#FFFEC7')
self.specie_name_label.grid(sticky = 'nw', column = 0, row = 0, pady = 10, padx = 5)
self.specie_name_entry = tk.Entry(self.frame_new_specie)
self.specie_name_entry.grid(sticky = 'nw', column = 1, row = 0, pady = 10, padx = 5)
#family
self.specie_family_label = tk.Label(self.frame_new_specie, text="Family:", bg='#FFFEC7')
self.specie_family_label.grid(sticky = 'nw', column = 0, row = 1, pady = 10, padx = 5)
self.specie_family_combobox = ttk.Combobox(self.frame_new_specie, values=families, state='normal', width= 17)
self.specie_family_combobox.grid(sticky = 'nw', column = 1, row = 1, pady = 10, padx = 5)
#description
self.specie_description_st = ScrolledText(self.frame_description, width=25, height=5)
self.specie_description_st.grid(sticky = 'nw', column = 0, row = 0, pady = 10, padx = 5)
#properties
self.properties_container = tk.Label(self.frame_new_specie, bg='#FFFEC7')
self.properties_container.grid(sticky = 'nw', column = 1, row = 2, pady = 10, padx = 4)
self.specie_properties_label = tk.Label(self.frame_new_specie, text="Properties:", bg='#FFFEC7')
self.specie_properties_label.grid(sticky = 'nw', column = 0, row = 2, pady = 10, padx = 4)
self.properties_entries = []
for i in range(7):
self.specie_properties_combobox = ttk.Combobox(self.properties_container, values=properties, state='normal', width = 17)
self.specie_properties_combobox.grid(sticky = 'nw', column = 1, row = i, pady = 0)
self.properties_entries.append(self.specie_properties_combobox)
#sideeffects
self.sides_container = tk.Label(self.frame_new_specie, bg='#FFFEC7')
self.sides_container.grid(sticky = 'nw', column = 1, row = 3, pady = 10, padx = 4)
self.specie_sides_label = tk.Label(self.frame_new_specie, text="Side Effects:", bg='#FFFEC7')
self.specie_sides_label.grid(sticky = 'nw', column = 0, row = 3, pady = 10, padx = 3)
self.sides_entries = []
for i in range(7):
self.specie_sides_combobox = ttk.Combobox(self.sides_container, values=side_effects, state='normal', width = 17)
self.specie_sides_combobox.grid(sticky = 'nw', column = 1, row = i, pady = 0)
self.sides_entries.append(self.specie_sides_combobox)
#image
img = Image.open(TOOLS_DIR + 'upload_image.png')
img = img.resize((80, 60))
self.imgTK = ImageTk.PhotoImage(img)
self.button_img = tk.Button(self.frame_image, image = self.imgTK, bg='#FFFEC7', command=self.upload_image)
self.button_img.grid(sticky = 'n', column=0, row=0, pady=5, padx=10)
self.label_img = tk.Label(self.frame_image, text="*please name the picture \n exactly as the specie's name", foreground='#FF0000', bg='#FFFEC7')
self.label_img.grid(sticky = 'n', column = 0, row = 1)
#submit
self.button_select = tk.Button(self.frame_right, text=' Submit ', command= self.upload_specie, bg='#62AC3D', activebackground='#5A8A29', fg='#FFFFFF')
self.button_select['font'] = Fonts().font3
self.button_select.grid(sticky = 'w', column = 0, row = 2, pady = 10, padx=80)
#back
self.button_back = tk.Button(self.mainFrame, text=' Back ', bg='#156823', command=self.click_back, activebackground='#5A8A29', fg='#FFFFFF')
self.button_back['font'] = Fonts().font3
self.button_back.grid(sticky = 'n', row = 2, pady = 10, column = 0)
The second function is called, upload_image()
, and as it says, it's used to upload an image related with the Specie, and it's called when clicked on the upload image icon (see image above). It allows only .jpg images. The function splits the directory where the image is saved, and returns only the name of the image (that must be the same as the specie's name), it is then saved in the desired directory, among the others species images.
def upload_image(self):
global set_file
self.files = [('.jpg', '*.jpg')]
self.image = askopenfilename(filetypes = self.files, defaultextension = self.files)
self.img_split = self.image.split('/')
self.file = self.img_split[len(self.img_split)-1]
shutil.copyfile(self.image, IMAGES_DIR + self.file)
self.filename = self.file.split('.')
self.filename = self.filename[0]
set_file = 1
Finally, after filling all the entries and comboboxes, one last function, upload_specie()
, is called to save the new specie in the Data Frame and Database (excel worksheet in this case).
def upload_specie(self):
global species
global families
global descriptions
global properties
global side_effects
global df
self.properties_array = []
self.sides_array = []
for combobox in self.properties_entries:
get_combobox = combobox.get()
self.properties_array.append(get_combobox)
for combobox in self.sides_entries:
get_combobox = combobox.get()
self.sides_array.append(get_combobox)
if set_file == 0:
self.file = ''
self.filename = ''
self.error = 1
while self.error == 1:
if self.specie_name_entry.get() == '' or self.specie_family_combobox.get() == '' or self.properties_entries[0] == '' or self.sides_entries[0] == '' or self.specie_description_st.get("1.0",'end-1c') == '' or self.file == '':
msg.showerror('Error', 'Missing field!')
return
elif self.filename != self.specie_name_entry.get():
os.remove(IMAGES_DIR + self.file)
msg.showerror('Error', "Please save the image with the specie name.")
return
elif self.specie_name_entry.get() in species:
msg.showerror('Error', "This Specie's name already exists. Please insert a different one. ")
return
else:
for property in self.properties_array:
if property == '':
self.properties_array.remove(property)
self.join_properties = "|".join(self.properties_array)
self.join_properties = self.join_properties.split('||')
self.join_properties = self.join_properties[0]
for side in self.sides_array:
if side == '':
self.sides_array.remove(side)
self.join_sides = "|".join(self.sides_array)
self.join_sides = self.join_sides.split('||')
self.join_sides = self.join_sides[0]
self.specie_description_st = self.specie_description_st.get("1.0",'end-1c').split(' ')
self.specie_description_st = self.specie_description_st[0]
self.new_row = {
tabs[0]:self.specie_name_entry.get(),
tabs[1]:self.specie_family_combobox.get(),
tabs[2]:self.specie_description_st,
tabs[3]:self.join_properties,
tabs[4]:self.join_sides}
df = df.append(self.new_row, ignore_index=True)
print(df)
species = get_species_array(df)
descriptions = get_descriptions_array(df)
properties = get_properties_array(df)
if '' in properties:
properties.remove('')
families = get_families_array(df)
side_effects = get_side_effects_array(df)
if '' in side_effects:
side_effects.remove('')
self.error = 0
df.reset_index(inplace=False)
df.to_excel(TEMPLATE_DIR, sheet_name='Sheet1', index=False, startrow=0, startcol=0)
msg.showinfo(title='Info', message='A Specie has been added to the list!')
self.create_specie()
return
This function was the most difficult among the three, it generates a row (Data Frame), that is then added to the main Data Frame. The variables df, species, properties, descriptions and side-effects, are updated in the overall code, by defining them as globals
. It also prompts several error messages according to certain conditions.
In order to remove a specie, another set of functions were implemented.
The first one is a callback function (callback()
), it prompts the structure of the page , when clicking Remove
at the upper menu.
def callback(self):
self.mainFrame.destroy()
self.create_mainFrame()
self.search_frame = tk.Frame(self.mainFrame, bg='#FFFEC7')
self.search_frame.pack(side='top', pady=10)
self.species_frame_label = tk.Label(self.search_frame, text='Select Specie To Remove:', bg='#FFFEC7')
self.species_frame_label['font'] = Fonts().font2
self.species_frame_label.grid(sticky="s", column=0, row=0, pady=5)
self.combobox_species_remove = ttk.Combobox(self.search_frame, values=species, width=20, state='normal')
self.combobox_species_remove.current(0) # to give first element in species array
self.combobox_species_remove.grid(sticky="s", column=0, row=1, pady=5, padx=5)
self.button_select = tk.Button(self.search_frame, text=' Select ', command= self.remove_specie, bg='#62AC3D', activebackground='#5A8A29', fg='#FFFFFF')
self.button_select['font'] = Fonts().font3
self.button_select.grid(sticky="s", column=0, row=2, pady=10)
self.button_back = tk.Button(self.search_frame, text=' Back ', bg='#156823', command=self.click_back, activebackground='#5A8A29', fg='#FFFFFF')
self.button_back['font'] = Fonts().font3
self.button_back.grid(sticky="s", column=0, row=3, pady=230)
One last function was needed, in order to drop the removed specie from the Data Frame and database, which I called remove_specie()
.
def remove_specie(self):
global species
global families
global descriptions
global properties
global side_effects
global df
if self.combobox_species_remove.get() == str_no_species:
msg.showerror('Error', 'Please you need to add a specie first')
return
else:
result = msg.askyesno(title='Warning', message='Are you sure you want to remove this specie?')
if result:
remove_specie = self.combobox_species_remove.get()
row_to_remove = df.loc[df['Species'] == remove_specie].index
print(row_to_remove[0])
book = openpyxl.load_workbook(TEMPLATE_DIR)
sheet = book['Sheet1']
sheet.delete_rows(row_to_remove[0]+2) # for single row
book.save(TEMPLATE_DIR)
print(Fore.GREEN + 'Specie removed from template')
df = df.drop(row_to_remove[0])
species = get_species_array(df)
descriptions = get_descriptions_array(df)
properties = get_properties_array(df)
families = get_families_array(df)
side_effects = get_side_effects_array(df)
os.remove(IMAGES_DIR + remove_specie + '.jpg')
self.callback()
else:
return
After these changes, the GUI is now fully operational. The last step will be to create an executable with all the dependencies, using Pyinstaller
.
The full code is quite big and some parts are repetitive, so I decided not to share everything in the posts (both First Part and Second Part).
The code can be easily changed and customized for other subjects, so please feel free to download it from my github:
The idea is really good, however, the interface looks bad. You could work more on the design of the application and try to make it better and more appealing.
HI @ppopescu, thank you for your comment. I was more focus on learning how to develop a functional GUI, then on the design. But I agree with you, indeed the design could be better. Have you ever worked with tkinter? It seems to lack on pretty widgets. Beside that, have you downloaded the GUI on my github? Do you you find it user-friendly? Thanks again!