wx.ListCtrl item max text length

Hello everyone,

I’m using a wxListCtrl as a log viewer. The logs typically range from 100k to 1000k lines. Each line is displayed as a single item in a one-column list, with some lines colored based on keywords.

I’ve noticed that long log lines are getting cut off, even though the column width is sufficient. The text simply stops at a certain point, leaving empty space in the column.

Is there a maximum length for text display in wxListCtrl? If so, is it adjustable? Given my use case, is there perhaps another widget that would be more suitable for displaying large logs?

XXX_Testingplayground.py (1.2 KB)
I add an example to run. Im running version (4, 2, 1, ‘’).

When I run your code using wxPython 4.2.2 gtk3 (phoenix) wxWidgets 3.2.6 + Python 3.12.3 + Linux Mint 22, it displays the full text in the ListCtrl.

Just to confirm. This is what i see. But you see the full text? I run it on windows 10 Python 3.12.4.

I’ve the same on Windows, so what about pythons textwrap and when it’s wrapped select and bring up a wx.PopupWindow :rofl:

Is the purpose of the application just to display the log messages for the user to read, or were you intending to provide additional functionality for the user to interact with the messages?

If it is the former, then I would suggest using one of the text controls: wx.TextCtrl, wx.richtext.RichTextCtrl or wx.stc.StyledTextCtrl.

Edit: add example using wx.TextCtrl

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((1920, 200))
        self.SetTitle("No wrap TextCtrl")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl = wx.TextCtrl(self.panel, wx.ID_ANY, "", style=wx.HSCROLL | wx.TE_MULTILINE | wx.TE_READONLY)
        sizer.Add(self.text_ctrl, 1, wx.EXPAND|wx.ALL, 5)
        self.panel.SetSizer(sizer)
        self.Layout()

        default_style = wx.TextAttr("BLACK")
        warning_style = wx.TextAttr("RED")

        long_text = ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "
                     "sed diam nonumy eirmod tempor invidunt ut labore et dolore "
                     "magna aliquyam erat, sed diam voluptua. At vero eos et "
                     "accusam et justo duo dolores et ea rebum. Stet clita kasd "
                     "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")

        for i in range(5):
            if i == 2:
                self.text_ctrl.SetDefaultStyle(warning_style)
            else:
                self.text_ctrl.SetDefaultStyle(default_style)
            self.text_ctrl.AppendText(long_text+'\n')


if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

So the issue is that it gets wraped by some windows setting?

I can not use textctrl because i have additional functionality. I also introduce copying the string with ctrl+c and found that when i do it i get the whole text.

I don’t think the text is wrapped (one would have to get it again), but there ar plenty of ways to show temporarily a shortend text in the context of a list control (after all the list control is only the opener to the list)
but if you are happy be pleased :innocent:

Does the UltimateListCtrl show the full text on Windows?

import wx
import wx.lib.agw.ultimatelistctrl as ULC

class MyFrame(wx.Frame):
    def __init__(self, *args, **kw):
        super(MyFrame, self).__init__(*args, **kw)

        panel = wx.Panel(self)

        self.list_ctrl = ULC.UltimateListCtrl(panel, wx.ID_ANY, agwStyle=ULC.ULC_REPORT)

        self.list_ctrl.InsertColumn(0, 'Log Messages')

        long_text = ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "
                     "sed diam nonumy eirmod tempor invidunt ut labore et dolore "
                     "magna aliquyam erat, sed diam voluptua. At vero eos et "
                     "accusam et justo duo dolores et ea rebum. Stet clita kasd "
                     "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")

        self.list_ctrl.InsertStringItem(0, long_text)

        self.list_ctrl.SetColumnWidth(0, 2500)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 1, wx.EXPAND | wx.ALL, 5)
        panel.SetSizer(sizer)

        # self.ShowFullScreen(True)

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, title="UltimateListCtrl", size=(1900, 300))
        frame.Show()
        return True

if __name__ == "__main__":

    print(wx.VERSION)
    app = MyApp(False)
    app.MainLoop()

I am no C++ expert, but it seems to me that the MSW implementation of wx.ListCtrl has a hardcoded limit of 512 characters in setting/getting items. See for example here:

Maybe if the OP can measure how many characters get displayed we may have a confirmation of that.

The Linux version has no such problem as it uses the generic version of wx.ListCtrl, which does not have any limits in that sense - I know because I have used that as a base to create UltimateListCtrl.

You can try @RichardT suggestion on UltimateListCtrl, but I’m not sure the performances will be great with 100,000+ items.

Ultimately, I agree with all the other posters that a list control may not be the best tool for this job.

Perhaps you could use a Grid control with a single column? It appears to handle (on linux at least) large numbers of entries. It also has built in support for Ctrl+C to copy the contents of the selected rows to the clipboard as long as there is either a single selected row or a single selected block of contiguous rows. If there are more than one non-contiguous selected rows or blocks, a warning dialog is displayed.

import wx
import wx.grid

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((1900, 300))
        self.SetTitle("Grid Control")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.grid = wx.grid.Grid(self.panel, wx.ID_ANY)
        self.grid.CreateGrid(0, 1)
        self.grid.SetRowLabelSize(0)
        self.grid.SetColLabelSize(30)
        self.grid.SetDefaultColSize(1900, True)
        self.grid.EnableEditing(0)
        self.grid.EnableDragRowSize(0)
        self.grid.SetSelectionMode(wx.grid.Grid.SelectRows)
        self.grid.SetColLabelValue(0, "Log Messages")
        sizer.Add(self.grid, 1, wx.EXPAND, 0)
        self.panel.SetSizer(sizer)
        self.Layout()

        long_text = ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "
                     "sed diam nonumy eirmod tempor invidunt ut labore et dolore "
                     "magna aliquyam erat, sed diam voluptua. At vero eos et "
                     "accusam et justo duo dolores et ea rebum. Stet clita kasd "
                     "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")

        num_rows = 100000
        self.grid.AppendRows(num_rows)
        for r in range(num_rows):
            self.grid.SetCellValue(r, 0, f"{r} {long_text}")
        self.grid.GoToCell(num_rows-1, 0)
        self.grid.Refresh()
        self.grid.Update()


if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Screenshot at 2024-10-22 20-39-47

Below is an attempt at overriding the built in Ctrl+C handler to support non-contiguous rows. It appears to work on linux.

import wx
import wx.grid

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((1900, 300))
        self.SetTitle("Grid Control")
        self.panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.grid = wx.grid.Grid(self.panel, wx.ID_ANY)
        self.grid.CreateGrid(0, 1)
        self.grid.SetRowLabelSize(0)
        self.grid.SetColLabelSize(30)
        self.grid.SetDefaultColSize(1900, True)
        self.grid.EnableEditing(0)
        self.grid.EnableDragRowSize(0)
        self.grid.SetSelectionMode(wx.grid.Grid.SelectCells)
        self.grid.SetColLabelValue(0, "Log Messages")
        sizer.Add(self.grid, 1, wx.EXPAND, 0)
        self.panel.SetSizer(sizer)
        self.Layout()

        long_text = ("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "
                     "sed diam nonumy eirmod tempor invidunt ut labore et dolore "
                     "magna aliquyam erat, sed diam voluptua. At vero eos et "
                     "accusam et justo duo dolores et ea rebum. Stet clita kasd "
                     "gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")

        num_rows = 100000
        self.grid.AppendRows(num_rows)
        for r in range(num_rows):
            self.grid.SetCellValue(r, 0, f"{r} {long_text}")
        self.grid.GoToCell(num_rows-1, 0)
        self.grid.Refresh()
        self.grid.Update()

        self.grid.Bind(wx.EVT_KEY_DOWN, self.OnChar)


    def OnChar(self, event):
        keycode = event.GetKeyCode()
        if event.GetModifiers() == wx.MOD_CONTROL:
            if keycode == ord('C'):
                rows = self.grid.GetSelectedRows()
                text_list = [self.grid.GetCellValue(row, 0) for row in rows]
                text = "\n".join(text_list)
                clip_data = wx.TextDataObject()
                clip_data.SetText(text)
                wx.TheClipboard.Open()
                wx.TheClipboard.SetData(clip_data)
                wx.TheClipboard.Close()
            else:
                event.Skip()
        else:
            event.Skip()


if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()