周末的休息

2018/04/22 Qthread

Posted by WangXiaoDong on April 22, 2018
    今天是周日,经过昨天一夜的大雨,早晨的天气真的非常清爽!今天准备思考一下论文对环境实际的意义,然后顺便
继续总结上周使用pyqt编程的经验。这样以后遇到同样的问题就能很快回忆起来啦~

今天准备总结一下pyqt编写界面的经验,这样以后遇到同样问题就可以查看并且很快回忆起来!

环境搭建

经过一周的研究,我发现使用pyqt编写界面最快的方式还是eric + pycharm

使用eric6pycharm同时打开你正在编写的程序,需要界面编写就在eric6里面完成,需要调试并且找到某个变量的类型 时,就在pycharm里面设置断点,然后进行断点调试。

主界面绘制经验

主界面绘制方法:

  • 打开左边Project-Viewer栏里面的Form标签
  • 右键选择New Form...里面的Main Window
  • 在弹出的Qt Design里面进行绘制即可

信号和槽的使用经验

如果想要在python中使用信号和槽函数,需要要求使用的类继承QObjcet。然后使用PyQt5.QtCore.pyqtSignal函数新建一个信号, 比如:

from PyQt5.QtCore import QObject, pyqtSignal

class Foo(QObject):

    # 定义一个无参数的信号'closed'。
    closed = pyqtSignal()

    # This defines a signal called 'rangeChanged' that takes two
    # integer arguments.
    range_changed = pyqtSignal(int, int, name='rangeChanged')

    # This defines a signal called 'valueChanged' that has two overloads,
    # one that takes an integer argument and one that takes a QString
    # argument.  Note that because we use a string to specify the type of
    # the QString argument then this code will run under Python v2 and v3.
    valueChanged = pyqtSignal([int], ['QString'])

定义了信号之后,就可以使用该信号的connect方法连接槽,或者使用emit方法发送信号,比如:

from PyQt5.QtCore import QObject, pyqtSignal

class Foo(QObject):

    # 新建一个无参数的信号
    trigger = pyqtSignal()

    def connect_and_emit_trigger(self):
        # 连接槽。
        self.trigger.connect(self.handle_trigger)

        # 发射信号。
        self.trigger.emit()

    def handle_trigger(self):
        # 打印该槽调用成功的消息。

        print "trigger signal received"

主界面和次级界面编程经验

我实际编写界面程序时的原则是:如果不需要次级窗口,就尽量不用,因为每多一个次级窗口,就代表要多两个.py文件。 但是当界面逻辑很多时,如果不用次级窗口,就会导致主窗口的槽函数非常多,看起来不方便。所以到底是否需要用次级窗口。 还是要根据你自己的习惯选择比较好。这里仅仅介绍如何实现次级窗口

Qt内置次级窗口

Qt内置次级窗口比较简单,只需要直接调用函数即可,比如保存对话框:

saveStrList, classify = QtWidgets.QFileDialog.getSaveFileName(self, '选择保存文件', os.path.join(self.FileDirStr, '检测出的异常类型'), 'text(*.txt)')

还有消息提示对话框:

QtWidgets.QMessageBox.about(self, '关于本软件', '本软件版权属于CNIC第314房间课题组~')

自定义对话框窗口

这里一定要使用QDialog,因为我发现使用QWidget 就没有关闭按钮,不知道为啥。但是Qdialog继续Qwidget,因此 使用它肯定能完成Qwidget的任务。

前两步跟编写主界面没区别,在Forms区域新建Qdialog,命名,然后打开QtDesign,绘制自己需要的控件,然后进行绘制即可.

然后在主界面中需要打开该对话框的位置输入代码:

    theSplitWidget = splitDialog()   #splitDialog是我自定义的一个次级窗口
    theSplitWidget.exec_()

主线程和次线程编程经验

在计算复杂任务的时候,为了保证主界面不失去响应,就必须要使用多线程编程。通常情况下,需要让主线程的界面上出现一些 进度条或者状态栏提示,让客户可以观察到复杂任务执行进度,这样才能让客户知道程序仍然在正常运行和执行的进度,决定是否 继续运行该复杂任务。

自己实现线程,需要继承PyQt5.QtCore.QThread类(一开始我继承(QObjcet,Thread.threading,发现有问题,最后想到还不如直接继承 Qt自带的线程类)。

这样可以自定义信号,因为主线程和次线程的通信要使用信号和槽函数,因为Qt的信号好槽是可以跨线程的,而子线程不能用于直接刷新界面。 如果直接自己定义信号和槽函数,按照“信号和槽的使用经验”里面讲的方式即可,这里创建的线程直接的通信只涉及基本数据结构,这样就不用在Qt中注册元信息了。

思考一下,如果直接主线程逻辑中新建定义一个线程,这样无法完成通信操作

对于复杂到数据结构的通信,我就只让子线程执行完毕后,将结果通过线程的一个函数getResult返回即可。

详细代码如下:

from PyQt5.QtCore import pyqtSignal,QThread

class theThread(QThread):
    def __init__(self):
        super(theThread, self).__init__()

    def setFun(self,fun):
        self.func = fun

    def setArgs(self,args):
        self.args = args
        self.FileDirStr = args[3]

    def getResult(self):
        return self.res

    def run(self):
        print('starting thread', self.name)
        self.res = self.func(self,self.args[0], self.args[1], self.args[2])
        print('finished thread', self.name)

    progress_changed = pyqtSignal(int, name='progressChanged')
    textBrower_changed = pyqtSignal(str, name='textBrowerChanged')
    status_changed = pyqtSignal(str, name='statusChanged')
    autoCheckBox_changed = pyqtSignal(bool, name='autoCheckBoxChanged')
    HeightLineEdit_changed = pyqtSignal(str, name='HeightLineEditChanged')

这样可以看出,在run函数中调用了该线程在设置setFun时的函数。可以看出,线程调用的需要复杂计算量的函数,最好使用 **kw关键字参数。因为在设置参数setArgs函数时必须确定传入的参数的数目,用一个关键字参数就可以解决很多问题。 同时函数中需要传入线程本身self的参数,需要更新界面状态就通过线程本身发射信号,这样主线程中开始该线程时要连接 对应信号即可。 必然在主线程中连接信号和槽函数的代码可以这样实现:

def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.thread = theThread.theThread()
        self.thread.progress_changed.connect(self.progressBar_gen.setValue)      #连接Qt的进度条自带的槽函数
        self.thread.textBrower_changed.connect(self.textBrowser_message.append)  #连接Qt的文本浏览器自带的槽函数
        self.thread.status_changed.connect(self.statusBar.showMessage)           #连接Qt的状态栏自带的槽函数
        self.thread.autoCheckBox_changed.connect(self.checkBox_auto.setChecked)  #连接Qt的选择框自带的槽函数
        self.thread.HeightLineEdit_changed.connect(self.lineEdit_height.setText) #连接Qt的行编辑自带的槽函数
        self.thread.finished.connect(self.threadFinished)                        #使用Qt的`Qthread`自带信号连接自定义的槽函数,后面需要实现threadFinished方法!!

注意:前五个槽连接的是Qt对应控件自带的槽函数,最后一个连接的是自定义槽,所以我后面实现了该槽的内容:

def threadFinished(self):
        try:
            IPReg = '(?<![\.\d])(?:25[0-5]\.|2[0-4]\d\.|[01]?\d\d?\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?![\.\d])'
            (patternLog_new, patternLog_old, modeClassDF, ParseDF, AnomalyList, timeMap, resultToShow) = self.thread.getResult()
            self.progressBar_gen.setValue(50)
            IPCompile = src.analysisAnomalyModeResult.getCompile(IPReg)
            result = dict()
            for typei in range(len(modeClassDF)):
                inputDf = modeClassDF[typei]
                inputDf.index = [i for i in range(inputDf.shape[0])]  #重排列Index,否则后面使用时会出错
                test = src.analysisAnomalyModeResult.dealOneAnomalyDF(inputDf,IPCompile)
                result['type'+str(typei)] = test
            self.progressBar_gen.setValue(75)
            fileNameForSaveResult = os.path.join(self.FileDirStr,'异常模式结果')
            src.analysisAnomalyModeResult.saveResult(fileNameForSaveResult,result)
            self.progressBar_gen.setValue(100)
            self.end = time.clock()
            self.statusBar.showMessage('生成异常模式完成!一共花费了%.2f秒' % (self.end-self.start))  # 状态栏提示信息
            #记录时间结果显示模块
            totalTime = self.end - self.start
            parseLogTime = timeMap['parseLogTime'] - self.start
            vectorTime = timeMap['vectorTime'] - timeMap['parseLogTime'] 
            logListTime = timeMap['logListTime'] - timeMap['vectorTime']
            filterTime = timeMap['filterTime'] - timeMap['logListTime']
            distanceTime = timeMap['distanceTime'] - timeMap['filterTime']
            clusterTime = timeMap['clusterTime'] - timeMap['distanceTime']
            
            self.textBrowser_message.append('\n生成异常模式完成!一共花费了%.2f秒:其中解析日志花费时间%.2f秒,占比%.2f%%;计算日志向量花费时间%.2f秒,占比%.2f%%; \
计算日志列表和日志模式花费时间%.2f秒,占比%.2f%%;异常流量筛选花费时间%.2f秒,占比%.2f%%;计算距离矩阵花费时间%.2f秒,占比%.2f%%;进行层次聚类花费时间%.2f秒,占比%.2f%%。'
            % (totalTime, parseLogTime, parseLogTime/totalTime*100, vectorTime, vectorTime/totalTime*100, logListTime, logListTime/totalTime*100, filterTime, filterTime/totalTime*100, 
            distanceTime, distanceTime/totalTime*100,clusterTime, clusterTime/totalTime*100,))
            QtWidgets.QMessageBox.information(self, '提醒', '生成异常模式完成!')
        except Exception as e:
            print(e)
            print(timeMap)
            self.statusBar.showMessage('生成异常模式失败!')  # 状态栏提示信息
            self.progressBar_gen.setValue(0)