I used self.close to close the widget that is running. Whole code is as follows:
class timeTracker(QWidget):
'''time tracker main application v_0.8'''
def __init__(self, parent=None):
super(timeTracker, self).__init__(parent)
self.grid = QGridLayout()
self.setLayout(self.grid)
while not self.connectivity():
print(self.connectivity())
ret = QMessageBox.warning(self, "Not connected to network. Please check the connection", "", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret == QMessageBox.No:
self.close()
qApp.quit()
break
qApp.quit()
# self.time_widget = timeline.timeWidget()
# self.grid.addWidget(self.time_widget, 0, 0)
def connectivity(self):
try:
urllib.request.urlopen("http://www.google.com", timeout=1)
return True
except urllib.error.URLError as error:
return False
if __name__ == "__main__":
application = QApplication(sys.argv)
main_widget = timeTracker()
main_widget.show()
main_widget.move(10, 10)
sys.exit(application.exec_())
What I expect from this code is, as long as the connectivity function is returning True, user will be repeatedly asked to do the certain action if they click yes. If they click no, I would like to terminate the whole widget. But when I click no, then it just goes through the lines after the self.close. What can I do to actually terminate the program?
To close an application forcefully as in this case we can use sys.exit()
if ret == QMessageBox.No:
sys.exit(0)
Related
I am trying to make the user not switch to the next TAB where "Form 2" is located until they fill in Form 1.
I tried the "currentChange" event but it doesn't work the way I want as it shows the alert when it was already changed from TAB.
Is there a way to leave the current TAB fixed until the task is complete?
I attach the code and an image
import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.setGeometry(0, 0, 800, 500)
self.setLayout(QtWidgets.QVBoxLayout())
#flag to not show the alert when starting the program
self.flag = True
#changes to True when the form is completed
self.form_completed = False
#WIDGET TAB 1
self.widget_form1 = QtWidgets.QWidget()
self.widget_form1.setLayout(QtWidgets.QVBoxLayout())
self.widget_form1.layout().setAlignment(Qt.AlignHCenter)
label_form1 = QtWidgets.QLabel("FORM 1")
self.widget_form1.layout().addWidget(label_form1)
#WIDGET TAB 2
self.widget_form2 = QtWidgets.QWidget()
self.widget_form2.setLayout(QtWidgets.QVBoxLayout())
self.widget_form2.layout().setAlignment(Qt.AlignHCenter)
label_form2 = QtWidgets.QLabel("FORM 2")
self.widget_form2.layout().addWidget(label_form2)
#QTABWIDGET
self.tab_widget = QtWidgets.QTabWidget()
self.tab_widget.currentChanged.connect(self.changed)
self.tab_widget.addTab(self.widget_form1,"Form 1")
self.tab_widget.addTab(self.widget_form2, "Form 2")
self.layout().addWidget(self.tab_widget)
def changed(self,index):
if self.flag:
self.flag = False
return
if not self.form_completed:
QtWidgets.QMessageBox.about(self, "Warning", "You must complete the form")
return
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
The currentChanged signal is emitted when the index is already changed (the verb is in past tense: Changed), so if you want to prevent the change, you have to detect any user attempt to switch tab.
In order to do so, you must check both mouse and keyboard events:
left mouse clicks on the tab bar;
Ctrl+Tab and Ctrl+Shift+Tab on the tab widget;
Since you have to control that behavior from the main window, the only solution is to install an event filter on both the tab widget and its tabBar(), then if the action would change the index but the form is not completed, you must return True so that the event won't be handled by the widget.
Please consider that the following assumes that the tab that has to be kept active is the current (the first added tab, or the one set using setCurrentIndex()).
class MyWidget(QtWidgets.QWidget):
def __init__(self):
# ...
self.tab_widget.installEventFilter(self)
self.tab_widget.tabBar().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == event.KeyPress and \
event.key() in (Qt.Key_Left, Qt.Key_Right):
return not self.form_completed
elif source == self.tab_widget.tabBar() and \
event.type() == event.MouseButtonPress and \
event.button() == Qt.LeftButton:
tab = self.tab_widget.tabBar().tabAt(event.pos())
if tab >= 0 and tab != self.tab_widget.currentIndex():
return self.isInvalid()
elif source == self.tab_widget and \
event.type() == event.KeyPress and \
event.key() in (Qt.Key_Tab, Qt.Key_Backtab) and \
event.modifiers() & Qt.ControlModifier:
return self.isInvalid()
return super().eventFilter(source, event)
def isInvalid(self):
if not self.form_completed:
QTimer.singleShot(0, lambda: QtWidgets.QMessageBox.about(
self, "Warning", "You must complete the form"))
return True
return False
Note that I showed the message box using a QTimer in order to properly return the event filter immediately.
Also consider that it's good practice to connect signals at the end of an object creation and configuration, and this is much more important for signals that notify property changes: you should not connect it before setting the property that could trigger it.
Since an empty QTabWidget has a -1 index, as soon as you add the first tab the index is changed to 0, thus triggering the signal. Just move the currentChanged signal connection after adding the tabs, and you can get rid of the self.flag check.
I am trying to troubleshoot/de-bug an issue I came across with my application using wxPython 4.0.7.
I re-wrote my entire program that was functioning with Python 2.7 and wxPython 2.8 on a Windows 7 32-bit system to now work with 64 bit Python 3.7.4 and wxPython 4.0.7 on a 64 bit Windows 10 system.
The problem I am having is that my program requires that it iterate multiple times based on the number of loops specified by the user, and it calls an instance of wx.App() from two different python scripts utilized.
I have read that calling multiple instances of wx.App() is a "no-no" (see creating multiple instances of wx.App)
Clearly this is a problem with this version of wxPython as my application crashes after the first iteration now, when it worked fine before.
Okay, so I understand this now, but I am not certain what the "fix" is for my particular issue.
The basic outline of my application is this:
A "runner.py" script is launched which contains the main wx.frame() gui and the following code is appended to the end of the script:
app = wx.App()
frame = Runner(parent=None, foo=Foo)
frame.Show()
app.MainLoop()
When the user clicks on the "execute" button in the wxPython GUI, I have a progress dialog that initiates using this code:
pd = wx.ProgressDialog(title = "Runner.py", message= "Starting Model", parent=self, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
The runner.py script executes a "for loop" that does a bunch of stuff (actually reads in some inputs from R scripts) and then once it's done, it opens up a second python script ("looping.py") and iterates through a set of processes based on the number of loops the user specifies in the GUI launched from runner.py.
As the user needs to visually see what loop process the model run is going through, I have inside this second "looping.py" script, yet another instance of wx.App() that calls up another wx.ProgressDialog(), And the script looks like this:
#Progress Bar to user to start model
app = wx.App()
pd = wx.ProgressDialog("looping.py", "Setup Iteration", parent=None, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)
My specific question is this: How do I initiate the wx.ProgressDialog() successfully within the "looping.py" script without it crashing my application past the first iteration?
You will probably have to sub-class wx.ProgressDialog, arguably it may be easier to write your own progress bar display.
Something like this, may give you some ideas.
I've included the ability to run multiple threads doing different things, with pause and stop buttons. The main frame has a button to test whether the Gui is still active, whilst running the threads.
Updates from the thread are driven by an event
You may wish to reduce or increase its options.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(400,400))
panel = MyPanel(self)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
self.btn_start = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(180,30), pos=(10,10))
btn_test = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(180,30), pos=(10,50))
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(300,100))
self.btn_start.Bind(wx.EVT_BUTTON, self.Start_Process)
btn_test.Bind(wx.EVT_BUTTON, self.ActiveText)
def Start_Process(self, event):
process1 = ProcessingFrame(title='Threaded Task 1', parent=self, job_no=1)
process2 = ProcessingFrame(title='Threaded Task 2', parent=self, job_no=2)
self.btn_start.Enable(False)
def ActiveText(self,event):
self.text_count += 1
txt = "Gui is still active " + str(self.text_count)+"\n"
self.txt.write(txt)
class ProcessingFrame(wx.Frame):
def __init__(self, title, parent=None,job_no=1):
wx.Frame.__init__(self, parent=parent, title=title, size=(400,400))
panel = wx.Panel(self)
self.parent = parent
self.job_no = job_no
self.btn = wx.Button(panel,label='Stop processing', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.btn_pause = wx.Button(panel,label='Pause processing', size=(200,30), pos=(10,50))
self.btn_pause.Bind(wx.EVT_BUTTON, self.OnPause)
self.progress = wx.Gauge(panel,size=(200,10), pos=(10,90), range=60)
self.process = wx.TextCtrl(panel,size = (200,250), pos=(10,120), style = wx.TE_MULTILINE)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
def OnProgress(self, event):
self.progress.SetValue(event.count)
self.process.write(event.process+"\n")
self.Refresh()
if event.count >= 60:
self.OnExit(None)
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.parent.btn_start.Enable(True)
self.Destroy()
def OnPause(self, event):
if self.mythread.isAlive():
self.mythread.pause() # Pause the thread
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.target = parent_target
self.stopthread = False
self.process = 1 # Testing only - mock process id
self.start() # start the thread
def run(self):
# A selectable test loop that will run for 60 loops then terminate
if self.target.job_no == 1:
self.run1()
else:
self.run2()
def run1(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 10 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Envoking process "+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def run2(self):
curr_loop = 0
while self.stopthread != True:
if self.stopthread == "Pause":
time.sleep(1)
continue
curr_loop += 1
self.process += 100 # Testing only - mock process id
if curr_loop <= 60: # Update progress bar
time.sleep(1.0)
evt = progress_event(count=curr_loop,process="Checking process"+str(self.process))
#Send back current count for the progress bar
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.terminate()
self.terminate()
def terminate(self):
self.stopthread = True
def pause(self):
if self.stopthread == "Pause":
self.stopthread = False
self.target.btn_pause.SetLabel('Pause processing')
else:
self.stopthread = "Pause"
self.target.btn_pause.SetLabel('Continue processing')
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
I am using following code to close Qwidget window automatically after some period of time
class ErrorWindow2(QtGui.QWidget):
def __init__( self ):
QtGui.QWidget.__init__( self, None, QtCore.Qt.WindowStaysOnTopHint)
msgBox = QMessageBox(self)
msgBox.move (500,500)
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText("Test 2")
msgBox.setWindowTitle("ERROR")
msgBox.setStandardButtons(QMessageBox.Ok)
self.errWin2Timer = QtCore.QTimer()
self.errWin2Timer.timeout.connect(self.closeBox)
self.errWin2Timer.setSingleShot(True)
self.errWin2Timer.start(10000)
ret = msgBox.exec_()
if ret == QtGui.QMessageBox.Ok:
return
else:
return
def closeBox(self):
self.close()
def closeEvent(self, event):
logger.debug("Reached Error window 1 close event")
if self.errWin2:
self.errWin2.stop()
self.errWin2.deleteLater()
event.accept()
But the problem is that self.close doesn't work. What is the best possible way to close the window automatically after some period of time?
The problem is that when you put ret = msgBox.exec_() before the constructor finishes executing, the window object has not finished being built, so there is nothing to close, so when the dialog is closed the window that was just opened will be displayed. I finish building. The idea is to finish building the window and then call ret = msgBox.exec_() and for that we will use a QTimer.singleShot().
On the other hand, the closeEvent method is not necessary since I was trying to do it. IMHO is to eliminate the self.errWin2Timer from memory (although it seems that there was a typo since you use errWin2 instead of errWin2Timer) but being son of the window is not necessary since in Qt if the parent dies the children will also die.
from PyQt4 import QtCore,QtGui
class ErrorWindow2(QtGui.QWidget):
def __init__( self ):
super(ErrorWindow2, self).__init__(None, QtCore.Qt.WindowStaysOnTopHint)
self.errWin2Timer = QtCore.QTimer(self,
interval=10*1000,
singleShot=True,
timeout=self.close)
self.errWin2Timer.start()
QtCore.QTimer.singleShot(0, self.showMessageBox)
def showMessageBox(self):
msgBox = QtGui.QMessageBox(self)
msgBox.move (500,500)
msgBox.setIcon(QtGui.QMessageBox.Critical)
msgBox.setText("Test 2")
ret = msgBox.exec_()
if ret == QtGui.QMessageBox.Ok:
print("OK")
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = ErrorWindow2()
w.show()
sys.exit(app.exec_())
I am trying to prevent the user from exiting my QDialog that contains a progress bar and to achieve that I thought of overriding the Qdialog's closeEvent and that is where my dilemma starts. I do not know how to pass some sort of confirmation to my overridden closeEvent that the very long process in my other thread is finished and can now accept a closeEvent.
here's what my what my code structure looks like
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def __init__(self, var1, parent=None):
QThread.__init__(self, parent)
self.sel_proj = var1
def run(self):
#very long process for var1
self.taskFinished.emit()
class progress_win(QDialog):
def closeEvent(self, event):
#if only I can pass a variable here depending on the process
#like if finished == True:
#event.accept()
#else:
#event.ignore()
event.ignore()#for now
class Window(QtGui.QMainWindow, TaskThread):
#my main window, also contains other QDialog
def progbar(self):
self.prog_win = progress_win()
self.prog_win.resize(400, 100)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.prog_win.installEventFilter(self)
self.prog_win.show()
self.myLongTask = TaskThread(var1 = myDataToBeProcessed)
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def onFinished(self):
self.progressBar.setRange(0,1)
self.processResult = self.myLongTask.processedVar1 #getting my result from the long process
self.prog_win.close()
I would like to close the Qdialog when the process is finished but not when the process is ongoing. Can I pass a variable to my overridden event from onFinished function? if not then what would be the best course of action?
The examples I always see is this:
def closeEvent(self, event):
reply = QtGui.QMessageBox.question(self, 'Close',
"Are you Sure? \nAll your Inputs will be lost.", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
which requires user interaction, It would be great if there is a guide or example I could follow on the idea that i want.
Basically I want to ignore the close event of the progbar QDialog from the user and let something like a function handle the closing.
I'm writing a PyQT4 (4.11) program that performs a long slow task which ideally needs a progress bar. The program works almost perfectly if I don't use threads, sub-classing a QWidget which only contains a QProgressBar and a layout. Instantiating this sub-class as form, I can call form.show() to put it on screen, and then my long slow loop can update the progress by calling form.progressbar.setValue(progress). There are two problems with this:
if the user tries to interact with the window they get 'not responding' messages from the windows manager/OS desktop process. This is because events are not being processed.
because events aren't being processed, the user can't cancel the long slow loop by closing the window.
So I tried making the long slow loop run in a separate thread, using a signal to update the progress bar. I overrode the closeEvent of my QWidget so that it can cancel interaction with the hardware device (all wrapped in mutexes so the device communication doesn't get out of sync). Again, this almost works. If I cancel, the application quits. If I leave it to run to completion though, I have to close the window manually (i.e. click the close icon or press alt-f4), even though I'm sending a close signal to the QWidget. As you can see in the code below there are some complications, since the app can't close immediately if cancelled, because it has to wait for some hardware clean up to happen. Here is a minimal version of my code
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)
It turns out the secret is to use a QThread which emits some signals when the thread exits. Either "finished" or "terminated" depending on whether the exit was normal or abnormal. Here's some code
import sys
import os
import time
from PyQt4 import QtCore, QtGui
class Ui_ProgressBarDialog(QtGui.QWidget):
def __init__(self, process, parent = None):
QtGui.QWidget.__init__(self, parent)
self.thread = process
self.setupUi(self)
self.center()
self.thread.start()
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def setupUi(self, ProgressBarDialog):
ProgressBarDialog.setObjectName("ProgressBarDialog")
ProgressBarDialog.resize(400, 33)
self.verticalLayout = QtGui.QVBoxLayout(ProgressBarDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.progressBar = QtGui.QProgressBar(ProgressBarDialog)
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
self.verticalLayout.addWidget(self.progressBar)
self.retranslateUi(ProgressBarDialog)
#Close when the thread finishes (normally)
QtCore.QObject.connect(
self.thread,
QtCore.SIGNAL("finished()"),
self.close
)
#Close when the thread is terminated (exception, cancelled etc)
QtCore.QObject.connect(
self.thread,
QtCore.SIGNAL("terminated()"),
self.close
)
#Let the thread update the progress bar position
QtCore.QObject.connect(
self.thread,
QtCore.SIGNAL("updateProgress"),
self.progressBar.setValue
)
#Repaint when the progress bar value changes
QtCore.QObject.connect(
self.progressBar,
QtCore.SIGNAL("valueChanged(int)"),
ProgressBarDialog.repaint
)
QtCore.QMetaObject.connectSlotsByName(ProgressBarDialog)
def retranslateUi(self, ProgressBarDialog):
ProgressBarDialog.setWindowTitle("Please Wait")
def closeEvent(self, event):
if self.thread.exit_status == self.thread.RUNNING:
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:
if self.thread.exit_status == self.thread.RUNNING:
self.thread.exiting = True
while self.thread.exiting:
time.sleep(0.25)
elif self.thread.exit_status == self.thread.SUCCESS:
self.thread.exit_status = self.thread.CANCELLED
else:
if self.thread.exit_status == self.thread.RUNNING:
event.ignore()
app = QtGui.QApplication(sys.argv)
filename = str(QtGui.QFileDialog.getSaveFileName(
None,
"Save as",
os.getcwd(),
"Data files: (*.dat)"
))
class DeviceDataStreamHandler(QtCore.QThread):
RUNNING = 1
SUCCESS = 0
FAILURE = -1
CANCELLED = -2
class CancelledError(Exception):
pass
def __init__(self, parent = None, **kwargs):
QtCore.QThread.__init__(self, parent)
self.exiting = False
self.exit_status = DeviceDataStreamHandler.RUNNING
self.device = device
self.filename = filename
def run(self):
progress = 0
try:
temp_binary_file = open(self.filename, "wb")
#getDataStream is an interator returing strings
for data in self.device.getDataStream():
temp_binary_file.write(data)
progress += 1
self.emit(QtCore.SIGNAL("updateProgress"), progress)
if self.exiting:
raise DeviceDataStreamHandler.CancelledError()
self.exit_status = DeviceDataStreamHandler.SUCCESS
except DeviceDataStreamHandler.CancelledError:
self.exit_status = DeviceDataStreamHandler.CANCELLED
self.device.cancelDownload()
except Exception as E:
self.exit_status = DeviceDataStreamHandler.FAILURE
self.error_details = str(E)
finally:
temp_binary_file.close()
if self.exit_status == DeviceDataStreamHandler.CANCELLED:
os.unlink(filename)
self.exiting = False
class HardwareDeviceObject(object):
def __init__(self):
#initialises comms with a hardware device
self.event_count = 0
self.capture = False
def startCapture(self):
self.capture = True
def cancelDownload():
self.capture = False
def read(self):
#returns a string sent from the device
time.sleep(1)
self.event_count += 1
return self.event_count
def getDataStream(self):
class DataStreamIterator(object):
def __init__(self, ds, max_capture_count = 100):
self.ds = ds
self.capture_count = 0
self.max_capture_count = max_capture_count
def __iter__(self):
return self
def next(self):
#return string received from device
if self.ds.capture and self.capture_count < self.max_capture_count:
self.capture_count += 1
return self.ds.read()
else:
raise StopIteration()
self.startCapture()
return DataStreamIterator(self)
capture = DeviceDataStreamHandler(device = HardwareDeviceObject(), filename = filename)
form = ProgressBarDialog.Ui_ProgressBarDialog(capture)
form.setWindowTitle("Dumping sessions")
form.progressBar.setMaximum(100) #expect 100 outputs from the device
form.progressBar.setValue(0)
form.show()
app.exec_()
if capture.exit_status == DeviceDataStreamHandler.SUCCESS:
QtGui.QMessageBox.information(None, 'Success', "Save to disk successful", QtGui.QMessageBox.Ok)
elif capture.exit_status == DeviceDataStreamHandler.FAILURE:
QtGui.QMessageBox.critical(None, 'Error interacting with device', "{}".format(capture.error_details), QtGui.QMessageBox.Ok)