PyQt5: QProgressBar will not update - python

I have an application where one of its functions is to use pytube to download YouTube videos. Everything works perfectly except for updating the QProgressBar with the download progress. Please see below code:
import pytube
import PyQt5.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
progress = QtCore.pyqtSignal(str, int, int)
def __init__(self, url, save_path, fname):
super(Worker, self).__init__()
self.url = url
self.save_path = save_path
self.fname = fname
self.perc_dl = 0
self.download_video()
def run(self):
if self.perc_dl < 100:
self.progress.emit('Downloading: {}%'.format(str(self.perc_dl)), int(self.perc_dl), 100)
print(self.perc_dl)
else:
self.progress.emit('Done!', 100, 100)
self.finished.emit()
def download_video(self):
yt = pytube.YouTube(self.url, on_progress_callback=self.progress_function)
stream = yt.streams.filter(progressive=True, file_extension='mp4').get_highest_resolution()
stream.download(output_path=self.save_path, filename=self.fname)
def progress_function(self, stream, chunk, bytes_remaining):
curr = stream.filesize - bytes_remaining
per_downloaded = round((curr / stream.filesize) * 100, 1)
self.perc_dl = per_downloaded
self.run()
class DownloadFromYT(QtWidgets.QDialog):
def __init__(self, url, save_path, fname):
super(DownloadFromYT, self).__init__()
self.url = url
self.save_path = save_path
self.fname = fname
self.vLayoutMaster = QtWidgets.QVBoxLayout()
self.hLayout = QtWidgets.QHBoxLayout()
self.hLayout.setAlignment(QtCore.Qt.AlignRight)
self.label = QtWidgets.QLabel()
self.label.setText('Downloading video')
self.pBar = QtWidgets.QProgressBar()
self.pBar.setInvertedAppearance(True)
self.pBar.setTextVisible(True)
self.pBar.setFixedWidth(200)
self.pBar.setAlignment(QtCore.Qt.AlignCenter)
self.pBar.setMaximum(100)
self.pBar.setFormat('Downloading: ')
self.noButton = QtWidgets.QPushButton('No')
self.noButton.setFixedWidth(50)
self.noButton.hide()
self.yesButton = QtWidgets.QPushButton('Yes')
self.yesButton.setFixedWidth(50)
self.yesButton.hide()
self.vLayoutMaster.addWidget(self.label)
self.vLayoutMaster.addSpacing(10)
self.vLayoutMaster.addWidget(self.pBar)
self.vLayoutMaster.addSpacing(20)
self.hLayout.addWidget(self.noButton)
self.hLayout.addWidget(self.yesButton)
self.vLayoutMaster.addLayout(self.hLayout)
# Signals / slots
self.noButton.clicked.connect(self.reject)
self.yesButton.clicked.connect(self.accept)
# Widget
self.setLayout(self.vLayoutMaster)
self.setWindowTitle('Downloading')
self.setFixedSize(250, 100)
self.show()
# Init download
self.run_thread(url, save_path, fname)
def run_thread(self, url, save_path, fname):
self.thrd = QtCore.QThread()
self.worker = Worker(url, save_path, fname)
self.worker.moveToThread(self.thrd)
self.thrd.start()
self.thrd.started.connect(self.worker.run)
self.worker.finished.connect(self.thrd.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker.progress.connect(self.show_progress)
self.thrd.finished.connect(self.thrd.deleteLater)
def show_progress(self, label, n, total):
self.pBar.setFormat(label)
self.pBar.setMaximum(total)
self.pBar.setValue(n)
QtCore.QCoreApplication.processEvents()
if label == 'Done!':
self.thrd.quit()
self.noButton.show()
self.yesButton.show()
self.label.setText('Video has been downloaded. Would you like to set the chosen\n'
'file path as the "Local file" path?')
I have used this basic structure in other parts of my application to display and update a progress bar with no issues, however for some reason here, self.pBar refuses to update when the progress signal is emitted from the Worker class. As you can see, I have a print() statement in the run method to check that the code is actually entering that if block and periodically printing out the download progress to the console, which it is. The application updates when the thread has finished running, so I get the final, completely filled progress bar, but in between the start and end the progress bar does not update as expected.
Any help would be greatly appreciated!

The problem seems to be that despite all your effort to wire up all that thread-worker components correctly, you are still running self.download_video() in the main (UI) thread. And therefore it is blocking refreshing of the UI. If you want to run in in the secondary thread, you must start it in Worker.run() function and not in the Worker constructor. If you wire up all threading correctly, then you should not need to call processEvents() at all to refresh UI.
If you are unsure about the thread in which the download function runs, print the thread ID in your print function and compare it to thread id of the main UI thread. Have a look at https://doc.qt.io/qt-5/qthread.html#currentThread

Related

Trouble with connecting functions to GUI generated with PyQt

I am currently working on a project of simple spectrum analyzer that cooperates with PlutoSDR, which is programmable radio.
I created a simple GUI with QTDesigner, then pyuic'ed it into python code (my version is 3.8)
Here's my code: (I've cut out some irrelevant parts)
# Form implementation generated from reading ui file 'signal_analyzer.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
import pyqtgraph
from PyQt5 import QtCore, QtGui, QtWidgets
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.widgets import PlotWidget
import sys
import numpy as np
import argparse
import adi
import time
import threading
import pyqtgraph as pg
np.seterr(divide='ignore')
class FakePluto:
# this is the class that simulates the radio if it's not connected to my computer
"""
perform some operations
"""
# output vector of data
return samples[:self.rx_buffer_size]
class SpectrumAnalyzerThread(threading.Thread):
def __init__(self, sdr, *args, **kwargs):
super().__init__(*args, **kwargs)
"""
set some initial parmeters
"""
self.sdr = sdr
elf.start_freq = int(70e6)
self.end_freq = int(150e6)
self.settings_changed = True
self.result = None
#property
def steps_per_scan(self):
return (self.end_freq - self.start_freq) // self.step_size
def setStop(self):
self.stop = True
print("stop")
def setStart(self):
self.stop = False
print("start")
def setStopFreq(self, win):
self.stop_freq = int(win.lineEdit.text)
self.start_freq += (self.stop_freq - self.start_freq) % self.step_size
def setStartFreq(self, win):
self.start_freq = int(win.lineEdit_2.text)
self.stop_freq += (self.stop_freq - self.start_freq) % self.step_size
def reconfigure(self, start_freq, end_freq, step_size):
self.start_freq = int(start_freq)
self.end_freq = int(end_freq)
self.step_size = int(step_size)
if (self.end_freq - self.start_freq) % self.step_size != 0:
raise Exception('range is not a multiple of the step size')
self.settings_changed = True
def run(self):
while not self.stop:
print("2", end = "\r")
if self.settings_changed:
self._current_freq = self.start_freq
self.result = np.zeros(self.steps_per_scan * self.samples_per_step)
self.sdr.gain_control_mode_chan0 = 'manual'
self.sdr.rx_hardwaregain_chan0 = self.gain
self.sdr.sample_rate = self.step_size
self.sdr.rx_rf_bandwidth = self.step_size
self.sdr.rx_buffer_size = self.samples_per_step
self.settings_changed = False
else:
if self._current_freq + self.step_size >= self.end_freq:
self._current_freq = self.start_freq
else:
self._current_freq += self.step_size
self.sdr.rx_lo = self._current_freq + self.step_size//2
self.sdr.rx() # skip one sample
rx_samples = self.sdr.rx()
psd = np.abs(np.fft.fftshift(np.fft.fft(rx_samples))) ** 2
assert len(psd) == self.samples_per_step
start_idx = (self._current_freq - self.start_freq) // self.step_size * self.samples_per_step
self.result[start_idx:start_idx + self.samples_per_step] = psd
#print(self.result.tolist())
class Ui_MainWindow(QtWidgets.QMainWindow):
def setupUi(self, MainWindow, analyzer_thread):
"""
define UI elements - this part was generated from .UI file
"""
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(800, 140, 231, 25))
self.lineEdit.setObjectName("lineEdit")
self.runButton = QtWidgets.QPushButton(self.centralwidget)
self.runButton.setGeometry(QtCore.QRect(790, 310, 161, 51))
self.runButton.setObjectName("runButton")
self.stopButton = QtWidgets.QPushButton(self.centralwidget)
self.stopButton.setGeometry(QtCore.QRect(790, 380, 161, 51))
self.stopButton.setObjectName("stopButton")
self.retranslateUi(MainWindow)
# connect gui elements with functions
self.stopButton.clicked.connect(analyzer_thread.setStop) # type: ignore
self.runButton.clicked.connect(analyzer_thread.setStart) # type: ignore
self.lineEdit.textChanged[str].connect(analyzer_thread.setStartFreq)
#self.lineEdit_2.textChanged[str].connect(analyzer_thread.setStopFreq)
#self.sweepButton.clicked.connect(self.widget.singleSweep) # type: ignore
#self.lineEdit_3.modified.connect()
QtCore.QMetaObject.connectSlotsByName(self)
# function below is just for testing
def tomatoes(self, analyzer_thread):
print(analyzer_thread.start_freq)
def retranslateUi(self, MainWindow):
"""
UI retranslation
"""
def main():
# this part checks input script arguments, currently I'm using --simulate
parser = argparse.ArgumentParser(description='SDR Spectrum Analyzer for PlutoSDR')
parser.add_argument('pluto_uri', nargs='?', default=adi.Pluto._uri_auto,
help=f'URI of the PlutoSDR device (default: "{adi.Pluto._uri_auto}")')
parser.add_argument('--simulate', action='store_true',
help='Simulate by generating random noise instead of querying the Pluto device')
args = parser.parse_args()
if args.simulate:
sdr = FakePluto()
else:
sdr = adi.Pluto(args.pluto_uri)
# create and start the thread
analyzer_thread = SpectrumAnalyzerThread(sdr)
analyzer_thread.start()
app = QtGui.QApplication(sys.argv)
win = Ui_MainWindow()
win.show()
win.setupUi(win, analyzer_thread)
# this is the function that refreshes the plotWindow in GUI
def update():
if analyzer_thread.result is not None and not analyzer_thread.settings_changed:
print("1", end = "\r")
psd = analyzer_thread.result
num_bins = analyzer_thread.samples_per_step
if len(psd) % num_bins != 0:
raise Exception('num_bins is not a multiple of sample count')
binned = psd.reshape((-1, len(psd) // num_bins)).sum(axis=1)
binned_dB = 10 * np.log10(binned)
f = np.linspace(analyzer_thread.start_freq, analyzer_thread.end_freq, len(binned_dB))
#spectrum_trace.setData(f, binned_dB)
win.graphicsView.clear()
win.graphicsView.plot(f, binned_dB)
dispMax = str(np.amax(binned_dB))
win.currentMaxAmp.display(dispMax[0:5])
# update function is connected to the timer
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(1)
win.tomatoes(analyzer_thread)
app.exec()
analyzer_thread.stop = True
analyzer_thread.join()
if __name__ == '__main__':
main()
Now, I have two independent problems.
First
When I press "Stop" on the GUI, which sets analyzer_thread stop value to 'true', this works correctly (i. e. run function from analyzer_thread stops executing), though the update function is still running - the plot keeps refreshing but with the same values. Yet when I hit "start", the analyzer_thread doesn't start over. I have no idea what causes that.
Second
When I try to change stop frequency value, which should call analyzer_thread.setStopFreq I get this message:
Traceback (most recent call last):
File "main_window_ui.py", line 77, in setStopFreq
self.stop_freq = int(win.lineEdit.text)
AttributeError: 'str' object has no attribute 'lineEdit'
which I think is my mistake while connecting GUI objects to functions, but I couldn't figure out how to fix it. It seems there's a problem with function's arguments, yet when I call tomatoes function from main, it works despite having the same argument (analyzer_thread).
I know that my code is messy and sorry for that. The processing part was done by another person, my role is to make it work with GUI. I am attaching the main window view:
https://i.stack.imgur.com/vUZjj.png
I'll be thankful for any help :)
Your code wont resume running, because when you set self.stop to True once, whole ,,run'' loop terminates and self.run function ends its job, terminating thread. You should rather try something like:
while True:
if not self.stop:
#do your code
else:
time.sleep(0.001) #
And add other possibility to terminate thread completly.
Your functions should rather be named suspend/resume, not stop and start.

PyQt5; problem with threading using QRunnable and QThreadPool

It seems that I have a problem with running threads in my application as follows:
in my MainForm class I have:
.
.
self.threadPool = QtCore.QThreadPool()
self.threadPool.setMaxThreadCount(4)
.
.
def openFileFcn(self):
worker = **Worker**(self, **self.ImageViewer.showImages**, self.files)
self.threadPool.start(worker)
where Worker is a wrapper QRunnable defined as:
class Worker(QtCore.QRunnable):
def __init__(self, fn, *args, **kwargs):
QtCore.QRunnable.__init__(self)
self.fn = fn
self.args = args
self.kwargs = kwargs
def run(self) -> None:
self.fn(*self.args, **self.kwargs)
and self.ImageViewer.showImages refers to the below section of the ImageViewer class:
def loadImg(self, file):
tabName = str()
if len(file) > 15: tabName = file[0:13] + "..."
widget = ImageViewer()
localPath = os.path.abspath(file)
localPath = QtCore.QDir().filePath(localPath)
pixmap = QtGui.QPixmap(localPath)
widget.setImage(pixmap)
self.tabwidget.addTab(widget, self.mainFormHandle.sharedData.imgIcon, tabName)
self.tabwidget.setCurrentIndex(self.tabwidget.currentIndex() + 1)
def **showImages**(self, files):
files = [file.lower() for file in files]
for file in files:
self.loadImg(file)
Nothing much happens when I run this code. It just freezes and after a while the application closes with exit code -1073740791 (0xC0000409). What do you suggest to be the cause?
The issue seems to be that you are trying to create widgets from another thread than the main thread. You can only create widgets from the main thread but since loadImg() creates several widgets, calling it from the thread pool crashes your application. One way to get around this is to split loadImg() into two methods, one method that loads the pixmaps, and another one that creates the ImageViewers and adds the tabs. The first one can then be moved to the thread pool, and you can use a signal to automatically call the second one every time a pixmap is loaded. For example:
class TabArea(QtWidgets.QWidget):
pixmap_loaded = QtCore.pyqtSignal(str, QtGui.QPixmap)
def __init__(self, parent=None):
...
self.pixmap_loaded.connect(self.addTab)
def loadImg(self, file):
localPath = os.path.abspath(file)
localPath = QtCore.QDir().filePath(localPath)
pixmap = QtGui.QPixmap(localPath)
# emit pixmap and file name
self.pixmap_loaded.emit(file, pixmap)
def addTab(self, file, pixmap):
tabName = str()
if len(file) > 15: tabName = file[0:13] + "..."
widget = ImageViewer()
widget.setImage(pixmap)
self.tabwidget.addTab(widget, self.mainFormHandle.sharedData.imgIcon, tabName)
self.tabwidget.setCurrentIndex(self.tabwidget.currentIndex() + 1)
def showImages(self, files):
files = [file.lower() for file in files]
for file in files:
self.loadImg(file)

Python PyQT - Web Browser | Bookmarks

This is a repost of my previous post which I deleted. In the first post I asked if someone knows a way of creating bookmarks with PyQT5, however, originally, I did not post what issues I am having now, with my way. I have 3 toolbars, in my web browser.
For additional buttons as - exit, minimize, maximize and etc.
For navigation in the web.
Bookmarks all are created with the instance of QToolBar().
Bookmarks toolbar code
self.bookmark_bar = QToolBar('Bookmark')
self.bookmark_bar.setIconSize(QSize(12, 12))
self.bookmark_bar.setMovable(False)
self.addToolBar(self.bookmark_bar)
After the tool bars are created the buttons are added, as there is a lot of code there I will show just the final result as a screenshot and the last few lines of code in init function.
The last few lines of code:
self.add_new_tab(QUrl('http://www.google.com'), 'Home Page')
self.bookmarks_load()
self.show()
self.setWindowTitle('Tempo')
Everything is fine with the Web Browser itself, as everything is working except the bookmarks. The line of code 'bookmarks_load()' load the bookmarks from a .txt file. As for now in the .txt document the bookmark is youtube.com (does not matter which link is used). The bookmarks functioncs:
Add a website to a bookmark.txt
def Bookmark(self):
try:
qurl = QUrl(self.urlbar.text())
print('Here we are using the QUrl toString method: %s ---> Type: %s' % (qurl.toString(), type(qurl)))
url = qurl.toString()
print('Here we are storing the new string to a new variable: %s ---> Type: %s' % (url, type(url)))
b = open(os.path.join('bookmarks', 'bookmarks.txt'), "wb")
self.bookmarks_write = pickle.dump(url, b)
b.close()
except:
print("Crash - Bookmarks not stored")
self.bookmark_btn.setText("★")
Load bookmarks from the document file.
def bookmarks_load(self):
try:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'rb')
self.bookmarks_write = pickle.load(bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
except:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'wb')
bookmarks = 'http://www.stackoverflow.com'
self.bookmarks_write = pickle.dump(bookmarks, bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
print('Crash - Did not load bookmarks')
Create the bookmarks 'button', when pressed open a new tab with that website.
def create_bookmarks(self):
bookmark_list = []
try:
for book in self.bookmarks_write.split():
print(book)
bookmark_list.append(book)
print(bookmark_list)
except:
print("Something went wrong with the list")
try:
button = QAction(QIcon(os.path.join('images', 'tab_icon.PNG')), 'Open bookmark', self)
button.triggered.connect(self.add_new_tab(QUrl(bookmark_list[0]), 'New'))
self.bookmark_bar.addAction(button)
except:
print('Button is causing the error')
Now this is the part which I start having issues. If I remove the - triggered.connect line and I do not add any functionality to that 'button' everything launches and works, without any errors. It stores and can load the bookmarks. However, when that line is added, it crashes and the button is not created(The app does not exit as there is an except statement which catches the error - which pyqt does not show what error it is.). This is the add_new_tab() function:
def add_new_tab(self, qurl=None, label='Blank'):
if qurl is None:
qurl = QUrl('')
web_browser = QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
web_browser.urlChanged.connect(lambda qurl, web_browser=web_browser:
self.update_urlbar(qurl, web_browser))
web_browser.loadFinished.connect(lambda _, i=index, web_browser=web_browser:
self.tabs.setTabText(i, web_browser.page().title()))
Originally I open tabs by 'double clicking' on the tab bar with this function:
def tab_open_doubleclick(self, index):
if index == -1:
self.add_new_tab()
As you can see on the trigger - I do pass the link as QUrl and I do add a test label. The problem I have it somehow does not want to work and I can not find why, because Python PyQT5 does not show an error, it just closes with the return code.
Screenshots as explanation:
Link not added to a bookmarks.txt
Link added to bookmarks.txt
Pickle does dump the link in .txt
The "except" statement is run, while the triggered.connect line is not commented out.
The app continues to run, but the loaded bookmarks buttons are not there.
In the following example I show how to add the functionality of the BookMark, for it use QSettings to store the data but for this you must use setOrganizationName(), setOrganizationDomain() and setApplicationName(). Also I added the functionality of obtaining the title of the web page. The BookMarkToolBar class has the bookmarkClicked method that returns the url associated with the BookMark and the title.
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class BookMarkToolBar(QtWidgets.QToolBar):
bookmarkClicked = QtCore.pyqtSignal(QtCore.QUrl, str)
def __init__(self, parent=None):
super(BookMarkToolBar, self).__init__(parent)
self.actionTriggered.connect(self.onActionTriggered)
self.bookmark_list = []
def setBoorkMarks(self, bookmarks):
for bookmark in bookmarks:
self.addBookMarkAction(bookmark["title"], bookmark["url"])
def addBookMarkAction(self, title, url):
bookmark = {"title": title, "url": url}
fm = QtGui.QFontMetrics(self.font())
if bookmark not in self.bookmark_list:
text = fm.elidedText(title, QtCore.Qt.ElideRight, 150)
action = self.addAction(text)
action.setData(bookmark)
self.bookmark_list.append(bookmark)
#QtCore.pyqtSlot(QtWidgets.QAction)
def onActionTriggered(self, action):
bookmark = action.data()
self.bookmarkClicked.emit(bookmark["url"], bookmark["title"])
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.defaultUrl = QtCore.QUrl()
self.tabs = QtWidgets.QTabWidget()
self.tabs.setTabsClosable(True)
self.setCentralWidget(self.tabs)
self.urlLe = QtWidgets.QLineEdit()
self.urlLe.returnPressed.connect(self.onReturnPressed)
self.favoriteButton = QtWidgets.QToolButton()
self.favoriteButton.setIcon(QtGui.QIcon("images/star.png"))
self.favoriteButton.clicked.connect(self.addFavoriteClicked)
toolbar = self.addToolBar("")
toolbar.addWidget(self.urlLe)
toolbar.addWidget(self.favoriteButton)
self.addToolBarBreak()
self.bookmarkToolbar = BookMarkToolBar()
self.bookmarkToolbar.bookmarkClicked.connect(self.add_new_tab)
self.addToolBar(self.bookmarkToolbar)
self.readSettings()
def onReturnPressed(self):
self.tabs.currentWidget().setUrl(QtCore.QUrl.fromUserInput(self.urlLe.text()))
def addFavoriteClicked(self):
loop = QtCore.QEventLoop()
def callback(resp):
setattr(self, "title", resp)
loop.quit()
web_browser = self.tabs.currentWidget()
web_browser.page().runJavaScript("(function() { return document.title;})();", callback)
url = web_browser.url()
loop.exec_()
self.bookmarkToolbar.addBookMarkAction(getattr(self, "title"), url)
def add_new_tab(self, qurl=QtCore.QUrl(), label='Blank'):
web_browser = QtWebEngineWidgets.QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
web_browser.urlChanged.connect(self.updateUlrLe)
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
self.urlLe.setText(qurl.toString())
def updateUlrLe(self, url):
self.urlLe.setText(url.toDisplayString())
def readSettings(self):
setting = QtCore.QSettings()
self.defaultUrl = setting.value("defaultUrl", QtCore.QUrl('http://www.google.com'))
self.add_new_tab(self.defaultUrl, 'Home Page')
self.bookmarkToolbar.setBoorkMarks(setting.value("bookmarks", []))
def saveSettins(self):
settings = QtCore.QSettings()
settings.setValue("defaultUrl", self.defaultUrl)
settings.setValue("bookmarks", self.bookmarkToolbar.bookmark_list)
def closeEvent(self, event):
self.saveSettins()
super(MainWindow, self).closeEvent(event)
if __name__ == '__main__':
import sys
QtCore.QCoreApplication.setOrganizationName("eyllanesc.org")
QtCore.QCoreApplication.setOrganizationDomain("www.eyllanesc.com")
QtCore.QCoreApplication.setApplicationName("MyApp")
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Using QApplication::exec() in a function or class

I want the following code to get the request url starting with http://down.51en.com:88 during the web loading process , and then do other processing with the response object of the url .
In my program, once targetUrl is assigned a value , I want the function targetUrlGetter(url) to return it to the caller, however , the problem is that QApplication::exec() enters the main event loop so cannot execute code at the end of thetargetUrlGetter() function after the exec() call , thus the function cannot return , I have tried with qApp.quit() in interceptRequest(self, info) in order to tell the application to exit so that targetUrlGetter(url) can return , but the function still cannot return and the program even crashes on exit(tested on Win7 32bit), so how can I return the targetUrl to the caller program ?
The difficulties here are how to exit the Qt event loop without crash and return the request url to the caller.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebEngineCore import *
from PyQt5.QtCore import *
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def __init__(self, parent=None):
super().__init__(parent)
self.page = parent
def interceptRequest(self, info):
if info.requestUrl().toString().startswith('http://down.51en.com:88'):
self.targetUrl = info.requestUrl().toString()
print('----------------------------------------------', self.targetUrl)
qApp.quit()
# self.page.load(QUrl(''))
def targetUrlGetter(url=None):
app = QApplication(sys.argv)
page = QWebEnginePage()
globalSettings = page.settings().globalSettings()
globalSettings.setAttribute(
QWebEngineSettings.PluginsEnabled, True)
globalSettings.setAttribute(
QWebEngineSettings.AutoLoadImages, False)
profile = page.profile()
webEngineUrlRequestInterceptor = WebEngineUrlRequestInterceptor(page)
profile.setRequestInterceptor(webEngineUrlRequestInterceptor)
page.load(QUrl(url))
# view = QWebEngineView()
# view.setPage(page)
# view.show()
app.exec_()
return webEngineUrlRequestInterceptor.targetUrl
url = "http://www.51en.com/news/sci/everything-there-is-20160513.html"
# url = "http://www.51en.com/news/sci/obese-dad-s-sperm-may-influence-offsprin.html"
# url = "http://www.51en.com/news/sci/mars-surface-glass-could-hold-ancient-fo.html"
targetUrl = targetUrlGetter(url)
print(targetUrl)
You should always initialize QApplication at the beginning of the program, and always call the QApplication::exec function at the end of the program.
Another thing is that QWebEngineUrlRequestInterceptor.interceptRequest is a callback function which is called asynchronously. Since info.requestUrl().toString() is called inside the callback function, there is no way to return the result it synchronously.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebEngineCore import *
from PyQt5.QtCore import *
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def __init__(self, parent=None):
super().__init__(parent)
self.page = parent
def interceptRequest(self, info):
if info.requestUrl().toString().startswith('http://down.51en.com:88'):
self.targetUrl = info.requestUrl().toString()
print('----------------------------------------------', self.targetUrl)
# Don't do thatDo you really want to exit the whole program?
# qApp.quit()
# Do something here, rather than return it.
# It must be run asynchronously
# self.page.load(QUrl(''))
def targetUrlGetter(url=None):
page = QWebEnginePage()
globalSettings = page.settings().globalSettings()
globalSettings.setAttribute(
QWebEngineSettings.PluginsEnabled, True)
globalSettings.setAttribute(
QWebEngineSettings.AutoLoadImages, False)
profile = page.profile()
webEngineUrlRequestInterceptor = WebEngineUrlRequestInterceptor(page)
profile.setRequestInterceptor(webEngineUrlRequestInterceptor)
page.load(QUrl(url))
# view = QWebEngineView()
# view.setPage(page)
# view.show()
# Don't return this. It cannot be called synchronously. It must be called asynchronously.
# return webEngineUrlRequestInterceptor.targetUrl
app = QApplication(sys.argv) # always initialize QApplication at the beginning of the program
url = "http://www.51en.com/news/sci/everything-there-is-20160513.html"
# url = "http://www.51en.com/news/sci/obese-dad-s-sperm-may-influence-offsprin.html"
# url = "http://www.51en.com/news/sci/mars-surface-glass-could-hold-ancient-fo.html"
targetUrl = targetUrlGetter(url)
print(targetUrl)
app.exec_() # always call the QApplication::exec at the end of the program

Refresh QTextEdit in PyQt

Im writing a PyQt app that takes some input in one widget, and then processes some text files.
What ive got at the moment is when the user clicks the "process" button a seperate window with a QTextEdit in it pops up, and ouputs some logging messages.
On Mac OS X this window is refreshed automatically and you cna see the process.
On Windows, the window reports (Not Responding) and then once all the proccessing is done, the log output is shown. Im assuming I need to refresh the window after each write into the log, and ive had a look around at using a timer. etc, but havnt had much luck in getting it working.
Below is the source code. It has two files, GUI.py which does all the GUI stuff and MOVtoMXF that does all the processing.
GUI.py
import os
import sys
import MOVtoMXF
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Form(QDialog):
def process(self):
path = str(self.pathBox.displayText())
if(path == ''):
QMessageBox.warning(self, "Empty Path", "You didnt fill something out.")
return
xmlFile = str(self.xmlFileBox.displayText())
if(xmlFile == ''):
QMessageBox.warning(self, "No XML file", "You didnt fill something.")
return
outFileName = str(self.outfileNameBox.displayText())
if(outFileName == ''):
QMessageBox.warning(self, "No Output File", "You didnt do something")
return
print path + " " + xmlFile + " " + outFileName
mov1 = MOVtoMXF.MOVtoMXF(path, xmlFile, outFileName, self.log)
self.log.show()
rc = mov1.ScanFile()
if( rc < 0):
print "something happened"
#self.done(0)
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.log = Log()
self.pathLabel = QLabel("P2 Path:")
self.pathBox = QLineEdit("")
self.pathBrowseB = QPushButton("Browse")
self.pathLayout = QHBoxLayout()
self.pathLayout.addStretch()
self.pathLayout.addWidget(self.pathLabel)
self.pathLayout.addWidget(self.pathBox)
self.pathLayout.addWidget(self.pathBrowseB)
self.xmlLabel = QLabel("FCP XML File:")
self.xmlFileBox = QLineEdit("")
self.xmlFileBrowseB = QPushButton("Browse")
self.xmlLayout = QHBoxLayout()
self.xmlLayout.addStretch()
self.xmlLayout.addWidget(self.xmlLabel)
self.xmlLayout.addWidget(self.xmlFileBox)
self.xmlLayout.addWidget(self.xmlFileBrowseB)
self.outFileLabel = QLabel("Save to:")
self.outfileNameBox = QLineEdit("")
self.outputFileBrowseB = QPushButton("Browse")
self.outputLayout = QHBoxLayout()
self.outputLayout.addStretch()
self.outputLayout.addWidget(self.outFileLabel)
self.outputLayout.addWidget(self.outfileNameBox)
self.outputLayout.addWidget(self.outputFileBrowseB)
self.exitButton = QPushButton("Exit")
self.processButton = QPushButton("Process")
self.buttonLayout = QHBoxLayout()
#self.buttonLayout.addStretch()
self.buttonLayout.addWidget(self.exitButton)
self.buttonLayout.addWidget(self.processButton)
self.layout = QVBoxLayout()
self.layout.addLayout(self.pathLayout)
self.layout.addLayout(self.xmlLayout)
self.layout.addLayout(self.outputLayout)
self.layout.addLayout(self.buttonLayout)
self.setLayout(self.layout)
self.pathBox.setFocus()
self.setWindowTitle("MOVtoMXF")
self.connect(self.processButton, SIGNAL("clicked()"), self.process)
self.connect(self.exitButton, SIGNAL("clicked()"), self, SLOT("reject()"))
self.ConnectButtons()
class Log(QTextEdit):
def __init__(self, parent=None):
super(Log, self).__init__(parent)
self.timer = QTimer()
self.connect(self.timer, SIGNAL("timeout()"), self.updateText())
self.timer.start(2000)
def updateText(self):
print "update Called"
AND MOVtoMXF.py
import os
import sys
import time
import string
import FileUtils
import shutil
import re
class MOVtoMXF:
#Class to do the MOVtoMXF stuff.
def __init__(self, path, xmlFile, outputFile, edit):
self.MXFdict = {}
self.MOVDict = {}
self.path = path
self.xmlFile = xmlFile
self.outputFile = outputFile
self.outputDirectory = outputFile.rsplit('/',1)
self.outputDirectory = self.outputDirectory[0]
sys.stdout = OutLog( edit, sys.stdout)
class OutLog():
def __init__(self, edit, out=None, color=None):
"""(edit, out=None, color=None) -> can write stdout, stderr to a
QTextEdit.
edit = QTextEdit
out = alternate stream ( can be the original sys.stdout )
color = alternate color (i.e. color stderr a different color)
"""
self.edit = edit
self.out = None
self.color = color
def write(self, m):
if self.color:
tc = self.edit.textColor()
self.edit.setTextColor(self.color)
#self.edit.moveCursor(QtGui.QTextCursor.End)
self.edit.insertPlainText( m )
if self.color:
self.edit.setTextColor(tc)
if self.out:
self.out.write(m)
self.edit.show()
If any other code is needed (i think this is all that is needed) then just let me know.
Any Help would be great.
Mark
It looks like your are running an external program, capturing its output into a QTextEdit. I didn't see the code of Form.process, but I am guessing on windows your function waits for the external program to finish, then quickly dumps everything to the QTextEdit.
If your interface really is waiting for the other process to finish, then it will hang in the manner you describe. You'll need to look at subprocess or perhaps even popen to get the program's output in a "non-blocking" manner.
The key to avoiding "(Not Responding)" is to call QApplication.processEvents a few times every few seconds. The QTimer is not going to help in this case, because if Qt cannot process its events, it cannot call any signal handlers.

Categories

Resources