МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
Факультет вычислительной математики и кибернетики
Кафедра программирования
Выполнил: студент группы 301
Иванов Иван Иванович
Проверил: доцент
Петров П.П.
Москва, 2023
В современном мире разработка программного обеспечения с графическим интерфейсом пользователя (GUI) стала неотъемлемой частью процесса создания прикладных программ. Даже самые простые утилиты, такие как калькулятор, требуют удобного пользовательского интерфейса для обеспечения эффективного взаимодействия с пользователем.
Целью данной самостоятельной работы является разработка калькулятора с графическим интерфейсом на языке программирования Python с использованием библиотеки Tkinter. В процессе работы будут применены основные принципы объектно-ориентированного программирования, а также методы создания графического пользовательского интерфейса.
Задачи работы включают:
Практическая значимость работы заключается в создании полнофункционального приложения, которое может быть использовано для выполнения базовых математических вычислений. Кроме того, полученные навыки разработки графического интерфейса могут быть применены при создании более сложных программных продуктов.
В процессе разработки будут рассмотрены такие аспекты, как размещение элементов управления на форме, обработка событий, валидация пользовательского ввода и реализация вычислительной логики. Окончательный продукт будет представлять собой завершенное приложение с интуитивно понятным интерфейсом.
Графический интерфейс пользователя (GUI – Graphical User Interface) – это тип интерфейса, который позволяет пользователям взаимодействовать с электронными устройствами через графические элементы управления: окна, иконки, меню, кнопки, поля ввода и другие визуальные индикаторы. Основная цель GUI – сделать взаимодействие с компьютерной программой интуитивно понятным и удобным для пользователя.
Основными преимуществами графических интерфейсов по сравнению с текстовыми (командной строкой) являются:
При проектировании графического интерфейса важно учитывать несколько основных принципов:
Для создания графических интерфейсов в языке Python существует несколько библиотек, среди которых наиболее распространены:
В данной работе мы будем использовать Tkinter как наиболее доступную и простую в освоении библиотеку, которая при этом входит в стандартную поставку Python.
Tkinter (от англ. "Tk interface") – это стандартная библиотека Python для создания графического интерфейса пользователя. Она представляет собой привязку к инструментарию Tk, который был первоначально разработан для языка программирования Tcl. Tkinter доступен на большинстве платформ Python (Windows, macOS, Unix) и входит в стандартную поставку языка.
Основные компоненты Tkinter, которые мы будем использовать при разработке калькулятора:
Базовая структура приложения Tkinter выглядит следующим образом:
import tkinter as tk
# Создание основного окна
root = tk.Tk()
root.title("Название приложения")
# Добавление виджетов
label = tk.Label(root, text="Привет, мир!")
label.pack()
button = tk.Button(root, text="Нажми меня", command=some_function)
button.pack()
# Запуск главного цикла
root.mainloop()
Важным аспектом разработки с использованием Tkinter является понимание диспетчеров геометрии:
Для нашего калькулятора наиболее подходящим будет диспетчер grid, так как кнопки калькулятора естественным образом образуют табличную структуру.
Также в Tkinter широко используется механизм обратных вызовов (callbacks), который позволяет связать действия пользователя (например, нажатие кнопки) с выполнением определенных функций. Это осуществляется через параметр command при создании виджетов:
def button_click():
print("Кнопка была нажата")
button = tk.Button(root, text="Нажми меня", command=button_click)
Для хранения и обработки данных мы будем использовать переменные Tkinter (StringVar, IntVar, DoubleVar, BooleanVar), которые предоставляют механизм привязки данных к GUI элементам с автоматическим обновлением интерфейса при изменении значений переменных.
Перед началом разработки необходимо чётко определить функциональные требования к приложению. Для нашего калькулятора определим следующий набор требований:
Также важно определить нефункциональные требования:
На основе функциональных требований спроектируем интерфейс калькулятора. Основными компонентами интерфейса будут:
Далее представим макет интерфейса калькулятора:
+-------------------------------+
| 0.0 | <- Поле вывода
+-------------------------------+
| CE | C | +/- | % | | <- Дополнительные кнопки
+----+----+----+----+ |
| 7 | 8 | 9 | / | | <- Цифровые кнопки и
+----+----+----+----+ | операционные кнопки
| 4 | 5 | 6 | * | |
+----+----+----+----+ |
| 1 | 2 | 3 | - | |
+----+----+----+----+ |
| 0 | . | = | + | |
+----+----+----+----+---------+
Для реализации этого интерфейса в Tkinter мы будем использовать менеджер геометрии grid, который позволит нам легко создать такую табличную структуру. Все кнопки будут размещены в соответствующих ячейках сетки.
При проектировании также учтем следующие аспекты:
Для разработки калькулятора будем использовать объектно-ориентированный подход. Создадим класс Calculator, который будет инкапсулировать всю логику работы калькулятора и взаимодействие с пользовательским интерфейсом.
Основная структура программы будет следующей:
# Импорт необходимых модулей
import tkinter as tk
from tkinter import messagebox
class Calculator:
def __init__(self, master):
self.master = master
self.master.title("Калькулятор")
self.master.resizable(False, False) # Фиксированный размер окна
# Переменные для хранения состояния калькулятора
self.current_input = "" # Текущий ввод
self.first_operand = 0 # Первый операнд
self.operation = "" # Текущая операция
self.result_displayed = False # Флаг отображения результата
# Создание переменной Tkinter для отображения в поле ввода
self.display_var = tk.StringVar()
self.display_var.set("0")
# Создание интерфейса
self.create_widgets()
def create_widgets(self):
# Здесь будет код создания всех виджетов интерфейса
pass
def digit_press(self, digit):
# Обработка нажатия цифровой кнопки
pass
def operation_press(self, op):
# Обработка нажатия кнопки операции
pass
def equals_press(self):
# Обработка нажатия кнопки равно
pass
def clear_press(self):
# Обработка нажатия кнопки очистки
pass
def calculate(self):
# Выполнение вычислений
pass
# Создание и запуск приложения
if __name__ == "__main__":
root = tk.Tk()
app = Calculator(root)
root.mainloop()
Такая структура программы позволит нам:
Теперь реализуем метод create_widgets(), который будет отвечать за создание всех элементов графического интерфейса нашего калькулятора:
def create_widgets(self):
# Создание поля вывода (дисплея калькулятора)
display_frame = tk.Frame(self.master, height=50, bg="lightgray")
display_frame.pack(fill='x')
display = tk.Entry(display_frame,
textvariable=self.display_var,
font=('Arial', 18),
bd=10,
insertwidth=4,
width=14,
bg="white",
justify='right')
display.pack(fill='both', expand=True)
# Создание рамки для кнопок
buttons_frame = tk.Frame(self.master, bg="gray")
buttons_frame.pack(fill='both', expand=True)
# Определение кнопок калькулятора
buttons = [
('CE', 0, 0), ('C', 0, 1), ('+/-', 0, 2), ('%', 0, 3),
('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('/', 1, 3),
('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('*', 2, 3),
('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('-', 3, 3),
('0', 4, 0), ('.', 4, 1), ('=', 4, 2), ('+', 4, 3)
]
# Создание кнопок
for (text, row, col) in buttons:
if text in '0123456789.':
# Цифровые кнопки и точка
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#f0f0f0", # Светло-серый цвет для цифр
command=lambda t=text: self.digit_press(t))
elif text in '+-*/':
# Кнопки операций
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ffa54f", # Оранжевый цвет для операций
command=lambda t=text: self.operation_press(t))
elif text == '=':
# Кнопка равно
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#87cefa", # Голубой цвет для равно
command=self.equals_press)
elif text == 'C':
# Кнопка очистки текущего ввода
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ff6347", # Красноватый цвет для очистки
command=self.clear_press)
elif text == 'CE':
# Кнопка полной очистки
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ff6347", # Красноватый цвет для очистки
command=self.reset)
elif text == '+/-':
# Кнопка смены знака
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#d3d3d3", # Серый цвет для доп. функций
command=self.toggle_sign)
elif text == '%':
# Кнопка процента
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#d3d3d3", # Серый цвет для доп. функций
command=self.percent)
# Размещаем кнопку в сетке с небольшими отступами
button.grid(row=row, column=col, padx=2, pady=2, sticky="nsew")
# Настраиваем, чтобы столбцы и строки растягивались равномерно
for i in range(5): # 5 строк
buttons_frame.grid_rowconfigure(i, weight=1)
for i in range(4): # 4 столбца
buttons_frame.grid_columnconfigure(i, weight=1)
В данном методе мы создаем все необходимые элементы интерфейса:
self.display_var;Теперь реализуем методы, которые будут обрабатывать взаимодействие пользователя с калькулятором:
def digit_press(self, digit):
# Если предыдущее действие было вывод результата, то начинаем новый ввод
if self.result_displayed:
self.current_input = ""
self.result_displayed = False
# Обрабатываем ввод десятичной точки
if digit == '.':
if '.' not in self.current_input:
# Если точки еще нет, добавляем её
if not self.current_input:
# Если ввод пуст, добавляем "0."
self.current_input = "0."
else:
self.current_input += "."
# Игнорируем попытку ввести вторую точку
else:
# Если текущий ввод "0", заменяем его на цифру
if self.current_input == "0":
self.current_input = digit
else:
# Иначе добавляем цифру к текущему вводу
self.current_input += digit
# Обновляем отображение
self.display_var.set(self.current_input)
def operation_press(self, op):
# Если есть текущий ввод
if self.current_input:
# Если это не первая операция, сначала выполняем предыдущую
if self.operation and not self.result_displayed:
self.equals_press()
# Сохраняем первый операнд и операцию
self.first_operand = float(self.current_input)
self.operation = op
self.current_input = ""
elif self.result_displayed: # Если нажали операцию после вывода результата
# Используем результат как первый операнд
self.operation = op
self.current_input = ""
self.result_displayed = False
# Если нет текущего ввода и это не после вывода результата, игнорируем
def equals_press(self):
# Если нет операции, нечего вычислять
if not self.operation:
return
# Если нет второго операнда, используем первый
if not self.current_input:
self.current_input = str(self.first_operand)
try:
# Получаем второй операнд
second_operand = float(self.current_input)
# Выполняем операцию
if self.operation == '+':
result = self.first_operand + second_operand
elif self.operation == '-':
result = self.first_operand - second_operand
elif self.operation == '*':
result = self.first_operand * second_operand
elif self.operation == '/':
# Проверяем деление на ноль
if second_operand == 0:
messagebox.showerror("Ошибка", "Деление на ноль невозможно")
self.reset() # Сбрасываем калькулятор
return
result = self.first_operand / second_operand
# Форматируем результат (убираем десятичную часть, если она равна 0)
if result == int(result):
result = int(result)
# Обновляем состояние и отображение
self.current_input = str(result)
self.display_var.set(self.current_input)
self.first_operand = result
self.operation = ""
self.result_displayed = True
except ValueError:
messagebox.showerror("Ошибка", "Некорректный ввод")
self.reset()
except Exception as e:
messagebox.showerror("Ошибка", str(e))
self.reset()
def clear_press(self):
# Очищаем текущий ввод
self.current_input = "0"
self.display_var.set(self.current_input)
def reset(self):
# Полная очистка калькулятора
self.current_input = "0"
self.first_operand = 0
self.operation = ""
self.result_displayed = False
self.display_var.set(self.current_input)
def toggle_sign(self):
# Смена знака числа
if self.current_input and self.current_input != "0":
if self.current_input.startswith("-"):
self.current_input = self.current_input[1:]
else:
self.current_input = "-" + self.current_input
self.display_var.set(self.current_input)
def percent(self):
# Вычисление процента
if self.current_input:
try:
value = float(self.current_input)
# Если есть первый операнд, вычисляем процент от него
if self.first_operand != 0 and self.operation in ['+', '-']:
value = self.first_operand * (value / 100)
else:
value = value / 100
# Форматируем результат
if value == int(value):
value = int(value)
self.current_input = str(value)
self.display_var.set(self.current_input)
except ValueError:
messagebox.showerror("Ошибка", "Некорректный ввод")
self.reset()
В приведенном коде реализованы следующие функции:
Соединим все части вместе, чтобы получить полный исходный код калькулятора:
import tkinter as tk
from tkinter import messagebox
class Calculator:
def __init__(self, master):
self.master = master
self.master.title("Калькулятор")
self.master.resizable(False, False) # Фиксированный размер окна
# Переменные для хранения состояния калькулятора
self.current_input = "0" # Текущий ввод
self.first_operand = 0 # Первый операнд
self.operation = "" # Текущая операция
self.result_displayed = False # Флаг отображения результата
# Создание переменной Tkinter для отображения в поле ввода
self.display_var = tk.StringVar()
self.display_var.set(self.current_input)
# Создание интерфейса
self.create_widgets()
def create_widgets(self):
# Создание поля вывода (дисплея калькулятора)
display_frame = tk.Frame(self.master, height=50, bg="lightgray")
display_frame.pack(fill='x')
display = tk.Entry(display_frame,
textvariable=self.display_var,
font=('Arial', 18),
bd=10,
insertwidth=4,
width=14,
bg="white",
justify='right')
display.pack(fill='both', expand=True)
# Создание рамки для кнопок
buttons_frame = tk.Frame(self.master, bg="gray")
buttons_frame.pack(fill='both', expand=True)
# Определение кнопок калькулятора
buttons = [
('CE', 0, 0), ('C', 0, 1), ('+/-', 0, 2), ('%', 0, 3),
('7', 1, 0), ('8', 1, 1), ('9', 1, 2), ('/', 1, 3),
('4', 2, 0), ('5', 2, 1), ('6', 2, 2), ('*', 2, 3),
('1', 3, 0), ('2', 3, 1), ('3', 3, 2), ('-', 3, 3),
('0', 4, 0), ('.', 4, 1), ('=', 4, 2), ('+', 4, 3)
]
# Создание кнопок
for (text, row, col) in buttons:
if text in '0123456789.':
# Цифровые кнопки и точка
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#f0f0f0", # Светло-серый цвет для цифр
command=lambda t=text: self.digit_press(t))
elif text in '+-*/':
# Кнопки операций
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ffa54f", # Оранжевый цвет для операций
command=lambda t=text: self.operation_press(t))
elif text == '=':
# Кнопка равно
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#87cefa", # Голубой цвет для равно
command=self.equals_press)
elif text == 'C':
# Кнопка очистки текущего ввода
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ff6347", # Красноватый цвет для очистки
command=self.clear_press)
elif text == 'CE':
# Кнопка полной очистки
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#ff6347", # Красноватый цвет для очистки
command=self.reset)
elif text == '+/-':
# Кнопка смены знака
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#d3d3d3", # Серый цвет для доп. функций
command=self.toggle_sign)
elif text == '%':
# Кнопка процента
button = tk.Button(buttons_frame,
text=text,
font=('Arial', 12),
padx=15,
pady=10,
bg="#d3d3d3", # Серый цвет для доп. функций
command=self.percent)
# Размещаем кнопку в сетке с небольшими отступами
button.grid(row=row, column=col, padx=2, pady=2, sticky="nsew")
# Настраиваем, чтобы столбцы и строки растягивались равномерно
for i in range(5): # 5 строк
buttons_frame.grid_rowconfigure(i, weight=1)
for i in range(4): # 4 столбца
buttons_frame.grid_columnconfigure(i, weight=1)
def digit_press(self, digit):
# Если предыдущее действие было вывод результата, то начинаем новый ввод
if self.result_displayed:
self.current_input = ""
self.result_displayed = False
# Обрабатываем ввод десятичной точки
if digit == '.':
if '.' not in self.current_input:
# Если точки еще нет, добавляем её
if not self.current_input:
# Если ввод пуст, добавляем "0."
self.current_input = "0."
else:
self.current_input += "."
# Игнорируем попытку ввести вторую точку
else:
# Если текущий ввод "0", заменяем его на цифру
if self.current_input == "0":
self.current_input = digit
else:
# Иначе добавляем цифру к текущему вводу
self.current_input += digit
# Обновляем отображение
self.display_var.set(self.current_input)
def operation_press(self, op):
# Если есть текущий ввод
if self.current_input:
# Если это не первая операция, сначала выполняем предыдущую
if self.operation and not self.result_displayed:
self.equals_press()
# Сохраняем первый операнд и операцию
self.first_operand = float(self.current_input)
self.operation = op
self.current_input = ""
elif self.result_displayed: # Если нажали операцию после вывода результата
# Используем результат как первый операнд
self.operation = op
self.current_input = ""
self.result_displayed = False
# Если нет текущего ввода и это не после вывода результата, игнорируем
def equals_press(self):
# Если нет операции, нечего вычислять
if not self.operation:
return
# Если нет второго операнда, используем первый
if not self.current_input:
self.current_input = str(self.first_operand)
try:
# Получаем второй операнд
second_operand = float(self.current_input)
# Выполняем операцию
if self.operation == '+':
result = self.first_operand + second_operand
elif self.operation == '-':
result = self.first_operand - second_operand
elif self.operation == '*':
result = self.first_operand * second_operand
elif self.operation == '/':
# Проверяем деление на ноль
if second_operand == 0:
messagebox.showerror("Ошибка", "Деление на ноль невозможно")
self.reset() # Сбрасываем калькулятор
return
result = self.first_operand / second_operand
# Форматируем результат (убираем десятичную часть, если она равна 0)
if result == int(result):
result = int(result)
# Обновляем состояние и отображение
self.current_input = str(result)
self.display_var.set(self.current_input)
self.first_operand = result
self.operation = ""
self.result_displayed = True
except ValueError:
messagebox.showerror("Ошибка", "Некорректный ввод")
self.reset()
except Exception as e:
messagebox.showerror("Ошибка", str(e))
self.reset()
def clear_press(self):
# Очищаем текущий ввод
self.current_input = "0"
self.display_var.set(self.current_input)
def reset(self):
# Полная очистка калькулятора
self.current_input = "0"
self.first_operand = 0
self.operation = ""
self.result_displayed = False
self.display_var.set(self.current_input)
def toggle_sign(self):
# Смена знака числа
if self.current_input and self.current_input != "0":
if self.current_input.startswith("-"):
self.current_input = self.current_input[1:]
else:
self.current_input = "-" + self.current_input
self.display_var.set(self.current_input)
def percent(self):
# Вычисление процента
if self.current_input:
try:
value = float(self.current_input)
# Если есть первый операнд, вычисляем процент от него
if self.first_operand != 0 and self.operation in ['+', '-']:
value = self.first_operand * (value / 100)
else:
value = value / 100
# Форматируем результат
if value == int(value):
value = int(value)
self.current_input = str(value)
self.display_var.set(self.current_input)
except ValueError:
messagebox.showerror("Ошибка", "Некорректный ввод")
self.reset()
# Создание и запуск приложения
if __name__ == "__main__":
root = tk.Tk()
app = Calculator(root)
root.mainloop()
Данный код представляет собой полностью функциональный калькулятор с графическим интерфейсом, реализованный на языке Python с использованием библиотеки Tkinter. Калькулятор имеет все необходимые базовые функции: арифметические операции, процент, смену знака, очистку и др.
Для обеспечения корректной работы разработанного калькулятора необходимо провести всестороннее тестирование. План тестирования будет включать следующие категории тестовых случаев:
После выполнения плана тестирования были получены следующие результаты:
| Тестовый случай | Ожидаемый результат | Фактический результат | Статус |
|---|---|---|---|
| Сложение: 2 + 2 | 4 | 4 | Пройден |
| Сложение дробных: 1.5 + 2.5 | 4 | 4 | Пройден |
| Вычитание: 5 - 3 | 2 | 2 | Пройден |
| Отрицательный результат: 3 - 5 | -2 | -2 | Пройден |
| Умножение: 3 * 4 | 12 | 12 | Пройден |
| Деление: 10 / 2 | 5 | 5 | Пройден |
| Дробное деление: 5 / 2 | 2.5 | 2.5 | Пройден |
| Деление на ноль: 5 / 0 | Сообщение об ошибке | Сообщение: "Деление на ноль невозможно" | Пройден |
| Изменение знака: 5 → +/- | -5 | -5 | Пройден |
| Процент: 10 % | 0.1 | 0.1 | Пройден |
| Процент в контексте: 50 + 10 % | 55 | 55 | Пройден |
| Последовательное сложение: 2 + 3 + 4 | 9 | 9 | Пройден |
| Смешанные операции: 2 + 3 * 4 | 14 | 14 | Пройден |
| Ввод нескольких точек: 1.2.3 | 1.23 (только первая точка) | 1.23 | Пройден |
| Кнопка очистки (C) | Очистка текущего ввода | Текущий ввод очищен | Пройден |
| Кнопка полной очистки (CE) | Полный сброс калькулятора | Калькулятор полностью очищен | Пройден |
Выявленные в ходе тестирования проблемы и их решения:
После исправления всех выявленных проблем калькулятор успешно прошёл все тестовые случаи и может считаться готовым к использованию.
В ходе выполнения самостоятельной работы был разработан полнофункциональный калькулятор с графическим интерфейсом на языке программирования Python с использованием библиотеки Tkinter. Разработка была проведена с соблюдением основных принципов объектно-ориентированного программирования, что обеспечило модульность и расширяемость созданного приложения.
В процессе выполнения работы были решены следующие задачи:
Результатом работы является готовое к использованию приложение-калькулятор, обладающее следующими характеристиками:
В процессе работы были применены знания, полученные по дисциплинам "Программирование", "Объектно-ориентированное программирование" и "Человеко-машинное взаимодействие".
Созданный калькулятор может быть в дальнейшем расширен дополнительными функциональными возможностями, такими как:
Таким образом, все поставленные цели и задачи были успешно выполнены, а полученные навыки могут быть использованы в дальнейшей разработке программного обеспечения с графическим интерфейсом пользователя.