Programming,  Python

[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()

Leave a Reply

Your email address will not be published. Required fields are marked *