DevStudy
PyQt5 본문
시그널과 슬롯 (Signal&Slot)
PyQt에서는 이벤트 처리에 있어서 시그널과 슬롯이라는 독특한 메커니즘을 사용합니다.
연결하기

다이얼 위젯으로 조절한 값을 화면에 출력하는 프로그램을 만들어 보겠습니다.
다이얼의 값이 변할 때 발생하는 시그널이 LCD 화면에 숫자를 표시하는 슬롯과 연결됩니다.
예제
## Ex 7-1. 연결하기.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLCDNumber, QDial, QVBoxLayout
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
lcd = QLCDNumber(self)
dial = QDial(self)
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(dial)
self.setLayout(vbox)
dial.valueChanged.connect(lcd.display)
self.setWindowTitle('Signal and Slot')
self.setGeometry(300, 300, 200, 200)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
다이얼을 움직이면 그 값에 맞춰서 LCD에 숫자가 표시됩니다.
설명
lcd = QLCDNumber(self)
dial = QDial(self)
QLCDNumber 위젯은 LCD 화면과 같이 숫자를 표시합니다.
QDial은 다이얼을 회전해서 값을 조절하는 위젯입니다.
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(dial)
self.setLayout(vbox)
수직 박스 레이아웃 (박스 레이아웃 페이지 참고)을 하나 만들어서 QLCDNumber와 QDial 위젯을 넣어줍니다.
그리고 MyApp 위젯의 레이아웃으로 설정합니다.
dial.valueChanged.connect(lcd.display)
QDial 위젯은 몇 가지 시그널을 갖고 있습니다. (QSlider, QDial 페이지 참고)
여기서는 valueChanged 시그널을 lcd의 display 슬롯에 연결합니다. display 슬롯은 숫자를 받아서 QLCDNumber 위젯에 표시하는 역할을 합니다.
여기서 시그널을 보내는 객체인 송신자 (sender)는 dial, 시그널을 받는 객체인 수신자 (receiver)는 lcd입니다.
슬롯 (slot)은 시그널에 어떻게 반응할지를 구현한 메서드입니다.
결과

이벤트 핸들러 만들기

PyQt에서 이벤트 (시그널) 처리를 할 때 사용되는 함수를 이벤트 핸들러 (슬롯)라고 합니다.
‘Big’, ‘Small’ 버튼을 클릭해서 시그널이 발생했을 때, 창의 크기가 바뀌도록 하는 함수를 정의해보겠습니다.
예제
## Ex 7-2. 이벤트 핸들러 만들기.
import sys
from PyQt5.QtWidgets import (QApplication, QWidget
, QLCDNumber, QDial, QPushButton, QVBoxLayout, QHBoxLayout)
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
lcd = QLCDNumber(self)
dial = QDial(self)
btn1 = QPushButton('Big', self)
btn2 = QPushButton('Small', self)
hbox = QHBoxLayout()
hbox.addWidget(btn1)
hbox.addWidget(btn2)
vbox = QVBoxLayout()
vbox.addWidget(lcd)
vbox.addWidget(dial)
vbox.addLayout(hbox)
self.setLayout(vbox)
dial.valueChanged.connect(lcd.display)
btn1.clicked.connect(self.resizeBig)
btn2.clicked.connect(self.resizeSmall)
self.setWindowTitle('Signal and Slot')
self.setGeometry(200, 200, 200, 250)
self.show()
def resizeBig(self):
self.resize(400, 500)
def resizeSmall(self):
self.resize(200, 250)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
‘Big’ 버튼과 ‘Small’ 버튼을 눌러서 창의 크기를 확대, 축소할 수 있습니다.
설명
btn1.clicked.connect(self.resizeBig)
btn2.clicked.connect(self.resizeSmall)
btn1과 btn2는 각각 resizeBig, resizeSmall 슬롯에 연결되어 있습니다.
def resizeBig(self):
self.resize(400, 500)
def resizeSmall(self):
self.resize(200, 250)
resizeBig() 메서드는 화면 크기를 가로 400px, 세로 500px로 확대, resizeSmall() 메서드는 화면 크기를 가로 200px, 세로 250px로 축소하는 기능을 가지게 됩니다.
결과


이벤트 핸들러 재구성하기
keyPressEvent 이벤트 핸들러를 수정해서, 특정 키를 눌렀을 때 위젯을 종료하거나 최대화, 보통 크기로 조절하는 기능을 구현해보겠습니다.
아래와 같이 자주 쓰이는 이벤트 핸들러는 이미 만들어져 있는 경우가 많습니다.
| keyPressEvent | 키보드를 눌렀을 때 동작합니다. |
| keyReleaseEvent | 키보드를 눌렀다가 뗄 때 동작합니다. |
| mouseDoubleClickEvent | 마우스를 더블클릭할 때 동작합니다. |
| mouseMoveEvent | 마우스를 움직일 때 동작합니다. |
| mousePressEvent | 마우스를 누를 때 동작합니다. |
| mouseReleaseEvent | 마우스를 눌렀다가 뗄 때 동작합니다. |
| moveEvent | 위젯이 이동할 때 동작합니다. |
| resizeEvent | 위젯의 크기를 변경할 때 동작합니다. |
예제
## Ex 7-3. 이벤트 핸들러 재구성하기.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Reimplementing event handler')
self.setGeometry(300, 300, 300, 200)
self.show()
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.close()
elif e.key() == Qt.Key_F:
self.showFullScreen()
elif e.key() == Qt.Key_N:
self.showNormal()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
‘esc’, ‘F’, ‘N’ 키를 클릭하면 창이 종료되거나 최대화, 보통 크기가 되도록 이벤트 핸들러를 재구성했습니다.
설명
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.close()
elif e.key() == Qt.Key_F:
self.showFullScreen()
elif e.key() == Qt.Key_N:
self.showNormal()
keyPressEvent 이벤트 핸들러는 키보드의 이벤트를 입력으로 받습니다.
e.key()는 어떤 키를 누르거나 뗐는지를 반환합니다.
‘esc’ 키를 눌렀다면, self.close()를 통해 위젯이 종료됩니다.
‘F’ 키 또는 ‘N’ 키를 눌렀다면, 위젯의 크기가 최대화되거나 보통 크기가 됩니다.
이벤트 핸들러 재구성하기2
이번에는 mouseMoveEvent를 이용해서 마우스의 위치를 트래킹해서 출력해보겠습니다.
예제
## Ex 7-4. 이벤트 핸들러 재구성하기2.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
x = 0
y = 0
self.text = 'x: {0}, y: {1}'.format(x, y)
self.label = QLabel(self.text, self)
self.label.move(20, 20)
self.setMouseTracking(True)
self.setWindowTitle('Reimplementing event handler')
self.setGeometry(300, 300, 300, 200)
self.show()
def mouseMoveEvent(self, e):
x = e.x()
y = e.y()
text = 'x: {0}, y: {1}'.format(x, y)
self.label.setText(text)
self.label.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
위젯 안에서 마우스를 움직이면 이벤트가 발생하고, 재구성한 이벤트 핸들러를 통해 마우스의 위치를 출력합니다.
설명
self.text = 'x: {0}, y: {1}'.format(x, y)
self.label = QLabel(self.text, self)
self.label.move(20, 20)
x, y의 값을 self.text로 저장하고, self.label의 텍스트로 설정합니다.
위치를 x=20, y=20 만큼 이동해줍니다.
self.setMouseTracking(True)
setMouseTracking을 True로 설정해주면, 마우스의 위치를 트래킹합니다.
디폴트는 setMouseTracking(False) 상태이며, 마우스 버튼을 클릭하거나 뗄 때만 mouseEvent가 발생합니다.
def mouseMoveEvent(self, e):
x = e.x()
y = e.y()
text = 'x: {0}, y: {1}'.format(x, y)
self.label.setText(text)
self.label.adjustSize()
이벤트 e는 이벤트에 대한 정보를 갖고 있는 하나의 객체입니다. 이 이벤트 객체 (event object)는 생성된 이벤트의 유형에 따라 다릅니다.
e.x(), e.y()는 위젯 안에서 이벤트가 발생했을 때 마우스 커서의 위치를 반환합니다.
만약 e.globalX(), e.globalY()로 설정해주면, 화면 전체에서 마우스 커서의 위치를 반환하게 됩니다.
self.label.adjustSize() 메서드로 라벨의 크기를 자동으로 조절하도록 합니다.
결과

사용자 정의 시그널
지정되어 있는 시그널 이외에도 새로 원하는 시그널을 만들어서 사용할 수도 있습니다.
이번 예제에서는 pyqtSignal()을 이용해서 사용자 정의 시그널을 만들고, 특정 이벤트가 발생했을 때 이 시그널이 방출되도록 해보겠습니다.
예제
## Ex 7-5. 사용자 정의 시그널.
import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow
class Communicate(QObject):
closeApp = pyqtSignal()
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.c = Communicate()
self.c.closeApp.connect(self.close)
self.setWindowTitle('Emitting Signal')
self.setGeometry(300, 300, 300, 200)
self.show()
def mousePressEvent(self, e):
self.c.closeApp.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
closeApp이라는 시그널을 하나 만들었습니다.
이 시그널은 마우스 클릭 시 발생해서 QMainWindow의 close() 슬롯에 연결되어 프로그램을 종료합니다.
설명
class Communicate(QObject):
closeApp = pyqtSignal()
pyqtSignal()을 가지고 Communicate 클래스의 속성으로서 closeApp이라는 시그널을 하나 만들었습니다.
self.c = Communicate()
self.c.closeApp.connect(self.close)
Communicate 클래스의 closeApp 시그널은 MyApp 클래스의 close() 슬롯에 연결됩니다.
def mousePressEvent(self, e):
self.c.closeApp.emit()
mousePressEvent 이벤트 핸들러를 사용해서, 마우스를 클릭했을 때 closeApp 시그널이 방출되도록 했습니다.