【发布时间】:2021-10-27 22:14:40
【问题描述】:
这个问题已被删除,但我将代码更新为 MRE。我已经在我的终端上运行了它,它没有任何编译/运行时错误,但其行为如下所述。由于版主在我更正后没有回复我重新打开问题的原始请求,因此我删除了旧问题并将新问题放在这里。
我的信号会更新进度值,但进度条本身从未出现。我的代码有错误吗?
(要重新创建,请将下面列出的每个文件的代码放在下面显示的项目结构中。您只需要安装PyQt5。我在 Windows 10 上并使用 Python 3.8 虚拟环境和诗歌。虚拟环境和诗歌是可选的)
主要
# 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.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.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.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()
当我在一个包含 6 个文件的文件夹中运行上述内容时,它的运行速度并没有太快(我实际上是在执行总共需要大约 5 秒的处理)。它成功完成,我的终端输出:
16
33
50
66
83
100
但我的ProgressDialog 窗口在整个过程中只是这个:
如果我在View 中的__init__() 末尾添加self.progress_dialog.show()(为简洁起见)
# view.py
# Snip...
class View(QtWidgets.QMainWindow):
def __init__( ... ):
# Snip...
self.progress_dialog.show()
然后添加一个进度条:
打开文件后,对话框的行为与预期一致:
【问题讨论】:
-
为了以后的参考,尽量让你的代码更容易重现。目录结构不是问题的关键,您甚至可以对所有部分使用单个代码块,而不是要求人们创建四个单独的文件。请记住:人们应该专注于问题,不要因为重新创建问题而分心,如果需要太多操作来执行代码,许多用户(可能会回答你)实际上会感到沮丧,结果他们将完全忽略这个问题;提供一个易于复制的示例应该是您的责任。
-
如果导入是问题(这不太可能,但仍然是一个正当的反对意见),尝试减少代码就会证明这一点。众所周知,创建 MRE 通常可以解决大约 50% 的问题。你是对的,你不必让任何人开心,也不应该在意。但你是在提出问题并寻找答案;确保它获得尽可能多的观众(通过增加答案的可能性)不仅符合您的利益,而且还通过提供他们的知识和经验。
-
同意不同意
-
好吧,当然,我们不必同意,这是一件好事。但是,您仍然有(有趣的)问题,除了您自己提供的答案之外,几乎没有有用的答案。这是一个公共社区,一旦我们同意加入它,我们也必须同意它的(有时是不言而喻的、微妙的甚至是有争议的)规则。我们可能不喜欢他们,这是我们个人和绝对正确的观点。但这是一个公共空间,而不是我们的后院:如果我们发布问题或答案并受到批评,同时知道 那 是一个极有可能的结果,那么大肆宣扬它对我们没有任何帮助方式。
标签: pyqt5 qthread qprogressbar