构建一个功能丰富的记事本应用
在现代软件开发中,记事本应用是一个经典的入门项目,它不仅能够帮助开发者熟悉 GUI 编程,还能在实践中掌握事件处理、文件操作等核心概念。本文将详细介绍如何使用 PyQt5 构建一个功能丰富的记事本应用,包括新建、打开、保存文件,以及字体大小调整等高级功能。
一、项目概述
我们的记事本应用将包含以下核心功能:
- 文件操作:新建、打开、保存、另存为、退出。
- 文本编辑:支持基本的文本编辑功能,包括字体大小调整。
- 状态显示:在状态栏显示当前文件状态,如是否已保存。
- 语言模式:支持多种语言模式,如 Python、Java、HTML 等,以便于代码高亮。
二、环境搭建
在开始编码之前,确保你的 Python 环境中已安装 PyQt5。如果未安装,可以通过以下命令进行安装:
pip install PyQt5
三、核心组件实现
1. 主窗口 Notebook
主窗口类 Notebook 负责初始化整个应用的 UI 组件,包括菜单栏、工具栏、编辑区和状态栏。
class Notebook(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("简易记事本")
self.resize(800, 600)
self.languageModeList = {
"xb": "qt记事本",
"txt": "txt",
"py": "python",
"java": "Java",
"js": "Javascript",
"html": "HTML",
"css": "CSS",
"cpp": "C++",
"c": "C",
"hpp": "C++",
"h": "C++",
}
self.languageMode = "qt记事本"
self.currentFilePath = "未命名1.xb"
self.hasSaved = False
self.languageLabel = QLabel(text=f"当前语言: {self.languageMode}", parent=self)
self.addController()
self.addShortCut()
2. 编辑区 CustomTextEdit
编辑区是记事本应用的核心,我们通过继承 QTextEdit 来创建 CustomTextEdit 类,增加了字体大小调整的功能。
class CustomTextEdit(QTextEdit):
def __init__(self):
super().__init__()
self.font_size = 14
self.max_font_size = 30
self.min_font_size = 8
self.canCtrl = True
self.setFontPointSize(self.font_size)
def wheelEvent(self, event: QWheelEvent):
modifiers = event.modifiers()
if modifiers == Qt.ControlModifier and self.canCtrl:
delta = event.angleDelta().y()
new_size = max(self.min_font_size, min(self.max_font_size, self.currentFont().pointSize() + delta // 120))
str = self.toPlainText()
self.clear()
self.setFontPointSize(new_size)
self.setPlainText(str)
else:
super().wheelEvent(event)
def setFontPointSize(self, size):
font = self.currentFont()
font.setPointSize(size)
self.setCurrentFont(font)
self.update()
3. 菜单和工具栏
菜单和工具栏为用户提供了便捷的文件操作入口。我们创建了一个工具栏和一个文件菜单,用于文件操作。
def addController(self):
toolBar = QToolBar(self)
self.file_menu = QMenu("文件(F)", self)
newfileAction = QAction("新建(N)", self)
openfileAction = QAction("打开(O)", self)
savefileAction = QAction("保存(S)", self)
saveAsfileAction = QAction("另存为(A)", self)
exitAction = QAction("退出(X)", self)
self.file_menu.addActions([newfileAction, openfileAction, savefileAction, saveAsfileAction])
self.file_menu.addSeparator()
self.file_menu.addAction(exitAction)
file_menu_button = QPushButton("文件(F)", self)
file_menu_button.setMenu(self.file_menu)
toolBar.addWidget(file_menu_button)
self.addToolBar(toolBar)
self.w = CustomTextEdit()
self.w.setPlaceholderText("请输入内容...")
self.w.document().contentsChanged.connect(self.checkModified)
self.setCentralWidget(self.w)
self.createStatusBar()
self.connectActions(newfileAction, openfileAction, savefileAction, saveAsfileAction, exitAction)
4. 状态栏
状态栏显示当前文件的路径和状态,如是否已保存。
def createStatusBar(self):
statusBar = QStatusBar(self)
statusBar.addWidget(QPushButton(text="状态栏", parent=statusBar))
statusBar.addWidget(self.languageLabel)
self.file_name_label = QLabel(text=self.currentFilePath if self.hasSaved else self.currentFilePath + " *", parent=statusBar)
statusBar.addWidget(self.file_name_label)
statusBar.setStyleSheet("QPushButton,QLabel{border: none;background-color: rgb(0,0,0);color: white;font-size: 14px;font-weight: bold;padding: 5px;} QStatusBar{border: 1px solid #C0C0C0;background-color: rgb(0,0,0)}")
self.setStatusBar(statusBar)
5. 文件操作
文件操作是记事本应用的基础功能,包括新建文件、打开文件、保存文件和另存为。
def saveFile(self):
try:
with open(self.currentFilePath, "w", encoding="utf-8") as file:
if self.languageMode == "qt记事本":
file.write(self.w.toHtml())
else:
file.write(self.w.toPlainText())
self.hasSaved = True
self.file_name_label.setText(self.currentFilePath)
QMessageBox.information(self, "提示", "文件已保存")
except Exception as e:
QMessageBox.critical(self, "错误", f"无法保存文件: {e}")
def saveAsFile(self):
options = QFileDialog.Options()
file_name, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "qt记事本文件(*.xb);;所有文件 (*)", options=options)
if file_name:
self.currentFilePath = file_name
self.saveFile()
def newFile(self):
self.w.clear()
self.hasSaved = False
self.file_name_label.setText(self.currentFilePath + " *")
self.w.canCtrl = False
self.setLanguageMode("qt记事本")
self.w.setPlaceholderText("请输入内容...")
def openFile(self):
options = QFileDialog.Options()
file_name, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "所有文件 (*);;qt记事本文件 (*.xb);;文本文件 (*.txt);;python文件 (*.py);;java文件 (*.java);;javascript文件 (*.js);;html文件 (*.html);;css文件 (*.css);;C++文件 (*.cpp *.hpp *.h)", options=options)
if file_name:
self.currentFilePath = file_name
suffix = file_name[file_name.rfind(".") + 1 :]
self.setLanguageMode(self.languageModeList.get(suffix, "qt记事本"))
try:
with open(file_name, "r", encoding="utf-8") as file:
if suffix == "xb":
self.w.setHtml(file.read())
else:
self.w.canCtrl = True
self.w.setPlainText(file.read())
except Exception as e:
QMessageBox.critical(self, "错误", f"无法打开文件: {e}")
四、扩展功能
为了使记事本应用更加强大,我们可以考虑添加以下扩展功能:
- 文本格式化:提供文本加粗、斜体、下划线等格式化选项。
- 查找和替换:实现文本查找和替换功能,提高编辑效率。
- 撤销和重做:支持撤销和重做操作,方便用户编辑文本。
- 代码高亮:根据不同的语言模式,实现代码高亮显示。
五、结论
通过本文的介绍,我们成功构建了一个功能丰富的记事本应用。这个应用不仅包括了基本的文件操作,还支持字体大小调整和多种语言模式。你可以在此基础上继续扩展,增加更多实用功能,使其成为一个更加完善的文本编辑工具。
希望这篇文章能帮助你更好地理解 PyQt5 的应用开发,为你的项目开发提供参考和启发。如果你有任何问题或想要进一步扩展这个项目,欢迎在评论区留言讨论。
六、完整代码
customTextEdit.py
import sys
from tkinter import font
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class CustomTextEdit(QTextEdit):
def __init__(self):
super().__init__()
self.font_size = 14 # 初始字体大小
self.max_font_size = 30 # 最大字体大小
self.min_font_size = 8 # 最小字体大小
self.canCtrl = True # 是否可以修改字体大小
self.setFontPointSize(self.font_size) # 应用初始字体大小
def wheelEvent(self, event: QWheelEvent):
print("wheelEvent", event.modifiers(), event.angleDelta().y())
# 获取当前的修饰符
modifiers = event.modifiers()
if modifiers == Qt.ControlModifier and self.canCtrl:
print("ControlModifier")
# 仅按下 Control 键,修改显示大小
delta = event.angleDelta().y()
new_size = max(
self.min_font_size,
min(self.max_font_size, self.currentFont().pointSize() + delta // 120),
)
str = self.toPlainText()
self.clear()
self.setFontPointSize(new_size) # 设置新字体大小
self.setPlainText(str)
# 检查是否按下 Ctrl 和 Shift 键
if (modifiers & (Qt.ControlModifier | Qt.ShiftModifier)) == (
Qt.ControlModifier | Qt.ShiftModifier
):
# 同时按下了 Control 和 Shift 修饰符,修改输入文字大小
delta = event.angleDelta().y()
if delta > 0:
self.font_size += 1 # 增加字体大小
elif delta < 0:
self.font_size -= 1 # 减小字体大小
if self.font_size < 1: # 限制最小字体大小
self.font_size = 1
self.setFontPointSize(self.font_size) # 更新字体大小
self.update() # 刷新控件的显示
else:
super().wheelEvent(event) # 调用基类的事件处理
def setFontPointSize(self, size):
font = self.currentFont()
font.setPointSize(size) # 设置字体大小
self.setCurrentFont(font) # 应用字体
self.update() # 刷新控件的显示
if __name__ == "__main__":
app = QApplication(sys.argv)
w = CustomTextEdit()
w.show()
sys.exit(app.exec_())
Notebook.py
from PyQt5.QtWidgets import *
import CustomTextEdit
class Notebook(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("简易记事本")
self.resize(800, 600)
# 语言模式列表
self.languageModeList = {
"xb": "qt记事本",
"txt": "txt",
"py": "python",
"java": "Java",
"js": "Javascript",
"html": "HTML",
"css": "CSS",
"cpp": "C++",
"c": "C",
"hpp": "C++",
"h": "C++",
}
self.languageMode = "qt记事本" # 默认语言模式
self.currentFilePath = "未命名1.xb" # 默认文件路径
self.hasSaved = False # 是否已保存标志
self.languageLabel = QLabel(text=f"当前语言: {self.languageMode}", parent=self)
self.languageLabel.setStyleSheet(
"QLabel{color: white;font-size: 14px;font-weight: bold;padding: 5px;}"
)
self.addController() # 添加组件
self.addShortCut() # 添加快捷键
def addController(self):
# 创建工具栏
toolBar = QToolBar(self)
toolBar.setMovable(True)
# 创建文件菜单
self.file_menu = QMenu("文件(F)", self)
self.file_menu.setStyleSheet(
"QMenu{background-color: rgb(255,255,255);color: black;font-size: 14px;font-weight: bold;padding: 5px;}"
)
# 创建菜单项
newfileAction = QAction("新建(N)", self)
openfileAction = QAction("打开(O)", self)
savefileAction = QAction("保存(S)", self)
saveAsfileAction = QAction("另存为(A)", self)
exitAction = QAction("退出(X)", self)
# 添加动作到菜单
self.file_menu.addActions(
[newfileAction, openfileAction, savefileAction, saveAsfileAction]
)
self.file_menu.addSeparator()
self.file_menu.addAction(exitAction)
# 创建菜单按钮并添加到工具栏
file_menu_button = QPushButton("文件(F)", self)
file_menu_button.setStyleSheet(
"QPushButton{border: none;color: black;font-size: 14px;font-weight: bold;padding: 5px;}"
)
file_menu_button.setMenu(self.file_menu)
toolBar.addWidget(file_menu_button)
self.addToolBar(toolBar)
# 创建中心编辑区
self.w = CustomTextEdit.CustomTextEdit()
self.w.setPlaceholderText("请输入内容...")
self.w.document().contentsChanged.connect(self.checkModified)
self.setCentralWidget(self.w)
# 创建状态栏
self.createStatusBar()
# 信号与槽的连接
self.connectActions(
newfileAction, openfileAction, savefileAction, saveAsfileAction, exitAction
)
def createStatusBar(self):
statusBar = QStatusBar(self)
statusBar.addWidget(QPushButton(text="状态栏", parent=statusBar))
statusBar.addWidget(self.languageLabel)
self.file_name_label = QLabel(
text=self.currentFilePath if self.hasSaved else self.currentFilePath + " *",
parent=statusBar,
)
statusBar.addWidget(self.file_name_label)
statusBar.setStyleSheet(
"QPushButton,QLabel{border: none;background-color: rgb(0,0,0);color: white;font-size: 14px;font-weight: bold;padding: 5px;} QStatusBar{border: 1px solid #C0C0C0;background-color: rgb(0,0,0)}"
)
self.setStatusBar(statusBar)
def connectActions(
self,
newfileAction,
openfileAction,
savefileAction,
saveAsfileAction,
exitAction,
):
openfileAction.triggered.connect(self.openFile)
savefileAction.triggered.connect(self.saveFile)
saveAsfileAction.triggered.connect(self.saveAsFile)
newfileAction.triggered.connect(self.newFile)
exitAction.triggered.connect(self.close)
def addShortCut(self):
self.file_menu.actions()[0].setShortcut("Ctrl+N")
self.file_menu.actions()[1].setShortcut("Ctrl+O")
self.file_menu.actions()[2].setShortcut("Ctrl+S")
self.file_menu.actions()[3].setShortcut("Ctrl+A")
self.file_menu.actions()[4].setShortcut("Ctrl+X")
def checkModified(self):
self.hasSaved = not self.w.document().isModified()
self.file_name_label.setText(
self.currentFilePath + (" *" if not self.hasSaved else "")
)
def setLanguageMode(self, languageMode):
self.languageMode = languageMode
self.languageLabel.setText(f"当前语言: {self.languageMode}")
def saveFile(self):
try:
with open(self.currentFilePath, "w", encoding="utf-8") as file:
if self.languageMode == "qt记事本":
file.write(self.w.toHtml())
else:
file.write(self.w.toPlainText())
self.hasSaved = True
self.file_name_label.setText(self.currentFilePath)
QMessageBox.information(self, "提示", "文件已保存")
except Exception as e:
QMessageBox.critical(self, "错误", f"无法保存文件: {e}")
def saveAsFile(self):
options = QFileDialog.Options()
file_name, _ = QFileDialog.getSaveFileName(
self, "保存文件", "", "qt记事本文件(*.xb);;所有文件 (*)", options=options
)
if file_name:
self.currentFilePath = file_name
self.saveFile() # 直接调用保存文件的方法
def newFile(self):
self.w.clear()
self.hasSaved = False
self.file_name_label.setText(self.currentFilePath + " *")
self.w.canCtrl = False
self.setLanguageMode("qt记事本")
self.w.setPlaceholderText("请输入内容...")
def openFile(self):
options = QFileDialog.Options()
file_name, _ = QFileDialog.getOpenFileName(
self,
"打开文件",
"",
"所有文件 (*);;qt记事本文件 (*.xb);;文本文件 (*.txt);;python文件 (*.py);;java文件 (*.java);;javascript文件 (*.js);;html文件 (*.html);;css文件 (*.css);;C++文件 (*.cpp *.hpp *.h)",
options=options,
)
if file_name:
self.currentFilePath = file_name
suffix = file_name[file_name.rfind(".") + 1 :]
self.setLanguageMode(self.languageModeList.get(suffix, "qt记事本"))
try:
with open(file_name, "r", encoding="utf-8") as file:
if suffix == "xb":
self.w.setHtml(file.read())
else:
self.w.canCtrl = True
self.w.setPlainText(file.read())
except Exception as e:
QMessageBox.critical(self, "错误", f"无法打开文件: {e}")
def closeEvent(self, event):
if not self.hasSaved:
reply = QMessageBox.question(
self,
"提示",
"是否保存文件?",
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
QMessageBox.Cancel,
)
if reply == QMessageBox.Yes:
self.saveFile()
elif reply == QMessageBox.Cancel:
event.ignore()
else:
event.accept()
else:
event.accept()




