QPixmapCache in PyQt6 - python

Hello I am trying to create a PyQt6 widget that animates over a set of images, but at a precise frame rate that varies as a function of realtime parameters. I insert QPixmaps into the cache within a for loop, and am able to find the QPixmap objects through QPixmap.find() within the same for loop, but outside the loop .find() returns None.
Below is the script. I can include the image series if needed. Appreciate any help.
import sys
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QLabel
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QPixmapCache
from pathlib import Path
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.animating_label = QLabel('animation here')
self.animating_label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
self.animating_label.pixmap().size().setWidth(200)
self.animating_label.pixmap().size().setHeight(200)
self.setCentralWidget(self.animating_label)
parent_path = Path(__file__).parent
self.images_path = parent_path / 'images/animation_set/'
self.animating_label.setPixmap(QPixmap(self.images_path.__str__() + "/clock01.png"))
self.pixmapcache_keys: [str] = []
self.load_images()
test = QPixmapCache.find("clock02")
try:
self.animating_label.setPixmap(test)
except:
if test == None:
print("Failure: QPixMapCache.find() returned", test)
def load_images(self):
image_suffixes = ['.jpg', '.png', '.gif', '.bmp']
imgs_found_count = 0
for filepath in self.images_path.iterdir():
if filepath.suffix in image_suffixes:
imgs_found_count += 1
cache_key = filepath.stem
self.pixmapcache_keys.append(cache_key)
if not QPixmapCache.find(cache_key):
pixmap = QPixmap(filepath.__str__())
QPixmapCache.insert(cache_key, pixmap)
print("pixmap %s" % cache_key, QPixmapCache.find(cache_key))
print(imgs_found_count, "image(s) found in animation_set directory.", len(self.pixmapcache_keys),
"keys loaded into QPixmapCache")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
# start Qt event loop
app.exec()
print("Script complete.")
sys.exit(1)
Here is the output:
pixmap clock04 <PyQt6.QtGui.QPixmap object at 0x101c84b30>
pixmap clock10 <PyQt6.QtGui.QPixmap object at 0x101c84970>
pixmap clock11 <PyQt6.QtGui.QPixmap object at 0x101c84b30>
pixmap clock05 <PyQt6.QtGui.QPixmap object at 0x101c84970>
...
24 image(s) found in animation_set directory. 24 keys loaded into QPixmapCache
Failure: QPixMapCache.find() returned None
Script complete.

I was able to get the animation working using a list of QPixmaps instead of QPixmapCache, but I am not sure if this is the optimal approach.
Generating Qpixmap list from files:
for filepath in sorted(self.images_path.iterdir()):
if filepath.suffix in image_suffixes:
qpixmap = QPixmap(filepath.__str__())
self.pixmaps.append(qpixmap)
called elsewhere on a timer
def advance(self):
if self.pixmap_current_index >= len(self.pixmaps) - 1:
self.pixmap_current_index = 0
else:
self.pixmap_current_index += 1
self.animating_label.setPixmap(self.pixmaps[self.pixmap_current_index])
self.timer.start(self.refresh_interval)

Related

In PyQt5, how do you save and restore user-typed values and dynamically created widgets inserted in to QTableWidget cells?

DESCRIPTION
I have a pyqt5 UI which has a QTableWidget with a dynamic row count; there is a button that adds rows. When a row is added, some of the cells contain QSpinBox(s) and one contains a QComboBox. The program also has a Save and a Restore QPushButton(s) that when selected, saves down all the widgets to an ini file located next to the py file. The save and restore methods, created by #eyllanesc and found here are pretty much universally used from what I have found on SO.
PROBLEM
The code below saves down the QSpinBox(s) and QComboBox fine. They are in the ini file. The restore function does not recover these widgets in to the QTableWidget. The row count is recovered, but no input text or widgets are inside the cells they were placed in.
WHAT I HAVE TRIED
I named the dynamically created widgets with a prefix_<row>_<column> name. I tried creating these in the initialisation of the UI, thinking there might be a link to the qApp.allWidgets() and the startup, but this didn't work. I was just guessing.
Reading #zythyr's post here, I thought I might have to add the widget to the QTableWidget in a parent<>child sort of arrangement (this is outside my knowledge), but the QTableWidget doesn't have the addWidget() method. I then tried *.setParent on the QComboBox (commented out in code below) and that didn't work either.
QUESTION
How do you save and restore user-input (typed in empty cell) data as well as QWidgets (namely QSpinBox and QComboBox) that are in a QTableWidget?
CODE
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QSettings, QFileInfo
from PyQt5.QtWidgets import (QApplication, qApp, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton, QLineEdit, QSpinBox,
QComboBox)
def value_is_valid(val):
#https://stackoverflow.com/a/60028282/4988010
if isinstance(val, QPixmap):
return not val.isNull()
return True
def restore(settings):
#https://stackoverflow.com/a/60028282/4988010
finfo = QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in qApp.allWidgets():
if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
last_value = w.property(name)
key = "{}/{}".format(w.objectName(), name)
if not settings.contains(key):
continue
val = settings.value(key, type=type(last_value),)
if (
val != last_value
and value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def save(settings):
#https://stackoverflow.com/a/60028282/4988010
for w in qApp.allWidgets():
if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
key = "{}/{}".format(w.objectName(), name)
val = w.property(name)
if value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
# custom spin box allowing for optional maximum
class CustomSpinBox(QSpinBox):
def __init__(self, sb_value=1, sb_step=1, sb_min=1, *args):
super(CustomSpinBox, self).__init__()
self.setValue(sb_value)
self.setSingleStep(sb_step)
self.setMinimum(sb_min)
if len(args) > 0:
sb_max = args[0]
self.setMaximum(sb_max)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 400, 100)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.le = QLineEdit()
self.le.setObjectName('le')
self.tbl = QTableWidget()
self.tbl.setObjectName('r_tbl')
header = self.tbl.horizontalHeader()
self.tbl.setRowCount(0)
self.tbl.setColumnCount(4)
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.le)
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
self.vbox.addWidget(self.pb_restore)
wid.setLayout(self.vbox)
# restore previous settings from *.ini file
restore(self.settings)
# pb signals
def initSignals(self):
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
self.pb_restore.clicked.connect(self.pb_restore_clicked)
# add a new row to the end - add spin boxes and a combobox
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
self.sb_1 = CustomSpinBox(1, 1, 1)
self.sb_1.setObjectName(f'sb_{row_count}_1')
self.sb_2 = CustomSpinBox(3, 1, 2, 6)
self.sb_2.setObjectName(f'sb_{row_count}_2')
self.cmb = QComboBox()
choices = ['choice_1', 'choice_2', 'choice_3']
self.cmb.addItems(choices)
self.cmb.setObjectName(f'cmb_{row_count}_0')
self.tbl.setCellWidget(current_row_count, 0, self.cmb)
self.tbl.setCellWidget(current_row_count, 1, self.sb_1)
self.tbl.setCellWidget(current_row_count, 2, self.sb_2)
#self.cmb.setParent(self.tbl) # <<<< this didn't work
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
def pb_save_clicked(self):
print(f'{self.pb_save.text()} clicked')
save(self.settings)
def pb_restore_clicked(self):
print(f'{self.pb_restore.text()} clicked')
restore(self.settings)
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
EDIT1
...removed as it didn't help and just made it more confusing...
EDIT2
Excluding the widgets, I have worked out how to use QSettings to save and restore user-entered cell data from a QTableWidget. Hope the following code helps someone. I have no doubt it could be done better and I welcome improvement suggestions. I'll update if I can work out the addition of widgets (QSpinBoxes and QComboBoxes) in to to cells.
import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
self.restore_settings()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 500, 300)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.tbl = QTableWidget(0, 4, self)
# config up the table
header = self.tbl.horizontalHeader()
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
self.vbox.addWidget(self.pb_restore)
wid.setLayout(self.vbox)
# pb signals
def initSignals(self):#
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
self.pb_restore.clicked.connect(self.pb_restore_clicked)
# reads in the ini file adn re-generate the table contents
def restore_settings(self):
self.setting_value = self.settings.value('table')
self.setting_row = self.settings.value('rows')
self.setting_col = self.settings.value('columns')
print(f'RESTORE: {self.setting_value}')
# change the table row/columns, create a dictionary out of the saved table
try:
self.tbl.setRowCount(int(self.setting_row))
self.tbl.setColumnCount(int(self.setting_col))
self.my_dict = dict(eval(self.setting_value))
except TypeError:
print(f'RESTORE: No ini file, resulting in no rows/columns')
# loop over each table cell and populate with old values
for row in range(self.tbl.rowCount()):
for col in range(self.tbl.columnCount()):
try:
if col == 0: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Label'][row]))
if col == 1: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['X'][row]))
if col == 2: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Y'][row]))
if col == 3: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Comment'][row]))
except IndexError:
print(f'INDEX ERROR')
# add a new row to the end
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
# remove selected row
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
# save the table contents and table row/column to the ini file
def pb_save_clicked(self):
# create an empty dictionary
self.tbl_dict = {'Label':[], 'X':[], 'Y':[], 'Comment':[]}
# loop over the cells and add to the table
for column in range(self.tbl.columnCount()):
for row in range(self.tbl.rowCount()):
itm = self.tbl.item(row, column)
try:
text = itm.text()
except AttributeError: # happens when the cell is empty
text = ''
if column == 0: self.tbl_dict['Label'].append(text)
if column == 1: self.tbl_dict['X'].append(text)
if column == 2: self.tbl_dict['Y'].append(text)
if column == 3: self.tbl_dict['Comment'].append(text)
# write values to ini file
self.settings.setValue('table', str(self.tbl_dict))
self.settings.setValue('rows', self.tbl.rowCount())
self.settings.setValue('columns', self.tbl.columnCount())
print(f'WRITE: {self.tbl_dict}')
def pb_restore_clicked(self):
self.restore_settings()
def closeEvent(self, event):
self.pb_save_clicked()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
CHALLENGE
I am sure there is a better way than this and I challenge those who are well versed in Qt (pyqt5) to find a better solution.
ANSWER
In the mean-time, I hope the below helps someone as I could not find this information anywhere. I can't really read the C++ Qt stuff and I am no pyqt5 Harry Potter, so this took some effort.
The general gist here, is the widgets were never saved. The values were saved to a dictionary and then to the QSettings ini file as a string. On boot, the ini file was parsed and the table rebuilt - rows and columns. The widgets were then built from scratch and re-inserted in to the table. The dictionary from the ini file was then parsed and the values applied to the widgets or the blank cells as required.
WISH
I really wish there was a method inline with the save / restore methods as per OP links. I found that if I included those save / restore methods in the init, it sometimes messed the table up completely. As a result, for my greater program, it looks like I am going to have to save and restore all settings manually.
MRE CODE
import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton, QComboBox, QSpinBox)
# custom spin box allowing for optional maximum
class CustomSpinBox(QSpinBox):
def __init__(self, sb_value=1, sb_step=1, sb_min=1, *args):
super(CustomSpinBox, self).__init__()
self.setValue(sb_value)
self.setSingleStep(sb_step)
self.setMinimum(sb_min)
if len(args) > 0:
sb_max = args[0]
self.setMaximum(sb_max)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
self.restore_settings()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 500, 300)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.tbl = QTableWidget(0, 4, self)
# config up the table
header = self.tbl.horizontalHeader()
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# name the table for QSettings
self.tbl.setObjectName('input_table')
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
wid.setLayout(self.vbox)
# create an empty dictionary to house the table widgets
self.table_widgets = {'cmb':[], 'sb_1':[], 'sb_2':[]}
# combobox values
self.choices = ['choice_1', 'choice_2', 'choice_3']
# pb signals
def initSignals(self):#
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
# reads in the ini file and re-generate the table contents
def restore_settings(self):
try:
self.setting_tbl = self.settings.value('table')
self.setting_row = self.settings.value('rows')
self.setting_col = self.settings.value('columns')
self.my_dict = dict(eval(self.setting_tbl))
# need to rebuild the table first
self.tbl.setRowCount(int(self.setting_row))
self.tbl.setColumnCount(int(self.setting_col))
print(f'RESTORE: row:{self.setting_row} and col:{self.setting_col} and table:{self.setting_tbl}')
# probably don't need to build and return values from the dictionary
for row in range(int(self.setting_row)):
self.table_widgets['cmb'].append(QComboBox())
self.table_widgets['sb_1'].append(CustomSpinBox(1, 1, 1))
self.table_widgets['sb_2'].append(CustomSpinBox(3, 1, 2, 6))
self.table_widgets['cmb'][row].addItems(self.choices)
self.tbl.setCellWidget(row, 0, self.table_widgets['cmb'][row])
self.tbl.setCellWidget(row, 1, self.table_widgets['sb_1'][row])
self.tbl.setCellWidget(row, 2, self.table_widgets['sb_2'][row])
self.tbl.cellWidget(row, 0).setCurrentText(self.my_dict['Label'][row])
self.tbl.cellWidget(row, 1).setValue(self.my_dict['X'][row])
self.tbl.cellWidget(row, 2).setValue(self.my_dict['Y'][row])
self.tbl.setItem(row, 3, QTableWidgetItem(self.my_dict['Comment'][row]))
except TypeError:
print('NO INI FILE PRESENT')
# add a new row to the end
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
self.table_widgets['cmb'].append(QComboBox())
self.table_widgets['sb_1'].append(CustomSpinBox(1, 1, 1))
self.table_widgets['sb_2'].append(CustomSpinBox(3, 1, 2, 6))
self.table_widgets['cmb'][-1].addItems(self.choices)
self.tbl.setCellWidget(current_row_count, 0, self.table_widgets['cmb'][current_row_count])
self.tbl.setCellWidget(current_row_count, 1, self.table_widgets['sb_1'][current_row_count])
self.tbl.setCellWidget(current_row_count, 2, self.table_widgets['sb_2'][current_row_count])
# save the table contents and table row/column to the ini file
def pb_save_clicked(self):
#save(self.settings)
# create an empty dictionary
self.tbl_dict = {'Label':[], 'X':[], 'Y':[], 'Comment':[]}
# loop over the cells and add to the dictionary
for row in range(self.tbl.rowCount()):
cmb_text = self.tbl.cellWidget(row, 0).currentText()
sb_1_value = self.tbl.cellWidget(row, 1).value()
sb_2_value = self.tbl.cellWidget(row, 2).value()
comment_text = self.tbl.item(row, 3)
try:
comment_text = comment_text.text()
except AttributeError: # happens when the cell is empty or a widget
comment_text = ''
self.tbl_dict['Label'].append(cmb_text)
self.tbl_dict['X'].append(sb_1_value)
self.tbl_dict['Y'].append(sb_2_value)
self.tbl_dict['Comment'].append(comment_text)
# write values to ini file
self.settings.setValue('table', str(self.tbl_dict))
self.settings.setValue('rows', self.tbl.rowCount())
self.settings.setValue('columns', self.tbl.columnCount())
print(f'WRITE TO INI FILE: {self.tbl_dict}')
# remove selected row
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
def closeEvent(self, event):
self.pb_save_clicked()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
I'm not well versed in PyQt by any means, but if I'm interpreting your problem correctly I believe PyQtConfig may help since nothing else has been suggested for some time. Here is the github version link.
Tucked away in a dark corner of github, this is not nearly as popular as it should be. A snippet from the introduction:
The core of the API is a ConfigManager instance that holds configuration settings (either as a Python dict, or a QSettings instance) and provides standard methods to get and set values.
Configuration parameters can have Qt widgets attached as handlers. Once attached the widget and the configuration value will be kept in sync. Setting the value on the ConfigManager will update any attached widgets and changes to the value on the widget will be reflected immediately in the ConfigManager. Qt signals are emitted on each update.
This should allow you to save the state of widgets and I would imagine the widgets inside them separately. However, you will need to map the necessary events to update the config file, then build the widgets "up". If it's still clearing the cache--as it seems to act similar to a dict--when pulling from the config file, this functionality might be easier if other languages were used.
Integrating database storage with java fetching methods might be overkill, but SQL for python would be a middle ground. You can have tables for parent and child widgets as well as parameters, then link them by unique id's. Using python to fetch these then build the widget on startup would be a great alternative, and would actively keep widgets organized. There is also the added benefit of being able to more easily take the application online at some point with MySQL or Oracle.

Converting multiple HTML files to PDF using PyQt5

I tried following this answer: How to use PyQT5 to convert multiple HTML docs to PDF in one loop
I modified it to convert all html files found in a local folder. For example htmls is a list of html files to be converted: [Q:\Ray\test1.html, Q:\Ray\prac2.html]
This is the code. However, when I try to run it, Python just freezes and I have to stop the run.
import os
import glob
from PyQt5 import QtWidgets, QtWebEngineWidgets
class PdfPage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self):
super().__init__()
self._htmls = []
self._current_path = ""
self.setZoomFactor(1)
self.loadFinished.connect(self._handleLoadFinished)
self.pdfPrintingFinished.connect(self._handlePrintingFinished)
def convert(self, htmls):
self._htmls = iter(zip(htmls))
self._fetchNext()
def _fetchNext(self):
try:
self._current_path = next(self._htmls)
except StopIteration:
return False
def _handleLoadFinished(self, ok):
if ok:
self.printToPdf(self._current_path)
def _handlePrintingFinished(self, filePath, success):
print("finished:", filePath, success)
if not self._fetchNext():
QtWidgets.QApplication.quit()
if __name__ == "__main__":
current_dir = os.path.dirname(os.path.realpath(__file__))
folder= current_dir+ '\\*.HTML'
htmls= glob.glob(folder)
app = QtWidgets.QApplication([])
page = PdfPage()
page.convert(htmls)
app.exec_()
print("finished")
It seems that the OP has not understood the logic of my previous solution which is:
Get the resource, in this case files,
Load it on the page,
When the load is finished then print the content of the page,
When the printing is finished then execute step 1 with the next resource.
In this it does not perform step 2, on the other hand it is recommended that the path of the pdf has a name other than the html
import os
import glob
from PyQt5.QtCore import QUrl
from PyQt5 import QtWidgets, QtWebEngineWidgets
class PdfPage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self):
super().__init__()
self._htmls = []
self._current_path = ""
self.setZoomFactor(1)
self.loadFinished.connect(self._handleLoadFinished)
self.pdfPrintingFinished.connect(self._handlePrintingFinished)
def convert(self, htmls):
self._htmls = iter(htmls)
self._fetchNext()
def _fetchNext(self):
try:
self._current_path = next(self._htmls)
except StopIteration:
return False
else:
self.load(QUrl.fromLocalFile(self._current_path))
return True
def _handleLoadFinished(self, ok):
if ok:
self.printToPdf(self._current_path + ".pdf")
def _handlePrintingFinished(self, filePath, success):
print("finished:", filePath, success)
if not self._fetchNext():
QtWidgets.QApplication.quit()
if __name__ == "__main__":
current_dir = os.path.dirname(os.path.realpath(__file__))
folder= current_dir+ '\\*.HTML'
htmls = glob.glob(folder)
print(htmls)
if htmls:
app = QtWidgets.QApplication([])
page = PdfPage()
page.convert(htmls)
app.exec_()
print("finished")

PyQt 5 QTableWidget.cellClicked Signal Not Working

I am trying to make a simple files app (or, file explorer app) and I am using the QTableWidget to Display the files and directories. When the user clicks an directory, I want the program to jump to that directory. I have used the QTableWidget.cellClicked signal, and it does not currently work.
The signal part:
self.filesTable.connect(print)#self.updateUiCellClick)
Added print instead of self.updateUiCellClick for debugging purposes.
Code (probably you do not need this):
#!/usr/bin/python3
print('i Import Modules')
print(' | Import sys')
import sys
print(' | Import PyQt5.QtCore')
from PyQt5.QtCore import *
print(' | Import PyQt5.QtGui')
from PyQt5.QtGui import *
print(' | Import PyQt5.QtWidgets')
from PyQt5.QtWidgets import * # PyQt5 Support
print(' | Import os')
import os
print(' | Import subprocess.Popen') # For backward-compatibility
from subprocess import Popen, PIPE
print(' | Done')
print('i Define class Form')
class root(QMainWindow):
def __init__(self, parent=None):
'''self.__init__ - Initializes QMainWindow'''
print(' self.__init__ - Initializes QMainWindow')
super(root, self).__init__(parent)
# Create Variables
self.currentPath = '/'
os.chdir(self.currentPath)
self.currentItems = os.listdir()
self.currentItemsLsProcess = Popen(['ls','-l'], stdout=PIPE, stderr=PIPE)
self.currentItemsLsProcessResult = self.currentItemsLsProcess.communicate()
if self.currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self,'Files - ls -l Error','ls -l responded with non-blank stderr.Error is shown here:<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br><hr><br>If you want to support the team, go to the GitHub Repository.'.format(self.currentItemsLsProcessResult[1].decode('utf-8')))
self.currentItemsLs = self.currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
# Create Table Widget
self.filesTable = QTableWidget()
# Init Table Widget
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.filesTable.setRowCount(len(self.currentItems))
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name','TimeStamp','Type','ls -l'])
# self.filesTable.setReadOnly(1)
# Create & Add Items
self.itemWidgets = [[],[],[],[]]
for i in range(len(self.currentItems)):
self.itemWidgets[0].append(QTableWidgetItem(self.currentItems[i]))
self.filesTable.setItem(i,0,self.itemWidgets[0][-1])
self.itemWidgets[3].append(QTableWidgetItem(self.currentItemsLs[i]))
self.filesTable.setItem(i,3,self.itemWidgets[3][-1])
# Init Widgets
# Align Widgets to root
self.setCentralWidget(self.filesTable)
# Signals-and-Slots
print('i Set self title')
self.setWindowTitle('{}'.format(self.currentPath))
def updateUi(self):
'''self.updateUi - None'''
os.chdir(self.currentPath)
self.currentItems = os.listdir()
self.currentItemsLsProcess = Popen(['ls','-l'], stdout=PIPE, stderr=PIPE)
self.currentItemsLsProcessResult = self.currentItemsLsProcess.communicate()
if self.currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self,'Files - ls -l Error','ls -l responded with non-blank stderr.Error is shown here:<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br><hr><br>If you want to support the team, go to the GitHub Repository.'.format(self.currentItemsLsProcessResult[1].decode('utf-8')))
self.currentItemsLs = self.currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.filesTable.setRowCount(len(self.currentItems))
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name','TimeStamp','Type','ls -l'])
self.itemWidgets = [[],[],[],[]]
for i in range(len(self.currentItems)):
self.itemWidgets[0].append(QTableWidgetItem(self.currentItems[i]))
self.filesTable.setItem(i,0,self.itemWidgets[0][-1])
self.filesTable..connect(print)#self.updateUiCellClick)
self.itemWidgets[3].append(QTableWidgetItem(self.currentItemsLs[i]))
self.filesTable.setItem(i,3,self.itemWidgets[3][-1])
self.filesTable.resizeColumnsToContents()
self.setWindowTitle('{}'.format(self.currentPath))
def updateUiCellClick(self, row, column):
'''self.updateUiCellClick - None'''
print('self.updateUiCellClick - None')
self.currentpath += self.itemWidgets[0][row].text+'/'
self.updateUi()
print(' | Done')
if __name__ == '__main__':
print('i Execute instance')
app = QApplication(sys.argv)
root = root()
root.show()
app.exec_()
print(' | Done')
The connection should be as follows
self.filesTable.cellClicked.connect(self.updateUiCellClick)
^^^^^^^^^^^
signal
In addition to this it is not necessary to create the connection every time you fill in the table, it is enough when you create it.
If you look at code you see that many parts are repeating, which is not necessary, I take the boldness to make improvements to your code as the verification of path and reduce the code.
import sys
import os
from subprocess import Popen, PIPE
from PyQt5.QtWidgets import *
class Root(QMainWindow):
def __init__(self, parent=None):
print(' self.__init__ - Initializes QMainWindow')
QMainWindow.__init__(self, parent)
# Create Table Widget
self.filesTable = QTableWidget()
self.filesTable.cellClicked.connect(self.updateUiCellClick)
# Init Table Widget
self.filesTable.clear()
self.filesTable.setSizeAdjustPolicy(QTableWidget.AdjustToContents)
self.filesTable.setColumnCount(4)
self.filesTable.setHorizontalHeaderLabels(['Name', 'TimeStamp', 'Type', 'ls -l'])
# Init Widgets
self.setCentralWidget(self.filesTable)
self.populate_table("/")
def populate_table(self, path):
# Verify that it is a directory.
if not os.path.isdir(path):
return
os.chdir(path)
current_items = os.listdir()
currentItemsLsProcess = Popen(['ls', '-l'], stdout=PIPE, stderr=PIPE)
currentItemsLsProcessResult = currentItemsLsProcess.communicate()
if currentItemsLsProcessResult[1].decode('utf-8'):
QMessageBox.warning(self, 'Files - ls -l Error',
'ls -l responded with non-blank stderr.Error is shown here:'
'<br><code>{}</code><br><hr><br>Error LsStderr (e-lsstderr)<br>'
'<hr><br>If you want to support the team, go to the '
'GitHub Repository.'.format(
currentItemsLsProcessResult[1].decode('utf-8')))
return
self.filesTable.clear()
currentItemsLs = currentItemsLsProcessResult[0].decode('utf-8').split('\n')[1:-1]
self.filesTable.setRowCount(len(current_items))
for i, values in enumerate(zip(current_items, currentItemsLs)):
name, ls = values
self.filesTable.setItem(i, 0, QTableWidgetItem(name))
self.filesTable.setItem(i, 3, QTableWidgetItem(ls))
self.setWindowTitle('{}'.format(path))
def updateUiCellClick(self, row, _):
path = os.path.join(os.getcwd(), self.filesTable.item(row, 0).text())
self.populate_table(path)
if __name__ == '__main__':
print('i Execute instance')
app = QApplication(sys.argv)
root = Root()
root.show()
status = app.exec_()
print(' | Done')
sys.exit(status)

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

Why does QFileSystemWatcher work for directories but not files in Python?

I borrowed this code from another StackOverflow answer:
from PyQt4 import QtCore
#QtCore.pyqtSlot(str)
def directory_changed(path):
print('Directory Changed!!!')
#QtCore.pyqtSlot(str)
def file_changed(path):
print('File Changed!!!')
fs_watcher = QtCore.QFileSystemWatcher(['/path/to/files_1', '/path/to/files_2', '/path/to/files_3'])
fs_watcher.connect(fs_watcher, QtCore.SIGNAL('directoryChanged(QString)'), directory_changed)
fs_watcher.connect(fs_watcher, QtCore.SIGNAL('fileChanged(QString)'), file_changed)
The problem is, file_changed never gets called, no matter what. directory_changed is reliably called when a file is added, for example, but changing the files content does not result in file_changed being called.
I called a few variations of QtCore.SIGNAL('fileChanged(QString)'), eg, QtCore.SIGNAL('fileChanged(const QString &)'), to no avail. There are no warnings, or errors -- it simply does not trigger the function.
Recommendations?
It's hard to be certain what's going wrong, because the example code is incomplete, and so cannot work at all as it stands.
However, assuming the real code you are running is more or less sane/complete, your problem is probably caused by not adding the directory itself to the list of paths.
A basic script should look something like this:
import sys
from PyQt4 import QtCore
def directory_changed(path):
print('Directory Changed: %s' % path)
def file_changed(path):
print('File Changed: %s' % path)
app = QtCore.QCoreApplication(sys.argv)
paths = [
'/path/to',
'/path/to/files_1',
'/path/to/files_2',
'/path/to/files_3',
]
fs_watcher = QtCore.QFileSystemWatcher(paths)
fs_watcher.directoryChanged.connect(directory_changed)
fs_watcher.fileChanged.connect(file_changed)
sys.exit(app.exec_())
import argparse
import configparser
import os
import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QDesktopWidget, QMessageBox
from PyQt5.QtWidgets import QMainWindow
from ProgressBar_ui import Ui_Form
class ProgressBarWindow(QMainWindow, Ui_Form):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.progressBar.setMinimum(0)
self.ui.progressBar.setMaximum(0)
self.ui.progressBar.setValue(0)
self.setWindowTitle("Progress Bar")
self.MSversion = ""
self.LOADING_LOG_PATH = ""
mainIco = ("Icons\myIcon.ico")
self.setWindowIcon(QIcon(mainIco))
self.ui.label.setText("")
self.ui.label.setWordWrap(True)
def location_on_the_screen(self):
ag = QDesktopWidget().availableGeometry()
sg = QDesktopWidget().screenGeometry()
widget = self.geometry()
x = ag.width() - widget.width()
y = 2 * ag.height() - sg.height() - widget.height()
self.move(x, y)
def file_changed(self, pathPassed):
if os.path.exists(pathPassed):
f = open(pathPassed, "r")
for x in f:
#print(x)
label =x
self.ui.label.setText(label)
if x == "CloseLoading":
self.close()
def main():
app = QApplication(sys.argv)
w = ProgressBarWindow()
w.setWindowFlags(w.windowFlags() & ~QtCore.Qt.WindowMaximizeButtonHint)
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(description='ProgressBar Arguments')
parser.add_argument('version', action="store", type=str)
args = vars(parser.parse_args())
if len(sys.argv) < 1:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
errorMsg = "There are less than 2 command line arguments provided.\nLauncher can not be run."
msg.setText(errorMsg)
msg.setWindowTitle("Save paths..")
msg.exec_()
sys.exit()
p= '/path/toFile/'
paths = [ p ]
fs_watcher = QtCore.QFileSystemWatcher(paths)
#fs_watcher.directoryChanged.connect(w.directory_changed)
fs_watcher.fileChanged.connect(w.file_changed)
w.location_on_the_screen()
w.show()
app.exec_()
if __name__ == "__main__":
sys.exit(main())

Categories

Resources