Python

[PyQt6] Data 전달 with QML

llHoYall 2022. 11. 4. 21:37

이번에는 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)
    }
  }
}

먼저, 간단하게 LabelSlider를 만들고, 각각에 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초 후에, QMLsetText() 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()을 사용한 방법도 거의 유사하게 사용하실 수 있을거에요.