I have a custom QWebEngineView named MyWebWidget, where I set html code, that contains links. When I click one link, a QDialog opens. In the dialog Window there is another MyWebWidget. But in the PopUpWindow no text is shown in MyWebWidget and I have no idea why. When I use QTextEdit instead of MyWebWidget or QWebEngineView the text is shown. Below is a minimal example to reproduce the error.
from PyQt5.QtWidgets import *
import sys
from MyWidgets import MyWebWidget
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.widget = MyWebWidget()
self.widget.setHtml('<a style="text-decoration:none;" href="data:link">link</a>')
self.widget.link.connect(self.function)
self.setCentralWidget(self.widget)
def function(self, link):
dialog = MyPopUpWindow(link, ["abcd"])
dialog.exec_()
class MyPopUpWindow(QDialog):
def __init__(self, title, inputList):
super().__init__()
layout = QVBoxLayout()
labelWiget = QLabel()
labelWiget.setText(title)
webWidget = MyWebWidget()
text = ""
for i in inputList:
text += str(i)
webWidget.setHtml(text)
layout.addWidget(labelWiget)
layout.addWidget(webWidget)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = App()
window.show()
sys.exit(app.exec_())
MyWebWidget looks like this:
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import QWebEngineView
from MyWidgets import MyWebEnginePage
from bs4 import BeautifulSoup
class MyWebWidget(QWebEngineView):
link = pyqtSignal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
page = MyWebEnginePage(self)
self.loaded = False
self.content = ""
# connect to the signal
page.dataLinkClicked.connect(self.handleDataLink)
self.setPage(page)
# use a data-url
self.loadFinished.connect(self.on_load_finished)
def on_load_finished(self):
self.page().runJavaScript("document.documentElement.outerHTML", self.callback)
def callback(self, r):
soup = BeautifulSoup(r, "html.parser")
if not self.loaded:
self.content = soup.text.strip()
self.loaded = True
def handleDataLink(self, text):
self.link.emit(text)
MyWebEnginePage looks like this:
from PyQt5.QtWebEngineWidgets import QWebEnginePage
from PyQt5.QtCore import *
class MyWebEnginePage(QWebEnginePage):
dataLinkClicked = pyqtSignal(str)
def acceptNavigationRequest(self, url, _type, isMainFrame):
if (_type == QWebEnginePage.NavigationTypeLinkClicked and
url.scheme() == 'data'):
# send only the url path
self.dataLinkClicked.emit(url.path())
return False
return super().acceptNavigationRequest(url, _type, isMainFrame)
Related
I'm making a custom browser, following a tutorial: https://www.youtube.com/watch?v=z-5bZ8EoKu4
I have coded everything to the T, and for some reason when I open the browser, even though I have self.browser.setUrl(QUrl('http://duckduckgo.com')) set for the default url to open to. How can I fix this?
The code:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.browser = QWebEngineView()
self.browser.setUrl(QUrl('http://duckduckgo.com'))
self.setCentralWidget(self.browser)
self.showMaximized()
# navbar
navbar = QToolBar()
self.addToolBar(navbar)
back_btn = QAction('Back', self)
back_btn.triggered.connect(self.browser.back)
navbar.addAction(back_btn)
forward_btn = QAction('Forward', self)
forward_btn.triggered.connect(self.browser.forward)
navbar.addAction(forward_btn)
reload_btn = QAction('Reload', self)
reload_btn.triggered.connect(self.browser.reload)
navbar.addAction(reload_btn)
home_btn = QAction('home', self)
home_btn.triggered.connect(self.navigate_home)
navbar.addAction(home_btn)
self.url_bar = QLineEdit()
self.url_bar.returnPressed.connect(self.navigate_to_url)
navbar.addWidget(self.url_bar)
def navigate_home(self):
self.browser.setUrl(QUrl('http://duckduckgo.com'))
def navigate_to_url(self):
url = self.url_bar.text()
self.browser.setUrl(QUrl(url))
app = QApplication(sys.argv)
QApplication.setApplicationName('quicksearch')
window = MainWindow()
app.exec_()"
I want to open self written html code in PyQt5 QWebEngineView. The html code should contain links, which are not url pages but have other information. Below a created a simple example. When clicking on the link named Link, python does not return the parameter inside the link. When I change the value of the link to a url, I get the QUrl as a result. Is there a way to get the content of the link (not a url) when clicked?
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
import sys
class MyWebEnginePage(QWebEnginePage):
def acceptNavigationRequest(self, url, _type, isMainFrame):
if _type == QWebEnginePage.NavigationTypeLinkClicked:
print(url)
return False
return super().acceptNavigationRequest(url, _type, isMainFrame)
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
browser = QWebEngineView()
browser.setPage(MyWebEnginePage(self))
link = "Hallo Welt"
text = f'<a style="text-decoration:none;" href="{link}">Link</a>'
browser.setHtml(text)
self.setCentralWidget(browser)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = App()
window.setGeometry(800, 100, 1000, 800)
window.show()
sys.exit(app.exec_())
You can use a url with the data: scheme to send the link value. When the url is received, you can then check that it has the correct scheme, and emit a signal with only the url's path (which does not include the scheme).
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
import sys
class MyWebEnginePage(QWebEnginePage):
dataLinkClicked = pyqtSignal(str)
def acceptNavigationRequest(self, url, _type, isMainFrame):
if (_type == QWebEnginePage.NavigationTypeLinkClicked and
url.scheme() == 'data'):
# send only the url path
self.dataLinkClicked.emit(url.path())
return False
return super().acceptNavigationRequest(url, _type, isMainFrame)
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
browser = QWebEngineView()
page = MyWebEnginePage(self)
# connect to the signal
page.dataLinkClicked.connect(self.handleDataLink)
browser.setPage(page)
# use a data-url
link = "data:Hallo Welt"
text = f'<a style="text-decoration:none;" href="{link}">Link</a>'
browser.setHtml(text)
self.setCentralWidget(browser)
def handleDataLink(self, text):
print(text) # prints "Hallo Welt"
if __name__ == '__main__':
app = QApplication(sys.argv)
window = App()
window.setGeometry(800, 100, 1000, 800)
window.show()
sys.exit(app.exec_())
I would like when I click on a buttom from a toolbar created with PyQt get the selected items in a QListWidget created in other class (LisWorkDirectory class).
In the ToolBar.py in the compilation_ function, I would like to get all selected items. I want to get the instance of the ListWorkDirectory class to get the QListWidget that I created the first time I have launched my app.
If I instanciate ListWorkDirectory, I get a new instance, and the selectedItems return a empty list, it is a normal behaviour.
Maybe my architecture is not correct, so if you have any suggestion or remarks don't' hesitate to learn me.
Below my code so that you understand my request :
main.py
from MainWindow import MainWindow
from MenuBar import MenuBar
from ToolBar import ToolBar
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
#Window
windowApp = MainWindow("pyCompile")
#MenuBar
menuBar = MenuBar()
#ToolBar
toolBar = ToolBar()
windowApp.setMenuBar(menuBar)
windowApp.addToolBar(toolBar)
windowApp.show()
sys.exit(app.exec_())
MainWindow.py
from PyQt5.QtWidgets import QMainWindow, QWidget, QLineEdit, QPushButton, QListWidget,QListWidgetItem,QPlainTextEdit, QFileDialog, QStatusBar ,QVBoxLayout, QHBoxLayout
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
from ListWorkDirectory import ListWorkDirectory
from Logger import MyDialog
import logging
import os
class MainWindow(QMainWindow):
def __init__(self, windowTitle):
super().__init__()
#Logger
self.logger = MyDialog()
logging.info("pyCompile version 0.1")
self.setGeometry(150,250,600,350)
self.setWindowTitle(windowTitle)
self.workDirectoryField = QLineEdit()
self.workDirectoryField.setPlaceholderText("Select your work directory ...")
self.workDirectoryField.setText("F:/WORKSPACE/Projects")
self.workDirectoryButton = QPushButton()
self.workDirectoryButton.setIcon(QIcon(":folder.svg"))
self.workDirectoryButton.clicked.connect(self.launchDialog)
self.hBoxLayout = QHBoxLayout()
self.hBoxLayout.addWidget(self.workDirectoryField)
self.hBoxLayout.addWidget(self.workDirectoryButton)
#List folder in work directory
self.myListFolder = ListWorkDirectory()
print(self.myListFolder)
self.workDirectoryField.textChanged[str].connect(self.myListFolder.update)
self.hBoxLayoutLogger = QHBoxLayout()
self.hBoxLayoutLogger.addWidget(self.myListFolder)
self.hBoxLayoutLogger2 = QHBoxLayout()
self.hBoxLayoutLogger2.addWidget(self.logger)
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget)
self.vBoxLayout = QVBoxLayout(self.centralWidget)
self.vBoxLayout.addLayout(self.hBoxLayout)
self.vBoxLayout.addLayout(self.hBoxLayoutLogger)
self.vBoxLayout.addLayout(self.hBoxLayoutLogger2)
#Status Bar
self.statusBar = QStatusBar()
self.statusBar.showMessage("Welcome in pyCompile", 5000)
self.setStatusBar(self.statusBar)
def launchDialog(self):
workDirectory = QFileDialog.getExistingDirectory(self, caption="Select work directory")
print(workDirectory)
self.workDirectoryField.setText(workDirectory)
MenuBar.py
from PyQt5.QtWidgets import QMenuBar
class MenuBar(QMenuBar):
def __init__(self):
super().__init__()
self.fileMenu = "&File"
self.editMenu = "&Edit"
self.helpMenu = "&Help"
self.initUI()
def initUI(self):
self.addMenu(self.fileMenu)
self.addMenu(self.editMenu)
self.addMenu(self.helpMenu)
ToolBar.py
from PyQt5.QtWidgets import QMainWindow, QToolBar, QAction
from PyQt5.QtGui import QIcon
import qrc_resources
from ListWorkDirectory import ListWorkDirectory
class ToolBar(QToolBar, ListWorkDirectory):
def __init__(self):
super().__init__()
self._createActions()
self.initUI()
def initUI(self):
self.setMovable(False)
self.addAction(self.compileAction)
self.addAction(self.settingsAction)
self.addAction(self.quitAction)
def _createActions(self):
self.compileAction = QAction(self)
self.compileAction.setStatusTip("Launch compilation")
self.compileAction.setText("&Compile")
self.compileAction.setIcon(QIcon(":compile.svg"))
self.compileAction.triggered.connect(self.compilation_)
self.settingsAction = QAction(self)
self.settingsAction.setText("&Settings")
self.settingsAction.setIcon(QIcon(":settings.svg"))
self.quitAction = QAction(self)
self.quitAction.setText("&Quit")
self.quitAction.setIcon(QIcon(":quit.svg"))
def compilation_(self):
"""
Get the instance of ListWorkDirectory to get selected items and launch the
compilation
"""
ListWorkDirectory.py
from PyQt5.QtWidgets import QListWidget,QListWidgetItem
from PyQt5.QtCore import Qt, QDir
import os
class ListWorkDirectory(QListWidget):
def __init__(self):
super().__init__()
self.clear()
def update(self, workDirectoryField):
isPathCorrect = self.checkPath(workDirectoryField)
if(isPathCorrect):
listOfDirectory = self.getFolderList(workDirectoryField, os.listdir(workDirectoryField))
for folder in listOfDirectory:
self.item = QListWidgetItem(folder)
self.item.setCheckState(Qt.Unchecked)
self.addItem(self.item)
else:
self.clear()
def checkPath(self, path):
QPath = QDir(path)
isQPathExist = QPath.exists()
isPathEmpty = self.isPathEmpty(path)
if(isQPathExist and not isPathEmpty):
return True
else:
return False
def getFolderList(self, path, listOfFiles):
listOfFolders=[]
for file_ in listOfFiles:
if(os.path.isdir(os.path.join(path, file_))):
listOfFolders.append(file_)
else:
pass
return listOfFolders
def isPathEmpty(self, path):
if(path != ""):
return False
else:
return True
Thank you for your help.
When you add widget to window (or to other widget) then this window (or widget) is its parent and you can use self.parent() to access element in window (or widget). When widgets are nested then you may even use self.parent().parent()
def compilation_(self):
"""
Get the instance of ListWorkDirectory to get selected items and launch the
compilation
"""
print(self.parent().myListFolder)
EDIT:
Class ListWorkDirectory has function item(number) to get item from list - but you overwrite it with line self.item = QListWidgetItem(folder). If you remove self. and use
item = QListWidgetItem(folder)
item.setCheckState(Qt.Unchecked)
self.addItem(item)
then this will show only checked items
def compilation_(self):
"""
Get the instance of ListWorkDirectory to get selected items and launch the
compilation
"""
lst = self.parent().myListFolder
for x in range(lst.count()):
item = lst.item(x)
#print(item.checkState(), item.text())
if item.checkState() :
print(item.text())
Full working code - everyone can simply copy all to one file and run it.
from PyQt5.QtWidgets import QMainWindow, QWidget, QLineEdit, QPushButton, QListWidget,QListWidgetItem,QPlainTextEdit, QFileDialog, QStatusBar ,QVBoxLayout, QHBoxLayout
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
import logging
import os
# ---
from PyQt5.QtWidgets import QMenuBar
class MenuBar(QMenuBar):
def __init__(self):
super().__init__()
self.fileMenu = "&File"
self.editMenu = "&Edit"
self.helpMenu = "&Help"
self.initUI()
def initUI(self):
self.addMenu(self.fileMenu)
self.addMenu(self.editMenu)
self.addMenu(self.helpMenu)
# ---
from PyQt5.QtWidgets import QMainWindow, QToolBar, QAction
from PyQt5.QtGui import QIcon
class ToolBar(QToolBar):
def __init__(self):
super().__init__()
self._createActions()
self.initUI()
def initUI(self):
self.setMovable(False)
self.addAction(self.compileAction)
self.addAction(self.settingsAction)
self.addAction(self.quitAction)
def _createActions(self):
self.compileAction = QAction(self)
self.compileAction.setStatusTip("Launch compilation")
self.compileAction.setText("&Compile")
self.compileAction.setIcon(QIcon(":compile.svg"))
self.compileAction.triggered.connect(self.compilation_)
self.settingsAction = QAction(self)
self.settingsAction.setText("&Settings")
self.settingsAction.setIcon(QIcon(":settings.svg"))
self.quitAction = QAction(self)
self.quitAction.setText("&Quit")
self.quitAction.setIcon(QIcon(":quit.svg"))
def compilation_(self):
"""
Get the instance of ListWorkDirectory to get selected items and launch the
compilation
"""
lst = self.parent().myListFolder
for x in range(lst.count()):
item = lst.item(x)
#print(item.checkState(), item.text())
if item.checkState() :
print(item.text())
# ---
from PyQt5.QtWidgets import QListWidget, QListWidgetItem
from PyQt5.QtCore import Qt, QDir
import os
class ListWorkDirectory(QListWidget):
def __init__(self):
super().__init__()
self.clear()
def update(self, workDirectoryField):
isPathCorrect = self.checkPath(workDirectoryField)
if(isPathCorrect):
listOfDirectory = self.getFolderList(workDirectoryField, os.listdir(workDirectoryField))
for folder in listOfDirectory:
item = QListWidgetItem(folder)
item.setCheckState(Qt.Unchecked)
self.addItem(item)
else:
self.clear()
def checkPath(self, path):
QPath = QDir(path)
isQPathExist = QPath.exists()
isPathEmpty = self.isPathEmpty(path)
if(isQPathExist and not isPathEmpty):
return True
else:
return False
def getFolderList(self, path, listOfFiles):
listOfFolders=[]
for file_ in listOfFiles:
if(os.path.isdir(os.path.join(path, file_))):
listOfFolders.append(file_)
else:
pass
return listOfFolders
def isPathEmpty(self, path):
if(path != ""):
return False
else:
return True
class MainWindow(QMainWindow):
def __init__(self, windowTitle):
super().__init__()
#Logger
#self.logger = MyDialog()
logging.info("pyCompile version 0.1")
self.setGeometry(150,250,600,350)
self.setWindowTitle(windowTitle)
self.workDirectoryField = QLineEdit()
self.workDirectoryField.setPlaceholderText("Select your work directory ...")
self.workDirectoryField.setText("F:/WORKSPACE/Projects")
self.workDirectoryButton = QPushButton()
self.workDirectoryButton.setIcon(QIcon(":folder.svg"))
self.workDirectoryButton.clicked.connect(self.launchDialog)
self.hBoxLayout = QHBoxLayout()
self.hBoxLayout.addWidget(self.workDirectoryField)
self.hBoxLayout.addWidget(self.workDirectoryButton)
#List folder in work directory
self.myListFolder = ListWorkDirectory()
print(self.myListFolder)
self.workDirectoryField.textChanged[str].connect(self.myListFolder.update)
self.hBoxLayoutLogger = QHBoxLayout()
self.hBoxLayoutLogger.addWidget(self.myListFolder)
self.hBoxLayoutLogger2 = QHBoxLayout()
#self.hBoxLayoutLogger2.addWidget(self.logger)
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget)
self.vBoxLayout = QVBoxLayout(self.centralWidget)
self.vBoxLayout.addLayout(self.hBoxLayout)
self.vBoxLayout.addLayout(self.hBoxLayoutLogger)
self.vBoxLayout.addLayout(self.hBoxLayoutLogger2)
#Status Bar
self.statusBar = QStatusBar()
self.statusBar.showMessage("Welcome in pyCompile", 5000)
self.setStatusBar(self.statusBar)
def launchDialog(self):
workDirectory = QFileDialog.getExistingDirectory(self, caption="Select work directory")
print(workDirectory)
self.workDirectoryField.setText(workDirectory)
# ---
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
#Window
windowApp = MainWindow("pyCompile")
#MenuBar
menuBar = MenuBar()
#ToolBar
toolBar = ToolBar()
windowApp.setMenuBar(menuBar)
windowApp.addToolBar(toolBar)
windowApp.show()
sys.exit(app.exec_())
from PyQt5 import Qt
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import *
from sys import argv
class Window(QMainWindow):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.browser = QWebEngineView()
self.browser.setUrl(QUrl('https://www.duckduckgo.com'))
self.browser.urlChanged.connect(self.update_AddressBar)
self.setCentralWidget(self.browser)
self.navigation_bar = QToolBar('Navigation Toolbar')
self.addToolBar(self.navigation_bar)
self.navigation_bar.setAttribute(Qt.Qt.WA_StyledBackground, True)
self.navigation_bar.setStyleSheet('background-color: white;')
self.navigation_bar.setMinimumSize(0, 75)
self.navigation_bar.setMovable(False)
back_button = QAction("←", self)
back_button.setStatusTip('Go to previous page you visited')
back_button.triggered.connect(self.browser.back)
self.navigation_bar.addAction(back_button)
next_button = QAction("→", self)
next_button.setStatusTip('Go to next page')
next_button.triggered.connect(self.browser.forward)
self.navigation_bar.addAction(next_button)
refresh_button = QAction("⟳", self)
refresh_button.setStatusTip('Refresh this page')
refresh_button.triggered.connect(self.browser.reload)
self.navigation_bar.addAction(refresh_button)
home_button = QAction("⌂", self)
home_button.setStatusTip('Go to home page (Google page)')
home_button.triggered.connect(self.go_to_home)
self.navigation_bar.addAction(home_button)
#self.navigation_bar.addSeparator()
self.URLBar = QLineEdit()
self.URLBar.returnPressed.connect(lambda: self.go_to_URL(QUrl(self.URLBar.text()))) # This specifies what to do when enter is pressed in the Entry field
self.navigation_bar.addWidget(self.URLBar)
self.addToolBarBreak()
self.show()
def go_to_home(self):
self.browser.setUrl(QUrl('https://www.duckduckgo.com/'))
def go_to_URL(self, url: QUrl):
if url.scheme() == '':
url.setScheme('http://')
self.browser.setUrl(url)
self.update_AddressBar(url)
def update_AddressBar(self, url):
self.URLBar.setText(url.toString())
self.URLBar.setCursorPosition(0)
app = QApplication(argv)
app.setApplicationName('Libertatem Browser')
window = Window()
app.exec_()
As you can see in my code, I have 4 QActions. I want to make these bigger. How can I do that? I have seen others using back_button.geometry(), but you can't do that with QActions.
I would prefer to keep them as QActions, because it took me a long while to set them up. As a side note, would changing the font size make the button bigger?
If you change the size of the button associated with the QAction it would not make a big difference, you could do it in the following way:
button = toolbar.widgetForAction(action)
button.setFixedSize(100, 100)
But in this case it is better to change the font size since the size of the button (and the height of the QToolBar) depends on it.
import sys
from PyQt5.QtCore import Qt, QUrl, QSize
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QAction, QApplication, QLineEdit, QMainWindow, QToolBar
from PyQt5.QtWebEngineWidgets import QWebEngineView
class Window(QMainWindow):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.browser = QWebEngineView()
self.browser.setUrl(QUrl("https://www.duckduckgo.com"))
self.browser.urlChanged.connect(self.update_AddressBar)
self.setCentralWidget(self.browser)
self.navigation_bar = QToolBar("Navigation Toolbar", movable=False)
self.addToolBar(self.navigation_bar)
self.navigation_bar.setAttribute(Qt.WA_StyledBackground, True)
self.navigation_bar.setStyleSheet("background-color: white;")
font = QFont()
font.setPixelSize(40)
back_action = QAction("←", self)
back_action.setStatusTip("Go to previous page you visited")
back_action.triggered.connect(self.browser.back)
back_action.setFont(font)
self.navigation_bar.addAction(back_action)
next_action = QAction("→", self)
next_action.setStatusTip("Go to next page")
next_action.triggered.connect(self.browser.forward)
next_action.setFont(font)
self.navigation_bar.addAction(next_action)
refresh_action = QAction("⟳", self)
refresh_action.setStatusTip("Refresh this page")
refresh_action.triggered.connect(self.browser.reload)
refresh_action.setFont(font)
self.navigation_bar.addAction(refresh_action)
home_action = QAction("⌂", self)
home_action.setStatusTip("Go to home page (Google page)")
home_action.setFont(font)
home_action.triggered.connect(self.go_to_home)
self.navigation_bar.addAction(home_action)
self.URLBar = QLineEdit()
self.URLBar.returnPressed.connect(self.handle_return_pressed)
self.navigation_bar.addWidget(self.URLBar)
def go_to_home(self):
self.browser.setUrl(QUrl("https://www.duckduckgo.com/"))
def go_to_URL(self, url: QUrl):
if url.scheme() == "":
url.setScheme("http://")
self.browser.setUrl(url)
self.update_AddressBar(url)
def update_AddressBar(self, url):
self.URLBar.setText(url.toString())
self.URLBar.setCursorPosition(0)
def handle_return_pressed(self):
url = QUrl.fromUserInput(self.URLBar.text())
self.go_to_URL(url)
def main():
app = QApplication(sys.argv)
app.setApplicationName("Libertatem Browser")
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
=== SUMMARY ===========================================
I use QNetworkAccessManager and QNetworkRequests to download two images simultaneously. How can I know for sure that two downloads finished?
=== DETAILED DESCRITION ===============================
I have URLs of two images and I want to download them asynchronously. To do that I initialize QNetworkAccessManager and use two QNetworkRequests. When finished, each request writes the contents of an image into a file.
The problem is that both requests know nothing of each other and, therefore, cannot verify whether the other one is finished.
Could you please tell me how can I wait for both requests to finish?
Here is the complete code:
import sys
from PyQt5.QtCore import QUrl, QFile, QIODevice
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtWidgets import QApplication, QWidget
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
img_url1 = "https://somesite.com/image1.jpg"
img_url2 = "https://somesite.com/image2.jpg"
self.downloader = QNetworkAccessManager()
self.requests = []
self.temp_files = []
for index, mediafile_url in enumerate([img_url1, img_url2]):
self.requests.append(self.downloader.get(QNetworkRequest(QUrl(mediafile_url))))
request = self.requests[index]
self.temp_files.append(QFile(f'{mediafile_url.split("/")[-1]}'))
temp_file = self.temp_files[index]
request.finished.connect(lambda *args, r=request, tf=temp_file: self.download_image(r, tf))
self.show()
#staticmethod
def download_image(request, temp_file):
image_data = request.readAll()
temp_file.open(QIODevice.WriteOnly)
temp_file.write(image_data)
temp_file.close()
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
You have to create a class that tracks downloads:
from dataclasses import dataclass
import sys
from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QFile, QIODevice
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtWidgets import QApplication, QWidget
#dataclass
class DownloadRequest:
url: QUrl
filename: str
class DownloadManager(QObject):
finished = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._manager = QNetworkAccessManager()
self._pending_downloads = 0
self.manager.finished.connect(self.handle_finished)
#property
def manager(self):
return self._manager
#property
def pending_downloads(self):
return self._pending_downloads
def download(self, requests):
for request in requests:
qrequest = QNetworkRequest(request.url)
qrequest.setAttribute(QNetworkRequest.User, request.filename)
self.manager.get(qrequest)
self._pending_downloads += 1
def handle_finished(self, reply):
self._pending_downloads -= 1
if reply.error() != QNetworkReply.NoError:
print(f"code: {reply.error()} message: {reply.errorString()}")
else:
print("successful")
filename = reply.attribute(QNetworkRequest.User)
file = QFile(filename)
if file.open(QIODevice.WriteOnly):
file.write(reply.readAll())
print(f"pending downloads {self.pending_downloads}")
if self.pending_downloads == 0:
self.finished.emit()
class MainWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._manager = DownloadManager()
self._manager.finished.connect(self.handle_finished)
img_url1 = "https://docs.python.org/3/_static/py.png"
img_url2 = "https://somesite.com/image2.jpg"
request1 = DownloadRequest(QUrl(img_url1), img_url1.split("/")[-1])
request2 = DownloadRequest(QUrl(img_url2), img_url2.split("/")[-1])
self._manager.download([request1, request2])
def handle_finished(self):
print("finished")
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == "__main__":
main()