Right widget for live-viewer of received messages

Hello,
I am looking for advice here. I hope it is right place :wink:

The bus-traffic-analyzer application shall show list of received messages (from CAN bus) in ID oriented way (see first column). This list of messages shall be refreshed/updated every 100 ms. Not with every received message, because it can be quite fast (e.g. 1 ms period between messages).
I have used wx.ListView. See how it looks now:

image

BUT This widget suffers by flickering (doubleBuffering causes even worse behavior on M$ Windows) and visualised data is not separated from widget itself. With every update, lot of things need to be refreshed.

What is the best widget for this task?

  • fast (100 ms update), no flickering
  • data separated from its representation
  • sortable items (only view, real data not necessary)
  • movable columns
  • no data modification through widget, just data visualisation

Take a look at the ListCtrl_virtual sample in the demo. Basically, instead of adding every data item to the ctrl, you have the ctrl ask you for the data it needs for displaying the visible rows. You could add your incoming data items to a list or something as they arrive, and then call the listctrl’s SetItemCount method to let it know how many are available. You also need to implement OnGetItemText which will be called by the list when it needs to fetch the text you want displayed.

Thank you very much! I have tried various widgets and it seems that DataViewCtrl is the best one for this purposes (no flickering, out-of-box sorting, custom renderer, columns reordering, columns can be hidden etc…).

But it brings another issue… The DataViewCtrl has no ListCtrlAutoWidthMixin so the last column does not occupy rest of window when the window is resized. See picture:
example

I can detect wx.EVT_SIZE event but I have no clue how compute and set width of the last column (even when columns are reorderable). Does anybody have idea how to solve ti?

You can iterate through the columns with dvc.GetColumns() and the column objects returned have a GetWidth method. You can get the width of the scrollbar with wx.SystemSettings.GetMetric, then it’s just math.

Thank you! It works fine with wx.EVT_SIZE. To make it perfect it would need also customization of another two events:

  • wx.EVT_HEADER_END_REORDER
  • wx.EVT_HEADER_END_RESIZE

But their are not triggered by DataViewListCtrl class! Is it bug? (e.g. missing event.Skip() at inherited class?) Notice that events

  • wx.EVT_HEADER_BEGIN_REORDER
  • wx.EVT_HEADER_BEGIN_RESIZE
    are triggered as expected.

Yep, it looks like the C++ wxDataViewHeaderWindow in the generic version of the dataview controls is catching those events and not calling Skip.

Thank you Robin for your help and investigation. So it is issue of wxWidgets and shall be reported there. I will try to do it, but I can give them only Python project, not C++ :neutral_face:.

For others, here is example of customized DataViewListrCtrl class with last column auto-width, but is not perfect due to issue above:

class MsgViewer(dv.DataViewListCtrl):
    """ Customized DataViewListCtrl to show CAN messages in CAN-ID oriented way """
    def __init__(self, *args, **kw):
        dv.DataViewListCtrl.__init__(self, *args, **kw)
         
        self.Bind(wx.EVT_HEADER_BEGIN_REORDER, self.eventHeaderBeginReorder)
        self.Bind(wx.EVT_HEADER_END_REORDER, self.eventHeaderEndReorder)
        self.Bind(wx.EVT_HEADER_END_RESIZE, self.eventHeaderEndResize)
        self.Bind(wx.EVT_SIZE, self.eventResized)
    
    def eventHeaderBeginReorder(self, event):
        for col in self.GetColumns():
            col.SetResizeable(True)

    def eventHeaderEndReorder(self, event):
        # Does not work :-/
        event.Skip()

    def eventHeaderEndResize(self, event):
        # Does not work :-/
        event.Skip()
        
    def eventResized(self, event):
        if self.HasScrollbar(wx.VERTICAL):
            vscrollwidth = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
        else:
            vscrollwidth = 0
        
        panelwidth = self.GetClientSize()[0]
        colswidth = 0
        lastCol = -1
        lastPosition = self.GetColumnCount() - 1
        
        for col in self.GetColumns():
            colswidth += col.GetWidth()
            if self.GetColumnPosition(col) == lastPosition:
                lastCol = col
            
        freewidth = panelwidth - vscrollwidth - colswidth
        lastCol.SetWidth(freewidth + lastCol.GetWidth())
        lastCol.SetResizeable(False)
        event.Skip()