嗯嗯,最近几周荒废了手头的一切事情,搞出来一个这个。可把胖虎累成大雄了(~__~)~zZ

先放程序预览图:

1571591453850

好久没写博文辣,今天咱就来撸一篇,记录一下这个项目的心得。

说起来,这算是我写的第三个Typecho博客管理的程序了。

  • 第一个程序(typecho_post)算是入门,功能仅限上传和创建分类。甚至当时没分析好token(因为当时没接触过PHP,实在读不懂源代码),所以被迫将token固定住。显然,这样很容易受到攻击。
  • 第二个程序(typecho_desktop_cmd)于我而言,是MySQL学习的一大助推力。没错,我,使用pymysql,近似地写了个Typecho的Python后端。后悔死我了。因为初学数据库,各方面处理的都不到位,考虑的并不全面。因为是直接对数据库操作,所以可能插入一些奇奇怪怪的东西,这些东西我都不会处理。而且最最重要的是,我的处理机制和官方的处理机制可能不可能完全相同,一旦有一处不同(比如说少插入了一列数据),后期在web段处理的时候就可能导致错误。轻则显示异常,重则直接报数据库错误。
  • 第三个程序(typecho_desktop)就是这个啦。为防止自己写入数据出错,所以,所有涉及数据库改动的操作均模拟web端的操作发包,只有部分读取数据的操作使用pymysql获取。最最重要的是,这个是可视化的程序啦。

Qt creater

Qt creater可以设计界面,得到.ui文件,然后通过PyQt5-tools中的pyuic5可以将其转化为.py代码。大大节省设计时间。

1571591866234

文件->新建文件或项目->Qt 设计师界面类(英文版为Qt designer)->choose

1571591892079

Dialog代表对话框界面,Main Window是主界面,可以根据实际选择。Widget是基类。

主界面并非一个项目只能有一个,不要走入误区。

1571592093927

设置基本信息,不懂的话默认即可。其实只需要改一下类名就好,对我们之后的工作有影响的只有类名。

1571592237916

靠左侧的一列选项是各种设计元素,点击后拖动到设计框中即可。

然后就可以开始设计了。这里不过多展开,毕竟网上一堆教程,说话又好听。

1571593001528

设计好之后保存,得到*.ui文件。

我们shift+鼠标右键

1571592467653

打开PowerShell,键入

pyuic5 -o xxx.py xxx.ui

1571592981503

即可转换成相应的python代码

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(341, 157)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(30, 50, 54, 12))
        self.label.setObjectName("label")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(90, 50, 221, 20))
        self.lineEdit.setObjectName("lineEdit")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 341, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "示例"))

但是这只是设计界面,并没有运行的语句

我们在代码的末尾添上以下初始化类的代码

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ex = Ui_MainWindow()            #创建对象
    w = QtWidgets.QMainWindow()
    ex.setupUi(w)                    #调用setupUi进行初始化
    w.show()                        #显示窗口
    sys.exit(app.exec_())

然后运行就可以了。

PyQt5

界面类的继承

我们分别设计好不同操作的界面后,如何整合在主程序中呢?

很简单,在主程序main.py中创建多个类,分别继承之前的界面的类就好啦。

比如

from typecho.gui.mainwindow import Ui_MainWindow    #导入设计好的主界面的类

class MainWindow(QMainWindow, Ui_MainWindow):        #新建的类继承QMainWindow和自定义的类Ui_MainWindow
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)    #super是用来解决多重继承问题的
        self.setupUi(self)                            #父类Ui_MainWindow的函数,用于初始化界面
        
def main():
    app = QApplication(sys.argv) 
    mainwindow = MainWindow()  
    mainwindow.show()                                内置方法,用于显示界面
    sys.exit(app.exec_())

if __name__=="__main__":  
    main()

信号与槽函数

基础

打开缓存目录的相关代码为例

#打开缓存目录
def open_dir():
    operation = Operation()
    return operation.open_dir()
mainwindow.pushButton_open_dir.clicked.connect(open_dir)

open_dir()是槽函数,clicked是内置的信号。connect的作用是将此信号连接到槽函数。

当mainwindow(对象)的pushButton_open_dir这个按钮被点击时,会发送一个clicked信号,从而触发槽函数。

通过lambda传递参数

connect的参数是一个表达式,无法传递参数。可以使用lambda,因为lambda本质上就是一个表达式,通过它可以把函数的参数隐藏在表达式内部,从而达到传递参数的作用。

下载并打开文章的相关代码为例

#快速更新独立页面
mainwindow.pushButton_update_page_fast.clicked.connect(
    lambda:  QMessageBox.warning(None, '提示', '请先进入独立页面界面!', QMessageBox.Ok, QMessageBox.Ok)
    if mainwindow.list_info != 'pages'
    else QMessageBox.warning(None, '提示', '请先选中要快速更新的独立页面!', QMessageBox.Ok, QMessageBox.Ok)
    if mainwindow.listView_passages.currentIndex().row() == -1 
    else update_page_fast())

def update_page_fast():
    operation = Operation()
    return operation.update_page_fast(mainwindow.passages_detail_list[mainwindow.listView_passages.currentIndex().row()]['cid'])

表达式有些长,我简写一下:

AAA.BBB.clicked.connect(lambda: A(x,y,z) if bool_1 else B(x,y,z) if bool_2 else C(x,y,z))

其中的lambda表达式换成函数,可以表示为:

def func():
    if bool_1:
        A(x,y,z)
    elif bool_2:
        B(x,y,z)
    elss:
        C(x,y,z)

自定义信号

有时候完成一定的任务后需要做一些操作,但是由于跨文件等原因,没法直接调用相关函数,因为无法判断何时完成操作。这时可以自定义信号,任务完成后发送信号,主程序中接受信号并调用槽函数。

#create_post.py

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_create_post(object):
    def setupUi(self, create_post):
        pass
        
    list_update_passages_signal = QtCore.pyqtSignal()    #自定义信号
    
    pass
    
    def start(self):
        #doing somgthing
        self.list_update_passages_signal.emit()            #emit发射信号
#main.py
#创建文章
    create_post = Create_post()
    mainwindow.pushButton_create_post.clicked.connect(
        lambda: create_post.open(mainwindow.metas_detail_list, mainwindow.listView_metas.currentIndex().row()))
    create_post.list_update_passages_signal.connect(
        lambda: create_post.pass_value(mainwindow.metas_detail_list))    #将自定义信号与槽函数连接
    create_post.list_update_passages_signal.connect(
        lambda: mainwindow.list_posts(0) 
            if mainwindow.listView_metas.currentIndex().row() == -1 or mainwindow.listView_metas.currentIndex().row() == 0
            else mainwindow.list_posts(mainwindow.metas_detail_list[mainwindow.listView_metas.currentIndex().row()]['mid']))    #将自定义信号与槽函数连接

总体就三条语句。

创建自定义信号,发射信号,连接信号与槽函数。

控件

下面记录一下我用到的控件(QtWidgets)以及了解到的方法函数

文本框QLineEdit

text()获取文本

setText(str)设置文本

setInputMask('0000-00-00 00:00;_')设置文本框掩码(具体继续百度)

文本段QTextEdit

toPlainText()获取文本

下拉菜单QComboBox

currentIndex()获取当前所在行索引(从0开始, -1为未选中)

setCurrentIndex(int)设置索引

addItems(list) 添加选项

clear()清空选项

复选框QCheckBox

setText()设置复选框右侧文字

setChecked(bool)设置选中/未选中

isChecked()是否选中

列表QListView

QListView没有数据库,所以需要配合QtCore.QStringListModel

self.listView_metas = QtWidgets.QListView(self.tab)
self.listView_metas.setGeometry(QtCore.QRect(-1, -1, 241, 523))
self.listView_metas.setObjectName("listView_metas")

self.slm = QtCore.QStringListModel();            #创建对象
self.slm.setStringList(self.metas_name_list)    #加载列表内容
self.listView_metas.setModel(self.slm)            #将StringListModel加载到QListView
self.listView_metas.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)    #禁止双击编辑文字
self.listView_metas.clicked.connect(self.clicked_meta)

currentIndex() #获取当前索引(不是int哦,而是对象)

currentIndex().row() #当前所在行

打包

如果需要重新编译并打包成exe的话,可以参考以下内容

环境win10 x86(虚拟机), python 3.7 x86

pip install PyQt5==5.11.3
pip install PyQt5-tools==5.9.2.1.3
pip install PyQt5-sip==4.19.19
pip install pyinstaller

然后在cmd(或PowerShell)中进入项目根目录,运行

pyinstaller -i typecho.ico -F -w main.py

成功后可在dist文件夹内看到exe文件

相关打包具体事宜记录在:PyQt5真香啊

其他

导入数据库问题

从linux导出后无法导入至windows

程序写好后,重新上传了所有的文章,并在linuxd导入了blog.sql,但是导入windows时失败

1571580949078

因为linux默认utf8,windows默认gbk。可使用下面的命令导入。

mysql -uroot -p --default-character-set=utf8 blog3 < d:/桌面/blog.sql

注意:

  • d:/桌面/blog.sqld:\\桌面\\blog.sql都行,千万不能d:\桌面\blog.sql,因为转义(虽然我仍然没搞明白命令行里又不是mysql语句,为什么会转义)
  • --default-character-set=utf8指定utf8

靠该命令解决了typecho博客数据库在本地导入失败的问题。(之前导入一直用source)

Last modification:September 9th, 2020 at 03:19 pm