Created a MapWidget using QQuickWidget and a qml file to zoom into the given location coordinates. However, the map does not refresh whenever the coordinates changed. I am trying to connect a button that can be clicked to update the map but with no luck so far. Is there a way to obtain an id for the button and pass it on to the qml file to update or refresh the map whenever the coordinates are changed?
main.py
class MapWidget(QtQuickWidgets.QQuickWidget):
def __init__(self, parent=None):
super(MapWidget, self).__init__(parent,
resizeMode=QtQuickWidgets.QQuickWidget.SizeRootObjectToView)
model = MarkerModel(self)
self.rootContext().setContextProperty("markermodel", model)
self.rootContext().setContextProperty("lataddr", globals.latitude)
self.rootContext().setContextProperty("lonaddr", globals.longitude)
qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
self.setSource(QtCore.QUrl.fromLocalFile(qml_path))
positions = [(globals.latitude, globals.longitude)]
urls = ["http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_red.png"]
for c, u in zip(positions, urls):
coord = QtPositioning.QGeoCoordinate(*c)
source = QtCore.QUrl(u)
model.appendMarker({"position": coord , "source": source})
class project_ui(QWidget):
def setup(self, window):
"omitted code"
self.btn = QPushButton('Search', self)
self.btn.setFixedWidth(200)
self.btn.clicked.connect("reload map in qml file")
main.qml
Rectangle {
id:rectangle
width: 640
height: 480
Plugin {
id: osmPlugin
name: "osm"
}
property variant locationTC: QtPositioning.coordinate(lataddr, lonaddr)
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: locationTC
zoomLevel: 10
}
MapItemView{
model: markermodel
delegate: MapQuickItem {
coordinate: position_marker
anchorPoint.x: image.width
anchorPoint.y: image.height
sourceItem:
Image { id: image; source: source_marker }
}
}
}
Considering that you want to move only one marker you must create a QObject that has the position as q-properties, export the object and make a binding in QML.
main.py
import os
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuickWidgets, QtPositioning
class MarkerObject(QtCore.QObject):
coordinateChanged = QtCore.pyqtSignal(QtPositioning.QGeoCoordinate)
sourceChanged = QtCore.pyqtSignal(QtCore.QUrl)
def __init__(self, parent=None):
super(MarkerObject, self).__init__(parent)
self._coordinate = QtPositioning.QGeoCoordinate()
self._source = QtCore.QUrl()
def getCoordinate(self):
return self._coordinate
def setCoordinate(self, coordinate):
if self._coordinate != coordinate:
self._coordinate = coordinate
self.coordinateChanged.emit(self._coordinate)
def getSource(self):
return self._source
def setSource(self, source):
if self._source != source:
self._source = source
self.sourceChanged.emit(self._source)
coordinate = QtCore.pyqtProperty(QtPositioning.QGeoCoordinate, fget=getCoordinate, fset=setCoordinate, notify=coordinateChanged)
source = QtCore.pyqtProperty(QtCore.QUrl, fget=getSource, fset=setSource, notify=sourceChanged)
class MapWidget(QtQuickWidgets.QQuickWidget):
def __init__(self, parent=None):
super(MapWidget, self).__init__(parent,
resizeMode=QtQuickWidgets.QQuickWidget.SizeRootObjectToView)
self._marker_object = MarkerObject(parent)
self._marker_object.setSource(QtCore.QUrl("http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_red.png"))
self.rootContext().setContextProperty("marker_object", self._marker_object)
qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
self.setSource(QtCore.QUrl.fromLocalFile(qml_path))
#QtCore.pyqtSlot(QtPositioning.QGeoCoordinate)
def moveMarker(self, pos):
self._marker_object.setCoordinate(pos)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._lat_spinbox = QtWidgets.QDoubleSpinBox(
minimum=-90,
maximum=90,
decimals=6,
value=59.91
)
self._lng_spinbox = QtWidgets.QDoubleSpinBox(
minimum=-180,
maximum=180,
decimals=6,
value=10.75
)
search_button = QtWidgets.QPushButton("Search", clicked=self.search)
self._map_widget = MapWidget()
flay = QtWidgets.QFormLayout(self)
flay.addRow("Latitude:", self._lat_spinbox)
flay.addRow("Longitude:", self._lng_spinbox)
flay.addRow(search_button)
flay.addRow(self._map_widget)
self.search()
#QtCore.pyqtSlot()
def search(self):
lat = self._lat_spinbox.value()
lng = self._lng_spinbox.value()
self._map_widget.moveMarker(QtPositioning.QGeoCoordinate(lat, lng))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
main.qml
import QtQuick 2.9
import QtLocation 5.12
import QtPositioning 5.12
Rectangle {
id:rectangle
width: 640
height: 480
Plugin {
id: osmPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: marker_object.coordinate
zoomLevel: 10
MapQuickItem {
coordinate: marker_object.coordinate
anchorPoint.x: image.width
anchorPoint.y: image.height
sourceItem: Image {
id: image
source: marker_object.source
}
}
}
}
Related
Here is the main.py script:
import sys, os, math
import numpy as np
import time
from PyQt5 import *
class Tab(QFrame):
def __init__(self):
super().__init__()
self.setGeometry(600, 600, 600, 600)
self.setWindowTitle("PyQt5 Tab Widget")
self.setWindowIcon(QIcon("../QML Files/Icons/Tab.png"))
vbox = QVBoxLayout()
tabWidget = QTabWidget()
tabWidget.addTab(Example01(), "Ex1")
vbox.addWidget(tabWidget)
self.setLayout(vbox)
class GG(QObject):
polygonsChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._polygons = []
def modify_Polygons(self) -> None:
for x in range(10):
time.sleep(1)
GG.set_dynamic_polygons(x, self)
def get_polygons(self) -> None:
return self._polygons
def set_polygons(self, polygons):
self._polygons = polygons
self.polygonsChanged.emit()
polygons = pyqtProperty(
"QVariant", fget=get_polygons, fset=set_polygons,
notify=polygonsChanged
)
def set_dynamic_polygons(i, p_gg) -> None:
numpy_arrays = np.array(
[[[100+i, 100], [150, 200], [50, 300]],
[[50, 60], [160, 20], [400, 10]]]
)
def set_polygons(myArray) -> []:
polygons = []
for ps in myArray:
polygon = []
# print("ps = "); print(ps)
for p in ps:
# print("p = "); print(p)
e = QPointF(*p)
polygon.append(e)
polygons.append(polygon)
return polygons
p_gg.polygons = set_polygons(numpy_arrays)
class Example01(QWidget):
def __init__(self):
super().__init__()
vbox = QVBoxLayout(self)
vbox.setContentsMargins(0, 0, 0, 0)
self.gg = GG()
GG.set_dynamic_polygons(0, self.gg)
view = QQuickWidget()
ROOT_DIR = os.path.realpath(os.path.dirname(sys.argv[0]))
qml = os.path.join(ROOT_DIR, "QML Files", "Demo01.qml")
view.setSource(QUrl.fromLocalFile(qml))
view.rootContext().setContextProperty("gg", self.gg)
view.setResizeMode(QQuickWidget.SizeRootObjectToView)
vbox.addWidget(view)
if __name__ == "__main__":
App = QApplication(sys.argv)
tabDialog = Tab()
tabDialog.show()
App.exec()
Next follows the Demo01.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtGraphicalEffects 1.0
import QtQuick.Controls 2.15
Rectangle {
id: rect
visible: true
anchors.fill: parent
LinearGradient {
anchors.fill: parent
//setting gradient at 45 degrees
start: Qt.point(rect.width, 0)
end: Qt.point(0, rect.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "#ee9d9d" }
GradientStop { position: 1.0; color: "#950707" }
}
}
Button{
id: btn
width: 100
height: 30
x: {parent.width - btn.width - 20}
y: {parent.height - btn.height - 20}
text: "Click Me"
onClicked: gg.modify_Polygons()
}
Canvas {
id: drawingCanvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = "rgb(100%,70%,30%)"
ctx.lineWidth = 5
ctx.strokeStyle = "blue"
//console.log(gg)
for(var i in gg.polygons){
var polygon = gg.polygons[i]
ctx.beginPath()
for(var j in polygon){
var p = polygon[j]
if(j === 0)
ctx.moveTo(p.x, p.y)
else
ctx.lineTo(p.x, p.y)
}
ctx.closePath()
ctx.fill()
ctx.stroke()
}
}
}
/*Connections{
target: gg
function onpolygonsChanged(){ drawingCanvas.requestPaint()}
}*/
}
The two triangles when I start the program are shown very nice.
The trouble appears when I click the button.
I tried all sorts of variants to indent the modify_Polygons() function inside the GG class.
In every version I received the same error: Property 'modify_Polygons' of object GG(0x103056bd0) is not a function comming from the Demo01.qml -> row 30.
I don't have any clue why this error appears because for me looks like a legitimate function.
What did I make wrong, please?
Only the elements of the QMetaObject are accessible from QML like the qproperties, signals and slots, and the other elements of the class are not visible from QML. So one solution is to use the #pyqtSlot decor.
On the other hand you should not use time.sleep as it will block the main thread and consequently freeze the GUI. If you want to do periodic tasks then use a QTimer.
#pyqtSlot()
def modify_Polygons(self) -> None:
for x in range(10):
# time.sleep(1)
GG.set_dynamic_polygons(x, self)
On the other hand, if you want to make animations (even if you only indicate it in the title of your post) then you must use QVariantAnimation:
#pyqtSlot()
def modify_Polygons(self) -> None:
animation = QVariantAnimation(self)
animation.setStartValue(0)
animation.setEndValue(10)
animation.valueChanged.connect(
lambda value: GG.set_dynamic_polygons(value, self)
)
animation.setDuration(10 * 1000)
animation.start(QAbstractAnimation.DeleteWhenStopped)
I have to do a menu in QML that contains the list of items in a directory and, by clicking over one of them, it plays a video (player.play())
This is what I tried, but is not working
___.qml
Menu {
id: menu
contentItem: ListView {
model: ui.textMenu
}
}
Menu {
id: menu
y: -200
x: 100
Repeater{
model: ui.textMenu
MenuItem {
text: textMenu.entry
onClicked:{
player.source = textMenu.address
player.play()
}
} //x: model.x*(videoPlayer.width - 10)
}
}
___.py
self.__textMenu = QStandardItemModel(self)
menuRoles = {entryRole: b"entry", addressRole: b"address"}
self.__textMenu.setItemRoleNames(menuRoles)
def menu(self):
directoryPath = os.path.join(self.__currentPath, r"video" )
entries = os.listdir(directoryPath)
for entry in entries:
menuVoice = QStandardItem()
menuVoice.setData(entry, entryRole)
videoPath = os.path.join(directoryPath, entry)
menuVoice.setData(videoPath, addressRole)
Your QML seems inconsistent with what you want. In this case I have created a Menu within the MenuBar where the MenuItem will be created using the model and a Repeater:
import os
import sys
from PySide2 import QtCore, QtGui, QtQml
NameRole = QtCore.Qt.UserRole + 1000
PathRole = QtCore.Qt.UserRole + 1001
def create_model(dir_path):
model = QtGui.QStandardItemModel()
roles = {NameRole: b"name", PathRole: b"path"}
model.setItemRoleNames(roles)
for name in os.listdir(dir_path):
it = QtGui.QStandardItem()
it.setData(name, NameRole)
it.setData(os.path.join(dir_path, name), PathRole)
model.appendRow(it)
return model
def main(args):
app = QtGui.QGuiApplication(args)
current_dir = os.path.dirname(os.path.realpath(__file__))
video_dir = os.path.join(current_dir, "video")
model = create_model(video_dir)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("video_model", model)
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))
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtMultimedia 5.13
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
menuBar: MenuBar {
Menu {
id: plugins_menu
title: qsTr("&Videos")
Repeater{
model: video_model
MenuItem{
text: model.name
onTriggered: {
video.source = Qt.resolvedUrl(model.path)
video.play()
}
}
}
}
}
Video {
id: video
anchors.fill: parent
}
}
I have an ListView, and inside of this i have delegate Rectangle, and inside Rectangle, i have Image object. So i want to get Image objectName into python PyQt5 file, and set for each image different sources!
ListView {
model: 12 /*that mean that i have 12 images!*/
delegate: Rectangle {
Image {
objectName: "img"
source: "file:///C:/LocalDir/Project/img/11.png"
}
}
}
def set_property():
self.context = QQuickWidget()
self.context.setSource(QUrl().fromLocalFile("QML-code.qml"))
self.rootObj = context.rootObject()
img = self.rootObj.findChild("img")
if img:
img.setProperty("source", path)
# and so on...but i don't know how to get img delegate
You are going down the wrong path, delegates can be deleted and created without notifying the GUI, so accessing them individually is not the right thing to do. The strategy in the views is to pass the information through the models, in this case as you are going to get the information of the path of the image in python it is advisable to create a model based on QAbstractListModel:
main.py
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuickWidgets
class ImageModel(QtCore.QAbstractListModel):
PathRole = QtCore.Qt.UserRole + 1
def __init__(self, parent=None):
super(ImageModel, self).__init__(parent)
self._paths = []
def addPath(self, path):
self.beginInsertRows(
QtCore.QModelIndex(), self.rowCount(), self.rowCount()
)
self._paths.append(path)
self.endInsertRows()
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self._paths)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == ImageModel.PathRole and 0 <= index.row() < self.rowCount():
return self._paths[index.row()]
return QtCore.QVariant()
def roleNames(self):
return {ImageModel.PathRole: b"path"}
if __name__ == "__main__":
import os
import sys
app = QtWidgets.QApplication(sys.argv)
model = ImageModel()
w = QtQuickWidgets.QQuickWidget(
resizeMode=QtQuickWidgets.QQuickWidget.SizeViewToRootObject
)
w.rootContext().setContextProperty("pathmodel", model)
filename = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "main.qml"
)
w.setSource(QtCore.QUrl.fromLocalFile(filename))
pictures_path = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.PicturesLocation
)
formats = (
"*{}".format(fmt.data().decode())
for fmt in QtGui.QImageReader.supportedImageFormats()
)
for finfo in QtCore.QDir(pictures_path).entryInfoList(formats):
model.addPath(finfo.absoluteFilePath())
w.show()
sys.exit(app.exec_())
main.qml
import QtQuick 2.12
Rectangle{
width: 640
height: 480
ListView {
anchors.fill: parent
model: pathmodel
delegate: Rectangle {
width: 100
height: 100
Image {
anchors.fill: parent
source: Qt.resolvedUrl(model.path)
}
}
}
}
Or more easily use a QStandardItemModel:
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuickWidgets
PathRole = QtCore.Qt.UserRole + 1
if __name__ == "__main__":
import os
import sys
app = QtWidgets.QApplication(sys.argv)
model = QtGui.QStandardItemModel()
model.setItemRoleNames({PathRole: b"path"})
w = QtQuickWidgets.QQuickWidget(
resizeMode=QtQuickWidgets.QQuickWidget.SizeViewToRootObject
)
w.rootContext().setContextProperty("pathmodel", model)
filename = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "main.qml"
)
w.setSource(QtCore.QUrl.fromLocalFile(filename))
pictures_path = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.PicturesLocation
)
formats = (
"*{}".format(fmt.data().decode())
for fmt in QtGui.QImageReader.supportedImageFormats()
)
for finfo in QtCore.QDir(pictures_path).entryInfoList(formats):
it = QtGui.QStandardItem()
it.setData(finfo.absoluteFilePath(), PathRole)
model.appendRow(it)
w.show()
sys.exit(app.exec_())
I'm trying to create some markers that will be moving dynamically on a QML map. Before that, however, I need to plot them all on the map using the mouse so that I can update them later. I have been using pyqtProperty to send the coordinates I need to QML but when I try to add them to a MapItemView, they are undefined. The following code demonstrates what I am hoping to accomplish. Is the problem that I am passing a pyqtProperty to QML from another python object that was not added with setContextProperty() like in main.py? Or am I using the MapItemView delegation incorrectly?
main.qml
import QtQuick 2.7
import QtQml 2.5
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
import QtQuick.Layouts 1.2
import QtPositioning 5.9
import QtLocation 5.3
import QtQuick.Dialogs 1.1
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
ListModel {
id: markers
}
Plugin {
id: mapPlugin
name: "osm" //"mapboxgl" "osm" "esri"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: atc.location
zoomLevel: 14
MapItemView {
model: markers
delegate: MapCircle {
radius: 50
color: 'red'
center: markLocation //issue here?
}
}
MapCircle {
id: home
center: atc.location
radius: 40
color: 'white'
}
MouseArea {
id: mousearea
anchors.fill: map
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
property var coord: map.toCoordinate(Qt.point(mouseX, mouseY))
onDoubleClicked: {
if (mouse.button === Qt.LeftButton)
{
//Capture information for model here
atc.plot_mark(
"marker",
mousearea.coord.latitude,
mousearea.coord.longitude)
markers.append({
name: "markers",
markLocation: atc.get_marker_center("markers")
})
}
}
}
}
}
atc.py
import geocoder
from DC import DC
from PyQt5.QtPositioning import QGeoCoordinate
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
class ATC(QObject):
#pyqt Signals
locationChanged = pyqtSignal(QGeoCoordinate)
def __init__(self, parent=None):
super().__init__(parent)
self._location = QGeoCoordinate()
self.dcs = {}
g = geocoder.ip('me')
self.set_location(QGeoCoordinate(*g.latlng))
def set_location(self, coordinate):
if self._location != coordinate:
self._location = coordinate
self.locationChanged.emit(self._location)
def get_location(self):
return self._location
#pyqt Property
location = pyqtProperty(QGeoCoordinate,
fget=get_location,
fset=set_location,
notify=locationChanged)
#pyqtSlot(str, str, str)
def plot_mark(self, mark_name, lat, lng):
dc = DC(mark_name)
self.dcs[mark_name] = dc
self.dcs[mark_name].set_location(
QGeoCoordinate(float(lat), float(lng)))
#pyqtSlot(str)
def get_marker_center(self, mark_name):
return self.dcs[mark_name].location
DC.py
from PyQt5.QtPositioning import QGeoCoordinate
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
class DC(QObject):
#pyqt Signals
locationChanged = pyqtSignal(QGeoCoordinate)
def __init__(self, name, parent=None):
super().__init__(parent)
self.name = name
self._location = QGeoCoordinate()
def set_location(self, coordinate):
if self._location != coordinate:
self._location = coordinate
self.locationChanged.emit(self._location)
def get_location(self):
return self._location
#pyqt Property
location = pyqtProperty(QGeoCoordinate,
fget=get_location,
fset=set_location,
notify=locationChanged)
main.py
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
from PyQt5.QtPositioning import QGeoCoordinate
from ATC import ATC
if __name__ == "__main__":
import os
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
atc = ATC()
engine.rootContext().setContextProperty("atc", atc)
qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
engine.load(QUrl.fromLocalFile(qml_path))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
Instead of creating a model in QML you must create it in python to be able to handle it directly for it you must inherit from QAbstractListModel. For a smooth movement you should use QxxxAnimation as QPropertyAnimation.
In the following example, each time a marker is inserted, the on_markersInserted function will be called, which will move the marker towards the center of the window.
main.py
from functools import partial
from PyQt5 import QtCore, QtGui, QtQml, QtPositioning
import geocoder
class Marker(QtCore.QObject):
locationChanged = QtCore.pyqtSignal(QtPositioning.QGeoCoordinate)
def __init__(self, location=QtPositioning.QGeoCoordinate(), parent=None):
super().__init__(parent)
self._location = location
def set_location(self, coordinate):
if self._location != coordinate:
self._location = coordinate
self.locationChanged.emit(self._location)
def get_location(self):
return self._location
location = QtCore.pyqtProperty(QtPositioning.QGeoCoordinate,
fget=get_location,
fset=set_location,
notify=locationChanged)
def move(self, location, duration=1000):
animation = QtCore.QPropertyAnimation(
targetObject=self,
propertyName=b'location',
startValue=self.get_location(),
endValue=location,
duration=duration,
parent=self
)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
def moveFromTo(self, start, end, duration=1000):
self.set_location(start)
self.move(end, duration)
class MarkerModel(QtCore.QAbstractListModel):
markersInserted = QtCore.pyqtSignal(list)
PositionRole = QtCore.Qt.UserRole + 1000
def __init__(self, parent=None):
super().__init__(parent)
self._markers = []
self.rowsInserted.connect(self.on_rowsInserted)
def rowCount(self, parent=QtCore.QModelIndex()):
return 0 if parent.isValid() else len(self._markers)
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid() and 0 <= index.row() < self.rowCount():
if role == MarkerModel.PositionRole:
return self._markers[index.row()].get_location()
return QtCore.QVariant()
def roleNames(self):
roles = {}
roles[MarkerModel.PositionRole] = b'position'
return roles
#QtCore.pyqtSlot(QtPositioning.QGeoCoordinate)
def appendMarker(self, coordinate):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
marker = Marker(coordinate)
self._markers.append(marker)
self.endInsertRows()
marker.locationChanged.connect(self.update_model)
def update_model(self):
marker = self.sender()
try:
row = self._markers.index(marker)
ix = self.index(row)
self.dataChanged.emit(ix, ix, (MarkerModel.PositionRole,))
except ValueError as e:
pass
#QtCore.pyqtSlot(QtCore.QModelIndex, int, int)
def on_rowsInserted(self, parent, first, end):
markers = []
for row in range(first, end+1):
markers.append(self.get_marker(row))
self.markersInserted.emit(markers)
def get_marker(self, row):
if 0 <= row < self.rowCount():
return self._markers[row]
class ManagerMarkers(QtCore.QObject):
locationChanged = QtCore.pyqtSignal(QtPositioning.QGeoCoordinate)
def __init__(self, parent=None):
super().__init__(parent)
self._center= Marker(parent=self)
self._model = MarkerModel(self)
g = geocoder.ip('me')
self.moveCenter(QtPositioning.QGeoCoordinate(*g.latlng))
def model(self):
return self._model
def center(self):
return self._center
def moveCenter(self, position):
self._center.set_location(position)
center = QtCore.pyqtProperty(QtCore.QObject, fget=center, constant=True)
model = QtCore.pyqtProperty(QtCore.QObject, fget=model, constant=True)
# testing
# When a marker is added
# it will begin to move toward
# the center of the window
def on_markersInserted(manager, markers):
end = manager.center.get_location()
for marker in markers:
marker.move(end, 5*1000)
if __name__ == "__main__":
import os
import sys
app = QtGui.QGuiApplication(sys.argv)
manager = ManagerMarkers()
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("manager", manager)
qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(qml_path))
if not engine.rootObjects():
sys.exit(-1)
manager.model.markersInserted.connect(partial(on_markersInserted, manager))
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtPositioning 5.9
import QtLocation 5.3
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl" "osm" "esri"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: manager.center.location
zoomLevel: 14
MapCircle {
id: home
center: manager.center.location
radius: 40
color: 'white'
}
MapItemView {
model: manager.model
delegate: MapCircle {
radius: 50
color: 'red'
center: model.position
}
}
MouseArea {
id: mousearea
anchors.fill: map
acceptedButtons: Qt.LeftButton | Qt.RightButton
onDoubleClicked: if (mouse.button === Qt.LeftButton)
manager.model.appendMarker(map.toCoordinate(Qt.point(mouseX, mouseY)))
}
}
}
I have built a grid of rectangles in QML which is run from Python. engine.load('main.qml')
Window {
id: channels
Grid {
columns: 2
spacing: 9
Rectangle {
color: "#333"
width: 75
height: 75
}
Rectangle {
color: "#333"
width: 75
height: 75
}
}
}
However, I would like to have over fifty rectangles, so I need to be able to dynamically create and update them from python. How can I do that?
To provide information from python (or C++) to qml we can read this link. In it recommends to use QAbstractListModel since this notifies the changes to the qml, in addition to be added dynamically we will use Repeater.
main.qml:
import QtQuick.Window 2.2
import QtQuick 2.0
import QtQuick.Controls 1.4
Window {
visible: true
id: channels
Grid {
columns: 3
spacing: 9
Repeater{
model: myModel
delegate: Rectangle{
height: model.height
width: model.height
color: model.color
}
}
}
}
.py:
class Data(object):
def __init__(self, width=35, height=35, color=QColor("red")):
self._width = width
self._height = height
self._color = color
def width(self):
return self._width
def height(self):
return self._height
def color(self):
return self._color
class Model(QAbstractListModel):
WidthRole = Qt.UserRole + 1
HeightRole = Qt.UserRole + 2
ColorRole = Qt.UserRole + 3
_roles = {WidthRole: b"width", HeightRole: b"height", ColorRole: b"color"}
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
self._datas = []
def addData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._datas.append(data)
self.endInsertRows()
def rowCount(self, parent=QModelIndex()):
return len(self._datas)
def data(self, index, role=Qt.DisplayRole):
try:
data = self._datas[index.row()]
except IndexError:
return QVariant()
if role == self.WidthRole:
return data.width()
if role == self.HeightRole:
return data.height()
if role == self.ColorRole:
return data.color()
return QVariant()
def roleNames(self):
return self._roles
To make a test we use the following code:
main.py
if __name__ == "__main__":
import sys
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
model = Model()
model.addData(Data(44, 33, QColor("red")))
model.addData(Data(23, 53, QColor("#333")))
context = engine.rootContext()
context.setContextProperty('myModel', model)
engine.load(QUrl.fromLocalFile("main.qml"))
if len(engine.rootObjects()) == 0:
sys.exit(-1)
qsrand(QTime.currentTime().msec())
timer = QTimer(engine)
timer.timeout.connect(lambda: model.addData(Data(20 + qrand() % 40,
20 + qrand() % 40,
QColor(qrand() % 255, qrand() % 255, qrand() % 255))))
timer.start(1000)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
The complete example you find here.