3

I'm writing a simple script for resizing photos. I'd like to have a widget with text-field in which messages appear after resizing each file.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import time, sys
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QWidget, QVBoxLayout

class Thread(QThread):
    log = pyqtSignal(str)
    def __init__(self, parent=None):
        super(Thread, self).__init__(parent)
    def test(self, i):
        time.sleep(1)
        self.log.emit(str(i))

class Widget(QWidget):
    def __init__(self):
        super().__init__()
        self.ui()
    def process(self):
        self.toLog('some text...')
        worker = Thread()
        worker.log.connect(self.toLog)
        for i in range(1, 5):
            worker.test(i)
    def ui(self):
        self.LogOutputTxt = QTextEdit()
        self.LogOutputTxt.setReadOnly(True)
        startBtn = QPushButton('Start')
        startBtn.clicked.connect(self.start)
        layout = QVBoxLayout()
        layout.addWidget(self.LogOutputTxt)
        layout.addWidget(startBtn)
        self.setLayout(layout)
        self.resize(400, 300)
        self.show()
    def start(self):
        self.toLog('start')
        self.process()
    def toLog(self, txt):
        self.LogOutputTxt.append(txt)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = Widget()
    sys.exit(app.exec_())

So far all the messages appear at once after all files are resized. Is there any way to do it one by one (I mean file resized, message displayed, etc.)?

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
przemekk
  • 351
  • 3
  • 10
  • The code you've posted is incomplete, and wrong in so many ways that it's very difficult to re-write it constructively. Please read the guidance on how to produce a [mcve]. – ekhumoro Nov 13 '16 at 17:18
  • Sorry, I rewrote the code, hope it's better now. – przemekk Nov 13 '16 at 18:17
  • Are you expecting these worker threads to run concurrently? If they are executing python code that is cpu-bound (like resizing images), only one of them can run at a time (because of the Global Interpreter Lock). For parallel processing, you would need to use separate processes, rather than threads. – ekhumoro Nov 13 '16 at 23:35
  • Not exactly - my intention was that after each resizing image GUI will be updated. For now it looks like firstly images are resized, and after that GUI is updated. Like in the code above - I wish that after each sleep I'll get a message to LogOutputTxt. – przemekk Nov 14 '16 at 17:16

1 Answers1

4

Below is a re-write of your script that should do want you want.

But note that this is quite simplistic, and doesn't try too hard to ensure thread-safety. The setItems method just makes a shallow copy of the data passed to it - which is only really okay for a list of immutable objects. You also must make sure you never do any gui operations inside the worker thread, which includes operations on pixmaps. If you want to manipulate images, use QImage. (And if you want to know how to stop a running thread, see for example this answer).

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import time, sys
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtWidgets import (
    QApplication, QPushButton, QTextEdit, QWidget, QVBoxLayout
    )

class Thread(QThread):
    log = pyqtSignal(str)

    def __init__(self, parent=None):
        super(Thread, self).__init__(parent)
        self._items = []

    def setItems(self, items):
        if not self.isRunning():
            self._items[:] = items

    def run(self):
        for item in self._items:
            time.sleep(1)
            self.log.emit('processing: %s' % item)

class Widget(QWidget):
    def __init__(self):
        super().__init__()
        self.ui()
        self._worker = Thread(self)
        self._worker.log.connect(self.toLog)
        self._worker.started.connect(lambda: self.toLog('start'))
        self._worker.finished.connect(lambda: self.toLog('finished'))

    def process(self):
        items = ['Image%02d.png' % i for i in range(10)]
        self._worker.setItems(items)
        self._worker.start()

    def ui(self):
        self.LogOutputTxt = QTextEdit()
        self.LogOutputTxt.setReadOnly(True)
        startBtn = QPushButton('Start')
        startBtn.clicked.connect(self.start)
        layout = QVBoxLayout()
        layout.addWidget(self.LogOutputTxt)
        layout.addWidget(startBtn)
        self.setLayout(layout)
        self.resize(400, 300)
        self.show()

    def start(self):
        if not self._worker.isRunning():
            self.process()

    def toLog(self, txt):
        self.LogOutputTxt.append(txt)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ui = Widget()
    sys.exit(app.exec_())
Community
  • 1
  • 1
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for your reply :-). It seems enough for me. At the beginning I wrote simple command batch script using PIL for images processing, I've tried to create GUI just of curiosity, so simplistic solution is really ok. – przemekk Nov 15 '16 at 19:02