#QT-可视化应用

😒5ea1


What&Why is QT

Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。

前段时间打suctf的时候看到一道qt逆向,虽然被恶心坏了,但是确实新颖。因为最近刚好要交一个拓展作业,就想着用qt搭一个可视化的应用😋

How to QT

因为QT是一种面向对象的框架,所以实际上和编写安卓应用极为相似。
下面的论述仅代表我自己在使用过程中的体会,仅供参考

CMakeList.txt

就像vs项目中的.sln文件一样,qt直接采用了txt的形式来规划整个项目。每个项目中都会有一个名为CMakeList.txt的文本,这个文本的作用就是告知qt这个项目的架构是什么样子的,有什么文件,以及有什么依赖等等等等

CMakeList.txt分析

cmake_minimum_required(VERSION 3.16)

project(Dijkstra1 VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)

set(TS_FILES Dijkstra1_zh_CN.ts)

set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mainwindow.h
        mainwindow.ui
        ${TS_FILES}
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(Dijkstra1
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
        mainIO.cpp
        mainIO.h
        maindijkstra.h
        maindijkstra.cpp
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET Dijkstra1 APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation

    qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
else()
    if(ANDROID)
        add_library(Dijkstra1 SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(Dijkstra1
            ${PROJECT_SOURCES}
        )
    endif()

    qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()

target_link_libraries(Dijkstra1 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Dijkstra1)
endif()
set_target_properties(Dijkstra1 PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS Dijkstra1
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(Dijkstra1)
endif()

CMake 最低版本要求
cmake_minimum_required(VERSION 3.16)

这行代码指定了运行此 CMake 脚本所需的最低 CMake 版本为 3.16。如果使用的 CMake 版本低于 3.16,将会抛出错误。

项目信息设置
project(Dijkstra1 VERSION 0.1 LANGUAGES CXX)

project 命令用于定义项目的基本信息。
Dijkstra1 是项目的名称。
VERSION 0.1 设定项目的版本号为 0.1。
LANGUAGES CXX 表明项目使用的编程语言是 C++。

Qt 自动处理功能开启
set(CMAKE_AUTOUIC ON)

开启 Qt 的 UI 文件自动处理功能,CMake 会自动将 .ui 文件转换为对应的 C++ 代码。
set(CMAKE_AUTOMOC ON)
开启 Qt 的元对象编译器(MOC)自动处理功能,用于处理 Qt 的信号和槽机制。
set(CMAKE_AUTORCC ON)
开启 Qt 的资源编译器(RCC)自动处理功能,用于处理 Qt 的资源文件。

C++ 标准设置
set(CMAKE_CXX_STANDARD 17)

指定项目使用的 C++ 标准为 C++17。
set(CMAKE_CXX_STANDARD_REQUIRED ON)
表示必须使用指定的 C++ 标准,如果编译器不支持 C++17,CMake 会报错。

查找 Qt 库
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)

尝试查找 Qt 库,优先查找 Qt6,如果找不到则查找 Qt5。同时要求找到 Widgets 和 LinguistTools 组件。REQUIRED 表示如果找不到这些组件,CMake 会报错。
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
根据实际找到的 Qt 版本(QT_VERSION_MAJOR)再次查找相应版本的 Qt 库,并要求包含 Widgets 和 LinguistTools 组件。

翻译文件设置
set(TS_FILES Dijkstra1_zh_CN.ts)

定义一个变量 TS_FILES,其值为 Dijkstra1_zh_CN.ts,这是一个 Qt 的翻译源文件(.ts 文件)。

项目源文件设置
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
${TS_FILES}
)

定义一个变量 PROJECT_SOURCES,包含了项目的源文件、头文件、UI 文件和翻译源文件。

根据 Qt 版本创建可执行文件或库

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(Dijkstra1
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
        mainIO.cpp
        mainIO.h
        maindijkstra.h
        maindijkstra.cpp
    )
    qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
else()
    if(ANDROID)
        add_library(Dijkstra1 SHARED
            ${PROJECT_SOURCES}
        )
    else()
        add_executable(Dijkstra1
            ${PROJECT_SOURCES}
        )
    endif()
    qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()

使用 qt_add_executable 命令创建一个名为 Dijkstra1 的可执行文件。MANUAL_FINALIZATION 表示需要手动调用 qt_finalize_executable 来完成最终的配置。
qt_create_translation 命令用于根据 .ts 文件生成 .qm 翻译文件。

链接 Qt 库
target_link_libraries(Dijkstra1 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

将 Dijkstra1 目标与相应版本的 Qt Widgets 库进行链接。PRIVATE 表示该链接仅对 Dijkstra1 目标有效。

设置 macOS/iOS 包标识符
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Dijkstra1)
endif()

如果使用的 Qt 版本低于 6.1.0,设置 BUNDLE_ID_OPTION 变量为 MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Dijkstra1,用于指定 macOS/iOS 应用程序的包标识符。

设置目标属性
set_target_properties(Dijkstra1 PROPERTIES

命令用于设置 Dijkstra1 目标的属性
${BUNDLE_ID_OPTION}
如果 Qt 版本低于 6.1.0,设置包标识符。
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
设置 macOS 应用程序包的版本号为项目版本号。
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
设置 macOS 应用程序包的短版本号为项目主版本号和次版本号。
MACOSX_BUNDLE TRUE
表示该目标是一个 macOS 应用程序包。
WIN32_EXECUTABLE TRUE
表示该目标是一个 Windows 可执行文件。

安装目标
include(GNUInstallDirs)

包含 GNUInstallDirs 模块,该模块定义了一些标准的安装目录变量。
install(TARGETS Dijkstra1
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
用于指定如何安装 Dijkstra1 目标

完成 Qt 6 可执行文件的最终配置

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(Dijkstra1)
endif()

调用 qt_finalize_executable 命令完成 Dijkstra1 可执行文件的最终配置。

##mainwindow.ui

这个文件指定了qt应用的布局,类似于安卓中layout中的布局文件,同样的,在qt中这个布局文件也可以用可视化的方式来进行编写,双击打开mainwindow.ui文件即可进入设计页面
1
可以看到左侧有一个组件盒,可以从中取不同的组件进行diy,我在此处因为要做一个Djikstra计算器,需要输入无向图,所以使用了下列模块:

一个Text Edit接收输入
一个Text Edit写成只读作为输出
一个Text Edit用作提示
2
三个Plain Text Edit来提示各个框的作用
3
一个Spin Box来充当起始点的输入,防止非预期输入
4
一个Push Button来开始计算,一块QGraphics View来画示意图
5

只要为每个由你设定的组件设置一个独一无二的名字即可

##main.cpp

主程序代码,基本不用修改

##mainwindow.h

主窗口头文件,需要导入要调用的库,以及加入一些变量的定义和函数的定义

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "mainIO.h"
#include "maindijkstra.h"
#include <QSpinBox>
#include <QGraphicsScene>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_calculate_clicked();

private:
    void drawGraph(int max_node);

    Ui::MainWindow *ui;
    int Data[100][10] = {0};
    int n;
    QGraphicsScene *scene;//此处就是额外加入的全局变量以及一些函数
};
#endif

##More

到这里一个最简单的qt代码已经成型了,其余部分就是一些额外的调用以及额外的算法调用,这里展示我的迪杰斯特拉计算器的代码

mainio.cpp

//mainio.cpp
#include "mainIO.h"
#include <QDebug>

void InputData(const QString& input, int Data[100][10], int* n)
{
    QStringList input_lines = input.split('\n'), line_split;

    int row = 0;

    for (const QString &line : input_lines)
    {
        int col = 0;

        line_split = line.split(' ');
        for (const QString &datas : line_split)
        {
            bool ok;

            Data[row][col] = datas.toInt(&ok);
            if (ok)
            {
                col++;
            }
        }
        row++;
    }
    *n=row;
}

void OutputData(QString& output, int Data[100][10], int n)
{

    for(int i=0;i<n;i++)
    {
        for(int j=0;j<3;j++)
        {
            output.append(" ");
            output.append(QString::number(Data[i][j]));
        }
        output.append("\n");
    }
}

这段 C++ 代码主要实现了两个功能函数:InputData 和 OutputData。InputData 函数用于将输入的字符串数据解析为二维数组,OutputData 函数则用于将二维数组的数据转换为字符串输出。即主要处理读入的无向图数据

###maindijkstra.cpp

//maindijkstra.cpp
#include <QDebug>
#include "maindijkstra.h"
#include <vector>

void Dijkstra(QString& output, int Data[100][10], int n, int source)
{
    int find[100][100], flag[100];
    int Way[100];
    int prev[100];
    int max = -1;

    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            find[i][j] = 99999999;
        }
        Way[i] = 99999999;
        flag[i] = 0;
        prev[i] = -1;
    }

    for (int i = 0; i < n; i++)
    {
        find[Data[i][0]][Data[i][1]] = Data[i][2];
        find[Data[i][1]][Data[i][0]] = Data[i][2];
        for (int j = 0; j < 2; j++)
        {
            if (Data[i][j] > max)
            {
                max = Data[i][j];
            }
        }
    }

    Way[source] = 0;
    for (int i = 0; i <= max; i++)
    {
        int Node = -1;
        for (int j = 0; j <= max; j++)
        {
            if (flag[j] == 0 && (Node == -1 || Way[j] < Way[Node]))
            {
                Node = j;
            }
        }
        if (Node == -1)
        {
            break;
        }

        for (int j = 0; j <= max; j++)
        {
            if (find[Node][j] != 99999999 && Way[Node] + find[Node][j] < Way[j])
            {
                Way[j] = Way[Node] + find[Node][j];
                prev[j] = Node;
            }
        }
        flag[Node] = 1;
    }

    for (int i = 0; i <= max; i++)
    {
        output.append(QString::number(source));
        output.append(" to ");
        output.append(QString::number(i));
        output.append("  Length: ");
        output.append(QString::number(Way[i]));
        output.append("  Path: ");

        std::vector<int> path;
        for (int at = i; at != -1; at = prev[at])
        {
            path.push_back(at);
        }
        if (path.size() == 0)
        {
            output.append("No path\n");
        }
        else
        {
            std::reverse(path.begin(), path.end());
            for (size_t j = 0; j < path.size(); j++)
            {
                output.append(QString::number(path[j]));
                if (j != path.size() - 1)
                {
                    output.append(" -> ");
                }
            }
            output.append("\n");
        }
    }
}


int min(int x, int y)
{
    if(x<y)
    {
        return x;
    }
    return y;
}

这段代码实现了 Dijkstra 算法,用于计算从给定源节点到图中所有其他节点的最短路径。代码接收一个二维数组 Data 来表示图的边信息,源节点 source,并将计算结果以字符串形式存储在 output 中。


打包

最后只要把代码打包成所要的构建,就能在外面运行了
6
如果要脱离软件,还要用qt自带的应用添加依赖文件