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

一、保存设置

为了保存用户的设置,我们可以使用QSettings类来保存设置。首先新建一个cofig.ini文件。
在这里插入图片描述

[DEFAULT]下的内容代表应用的默认设置,在用户恢复默认设置时使用。[FILE]下的用户当前对文件的设置。
每次打开文件时,初始化设置。

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

    # 初始化设置
    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 = []

因为last_files是一个数组文件,所以当它为空时,要初始化为空数组。
然后在退出窗口时要保存配置文件。

# 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)

至于退出时的操作,我们之后再讲。

二、打开文件

为打开文件这个菜单栏设置一个触发器。

# 打开文件
        self.actionfile.triggered.connect(self.open_file)

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 load_file(self, file):
        # 文件不为空
        if file:
            try:
                # 更改目前打开的文件
                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:
                    txt = f.read()
                    self.textBrowser.setText(txt)
                    self.textBrowser.setStatusTip(self.filename)
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

因为事先不知道文本的编码格式,打开文件时会报错,所以需要使用get_encoding函数来获取文本编码。

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

show_msg是一个消息提示框,打开文件出错时会弹出弹窗。
打开文件的功能就完成了。
使用Python库pyqt5制作TXT阅读器(二)——-打开文件和浏览最近的文件-小白菜博客
不过目前我们还没有保存我们的设置,在关闭窗口时,我们弹出一个确认窗口,并且保存我们的配置。

    # 关闭窗口时更新窗口大小
    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 self.cur_file:
            self.load_file(self.cur_file)

在这里插入图片描述

目前代码如下。

import ctypes
import sys

import cchardet as cchardet
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox

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 load_file(self, file):
        # 文件不为空
        if file:
            try:
                # 更改目前打开的文件
                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:
                    txt = f.read()
                    self.textBrowser.setText(txt)
                    self.textBrowser.setStatusTip(self.filename)
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    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 = []

    # 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)

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

三、浏览最近打开的文件

菜单栏里我们已经添加了打开最近文件的功能,现在需要为其添加子动作显示最近打开的文件,并且设置触发器。

    # 显示最近的文件
    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])])

根据点击第几个动作来判断打开哪个文件。
同时在每次加载文件时都重新绘制一遍最近打开的文件,将下面那段代码添加到load_file函数中去。

# 显示最近打开的文件
                self.show_last_file()

在这里插入图片描述

完整代码如下:

import ctypes
import sys

import cchardet as cchardet
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QMessageBox

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:
                # 更改目前打开的文件
                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:
                    txt = f.read()
                    self.textBrowser.setText(txt)
                    self.textBrowser.setStatusTip(self.filename)
                    # 显示最近打开的文件
                    self.show_last_file()
            except:
                self.show_msg('文件不存在或者错误!')
        else:   # 文件为空,说明没有选择文件
            self.show_msg('您没有选择文件!')

    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 = []

    # 保存设置
    def save_info(self):
        self.setting.setValue("FILE/file", self.cur_file)
        self.setting.setValue("FILE/files", self.last_files)

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

下期我们讲怎么分割章节,切换章节内容。