做桌面程序的时候,大家一般都会想在程序启动时怎么可以更友好,特别是python启动慢的时候,一直等着跟个傻子一样。这时候就用到Qt提供的控件QSplashScreen,除了能提升程序响应(避免因加载东西过多导致程序半天起不来),还能显得更专业。
Qt Widgets中的QSplashScreen
,就是专门干这个事情的。在Qt Quick程序中我们同样可以用这个类,方法是先启动一个QSplashScreen
,然后用QQuickView
加载你的主QML文件,加载完毕后,关掉QSplashScreen
。但该方法必须带上Qt5Widgets这个模块,dll个头不小,release版的都要4M多。
那如何用Qt Quick来实现Splash Screen呢?下面就是我们现在用的方案。
第一种方案:在python中定义SplashScreen
我现在是这样的,觉得暂时没有问题,速度上也影响不大,但是不知道是不是真的合理
app = QApplication(sys.argv)
splash = QSplashScreen(QPixmap("logo//logo.jpg?x-oss-process=image/watermark,g_center,image_YXJ0aWNsZS9wdWJsaWMvd2F0ZXJtYXJrLnBuZz94LW9zcy1wcm9jZXNzPWltYWdlL3Jlc2l6ZSxQXzQwCg==,t_20"))
splash.show()
splash.showMessage("正在加载图片资源……", Qt.AlignLeft, Qt.red)
time.sleep(1)
splash.showMessage("正在加载音频资源……", Qt.AlignLeft, Qt.red)
time.sleep(1)
splash.showMessage("正在加载摄像头资源……", Qt.AlignLeft, Qt.red)
time.sleep(1)
engine = QQmlApplicationEngine()
engine.load(os.path.join(os.path.dirname(__file__), "main.qml"))
splash.close()
sys.exit(app.exec_())
第二种方案:
定义Splash Screen
首先当然是定义我们的Splash Screen。现在我们用一个QML文件来实现。创建一个QML文件,取名Splash.qml,派生自Window
,内部内容可以自由发挥,一般主要部件是一个Image
,然后你可以放上Text
等其他控件来显示程序启动进度。
需要注意的是:
- 背景
color
属性要设置为transparent
,这样才能够将图片中的alpha通道的作用体现出来; flags
设置为Qt.SplashScreen | Qt.WindowStaysOnTopHint
,因为Splash Screen一定是显示在程序最前面的;x
和y
要根据当前屏幕尺寸以及我们图片尺寸进行适配计算,好让我们的Splash Screen刚好显示在屏幕正中间;- 别忘了导入
Window
模块,即import QtQuick.Window 2.3
; - 为了更好的体验,一般主程序显示出来后Splash Screen还要待1s,这需要设置一个定时器。
下面就是Splash.qml
import QtQuick 2.7
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
Window {
id: splash
color: "transparent"
title: "Splash Window"
modality: Qt.ApplicationModal
flags: Qt.SplashScreen | Qt.WindowStaysOnTopHint
x: (Screen.width - splashImage.width) / 2
y: (Screen.height - splashImage.height) / 2
width: splashImage.width
height: splashImage.height
property alias timer: timer
Image {
id: splashImage
source: "qrc:/res/splashscreen.png?x-oss-process=image/watermark,g_center,image_YXJ0aWNsZS9wdWJsaWMvd2F0ZXJtYXJrLnBuZz94LW9zcy1wcm9jZXNzPWltYWdlL3Jlc2l6ZSxQXzQwCg==,t_20"
}
Text{
id: textCtrl
width: contentWidth
height: contentHeight
anchors{left: splashImage.left; bottom: splashImage.bottom}
font{pointSize: 30}
}
Timer {
id: timer
interval: 1000; running: false; repeat: false
onTriggered: {
splash.visible = false;
}
}
function delay(){
timer.start();
}
Component.onCompleted: visible = true
}
显示Splash Screen并异步加载主窗体
接下去要显示Splash Screen了。回归初心,我们做这个Splash Screen是为了在主程序加载的时候显示给用户看,让用户知道“稍等,我们的程序已经在拼命启动了”。所以还需要异步加载主窗体。这个通过使用Loader
加载主窗体QML文件,并将它的asynchronous
设置为true
来实现的。main.qml
示例代码如下:
import QtQuick 2.7
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
QtObject {
id: root
property QtObject splashScreen: Splash{}
property var loader: Loader{
asynchronous: true
source: "qrc:/MainView.qml"
active: false
onLoaded: {
splashScreen.delay();
}
}
Component.onCompleted:{
loader.active = true;
}
}
注意,加载器Loader
在定义时active
属性要设置为false
,表示我们不希望它一开始就自动加载。我们希望Splash Screen以最快的速度显示,而Loader
同步加载的话肯定会对此有影响。
Component.onCompleted
会在我们的root
对象构造完成时调用,此时根据Qt QML的文档,root
的所有属性都应该构造完毕,包括我们的Splash Screen$splashScreen
和加载器loader
,说明此时Splash Screen已经显示出来了,然后我们将loader
的active
属性设为true
,OK,真正开始主程序的异步加载。
当加载完毕后,Loader
会发送loaded
信号。我们在响应函数onLoaded
中调用$splashScreen.delay()
即可。
主窗体
主窗可以是ApplicationWindow
或者Window
:
import QtQuick 2.6
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
visible: true
width: 800
height: 600
title: qsTr("Splash Demo")
flags: Qt.Window | Qt.FramelessWindowHint
Button{
anchors{top: parent.top; right: parent.right;margins: 5}
text: "X"
width: 50
height: 50
onClicked: Qt.quit();
}
Text{
text: qsTr("Test window");
anchors.centerIn: parent
font.pointSize: 30
}
Component.onCompleted: window.show()
}
注意两点:
flags
属性要带上Qt.FramelessWindowHint
,否则会发现启动的时候,主窗体会先显示在Splash Screen之前、再退到Splash Screen之后的问题;Component.onCompleted
里手动调用show()
显示出来,否则在某些平台上会有主窗体不显示的问题。
加载起始QML文件
最后一步,其实是最开始的时候,就是在main
函数中加载我们的main.qml
。主要代码如下:
QGuiApplication app(argc, argv);
auto engine = new QQmlApplicationEngine();
engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
用QGuiApplication
意味着我们发布时只需要带上Qt5Core和Qt5Gui就可以了,不需要Qt5Widgets。
问题
目前的实现也有些问题。只有当主窗体为Frameless
时才能保证Splash Screen一直在程序最前面,否则会短暂地被主窗体抢前。但无边框的窗体就没有了各操作系统基于窗体的边框效果,例如阴影、Aero等。网上有针对该问题的解决方法,但我没有尝试。