使用Python库pyqt5制作TXT阅读器(三)-------显示内容和选择章节

项目地址:https://github.com/pikeduo/TXTReader
PyQt5中文手册:https://maicss.gitbook.io/pyqt-chinese-tutoral/pyqt5/
QtDesigner学习地址:https://youcans.blog.csdn.net/article/details/120640342

一、设置章节目录

首先我们来说明一下分割章节的原理。打开文件时,将每一行作为一个元素存入数组,逐行比较判断该行是否存在章节名称,并记录章节名所在的位置。
点击章节时,根据数组小标位置来判断其所在的行,去该行到下一行的内容显示到文本浏览器中。

                with open(file, 'r', encoding=encodings) as f:
                    # 打开文件,生成章节目录
                    self.chapters = []
                    # 包含了txt文本的全部内容
                    self.lines = f.readlines()
                    # 一种匹配章节目录的规则
                    pattern = r"(第)([\u4e00-\u9fa5a-zA-Z0-9]{1,7})[章|节][^\n]{1,35}(|\n)"
                    for i in range(len(self.lines)):
                        line = self.lines[i].strip()
                        if line != "" and re.match(pattern, line):
                            line = line.replace("\n", "").replace("=", "")
                            if len(line) < 30:
                                self.chapters.append({line: i})

将匹配到的章节名作为字典的key,其所在的列作为value,将字典存入数组中去。
有些文本不具有可用的章节名,那就将文件名作为key,首行(0)作为value,存入数组。
可以将此段代码单独拿出来看看生成的数据是什么样的。

# 如果没有可用的目录,那就显示全部
                if not self.chapters:
                    self.chapters.append({self.filename: 0})

加载新文件时将当前目录设置为第一章。

# 打开新文件时重置章节
                if not self.last_files or self.cur_file != self.last_files[-1]:
                    self.chapter = 0

随后,我们来绘制章节目录。

    # 设置章节目录
    def setChapters(self):
        # 每次绘制目录时先清除一下
        self.treeWidget.clear()
        _translate = QtCore.QCoreApplication.translate
        __sortingEnabled = self.treeWidget.isSortingEnabled()
        for i, value in enumerate(self.chapters):
            item = QTreeWidgetItem(self.treeWidget)
            item.setText(0, _translate("MyMainWindow", list(value.keys())[0]))
            self.treeWidget.addTopLevelItem(item)
        self.treeWidget.setSortingEnabled(__sortingEnabled)
        self.treeWidget.clicked.connect(self.onTreeClicked)
        self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(self.chapter),0)
        # 为当前章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))

循环添加章节目录,并且在页面显示当前的目录,突出显示目前的章节。self.chapter代表目前的章节,默认是0,在配置里加上这个属性,同时在加载和保存配置是添加上这个属性。
接下来设置触发事件。

    # 点击目录跳转到章节
    def onTreeClicked(self, index):
        # 恢复原来章节的背景色(设置透明度为0),为新章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0,0,0, 0))
        # 获取点击的项目下标
        self.chapter = int(index.row())
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))
        # 显示内容
        self.show_content()
    # 设置文本浏览器的内容
    def show_content(self):
        # 将文件内容添加到文本浏览器中
        self.textBrowser.setText(self.get_content())
        # 状态栏显示当前的章节内容和目录名
        self.textBrowser.setStatusTip(self.filename + "   " +list(self.chapters[self.chapter].keys())[0])
    # 获取章节内容
    def get_content(self):
        index = self.chapter
        # 起始行
        start = list(self.chapters[index].values())[0]
        # 如果是终章
        if index == self.treeWidget.topLevelItemCount() - 1:
            return "".join(self.lines[start:-1])
        else:
            # 终止行
            end = list(self.chapters[index + 1].values())[0]
            return "".join(self.lines[start:end])

第一章之前的内容将不会再显示,最后一章会将后面的内容全部显示出来。
在打开文件的函数里调用下面这两个函数。

                # 设置章节目录
                self.setChapters()
                # 设置文本浏览器的内容
                self.show_content()

现在就可以选择章节了。
在这里插入图片描述
完整代码。

import ctypes
import re
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QTreeWidgetItem

from QtDesigner.UI.UIReader import Ui_MainWindow

# 加上这段话,在运行程序时,设置的窗口图标才会出现
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类
        # 加载设置
        self.init_info()
        # 如果之前打开过文件,则直接加载文件
        if self.cur_file:
            self.load_file(self.cur_file)
        # 打开文件
        self.actionfile.triggered.connect(self.open_file)

    # 打开文件
    def open_file(self):
        # 弹出QFileDialog窗口。getOpenFileName()方法的第一个参数是说明文字,
        # 第二个参数是默认打开的文件夹路径。默认情况下显示所有类型的文件。
        if not self.cur_file:
            path = '/'
        else:
            path = self.cur_file
        fname = QFileDialog.getOpenFileName(self, '打开文件', path, filter='*.txt')
        self.load_file(fname[0])

    # 显示最近的文件
    def show_last_file(self):
        # 每次绘制时将之前的清空
        self.lastfile.clear()
        _translate = QtCore.QCoreApplication.translate
        for i, file in enumerate(self.last_files):
            # 截取文件名
            name = file.split('/')[-1].split('.')[0]
            # 添加action
            action = QtWidgets.QAction(self)
            action.setObjectName(f'file{i}')    # 设置对象名
            self.lastfile.addAction(action)  # 添加到菜单栏中
            action.setText(_translate("MyMainWindow", name))    # 添加到主窗口,且设置text
            action.triggered.connect(self.open_last_file)   # 设置触发事件

    # 打开最近的文件
    def open_last_file(self):
        sender = self.sender().objectName()  # 获取当前信号 sender
        # 根据我们设置的对象名,截取字符,然后从配置文件寻找文件路径
        self.load_file(self.last_files[int(sender[-1])])

    def load_file(self, file):
        # 文件不为空
        if file:
            try:
            	# 打开新文件时重置章节
                if not self.last_files or self.cur_file != self.last_files[-1]:
                    self.chapter = 0
                # 更改目前打开的文件
                self.cur_file = file
                self.filename = file.split('/')[-1].split('.')[0]
                # 将打开的文件添加到最近文件中去
                # 如果文件存在,则要更改文件的打开顺序
                if file in self.last_files:
                    self.last_files.remove(file)
                self.last_files.append(file)
                # 只存储最近打开的五本书籍
                if len(self.last_files) > 5:
                    self.last_files.pop(0)
                # 获取文件的编码格式
                encodings = self.get_encoding(file)
                with open(file, 'r', encoding=encodings) as f:
                    # 打开文件,生成章节目录
                    self.chapters = []
                    # 包含了txt文本的全部内容
                    self.lines = f.readlines()
                    # 一种匹配章节目录的规则
                    pattern = r"(第)([\u4e00-\u9fa5a-zA-Z0-9]{1,7})[章|节][^\n]{1,35}(|\n)"
                    for i in range(len(self.lines)):
                        line = self.lines[i].strip()
                        if line != "" and re.match(pattern, line):
                            line = line.replace("\n", "").replace("=", "")
                            if len(line) < 30:
                                self.chapters.append({line: i})
                 # 如果没有可用的目录,那就显示全部
                if not self.chapters:
                    self.chapters.append({self.filename: 0})
                # print(self.chapters)
                # 显示最近打开的文件
                self.show_last_file()
                # 设置章节目录
                self.setChapters()
                # 设置文本浏览器的内容
                self.show_content()
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    # 设置章节目录
    def setChapters(self):
        # 每次绘制目录时先清除一下
        self.treeWidget.clear()
        _translate = QtCore.QCoreApplication.translate
        __sortingEnabled = self.treeWidget.isSortingEnabled()
        for i, value in enumerate(self.chapters):
            item = QTreeWidgetItem(self.treeWidget)
            item.setText(0, _translate("MyMainWindow", list(value.keys())[0]))
            self.treeWidget.addTopLevelItem(item)
        self.treeWidget.setSortingEnabled(__sortingEnabled)
        self.treeWidget.clicked.connect(self.onTreeClicked)
        self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(self.chapter),0)
        # 为当前章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))

    # 点击目录跳转到章节
    def onTreeClicked(self, index):
        # 恢复原来章节的背景色(设置透明度为0),为新章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0,0,0, 0))
        # 获取点击的项目下标
        self.chapter = int(index.row())
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))
        # 显示内容
        self.show_content()

    # 设置文本浏览器的内容
    def show_content(self):
        # 将文件内容添加到文本浏览器中
        self.textBrowser.setText(self.get_content())
        # 状态栏显示当前的章节内容和目录名
        self.textBrowser.setStatusTip(self.filename + "   " +list(self.chapters[self.chapter].keys())[0])

    # 获取章节内容
    def get_content(self):
        index = self.chapter
        # 起始行
        start = list(self.chapters[index].values())[0]
        # 如果是终章
        if index == self.treeWidget.topLevelItemCount() - 1:
            return "".join(self.lines[start:-1])
        else:
            # 终止行
            end = list(self.chapters[index + 1].values())[0]
            return "".join(self.lines[start:end])

    def show_msg(self, msg):
        # 后两项分别为按钮(以|隔开,共有7种按钮类型,见示例后)、默认按钮(省略则默认为第一个按钮)
        reply = QMessageBox.information(self, "提示", msg, QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.Yes)

    # 获取文件编码类型
    def get_encoding(self, file):
        # 二进制方式读取,获取字节数据,检测类型
        with open(file, 'rb') as f:
            return cchardet.detect(f.read())['encoding']

    # 初始化设置
    def init_info(self):
        self.setting = QtCore.QSettings("./config.ini", QtCore.QSettings.IniFormat)   # 配置文件
        self.setting.setIniCodec('utf-8')   # 设置配置文件的编码格式
        self.cur_file = self.setting.value("FILE/file")     # 目前打开的文件
        self.last_files = self.setting.value("FILE/files")  # 最近打开的文件
        if not self.last_files:
            self.last_files = []
        self.chapter = int(self.setting.value("FILE/chapter"))  # 上次浏览的章节
    # 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)
        self.setting.setValue("FILE/chapter", self.chapter)

    # 关闭窗口时更新窗口大小
    def closeEvent(self, event):
        result = QMessageBox.question(self, "关闭应用", "确定关闭应用?",
                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            # 在关闭窗口时保存设置
            self.save_info()
            event.accept()
            QMainWindow.closeEvent(self, event)
        else:
            event.ignore()

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序

二、添加按钮

首先我们要设置两个按,分别代表上一章和下一章。
从左边,选择一个水平布局移到文本浏览器下面,如何选择两个按钮,移到右上角对象的水平布局下,这样子两个按键就设置好了。
将按钮设置成靠左和靠右,添加图标,这时会发现居右的按钮图标在文本左边。
在这里插入图片描述
不用担心,在属性编辑器里找到layoutDirection,设置为从右到左。
在这里插入图片描述
但是要注意,右边的按钮要设置为居左,否则就会变成下面这个样子。
在这里插入图片描述
这样就完成了UI界面的设置,编译运行。
在这里插入图片描述
ui界面的代码如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'UIReader.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1280, 720)
        MainWindow.setMinimumSize(QtCore.QSize(1280, 720))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icon/reader.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.last = QtWidgets.QPushButton(self.centralwidget)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap(":/icon/last.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.last.setIcon(icon1)
        self.last.setObjectName("last")
        self.horizontalLayout.addWidget(self.last, 0, QtCore.Qt.AlignLeft)
        self.next = QtWidgets.QPushButton(self.centralwidget)
        self.next.setLayoutDirection(QtCore.Qt.RightToLeft)
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap(":/icon/next.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.next.setIcon(icon2)
        self.next.setObjectName("next")
        self.horizontalLayout.addWidget(self.next, 0, QtCore.Qt.AlignLeft)
        self.gridLayout.addLayout(self.horizontalLayout, 1, 1, 1, 1)
        self.treeWidget = QtWidgets.QTreeWidget(self.centralwidget)
        self.treeWidget.setObjectName("treeWidget")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap(":/icon/catlogs.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.treeWidget.headerItem().setIcon(0, icon3)
        self.gridLayout.addWidget(self.treeWidget, 0, 0, 1, 1, QtCore.Qt.AlignLeft)
        self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
        self.textBrowser.setObjectName("textBrowser")
        self.gridLayout.addWidget(self.textBrowser, 0, 1, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 26))
        self.menubar.setObjectName("menubar")
        self.files = QtWidgets.QMenu(self.menubar)
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap(":/icon/files.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.files.setIcon(icon4)
        self.files.setObjectName("files")
        self.lastfile = QtWidgets.QMenu(self.files)
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap(":/icon/file_last.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.lastfile.setIcon(icon5)
        self.lastfile.setObjectName("lastfile")
        self.setting = QtWidgets.QMenu(self.menubar)
        icon6 = QtGui.QIcon()
        icon6.addPixmap(QtGui.QPixmap(":/icon/setting.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setting.setIcon(icon6)
        self.setting.setObjectName("setting")
        self.fontcolor = QtWidgets.QMenu(self.setting)
        icon7 = QtGui.QIcon()
        icon7.addPixmap(QtGui.QPixmap(":/icon/fontcolor.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.fontcolor.setIcon(icon7)
        self.fontcolor.setObjectName("fontcolor")
        self.bg = QtWidgets.QMenu(self.setting)
        icon8 = QtGui.QIcon()
        icon8.addPixmap(QtGui.QPixmap(":/icon/bg.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.bg.setIcon(icon8)
        self.bg.setObjectName("bg")
        self.exit = QtWidgets.QMenu(self.menubar)
        icon9 = QtGui.QIcon()
        icon9.addPixmap(QtGui.QPixmap(":/icon/exit.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.exit.setIcon(icon9)
        self.exit.setObjectName("exit")
        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.actionfile = QtWidgets.QAction(MainWindow)
        icon10 = QtGui.QIcon()
        icon10.addPixmap(QtGui.QPixmap(":/icon/file.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionfile.setIcon(icon10)
        self.actionfile.setObjectName("actionfile")
        self.action1 = QtWidgets.QAction(MainWindow)
        self.action1.setObjectName("action1")
        self.actionfont = QtWidgets.QAction(MainWindow)
        icon11 = QtGui.QIcon()
        icon11.addPixmap(QtGui.QPixmap(":/icon/font.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionfont.setIcon(icon11)
        self.actionfont.setObjectName("actionfont")
        self.actioncolor = QtWidgets.QAction(MainWindow)
        icon12 = QtGui.QIcon()
        icon12.addPixmap(QtGui.QPixmap(":/icon/color.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actioncolor.setIcon(icon12)
        self.actioncolor.setObjectName("actioncolor")
        self.actionimport = QtWidgets.QAction(MainWindow)
        icon13 = QtGui.QIcon()
        icon13.addPixmap(QtGui.QPixmap(":/icon/import.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionimport.setIcon(icon13)
        self.actionimport.setObjectName("actionimport")
        self.actionclose = QtWidgets.QAction(MainWindow)
        icon14 = QtGui.QIcon()
        icon14.addPixmap(QtGui.QPixmap(":/icon/close.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionclose.setIcon(icon14)
        self.actionclose.setObjectName("actionclose")
        self.actiondefault = QtWidgets.QAction(MainWindow)
        icon15 = QtGui.QIcon()
        icon15.addPixmap(QtGui.QPixmap(":/icon/default.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actiondefault.setIcon(icon15)
        self.actiondefault.setObjectName("actiondefault")
        self.actionexit = QtWidgets.QAction(MainWindow)
        self.actionexit.setIcon(icon9)
        self.actionexit.setObjectName("actionexit")
        self.files.addAction(self.actionfile)
        self.files.addAction(self.lastfile.menuAction())
        self.fontcolor.addAction(self.actionfont)
        self.fontcolor.addAction(self.actioncolor)
        self.bg.addAction(self.actionimport)
        self.bg.addAction(self.actionclose)
        self.setting.addAction(self.fontcolor.menuAction())
        self.setting.addAction(self.bg.menuAction())
        self.setting.addAction(self.actiondefault)
        self.exit.addAction(self.actionexit)
        self.menubar.addAction(self.files.menuAction())
        self.menubar.addAction(self.setting.menuAction())
        self.menubar.addAction(self.exit.menuAction())

        self.retranslateUi(MainWindow)
        self.actionexit.triggered.connect(MainWindow.close)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "TXT阅读器"))
        MainWindow.setStatusTip(_translate("MainWindow", "TXT阅读器"))
        self.last.setToolTip(_translate("MainWindow", "上一章"))
        self.last.setStatusTip(_translate("MainWindow", "上一章"))
        self.last.setText(_translate("MainWindow", "上一章"))
        self.next.setToolTip(_translate("MainWindow", "下一章"))
        self.next.setStatusTip(_translate("MainWindow", "下一章"))
        self.next.setText(_translate("MainWindow", "下一章"))
        self.treeWidget.setToolTip(_translate("MainWindow", "章节目录"))
        self.treeWidget.setStatusTip(_translate("MainWindow", "章节目录"))
        self.treeWidget.headerItem().setText(0, _translate("MainWindow", "章节目录"))
        self.files.setStatusTip(_translate("MainWindow", "文件"))
        self.files.setTitle(_translate("MainWindow", "文件"))
        self.lastfile.setToolTip(_translate("MainWindow", "打开最近的文件"))
        self.lastfile.setStatusTip(_translate("MainWindow", "打开最近的文件"))
        self.lastfile.setTitle(_translate("MainWindow", "打开最近的文件"))
        self.setting.setToolTip(_translate("MainWindow", "设置"))
        self.setting.setStatusTip(_translate("MainWindow", "设置"))
        self.setting.setTitle(_translate("MainWindow", "设置"))
        self.fontcolor.setTitle(_translate("MainWindow", "字体和颜色"))
        self.bg.setTitle(_translate("MainWindow", "背景图片"))
        self.exit.setToolTip(_translate("MainWindow", "关闭应用"))
        self.exit.setStatusTip(_translate("MainWindow", "关闭应用"))
        self.exit.setTitle(_translate("MainWindow", "退出"))
        self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
        self.actionfile.setText(_translate("MainWindow", "打开文件"))
        self.actionfile.setStatusTip(_translate("MainWindow", "打开文件"))
        self.actionfile.setShortcut(_translate("MainWindow", "Ctrl+O"))
        self.action1.setText(_translate("MainWindow", "1"))
        self.actionfont.setText(_translate("MainWindow", "选择字体"))
        self.actionfont.setStatusTip(_translate("MainWindow", "选择字体"))
        self.actioncolor.setText(_translate("MainWindow", "选择背景颜色"))
        self.actioncolor.setStatusTip(_translate("MainWindow", "选择背景颜色"))
        self.actionimport.setText(_translate("MainWindow", "导入背景图片"))
        self.actionimport.setStatusTip(_translate("MainWindow", "导入背景图片"))
        self.actionclose.setText(_translate("MainWindow", "关闭背景图片"))
        self.actionclose.setStatusTip(_translate("MainWindow", "关闭背景图片"))
        self.actiondefault.setText(_translate("MainWindow", "恢复默认设置"))
        self.actiondefault.setStatusTip(_translate("MainWindow", "恢复默认设置"))
        self.actionexit.setText(_translate("MainWindow", "退出应用"))
        self.actionexit.setStatusTip(_translate("MainWindow", "退出应用"))
        self.actionexit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
import resource_rc

三、切换章节

首先为两个按钮设置触发事件。

# 上一章,下一章按钮点击事件
        self.last.clicked.connect(self.show_last)
        self.next.clicked.connect(self.show_next)
    # 展示上一章
    def show_last(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter - 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))
    # 展示下一章
    def show_next(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter + 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))

现在可以点击按钮切换上一章或者下一章了。
在这里插入图片描述
但是还有一个问题,当显示第一章和最后一章时,点击按钮会出现问题,因此我们还要再做一些改动。

    # 根据显示的章节来决定按钮是否要改动,简单一点,直接隐藏按钮
    def show_button(self):
        # 考虑只有一个章节的情况
        if len(self.chapters) == 1:
            self.last.setHidden(True)
            self.next.setHidden(True)
        # 第一章
        elif self.chapter == 0:
            self.last.setHidden(True)
            self.next.setVisible(True)
        # 末章
        elif self.chapter == len(self.chapters) - 1:
            self.last.setVisible(True)
            self.next.setHidden(True)
        # 其他情况,恢复按钮
        else:
            if self.last.isHidden():
                self.last.setVisible(True)
            if self.next.isHidden():
                self.next.setVisible(True)

上面的函数是判断按钮是否要显示,另外还要在初始化界面因此按钮

        # 开局先隐藏按钮
        self.last.setHidden(True)
        self.next.setHidden(True)

在显示内容时判断按是否要显示。

    # 设置文本浏览器的内容
    def show_content(self):
        # 在展示内容时直接判断按钮是否要隐藏
        self.show_button()
        # 将文件内容添加到文本浏览器中
        self.textBrowser.setText(self.get_content())
        # 状态栏显示当前的章节内容和目录名
        self.textBrowser.setStatusTip(self.filename + "   " +list(self.chapters[self.chapter].keys())[0])

在选择章节目录时也要进行判断。

    # 点击目录跳转到章节
    def onTreeClicked(self, index):
        # 恢复原来章节的背景色(设置透明度为0),为新章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0,0,0, 0))
        # 获取点击的项目下标
        self.chapter = int(index.row())
        # 判断按钮是否要显示
        self.show_button()
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))
        self.show_content()

切换章节的内容设计完成了。
在这里插入图片描述
完整代码:

import ctypes
import re
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QTreeWidgetItem

from QtDesigner.UI.UIReader import Ui_MainWindow

# 加上这段话,在运行程序时,设置的窗口图标才会出现
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类
        # 加载设置
        self.init_info()
        # 开局先隐藏按钮
        self.last.setHidden(True)
        self.next.setHidden(True)
        # 如果之前打开过文件,则直接加载文件
        if self.cur_file:
            self.load_file(self.cur_file)
        # 打开文件
        self.actionfile.triggered.connect(self.open_file)
        # 上一章,下一章按钮点击事件
        self.last.clicked.connect(self.show_last)
        self.next.clicked.connect(self.show_next)

    # 打开文件
    def open_file(self):
        # 弹出QFileDialog窗口。getOpenFileName()方法的第一个参数是说明文字,
        # 第二个参数是默认打开的文件夹路径。默认情况下显示所有类型的文件。
        if not self.cur_file:
            path = '/'
        else:
            path = self.cur_file
        fname = QFileDialog.getOpenFileName(self, '打开文件', path, filter='*.txt')
        self.load_file(fname[0])

    # 显示最近的文件
    def show_last_file(self):
        # 每次绘制时将之前的清空
        self.lastfile.clear()
        _translate = QtCore.QCoreApplication.translate
        for i, file in enumerate(self.last_files):
            # 截取文件名
            name = file.split('/')[-1].split('.')[0]
            # 添加action
            action = QtWidgets.QAction(self)
            action.setObjectName(f'file{i}')    # 设置对象名
            self.lastfile.addAction(action)  # 添加到菜单栏中
            action.setText(_translate("MyMainWindow", name))    # 添加到主窗口,且设置text
            action.triggered.connect(self.open_last_file)   # 设置触发事件

    # 打开最近的文件
    def open_last_file(self):
        sender = self.sender().objectName()  # 获取当前信号 sender
        # 根据我们设置的对象名,截取字符,然后从配置文件寻找文件路径
        self.load_file(self.last_files[int(sender[-1])])

    def load_file(self, file):
        # 文件不为空
        if file:
            try:
            	# 打开新文件时重置章节
                if not self.last_files or self.cur_file != self.last_files[-1]:
                    self.chapter = 0
                # 更改目前打开的文件
                self.cur_file = file
                self.filename = file.split('/')[-1].split('.')[0]
                # 将打开的文件添加到最近文件中去
                # 如果文件存在,则要更改文件的打开顺序
                if file in self.last_files:
                    self.last_files.remove(file)
                self.last_files.append(file)
                # 只存储最近打开的五本书籍
                if len(self.last_files) > 5:
                    self.last_files.pop(0)
                # 获取文件的编码格式
                encodings = self.get_encoding(file)
                with open(file, 'r', encoding=encodings) as f:
                    # 打开文件,生成章节目录
                    self.chapters = []
                    # 包含了txt文本的全部内容
                    self.lines = f.readlines()
                    # 一种匹配章节目录的规则
                    pattern = r"(第)([\u4e00-\u9fa5a-zA-Z0-9]{1,7})[章|节][^\n]{1,35}(|\n)"
                    for i in range(len(self.lines)):
                        line = self.lines[i].strip()
                        if line != "" and re.match(pattern, line):
                            line = line.replace("\n", "").replace("=", "")
                            if len(line) < 30:
                                self.chapters.append({line: i})
                 # 如果没有可用的目录,那就显示全部
                if not self.chapters:
                    self.chapters.append({self.filename: 0})
                # print(self.chapters)
                # 显示最近打开的文件
                self.show_last_file()
                # 设置章节目录
                self.setChapters()
                # 设置文本浏览器的内容
                self.show_content()
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    # 设置章节目录
    def setChapters(self):
        # 每次绘制目录时先清除一下
        self.treeWidget.clear()
        _translate = QtCore.QCoreApplication.translate
        __sortingEnabled = self.treeWidget.isSortingEnabled()
        for i, value in enumerate(self.chapters):
            item = QTreeWidgetItem(self.treeWidget)
            item.setText(0, _translate("MyMainWindow", list(value.keys())[0]))
            self.treeWidget.addTopLevelItem(item)
        self.treeWidget.setSortingEnabled(__sortingEnabled)
        self.treeWidget.clicked.connect(self.onTreeClicked)
        self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(self.chapter),0)
        # 为当前章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))

    # 点击目录跳转到章节
    def onTreeClicked(self, index):
        # 恢复原来章节的背景色(设置透明度为0),为新章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0,0,0, 0))
        # 获取点击的项目下标
        self.chapter = int(index.row())
        # 判断按钮是否要显示
        self.show_button()
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))
        self.show_content()


    # 展示上一章
    def show_last(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter - 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))

    # 展示下一章
    def show_next(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter + 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))

    # 设置文本浏览器的内容
    def show_content(self):
        # 在展示内容时直接判断按钮是否要隐藏
        self.show_button()
        # 将文件内容添加到文本浏览器中
        self.textBrowser.setText(self.get_content())
        # 状态栏显示当前的章节内容和目录名
        self.textBrowser.setStatusTip(self.filename + "   " +list(self.chapters[self.chapter].keys())[0])

    # 获取章节内容
    def get_content(self):
        index = self.chapter
        # 起始行
        start = list(self.chapters[index].values())[0]
        # 如果是终章
        if index == self.treeWidget.topLevelItemCount() - 1:
            return "".join(self.lines[start:-1])
        else:
            # 终止行
            end = list(self.chapters[index + 1].values())[0]
            return "".join(self.lines[start:end])

    # 根据显示的章节来决定按钮是否要改动,简单一点,直接隐藏按钮
    def show_button(self):
        # 考虑只有一个章节的情况
        if len(self.chapters) == 1:
            self.last.setHidden(True)
            self.next.setHidden(True)
        # 第一章
        elif self.chapter == 0:
            self.last.setHidden(True)
            self.next.setVisible(True)
         # 末章
        elif self.chapter == len(self.chapters) - 1:
            self.last.setVisible(True)
            self.next.setHidden(True)
        # 其他情况,恢复按钮
        else:
            if self.last.isHidden():
                self.last.setVisible(True)
            if self.next.isHidden():
                self.next.setVisible(True)

    def show_msg(self, msg):
        # 后两项分别为按钮(以|隔开,共有7种按钮类型,见示例后)、默认按钮(省略则默认为第一个按钮)
        reply = QMessageBox.information(self, "提示", msg, QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.Yes)

    # 获取文件编码类型
    def get_encoding(self, file):
        # 二进制方式读取,获取字节数据,检测类型
        with open(file, 'rb') as f:
            return cchardet.detect(f.read())['encoding']

    # 初始化设置
    def init_info(self):
        self.setting = QtCore.QSettings("./config.ini", QtCore.QSettings.IniFormat)   # 配置文件
        self.setting.setIniCodec('utf-8')   # 设置配置文件的编码格式
        self.cur_file = self.setting.value("FILE/file")     # 目前打开的文件
        self.last_files = self.setting.value("FILE/files")  # 最近打开的文件
        if not self.last_files:
            self.last_files = []
        self.chapter = int(self.setting.value("FILE/chapter"))  # 上次浏览的章节
    # 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)
        self.setting.setValue("FILE/chapter", self.chapter)

    # 关闭窗口时更新窗口大小
    def closeEvent(self, event):
        result = QMessageBox.question(self, "关闭应用", "确定关闭应用?",
                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            # 在关闭窗口时保存设置
            self.save_info()
            event.accept()
            QMainWindow.closeEvent(self, event)
        else:
            event.ignore()

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序

四、显示/隐藏目录

首先我们在工具栏里设置一个目录图标。
在右下角选择动作编辑器,添加一个目录的动作。
在这里插入图片描述
将其拖到主页面的工具栏位置,这样就添加完成了。
在这里插入图片描述
保存编译,为目录添加触发事件。

# 点击目录显示/隐藏目录
        self.catlog.triggered.connect(self.tab_catlog)
    # 显示/隐藏目录
    def tab_catlog(self):
        if self.treeWidget.isVisible():
            self.treeWidget.setHidden(True)
        else:
            self.treeWidget.setVisible(True)

在初始化时设置目录为隐藏状态。

        # 设置目录栏初始为隐藏状态
        self.treeWidget.setHidden(True)

现在目录可以显示和隐藏了。
在这里插入图片描述
UI界面完整代码。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'UIReader.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1280, 720)
        MainWindow.setMinimumSize(QtCore.QSize(1280, 720))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icon/reader.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.last = QtWidgets.QPushButton(self.centralwidget)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap(":/icon/last.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.last.setIcon(icon1)
        self.last.setObjectName("last")
        self.horizontalLayout.addWidget(self.last, 0, QtCore.Qt.AlignLeft)
        self.next = QtWidgets.QPushButton(self.centralwidget)
        self.next.setLayoutDirection(QtCore.Qt.RightToLeft)
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap(":/icon/next.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.next.setIcon(icon2)
        self.next.setObjectName("next")
        self.horizontalLayout.addWidget(self.next, 0, QtCore.Qt.AlignLeft)
        self.gridLayout.addLayout(self.horizontalLayout, 1, 1, 1, 1)
        self.treeWidget = QtWidgets.QTreeWidget(self.centralwidget)
        self.treeWidget.setObjectName("treeWidget")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap(":/icon/catlogs.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.treeWidget.headerItem().setIcon(0, icon3)
        self.gridLayout.addWidget(self.treeWidget, 0, 0, 1, 1, QtCore.Qt.AlignLeft)
        self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
        self.textBrowser.setObjectName("textBrowser")
        self.gridLayout.addWidget(self.textBrowser, 0, 1, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 26))
        self.menubar.setObjectName("menubar")
        self.files = QtWidgets.QMenu(self.menubar)
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap(":/icon/files.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.files.setIcon(icon4)
        self.files.setObjectName("files")
        self.lastfile = QtWidgets.QMenu(self.files)
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap(":/icon/file_last.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.lastfile.setIcon(icon5)
        self.lastfile.setObjectName("lastfile")
        self.setting = QtWidgets.QMenu(self.menubar)
        icon6 = QtGui.QIcon()
        icon6.addPixmap(QtGui.QPixmap(":/icon/setting.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setting.setIcon(icon6)
        self.setting.setObjectName("setting")
        self.fontcolor = QtWidgets.QMenu(self.setting)
        icon7 = QtGui.QIcon()
        icon7.addPixmap(QtGui.QPixmap(":/icon/fontcolor.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.fontcolor.setIcon(icon7)
        self.fontcolor.setObjectName("fontcolor")
        self.bg = QtWidgets.QMenu(self.setting)
        icon8 = QtGui.QIcon()
        icon8.addPixmap(QtGui.QPixmap(":/icon/bg.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.bg.setIcon(icon8)
        self.bg.setObjectName("bg")
        self.exit = QtWidgets.QMenu(self.menubar)
        icon9 = QtGui.QIcon()
        icon9.addPixmap(QtGui.QPixmap(":/icon/exit.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.exit.setIcon(icon9)
        self.exit.setObjectName("exit")
        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.actionfile = QtWidgets.QAction(MainWindow)
        icon10 = QtGui.QIcon()
        icon10.addPixmap(QtGui.QPixmap(":/icon/file.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionfile.setIcon(icon10)
        self.actionfile.setObjectName("actionfile")
        self.action1 = QtWidgets.QAction(MainWindow)
        self.action1.setObjectName("action1")
        self.actionfont = QtWidgets.QAction(MainWindow)
        icon11 = QtGui.QIcon()
        icon11.addPixmap(QtGui.QPixmap(":/icon/font.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionfont.setIcon(icon11)
        self.actionfont.setObjectName("actionfont")
        self.actioncolor = QtWidgets.QAction(MainWindow)
        icon12 = QtGui.QIcon()
        icon12.addPixmap(QtGui.QPixmap(":/icon/color.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actioncolor.setIcon(icon12)
        self.actioncolor.setObjectName("actioncolor")
        self.actionimport = QtWidgets.QAction(MainWindow)
        icon13 = QtGui.QIcon()
        icon13.addPixmap(QtGui.QPixmap(":/icon/import.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionimport.setIcon(icon13)
        self.actionimport.setObjectName("actionimport")
        self.actionclose = QtWidgets.QAction(MainWindow)
        icon14 = QtGui.QIcon()
        icon14.addPixmap(QtGui.QPixmap(":/icon/close.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionclose.setIcon(icon14)
        self.actionclose.setObjectName("actionclose")
        self.actiondefault = QtWidgets.QAction(MainWindow)
        icon15 = QtGui.QIcon()
        icon15.addPixmap(QtGui.QPixmap(":/icon/default.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actiondefault.setIcon(icon15)
        self.actiondefault.setObjectName("actiondefault")
        self.actionexit = QtWidgets.QAction(MainWindow)
        self.actionexit.setIcon(icon9)
        self.actionexit.setObjectName("actionexit")
        self.catlog = QtWidgets.QAction(MainWindow)
        icon16 = QtGui.QIcon()
        icon16.addPixmap(QtGui.QPixmap(":/icon/catlog.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.catlog.setIcon(icon16)
        self.catlog.setObjectName("catlog")
        self.files.addAction(self.actionfile)
        self.files.addAction(self.lastfile.menuAction())
        self.fontcolor.addAction(self.actionfont)
        self.fontcolor.addAction(self.actioncolor)
        self.bg.addAction(self.actionimport)
        self.bg.addAction(self.actionclose)
        self.setting.addAction(self.fontcolor.menuAction())
        self.setting.addAction(self.bg.menuAction())
        self.setting.addAction(self.actiondefault)
        self.exit.addAction(self.actionexit)
        self.menubar.addAction(self.files.menuAction())
        self.menubar.addAction(self.setting.menuAction())
        self.menubar.addAction(self.exit.menuAction())
        self.toolBar.addAction(self.catlog)

        self.retranslateUi(MainWindow)
        self.actionexit.triggered.connect(MainWindow.close)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "TXT阅读器"))
        MainWindow.setStatusTip(_translate("MainWindow", "TXT阅读器"))
        self.last.setToolTip(_translate("MainWindow", "上一章"))
        self.last.setStatusTip(_translate("MainWindow", "上一章"))
        self.last.setText(_translate("MainWindow", "上一章"))
        self.next.setToolTip(_translate("MainWindow", "下一章"))
        self.next.setStatusTip(_translate("MainWindow", "下一章"))
        self.next.setText(_translate("MainWindow", "下一章"))
        self.treeWidget.setToolTip(_translate("MainWindow", "章节目录"))
        self.treeWidget.setStatusTip(_translate("MainWindow", "章节目录"))
        self.treeWidget.headerItem().setText(0, _translate("MainWindow", "章节目录"))
        self.files.setStatusTip(_translate("MainWindow", "文件"))
        self.files.setTitle(_translate("MainWindow", "文件"))
        self.lastfile.setToolTip(_translate("MainWindow", "打开最近的文件"))
        self.lastfile.setStatusTip(_translate("MainWindow", "打开最近的文件"))
        self.lastfile.setTitle(_translate("MainWindow", "打开最近的文件"))
        self.setting.setToolTip(_translate("MainWindow", "设置"))
        self.setting.setStatusTip(_translate("MainWindow", "设置"))
        self.setting.setTitle(_translate("MainWindow", "设置"))
        self.fontcolor.setTitle(_translate("MainWindow", "字体和颜色"))
        self.bg.setTitle(_translate("MainWindow", "背景图片"))
        self.exit.setToolTip(_translate("MainWindow", "关闭应用"))
        self.exit.setStatusTip(_translate("MainWindow", "关闭应用"))
        self.exit.setTitle(_translate("MainWindow", "退出"))
        self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
        self.actionfile.setText(_translate("MainWindow", "打开文件"))
        self.actionfile.setStatusTip(_translate("MainWindow", "打开文件"))
        self.actionfile.setShortcut(_translate("MainWindow", "Ctrl+O"))
        self.action1.setText(_translate("MainWindow", "1"))
        self.actionfont.setText(_translate("MainWindow", "选择字体"))
        self.actionfont.setStatusTip(_translate("MainWindow", "选择字体"))
        self.actioncolor.setText(_translate("MainWindow", "选择背景颜色"))
        self.actioncolor.setStatusTip(_translate("MainWindow", "选择背景颜色"))
        self.actionimport.setText(_translate("MainWindow", "导入背景图片"))
        self.actionimport.setStatusTip(_translate("MainWindow", "导入背景图片"))
        self.actionclose.setText(_translate("MainWindow", "关闭背景图片"))
        self.actionclose.setStatusTip(_translate("MainWindow", "关闭背景图片"))
        self.actiondefault.setText(_translate("MainWindow", "恢复默认设置"))
        self.actiondefault.setStatusTip(_translate("MainWindow", "恢复默认设置"))
        self.actionexit.setText(_translate("MainWindow", "退出应用"))
        self.actionexit.setStatusTip(_translate("MainWindow", "退出应用"))
        self.actionexit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
        self.catlog.setText(_translate("MainWindow", "目录"))
        self.catlog.setToolTip(_translate("MainWindow", "目录"))
import resource_rc

配置文件。

[DEFAULT]
file=
files=
chapter=0

[FILE]
file=
files=
chapter=0

完整代码。

import ctypes
import re
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QTreeWidgetItem

from QtDesigner.UI.UIReader import Ui_MainWindow

# 加上这段话,在运行程序时,设置的窗口图标才会出现
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类
    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类
        # 加载设置
        self.init_info()
        # 开局先隐藏按钮
        self.last.setHidden(True)
        self.next.setHidden(True)
        # 设置目录栏初始为隐藏状态
        self.treeWidget.setHidden(True)
        # 如果之前打开过文件,则直接加载文件
        if self.cur_file:
            self.load_file(self.cur_file)
        # 打开文件
        self.actionfile.triggered.connect(self.open_file)
        # 上一章,下一章按钮点击事件
        self.last.clicked.connect(self.show_last)
        self.next.clicked.connect(self.show_next)
        # 点击目录显示/隐藏目录
        self.catlog.triggered.connect(self.tab_catlog)

    # 显示/隐藏目录
    def tab_catlog(self):
        if self.treeWidget.isVisible():
            self.treeWidget.setHidden(True)
        else:
            self.treeWidget.setVisible(True)

    # 打开文件
    def open_file(self):
        # 弹出QFileDialog窗口。getOpenFileName()方法的第一个参数是说明文字,
        # 第二个参数是默认打开的文件夹路径。默认情况下显示所有类型的文件。
        if not self.cur_file:
            path = '/'
        else:
            path = self.cur_file
        fname = QFileDialog.getOpenFileName(self, '打开文件', path, filter='*.txt')
        self.load_file(fname[0])

    # 显示最近的文件
    def show_last_file(self):
        # 每次绘制时将之前的清空
        self.lastfile.clear()
        _translate = QtCore.QCoreApplication.translate
        for i, file in enumerate(self.last_files):
            # 截取文件名
            name = file.split('/')[-1].split('.')[0]
            # 添加action
            action = QtWidgets.QAction(self)
            action.setObjectName(f'file{i}')    # 设置对象名
            self.lastfile.addAction(action)  # 添加到菜单栏中
            action.setText(_translate("MyMainWindow", name))    # 添加到主窗口,且设置text
            action.triggered.connect(self.open_last_file)   # 设置触发事件

    # 打开最近的文件
    def open_last_file(self):
        sender = self.sender().objectName()  # 获取当前信号 sender
        # 根据我们设置的对象名,截取字符,然后从配置文件寻找文件路径
        self.load_file(self.last_files[int(sender[-1])])

    def load_file(self, file):
        # 文件不为空
        if file:
            try:
            	# 打开新文件时重置章节
                if not self.last_files or self.cur_file != self.last_files[-1]:
                    self.chapter = 0
                # 更改目前打开的文件
                self.cur_file = file
                self.filename = file.split('/')[-1].split('.')[0]
                # 将打开的文件添加到最近文件中去
                # 如果文件存在,则要更改文件的打开顺序
                if file in self.last_files:
                    self.last_files.remove(file)
                self.last_files.append(file)
                # 只存储最近打开的五本书籍
                if len(self.last_files) > 5:
                    self.last_files.pop(0)
                # 获取文件的编码格式
                encodings = self.get_encoding(file)
                with open(file, 'r', encoding=encodings) as f:
                    # 打开文件,生成章节目录
                    self.chapters = []
                    # 包含了txt文本的全部内容
                    self.lines = f.readlines()
                    # 一种匹配章节目录的规则
                    pattern = r"(第)([\u4e00-\u9fa5a-zA-Z0-9]{1,7})[章|节][^\n]{1,35}(|\n)"
                    for i in range(len(self.lines)):
                        line = self.lines[i].strip()
                        if line != "" and re.match(pattern, line):
                            line = line.replace("\n", "").replace("=", "")
                            if len(line) < 30:
                                self.chapters.append({line: i})
                 # 如果没有可用的目录,那就显示全部
                if not self.chapters:
                    self.chapters.append({self.filename: 0})
                # print(self.chapters)
                # 显示最近打开的文件
                self.show_last_file()
                # 设置章节目录
                self.setChapters()
                # 设置文本浏览器的内容
                self.show_content()
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    # 设置章节目录
    def setChapters(self):
        # 每次绘制目录时先清除一下
        self.treeWidget.clear()
        _translate = QtCore.QCoreApplication.translate
        __sortingEnabled = self.treeWidget.isSortingEnabled()
        for i, value in enumerate(self.chapters):
            item = QTreeWidgetItem(self.treeWidget)
            item.setText(0, _translate("MyMainWindow", list(value.keys())[0]))
            self.treeWidget.addTopLevelItem(item)
        self.treeWidget.setSortingEnabled(__sortingEnabled)
        self.treeWidget.clicked.connect(self.onTreeClicked)
        self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(self.chapter),0)
        # 为当前章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))

    # 点击目录跳转到章节
    def onTreeClicked(self, index):
        # 恢复原来章节的背景色(设置透明度为0),为新章节设置背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0,0,0, 0))
        # 获取点击的项目下标
        self.chapter = int(index.row())
        # 判断按钮是否要显示
        self.show_button()
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15,136,235))
        self.show_content()


    # 展示上一章
    def show_last(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter - 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))

    # 展示下一章
    def show_next(self):
        # 更改目录背景色
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(0, 0, 0, 0))
        self.chapter = self.chapter + 1
        self.show_content() # 显示内容
        self.treeWidget.topLevelItem(self.chapter).setBackground(0, QColor(15, 136, 235))

    # 设置文本浏览器的内容
    def show_content(self):
        # 在展示内容时直接判断按钮是否要隐藏
        self.show_button()
        # 将文件内容添加到文本浏览器中
        self.textBrowser.setText(self.get_content())
        # 状态栏显示当前的章节内容和目录名
        self.textBrowser.setStatusTip(self.filename + "   " +list(self.chapters[self.chapter].keys())[0])

    # 获取章节内容
    def get_content(self):
        index = self.chapter
        # 起始行
        start = list(self.chapters[index].values())[0]
        # 如果是终章
        if index == self.treeWidget.topLevelItemCount() - 1:
            return "".join(self.lines[start:-1])
        else:
            # 终止行
            end = list(self.chapters[index + 1].values())[0]
            return "".join(self.lines[start:end])

    # 根据显示的章节来决定按钮是否要改动,简单一点,直接隐藏按钮
    def show_button(self):
        # 考虑只有一个章节的情况
        if len(self.chapters) == 1:
            self.last.setHidden(True)
            self.next.setHidden(True)
        # 第一章
        elif self.chapter == 0:
            self.last.setHidden(True)
            self.next.setVisible(True)
         # 末章
        elif self.chapter == len(self.chapters) - 1:
            self.last.setVisible(True)
            self.next.setHidden(True)
        # 其他情况,恢复按钮
        else:
            if self.last.isHidden():
                self.last.setVisible(True)
            if self.next.isHidden():
                self.next.setVisible(True)

    def show_msg(self, msg):
        # 后两项分别为按钮(以|隔开,共有7种按钮类型,见示例后)、默认按钮(省略则默认为第一个按钮)
        reply = QMessageBox.information(self, "提示", msg, QMessageBox.Yes | QMessageBox.No,
                                        QMessageBox.Yes)

    # 获取文件编码类型
    def get_encoding(self, file):
        # 二进制方式读取,获取字节数据,检测类型
        with open(file, 'rb') as f:
            return cchardet.detect(f.read())['encoding']

    # 初始化设置
    def init_info(self):
        self.setting = QtCore.QSettings("./config.ini", QtCore.QSettings.IniFormat)   # 配置文件
        self.setting.setIniCodec('utf-8')   # 设置配置文件的编码格式
        self.cur_file = self.setting.value("FILE/file")     # 目前打开的文件
        self.last_files = self.setting.value("FILE/files")  # 最近打开的文件
        if not self.last_files:
            self.last_files = []
        self.chapter = int(self.setting.value("FILE/chapter"))  # 上次浏览的章节
    # 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)
        self.setting.setValue("FILE/chapter", self.chapter)

    # 关闭窗口时更新窗口大小
    def closeEvent(self, event):
        result = QMessageBox.question(self, "关闭应用", "确定关闭应用?",
                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            # 在关闭窗口时保存设置
            self.save_info()
            event.accept()
            QMainWindow.closeEvent(self, event)
        else:
            event.ignore()

if __name__ == '__main__':
    app = QApplication(sys.argv)  # 在 QApplication 方法中使用,创建应用程序对象
    myWin = MyMainWindow()  # 实例化 MyMainWindow 类,创建主窗口
    myWin.show()  # 在桌面显示控件 myWin
    sys.exit(app.exec_())  # 结束进程,退出程序

下期我们为设置的菜单栏添加动作。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值