[Tutor] Python v3 Tkinter GUI program

Elliott Andrews elliott.andrews1998 at gmail.com
Mon Oct 24 17:36:55 EDT 2016


Hi there, I am making a program in python based on a Subway operation. All
descriptions and comments codes are provided in the attached program. I've
been using IDLE to run the program and it seems to work the way I want it
too.

However I have heard from many, that global variables are bad practise in
any programming language, and so I want to basically replace all my global
variables with another variable that does the same thing.

The problem is, some data is entered and then when the data is submitted,
the GUI window terminates and automatically re-opens for another data
entry. I need some sort of variable to store some data while the program
resetts itself.

Sorry is this sounds really broad, and I am happy to clarify and points.
-------------- next part --------------
''' Author: Elliott Andrews, Date: 22/09/16, Programmed with: IDLE 3.5.1 via Python 3.5.1 and Tk 8.6.4

Program Title: "Phone Ordering System for SUBS R US."

Program Description: This program is designed for a company (e.g Subway) to enter in an order for a sub as a phone operator. The phone operater will ask for
customer infomation, ingedients to be put in the sub and then order it. The phone operator will then ask the customer if they wish to cancel the order and if they
want to order another sub. After all subs have been ordered, the phone operator will tell the customer the total cost of all the subs orders and then exit the prgoram'''

from tkinter import * #Importing GUI from tkinter library to display in window.
import time #Importing time controls for diagnostic messages (readability).

#Global variables (major) have to be used to pass 'cancelled' and 'total_cost' through whole application. 
#This will ensure when window is told to destory, variables will be kept for a maximum of 5 future orders and will avoid any 'Traceback' errors.
#Not using global variables or other means such as (.get) will not work because when window is told to destory, a new window instance is created, defaulting and resetting orig variables.
global cancelled 
global total_cost
total_cost = 0 #Setting total cost (for all orders - max 5) to 0.

class Startup: #First class 'Startup' which will run diagnostic checks to make sure program will run.

        print("Current python version: 3.5.1 \nTk version: 8.6.4") #Recommended version to run python and tkinter in.

        if total_cost != 0: #If total_cost is not equal to 0, then... This variable has to be set to 0, as it will be added too, each time a order is added.
                print("Sorry, the application failed to start. Please make sure you have the latest python version and try again. \n Error: Failed to reset top variabe (Total_Cost)")
        else: #Otherwise, continue as normal.
                print("Application starting...")
        time.sleep (2) #Giving time for operator/user to see the status for application startup.
        
class GUI(Frame, Startup): #Second class 'GUI' which will run the main display (visual elements and will continue from first class).
    def __init__(self, master): #Initalising the GUI application.
 
        super(GUI, self).__init__(master) #For use with lots of mutiple instances of __init__. Future use for parenting within 'master'.
        self.grid() #Defining layout for GUI (grid method), could also use pack.
        self.clear_variables() #Stating all functions with 'def'...
        self.cancel()
        self.create_widgets()
        self.check_address()
        self.show_order()
        self.close_window()

    def close_window(self): #Defining the process to close the window, reset cancelled variable equal to 0, then quit.
            
        global cancelled
        cancelled = 0
        root.quit() #Will prepare a quit function if needed.
        
    def clear_variables(self): #Defining the process to set all other minor variables to proper type and to set them to None or 0 where appropriate.
            
        self.ordertype = StringVar() #StringVar is ideal for letters...
        self.cusName = StringVar()
        self.cusAddress = StringVar()
        self.cusPhone = StringVar() #Can use IntVar for phone number, but for textbox display only, StringVar is ok.
        self.breads_var = StringVar()
        self.cheeses_var = StringVar()
        self.sauces_var = StringVar()

        self.ordertype.set (None) #Has a 'None' value, similar to 'Null'.
        self.cusName.set (None)
        self.cusAddress.set (None)
        self.cusPhone.set (None)
        self.breads_var.set (None)
        self.cheeses_var.set (None)
        self.sauces_var.set (None)
        self.cost = 0 #Since self.cost is to be a integer, integer operations must be used
        root.quit() #Will prepare a quit function if needed.
        
    def cancel(self): #Defining the process for cancelling the order, to set all other minor variables to proper type and to set them to None or 0 where appropriate.
        #This process allows orders that have been cancelled to continue the program.
        self.ordertype = StringVar()
        self.cusName = StringVar()
        self.cusAddress = StringVar()
        self.cusPhone = StringVar()
        self.breads_var = StringVar()
        self.cheeses_var = StringVar()
        self.sauces_var = StringVar()

        self.ordertype.set (None)
        self.cusName.set (None)
        self.cusAddress.set (None)
        self.cusPhone.set (None)
        self.breads_var.set (None)
        self.cheeses_var.set (None)
        self.sauces_var.set (None)
        
        global total_cost 
        total_cost-=self.cost #Following on from when order is cancelled, subtract the cost of that order (since cancelled).
        self.cost = 0
        global cancelled #If cancel is true (when equal to 1) then do cancel
        cancelled = 1
        root.quit() #A quit function if needed 
            
    def create_widgets(self): #Define the function for 'create_widgets'. Widgets are all the objects/clickables that appear on the GUI.

        #Stored lists. These store the ingredients for the subs and will be listed on the GUI for the user to choose from.    
        breads = ["White", "Wheat"] #"Multigrain" - Can be added to list, to see GUI respond to new ingredients.
        cheeses = ["Swiss", "Cheddar"] #Edam - Can be added...
        sauces = ["Mayo"] #Honey Mustard - Can be added...

        #Create a simple label (text format) which will display 'text=...' in a certain table position. Text will align to the left (W = West).
        Label(self,
              text = "Welcome! Please enter customer's infomation and sub-sandwich ingredients/choices. Note: All fields and required below..."
              ).grid(row = 0, column = 0, columnspan = 3, sticky = W)

        Label(self,
              text = "How will the customer receive the order?",
              ).grid(row = 1, column = 0, sticky = W)

        #Using the radiobutton operation to create a radio button. Only two options needed so list display is not really needed.
        Radiobutton(self,
                    text = "Delivery ($3)", #Text for radio button to display.
                    variable = self.ordertype, #Variable to assign value to.
                    value = "Delivery", #Value to be used as variable.
                    command = self.check_address, #When radio button is presses, go to module 'check_address'.
                    ).grid(row = 2, column = 0, sticky = W)

        Radiobutton(self,
                    text = "Pickup",
                    variable = self.ordertype,
                    value = "Pickup",
                    command = self.check_address,
                    ).grid(row = 3, column = 0, sticky = W)


        Label(self, 
              text = "Customer's Name:",
              ).grid(row = 4, column = 0, sticky = W)
        self.cusName = Entry(self)
        self.cusName.grid(row = 5,column = 0, sticky = W)


        Label(self,
              text = "Customer's Address:",
              ).grid(row = 6, column = 0, sticky = W)
        self.cusAddress = Entry(self)
        self.cusAddress.grid(row = 7,column = 0, sticky = W)

        
        Label(self,
              text = "Customer's Phone Number:",
              ).grid(row = 8, column = 0, sticky = W)
        self.cusPhone = Entry(self)
        self.cusPhone.grid(row = 9, column = 0, sticky = W)


        Label(self,
              text = "Bread Type ($2):",
              ).grid(row = 10, column = 0, sticky = W)
        self.breads_btns = [] #Stating a empty list to be filled with ingredients from lists above (i.e bread = ...)
        for i in range(len(breads)): #Let 'i' be the variable to find the range within the length of the list 'breads'. Essentialy will gather all bread options.
            rb = Radiobutton(self, variable = self.breads_var, value = breads[i] , anchor = W, text = breads[i]) #List all breads as radiobuttons with their assigned names in terms of 'i'.
            self.breads_btns.append(rb) #Attach these radiobuttons to 'rb'.
            rb.grid(row =(i + 11), column = 0, sticky = W) #State 'rb' to use grid. Start from row 11 and list breads downwards and align to left.

        
        Label(self,
              text = "Cheeses ($1):", #Copy and pasted from above as cheese's will use same code as bread's.
              ).grid(row = 10, column = 1, sticky = W)
        self.cheeses_btns = []
        for i in range(len(cheeses)):
            rb = Radiobutton(self, variable = self.cheeses_var, value = cheeses[i], anchor = W, text = cheeses[i])
            self.cheeses_btns.append(rb)
            rb.grid(row =(i + 11), column = 1, sticky = W)

        
        Label(self,
              text = "Salads ($2):",
              ).grid(row = 33, column = 0, sticky = W)

        self.salads_lettuce = BooleanVar() #Defining salads as a BooleanVar (True or False) since checkbuttons are needed to be setup differetly to radiobuttons.
        #Checkbuttons can store many values opposed to radiobuttons which only store one, hence why my checkbuttons are listed seperatly.
        Checkbutton(self,
                    text = "Lettuce", #Text to display next to checkbutton.
                    variable = self.salads_lettuce, #Variable to be true when checkbutton is clicked.
                    ).grid(row = 34, column = 0, sticky = W) #To be placed on grid at row 34, aligned left.
        self.salads_tomato = BooleanVar()
        Checkbutton(self,
                    text = "Tomato",
                    variable = self.salads_tomato,
                    ).grid(row = 35, column = 0, sticky = W)
        self.salads_capsicum = BooleanVar()
        Checkbutton(self,
                    text = "Capsicum",
                    variable = self.salads_capsicum,
                    ).grid(row = 36, column = 0, sticky = W)

        
        Label(self,
              text = "Meats ($4):",
              ).grid(row = 33, column = 1, sticky = W)

        self.meats_bacon = BooleanVar() #Same method for meats so copy and pasted, as meats are checkboxes as well. 
        Checkbutton(self,
                    text = "Bacon",
                    variable = self.meats_bacon,
                    ).grid(row = 34, column = 1, sticky = W)
        self.meats_salami = BooleanVar()
        Checkbutton(self,
                    text = "Salami",
                    variable = self.meats_salami,
                    ).grid(row = 35, column = 1, sticky = W)
        self.meats_ham = BooleanVar()
        Checkbutton(self,
                    text = "Ham",
                    variable = self.meats_ham,
                    ).grid(row = 36, column = 1, sticky = W)
        
        Label(self, 
              text = "Sauces ($1):",
              ).grid(row = 55, column = 0, sticky = W) 
        self.sauces_btns = []
        for i in range(len(sauces)): #Copy and pasted from above as sauces's will use same code as cheese's.
            rb = Radiobutton(self, variable = self.sauces_var, value = sauces[i], anchor = W, text = sauces[i])
            self.sauces_btns.append(rb)
            rb.grid(row =(i + 56), column = 0, sticky = W)



        Button(self, #Create a button by defing a button function.
                text = "Show Order", #Text to display on the button
                command = self.show_order, #When button is pressed, go to show_order module (to display summary message by refresh).
                ).grid(row = 60, column = 0, sticky = W) #To be placed on grid at row 60, aligned to left.
        
        Button(self,
                text = "Cancel Order",
                command = self.cancel, #When button is pressed, go to cancel module (to close current window and start again for another order).
                ).grid(row = 60, column = 1, sticky = W)
        
        Button(self,
                text = "Save and order another sub! (Max 5)",
                command =  self.close_window, #When button is pressed, go to close_window module (to close window, save cost of sub and to order another sub).
                ).grid(row = 60, column = 2, sticky = W)
        
        Button(self,
                text = "Finished? Exit program",
                command = exit, #When button is pressed, simply exit program (prompt will appear asking user to confirm to kill program).
                ).grid(row = 60, column = 3, sticky = W)

                
    def check_address(self): #Defining the process to grey out option for filling in address, when 'pickup' is selected.
        orderselection[item_ct] = self.ordertype.get() #Get the value for variable self.ordertype to determine if pickup or delivery is selected.
        #Then put that value in another variable 'orderselection', to be appended to the orderselction list (item count).

        if orderselection[item_ct] == "Pickup": #If statement, if orderselction has been choosen as 'pickup' then...
            self.cusAddress['state'] = DISABLED #Set self.cusAddress (entry box for address) to be disabled (grey out).
            Label(self, state = DISABLED, #Also set the label for Customer's Address to be disabled (grey out).
              text = "Customer's Address:",
              ).grid(row = 6, column = 0, sticky = W) #Overwrite exisitng label at grid location row 6, column 0 to grey out.
            
        elif orderselection[item_ct] == "Delivery": #Else-if statement, elif orderselction has been choosen as 'delivery' then...
            self.cusAddress['state'] = NORMAL #Set self.cusAddress (entry box for address) to be back to normal (un-grey).
            Label(self, state = NORMAL, #Also set the label for Customer's Address to be back to normal (un-grey).
              text = "Customer's Address:",
              ).grid(row = 6, column = 0, sticky = W) #Overwrite exisitng label at grid location row 6, column 0 to un-grey.

        else: #Else statement, if none of these if statments are true, then display the message in IDLE...
                print("A orderselection has not been choosen")

    def show_order(self): #Defining the process to display all order infomation in text box and to calculate prices of all items selected.
            
        saladschoice[item_ct] = "" #Setting items, if they have selected a ceratin ingredient/item then assign to saladschoice list with relation to item count [item_ct].
        if self.salads_lettuce.get(): #If self.salads_lettuce has the value lettuce selected, then add lettuce to the list to be displayed.
            saladschoice[item_ct] += "Lettuce, " #Text to be added on, with a comma and space for other salads (tidy formating).
        if self.salads_tomato.get():
            saladschoice[item_ct] += "Tomato, " #Copy and paste above
        if self.salads_capsicum.get():
            saladschoice[item_ct] += "Capsicum, " #Copy and paste above

        meatschoice[item_ct] = "" #Setting items, if they have selected a ceratin ingredient/item then assign to meatschoice list with relation to item count [item_ct].
        if self.meats_bacon.get(): #If self.meats_bacon has the value bacon selected, then add bacon to the list to be displayed.
            meatschoice[item_ct] += "Bacon, "
        if self.meats_salami.get():
            meatschoice[item_ct] += "Salami, " #Copy and paste above
        if self.meats_ham.get():
            meatschoice[item_ct] += "Ham, " #Copy and paste above
            
        orderselection[item_ct] = self.ordertype.get() #Process of actual assignment from ingredients selected to their defined list with relation to item count (item_ct).
        cname[item_ct] = self.cusName.get()
        caddress[item_ct] = self.cusAddress.get()
        cphone[item_ct] = self.cusPhone.get()
        breadchoice[item_ct] = self.breads_var.get()
        cheesechoice[item_ct] = self.cheeses_var.get()
        #Note that salads and meats do not need a get operation as they have already been performed above. Salads and meats once again need to get each value
        #individually, to display mutiple values selected, meaning several checks are needed for each.
        saucechoice[item_ct] = self.sauces_var.get()

        orderselection.append('')
        #Append functions for storing ingredients in lists. Currently program only displays total cost of all orders who have ordered many subs (max of 5).
        #However extra code below will make it easy if this program was to be extended in the future to show ingredients of all subs ordered for one summary page.
        #For this particuar program, this functionality is not needed, but the code here would be able to add extra functionality if desired.
        cname.append('') #These append function are telling all values using that are using the 'get' operation to assign to the variable with relation to [item_ct]
        #Therefor enabling these statments here to attach/add all value into the list.

        caddress.append('')
        cphone.append('')
        breadchoice.append('') 
        cheesechoice.append('')
        saladschoice.append('')
        meatschoice.append('')
        saucechoice.append('')

        self.results_txt = Text(self, width = 60, height = 15, wrap = WORD) #Creating the textbox as self.results_txt.
        #Setting height and width and wraping text so text will appear on next line instead of going off the page.
        self.results_txt.grid (row = 61, column = 0) #Placment of text box will be on grid, on row 61, column 0.
        
        message = "Transportation of sub-sandwich to customer: " #Display first statment of message text...
        message += orderselection[item_ct] #Add onto previous statement, the ordertype to display.
        message += "\nCustomer's Name: " #And so on...
        message += cname[item_ct]
        message += "\nCustomer's Address: "
        message += caddress[item_ct]
        message += "\nCustomer's Phone: "
        message += cphone[item_ct]
        message += "\nBread Type: "
        message += breadchoice[item_ct]
        message += "\nCheese Type: "
        message += cheesechoice[item_ct]
        message += "\nSalads Selected: "
        message += saladschoice[item_ct]
        message += "\nMeats Selected: "
        message += meatschoice[item_ct]
        message += "\nSauces Selected: "
        message += saucechoice[item_ct]

        #Messages above will only display if...
        #This long if statment has been created for purposes of validation, meaning the customer must at least enter a certain amount of infomation (seen below)
        #TO be able to actually order (e.g It is no good someone ordering a sub if no ingredients are selected!)
        #!= stament mean if something is not equal to something then... (i.e meaning the customer has to select something) and 'and' is to add another condition
        #instead of having mutiple if staments.
        if orderselection[item_ct] != "None" and  cname[item_ct] != "None" and breadchoice[item_ct] != "None" and cheesechoice[item_ct] != "None" and saucechoice[item_ct] != "None":
                
                if orderselection[item_ct] == "Delivery": #Add a delivery cost of 3 to self.cost variable if delivery is selected.
                        #self.cost is the cost of each sub ordered, it will be used later on to add all costs together.
                        self.cost += 3
                else:
                        print("Delivery/Pickup has not been selected, add a cost of $3 if Delivery")
                        # In theory it is not possible for the program to run this statment because of the validation statment, but is good practise to have a secondary
                        #validation statment, if somehow the first one shall fail.
                                               
                if breadchoice[item_ct] != "None":
                        self.cost += 2
                        #If breadchoice does not equal none (i.e a bread has been selected) then add a cost of 2.
                else:
                        print("A bread is not selected and costs $2")
                        

                if cheesechoice[item_ct] != "None":
                        self.cost += 1
                        #And so on...
                else:
                        print("A cheese is not selected and costs $1")
                        

                if saladschoice[item_ct] != "":
                        self.cost += 2
                        #Any type of salads will cost $2, meaning the salad cost is one of the few options which will have (each cost charge)
                        #(e.g If I order just lettuce, the cost is $2, if order lettuce, tomato and capsicum the cost is still $2)
                else:
                        print("A salad/s is not selected, all selected cost $2")


                if meatschoice[item_ct] != "":
                        self.cost += 4
                else:
                        print("A meat/s is selected, all selected cost $4")

                                                     
                if saucechoice[item_ct]  != "None":
                        self.cost += 1
                        message += "\n\nCost of this sub: $"
                        #Since if all staments have added all costs sucessfully then we can display the total below:
                        message += str(self.cost) #Converting integer into stringvar to display in text box.
                        message += "\n\nTotal overall cost: $"
                        global total_cost #Stating the total_cost variable
                        total_cost=total_cost+self.cost #Adding the single cost to the total cost(which will keep value as next sub is ordered).
                        message += str(total_cost) #Converting integer into stringvar to display in text box.
                else:
                        print("A sauce has not been selected, and costs $1")
        else:
                print("Program is running through validation checks... Complete!")

        self.results_txt.delete(0.0, END) #Stating the end of the textbox message
        self.results_txt.insert(0.0, message) #Will insert all variables stated as 'message' into text box.
              
choice = 0 #This is the number of subs ordered (will start as 0, since this is the deafult order for counting (e.g 0,1,2,3))
item_ct=0 #This is also the number of subs ordered but will be used for counting till a maximum of 5.

orderselection = [] #Again these statments need to be listed as all items and ingredients need to be saved before another sub is to be ordered.
orderselection.append('') #Confirming that the above items have been ordered, they are put in the list once more.
cname = []
cname.append('')
caddress = []
caddress.append('')
cphone = []
cphone.append('')
breadchoice = []
breadchoice.append('')
cheesechoice = []
cheesechoice.append('')
saladschoice = []
saladschoice.append('')
meatschoice = []
meatschoice.append('')
saucechoice = []
saucechoice.append('')


while item_ct < 5: #To run program one again, the ending and restarting functions have all been put in a while item_ct (subs order count is less than 5)
        #(i.e 4,3,2,1 and 0)
        global cancelled #Stating the cancelled variable, to reset itself, so when the program restarts, it won't keep cancelling in on itself, repeating.
        cancelled=0
           
        root = Tk() #The actual statment which opens the window, providing there is an import statment for 'tkinter'.
        
        toplevel = root.winfo_toplevel()#Setting window header to attach to top of screen (first half of making window maximised)
        toplevel.wm_state('zoomed')#Setting inner window to stretch window to all sides of screen (second half of making window maximised)
        #Two above statments automatically will maximise window when program starts.

        root.title("Phone Ordering System - SUBS R US") #Text title for the window to display.

        app = GUI(root) #Main application is defined in class, GUI with respect to 'root'.
        root.mainloop() #Enables program to repeat in a loop.

        root.destroy() #Will close the window.
        print("You have finished order number",str(item_ct))
        if cancelled==0:
                item_ct+=1 #Adding 1 to item_ct (sub order number)

        if item_ct > 4: #When tem_ct is more than 4, then a maximum of 5 subs has been ordered, the program should have been closed and a summary statement
                #Will appear in IDLE.
                print("The program has ended automatically as the maximum of 5 subs has been ordered")
                print("Please wait, we are calculating your order total...")
                time.sleep (5) #Giving a time of 5 seconds or the user to read.
                print("You have ordered a maximum of 5 subs.")
                print("The total cost of all subs ordered is $",str(total_cost))
        else: #If item_cut is not > 4 the 'continue' the program (begin program again).
                print("Ready for a new order!")
                continue

        





More information about the Tutor mailing list