拓扑排序软件设计——ToplogicalSort_app(含有源码、需求分析、可行性分析、概要设计、用户使用手册)

拓扑排序软件设计

  • 前言
  • 1. 需求分析
  • 2. 可行性分析
    • 2.1 简介
    • 2.2 技术可行性分析
      • 2.2.1 技术实现方案
      • 2.2.2 开发人员技能要求
      • 2.2.3 可行性
    • 2.3 操作可行性分析
    • 2.4 结论
  • 3. 项目报告
    • 3.1 修订历史记录
    • 3.2 软硬件环境
    • 3.3 需求分析
    • 3.4 详细设计
      • 3.4.1 类设计
      • 3.4.2 核心流程描述
      • 3.4.3 核心算法设计
    • 4. 运行结果截图
      • 4.1 样例1
      • 4.2 样例2
      • 4.3 样例3
      • 4.4 样例4
      • 4.5 样例5
    • 5. 测试
      • 5.1 测试样例1
      • 5.2 测试样例2
      • 5.3 测试样例3
      • 5.4 测试样例4
      • 5.5 测试样例5
    • 6. 系统特色以及可扩展点
      • 6.1系统特色:
      • 6.2可扩展点:
  • 4. 源代码部分
    • 4.1 项目层级
    • 4.2 运行环境
    • 4.3 项目核心代码
      • 4.3.1 拓扑排序算法TopologicalSort.cpp
      • 4.3.2 draw_diagram.py
      • 4.3.3 file_manager.py
      • 4.3.4 input_manager.py
      • 4.3.5 main_window.py
      • 4.3.6 topology_manager.py
    • 4.4 GitHub仓库
  • 5. 用户使用手册
    • 5.1 运行软件APP
    • 5.2 操作app
  • 6. 结束语

前言

这篇博客可能会有点长,因为是一个课程的大作业,包含的内容比较多,这个项目的开发的时间在两周左右,所以这个软件指是一个简单又比较简陋的小桌面应用。


1. 需求分析

  1. 导入文件:
    用户能够通过界面导入描述课程依赖关系的文本文件(.txt),文件格式为每行表示一个有向边的关系。

  2. 绘制拓扑排序图:
    根据导入的课程依赖关系,能够绘制出对应的拓扑排序图,使用直观的图形方式展示课程间的依赖关系。

  3. 导出图像:
    用户可以将绘制好的拓扑排序图导出为图片(.png格式),以便于保存和分享。

  4. 导出拓扑排序结果:
    用户可以将拓扑排序的结果导出为文本文件(.txt格式),用于后续分析和处理。

  5. 展示C++程序输出:
    显示调用外部C++程序计算拓扑排序后的输出结果,以便用户查看拓扑排序的详细信息。

  6. 主界面:
    提供导入文件、绘制图像、导出图像、导出拓扑排序结果的按钮,以及显示绘制好的图像和C++程序输出的区域。

  7. 用户操作反馈:
    显示错误、警告等反馈信息,确保用户能够清晰了解操作结果。

  8. 性能需求
    若所有拓扑排序的结果非常多则需要很快速的返回正确并且结果个数正确的结果,避免用户等待过长的时间。同时绘制拓扑排序图应在合理的时间范围内完成,避免用户等待时间过长。

  9. 其他需求
    ①软件应该通过所给样例的测试;
    ②软件应该支持在Windows操作系统上运行


2. 可行性分析

2.1 简介

该文档对拓扑排序图绘制工具项目——TopologicalSort_app软件进行可行性分析,主要包括技术可行性和操作可行性的分析,以确保项目的实施和开发是合理、可行的。

2.2 技术可行性分析

2.2.1 技术实现方案

①使用PySide2库实现图形用户界面,提供友好的交互。
②使用networkx和matplotlib库绘制拓扑排序图,能够高效、准确地展示图形。
③利用Python内置的文件处理功能实现文件导入、导出功能。
④使用subprocess库调用外部C++程序进行拓扑排序,实现图的计算。
⑤使用tempfile库创建临时文件,以保存绘制好的图像。
⑥通过在Python项目中调用C++程序,实现了对拓扑排序的计算。
⑦使用Python的subprocess库调用外部C++程序,并获取其输出。
⑧这种跨语言调用对实现拓扑排序算法具有良好的技术可行性,确保了项目核心功能的实现。

2.2.2 开发人员技能要求

①开发人员需熟悉Python编程语言及其相关库,如PySide2、networkx、matplotlib、subprocess等。
②需要了解图论中的拓扑排序算法以及相关概念。

2.2.3 可行性

①技术方案基于成熟的Python库实现,具有较高的技术可行性。
②Python具有丰富的第三方库和开发资源,能够快速实现项目需求。

2.3 操作可行性分析

①项目设计简单明了,操作界面直观友好,用户容易上手。
②提供了导入、导出、绘制图像等功能按钮,用户操作便捷,符合用户使用习惯。
③C++程序的调用对用户是透明的,用户只需使用界面提供的功能,不需要关心底层实现语言。
④用户操作界面简单明了,易于上手,提供了直观的导入、导出、绘制图像等功能按钮,满足用户操作习惯,操作可行性较高。

2.4 结论

①该项目具有较高的技术可行性,开发成本较低,运维成本也较低。操作界面简单明了,用户操作便捷。
②调用C++程序作为拓扑排序的计算引擎是技术上可行的,不会对整体的可行性产生负面影响。
③用户无需关心C++程序调用细节,操作界面简单易用,用户操作的可行性较高,确保了项目的实施和开发是合理、可行的。


3. 项目报告

3.1 修订历史记录

日期版本说明作者
2023.9.101.0.0创建好初步的页面hiddenSharp
2023.9.111.0.1完善了页面的排版hiddenSharp
2023.9.141.1.01. 为生成的拓扑排序图片添加了放大和缩小按钮
2. 为生成的拓扑图片添加了背景颜色
hiddenSharp
2023.9.151.2.01. 删除了放大和缩小按钮
2. 优化了图片大小格式以及清晰度
3. 新增导入文件后可以之间生成该图的所有拓扑排序结果
hiddenSharp
2023.9.161.2.11. 重新导入文件后将清空之前生成的图片并且进度条置零
2. 初始化进度条值为0,导入文件后增加50,生成图片后再加50
3. 删除了自动导出结果的功能,修改为用户手动点击Export进行结果的导出
4. 新增用户进行导出操作后,可以下拉选择导出的文件类型(.txt为所有的拓扑排序结果,.png为拓扑图)
hiddenSharp
2023.9.171.2.21. 固定了软件的窗口大小,不可调整窗口大小以及最大化
2. 调整了进度条的逻辑,取消了50的值,只有0与100
3. 完善了ADD 和 DEL按钮后生成图片以及拓扑排序结果的逻辑
4. DEL 按钮和 ADD按钮异常BUG
hiddenSharp
2023.9.231.3.0进行了项目结构的重构,更加具有面向对象的思想,将各模块分离出来了。新增FileManager类、InputManager类、TopologyManager类,将MainWindow类进行解耦和重构。hiddenSharp

3.2 软硬件环境

  • 操作系统:Windows
  • 硬件要求:暂无特定硬件需求
  • 开发工具:主要的IDE为PyCharm + Visual Stadio 2010;使用到的编程语言为Python + C++;文本编辑器使用的为Notepad++(不是硬性要求);编译器为Python3.6
  • 第三方库和依赖项:PySide2 v5.15.2.1、networkx v2.5.1

3.3 需求分析

TopologicalSort_app的主要功能是执行拓扑排序算法,用户可以导入图的节点和边,一条边的格式应该为 <arc_start,arc_end> 然后调用C++算法进行排序,随后返回结果并显示在软件屏幕上。上面已经详细说明了需求分析,故在此不再赘述。

3.4 详细设计

3.4.1 类设计

  • MainWindow类
    1. 在_init_函数中创建主窗口页面、存储主窗口信息、初始化控制器、连接槽函数。
    2. 在clear_topology_graph函数中清空拓扑图
    3. 在run_cpp函数中调用编译后的.cpp文件
    4. 在display_cpp_output函数中显示C++程序的输出到指定位置
    5. 在export_image函数中设置导出拓扑排序图片的操作
    6. 在export_topology函数中设置导出拓扑排序结果的操作
  • FileManager类
    1. 在_init_函数中载入MainWindow实例和InputManager实例
    2. 在import_file函数中设置导入文件的操作
    3. 在export函数中设置导出的操作
  • InputManager类
    1. 在__init__函数中载入MainWindow实例
    2. 在fill_input_boxes函数中填写相关数据到输入框中
    3. 在add_input_field函数中设置添加输入框的操作
    4. 在del_input_field函数中设置删除输入框的操作
  • TopologyManager类
    1. 在__init__函数中载入MainWindow实例
    2. 在generate_draw函数中通过调用draw_diagram文件的相关函数来完成将图片显示在相关位置上。

注:并没有draw_diagram类,只是一个py文件,里面写了一个draw_directed_graph函数,该函数通过调用networkx和matplotlib来完成图片的生成。

3.4.2 核心流程描述

定义一个课程结构体,声明二维向量,利用dfs函数递归进行深度优先搜索,生成所有可能的结果,判断是否存在循环依赖关系,用户可导出排序结果

3.4.3 核心算法设计

  1. dfs 函数接收两个参数:课程向量 courses 和拓扑排序的结果向量 result。
  2. 在函数开始定义了一个递归停止条件判断:如果当前拓扑排序结果的大小等于课程向量 courses 的大小,即 result 的大小等于 courses 的大小,说明已经生成了一个完整的拓扑排序结果。
  3. 遍历课程向量 courses。

(1)对于每一个课程,判断当前课程是否满足拓扑排序的条件,即入度为0且未被访问过;
(2)如果满足条件,将其添加到 result 中并将当前课程标记为已访问;
(3)对于每一个课程,判断当前课程是否满足拓扑排序的条件,即入度为0且未被访问过;
(4)如果满足条件,将其添加到 result 中并将当前课程标记为已访问;
(5)遍历当前课程的后继课程;将所有当前课程对应的后继课程入度减1;
(6)递归调用 dfs 函数处理下一个课程;
(7)回溯,将当前课程标记为未访问,回复后继课程的入度,从 result 中移除最后一门课程,得出其他分支结果;
(8)继续遍历下一个课程(更换拓扑排序的起始课程),重复上述步骤(在循环里);
(9)当所有的课程都被遍历完后,dfs 函数执行结束;将当前的拓扑排序结果 result 添加到 allTopologicalSorts 向量中。

4. 运行结果截图

4.1 样例1

在这里插入图片描述

4.2 样例2

在这里插入图片描述

4.3 样例3

在这里插入图片描述

4.4 样例4

在这里插入图片描述

4.5 样例5

在这里插入图片描述

5. 测试

5.1 测试样例1

  • Filename:data_1.txt
  • Content
    <a,b>
    <b,c>
    <c,d>
    <d,f>
    <f,g>
  • Result
    sort ruselt_1:a b c d f g
  • photo
    在这里插入图片描述

5.2 测试样例2

  • Filename:data_2.txt
  • Content
    <CS 100,CS 200>
    <CS 200,CS 250>
    <CS 200,CS 300>
    <CS 250,CS 350>
  • Result
    sort ruselt_1:CS 100 CS 200 CS 250 CS 300 CS 350
    ---------------------
    sort ruselt_2:CS 100 CS 200 CS 250 CS 350 CS 300
    ---------------------
    sort ruselt_3:CS 100 CS 200 CS 300 CS 250 CS 350
  • photo
    在这里插入图片描述

5.3 测试样例3

  • Filename:data_3.txt
  • Content
    <CS 350,CS 250>
    <MA 140,MA 141>
    <MA 141,CS 150>
    <CS 250,CS 225>
    <MA 141,CS 225>
    <CS 225,CS 155>
    <CS 150,CS 155>
    <CS 155,CS 200>
    <CS 225,CS 230>
    <CS 225,CS 300>
    <CS 300,CS 301>
    <CS 300,CS 340>
    <CS 340,CS 345>
    <CS 340,CS 360>
    <CS 250,CS 360>
    <CS 360,CS 390>
  • Result
    sort ruselt_1:CS 350 CS 250 MA 140 MA 141 CS 150 CS 225 CS 155 CS 200 CS 230 CS 300 CS 301 CS 340 CS 345 CS 360 CS 390
    ---------------------
    sort ruselt_2:CS 350 CS 250 MA 140 MA 141 CS 150 CS 225 CS 155 CS 200 CS 230 CS 300 CS 301 CS 340 CS 360 CS 345 CS 390
    ……
    ……
    ……
    sort ruselt_113399:MA 140 MA 141 CS 150 CS 350 CS 250 CS 225 CS 300 CS 340 CS 360 CS 390 CS 345 CS 301 CS 155 CS 230 CS 200
    ---------------------
    sort ruselt_113400:MA 140 MA 141 CS 150 CS 350 CS 250 CS 225 CS 300 CS 340 CS 360 CS 390 CS 345 CS 301 CS 230 CS 155 CS 200

5.4 测试样例4

  • Filename:data_4.txt

  • Content
    < 0,1>
    < 1,3>
    < 0,2>
    < 2,4>
    < 4,5>
    < 3,5>

  • Result
    sort ruselt_1:0 1 3 2 4 5
    ---------------------
    sort ruselt_2:0 1 2 3 4 5
    ---------------------
    sort ruselt_3:0 1 2 4 3 5
    ---------------------
    sort ruselt_4:0 2 1 3 4 5
    ---------------------
    sort ruselt_5:0 2 1 4 3 5
    ---------------------
    sort ruselt_6:0 2 4 1 3 5

  • Photo
    在这里插入图片描述

5.5 测试样例5

  • Filename:data_5.txt
  • Content
    <0,1>
    <1,2>
    <2,0>
  • Result
    存在循环依赖关系
  • Photo
    在这里插入图片描述

6. 系统特色以及可扩展点

6.1系统特色:

TopologicalSort_app 是一个Python项目,它基于PySide2库实现的图形用户界面(GUI)应用程序,用于创建、导入、导出拓扑排序图。下面将介绍它的一些系统特色:

  1. 图形用户界面(GUI):
    使用PySide2的QtDesigner创建了一个图形用户界面名为main.ui存放在statics文件夹下面,提供了文件导入、图形绘制、导出等功能接口。
  2. 拓扑排序图绘制:
    通过调用Draw_diagram.py里面的draw_directed_graph函数返回给主界面一个图片的临时地址,生成后若不保存则自动释放资源,不占用计算机内部关键资源。在python文件调用了networkx和matplotlib库实现了拓扑排序图的绘制功能。使用有向图表示节点和边的关系,并在GUI中名为photoLable的QLabel显示该图。
  3. 文件导入和导出:
    允许用户导入文本文件(.txt)以填充输入框并绘制拓扑排序图,同时也支持将绘制好的图像导出为图片(.png)或拓扑排序结果导出为文本文件。
  4. C++程序调用:
    由于C++运行速度比python快,故复杂的计算任务交给C++来完成,项目能够调用外部的C++程序(通过使用g++来编译.cpp文件,从而生成.exe文件)python运行该.exe并传入相关参数给.exe文件,最后将输出c++文件返回的结果显示在GUI中。此外,C++程序的输出可以导出为文本文件。
  5. 动态UI加载:
    使用PySide2的QUiLoader动态加载UI文件,使得UI可以通过简单的编辑UI文件而不需要修改代码。以此来到模块化编程,更加灵活多变。
  6. 用户操作反馈:
    使用QMessageBox提供信息、警告和错误提示,以向用户提供反馈。
  7. 临时文件处理:
    使用tempfile库创建临时文件以保存绘制的图像,以便导出功能可以使用这些临时文件。
  8. 异常处理:
    项目中实现了异常处理机制,能够捕获并显示错误信息,提高应用程序的健壮性。

6.2可扩展点:

  1. 将鼠标放置在按钮上会显示出相应信息
  2. 结果图的大小可以自行调节

4. 源代码部分

4.1 项目层级

TopologicalSort_app

├─.idea

├─build

├─core (核心算法)

├─data (读入的文件信息)

├─dist (发布软件的各个版本)

├─docs (所有文档信息)

├─lib

├─log

├─statics (静态资源)

├─test  (测试)

├─venv

└─__pycache__

4.2 运行环境

  • IDE:pycharm community v2023+

  • 解释器:python v3.6.8

  • 外部库: PySide2 v5.15.2.1、networkx v2.5.1、matplotlib v3.3.4

  • GUI工具:QtDesigner

4.3 项目核心代码

4.3.1 拓扑排序算法TopologicalSort.cpp

写在前面:这个c++程序在整个项目中是比较核心的一个部分,它利用c++运行速度更快来作为核心程序,让整个python项目调用,以达到核心业务和整个项目解耦的目的。这个.cpp文件不会直接调用,项目只会调用编译过后的TopologicalSort.exe,而这个文件会存放在statics文件夹下面。

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <algorithm>

using namespace std;

struct Course {
    string name;  // 课程名
    vector<Course*> prerequisites;  // 对应的先修课程的指针向量
    int indegree;  // 入度(有多少个先修条件)
    bool visited;  // 判断课程是否被访问过
    Course(const string& n) : name(n), indegree(0), visited(false) {}  // 构造函数
};

vector<vector<string>> allTopologicalSorts;

void dfs(vector<Course*>& courses, vector<string>& result) {
    if (result.size() == courses.size()) {
        allTopologicalSorts.push_back(result);
        return; // 递归终止条件:完成了一次拓扑排序
    }

    for (size_t i = 0; i < courses.size(); ++i) { // 遍历每个课程
        Course* course = courses[i]; // 当前课程

        if (course->indegree == 0 && !course->visited) { // 如果当前课程的入度为0且未被访问过
            course->visited = true; // 标记当前课程已访问

            result.push_back(course->name); // 将当前课程添加到当前排列中

            for (size_t j = 0; j < course->prerequisites.size(); ++j) { // 减少当前课程的邻接课程的入度
                Course* prerequisite = course->prerequisites[j];
                prerequisite->indegree--;
            }

            dfs(courses, result);       //递归

            course->visited = false; // 标记当前课程为未访问状态
            for (size_t j = 0; j < course->prerequisites.size(); ++j) { // 回溯:撤销之前的修改
                Course* prerequisite = course->prerequisites[j];
                prerequisite->indegree++; // 恢复后续邻接课程的入度
            }
            result.pop_back(); // 移除当前排列中的最后一门课程
        }
    }
}

bool printAllTopologicalSorts(vector<Course*>& courses) {
    vector<string> result;
    dfs(courses, result);
    return !allTopologicalSorts.empty();
}

void shuchu(vector<Course*>& courses) {
    int count = 0;
    for (size_t i = 0; i < allTopologicalSorts.size(); ++i) {
        cout << "sort ruselt_" << ++count << ':';
        for (size_t j = 0; j < allTopologicalSorts[i].size(); ++j) {
            cout << allTopologicalSorts[i][j] << " ";
        }
        cout << endl;
        if (i != allTopologicalSorts.size() - 1) {
            cout << "---------------------" << endl;
        }
    }
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        cout << "Usage: " << argv[0] << " <filename>" << endl;
        return 0;
    }

    string filename = argv[1];  
    vector<Course*> courses;
    unordered_map<string, Course*> courseMap;

    ifstream file(filename);
    if (file.is_open()) {
        string line;
        while (getline(file, line)) {
            if (line.empty()) {
                continue;
            }

            line = line.substr(1, line.size() - 2);
            stringstream ss(line);
            string courseName, prereqName;
            getline(ss, courseName, ',');
            getline(ss, prereqName);

            Course* course = courseMap[courseName];
            if (!course) {
                course = new Course(courseName);
                courses.push_back(course);
                courseMap[courseName] = course;
            }

            Course* prereq = courseMap[prereqName];
            if (!prereq) {
                prereq = new Course(prereqName);
                courses.push_back(prereq);
                courseMap[prereqName] = prereq;
            }

            course->prerequisites.push_back(prereq);
            prereq->indegree++;
        }
        file.close();
    } else {
        cout << "无法打开文件" << endl;
        return 0;
    }

    if (printAllTopologicalSorts(courses)) {
        shuchu(courses);
    } else {
        cout << "存在循环依赖关系" << endl;
    }

    for (size_t i = 0; i < courses.size(); i++) {
        delete courses[i];
    }
    courses.clear();

    return 0;
}

4.3.2 draw_diagram.py

import networkx as nx
import matplotlib.pyplot as plt
import tempfile

def draw_directed_graph(edges, figsize=(3, 3)):
    try:
        # 创建一个有向图对象
        G = nx.DiGraph()

        # 添加有向边
        for data in edges:
            data = data.strip('<>')
            source, target = data.split(',')
            G.add_edge(source, target)

        # 设置图片的大小
        plt.figure(figsize=figsize)

        # 绘制有向图
        pos = nx.spring_layout(G)

        # Adjust node positions for labels to be around the nodes
        pos_labels = {node: (x, y + 0.01) for node, (x, y) in pos.items()}

        nx.draw(G, pos, with_labels=False, node_color='g', node_size=200, arrows=True)

        # Draw labels separately with adjusted positions
        nx.draw_networkx_labels(G, pos_labels, font_size=10)

        # 保存图形到临时文件
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
            plt.savefig(tmpfile, format="png", bbox_inches="tight")

        # 返回临时文件的路径
        return tmpfile.name

    except Exception as e:
        print(f"生成图时出现错误:{str(e)}")

4.3.3 file_manager.py

from PySide2.QtWidgets import QFileDialog, QMessageBox
class FileManager:
    def __init__(self, main_window, input_manager):
        self.main_window = main_window
        self.input_manager = input_manager

    def import_file(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self.main_window.ui, "选择要导入的文件", "", "文本文件 (*.txt);;所有文件 (*)", options=options)

        if file_path:
            try:
                self.main_window.clear_topology_graph()  # 清空拓扑排序图
                self.main_window.file_path = file_path

                with open(file_path, 'r', encoding='utf-8') as file:
                    file_content = file.read()

                    # 调用C++文件并获取结果
                    cpp_output = self.main_window.run_cpp(file_path)
                    # 将结果显示在plainTextEdit上
                    self.main_window.display_cpp_output(cpp_output)
                    self.input_manager.fill_input_boxes(file_content)

            except Exception as e:
                QMessageBox.critical(self.main_window.ui, "错误", f"导入文件时出现错误:{str(e)}")
        else:
            QMessageBox.warning(self.main_window.ui, "警告", "未选择任何文件")

    def export(self):
        options = QFileDialog.Options()
        export_option, _ = QFileDialog.getSaveFileName(self.main_window.ui, "选择导出路径", "",
                                                       "Images (*.png);;Text Files (*.txt)", options=options)

        if export_option:
            if export_option.endswith(".png"):
                # 导出图片
                self.main_window.export_image(export_option)
            elif export_option.endswith(".txt"):
                # 导出拓扑排序结果
                self.main_window.export_topology(export_option)
            else:
                QMessageBox.warning(self.main_window.ui, "警告", "不支持的导出格式")


4.3.4 input_manager.py

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QListWidgetItem, QLineEdit, QAbstractItemView

class InputManager:
    def __init__(self, main_window):
        self.main_window = main_window

    def fill_input_boxes(self, file_content):
        # 清空现有输入框的内容
        self.main_window.ui.inputList.clear()
        data_list = file_content.split('\n')  # 按换行符分割数据
        for data in data_list:
            data = data.strip()
            if data:
                # 提取源节点和目标节点
                source, target = data.split(',')
                source = source.strip()
                target = target.strip()

                # 创建适当的输入格式
                input_item = QListWidgetItem()
                input_line_edit = QLineEdit(f"{source},{target}")
                input_line_edit.setAlignment(Qt.AlignCenter)
                self.main_window.ui.inputList.addItem(input_item)
                self.main_window.ui.inputList.setItemWidget(input_item, input_line_edit)

    def add_input_field(self):
        input_item = QListWidgetItem()
        input_line_edit = QLineEdit('<start,end>')
        input_line_edit.setAlignment(Qt.AlignCenter)  # 设置文本居中对齐
        self.main_window.ui.inputList.addItem(input_item)
        self.main_window.ui.inputList.setItemWidget(input_item, input_line_edit)

        # 将边信息添加到列表中
        self.main_window.edge_info.append('<start,end>')

    def del_input_field(self):
        selected_items = self.main_window.ui.inputList.selectedItems()
        for item in selected_items:
            index = self.main_window.ui.inputList.row(item)
            self.main_window.ui.inputList.takeItem(index)

            # 从列表中删除对应的边信息
            if index < len(self.main_window.edge_info):
                del self.main_window.edge_info[index]

4.3.5 main_window.py

import os
import subprocess

from PySide2.QtWidgets import (
    QFileDialog, QMessageBox, QListWidgetItem,
    QLineEdit, QAbstractItemView, QWidget,
    QVBoxLayout, QLabel
)
from PySide2.QtUiTools import QUiLoader
from PySide2.QtCore import Qt
from .file_manager import FileManager
from .input_manager import InputManager
from .topology_manager import TopologyManager

class MainWindow:
    def __init__(self):
        # 动态加载.ui文件
        self.ui = QUiLoader().load('statics/main.ui')
        # 禁止调整窗口大小
        self.ui.setFixedSize(self.ui.size())
        # 设置窗口属性,禁止最大化
        self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.WindowMaximizeButtonHint)

        # 用于存储边信息的列表
        self.edge_info = []
        # 存储文件路径
        self.file_path = None

        # 初始化控制器
        self.input_manager = InputManager(self)
        self.file_manager = FileManager(self, self.input_manager)
        self.topology_manager = TopologyManager(self)

        # 连接相关操作的槽函数
        self.ui.actionImport.triggered.connect(self.file_manager.import_file)
        self.ui.actionExport.triggered.connect(self.file_manager.export)
        self.ui.addButton.clicked.connect(self.input_manager.add_input_field)
        self.ui.delButton.clicked.connect(self.input_manager.del_input_field)
        self.ui.generateButton.clicked.connect(self.topology_manager.generate_draw)

        # 设置输入框为单选模式
        self.ui.inputList.setSelectionMode(QAbstractItemView.SingleSelection)

        # 设置按钮的提示文本
        self.ui.addButton.setToolTip("添加输入框 (Alt + A)")
        self.ui.delButton.setToolTip("删除输入框 (Alt + D)")
        self.ui.generateButton.setToolTip("生成拓扑排序结果 (Alt + Enter)")

        # 创建一个 QWidget 作为容器
        self.photo_container = QWidget()
        self.ui.photoLabel.layout().addWidget(self.photo_container)
        self.ui.photoLabel.setStyleSheet("background-color: white;")

        # 在容器上设置布局
        container_layout = QVBoxLayout()
        self.photo_container.setLayout(container_layout)

        # 将 QLabel 添加到容器中
        self.photo_label = QLabel()
        container_layout.addWidget(self.photo_label)

    def clear_topology_graph(self):
        self.photo_label.clear()  # 清空 QLabel 上的图像
        self.ui.progressBar.setValue(0)  # 重置进度条的值

    def run_cpp(self, file_path):
        try:
            # 获取当前脚本所在目录
            script_directory = os.path.dirname(os.path.abspath(__file__))

            # 构建调用命令
            cpp_executable_path = os.path.join(script_directory, 'TopologicalSort.exe')
            command = f'"{cpp_executable_path}" "{file_path}"'

            # 调用外部程序
            result =  subprocess.run(command, shell=True, stdout=subprocess.PIPE)

            # 返回结果
            return result.stdout

        except Exception as e:
            print("Error during running C++ program:", str(e))
            return "Error: Unable to run C++ program"

    # 在plainTextEdit中显示C++程序的输出
    def display_cpp_output(self, cpp_output):
        try:
            # 将字节串解码为字符串
            cpp_output_str = cpp_output.decode('utf-8')

            # 在plainTextEdit中显示C++程序的输出
            self.ui.plainTextEdit.setPlainText(cpp_output_str)
        except Exception as e:
            print("Error: Unable to display C++ output:", str(e))
            self.ui.plainTextEdit.setPlainText("Error: Unable to display C++ output")

    def export_image(self, export_path):
        pixmap = self.photo_label.pixmap()
        if pixmap:
            pixmap.save(export_path, "PNG")
            QMessageBox.information(self.ui, "导出成功", f"图像已成功导出到:{export_path}")
        else:
            QMessageBox.warning(self.ui, "警告", "没有图像可导出")

    def export_topology(self, export_path):
        try:
            # 获取C++程序的输出
            cpp_output = self.ui.plainTextEdit.toPlainText().encode('utf-8')

            # 写入文件
            with open(export_path, 'wb') as file:
                file.write(cpp_output)

            QMessageBox.information(self.ui, "导出成功", f"拓扑排序结果已成功导出到:{export_path}")
        except Exception as e:
            QMessageBox.critical(self.ui, "错误", f"导出拓扑排序结果时出现错误:{str(e)}")

if __name__ == "__main__":
    # Create the application instance
    import sys
    from PySide2.QtWidgets import QApplication
    app = QApplication(sys.argv)

    # Create and show the main window
    mainWindow = MainWindow()
    mainWindow.ui.show()

    # Start the event loop
    sys.exit(app.exec_())

4.3.6 topology_manager.py

from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QMessageBox

from .draw_diagram import draw_directed_graph

class TopologyManager:
    def __init__(self, main_window):
        self.main_window = main_window
    def generate_draw(self):
        try:
            if not self.main_window.file_path:
                QMessageBox.warning(self.main_window.ui, "警告", "未选择任何文件")
                return

            # 获取所有输入框的值
            input_items = [self.main_window.ui.inputList.itemWidget(self.main_window.ui.inputList.item(i)).text()
                           for i in range(self.main_window.ui.inputList.count())]

            # 将边信息更新为当前输入框中的值
            self.main_window.edge_info = input_items

            # 调用绘图函数并获取图形文件路径
            graph_image_path = draw_directed_graph(input_items)

            if graph_image_path:
                # 将图形文件设置为QLabel的图像
                pixmap = QPixmap(graph_image_path)
                self.main_window.photo_label.setPixmap(pixmap)
                self.main_window.ui.progressBar.setValue(100)

            # 更新文件中的边信息(覆盖原文件)
            with open(self.main_window.file_path, 'w', encoding='utf-8') as file:
                file.write('\n'.join(input_items))

            # 重新调用C++程序并更新输出
            cpp_output = self.main_window.run_cpp(self.main_window.file_path)
            self.main_window.display_cpp_output(cpp_output)

        except Exception as e:
            print("Error during generate_draw:", str(e))

4.4 GitHub仓库

有这些核心源代码可能远远不够,因为还有些不是代码的核心文件,如:使用QtDesigner设计的页面UI——main.ui文件,因此在下面我会放上这个项目的GitHub仓库地址,如果有需要可以自取哦~

https://github.com/hiddenSharp429/ToplogicalSort_app


5. 用户使用手册

5.1 运行软件APP

①打开TopologicalSort_app文件夹,双击dist文件夹进入所有发布程序选择页面
在这里插入图片描述

②选择需要运行的软件版本
在这里插入图片描述

各个版本的区别请看\ToplogicalSort_app\docs\ToplogicalSort_app更新日志.md

③进入某一个版本的文件夹后下拉找到main.exe文件
在这里插入图片描述

④双击后启动软件

5.2 操作app

①点击左上角菜单 ico
在这里插入图片描述

②选择导入选项卡
在这里插入图片描述
③选择需要导入的文件
在这里插入图片描述

一般测试样例都会放在\ToplogicalSort_app\data\里面
④导入后左边的输入框将会生成,文件中的文本将会被匹配填充,并且在下面的输入框中输出了所有拓扑排序的结果

在这里插入图片描述

测试样例的格式如下
在这里插入图片描述

⑤随后点击Generate按钮生成拓扑排序图片

在这里插入图片描述
在这里插入图片描述

⑥至此完成基本的功能,可以点击菜单选择导出选项卡
在这里插入图片描述

⑦可选择导出.txt类型文件还是.png文件,若为.txt文件则导出所有拓扑排序的结果,若为.png文件则导出拓扑图
在这里插入图片描述

运行结果:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


6. 结束语

如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/132610.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

部署ruoyi-vue-plus和ruoyi-app

nginx.conf worker_processes 1;error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;# 限制…

黑客技术(网络安全)-自学

前言 一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防…

SPSS二元Logistic回归

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

湖南大学-数据库系统-2018期中考试解析

答案是自己做的&#xff0c;仅供参考。 一、单选题&#xff08;每小题2分&#xff0c;共30分&#xff09; 1、下列关于数据库系统正确的描述是&#xff08; A &#xff09;。 A、数据库系统减少了数据的冗余 B、数据库系统避免了一切冗余 C、数据库系统中数据的一致性是指数据…

动态规划(5)---Leetcode338.比特位计数

题目 给你一个整数 n &#xff0c;对于 0 < i < n 中的每个 i &#xff0c;计算其二进制表示中 1 的个数 &#xff0c;返回一个长度为 n 1 的数组 ans 作为答案。 分析 通常动态规划的做题顺序&#xff0c;先确定dp数组dp[i],然后确定确定递推公式&#xff0c;再dp数…

C语言实现写一个函数,求一个字符串的长度,在main函数中输入字符串,并输出其长度

完整代码&#xff1a; // 写一个函数&#xff0c;求一个字符串的长度&#xff0c;在main函数中输入字符串&#xff0c;并输出其长度 #include<stdio.h> //字符串最大长度 #define N 100 int strlen(char *str){int i0;//字符串结尾为‘\0’while (*str!\0){i;//指针移动…

汽车制动系统技术分析概要

目录 1.基本功能概述 2. 基本工作原理分析 2.1 Two-Box系统架构(Bosch_IBooster) 2.2 One-Box系统架构(Bosch_IPB) 2.3 ​​​​​​​ABS技术 2.4 TCS技术 2.5 VDC技术 2.6 EPB技术 2.7 小结 3. 该场景应用发展趋势分析 1.基本功能概述 传统汽车的底盘主要由传动系、…

安全框架SpringSecurity-1(认证入门数据库授权)

一、Spring Security ①&#xff1a;什么是Spring Security Spring Security是一个能够为基于Spring的企业应用系统提供声明式&#xff08;注解&#xff09;的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean&#xff0c;充分利用了Spring …

【数据结构】C语言实现单链表万字详解(附完整运行代码)

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.了解项目功能 在本次项目中我们的目标是实现一个单链表: 该单链表使用动态内存分配空间,可以用来存储任意数量的同类型数据. 单链表结点(Node)需要包含两个要素:数据域data…

BMVC 23丨多模态CLIP:用于3D场景问答任务的对比视觉语言预训练

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2306.02329 摘要&#xff1a; 训练模型将常识性语言知识和视觉概念从 2D 图像应用到 3D 场景理解是研究人员最近才开始探索的一个有前景的方向。然而&#xff0c…

【线上问题】服务器关机导致docker启动的mysql数据库消失了

目录 一、问题描述二、解决方式 一、问题描述 1. 服务器迁移断电导致docker启动的mysql数据库没有了数据 2. data目录是空的 3. mysql重启数据库消失了 二、解决方式 1. sudo -i切换root账号 2. 查找mysql的容器卷 find /var/lib/docker/volumes/ -name mysql3. 进入各个_dat…

FTP、NFS以及SAMBA服务

一、FTP服务 1、Linux下ftp客户端管理工具 ftp、lftp都是Linux下ftp的客户端管理工具&#xff0c;但是需要独立安装 # yum install ftp lftp -y ☆ ftp工具 # ftp 10.1.1.10 Connected to 10.1.1.10 (10.1.1.10). 220 (vsFTPd 3.0.2) Name (10.1.1.10:root): 输入FTP的账号…

游戏公司数据分析师必备知识(持续补充中...)

1.如何撰写专题报告&#xff1f; ①原则 只有一个主题&#xff1a;即使不讲ppt&#xff0c;业务方也能看得懂行文通俗简单易懂&#xff1a;学习产品经理平常是如何写报告的明确的数据结论和落地项先行&#xff1a;跟业务方多沟通数据结论&#xff0c;让他们给出落地项 ②结构…

CSS特效第一弹:右上角tag标志纯代码前端实现(非图片)

&#x1f60e;效果&#xff1a; &#x1f937;‍♂️思路&#xff1a; 分为2个部分&#xff1a; 1.文字方块右下角折角 文字方块用绝对定位z-index让文字方块悬浮在右上角的位置 2.右下角折角通过before伪元素border属性实现(三角形实现方法&#xff09; &#x1f44d;核心代…

基于springboot乐器视频学习网站设计与实现(源码齐全可用)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

Redis(12)| 过期删除策略和内存淘汰策略

Redis 是可以对 key 设置过期时间的&#xff0c;因此需要有相应的机制将已过期的键值对删除&#xff0c;而做这个工作的就是过期键值删除策略。 如何设置过期时间 先说一下对 key 设置过期时间的命令。 设置 key 过期时间的命令一共有 4 个&#xff1a; expire key n&#x…

js 根据当前时间往前推15天或往后推15天等(例如当前时间往后15天后的日期。并实现今天、明天、后天、周)

本次分享&#xff0c;在项目中开发车票购买功能需要用到日期筛选 思路&#xff1a; 1、首先获取当前时间戳 2、根据当前时间戳拿到15天后的日期 3、根据天匹配星期几 4、将时间戳转换年、月、日并重组 实现代码 // 获取当前日期 const today new Date();// 往前推15天的…

畅通工程之局部最小花费问题 (C++)

目录 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; 结果 题目&#xff1a; 思路&#xff1a; 详细思路都在代码注释里 。 代码&#xff1a; #include<iostream>//无向图邻接矩阵 #include<map> #include<algorithm> #define mvnum 1005 using …

“辛巴猫舍”内网渗透、提权、撞库学习笔记

前言&#xff1a; 在拿到靶机时&#xff0c;我们最先需要做的是信息收集&#xff0c;包括不限于&#xff1a;C段扫描&#xff0c;端口探测&#xff0c;指纹识别&#xff0c;版本探测等。其次就是 漏洞挖掘、漏洞利用、提权、维持权限、日志清理、留下后门。 以上就是渗透的基本…

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测(SE注意力机制)

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09; 目录 时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描述 1.MATLA…