Reputation: 13
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
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