title: Connect python signal to QML ui slot with PySide2
body: <p>I've just started playing with PySide2 and QML for an upcoming project and I immediately stumbled on a problem: How do I connect a signal emitted by a python class (that inherits from QObject) to a slot in a .qml file?
  For example: I have a QThread (python class) that generates a couple of xy coordinates every 50 milliseconds. I want to add the generated couple to a LineSeries defined in a QML file, in order to generate an oscilloscope-like plot.</p>
  
  <p>The question is probably really basic and dumb, but I really need some help.</p>
  
  <p>Best regards</p>
  
  <p>Lando</p>
  
  <p>EDIT 4:</p>
  
  <p>I've found a solution, but I do not really like it. Can you suggest me (if it exists) a more elegant way to do it?</p>
  
  <p>Code for Python:</p>
  
  <pre><code>class Manager(QObject):
  
      dataReady = Signal(float,float)
  
      def __init__(self):
          QObject.__init__(self)
          self._currX = 0
          self._currY = 0
          self._delay = 0.5
          self._multiplier = 1.0
          self._power = 1.0
          self._xIncrement = 1.0
          self._starter = False
          self._threader = None
  
      @Property(bool)
      def starter(self):
          return self._starter
  
      @starter.setter
      def setStarter(self, val):
          print("New val: {0}, oldVal: {1}".format(val,self._starter))
          if self._starter == val:
              return
  
          self._starter = val
          if val:
              self.start()
          else:
              self.stop()
  
      @Property(float)
      def multiplier(self):
          return self._multiplier
  
      @multiplier.setter
      def setMultiplier(self, val):
          if self._multiplier == val:
              return
          print(val)
          self._multiplier = val
  
      @Property(int)
      def power(self):
          return self._power
  
      @power.setter
      def setPower(self, val):
          if self._power == val:
              return
          print(val)
          self._power = val
  
      @Property(float)
      def delay(self):
          return self._delay
  
      @delay.setter
      def setDelay(self, val):
          if self._delay == val:
              return
          print(val)
          self._delay = val
  
      @Property(float)
      def xIncrement(self):
          return self._xIncrement
  
      @xIncrement.setter
      def setXIncrement(self, val):
          if self._xIncrement == val:
              return
          print(val)
          self._xIncrement = val
  
      def generatePoint(self):
          self._currX += self._xIncrement
          self._currY = self._multiplier*(self._currX**self._power)
  
          return self._currX,self._currY
  
      def stop(self):
          self._goOn = False
          if self._threader is not None:
              while self._threader.isRunning():
                  sleep(0.1)
  
      def start(self):
          self._goOn = True
          self._threader = Threader(core=self.core)
          self._threader.start()
  
      def core(self):
          while self._goOn:
              x,y = self.generatePoint()
              print([x,y])
              self.dataReady.emit(x,y)
              sleep(self._delay)
  
  class Threader(QThread):
  
      def __init__(self,core,parent=None):
          QThread.__init__(self,parent)
          self._core = core
          self._goOn = False
  
      def run(self):
          self._core()
  
  if __name__ == "__main__":
      os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
      app = QApplication(sys.argv)
      manager = Manager()
      engine = QQmlApplicationEngine()
      ctx = engine.rootContext()
      ctx.setContextProperty("Manager", manager)
      engine.load('main.qml')
      if not engine.rootObjects():
          sys.exit(-1)
  
      sys.exit(app.exec_())
  </code></pre>
  
  <p>Code for QML:</p>
  
  <pre><code>ApplicationWindow {
  id: mainWindow
  width:640
  height: 480
  title: qsTr("Simple ui")
  visible: true
  locale:locale
  
  property var controlsColor: Material.DeepPurple
  property var controlsAccent: Material.BlueGrey
  property real x: 0.0
  property int controlsElevation: 6
  property int paneElevation: 4
  
  function drawPoint(theX,theY){
      console.log(theX);
      mainLine.append(theX,theY)
      if (theX &gt;= testXAxis.max){
          testXAxis.max = theX;
      }
      if (theY &gt;= testYAxis.max){
          testYAxis.max = theY;
      }
      if (theY &lt;= testYAxis.min){
          testYAxis.min = theY;
      }
  }
  
  function clearLine(){
      mainLine.clear();
      mainLine.append(0,0);
  }
  
  Pane{
      id: mainPanel
      anchors.fill: parent
      //Material.theme: Material.Dark
  
      RowLayout{
          id: mainRowLO
          anchors.fill: parent
          spacing: 15
  
          //Chart pane
          Pane{
              id: chartPane
              Material.elevation: paneElevation
              //Material.background: Material.Grey
              Layout.fillHeight: true
              Layout.fillWidth: true
              Layout.minimumHeight: 200
              Layout.minimumWidth: 400
  
              ChartView {
                  id: testChart
                  title: "Line"
                  anchors.fill: parent
                  antialiasing: true
                  LineSeries {
                      id: mainLine
                      name: "LineSeries"
                      axisX: ValueAxis{
                          id: testXAxis
                          min: 0.0
                          max: 2.0
                      }
                      axisY: ValueAxis{
                          id: testYAxis
                          min: 0.0
                          max: 2.0
                      }
                      XYPoint { x: 0; y: 0 }
                  }
              }
          }
  
          Pane{
              id: controlsPane
              Material.elevation: paneElevation
              //Material.background: Material.Grey
              Layout.fillHeight: true
              Layout.fillWidth: true
              Layout.minimumHeight: 200
              Layout.minimumWidth: 200
              Layout.maximumWidth: 200
  
              ColumnLayout{
                  id: controlsColumnLO
                  anchors.fill: parent
                  spacing: 40
  
                  Label{
                      id: powerLabel
                      text: "Exponent"
                      Layout.topMargin: 40
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                  }
  
                  SpinBox{
                      id: powerNum
                      from: 0
                      value: 1
                      to: 5
                      stepSize: 1
                      width: 80
                      validator: DoubleValidator {
                          bottom: Math.min(powerNum.from, powerNum.to)
                          top:  Math.max(powerNum.from, powerNum.to)
                      }
                      Material.foreground: controlsColor
                      Material.accent: controlsAccent
                      Material.elevation: controlsElevation
                      Layout.fillWidth: true
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                      editable: true
                      onValueChanged: function(){
                          Manager.power = value;
                      }
                  }
  
                  Label{
                      id: multiplierLabel
                      text: "Multiplier"
                      Layout.fillWidth: true
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                  }
  
                  Slider{
                      id: multiplierSlider
                      from: -50
                      value: 1
                      to: 50
                      Material.foreground: controlsColor
                      Material.accent: controlsAccent
                      Material.elevation: controlsElevation
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                      Layout.fillWidth: true
                      onValueChanged: function(){
                          Manager.multiplier = value;
                      }
                  }
  
                  Label{
                      id: multValueLabel
                      text: String(multiplierSlider.value)
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                  }
  
                  Label{
                      id: delayLable
                      text: "Delay[s]"
                      Layout.fillWidth: true
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                  }
  
                  Slider{
                      id: delaySlider
                      from: 0.05
                      value: 0.1
                      to: 1
                      stepSize: 0.01
                      Material.foreground: controlsColor
                      Material.accent: controlsAccent
                      Material.elevation: controlsElevation
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                      Layout.fillWidth: true
                      onValueChanged: function(){
                          Manager.delay = value;
                      }
                  }
  
                  Label{
                      id: incrementLable
                      text: "Increment"
                      Layout.fillWidth: true
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                  }
  
                  Slider{
                      id: incrementSlider
                      from: 1.0
                      value: 1.0
                      to: 5.0
                      stepSize: 0.01
                      Material.foreground: controlsColor
                      Material.accent: controlsAccent
                      Material.elevation: controlsElevation
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                      Layout.fillWidth: true
                      onValueChanged: function(){
                          Manager.xIncrement = value;
                      }
                  }
  
                  Item {
                      // spacer item
                      id: controlsSpacer
                      Layout.fillWidth: true
                      Layout.fillHeight: true
                      Pane { anchors.fill: parent }//; Material.background: Material.Light; Material.elevation: 4 } // to visualize the spacer
                  }
  
                  Button{
                      id: startPointBtn
                      text: "START"
                      Material.foreground: controlsColor
                      Material.accent: controlsAccent
                      Material.elevation: controlsElevation
                      Layout.fillWidth: true
                      Layout.leftMargin: 10
                      Layout.rightMargin: 10
                      onClicked: function(){
                          console.log(text);
                          console.log(text=="START")
                          if(text=="START"){
                              Manager.starter = true;
                              Manager.dataReady.connect(drawPoint);
                              clearLine();
                              text = "STOP";
                          }
                          else{
                              Manager.starter = false;
                              text = "START";
                              Manager.dataReady.disconnect(drawPoint);
                          }
                      }
                  }
              }
          }
      }
  }
  }
  </code></pre>