|
文章目錄
- 樹控件
- 添加文件
- 補充學習: 函數定義中循環體里的局部變量
- 補充學習: 動態添加對象屬性
- 刷新文件
- 上下文菜單 (右鍵菜單)
- 實現右鍵菜單功能
- 編輯節點文本

在學習本篇文章之前, 建議先看一下上篇介紹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)
|
|