Search code examples
pythonwxpythonwxwidgets

Add images to rightmost column of ListCtrl in wx Python


TLDR

Is it possible to add an image to the second/rightmost column of a wx.ListCtrl in Python? This is what I am looking for:

enter image description here

Background

I have a wx.ListCtrl and image list as follows:

self.health_grid = wx.ListCtrl(self, style=wx.LC_REPORT| wx.LC_ALIGN_TOP)
self.health_grid.InsertColumn(0, heading="Organ")
self.health_grid.InsertColumn(1, heading="Status")
self.health_icons = wx.ImageList(16, 16, False, 0)
self.unhealthy = wx.Bitmap(self.getFilePath("gui" + os.sep + "images" + os.sep + "unhealthy.png"))
self.health_icons.Add(self.unhealthy)
self.healthy = wx.Bitmap(self.getFilePath("gui" + os.sep + "images" + os.sep + "healthy.png"))
self.health_icons.Add(self.healthy)
self.unknown = wx.Bitmap(self.getFilePath("gui" + os.sep + "images" + os.sep + "unknown.png"))
self.health_icons.Add(self.unknown)
self.health_grid.SetImageList(self.health_icons, wx.IMAGE_LIST_SMALL)

Attempt 1

When I add my items in the following way:

self.health_grid.InsertImageStringItem(self.health_grid.GetItemCount(), "Heart", 0)
self.health_grid.InsertImageStringItem(self.health_grid.GetItemCount(), "Brain", 0)
self.health_grid.InsertImageStringItem(self.health_grid.GetItemCount(), "Lungs", 1)
self.health_grid.InsertImageStringItem(self.health_grid.GetItemCount(), "Liver", 2)

It produces the following output:

enter image description here

This is to be expected; there is nowhere to explicitly say I want my images to be put into column 1.

Attempt 2

When I try to add using the append method and setting the bitmap as the second column item as follows:

self.health_grid.Append(["Heart", self.unhealthy])

It produces this output:

enter image description here

This is slightly better; at least the reference has been added to the second column but it still isn't what I want.

Attempt 2.5

I made a modification in an attempt to make the bitmap display:

self.static_unhealthy = wx.StaticBitmap(self, -1, self.unhealthy)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.sizer.Add(self.static_unhealthy, 0)
self.health_grid.Append(["Heart", self.sizer])

It produces this:

enter image description here

Another wrong version.

Attempt 3

I tried to explicitly assign the image to the second column and the text to the first in the following code:

item = wx.ListItem()
item.SetId(wx.NewId())
item.SetColumn(0)
item.SetText("Heart")
item.SetColumn(1)
item.SetImage(0)
self.health_grid.InsertItem(item)

However, this produced this:

enter image description here

Which I suppose is the closest to what I want out of them all while still failing.

Finally

Is it possible to produce what I want? Have I missed something obvious?


Solution

  • The functionality I was looking for was not possible under Windows as far as I could find so I went with a different approach as follows.

    Used a wx.grid.Grid instead of wx.ListCtrl and implemented an overridden version of wx.grid.PyGridCellRenderer as shown in code excerpt below:

    import wx.grid as grid
    import os
    import wx
    
    class healthStatus(wx.Frame):
        organs = ["heart", "lungs", "brain", "liver"]
        health_icons = []
        (row, progress) = (0, 0)
    
        def __init__(self, parent, id, title):
        # initialise the frame container
        wx.Frame.__init__(self, parent, id, title)
        # main gui sizer, shall be the sizer for the window
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
    
        self.health_grid = grid.Grid(self)
        self.health_grid.CreateGrid(len(self.organs),2)
        self.health_grid.SetColLabelValue(0, "Organ")
        self.health_grid.SetColLabelValue(1, "Status")
        self.health_grid.SetRowLabelSize(0)
    
        try:
            self.health_icons.append(self.getFilePath("images" + os.sep + "unknown.png"))
            self.health_icons.append(self.getFilePath("images" + os.sep + "unhealthy.png"))
            self.health_icons.append(self.getFilePath("images" + os.sep + "healthy.png"))
        except Exception as e:
            wx.MessageBox("Cannot load icons. \n Please ensure the images directory has not been moved\n\n"
                          + e.message, "Cannot Load Icons", wx.ICON_ERROR)
        index = 0
        for organ in self.organs:
            self.addItem(index, organ)
            index += 1
        self.main_sizer.Add(self.health_grid, 0, wx.ALL, 5)
    
        self.testing_button = wx.Button(self, wx.ID_ANY, "Testing")
        self.testing_button.Bind(wx.EVT_BUTTON, self.onProgress)
        self.main_sizer.Add(self.testing_button, 0, wx.ALL, 5)
    
        self.SetSizer(self.main_sizer)
        self.Fit()
    
        def addItem(self, index, organ):
        self.health_grid.SetCellValue(index, 0, organ)
        self.health_grid.SetCellRenderer(index, 1, BitmapRenderer(self.health_icons[0]))
    
    
        def updateProgress(self, index, progress):
        self.health_grid.SetCellRenderer(index, 1, BitmapRenderer(self.health_icons[progress]))
        self.health_grid.Refresh()
    
        def getFilePath(self, directory):
        curr = os.getcwd() + os.sep
        parent = "listctrlproblem" + os.sep
        parent_offset = len(parent)
    
        if curr.index(parent) + parent_offset != len(curr + os.sep):
            curr = curr[:curr.index(parent) + parent_offset]
            print curr
        return curr + os.sep + directory
    
        def onProgress(self, evt):        
        if self.health_grid.GetNumberRows() > 1:
            if self.progress + 1 < len(self.health_icons):
                self.progress += 1
            elif self.row < self.health_grid.GetNumberRows() + 1:
                self.progress = 0
                self.row +=1
    
        self.updateProgress(self.row, self.progress)
    
    
    class BitmapRenderer(wx.grid.PyGridCellRenderer):
        def __init__(self, image):
        self.image = image
        wx.grid.PyGridCellRenderer.__init__(self)
    
        def Draw(self, grid, attr, dc, rect, row, col, is_selected):
        bmp = wx.Bitmap(self.image)
        dc.DrawBitmap(bmp, rect.X, rect.Y)
    
        def Clone(self):
        return self.__class__()
    
    
    app = wx.App(False)
    frame = healthStatus(None, -1, "Organ Status")
    frame.Show(1)
    app.MainLoop()
    

    This creates the following output on Linux:

    enter image description here enter image description here

    And the following output on Windows:

    enter image description here enter image description here