Reputation: 31
Can anyone help me to avoid "any pushed event handlers must have been removed". when i runing Grid_MegaExample.py of the wxpython4.0.2 example ,normal it's ok . but when exit the app ,or when i click the booleditor change the bool column value,get the error code like this.
wx._core.wxAssertionError: C++ assertion "GetEventHandler() == this"
failed at ..\..\src\common\wincmn.cpp(478) in wxWindowBase::~wxWindowBase():
any pushed event handlers must have been removed
I konw the error maybe occur in SetEditor() SetColAttr() of the wx.grid ,and have check the google message, but i cann't correction it... Any reply is greatly appreciated.
import wx
import wx.grid as Grid
import images
import glob
import os
import sys
#---------------------------------------------------------------------------
class MegaTable(Grid.GridTableBase):
"""
A custom wx.Grid Table using user supplied data
"""
def __init__(self, data, colnames, plugins):
"""data is a list of the form
[(rowname, dictionary),
dictionary.get(colname, None) returns the data for column
colname
"""
# The base class must be initialized *first*
Grid.GridTableBase.__init__(self)
self.data = data
self.colnames = colnames
self.plugins = plugins or {}
# XXX
# we need to store the row length and column length to
# see if the table has changed size
self._rows = self.GetNumberRows()
self._cols = self.GetNumberCols()
def GetNumberCols(self):
return len(self.colnames)
def GetNumberRows(self):
return len(self.data)
def GetColLabelValue(self, col):
return self.colnames[col]
def GetRowLabelValue(self, row):
return "row %03d" % int(self.data[row][0])
def GetValue(self, row, col):
return str(self.data[row][1].get(self.GetColLabelValue(col), ""))
def GetRawValue(self, row, col):
return self.data[row][1].get(self.GetColLabelValue(col), "")
def SetValue(self, row, col, value):
self.data[row][1][self.GetColLabelValue(col)] = value
def ResetView(self, grid):
"""
(Grid) -> Reset the grid view. Call this to
update the grid if rows and columns have been added or deleted
"""
grid.BeginBatch()
for current, new, delmsg, addmsg in [
(self._rows, self.GetNumberRows(), Grid.GRIDTABLE_NOTIFY_ROWS_DELETED, Grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
(self._cols, self.GetNumberCols(), Grid.GRIDTABLE_NOTIFY_COLS_DELETED, Grid.GRIDTABLE_NOTIFY_COLS_APPENDED),
]:
if new < current:
msg = Grid.GridTableMessage(self,delmsg,new,current-new)
grid.ProcessTableMessage(msg)
elif new > current:
msg = Grid.GridTableMessage(self,addmsg,new-current)
grid.ProcessTableMessage(msg)
self.UpdateValues(grid)
grid.EndBatch()
self._rows = self.GetNumberRows()
self._cols = self.GetNumberCols()
# update the column rendering plugins
self._updateColAttrs(grid)
# update the scrollbars and the displayed part of the grid
grid.AdjustScrollbars()
grid.ForceRefresh()
def UpdateValues(self, grid):
"""Update all displayed values"""
# This sends an event to the grid table to update all of the values
msg = Grid.GridTableMessage(self, Grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
grid.ProcessTableMessage(msg)
def _updateColAttrs(self, grid):
"""
wx.Grid -> update the column attributes to add the
appropriate renderer given the column name. (renderers
are stored in the self.plugins dictionary)
Otherwise default to the default renderer.
"""
col = 0
for colname in self.colnames:
attr = Grid.GridCellAttr()
if colname in self.plugins:
renderer = self.plugins[colname](self)
if renderer.colSize:
grid.SetColSize(col, renderer.colSize)
if renderer.rowSize:
grid.SetDefaultRowSize(renderer.rowSize)
attr.SetReadOnly(True)
attr.SetRenderer(renderer)
grid.SetColAttr(col, attr)
col += 1
attr = Grid.GridCellAttr()
attr.SetEditor(Grid.GridCellBoolEditor())
attr.SetRenderer(Grid.GridCellBoolRenderer())
grid.SetColAttr(5,attr)
grid.SetColSize(5,20)
# --------------------------------------------------------------------
# Sample wx.Grid renderers
class MegaImageRenderer(Grid.GridCellRenderer):
def __init__(self, table):
"""
Image Renderer Test. This just places an image in a cell
based on the row index. There are N choices and the
choice is made by choice[row%N]
"""
Grid.GridCellRenderer.__init__(self)
self.table = table
'''
self._choices = [images.Smiles.GetBitmap,
images.Mondrian.GetBitmap,
images.WXPdemo.GetBitmap,
]
'''
self.colSize = None
self.rowSize = None
def Draw(self, grid, attr, dc, rect, row, col, isSelected):
choice = self.table.GetRawValue(row, col)
bmp = wx.Bitmap( 1, 1 ) # Create a bitmap container object. The size values are dummies.
if isinstance(choice,str) and os.path.isfile(choice):
bmp.LoadFile( choice, wx.BITMAP_TYPE_ANY ) # Load it with a file image.
image = wx.MemoryDC()
image.SelectObject(bmp)
# clear the background
dc.SetBackgroundMode(wx.SOLID)
if isSelected:
dc.SetBrush(wx.Brush(wx.BLUE, wx.BRUSHSTYLE_SOLID))
dc.SetPen(wx.Pen(wx.BLUE, 1, wx.PENSTYLE_SOLID))
else:
dc.SetBrush(wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID))
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.PENSTYLE_SOLID))
dc.DrawRectangle(rect)
# copy the image but only to the size of the grid cell
width, height = bmp.GetWidth(), bmp.GetHeight()
if width > rect.width-2:
width = rect.width-2
if height > rect.height-2:
height = rect.height-2
dc.Blit(rect.x+1, rect.y+1, width, height,
image,
0, 0, wx.COPY, True)
class MegaFontRenderer(Grid.GridCellRenderer):
def __init__(self, table, color="blue", font="ARIAL", fontsize=8):
"""Render data in the specified color and font and fontsize"""
Grid.GridCellRenderer.__init__(self)
self.table = table
self.color = color
self.font = wx.Font(fontsize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, font)
self.selectedBrush = wx.Brush("blue", wx.BRUSHSTYLE_SOLID)
self.normalBrush = wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID)
self.colSize = None
self.rowSize = 50
def Draw(self, grid, attr, dc, rect, row, col, isSelected):
# Here we draw text in a grid cell using various fonts
# and colors. We have to set the clipping region on
# the grid's DC, otherwise the text will spill over
# to the next cell
dc.SetClippingRegion(rect)
# clear the background
dc.SetBackgroundMode(wx.SOLID)
if isSelected:
dc.SetBrush(wx.Brush(wx.BLUE, wx.BRUSHSTYLE_SOLID))
dc.SetPen(wx.Pen(wx.BLUE, 1, wx.PENSTYLE_SOLID))
else:
dc.SetBrush(wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID))
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.PENSTYLE_SOLID))
dc.DrawRectangle(rect)
text = self.table.GetValue(row, col)
dc.SetBackgroundMode(wx.SOLID)
# change the text background based on whether the grid is selected
# or not
if isSelected:
dc.SetBrush(self.selectedBrush)
dc.SetTextBackground("blue")
else:
dc.SetBrush(self.normalBrush)
dc.SetTextBackground("white")
dc.SetTextForeground(self.color)
dc.SetFont(self.font)
dc.DrawText(text, rect.x+1, rect.y+1)
# Okay, now for the advanced class :)
# Let's add three dots "..."
# to indicate that that there is more text to be read
# when the text is larger than the grid cell
width, height = dc.GetTextExtent(text)
if width > rect.width-2:
width, height = dc.GetTextExtent("...")
x = rect.x+1 + rect.width-2 - width
dc.DrawRectangle(x, rect.y+1, width+1, height)
dc.DrawText("...", x, rect.y+1)
dc.DestroyClippingRegion()
# --------------------------------------------------------------------
# Sample Grid using a specialized table and renderers that can
# be plugged in based on column names
class MegaGrid(Grid.Grid):
def __init__(self, parent, data, colnames, plugins=None):
"""parent, data, colnames, plugins=None
Initialize a grid using the data defined in data and colnames
(see MegaTable for a description of the data format)
plugins is a dictionary of columnName -> column renderers.
"""
# The base class must be initialized *first*
Grid.Grid.__init__(self, parent, -1)
self._table = MegaTable(data, colnames, plugins)
self.SetTable(self._table)
self._plugins = plugins
def Reset(self):
"""reset the view based on the data in the table. Call
this when rows are added or destroyed"""
self._table.ResetView(self)
class MegaFontRendererFactory:
def __init__(self, color, font, fontsize):
"""
(color, font, fontsize) -> set of a factory to generate
renderers when called.
func = MegaFontRenderFactory(color, font, fontsize)
renderer = func(table)
"""
self.color = color
self.font = font
self.fontsize = fontsize
def __call__(self, table):
return MegaFontRenderer(table, self.color, self.font, self.fontsize)
#---------------------------------------------------------------------------
class TestFrame(wx.Frame):
def __init__(self, parent, plugins={"This":MegaFontRendererFactory("red", "ARIAL", 20),
"A":MegaImageRenderer,
"Test":MegaFontRendererFactory("orange", "TIMES", 24),
}):
wx.Frame.__init__(self, parent, -1,"Test Frame", size=(640,480))
self.panel = wx.Panel(self)
self.data = []
self.colnames = ["Row", "This", "Is", "A", "Test","Add"]
self.plugins = plugins
self.grid = MegaGrid(self.panel, self.data, self.colnames, self.plugins)
self.btnInit = wx.Button(self.panel, wx.ID_ANY, u"&Init Row")
self.btnAdd = wx.Button(self.panel, wx.ID_ANY, u"&Add Row")
self.btnQuit = wx.Button(self.panel, wx.ID_CANCEL, u"&Exit")
sizer_all = wx.BoxSizer(wx.HORIZONTAL)
sizer_bottom = wx.BoxSizer(wx.VERTICAL)
sizer_all.Add(self.grid, proportion=3,flag= wx.ALL|wx.EXPAND,border = 5)
sizer_all.Add(sizer_bottom, proportion=0,flag= wx.ALL|wx.EXPAND,border = 5)
sizer_bottom.Add(self.btnInit, proportion =0, flag= wx.ALIGN_RIGHT)
sizer_bottom.Add(self.btnAdd, proportion =0, flag= wx.ALIGN_RIGHT)
sizer_bottom.Add(self.btnQuit, proportion =0, flag = wx.ALIGN_RIGHT)
self.panel.SetSizer(sizer_all)
self.Layout()
self.grid.Reset()
self.Bind(wx.EVT_BUTTON, self.OnBtnInit, self.btnInit)
self.Bind(wx.EVT_BUTTON, self.OnBtnAdd, self.btnAdd)
self.Bind(wx.EVT_BUTTON, self.OnClose, self.btnQuit)
def OnBtnInit(self, event):
self.data = []
for row in range(100):
d = {}
for name in ["This", "Test", "Is"]:
d[name] = random.random()
d["Row"] = '%03d' %len(self.data)
# XXX
# the "A" column can only be between one and 4
d["A"] = random.choice(range(4))
d["Add"]='1'
self.data.append((str(row), d))
#self._table = MegaTable(data, colnames, plugins)
self.grid._table.data = self.data
self.grid.Reset()
def OnBtnAdd(self, event):
rows = self.grid.GetSelectedRows()
if len(rows)<=0 :
row = 0
else:
row = rows[0]
entry = {}
for name in ["This", "Test", "Is"]:
entry[name] = random.random()
entry["Row"] = row
# XXX
# the "A" column can only be between one and 4
entry["A"] = random.choice(range(4))
entry["Add"]='1'
self.data.insert(row, [row, entry])
#self.grid.SetTable(MegaTable(self.data, self.colnames, plugins))
self.grid._table.data = self.data
self.grid.Reset()
def OnClose(self, event):
dlg = wx.MessageDialog(self, "Are you sure exit?", "Question", wx.YES_NO | wx.ICON_WARNING |wx.CENTRE |wx.STAY_ON_TOP)
dlg.SetIcon(self.GetIcon())
if dlg.ShowModal() == wx.ID_YES:
dlg.Destroy()
self.Destroy()
else:
dlg.Destroy()
event.StopPropagation()
class GridDemoApp(wx.App):
def OnInit(self):
self.win = TestFrame(None)
self.win.Show(True)
self.win.Maximize(True)
return True
import random
if __name__ == '__main__':
app = GridDemoApp(0)
app.MainLoop()
Upvotes: 2
Views: 2157
Reputation: 16942
This is a late (and incomplete) answer, but this authoritative analysis by Robin Dunn himself may help.
I can't pretend to fully understand the analysis, and there is way too much of your code for me to find parallels, but I had a similar message on shutdown and the problem came down to having cell attributes (instances of wx.grid.GridCellAttr
), and cell editors, where the editors were part of the attributes, and some of these attributes were cell attributes overriding column attributes.
If you have a column attribute, then any overrides to the column attribute in a particular cell must be a .Clone
because in that circumstance the attribute gets DecRef
d when the cell does. But the editor associated with such a cell attribute (a "pushed event handler") must be shared among all the clones and not itself be a clone. If it is a clone then, for apparently good reasons, it doesn't get DecRef
d when the cell does and so hangs around at shutdown, which results in the message you are seeing.
Upvotes: 1