Wxpython Button Inside Grid

Viewed 1179

I am trying to add a button to a grid cell.
I am using latest demo 4.0.7 and python 3.7 (32bit)
I fixed an old example found here: https://groups.google.com/forum/#!topic/wxpython-users/HhtKCxPVX_s The problem is the button is not working correctly and pressing it causing the whole grid to disappear.

class MyCustomRenderer2(gridlib.GridCellRenderer):
def __init__(self):
    gridlib.GridCellRenderer.__init__(self)
    self.down = False
    self.click_handled = False

def Draw(self, grid, attr, dc, rect, row, col, isSelected):
    """This is called when the widget is Refreshed"""
    print ('drawing button')
    dc.Clear()
    if self.down:
        state = wx.CONTROL_PRESSED | wx.CONTROL_SELECTED
    else:
        state = 0

    #if not self.IsEnabled():
    #    state = wx.CONTROL_DISABLED
    #pt = self.ScreenToClient(wx.GetMousePosition())
    #if self.GetClientRect().Contains(pt):
    #    state |= wx.CONTROL_CURRENT

    wx.RendererNative.Get().DrawPushButton(grid, dc, rect, state)
    #extra logic required since a button gets drawn at various times that could be while the mouse button is held down
    if self.down and not self.click_handled:
        self.click_handled = True
        self.HandleClick()

def HandleClick(self):
    print ('clicked')

def GetBestSize(self, grid, attr, dc, row, col):
    text = grid.GetCellValue(row, col)
    dc.SetFont(attr.GetFont())
    w, h = dc.GetTextExtent(text)
    return wx.Size(w, h)


def Clone(self):
    return MyCustomRenderer2()
2 Answers

This script launches a frame with a grid inside. When you click on the button the message "active/inactive" is displayed in the shell. The point is that classes communicate by using 2 switches variables (which are 2 inverted dictionnaries).

import  wx
import  wx.grid as  gridlib

#---------------------------------------------------------------------------
class myFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, size=(400, 300))

        # ...
        self.gridBox = wx.BoxSizer(wx.VERTICAL) 
        # ...
        self.mainGrid = showGrid(self, numRows=5, numCols=5)
        # ...
        self.gridBox.Add(self.mainGrid)        
        self.SetSizerAndFit(self.gridBox)


class showGrid(gridlib.Grid):
    def __init__(self, parent, numRows, numCols):
        """Constructeur"""
        gridlib.Grid.__init__(self, parent, size=parent.GetSize())

        self.CreateGrid(numRows, numCols)
        self.SetRowLabelSize(50)
        # Display content
        self.displayContent()
        # ...
        self.handleEvents()        

    def displayContent(self):
        self.rd = myRenderer(self)
        # Button coordinates
        self.rend_row = 2
        self.rend_col = 1
        # ...
        self.SetCellValue(1, 0, 'Click on the Button')
        # Set Button in cell
        self.SetCellRenderer(self.rend_row, self.rend_col, self.rd)
         # Prevent button cell selection
        self.SetReadOnly(self.rend_row, self.rend_col, True)

    def handleEvents(self):
        self.Bind(gridlib.EVT_GRID_SELECT_CELL, self.onCellSelected)

    def onCellSelected(self, event):
        # # Button inverted switch var
        switch_inv = {False: wx.CONTROL_NONE, True: wx.CONTROL_PRESSED}
        # To avoid bug verify if it's possible to enable cell control
        # Typical bug : "wx._core.wxAssertionError: C++ assertion "CanEnableCellControl()" failed at /home/... 
        # in EnableCellEditControl(): can't enable editing for this cell!"
        if self.CanEnableCellControl():
            self.EnableCellEditControl()
        # Invert button status : CONTROL_NONE (inactive) / wx.CONTROL_PRESSED (active)
        self.rd.buttonState = switch_inv[not self.rd.boolstat['boolstat']]
        # Print button status
        print("Button status : {0}".format({0:"inactive", 4:"active"}[self.rd.buttonState]))

class myRenderer(wx.grid.GridCellRenderer):
    def __init__(self, parent):
        wx.grid.GridCellRenderer.__init__(self)

        # Button neutral state
        self.boolstat = {'boolstat': True}
        # Button state when initializing
        self.buttonState = 0

    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
        # Button switch var
        switch = {wx.CONTROL_NONE: False, wx.CONTROL_PRESSED: True}
        # Renderer state
        self.boolstat['boolstat'] =  switch[self.buttonState]
        # ...
        wx.RendererNative.Get().DrawPushButton(grid, dc, rect, self.buttonState)


#---------------------------------------------------------------------------

if __name__ == '__main__':
    app = wx.App()
    frame = myFrame(None)
    frame.Show()
    frame.Centre()
    app.MainLoop()

This script creates a frame and a grid with two rows and five columns. The fifth column is an image that can be used as a button. See the onCellSelected function to respond to the button selection.

import wx
import wx.grid as Grid

class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super(MyFrame, self).__init__(parent, title=title, size=(400, 300))

        self.panel = MyPanel(self)
        self.MyGrid = New_Grid(self, numRows=2, numCols=5)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.MyGrid,1,wx.ALL|wx.EXPAND,0)
        self.SetSizer(mainSizer)


class MyPanel(wx.Panel):
    def __init__(self, parent):
        super(MyPanel, self).__init__(parent)


class New_Grid(Grid.Grid):
    def __init__(self, parent, numRows, numCols):
        Grid.Grid.__init__(self, parent)

        self.CreateGrid(numRows, numCols)
        self.SetRowLabelSize(35)
        self.AutoSizeColumns(True)
        # Display content
        self.displayContent()
        # ...
        self.handleEvents()

    def displayContent(self):
        myList = [
            "Col1",
            "Col2",
            "Col3",
            "Col4",
            "Set"
        ]
        x = 0
        for item in myList:
            self.SetColLabelValue(x, item)
            x += 1
        self.SetColLabelSize(25)
        self.AutoSizeColumns(True)
        self.update_Grid()

    def handleEvents(self):
        self.Bind(Grid.EVT_GRID_SELECT_CELL, self.onCellSelected)

    def onCellSelected(self, event):
        col = event.GetCol()
        if col == 5:
            Selected = event.GetRow()

    def update_Grid(self):
        img = wx.Bitmap("SET.png", wx.BITMAP_TYPE_PNG)
        self.rd = MyImageRenderer(img)
        # Buttons coordinates
        numRows = self.GetNumberRows()
        for y in range(numRows):
            self.rd.rend_row = y
            self.rd.rend_col = 4
            self.SetCellRenderer(self.rd.rend_row, self.rd.rend_col, self.rd)
            self.SetColSize(0, img.GetWidth() + 2)
            self.SetRowSize(0, img.GetHeight() + 3)
            self.SetReadOnly(self.rd.rend_row, self.rd.rend_col, True)

class MyImageRenderer(wx.grid.GridCellRenderer):
    def __init__(self, img):
        wx.grid.GridCellRenderer.__init__(self)
        self.img = img

    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
        image = wx.MemoryDC()
        image.SelectObject(self.img)
        dc.SetBackgroundMode(wx.SOLID)
        if isSelected:
            dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
            dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
        else:
            dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
            dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID))
        dc.DrawRectangle(rect)
        width, height = self.img.GetWidth(), self.img.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)

    def GetBestSize(self, grid, attr, dc, row, col):
        text = grid.GetCellValue(row, col)
        dc.SetFont(attr.GetFont())
        w, h = dc.GetTextExtent(text)
        return wx.Size(w, h)


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(parent=None,title="wxPython Window")
        self.frame.Show()
        return True


app = MyApp()
app.MainLoop()

The above script output is: Table with buttons in the fifth column.

Related