Home > Mobile >  Python on Windows: wx.CheckListBox from wxPython doesn't behave as checkable listbox but as an
Python on Windows: wx.CheckListBox from wxPython doesn't behave as checkable listbox but as an

Time:08-21

In Python 3.10.5 on Windows 11 using the wxPython 4.2.0 package, I am trying to create a listbox with checkable items. For this purpose, wxPython should offer the wx.CheckListBox widget. The problem is that when I create this widget, from the screen reader perspective, it works as an ordinary listbox, like if I created wx.ListBox instead. That is, when I navigate within a wx.CheckListBoxwidget using the arrow keys, the screen reader does not narrate neither the checked nor selected state of the list items, though I verified that spacebar actually toggles the selection but no screen reader feedback is given. A simplified code I am using for such widget createion is the following:

checkListBox = wx.CheckListBox(self.panel, -1, choices=['first', 'second', 'third'])

The questions therefore are: How can I create a wx.CheckListBox widget which would behave as a checkable listbox using the screen reader, or is there any other way how to create such widget in an accessible manner, apart from using wx.CheckListBox? Is there some style which should be applied to the widget so the list items become checkable?

The only resources I could find for this issue are the following, but I have not been able to use them to resolve my problem:

Update

So based on the first anser to this post, what I want to achieve should be possible using a modified version of the wx.ListCtrl widget and some wxPython mixins. But I am unable to make it work. So far, I've got to the following code:

import wx
import wx.lib.mixins.listctrl as listmix

class CheckboxListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin, listmix.ListCtrlAutoWidthMixin):

    def __init__ (self, *args, **kwargs):
        wx.ListCtrl.__init__(self, *args, **kwargs)
        listmix.CheckListCtrlMixin.__init__(self)
        listmix.ListCtrlAutoWidthMixin.__init__(self)
      
      # Elsewhere in the code, I am using the class as follows:
      
      choices = ['first', 'second', 'third']
list = CheckboxListCtrl(self.panel, wx.ID_ANY, style=wx.LC_LIST)
for choice in choices:
    self.showAppsCheckListbox.Append([choice])

But such code still creates only an ordinary listbox, like I described above,. Please, how can I add accessible checkboxes to the items of my CheckListCtrl widget?

CodePudding user response:

This is an already known issue and I myself asked about it on the WXWidgets mailing-list a while ago.

In reality, wxCheckListBox is in fact an owner drawn list box. An owner drawn list box is a list box where the items are manually drawn by the program, rather than automatically drawn by windows. This allows items to be much more than just ordinary text. It's commonly used to draw small icons and/or draw different items with different colors.

IN essence, checkboxes are drawn along with the text with that technique, and of course, the scren reader has no idea of what is drawn by the program.

There are two solutions to make it accessible:

  • Implement wxAccessible and provide information to screen reader that way
  • Replace wxCheckListBox with wxListCtrl/wxListView

You can implement the wxAccessible interface and then call SetAccessible() method of the wxCheckListBox with an instance of it. However, implementing the different methods of the interface the proper way is quite difficult, unintuitive and tedious. The documentation isn't very precise for wxAccessible and you will probably need to dig into MSAA in the MSDN.

It's much simpler to replace your wxCheckListBox with a wxListCtrl or wxListView. Call EnableCheckboxes() to make the checkboxes appear and actually transform it into a checkable list box.

By the way, it works also with wxTreeCtrl, just in case. For wxTreeCtrl, you must use state images, where 0=unchecked, 1=checked and 2=partially checked. You must program yourself state transitionning using the keyboard (with space key). You also need to program yourself the checkbox logic for example if you want all child items to be automatically checked/unchecked when the parent is.

When I asked why they didn't use wxListCtrl as a base for their wxCheckListBox, the reason is essentially historical. WXWidgets is 25 years old and that control didn't exists in earlier versions of windows.

CodePudding user response:

So the problem can be solved using the wx.ListCtrl widget instead of wx.CheckListBox, but since certain version of wxPython 4, a custom Class inheriting from wx.ListCtrl and the wxPyhton mixins mentioned in the question is not necessary - the support for checkboxes has been built into wx.ListCtrl. The whole trick for turning wx.ListCtrl into a list view with checkboxes lies in the use of the EnableCheckBoxes() method call. Therefore the widget can be created and check/uncheck events can be bound and handled like this:

list = wx.ListCtrl(panel, wx.ID_ANY)
list.EnableCheckBoxes()

list.Append(['First'])
list.Append(['Second'])
list.Append(['Third'])

list.Bind(wx.EVT_LIST_ITEM_CHECKED, onItemCheck)
list.Bind(wx.EVT_LIST_ITEM_UNCHECKED, onItemUncheck)

def onItemCheck(self, event):
    print(event.Item   '. item has been checked')

def onItemUncheck(self, event):
    print(event.Item   '. item has been unchecked')

For completeness, in the following way, you can find out which items are checked.

for index in range(list.GetItemCount()):
    if list.IsItemChecked(index):
            print(index   '. item is checked')
    else:
            print(index   '. item is not checked')

An finally, if you want to check an item programmatically, e.g. the third item, you can do:

list.CheckItem(2)# 2 because the index starts with 0
  • Related