Oscar Thörn
Oscar Thörn

Reputation: 13

PySide2 - Binding model to view

Hopefully someone can help me cause I can't find anything that works online.

I'm building a simple GUI for a AI project and using PySide2 and QML. I have managed to understand how to bind a function to a button and make that work. But I can't seem to figure out how to populate a combobox from a python list of strings (and then use the selection in python). I know it has something to do with properties and model, but I can't get it to work.

Here is my python code:


import multiprocessing as mp
import sys

import mido
from Fuzzy.aidrummer import AiDrummer

from PySide2.QtWidgets import QApplication
from PySide2.QtQuick import QQuickView
from PySide2.QtCore import QUrl, Slot, QObject, Property, Signal

def run():
    d = AiDrummer('playback', file='../Music/28 coltrane 2.mid', play_instrument='yes', instrument_port='VirtualMIDISynth #1 0',
                  out_port='strike 3', visualize=True)
    d.run()


class Api(QObject):
    proc = None

    def __init__(self):
        QObject.__init__(self)
        self._midi_out = mido.get_output_names()
        print(self._midi_out)
        self._midi_in = mido.get_input_names()

    @Slot()
    def play(self):
        self.proc = mp.Process(target=run)
        self.proc.start()

    @Slot()
    def stop(self):
        try:
            assert isinstance(self.proc, mp.Process)
            self.proc.kill()
        except:
            return

    def read_midi_out(self):
        return self._midi_out

    def set_midi_out(self, val):
        self._midi_out = val

    @Signal
    def midi_out_changed(self):
        pass

    midi_out = Property(list, read_midi_out, set_midi_out, notify=midi_out_changed)


if __name__ == '__main__':
    app = QApplication([])
    view = QQuickView()
    view.setResizeMode(QQuickView.SizeRootObjectToView)
    url = QUrl("main.qml")

    api = Api()

    view.setSource(url)
    view.rootContext().setContextProperty('api', api)

    sys.exit(app.exec_())

And my main.qml (the combobox with model is close to the bottom):

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Controls.Material 2.0

Window {
    id: window
    visible: true
    width: 980
    height: 700
    title: qsTr("AI Drummer")

    Rectangle {
        id: rectangle1
        color: "#191919"
        anchors.rightMargin: 0
        anchors.bottomMargin: 0
        anchors.leftMargin: 0
        anchors.topMargin: 0
        anchors.fill: parent
        clip: true
        rotation: 0

        Rectangle {
            id: rectangle
            x: 711
            width: 400
            height: 200
            color: "#3a3a3a"
            anchors.top: parent.top
            anchors.topMargin: -33
            anchors.right: parent.right
            anchors.rightMargin: -131
            rotation: 45
            clip: true
            Material.theme: Material.Dark
            Material.accent: Material.DeepOrange
        }

        RoundButton {
            id: playButton
            x: 356
            y: 632
            width: 100
            text: "Play"
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 28
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 70
            onClicked: { api.play()}
        }

        RoundButton {
            id: stopButton
            x: 462
            y: 632
            width: 100
            text: "Stop"
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 28
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: -70
            onClicked: { api.stop()}
        }

        ComboBox {
            id: instrument_port
            x: 214
            y: 637
            width: 120
            height: 30
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 176
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 33
        }

        ComboBox {
            id: out_port
            x: 68
            y: 637
            width: 120
            height: 30
            anchors.bottomMargin: 33
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 302
            anchors.bottom: parent.bottom
            model: api.midi_out
        }
    }
    Connections {
        target: api
    }
}

Upvotes: 1

Views: 1066

Answers (1)

eyllanesc
eyllanesc

Reputation: 243887

Your code has the following errors:

  • The midi_out property can only be read and notifiable since you cannot write (create) midi devices so don't implement the setter.

  • The names of midi devices are only obtained at the beginning. And if other devices are connected? I would have to close and reopen the application, instead I added the "reload()" function that allows updating the device names.

  • If the root element is Window or ApplicationWindow then you must use QQmlApplicationEngine, if it is an Item then you must use QQuickView.

  • If you want to export as a property a list in PySide2 you must use a QVariantList (1)

import multiprocessing as mp

import mido

from Fuzzy.aidrummer import AiDrummer

from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QUrl, Slot, QObject, Property, Signal


def run():
    d = AiDrummer(
        "playback",
        file="../Music/28 coltrane 2.mid",
        play_instrument="yes",
        instrument_port="VirtualMIDISynth #1 0",
        out_port="strike 3",
        visualize=True,
    )
    d.run()


class Api(QObject):
    midi_in_names_Changed = Signal()
    midi_out_names_Changed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self.proc = None
        self.reload()

    @Slot()
    def reload(self):
        self._midi_in_names = mido.get_input_names()
        self._midi_out_names = mido.get_output_names()
        self.midi_in_names_Changed.emit()
        self.midi_out_names_Changed.emit()

    def get_midi_in_names(self):
        return self._midi_in_names

    def get_midi_out_names(self):
        return self._midi_out_names

    midi_in_names = Property(
        "QVariantList", fget=get_midi_in_names, notify=midi_in_names_Changed
    )
    midi_out_names = Property(
        "QVariantList", fget=get_midi_out_names, notify=midi_out_names_Changed
    )

    @Slot()
    def play(self):
        self.proc = mp.Process(target=run)
        self.proc.start()

    @Slot()
    def stop(self):
        self.proc.kill()


if __name__ == "__main__":
    import os
    import sys

    app = QApplication(sys.argv)

    api = Api()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("api", api)

    current_dir = os.path.dirname(os.path.realpath(__file__))

    url = QUrl.fromLocalFile(os.path.join(current_dir, "main.qml"))

    engine.load(url)

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Controls.Material 2.0

Window {
    id: window
    visible: true
    width: 980
    height: 700
    title: qsTr("AI Drummer")

    Rectangle {
        id: rectangle1
        color: "#191919"
        anchors.rightMargin: 0
        anchors.bottomMargin: 0
        anchors.leftMargin: 0
        anchors.topMargin: 0
        anchors.fill: parent
        clip: true
        rotation: 0

        Rectangle {
            id: rectangle
            x: 711
            width: 400
            height: 200
            color: "#3a3a3a"
            anchors.top: parent.top
            anchors.topMargin: -33
            anchors.right: parent.right
            anchors.rightMargin: -131
            rotation: 45
            clip: true
            Material.theme: Material.Dark
            Material.accent: Material.DeepOrange
        }

        RoundButton {
            id: playButton
            x: 356
            y: 632
            width: 100
            text: "Play"
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 28
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 70
            onClicked: { api.play()}
        }

        RoundButton {
            id: stopButton
            x: 462
            y: 632
            width: 100
            text: "Stop"
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 28
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: -70
            onClicked: { api.stop()}
        }

        ComboBox {
            id: instrument_port
            x: 214
            y: 637
            width: 120
            height: 30
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 176
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 33
        }

        ComboBox {
            id: out_port
            x: 68
            y: 637
            width: 120
            height: 30
            anchors.bottomMargin: 33
            anchors.right: parent.horizontalCenter
            anchors.rightMargin: 302
            anchors.bottom: parent.bottom
            model: api.midi_out_names
        }
    }
}

(1) Registering a Python list property to QML in pyside2

Upvotes: 1

Related Questions