본문 바로가기

프로그래밍/PyQt5 GUI

59. 이벤트 루프

  • 대기(waiting)가 필요한 상황

PyQt에는 운영체제로부터 전달되는 이벤트나 PyQt의 시그널등을 처리하는 이벤트 루프가 존재합니다.

일반적으로 QApplication 클래스의 객체에서 exec_ 메소드를 호출하여 생성합니다.

이러한 이벤트 루프를 Main Event Loop 라고 합니다.

app = QApplication(sys.argv)
window = MyWindow()
window.show()
app.exec_()

Main Event Loop는 용어 그대로 루프를 돌면서 이벤트를 처리하는 역할을 합니다. 사용자가 발생한 이벤트나 또는 위젯에서 발생한 이벤트들이 순서대로 Event Queue에 입력됩니다. Main Event Loop는 Event Queue로 부터 순차적으로 이벤트를 가져간 후 해당 이벤트를 처리할 메소드를 호출하여 이벤트를 처리합니다. Event Queue에 입력된 이벤트는 순차적으로 처리되게 됩니다.

우리는 로그인이 완료될 때까지 다른 코드가 수행되지 않고 기다려야 하는 경우나, 어떤 자료가 다운로드 될 때까지 기다렸다가 해당 자료로 다른 동작을 수행해야 하는 경우 처럼 특정 작업이 완료될 때까지 대기해야 할 경우가 생깁니다.

이때 단순히 time.sleep(3)과 같은 코드를 사용하면 이벤트를 처리하는 부분까지 멈추게 되면서 GUI 창이 버벅이거나 심지어 프로그램이 종료될 수 있습니다.

PyQt에서 어떤 작업이 완료될 때까지 대기하기 위해 많이 사용하는 방법은 Local Event Loop를 사용합니다.

 

59-1 예제: ex58

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtCore import QEventLoop, QTimer

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.lbl = QLabel(self)
        self.lbl.move(10, 10)
        self.lbl.resize(100, 30)
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle("Local Event Loop")
        self.show()

        self.timer = QTimer(self)
        self.timer.singleShot(1000 * 3, self.login_callback)
        self.check_balance()

    def login_callback(self):
        self.balance = 100

    def check_balance(self):
        self.lbl.setText(str(self.balance))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MyApp()
    sys.exit(app.exec_())

59-2 설명

위 코드를 수행하면 check_balance 메서드를 호출할 때 self.balance가 3초 전에는 아직 존재하지 않으므로 에러가 발생합니다. 그러므로 login_callback 메서드가 호출될 때까지 이벤트 루프를 통하여 login_callback 슬롯(메서드)가 처리되었는지 확인하는 작업이 필요합니다.

위 코드의 수행 흐름을 그림으로 나타내면 다음과 같습니다.

QEventLoop 사용하기

이번에는 QEventLoop 클래스를 사용해서 로그인이 완료될 때까지 댁하도록 코드를 수정해보겠습니다.

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtCore import QEventLoop, QTimer

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.lbl = QLabel(self)
        self.lbl.move(10, 10)
        self.lbl.resize(100, 30)
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle("Local Event Loop")
        self.show()

        self.local_event_loop = QEventLoop()

        self.timer = QTimer(self)
        self.timer.singleShot(1000 * 3, self.login_callback)
        self.local_event_loop.exec()
        self.check_balance()

    def login_callback(self):
        self.balance = 100
        self.local_event_loop.exit()

    def check_balance(self):
        self.lbl.setText(str(self.balance))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MyApp()
    sys.exit(app.exec_())

59-3 결과

59-4 예제2 : QEventLoop 동작원리 분석

import sys
import time

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QPlainTextEdit, QScrollBar, QVBoxLayout, QHBoxLayout
from PyQt5.QtCore import Qt, QEventLoop, QTimer

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.event_loop = QEventLoop()

        self.pte = QPlainTextEdit(self)
        self.pte.addScrollBarWidget(QScrollBar(Qt.Vertical), Qt.AlignRight)

        btn1 = QPushButton("버튼1", self)
        btn1.clicked.connect(self.btn1_clicked)

        btn2 = QPushButton("버튼2", self)
        btn2.clicked.connect(self.btn2_clicked)

        btn3 = QPushButton("버튼3", self)
        btn3.clicked.connect(self.btn3_clicked)

        vLayout = QVBoxLayout(self)
        hLayout = QHBoxLayout(self)
        hLayout.addWidget(btn1)
        hLayout.addWidget(btn2)
        hLayout.addWidget(btn3)
        vLayout.addWidget(self.pte)
        vLayout.addLayout(hLayout)
        vLayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(vLayout)

        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle("Event Loop Logic")
        self.show()

    def btn1_clicked(self):
        self.pte.appendPlainText("before loop exec")
        self.event_loop.exec_()
        self.pte.appendPlainText("after loop exec")

    def btn2_clicked(self):
        self.pte.appendPlainText("before loop exit")
        self.event_loop.exit()
        self.pte.appendPlainText("after loop exit")
        time.sleep(5)
        self.pte.appendPlainText("after time sleep")

    def btn3_clicked(self):
        self.pte.appendPlainText("버튼3 clicked event")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MyApp()
    sys.exit(app.exec_())

59-5 결과

'프로그래밍 > PyQt5 GUI' 카테고리의 다른 글

61. 주식 데이터  (0) 2021.08.12
60. 멀티스레딩  (0) 2021.08.10
58. 타이머와 스레드  (0) 2021.08.09
57. 타이머  (0) 2021.08.09
35. QPlainTextEdit  (0) 2021.08.09