Unsolved Widgets are shrinked when added to scroll area
-
It doesn't work. I tried it already. The labels shrink anyway.
-
Hi
thats odd. works fine here in c++
It wont make them less than min size.
can you show your exact code ? Maybe we can spot something.
-
The code above is basically my code. One thing I forgot to say that I do some "computations" in a working thread and then use signals/slots to emit intermediate results. So the code where I create labels with images is my slot updating the GUI.
I recorded the screen one more time. This what I see with the code above: https://gfycat.com/lighthearteddentalcrustacean . If I add
widget.updateGeometry()
after adding a new label, these jumps/flickers almost disappear but sometimes happen (way less frequently).
If I add
time.sleep(1)
after emitting a new signal (so slot creating my labels is called less often), these jumps/flickers don't happen at all: https://gfycat.com/calculatinggenuinekoodoo .
Can it be some problem with processing too many signals in a short period of time so the widgets cannot be repainted/updated fast enough to avoid these jumps/flickers?
-
Hi,
How many signals are you sending per seconds for your image updates ?
-
About 5-10. It's not too many I think. But I have quite old CPU - Athlon 64 x2 3800+. I do not have any fancy hardware with me right now to test it.
But, indeed, the rarer the GUI needs to be updated, the less frequent these glitch are.
-
The number seems fine but is it per image ?
It could also come from your graphics stack. -
No, It's one signal per image.
-
Can you provide a minimal buildable example that shows that behaviour ?
-
I think the "too many signals" hypothesis is right. I added a label and update it hundreds times per second. The glitches gets way worse. If I do not do it, they almost disappear. Also I tested it on another hardware and got the same behaviour ( less signals - no glitches, more signals - glitches).
Here is the code (but it is use PyQt, is it ok?):
import sys from multiprocessing import Pool from pathlib import Path from PyQt5 import QtCore, QtGui, QtWidgets # !!! Change to your folder with images FOLDER = '/home/user/images' def find_images(folder): '''Return paths of the images in the folder''' images = [] for filename in Path(folder).rglob('*.jpg'): if filename.is_file(): images.append(str(filename)) return images def scale_image_BA(image_path): '''To generate more signals, concurrent processing is used (via lib "multiprocessing"). QPixmap or QImage cannot be serialized so converting to QByteArray is used''' reader = QtGui.QImageReader(image_path) reader.setScaledSize(QtCore.QSize(200, 200)) image = reader.read() ba = QtCore.QByteArray() buf = QtCore.QBuffer(ba) buf.open(QtCore.QIODevice.WriteOnly) image.save(buf, 'JPG', 100) buf.close() return ba def QByteArray_to_QPixmap(ba_image): '''Convert from QByteArray back to QPixmap''' pixmap = QtGui.QPixmap() pixmap.loadFromData(ba_image) return pixmap class Signals(QtCore.QObject): image = QtCore.pyqtSignal(QtCore.QByteArray) label = QtCore.pyqtSignal(str) class Worker(QtCore.QRunnable): def __init__(self, image_paths): super().__init__() self.image_paths = image_paths self.signals = Signals() @QtCore.pyqtSlot() def run(self): '''Run image scaling on all available cores to emit more signals''' with Pool() as p: for baimg in p.imap_unordered(scale_image_BA, self.image_paths): # Glitches happen if emit many signals per second??? # Without updating 'label' many times, everything's fine for i in range(1000): self.signals.label.emit(str(i)) self.signals.image.emit(baimg) class MyMainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setMinimumSize(QtCore.QSize(800, 600)) centralWidget = QtWidgets.QWidget() self.setCentralWidget(centralWidget) scrollArea = QtWidgets.QScrollArea() scrollArea.setWidgetResizable(True) imagesWidget = QtWidgets.QWidget() scrollArea.setWidget(imagesWidget) self.label = QtWidgets.QLabel('Counter') startButton = QtWidgets.QPushButton('Start') startButton.clicked.connect(self.run_worker) verticalLayout = QtWidgets.QVBoxLayout() verticalLayout.addWidget(scrollArea) verticalLayout.addWidget(self.label) verticalLayout.addWidget(startButton) centralWidget.setLayout(verticalLayout) self.imagesLayout = QtWidgets.QVBoxLayout() imagesWidget.setLayout(self.imagesLayout) def run_worker(self): images = find_images(FOLDER) threadpool = QtCore.QThreadPool.globalInstance() w = Worker(images) w.signals.image.connect(self.update_gui) w.signals.label.connect(self.label.setText) threadpool.start(w) def update_gui(self, image): label = QtWidgets.QLabel() pixmap = QByteArray_to_QPixmap(image) label.setPixmap(pixmap) self.imagesLayout.addWidget(label) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mw = MyMainWindow() mw.show() app.exec()
-
What version of PyQt5 are you using ?
-
5.12.2, but tested also on 5.14.2 and 5.9 - got the same behaviour.
-
What graphics stack do you have on your machine ?
-
One PC has Geforce GT 730, driver v. 435.21, Linux Mint with Cinnamon DE (last one);
the other one has Intel UHD 630, Manjaro with KDE (use Qt, right?) - again get the same thing, -
I've made the peace of code showing this behaviour a little simpler:
import sys from PyQt5 import QtCore, QtGui, QtWidgets # Maybe you need to play with these constants PIXMAP_NUM = 1000 EVENTLOOP_RUN_NUM = 100 class Signals(QtCore.QObject): pixmaps = QtCore.pyqtSignal(list) class Worker(QtCore.QRunnable): def __init__(self): super().__init__() self.signals = Signals() @QtCore.pyqtSlot() def run(self): '''Make QPixmap object with different colours, add them to a list and emit the list''' c = QtGui.QColor colors = [c('red'), c('green'), c('blue'), c('cyan'), c('magenta'), c('yellow')] i = 0 pms = [] for _ in range(PIXMAP_NUM): pixmap = QtGui.QPixmap(200, 200) pixmap.fill(colors[i]) i += 1 if i == len(colors): i = 0 pms.append(pixmap) self.signals.pixmaps.emit(pms) class MyMainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setMinimumSize(QtCore.QSize(800, 600)) centralWidget = QtWidgets.QWidget() self.setCentralWidget(centralWidget) scrollArea = QtWidgets.QScrollArea() scrollArea.setWidgetResizable(True) imagesWidget = QtWidgets.QWidget() scrollArea.setWidget(imagesWidget) startButton = QtWidgets.QPushButton('Start') startButton.clicked.connect(self.run_worker) verticalLayout = QtWidgets.QVBoxLayout() verticalLayout.addWidget(scrollArea) verticalLayout.addWidget(startButton) centralWidget.setLayout(verticalLayout) self.imagesLayout = QtWidgets.QVBoxLayout() imagesWidget.setLayout(self.imagesLayout) def run_worker(self): threadpool = QtCore.QThreadPool.globalInstance() w = Worker() w.signals.pixmaps.connect(self.update_gui) threadpool.start(w) def update_gui(self, pixmaps): for i, img in enumerate(pixmaps): label = QtWidgets.QLabel() # The next line doesn't change the behaviour label.setMinimumSize(QtCore.QSize(200, 200)) label.setPixmap(img) self.imagesLayout.addWidget(label) # The next line doesn't change the behaviour self.updateGeometry() if i % EVENTLOOP_RUN_NUM == 0: QtCore.QCoreApplication.processEvents() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mw = MyMainWindow() mw.show() app.exec()
This code reproduce the glitches on both the machines.
I've noticed one thing. If we do not set the minimum size,
label.setMinimumSize(QtCore.QSize(200, 200))
labels shrink. But if we do, they overlap (so, indeed, the minimum size is respected but imagesWidget is not big enough - cannot change its own size fast enough??? - to set the labels without overlapping???)