如何更改 QQuickView 的来源

Posted

技术标签:

【中文标题】如何更改 QQuickView 的来源【英文标题】:How to change source of QQuickView 【发布时间】:2015-01-15 19:49:01 【问题描述】:

我正在做游戏,它是用 qml 和 pyqt 编写的,但应该分为两个窗口(启动器 + 游戏)。在这两个 qml 文件之间切换的正确方法是什么?我不想使用 QmlLoader,因为它不会调整窗口大小而且它需要很多信号!我也在尝试这个变种:

view.engine().clearComponentCache()
view.setSource(source())

但它不起作用(qml 窗口停止工作...-经典窗口错误,但是在 pycharm 控制台中没有写入错误)

我的代码如下所示:

from PyQt5.QtCore import pyqtProperty, QRectF, QUrl, QObject, pyqtSignal, pyqtSlot, QVariant
from PyQt5.QtGui import QColor, QGuiApplication, QPainter, QPen
from PyQt5.QtQml import qmlRegisterType
from PyQt5.QtQuick import QQuickItem, QQuickPaintedItem, QQuickView
from PyQt5 import QtNetwork as QN
from PyQt5 import QtCore as QC

from multiprocessing import Process
import server as S
import client as C
from time import time, sleep


class Launcher(QQuickItem):
    PORTS = (9998, 9999)
    PORT = 9999
    SIZEOF_UINT32 = 4

    changeUI = pyqtSignal()
    changeName= pyqtSignal(int, str)
    connection= pyqtSignal(int, bool)
    connected= pyqtSignal(QVariant)


    @pyqtSlot(name="startGame")
    def start_game(self):
        #app.exit()
        startGame()
        print ("startGame")

    @pyqtProperty(str)
    def name(self):
        print ("return name")
        return self._name

    @name.setter
    def name(self, n):
        print ("set name")
        self._name= n


    @pyqtSlot(name= "terminate")
    def terminate_server(self):
        if not self.server: return
        self.server.terminate()     #Bye
        self.server.join()
        #self.bstopServer.setEnabled(False)
        #self.bserver.setEnabled(True)
        self.server = None

    @pyqtSlot()
    def _quit(self):
        if self.server:
            self.server.terminate()     #Bye
            self.server.join()
        app.exit()

    @pyqtSlot()
    def back(self):
        self.client.disconnect()


    def __init__(self, parent=None):
        super(Launcher, self).__init__(parent)

        self.socket= QN.QTcpSocket()        #Yeah, the game will be over internet
        self.server = None
        self.client = None                  #client is a QObject

        self._turnedOn = False
        self._players = 1
        self._name = "YourName"


class Game(QQuickItem):
    def __init__(self, parent= None):
        super(Game, self).__init__(parent)

        self.client = True      #I should get the client from the launcher, but I don´t know how


def startGame():
    view.engine().clearComponentCache()
    view.setResizeMode(QQuickView.SizeViewToRootObject)
    view.showFullScreen()
    view.setSource(
            QUrl.fromLocalFile(
                    os.path.join(os.path.dirname(__file__),'Game.qml')))
    view.show()
    #app.exec_()

def startLauncher():
    view.engine().clearComponentCache()
    view.setResizeMode(QQuickView.SizeViewToRootObject)
    view.setSource(
            QUrl.fromLocalFile(
                    os.path.join(os.path.dirname(__file__),'Launcher.qml')))


    view.show()

    app.exec_()

if __name__ == '__main__':
    import os
    import sys

    app = QGuiApplication(sys.argv)

    qmlRegisterType(Launcher, "ParanoiaLauncher", 1, 0, "App")
    qmlRegisterType(Game, "ParanoiaGame", 1, 0, "App")

    view = QQuickView()

    startLauncher()

如你所见,我的结构有点混乱,因为我是第一次做这种切换行为,所以我真的不知道应该如何正确地做...欢迎大家提出建议! :)

【问题讨论】:

【参考方案1】:

我不得不在同一时间面临同样的问题。而是一次又一次地使用相同的QQuickView 加载相同的组件,我在 QML 中创建了一个组件作为内容容器,并将所需的组件加载为它的子组件。每次设置新组件时,我们都会销毁当前组件并再次将新组件设置为子组件。

// ContentFrame.qml
Item
    width: 800
    height: 600
    Item
        id: contentContainer
        anchors.fill: parent
        anchors.margins: 4
    

首先,请原谅我,但功能代码是用 C++ 编写的,但我认为这个概念是可以理解的。我做了一个简化版的流程,希望可以移植到python。

为了加载 ContentFrame 组件,我使用了一个派生自 QQuickView (ViewManager) 的类,该类有一个名为 setView 的方法。此方法加载一个组件(在您的情况下为 Launcher 或 Game),将其设置为 contentContainer 的子级,并将其 anchor.fill 设置为填充整个父级。

ViewManager::ViewManager() :  QQuickView("ContentFrame.qml")

    // More ctor code


 // Other stuff

void ViewManager::setView(const QString &filename)

    QQuickItem *mostTopItem = rootObject(); //QQuickView::rootObject() method
    QQuickItem *contentItem->findChild<QQuickItem*>("contentContainer");
    if(m_current != NULL)
    
        m_current->setProperty("visible", false);
    

    // Call a method to load the component and create an instance of it
    QQuickItem *newItem = createOurComponentFromFile(filename);

    newItem->setProperty("parent", QVariant::fromValue<QObject*>(contentItem));

    // When "delete item" in C++, the object will be destroyed in QQmlEngine
    // in pyqt???
    QQmlEngine::setObjectOwnership(newItem, QQmlEngine::CppOwnership);

    newItem->setProperty("anchors.fill", QVariant::fromValue<QObject*>(contentItem));

    // Cleanup current object
    if(m_current != NULL)
    
        delete m_current;
    
    m_current = newItem;

还有更多的代码,但是 ViewManager 的核心是这个方法。我不在这里调用QQmlEngine::clearComponentCache(),因为我不止一次加载相同的组件,但在你的情况下,这可能是个好主意。

【讨论】:

谢谢你,对我帮助很大。

以上是关于如何更改 QQuickView 的来源的主要内容,如果未能解决你的问题,请参考以下文章

如何将 qml 文件重新加载到 QQuickView

退出时删除 QQuickView 会导致 Qt 应用程序冻结

QQuickView 新删除类型不匹配

避免 QQuickView 延迟 qml 加载

带有 QQuickView 的 QML 信号 QT 插槽

Qt:隐藏然后显示 QQuickView 防止未来的鼠标事件