Respond to Listctrl change exactly once
문제
I'm working on a form using wxPython where I want want listctrl's list of values to change based on the selection of another listctrl. To do this, I'm using methods linked to the controlling object's EVT_LIST_ITEM_SELECTED
and EVT_LIST_ITEM_DESELECTED
events to call Publisher.sendMessage
. The control to be changed has a method that is a subscriber to that publisher. This works: when the first listctrl is clicked, the second is refreshed.
The problem is that the data must be refreshed from the database and a message is sent for every selection and deselection. This means that even if I simply click on one item, the database gets queried twice (once for the deselection, then again for the selection). If I shift-click to multi-select 5 items, then 5 calls get made. Is there any way to have the listctrl respond to the set, rather than the individual selections?
해결책
The best solution seems to be to use wx.CallAfter
with a flag to execute the follow-up procedure exactly once:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.list_ctrl_1 = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)
self.list_ctrl_1.InsertColumn(0,"1")
self.list_ctrl_1.InsertStringItem(0,"HELLO1")
self.list_ctrl_1.InsertStringItem(0,"HELLO2")
self.list_ctrl_1.InsertStringItem(0,"HELLO3")
self.list_ctrl_1.InsertStringItem(0,"HELLO4")
self.list_ctrl_1.InsertStringItem(0,"HELLO5")
self.list_ctrl_1.InsertStringItem(0,"HELLO6")
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list_ctrl_1)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.list_ctrl_1)
self.dirty = False
def Cleanup(self, StringToPrint):
print 'No Longer Dirty!'
self.dirty = False
def OnItemSelected(self,event):
print str(self.__class__) + " - OnItemSelected"
if not self.dirty:
self.dirty = True
wx.CallAfter(self.Cleanup)
event.Skip()
def OnItemDeselected(self,event):
print str(self.__class__) + " - OnItemDeselected"
if not self.dirty:
self.dirty = True
wx.CallAfter(self.Cleanup)
event.Skip()
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
다른 팁
You can try EVT_LIST_ITEM_RIGHT_CLICK. That should work. Otherwise you'd want to use a flag and check said flag every time the selection event fires to see if it needs to query the database or not. There's also the UltimateListCtrl, a pure python widget, that you can probably hack to do this too.
You can push in a custom event handler
import wx
class MyEventHandler(wx.PyEvtHandler):
def __init__(self,target):
self.target = target
wx.PyEvtHandler.__init__(self)
def ProcessEvent(self,event):
# there must be a better way of getting the event type,
# but I couldn't find it
if event.GetEventType() == wx.EVT_LEFT_DOWN.evtType[0]:
print "Got Mouse Down event"
(item,where) = self.target.HitTest(event.GetPosition())
if item != -1:
print self.target.GetItem(item,0).GetText()
print where
else:
print "Not on list item though"
return True
else:
return False
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.list_ctrl_1 = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.myevthandler = MyEventHandler(self.list_ctrl_1)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)
self.list_ctrl_1.InsertColumn(0,"1")
self.list_ctrl_1.InsertStringItem(0,"HELLO1")
self.list_ctrl_1.PushEventHandler(self.myevthandler)
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frame_1 = MyFrame(None, -1, "")
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()