wxPython FloatCanvas绑定函数延迟与程序崩溃问题求助
解决FloatCanvas绑定事件延迟与崩溃问题
我排查了你的代码,发现问题主要出在频繁的事件绑定累积和重复触发画布重绘上,下面是具体分析和修复方案:
问题根源
- 重复调用
ZoomToBB:你在每条线绘制完成后都调用wx.CallAfter(self.Canvas.ZoomToBB),这会在UI线程中堆积大量重绘请求,随着线条数量增加,UI阻塞越来越明显,表现为Bind操作延迟飙升。 - 逐个对象绑定事件:给每个生成的文本标签单独绑定
EVT_FC_LEFT_DOWN事件,会导致事件处理链越来越长,不仅降低响应效率,还可能引发内存泄漏,最终导致程序崩溃。
修复方案
1. 改用全局事件委托
不再给每个文本对象单独绑定事件,而是给Canvas绑定一次全局点击事件,通过HitTest方法判断点击的对象类型和内容,大幅减少事件绑定的数量。
2. 批量执行画布重绘
将ZoomToBB调用移到整个绘制循环结束后,避免重复触发重绘,减轻UI线程负担。
3. 优化对象管理
将创建的标签对象存储到实例变量中,方便后续识别和管理,避免内存泄漏。
修改后的完整代码
import time import string import wx from wx.lib.floatcanvas import NavCanvas, FloatCanvas import wx.lib.colourdb class InputForm(wx.Frame): '''set up the form and draw axis''' def __init__(self): super(InputForm, self).__init__(None, wx.ID_ANY, title='Plot Lines', size=(1300, 830)) # 存储各类标签对象,方便后续识别 self.origin_label = None self.line_labels = [] self.node_labels = [] # create the form level sizer Main_Sizer = wx.BoxSizer(wx.HORIZONTAL) # add the sizer for the left side widgets sizerL = wx.BoxSizer(wx.VERTICAL) # add the grid and then set it to the left panel btnsizer = wx.BoxSizer(wx.HORIZONTAL) drw = wx.Button(self, -1, "Draw\nLines") btnsizer.Add(drw, 0, wx.ALL|wx.ALIGN_CENTER, 5) # bind the button events to handlers self.Bind(wx.EVT_BUTTON, self.OnDraw, drw) sizerL.Add((10, 20)) sizerL.Add(btnsizer, 1, wx.ALIGN_CENTER) # add the draw panel rght = NavCanvas.NavCanvas(self, ProjectionFun=None, Debug=0, BackgroundColor="LIGHT GREY", ) self.Canvas = rght.Canvas # 绑定Canvas全局点击事件,替代逐个对象绑定 self.Canvas.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnCanvasLeftDown) self.InitCanvas() Main_Sizer.Add(sizerL, 0, wx.EXPAND) Main_Sizer.Add((10, 10)) Main_Sizer.Add(rght, 1, wx.EXPAND) self.SetSizer(Main_Sizer) def InitCanvas(self): # add the x & y axis self.Canvas.AddLine([(0, 0), (0, 5)], LineWidth=2, LineColor='Yellow') self.Canvas.AddLine([(0, 0), (5, 0)], LineWidth=2, LineColor='Green') # 存储原点标签对象 self.origin_label = self.Canvas.AddScaledTextBox('origin', (0, 0), Color='blue', Size=.5, PadSize=0, Width=None, LineColor=None, Family=wx.MODERN, Position='tr', Alignment='bottom', InForeground=True) self.origin_label.Name = 'Origin' wx.CallAfter(self.Canvas.ZoomToBB) def OnDraw(self, evt): pts1 = (0, 0) x = [i for i in range(5, 30, 2)] y = x[::-1] pts2 = [(x[i], y[i]) for i in range(0, len(x))] alph = string.ascii_uppercase LnLbls = [alph[i] for i in range(0, len(x))] New_EndPt = True n = 0 for pt in pts2: points = [] points.append(pts1) points.append(pt) LnLbl = LnLbls[n] New_EndPt = True n += 1 self.DrawLine(points, LnLbl, New_EndPt) # 所有线条绘制完成后,统一执行一次ZoomToBB wx.CallAfter(self.Canvas.ZoomToBB) def DrawLine(self, points, LnLbl, New_EndPt): '''Draws the line object as specified in the VarifyData() function''' # label the end point of the line in lower case if New_EndPt is True: new_end = self.Canvas.AddScaledTextBox(LnLbl.lower(), tuple(points[1]), Color='black', Size=.5, PadSize=.2, Width=None, LineColor=None, Family=wx.MODERN, Position='cc', Alignment='bottom', InForeground=True) new_end.Name = LnLbl.lower() self.node_labels.append(new_end) # define the new line self.Canvas.AddLine(points, LineWidth=2, LineColor='red') # add the new line to the list of lines self.Canvas.AddPoint(tuple(points[1]), 'black', 8) # locate the center of the new line for the label location lncntr = ((int(points[0][0])+int(points[1][0]))//2, (int(points[0][1])+int(points[1][1]))//2) # place the new line label new_line = self.Canvas.AddScaledTextBox(LnLbl, lncntr, Color='red', Size=.5, PadSize=None, Width=None, LineColor=None, Family=wx.MODERN, Position='tc', Alignment='bottom', InForeground=True) new_line.Name = LnLbl self.line_labels.append(new_line) # 移除原有的逐个Bind操作,改用全局事件处理 tic = time.perf_counter() toc = time.perf_counter() print(f'time to execute DrawLine (no Bind) = {toc-tic:0.6f}') def OnCanvasLeftDown(self, evt): # 获取点击位置对应的对象(取最上层的前景对象) hit_objects = self.Canvas.HitTest(evt.GetPosition()) if not hit_objects: return clicked_obj = hit_objects[-1] # 获取对象的标识信息 lbl = getattr(clicked_obj, 'Name', '') if not lbl and hasattr(clicked_obj, 'Text'): lbl = clicked_obj.Text # 根据标识执行对应逻辑 if lbl in ['Origin', 'origin']: print('you have selected the origin') elif 65 <= ord(lbl[0]) <= 90: print('you have selected line ', lbl) elif 97 <= ord(lbl[0]) <= 122: print('you have selected node ', lbl) # Run the program if __name__ == "__main__": app = wx.App(False) frame = InputForm() frame.Center() frame.Show() app.MainLoop()
修复效果
- 消除Bind操作延迟:全局事件委托替代逐个对象绑定,彻底避免了事件绑定的累积开销。
- 解决程序崩溃问题:减少事件绑定数量+优化重绘时机,大幅降低UI线程负担,避免内存泄漏。
- 提升绘制效率:批量执行
ZoomToBB,减少不必要的画布重绘次数。
内容的提问来源于stack exchange,提问作者user9133067
相关产品推荐
相关产品推荐

