I want to make editable ListCtrl which accept only numbers from user . I have this code :
import wx
import wx.lib.mixins.listctrl as listmix
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
''' TextEditMixin allows any column to be edited. '''
#----------------------------------------------------------------------
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
"""Constructor"""
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.TextEditMixin.__init__(self)
def OpenEditor(self, col, row):
# '''Enable the editor for the column 2(year)'''
if col == 2 :
self._editing = (col, row)
listmix.TextEditMixin.OpenEditor(self, col, row)
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
rows = [("Ford", "Taurus", "1996", "Blue"),
("Nissan", "370Z", "2010", "Green"),
("Porche", "911", "2009", "Red")
]
self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
for row in rows:
self.list_ctrl.InsertStringItem(index, row[0])
self.list_ctrl.SetStringItem(index, 1, row[1])
self.list_ctrl.SetStringItem(index, 2, row[2])
self.list_ctrl.SetStringItem(index, 3, row[3])
index = 1
self.list_ctrl.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnUpdate)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
def OnUpdate(self, event):
row_id = event.GetIndex() #Get the current row
col_id = event.GetColumn () #Get the current column
new_data = event.GetLabel() #Get the changed data
item = self.list_ctrl.GetItem(row_id, col_id)
OldData= item .GetText()
try :
new_data_int = int(new_data)#check if user enter number or not
except: #if not , add the old data again
self.list_ctrl.SetStringItem(row_id,col_id,OldData)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
But when I try to add the old data again :
self.list_ctrl.SetStringItem(row_id,col_id,OldData)
ListCtrl save the change from user (ListCtrl does not add the old data) , what can I do to make ListCtrl add the old data OR is there another way to Make wxPython editable ListCtrl accept only numbers from user?
CodePudding user response:
You can make use of the CloseEditor
function in the mixin to check for validity.
Although this version is simplistic and only works for the one item. You'd have to do some work if you wanted to extend it.
import wx
import wx.lib.mixins.listctrl as listmix
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.TextEditMixin.__init__(self)
self.parent = parent
self.OrigData = 0
def OpenEditor(self, col, row):
# Enable the editor for the column 2 (year)
lc = self.parent.list_ctrl
item = lc.GetItem(row, col)
listmix.TextEditMixin.OpenEditor(self, col, row)
self.Historic = self.OrigData
curr_data = item.GetText()
if not curr_data.isnumeric():
self.OrigData = self.Historic
else:
self.OrigData = curr_data
def CloseEditor(self, event=None):
text = self.editor.GetValue()
if not text.isnumeric():
self.editor.SetValue(self.OrigData)
wx.MessageBox('Non-Numeric entry ' text "\nResetting to " str(self.OrigData), \
'Error', wx.OK | wx.ICON_ERROR)
listmix.TextEditMixin.CloseEditor(self, event)
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
rows = [("Ford", "Taurus", "1996", "Blue"),
("Nissan", "370Z", "2010", "Green"),
("Porche", "911", "2009", "Red")
]
self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
for row in rows:
self.list_ctrl.InsertItem(index, row[0])
self.list_ctrl.SetItem(index, 1, row[1])
self.list_ctrl.SetItem(index, 2, row[2])
self.list_ctrl.SetItem(index, 3, row[3])
index = 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
self.list_ctrl.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnVetoItems)
def OnVetoItems(self, event):
if event.Column != 2:
event.Veto()
return
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control")
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
Edited to simplify the code, where I got confused by the fact that CloseEditor
gets called twice, once for the control and again for a Focus event, which could result in non-numeric data still be written.
CodePudding user response:
Here's a slightly improved version that, depending on the column (defined by you), will validate a numeric or a date format (again defined by you).
Columns with an asterisk* are editable
import wx
import wx.lib.mixins.listctrl as listmix
import datetime
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.TextEditMixin.__init__(self)
self.parent = parent
self.OrigData = ""
self.col = None
def OpenEditor(self, col, row):
# Enable the editor for the column
lc = self.parent.list_ctrl
item = lc.GetItem(row, col)
listmix.TextEditMixin.OpenEditor(self, col, row)
self.col = col # column is used for type of validity check
curr_data = item.GetText()
self.OrigData = curr_data # original data to swap back in case of error
def CloseEditor(self, event=None):
text = self.editor.GetValue()
swap = False
# Numeric Check
if self.col in self.parent.lc_edit_numeric_cols:
if not text.isnumeric():
mess = "Not Numeric\n"
swap = True
# Date check
if self.col in self.parent.lc_edit_date_cols:
try:
datetime.datetime.strptime(text, '%Y/%m/%d')
except Exception as e:
mess = str(e) "\n"
swap = True
if swap: # Invalid data error swap back original data
self.editor.SetValue(self.OrigData)
wx.MessageBox(mess 'Invalid: ' text "\nResetting to " str(self.OrigData), \
'Error', wx.OK | wx.ICON_ERROR)
listmix.TextEditMixin.CloseEditor(self, event)
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
rows = [("Ford", "Taurus", "1996/01/01", "Blue", "3"),
("Nissan", "370Z", "2010/11/22", "Green", "1"),
("Porche", "911", "2009/02/28", "Red", "10")
]
self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Date*")
self.list_ctrl.InsertColumn(3, "Color")
self.list_ctrl.InsertColumn(4, "Model No*")
self.list_ctrl.InsertColumn(5, "Integer*")
self.list_ctrl.InsertColumn(6, "Text*")
index = 0
for row in rows:
self.list_ctrl.InsertItem(index, row[0])
self.list_ctrl.SetItem(index, 1, row[1])
self.list_ctrl.SetItem(index, 2, row[2])
self.list_ctrl.SetItem(index, 3, row[3])
self.list_ctrl.SetItem(index, 4, row[4])
self.list_ctrl.SetItem(index, 5, str(index))
self.list_ctrl.SetItem(index, 6, "")
index = 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
self.list_ctrl.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnVetoItems)
# Note columns that require validation for editablelistctrl
# here columns 4 & 5 should be numeric and column 2 should be a date
self.lc_edit_numeric_cols = [4,5]
self.lc_edit_date_cols = [2]
def OnVetoItems(self, event):
# Enable editing only for columns 2, 4, 5 & 6
ec = event.Column
if ec != 2 and ec != 4 and ec != 5 and ec != 6:
event.Veto()
return
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control Integer and Date checking", size=(600, -1))
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()