[toc]

QSplashScreen

设置图片

Qt内置了用于程序启动的动画直接使用QSplashScreen即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
QPixmap pixmap(":load.gif");     //读取图片
QSplashScreen splash(pixmap); //
splash.setWindowOpacity(0.8); // 设置窗口透明度
splash.show();
splash.showMessage("程序正在加载......", Qt::AlignCenter, Qt::red); //显示文字

// 可选项
#ifdeg DEBUG
QDateTime time = QDateTime::currentDateTime();
QDateTime currentTime = QDateTime::currentDateTime(); //记录当前时间

while (time.secsTo(currentTime) <= 5) // 5为需要延时的秒数
{
currentTime = QDateTime::currentDateTime();
a.processEvents();
};
#endif

widget w;
w.show();

splash.finish(&w); //在主体对象初始化完成后结束启动动画

以上代码放在程序的入口main函数中即可。 widget就是我们需要启动的程序。

自定义启动动画

自定义启动动画的方式网上有几种重写 QSplashScreen 的。笔者也尝试使用了一下,发现并不好用,于是按照一开始的方案,准备用一个 widget 去写启动动画。

参考 QSplashScreen 的方式,还是使用 finish(QWidget *mainWin) 的接口去作为窗体关闭的入口。

finish(Qwidget *mainWin)

关于 finished(QWidget *mainWin) 直接把 QSplashScreen 的源码搬过来即可。

源码一般在你使用的版本的 src 文件夹下:

${install dir}\5.9.9\Src\qtbase\src\widgets\widgets

QSplashScreen 源码中的 finish() 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*!
Makes the splash screen wait until the widget \a mainWin is displayed
before calling close() on itself.
*/
// 这个接口我们需要复制到自定义的启动动画窗体中。
void QSplashScreen::finish(QWidget *mainWin)
{
if (mainWin) {
if (!mainWin->windowHandle())
mainWin->createWinId();
waitForWindowExposed(mainWin->windowHandle());
}
close();
}

上述代码,唯一的问题在于 waitForWindowExposed 这个函数我没有找到实现,也没有仔细找,但是在 Qttestlib 库中有一个类似的接口 QTest::qwaitForWindowExposed() 两个接口的作用应该是一致的。上述代码中 waitForWindowExposed(mainWin->windowHandle()); 经笔者测试哈(不一定准确),即使去掉也不会影响 finish() 接口的作用。^2022年7月27日08:39:40^ 去掉还是会有影响的,相当于判断窗口的逻辑没了。关于 waitForWindowExposed() 在 Qt源码中的实现,笔者在调试程序的时候发现去掉这行代码,怎么看启动动画和运行的程序之间的衔接都不太合理,遂决定找一下这个 waitForWindowExposed() 的源码,这一找,真的是远在天边,近在眼前。函数的实现就在 qsplashscreen.cpp 中。这里贴一下其源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// A copy of Qt Test's qWaitForWindowExposed() and qSleep().
inline static bool waitForWindowExposed(QWindow *window, int timeout = 1000)
{
enum { TimeOutMs = 10 };
QElapsedTimer timer;
timer.start();
while (!window->isExposed()) {
const int remaining = timeout - int(timer.elapsed());
if (remaining <= 0)
break;
QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
#if defined(Q_OS_WINRT)
WaitForSingleObjectEx(GetCurrentThread(), TimeOutMs, false);
#elif defined(Q_OS_WIN)
Sleep(uint(TimeOutMs));
#else
struct timespec ts = { TimeOutMs / 1000, (TimeOutMs % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
#endif
}
return window->isExposed();
}

如果要使用你源码中的实现,则需要引入两个头问题。

1
2
#include <QElapsedTimer>
#include <QtGui/QWindow>

*设置窗体

设置窗体的逻辑也比较简单,就是创建一个widgetDialog专门用来实现启动动画的逻辑。如下所示为Qt程序的入口函数:

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog d; // 启动动画的窗台
d.show();

Widget w;
w.show();

d.finish(&w); // 复制的 QSplashScreen 中的 finished()
return a.exec();
}

其中的 Dialog d 就是我们创建的一个自定义的启动动画窗口,至于窗口内要实现什么,依据你个人的业务和需求去实现即可。

问题1

上文中的 Dialog 还没有主界面就展示了,或者 Dialog 展示了但是没有画面。

关于这个问题,我认为是主界面的刷新太快了(大家可以调试代码去观察一下现象),也就是 finish 相当于是瞬间调用了。导致 Dialog没有展示和来得及刷新 。这个问题网友也给出了方案,让 main 做一些别的操作延时一下。目前笔者用过的最好的不是在 mainsleep 。而是执行以下 processEvent()

具体的代码就是在 Widget::show() 之前调用,如下所示:

1
2
3
4
5
6
7
8
// 这里的时间只是为了做一个判断,只调用一下 a.processEvents(); 应该也是可以的
QDateTime time = QDateTime::currentDateTime();
QDateTime currentTime = QDateTime::currentDateTime(); //记录当前时间
while (time.secsTo(currentTime) <= 5) //5为需要延时的秒数
{
currentTime = QDateTime::currentDateTime();
a.processEvents();
};

问题2

Dialog 结束的时候界面关闭和主界面的展示会中断一下,强迫症难以接受这种突然闪现怎么办?

还是会到 finish()

closed() 的位置 sleep(),让关闭的窗口稍微等一等,等主界面展示出来之后再关闭。1s 的时间足够主界面刷新出来了 。代码如下:

1
2
3
4
5
6
7
8
9
10
void Dialog::finish(QWidget *mainWin) // 这里已经是我们自定义的启动动画窗口 Dialog 了
{
if (mainWin) {
if (!mainWin->windowHandle())
mainWin->createWinId();
waitForWindowExposed(mainWin->windowHandle());
}
QThread::sleep(1); // sleep 的单位是秒
close();
}

完整代码

widget 的代码不展示

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "widget.h"
#include "dialog.h"

#include <QApplication>
#include <QLabel>
#include <QPixmap>
#include <QSplashScreen>
#include <QThread>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

#ifdef QT_DEBUG // 使用 QSplashScreen 的启动动画
QPixmap pixmap(":/test.gif");
QSplashScreen splash(pixmap,10);
splash.show();
QDateTime time = QDateTime::currentDateTime();
QDateTime currentTime = QDateTime::currentDateTime(); //记录当前时间
while (time.secsTo(currentTime) <= 5) //5为需要延时的秒数
{
currentTime = QDateTime::currentDateTime();
a.processEvents();
};
#endif

Dialog d; // 使用自定义的启动动画
d.show();

QDateTime time = QDateTime::currentDateTime();
QDateTime currentTime = QDateTime::currentDateTime(); //记录当前时间
while (time.secsTo(currentTime) <= 5) //5为需要延时的秒数
{
currentTime = QDateTime::currentDateTime();
a.processEvents();
};

Widget w; // 主程序
w.show();

#ifdef QT_DEBUG // 使用 QSplashScreen 的启动动画
splash.finish(&w);
#endif

d.finish(&w);
return a.exec();
}

dialog.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
Q_OBJECT

public:
explicit Dialog(QWidget *parent = nullptr);
~Dialog();

void finish(QWidget *mainWin);
private:
Ui::Dialog *ui;
};

#endif // DIALOG_H

dialog.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint | Qt::Window);
//setAttribute(Qt::WA_TranslucentBackground);
setStyleSheet("#Dialog{"
"border-image: url(:/bgimg_334.png);" // 自己添加一个图片到程序,不添加也不影响程序运行
"background-position: center;"
"backgroun d-repeat: no-repeat;"
"}");
}

Dialog::~Dialog()
{
delete ui;
}

void Dialog::finish(QWidget* mainWin)
{
if (mainWin) {
if (!mainWin->windowHandle())
mainWin->createWinId();
// QTest::qWaitForWindowExposed(mainWin->windowHandle());
}
close();
}

dialog.ui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="page">
<widget class="QWidget" name="widget" native="true">
<property name="geometry">
<rect>
<x>-101</x>
<y>-10</y>
<width>231</width>
<height>51</height>
</rect>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_2"/>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget_2">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page_3"/>
<widget class="QWidget" name="page_4"/>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>