The best way to understand Qt is to start from a small example. This application creates a simple "Hello World!"
string and writes it into a file using Unicode characters.
理解Qt最好的方法是从一个小例子开始。这个应用程序创建了一个简单的“Hello World!”字符串,并使用Unicode字符将其写入文件。
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// prepare the message
QString message("Hello World!");
// prepare a file in the users home directory named out.txt
QFile file(QDir::home().absoluteFilePath("out.txt"));
// try to open the file in write mode
if(!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can not open file with write access";
return -1;
}
// as we handle text we need to use proper text codecs
QTextStream stream(&file);
// write message to file via the text stream
stream << message;
// do not start the eventloop as this would wait for external IO
// app.exec();
// no need to close file, closes automatically when scope ends
return 0;
}
The example demonstrates the use of file access and the how to write text to a a file using text codecs using a text stream. For binary data, there is a cross-platform binary stream called QDataStream
that takes care of endianess and other details. The different classes we use are included using their class name at the top of the file. You can also include classes using the module and class name e.g. #include
. For the lazy, there is also the possibility to include all the clases from a module using #include
. For instance, in QtCore
you have the most common classes used for an application that are not UI related. Have a look at the QtCore class list or the QtCore overview.
该示例演示了如何使用文件访问,以及如何使用文本流使用文本编解码器将文本写入文件。对于二进制数据,有一个称为QDataStream的跨平台二进制流,负责存储和其他细节。我们使用的不同类包含在文件的顶部,并使用它们的类名。还可以使用模块和类名包含类,例如#include
You build the application using CMake and make. CMake reads a project file, CMakeLists.txt
and generates a Makefile which is used to build the application. CMake supports other build systems too, for example ninja. The project file is platform independent and CMake has some rules to apply the platform specific settings to the generated makefile. The project can also contain platform scopes for platform-specific rules, which are required in some specific cases.
您可以使用CMake和make构建应用程序。CMake读取一个项目文件,CMakeLists.txt
并生成用于构建应用程序的Makefile。CMake也支持其他构建系统,例如ninja。项目文件是独立于平台的,CMake有一些规则将特定于平台的设置应用于生成的makefile。该项目还可以包含特定于平台的规则的作用域,在某些特定情况下需要这些规则。
Here is an example of a simple project file generated by Qt Creator. Notice that Qt attempts to create a file that is compatible with both Qt 5 and Qt 6, as well as various platforms such as Android, OS X and such.
下面是一个由Qt Creator生成的简单项目文件的示例。请注意,Qt试图创建一个与Qt5和Qt6以及各种平台(如Android、OSX等)兼容的文件。
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 $<$,$>: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()
We will not go into the depths of this file. Just remember Qt uses CMake's CMakeLists.txt
files to generate platform-specific makefiles, that are then used to build a project. In the build system section we will have a look at more basic, handwritten, CMake files.
我们不会深入讨论这个文件。记住Qt使用了CMake的CMakeLists.txt
文件生成特定于平台的makefiles,然后用于构建项目。在构建系统部分,我们将看到更基本的、手写的CMake文件。
The simple code example above just writes the text and exits the application. For a command line tool, this is good enough. For a user interface you need an event loop which waits for user input and somehow schedules draw operations. Here follows the same example now uses a button to trigger the writing.
上面的简单代码示例只编写文本并退出应用程序。对于命令行工具来说,这已经足够好了。对于用户界面,您需要一个事件循环,它等待用户输入并以某种方式安排绘制操作。下面是同样的例子,现在使用一个按钮来触发写入。
Our main.cpp
surprisingly got smaller. We moved code into an own class to be able to use Qt's signal and slots for the user input, i.e. to handle the button click. The signal and slot mechanism normally needs an object instance as you will see shortly, but it can also be used with C++ lambdas.
我们的main.cpp
出人意料地变小了。我们将代码移动到自己的类中,以便能够使用Qt的信号和槽进行用户输入,即处理按钮点击。信号和槽机制通常需要一个对象实例,如您将很快看到的,但它也可以与C++ lambdas表达式一起使用。
#include
#include
#include
#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();
}
In the main
function we create the application object, a window, and then start the event loop using exec()
. For now, the application sits in the event loop and waits for user input.
在main函数中,我们创建应用程序对象,即一个窗口,然后使用exec()启动事件循环。目前,应用程序位于事件循环中,等待用户输入。
int main(int argc, char** argv)
{
QApplication app(argc, argv); // init application
// create the ui
return app.exec(); // execute event loop
}
Using Qt, you can build user interfaces in both QML and Widgets. In this book we focus on QML, but in this chapter, we will look at Widgets. This lets us create the program only C++.
使用Qt,您可以在QML或窗体中构建用户界面。在本书中,我们将重点介绍QML,但在本章中,我们将介绍Widgets窗体。这让我们只创建C++程序。
The main window itself is a widget. It becomes a top-level window as it does not have any parent. This comes from how Qt sees a user interface as a tree of UI elements. In this case, the main window is the root element, thus becomes a window, while the push button, that is a child of the main window, becomes a widget inside the window.
主窗口本身就是一个Widgets窗体。由于没有任何父窗口,因此它将成为顶级窗口。这源于Qt如何将用户界面视为UI元素树。在这种情况下,主窗口是根元素,因此成为窗口,而作为主窗口子窗口的按钮则成为窗口内的窗体。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
class MainWindow : public QMainWindow
{
public:
MainWindow(QWidget* parent=0);
~MainWindow();
public slots:
void storeContent();
private:
QPushButton *m_button;
};
#endif // MAINWINDOW_H
Additionally, we define a public slot called storeContent()
in a custom section in the header file. Slots can be public, protected, or private, and can be called just like any other class method. You may also encounter a signals
section with a set of signal signatures. These methods should not be called and must not be implemented. Both signals and slots are handled by the Qt meta information system and can be introspected and called dynamically at run-time.
此外,我们在头文件的自定义部分中定义了一个名为storeContent()的公有槽。槽可以是公有的、受保护的或私有的,并且可以像调用任何其他类方法一样进行调用。您可能还会遇到带有一组signals
签名的信号部分。这些方法不应该被调用,也不能被实现。信号和槽都由Qt元信息系统处理,可以在运行时进行内省和动态调用。
The purpose of the storeContent()
slot is that is is called when the button is clicked. Let's make that happen!
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;
}
In the main window, we first create the push button and then register the signal clicked()
with the slot using the connect method. Every time the signal clicked
is emitted the slot storeContent()
is called. And now, the two objects communicate via signal and slots despite not being aware of each other. This is called loose coupling and is made possible using the QObject
base class which most Qt classes derive from.
在主窗口中,我们首先创建按钮,然后使用connect方法将信号clicked()与槽storeContent()关联
。每次发出clicked
的信号时,都会调用槽storeContent()。现在,这两个物体通过信号和槽进行通信,尽管它们彼此不知道。这被称为松耦合,可以使用大多数Qt类派生的QObject基类实现。
示例源码下载