在使用wxpy的wx.grid时,如何避免错误“任何推送的事件处理程序必须已被删除”

问题描述 投票:0回答:1

任何人都可以帮我避免“任何被推送的事件处理程序必须被删除”。当我运行wxpython4.0.2示例的Grid_MegaExample.py时,正常就可以了。但是当退出应用程序时,或者当我点击booleditor更改bool列值时,请获取这样的错误代码。

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

我知道错误可能发生在wx.grid的SetEditor()SetColAttr(),并检查谷歌消息,但我不能纠正它...任何回复非常感谢。

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()
wxpython
1个回答
0
投票

这是一个迟到(并且不完整)的答案,但罗宾·邓恩本人的这个authoritative analysis可能会有所帮助。

我不能假装完全理解分析,并且有太多的代码让我找到相似之处,但我在关机时有类似的消息,问题归结为具有单元属性(wx.grid.GridCellAttr的实例),以及单元格编辑器,其中编辑器是属性的一部分,其中一些属性是覆盖列属性的单元属性。

如果你有一个列属性,那么特定单元格中列属性的任何覆盖都必须是.Clone,因为在那种情况下,当单元格时,属性会获得DecRefd。但是与这样的单元属性相关联的编辑器(“推送事件处理程序”)必须在所有克隆之间共享,而不是它本身就是克隆。如果它是一个克隆然后,由于显而易见的原因,它不会得到DecRefd当单元格,因此在关机时挂起,这导致你看到的消息。

© www.soinside.com 2019 - 2024. All rights reserved.