I made a GUI application with Python + PyQt5

I made an application that operates with GUI with python. I will briefly summarize that time.

Specifications: Create a list of files with a specific extension, process them one by one, and view their progress

For example, it is an image that opens a Python file under a specific folder, processes it one by one, and displays that XX out of XX is being executed.

Create a class that collects files with a specific extension

First, create a class `` `FileList``` that collects files with a specific extension and creates a list.

filelist.py


import os


class FileList():
    ''' store file list'''
    def __init__(self, root_dir, ext):
        self.root_dir = root_dir
        self.ext = ext
        self.files = []

    def retrieve(self):
        for rd, _, fl in os.walk(self.root_dir):
            for f in fl:
                _, fext = os.path.splitext(f)
                if fext == self.ext:
                    self.files.append(os.path.join(rd, f))

    def print(self):
        for f in self.files:
            print(f)

It's a disappointingly easy class.

I will try it.

20170625001.png

Yeah, maybe right.

GUI design

Design the GUI. The required elements are as follows.

  1. Parts for entering the target folder
  2. Parts for instructing execution
  3. Parts that display progress

The parts for entering the target folder should have a "Browse for Folder" button and a text box that displays the specified folder.

The part for instructing execution should have a "execute" button.

For the part that displays the progress, the name of the file being processed is displayed in the text box, and the overall progress rate is displayed in the progress bar.

20170625002.png

I think that this part placement can be easily done by using an appropriate GUI design tool, but for the time being, I will write it by hand.

Basically, plagiarize from First programs in PyQt5.

guimain.py


import sys
import os
from PyQt5.QtWidgets import (QWidget, QApplication, 
							 QPushButton, QLineEdit,
                             QHBoxLayout, QVBoxLayout, 
                             QTextEdit, QProgressBar,
                             QFileDialog)
from PyQt5.QtCore import Qt


class MainWidget(QWidget):
    dirname = ''
    step = 0

    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.initUI()

    def initUI(self):
        self.resize(480, 360)

        self.txtFolder = QLineEdit()
        self.btnFolder = QPushButton('reference...')

        hb1 = QHBoxLayout()
        hb1.addWidget(self.txtFolder)
        hb1.addWidget(self.btnFolder)

        self.btnExec = QPushButton('Run')
        self.btnExec.setEnabled(False)
        
        hb2 = QHBoxLayout()
        hb2.addWidget(self.btnExec)

        self.txtLog = QTextEdit()

        self.pbar = QProgressBar()
        self.pbar.setTextVisible(False)

        layout = QVBoxLayout()
        layout.addLayout(hb1)
        layout.addLayout(hb2)
        layout.addWidget(self.txtLog)
        layout.addWidget(self.pbar)
        self.setLayout(layout)

        self.setWindowTitle('PyQt5 Sample')


def main(args):
    app = QApplication(args)
    dialog = MainWidget()
    dialog.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main(sys.argv)

20170626001.png

It's almost as expected.

Implementation

Function of "Browse ..." button (btnFolder)

Define it as a clicked event in btnFolder.

self.btnFolder.clicked.connect(self.show_folder_dialog)
def show_folder_dialog(self):
    ''' open dialog and set to foldername '''
    dirname = QFileDialog.getExistingDirectory(self,
                                               'open folder',
                                               os.path.expanduser('.'),
                                               QFileDialog.ShowDirsOnly)
    if dirname:
        self.dirname = dirname.replace('/', os.sep)
        self.txtFolder.setText(self.dirname)
        self.btnExec.setEnabled(True)
        self.step = 0

Open the dialog to specify the folder and enable the "Run" button.

Function of "Run" button (btnExec)

Collect ".py" files under the specified folder and display the file name in txtLog. In addition, the progress during execution is represented by a progress bar.

Import the FileList class

guimain.py


from filelist import FileList

Execute the retrieve () method of FileList class

Modify the FileList class slightly from the original version.

filelist.py


class FileList():
    ''' store file list'''
    def __init__(self):
        self.root_dir = ''
        self.ext = ''
        self.files = []

    def setup(self, root_dir, ext):
        self.root_dir = root_dir
        self.ext = ext
        self.retrieve()

    def retrieve(self):
        self.files = []
        for rd, _, fl in os.walk(self.root_dir):
            for f in fl:
                _, fext = os.path.splitext(f)
                if fext == self.ext:
                    self.files.append(os.path.join(rd, f))

    def print(self):
        for f in self.files:
            print(f)

init()so,root_dirWhenextWas specified, but it was newly definedsetup()Move to the method.

For inheritance from QThread

In the GUI program, since the GUI array is operated by mutti-thread, the FileList class is inherited from QThread so that the file array operation itself also operates by multi-thread.

In addition, we will implement a function to send a signal so that the progress bar can be moved for each process.

Originally, in process_file (), for example, if it is an image file, some editing is done, but in this sample, it is not the main point, so I will not do anything for the time being.

filelist.py


import os
import sys
from PyQt5.QtCore import pyqtSignal, QMutexLocker, QMutex, QThread


class FileList(QThread):
    ''' store file list'''

    sig_file = pyqtSignal(str)

    def __init__(self, parent=None):
        super(FileList, self).__init__(parent)
        self.stopped = False
        self.mutex = QMutex()

    def setup(self, root_dir, ext):
        self.root_dir = root_dir
        self.ext = ext
        self.retrieve()
        self.stopped = False

    def stop(self):
        with QMutexLocker(self.mutex):
            self.stopped = True

    def run(self):
        for f in self.files:
            fname = f
            self.process_file(fname)
            self.sig_file.emit(fname)	#signal transmission
        self.stop()
        self.finished.emit()		#signal transmission

    def retrieve(self):
    	''' root_Get a file with the ext extension from dir'''
        self.files = []
        for rd, _, fl in os.walk(self.root_dir):
            for f in fl:
                _, fext = os.path.splitext(f)
                if fext == self.ext:
                    self.files.append(os.path.join(rd, f))
        self.length = len(self.files)

    def process_file(self, path):
        '''Do nothing for the time being'''
        cnt = 0
        if os.path.exists(path):
            cnt += 1
        else:
            cnt = 0

    def print(self):
        for f in self.files:
            print(f)


def main(args):
    root_dir = '.'
    ext = '.py'
    if len(args) == 3:
        root_dir = args[1]
        ext = args[2]
    fileList = FileList()
    fileList.setup(root_dir, ext)
    fileList.print()

if __name__ == '__main__':
    main(sys.argv)

If you refer to this FileList class and press the button, guimain.py that scans the specified folder and displays the process will be as follows.

guimain.py


import sys
import os
from PyQt5.QtWidgets import (QWidget, QApplication, QPushButton, QLineEdit,
                             QHBoxLayout, QVBoxLayout, QTextEdit, QProgressBar,
                             QFileDialog)
from PyQt5.QtCore import pyqtSlot, Qt
from filelist import FileList


class MainWidget(QWidget):
    dirname = ''
    step = 0

    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.initUI()
        self.fileList = FileList()
        self.fileList.sig_file.connect(self.update_status)
        self.fileList.finished.connect(self.finish_process)

    def initUI(self):
        self.resize(480, 360)

        self.txtFolder = QLineEdit()
        self.txtFolder.setReadOnly(True)
        self.btnFolder = QPushButton('reference...')
        self.btnFolder.clicked.connect(self.show_folder_dialog)
        hb1 = QHBoxLayout()
        hb1.addWidget(self.txtFolder)
        hb1.addWidget(self.btnFolder)

        self.btnExec = QPushButton('Run')
        self.btnExec.clicked.connect(self.exec_process)
        self.btnExec.setEnabled(False)
        self.btnExec.setVisible(True)

        self.btnExit = QPushButton('End')
        self.btnExit.setVisible(False)	#Invalidation
        self.btnExit.setEnabled(False)	#Do not show
        self.btnExit.clicked.connect(self.close)

        hb2 = QHBoxLayout()
        hb2.addWidget(self.btnExec)
        hb2.addWidget(self.btnExit) 	#Added an invisible button in the initial state

        self.txtLog = QTextEdit()
        self.txtLog.setReadOnly(True)

        self.pbar = QProgressBar()
        self.pbar.setTextVisible(False)

        layout = QVBoxLayout()
        layout.addLayout(hb1)
        layout.addLayout(hb2)
        layout.addWidget(self.txtLog)
        layout.addWidget(self.pbar)
        self.setLayout(layout)

        self.setWindowTitle('PyQt5 Sample')

    def show_folder_dialog(self):
        ''' open dialog and set to foldername '''
        dirname = QFileDialog.getExistingDirectory(self,
                                                   'open folder',
                                                   os.path.expanduser('.'),
                                                   QFileDialog.ShowDirsOnly)
        if dirname:
            self.dirname = dirname.replace('/', os.sep) #Convert directory delimiters according to the OS
            self.txtFolder.setText(self.dirname)
            self.btnExec.setEnabled(True)
            self.step = 0

    def print_log(self, logstr):
        self.txtLog.append(logstr)

    @pyqtSlot()
    def exec_process(self):
        if os.path.exists(self.dirname):
            try:
                QApplication.setOverrideCursor(Qt.WaitCursor)
                self.fileList.setup(self.dirname, '.py')
                maxCnt = self.fileList.length
                self.pbar.setValue(0)
                self.pbar.setMinimum(0)
                self.pbar.setMaximum(maxCnt)
                self.fileList.start()
            except Exception as e:
                self.print_log(str(e))
            finally:
                QApplication.restoreOverrideCursor()
        else:
            self.print_log('{0} is not exists'.format(self.dirname))

    @pyqtSlot(str)
    def update_status(self, filename):
        self.txtLog.append(filename)
        self.step += 1
        self.pbar.setValue(self.step)	#progress Bar

    @pyqtSlot()
    def finish_process(self):
        self.fileList.wait()
        #Hide the run button
        self.btnExec.setEnabled(False)
        self.btnExec.setVisible(False)
        #Show the end button
        self.btnExit.setEnabled(True)
        self.btnExit.setVisible(True)

def main(args):
    app = QApplication(args)
    dialog = MainWidget()
    dialog.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main(sys.argv)

Plenty of rapid acceleration in the second half

It's getting annoying, so if you write it like the above, it will work for the time being.

In FileList.run (), by doing `self.sig_file.emit (fname)` one by one, a signal is sent, and by receiving it with guimain.update_status (), the progress bar is displayed. You can proceed one by one.


Summary

I wrote a GUI app in Python. The key is to inherit QThread.

Today's code

Recommended Posts

I made a GUI application with Python + PyQt5
I made a fortune with Python.
I made a daemon with Python
I made a character counter with Python
I made a simple book application with python + Flask ~ Introduction ~
I made a Hex map with Python
I made a roguelike game with Python
I made a simple blackjack with Python
I made a configuration file with Python
I made a WEB application with Django
I made a neuron simulator with Python
I made a competitive programming glossary with Python
I made a weather forecast bot-like with Python.
I made a Twitter fujoshi blocker with Python ①
[Python] I made a Youtube Downloader with Tkinter.
I made a bin picking game with Python
I made a Mattermost bot with Python (+ Flask)
I made blackjack with python!
I made a python text
I made blackjack with Python.
[GUI with Python] PyQt5 -Preparation-
I made wordcloud with Python.
[GUI with Python] PyQt5 -Paint-
I tried to discriminate a 6-digit number with a number discrimination application made with python
I made a Twitter BOT with GAE (python) (with a reference)
I made a Christmas tree lighting game with Python
I made a net news notification app with Python
I made a Python3 environment on Ubuntu with direnv.
I made a LINE BOT with Python and Heroku
[GUI with Python] PyQt5 -Widget II-
Let's make a GUI with python.
I played with PyQt5 and Python3
[GUI with Python] PyQt5 -Custom Widget-
I made a simple typing game with tkinter in Python
I made a package to filter time series with python
I made a puzzle game (like) with Tkinter in Python
I made a payroll program in Python!
I drew a heatmap with seaborn [Python]
I tried a functional language with Python
[Python] A quick web application with Bottle!
What I did with a Python array
I made a life game with Numpy
Run a Python web application with Docker
I made a stamp generator with GAN
After studying Python3, I made a Slackbot
I made a simple circuit with Python (AND, OR, NOR, etc.)
I made a package that can compare morphological analyzers with Python
I made a Nyanko tweet form with Python, Flask and Heroku
I made a lot of files for RDP connection with Python
[Python] I made an image viewer with a simple sorting function.
I made a shuffle that can be reset (reverted) with Python
I tried to make a 2channel post notification application with Python
I tried to make a todo application using bottle with python
I made a poker game server chat-holdem using websocket with python
I made a segment tree with python, so I will introduce it
I made a stamp substitute bot with line
Run a Python script from a C # GUI application
I made a python dictionary file for Neocomplete
〇✕ I made a game
I made a tool to automatically browse multiple sites with Selenium (Python)
I made a web application in Python that converts Markdown to HTML