问题背景:
今天遇到了一个奇特的问题,使用wx设计gui界面,调用self.Destroy()关闭窗口,却在某些时候无法立刻关闭。
总结规律发现,窗口激活的时候发起self.Destroy(),窗口可以正常关闭;窗口未激活的时候发起self.Destroy(),窗口就不会关闭,但是鼠标路过的时候就会立刻关闭。
找到原因:
奇葩问题检查了好久,终于找到原因,原理其实很简单:
对于gui库,无论是tkinter,还是wxpython,或者pyqt,单线程的Python都是通过事件循环响应窗口操作。移动窗口是事件、拉伸窗口是事件、点击按钮是事件。有事件的时候窗口就根据需要更新,没有事件的时候窗口就不变。
而我外部发起的self.Destroy(),并不是窗口事件,所以窗口不会更新,界面不会销毁。而鼠标路过的时候触发了鼠标进入事件,窗口更新,就把自己给关闭了。
另外窗口处于激活状态时,点击任务栏图标,会首先产生窗口失去焦点事件,窗口也会刷新,也能把自己关闭。
测试例子:
运行后显示窗口,并会在系统托盘显示图标。鼠标左键单击和右键单击测试调用两种方法尝试关闭窗口(点击后需重启程序)。
鼠标左键调用self.Destroy()方法关闭窗口,当窗口处于激活状态时可以关闭窗口,当窗口处于非激活状态时窗口不会关闭,但是鼠标路过(触发鼠标进入事件)时就会关闭。
鼠标右键调用wx.CallAfter(self.Destroy)关闭窗口,使用发起新事件的方法关闭窗口,事件处理完毕后退出MainLoop事件循环,窗口销毁。
但是如果在self.Destroy()销毁窗口前,调用self.SetFocus()激活窗口,也可以将窗口关闭,但是有些绕弯路,可根需要据情况使用。
示例代码:
import wx
import wx.adv
class MyTaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, parent):
wx.adv.TaskBarIcon.__init__(self, wx.adv.TBI_DOCK) # wx.adv.TBI_CUSTOM_STATUSITEM
self.icon = wx.Icon(wx.Bitmap(wx.Image(IMG_ICON)))
self.SetIcon(self.icon)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_UP, parent.OnClose1)
self.Bind(wx.adv.EVT_TASKBAR_RIGHT_UP, parent.OnClose2)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, style=wx.STAY_ON_TOP) # | wx.BORDER_NONE
self.icon = MyTaskBarIcon(self)
self.Center()
self.Show()
def OnClose1(self, evt):
print('OnClose1')
self.icon.Destroy() # only `RemoveIcon` will still run in mainloop.
print('icon.Destroy')
# self.SetFocus()
self.Destroy()
print('Destroy')
def OnClose2(self, evt):
print('OnClose2')
self.icon.Destroy() # only `RemoveIcon` will still run in mainloop.
print('icon.Destroy')
wx.CallAfter(self.Destroy)
print('CallAfter Destroy')
if __name__ == '__main__':
IMG_ICON = 'icon.ico'
app = wx.App()
frm = MyFrame()
app.MainLoop()
print('end')