压测工具开发实战篇(四)——client子窗口功能

在这里插入图片描述

你好,我是安然无虞。

文章目录

    • 树控件
    • 添加文件
      • 补充学习: 函数定义中循环体里的局部变量
      • 补充学习: 动态添加对象属性
    • 刷新文件
    • 上下文菜单 (右键菜单)
      • 实现右键菜单功能
    • 编辑节点文本

在这里插入图片描述

在学习本篇文章之前, 建议先看一下上篇介绍MDI子窗口的文章:
压测工具开发实战篇(三)——开发MDI子窗口功能

树控件

接下来, 我们想在client子窗口功能中添加 展现所选目录的代码文件的功能, 可以采用QTreeWidget树控件, 所展现的样式类似于下图:
在这里插入图片描述

说明: 我们这里暂时只实现展示的内容都是文件的功能, 以子目录的形式以后实现.

首先我们在 client.ui 子窗口中加入 QTreeWidget 树控件, 并且将对象名设置为 tree_file, 像这样:
在这里插入图片描述
OK, 有了树控件之后, 该怎么将获取到的文件显示到树控件上呢?

其实很简单, 只需要像这样改写代码即可:

# client.pyclass Client:def __init__(self):self.ui = uiLoader.load('./ui/client.ui')self.nodeIcon = qta.icon('fa5.user', color='steelBlue')# 在树控件上显示文件self.list_file_on_tree()def list_file_on_tree(self):# 清除树上所有文件self.ui.tree_file.clear()# 这里获取的文件是完整的文件路径名pyfiles = glob.glob(os.path.join(SI.projectPath, 'client/*.py'))# 隐藏标头栏self.ui.tree_file.setHeaderHidden(True)# 获取树控件的不可见根结点root = self.ui.tree_file.invisibleRootItem()for pyf in pyfiles:# 获取完整文件路径名的基本文件名fname = os.path.basename(pyf)# 准备一个树节点nodeItem = QTreeWidgetItem()# 设置节点图标nodeItem.setIcon(0, self.nodeIcon)# 设置该节点的第一个列文本nodeItem.setText(0, fname)# 设置该节点在以前的flag基础上, 多一个可编辑ItemIsEditablenodeItem.setFlags(nodeItem.flags() | Qt.ItemIsEditable)root.addChild(nodeItem)

这样之后我们看:
在这里插入图片描述
符合预期, 这里需要再补充一个功能就是: 在还没有设置项目目录的时候, 应该在日志输出文本框中显示提示信息, 并且不能打开子窗口, 只有在设置了项目目录之后才能在点击侧边按钮的时候正确打开子窗口显示对应的数据.

如果要实现上面的功能, 只需要在 main.py 文件中的 打开子窗口方法之前判断是否已经设置了当前的项目目录.

# main.pydef load_client_sub_win(self):# 打开子窗口之前需要先判断是否已经设置了当前的项目目录if not SI.projectPath:self.logInfo('请先设置项目目录')return...

这样实现之后, 我们看:
在这里插入图片描述
OK, 符合预期, 继续看接下来的内容.

添加文件

接下来我们在 client.ui 中定义工具栏, 实现 添加客户端代码和刷新客户端代码的功能:
比如我们点击添加的动作:
在这里插入图片描述
会自动帮我们显示输入对话框, 并且给出默认的文件名(并且保证文件名没有重复):

# 设置工具栏条目图标
self.ui.action_addone.setIcon(qta.icon('fa5.file'))
self.ui.action_refresh.setIcon(qta.icon('mdi.refresh'))
# 定义点击工具栏action事件处理
self.ui.action_addone.triggered.connect(self.action_addone)
self.ui.action_refresh.triggered.connect(self.list_file_on_tree)

当点击添加动作时, 会执行 action_addone 信号处理函数:

def action_addone(self):# 获得合适的初始名字for i in range(1, 1000):filename = f'client_{i}.py'if not os.path.exists(os.path.join(self.thisFolderPath, filename)):breakwhile True:# 输入对话框 - 返回值分别是输入数据 和 是否点击了 OK按钮filename, okPressed = QInputDialog.getText(self.ui, "请输入文件名字", "文件名: ",QLineEdit.Normal, filename)filename = filename.strip()if not okPressed:returnfilepath = os.path.join(self.thisFolderPath, filename)if os.path.exists(filepath):QMessageBox.warning(self.ui, "错误", f"文件{filepath}已经存在, 请重新输入!!!")else:break# 直接创建一个新文件open(filepath, 'w', encoding='utf-8')parentItem = self.ui.tree_file.invisibleRootItem()# 准备一个树节点nodeItem = QTreeWidgetItem()# 保存原始文件名 - 添加树节点对象的动态属性nodeItem._original_filename = filenamenodeItem.setIcon(0, self.nodeIcon)nodeItem.setText(0, filename)nodeItem.setFlags(nodeItem.flags() | Qt.ItemIsEditable)# 添加到树的不可见根结点下面, 成为第一层节点parentItem.addChild(nodeItem)

补充学习: 函数定义中循环体里的局部变量

问题是: 上面的代码中 for循环里 filename 的作用域, 它为什么可以作为 .getText(self.ui, "请输入文件名字", "文件名: ",QLineEdit.Normal, filename)中的参数filename, 不是出了for循环的作用域了吗?

在 for 循环中,filename 是在循环体内部被赋值的, 根据 Python 的作用域规则,如果一个变量在某个代码块(如循环体、if 块等)中被赋值,那么它会被提升到整个函数的作用域中,而不仅仅局限于该代码块.

随意对于整个函数来说:

虽然 filename 是在 for 循环中被首次赋值,但由于它是在函数 action_addone 的作用域内被赋值的,因此它是一个局部变量,可以在整个函数内部被访问.

补充学习: 动态添加对象属性

在上面的代码中, 我们在 创建树节点的时候, 紧接着就为这个新建的树节点添加属性_original_filename, 保留原始文件名, 为了方便后面修改文件名.

我们知道, 本身 QTreeWidgetItem 类没有定义 _original_filename 属性, 我们可以像上面那样直接赋值为其实例添加新的属性, 但是要注意的是最好在新建节点的时候就加上, 防止后面忘记.

所以代码就这样:

# 准备一个树节点
nodeItem = QTreeWidgetItem()
# 保存原始文件名 - 添加树节点对象的动态属性
nodeItem._original_filename = filename

这样做的好处是:

  • 当用户点击某个节点时,你可以通过 item._original_filename 获取到该节点对应的原始文件名.
  • 在处理文件操作(如打开文件、删除文件等)时,可以直接使用 _original_filename 属性来获取文件名,而不需要再从其他地方查找.

刷新文件

定义刷新信号处理方法就很简单了, 直接列出树控件上的所有文件节点即可.

def list_file_on_tree(self):# 清除树上所有文件self.ui.tree_file.clear()# 这里获取的文件是完整的文件路径名pyfiles = glob.glob(os.path.join(SI.projectPath, 'client/*.py'))# 隐藏标头栏self.ui.tree_file.setHeaderHidden(True)# 获取树控件的不可见根结点root = self.ui.tree_file.invisibleRootItem()for pyf in pyfiles:# 获取完整文件路径名的基本文件名fname = os.path.basename(pyf)# 树控件需要通过节点来组织和显示数据,而不是直接显示文件路径# 准备一个树节点nodeItem = QTreeWidgetItem()# 保存原始文件名 - 添加树节点对象的动态属性nodeItem._original_filename = fname# 设置节点图标nodeItem.setIcon(0, self.nodeIcon)# 设置该节点的第一个列文本nodeItem.setText(0, fname)# 设置该节点在以前的flag基础上, 多一个可编辑ItemIsEditablenodeItem.setFlags(nodeItem.flags() | Qt.ItemIsEditable)root.addChild(nodeItem)

上下文菜单 (右键菜单)

现在我们还需要实现右键树节点时的删除操作:
在这里插入图片描述
这就要补充学习上下文菜单 的知识了:

“上下文菜单”指的是在某个特定的上下文(如树节点)中,通过鼠标右键点击而弹出的菜单. 这种菜单通常包含与当前上下文相关的操作选项,例如在树形控件中,右键点击某个节点可能会弹出一个菜单,提供对该节点进行操作的选项,如“添加子节点”、“删除节点”、“重命名节点”等.

实现右键菜单功能

1.设置上下文菜单策略:

通过调用setContextMenuPolicy(Qt.CustomContextMenu)方法,将树形控件的上下文菜单策略设置为自定义模式.

这意味着菜单的显示和内容将由用户自己定义,而不是使用默认的系统菜单.

2.连接信号与槽:

通过customContextMenuRequested.connect(self.show_context_menu_onfiletree),将树形控件的customContextMenuRequested信号连接到一个自定义的槽函数show_context_menu_onfiletree.

当用户右键点击树形控件时,会触发这个信号,进而调用槽函数来显示自定义的上下文菜单.

# 设置树控件上下文策略 - 也可以在 Qt Designer 中设置
self.ui.tree_file.setContextMenuPolicy(Qt.CustomContextMenu)
# 定义信号处理方法
self.ui.tree_file.customContextMenuRequested.connect(self.show_context_menu_on_filetree)

我们来实现这个右键树控件时触发信号调用的槽函数:

def show_context_menu_on_filetree(self, position):"""右键树控件菜单"""tree = self.ui.tree_file# 获取当前用户点选的节点curItem = tree.currentItem()# 没有当前选中节点if not curItem:print('没有选中节点,返回')return# 创建 上下文菜单 和 菜单项Actionmenu = QMenu(tree)action_delnode = QAction("删除")action_delnode.triggered.connect(self.action_delnode)menu.addAction(action_delnode)# 在鼠标点击处展示上下文菜单menu.exec_(tree.mapToGlobal(position))

我们发现触发上面的删除动作会执行 action_delnode 方法:

# 右键菜单的删除节点方法 - 注意别忘了删除电脑中的文件
def action_delnode(self, position):tree = self.ui.tree_file# 获取当前用户点选的节点currentItem = tree.currentItem()# 真正的在电脑中把文件删掉filepath = os.path.join(self.thisFolderPath, currentItem.text(0))try:os.remove(filepath)except:pass# 在Qt界面上把文件删掉# 找到该节点的父节点parentItem = currentItem.parent()# 如果没有父节点, 就是不可见的父节点if not parentItem:parentItem = tree.invisibleRootItem()# 删除该节点parentItem.removeChild(currentItem)

编辑节点文本

如果我们要实现 双击树节点 可以编辑节点文本, 首先需要在创建节点的时候, 设置节点的flag为: 可编辑 ItemIsEditable

# 准备一个树节点
nodeItem = QTreeWidgetItem()
# 保存原始文件名 - 添加树节点对象的动态属性
nodeItem._original_filename = filenamenodeItem.setIcon(0, self.nodeIcon)
nodeItem.setText(0, filename)# 设置该节点在以前的flag基础上,多一个可编辑 ItemIsEditable
nodeItem.setFlags(nodeItem.flags() | Qt.ItemIsEditable)

树节点文本被编辑时会触发ItemChanged信号:

# 树节点文本被编辑后会触发ItemChanged信号
self.ui.tree_file.itemChanged.connect(self.item_changed)

对这个信号进行处理:

def item_changed(self, item, column):"""对文件重命名"""new_name = item.text(column)# 这个时候就用到了之前为树节点添加的属性_original_filename属性,保存原文件名original_name = item._original_filenameoriginal_path = os.path.join(self.thisFolderPath, original_name)new_path = os.path.join(self.thisFolderPath, new_name)# 检查新文件名是否已经存在if os.path.exists(new_path):QMessageBox.warning(self.ui, "错误", f"文件{new_path}已经存在, 请重新输入!!!")# 注意需要将名字改为原来的名字item.setText(column, original_name)else:# 如果文件名合法, 更新原始文件名属性item._original_filename = new_name# 进行文件重命名操作os.rename(original_path, new_path)
遇见安然遇见你,不负代码不负卿。
谢谢老铁的时间,咱们下篇再见!

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

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

相关文章

PyTorch使用(4)-张量拼接操作

文章目录 张量拼接操作1. torch.cat 函数的使用1.1. torch.cat 定义1.2. 语法1.3. 关键规则 1.4. 示例代码1.4.1. 沿行拼接(dim0)1.4.2. 沿列拼接(dim1)1.4.3. 高维拼接(dim2) 1.5. 错误场景分析1.5.1. 维度…

linux命令之yes(Linux Command Yes)

linux命令之yes 简介与功能 yes 命令在 Linux 系统中用于重复输出一行字符串,直到被杀死(kill)。该命令最常见的用途是自动化控制脚本中的交互式命令,以便无需用户介入即可进行连续的确认操作。 用法示例 基本用法非常简单&am…

《算法笔记》10.3小节——图算法专题->图的遍历 问题 B: 连通图

题目描述 给定一个无向图和其中的所有边&#xff0c;判断这个图是否所有顶点都是连通的。 输入 每组数据的第一行是两个整数 n 和 m&#xff08;0<n<1000&#xff09;。n 表示图的顶点数目&#xff0c;m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据…

使用Prometheus监控systemd服务并可视化

实训背景 你是一家企业的运维工程师&#xff0c;需将服务器的systemd服务监控集成到Prometheus&#xff0c;并通过Grafana展示实时数据。需求如下&#xff1a; 数据采集&#xff1a;监控所有systemd服务的状态&#xff08;运行/停止&#xff09;、资源占用&#xff08;CPU、内…

OpenCV--图像边缘检测

在计算机视觉和图像处理领域&#xff0c;边缘检测是极为关键的技术。边缘作为图像中像素值发生急剧变化的区域&#xff0c;承载了图像的重要结构信息&#xff0c;在物体识别、图像分割、目标跟踪等众多应用场景中发挥着核心作用。OpenCV 作为强大的计算机视觉库&#xff0c;提供…

Rollup详解

Rollup 是一个 JavaScript 模块打包工具&#xff0c;专注于 ES 模块的打包&#xff0c;常用于打包 JavaScript 库。下面从它的工作原理、特点、使用场景、配置和与其他打包工具对比等方面进行详细讲解。 一、 工作原理 Rollup 的核心工作是分析代码中的 import 和 export 语句…

Chapter 7: Compiling C++ Sources with CMake_《Modern CMake for C++》_Notes

Chapter 7: Compiling C Sources with CMake 1. Understanding the Compilation Process Key Points: Four-stage process: Preprocessing → Compilation → Assembly → LinkingCMake abstracts low-level commands but allows granular controlToolchain configuration (c…

5分钟上手GitHub Copilot:AI编程助手实战指南

引言 近年来&#xff0c;AI编程工具逐渐成为开发者提升效率的利器。GitHub Copilot作为由GitHub和OpenAI联合推出的智能代码补全工具&#xff0c;能够根据上下文自动生成代码片段。本文将手把手教你如何快速安装、配置Copilot&#xff0c;并通过实际案例展示其强大功能。 一、…

谢志辉和他的《韵之队诗集》:探寻生活与梦想交织的诗意世界

大家好&#xff0c;我是谢志辉&#xff0c;一个扎根在文字世界&#xff0c;默默耕耘的写作者。写作于我而言&#xff0c;早已不是简单的爱好&#xff0c;而是生命中不可或缺的一部分。无数个寂静的夜晚&#xff0c;当世界陷入沉睡&#xff0c;我独自坐在书桌前&#xff0c;伴着…

Logo语言的死锁

Logo语言的死锁现象研究 引言 在计算机科学中&#xff0c;死锁是一个重要的研究课题&#xff0c;尤其是在并发编程中。它指的是两个或多个进程因争夺资源而造成的一种永久等待状态。在编程语言的设计与实现中&#xff0c;如何避免死锁成为了优化系统性能和提高程序可靠性的关…

深入理解矩阵乘积的导数:以线性回归损失函数为例

深入理解矩阵乘积的导数&#xff1a;以线性回归损失函数为例 在机器学习和数据分析领域&#xff0c;矩阵微积分扮演着至关重要的角色。特别是当我们涉及到优化问题&#xff0c;如最小化损失函数时&#xff0c;对矩阵表达式求导变得必不可少。本文将通过一个具体的例子——线性…

real_time_camera_audio_display_with_animation

视频录制 import cv2 import pyaudio import wave import threading import os import tkinter as tk from PIL import Image, ImageTk # 视频录制设置 VIDEO_WIDTH = 640 VIDEO_HEIGHT = 480 FPS = 20.0 VIDEO_FILENAME = _video.mp4 AUDIO_FILENAME = _audio.wav OUTPUT_…

【Pandas】pandas DataFrame astype

Pandas2.2 DataFrame Conversion 方法描述DataFrame.astype(dtype[, copy, errors])用于将 DataFrame 中的数据转换为指定的数据类型 pandas.DataFrame.astype pandas.DataFrame.astype 是一个方法&#xff0c;用于将 DataFrame 中的数据转换为指定的数据类型。这个方法非常…

Johnson

理论 全源最短路算法 Floyd 算法&#xff0c;时间复杂度为 O(n)跑 n 次 Bellman - Ford 算法&#xff0c;时间复杂度是 O(nm)跑 n 次 Heap - Dijkstra 算法&#xff0c;时间复杂度是 O(nmlogm) 第 3 种算法被 Johnson 做了改造&#xff0c;可以求解带负权边的全源最短路。 J…

Exce格式化批处理工具详解:高效处理,让数据更干净!

Exce格式化批处理工具详解&#xff1a;高效处理&#xff0c;让数据更干净&#xff01; 1. 概述 在数据分析、报表整理、数据库管理等工作中&#xff0c;数据清洗是不可或缺的一步。原始Excel数据常常存在格式不统一、空值、重复数据等问题&#xff0c;影响数据的准确性和可用…

(三十七)Dart 中使用 Pub 包管理系统与 HTTP 请求教程

Dart 中使用 Pub 包管理系统与 HTTP 请求教程 Pub 包管理系统简介 Pub 是 Dart 和 Flutter 的包管理系统&#xff0c;用于管理项目的依赖。通过 Pub&#xff0c;开发者可以轻松地添加、更新和管理第三方库。 使用 Pub 包管理系统 1. 找到需要的库 访问以下网址&#xff0c…

代码随想录算法训练营第三十五天 | 416.分割等和子集

416. 分割等和子集 题目链接&#xff1a;416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划之背包问题&#xff0c;这个包能装满吗&#xff1f;| LeetCode&#xff1a;416.分割等和子集_哔哩哔哩_bilibi…

HTTP 教程 : 从 0 到 1 全面指南 教程【全文三万字保姆级详细讲解】

目录 HTTP 的请求-响应 HTTP 方法 HTTP 状态码 HTTP 版本 安全性 HTTP/HTTPS 简介 HTTP HTTPS HTTP 工作原理 HTTPS 作用 HTTP 与 HTTPS 区别 HTTP 消息结构 客户端请求消息 服务器响应消息 实例 HTTP 请求方法 各个版本定义的请求方法 HTTP/1.0 HTTP/1.1 …

spring功能汇总

1.创建一个dao接口&#xff0c;实现类&#xff1b;service接口&#xff0c;实现类并且service里用new创建对象方式调用dao的方法 2.使用spring分别获取dao和service对象(IOC) 注意 2中的service里面获取dao的对象方式不用new的(DI) 运行测试&#xff1a; 使用1的方式创建servic…

Vue.js 实现下载模板和导入模板、数据比对功能核心实现。

在前端开发中&#xff0c;数据比对是一个常见需求&#xff0c;尤其在资产管理等场景中。本文将基于 Vue.js 和 Element UI&#xff0c;通过一个简化的代码示例&#xff0c;展示如何实现“新建比对”和“开始比对”功能的核心部分。 一、功能简介 我们将聚焦两个核心功能&…