【问题标题】:pyqt5 children access with qml filepyqt5 孩子使用 qml 文件访问
【发布时间】:2018-11-18 19:32:53
【问题描述】:

我想运行 qt QSyntaxHighlight 示例,但使用 QML 构建窗口,而不是使用 python 代码。虽然我仍然设法这样做,但代码很尴尬,我相信我的 QML 或 API 理解是错误的。

我使用名为 main.qml 的 TextEdits 创建了简单的 QML 文件

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello")

    TextEdit {
        id: text_view
        objectName: "text_view"
        x: 0
        y: 0
        width: parent.width
        height: parent.height * 0.8
        color: "black"
        text: qsTr("long class")
        font.pixelSize: 12
        anchors.margins: 5
    }

    TextEdit {
        id: text_input
        anchors.top: text_view.bottom
        width: parent.width
        height: parent.height - text_view.height
        color: "#ef1d1d"
        text: qsTr("")
        font.capitalization: Font.MixedCase
        font.pixelSize: 12
    }
}

然后添加main.py

#!/bin/python3

import sys

from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QGuiApplication, QSyntaxHighlighter, QTextDocument
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType


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

        keywordFormat = QtGui.QTextCharFormat()
        keywordFormat.setForeground(QtCore.Qt.darkBlue)
        keywordFormat.setFontWeight(QtGui.QFont.Bold)

        keywordPatterns = ["\\bchar\\b", "\\bclass\\b", "\\bconst\\b",
                "\\bdouble\\b", "\\benum\\b", "\\bexplicit\\b", "\\bfriend\\b",
                "\\binline\\b", "\\bint\\b", "\\blong\\b", "\\bnamespace\\b",
                "\\boperator\\b", "\\bprivate\\b", "\\bprotected\\b",
                "\\bpublic\\b", "\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b",
                "\\bslots\\b", "\\bstatic\\b", "\\bstruct\\b",
                "\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b",
                "\\bunion\\b", "\\bunsigned\\b", "\\bvirtual\\b", "\\bvoid\\b",
                "\\bvolatile\\b"]

        self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat)
                for pattern in keywordPatterns]

        classFormat = QtGui.QTextCharFormat()
        classFormat.setFontWeight(QtGui.QFont.Bold)
        classFormat.setForeground(QtCore.Qt.darkMagenta)
        self.highlightingRules.append((QtCore.QRegExp("\\bQ[A-Za-z]+\\b"),
                classFormat))

        singleLineCommentFormat = QtGui.QTextCharFormat()
        singleLineCommentFormat.setForeground(QtCore.Qt.red)
        self.highlightingRules.append((QtCore.QRegExp("//[^\n]*"),
                singleLineCommentFormat))

        self.multiLineCommentFormat = QtGui.QTextCharFormat()
        self.multiLineCommentFormat.setForeground(QtCore.Qt.red)

        quotationFormat = QtGui.QTextCharFormat()
        quotationFormat.setForeground(QtCore.Qt.darkGreen)
        self.highlightingRules.append((QtCore.QRegExp("\".*\""), quotationFormat))

        functionFormat = QtGui.QTextCharFormat()
        functionFormat.setFontItalic(True)
        functionFormat.setForeground(QtCore.Qt.blue)
        self.highlightingRules.append((QtCore.QRegExp("\\b[A-Za-z0-9_]+(?=\\()"), functionFormat))

        self.commentStartExpression = QtCore.QRegExp("/\\*")
        self.commentEndExpression = QtCore.QRegExp("\\*/")

    def highlightBlock(self, text):
        for pattern, format in self.highlightingRules:
            expression = QtCore.QRegExp(pattern)
            index = expression.indexIn(text)
            while index >= 0:
                length = expression.matchedLength()
                self.setFormat(index, length, format)
                index = expression.indexIn(text, index + length)

        self.setCurrentBlockState(0)

        startIndex = 0
        if self.previousBlockState() != 1:
            startIndex = self.commentStartExpression.indexIn(text)

        while startIndex >= 0:
            endIndex = self.commentEndExpression.indexIn(text, startIndex)

            if endIndex == -1:
                self.setCurrentBlockState(1)
                commentLength = len(text) - startIndex
            else:
                commentLength = endIndex - startIndex + self.commentEndExpression.matchedLength()

            self.setFormat(startIndex, commentLength, self.multiLineCommentFormat)
            startIndex = self.commentStartExpression.indexIn(text, startIndex + commentLength)


def print_child(el):
    print(" " * 2 * print_child.max + "type: {}".format(type(el)))
    print(" " * 2 * print_child.max + "name: {}".format(el.objectName()))
    print_child.max += 1
    try:
        for subel in el.children():
            print_child(subel)
    except TypeError:
        pass
    print_child.max -= 1
print_child.max = 0


def child_selector(children, ftype):
    for ch in children:
        if type(ch) is ftype:
            return ch
    return None

if __name__ in "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    engine.load("main.qml")
    print_child(engine.rootObjects()[0])
    el = Highlighter(
                child_selector(engine.rootObjects()[0].findChild(QObject, "text_view").children(), QTextDocument)
            )
    engine.quit.connect(app.quit)
    sys.exit(app.exec_())

Print child 产生这个输出:

type: <class 'PyQt5.QtGui.QWindow'>
name: 
  type: <class 'PyQt5.QtCore.QObject'>
  name: 
  type: <class 'PyQt5.QtCore.QObject'>
  name: text_view
    type: <class 'PyQt5.QtGui.QTextDocument'>
    name: 
      type: <class 'PyQt5.QtGui.QAbstractTextDocumentLayout'>
      name: 
        type: <class 'PyQt5.QtCore.QObject'>
        name: 
      type: <class 'PyQt5.QtGui.QTextFrame'>
      name: 
    type: <class 'PyQt5.QtCore.QObject'>
    name: 
  type: <class 'PyQt5.QtCore.QObject'>
  name: 
    type: <class 'PyQt5.QtGui.QTextDocument'>
    name: 
      type: <class 'PyQt5.QtGui.QAbstractTextDocumentLayout'>
      name: 
        type: <class 'PyQt5.QtCore.QObject'>
        name: 
      type: <class 'PyQt5.QtGui.QTextFrame'>
      name: 
    type: <class 'PyQt5.QtCore.QObject'>
    name: 

问题:

  • 我已经定义了带有两个 TextEdits 的 Window,而我看到我的 TextEdits 嵌入在额外的 QObject 中(这就是为什么我必须添加 child_selector(...) 并且不能直接使用 findChild 输出)为什么会这样还能做得更好吗?

  • 是否有一些功能与findChild(...) 相同但使用 id 而不是 objectName?

  • engine 上使用 findChildren(...) 而没有 rootObject() 导致 None 我不明白为什么?

【问题讨论】:

    标签: python qml pyqt5


    【解决方案1】:

    - 我已经定义了 Window 里面有两个 TextEdits,而我看到我的 TextEdits 嵌入在额外的 QObject 中(这就是为什么我必须添加 child_selector(...) 并且不能直接使用 findChild 输出) 为什么会这样,能不能做得更好

    是的,你可以使用 findChild,在下面的部分我会告诉你解决方案:

    findChild 有选择器:type 和 objectName,在 textDocument 的情况下,所以作为第一个测试它可以按类型过滤,我将使用 findChildren,因为每个 TextEdit 都有一个 textDocument:

    root = engine.rootObjects()[0]
    docs = root.findChildren(QtGui.QTextDocument)
    print(docs)
    for doc in docs:
        el = Highlighter(doc)
    

    输出:

    [<PyQt5.QtGui.QTextDocument object at 0x7f5703eb4af8>, <PyQt5.QtGui.QTextDocument object at 0x7f5703eb4b88>]
    

    通过应用 objectname 的过滤器,我们可以过滤第一个 TextEdit 的 textDocument:

    root = engine.rootObjects()[0]
    text_view = root.findChild(QtCore.QObject, "text_view")
    doc = text_view.findChild(QtGui.QTextDocument)
    el = Highlighter(doc)
    

    - 是否有一些与 findChild(...) 相同但使用 id 而不是 objectName 的功能?

    id 只在 QML 中有意义,所以你不能在 python 中使用它,同样 findChild() 和 findChildren() 只使用对象名和类型。

    id是一个依赖于作用域的标识符,例如:

    MyItem.qml

    Item{
        id: root
        // others code
    }
    

    ma​​in.qml

    Window{
        MyItem{
            id: it1
        }
        MyItem{
            id: it2
        }
    }
    

    如您所见,根 id 只会识别 MyItem.qml 中的项目,在它之外没有任何意义,从概念上讲,它就像 c++ 中的 this 或 python 中的 self,因为这些属性是有意义的关于范围。


    - 在没有 rootObject() 的引擎上使用 findChildren(...) 导致 None 我不明白为什么?

    QmlEngine 是一个允许创建组件的类,它没有层次关系,因此 QML 中层次结构树的任何元素都没有像引擎一样的父亲,因此它返回 None。


    我会回答在 cmets 中提出的问题,因为答案值得。

    QTextDocument 继承自 QObject,所以它有 objectName,所以 QTextDocument 是 TextEdit 的一个属性,是否可以在 QML 中以某种方式设置 objectName?

    不幸的是,不能将 QML 中的 objectName 放置到 QTextDocument 中,因为在 QML 中 QTextDocument 不可访问。如果我们查看文档:

    textDocumentTextEdit 的属性,它是QQuickTextDocument,并且QQuickTextDocument 有一个方法调用textDocument(),它只返回一个QTextDocument,但该方法不是Q_INVOKABLE,也不是一个SLOT.

    更接近您想要的是将objectName设置为textDocument,然后使用textDocument()方法获取QTextDocument(可能会因为名称相似而混淆),设置objectName我们将使用 Component.OnCompleted 信号:

    ma​​in.qml

    import QtQuick 2.9
    import QtQuick.Window 2.2
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello")
    
        TextEdit {
            id: text_view
            width: parent.width
            height: parent.height * 0.8
            color: "black"
            text: qsTr("long class")
            font.pixelSize: 12
            anchors.margins: 5
            Component.onCompleted: text_view.textDocument.objectName = "textDocument1"
        }
    
        TextEdit {
            id: text_input
            anchors.top: text_view.bottom
            width: parent.width
            height: parent.height - text_view.height
            color: "#ef1d1d"
            text: qsTr("")
            font.capitalization: Font.MixedCase
            font.pixelSize: 12
            Component.onCompleted: text_input.textDocument.objectName = "textDocument2"
        }
    }
    

    *.py

    # ...
    
    if __name__ in "__main__":
        app = QtGui.QGuiApplication(sys.argv)
    
        engine = QtQml.QQmlApplicationEngine()
        engine.load("main.qml")
        if not engine.rootObjects():
            sys.exit(-1)
        root = engine.rootObjects()[0]
    
        q_text_document1 = root.findChild(QtQuick.QQuickTextDocument, "textDocument1")
        h1 = Highlighter(q_text_document1.textDocument())
    
        q_text_document2 = root.findChild(QtQuick.QQuickTextDocument, "textDocument2")
        h2 = Highlighter(q_text_document2.textDocument())
    
        engine.quit.connect(app.quit)
        sys.exit(app.exec_())
    

    虽然我个人不希望通过 objectName 将 QML 中创建的元素公开给 python/C++,因为 QML 处理其生命周期而不是 python/C++。在这种情况下,我更喜欢将类导出为新项目,并创建一个插槽来传递QQuickTextDocument 的属性:

    ma​​in.py

    #!/bin/python3
    
    import sys
    
    from PyQt5 import QtCore, QtGui, QtQml, QtQuick
    
    class Highlighter(QtGui.QSyntaxHighlighter):
        def __init__(self, parent=None):
            super(Highlighter, self).__init__(parent)
    
            keywordFormat = QtGui.QTextCharFormat()
            keywordFormat.setForeground(QtCore.Qt.darkBlue)
            keywordFormat.setFontWeight(QtGui.QFont.Bold)
    
            keywordPatterns = ["\\bchar\\b", "\\bclass\\b", "\\bconst\\b",
                    "\\bdouble\\b", "\\benum\\b", "\\bexplicit\\b", "\\bfriend\\b",
                    "\\binline\\b", "\\bint\\b", "\\blong\\b", "\\bnamespace\\b",
                    "\\boperator\\b", "\\bprivate\\b", "\\bprotected\\b",
                    "\\bpublic\\b", "\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b",
                    "\\bslots\\b", "\\bstatic\\b", "\\bstruct\\b",
                    "\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b",
                    "\\bunion\\b", "\\bunsigned\\b", "\\bvirtual\\b", "\\bvoid\\b",
                    "\\bvolatile\\b"]
    
            self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat)
                    for pattern in keywordPatterns]
    
            classFormat = QtGui.QTextCharFormat()
            classFormat.setFontWeight(QtGui.QFont.Bold)
            classFormat.setForeground(QtCore.Qt.darkMagenta)
            self.highlightingRules.append((QtCore.QRegExp("\\bQ[A-Za-z]+\\b"),
                    classFormat))
    
            singleLineCommentFormat = QtGui.QTextCharFormat()
            singleLineCommentFormat.setForeground(QtCore.Qt.red)
            self.highlightingRules.append((QtCore.QRegExp("//[^\n]*"),
                    singleLineCommentFormat))
    
            self.multiLineCommentFormat = QtGui.QTextCharFormat()
            self.multiLineCommentFormat.setForeground(QtCore.Qt.red)
    
            quotationFormat = QtGui.QTextCharFormat()
            quotationFormat.setForeground(QtCore.Qt.darkGreen)
            self.highlightingRules.append((QtCore.QRegExp("\".*\""), quotationFormat))
    
            functionFormat = QtGui.QTextCharFormat()
            functionFormat.setFontItalic(True)
            functionFormat.setForeground(QtCore.Qt.blue)
            self.highlightingRules.append((QtCore.QRegExp("\\b[A-Za-z0-9_]+(?=\\()"), functionFormat))
    
            self.commentStartExpression = QtCore.QRegExp("/\\*")
            self.commentEndExpression = QtCore.QRegExp("\\*/")
    
        def highlightBlock(self, text):
            for pattern, format in self.highlightingRules:
                expression = QtCore.QRegExp(pattern)
                index = expression.indexIn(text)
                while index >= 0:
                    length = expression.matchedLength()
                    self.setFormat(index, length, format)
                    index = expression.indexIn(text, index + length)
    
            self.setCurrentBlockState(0)
    
            startIndex = 0
            if self.previousBlockState() != 1:
                startIndex = self.commentStartExpression.indexIn(text)
    
            while startIndex >= 0:
                endIndex = self.commentEndExpression.indexIn(text, startIndex)
    
                if endIndex == -1:
                    self.setCurrentBlockState(1)
                    commentLength = len(text) - startIndex
                else:
                    commentLength = endIndex - startIndex + self.commentEndExpression.matchedLength()
    
                self.setFormat(startIndex, commentLength, self.multiLineCommentFormat)
                startIndex = self.commentStartExpression.indexIn(text, startIndex + commentLength)
    
        @QtCore.pyqtSlot(QtQuick.QQuickTextDocument)
        def setQQuickTextDocument(self, q):
            if isinstance(q, QtQuick.QQuickTextDocument):
                self.setDocument(q.textDocument())
    
    
    if __name__ in "__main__":
        app = QtGui.QGuiApplication(sys.argv)
        QtQml.qmlRegisterType(Highlighter, "Foo", 1, 0, "Highlighter")
        engine = QtQml.QQmlApplicationEngine()
        engine.load("main.qml")
        if not engine.rootObjects():
            sys.exit(-1)
        root = engine.rootObjects()[0]
        engine.quit.connect(app.quit)
        sys.exit(app.exec_())
    

    ma​​in.qml

    import QtQuick 2.9
    import QtQuick.Window 2.2
    import Foo 1.0
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello")
    
        TextEdit {
            id: text_view
            width: parent.width
            height: parent.height * 0.8
            color: "black"
            text: qsTr("long class")
            font.pixelSize: 12
            anchors.margins: 5
        }
    
        Highlighter{
            id: h1
            Component.onCompleted: h1.setQQuickTextDocument(text_view.textDocument)
        }
    
        TextEdit {
            id: text_input
            anchors.top: text_view.bottom
            width: parent.width
            height: parent.height - text_view.height
            color: "#ef1d1d"
            text: qsTr("")
            font.capitalization: Font.MixedCase
            font.pixelSize: 12
        }
    
        Highlighter{
            id: h2
            Component.onCompleted: h2.setQQuickTextDocument(text_input.textDocument)
        }
    
    }
    

    使用最后一种方法,对象的生命周期由 QML 处理,使用初始方法 QML 可以消除某些项目,python/C++ 不会知道它,因为没有人通知它。

    【讨论】:

    • 1:我实际上使用 findChild 显示的方式,所以没有更好/更清洁的方式来按 objectName 选择 QTextDocument?我认为 TextEdit 正在一对一地翻译为 QTextDocument,但正如我所见,它是 QObject,里面有 QTextDocument?更准确地说,我想调用一次 findChild 来按 objectName 获取 QTextDocument。 2:我不明白为什么 ID 在 QML 之外没有意义 :) 3:至于 QmlEngine - 那么为什么它有 findChildren(...) 可用?出于什么目的?它可能没有层次关系,jet 因为它可以给 rootObjects() 它有点启动树。
    • 我提出这个问题是为了获得尽可能清晰的观点,以便更好地理解“为什么”寻找我可以在文档中找到的更多信息。更深入的解释将不胜感激。
    • @pholat 1. QTextDocument 没有objectName 所以你不能直接访问它,你必须通过TextEdit 访问它。 2. ID 仅对 QML 有意义,它是由设计制作的,因为在声明性语言中,习惯上通过 id 来标识每个元素。 3. QmlEngine 是一个 QObject,所以它会有 findChildren 方法,因为 QmlEngine 可以有孩子,但是当一个窗口被构建时,它有一个根元素,并且该窗口内的项目是该根的孩子。
    • @pholat 您还有其他问题吗?
    • @pholat 总之: 1. QTextDocument 无法访问,因为它没有objectName。 2. ID by design 仅在 QML 中有意义。 3.每个QObject都可以有children,所以QmlEngine也可以有children,但这并不意味着findchildren的目的是获取item,目的只是为了获取children,那个feature就是用来获取items的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-05
    • 2018-09-15
    相关资源
    最近更新 更多