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

news2024/11/17 16:35:26

拓扑排序软件设计

  • 前言
  • 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. 结束语

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

水果音乐编曲软件 FL Studio v21.1.1.3750 中文免费破解版下载(附中文设置教程)

FL studio21中文别名水果编曲软件&#xff0c;是一款全能的音乐制作软件&#xff0c;包括编曲、录音、剪辑和混音等诸多功能&#xff0c;让你的电脑编程一个全能的录音室&#xff0c;它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得…

内网安全-基础设施构建-cobaltstrike远控工具beacon使用

kali在CS文件目录下&#xff0c;打开终端,运行命令&#xff1a; /teamserver 192.168.77.128 123456 在windows中双击bat文件&#xff1a; 填写图下信息&#xff1a; 双击运行&#xff0c;CS上线 自查方法&#xff1a;1、kali与物理机可互通 2、物理机与windows10跳板…

黑客技术-小白学习手册

一、黑客是什么 原是指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。但后来&#xff0c;黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实&#xff0c;网络信息空间安全已经成为海陆空之…

告别龟速,从GitHub快速下载项目的技巧分享,简单又高效!

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

【mysql】将逗号分割的字段内容转换为多行并group by

先说需求&#xff1a; 公司想让我通过mysql导出一个报表&#xff0c;内容为公司每个人参加会议的次数&#xff0c;现在有一个会议表fusion_meeting&#xff0c;正常的逻辑是通过人员直接group by就可以得出结果&#xff0c;但是我们的参会人是通过逗号分割这种方式存在一个字段…

Linux的命令——关于操作用户及用户组的命令

目录 1.Linux的命令格式 2.用户与用户组管理 2.1用户管理 添加用户 设置用户密码 删除用户 修改用户 2.2用户组管理 新增用户组 删除用户组 修改用户组属性 用户组切换 用户组管理 用户切换 1. su 2.sudo 1.Linux的命令格式 Linux系统中几乎所有操作&#xff0…

【Unity细节】Unity中如何让组件失活而不是物体失活

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

基于springboot实现协同过滤算法商品推荐系统项目【项目源码】计算机毕业设计

基于springboot实现协同过滤算法商品推荐系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备…

使用MybatisPlus时出现的java.lang.NullPointerException异常~

错误描述如下所示&#xff1a; 错误原因&#xff1a;Junit的导包错误 单元测试的包有如下所示两个 我们应该根据springboot的版本进行选择&#xff0c; 在Spring Boot 2.2.X以后使用import org.junit.jupiter.api.Test Junit5 在Spring Boot 2.2.x之前使用import org.junit.T…

U盘不可以访问的维护

u盘打不开&#xff0c;可按下图&#xff0c;设置&#xff1a;winR→gpedit.msc&#xff1b;配置“管理模板”→“系统”→“可移动存储访问”→“所有可移动存储类”。 然后&#xff0c;选择“未配置”&#xff0c;如下图

【Linux网络】系统调优之时间同步,搭建内网时间同步服务器

目录 一、时间同步是什么 二、时间同步实验 pc1的chrony配置修改&#xff1a; pc2和pc3时间同步配置一样 关于时间调整再同步回来&#xff1a;ntpdate命令 最后&#xff0c;再总结一下&#xff08;关于服务端口&#xff09;&#xff1a; 三、命令记录 一、时间同步是什…

大数据治理——为业务提供持续的、可度量的价值(一)

目录 大数据治理——为业务提供持续的、可度量的价值... 1 概述... 2 大数据治理系列... 2 第一部分&#xff1a;大数据治理统一流程模型概述和明确元数据管理策略... 2 第二部分&#xff1a;元数据集成体系结构... 15 第三部分&#xff1a;实施元数据管理... 25 第四部…

复杂度计算实例

1.常见时间复杂度计算举例 实例1 实例1基本操作执行了2N10次&#xff0c;通过推导大O阶方法知道&#xff0c;时间复杂度为 O(N) 实例2 实例2基本操作执行了MN次&#xff0c;有两个未知数M和N&#xff0c;时间复杂度为 O(NM) 实例3 实例3基本操作执行了100次&#xff0c;通过…

技术架构-单机架构

前言 从今天开始系统学习 Docker 课程&#xff0c;总结下 Docker 是什么&#xff0c;用来做什么&#xff0c;架构是怎样的。 注&#xff1a;&#xff08;1&#xff09;当浏览器 / APP访问服务器时&#xff0c;如果服务器适用的时 http 协议&#xff0c;那么默认端口时80&#…

LeetCode【33】搜索旋转排序数组

题目&#xff1a; 思路&#xff1a; https://www.cnblogs.com/CherryTab/p/12196580.html 代码&#xff1a; class Solution {int [] nums;int target;public int find_rotate_index(int left, int right) {if (nums[left] < nums[right])return 0;while (left < righ…

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

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

drawio连接线使用技巧和功能大全

drawio连接线使用技巧和功能大全 drawio是一款强大的图表绘制软件&#xff0c;支持在线云端版本以及windows, macOS, linux安装版。 如果想在线直接使用&#xff0c;则直接输入网址draw.io或者使用drawon(桌案), drawon.cn内部完整的集成了drawio的所有功能&#xff0c;并实现了…

[游戏中的图形学实时渲染技术] Part1 实时阴影技术

原理篇&#xff1a; 常见的渲染方程如下&#xff1a; 在不考虑自发光项与考虑阴影对于着色结果的影响之后可以将方程变化为如下形式&#xff1a; 如果射线在到达光源前击中了其他物体时&#xff0c;就认为这条来自光源的光线对着色点没有贡献。 利用上述渲染方程进行正确的着…

基于springboot的教学在线作业管理系统(源码+调试)

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

一文图解爬虫(spider)

—引导语 互联网&#xff08;Internet&#xff09;进化到今天&#xff0c;已然成为爬虫&#xff08;Spider&#xff09;编制的天下。从个体升级为组合、从组合联结为网络。因为有爬虫&#xff0c;我们可以更迅速地触达新鲜“网事”。 那么爬虫究竟如何工作的呢&#xff1f;允许…