[PyQt6] Data 전달 with QML
이번에는 QML을 사용하여 widget 간의 데이터 전달에 대해 살펴보겠습니다.
기본적인 application을 만드는 방법은 이전 포스팅을 참고해주세요.
2022.11.03 - [Python] - [PyQt6] Getting Started with QtQuick
간단한 예제와 함께 살펴보겠습니다.
Widget간의 Data 전달
// main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
ApplicationWindow {
visible: true
width: 640
height: 480
title: "Example App"
Material.theme: Material.Dark
Material.accent: Material.Orange
Label {
id: idLabel
anchors.centerIn: parent
text: idSlider.value
font.pixelSize: 24
}
Slider {
id: idSlider
anchors.top: idLabel.bottom
anchors.horizontalCenter: parent.horizontalCenter
onMoved: {
idLabel.text = parseInt(idSlider.value * 100)
}
}
}
먼저, 간단하게 Label과 Slider를 만들고, 각각에 id를 부여했습니다.
이 id를 사용하여 서로 데이터를 주고받을 수 있습니다.
위 예에서는 Slider를 움직일 때마다, Label에 현재 값을 표시하도록 했습니다.
간단히 잘 동작합니다.
의외로 이런 간단한 내용도 제대로 설명한 자료를 찾기가 어렵더라고요. ㅠ
Main Application으로 데이터 전달
다음으로 QtWidgets를 사용하셨다면 많이 익숙한 signal & slot에 대한 내용 중 slot에 대한 예제를 살펴보겠습니다.
# main.py
import sys
from PyQt6.QtCore import QObject, QUrl, pyqtSlot
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine
class SliderTest(QObject):
@pyqtSlot(float)
def print_value(self, v: float) -> None:
print(v)
app = QGuiApplication([])
st = SliderTest()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("sliderTest", st)
engine.load(QUrl("src/main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
먼저, QML에서 날아온 signal을 처리할 class를 하나 만들어서 익숙한 slot을 만들어 전달받습니다.
단순히 넘어온 값을 print하는 method를 만들었지만, 값을 받을 수 있다면 뭐든 할 수 있으니 요것만 알면 충분합니다.
다음은 engine의 rootContext에 우리의 class instance를 context property로 넣어줍니다.
여기서 설정한 이름으로 QML에서 signal을 보내게 되요.
별 것 아닌 내용인데 이것도 설명 해놓은 곳을 못찾아서 한참을 삽질했네요... ㅠ
// main.qml
onMoved: {
idLabel.text = parseInt(idSlider.value * 100)
sliderTest.print_value(parseFloat(idSlider.value))
}
요렇게 넘겨받은 instance의 method를 직접 호출하는 방식으로 값을 전달할 수 있습니다.
돌려 보시면, console에 잘 출력되는 것을 보실 수 있을거에요.
Main Application에서 데이터 전달
# main.py
import sys
from PyQt6.QtCore import QObject, QUrl, QTimer, pyqtSlot
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine
class SliderTest(QObject):
@pyqtSlot(float)
def print_value(self, v: float) -> None:
print(v)
app = QGuiApplication([])
st = SliderTest()
timer = QTimer()
timer.start(3000)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("sliderTest", st)
engine.load(QUrl("src/main.qml"))
timer.timeout.connect(engine.rootObjects()[0].setText)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
이번엔 반대로 전달을 해보겠습니다.
간단한 예제를 위해 timer를 하나 만들어서 3초 후에, QML의 setText() function을 호출하도록 했습니다.
코드 보시면 감이 오시겠지만 engine의 rootObjects를 얻어와야 해서, QML load를 먼저 해주어야 합니다.
// main.qml
Label {
id: idLabelTest
anchors.top: idSlider.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: "Loading..."
font.pixelSize: 16
}
function setText() {
idLabelTest.text = "Loaded"
}
테스트를 위해 새로 Label을 하나 만들고, setText() 함수를 통해 text를 설정하도록 했어요.
즉, application을 시작하면 Loading... 글자가 나오고, 3초 후 Loaded로 바뀝니다.
Conclusion
기본적인 application들 만들때는 이정도면 충분하지 않을까 생각되요.
이 쉬운 게 자료가 없어서 몇 시간을 삽질했네요. ㅠ
pyside를 사용하거나, 쓸데없이 복잡하게 사용하거나, 과거 자료거나...
이 예제를 참고하면 pyqtSignal()과 emit()을 사용한 방법도 거의 유사하게 사용하실 수 있을거에요.