Joshua Zou
Joshua Zou

Reputation: 31

how to avoid error "any pushed event handlers must have been removed" while using wx.grid of wxpython

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

Answers (1)

BoarGules
BoarGules

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 DecRefd 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 DecRefd when the cell does and so hangs around at shutdown, which results in the message you are seeing.

Upvotes: 1

Related Questions