The ProgressDialog
class allows passing the option wx.PD_CAN_ABORT
which
adds a "Cancel" button to the dialog. I need to rebind the event bound to this
button, to make it Destroy()
the dialog instead of just "making the next call
to Update() [to] return False" as the class's documentation describes.
class PortScanProgressDialog(object):
"""Dialog showing progress of the port scan."""
def __init__(self):
self.dialog = wx.ProgressDialog(
"COM Port Scan",
PORT_SCAN_DLG_MSG,
MAX_COM_PORT,
style=wx.PD_CAN_ABORT | wx.PD_AUTO_HIDE)
def get_available_ports(self):
"""Get list of connectable COM ports.
:return: List of ports that e.g. exist, are not already open,
that we have permission to open, etc.
:rtype: list of str
"""
com_list = []
keep_going = True
progress_count = 0
for port_num in range(MIN_COM_PORT, MAX_COM_PORT + 1):
if not keep_going:
break
port_str = "COM{}".format(port_num)
try:
# Check if the port is connectable by attempting to open
# it.
t_port = Win32Serial(
port_str, COMPATIBLE_BAUDRATE,
bytesize=SerialThread.BYTESIZE,
parity=SerialThread.PARITY,
stopbits=SerialThread.STOPBITS, timeout=4)
t_port.close()
com_list.append(port_str)
finally:
progress_count += 1
# This returns a tuple with 2 values, the first of which
# indicates if progress should continue or stop--as in
# the case of all ports having been scanned or the
# "Cancel" button being pressed.
keep_going = self.dialog.Update(progress_count, msg)[0]
return com_list
This class is used elsewhere in this fashion:
# Scan for available ports.
port_scan_dlg = PortScanProgressDialog()
ports = port_scan_dlg.get_available_ports()
port_scan_dlg.dialog.Destroy()
When an unhandled exception occurs in get_available_ports()
the progress
dialog will stay open (which is expected behaviour), but the problem is that
when I hit "Cancel" the button is greyed and the window is not closed (clicking
"X" also fails to close the window).
I'm trying to re-bind the "Cancel" button to a method that Destroy()
s the
dialog. How can I do this?
I'm aware of this workaround, but I think it's cleaner to use ProgressDialog
and modify it to my needs.
The Cancel button is not exposed directly in this widget. You can get to it using the dialog's GetChildren
method. Here's one way to do it:
import wx
class TestPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
b = wx.Button(self, -1, "Create and Show a ProgressDialog", (50,50))
self.Bind(wx.EVT_BUTTON, self.OnButton, b)
def OnButton(self, evt):
max = 80
dlg = wx.ProgressDialog("Progress dialog example",
"An informative message",
maximum = max,
parent=self,
style = wx.PD_CAN_ABORT
| wx.PD_APP_MODAL
| wx.PD_ELAPSED_TIME
#| wx.PD_ESTIMATED_TIME
| wx.PD_REMAINING_TIME
)
for child in dlg.GetChildren():
if isinstance(child, wx.Button):
cancel_function = lambda evt, parent=dlg: self.onClose(evt, parent)
child.Bind(wx.EVT_BUTTON, cancel_function)
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(250) # simulate some time-consuming thing...
if count >= max / 2:
(keepGoing, skip) = dlg.Update(count, "Half-time!")
else:
(keepGoing, skip) = dlg.Update(count)
dlg.Destroy()
#----------------------------------------------------------------------
def onClose(self, event, dialog):
""""""
print "Closing dialog!"
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='Progress')
panel = TestPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
This is based on the wxPython demo example of this particular dialog. Depending on your platform, you will get the native widget if it exists, otherwise you'll get wx.GenericProgressDialog
. I suspect the native widget won't allow you to access the Cancel button at all, but I'm not sure.