# 样板应用
理解Qt最好方法是从一个小的示例开始。 此应用程序创建一个简单的“Hello World!”
字符串,并使用Unicode字符将其写入文件。
#include <QCoreApplication>
#include <QString>
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 准备消息
QString message("Hello World!");
// 准备一个在用户家路径下名为out.txt的文件
QFile file(QDir::home().absoluteFilePath("out.txt"));
// 尝试以写模式打开文件
if(!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can not open file with write access";
return -1;
}
// 需要使用合适的文本编码处理文本
QTextStream stream(&file);
// 通过文本流将消息写入文件
stream << message;
// 不要启动事件循环,因为这会等待额外的输入输出
// app.exec();
// 无需关闭文件,作用域结束时会自动关闭
return 0;
}
该示例演示了文件访问的使用,以及如何使用文本编解码器通过文本流将文本写入文件。对于二进制数据,有一个名为QDataStream
的跨平台二进制流负责处理字节顺序和其他细节。使用的不同类需在文件顶部使用它们的类名将它们包含在内,还可以使用模块加上类名来包含类,例如#include <QtCore/QFile>
。对于懒惰的人,还可以使用#include <QtCore>
包含模块中的所有类。例如,在QtCore
中,可以使用与UI无关的应用程序最常用的类。可以查看QtCore类列表(QtCore class list) (opens new window)或QtCore 概述(QtCore overview) (opens new window)。
使用CMake和make构建应用程序。 CMake读取项目文件CMakeLists.txt
并生成用于构建应用程序的Makefile。 CMake也支持其他构建系统,例如ninja。项目文件与平台无关,CMake有一些规则可以将特定平台的设置应用于生成的makefile。该项目还可以包含平台特定规则的平台范围,这在某些特定情况下是必需的。
这是一个由Qt Creator生成的简单项目文件的示例。请注意,Qt尝试创建一个与Qt 5、Qt 6以及各种平台(如 Android、OS X等)兼容的文件。
cmake_minimum_required(VERSION 3.14)
project(projectname VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# QtCreator supports the following variables for Android, which are identical to qmake Android variables.
# Check https://doc.qt.io/qt/deployment-android.html for more information.
# They need to be set before the find_package(...) calls below.
#if(ANDROID)
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
# if (ANDROID_ABI STREQUAL "armeabi-v7a")
# set(ANDROID_EXTRA_LIBS
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so)
# endif()
#endif()
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick REQUIRED)
set(PROJECT_SOURCES
main.cpp
qml.qrc
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(projectname
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
else()
if(ANDROID)
add_library(projectname SHARED
${PROJECT_SOURCES}
)
else()
add_executable(projectname
${PROJECT_SOURCES}
)
endif()
endif()
target_compile_definitions(projectname
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
target_link_libraries(projectname
PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick)
set_target_properties(projectname PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_import_qml_plugins(projectname)
qt_finalize_executable(projectname)
endif()
这里不会深入探讨这个文件。 请记住Qt使用CMake的 CMakeLists.txt
文件来生成特定于平台的makefile,然后用于构建项目。 在构建系统部分,将了解更基本的手写的CMake文件。
上面的简单代码示例只是写入文本并退出应用程序。 使用命令行工具已经足够了。 对于用户界面,需要一个事件循环来等待用户输入并以某种方式安排做出操作。 下面是与上面相同的例子,现在使用一个按钮来触发写入。
main.cpp
文件出人意料地变小了。 将代码移动到一个自定义的类中,以便能够使用Qt的信号和槽用于应用户输入,即处理按钮单击。 信号和槽机制通常需要一个对象实例,很快就会看到,但它也可以与C++ lambda表达式一起使用。
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
#include "mainwindow.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MainWindow win;
win.resize(320, 240);
win.setVisible(true);
return app.exec();
}
在main
函数中,创建了应用程序对象,一个窗口,然后使用exec()
来启动事件循环。 目前,应用程序位于事件循环中并等待用户输入。
int main(int argc, char** argv)
{
QApplication app(argc, argv); // init application
// create the ui
return app.exec(); // execute event loop
}
使用Qt,可以在QML和Widgets中构建用户界面。 在本书中,关注QML;但在本章中,将关注Widgets,这样就只创建C++程序。
主窗口本身就是一个窗口小部件。 因为它没有任何父窗口,它成为了一个顶级窗口。 这来自于Qt如何将用户界面视为UI元素树。 在这种情况下,主窗口是根元素,因此成为窗口,而作为主窗口的子窗口的按钮成为窗口内的小部件。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets>
class MainWindow : public QMainWindow
{
public:
MainWindow(QWidget* parent=0);
~MainWindow();
public slots:
void storeContent();
private:
QPushButton *m_button;
};
#endif // MAINWINDOW_H
此外,在头文件的自定义部分中定义了一个名为storeContent()
的public的槽。 槽可以是public的、protected的或private的,并且可以像任何其他类方法一样被调用。 可能还会遇到带有一组信号签名的signals
部分。 这些方法不应该被调用,也不能被实现。 信号和槽都由Qt元信息系统处理,并且可以在运行时进行内省和动态调用。
槽storeContent()
的用途是在按钮被单击时调用它。 继续完成这一点。
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_button = new QPushButton("Store Content", this);
setCentralWidget(m_button);
connect(m_button, &QPushButton::clicked, this, &MainWindow::storeContent);
}
MainWindow::~MainWindow()
{
}
void MainWindow::storeContent()
{
qDebug() << "... store content";
QString message("Hello World!");
QFile file(QDir::home().absoluteFilePath("out.txt"));
if(!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can not open file with write access";
return;
}
QTextStream stream(&file);
stream << message;
}
在主窗口中,首先创建按钮,然后使用connect方法注册信号clicked()
与槽storeContent()
。 每当发出clicked
信号时,都会调用槽storeContent()
。 现在,这两个对象可以通过信号和槽进行通信,尽管彼此不了解彼此。 将这称为松散耦合,以QObject
为基类的大多数Qt派生类都可以实现这样的功能。