PyQt5 笔记(04):主窗口卡死问题

本文基于:windows 7 + python 3.4

知识点:

 1. 将 time.sleep 替换为 QTimer

 2. 将 time.sleep 放入到 QThread

 3. 使用 QThread 自己的 sleep 方法

 

我们希望实现一个这样的小程序:

 

                                   

 

当点击开始按钮的时候,下面的文本标签每隔一秒自动加1。

 

 

一、直接用 time.sleep(1)

import time

class TestWindow(QDialog):
    def __init__(self):
        # ...

        btn1.clicked.connect(self.update) # 按钮连接到槽
        # ...

    def update(self):
        for i in range(20):
            time.sleep(1) # 每隔一秒
            self.sec += 1
            self.sec_label.setText(str(self.sec))

 

看起来没有任何逻辑上的错误。

那就运行一下看看,点击按钮。。。神马情况?主界面卡死了!如图

 

 

我猜测这可能与python的GIL问题有关:

  1. time库是纯python的,而PyQt的背后是Qt,这是纯C++的。

  2. 换句话说,就是time.sleep(1)时,并没有将CPU控制权交还给Qt,从而造成界面卡死

 

解决这个问题,既然不能用 python 的 time 库,那就用 PyQt 自己的 QTimer 类

 

二、使用 QTimer 类

class TestWindow(QDialog):
    def __init__(self):

        # ...

        timer = QTimer() # 计时器
        timer.timeout.connect(self.update)

        btn1.clicked.connect(lambda :timer.start(1000)) # 启动计时器,间隔1秒
        btn2.clicked.connect(lambda :timer.stop())

    def update(self):
        self.sec += 1
        self.sec_label.setText(str(self.sec))

 

再运行一下。。。 OK,搞定!如图:

 

 

完整代码:

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys

class TestWindow(QDialog):
    def __init__(self):

        super().__init__()

        self.sec = 0

        btn1 = QPushButton("Start", self)
        btn2 = QPushButton("Stop", self)
        self.sec_label = QLabel(self)

        layout = QGridLayout(self)
        layout.addWidget(btn1,0,0)
        layout.addWidget(btn2,0,1)
        layout.addWidget(self.sec_label,1,0,1,2)

        timer = QTimer()
        timer.timeout.connect(self.update) # 计时器挂接到槽:update
        btn1.clicked.connect(lambda :timer.start(1000))
        btn2.clicked.connect(lambda :timer.stop())

    def update(self):
        self.sec += 1
        self.sec_label.setText(str(self.sec))

app=QApplication(sys.argv)
form=TestWindow()
form.show()
app.exec_()

 

三、将 time.sleep 放入到 QThread

解决这个问题的另外一个思路:开一个线程,专门用于计时(即:专门运行 time.sleep)

 

在 QThread 中使用 time.sleep 和 for 循环,无压力!

当然,线程与主窗口的通信使用了信号/槽。

                                       

 

代码如下:

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import time

class TestWindow(QDialog):
    def __init__(self):
        super().__init__()

        btn1 = QPushButton("Start", self)
        btn2 = QPushButton("Stop", self)
        self.sec_label = QLabel(self)

        layout = QGridLayout(self)
        layout.addWidget(btn1,0,0)
        layout.addWidget(btn2,0,1)
        layout.addWidget(self.sec_label,1,0,1,2)

        thread = MyThread() # 创建一个线程
        thread.sec_changed_signal.connect(self.update) # 线程发过来的信号挂接到槽:update
        btn1.clicked.connect(lambda :thread.start())
        btn2.clicked.connect(lambda :thread.terminate()) # 线程中止

    def update(self, sec):
        self.sec_label.setText(str(sec))

class MyThread(QThread):  

    sec_changed_signal = pyqtSignal(int) # 信号类型:int

    def __init__(self, sec=1000, parent=None):
        super().__init__(parent)
        self.sec = sec # 默认1000秒

    def run(self):
        for i in range(self.sec):
            self.sec_changed_signal.emit(i)  #发射信号
            time.sleep(1)

app = QApplication(sys.argv)
form = TestWindow()
form.show()
app.exec_()

 

 

4. QThread 自身也有一个 sleep 方法

 

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

import sys

class Test(QDialog):
    def __init__(self,parent=None):
        super().__init__(parent)

        self.file_list = QListWidget()
        self.btn = QPushButton('Start')
        layout = QGridLayout(self)
        layout.addWidget(self.file_list,0,0,1,2)
        layout.addWidget(self.btn,1,1)

        self.thread = Worker()
        self.thread.file_changed_signal.connect(self.update_file_list)
        self.btn.clicked.connect(self.thread_start)

    def update_file_list(self, file_inf):
        self.file_list.addItem(file_inf)

    def thread_start(self):
        self.btn.setEnabled(False)
        self.thread.start()

class Worker(QThread):

    file_changed_signal = pyqtSignal(str) # 信号类型:str

    def __init__(self, sec=0, parent=None):
        super().__init__(parent)
        self.working = True
        self.sec = sec

    def __del__(self):
        self.working = False
        self.wait()

    def run(self):
        while self.working == True:
            self.file_changed_signal.emit('当前秒数:{}'.format(self.sec))
            self.sleep(1)
            self.sec += 1

app = QApplication(sys.argv)
dlg = Test()
dlg.show()
sys.exit(app.exec_())

 

补:QObject -> moveToThread 方式应用 QThread

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot

import time
import sys

class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)

    @pyqtSlot()
    def work(self): # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)

        self.finished.emit()

class Form(QWidget):
    def __init__(self):
       super().__init__()
       self.label = QLabel("0")

       # 1 - create Worker and Thread inside the Form
       self.worker = Worker()  # no parent!
       self.thread = QThread()  # no parent!

       self.worker.intReady.connect(self.updateLabel)
       self.worker.moveToThread(self.thread)
       self.worker.finished.connect(self.thread.quit)
       self.thread.started.connect(self.worker.work)
       #self.thread.finished.connect(app.exit)

       self.thread.start()

       self.initUI()

    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)
        grid.addWidget(self.label,0,0)

        self.move(300, 150)
        self.setWindowTitle('thread test')

    def updateLabel(self, i):
        self.label.setText("{}".format(i))
        #print(i)

app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())

 

时间: 2024-09-24 10:50:10

PyQt5 笔记(04):主窗口卡死问题的相关文章

在C#隐藏主窗口的几种方法

写过一个程序,要求在程序启动的时候主窗口隐藏,只在系统托盘里显示一个图标.一直以来采用的方法都是设置窗口的ShowInTaskBar=false, WindowState=Minimized.但是偶然发现尽管这样的方法可以使主窗口隐藏不见,但是在用Alt+Tab的时候却可以看见这个程序的图标并把这个窗口显示出来.因此这种方法其实并不能满足要求. 经过研究,又找到两个方法. 方法一: 重写setVisibleCore方法 protected override void SetVisibleCore

如何获取某个进程的主窗口以及创建进程的程序名

在编写工具程序以及系统管理程序的时候.常常需要获取某个进程的主窗口以及创建此进程的程序名.获取主窗口的目的是向窗口发送各种消息.获取启动进程的程序名可以控制对进程的操作.但是有些进程往往有多个主窗口.你要的是哪一个主窗口呢?如果你用过Outlook程序,你就会发现它有多个主窗口,一个窗口列出收件箱和其它文件夹.如果你打开e-mail,便会有另外一个窗口显示信息.它们都是没有父窗口(或者说宿主窗口)的主窗口.运行一下Spy程序,你甚至会发现它们的窗口类名都相同:rctrl_renwnd32.资源管

事件链接-C# 如何将子窗口的BUTTON按钮的CLICK事件传递给主窗口的BUTTON的CLICK事件?

问题描述 C# 如何将子窗口的BUTTON按钮的CLICK事件传递给主窗口的BUTTON的CLICK事件? 我想把在一个主窗口之下新建的一个窗口的打开按钮的事件和主窗口的打开按钮事件链接在一起,从而不管新建多少个子窗口,打开按钮都统一用主窗口的打开按钮,,,,,求完整代码,要C# WINFORM的... 解决方案 可以把主窗口中的处理操作单独写出来成一个函数,然后所有的点击函数都调用这个函数不就一样了么

基于Dialog程序,启动时不显示主窗口,只显示子窗口的实现

在项目中有如下的一个需求:软件在网络启动状态下显示主窗口,而在单机状态下只显示其子对话框(我的是无模式的).在网上找了一天,各种办法都似乎不太好,不过晚上终于找到了一个不错的解决办法.使得我很好的解决了这个问题.     if (theApp.m_bUnConnect)    {        CRecordDlg* dlg;        CWnd* m_pCWnd = this;        dlg = new CRecordDlg(m_pCWnd);        dlg->Create

qt c++-Qt中,到底如何实现主窗口和子窗口之间的通信?

问题描述 Qt中,到底如何实现主窗口和子窗口之间的通信? RT,比如,当子窗口关闭时,重新打开主窗口.这个是如何通信的,希望能给上例子.我知道是用信号和槽,可是两个窗口(类)之间的信号和槽我还不太会.网上也没找到具体的例子.希望大婶们能给个好点的直观的例子. 解决方案 主窗体类为A,子窗体类为B,在A中实例化B,其对象为b,关闭b,但不要释放b,调用b的public方法返回数据.仔细理解. void A::buttonClick(){ B b; b.exec(); b.getData();} 或

c#改变进程主窗口后,窗口上的timer,backgroundworker异常

问题描述 c#改变进程主窗口后,窗口上的timer,backgroundworker异常 原先program里的application.run是登录窗口,我现在改为主页面后,主页面上的timer控件设定的是1分钟执行1次,但是实际却1分钟执行3-5次,timer控件控制的backgroundworker也是,isbusy属性一直都是false,哪怕独立线程没结束,还是会运行.之前还好好的呢,为啥? 图片是main入口 下面是主页面上的1个timer和对于的独立线程. /// /// 数据质量监控

winform-Winform中如何保持登录窗口在主窗口的前面

问题描述 Winform中如何保持登录窗口在主窗口的前面 Winform中如何保持登录窗口在主窗口的前面,并且只有输入了正确的密码,才能切换掉主窗口? 解决方案 窗体.ShowDialog() 解决方案二: 一般是通过登录窗口验证到主窗口的,为何要保持在前面 解决方案三: 参考winform登陆后关闭登录窗口跳转到主窗体 解决方案四: 先做个你想要提前显示的窗体,然后用这个窗体类在form_load函数中创建对象,然后显示这个窗体即可.

【C/C++学院】(18)QT文件读写/主窗口类/获取host信息

1.文件读写 QT提供了QFile类用于文件读写. QFile可以读写文本文件,也可以读写二进制文件 #include <QFile> #include <QTextStream> 读文本文件例子 QString s; QFile file("abc.txt); if (file.open(QFile::ReadOnly)) { QTextStream stream(&file); while (!stream.atEnd()) { s = stream.read

wpf 主窗口,里面含有子窗口,可以在打开的子窗口之间切换

问题描述 wpf 主窗口,里面含有子窗口,可以在打开的子窗口之间切换 主窗口,里面含有子窗口,可以在打开的子窗口之间切换,也可以将打开的子窗口关闭,类似于tablecontrol是否可以在tablecontrol控件放窗口 解决方案 可以使用mdi窗口http://www.codeproject.com/Articles/32362/Tabbed-MDI-in-WPF