How to change the color of ultimatelistctrl hrules

Hello Everyone, I’m developing the desktop application for mac and I’m using the ultimatlistctrl to show the data on that app. I want to change the color of the hrules. I have attached the mock up image below:

@Robin need your help here

One possible solution would be to create a derived class from UltimateListMainWindow and override its GetRuleColour() method to return your required colour.

Then create a derived class from UltimateListCtrl and override its __init__() method to use the custom version of UltimateListMainWindow.

Example:

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


class CustomUltimateListMainWindow(ULC.UltimateListMainWindow):

    # get the colour to be used for drawing the rules
    def GetRuleColour(self):
        """ Returns the colour to be used for drawing the horizontal and vertical rules. """

        return wx.Colour(255, 0, 0)


class CustomUltimateListCtrl(ULC.UltimateListCtrl):
    """
    UltimateListCtrl is a class that mimics the behaviour of :class:`ListCtrl`, with almost
    the same base functionalities plus some more enhancements. This class does
    not rely on the native control, as it is a full owner-drawn list control.
    """


    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=0, agwStyle=0, validator=wx.DefaultValidator, name="UltimateListCtrl"):
        """
        Default class constructor.

        :param `parent`: parent window. Must not be ``None``;
        :param `id`: window identifier. A value of -1 indicates a default value;
        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `size`: the control size. A value of (-1, -1) indicates a default size,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `style`: the underlying :class:`wx.Control` window style;
        :param `agwStyle`: the AGW-specific window style; can be almost any combination of the following
         bits:

         ===============================  =========== ====================================================================================================
         Window Styles                    Hex Value   Description
         ===============================  =========== ====================================================================================================
         ``ULC_VRULES``                           0x1 Draws light vertical rules between rows in report mode.
         ``ULC_HRULES``                           0x2 Draws light horizontal rules between rows in report mode.
         ``ULC_ICON``                             0x4 Large icon view, with optional labels.
         ``ULC_SMALL_ICON``                       0x8 Small icon view, with optional labels.
         ``ULC_LIST``                            0x10 Multicolumn list view, with optional small icons. Columns are computed automatically, i.e. you don't set columns as in ``ULC_REPORT``. In other words, the list wraps, unlike a :class:`ListBox`.
         ``ULC_REPORT``                          0x20 Single or multicolumn report view, with optional header.
         ``ULC_ALIGN_TOP``                       0x40 Icons align to the top. Win32 default, Win32 only.
         ``ULC_ALIGN_LEFT``                      0x80 Icons align to the left.
         ``ULC_AUTOARRANGE``                    0x100 Icons arrange themselves. Win32 only.
         ``ULC_VIRTUAL``                        0x200 The application provides items text on demand. May only be used with ``ULC_REPORT``.
         ``ULC_EDIT_LABELS``                    0x400 Labels are editable: the application will be notified when editing starts.
         ``ULC_NO_HEADER``                      0x800 No header in report mode.
         ``ULC_NO_SORT_HEADER``                0x1000 No Docs.
         ``ULC_SINGLE_SEL``                    0x2000 Single selection (default is multiple).
         ``ULC_SORT_ASCENDING``                0x4000 Sort in ascending order. (You must still supply a comparison callback in :meth:`ListCtrl.SortItems`.)
         ``ULC_SORT_DESCENDING``               0x8000 Sort in descending order. (You must still supply a comparison callback in :meth:`ListCtrl.SortItems`.)
         ``ULC_TILE``                         0x10000 Each item appears as a full-sized icon with a label of one or more lines beside it (partially implemented).
         ``ULC_NO_HIGHLIGHT``                 0x20000 No highlight when an item is selected.
         ``ULC_STICKY_HIGHLIGHT``             0x40000 Items are selected by simply hovering on them, with no need to click on them.
         ``ULC_STICKY_NOSELEVENT``            0x80000 Don't send a selection event when using ``ULC_STICKY_HIGHLIGHT`` style.
         ``ULC_SEND_LEFTCLICK``              0x100000 Send a left click event when an item is selected.
         ``ULC_HAS_VARIABLE_ROW_HEIGHT``     0x200000 The list has variable row heights.
         ``ULC_AUTO_CHECK_CHILD``            0x400000 When a column header has a checkbox associated, auto-check all the subitems in that column.
         ``ULC_AUTO_TOGGLE_CHILD``           0x800000 When a column header has a checkbox associated, toggle all the subitems in that column.
         ``ULC_AUTO_CHECK_PARENT``          0x1000000 Only meaningful foe checkbox-type items: when an item is checked/unchecked its column header item is checked/unchecked as well.
         ``ULC_SHOW_TOOLTIPS``              0x2000000 Show tooltips for ellipsized items/subitems (text too long to be shown in the available space) containing the full item/subitem text.
         ``ULC_HOT_TRACKING``               0x4000000 Enable hot tracking of items on mouse motion.
         ``ULC_BORDER_SELECT``              0x8000000 Changes border colour when an item is selected, instead of highlighting the item.
         ``ULC_TRACK_SELECT``              0x10000000 Enables hot-track selection in a list control. Hot track selection means that an item is automatically selected when the cursor remains over the item for a certain period of time. The delay is retrieved on Windows using the `win32api` call `win32gui.SystemParametersInfo(win32con.SPI_GETMOUSEHOVERTIME)`, and is defaulted to 400ms on other platforms. This style applies to all views of `UltimateListCtrl`.
         ``ULC_HEADER_IN_ALL_VIEWS``       0x20000000 Show column headers in all view modes.
         ``ULC_NO_FULL_ROW_SELECT``        0x40000000 When an item is selected, the only the item in the first column is highlighted.
         ``ULC_FOOTER``                    0x80000000 Show a footer too (only when header is present).
         ``ULC_USER_ROW_HEIGHT``          0x100000000 Allows to set a custom row height (one value for all the items, only in report mode).
         ===============================  =========== ====================================================================================================

        :param `validator`: the window validator;
        :param `name`: the window name.
        """

        self._imageListNormal = None
        self._imageListSmall = None
        self._imageListState = None

        if not agwStyle & ULC.ULC_MASK_TYPE:
            raise Exception("UltimateListCtrl style should have exactly one mode bit set")

        if not (agwStyle & ULC.ULC_REPORT) and agwStyle & ULC.ULC_HAS_VARIABLE_ROW_HEIGHT:
            raise Exception("Style ULC_HAS_VARIABLE_ROW_HEIGHT can only be used in report, non-virtual mode")

        if agwStyle & ULC.ULC_STICKY_HIGHLIGHT and agwStyle & ULC.ULC_TRACK_SELECT:
            raise Exception("Styles ULC_STICKY_HIGHLIGHT and ULC_TRACK_SELECT can not be combined")

        if agwStyle & ULC.ULC_NO_HEADER and agwStyle & ULC.ULC_HEADER_IN_ALL_VIEWS:
            raise Exception("Styles ULC_NO_HEADER and ULC_HEADER_IN_ALL_VIEWS can not be combined")

        if agwStyle & ULC.ULC_USER_ROW_HEIGHT and (agwStyle & ULC.ULC_REPORT) == 0:
            raise Exception("Style ULC_USER_ROW_HEIGHT can be used only with ULC_REPORT")

        wx.Control.__init__(self, parent, id, pos, size, style | wx.CLIP_CHILDREN, validator, name)

        self._mainWin = None
        self._headerWin = None
        self._footerWin = None

        self._headerHeight = wx.RendererNative.Get().GetHeaderButtonHeight(self)
        self._footerHeight = self._headerHeight

        if wx.Platform == "__WXGTK__":
            style &= ~wx.BORDER_MASK
            style |= wx.BORDER_THEME
        else:
            if style & wx.BORDER_THEME:
                style -= wx.BORDER_THEME

        self._agwStyle = agwStyle
        if style & wx.SUNKEN_BORDER:
            style -= wx.SUNKEN_BORDER

        self._mainWin = CustomUltimateListMainWindow(self, wx.ID_ANY, wx.Point(0, 0), wx.DefaultSize, style, agwStyle)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self._mainWin, 1, wx.GROW)
        self.SetSizer(sizer)

        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)

        self.CreateOrDestroyHeaderWindowAsNeeded()
        self.CreateOrDestroyFooterWindowAsNeeded()

        self.SetInitialSize(size)
        wx.CallAfter(self.Layout)


# --- DEMO APP ---

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "CustomUltimateListCtrl Demo")
        agw_style = ULC.ULC_REPORT | ULC.ULC_VRULES | ULC.ULC_HRULES | ULC.ULC_SINGLE_SEL | ULC.ULC_HAS_VARIABLE_ROW_HEIGHT
        u_list = CustomUltimateListCtrl(self, wx.ID_ANY, agwStyle=agw_style)

        u_list.InsertColumn(0, "Column 1", format=ULC.ULC_FORMAT_CENTER)
        u_list.InsertColumn(1, "Column 2")
        u_list.InsertColumn(2, "Column 3", format=ULC.ULC_FORMAT_RIGHT)

        index = u_list.InsertStringItem(sys.maxsize, "Item 1")
        u_list.SetStringItem(index, 1, "bb")
        u_list.SetStringItem(index, 2, "56")

        index = u_list.InsertStringItem(sys.maxsize, "Item 2")
        u_list.SetStringItem(index, 1, "hh")
        u_list.SetStringItem(index, 2, "34")

        choice = wx.Choice(u_list, -1, choices=["one", "two"])
        index = u_list.InsertStringItem(sys.maxsize, "A widget")
        u_list.SetItemWindow(index, 1, choice, expand=True)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(u_list, 1, wx.EXPAND)
        self.SetSizer(sizer)


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

Tested using Python 3.10.12 + wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1 on Linux Mint 21.2

Screenshot at 2023-08-02 16-15-42

1 Like

Here is a simpler alternative (if you don’t mind monkey-patching a protected member of a class):

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

# Replacement for UltimateListMainWindow.GetRuleColour()
def getRuleColour(self):
    return wx.Colour(255, 0, 0)


class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "CustomUltimateListCtrl Demo")
        agw_style = ULC.ULC_REPORT | ULC.ULC_VRULES | ULC.ULC_HRULES | ULC.ULC_SINGLE_SEL | ULC.ULC_HAS_VARIABLE_ROW_HEIGHT
        u_list = ULC.UltimateListCtrl(self, wx.ID_ANY, agwStyle=agw_style)

        # Replace UltimateListMainWindow.GetRuleColour method
        u_list._mainWin.GetRuleColour = types.MethodType(getRuleColour, u_list._mainWin)

        u_list.InsertColumn(0, "Column 1", format=ULC.ULC_FORMAT_CENTER)
        u_list.InsertColumn(1, "Column 2")
        u_list.InsertColumn(2, "Column 3", format=ULC.ULC_FORMAT_RIGHT)

        index = u_list.InsertStringItem(sys.maxsize, "Item 1")
        u_list.SetStringItem(index, 1, "bb")
        u_list.SetStringItem(index, 2, "56")

        index = u_list.InsertStringItem(sys.maxsize, "Item 2")
        u_list.SetStringItem(index, 1, "hh")
        u_list.SetStringItem(index, 2, "34")

        choice = wx.Choice(u_list, -1, choices=["one", "two"])
        index = u_list.InsertStringItem(sys.maxsize, "A widget")
        u_list.SetItemWindow(index, 1, choice, expand=True)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(u_list, 1, wx.EXPAND)
        self.SetSizer(sizer)


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

Based on one of the answers to a question on stack overflow.

1 Like