标签存档: timer

一个上传客户端的python实现(二)

FTP、C/S通讯都已经搞定了,接下来主要就是界面编程以及客户端逻辑了。本来随便写的一个界面,给老师看了后,说实在太土了,得重新写。现在想想当时写的真是不能用啊,都不知道设置layout,这样程序在窗口缩小的时候,里面的元素都不会变小的,很容易就看不见了。重写之前老老实实地看了Rapid_GUI_Programming_with_PyQt,果然还是得系统地学习。主要有三点:

1、用layout来布局,最后要给顶级窗口设置layout属性,这样缩放的时候就能自动改变大小

2、可以设置label 和其它控件的buddy关系,这样能利用快捷键迅速让某个控件获得焦点。

3、QStackedWidgete来设置安装程序时上一步下一步,主窗口内容切换的那种效果。不用我自己设置几个Group然后再手动控制。

界面画好了,接下来将里面都填充好,SIGNAL和SLOT都连接好。然后qt除了提供信号与槽这种方式外,还有和MFC类似的事件处理机制(其实SIGNAL、SLOT都是用event来实现的)。如果要简单地定义响应一个按钮被单击的函数,可以直接这样写:

@pyqtSignature(“”)
def on_ButtonName_clicked:

这样就不用再connect了,适合于事件和函数一对一的简单关系,不过也看个人习惯了。

当处理下拉框的时候碰到了问题,PyQt的QComboBox的下拉选项被更改的SIGNAL中,只提供了更改成哪一个,而没有从哪一个更改的。但是我有很多用户输入的框,我希望在用户点击下拉框更改它的选项的时候,保存前一个,再把这些框的值更新为当前被选中的元素的值。想了半天,没有什么好的办法,除非把每一个input的focusout都与保存的函数连接起来,但这样太累了。上网搜了下,最终确定要自己更改ComboBox对事件的处理方式。

PyQt中的app.exec_()函数其实就是启动了主窗口的事件循环,程序会保持着一个事件队列,里面的事件可能是系统发出的,可能是用户发出的。一般如果新产生一个事件,它会被利用postEvent()方法添加到队列尾,等待被处理,也就是说是异步处理,如果想要马上被处理的话,可以用sendEvent(),窗口则会不断地在某些时候处理这些事件(也可手动强制处理,通过processEvent()),简单地来说,它接收事件,然后查看是不是有对应的处理函数,如果有的话就调用相关函数进行处理,否则就丢弃。QT中还有一个事件过滤器的概念,如果把A对象添加为B对象的事件过滤器,那么所有送到B的事件都会先到达A,然后A再处理这些事件,一般A有两种选择,丢弃掉事件或转发给B。

知道了这些要解决这个问题就比较简单了,我自己创建了一个继承QComboBox的对象,在它的focusin事件中,emit一个信号,然后再在主窗口中把这个信号连接到处理函数。不过这样有一点坏处就是UI的代码是通过pyuic4编译ui文件得到的,每次都得重新手动去改那个文件,因为改一下ui,重新编译下我自己的改动就被覆盖了。不过在QtDesigner中好像也是可以使用自定义的累的,如果那样的话就不用这么麻烦了,但我没有去研究,估计也不会太难吧。

在文件上传的时候,我希望界面能够不被锁死,能继续和用户交互。在最初的版本中,我就直接用继承了threading.Thread类,每一个文件开一个线程然后调用它们的run,甚至都没有用一个list存储它们的名称。重写的时候这块当然是得大改的。首先要知道怎么管理多线程,线程间怎么通信。

在看了这篇后,对多线程大概有一个了解了。Queue其实就相当于是缓冲区,而往里面放数据的则是生产者,取数据的是消费者。Lock之类的也都比较简单,想想当时OS的知识,设计一下之间的通信流程管理逻辑也不难了。但是等我开始动手时,偶然在邮件列表看到有人说子线程不能直接操作GUI的,又去网上搜了下,发现绝大多数GUI框架都不是线程安全的。这下可又要重新想了。

主线程给子线程分配任务,这个可以直接对过Queue解决,子线程完成后如果通知主线程呢?我采用的方法是再开一个结果队列,子线程往上面放东西,而主线程去检测,当子线程没有再运行了并且两个队列都为空的时候处理过程完成,并且所有的结果都被主线程更新到GUI上去了。但如果去检测这个结果队列呢?

再次求助Google后,我找到了这篇,当时以为问题应该可以解决了,但是动手编完了后一试发现不行。分析了一下,limodou那篇文章里更新GUI就是在Controller里面,而我这里一定要在主窗口上,所以如果有Controller的话也要通知主窗口,所以主窗口一定要有一种机制去查询事件处理的状态。而不管用time.sleep(),还是用ChildThread.join()都无一会锁死窗口。我给limodou发了邮件询问,他也并没有说出一个解决的方法。不过大概地提供了几个子线程和主线程通信的方法,分别有queue,全局变量,lock,socket,事件循环等。

后来乱翻python的官方手册,发现在threading里有一个Timer类,当时就想如果这个能设定为一段时间后调用某个方法不是正好?比如每隔一秒调用一下检测函数,如果结果队列里有新数据的话就更新到主窗口,但仔细阅读后才发现它的用法是多长时间后调用一个子线程执行某个操作,大失所望,子线程就算执行了检测发现有新数据的话还是要和主线程通信的,又绕回来了。有没有这样一种时间函数,让主线程过一段时间来执行一个函数,但在这一段时间内没有阻塞呢?后来翻来翻去终于在PyQt里发现了QTimer这个类,它可以定时,并且不会阻塞!那问题就解决了,设定一个定时器,比如0.5秒钟,再把它的timeout SIGNAL连接到检测函数。在函数中先停止这个记时器,如果满足退出条件的话就退出,如果不满足的话,进行一些处理后让记时器重新开始记时。这样问题就解决了。

后来我开始思考这个问题时想到,不可能存在这样的一种记时函数的,让某个线程即不阻塞,又会在一段时间后来响应记时器,除非记时这个操作本身就是在另一个线程里进行的,它们到时间后进行通信,而这些都对用户透明,所以才会觉得它即不会阻塞线程,又能达到记时的目的。如果是在QT中的话,那它应该是通过事件来实现的,主线程可以和子线程通过事件来通信的。本来我也可以直接用事件,但当时觉得事件这块太大了,程序又比较急,以后再系统地学习一下比较好。其实到现在想到一个看起来最简单的方法,就是子线程emit一个SIGNAL,主线程来接收,不过我不知道信号能不能跨线程。因为connect里要指定发出SIGNAL的对象,我都不知道应该怎么写。