[Python] PyQt – 계산기 만들기
MVC (Model – View – Controller) 디자인 패턴을 사용해서 계산기를 구현한다.
MVC
Model
Model은 핵심 기능과 데이터를 포함하는데, 계산기에선 계산을 한다.
View
GUI를 구현하며 application과 상호 작용하는데 필요한 모든 위젯을 호스팅한다. 여기에선 보이는 화면을 말한다.
Controller
사용자 이벤트 (여기에선 GUI로 입력받는 값)를 모델로 전송하거나, Model이 처리한 값을 View로 전달해준다.
구현
Skeleton 생성
import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QMainWindow from PyQt5.QtWidgets import QWidget class PyCalcUi(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('PyCalc') self.setFixedSize(235, 235) self._centralWidget = QWidget(self) self.setCentralWidget(self._centralWidget) def main(): pycalc = QApplication(sys.argv) view = PyCalcUi() view.show() sys.exit(pycalc.exec_()) if __name__ == '__main__': main()
View
import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGridLayout from PyQt5.QtWidgets import QLineEdit from PyQt5.QtWidgets import QPushButton from PyQt5.QtWidgets import QVBoxLayout from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QMainWindow from PyQt5.QtWidgets import QWidget from functools import partial class PyCalcUi(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('PyCalc') self.setFixedSize(235, 235) self.generalLayout = QVBoxLayout() self._centralWidget = QWidget(self) self.setCentralWidget(self._centralWidget) self._centralWidget.setLayout(self.generalLayout) self._createDisplay() self._createButtons() def _createDisplay(self): self.display = QLineEdit() self.display.setFixedHeight(35) self.display.setAlignment(Qt.AlignRight) self.display.setReadOnly(True) self.generalLayout.addWidget(self.display) def _createButtons(self): self.buttons = {} buttonsLayout = QGridLayout() buttons = { '7': (0, 0), '8': (0, 1), '9': (0, 2), '/': (0, 3), 'C': (0, 4), '4': (1, 0), '5': (1, 1), '6': (1, 2), '*': (1, 3), '(': (1, 4), '1': (2, 0), '2': (2, 1), '3': (2, 2), '-': (2, 3), ')': (2, 4), '0': (3, 0), '00': (3, 1),'.': (3, 2), '+': (3, 3), '=': (3, 4), } for btnText, pos in buttons.items(): self.buttons[btnText] = QPushButton(btnText) self.buttons[btnText].setFixedSize(40, 40) buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1]) self.generalLayout.addLayout(buttonsLayout) def setDisplayText(self, text): """Set display's text.""" self.display.setText(text) self.display.setFocus() def displayText(self): """Get display's text.""" return self.display.text() def clearDisplay(self): """Clear the display.""" self.setDisplayText('') def main(): pycalc = QApplication(sys.argv) view = PyCalcUi() view.show() sys.exit(pycalc.exec_()) if __name__ == '__main__': main()
View까지 수행시키면 계산기처럼 생긴 GUI가 실행되지만 동작하지 않는다.
Controller
class PyCalcCtrl: def __init__(self, view): self._view = view self._connectSignals() def _buildExpression(self, sub_exp): # Previous text is appended expression = self._view.displayText() + sub_exp self._view.setDisplayText(expression) def _connectSignals(self): for btnText, btn in self._view.buttons.items(): # Neither '=' nor 'C' should be appended to previous text if btnText not in {'=', 'C'}: btn.clicked.connect(partial(self._buildExpression, btnText)) # 'C' is for clear operation self._view.buttons['C'].clicked.connect(self._view.clearDisplay) def main(): pycalc = QApplication(sys.argv) view = PyCalcUi() view.show() PyCalcCtrl(view=view) sys.exit(pycalc.exec_()) if __name__ == '__main__': main()
이렇게 controller까지 구현하면 숫자가 입력되어 화면에 뿌려지는 것 까지 된다.
Model
ERROR_MSG = 'ERROR' def evaluateExpression(expression): try: result = str(eval(expression, {}, {})) except Exception: result = ERROR_MSG return result
eval()
함수를 통해서 사칙연산에 대해서 쉽게 수행시킬 수 있다.
Controller
최종 controller 코드는 다음과 같다.
class PyCalcCtrl: def __init__(self, model, view): self._evaluate = model self._view = view self._connectSignals() def _calculateResult(self): result = self._evaluate(expression=self._view.displayText()) self._view.setDisplayText(result) def _buildExpression(self, sub_exp): if self._view.displayText() == ERROR_MSG: self._view.clearDisplay() expression = self._view.displayText() + sub_exp self._view.setDisplayText(expression) def _connectSignals(self): for btnText, btn in self._view.buttons.items(): if btnText not in {'=', 'C'}: btn.clicked.connect(partial(self._buildExpression, btnText)) self._view.buttons['='].clicked.connect(self._calculateResult) self._view.display.returnPressed.connect(self._calculateResult) self._view.buttons['C'].clicked.connect(self._view.clearDisplay) def main(): pycalc = QApplication(sys.argv) view = PyCalcUi() view.show() model = evaluateExpression PyCalcCtrl(model=model, view=view) sys.exit(pycalc.exec_()) if __name__ == '__main__': main()