PySide6 Win10记事本从零到一——第十章 纯文本编辑自定义上下文菜单界面与功能实现
本章介绍了如何通过自定义上下文菜单实现类似Win10记事本的功能。主要内容包括: 讲解了上下文菜单的基本概念和实现方法,包括设置菜单策略和信号连接 分析了Win10记事本上下文菜单的界面结构 提供了完整的代码实现,包括: 自定义上下文菜单类(ContentMenu) 自定义纯文本编辑类(PlainTextEdit) 主窗口实现(NotepadMain) 指出功能分为两类:更新行为状态和行为触发,并
文章目录
第十章 纯文本编辑自定义上下文菜单界面与功能实现
在这一章我们会学习如何通过自定义上下文菜单来实现和win10上下文菜单相似的效果和功能。
效果演示:后续会陆续更新
学习完本章,你将学会或了解如下知识:
- 默认上下文菜单的实现和调用
- 实现自定义上下文菜单
- 使用默认浏览器的
Bing搜索引擎搜索内容
10.1 重点知识
关于自定义上下文菜单我们需要知道如下:
- 是什么?
- 怎么实现?
上下文菜单是右键单击应用界面某处(或按下快捷键,例如 Windows 上的 Shift + F10)时出现的弹出菜单。
关于实现我们需要了解如下:
- QWidget.setContextMenuPolicy()方法:基础窗口,设置上下文菜单策略
- PySide6.QtCore.Qt.ContextMenuPolicy: 枚举类型,上下文菜单策略
- QWidget.customContextMenuRequested(pos)信号:当1设置为自定义上下文菜单时会触发此信号
简单解释一下:
首先,基于QWidget的窗口触发上下文菜单的时候,如果不做修改就是使用默认的上下文菜单(Qt.DefaultContextMenu),这时会调用contextMenuEvent(event)槽函数。
想要显示自定义的上下文菜单:
- 先设置上下文菜单策略为: 自定义菜单(
Qt.CustomContextMenu) - 自定义上下文菜单请求信号连接好自定义的上下文菜单槽函数
注意: 继承自
QWidget的QAbstractScrollArea及其子类无法使用上诉方法实现。
上下文菜单策略与对应意义一览表:
| 策略 | 意义 |
|---|---|
Qt.NoContextMenu |
小部件没有上下文菜单,上下文菜单处理将推迟到小部件的父级。 |
Qt.PreventContextMen |
该小组件没有上下文菜单,并且与 NoContextMenu 相比,处理不会延迟到小组件的父级。这意味着所有鼠标右键事件都保证通过 QWidget.mousePressEvent() 和 QWidget.mouseReleaseEvent() 传递到小部件本身。 |
Qt.DefaultContextMenu |
默认上下文菜单,调用小部件的 QWidget.contextMenuEvent()处理程序 |
Qt.ActionsContextMenu |
Actions 上下文菜单,小部件将其 QWidget.actions() 显示为上下文菜单。 |
Qt.CustomContextMenu |
自定义上下文菜单,该小部件发出 QWidget.customContextMenuRequested() 信号。 |
注意: 部分版本比如PySide6.10可能需要全称才能使用,如
Qt.ContextMenuPolicy.CustomContextMenu
10.1.1 简单实现
from PySide6.QtWidgets import QApplication, QWidget, QMenu
from PySide6.QtCore import Qt,QPoint
import sys
class MyWidget(QWidget):
def __init__(self):
super().__init__()
# 设置上下文菜单策略 为 自定义上下文菜单
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.set_event_bind()
def set_event_bind(self):
"""设置事件绑定
"""
# 自定义上下文菜单请求信号 连接 自定义上下文菜单 槽函数
self.customContextMenuRequested.connect(self.show_custom_content_menu)
def show_custom_content_menu(self, pos:QPoint):
"""显示自定义上下文菜单
:param pos: customContextMenuRequested 传出的 QPoint 位置
"""
menu = QMenu(self)
menu.addAction("123")
menu.addAction("321")
menu.exec(pos)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWidget()
window.show()
app.exec()
10.2 界面分析
关于win10记事本的上下文菜单:
基本和菜单项差不多
注意:尽管笔者已经努力而且
python for qt也够强大,但是还是存在不足之处:从全选到使用Bing搜索之间的暂时没办法实现。
10.2.1 界面实现
这里将使用俩个自定义类:自定义上下文菜单类与自定义纯文本编辑类。
因为是类实现,所以事件绑定就成为了一个问题,这也是代码责任划分的意义。因为上下文菜单是基于纯文本编辑来实现,所以我们就在自定义纯文本编辑类里实现事件绑定。
代码如下:
custome_content_menu.py
from PySide6.QtWidgets import QMenu
class ContentMenu(QMenu):
"""上下文菜单
:param QMenu: PySide6 菜单类
"""
def __init__(self,parent=None):
"""初始化"""
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""设置界面"""
self.addAction("撤销(&U)")
self.addSeparator()
self.addAction("剪切(&T)")
self.addAction("复制(&C)")
self.addAction("粘贴(&P)")
self.addAction("删除(&D)")
self.addSeparator()
self.addAction("全选(&A)")
self.addSeparator()
self.addAction("使用Bing搜索(&B)")
custome_plaintext_edit.py
from PySide6.QtWidgets import QPlainTextEdit
from PySide6.QtCore import QPoint,Qt
from custome_content_menu import ContentMenu
class PlainTextEdit(QPlainTextEdit):
"""自定义纯文本编辑
:param QPlainTextEdit: PySide6 纯文本编辑
"""
def __init__(self):
"""初始化"""
super().__init__()
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_custome_content_menu)
def show_custome_content_menu(self,pos: QPoint):
"""显示自定义上下文菜单
:param pos: customContextMenuRequested 传出的 QPoint 位置
"""
menu = ContentMenu(self)
menu.exec(pos)
notepad_main.py
from PySide6.QtWidgets import QMainWindow,QFrame,QApplication
from custome_plaintext_edit import PlainTextEdit
import sys
class NotepadMain(QMainWindow):
"""记事本主界面
:param QMainWindow: 主窗口
"""
def __init__(self):
"""初始化"""
super().__init__()
self.setup_ui()
def setup_ui(self):
"""设置用户界面"""
# 设置窗口标题
self.setWindowTitle("无标题 - 记事本")
# 设置窗口大小
self.resize(800, 500)
# 创建菜单栏 设置为私有属性
self.__menubar = self.menuBar()
# 示例化纯文本编辑
self.__plain_text_edit = PlainTextEdit()
# 消除框线
self.__plain_text_edit.setFrameShape(QFrame.Shape.NoFrame)
# 添加纯文本编辑到 中心窗口
self.setCentralWidget(self.__plain_text_edit)
# 添加状态栏目
self.status_bar = self.statusBar()
if __name__ == "__main__":
app = QApplication(sys.argv)
# 添加其他菜单
notepad_main = NotepadMain()
notepad_main.show()
sys.exit(app.exec())
10.3 功能分析
功能分为俩类:
- 更新行为状态
- 行为触发
1主要就是撤销、剪切、复制、粘贴、删除、全选 这些行为默认不可用,使用Bing搜索默认就是可用的。
2除使用Bing搜索以外都有定义好的函数
10.3.1 更新行为状态
更新行为状态分析
更新策略:
- 撤销: 撤销可用(
QPlainTextEdit.undoAvailable)信号 -连接- 行为对应的setEnabled函数 - 剪切、复制、删除:都是有选中文本=> 复制可用(
QPlainTextEdit.copyAvailable)信号 -连接- 行为对应的setEnabled函数 - 粘贴: 根据剪贴板是否有内容来实现 => 数据改变(
clipbaord.dataChanged)信号(还需要自定义槽函数) - 全选:根据文本编辑是否有内容来实现 =>自定义信号(Signal)来判断是否有文本
- 使用
Bing搜索: 默认可用
注意:
undoAvailable与copyAvailable信号会发射一个是否可用的布尔值正好与setEnabled需要的值对应,所以无需传入参数。
注意: 剪贴板需要使用
QGuiApplication.clipboard()来实例化
如何更新粘贴行为的状态? 通过判断剪贴板是否有内容并更新行为状态来实现。
def reset_paste_state(self):
"""重新设置粘贴状态"""
# 获取剪贴板文本
clipbaord_text = self.clipbaord_.text()
# 非空可用 为空不可用
if clipbaord_text:
self.menu.paste_action.setEnabled(True)
else:
self.menu.paste_action.setEnabled(False)
如何自定义文本信号来检测纯文本编辑是否有内容?见代码
from PySide6.QtWidgets import QPlainTextEdit
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QPoint,Qt,Signal
class PlainTextEdit(QPlainTextEdit):
"""自定义纯文本编辑
:param QPlainTextEdit: PySide6 纯文本编辑
"""
hasText = Signal(bool)
def __init__(self):
"""初始化"""
super().__init__()
self.textChanged.connect(self.__has_text)
def __has_text(self) -> bool:
"""判断是否有文本
"""
self.hasText.emit(True) if self.toPlainText() else self.hasText.emit(False)
更新行为状态代码
custome_content_menu.py
from PySide6.QtWidgets import QMenu
class ContentMenu(QMenu):
"""上下文菜单
:param QMenu: PySide6 菜单类
"""
def __init__(self,parent=None):
"""初始化"""
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
"""设置界面"""
self.undo_action = self.addAction("撤销(&U)")
self.addSeparator()
self.cut_action = self.addAction("剪切(&T)")
self.copy_action = self.addAction("复制(&C)")
self.paste_action = self.addAction("粘贴(&P)")
self.delete_action = self.addAction("删除(&D)")
self.addSeparator()
self.select_all = self.addAction("全选(&A)")
self.addSeparator()
self.addAction("使用Bing搜索(&B)")
# 默认 撤销 剪切 复制 粘贴 删除 全选 不可用
self.undo_action.setEnabled(False)
self.cut_action.setEnabled(False)
self.copy_action.setEnabled(False)
self.paste_action.setEnabled(False)
self.delete_action.setEnabled(False)
self.select_all.setEnabled(False)
custome_plaintext_edit.py
from PySide6.QtWidgets import QPlainTextEdit
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QPoint,Qt,Signal
from custome_content_menu import ContentMenu
class PlainTextEdit(QPlainTextEdit):
"""自定义纯文本编辑
:param QPlainTextEdit: PySide6 纯文本编辑
"""
hasText = Signal(bool)
def __init__(self):
"""初始化"""
super().__init__()
self.__init_signal_event()
self.__init_content_menu_event()
# 初始化一个 剪贴板
self.clipbaord_ = QGuiApplication.clipboard()
self.menu = ContentMenu(self)
def __init_signal_event(self):
"""初始化"""
self.textChanged.connect(self.__has_text)
def __has_text(self) -> bool:
"""判断是否有文本
"""
self.hasText.emit(True) if self.toPlainText() else self.hasText.emit(False)
def __init_content_menu_event(self):
"""初始化上下文菜单"""
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_custome_content_menu)
def show_custome_content_menu(self,pos: QPoint):
"""显示自定义上下文菜单
:param pos: customContextMenuRequested 传出的 QPoint 位置
"""
self.menu.exec(pos)
def set_event_bind(self):
"""设置事件绑定"""
# 设置状态 撤销、剪切、复制、粘贴、删除、全选
self.undoAvailable.connect(self.menu.undo_action.setEnabled)
self.copyAvailable.connect(self.menu.cut_action.setEnabled)
self.copyAvailable.connect(self.menu.copy_action.setEnabled)
self.clipbaord_.dataChanged.connect(self.reset_paste_state)
self.copyAvailable.connect(self.menu.delete_action.setEnabled)
self.hasText.connect(self.menu.select_all.setEnabled)
def reset_paste_state(self):
"""重新设置粘贴状态"""
clipbaord_text = self.clipbaord_.text()
if clipbaord_text:
self.menu.paste_action.setEnabled(True)
else:
self.menu.paste_action.setEnabled(False)
notepad_main.py不做修改
10.3.2 行为触发
行为触发分析
撤销、剪切、复制、粘贴、删除、全选对应的自带方法如下:
- 撤销:
QPlainTextEdit.undo() - 剪切:
QPlainTextEdit.cut() - 复制:
QPlainTextEdit.copy() - 粘贴:
QPlainTextEdit.paste() - 删除:
QPlainTextEdit.clear() - 全选:
QPlainTextEdit.selectAll()
注意: 这也解释了为什么菜单命名,使用的是有
_action后缀的,不然的话容易混淆
使用Bing搜索如何实现?
- 使用默认浏览器访问网址
这里需要引入另一个知识QDesktopServices提供了访问常见桌面服务的方法,比如我们要用到的openUrl()方法(openUrl()方法传入一个网页链接然后调用系统默认浏览器打开该链接。) - 使用Bing搜索引擎
使用https://cn.bing.com/search?q=%s链接并传入搜索内容(%s) - 怎么搜索选中文本?
a. 获取选中文本?QPlainTextEdit.textCursor().selectedText()可以获取选中文本
b. 如何将文本传入Url? f-str格式化字符串 - 信号触发后直接连接
bing_search槽函数即可
代码实现
def bing_search(self):
"""使用Bing搜索"""
search_text = self.textCursor().selectedText()
QDesktopServices.openUrl(f"https://cn.bing.com/search?q={search_text}")
行为触发代码
custome_content_menu.py 修改如下:
## setup_ui 设置界面里
# 原 self.addAction("使用Bing搜索(&B)")
self.bing_search_action = self.addAction("使用Bing搜索(&B)") # 修改后
custome_plaintext_edit.py修改如下:
## set_event_bind 设置事件绑定里 最下边 添加
self.menu.bing_search_action.triggered.connect(self.bing_search)
## 添加一个类方法
def bing_search(self):
"""Bing搜索"""
search_text = self.textCursor().selectedText()
QDesktopServices.openUrl(f"https://cn.bing.com/search?q={search_text}")
notepad_main.py不做修改
更多推荐


所有评论(0)