Feb-12-2020, 07:20 PM
Module: main.py
import tkinter import application class TkinterWindow(tkinter.Tk): def __init__(self): super().__init__() self.geometry('640x480') def main(): window = TkinterWindow() app = application.Calculator(window) app.pack(fill='both', expand=1, padx=10, pady=10) app.mainloop() if __name__ == '__main__': main()Module: application.py
import tkinter class Calculator(tkinter.Frame): def __init__(self, parent): super().__init__(parent) self.rowconfigure(0, weight=1) # Row for display self.rowconfigure(1, weight=1) # Row for buttons frame self.columnconfigure(0, weight=1) self.focus_set() # Variables for calculations and process control self.display_chars = tkinter.StringVar() self.display_chars.set(0) self.aggregator = [] self.operator = None self.decimal_separator = False self.total = 0 self.error = None self.first_operator = 0 self.second_operator = 0 self.first_operator_status = False self.new_entry = True self.aggregator_status = 'Inactive' self.place_frames() self.bind("<Key>", self.key_handler) self.bind("<Return>", self.return_key_handler) self.bind("<BackSpace>", self.backspace_handler) self.bind("<Escape>", self.esc_handler) ##self.debugger() def place_frames(self): display_frame = DisplayContainer(self ).grid(row=0, column=0, sticky='nsew') buttons_frame = ButtonsContainer(self ).grid(row=1, column=0, sticky='nsew') def debugger(self): print(f'AGGREGATOR current content: {self.aggregator}') print(f'AGGREGATOR current status: {self.aggregator_status}') print(f'new_entry: {self.new_entry}') print(f'decimal_separator: {self.decimal_separator}') print(f'Current OPERATOR: {self.operator}') print(f'first_operator value: {self.first_operator}') print(f'second_operator value: {self.second_operator}') print(f'first_operator_status: {self.first_operator_status}') print(f'TOTAL: {self.total}') print('--------------------------------------------------') def key_handler(self, event): '''Handles inputs comming from the keyboard. This function only accepts four types of keyboard inputs: (1) Numbers (2) Basic math operators: +, -, *, / (3) '=' key (4) ',' or '.' for decimal separator Obs: Other keys are ignored Cases (1) and (4) make the respective value to be shown at the calculator display. Case (2) calls the operator handling, that either stores a value or executes an operation. Case (3) calls the resolving function. ''' if event.char.isdigit(): numerical_char = event.char self.put_char_on_display(numerical_char) #self.debugger() elif event.char in ('+', '-', '*', '/'): operator_char = event.char self.operators_handler(operator_char) elif event.char == '=': self.resolve_operation() elif event.char == ',' or event.char == '.': # There can be only one decimal separator at time. # # Also, self.new_entry checks if it´s the initial entry # at first or second number. If so, considers its # beggining as been a decimal of 0. That´s the case # when user presses comma/dot at the beggining of the entry. if self.decimal_separator == False: if self.new_entry: zeropoint_char = '0.' self.put_char_on_display(zeropoint_char) self.decimal_separator = True #self.debugger() else: decimal_char = '.' self.put_char_on_display(decimal_char) self.decimal_separator = True #self.debugger() def buttons_handler(self, button): '''Handles inputs comming from the calculator´s buttons. As function key_handler does, it handles the following inputs: (1) Numbers, that are displayed or stored. (2) Basic math operators, that stores a value and operator. or process an operation, according to context. (3) Resolves an operation with the values and operator stored. (4) Sets an decimal separator ''' if button in range(10): numerical_char = str(button) self.put_char_on_display(numerical_char) #self.debugger() elif button in ('+', '-', '*', '/'): operator_char = str(button) self.operators_handler(operator_char) elif button == '=': self.resolve_operation() elif button == '.': # There can be only one decimal separator at time. # # Also, self.new_entry checks if it´s the initial entry # at first or second number. If so, clicking or typing the # comma or dot makes the program consider the beggining as # been a decimal of zero. # That´s the case when user presses comma/dot at the # beggining of the entry. if self.decimal_separator == False: if self.new_entry: zeropoint_char = '0.' self.put_char_on_display(zeropoint_char) self.decimal_separator = True #self.debugger() else: decimal_char = '.' self.put_char_on_display(decimal_char) self.decimal_separator = True #self.debugger() def return_key_handler(self, event): self.resolve_operation() def backspace_handler(self, event): if self.aggregator: del self.aggregator[-1] self.display_chars.set(self.aggregator) #self.debugger() def esc_handler(self, event): self.set_to_default() def put_char_on_display(self, char): '''Makes an numeric input from keyboard or button be displayed. This function handles two situations: - Checks if it is the beggining of the input, through the self.new_entry variable. If true, clears the inputs aggregator and displays the new value passed, allowing posterior values to be added. - If chars input is process is already in course, keep adding the values passed through the params into the inputs aggregator. The aggregator variable self.aggregator progressivly gets numbers or a decimal separator to form a value that will be stored when an operator or the equal sign is activated. ''' if self.new_entry == True: self.aggregator = [] self.aggregator.append(char) self.display_chars.set(self.aggregator) self.aggregator_status = 'Active' self.new_entry = False else: self.aggregator.append(char) self.display_chars.set(self.aggregator) self.aggregator_status = 'Active' def operators_handler(self, char): '''Gets an operator char and handles as context. Set an operator via button click or keyboard envolves three situations: (1) If no value is stored yet, and inputs are already in course. This case, makes the value in aggregator be stored as the first operand. (2) First operand is already stored, no new values are been inserted. The program then changes the operator, if it´s different. (3) If first operand is already stored, a new input is in course. The program stores the value as the second operand and executes the operation according to the operator set. ''' # Assingn the first operand if self.first_operator_status == False: self.first_operator = self.get_values_from_aggregator() self.first_operator_status = True self.operator = char self.new_entry = True self.decimal_separator = False self.aggregator_status = 'Inactive' #self.debugger() else: # Handles user changing the operators if (self.first_operator_status == True and self.aggregator_status == 'Inactive'): if char != self.operator: self.operator = char #self.debugger() else: # If user press any operator after typing the second value # the value on display will be assingned in second_operator # variable and resolve_operation() will be called. self.second_operator = self.get_values_from_aggregator() self.operator = char self.aggregator_status = 'Inactive' self.resolve_operation() def get_values_from_aggregator(self): '''Converts the content of aggregator in a float number and returns it. If nothing is inserted into aggregator (and it´s empty), then it means that the value is zero. ''' values = ''.join(self.aggregator) if values: return float(values) else: return 0 def resolve_operation(self): '''Stores value as first or second operator, then executes the corresponding operation. So, call the finish method with its result as parameter. ''' if self.first_operator_status == False: self.first_operator = self.get_values_from_aggregator() else: self.second_operator = self.get_values_from_aggregator() if self.operator == '+': result = self.first_operator + self.second_operator self.finish(result) elif self.operator == '-': result = self.first_operator - self.second_operator self.finish(result) elif self.operator == '*': result = self.first_operator * self.second_operator self.finish(result) elif self.operator == '/': try: result = self.first_operator / self.second_operator except ZeroDivisionError as error: self.error = error self.finish(0) except: self.error = error self.finish(0) else: self.finish(result) def finish(self, result): # Displays the result and sets variables for new operations. self.total = result self.display_chars.set(f'{self.total}') self.aggregator = [str(self.total)] self.aggregator_status = 'Inactive' self.new_entry = True self.decimal_separator = False self.first_operator_status = False if self.error: if isinstance(self.error, ZeroDivisionError): self.set_to_default() self.display_chars.set('ZeroDivisionError') else: set_to_default() self.display_chars.set('Unknown Error') #self.debugger() def set_to_default(self): '''Sets program to its default parameters.. This happens if an error is found or CLEAR is activated. ''' self.display_chars.set(0) self.aggregator = [] self.operator = None self.decimal_separator = False self.total = 0 self.error = None self.first_operator = 0 self.second_operator = 0 self.first_operator_status = False self.new_entry = True self.aggregator_status = 'Inactive' #self.debugger() class DisplayContainer(tkinter.Frame): def __init__(self, parent): super().__init__(parent) self.parent = parent tkinter.Label( self, background='lightgrey', relief='ridge', border=5, textvariable=self.parent.display_chars, ).pack(fill='both', expand=1) class ButtonsContainer(tkinter.Frame): def __init__(self, parent): super().__init__(parent) self.parent = parent for x in range(0, 5): self.rowconfigure(x, weight=1) if x < 4: self.columnconfigure(x, weight=1) self.create_buttons() def create_buttons(self): pad = 15 row = 0 column = 0 for i in range(10): if i == 0: tkinter.Button( self, text=i, padx=pad, pady=pad, command=lambda n=i: self.parent.buttons_handler(n) ).grid(row=3, column=1, sticky='nsew') else: tkinter.Button( self, text=i, padx=pad, pady=pad, command=lambda n=i: self.parent.buttons_handler(n) ).grid(row=row, column=column, sticky='nsew') if column == 2: column = 0 row += 1 else: column += 1 for i in [ ['+', 0, 3], ["-", 1, 3], ['*', 2, 3], ['/', 3, 3], ['.', 3, 0], ['=', 3, 2], ['CLEAR', 4, 0] ]: if i[0] == 'CLEAR': tkinter.Button( self, text=i[0], padx=pad, pady=pad, command=self.parent.set_to_default ).grid(row=i[1], column=i[2], columnspan=4, sticky='nsew') elif i[0] == '=': tkinter.Button( self, text=i[0], padx=pad, pady=pad, command=self.parent.resolve_operation ).grid(row=i[1], column=i[2], sticky='nsew') else: tkinter.Button( self, text=i[0], padx=pad, pady=pad, command=lambda n=i[0]: self.parent.buttons_handler(n) ).grid(row=i[1], column=i[2], sticky='nsew')