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

一、字体和背景颜色

(1)字体

在设置文件里添加字体和大小。

[FONT]
font=SimSun
fontsize=9

默认字体是SimSun,大小为9
记得要在默认设置里添加。

[DEFAULT]
file=
files=
chapter=0
font=SimSun
fontsize=9

展示文本内容时设置字体和大小。

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

self.fontsself.fontsize是配置文件里的,在导入配置时导入。

self.fonts = self.setting.value("FONT/font")    # 字体
self.fontsize = int(self.setting.value("FONT/fontsize")) # 字体大小

需要保存配置。

self.setting.setValue("FONT/font", self.fonts)
self.setting.setValue("FONT/fontsize", self.fontsize)

为选择字体添加一个事件。

self.actionfont.triggered.connect(self.select_font)
    # 选择字体
    def select_font(self):
        # 弹出一个字体选择对话框。getFont()方法返回一个字体名称和状态信息。
        # 状态信息有OK和其他两种。
        font, ok = QFontDialog.getFont(QFont(self.fonts, self.fontsize), self, '选择字体和大小')
        # 如果点击OK,标签的字体就会随之更改
        if ok:
            self.textBrowser.setFont(font)
            self.fonts = font.family()
            self.fontsize = font.pointSize()

打开一个字体选择框,选择字体和大小后返回数据,重新设置文本区域的字体格式。
使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
上面选择了楷体,字体为9号。

(2)背景颜色

在配置文件添加背景颜色。

[BACKGROUND]
color=130, 135, 144

默认颜色为(130, 135, 144)

[DEFAULT]
file=
files=
chapter=0
font=SimSun
fontsize=9
color=130, 135, 144

导入和保存配置。

self.color = self.setting.value("BACKGROUND/color") # 背景颜色
self.setting.setValue("BACKGROUND/color", self.color)

初始化时设置背景颜色。

# 设置默认背景色
        self.treeWidget.setStyleSheet(f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        self.textBrowser.setStyleSheet(f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")      

添加选择背景颜色的事件。

self.actioncolor.triggered.connect(self.select_color)
    # 选择颜色
    def select_color(self):
        col = QColorDialog.getColor(self.textBrowser.textColor(), self, "设置背景颜色")
        if col.isValid():
            print(col)
            self.treeWidget.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.textBrowser.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.color = [col.red(), col.green(), col.blue()]

下面是设置了背景颜色后的界面。
使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
更改背景颜色为米黄色。
使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
完整代码如下。

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, QFontDialog, \
    QColorDialog

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.treeWidget.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        self.textBrowser.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        # 打开文件
        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)
        # 设置字体和颜色
        self.actionfont.triggered.connect(self.select_font)
        self.actioncolor.triggered.connect(self.select_color)

    # 显示/隐藏目录
    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.setFont(QFont(self.fonts, self.fontsize))
        # 状态栏显示当前的章节内容和目录名
        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 select_font(self):
        # 弹出一个字体选择对话框。getFont()方法返回一个字体名称和状态信息。
        # 状态信息有OK和其他两种。
        font, ok = QFontDialog.getFont(QFont(self.fonts, self.fontsize), self, '选择字体和大小')
        # 如果点击OK,标签的字体就会随之更改
        if ok:
            self.textBrowser.setFont(font)
            self.fonts = font.family()
            self.fontsize = font.pointSize()

    # 选择颜色
    def select_color(self):
        col = QColorDialog.getColor(self.textBrowser.textColor(), self, "设置背景颜色")
        if col.isValid():
            print(col)
            self.treeWidget.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.textBrowser.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.color = [col.red(), col.green(), col.blue()]

    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"))  # 上次浏览的章节
        self.fonts = self.setting.value("FONT/font")  # 字体
        self.fontsize = int(self.setting.value("FONT/fontsize"))  # 字体大小
        self.color = self.setting.value("BACKGROUND/color")  # 背景颜色

    # 保存设置
    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)
        self.setting.setValue("FONT/font", self.fonts)
        self.setting.setValue("FONT/fontsize", self.fontsize)
        self.setting.setValue("BACKGROUND/color", self.color)

    # 关闭窗口时更新窗口大小
    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_())  # 结束进程,退出程序

二、背景图片

(1)设置默认的背景图片

添加一张图片在/static/img文件夹下,为默认的背景图片。

[BACKGROUND]
bg=./static/img/default.png
[DEFAULT]
file=
files=
chapter=0
font=SimSun
fontsize=9
color=130, 135, 144
bg=./static/img/default.png

导入和保存设置。

self.bg = self.setting.value("BACKGROUND/bg")   # 背景图片
self.setting.setValue("BACKGROUND/bg", self.bg)

初始化时绘制背景图片。

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(self.rect(), QPixmap(self.bg))
        super().paintEvent(event)

使用paintEvent函数绘制图片,会让图片铺满整个区域。
设置图片自动填充区域。

self.setAutoFillBackground(True)

使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
因为在设置背景图片时,已经为目录区域和文本区域设置了透明度,因此可以看到图片。

(2)选择背景图片

添加触发事件。

# 设置背景图片
        self.actionimport.triggered.connect(self.select_bg)
    # 选择背景图片
    def select_bg(self):
        if not self.bg:
            path = '/'
        else:
            path = self.bg
        fname = QFileDialog.getOpenFileName(self, '选择图片', path, filter='*.jpg,*.png,*.jpeg,All Files(*)')
        # 文件不为空
        if fname[0]:
            try:
                # 更改目前的背景图片
                self.bg = fname[0]
                self.update()   # 刷新页面
            except:
                self.show_msg('文件不存在或者错误!')
        else:  # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

打开一个文件选择框,选择背景图片,self.update()会刷新页面,重绘背景图片。
现在可以选择背景图片了。
在这里插入图片描述

(3)关闭背景图片

考虑到有些人可能不喜欢设置背景图片,因此添加了关闭背景图片的功能。

# 关闭背景图片
        self.actionclose.triggered.connect(self.close_bg)
    # 关闭背景图片
    def close_bg(self):
        self.bg = ''    # 将背景图片设置为空即可
        self.update()   # 刷新页面

很简单,将背景图片设置为空即可。
使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
完整代码。

import ctypes
import re
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QTreeWidgetItem, QFontDialog, \
    QColorDialog

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.treeWidget.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        self.textBrowser.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        # 打开文件
        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)
        # 设置字体和颜色
        self.actionfont.triggered.connect(self.select_font)
        self.actioncolor.triggered.connect(self.select_color)
        # 设置背景图片
        self.actionimport.triggered.connect(self.select_bg)
        self.setAutoFillBackground(True)
        # 关闭背景图片
        self.actionclose.triggered.connect(self.close_bg)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(self.rect(), QPixmap(self.bg))
        super().paintEvent(event)

    # 显示/隐藏目录
    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.setFont(QFont(self.fonts, self.fontsize))
        # 状态栏显示当前的章节内容和目录名
        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 select_font(self):
        # 弹出一个字体选择对话框。getFont()方法返回一个字体名称和状态信息。
        # 状态信息有OK和其他两种。
        font, ok = QFontDialog.getFont(QFont(self.fonts, self.fontsize), self, '选择字体和大小')
        # 如果点击OK,标签的字体就会随之更改
        if ok:
            self.textBrowser.setFont(font)
            self.fonts = font.family()
            self.fontsize = font.pointSize()

    # 选择颜色
    def select_color(self):
        col = QColorDialog.getColor(self.textBrowser.textColor(), self, "设置背景颜色")
        if col.isValid():
            print(col)
            self.treeWidget.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.textBrowser.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.color = [col.red(), col.green(), col.blue()]

    # 选择背景图片
    def select_bg(self):
        if not self.bg:
            path = '/'
        else:
            path = self.bg
        fname = QFileDialog.getOpenFileName(self, '选择图片', path, filter='*.jpg,*.png,*.jpeg,All Files(*)')
        # 文件不为空
        if fname[0]:
            try:
                # 更改目前的背景图片
                self.bg = fname[0]
                self.update()   # 刷新页面
            except:
                self.show_msg('文件不存在或者错误!')
        else:  # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    # 关闭背景图片
    def close_bg(self):
        self.bg = ''    # 将背景图片设置为空即可
        self.update()   # 刷新页面

    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"))  # 上次浏览的章节
        self.fonts = self.setting.value("FONT/font")  # 字体
        self.fontsize = int(self.setting.value("FONT/fontsize"))  # 字体大小
        self.color = self.setting.value("BACKGROUND/color")  # 背景颜色
        self.bg = self.setting.value("BACKGROUND/bg")  # 背景图片

    # 保存设置
    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)
        self.setting.setValue("FONT/font", self.fonts)
        self.setting.setValue("FONT/fontsize", self.fontsize)
        self.setting.setValue("BACKGROUND/color", self.color)
        self.setting.setValue("BACKGROUND/bg", self.bg)

    # 关闭窗口时更新窗口大小
    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_())  # 结束进程,退出程序

三、窗口大小

记住用户上次关闭时的窗口大小,默认为1280*720

[SCREEN]
screen=1280, 720
[DEFAULT]
screen=1280, 720
file=
files=
chapter=0
font=SimSun
fontsize=9
color=130, 135, 144
bg=./static/img/default.png

导入和保存设置。

self.win = self.setting.value("SCREEN/screen")  # 窗口大小
self.setting.setValue("SCREEN/screen", self.win)

重写窗口移动事件

    # 窗口移动事件,保存用户最后设置的窗口大小
    def resizeEvent(self, event):
        self.win = [event.size().width(), event.size().height()]

四、恢复默认设置

恢复默认设置时,将配置文件里面的设置恢复为原来的即可。

# 恢复默认设置
        self.actiondefault.triggered.connect(self.default)
    # 恢复默认设置
    def default(self):
        result = QMessageBox.question(self, "恢复默认设置", "确定恢复默认设置?这将使您的设置全部失效且关闭应用",
                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            # 重新配置设置文件
            self.setting.setValue("SCREEN/screen", self.setting.value("DEFAULT/screen"))
            self.setting.setValue("FILE/file", self.setting.value("DEFAULT/file"))
            self.setting.setValue("FILE/files", self.setting.value("DEFAULT/files"))
            self.setting.setValue("BACKGROUND/bg", self.setting.value("DEFAULT/bg"))
            self.setting.setValue("BACKGROUND/color", self.setting.value("DEFAULT/color"))
            self.setting.setValue("FONT/font", self.setting.value("DEFAULT/font"))
            self.setting.setValue("FONT/fontsize", self.setting.value("DEFAULT/fontsize"))
            QtCore.QCoreApplication.instance().quit()

点击恢复默认设置时,会弹出提示框,用户点击确认后将关闭应用。
QtCore.QCoreApplication.instance().quit()可以保证关闭窗口时不会触发closeEvent事件,且不会报错。
在这里插入图片描述

五、最终实现代码

(1)配置文件

[DEFAULT]
screen=1280, 720
file=
files=
chapter=0
font=SimSun
fontsize=9
color=130, 135, 144
bg=./static/img/default.png

[SCREEN]
screen=1280, 720

[FILE]
file=
files=@Invalid()
chapter=11

[BACKGROUND]
bg=./static/img/default.png
color=130, 135, 144

[FONT]
font=SimSun
fontsize=9

(2)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

(3)主界面

import ctypes
import re
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox, QTreeWidgetItem, QFontDialog, \
    QColorDialog

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.treeWidget.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        self.textBrowser.setStyleSheet(
            f"background-color: rgba({self.color[0]}, {self.color[1]}, {self.color[2]},0.5);border: 1px solid #000000;border-color: rgb({self.color[0]}, {self.color[1]}, {self.color[2]})")
        # 打开文件
        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)
        # 设置字体和颜色
        self.actionfont.triggered.connect(self.select_font)
        self.actioncolor.triggered.connect(self.select_color)
        # 设置背景图片
        self.actionimport.triggered.connect(self.select_bg)
        self.setAutoFillBackground(True)
        # 关闭背景图片
        self.actionclose.triggered.connect(self.close_bg)
        # 恢复默认设置
        self.actiondefault.triggered.connect(self.default)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(self.rect(), QPixmap(self.bg))
        super().paintEvent(event)

    # 显示/隐藏目录
    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.setFont(QFont(self.fonts, self.fontsize))
        # 状态栏显示当前的章节内容和目录名
        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 select_font(self):
        # 弹出一个字体选择对话框。getFont()方法返回一个字体名称和状态信息。
        # 状态信息有OK和其他两种。
        font, ok = QFontDialog.getFont(QFont(self.fonts, self.fontsize), self, '选择字体和大小')
        # 如果点击OK,标签的字体就会随之更改
        if ok:
            self.textBrowser.setFont(font)
            self.fonts = font.family()
            self.fontsize = font.pointSize()

    # 选择颜色
    def select_color(self):
        col = QColorDialog.getColor(self.textBrowser.textColor(), self, "设置背景颜色")
        if col.isValid():
            print(col)
            self.treeWidget.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.textBrowser.setStyleSheet(
                f"background-color: rgba({col.red()}, {col.green()}, {col.blue()},0.5);border: 1px solid #000000;border-color: rgb({col.red()}, {col.green()}, {col.blue()})")
            self.color = [col.red(), col.green(), col.blue()]

    # 选择背景图片
    def select_bg(self):
        if not self.bg:
            path = '/'
        else:
            path = self.bg
        fname = QFileDialog.getOpenFileName(self, '选择图片', path, filter='*.jpg,*.png,*.jpeg,All Files(*)')
        # 文件不为空
        if fname[0]:
            try:
                # 更改目前的背景图片
                self.bg = fname[0]
                self.update()   # 刷新页面
            except:
                self.show_msg('文件不存在或者错误!')
        else:  # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    # 关闭背景图片
    def close_bg(self):
        self.bg = ''    # 将背景图片设置为空即可
        self.update()   # 刷新页面

    # 窗口移动事件,保存用户最后设置的窗口大小
    def resizeEvent(self, event):
        self.win = [event.size().width(), event.size().height()]

    # 恢复默认设置
    def default(self):
        result = QMessageBox.question(self, "恢复默认设置", "确定恢复默认设置?这将使您的设置全部失效且关闭应用",
                                      QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if result == QMessageBox.Yes:
            # 重新配置设置文件
            self.setting.setValue("SCREEN/screen", self.setting.value("DEFAULT/screen"))
            self.setting.setValue("FILE/file", self.setting.value("DEFAULT/file"))
            self.setting.setValue("FILE/files", self.setting.value("DEFAULT/files"))
            self.setting.setValue("BACKGROUND/bg", self.setting.value("DEFAULT/bg"))
            self.setting.setValue("BACKGROUND/color", self.setting.value("DEFAULT/color"))
            self.setting.setValue("FONT/font", self.setting.value("DEFAULT/font"))
            self.setting.setValue("FONT/fontsize", self.setting.value("DEFAULT/fontsize"))
            QtCore.QCoreApplication.instance().quit()

    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.win = self.setting.value("SCREEN/screen")  # 窗口大小
        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"))  # 上次浏览的章节
        self.fonts = self.setting.value("FONT/font")  # 字体
        self.fontsize = int(self.setting.value("FONT/fontsize"))  # 字体大小
        self.color = self.setting.value("BACKGROUND/color")  # 背景颜色
        self.bg = self.setting.value("BACKGROUND/bg")  # 背景图片

    # 保存设置
    def save_info(self):
        self.setting.setValue("SCREEN/screen", self.win)
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)
        self.setting.setValue("FILE/chapter", self.chapter)
        self.setting.setValue("FONT/font", self.fonts)
        self.setting.setValue("FONT/fontsize", self.fontsize)
        self.setting.setValue("BACKGROUND/color", self.color)
        self.setting.setValue("BACKGROUND/bg", self.bg)

    # 关闭窗口时更新窗口大小
    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_())  # 结束进程,退出程序

(4)初始界面

在这里插入图片描述

六、打包程序

(1)安装依赖

使用Python的pyinstaller打包,安装命令如下

pip install pyinstaller

(2)添加图标

安装完后,为了让程序更加完善,我们添加一个图标。
首先在static/img文件夹下添加一个图标文件,后缀名为ico。
可以将图片文件转换为图标文件,推荐一个网站https://www.zhengpic.com/ico/

(3)打包

打包命令如下。

pyinstaller -w -i static/img/reader.ico TXTReader.py

运行程序,发现报错。
使用Python库pyqt5制作TXT阅读器(四)——-应用设置和程序打包-小白菜博客
原因是确实配置文件和图片文件。
将config.inistatic文件夹复制到可执行文件所在的目录下。
可以运行了。
在这里插入图片描述