Search code examples
pythonpython-3.xpyqtpyqt5qtimer

Using Timers with QThread


I've got a Error like this:

QObject::startTimer: Timers can only be used with threads started with QThread

My Whole Code:

from PyQt5.QtWidgets import *
from newCode import mp3Download
from PyQt5.QtCore import QTimer,QThread
from threading import Thread
import sys


class ListDownload(QWidget):
    def __init__(self,path):
        super().__init__()
        self.path = path
        self.time = 100
        self.initUI()
    def initUI(self):
        self.VerticalLayout = QVBoxLayout()

        self.SeasonOfWitch = QLabel()
        self.SeasonOfWitch.setText("Enter the URLs : ")

        self.URLs = QTextEdit()
        self.URLs.setText("""enter the addresses in the here .a row for each url.
        Delete this message before entering the URLs.""")

        self.horizontalControlLayout = QHBoxLayout()
        self.download = QPushButton("Download")
        self.download.clicked.connect(self.videoDownload)

        self.Cancel = QPushButton("Cancel")
        self.Cancel.clicked.connect(self.cancelFunc)
        self.horizontalControlLayout.addWidget(self.download)
        self.horizontalControlLayout.addWidget(self.Cancel)


        self.VerticalLayout.addWidget(self.SeasonOfWitch)
        self.VerticalLayout.addWidget(self.URLs)
        self.VerticalLayout.addLayout(self.horizontalControlLayout)

        self.setLayout(self.VerticalLayout)        

    def cancelFunc(self):
        self.close()

    def videoDownload(self):
        self.urlList = self.URLs.toPlainText().split("\n")
        row = 1
        errorList = list()
        for url in self.urlList:
            if 'www.youtube.com' in url.split("/") and (url.startswith("https://") or url.startswith("http://")):
                row+=1

            else:
                errorList.append(row)
                row+=1

        decrease = 0#Each element deleting index changes the lenght of the list.Cause of that.
        for row in errorList:
            self.urlList.pop(row-1-decrease)
            decrease += 1

        messageObj = Thread(target=self.messageAnimation,name="message")
        downloadObj = Thread(target=self.downloadFunc,name="download")


        messageObj.start()
        downloadObj.start()

        while not(downloadObj.is_alive()):
            messageObj._stop()

    def downloadFunc(self):
        mp3Download(self.urlList,self.path)

    def messageAnimation(self):
        def timerFunc():
            self.animatedMessageFunc("Downloading ....")

        self.timer = QTimer()
        self.timer.timeout.connect(timerFunc)
        self.timer.start(1000)    


    def remove_widget(self,layout,widget_name):
        layout.removeWidget(widget_name)
        widget_name.deleteLater()
        widget_name = None

    def animatedMessageFunc(self,message):
        animatedMessage = message
        self.URLs.clear()
        iterator = iter(range(len(animatedMessage)))
        for i in range(len(animatedMessage)):
            QTimer.singleShot(self.time,lambda :self.URLs.setText(self.URLs.toPlainText()+animatedMessage[next(iterator)]))
            self.time += 50
        self.time = 100

Problem is the Timers. In animatedMessageFunc(). I want to start two function in same time.I use Thread class from threading module. I want it because when self.downloadFunc() ends I need to stop the self.messageAnimation() function. I try to use QThread instead Thread. But I can't figure it out how do I use this class.There is errors as mine.But that errors in java or some other language.I can't find my solution in that questions.


Solution

  • It is not necessary that the timer is in another thread, it is enough to start when the thread starts and stop it when the thread ends its task, for this we create 2 signals: started and finished that will be emitted before and after calling mp3Download() function, respectively.

    import sys
    from itertools import cycle
    from threading import Thread
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    from newCode import mp3Download
    
    
    class ListDownload(QtWidgets.QWidget):
        started = QtCore.pyqtSignal()
        finished = QtCore.pyqtSignal()
    
        def __init__(self, path):
            super().__init__()
            self.path = path
            self.initUI()
    
        def initUI(self):
            VerticalLayout = QtWidgets.QVBoxLayout(self)
    
            SeasonOfWitch = QtWidgets.QLabel("Enter the URLs : ")
    
            self.URLs = QtWidgets.QTextEdit()
            self.URLs.setText("""enter the addresses in the here .a row for each url.
            Delete this message before entering the URLs.""")
    
            horizontalControlLayout = QtWidgets.QHBoxLayout()
            download = QtWidgets.QPushButton("Download")
            download.clicked.connect(self.videoDownload)
    
            Cancel = QtWidgets.QPushButton("Cancel")
            Cancel.clicked.connect(self.close)
            horizontalControlLayout.addWidget(download)
            horizontalControlLayout.addWidget(Cancel)
    
            VerticalLayout.addWidget(SeasonOfWitch)
            VerticalLayout.addWidget(self.URLs)
            VerticalLayout.addLayout(horizontalControlLayout)
    
            self.started.connect(self.messageAnimation)
    
        def videoDownload(self):
            lines = self.URLs.toPlainText().split("\n")
            urls = []
            for line in lines:
                if 'www.youtube.com' in line.split("/") and (line.startswith("https://") or line.startswith("http://")):
                    urls.append(line)
            if urls:
                Thread(target=self.downloadFunc, args=(urls,), name="download", daemon=True).start()
    
        def downloadFunc(self, urls):
            self.started.emit()
            mp3Download(urls, self.path)
            self.finished.emit()
    
        def messageAnimation(self):
            self.URLs.clear()
            text = "Downloading ...."
            timer = QtCore.QTimer(self, interval=50)
            it = cycle(text+"\n")
            timer.timeout.connect(lambda: self.appendLetter(next(it)))
            timer.start()
            self.finished.connect(timer.stop)
            self.finished.connect(self.URLs.clear)
    
        def appendLetter(self, letter):
            if letter == "\n":
                self.URLs.clear()
            else:
                self.URLs.moveCursor(QtGui.QTextCursor.End)
                self.URLs.insertPlainText(letter)
                self.URLs.moveCursor(QtGui.QTextCursor.End)
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        path = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
        w = ListDownload(path)
        w.show()
        sys.exit(app.exec_())