0

I write a small GUI to move files from one directory to another, my idea is using QTextEdit to show the progress, when one file moved, then write this file name in QTextEdit, and so on till all files are moved. I tried like below:

from PyQt5 import QtGui
from PyQt5.QtWidgets import QStyleFactory, QApplication, QWidget, QListWidget, QLabel, QVBoxLayout, QHBoxLayout, QPushButton
from PyQt5.QtWidgets import (QWidget, QLabel, QLineEdit, QFileDialog,
                             QTextEdit, QGridLayout, QApplication, QMessageBox, QProgressBar)

from PyQt5.QtCore import Qt, QThread, pyqtSignal
import sys, os, time

     
            
class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.title = "test"
        self.top = 200
        self.left = 500
        self.width = 400
        self.height = 300
        self.InitWindow()
        
    def InitWindow(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
       
        # main layout
        self.vbox = QVBoxLayout()
        
        # for src path
        self.hbox1 = QHBoxLayout()
        self.label1 = QLabel('Source      ')
        self.src = QLineEdit()
        self.path1 = self.src.text()
        self.btnSrc = QPushButton('...')
        self.btnSrc.clicked.connect(lambda: self.getSrc())
        self.hbox1.addWidget(self.label1)
        self.hbox1.addWidget(self.src)
        self.hbox1.addWidget(self.btnSrc)
            
        # for des path
        self.hbox2 = QHBoxLayout()
        self.label2 = QLabel('Destination')
        self.des = QLineEdit()
        self.path2 = self.des.text()
        self.btnDes = QPushButton('...')
        self.btnDes.clicked.connect(lambda: self.getDes())
        self.hbox2.addWidget(self.label2)
        self.hbox2.addWidget(self.des)
        self.hbox2.addWidget(self.btnDes)
        
        self.statusBox = QTextEdit()
        self.statusBox.setReadOnly(True)
        
        self.btnStart = QPushButton('Start')
        self.btnStart.clicked.connect(self.startMov)
        
        self.vbox.addLayout(self.hbox1)
        self.vbox.addLayout(self.hbox2)
        self.vbox.addWidget(self.statusBox)
        self.vbox.addWidget(self.btnStart)
        
        self.setLayout(self.vbox)
        
        self.show()
        
    def getSrc(self):
        try:
            self.path1 = QFileDialog.getExistingDirectory(self)
            self.src.setText(self.path1)
            #all file in this path
            self.filenames = os.listdir(self.path1)
            self.statusBox.setPlainText(str(len(self.filenames)) + ' files found in this folder:')
            self.statusBox.append('')
            for i, sample in enumerate(self.filenames):
                self.statusBox.append(str(i+1) + '. ' + sample)
        except:
            pass
        
    def getDes(self):
        try:
            self.path2 = QFileDialog.getExistingDirectory(self)
            self.des.setText(self.path2)
        except:
            pass
    
    def startMov(self):    
        buttonReply = QMessageBox.question(self, 'wait...', "Do you want to move all this files?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if buttonReply == QMessageBox.Yes:
            for file in self.filenames:
                blueText = "<span style=\" font-size:8pt; font-weight:600; color:#0047b3;\" >moving: </span>"
                greenText = "<span style=\" font-size:8pt; font-weight:600; color:#009933;\" >done! </span>"
                self.statusBox.append(blueText)                          
                os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file)
                self.statusBox.append(file)
                self.statusBox.append(greenText)
        else:
            print('No clicked.')
    
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

but it only show text in the QTextEdit after all files are moved and I can not see the progress at all.

why is it so and how can I fix it?

Edit: I want the order as below:

# 1. before one file is moved --> in QLineEdit should be: moving 
self.statusBox.append(blueText) 

# 2. file should be moved from src to des 
os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file) 

# 3. name of file stand in QLineEdit 
self.statusBox.append(file) 

# 4. then 'done' stand in QLineEdit 
self.statusBox.append(greenText)

but the order now is: 1 2 4 3 after all text was there then the files are moved

actnmk
  • 156
  • 12

1 Answers1

0

I tried to test by this code.

def startMov(self):    
        buttonReply = QMessageBox.question(self, 'wait...', "Do you want to move all this files?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if buttonReply == QMessageBox.Yes:
            dir_ = QDir()
            import time
            for file in self.filenames:
                # QApplication.processEvents()
                blueText = "<span style=\" font-size:8pt; font-weight:600; color:#0047b3;\" >moving: </span>"
                greenText = "<span style=\" font-size:8pt; font-weight:600; color:#009933;\" >done! </span>"
                self.statusBox.append(blueText)                   
                os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file)
                print(104, dir_.exists(self.path2 + '\\' + file))                      
                self.statusBox.append(file)
                self.statusBox.append(greenText)
                self.statusBox.repaint()

104 always returns True.It indicates the moved files exist the destination file before 3 & 4. I assume that OS stocks the data of files to some extent like a buffer object. After doing it, OS shows the names of files on the destination folda all at once. As I wrote a comment, I don't know the way for makes a connection to the OS execution about it.

I'm sorry I cannot solve this problem as you demand.I hope other hero comes.

Solution

One possible solution is to use QApplication.processEvents().

As for you are importing QThread, I assume you might attempt to implement thread.

Qt is running at one thread, and Qt GUIs should be run at the thread.

You should not use a kind of Qt GUIS in another thread.

processEvents, you can use the concurrent process in only the thread.

#is omitting...

# class Thread(QThread):
#     def __init__(self, parent=None):
#         super(Thread, self).__init__(parent)
        
#     def run(self):
#         self.parent().forloop()
            

#is omitting...  
        
    def startMov(self):    
        buttonReply = QMessageBox.question(self, 'wait...', "Do you want to move all this files?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if buttonReply == QMessageBox.Yes:
            QApplication.processEvents()
            for file in self.filenames:
                QApplication.processEvents()
                blueText = "<span style=\" font-size:8pt; font-weight:600; color:#0047b3;\" >moving: </span>"
                greenText = "<span style=\" font-size:8pt; font-weight:600; color:#009933;\" >done! </span>"
                self.statusBox.append(blueText)                          
                os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file)        
                self.statusBox.append(file)
                self.statusBox.append(greenText)
            # I don't recommend it.Error happends.
            # thread = Thread(self)
            # thread.execute.connect(self.execute)
            # thread.start()
            
        else:
            print('No clicked.')
#is omitting...

Second Solution You call repaint() of the method from QWidget.

#is omitting...
def startMov(self):    
        buttonReply = QMessageBox.question(self, 'wait...', "Do you want to move all this files?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if buttonReply == QMessageBox.Yes:
            # QApplication.processEvents()
            for file in self.filenames:
                # QApplication.processEvents()
                blueText = "<span style=\" font-size:8pt; font-weight:600; color:#0047b3;\" >moving: </span>"
                greenText = "<span style=\" font-size:8pt; font-weight:600; color:#009933;\" >done! </span>"
                self.statusBox.append(blueText)                          
                os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file)        
                self.statusBox.append(file)
                self.statusBox.append(greenText)
                self.statusBox.repaint()
            # I don't recommend it.
            # thread = Thread(self)
            # thread.start()
            
        else:
            print('No clicked.')
#is omitting...

Why does the append method of QTextEdit work?

In fact, this works under the surface of QTextEdit. You can check it by these codes, for example.

            print(self.statusBox.document().characterCount())
            print(self.statusBox.document().blockCount())

You can make sure the blockcount & characterCount are increasing.

But it seems that Qt doesn't repaint one by one. Qt repaint the gui at the last time all at once.Probably, it may be for the save.It depends on the developper.

If you want to use QThread, I recommend you read Modify Qt GUI from background worker thread in advance. But I think it is needless.

Haru
  • 1,884
  • 2
  • 12
  • 30
  • Hi Haru, thank you for the answer, I have tried both of your solutions. Both update the statusBox line by line BUT after all, the files are moved and not in the order of code as I wrote – actnmk Apr 14 '21 at 14:09
  • Thanks to your reply.Anyway, I have written `QProcessEvents()` it does not exist. `processEvents()` is correct.My current understanding of your question is as follows: 1- you want to view the process of the files are moving. 2- but now, only the result of the process are shown. 3-how to repair this? According to your comment, is there any problem in the order of files?In my test, the files are ordered, no problem.Or, is the problem of order about your coding?AFAI read your first question, I think you didn't write the order.Would you write the points more clearly? – Haru Apr 15 '21 at 02:15
  • hi, I want the order as below: # 1. before one file is moved --> in QLineEdit should be: moving self.statusBox.append(blueText) # 2. file should be moved from src to des os.replace(self.path1 + '\\' + file, self.path2 + '\\' + file) # 3. name of file stand in QLineEdit self.statusBox.append(file) # 4. then 'done' stand in QLineEdit self.statusBox.append(greenText) – actnmk Apr 15 '21 at 08:36
  • I edit my post, and I also tried with processEvents(), did not work as well – actnmk Apr 15 '21 at 08:45
  • 1
    @actnmk I see. You want files to move into another file during 2 & 4.For making sure this phenomenon, we need to execute this code with both of the files (at the position designated by two QLineEdits) viewed.right?Certainly, files are not moved at the time.I have understood what you want exactly, thanks to your reply!I endevour to solve this problem. – Haru Apr 15 '21 at 12:30
  • yes this is what I want, arigatou Haru :) – actnmk Apr 15 '21 at 12:54
  • 1
    theoretically , os.replace is under the execution of your OS.python mandates the execution to it.After that, OS executes the command as speedy as it can.That is to say, this is other thread, Qt has its own thread, so after python(Qt) mandates the execution to OS, Qt come back to its thread execution, that is to say, 4. If you don't worry about speed, you must check the existence of the moved file one by one.In short, the job of Qt is to pass the work of os.replace to your OS system. – Haru Apr 15 '21 at 13:04
  • 1
    In short, your code is certainly executed by order, but the view of the file doesn't reflect it.Because the area of execution is not in Qt.In a word, this problem is a kind of thread programming between Qt and your OS.I think you cannot solve this problem only in the area of Qt. – Haru Apr 16 '21 at 04:58