Я пишу программу PyQT4 (4.11), которая выполняет длинную медленную задачу, которая идеально нуждается в индикаторе выполнения. Программа работает почти идеально, если я не использую потоки, подклассифицируя QWidget, который содержит только QProgressBar и макет. Создавая этот подкласс таким образом, как form
, я могу позвонить form.show()
, чтобы поместить его на экран, а затем мой длинный медленный цикл может обновить ход, вызвав form.progressbar.setValue(progress)
. Есть две проблемы:PyQT4 QWidget должен получить сигнал «закрыть» дважды перед закрытием
, если пользователь пытается взаимодействовать с окном, они получают «не отвечает» сообщения из процесса рабочего стола оконного менеджера/OS. Это происходит потому, что события не обрабатываются.
поскольку события не обрабатываются, пользователь не может отменить длинный медленный цикл, закрыв окно.
Поэтому я попытался выполнить длинный медленный цикл в отдельном потоке, используя сигнал для обновления индикатора выполнения. Я перепробовал closeEvent моего QWidget, чтобы он мог отменить взаимодействие с аппаратным устройством (все обернуты мьютексами, чтобы связь устройства не выходила из синхронизации). Опять же, это почти работает. Если я отменил, приложение завершает работу. Если я оставлю его для завершения до завершения, я должен закрыть окно вручную (например, щелкнуть значок закрытия или нажать alt-f4), хотя я посылаю сигнал QWidget с близкого расстояния. Как вы можете видеть в приведенном ниже коде, есть некоторые сложности, так как приложение не может сразу закрыть его, если оно отменено, потому что ему нужно дождаться, когда произойдет какое-то аппаратное обеспечение. Вот минимальный вариант моего кода
import sys
import os
import time
from PyQt4 import QtCore, QtGui
class Ui_ProgressBarDialog(QtGui.QWidget):
def __init__(self, on_close=None):
QtGui.QWidget.__init__(self)
self.setupUi(self)
self.center()
#on_close is a function that is called to cancel
#the long slow loop
self.on_close = on_close
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def setupUi(self, ProgressBarDialog):
ProgressBarDialog.setObjectName(_fromUtf8("ProgressBarDialog"))
ProgressBarDialog.resize(400, 33)
self.verticalLayout = QtGui.QVBoxLayout(ProgressBarDialog)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.progressBar = QtGui.QProgressBar(ProgressBarDialog)
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName(_fromUtf8("progressBar"))
self.verticalLayout.addWidget(self.progressBar)
self.retranslateUi(ProgressBarDialog)
#This allows the long slow loop to update the progress bar
QtCore.QObject.connect(
self,
QtCore.SIGNAL("updateProgress"),
self.progressBar.setValue
)
#Catch the close event so we can interrupt the long slow loop
QtCore.QObject.connect(
self,
QtCore.SIGNAL("closeDialog"),
self.closeEvent
)
#Repaint the window when the progress bar's value changes
QtCore.QObject.connect(
self.progressBar,
QtCore.SIGNAL("valueChanged(int)"),
self.repaint
)
QtCore.QMetaObject.connectSlotsByName(ProgressBarDialog)
def retranslateUi(self, ProgressBarDialog):
ProgressBarDialog.setWindowTitle("Please Wait")
def closeEvent(self, event, force=False):
if self.on_close is not None and not force:
self.on_close()
app = QtGui.QApplication(sys.argv)
filename = str(QtGui.QFileDialog.getSaveFileName(
None,
"Save as",
os.getcwd(),
"Data files: (*.dat)"
))
loop_mutex = thread.allocate_lock()
cancelled = False
can_quit = False
result = None
def cancel_download():
global cancelled
if can_quit:
return
if QtGui.QMessageBox.question(
None,
'Cancel Download',
"Are you sure you want to cancel the download?",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
with loop_mutex:
selected_device.cancelDownload()
cancelled = True
while not can_quit:
time.sleep(0.25)
form = ProgressBarDialog.Ui_ProgressBarDialog(cancel_download)
form.setWindowTitle("Please Wait")
form.progressBar.setMaximum(1000)
form.progressBar.setValue(0)
form.show()
def long_slow_loop(mutex, filename):
global can_quit, result
progress = 0
temp_binary_file = open(filename, "wb")
#The iterator below does the actual work of interacting with a
#hardware device, so I'm locking around the "for" line. I must
#not fetch more data from the device while a cancel command is
#in progress, and vice versa
mutex.acquire()
for data in ComplexIteratorThatInteractsWithHardwareDevice():
mutex.release()
temp_binary_file.write(datablock)
progress += 1
form.emit(QtCore.SIGNAL("updateProgress"), progress)
mutex.acquire()
if cancelled:
break
mutex.release()
result = not cancelled
temp_binary_file.close()
if cancelled:
os.unlink(filename)
#having set can_quit to True the following emission SHOULD
#cause the app to exit by closing the last top level window
can_quit = True
form.emit(QtCore.SIGNAL("closeDialog"), QtGui.QCloseEvent(), True)
thread.start_new_thread(do_dump, (loop_mutex, filename))
app.exec_()
if result == True:
QtGui.QMessageBox.information(None, 'Success', "Save to disk successful", QtGui.QMessageBox.Ok)
Сигналы не совпадают с событиями. Вы не можете отправить событие с помощью сигнала. Но в любом случае, если вы хотите закрыть окно, почему бы просто не называть 'form.close()'? – ekhumoro