Responding to ListCtrl multi-select only once?

Does anyone know how to setup an event handler that will fire only once during a ListCtl multi-select?

Currently I’m listening for EVT_LIST_ITEM_SELECTED, but this get’s fired once for each item, even if the items are selected all at once.

Any suggestions would be greatly appreciated.

Thanks!

Keith

class MyList(wx.ListCtrl):
    def __init__(self,*args,**kwargs):
        wx.ListCtrl.__init__(self,*args,**kwargs)
        self._was_selchange = False
        self.Bind(wx.EVT_LIST_ITEM_SELECTED,self._OnSel)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED,self._OnSel)

    def _OnSel(self,evt):
        self._was_selchange = True
        wx.CallAfter(self._OnEndSel)
        evt.Skip()

    def _OnEndSel(self):
        if self._was_selchange:
            try:
                self.OnSelChange()
            finally:
                self._was_selchange = False

    def OnSelChange(self):
        print "Multi-select ended!"

Hmm. That is very close.

The event now only fires once for each select event, the item acted on is not as expected.

For example, for a list with four items, I start by selecting item 0, then shift+click o select all the way to item 4, then click item 2. What I expect to see is:

showing item 1

showing item 4 // last item click during multi-select

showing item 2

Instead, I get:

showing item 1

showing item 2

showing item 1

Code:

self._was_selchange = False

self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_start_select)

self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.on_start_select)

def on_start_select(self, evt):

self._was_selchange = True

wx.CallAfter(self.on_select, evt.GetIndex())

evt.Skip()

def on_select(self, index):

if not self._was_selchange:

return

self._was_selchange = False

print(“showing item %d” % (index + 1))

Any suggestions?

I tweaked my code based on the AUI demo so I create the panel with a def
inside the AuiFrame (I iused to have a seperate class fot that ...)

The panel is created and added as tab to the notebook sccessfully.
But when I bind the button on the new tab (the pane) to a function of
the AuiFrame
This function is ececuted right away and then breaks with an assertion
error.

So: how do i bind an event defined insise AuiFrame to that button?
I think that I do sthg wrong with my event handler, i.e. do not call the
event with a valid event handler ... ?

So I have my AuiFrame with a notebook inside (all the AUI demo)

# have a function in side AuiFrame to
#get a dialog etc and then trigger
#the make tab function

def OnNewCalculation(self, event):
self.MkTabPaneCustomer(self,calculationname)

# AUI demo about dialog
def OnAbout(self, event):
msg = "GUI approach "
dlg = wx.MessageDialog(self, msg, "ABOUT", wx.OK | wx.ICON_INFORMATION)
if wx.Platform != '__WXMAC__':
dlg.SetFont(wx.Font(8, wx.NORMAL, wx.NORMAL, wx.NORMAL, False))
dlg.ShowModal()
dlg.Destroy()

#I add a function that creates a new tab

def MkTabPaneCustomer(self, event, calculationname):
# create the new panel on which we put our sizers and ctrls
panel = wx.Panel(self)
# create a static box with label etc to be around it all
bordername = calculationname
panel.SetBackgroundColour('White')
box1 = wx.StaticBox(panel, -1, unicode(bordername))
box1.SetForegroundColour('Red')
font = box1.GetFont()
font.SetWeight(wx.BOLD)
box1.SetFont(font)

# and put a sizer inside it
box1_sizer = wx.StaticBoxSizer(box1, wx.VERTICAL)

# the controls
save_btn = wx.Button(panel, -1, u'Save')
clear_btn = wx.Button(panel, -1, u'Clear')

# add some functionality to the buttons
# the about box shows on load and when
#ok is pressed I get the assertion error
panel.Bind(wx.EVT_BUTTON, self.OnAbout(-1))

EXCEPTION:
AssertionError:
Datei "gui2-standaloneV6.py", Zeile 3695, in <module>
app.MainLoop()
Datei "C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
Zeile 8010, in MainLoop
wx.PyApp.MainLoop(self)
Datei "C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
Zeile 7306, in MainLoop
return _core_.PyApp_MainLoop(*args, **kwargs)
Datei
"C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\lib\agw\aui\auibar.py",
Zeile 3641, in OnLeftUp
self.ProcessEvent(e)
Datei "C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
Zeile 3863, in ProcessEvent
return _core_.EvtHandler_ProcessEvent(*args, **kwargs)
Datei "gui2-standaloneV6.py", Zeile 2918, in OnNewCalculation
self.MkTabPaneCustomer(self,calculationname)
Datei "gui2-standaloneV6.py", Zeile 2974, in MkTabPaneCustomer
panel.Bind(wx.EVT_BUTTON, self.OnAbout(-1))
Datei "C:\Python27\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py",
Zeile 3917, in Bind
assert callable(handler)

···

--
--------------------------------------------------
Tobias Weber
CEO

The ROG Corporation GmbH
Donaustaufer Str. 200
93059 Regensburg
Tel: +49 941 4610 57 55
Fax: +49 941 4610 57 56

www.roglink.com

GeschŠftsfŸhrer: Tobias Weber
Registergericht: Amtsgericht Regensburg - HRB 8954
UStID DE225905250 - Steuer-Nr.184/59359
--------------------------------------------------

Almost there:

self._select_stack = []

def on_start_select(self, evt):

    self._select_stack.append(evt.GetIndex())

    wx.CallAfter(self.on_select) 

    evt.Skip()

    

def on_select(self):

    if not self._select_stack:

        return

    last_selected = self._select_stack.pop()

    print("showing item %d" % (index + 1))

    self._select_stack = []

This seems to work in all cases except when I shift-click to select from the bottom of the list => top of the list. Instead of acting on the index I last-clicked (e.g. 0) it acts on the first index selected moving from the bottom to the top of the list (e.g. item 3).

Any ideas?

Any ideas?

Yes. Save the list of selected items into a variable. When the multi-
select finished, get the list again and compare them.

> Any ideas?

Yes. Save the list of selected items into a variable. When the multi-
select finished, get the list again and compare them.

Also think about this: with one multi select operation, you might be
able select and deselect many items at the same time. In this event
handler, you can calculate a set of selected and a set of deselected
items.

Try this:

import sys
import wx

class MyList(wx.ListCtrl):
    def __init__(self,*args,**kwargs):
        wx.ListCtrl.__init__(self,*args,**kwargs)
        self._was_selchange = False
        self._before_selchange = self.GetSelectedItems()
        self.Bind(wx.EVT_LIST_ITEM_SELECTED,self._OnSel)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED,self._OnSel)

    def _OnSel(self,evt):
        self._was_selchange = True
        wx.CallAfter(self._OnEndSel)
        evt.Skip()

    def _OnEndSel(self):
        if self._was_selchange:
            try:
                after_selchange = self.GetSelectedItems()

self.OnSelChange(self._before_selchange,after_selchange)
            finally:
                self._was_selchange = False
                self._before_selchange = after_selchange

    def GetSelectedItems(self):
        res = set()
        for idx in xrange(self.GetItemCount()):
            if self.GetItemState(idx,wx.LIST_STATE_SELECTED) ==
wx.LIST_STATE_SELECTED:
                res.add(idx)
        return res

    def OnSelChange(self,before,after):
        print "Multi-select ended!"
        print " Selected:", after-before
        print " De-selected:", before-after

class MyFrame(wx.Frame):

    def __init__(self, parent, id=-1, title='MultiSelect test',
                 pos=wx.DefaultPosition, size=(500, 600),
                 style=wx.DEFAULT_FRAME_STYLE):
        wx.Frame.__init__(self, parent, id, title, pos, size, style)
        lst = MyList(self,-1,style=wx.LC_REPORT)
        lst.InsertColumn(0, "Column 01")
        lst.SetColumnWidth(0,400)
        for i in range(100):
            lst.InsertStringItem(sys.maxint, "%s poaWERAKMRGGADLFKV
ADFBVADFB"%i)
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, event):
        self.Destroy()

app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()