Package m45wxcontrols

Custom control wrappers for wxPython by M45Development

Sub-modules

m45wxcontrols.accessible_listbook

Accessible listbook control backed by wx.ListBox.

m45wxcontrols.accessible_spin

Accessible spinbutton class

m45wxcontrols.custom_text_entry

Text entry dialog with custom button labels.

m45wxcontrols.universal_list

Universal list class to choose between ListCtrl and DataViewListCtrl

Classes

class AccessibleListbook (parent,
id=-1,
pos=wx.Point(-1, -1),
size=wx.Size(-1, -1),
style=524288,
name='AccessibleListbook',
list_width=180)
Expand source code
class AccessibleListbook(wx.Panel):
    """
    Accessible wx.Listbook-style control using wx.ListBox for page selection.

    The category list is a plain wx.ListBox, which is easier to use with
    screen readers on platforms where wx.ListCtrl accessibility is limited.
    Pages are ordinary wx.Window instances that are shown when their matching
    list entry is selected.
    """

    def __init__(
        self,
        parent,
        id=wx.ID_ANY,
        pos=wx.DefaultPosition,
        size=wx.DefaultSize,
        style=wx.TAB_TRAVERSAL,
        name="AccessibleListbook",
        list_width=180,
    ):
        """
        Initialize the accessible listbook.

        Args:
            parent: Parent wx window.
            id: Window id.
            pos: Initial position.
            size: Initial size.
            style: wx.Panel style flags.
            name: Control name.
            list_width: Width of the category list in pixels.
        """
        super().__init__(parent, id=id, pos=pos, size=size, style=style, name=name)

        self._pages = []
        self._page_texts = []
        self._page_images = []
        self._selection = wx.NOT_FOUND
        self._changing_selection = False

        self.list_label = wx.StaticText(self, label="Cate&gories")
        self.listbox = wx.ListBox(self, style=wx.LB_SINGLE | wx.BORDER_SUNKEN)
        self.listbox.SetName(f"{name} categories")
        self.page_area = wx.Panel(self, style=wx.TAB_TRAVERSAL)

        self._page_sizer = wx.BoxSizer(wx.VERTICAL)
        self.page_area.SetSizer(self._page_sizer)

        left = wx.BoxSizer(wx.VERTICAL)
        left.Add(self.list_label, 0, wx.LEFT | wx.RIGHT | wx.TOP, 5)
        left.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 5)

        root = wx.BoxSizer(wx.HORIZONTAL)
        root.Add(left, 0, wx.EXPAND)
        root.SetItemMinSize(left, list_width, -1)
        root.Add(self.page_area, 1, wx.EXPAND | wx.TOP | wx.RIGHT | wx.BOTTOM, 5)
        self.SetSizer(root)

        self.listbox.Bind(wx.EVT_LISTBOX, self._on_listbox_selection)

    def AddPage(self, page, text, select=False, imageId=wx.NOT_FOUND):
        """
        Add a page to the end of the control.

        Args:
            page: wx.Window displayed as page content.
            text: Label shown in the category list.
            select: Whether to select the new page immediately.
            imageId: Stored for Listbook API compatibility. Images are not
                displayed by wx.ListBox.

        Returns:
            bool: True when the page was added.
        """
        return self.InsertPage(self.GetPageCount(), page, text, select, imageId)

    def InsertPage(self, index, page, text, select=False, imageId=wx.NOT_FOUND):
        """
        Insert a page at a specific index.
        """
        index = max(0, min(index, self.GetPageCount()))
        self._prepare_page(page)

        self._pages.insert(index, page)
        self._page_texts.insert(index, str(text))
        self._page_images.insert(index, imageId)
        self.listbox.Insert(str(text), index)
        self._page_sizer.Insert(index, page, 1, wx.EXPAND)
        page.Hide()

        if self._selection == wx.NOT_FOUND or select:
            self.ChangeSelection(index)
        elif index <= self._selection:
            self._selection += 1
            self.listbox.SetSelection(self._selection)

        self.Layout()
        return True

    def DeletePage(self, index):
        """
        Delete and destroy a page.

        Returns:
            bool: True when a page was deleted.
        """
        page = self.RemovePage(index)
        if page is None:
            return False
        page.Destroy()
        return True

    def RemovePage(self, index):
        """
        Remove a page without destroying it.

        Returns:
            wx.Window | None: Removed page, or None for an invalid index.
        """
        if not self._is_valid_index(index):
            return None

        page = self._pages.pop(index)
        del self._page_texts[index]
        del self._page_images[index]
        self.listbox.Delete(index)
        self._page_sizer.Detach(page)
        page.Hide()

        if not self._pages:
            self._selection = wx.NOT_FOUND
            self.listbox.SetSelection(wx.NOT_FOUND)
        elif index == self._selection:
            self._selection = wx.NOT_FOUND
            self.ChangeSelection(min(index, self.GetPageCount() - 1))
        elif index < self._selection:
            self._selection -= 1
            self.listbox.SetSelection(self._selection)

        self.Layout()
        return page

    def DeleteAllPages(self):
        """
        Delete and destroy all pages.
        """
        for page in self._pages:
            self._page_sizer.Detach(page)
            page.Destroy()
        self._pages.clear()
        self._page_texts.clear()
        self._page_images.clear()
        self.listbox.Clear()
        self._selection = wx.NOT_FOUND
        self.Layout()

    def GetPage(self, index):
        """
        Return the page at index, or None when the index is invalid.
        """
        if not self._is_valid_index(index):
            return None
        return self._pages[index]

    def GetCurrentPage(self):
        """
        Return the currently selected page, or None when no page is selected.
        """
        return self.GetPage(self._selection)

    def GetPageCount(self):
        """
        Return the number of pages.
        """
        return len(self._pages)

    def GetPageText(self, index):
        """
        Return a page label.
        """
        if not self._is_valid_index(index):
            return ""
        return self._page_texts[index]

    def SetPageText(self, index, text):
        """
        Set a page label.

        Returns:
            bool: True when the label was updated.
        """
        if not self._is_valid_index(index):
            return False
        self._page_texts[index] = str(text)
        self.listbox.SetString(index, str(text))
        return True

    def GetPageImage(self, index):
        """
        Return the stored image id for API compatibility.
        """
        if not self._is_valid_index(index):
            return wx.NOT_FOUND
        return self._page_images[index]

    def SetPageImage(self, index, imageId):
        """
        Store an image id for API compatibility.

        wx.ListBox does not display images, but callers can still keep their
        Listbook-oriented code paths intact.
        """
        if not self._is_valid_index(index):
            return False
        self._page_images[index] = imageId
        return True

    def GetSelection(self):
        """
        Return the selected page index, or wx.NOT_FOUND.
        """
        return self._selection

    def SetSelection(self, index):
        """
        Select a page and emit listbook page changing/changed events.

        Returns:
            int: Previously selected index, or wx.NOT_FOUND if selection failed.
        """
        return self._set_selection(index, emit_events=True)

    def ChangeSelection(self, index):
        """
        Select a page without emitting page changing/changed events.

        Returns:
            int: Previously selected index, or wx.NOT_FOUND if selection failed.
        """
        return self._set_selection(index, emit_events=False)

    def AdvanceSelection(self, forward=True):
        """
        Move selection to the next or previous page.
        """
        count = self.GetPageCount()
        if count == 0:
            return

        if self._selection == wx.NOT_FOUND:
            self.SetSelection(0)
            return

        step = 1 if forward else -1
        self.SetSelection((self._selection + step) % count)

    def GetStaticLabel(self):
        """
        Return the text shown above the category list.
        """
        return self.list_label.GetLabel()

    def SetStaticLabel(self, label):
        """
        Set the text shown above the category list.

        This is useful for localized applications because wx.Panel itself does
        not accept a label argument.
        """
        self.list_label.SetLabel(str(label))
        self.Layout()

    def GetListBox(self):
        """
        Return the wx.ListBox used for category navigation.
        """
        return self.listbox

    def GetPageArea(self):
        """
        Return the panel that owns page windows.
        """
        return self.page_area

    def SetListWidth(self, width):
        """
        Set the fixed width of the category list.
        """
        item = self.GetSizer().GetItem(self.listbox)
        if item is not None:
            item.SetMinSize(width, -1)
            self.Layout()

    def _prepare_page(self, page):
        """
        Ensure a page belongs to the internal page area.
        """
        if page.GetParent() is not self.page_area:
            page.Reparent(self.page_area)

    def _is_valid_index(self, index):
        """
        Return whether index points to an existing page.
        """
        return 0 <= index < self.GetPageCount()

    def _set_selection(self, index, emit_events, sync_listbox=True):
        """
        Internal selection implementation.
        """
        if not self._is_valid_index(index):
            return wx.NOT_FOUND

        old_selection = self._selection
        if index == old_selection:
            if sync_listbox:
                self.listbox.SetSelection(index)
            return old_selection

        if emit_events and not self._send_book_event(
            wx.wxEVT_LISTBOOK_PAGE_CHANGING,
            index,
            old_selection,
        ):
            if sync_listbox:
                self.listbox.SetSelection(old_selection)
            return wx.NOT_FOUND

        self._show_page(index, sync_listbox=sync_listbox)

        if emit_events:
            self._send_book_event(wx.wxEVT_LISTBOOK_PAGE_CHANGED, index, old_selection)

        return old_selection

    def _show_page(self, index, sync_listbox=True):
        """
        Show one page and hide the previously selected page.
        """
        old_selection = self._selection
        self._changing_selection = True
        self.page_area.Freeze()
        try:
            if self._is_valid_index(old_selection):
                self._pages[old_selection].Hide()
            self._pages[index].Show()
            self._selection = index
            if sync_listbox:
                self.listbox.SetSelection(index)
            self.page_area.Layout()
        finally:
            self.page_area.Thaw()
            self._changing_selection = False

    def _send_book_event(self, event_type, selection, old_selection):
        """
        Send a wx.BookCtrlEvent and report whether it was allowed.
        """
        event = wx.BookCtrlEvent(event_type, self.GetId(), selection, old_selection)
        event.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(event)
        return event.IsAllowed()

    def _on_listbox_selection(self, event):
        """
        Select pages when users move through the listbox.
        """
        if self._changing_selection:
            return

        previous = self._selection
        selected = event.GetSelection()
        result = self._set_selection(selected, emit_events=True, sync_listbox=False)
        if result == wx.NOT_FOUND and previous != wx.NOT_FOUND:
            self.listbox.SetSelection(previous)

Accessible wx.Listbook-style control using wx.ListBox for page selection.

The category list is a plain wx.ListBox, which is easier to use with screen readers on platforms where wx.ListCtrl accessibility is limited. Pages are ordinary wx.Window instances that are shown when their matching list entry is selected.

Initialize the accessible listbook.

Args

parent
Parent wx window.
id
Window id.
pos
Initial position.
size
Initial size.
style
wx.Panel style flags.
name
Control name.
list_width
Width of the category list in pixels.

Ancestors

  • wx._core.Panel
  • wx._core.Window
  • wx._core.WindowBase
  • wx._core.EvtHandler
  • wx._core.Object
  • wx._core.Trackable
  • sip.wrapper
  • sip.simplewrapper

Methods

def AddPage(self, page, text, select=False, imageId=-1)
Expand source code
def AddPage(self, page, text, select=False, imageId=wx.NOT_FOUND):
    """
    Add a page to the end of the control.

    Args:
        page: wx.Window displayed as page content.
        text: Label shown in the category list.
        select: Whether to select the new page immediately.
        imageId: Stored for Listbook API compatibility. Images are not
            displayed by wx.ListBox.

    Returns:
        bool: True when the page was added.
    """
    return self.InsertPage(self.GetPageCount(), page, text, select, imageId)

Add a page to the end of the control.

Args

page
wx.Window displayed as page content.
text
Label shown in the category list.
select
Whether to select the new page immediately.
imageId
Stored for Listbook API compatibility. Images are not displayed by wx.ListBox.

Returns

bool
True when the page was added.
def AdvanceSelection(self, forward=True)
Expand source code
def AdvanceSelection(self, forward=True):
    """
    Move selection to the next or previous page.
    """
    count = self.GetPageCount()
    if count == 0:
        return

    if self._selection == wx.NOT_FOUND:
        self.SetSelection(0)
        return

    step = 1 if forward else -1
    self.SetSelection((self._selection + step) % count)

Move selection to the next or previous page.

def ChangeSelection(self, index)
Expand source code
def ChangeSelection(self, index):
    """
    Select a page without emitting page changing/changed events.

    Returns:
        int: Previously selected index, or wx.NOT_FOUND if selection failed.
    """
    return self._set_selection(index, emit_events=False)

Select a page without emitting page changing/changed events.

Returns

int
Previously selected index, or wx.NOT_FOUND if selection failed.
def DeleteAllPages(self)
Expand source code
def DeleteAllPages(self):
    """
    Delete and destroy all pages.
    """
    for page in self._pages:
        self._page_sizer.Detach(page)
        page.Destroy()
    self._pages.clear()
    self._page_texts.clear()
    self._page_images.clear()
    self.listbox.Clear()
    self._selection = wx.NOT_FOUND
    self.Layout()

Delete and destroy all pages.

def DeletePage(self, index)
Expand source code
def DeletePage(self, index):
    """
    Delete and destroy a page.

    Returns:
        bool: True when a page was deleted.
    """
    page = self.RemovePage(index)
    if page is None:
        return False
    page.Destroy()
    return True

Delete and destroy a page.

Returns

bool
True when a page was deleted.
def GetCurrentPage(self)
Expand source code
def GetCurrentPage(self):
    """
    Return the currently selected page, or None when no page is selected.
    """
    return self.GetPage(self._selection)

Return the currently selected page, or None when no page is selected.

def GetListBox(self)
Expand source code
def GetListBox(self):
    """
    Return the wx.ListBox used for category navigation.
    """
    return self.listbox

Return the wx.ListBox used for category navigation.

def GetPage(self, index)
Expand source code
def GetPage(self, index):
    """
    Return the page at index, or None when the index is invalid.
    """
    if not self._is_valid_index(index):
        return None
    return self._pages[index]

Return the page at index, or None when the index is invalid.

def GetPageArea(self)
Expand source code
def GetPageArea(self):
    """
    Return the panel that owns page windows.
    """
    return self.page_area

Return the panel that owns page windows.

def GetPageCount(self)
Expand source code
def GetPageCount(self):
    """
    Return the number of pages.
    """
    return len(self._pages)

Return the number of pages.

def GetPageImage(self, index)
Expand source code
def GetPageImage(self, index):
    """
    Return the stored image id for API compatibility.
    """
    if not self._is_valid_index(index):
        return wx.NOT_FOUND
    return self._page_images[index]

Return the stored image id for API compatibility.

def GetPageText(self, index)
Expand source code
def GetPageText(self, index):
    """
    Return a page label.
    """
    if not self._is_valid_index(index):
        return ""
    return self._page_texts[index]

Return a page label.

def GetSelection(self)
Expand source code
def GetSelection(self):
    """
    Return the selected page index, or wx.NOT_FOUND.
    """
    return self._selection

Return the selected page index, or wx.NOT_FOUND.

def GetStaticLabel(self)
Expand source code
def GetStaticLabel(self):
    """
    Return the text shown above the category list.
    """
    return self.list_label.GetLabel()

Return the text shown above the category list.

def InsertPage(self, index, page, text, select=False, imageId=-1)
Expand source code
def InsertPage(self, index, page, text, select=False, imageId=wx.NOT_FOUND):
    """
    Insert a page at a specific index.
    """
    index = max(0, min(index, self.GetPageCount()))
    self._prepare_page(page)

    self._pages.insert(index, page)
    self._page_texts.insert(index, str(text))
    self._page_images.insert(index, imageId)
    self.listbox.Insert(str(text), index)
    self._page_sizer.Insert(index, page, 1, wx.EXPAND)
    page.Hide()

    if self._selection == wx.NOT_FOUND or select:
        self.ChangeSelection(index)
    elif index <= self._selection:
        self._selection += 1
        self.listbox.SetSelection(self._selection)

    self.Layout()
    return True

Insert a page at a specific index.

def RemovePage(self, index)
Expand source code
def RemovePage(self, index):
    """
    Remove a page without destroying it.

    Returns:
        wx.Window | None: Removed page, or None for an invalid index.
    """
    if not self._is_valid_index(index):
        return None

    page = self._pages.pop(index)
    del self._page_texts[index]
    del self._page_images[index]
    self.listbox.Delete(index)
    self._page_sizer.Detach(page)
    page.Hide()

    if not self._pages:
        self._selection = wx.NOT_FOUND
        self.listbox.SetSelection(wx.NOT_FOUND)
    elif index == self._selection:
        self._selection = wx.NOT_FOUND
        self.ChangeSelection(min(index, self.GetPageCount() - 1))
    elif index < self._selection:
        self._selection -= 1
        self.listbox.SetSelection(self._selection)

    self.Layout()
    return page

Remove a page without destroying it.

Returns

wx.Window | None
Removed page, or None for an invalid index.
def SetListWidth(self, width)
Expand source code
def SetListWidth(self, width):
    """
    Set the fixed width of the category list.
    """
    item = self.GetSizer().GetItem(self.listbox)
    if item is not None:
        item.SetMinSize(width, -1)
        self.Layout()

Set the fixed width of the category list.

def SetPageImage(self, index, imageId)
Expand source code
def SetPageImage(self, index, imageId):
    """
    Store an image id for API compatibility.

    wx.ListBox does not display images, but callers can still keep their
    Listbook-oriented code paths intact.
    """
    if not self._is_valid_index(index):
        return False
    self._page_images[index] = imageId
    return True

Store an image id for API compatibility.

wx.ListBox does not display images, but callers can still keep their Listbook-oriented code paths intact.

def SetPageText(self, index, text)
Expand source code
def SetPageText(self, index, text):
    """
    Set a page label.

    Returns:
        bool: True when the label was updated.
    """
    if not self._is_valid_index(index):
        return False
    self._page_texts[index] = str(text)
    self.listbox.SetString(index, str(text))
    return True

Set a page label.

Returns

bool
True when the label was updated.
def SetSelection(self, index)
Expand source code
def SetSelection(self, index):
    """
    Select a page and emit listbook page changing/changed events.

    Returns:
        int: Previously selected index, or wx.NOT_FOUND if selection failed.
    """
    return self._set_selection(index, emit_events=True)

Select a page and emit listbook page changing/changed events.

Returns

int
Previously selected index, or wx.NOT_FOUND if selection failed.
def SetStaticLabel(self, label)
Expand source code
def SetStaticLabel(self, label):
    """
    Set the text shown above the category list.

    This is useful for localized applications because wx.Panel itself does
    not accept a label argument.
    """
    self.list_label.SetLabel(str(label))
    self.Layout()

Set the text shown above the category list.

This is useful for localized applications because wx.Panel itself does not accept a label argument.

class AccessibleSpinCtrl (parent, label_text, initial_val, min_val, max_val, inc)
Expand source code
class AccessibleSpinCtrl(wx.BoxSizer):
    """
    Accessible floating-point spin control composed of wx widgets.

    The control exposes a labeled text field as the primary interaction point
    and keeps a wx.SpinButton in sync for mouse users. Arrow keys on the text
    field adjust the value directly, which makes the widget easier to use with
    screen readers.
    """

    def __init__(self, parent, label_text, initial_val, min_val, max_val, inc):
        """
        Initialize accessible spin control.

        Args:
            parent: Parent wx window that owns the child controls.
            label_text: Label shown next to the text field and used as its
                accessible name.
            initial_val: Initial numeric value displayed in the control.
            min_val: Minimum allowed value.
            max_val: Maximum allowed value.
            inc: Increment used by the spin button and Up/Down arrow keys.
        """
        super().__init__(wx.HORIZONTAL)
        
        self.min_val = min_val
        self.max_val = max_val
        self.inc = inc
        
        # 1. The Label
        self.label = wx.StaticText(parent, label=label_text)
        self.Add(self.label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
        
        # 2. The TextCtrl (The primary interaction point)
        self.text_ctrl = wx.TextCtrl(parent, value=f"{initial_val:.1f}")
        self.text_ctrl.SetName(label_text)
        self.Add(self.text_ctrl, 1, wx.EXPAND | wx.ALL, 5)
        
        # 3. The SpinButton (Visible for mouse users)
        self.spin_btn = wx.SpinButton(parent, style=wx.SP_VERTICAL)
        self.spin_btn.SetRange(int(min_val / inc), int(max_val / inc))
        self.spin_btn.SetValue(int(initial_val / inc))
        self.spin_btn.SetCanFocus(False)
        self.Add(self.spin_btn, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
        
        # Bindings
        self.spin_btn.Bind(wx.EVT_SPIN, self.on_spin)
        self.text_ctrl.Bind(wx.EVT_TEXT, self.on_text_entry)
        
        # Accessibility: Allow Up/Down arrow keys directly in the TextCtrl
        self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.on_key_down)

    def Bind(self, event_type, handler, *args, **kwargs):
        """
        Proxy method to allow the dialog to bind to changes.
        We map any binding attempt to our internal controls.

        Args:
            event_type: Ignored wx event binder supplied by the caller.
            handler: Callable to bind to text and spin changes.
            *args: Additional positional arguments forwarded to wx.Bind.
            **kwargs: Additional keyword arguments forwarded to wx.Bind.
        """
        # We bind to both text changes and spin changes
        self.text_ctrl.Bind(wx.EVT_TEXT, handler, *args, **kwargs)
        self.spin_btn.Bind(wx.EVT_SPIN, handler, *args, **kwargs)

    def _adjust_value(self, steps):
        """
        Internal helper to increment/decrement the value.

        Args:
            steps: Number of increments to apply. Negative values decrement.
        """
        try:
            current = float(self.text_ctrl.GetValue())
            new_val = current + (steps * self.inc)
            # Clamp value
            new_val = max(self.min_val, min(self.max_val, new_val))
            
            # Update both controls
            formatted_val = f"{new_val:.1f}"
            self.text_ctrl.SetValue(formatted_val) 
            # Note: SetValue triggers NVDA to read the new content
            
            self.spin_btn.SetValue(int(new_val / self.inc))
        except ValueError:
            pass

    def on_spin(self, event):
        """
        Handle spin button changes and mirror the value into the text field.

        Args:
            event: wx spin event containing the current spin position.
        """
        new_val = event.GetPosition() * self.inc
        self.text_ctrl.ChangeValue(f"{new_val:.1f}")
        event.Skip() # CRITICAL: Allows the event to propagate to the dialog

    def on_text_entry(self, event):
        """
        Handle manual text edits and keep the spin button position in sync.

        Args:
            event: wx text event emitted by the internal text control.
        """
        try:
            val = float(self.text_ctrl.GetValue())
            self.spin_btn.SetValue(int(val / self.inc))
        except ValueError:
            pass
        event.Skip() # CRITICAL: Allows the event to propagate to the dialog

    def on_key_down(self, event):
        """
        Handle keyboard increments from the internal text control.

        Args:
            event: wx key event. Up and Down adjust the value; all other keys
                are passed through.
        """
        key = event.GetKeyCode()
        if key in (wx.WXK_UP, wx.WXK_DOWN):
            steps = 1 if key == wx.WXK_UP else -1
            self._adjust_value(steps)
            
            # Manually fire a text event so the dialog knows something changed
            # since _adjust_value uses SetValue/ChangeValue
            cmd_event = wx.CommandEvent(wx.wxEVT_TEXT, self.text_ctrl.GetId())
            cmd_event.SetString(self.text_ctrl.GetValue())
            self.text_ctrl.GetEventHandler().ProcessEvent(cmd_event)
        else:
            event.Skip()

    def GetValue(self):
        """
        Return the current numeric value.

        Returns:
            float: Parsed text control value, or 2.0 if the text is not a
            valid number.
        """
        try:
            return float(self.text_ctrl.GetValue())
        except ValueError:
            return 2.0

Accessible floating-point spin control composed of wx widgets.

The control exposes a labeled text field as the primary interaction point and keeps a wx.SpinButton in sync for mouse users. Arrow keys on the text field adjust the value directly, which makes the widget easier to use with screen readers.

Initialize accessible spin control.

Args

parent
Parent wx window that owns the child controls.
label_text
Label shown next to the text field and used as its accessible name.
initial_val
Initial numeric value displayed in the control.
min_val
Minimum allowed value.
max_val
Maximum allowed value.
inc
Increment used by the spin button and Up/Down arrow keys.

Ancestors

  • wx._core.BoxSizer
  • wx._core.Sizer
  • wx._core.Object
  • sip.wrapper
  • sip.simplewrapper

Methods

def Bind(self, event_type, handler, *args, **kwargs)
Expand source code
def Bind(self, event_type, handler, *args, **kwargs):
    """
    Proxy method to allow the dialog to bind to changes.
    We map any binding attempt to our internal controls.

    Args:
        event_type: Ignored wx event binder supplied by the caller.
        handler: Callable to bind to text and spin changes.
        *args: Additional positional arguments forwarded to wx.Bind.
        **kwargs: Additional keyword arguments forwarded to wx.Bind.
    """
    # We bind to both text changes and spin changes
    self.text_ctrl.Bind(wx.EVT_TEXT, handler, *args, **kwargs)
    self.spin_btn.Bind(wx.EVT_SPIN, handler, *args, **kwargs)

Proxy method to allow the dialog to bind to changes. We map any binding attempt to our internal controls.

Args

event_type
Ignored wx event binder supplied by the caller.
handler
Callable to bind to text and spin changes.
*args
Additional positional arguments forwarded to wx.Bind.
**kwargs
Additional keyword arguments forwarded to wx.Bind.
def GetValue(self)
Expand source code
def GetValue(self):
    """
    Return the current numeric value.

    Returns:
        float: Parsed text control value, or 2.0 if the text is not a
        valid number.
    """
    try:
        return float(self.text_ctrl.GetValue())
    except ValueError:
        return 2.0

Return the current numeric value.

Returns

float
Parsed text control value, or 2.0 if the text is not a

valid number.

def on_key_down(self, event)
Expand source code
def on_key_down(self, event):
    """
    Handle keyboard increments from the internal text control.

    Args:
        event: wx key event. Up and Down adjust the value; all other keys
            are passed through.
    """
    key = event.GetKeyCode()
    if key in (wx.WXK_UP, wx.WXK_DOWN):
        steps = 1 if key == wx.WXK_UP else -1
        self._adjust_value(steps)
        
        # Manually fire a text event so the dialog knows something changed
        # since _adjust_value uses SetValue/ChangeValue
        cmd_event = wx.CommandEvent(wx.wxEVT_TEXT, self.text_ctrl.GetId())
        cmd_event.SetString(self.text_ctrl.GetValue())
        self.text_ctrl.GetEventHandler().ProcessEvent(cmd_event)
    else:
        event.Skip()

Handle keyboard increments from the internal text control.

Args

event
wx key event. Up and Down adjust the value; all other keys are passed through.
def on_spin(self, event)
Expand source code
def on_spin(self, event):
    """
    Handle spin button changes and mirror the value into the text field.

    Args:
        event: wx spin event containing the current spin position.
    """
    new_val = event.GetPosition() * self.inc
    self.text_ctrl.ChangeValue(f"{new_val:.1f}")
    event.Skip() # CRITICAL: Allows the event to propagate to the dialog

Handle spin button changes and mirror the value into the text field.

Args

event
wx spin event containing the current spin position.
def on_text_entry(self, event)
Expand source code
def on_text_entry(self, event):
    """
    Handle manual text edits and keep the spin button position in sync.

    Args:
        event: wx text event emitted by the internal text control.
    """
    try:
        val = float(self.text_ctrl.GetValue())
        self.spin_btn.SetValue(int(val / self.inc))
    except ValueError:
        pass
    event.Skip() # CRITICAL: Allows the event to propagate to the dialog

Handle manual text edits and keep the spin button position in sync.

Args

event
wx text event emitted by the internal text control.
class CustomTextEntryDialog (parent, message, caption, default_value='', ok_label='&OK', cancel_label='&Cancel')
Expand source code
class CustomTextEntryDialog(wx.Dialog):
    """
    Text entry dialog with custom button labels.

    The dialog mirrors the simple wx.TextEntryDialog workflow while allowing
    callers to provide custom OK and Cancel labels.
    """

    def __init__(self, parent, message, caption, default_value="", ok_label="&OK", cancel_label="&Cancel"):
        """
        Initialize custom text entry dialog.

        Args:
            parent: Parent window (MainFrame)
            message: text input message
            caption: text input caption
            default_value: optional default value
            ok_label: optional OK button label
            cancel_label: optional cancel button label
        """
        super().__init__(parent, title=caption)

        sizer = wx.BoxSizer(wx.VERTICAL)

        # Message
        text = wx.StaticText(self, label=message)
        sizer.Add(text, 0, wx.ALL, 10)

        # Text input
        self.text_ctrl = wx.TextCtrl(self, value=default_value)
        sizer.Add(self.text_ctrl, 0, wx.EXPAND | wx.ALL, 10)

        # Buttons
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        ok_button = wx.Button(self, wx.ID_OK, ok_label)
        cancel_button = wx.Button(self, wx.ID_CANCEL, cancel_label)

        ok_button.SetDefault()  # OK as default button

        button_sizer.Add(ok_button, 0, wx.ALL, 5)
        button_sizer.Add(cancel_button, 0, wx.ALL, 5)

        sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)

        self.SetSizer(sizer)
        self.Fit()
        self.Center()

        self.text_ctrl.SetFocus()

    def GetValue(self):
        """
        Return value from custom text entry dialog.

        Returns:
            str: Current text entered by the user.
        """
        return self.text_ctrl.GetValue()

Text entry dialog with custom button labels.

The dialog mirrors the simple wx.TextEntryDialog workflow while allowing callers to provide custom OK and Cancel labels.

Initialize custom text entry dialog.

Args

parent
Parent window (MainFrame)
message
text input message
caption
text input caption
default_value
optional default value
ok_label
optional OK button label
cancel_label
optional cancel button label

Ancestors

  • wx._core.Dialog
  • wx._core.TopLevelWindow
  • wx._core.NonOwnedWindow
  • wx._core.Window
  • wx._core.WindowBase
  • wx._core.EvtHandler
  • wx._core.Object
  • wx._core.Trackable
  • sip.wrapper
  • sip.simplewrapper

Methods

def GetValue(self)
Expand source code
def GetValue(self):
    """
    Return value from custom text entry dialog.

    Returns:
        str: Current text entered by the user.
    """
    return self.text_ctrl.GetValue()

Return value from custom text entry dialog.

Returns

str
Current text entered by the user.
class UniversalListCtrl (parent,
size=wx.Size(-1, -1),
style=134217760,
checkboxes=False,
force_dataview=False)
Expand source code
class UniversalListCtrl:
    """
    Cross-platform list wrapper with optional checkbox support.

    wx.ListCtrl is used on Windows, while wx.dataview.DataViewListCtrl is used
    on Linux and macOS for better accessibility. The wrapper normalizes list
    operations used by applications so callers do not need to branch on the
    active control type.
    """

    EVT_ITEM_CHECKED = wx.NewEventType()

    def __init__(
        self,
        parent,
        size=wx.DefaultSize,
        style=wx.LC_REPORT | wx.BORDER_SUNKEN,
        checkboxes=False,
        force_dataview=False
    ):
        """
        Initialize universal list control.

        Args:
            parent: Parent wx window.
            size: Initial control size.
            style: wx style flags applied to the underlying list control.
            checkboxes: Whether rows should support checked/unchecked state.
            force_dataview: Force DataViewListCtrl even on platforms that would
                normally use wx.ListCtrl.
        """
        # We use DataViewListCtrl for Linux and macOS unless forced (better accessibility)
        self.use_dataview = sys.platform.startswith('linux') or sys.platform == 'darwin' or force_dataview
        self.checkboxes = checkboxes
        self._checkbox_column = None
        self._row_data = []

        if self.use_dataview:
            self.control = dv.DataViewListCtrl(parent, style=style, size=size)
        else:
            self.control = wx.ListCtrl(parent, style=style, size=size)
            if self.checkboxes:
                self.control.EnableCheckBoxes()

    def _format_row(self, entry):
        """
        Convert row values into the representation expected by the active
        control.
        """
        return [
            bool(item) if col_idx == self._checkbox_column else str(item)
            for col_idx, item in enumerate(entry)
        ]

    def _is_valid_row(self, index):
        """
        Return whether a row index points to an existing row.
        """
        return 0 <= index < self.GetItemCount()

    def InsertColumn(self, col, heading, width=wx.LIST_AUTOSIZE, checkbox=False):
        """
        Insert a column in the underlying control.

        Args:
            col: Zero-based column index.
            heading: Text shown in the column header.
            width: Column width or wx autosize constant.
            checkbox: Whether this column stores checkbox/toggle values.
        """
        if self.use_dataview:
            if checkbox:
                self.control.AppendToggleColumn(
                    heading,
                    mode=dv.DATAVIEW_CELL_ACTIVATABLE,
                    width=width
                )
                self._checkbox_column = col
            else:
                self.control.AppendTextColumn(heading, width=width)
        else:
            self.control.InsertColumn(col, heading, width=width)
            if checkbox:
                self._checkbox_column = col

    def Append(self, entry):
        """
        Adds a row to the list.
        'entry' must be a list or tuple of values matching the column count.

        Args:
            entry: Sequence of row values. Checkbox columns should contain
                truthy or falsy values; text columns are converted to strings.
        """
        if self.use_dataview:
            # DataViewListCtrl.AppendItem expects exactly one argument: a sequence
            # Keep toggle columns as bool and format text columns as strings.
            self.control.AppendItem(self._format_row(entry))
        else:
            # ListCtrl: Insert the first item, then set sub-items
            index = self.control.GetItemCount()
            if self._checkbox_column == 0:
                self.control.InsertItem(index, "")
                self.control.CheckItem(index, bool(entry[0]))
                text_start_col = 1
            else:
                self.control.InsertItem(index, str(entry[0]))
                text_start_col = 1
            for col_idx in range(text_start_col, len(entry)):
                self.control.SetItem(index, col_idx, str(entry[col_idx]))

        self._row_data.append(None)

    def AppendRow(self, entry, data=None):
        """
        Append a row and optionally attach application data to it.

        Args:
            entry: Sequence of row values.
            data: Optional application-specific object associated with the row.
        """
        self.Append(entry)
        self.SetRowData(self.GetItemCount() - 1, data)

    def InsertRow(self, index, entry, data=None):
        """
        Insert a row at a specific index.

        Args:
            index: Zero-based insertion index. Values outside the list are
                clamped to the beginning or end.
            entry: Sequence of row values.
            data: Optional application-specific object associated with the row.
        """
        index = max(0, min(index, self.GetItemCount()))

        if self.use_dataview:
            self.control.InsertItem(index, self._format_row(entry))
        else:
            if self._checkbox_column == 0:
                self.control.InsertItem(index, "")
                self.control.CheckItem(index, bool(entry[0]))
                text_start_col = 1
            else:
                self.control.InsertItem(index, str(entry[0]))
                text_start_col = 1
            for col_idx in range(text_start_col, len(entry)):
                self.control.SetItem(index, col_idx, str(entry[col_idx]))

        self._row_data.insert(index, data)

    def DeleteRow(self, index):
        """
        Delete a row by index.

        Args:
            index: Zero-based row index to delete.

        Returns:
            bool: True when a row was deleted, otherwise False.
        """
        if not self._is_valid_row(index):
            return False

        self.control.DeleteItem(index)
        if index < len(self._row_data):
            del self._row_data[index]
        return True

    def DeleteAllItems(self):
        """
        Delete all rows from the list.
        """
        self.control.DeleteAllItems()
        self._row_data.clear()

    def Clear(self):
        """
        Delete all rows from the list.
        """
        self.DeleteAllItems()

    def GetItemText(self, row, col=0):
        """
        Return the text shown in a cell.

        Args:
            row: Zero-based row index.
            col: Zero-based column index.

        Returns:
            str: Cell text, or an empty string for invalid rows.
        """
        if not self._is_valid_row(row):
            return ""

        if self.use_dataview:
            if col == self._checkbox_column:
                return str(self.control.GetToggleValue(row, col))
            return self.control.GetTextValue(row, col)
        return self.control.GetItemText(row, col)

    def SetItemText(self, row, col, text):
        """
        Set the text/value shown in a cell.

        Args:
            row: Zero-based row index.
            col: Zero-based column index.
            text: New cell value. Checkbox columns interpret this as bool.
        """
        if not self._is_valid_row(row):
            return

        if col == self._checkbox_column:
            self.SetChecked(row, bool(text))
        elif self.use_dataview:
            self.control.SetTextValue(str(text), row, col)
        else:
            self.control.SetItem(row, col, str(text))

    def GetRow(self, row):
        """
        Return all cell values for a row.

        Args:
            row: Zero-based row index.

        Returns:
            list: Row values. Checkbox columns are returned as bool values.
        """
        if not self._is_valid_row(row):
            return []

        values = []
        for col in range(self.GetColumnCount()):
            if col == self._checkbox_column:
                values.append(self.IsChecked(row))
            else:
                values.append(self.GetItemText(row, col))
        return values

    def SetRow(self, row, values):
        """
        Replace the visible values in an existing row.

        Args:
            row: Zero-based row index.
            values: Sequence of new row values.
        """
        if not self._is_valid_row(row):
            return

        for col, value in enumerate(values):
            self.SetItemText(row, col, value)

    def SetRowData(self, row, data):
        """
        Associate arbitrary application data with a row.

        Args:
            row: Zero-based row index.
            data: Application-specific object to store.
        """
        if not self._is_valid_row(row):
            return

        while len(self._row_data) < self.GetItemCount():
            self._row_data.append(None)
        self._row_data[row] = data

    def GetRowData(self, row):
        """
        Return application data associated with a row.

        Args:
            row: Zero-based row index.

        Returns:
            object: Stored row data, or None when no data is stored.
        """
        if not self._is_valid_row(row) or row >= len(self._row_data):
            return None
        return self._row_data[row]

    def Bind(self, event_type, handler):
        """
        Unifies binding for common list events.

        Args:
            event_type: wx event binder or UniversalListCtrl event type.
            handler: Callable that receives the normalized event.
        """
        if event_type == wx.EVT_LIST_ITEM_SELECTED:
            if self.use_dataview:
                # Map DataView selection to List selection logic
                self.control.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED,
                                  lambda evt: self._handle_selection(evt, handler))
            else:
                self.control.Bind(wx.EVT_LIST_ITEM_SELECTED, handler)

        elif event_type in [wx.EVT_CONTEXT_MENU, wx.EVT_CHAR_HOOK]:
            # These are standard wx.Window events, no mapping needed
            self.control.Bind(event_type, handler)

        elif event_type == self.EVT_ITEM_CHECKED:
            if self.use_dataview:
                self.control.Bind(
                    dv.EVT_DATAVIEW_ITEM_VALUE_CHANGED,
                    lambda evt: self._handle_check(evt, handler)
                )
            else:
                self.control.Bind(
                    wx.EVT_LIST_ITEM_CHECKED,
                    lambda evt: self._handle_check(evt, handler)
                )
                self.control.Bind(
                    wx.EVT_LIST_ITEM_UNCHECKED,
                    lambda evt: self._handle_check(evt, handler)
                )

        else:
            # Fallback for other events
            self.control.Bind(event_type, handler)

    def _handle_selection(self, evt, user_handler):
        """
        Internal helper to normalize DataViewEvent so it feels
        closer to a ListEvent for the handler.

        Args:
            evt: Native DataView selection event.
            user_handler: Original event handler supplied by the caller.
        """
        # Accessibility: Ensure screen reader focus remains stable
        # while processing selection logic.
        if self.use_dataview:
            item = evt.GetItem()
            if item.IsOk():
                # We can inject a 'GetIndex' method into the event object
                # to mimic ListEvent if necessary, or just call the handler.
                evt.GetIndex = lambda: self.control.ItemToRow(item)

        user_handler(evt)

    def _handle_check(self, evt, user_handler):
        """
        Normalize checkbox/toggle events across ListCtrl and DataViewListCtrl.

        Args:
            evt: Native checkbox or DataView value-changed event.
            user_handler: Original event handler supplied by the caller.
        """
        row = -1
        checked = False

        if self.use_dataview:
            if evt.GetColumn() != self._checkbox_column:
                evt.Skip()
                return
            item = evt.GetItem()
            if item.IsOk():
                row = self.control.ItemToRow(item)
                checked = self.control.GetToggleValue(row, self._checkbox_column)
        else:
            row = evt.GetIndex()
            checked = self.control.IsItemChecked(row)

        evt.GetIndex = lambda: row
        evt.IsChecked = lambda: checked
        user_handler(evt)

    def GetSelectedRow(self):
        """
        Return the currently selected row index.

        Returns:
            int: Selected row index, or -1 when no row is selected.
        """
        if self.use_dataview:
            item = self.control.GetSelection()
            return self.control.ItemToRow(item) if item.IsOk() else -1
        else:
            return self.control.GetFirstSelected()

    def GetSelectedRows(self):
        """
        Return all selected row indices.

        Returns:
            list[int]: Selected row indices.
        """
        if self.use_dataview:
            selections = self.control.GetSelections()
            return [
                self.control.ItemToRow(item)
                for item in selections
                if item.IsOk()
            ]

        rows = []
        row = self.control.GetFirstSelected()
        while row != -1:
            rows.append(row)
            row = self.control.GetNextSelected(row)
        return rows

    def IsSelected(self, index):
        """
        Return whether a row is currently selected.
        """
        if not self._is_valid_row(index):
            return False

        if self.use_dataview:
            item = self.control.RowToItem(index)
            return item.IsOk() and self.control.IsSelected(item)
        return self.control.GetItemState(index, wx.LIST_STATE_SELECTED) != 0

    def GetItemCount(self):
        """
        Return the total number of items in the list.

        Returns:
            int: Number of rows in the underlying control.
        """
        return self.control.GetItemCount()

    def SelectRow(self, index):
        """
        Selects and focuses a row by index.

        Args:
            index: Zero-based row index to select.
        """
        if index < 0 or index >= self.GetItemCount():
            return

        if self.use_dataview:
            # Try to avoid ATK noise
            if self.control.GetColumnCount() > 0:
                item = self.control.RowToItem(index)
                if item.IsOk():
                    self.control.Select(item)
                    self.control.EnsureVisible(item)
        else:
            self.control.Select(index)
            self.control.Focus(index)

    def SelectRows(self, indices):
        """
        Select multiple rows.

        Args:
            indices: Iterable of zero-based row indices.
        """
        self.ClearSelection()
        for index in indices:
            if not self._is_valid_row(index):
                continue
            if self.use_dataview:
                item = self.control.RowToItem(index)
                if item.IsOk():
                    self.control.Select(item)
            else:
                self.control.Select(index)

    def ClearSelection(self):
        """
        Clear the current row selection.
        """
        if self.use_dataview:
            self.control.UnselectAll()
        else:
            for index in self.GetSelectedRows():
                self.control.Select(index, False)

    def SetFocus(self):
        """
        Move keyboard focus to list control.
        """
        self.control.SetFocus()

    def FocusRow(self, index):
        """
        Move keyboard focus to a row without changing its checked state.
        """
        if not self._is_valid_row(index):
            return

        if self.use_dataview:
            item = self.control.RowToItem(index)
            if item.IsOk():
                self.control.SetCurrentItem(item)
                self.control.EnsureVisible(item)
        else:
            self.control.Focus(index)

    def EnsureVisible(self, index):
        """
        Scroll a row into view.
        """
        if not self._is_valid_row(index):
            return

        if self.use_dataview:
            item = self.control.RowToItem(index)
            if item.IsOk():
                self.control.EnsureVisible(item)
        else:
            self.control.EnsureVisible(index)

    def GetFocusedRow(self):
        """
        Return the row that currently has keyboard focus.

        Returns:
            int: Focused row index, or -1 when no row has focus.
        """
        if self.use_dataview:
            item = self.control.GetCurrentItem()
            return self.control.ItemToRow(item) if item.IsOk() else -1
        return self.control.GetFocusedItem()

    def GetColumnCount(self):
        """
        Return the number of columns.
        """
        return self.control.GetColumnCount()

    def SetColumnWidth(self, col, width):
        """
        Set the width of a column.
        """
        if col < 0 or col >= self.GetColumnCount():
            return

        if self.use_dataview:
            self.control.GetColumn(col).SetWidth(width)
        else:
            self.control.SetColumnWidth(col, width)

    def GetColumnWidth(self, col):
        """
        Return the width of a column.
        """
        if col < 0 or col >= self.GetColumnCount():
            return 0

        if self.use_dataview:
            return self.control.GetColumn(col).GetWidth()
        return self.control.GetColumnWidth(col)

    def AutosizeColumn(self, col):
        """
        Resize a column to fit its content.
        """
        if col < 0 or col >= self.GetColumnCount():
            return

        self.SetColumnWidth(col, wx.LIST_AUTOSIZE)

    def Freeze(self):
        """
        Freeze visual updates for batch changes.
        """
        self.control.Freeze()

    def Thaw(self):
        """
        Resume visual updates after batch changes.
        """
        self.control.Thaw()

    @contextmanager
    def batch_update(self):
        """
        Context manager that freezes the control during multiple updates.
        """
        self.Freeze()
        try:
            yield self
        finally:
            self.Thaw()

    def SetChecked(self, index, checked):
        """
        Set a row checkbox/toggle value without changing selection.

        Args:
            index: Zero-based row index to update.
            checked: New checked state.
        """
        if self._checkbox_column is None or index < 0 or index >= self.GetItemCount():
            return

        if self.use_dataview:
            self.control.SetToggleValue(bool(checked), index, self._checkbox_column)
        else:
            self.control.CheckItem(index, bool(checked))

    def IsChecked(self, index):
        """
        Return the row checkbox/toggle state.

        Args:
            index: Zero-based row index to inspect.

        Returns:
            bool: True when the row is checked, otherwise False.
        """
        if self._checkbox_column is None or index < 0 or index >= self.GetItemCount():
            return False

        if self.use_dataview:
            return self.control.GetToggleValue(index, self._checkbox_column)
        return self.control.IsItemChecked(index)

    def GetControl(self):
        """
        Return the wrapped wx control.

        Returns:
            wx.Window: Underlying wx.ListCtrl or DataViewListCtrl instance.
        """
        return self.control

Cross-platform list wrapper with optional checkbox support.

wx.ListCtrl is used on Windows, while wx.dataview.DataViewListCtrl is used on Linux and macOS for better accessibility. The wrapper normalizes list operations used by applications so callers do not need to branch on the active control type.

Initialize universal list control.

Args

parent
Parent wx window.
size
Initial control size.
style
wx style flags applied to the underlying list control.
checkboxes
Whether rows should support checked/unchecked state.
force_dataview
Force DataViewListCtrl even on platforms that would normally use wx.ListCtrl.

Class variables

var EVT_ITEM_CHECKED

The type of the None singleton.

Methods

def Append(self, entry)
Expand source code
def Append(self, entry):
    """
    Adds a row to the list.
    'entry' must be a list or tuple of values matching the column count.

    Args:
        entry: Sequence of row values. Checkbox columns should contain
            truthy or falsy values; text columns are converted to strings.
    """
    if self.use_dataview:
        # DataViewListCtrl.AppendItem expects exactly one argument: a sequence
        # Keep toggle columns as bool and format text columns as strings.
        self.control.AppendItem(self._format_row(entry))
    else:
        # ListCtrl: Insert the first item, then set sub-items
        index = self.control.GetItemCount()
        if self._checkbox_column == 0:
            self.control.InsertItem(index, "")
            self.control.CheckItem(index, bool(entry[0]))
            text_start_col = 1
        else:
            self.control.InsertItem(index, str(entry[0]))
            text_start_col = 1
        for col_idx in range(text_start_col, len(entry)):
            self.control.SetItem(index, col_idx, str(entry[col_idx]))

    self._row_data.append(None)

Adds a row to the list. 'entry' must be a list or tuple of values matching the column count.

Args

entry
Sequence of row values. Checkbox columns should contain truthy or falsy values; text columns are converted to strings.
def AppendRow(self, entry, data=None)
Expand source code
def AppendRow(self, entry, data=None):
    """
    Append a row and optionally attach application data to it.

    Args:
        entry: Sequence of row values.
        data: Optional application-specific object associated with the row.
    """
    self.Append(entry)
    self.SetRowData(self.GetItemCount() - 1, data)

Append a row and optionally attach application data to it.

Args

entry
Sequence of row values.
data
Optional application-specific object associated with the row.
def AutosizeColumn(self, col)
Expand source code
def AutosizeColumn(self, col):
    """
    Resize a column to fit its content.
    """
    if col < 0 or col >= self.GetColumnCount():
        return

    self.SetColumnWidth(col, wx.LIST_AUTOSIZE)

Resize a column to fit its content.

def Bind(self, event_type, handler)
Expand source code
def Bind(self, event_type, handler):
    """
    Unifies binding for common list events.

    Args:
        event_type: wx event binder or UniversalListCtrl event type.
        handler: Callable that receives the normalized event.
    """
    if event_type == wx.EVT_LIST_ITEM_SELECTED:
        if self.use_dataview:
            # Map DataView selection to List selection logic
            self.control.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED,
                              lambda evt: self._handle_selection(evt, handler))
        else:
            self.control.Bind(wx.EVT_LIST_ITEM_SELECTED, handler)

    elif event_type in [wx.EVT_CONTEXT_MENU, wx.EVT_CHAR_HOOK]:
        # These are standard wx.Window events, no mapping needed
        self.control.Bind(event_type, handler)

    elif event_type == self.EVT_ITEM_CHECKED:
        if self.use_dataview:
            self.control.Bind(
                dv.EVT_DATAVIEW_ITEM_VALUE_CHANGED,
                lambda evt: self._handle_check(evt, handler)
            )
        else:
            self.control.Bind(
                wx.EVT_LIST_ITEM_CHECKED,
                lambda evt: self._handle_check(evt, handler)
            )
            self.control.Bind(
                wx.EVT_LIST_ITEM_UNCHECKED,
                lambda evt: self._handle_check(evt, handler)
            )

    else:
        # Fallback for other events
        self.control.Bind(event_type, handler)

Unifies binding for common list events.

Args

event_type
wx event binder or UniversalListCtrl event type.
handler
Callable that receives the normalized event.
def Clear(self)
Expand source code
def Clear(self):
    """
    Delete all rows from the list.
    """
    self.DeleteAllItems()

Delete all rows from the list.

def ClearSelection(self)
Expand source code
def ClearSelection(self):
    """
    Clear the current row selection.
    """
    if self.use_dataview:
        self.control.UnselectAll()
    else:
        for index in self.GetSelectedRows():
            self.control.Select(index, False)

Clear the current row selection.

def DeleteAllItems(self)
Expand source code
def DeleteAllItems(self):
    """
    Delete all rows from the list.
    """
    self.control.DeleteAllItems()
    self._row_data.clear()

Delete all rows from the list.

def DeleteRow(self, index)
Expand source code
def DeleteRow(self, index):
    """
    Delete a row by index.

    Args:
        index: Zero-based row index to delete.

    Returns:
        bool: True when a row was deleted, otherwise False.
    """
    if not self._is_valid_row(index):
        return False

    self.control.DeleteItem(index)
    if index < len(self._row_data):
        del self._row_data[index]
    return True

Delete a row by index.

Args

index
Zero-based row index to delete.

Returns

bool
True when a row was deleted, otherwise False.
def EnsureVisible(self, index)
Expand source code
def EnsureVisible(self, index):
    """
    Scroll a row into view.
    """
    if not self._is_valid_row(index):
        return

    if self.use_dataview:
        item = self.control.RowToItem(index)
        if item.IsOk():
            self.control.EnsureVisible(item)
    else:
        self.control.EnsureVisible(index)

Scroll a row into view.

def FocusRow(self, index)
Expand source code
def FocusRow(self, index):
    """
    Move keyboard focus to a row without changing its checked state.
    """
    if not self._is_valid_row(index):
        return

    if self.use_dataview:
        item = self.control.RowToItem(index)
        if item.IsOk():
            self.control.SetCurrentItem(item)
            self.control.EnsureVisible(item)
    else:
        self.control.Focus(index)

Move keyboard focus to a row without changing its checked state.

def Freeze(self)
Expand source code
def Freeze(self):
    """
    Freeze visual updates for batch changes.
    """
    self.control.Freeze()

Freeze visual updates for batch changes.

def GetColumnCount(self)
Expand source code
def GetColumnCount(self):
    """
    Return the number of columns.
    """
    return self.control.GetColumnCount()

Return the number of columns.

def GetColumnWidth(self, col)
Expand source code
def GetColumnWidth(self, col):
    """
    Return the width of a column.
    """
    if col < 0 or col >= self.GetColumnCount():
        return 0

    if self.use_dataview:
        return self.control.GetColumn(col).GetWidth()
    return self.control.GetColumnWidth(col)

Return the width of a column.

def GetControl(self)
Expand source code
def GetControl(self):
    """
    Return the wrapped wx control.

    Returns:
        wx.Window: Underlying wx.ListCtrl or DataViewListCtrl instance.
    """
    return self.control

Return the wrapped wx control.

Returns

wx.Window
Underlying wx.ListCtrl or DataViewListCtrl instance.
def GetFocusedRow(self)
Expand source code
def GetFocusedRow(self):
    """
    Return the row that currently has keyboard focus.

    Returns:
        int: Focused row index, or -1 when no row has focus.
    """
    if self.use_dataview:
        item = self.control.GetCurrentItem()
        return self.control.ItemToRow(item) if item.IsOk() else -1
    return self.control.GetFocusedItem()

Return the row that currently has keyboard focus.

Returns

int
Focused row index, or -1 when no row has focus.
def GetItemCount(self)
Expand source code
def GetItemCount(self):
    """
    Return the total number of items in the list.

    Returns:
        int: Number of rows in the underlying control.
    """
    return self.control.GetItemCount()

Return the total number of items in the list.

Returns

int
Number of rows in the underlying control.
def GetItemText(self, row, col=0)
Expand source code
def GetItemText(self, row, col=0):
    """
    Return the text shown in a cell.

    Args:
        row: Zero-based row index.
        col: Zero-based column index.

    Returns:
        str: Cell text, or an empty string for invalid rows.
    """
    if not self._is_valid_row(row):
        return ""

    if self.use_dataview:
        if col == self._checkbox_column:
            return str(self.control.GetToggleValue(row, col))
        return self.control.GetTextValue(row, col)
    return self.control.GetItemText(row, col)

Return the text shown in a cell.

Args

row
Zero-based row index.
col
Zero-based column index.

Returns

str
Cell text, or an empty string for invalid rows.
def GetRow(self, row)
Expand source code
def GetRow(self, row):
    """
    Return all cell values for a row.

    Args:
        row: Zero-based row index.

    Returns:
        list: Row values. Checkbox columns are returned as bool values.
    """
    if not self._is_valid_row(row):
        return []

    values = []
    for col in range(self.GetColumnCount()):
        if col == self._checkbox_column:
            values.append(self.IsChecked(row))
        else:
            values.append(self.GetItemText(row, col))
    return values

Return all cell values for a row.

Args

row
Zero-based row index.

Returns

list
Row values. Checkbox columns are returned as bool values.
def GetRowData(self, row)
Expand source code
def GetRowData(self, row):
    """
    Return application data associated with a row.

    Args:
        row: Zero-based row index.

    Returns:
        object: Stored row data, or None when no data is stored.
    """
    if not self._is_valid_row(row) or row >= len(self._row_data):
        return None
    return self._row_data[row]

Return application data associated with a row.

Args

row
Zero-based row index.

Returns

object
Stored row data, or None when no data is stored.
def GetSelectedRow(self)
Expand source code
def GetSelectedRow(self):
    """
    Return the currently selected row index.

    Returns:
        int: Selected row index, or -1 when no row is selected.
    """
    if self.use_dataview:
        item = self.control.GetSelection()
        return self.control.ItemToRow(item) if item.IsOk() else -1
    else:
        return self.control.GetFirstSelected()

Return the currently selected row index.

Returns

int
Selected row index, or -1 when no row is selected.
def GetSelectedRows(self)
Expand source code
def GetSelectedRows(self):
    """
    Return all selected row indices.

    Returns:
        list[int]: Selected row indices.
    """
    if self.use_dataview:
        selections = self.control.GetSelections()
        return [
            self.control.ItemToRow(item)
            for item in selections
            if item.IsOk()
        ]

    rows = []
    row = self.control.GetFirstSelected()
    while row != -1:
        rows.append(row)
        row = self.control.GetNextSelected(row)
    return rows

Return all selected row indices.

Returns

list[int]
Selected row indices.
def InsertColumn(self, col, heading, width=-1, checkbox=False)
Expand source code
def InsertColumn(self, col, heading, width=wx.LIST_AUTOSIZE, checkbox=False):
    """
    Insert a column in the underlying control.

    Args:
        col: Zero-based column index.
        heading: Text shown in the column header.
        width: Column width or wx autosize constant.
        checkbox: Whether this column stores checkbox/toggle values.
    """
    if self.use_dataview:
        if checkbox:
            self.control.AppendToggleColumn(
                heading,
                mode=dv.DATAVIEW_CELL_ACTIVATABLE,
                width=width
            )
            self._checkbox_column = col
        else:
            self.control.AppendTextColumn(heading, width=width)
    else:
        self.control.InsertColumn(col, heading, width=width)
        if checkbox:
            self._checkbox_column = col

Insert a column in the underlying control.

Args

col
Zero-based column index.
heading
Text shown in the column header.
width
Column width or wx autosize constant.
checkbox
Whether this column stores checkbox/toggle values.
def InsertRow(self, index, entry, data=None)
Expand source code
def InsertRow(self, index, entry, data=None):
    """
    Insert a row at a specific index.

    Args:
        index: Zero-based insertion index. Values outside the list are
            clamped to the beginning or end.
        entry: Sequence of row values.
        data: Optional application-specific object associated with the row.
    """
    index = max(0, min(index, self.GetItemCount()))

    if self.use_dataview:
        self.control.InsertItem(index, self._format_row(entry))
    else:
        if self._checkbox_column == 0:
            self.control.InsertItem(index, "")
            self.control.CheckItem(index, bool(entry[0]))
            text_start_col = 1
        else:
            self.control.InsertItem(index, str(entry[0]))
            text_start_col = 1
        for col_idx in range(text_start_col, len(entry)):
            self.control.SetItem(index, col_idx, str(entry[col_idx]))

    self._row_data.insert(index, data)

Insert a row at a specific index.

Args

index
Zero-based insertion index. Values outside the list are clamped to the beginning or end.
entry
Sequence of row values.
data
Optional application-specific object associated with the row.
def IsChecked(self, index)
Expand source code
def IsChecked(self, index):
    """
    Return the row checkbox/toggle state.

    Args:
        index: Zero-based row index to inspect.

    Returns:
        bool: True when the row is checked, otherwise False.
    """
    if self._checkbox_column is None or index < 0 or index >= self.GetItemCount():
        return False

    if self.use_dataview:
        return self.control.GetToggleValue(index, self._checkbox_column)
    return self.control.IsItemChecked(index)

Return the row checkbox/toggle state.

Args

index
Zero-based row index to inspect.

Returns

bool
True when the row is checked, otherwise False.
def IsSelected(self, index)
Expand source code
def IsSelected(self, index):
    """
    Return whether a row is currently selected.
    """
    if not self._is_valid_row(index):
        return False

    if self.use_dataview:
        item = self.control.RowToItem(index)
        return item.IsOk() and self.control.IsSelected(item)
    return self.control.GetItemState(index, wx.LIST_STATE_SELECTED) != 0

Return whether a row is currently selected.

def SelectRow(self, index)
Expand source code
def SelectRow(self, index):
    """
    Selects and focuses a row by index.

    Args:
        index: Zero-based row index to select.
    """
    if index < 0 or index >= self.GetItemCount():
        return

    if self.use_dataview:
        # Try to avoid ATK noise
        if self.control.GetColumnCount() > 0:
            item = self.control.RowToItem(index)
            if item.IsOk():
                self.control.Select(item)
                self.control.EnsureVisible(item)
    else:
        self.control.Select(index)
        self.control.Focus(index)

Selects and focuses a row by index.

Args

index
Zero-based row index to select.
def SelectRows(self, indices)
Expand source code
def SelectRows(self, indices):
    """
    Select multiple rows.

    Args:
        indices: Iterable of zero-based row indices.
    """
    self.ClearSelection()
    for index in indices:
        if not self._is_valid_row(index):
            continue
        if self.use_dataview:
            item = self.control.RowToItem(index)
            if item.IsOk():
                self.control.Select(item)
        else:
            self.control.Select(index)

Select multiple rows.

Args

indices
Iterable of zero-based row indices.
def SetChecked(self, index, checked)
Expand source code
def SetChecked(self, index, checked):
    """
    Set a row checkbox/toggle value without changing selection.

    Args:
        index: Zero-based row index to update.
        checked: New checked state.
    """
    if self._checkbox_column is None or index < 0 or index >= self.GetItemCount():
        return

    if self.use_dataview:
        self.control.SetToggleValue(bool(checked), index, self._checkbox_column)
    else:
        self.control.CheckItem(index, bool(checked))

Set a row checkbox/toggle value without changing selection.

Args

index
Zero-based row index to update.
checked
New checked state.
def SetColumnWidth(self, col, width)
Expand source code
def SetColumnWidth(self, col, width):
    """
    Set the width of a column.
    """
    if col < 0 or col >= self.GetColumnCount():
        return

    if self.use_dataview:
        self.control.GetColumn(col).SetWidth(width)
    else:
        self.control.SetColumnWidth(col, width)

Set the width of a column.

def SetFocus(self)
Expand source code
def SetFocus(self):
    """
    Move keyboard focus to list control.
    """
    self.control.SetFocus()

Move keyboard focus to list control.

def SetItemText(self, row, col, text)
Expand source code
def SetItemText(self, row, col, text):
    """
    Set the text/value shown in a cell.

    Args:
        row: Zero-based row index.
        col: Zero-based column index.
        text: New cell value. Checkbox columns interpret this as bool.
    """
    if not self._is_valid_row(row):
        return

    if col == self._checkbox_column:
        self.SetChecked(row, bool(text))
    elif self.use_dataview:
        self.control.SetTextValue(str(text), row, col)
    else:
        self.control.SetItem(row, col, str(text))

Set the text/value shown in a cell.

Args

row
Zero-based row index.
col
Zero-based column index.
text
New cell value. Checkbox columns interpret this as bool.
def SetRow(self, row, values)
Expand source code
def SetRow(self, row, values):
    """
    Replace the visible values in an existing row.

    Args:
        row: Zero-based row index.
        values: Sequence of new row values.
    """
    if not self._is_valid_row(row):
        return

    for col, value in enumerate(values):
        self.SetItemText(row, col, value)

Replace the visible values in an existing row.

Args

row
Zero-based row index.
values
Sequence of new row values.
def SetRowData(self, row, data)
Expand source code
def SetRowData(self, row, data):
    """
    Associate arbitrary application data with a row.

    Args:
        row: Zero-based row index.
        data: Application-specific object to store.
    """
    if not self._is_valid_row(row):
        return

    while len(self._row_data) < self.GetItemCount():
        self._row_data.append(None)
    self._row_data[row] = data

Associate arbitrary application data with a row.

Args

row
Zero-based row index.
data
Application-specific object to store.
def Thaw(self)
Expand source code
def Thaw(self):
    """
    Resume visual updates after batch changes.
    """
    self.control.Thaw()

Resume visual updates after batch changes.

def batch_update(self)
Expand source code
@contextmanager
def batch_update(self):
    """
    Context manager that freezes the control during multiple updates.
    """
    self.Freeze()
    try:
        yield self
    finally:
        self.Thaw()

Context manager that freezes the control during multiple updates.