Home > Software engineering >  Make wxPython editable ListCtrl accept only numbers from user
Make wxPython editable ListCtrl accept only numbers from user

Time:10-07

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() 

enter image description hereenter image description here

  • Related