i'm trying to fix problem in my program and this problem is when i start download video the program not responding and i can't see also progress bar move so i tried used threading module but i can't fix problem so how i can fix problem
From this code I can download the video and send the data to another function to retrieve information that I use to connect it to the progress bar
def video(self):
video_url = self.lineEdit_4.text()
video_save = self.lineEdit_3.text()
pafy_video = pafy.new(video_url)
type_video = pafy_video.videostreams
quality = self.comboBox.currentIndex()
start_download = type_video[quality].download(filepath=video_save,callback=self.video_progressbar)
This code is received information from video function to connect with progress bar
def video_progressbar(self,total, recvd, ratio, rate, eta):
self.progressBar_2.setValue(ratio * 100)
I use;python3.5 pyqt5 pafy
One way to move to another thread is to create a QObject that lives in another thread and execute that task in a slot. And that slot must be invoked through QMetaObject::invokeMethod or a signal.
with QThread and QMetaObject::invokeMethod:
import pafy
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.le_url = QtWidgets.QLineEdit("https://www.youtube.com/watch?v=bMt47wvK6u0")
path = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self.le_output = QtWidgets.QLineEdit(path)
self.btn_quality = QtWidgets.QPushButton("Get qualities")
self.combo_quality = QtWidgets.QComboBox()
self.btn_download = QtWidgets.QPushButton("Download")
self.progressbar = QtWidgets.QProgressBar(maximum=100)
self.downloader = DownLoader()
thread = QtCore.QThread(self)
thread.start()
self.downloader.moveToThread(thread)
self.btn_quality.clicked.connect(self.on_clicked_quality)
self.btn_download.clicked.connect(self.download)
self.btn_download.setDisabled(True)
self.downloader.progressChanged.connect(self.progressbar.setValue)
self.downloader.qualitiesChanged.connect(self.update_qualityes)
self.downloader.finished.connect(self.on_finished)
form_lay = QtWidgets.QFormLayout(central_widget)
form_lay.addRow("Url: ", self.le_url)
form_lay.addRow(self.btn_quality)
form_lay.addRow("qualities: ", self.combo_quality)
form_lay.addRow("Output: ", self.le_output)
form_lay.addRow(self.btn_download)
form_lay.addRow(self.progressbar)
#QtCore.pyqtSlot()
def on_finished(self):
self.update_disables(False)
#QtCore.pyqtSlot()
def on_clicked_quality(self):
video_url = self.le_url.text()
QtCore.QMetaObject.invokeMethod(self.downloader, "get_qualities",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, video_url))
#QtCore.pyqtSlot(list)
def update_qualityes(self, types_of_video):
for t in types_of_video:
self.combo_quality.addItem(str(t), t)
self.btn_download.setDisabled(False)
#QtCore.pyqtSlot()
def download(self):
video_save = self.le_output.text()
d = self.combo_quality.currentData()
QtCore.QMetaObject.invokeMethod(self.downloader, "start_download",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(object, d),
QtCore.Q_ARG(str, video_save))
self.update_disables(True)
def update_disables(self, state):
self.combo_quality.setDisabled(state)
self.btn_quality.setDisabled(state)
self.le_output.setDisabled(state)
self.le_url.setDisabled(state)
self.btn_download.setDisabled(not state)
class DownLoader(QtCore.QObject):
progressChanged = QtCore.pyqtSignal(int)
qualitiesChanged = QtCore.pyqtSignal(list)
finished = QtCore.pyqtSignal()
#QtCore.pyqtSlot(str)
def get_qualities(self, video_url):
pafy_video = pafy.new(video_url)
types_of_video = pafy_video.allstreams # videostreams
self.qualitiesChanged.emit(types_of_video)
#QtCore.pyqtSlot(object, str)
def start_download(self, d, filepath):
d.download(filepath=filepath, callback=self.callback)
def callback(self, total, recvd, ratio, rate, eta):
val = int(ratio * 100)
self.progressChanged.emit(val)
if val == 100:
self.finished.emit()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(320, 480)
w.show()
sys.exit(app.exec_())
with threading.Thread:
import pafy
import threading
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
qualitiesChanged = QtCore.pyqtSignal(list)
progressChanged = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.le_url = QtWidgets.QLineEdit("https://www.youtube.com/watch?v=bMt47wvK6u0")
path = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self.le_output = QtWidgets.QLineEdit(path)
self.btn_quality = QtWidgets.QPushButton("Get qualities")
self.combo_quality = QtWidgets.QComboBox()
self.btn_download = QtWidgets.QPushButton("Download")
self.progressbar = QtWidgets.QProgressBar(maximum=100)
self.btn_quality.clicked.connect(self.on_clicked_quality)
self.btn_download.clicked.connect(self.download)
self.btn_download.setDisabled(True)
self.progressChanged.connect(self.progressbar.setValue)
self.qualitiesChanged.connect(self.update_qualityes)
self.finished.connect(self.on_finished)
form_lay = QtWidgets.QFormLayout(central_widget)
form_lay.addRow("Url: ", self.le_url)
form_lay.addRow(self.btn_quality)
form_lay.addRow("qualities: ", self.combo_quality)
form_lay.addRow("Output: ", self.le_output)
form_lay.addRow(self.btn_download)
form_lay.addRow(self.progressbar)
#QtCore.pyqtSlot()
def on_finished(self):
self.update_disables(False)
#QtCore.pyqtSlot()
def on_clicked_quality(self):
video_url = self.le_url.text()
threading.Thread(target=self.get_qualities, args=(video_url,)).start()
def get_qualities(self, video_url):
pafy_video = pafy.new(video_url)
types_of_video = pafy_video.allstreams # videostreams
self.qualitiesChanged.emit(types_of_video)
#QtCore.pyqtSlot(list)
def update_qualityes(self, types_of_video):
for t in types_of_video:
self.combo_quality.addItem(str(t), t)
self.btn_download.setDisabled(False)
#QtCore.pyqtSlot()
def download(self):
video_save = self.le_output.text()
d = self.combo_quality.currentData()
threading.Thread(target=d.download, kwargs={'filepath': video_save, 'callback': self.callback}, daemon=True).start()
def callback(self, total, recvd, ratio, rate, eta):
print(ratio)
val = int(ratio * 100)
self.progressChanged.emit(val)
if val == 100:
self.finished.emit()
def update_disables(self, state):
self.combo_quality.setDisabled(state)
self.btn_quality.setDisabled(state)
self.le_output.setDisabled(state)
self.le_url.setDisabled(state)
self.btn_download.setDisabled(not state)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(320, 480)
w.show()
sys.exit(app.exec_())
Related
I have a pyside6 app which has 2 TextEdit widgets and I want the 2 TextEdits to scroll in sync.
The following code is used for this function:
self.scrollconnect1 = self.TextEdit_1.verticalScrollBar().valueChanged.connect(
self.TextEdit_2.verticalScrollBar().setValue)
self.scrollconnect2 = self.TextEdit_2.verticalScrollBar().valueChanged.connect(
self.TextEdit_1.verticalScrollBar().setValue)
It works fine when there are few lines in TextEdit, but if there are too many lines (about 50,000 lines) in both TextEdit, it will not precisely synchronize in same line. (Especially when I scroll the widget by both mouse wheel and scroll bar).
How to keep them synchronize precisely? Thank you!
Following code is for test:
from PySide6 import QtCore, QtGui, QtWidgets
class ScrollBar(QtWidgets.QScrollBar):
valueUpdated = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTracking(True)
self.actionTriggered.connect(self.handleAction)
def handleAction(self, action):
action = QtWidgets.QAbstractSlider.SliderAction(action)
print(self.objectName(), action, (self.value(), self.sliderPosition()))
if self.value() != self.sliderPosition():
self.valueUpdated.emit(self.sliderPosition())
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.editTop = QtWidgets.QPlainTextEdit()
self.editBtm = QtWidgets.QPlainTextEdit()
self.editTop.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-TOP'))
self.editBtm.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-BTM'))
self.editTop.verticalScrollBar().actionTriggered.connect(
lambda a: self.editBtm.verticalScrollBar().triggerAction(
QtWidgets.QAbstractSlider.SliderAction(a)))
self.editTop.verticalScrollBar().valueUpdated.connect(
self.editBtm.verticalScrollBar().setValue)
self.editBtm.verticalScrollBar().valueUpdated.connect(
self.editTop.verticalScrollBar().setValue)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.editTop)
layout.addWidget(self.editBtm)
line = ' text' * 40
# for index in range(50000):
for index in range(500):
self.editTop.insertPlainText("\n"+str(index) + line)
self.editBtm.insertPlainText("\n"+str(index) + line)
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(800, 100, 600, 400)
window.show()
app.exec()
Test Script:
from PySide6 import QtCore, QtGui, QtWidgets
class ScrollBar(QtWidgets.QScrollBar):
valueUpdated = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTracking(True)
self.actionTriggered.connect(self.handleAction)
def handleAction(self, action):
action = QtWidgets.QAbstractSlider.SliderAction(action)
print(self.objectName(), action, (self.value(), self.sliderPosition()))
if self.value() != self.sliderPosition():
self.valueUpdated.emit(self.sliderPosition())
def sliderChange(self, change):
change = QtWidgets.QAbstractSlider.SliderChange(change)
if (change == QtWidgets.QAbstractSlider.SliderValueChange and
self.signalsBlocked()):
self.blockSignals(False)
print(self.objectName(), change, self.value())
self.valueUpdated.emit(self.value())
super().sliderChange(change)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
# self.editTop = QtWidgets.QTextEdit()
# self.editBtm = QtWidgets.QTextEdit()
self.editTop = QtWidgets.QPlainTextEdit()
self.editBtm = QtWidgets.QPlainTextEdit()
self.editTop.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-TOP'))
self.editBtm.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-BTM'))
self.editTop.verticalScrollBar().valueUpdated.connect(
self.editBtm.verticalScrollBar().setValue)
self.editBtm.verticalScrollBar().valueUpdated.connect(
self.editTop.verticalScrollBar().setValue)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.editTop)
layout.addWidget(self.editBtm)
text = ' text' * 20
# for index in range(50000):
for index in range(500):
line = f'{index}{text}\n'
self.editTop.insertPlainText(line)
self.editBtm.insertPlainText(line)
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(800, 100, 600, 400)
window.show()
app.exec()
I built a google search suggestion completer for my simple browser built with pyqt5 and python. code is below.
self.url_bar = QLineEdit()
self.url_bar.textChanged.connect(self.suggest)
def suggest(self):
suggestlist =[]
term = self.url_bar.text()
if not (validators.url(term) and term != '') :
url = f"http://toolbarqueries.google.com/complete/search?q={term}&output=toolbar&hl=en"
suggestions = requests.get(url)
suggestions = ET.fromstring(suggestions.content)
for data in suggestions.iter('suggestion'):
suggestlist.append(data.attrib['data'])
suggester = QCompleter(suggestlist)
self.url_bar.setCompleter(suggester)
this generates the suggestions correctly but browser is accidentially closed. please help me.
Based on my previous post and making some modifications you can have the following solution. I don't use requests as it potentially blocks the eventloop if the request takes too long.
import xml.etree.ElementTree as ET
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
class SuggestionModel(QtGui.QStandardItemModel):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(SuggestionModel, self).__init__(parent)
self._manager = QtNetwork.QNetworkAccessManager(self)
self._reply = None
#QtCore.pyqtSlot(str)
def search(self, text):
self.clear()
if self._reply is not None:
self._reply.abort()
if text:
r = self.create_request(text)
self._reply = self._manager.get(r)
self._reply.finished.connect(self.on_finished)
loop = QtCore.QEventLoop()
self.finished.connect(loop.quit)
loop.exec_()
def create_request(self, text):
url = QtCore.QUrl("http://toolbarqueries.google.com/complete/search")
query = QtCore.QUrlQuery()
query.addQueryItem("q", text)
query.addQueryItem("output", "toolbar")
query.addQueryItem("hl", "en")
url.setQuery(query)
request = QtNetwork.QNetworkRequest(url)
return request
#QtCore.pyqtSlot()
def on_finished(self):
reply = self.sender()
if reply.error() == QtNetwork.QNetworkReply.NoError:
content = reply.readAll().data()
suggestions = ET.fromstring(content)
for data in suggestions.iter("suggestion"):
suggestion = data.attrib["data"]
self.appendRow(QtGui.QStandardItem(suggestion))
self.error.emit("")
elif reply.error() != QtNetwork.QNetworkReply.OperationCanceledError:
self.error.emit(reply.errorString())
else:
self.error.emit("")
self.finished.emit()
reply.deleteLater()
self._reply = None
class Completer(QtWidgets.QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionModel(self)
completer = Completer(self, caseSensitivity=QtCore.Qt.CaseInsensitive)
completer.setModel(self._model)
lineedit = QtWidgets.QLineEdit()
lineedit.setCompleter(completer)
label = QtWidgets.QLabel()
self._model.error.connect(label.setText)
lay = QtWidgets.QFormLayout(self)
lay.addRow("Location: ", lineedit)
lay.addRow("Error: ", label)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(400, w.sizeHint().height())
w.show()
sys.exit(app.exec_())
So , I am trying to blink the push button with the color that I pass and between white. And it only seems to blink it so many times after that code would crash.
I have tried it to implement the blinking with different blink rates and it
would it still break at some time.
in 'a' I have the string like "Background-color: rgb(255,0,0)".
in 'timings' i have a list like[208, 280]or it could be [48,32,48,32,100,280], this represents the on and off timings 'zeroth index' represents on and 'index 1' represents off time and follows the pattern so on.
while True:
i = 0
while i < len(timings):
if self.p[2] == 1:
self.b.setStyleSheet("{}".format(a))
self.b.update()
time.sleep(timings[i]/1000)
self.b.setStyleSheet("Background-color: rgb(255,255,255)")
self.b.update()
time.sleep(timings[i+1]/1000)
i = i + 2
self.head1, self.head2, self.head3 all have list with some thing like this ["Background-color:rgb(255,0,0)",list of on and off patterns, head number#]
right now, I am working with three heads.
def flash(self):
obj1 = threads(self.head1, self.head1_pb)
obj2 = threads(self.head2, self.head2_pb)
obj3 = threads(self.head3, self.head3_pb)
obj1.start()
time.sleep(.02)
obj2.start()
time.sleep(.02)
obj3.start()
class threads(Thread):
def __init__(self, a, pb):
Thread.__init__(self)
self.p = a
self.b = pb
def run(self):
a = self.p[0]
timings = self.p[1]
print(timings[0])
while True:
i = 0
while i < len(timings):
if self.p[2] == 1:
self.b.setStyleSheet("{}".format(a))
self.b.update()
time.sleep(timings[i]/1000)
self.b.setStyleSheet("Background-color: rgb(255,255,255)")
self.b.update()
time.sleep(timings[i+1]/1000)
i = i + 2
elif self.p[2] == 2:
self.b.setStyleSheet("{}".format(a))
self.b.update()
time.sleep(timings[i]/1000)
self.b.setStyleSheet("Background-color: rgb(255,255,255)")
self.b.update()
time.sleep(timings[i+1]/1000)
i = i + 2
else:
self.b.setStyleSheet("{}".format(a))
self.b.update()
time.sleep(timings[i]/1000)
self.b.setStyleSheet("Background-color: rgb(255,255,255)")
self.b.update()
time.sleep(timings[i+1]/1000)
i = i + 2
You can change the color as many times as you want, the problem is that you should not use a time consuming loop or use time.sleep() because they block the eventloop generating the GUI to freeze. Instead, use a QTimer to call the task that changes color every so often.
In the following example, create a custom button that implements what you want:
from PySide2 import QtCore, QtGui, QtWidgets
class PushButton(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super(PushButton, self).__init__(*args, **kwargs)
self._color = QtGui.QColor("white")
timer_on = QtCore.QTimer(singleShot=True, timeout=self.on_timeout)
timer_off = QtCore.QTimer(singleShot=True, timeout=self.on_timeout)
self._timers = (timer_on, timer_off)
for timer, function in zip(self._timers, (self.on, self.off)):
timer.timeout.connect(function)
def setTime(self, on_time, off_time):
for t, timer in zip((on_time, off_time), self._timers):
timer.setInterval(t)
#QtCore.Slot()
def on_timeout(self):
timer = self.sender()
if timer not in self._timers:
return
timer_on, timer_off = self._timers
another_timer = timer_off if timer is timer_on else timer_on
another_timer.start()
def start(self):
timer_on, _ = self._timers
timer_on.start()
def stop(self):
for timer in self._timers:
timer.stop()
self.off()
def color(self):
return self._color
def setColor(self, color):
if self.color() == color:
return
self._color = color
def on(self):
self.setStyleSheet(
"""PushButton{ background-color: %s}""" % (self.color().name(),)
)
def off(self):
self.setStyleSheet(
"""PushButton{ background-color: rgb(255,255,255)}"""
)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
button = PushButton()
button.setColor(QtGui.QColor("salmon"))
button.setTime(208, 280)
button.start()
# stop blink in 30 seconds
# QtCore.QTimer.singleShot(30 * 1000, button.stop)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyle("fusion")
w = Widget()
w.show()
sys.exit(app.exec_())
Plus:
As you note that you have a lot of data, it is best to create an iterator to save memory. Considering the above I have created a QPushButton that obtains the data from the iterator.
import random
from functools import partial
from PySide2 import QtCore, QtGui, QtWidgets
def generate_data():
i = 0
while i < 1000000:
color_on = random.randint(10, 500)
color_off = random.randint(10, 500)
color = QtGui.QColor(*random.sample(range(255), 3))
yield color_on, color_off, color
i += 1
class PushButton(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super(PushButton, self).__init__(*args, **kwargs)
self._color = QtGui.QColor("white")
self._generator = None
self.m_timer = QtCore.QTimer(
self, timeout=self.on_timeout, singleShot=True
)
def setGenerator(self, generator):
self._generator = generator
def start(self):
self.on_timeout()
#QtCore.Slot()
def on_timeout(self):
try:
time_on, time_off, color = next(self._generator)
self.setColor(color)
self.m_timer.start(time_on + time_off)
QtCore.QTimer.singleShot(
time_on, partial(self.setColor, QtGui.QColor("white"))
)
except StopIteration:
self.m_timer.stop()
def setColor(self, color):
self.setStyleSheet(
"""PushButton{ background-color: %s}""" % (color.name(),)
)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
for _ in range(6):
button = PushButton()
button.setGenerator(generate_data())
button.start()
lay.addWidget(button)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyle("fusion")
w = Widget()
w.resize(320, 240)
w.show()
sys.exit(app.exec_())
I want to show a spinner while a task is being performed in my pyqt5 application. I found this nice implementation of a spinner, so I tried it: https://github.com/z3ntu/QtWaitingSpinner
The demo works ok, but in the demo the spinner is shown into an empty area of the dialog. I would like it to be an overlay which covers the whole dialog.
The author of QtWaitingSpinner suggests that "As an alternative example, the code below will create a spinner that (1) blocks all user input to the main application for as long as the spinner is active, (2) automatically centers itself on its parent widget every time "start" is called and (3) makes use of the default shape, size and color settings." with the following code:
spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)
spinner.start() # starts spinning
But I tried this implementation as an example, and it didn't work:
from PyQt5.QtWidgets import QApplication, QDialog, QTabWidget, QWidget, QGroupBox, QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
import requests
import urllib
from waitingspinnerwidget import QtWaitingSpinner
class DownloadDataDialog(QDialog):
def __init__(self, parent=None):
super(DownloadDataDialog, self).__init__(parent)
self.spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)
tabWidget = QTabWidget(self)
tabWidget.addTab(MyTab(tabWidget), "MyTab")
mainLayout = QVBoxLayout()
mainLayout.addWidget(tabWidget)
self.setLayout(mainLayout)
self.setWindowTitle("Download option chain data from web")
class MyTab(QWidget):
def __init__(self, parent=None):
super(MyTab, self).__init__(parent)
dataGroup = QGroupBox('Data')
getButton = QPushButton('Download')
getButton.clicked.connect(self.download_data)
dataLayout = QVBoxLayout()
dataLayout.addWidget(getButton)
dataGroup.setLayout(dataLayout)
mainLayout = QVBoxLayout()
mainLayout.addWidget(dataGroup)
mainLayout.addStretch(1)
self.setLayout(mainLayout)
def download_data(self):
self.parent().parent().parent().spinner.start()
url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
filepath = None
try:
filepath = self.download_data_file(url)
except Exception as e:
print(e)
self.parent().parent().parent().spinner.stop()
if filepath:
#TODO doing stuff here
self.parent().parent().parent().close()
else:
pass #TODO show error dialog
def download_data_file(self, download_url):
# Build request URL and download the file
destination = 'test.zip'
urllib.request.urlretrieve(download_url, destination)
return destination
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
tabdialog = DownloadDataDialog()
tabdialog.show()
sys.exit(app.exec_())
So my intention is creating an invisible layer, setting the spinner as its only widget, and showing the translucid layer over the whole dialog window.
Any idea of how I should do that?
Once I also had that problem so I modified the library, first activate the flags: QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint, and the other change must be done in updatePosition() method:
def updatePosition(self):
if self.parentWidget() and self._centerOnParent:
parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
The result is as follows:
waitingspinnerwidget.py
import math
from PyQt5 import QtCore, QtGui, QtWidgets
class QtWaitingSpinner(QtWidgets.QWidget):
def __init__(self, parent=None, centerOnParent=True, disableParentWhenSpinning=False, modality=QtCore.Qt.NonModal):
super().__init__(parent, flags=QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint)
self._centerOnParent = centerOnParent
self._disableParentWhenSpinning = disableParentWhenSpinning
# WAS IN initialize()
self._color = QtGui.QColor(QtCore.Qt.black)
self._roundness = 100.0
self._minimumTrailOpacity = 3.14159265358979323846
self._trailFadePercentage = 80.0
self._revolutionsPerSecond = 1.57079632679489661923
self._numberOfLines = 20
self._lineLength = 10
self._lineWidth = 2
self._innerRadius = 10
self._currentCounter = 0
self._isSpinning = False
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self.rotate)
self.updateSize()
self.updateTimer()
self.hide()
# END initialize()
self.setWindowModality(modality)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
def paintEvent(self, QPaintEvent):
self.updatePosition()
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0
painter.setPen(QtCore.Qt.NoPen)
for i in range(0, self._numberOfLines):
painter.save()
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
rotateAngle = float(360 * i) / float(self._numberOfLines)
painter.rotate(rotateAngle)
painter.translate(self._innerRadius, 0)
distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines)
color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage,
self._minimumTrailOpacity, self._color)
painter.setBrush(color)
painter.drawRoundedRect(QtCore.QRect(0, -self._lineWidth / 2, self._lineLength, self._lineWidth), self._roundness,
self._roundness, QtCore.Qt.RelativeSize)
painter.restore()
def start(self):
self.updatePosition()
self._isSpinning = True
self.show()
if self.parentWidget and self._disableParentWhenSpinning:
self.parentWidget().setEnabled(False)
if not self._timer.isActive():
self._timer.start()
self._currentCounter = 0
def stop(self):
self._isSpinning = False
self.hide()
if self.parentWidget() and self._disableParentWhenSpinning:
self.parentWidget().setEnabled(True)
if self._timer.isActive():
self._timer.stop()
self._currentCounter = 0
def setNumberOfLines(self, lines):
self._numberOfLines = lines
self._currentCounter = 0
self.updateTimer()
def setLineLength(self, length):
self._lineLength = length
self.updateSize()
def setLineWidth(self, width):
self._lineWidth = width
self.updateSize()
def setInnerRadius(self, radius):
self._innerRadius = radius
self.updateSize()
def color(self):
return self._color
def roundness(self):
return self._roundness
def minimumTrailOpacity(self):
return self._minimumTrailOpacity
def trailFadePercentage(self):
return self._trailFadePercentage
def revolutionsPersSecond(self):
return self._revolutionsPerSecond
def numberOfLines(self):
return self._numberOfLines
def lineLength(self):
return self._lineLength
def lineWidth(self):
return self._lineWidth
def innerRadius(self):
return self._innerRadius
def isSpinning(self):
return self._isSpinning
def setRoundness(self, roundness):
self._roundness = max(0.0, min(100.0, roundness))
def setColor(self, color=QtCore.Qt.black):
self._color = QColor(color)
def setRevolutionsPerSecond(self, revolutionsPerSecond):
self._revolutionsPerSecond = revolutionsPerSecond
self.updateTimer()
def setTrailFadePercentage(self, trail):
self._trailFadePercentage = trail
def setMinimumTrailOpacity(self, minimumTrailOpacity):
self._minimumTrailOpacity = minimumTrailOpacity
def rotate(self):
self._currentCounter += 1
if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0
self.update()
def updateSize(self):
size = (self._innerRadius + self._lineLength) * 2
self.setFixedSize(size, size)
def updateTimer(self):
self._timer.setInterval(1000 / (self._numberOfLines * self._revolutionsPerSecond))
def updatePosition(self):
if self.parentWidget() and self._centerOnParent:
parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
distance = primary - current
if distance < 0:
distance += totalNrOfLines
return distance
def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput):
color = QtGui.QColor(colorinput)
if countDistance == 0:
return color
minAlphaF = minOpacity / 100.0
distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
if countDistance > distanceThreshold:
color.setAlphaF(minAlphaF)
else:
alphaDiff = color.alphaF() - minAlphaF
gradient = alphaDiff / float(distanceThreshold + 1)
resultAlpha = color.alphaF() - gradient * countDistance
# If alpha is out of bounds, clip it.
resultAlpha = min(1.0, max(0.0, resultAlpha))
color.setAlphaF(resultAlpha)
return color
With the above we solve one of those problems, the other problem is that urllib.request.urlretrieve() is blocking so it will cause the GUI to freeze, so the solution is to move it to another thread, using a previous response we can do it in the following way:
from PyQt5 import QtCore, QtGui, QtWidgets
import urllib.request
from waitingspinnerwidget import QtWaitingSpinner
class RequestRunnable(QtCore.QRunnable):
def __init__(self, url, destination, dialog):
super(RequestRunnable, self).__init__()
self._url = url
self._destination = destination
self.w = dialog
def run(self):
urllib.request.urlretrieve(self._url, self._destination)
QMetaObject.invokeMethod(self.w, "FinishedDownload", QtCore.Qt.QueuedConnection)
class DownloadDataDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(DownloadDataDialog, self).__init__(parent)
self.spinner = QtWaitingSpinner(self, True, True, QtCore.Qt.ApplicationModal)
tabWidget = QtWidgets.QTabWidget(self)
tabWidget.addTab(MyTab(), "MyTab")
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(tabWidget)
self.setWindowTitle("Download option chain data from web")
class MyTab(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyTab, self).__init__(parent)
dataGroup = QtWidgets.QGroupBox('Data')
getButton = QtWidgets.QPushButton('Download')
getButton.clicked.connect(self.download_data)
dataLayout = QtWidgets.QVBoxLayout(self)
dataLayout.addWidget(getButton)
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(dataGroup)
mainLayout.addStretch(1)
def download_data(self):
self.parentWidget().window().spinner.start()
url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
destination = 'test.zip'
runnable = RequestRunnable(url, destination, self)
QtCore.QThreadPool.globalInstance().start(runnable)
#QtCore.pyqtSlot()
def FinishedDownload(self):
self.parentWidget().window().spinner.stop()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
tabdialog = DownloadDataDialog()
tabdialog.show()
sys.exit(app.exec_())
I have a situation where a Gui is active and a QThread.
The QThread is getting and saving data in the background all the time, it should not stop doing this.
Now I want to process the latest data from the QThread without interrupting the QThread or freezing the Gui.
So I think I need another thread to do this?!
How can I pass its data to another thread and do something with it?
import sys, random, time
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
self.setWindowTitle('Window')
#getData
self.myGetData = getData()
self.myGetData.start()
self.show()
class getData(QThread):
#This Thread should run all the time
def run(self):
while True:
myNumber = random.randint(0, 100)
#He 'processData Thread'!! Do Something with mynumber!!
time.sleep(1)
class processData(QThread):
def processNumber(self, myNumber):
#Extremly complex code will execute while 'getData' is doing its thing.
newNumber = myNumber * 10
return newNumber
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Ui()
sys.exit(app.exec_())
Found a lot of examples on how to pass data from a QThread to the interface (signals).. But not the other way around.
The signals are confusing me a bit in this case..
I would like to propose do next:
import sys, random, time
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5 import Qt #+
class getData(QThread):
#This Thread should run all the time
threadSignalGetData = pyqtSignal(int)
def __init__(self, myNumber):
super().__init__()
self.myNumber = myNumber
def run(self):
#myNumber = 0
while True:
#He 'processData Thread'!! Do Something with mynumber!!
Qt.QThread.msleep(1000)
self.myNumber += 1
self.threadSignalGetData.emit(self.myNumber)
class MsgBoxGetData(Qt.QDialog):
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.label = Qt.QLabel("")
layout.addWidget(self.label)
close_btn = Qt.QPushButton("Close window GetData")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 65, 400, 80)
self.setWindowTitle('MsgBox GetData')
self.setStyleSheet("""QLabel{
font-family:'Consolas';
color: green;
font-size: 16px;}""")
class processData(QThread):
threadSignalProcessData = pyqtSignal(int, int)
def __init__(self, myNumber):
super().__init__()
self.newNumber = myNumber
def run(self):
flagProcessData = True
while flagProcessData:
#Extremly complex code will execute while 'getData' is doing its thing.
newNumber = self.newNumber * 10
self.threadSignalProcessData.emit(self.newNumber, newNumber)
flagProcessData = False
class MsgBoxProcessData(Qt.QDialog):
def __init__(self):
super().__init__()
layout = Qt.QVBoxLayout(self)
self.label = Qt.QLabel("")
layout.addWidget(self.label)
close_btn = Qt.QPushButton("Close window ProcessData")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 200, 400, 80)
self.setWindowTitle('MsgBox ProcessData')
self.setStyleSheet("""QLabel{
font-family:'Consolas';
color: red;
font-size: 24px;}""")
class Ui(Qt.QWidget):
def __init__(self, parent=None):
super(Ui, self).__init__(parent)
layout = Qt.QVBoxLayout(self)
self.lbl = Qt.QLabel("process GUI")
layout.addWidget(self.lbl)
self.btnA = Qt.QPushButton("Start getData")
layout.addWidget(self.btnA)
self.btnB = Qt.QPushButton("Start processData")
layout.addWidget(self.btnB)
self.setGeometry(550, 65, 300, 300)
self.setWindowTitle('Window')
self.btnA.clicked.connect(self.usingGetData)
self.btnB.clicked.connect(self.usingProcessData)
self.myNumber = 0
self.newNumber = None
self.msgGetData = MsgBoxGetData()
self.threadGetData = None
self.msgProcessData = MsgBoxProcessData()
self.threadProcessData = None
self.counter = 0
self.timer = Qt.QTimer()
self.timer.setInterval(1000)
# -------- timeout -------> def recurring_timer(self):
self.timer.timeout.connect(self.recurring_timer)
self.timer.start()
self.setStyleSheet("""QLabel{
font-family:'Consolas';
color: blue;
font-size: 20px;}""")
self.show()
def recurring_timer(self):
self.counter += 1
self.lbl.setText("process GUI: %d" % self.counter)
# ---- getData(QThread) -----------------------------------------------------#
def usingGetData(self):
if self.threadGetData is None:
self.threadGetData = getData(self.myNumber)
self.threadGetData.threadSignalGetData.connect(self.on_threadSignalGetData)
self.threadGetData.finished.connect(self.finishedGetData)
self.threadGetData.start()
self.btnA.setText("Stop getData(QThread)")
else:
self.threadGetData.terminate()
self.threadGetData = None
self.btnA.setText("Start getData(QThread)")
def finishedGetData(self):
self.threadGetData = None
self.btnA.setText("Start getData(QThread)")
def on_threadSignalGetData(self, value):
self.myNumber = value
self.msgGetData.label.setText(str(self.myNumber))
if not self.msgGetData.isVisible():
self.msgGetData.show()
# --END-- getData(QThread) -------------------#
# ---- processData(QThread) -----------------------------------------------------#
def usingProcessData(self):
if self.threadProcessData is None:
self.threadProcessData = processData(self.myNumber)
self.threadProcessData.threadSignalProcessData.connect(self.on_threadSignalProcessData)
self.threadProcessData.finished.connect(self.finishedProcessData)
self.threadProcessData.start()
self.btnB.setText("Stop processData(QThread)")
else:
self.threadProcessData.terminate()
self.threadProcessData = None
self.btnB.setText("Start processData(QThread)")
def finishedProcessData(self):
self.threadProcessData = None
self.btnB.setText("Start processData(QThread)")
def on_threadSignalProcessData(self, value1, value2):
self.newNumber = value2
self.msgProcessData.label.setText(str(value1)+" * 10 = "+str(self.newNumber))
if not self.msgProcessData.isVisible():
self.msgProcessData.show()
# --END-- processData(QThread) -------------------#
# -------------------- closeEvent --------------------------------------- #
def closeEvent(self, event):
reply = Qt.QMessageBox.question\
(self, 'Question',
"QUIT ?",
Qt.QMessageBox.Yes,
Qt.QMessageBox.No)
if reply == Qt.QMessageBox.Yes:
# close getData(QThread)
if self.threadGetData:
self.threadGetData.terminate()
self.msgGetData.close()
# close processData(QThread)
if self.threadProcessData:
self.threadProcessData.terminate()
self.msgProcessData.close()
#event.accept()
super().closeEvent(event)
else:
event.ignore()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Ui()
sys.exit(app.exec_())