I should display some rects with qml.
These Rectangle{} the positiopn of those depend on x=array[i][0] and y=array[i][1], and the quantity of those depend on array.lenght().
array in qml should be equal to self.__rectanglePos in Python
So I need a way to draw Rectangle in a different position and in different amount that depends array.
how can I do that?
_____.py
class NN(QObject):
def __init__(self):
QObject.__init__(self)
self.__rectanglePos = [] #each element contains:[x,y,orientation 360°]
#signals
rectanglePosChanged = Signal(type(self.__rectanglePos))
#Slot()
def rects(self):
self.rectanglePosChanged.emit(self.__rectanglePos)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
qmlRegisterType(NN, 'gnn', 1, 0, 'NN')
engine.load(QUrl.fromLocalFile(gnn.qml))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
this is not working in qml:
________.qml
#[...]
import gnn 1.0
ApplicationWindow {
ListView{
id: rect
function f(){
for (var i = 0; i < array.lenght(); ++i){
Rectangle {
x: array[i][0]
y: array[i][1]
}
}
}
}
MouseArea{
onClicked: rects()
}
NN{
id: nn
signal getRectangles(var array) // not working
Component.onCompleted: {
nn.rectanglePosChanged.connect(getRectangles)
}
}
Connections {
target: nn
onGetRectangles: {
}
}
}
Your code has many errors, for example you want to access an attribute of the class: self.__rectanglePos) which only makes sense when creating an object from a part of the code that is executed before creating the class: rectanglePosChanged = Signal(type(self.__rectanglePos)), so I will avoid pointing out what your errors are except those necessary for my solution.
If you want to send information as a list where you add, replace or remove items then it is better to use a model as it will notify you in view of the changes. In this case for simplicity I use a QStandardItemModel, so each part of the element as "x" and "y" will be accessed through roles.
In the case of QML, as you want to place Rectangles in any position, using ListView is not the right option since this view restricts the position, instead you must use a Repeater.
main.py
import os
import sys
from PySide2 import QtCore, QtGui, QtQml
XRole = QtCore.Qt.UserRole + 1000
YRole = QtCore.Qt.UserRole + 1001
AngleRole = QtCore.Qt.UserRole + 1002
class RectangleManager(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.m_model = QtGui.QStandardItemModel(self)
roles = {XRole: b"x", YRole: b"y", AngleRole: b"angle"}
self.m_model.setItemRoleNames(roles)
#QtCore.Property(QtCore.QObject, constant=True)
def model(self):
return self.m_model
#QtCore.Slot(float, float, float)
def add_rectangle(self, x, y, angle):
it = QtGui.QStandardItem()
it.setData(x, XRole)
it.setData(y, YRole)
it.setData(angle, AngleRole)
self.model.appendRow(it)
#QtCore.Slot()
def clear_rectangles(self):
self.model.clear()
def main(args):
app = QtGui.QGuiApplication(args)
manager = RectangleManager()
import random
for _ in range(10):
manager.add_rectangle(
random.randint(0, 500), random.randint(0, 500), random.randint(0, 360)
)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("rectangle_manager", manager)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
return -1
ret = app.exec_()
return ret
if __name__ == "__main__":
sys.exit(main(sys.argv))
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
Repeater{
model: rectangle_manager.model
Rectangle{
x: model.x
y: model.y
rotation: model.angle
width: 100
height: 100
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
}
Related
I want to plot data in Qt charts (QML) from python. The x,y data are saved in array x = np.array([0, 6]) y = np.array([0, 250]) . I am desperate how to pass these data to Qt Charts with one step. I can do it (step by step) with Signal/Slot, where the Signal is with parameters (x,y).
My working (very slow) code:
Creating signal from python to qml:
class AppWindow(QObject):
# Signals from python to QML
sigPlotData = Signal(int, int, arguments=['x','y'])
and later () I generate and send data to chart like that:
...
for i in range(50):
self.app.sigPlotData.emit(i, random.randint(0,150))
...
In QML file I do this:
//connections from Python to QML via signals
Connections {
target: backend
function onSigPlotData(x,y){
lineSer.append(x, y);
}
}
ChartView {
id: chartView
title: "Line"
anchors.fill: parent
ValueAxis{
id: axisX
min: 0
max: maxX
}
ValueAxis{
id: axisY
min: 0
max: 150
}
LineSeries {
id: lineSer
name: "data"
axisX: axisX
axisY: axisY
}
}
Thank you very much for help.
you can have this :
in main.py :
import os
from pathlib import Path
import sys
from PyQt5.QtCore import QCoreApplication, Qt, QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
def main():
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(
handle_object_created, Qt.ConnectionType.QueuedConnection
)
engine.load(url)
sys.exit(app.exec())
if __name__ == "__main__":
main()
and in main.qml :
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtCharts 2.15
ApplicationWindow {
visible: true
width: 600
height: 300
property int timeStep: 0
ChartView {
id: chartView
anchors.fill: parent
ValueAxis {
id: axisX
min: 0
max: 400
}
LineSeries {
id: series1
axisX: axisX
name: "data"
}
}
Timer {
interval: 100
repeat: true
running: true
onTriggered: {
timeStep++;
var y = (1+Math.cos(timeStep/10.0))/2.0;
series1.append(timeStep, y);
}
}
}
Result is:
In QML you can create your function or other things like(for, if,..) with javascript syntax
and as you see I use series1.append(timeStep, y); means append function for adding data.
I am trying to get my .qml map to display a circle where the user clicks however keep tying myself in knots. The window output the coordinates to the spinboxes in GeoWidget properly, however I want a circle to appear in that location so the user has some feedback.
coord_map.qml
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import Fluid.Controls 1.0
import QtLocation 5.6
import QtPositioning 5.6
Item {
width: 600
height: 600
Map {
id: map
anchors.fill: parent
zoomLevel: 6
center: (0, 0)
MapItemView {
model: src
delegate: MapQuickItem {
coordinate: src.position.coordinate
anchorPoint.x: image.width * 0.5
anchorPoint.y: image.height
sourceItem: Column {
Image { id: image; source: "marker.png" }
Text { text: title; font.bold: true }
}
}
}
MapCircle {
id: circle
radius: parent.width/10
border.width: 1
color: 'green'
}
}
MouseArea {
id: mouse
anchors.fill: parent
onClicked: {controller.update_from_map(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
circle.center(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
}
plugin: Plugin {
name: "osm"
}
}
}
coordbox.py
import os
import sys
from pathlib import Path
from PySide2.QtCore import Property, Signal, Slot, Qt, QUrl
from PySide2.QtPositioning import QGeoCoordinate
from PySide2.QtQuickWidgets import QQuickWidget
from PySide2.QtWidgets import (
QApplication,
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QHBoxLayout,
QLabel,
QToolButton,
QVBoxLayout,
QWidget,
QWizard,
QWizardPage,
)
CURRENT_DIRECTORY = Path(__file__).resolve().parent
# TODO: Give the user feedback on where their coordinate is when clicked
class MapDialog(QDialog):
def __init__(self, geo_widget):
super().__init__(geo_widget)
self.setWindowTitle("Map")
self.map_widget = QQuickWidget(resizeMode=QQuickWidget.SizeRootObjectToView)
self.map_widget.rootContext().setContextProperty("controller", geo_widget)
filename = os.fspath(CURRENT_DIRECTORY / "coord_map.qml")
url = QUrl.fromLocalFile(filename)
self.map_widget.setSource(url)
button_box = QDialogButtonBox()
button_box.setOrientation(Qt.Horizontal)
button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
lay = QVBoxLayout(self)
lay.addWidget(self.map_widget)
lay.addWidget(button_box)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
class GeoWidget(QWidget):
coordinate_changed = Signal(name="coordinateChanged")
def __init__(self, parent=None):
super().__init__(parent)
self._coordinate = QGeoCoordinate(0, 0)
self._lat_spinbox = QDoubleSpinBox(
minimum=49.0, maximum=56.0 # , valueChanged=self.handle_value_changed,
)
self._lng_spinbox = QDoubleSpinBox(
minimum=-8, maximum=2 # , valueChanged=self.handle_value_changed,
)
self.btn = QToolButton(text="Map", clicked=self.handle_clicked)
self.map_view = MapDialog(self)
# self.coordinate_changed.connect(self.handle_value_changed)
# self.coordinate_changed.connect(self.handle_value_changed)
lay = QHBoxLayout(self)
lay.addWidget(QLabel("Latitude:"))
lay.addWidget(self._lat_spinbox)
lay.addWidget(QLabel("Longitude:"))
lay.addWidget(self._lng_spinbox)
lay.addWidget(self.btn)
#Property(QGeoCoordinate, notify=coordinate_changed)
def coordinate(self):
return self._coordinate
#coordinate.setter
def coordinate(self, coordinate):
if self.coordinate == coordinate:
return
self._coordinate = coordinate
self.coordinate_changed.emit()
def handle_value_changed(self):
coordinate = QGeoCoordinate(
self._lat_spinbox.value(), self._lng_spinbox.value()
)
self.coordinate = coordinate
#Slot(QGeoCoordinate)
def update_from_map(self, coordinate):
self.coordinate = coordinate
self._lat_spinbox.setValue(self.coordinate.latitude())
self._lng_spinbox.setValue(self.coordinate.longitude())
def handle_clicked(self):
self.map_view.exec_()
# Rest of code is used to debug this coordinate box
class WizardPage(QWizardPage):
def __init__(self, parent=None):
super().__init__(parent)
self.geo_widget1 = GeoWidget()
self.geo_widget2 = GeoWidget()
self.registerField("coordinate1", self.geo_widget1)
self.registerField("coordinate2", self.geo_widget2)
lay = QVBoxLayout(self)
lay.addWidget(self.geo_widget1)
lay.addWidget(self.geo_widget2)
def main():
app = QApplication(sys.argv)
w = QWizard()
page = WizardPage()
w.addPage(page)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have had it working however it just shows a blank screen now I am attempting to implement the feedback. I have tried to pick up as much as I can from the documents but not being able to debug qml is causing issues as I don't even know if onClicked can do two things at once (eg. emit the coordinates to the spinboxes and add a feedback circle.
Your code has several things not defined so I will delete them. On the other hand, center is a property, it is not a function, so what you must do is assign the position. It should also be noted that the radius of MapCircle is in meters and not in pixels, so you must choose an appropriate value for the zoom you use.
import QtLocation 5.6
import QtPositioning 5.6
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
Item {
width: 600
height: 600
Map {
id: map
anchors.fill: parent
zoomLevel: 6
center: QtPositioning.coordinate(0, 0)
MapCircle {
id: circle
radius: 100000
border.width: 1
color: 'green'
}
MouseArea {
id: mouse
anchors.fill: parent
onClicked: {
var pos = map.toCoordinate(Qt.point(mouse.x, mouse.y));
circle.center = pos;
controller.update_from_map(pos);
}
}
plugin: Plugin {
name: "osm"
}
}
}
I am trying to replicate below example using PySide2.
https://evileg.com/en/post/242/
But as PySide2 doesn't support emitting a Signal with named parameter to QML, i have no clue about how to do this using PySide2 ?
Here is my code
main.py
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Signal, Slot, Property
class Calculator(QObject):
def __init__(self):
QObject.__init__(self)
sumResult = Signal(int)
subResult = Signal(int)
#Slot(int, int)
def sum(self, arg1, arg2):
self.sumResult.emit(arg1 + arg2)
#Slot(int, int)
def sub(self, arg1, arg2):
self.subResult.emit(arg1 - arg2)
if __name__ == "__main__":
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
calculator = Calculator()
engine.rootContext().setContextProperty("calculator", calculator)
engine.load("/code/QML/calc.qml")
engine.quit.connect(app.quit)
sys.exit(app.exec_())
You can not replicate it like this, if you want to implement the project you can make the slot return the value:
main.py
from PySide2 import QtCore, QtGui, QtQml
class Calculator(QtCore.QObject):
# Slot for summing two numbers
#QtCore.Slot(int, int, result=int)
def sum(self, arg1, arg2):
return arg1 + arg2
# Slot for subtraction of two numbers
#QtCore.Slot(int, int, result=int)
def sub(self, arg1, arg2):
return arg1 - arg2
if __name__ == "__main__":
import os
import sys
# Create an instance of the application
app = QtGui.QGuiApplication(sys.argv)
# Create QML engine
engine = QtQml.QQmlApplicationEngine()
# Create a calculator object
calculator = Calculator()
# And register it in the context of QML
engine.rootContext().setContextProperty("calculator", calculator)
# Load the qml file into the engine
file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
engine.load(file)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
ApplicationWindow {
visible: true
width: 640
height: 240
title: qsTr("PyQt5 love QML")
color: "whitesmoke"
GridLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 9
columns: 4
rows: 4
rowSpacing: 10
columnSpacing: 10
Text {
text: qsTr("First number")
}
// Input field of the first number
TextField {
id: firstNumber
}
Text {
text: qsTr("Second number")
}
// Input field of the second number
TextField {
id: secondNumber
}
Button {
height: 40
Layout.fillWidth: true
text: qsTr("Sum numbers")
Layout.columnSpan: 2
onClicked: {
// Invoke the calculator slot to sum the numbers
sumResult.text = calculator.sum(firstNumber.text, secondNumber.text)
}
}
Text {
text: qsTr("Result")
}
// Here we see the result of sum
Text {
id: sumResult
}
Button {
height: 40
Layout.fillWidth: true
text: qsTr("Subtraction numbers")
Layout.columnSpan: 2
onClicked: {
// Invoke the calculator slot to subtract the numbers
subResult.text = calculator.sub(firstNumber.text, secondNumber.text)
}
}
Text {
text: qsTr("Result")
}
// Here we see the result of subtraction
Text {
id: subResult
}
}
}
Note: For PyQt5 change Slot to pyqtSlot.
I'm kinda new to PySide2 and QML and I really need a way to replace all the points in an XYSeries at once. Since the QML item does not have a function that does so, I thought I had to create a custom class (that would inherits from QtCharts.QXYSeries), implement the function I need and then register the new type with PySide2.QtQml.qmlRegisterType, but I don't know how that should be done and I haven't been able to find an answer online (or at least one that I could understand).
So, to cut a long story short, what I need to know is if there's a way to change all the points of an XYSeries and how can it be done (e.g. creating a custom class and registering it, accessing the Item declarend in the .qml file from python and chaning its properties, etc...).
I know my question is really vague but I do not know where to look and what to do...
EDIT
I have a python class that acquires data from an instruments and generates an array of X and Y points. Since this arrays are made from at least 1000 points and since I need to have a refresh rate of at least 1Hz, it's impossible to do it adding one point at a time (I have an signal that sends the whole array to the qml interface and there, for the moment, I simply clear the series and add one XY couple at a time. It works but it's too damn slow).
One possible solution is to create a class that allows access to a QML object from Python, in this case I create the helper class that I export to QML through setContextProperty by linking the series with a qproperty.
main.py
import random
from PySide2 import QtCore, QtWidgets, QtQml
from PySide2.QtCharts import QtCharts
class Helper(QtCore.QObject):
serieChanged = QtCore.Signal()
def __init__(self, parent=None):
super(Helper, self).__init__(parent)
self._serie = None
def serie(self):
return self._serie
def setSerie(self, serie):
if self._serie == serie:
return
self._serie = serie
self.serieChanged.emit()
serie = QtCore.Property(QtCharts.QXYSeries, fget=serie, fset=setSerie, notify=serieChanged)
#QtCore.Slot(list)
def replace_points(self, points):
if self._serie is not None:
self._serie.replace(points)
class Provider(QtCore.QObject):
pointsChanged = QtCore.Signal(list)
def __init__(self, parent=None):
super(Provider, self).__init__(parent)
timer = QtCore.QTimer(
self,
interval=100,
timeout=self.generate_points
)
timer.start()
#QtCore.Slot()
def generate_points(self):
points = []
for i in range(101):
point = QtCore.QPointF(i, random.uniform(-10, 10))
points.append(point)
self.pointsChanged.emit(points)
if __name__ == '__main__':
import os
import sys
app = QtWidgets.QApplication(sys.argv)
helper = Helper()
provider = Provider()
provider.pointsChanged.connect(helper.replace_points)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("helper", helper)
file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(file))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtCharts 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ChartView{
anchors.fill: parent
LineSeries{
id: serie
axisX: axisX
axisY: axisY
}
ValueAxis {
id: axisX
min: 0
max: 100
}
ValueAxis {
id: axisY
min: -10
max: 10
}
Component.onCompleted: helper.serie = serie
}
}
i have created a "Spectrum Analyser" Python project that is fully working, i hope it may be useful for some of you.
(In the real-life the "createserie" function may contain SCPI commands that would read actual data from any Spectrum Analyser, Oscilloscopes...)
This example demonstrates how to use QtQuick/QML, QtCharts and QThread together.
After clicking the "START" button the Qthread starts and enters a infinite loop (the loop can be terminated by clicking the "STOP" button).
In each loop some "dummy" random data (basically a "QXYSeries" of 1000 points) are generated and the plot is updated (it's actually very fast).
I am using a QThread so that the GUI remains anytime responsive.
I want to share this example because it took me a lot of time to write it and it was not that easy to find some good QML information online.
Main.py:
import sys
import os
# import time
import random
from PySide2.QtCore import Qt, QUrl, QThread, QPoint, QPointF, Slot, Signal, QObject, QProcess, Property, qInstallMessageHandler, QtInfoMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg
from PySide2.QtQuick import QQuickView
from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
# from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCharts import QtCharts
# import pdb
print(chr(27) + "[2J")
def qt_message_handler(mode, context, message):
if mode == QtInfoMsg:
mode = 'Info'
elif mode == QtWarningMsg:
mode = 'Warning'
elif mode == QtCriticalMsg:
mode = 'critical'
elif mode == QtFatalMsg:
mode = 'fatal'
else:
mode = 'Debug'
print("%s: %s (%s:%d, %s)" % (mode, message, context.file, context.line, context.file))
class Worker1(QObject):
set_val = Signal(QtCharts.QXYSeries)
finished = Signal()
def __init__(self, serie, parent=None):
QObject.__init__(self, parent)
self._serie = serie
self._isRunning = True
def run(self):
measure(self)
def stop(self):
self._isRunning = False
def measure(self): # Called inside Thread1
while 1:
if self._isRunning == True:
createserie(self)
self.set_val.emit(self._serie)
# time.sleep(0.002)
else:
print("QUITING LOOP")
break
self.finished.emit()
return
def createserie(self):
points = []
for i in range(1001):
points.append(QPointF(i/1000, random.random()))
self._serie.replace(points)
class Backend(QObject):
setval = Signal(QtCharts.QXYSeries)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._serie = None
#Slot(QtCharts.QXYSeries) # expose QML serie to Python
def exposeserie(self, serie):
self._serie = serie
print(serie)
print("QML serie exposed to Python")
#Slot(str)
def startthread(self, text):
self.WorkerThread = QThread()
self.worker = Worker1(self._serie)
self.WorkerThread.started.connect(self.worker.run)
self.worker.finished.connect(self.end)
self.worker.set_val.connect(self.setval)
self.worker.moveToThread(self.WorkerThread) # Move the Worker object to the Thread object
self.WorkerThread.start()
#Slot(str)
def stopthread(self, text):
self.worker.stop()
print("CLOSING THREAD")
def end(self):
self.WorkerThread.quit()
self.WorkerThread.wait()
msgBox = QMessageBox()
msgBox.setText("THREAD CLOSED")
msgBox.exec()
class MainWindow(QObject):
def __init__(self, parent = None):
# Initialization of the superclass
super(MainWindow, self).__init__(parent)
qInstallMessageHandler(qt_message_handler)
self.backend = Backend()
# Expose the Python object to QML
self.engine = QQmlApplicationEngine()
self.context = self.engine.rootContext()
self.context.setContextProperty("backend", self.backend)
# Load the GUI
self.engine.load(os.path.join(os.path.dirname(__file__), "SpecPXA_QML.qml"))
if not self.engine.rootObjects():
sys.exit(-1)
self.win = self.engine.rootObjects()[0]
# Execute a function if "Start" button clicked
startbutton = self.win.findChild(QObject, "startbutton")
startbutton.startclicked.connect(self.startclicked)
# Execute a function if "Stop" button clicked
stopbutton = self.win.findChild(QObject, "stopbutton")
stopbutton.stopclicked.connect(self.stopclicked)
def startclicked(self):
print("START")
self.backend.startthread("test")
def stopclicked(self):
print("STOP")
self.backend.stopthread("test")
if __name__ == "__main__":
if not QApplication.instance():
app = QApplication(sys.argv)
else:
app = QApplication.instance()
app.setStyle('Fusion') # 'Breeze', 'Oxygen', 'QtCurve', 'Windows', 'Fusion'
w = MainWindow()
sys.exit(app.exec_())
and SpecPXA_QML.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Dialogs 1.2
import QtCharts 2.3
ApplicationWindow {
width: 1200
height: 700
visible: true
title: qsTr("Hello World")
property var xySeries;
// MessageDialog {
// id: messageDialogQuit
// title: "Question:"
// icon: StandardIcon.Question
// text: "Quit program?"
// standardButtons: StandardButton.Yes |StandardButton.No
// // Component.onCompleted: visible = true
// onYes: {
// Qt.quit()
// close.accepted = true
// }
// onNo: {
// close.accepted = false
// }
// }
// onClosing: {
// close.accepted = true
// onTriggered: messageDialogQuit.open()
// }
MenuBar {
id: menuBar
width: Window.width
Menu {
title: qsTr("&File")
Action { text: qsTr("&New...") }
Action { text: qsTr("&Open...") }
Action { text: qsTr("&Save") }
Action { text: qsTr("Save &As...") }
MenuSeparator { }
Action { text: qsTr("&Quit") }
}
Menu {
title: qsTr("&Edit")
Action { text: qsTr("Cu&t") }
Action { text: qsTr("&Copy") }
Action { text: qsTr("&Paste") }
}
Menu {
title: qsTr("&Help")
Action { text: qsTr("&About") }
}
}
SplitView {
id: splitView
y: menuBar.height
width: Window.width
height: Window.height-(menuBar.height+infoBar.height)
orientation: Qt.Horizontal
Rectangle {
id: leftitem
height: Window.height
implicitWidth: 200
color: "red"
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.leftMargin: 0
anchors.bottomMargin: 0
anchors.topMargin: 0
Button {
//id: startbutton
signal startclicked
objectName: "startbutton"
y: 40
height: 40
text: qsTr("Start")
anchors.left: parent.left
anchors.right: parent.right
checkable: false
anchors.rightMargin: 30
anchors.leftMargin: 30
onClicked: startclicked("START")
//onClicked: backend.text = "Button was pressed"
}
Button {
//id: stopbutton
signal stopclicked
objectName: "stopbutton"
y: 100
height: 40
text: qsTr("Stop")
anchors.left: parent.left
anchors.right: parent.right
checked: false
checkable: false
anchors.rightMargin: 30
anchors.leftMargin: 30
onClicked: stopclicked("STOP")
}
}
Rectangle {
id: rightitem
height: Window.height
color: "green"
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: 0
anchors.rightMargin: 0
anchors.bottomMargin: 0
Rectangle {
id: rectangle
color: "#ffffff"
anchors.fill: parent
anchors.rightMargin: 30
anchors.leftMargin: 30
anchors.bottomMargin: 30
anchors.topMargin: 30
ChartView {
id: line
anchors.fill: parent
ValueAxis {
id: axisX
min: 0
max: 1
}
ValueAxis {
id: axisY
min: 0
max: 1
}
// LineSeries {
// id: xySeries
// name: "my_Serie"
// axisX: axisX
// axisY: axisY
// useOpenGL: true
// XYPoint { x: 0.0; y: 0.0 }
// XYPoint { x: 1.1; y: 2.1 }
// XYPoint { x: 1.9; y: 3.3 }
// XYPoint { x: 2.1; y: 2.1 }
// XYPoint { x: 2.9; y: 4.9 }
// XYPoint { x: 3.4; y: 3.0 }
// XYPoint { x: 4.1; y: 3.3 }
// }
Component.onCompleted: {
xySeries = line.createSeries(ChartView.SeriesTypeLine, "my_plot", axisX, axisY);
xySeries.useOpenGL = true
backend.exposeserie(xySeries) // expose the serie to Python (QML to Python)
}
}
}
}
}
MenuBar {
id: infoBar
x: 0
y: 440
width: Window.width
height: 30
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
}
Connections {
target: backend
function onSetval(serie) { // "serie" is calculated in python (Python to QML)
xySeries = serie; // progressbar.value = val
// console.log(serie);
}
}
}
Best Regards.
Olivier.
I want to create a similar looking UI using QML as in the link below, that was my question on using Qcompleter in pyqt5 , pyqt5 autocomplete QLineEdit - Google places autocomplete . How can we implement the same model in Qt Quick, i cant seem to use the models in TextField of QML as i get the below error
Using the previous model it is only necessary to create the GUI, in this case use a ListView + Popup implementing the logic of selection:
main.py
import os
import sys
import json
from PyQt5 import QtCore, QtGui, QtNetwork, QtQml
API_KEY = "<API_KEY>"
class SuggestionPlaceModel(QtGui.QStandardItemModel):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str)
countChanged = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(SuggestionPlaceModel, self).__init__(parent)
self._manager = QtNetwork.QNetworkAccessManager(self)
self._reply = None
def count(self):
return self.rowCount()
count = QtCore.pyqtProperty(int, fget=count, notify=countChanged)
#QtCore.pyqtSlot(str)
def search(self, text):
self.clear()
self.countChanged.emit()
if self._reply is not None:
self._reply.abort()
if text:
r = self.create_request(text)
self._reply = self._manager.get(r)
self._reply.finished.connect(self.on_finished)
loop = QtCore.QEventLoop()
self.finished.connect(loop.quit)
loop.exec_()
def create_request(self, text):
url = QtCore.QUrl("https://maps.googleapis.com/maps/api/place/autocomplete/json")
query = QtCore.QUrlQuery()
query.addQueryItem("key", API_KEY)
query.addQueryItem("input", text)
query.addQueryItem("types", "geocode")
query.addQueryItem("language", "en")
url.setQuery(query)
request = QtNetwork.QNetworkRequest(url)
return request
#QtCore.pyqtSlot()
def on_finished(self):
reply = self.sender()
if reply.error() == QtNetwork.QNetworkReply.NoError:
data = json.loads(reply.readAll().data())
if data['status'] == 'OK':
for prediction in data['predictions']:
self.appendRow(QtGui.QStandardItem(prediction['description']))
self.error.emit(data['status'])
self.countChanged.emit()
self.finished.emit()
reply.deleteLater()
self._reply = None
if __name__ == '__main__':
app = QtGui.QGuiApplication(sys.argv)
QtQml.qmlRegisterType(SuggestionPlaceModel, "PlaceModel", 1, 0, "SuggestionPlaceModel")
engine = QtQml.QQmlApplicationEngine()
qml_filename = os.path.join(os.path.dirname(__file__), 'main.qml')
engine.load(QtCore.QUrl.fromLocalFile(qml_filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import PlaceModel 1.0
ApplicationWindow {
width: 400
height: 400
visible: true
QtObject {
id: internal
property bool finished: false
property bool busy: false
}
SuggestionPlaceModel{
id: suggestion_model
onFinished: {
internal.busy = false
if(count == 0) internal.finished = true
}
}
TextField {
anchors.centerIn: parent
id: textfield
onTextChanged: {
internal.busy = true
internal.finished = false
Qt.callLater(suggestion_model.search, text)
}
Popup {
id: popup
y: parent.height
visible: !internal.finished && textfield.length > 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
contentItem: Loader{
sourceComponent: internal.busy ? busy_component: lv_component
}
}
}
Component{
id: busy_component
BusyIndicator {
running: true
}
}
Component{
id: lv_component
ListView {
implicitWidth: contentItem.childrenRect.width
implicitHeight: contentHeight
model: suggestion_model
delegate: Text {
text: model.display
MouseArea{
id: mousearea
anchors.fill: parent
hoverEnabled: true
onClicked: {
textfield.text = model.display
internal.finished = true
}
}
}
}
}
}