暑假在家时自学了一段时间的python,开学后学校要求写一个多文本文档编辑器。听说Qt designer和PyQt5结合起来写代码会非常方便,做出来的界面比tkinter和wxPython好看许多,于是决定试一试。我在文件加载部分参照了《Python Qt GUI快速编程》的代码,为了保存文件加粗、颜色等.txt不能保存的格式,于是在文件保存过程中又增加.html类型。
程序里的图标文件可以在icon8官网下载,免费实用又好看。我这里使用的是office风格。下载地址:https://icons8.cn/icon/new-icons/all
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'E:\多文档编辑器\panel.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1132, 729)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.mdiArea = QtWidgets.QMdiArea(self.centralwidget)
self.mdiArea.setObjectName("mdiArea")
self.verticalLayout.addWidget(self.mdiArea)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1132, 30))
self.menubar.setObjectName("menubar")
self.menu = QtWidgets.QMenu(self.menubar)
self.menu.setObjectName("menu")
self.menu_2 = QtWidgets.QMenu(self.menubar)
self.menu_2.setObjectName("menu_2")
self.menu_3 = QtWidgets.QMenu(self.menubar)
self.menu_3.setObjectName("menu_3")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.toolBar = QtWidgets.QToolBar(MainWindow)
self.toolBar.setObjectName("toolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
self.actionNew = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("icons8-新文件-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionNew.setIcon(icon)
self.actionNew.setAutoRepeat(False)
self.actionNew.setObjectName("actionNew")
self.actionOpen = QtWidgets.QAction(MainWindow)
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap("icons8-打开文件夹-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionOpen.setIcon(icon1)
self.actionOpen.setObjectName("actionOpen")
self.actionSave = QtWidgets.QAction(MainWindow)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap("icons8-保存-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
icon2.addPixmap(QtGui.QPixmap("icons8-保存-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.On)
self.actionSave.setIcon(icon2)
self.actionSave.setObjectName("actionSave")
self.actionClose = QtWidgets.QAction(MainWindow)
icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap("icons8-删除-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionClose.setIcon(icon3)
self.actionClose.setObjectName("actionClose")
self.actionPile = QtWidgets.QAction(MainWindow)
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap("icons8-一堆钱-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionPile.setIcon(icon4)
self.actionPile.setObjectName("actionPile")
self.actionHorizontal = QtWidgets.QAction(MainWindow)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap("icons8-水平标志-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionHorizontal.setIcon(icon5)
self.actionHorizontal.setObjectName("actionHorizontal")
self.actionVertical = QtWidgets.QAction(MainWindow)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap("icons8-竖旗-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionVertical.setIcon(icon6)
self.actionVertical.setObjectName("actionVertical")
self.actionCut = QtWidgets.QAction(MainWindow)
icon7 = QtGui.QIcon()
icon7.addPixmap(QtGui.QPixmap("icons8-剪刀-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionCut.setIcon(icon7)
self.actionCut.setObjectName("actionCut")
self.actionCopy = QtWidgets.QAction(MainWindow)
icon8 = QtGui.QIcon()
icon8.addPixmap(QtGui.QPixmap("icons8-复制-30.png"), QtGui.QIcon.Normal, QtGui.QIcon.On)
icon8.addPixmap(QtGui.QPixmap("icons8-复制-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionCopy.setIcon(icon8)
self.actionCopy.setObjectName("actionCopy")
self.actionPaste = QtWidgets.QAction(MainWindow)
icon9 = QtGui.QIcon()
icon9.addPixmap(QtGui.QPixmap("icons8-粘贴-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionPaste.setIcon(icon9)
self.actionPaste.setObjectName("actionPaste")
self.actionSearch = QtWidgets.QAction(MainWindow)
icon10 = QtGui.QIcon()
icon10.addPixmap(QtGui.QPixmap("icons8-搜索-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionSearch.setIcon(icon10)
self.actionSearch.setObjectName("actionSearch")
self.actionBold = QtWidgets.QAction(MainWindow)
icon11 = QtGui.QIcon()
icon11.addPixmap(QtGui.QPixmap("icons8-粗体-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionBold.setIcon(icon11)
self.actionBold.setObjectName("actionBold")
self.actionItalic = QtWidgets.QAction(MainWindow)
icon12 = QtGui.QIcon()
icon12.addPixmap(QtGui.QPixmap("icons8-斜体-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionItalic.setIcon(icon12)
self.actionItalic.setObjectName("actionItalic")
self.actionDelete = QtWidgets.QAction(MainWindow)
icon13 = QtGui.QIcon()
icon13.addPixmap(QtGui.QPixmap("icons8-撤销-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionDelete.setIcon(icon13)
self.actionDelete.setObjectName("actionDelete")
self.actionRecover = QtWidgets.QAction(MainWindow)
icon14 = QtGui.QIcon()
icon14.addPixmap(QtGui.QPixmap("icons8-恢复-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionRecover.setIcon(icon14)
self.actionRecover.setObjectName("actionRecover")
self.actionLeft = QtWidgets.QAction(MainWindow)
icon15 = QtGui.QIcon()
icon15.addPixmap(QtGui.QPixmap("icons8-左对齐-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionLeft.setIcon(icon15)
self.actionLeft.setObjectName("actionLeft")
self.actionRight = QtWidgets.QAction(MainWindow)
icon16 = QtGui.QIcon()
icon16.addPixmap(QtGui.QPixmap("icons8-右对齐-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionRight.setIcon(icon16)
self.actionRight.setObjectName("actionRight")
self.actionCenter = QtWidgets.QAction(MainWindow)
icon17 = QtGui.QIcon()
icon17.addPixmap(QtGui.QPixmap("icons8-居中对齐-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionCenter.setIcon(icon17)
self.actionCenter.setObjectName("actionCenter")
self.actionUnderline = QtWidgets.QAction(MainWindow)
icon18 = QtGui.QIcon()
icon18.addPixmap(QtGui.QPixmap("icons8-下划线-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionUnderline.setIcon(icon18)
self.actionUnderline.setObjectName("actionUnderline")
self.actionFont = QtWidgets.QAction(MainWindow)
icon19 = QtGui.QIcon()
icon19.addPixmap(QtGui.QPixmap("icons8-类型-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionFont.setIcon(icon19)
self.actionFont.setObjectName("actionFont")
self.actionColor = QtWidgets.QAction(MainWindow)
icon20 = QtGui.QIcon()
icon20.addPixmap(QtGui.QPixmap("icons8-文字颜色-30.png"), QtGui.QIcon.Selected, QtGui.QIcon.Off)
self.actionColor.setIcon(icon20)
self.actionColor.setObjectName("actionColor")
self.fontComboBox = QtWidgets.QFontComboBox()
self.menu.addAction(self.actionNew)
self.menu.addAction(self.actionOpen)
self.menu.addAction(self.actionSave)
self.menu.addSeparator()
self.menu.addAction(self.actionClose)
self.menu_2.addAction(self.actionPile)
self.menu_2.addAction(self.actionHorizontal)
self.menu_2.addAction(self.actionVertical)
self.menu_3.addAction(self.actionCut)
self.menu_3.addAction(self.actionCopy)
self.menu_3.addAction(self.actionPaste)
self.menu_3.addSeparator()
self.menu_3.addAction(self.actionSearch)
self.menubar.addAction(self.menu.menuAction())
self.menubar.addAction(self.menu_3.menuAction())
self.menubar.addAction(self.menu_2.menuAction())
self.toolBar.addAction(self.actionNew)
self.toolBar.addAction(self.actionOpen)
self.toolBar.addAction(self.actionSave)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionCut)
self.toolBar.addAction(self.actionCopy)
self.toolBar.addAction(self.actionPaste)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionDelete)
self.toolBar.addAction(self.actionRecover)
self.toolBar.addSeparator()
self.toolBar.addWidget(self.fontComboBox)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionBold)
self.toolBar.addAction(self.actionItalic)
self.toolBar.addAction(self.actionUnderline)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionFont)
self.toolBar.addAction(self.actionColor)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionLeft)
self.toolBar.addAction(self.actionCenter)
self.toolBar.addAction(self.actionRight)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionSearch)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.menu.setTitle(_translate("MainWindow", "文件"))
self.menu_2.setTitle(_translate("MainWindow", "视图"))
self.menu_3.setTitle(_translate("MainWindow", "编辑"))
self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
self.actionNew.setText(_translate("MainWindow", "新建(&N)"))
self.actionNew.setToolTip(_translate("MainWindow", "新建(N)"))
self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))
self.actionOpen.setText(_translate("MainWindow", "打开(&O)"))
self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
self.actionSave.setText(_translate("MainWindow", "保存(&S)"))
self.actionSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
self.actionClose.setText(_translate("MainWindow", "退出(&Q)"))
self.actionClose.setShortcut(_translate("MainWindow", "Ctrl+Q"))
self.actionPile.setText(_translate("MainWindow", "平铺"))
self.actionHorizontal.setText(_translate("MainWindow", "水平布局"))
self.actionVertical.setText(_translate("MainWindow", "垂直布局"))
self.actionCut.setText(_translate("MainWindow", "剪切(&X)"))
self.actionCut.setShortcut(_translate("MainWindow", "Ctrl+X"))
self.actionCopy.setText(_translate("MainWindow", "复制(&C)"))
self.actionCopy.setShortcut(_translate("MainWindow", "Ctrl+C"))
self.actionPaste.setText(_translate("MainWindow", "粘贴(&V)"))
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
self.actionSearch.setText(_translate("MainWindow", "查找(&Q)"))
self.actionSearch.setShortcut(_translate("MainWindow", "Alt+Q"))
self.actionBold.setText(_translate("MainWindow", "Bold"))
self.actionItalic.setText(_translate("MainWindow", "Italic"))
self.actionDelete.setText(_translate("MainWindow", "Delete"))
self.actionRecover.setText(_translate("MainWindow", "Recover"))
self.actionLeft.setText(_translate("MainWindow", "Left"))
self.actionRight.setText(_translate("MainWindow", "Right"))
self.actionCenter.setText(_translate("MainWindow", "Center"))
self.actionUnderline.setText(_translate("MainWindow", "Underline"))
self.actionFont.setText(_translate("MainWindow", "字体"))
self.actionColor.setText(_translate("MainWindow", "颜色"))
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QFile, QFileInfo, QIODevice, QTextStream, Qt
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTextEdit
#from zopyx.convert2 import Converter
class TextEdit(QTextEdit):
NextId = 1
def __init__(self, filename="", parent=None):
super(TextEdit, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.filename = filename
if self.filename == "":
self.filename = "Untitled-{0}".format(TextEdit.NextId)
TextEdit.NextId += 1
self.document().setModified(False)
self.windowName = QFileInfo(self.filename).fileName()
self.setWindowTitle(self.windowName)
def closeEvent(self, event):
if self.document().isModified():
dlg = QMessageBox.question(self, "Notepad", "是否保存对'{0}'的修改?".format(self.windowName),QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
if dlg == QMessageBox.Yes:
self.save()
elif dlg == QMessageBox.No:
self.close()
else:
event.ignore()
def isModified(self):
return self.document().isModified()
def save(self):
if self.filename.startswith("Untitled"):
self.filename = QFileDialog.getSaveFileName(self, "保存文件", self.filename,"Text files (*.txt);;HTML files (*.html)")[0]
self.windowName = QFileInfo(self.filename).fileName()
with open(self.filename, 'w') as file_path:
if 'rtf' in self.windowName:
file_path.write(self.toHtml())
if 'txt' in self.windowName:
file_path.write(self.toPlainText())
self.setWindowTitle(QFileInfo(self.filename).fileName())
self.document().setModified(False)
def load(self):
fh = None
try:
fh = QFile(self.filename)
if not fh.open(QIODevice.ReadOnly):
raise IOError(str(fh.errorString()))
stream = QTextStream(fh)
# stream.setCodec("UTF-8")
self.setPlainText(stream.readAll())
self.document().setModified(False)
except EnvironmentError as e:
QMessageBox.warning(self,"加载错误"
"不能加载 {0}".format(self.filename))
finally:
if fh is not None:
fh.close()
self.setWindowTitle(QFileInfo(self.filename).fileName())
import sys
import os
import panel
import textedit
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QWidget, QApplication, QColorDialog, QFontDialog, QTextEdit, QFileDialog, QMessageBox
from PyQt5.QtCore import QFile, QFileInfo
from PyQt5.QtGui import QPalette,QTextCursor
class Editor(QtWidgets.QMainWindow, panel.Ui_MainWindow):
def __init__(self, parent=None):
super(Editor, self).__init__(parent)
self.setupUi(self)
self.setWindowTitle("Notepad")
self.actionOpen.triggered.connect(self.fileOpen)
self.actionNew.triggered.connect(self.fileNew)
self.actionSave.triggered.connect(self.fileSave)
self.actionClose.triggered.connect(self.closeEvent)
self.actionPile.triggered.connect(self.filePile)
self.actionHorizontal.triggered.connect(self.fileHorizontal)
self.actionVertical.triggered.connect(self.fileVertical)
self.actionDelete.triggered.connect(self.fileUndo)
self.actionRecover.triggered.connect(self.fileRedo)
self.actionCopy.triggered.connect(self.fileCopy)
self.actionCut.triggered.connect(self.fileCut)
self.actionPaste.triggered.connect(self.filePaste)
self.actionBold.triggered.connect(self.fileBold)
self.actionItalic.triggered.connect(self.fileItalic)
self.actionUnderline.triggered.connect(self.fileUnderline)
self.fontComboBox.currentFontChanged.connect(self.fileChangeFont)
self.actionSearch.triggered.connect(self.fileSearch)
self.actionLeft.triggered.connect(self.fileLeft)
self.actionRight.triggered.connect(self.fileRight)
self.actionFont.triggered.connect(self.fileFontBox)
self.actionColor.triggered.connect(self.fileColorBox)
def fileFontBox(self):
font, okPressed = QFontDialog.getFont()
if okPressed:
self.mdiArea.activeSubWindow().widget().setCurrentFont(font)
def fileColorBox(self):
col = QColorDialog.getColor()
if col.isValid():
self.mdiArea.activeSubWindow().widget().setTextColor(col)
def fileLeft(self):
self.mdiArea.activeSubWindow().widget().setAlignment(Qt.AlignLeft)
def fileRight(self):
self.mdiArea.activeSubWindow().widget().setAlignment(Qt.AlignRight)
def fileCenter(self):
self.mdiArea.activeSubWindow().widget().setAlignment(Qt.AlignCenter)
def fileHorizontal(self):
wList = self.mdiArea.subWindowList()
size = len(wList)
if size > 0:
position = QtCore.QPoint(0, 0)
for w in wList:
rect = QtCore.QRect(0, 0, self.mdiArea.width() / size,
self.mdiArea.height())
w.setGeometry(rect)
w.move(position)
position.setX(position.x() + w.width())
def fileVertical(self):
wList = self.mdiArea.subWindowList()
size = len(wList)
if size > 0:
position = QtCore.QPoint(0, 0)
for w in wList:
rect = QtCore.QRect(0, 0, self.mdiArea.width(),
self.mdiArea.height() / size)
w.setGeometry(rect)
w.move(position)
position.setY(position.y() + w.height())
def fileChangeFont(self, font):
self.mdiArea.activeSubWindow().widget().setCurrentFont(font)
def fileSearch(self):
pattern, okPressed = QtWidgets.QInputDialog.getText(self,
"查找", "查找字符串:", QtWidgets.QLineEdit.Normal, "")
if okPressed and pattern != '':
sub = self.mdiArea.activeSubWindow().widget()
sub.moveCursor(QTextCursor.StartOfLine, QTextCursor.MoveAnchor)
if sub.find(pattern):
palette = sub.palette()
palette.setColor(QPalette.Highlight, palette.color(QPalette.Active,QPalette.Highlight))
sub.setPalette(palette)
def fileBold(self):
sub = self.mdiArea.activeSubWindow().widget()
tmpFormat = sub.currentCharFormat()
if tmpFormat.fontWeight() == QtGui.QFont.Bold:
tmpFormat.setFontWeight(QtGui.QFont.Normal)
else:
tmpFormat.setFontWeight(QtGui.QFont.Bold)
sub.mergeCurrentCharFormat(tmpFormat)
def fileItalic(self):
tmpTextBox = self.mdiArea.activeSubWindow().widget()
tmpTextBox.setFontItalic(not tmpTextBox.fontItalic())
def fileUnderline(self):
tmpTextBox = self.mdiArea.activeSubWindow().widget()
tmpTextBox.setFontUnderline(not tmpTextBox.fontUnderline())
def fileCopy(self):
self.mdiArea.activeSubWindow().widget().copy()
def fileCut(self):
self.mdiArea.activeSubWindow().widget().cut()
def filePaste(self):
self.mdiArea.activeSubWindow().widget().paste()
def fileRedo(self):
self.mdiArea.activeSubWindow().widget().redo()
def fileUndo(self):
self.mdiArea.activeSubWindow().widget().undo()
def filePile(self):
if len(self.mdiArea.subWindowList()) > 1:
self.mdiArea.cascadeSubWindows()
def fileSave(self):
tmpTextEdit = self.mdiArea.activeSubWindow()
tmpTextEdit=tmpTextEdit.widget()
if tmpTextEdit is None or not isinstance(tmpTextEdit, QTextEdit):
return True
tmpTextEdit.save()
def fileOpen(self):
filename,filetype = QFileDialog.getOpenFileName(self,"打开文件","C:","Text files (*.txt);;HTML files (*html)")
if filename:
for window in self.mdiArea.subWindowList():
textEdit=window.widget()
if textEdit.filename == filename:
self.mdiArea.setActiveSubWindow(window)
break
else:
self.loadFile(filename)
def fileNew(self):
tmpTextEdit = textedit.TextEdit()
self.mdiArea.addSubWindow(tmpTextEdit)
tmpTextEdit.show()
def loadFile(self, filename):
tmpTextEdit = textedit.TextEdit(filename)
tmpTextEdit.load()
self.mdiArea.addSubWindow(tmpTextEdit)
tmpTextEdit.show()
def closeEvent(self, event):
unSaveFile = 0
for window in self.mdiArea.subWindowList():
textEdit = window.widget()
if textEdit.isModified():
unSaveFile += 1
if unSaveFile != 0:
dlg = QMessageBox.warning(self, "Notepad", "{0}个文档尚未保存,是否关闭?".format(unSaveFile), QMessageBox.Yes|QMessageBox.No)
if dlg == QMessageBox.Yes:
QtCore.QCoreApplication.quit()
elif dlg == QMessageBox.No:
event.ignore()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = Editor()
ex.show()
app.exec_()
每种语言都有自己擅长的领域,感觉C#实现会更容易一些。