【问题标题】:Clickable SVG image to run method可点击的 SVG 图像运行方法
【发布时间】:2019-11-28 14:23:16
【问题描述】:

我创建了一个需要交互的地图(火车)的 SVG 图像,当点击地图的一部分时,颜色会发生变化,并且会执行控制事物的方法(通过树莓派)。

将按钮连接到插槽很简单,我猜测一下:

self.path.clicked.connect(self.some_func)

是否可以单击 SVG 元素并让它根据给定的id 执行方法,就像单击按钮时一样?

这是一个 MVCE:

test.svg

<svg height="100" width="100">
    <circle id="elem1" cx="50" cy="50" r="40"/>
</svg> 

svg_widget.py

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtXml import *
from PyQt5.QtSvg import *
import sys

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

        self.display = QTextEdit()
        self.doc = QDomDocument("doc")

        fName = QFile("test.svg")

        if not fName.open(QIODevice.ReadOnly):
            print("Cannot open the File")
            exit(-1)

        if not self.doc.setContent(fName):
            print("Cannot parse the content")
            fName.close()
            exit(-1)
        fName.close()

        roots = self.doc.elementsByTagName("svg")

        if roots.size() < 1:
            print("Cannot find root")
            exit(-1)

        self.root = roots.at(0).toElement()
        self.path = self.root.firstChild().toElement()

        self.load(self.doc.toByteArray())

        # self.path.clicked.connect(self.some_func)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        QTimer.singleShot(2000, lambda: self.some_func()) 

    def some_func(self):
        print('setting fill')
        self.path.setAttribute("fill", "#44dd75")
        print('setting stroke')
        self.path.setAttribute("stroke", "#FF0000")
        print('setting stroke-width')
        self.path.setAttribute("stroke-width", "3")

        self.load(self.doc.toByteArray())

        self.execute_method()


    def execute_method(self):
        print('method executed')

if __name__ == "__main__":  
    app = QApplication(sys.argv)
    svgWidget = svgWidget()    
    svgWidget.show()                                                                                                                                                        
    sys.exit(app.exec_()) 

【问题讨论】:

  • 不,没有那样的东西。如果想使用 SVG,你必须自己从 scatch 实现一切。使用 Qt 的 Graphics View Framework 创建地图会更好。
  • 实际上,您可以从 SVG 文档(使用 QSvgRenderer)中提取单个命名元素,然后独立绘制它们(作为可点击/可选择的项目,可选择在 QGraphicsView 或类似中)。您也不需要手动解析 SVG(作为 XML)。或者,获取每个元素的边界并手动创建 SVG 图形项的“可点击区域”(例如通过监听鼠标事件)。你的 SVG 源是否支持这样的东西(IOW,它有命名元素)吗?
  • 是的,svg 中的每个元素都可以使用 id="elem_name" 。是的,这正是我所希望的,让我能够将元素 ID 分配给鼠标单击和悬停事件。我已经更新了 svg 以反映这一点。

标签: python pyqt pyqt5 qtsvg


【解决方案1】:

我通过我的朋友找到了解决方案。

它是在 Stackoverflow 上找到的(他认为)。

如果有人知道出处,请编辑并添加链接以获得信用。

test.svg

<svg viewBox='0 0 108 95' 
xmlns='http://www.w3.org/2000/svg'>
<g transform='scale(0.1)'>
        <path id="p2" fill='blue' stroke='red' d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
                        2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
                        0-4-1c0-2,0-3-1-4v-3v-3z'/>
    <path id="p3" fill='blue' d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
</g>
</svg>

svg_mouse.py

from PyQt5 import QtWidgets
from PyQt5.QtSvg import QGraphicsSvgItem, QSvgRenderer

class SvgItem(QGraphicsSvgItem):

    def __init__(self, id, renderer, parent=None):
        super().__init__(parent)
        self.id = id
        self.setSharedRenderer(renderer)
        self.setElementId(id)
        bounds = renderer.boundsOnElement(id)
        self.setPos(bounds.topLeft())
        #self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) #horrible selection-box

    def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
        print('svg item: ' + self.id + ' - mousePressEvent()')
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
        print('svg item: ' + self.id + ' - mouseReleaseEvent()')
        super().mouseReleaseEvent(event)

class SvgViewer(QtWidgets.QGraphicsView):
    def __init__(self, parent):
        super().__init__(parent)
        self._scene = QtWidgets.QGraphicsScene(self)
        self._renderer = QSvgRenderer()
        self.setScene(self._scene)

    def set_svg(self, data):
        self.resetTransform()
        self._scene.clear()
        self._renderer.load(data)
        item1 = SvgItem('p2', self._renderer)
        self._scene.addItem(item1)
        item2 = SvgItem('p3', self._renderer)
        self._scene.addItem(item2)

    def mousePressEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
        print('SvgViewer - mousePressEvent()')
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event: 'QtWidgets.QGraphicsSceneMouseEvent'):
        print('SvgViewer - mouseReleaseEvent()')
        super().mouseReleaseEvent(event)

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.viewer = SvgViewer(self)
        vb_layout = QtWidgets.QVBoxLayout(self)
        vb_layout.addWidget(self.viewer)
        img = b'''
                <svg viewBox='0 0 108 95' xmlns='http://www.w3.org/2000/svg'>
                    <g transform='scale(0.1)'>
                        <a href=""><path id="p2" d='M249,699v43h211v-43h-64l-2,3l-2,4l-4,3c0,0-1,2-2,2h-4c-2,0-3,0-4,1c-1,1-3,1-3,
                            2l-3,4c0,1-1,2-2,2h-4c0,0-2,1-3,0l-3-1c-1,0-3-1-3-2c-1-1,0-2-1-3l-1-3c-1-1-2-1-3-1c-1,0-4,
                            0-4-1c0-2,0-3-1-4v-3v-3z'/></a>
                        <path id="p3" d='M385,593c0,9-6,15-13,15c-7,0-13-6-13-15c0-8,12-39,14-39c1,0,12,31,12,39'/>
                    </g>
                </svg>'''
        self.viewer.set_svg(img)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 600, 400)
    window.show()
    sys.exit(app.exec_())

【讨论】:

  • 这看起来不错。如果您不想使用内置选择模式,请在 SvgItem 初始化中保存 bounds 并在鼠标事件处理程序中检查 self.bounds.contains(event.pos())。例如,发出点击信号。请注意,如果您想在鼠标释放时发出,您需要event.accept() 鼠标按下事件才能获得释放一个。并检查每个bounds.contains(event.pos())
猜你喜欢
  • 2016-04-19
  • 2020-04-24
  • 1970-01-01
  • 2011-08-06
  • 2018-08-17
  • 1970-01-01
  • 1970-01-01
  • 2017-04-01
  • 2015-02-08
相关资源
最近更新 更多