This question was deleted, but I updated the code to an MRE. I have run it on my terminal and it does not have any compilation/runtime errors, but behaves as I explain below. Since the moderators have not responded to my original request to reopen my question after I have corrected it, I have deleted the old question and am placing this new one here.
My signals update the progress value, but the progress bar itself never appears. Is there an error in my code?
(To recreate, please place the code for each file listed below in the project structure shown below. You will only need to install PyQt5
. I am on Windows 10 and using a Python 3.8 virtual environment with poetry. The virtual environment and poetry are optional)
Main
# main.py
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from app.controller.controller import Controller
from app.model.model import Model
from app.view.view import View
class MainApp:
def __init__(self) -> None:
self.controller = Controller()
self.model: Model = self.controller.model
self.view: View = self.controller.view
def show(self) -> None:
self.view.showMaximized()
if __name__ == "__main__":
app: QApplication = QApplication([])
app.setStyle("fusion")
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
root: MainApp = MainApp()
root.show()
app.exec_()
View
# view.py
from typing import Any, Optional
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal
class ProgressDialog(QtWidgets.QDialog):
def __init__(
self,
parent_: Optional[QtWidgets.QWidget] = None,
title: Optional[str] = None,
):
super().__init__(parent_)
self._title = title
self.pbar = QtWidgets.QProgressBar(self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.pbar)
self.setLayout(layout)
self.resize(500, 50)
def on_start(self):
self.setModal(True)
self.show()
def on_finish(self):
self.hide()
self.setModal(False)
self.pbar.reset()
self.title = None
def on_update(self, value: int):
self.pbar.setValue(value)
print(self.pbar.value()) # For debugging...
@property
def title(self):
return self._title
@title.setter
def title(self, title_):
self._title = title_
self.setWindowTitle(title_)
class View(QtWidgets.QMainWindow):
def __init__(
self, controller, parent_: QtWidgets.QWidget = None, *args: Any, **kwargs: Any
) -> None:
super().__init__(parent_, *args, **kwargs)
self.controller: Controller = controller
self.setWindowTitle("App")
self.container = QtWidgets.QFrame()
self.container_layout = QtWidgets.QVBoxLayout()
self.container.setLayout(self.container_layout)
self.setCentralWidget(self.container)
# Create and position widgets
self.open_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DirOpenIcon)
self.open_action = QtWidgets.QAction(self.open_icon, "&Open file...", self)
self.open_action.triggered.connect(self.controller.on_press_open_button)
self.toolbar = QtWidgets.QToolBar("Main ToolBar")
self.toolbar.setIconSize(QtCore.QSize(16, 16))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.open_action)
self.file_dialog = self._create_open_file_dialog()
self.progress_dialog = ProgressDialog(self)
def _create_open_file_dialog(self) -> QtWidgets.QFileDialog:
file_dialog = QtWidgets.QFileDialog(self)
filters = [
"Excel Documents (*.xlsx)",
]
file_dialog.setWindowTitle("Open File...")
file_dialog.setNameFilters(filters)
file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
return file_dialog
Model
# model.py
import time
from typing import Any
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QObject, pyqtSignal
class Model(QObject):
start_task: pyqtSignal = pyqtSignal()
finish_task: pyqtSignal = pyqtSignal()
update_task: pyqtSignal = pyqtSignal(int)
def __init__(
self,
controller,
*args: Any,
**kwargs: Any,
) -> None:
super().__init__()
self.controller = controller
def open_file(self, files: str) -> None:
self.start_task.emit()
for ndx, file_ in enumerate(files):
print(file_) # In truth, here, I'm actually performing processing
time.sleep(1) # Only here for simulating a long-running task
self.update_task.emit(int((ndx + 1) / len(files) * 100))
self.finish_task.emit()
Controller
# controller.py
from typing import Any
from app.model.model import Model
from app.view.view import View
from PyQt5 import QtCore, QtGui, QtWidgets
class Controller:
def __init__(
self,
*args: Any,
**kwargs: Any,
) -> None:
self.model = Model(controller=self, *args, **kwargs)
self.view = View(controller=self, *args, **kwargs)
def on_press_open_button(self) -> None:
if self.view.file_dialog.exec_() == QtWidgets.QDialog.Accepted:
file_names = self.view.file_dialog.selectedFiles()
self.view.progress_dialog.title = "Opening files..."
self.thread = QtCore.QThread()
self.model.moveToThread(self.thread)
self.thread.started.connect(lambda: self.model.open_file(file_names))
self.thread.finished.connect(self.thread.deleteLater)
self.model.start_task.connect(self.view.progress_dialog.on_start)
self.model.update_task.connect(
lambda value: self.view.progress_dialog.on_update(value)
)
self.model.finish_task.connect(self.view.progress_dialog.on_finish)
self.model.finish_task.connect(self.thread.quit)
self.model.finish_task.connect(self.model.deleteLater)
self.model.finish_task.connect(self.thread.deleteLater)
self.thread.start()
When I run the above in a folder of 6 files, it's not running through things too fast (I'm actually performing processing which takes a total of about 5 seconds). It completes successfully and my terminal outputs:
16
33
50
66
83
100
but my ProgressDialog
window is just this for the whole process:
If I add self.progress_dialog.show()
at the end of __init__()
in View
(snipped for brevity)
# view.py
# Snip...
class View(QtWidgets.QMainWindow):
def __init__( ... ):
# Snip...
self.progress_dialog.show()
then a progress bar is added:
and upon opening files, the dialog behaves as expected: