Tuesday, March 19, 2024

Building a Step-by-Step Progress Window with PyQt6

 Have you ever needed to guide users through a series of steps within an application? Creating a step-by-step progress window can be an effective way to accomplish this. In this tutorial, we'll learn how to build a simple yet powerful step-by-step progress window using PyQt6, a popular Python framework for creating desktop applications with graphical user interfaces.


Introduction to the Program

We'll create a PyQt6 application with the following features:

  1. A main progress bar indicating overall progress.
  2. A secondary progress bar that appears during specific steps.
  3. Buttons for navigation (Back, Next, Skip, Cancel).
  4. Dynamic labels indicating the current step and instructions.

Setting Up the Environment

First, let's ensure you have PyQt6 installed. If not, you can install it using pip:

pip install PyQt6

Now, let's dive into the code!

Step 1: Importing Necessary Libraries

We start by importing the required libraries for our application. These include PyQt6 widgets, QPushButton, QProgressBar, QLabel, and QTimer.

Step 2: Creating the MainWindow Class

We define a class MainWindow that inherits from QWidget, which is the base class for all user interface objects in PyQt6.

Step 3: Initializing the Window and Widgets

We set up the window title, geometry, and various widgets such as progress bars, buttons, and labels within the __init__ method.

Step 4: Implementing Navigation Functions

We define functions for navigation: next_step, previous_step, skip_step, and cancel. These functions handle the logic for advancing to the next step, going back to the previous step, skipping a step, and canceling the process, respectively.

Step 5: Updating Step Labels

We create a function update_step_label to update the step labels dynamically based on the current step.

Step 6: Updating Task Labels

Another function update_task_label updates the task labels, which display information about the current task being performed.

Step 7: Updating Secondary Progress

We use a QTimer to update the secondary progress bar dynamically during specific steps. This progress bar appears and disappears as needed.

Step 8: Executing the Application

Finally, we instantiate the QApplication, create an instance of the MainWindow class, and show the window using window.show(). We then run the application's event loop using app.exec().

Conclusion

In this tutorial, we've created a step-by-step progress window using PyQt6. This application can be useful for guiding users through a series of tasks or operations, providing clear instructions and visual feedback along the way. You can further customize this application by adding more steps, integrating additional features, or enhancing the user interface to fit your specific requirements.

Feel free to explore and extend the functionality of this application to suit your needs. Happy coding!

Here is the complete program listing:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QLabel
from PyQt6.QtCore import QTimer


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Progress Window")
        self.setGeometry(100, 100, 800, 480)

        self.progress_bar_main = QProgressBar(self)
        self.progress_bar_main.setGeometry(100, 80, 600, 40)
        self.progress_bar_main.setMinimum(0)
        self.progress_bar_main.setMaximum(100)
        self.progress_bar_main.setValue(0)

        self.label_task = QLabel("", self)
        self.label_task.setGeometry(100, 330, 600, 20)

        self.progress_bar_secondary = QProgressBar(self)
        self.progress_bar_secondary.setGeometry(100, 360, 600, 20)
        self.progress_bar_secondary.setMinimum(0)
        self.progress_bar_secondary.setMaximum(5)
        self.progress_bar_secondary.setValue(0)
        self.progress_bar_secondary.setVisible(False)

        self.back_button = QPushButton("Back", self)
        self.back_button.setGeometry(350, 420, 100, 30)

        self.next_button = QPushButton("Next", self)
        self.next_button.setGeometry(460, 420, 100, 30)
        self.next_button.setEnabled(True)

        self.skip_button = QPushButton("Skip", self)
        self.skip_button.setGeometry(570, 420, 100, 30)
        self.skip_button.setEnabled(False)

        self.cancel_button = QPushButton("Cancel", self)
        self.cancel_button.setGeometry(680, 420, 100, 30)

        self.next_button.clicked.connect(self.next_step)
        self.back_button.clicked.connect(self.previous_step)
        self.skip_button.clicked.connect(self.skip_step)
        self.cancel_button.clicked.connect(self.cancel)

        self.step = 0

        self.label_step = QLabel("Step 1: Select file", self)
        self.label_step.setGeometry(100, 130, 600, 40)
        self.label_step.setStyleSheet("font-size: 24pt; font-family: Helvetica;")

        self.label_instruction = QLabel("Instructions: Select a file to proceed", self)
        self.label_instruction.setGeometry(100, 170, 600, 20)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_secondary_progress)

    def next_step(self):
        self.step += 1
        if self.step > 5:
            self.close()  # Finish the program when all steps are completed
        else:
            self.progress_bar_main.setValue((self.step + 1) * 20)

            if self.step == 5:
                self.next_button.setText("Finish")
                self.skip_button.setEnabled(False)
            else:
                self.next_button.setEnabled(True)
                self.back_button.setEnabled(True)
                self.skip_button.setEnabled(True)

            #self.update_step_label()
            self.update_task_label()
            self.label_task.setVisible(True)
            self.progress_bar_secondary.setVisible(True)
            self.progress_bar_secondary.setValue(0)
            self.next_button.setEnabled(False)
            self.back_button.setEnabled(False)
            self.timer.start(1000)

    def update_secondary_progress(self):
        value = self.progress_bar_secondary.value()
        if value < 5:
            self.progress_bar_secondary.setValue(value + 1)
        else:
            self.timer.stop()
            self.next_button.setEnabled(True)
            self.back_button.setEnabled(True)
            self.progress_bar_secondary.setVisible(False)
            self.label_task.setVisible(False)
            
            self.update_step_label()  # Update labels after the second progress bar finishes

    def previous_step(self):
        self.step -= 1
        if self.step < 0:
            self.step = 0
        self.progress_bar_main.setValue((self.step + 1) * 20)

        if self.step == 0:
            self.back_button.setEnabled(False)
        else:
            self.back_button.setEnabled(True)
            self.next_button.setEnabled(True)
            self.skip_button.setEnabled(True)

        self.update_step_label()

    def skip_step(self):
        self.step += 1
        if self.step > 5:
            self.step = 5
        self.progress_bar_main.setValue((self.step + 1) * 20)

        if self.step == 5:
            self.next_button.setText("Finish")
            self.skip_button.setEnabled(False)
        else:
            self.next_button.setEnabled(True)
            self.back_button.setEnabled(True)
            self.skip_button.setEnabled(True)

        self.update_step_label()

    def cancel(self):
        self.close()

    def update_step_label(self):
        self.instructions = [
            "Select file",
            "Check compatibility",
            "Confirm",
            "Apply filters",
            "Save data","Finish!"
        ]
        self.label_step.setText(f"Step {self.step + 1}: {self.instructions[self.step]}")
        self.label_instruction.setText(f"Instructions: {self.instructions[self.step]}")

    def update_task_label(self):
        self.tasks = [
            "Checking file...",
            "Checking compatibility...",
            "Confirming...",
            "Applying filters...",
            "Saving data..."
        ]
        if self.step < 5:
            self.label_task.setText(f"{self.tasks[self.step]}")
        


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

Wednesday, March 6, 2024

Upgrading my Simple File List App in Python

 I conceived the notion to enhance the Simple File List App in Python, recognizing its potential utility for other developers. The enhancement introduces a significant improvement: rather than solely presenting a list of files within the selected folder, the application now showcases the contents of each file. This transformation imbues the interface with the familiar appearance and functionality of a file explorer application, augmenting its usability and appeal.

The File List App is a PyQt6-based Python application aimed at providing users with an intuitive interface for browsing and inspecting files within a designated directory. It offers an upgraded user experience compared to traditional file explorers by displaying file contents directly within the application.



Components:

  • QMainWindow: This serves as the main window of the application, providing a framework for organizing various widgets and controls.
  • QTreeView: This widget displays the hierarchical structure of files and directories in a tree-like format, akin to a file explorer. It allows users to navigate through directories and select files of interest.
  • QTextEdit: This widget acts as a text editor, enabling the display of the contents of selected files. It supports read-only mode to prevent accidental modifications to files.
  • QPushButton: This button triggers the action of opening a folder, allowing users to choose the directory they wish to explore.
  • QSplitter: This widget divides the main window into resizable sections, facilitating the adjustment of the size of the tree view and text editor based on user preferences.


Functions:

  • initUI(): This function initializes the user interface by setting up the main window, creating and configuring widgets, and organizing them within layouts.
  • openFolder(): When the "Open Folder" button is clicked, this function prompts the user to select a directory using a file dialog. Upon selection, it updates the window title and calls displayFolderContent() to populate the tree view with the contents of the selected directory.
  • displayFolderContent(folder_path): This function populates the tree view with the contents of the specified folder. It sets up a file system model to represent the directory structure and connects the clicked signal of the tree view to displayFileContent() for displaying file contents.
  • resizeEvent(event): This event handler is triggered when the window is resized. It dynamically adjusts the sizes of the splitter sections to maintain the desired proportions between the tree view and text editor.
  • displayFileContent(index: QModelIndex): This function is called when a file in the tree view is clicked. It retrieves the path of the selected file, reads its contents using utf-8 encoding, and displays the content in the text editor. If an error occurs during file reading, it displays an error message in the text editor.


Conclusion:

The File List App provides an efficient and user-friendly solution for exploring and inspecting files within directories. By seamlessly integrating file browsing and content viewing functionalities, it streamlines the file management process and enhances productivity. With its intuitive interface and robust functionality, it stands as a testament to the power and versatility of Python and PyQt6 in developing rich desktop applications.

Here is the complete program listing:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QFileDialog, QTreeView, QPushButton, QWidget, QTextEdit, QSplitter
from PyQt6.QtGui import QStandardItemModel, QStandardItem, QFileSystemModel
from PyQt6.QtCore import Qt, QDir, QModelIndex

# Global variable to store the folder path
folder_path = ""

class FileListApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setWindowTitle('File List App')
        self.setGeometry(100, 100, 800, 600)

        # Create widgets
        self.tree_view = QTreeView()
        self.open_button = QPushButton('Open Folder', self)
        self.text_edit = QTextEdit(self)
        self.text_edit.setReadOnly(True)

        # Create a splitter to separate the tree view and the text edit
        self.splitter = QSplitter(Qt.Orientation.Horizontal)
        self.splitter.addWidget(self.tree_view)
        self.splitter.addWidget(self.text_edit)

        # Create layout
        layout = QVBoxLayout()
        layout.addWidget(self.open_button)
        layout.addWidget(self.splitter)

        # Create central widget and set layout
        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

        # Connect the openFolder button to the openFolder slot
        self.open_button.clicked.connect(self.openFolder)

    def openFolder(self):
        global folder_path
        folder_path = QFileDialog.getExistingDirectory(self, 'Open Folder')

        if folder_path:
            self.setWindowTitle(f'File List App - {folder_path}')
            self.displayFolderContent(folder_path)

    def displayFolderContent(self, folder_path):
        # Set up the file system model
        model = QFileSystemModel()
        model.setRootPath(folder_path)
        model.setFilter(QDir.Filter.NoDotAndDotDot | QDir.Filter.AllEntries)

        # Set the model to the tree view
        self.tree_view.setModel(model)
        self.tree_view.setRootIndex(model.index(folder_path))
        self.tree_view.setColumnWidth(0, 250)  # Adjust column width

        # Connect itemClicked signal to displayFileContent
        self.tree_view.clicked.connect(self.displayFileContent)

        # Adjust the sizes of the splitter
        self.splitter.setSizes([self.width() * 0.3, self.width() * 0.7])

    def resizeEvent(self, event):
        super().resizeEvent(event)
        # Update splitter sizes when window is resized
        self.splitter.setSizes([self.width() * 0.3, self.width() * 0.7])

    def displayFileContent(self, index: QModelIndex):
        global folder_path
        # Get the file path from the selected index
        file_path = self.tree_view.model().filePath(index)

        # Read the content of the file using utf-8 encoding
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                content = file.read()
                self.text_edit.setText(content)
        except Exception as e:
            self.text_edit.setText(f"Error reading file: {str(e)}")

def main():
    app = QApplication(sys.argv)
    window = FileListApp()
    window.show()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()