# 样板应用

理解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++程序。

image

主窗口本身就是一个窗口小部件。 因为它没有任何父窗口,它成为了一个顶级窗口。 这来自于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派生类都可以实现这样的功能。

最后更新: 2/3/2022, 4:21:42 PM