Make wxPython editable ListCtrl accept only numbers from user - python

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?
Edit :
I used Veto() And It is worked Thank you for your nice answers.
My code became Like this :
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
try :
new_data_int = int(new_data)#check if user enter number or not
event.Skip()
except: #if not , Kill The Edit Event
event.Veto()
########################################################################
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()

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.

Here's a slightly improved version that, depending on the column (defined by you), will validate:
Integer
Float
Date, you choose the format
Time, ditto
Range
Group
Text to upper, lower, capitalise and title
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)
#
# Validating Editable List Control Extension
#
# Author: rolfofsaxony
# Created: 8th October 2022
#
# *** Requires datetime having been imported if using "date" or "time" checks ***
#
# Create a base dictionary entry for each type of test to apply when a column is edited
# This should be amended after this class has been created
# Erroneous input can be refused, retaining the original data, with a report or silently
# Erroneous input can be simply reported as not within parameters but retained
#
# Entries for integer and float, are a simple list of columns requiring those tests
# e.g. MyListCtrl.mixin_test["integer"] = [2,5] will perform integer tests on columns 2 and 5
# if they are edited
# MyListCtrl.mixin_test["float"] = [6] would perform a float test on column 6
#
# Range and Group are a dictionary of columns, each with a list of min/max or valid entries
# e.g. MyListCtrl.mixin_test["range"] = {4:["A","D"], 5:[1,9], 6:[1.0, 1.9]}
# e.g. MyListCtrl.mixin_test["group"] = {4:["A","B","E","P"]}
#
# Range and Group can be integers, floats or strings, the test is adjusted by including the
# column number in the "integer" or "float" tests.
# For strings you may add a column "case" test for "upper", "lower" etc
# e.g. MyListCtrl.mixin_test["integer"] = [5] combined with the range entry above would ensure
# an integer test for values between 1 and 9
# e.g. MyListCtrl.mixin_test["case"] = {4:["upper"]} combined with the range or group test above
# would ensure the input is converted to uppercase before the range or group test is
# applied
#
# Date is a dictionary item of columns, each with a list item containing the date format required
# e.g. ListCtrl_name.mixin_test["date"] = {2:['%Y/%m/%d'], 3:['%Y/%m/%d']}
# *** Remember %x for locale's date format ***
#
# Picking the appropriate datetime format means that a date check can be used for input of
# Month names %B, Day names %A, Years %Y, Week numbers %W etc
#
# Time is a dictionary item of columns, each with a list item containing the time format required
# e.g. ListCtrl_name.mixin_test["time"] = {2:['%H:%M:%S'], 3:['%M:%S.%f'], 4:['%H:%M'],}
# *** Remember %X for locale's time format ***
#
# Time may also have a null format {2:[]} using an empty list
# this will utilise a generic time format checking both hh:mm:ss and mm:ss (hh:mm) for a match
#
# Case is a dictionary item of columns, each with a list item containing a string function:
# "upper", "lower", "capitalize" or "title"
# that function will be applied to the string in the given column
#
# Report should be True or False allowing Reporting of Errors or silent operation
# report is global, not for individual columns
#
# Warn should be True or False
# warn overrides erroneous data being swapped back to the original data
# A warning is issued but the erroreous data is retained
# warn is global, not for individual columns
#
# Tips should be True or False
# if tips is True simple ToolTips are constructed depending on the test type and validating rules
# tips is global, not for individual columns
#
self.mixin_test = {
"integer": [],
"float": [],
"time": {None:[],},
"date": {None:[],},
"range": {None:[],},
"group": {None:[],},
"case": {None:[],},
"report": True,
"warn": False,
"tips": True
}
def OpenEditor(self, col, row):
# Enable the editor for the column construct tooltip
listmix.TextEditMixin.OpenEditor(self, col, row)
self.col = col # column is used for type of validity check
self.OrigData = self.GetItemText(row, col) # original data to swap back in case of error
if self.mixin_test["tips"]:
tip = self.OnSetTip(tip="")
self.editor.SetToolTip(tip)
def OnSetTip(self, tip=""):
if self.col in self.mixin_test["integer"]:
tip += "Integer\n"
if self.col in self.mixin_test["float"]:
tip += "Float\n"
if self.col in self.mixin_test["date"]:
try:
format = self.mixin_test["date"][self.col][0]
format_ = datetime.datetime.today().strftime(format)
tip += "Date format "+format_
except Exception as e:
tip += "Date format definition missing "+str(e)
if self.col in self.mixin_test["time"]:
try:
format = self.mixin_test["time"][self.col][0]
format_ = datetime.datetime.today().strftime(format)
tip += "Time format "+format_
except Exception as e:
tip += "Time generic format hh:mm:ss or mm:ss"
if self.col in self.mixin_test["range"]:
try:
r_min = self.mixin_test["range"][self.col][0]
r_max = self.mixin_test["range"][self.col][1]
tip += "Range - Min: "+str(r_min)+" Max: "+str(r_max)+"\n"
except Exception as e:
tip += "Range definition missing "+str(e)
if self.col in self.mixin_test["group"]:
try:
tip += "Group: "+str(self.mixin_test["group"][self.col])+"\n"
except Exception as e:
tip += "Group definition missing "+str(e)
if self.col in self.mixin_test["case"]:
try:
tip += "Text Case "+str(self.mixin_test["case"][self.col])
except Exception as e:
tip += "Case definition missing "+str(e)
return tip
def OnRangeCheck(self, text):
head = mess = ""
swap = False
try:
r_min = self.mixin_test["range"][self.col][0]
r_max = self.mixin_test["range"][self.col][1]
except Exception as e:
head = "Range Missing - Error"
mess = "Error: "+str(e)+"\n"
swap = True
return head, mess, swap
try:
if self.col in self.mixin_test["float"]:
item = float(text)
head = "Float Range Test - Error"
elif self.col in self.mixin_test["integer"]:
item = int(text)
head = "Integer Range Test - Error"
else:
item = text
head = "Text Range Test - Error"
if item < r_min or item > r_max:
mess += text+" Out of Range: Min - "+str(r_min)+" Max - "+str(r_max)+"\n"
swap = True
except Exception as e:
head = "Range Test - Error"
mess += "Error: "+str(e)+"\n"
swap = True
return head, mess, swap
def OnDateCheck(self, text):
head = mess = ""
swap = False
try:
format = self.mixin_test["date"][self.col][0]
except Exception as e:
head = "Date Format Missing - Error"
mess = "Error: "+str(e)+"\n"
swap = True
return head, mess, swap
try:
datetime.datetime.strptime(text, format)
except Exception as e:
format_ = datetime.datetime.today().strftime(format)
head = "Date Test - Error"
mess = text+" does not match format "+format_+"\n"
swap = True
return head, mess, swap
def OnTimeCheck(self, text):
head = mess = ""
swap = False
try:
format = self.mixin_test["time"][self.col][0]
except Exception as e:
try:
datetime.datetime.strptime(text, '%H:%M:%S')
except Exception as e:
try:
datetime.datetime.strptime(text, '%M:%S')
except:
head = "Time Test - Error"
mess = "Generic Time format must be hh:mm:ss or mm:ss\n"
swap = True
return head, mess, swap
try:
datetime.datetime.strptime(text, format)
except Exception as e:
format_ = datetime.datetime.today().strftime(format)
head = "Time Test - Error"
mess = text+" does not match format "+format_+"\n"
swap = True
return head, mess, swap
def OnCaseCheck(self, text):
try:
format = self.mixin_test["case"][self.col][0]
except:
format = None
if format == "upper":
text = text.upper()
if format == "lower":
text = text.lower()
if format == "capitalize":
text = text.capitalize()
if format == "title":
text = text.title()
self.editor.SetValue(text)
return text
def OnGroupCheck(self, text):
head = mess = ""
swap = False
try:
tests = self.mixin_test["group"][self.col]
except Exception as e:
head = "Group Missing - Error"
mess = "Error: "+str(e)+"\n"
swap = True
return head, mess, swap
if text in tests:
pass
else:
head = "Group Test - Error"
mess = text+" Not in Group: "+str(tests)+"\n"
swap = True
return head, mess, swap
def CloseEditor(self, event=None):
text = self.editor.GetValue()
swap = False
warn = self.mixin_test["warn"]
report = self.mixin_test["report"]
if warn:
report = False
# Integer Check
if self.col in self.mixin_test["integer"]:
try:
int(text)
except Exception as e:
head = "Integer Test - Error"
mess = "Not Integer\n"
swap = True
# Float Check
if self.col in self.mixin_test["float"]:
try:
float(text)
except Exception as e:
head = "Float Test - Error"
mess = str(e)+"\n"
swap = True
# Time check
if self.col in self.mixin_test["time"]:
head, mess, swap = self.OnTimeCheck(text)
# Date check
if self.col in self.mixin_test["date"]:
head, mess, swap = self.OnDateCheck(text)
# Case check
if self.col in self.mixin_test["case"]:
text = self.OnCaseCheck(text)
# Range check
if not swap and self.col in self.mixin_test["range"]:
head, mess, swap = self.OnRangeCheck(text)
# Group check
if not swap and self.col in self.mixin_test["group"]:
head, mess, swap = self.OnGroupCheck(text)
if warn and swap:
wx.MessageBox(mess + 'Invalid entry: ' + text + "\n", \
head, wx.OK | wx.ICON_ERROR)
elif swap: # Invalid data error swap back original data
self.editor.SetValue(self.OrigData)
if report:
wx.MessageBox(mess + 'Invalid entry: ' + text + "\nResetting to "+str(self.OrigData), \
head, 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", "C", "1", "1.1", "12:32", "M"),
("Nissan", "370Z", "2010/11/22", "Green", "B", "2", "1.8", "10:10", "F"),
("Porche", "911", "2009/02/28", "Red", "A", "1", "1.3", "23:44", "F")
]
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, "Text*")
self.list_ctrl.InsertColumn(4, "Range*")
self.list_ctrl.InsertColumn(5, "Integer*")
self.list_ctrl.InsertColumn(6, "Float*")
self.list_ctrl.InsertColumn(7, "Time*")
self.list_ctrl.InsertColumn(8, "Group*")
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, row[5])
self.list_ctrl.SetItem(index, 6, row[6])
self.list_ctrl.SetItem(index, 7, row[7])
self.list_ctrl.SetItem(index, 8, row[8])
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)
# Here we note columns that require validation for the editablelistctrl
#
# column 2 should be a date with the format yyyy/mm/dd
# column 3 should be capitalised
# column 4 should be in a range from "A" to "D" and uppercase
# column 5 should be in a integer range from 1 to 9
# column 6 should be in a float range from 1.0 to 1.9
# column 7 should be in a time, format hh:mm
# column 8 should be in a group the M or F and upper case
self.list_ctrl.mixin_test["date"] = {2:['%Y/%m/%d']}
self.list_ctrl.mixin_test["integer"] = [5]
self.list_ctrl.mixin_test["float"] = [6]
self.list_ctrl.mixin_test["case"] = {3:["capitalize"], 4:["upper"], 8:["upper"]}
self.list_ctrl.mixin_test["range"] = {4:["A","D"], 5:[1,9], 6:[1.0, 1.9]}
self.list_ctrl.mixin_test["time"] = {7:['%H:%M']}
self.list_ctrl.mixin_test["group"] = {8:["M","F"]}
# This would be column 7 with a time format including micro seconds
#self.list_ctrl.mixin_test["time"] = {7:['%M:%S.%f']}
# This would be column 7 with a time format hh:mm:ss
#self.list_ctrl.mixin_test["time"] = {7:['%H:%M:%S']}
# This would be column 7 with a generic time format of either hh:mm:ss, hh:mm or mm:ss
#self.list_ctrl.mixin_test["time"] = {7:[]}
self.list_ctrl.mixin_test["report"] = True
#self.list_ctrl.mixin_test["warn"] = False
#self.list_ctrl.mixin_test["tips"] = False
def OnVetoItems(self, event):
# Enable editing only for columns 2 and above
if event.Column < 2:
event.Veto()
return
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Validating Editable List Control", size=(750, -1))
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()

Related

Single column QTreeview search filter

I have two questions:
I was wondering if this is the proper way to do a search/filter on a single column treeview. I feel like a lot of my copying/pasting could contain unnecessary stuff. Is all the code in the subclass of QSortFilterProxyModel and the code in the search_text_changed method needed? I don't feel like the regex is needed, since I set the filter-proxy to ignore case-sensitivity.
How can I make it so that when a user double-clicks a treeview item a signal emits a string list containing the string of the item clicked and all of its ancestors recursively? For example, if I double-clicked "Birds", it would return ['Birds','Animals']; and if I double-clicked "Animals", it would just return ['Animals'].
import os, sys
from PySide import QtCore, QtGui
tags = {
"Animals": [
"Birds",
"Various"
],
"Brick": [
"Blocks",
"Special"
],
"Manmade": [
"Air Conditioners",
"Audio Equipment"
],
"Food": [
"Fruit",
"Grains and Seeds"
]
}
class SearchProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(SearchProxyModel, self).__init__(parent)
self.text = ''
# Recursive search
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(role=QtCore.Qt.DisplayRole).lower()
condition = text.find(self.text) >= 0
if condition:
return True
for childnum in range(idx.model().rowCount(parent=idx)):
if self._accept_index(idx.model().index(childnum, 0, parent=idx)):
return True
return False
def filterAcceptsRow(self, sourceRow, sourceParent):
# Only first column in model for search
idx = self.sourceModel().index(sourceRow, 0, sourceParent)
return self._accept_index(idx)
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
return leftData < rightData
class TagsBrowserWidget(QtGui.QWidget):
clickedTag = QtCore.Signal(list)
def __init__(self, parent=None):
super(TagsBrowserWidget, self).__init__(parent)
self.resize(300,500)
# controls
self.ui_search = QtGui.QLineEdit()
self.ui_search.setPlaceholderText('Search...')
self.tags_model = SearchProxyModel()
self.tags_model.setSourceModel(QtGui.QStandardItemModel())
self.tags_model.setDynamicSortFilter(True)
self.tags_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.ui_tags = QtGui.QTreeView()
self.ui_tags.setSortingEnabled(True)
self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.ui_tags.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_tags.setHeaderHidden(True)
self.ui_tags.setRootIsDecorated(True)
self.ui_tags.setUniformRowHeights(True)
self.ui_tags.setModel(self.tags_model)
# layout
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(self.ui_search)
main_layout.addWidget(self.ui_tags)
self.setLayout(main_layout)
# signals
self.ui_tags.doubleClicked.connect(self.tag_double_clicked)
self.ui_search.textChanged.connect(self.search_text_changed)
# init
self.create_model()
def create_model(self):
model = self.ui_tags.model().sourceModel()
self.populate_tree(tags, model.invisibleRootItem())
self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
def populate_tree(self, children, parent):
for child in sorted(children):
node = QtGui.QStandardItem(child)
parent.appendRow(node)
if isinstance(children, dict):
self.populate_tree(children[child], node)
def tag_double_clicked(self, item):
text = item.data(role=QtCore.Qt.DisplayRole)
print [text]
self.clickedTag.emit([text])
def search_text_changed(self, text=None):
regExp = QtCore.QRegExp(self.ui_search.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)
self.tags_model.text = self.ui_search.text().lower()
self.tags_model.setFilterRegExp(regExp)
if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
self.ui_tags.expandAll()
else:
self.ui_tags.collapseAll()
def main():
app = QtGui.QApplication(sys.argv)
ex = TagsBrowserWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
There's no point in setting the case-sensivity of the filter-proxy at all, because you are by-passing the built-in filtering by overriding filterAcceptsRow. And even if you weren't doing that, setFilterRegExp ignores the current case sensitiviy settings anyway.
I would simplify the filter-proxy to this:
class SearchProxyModel(QtGui.QSortFilterProxyModel):
def setFilterRegExp(self, pattern):
if isinstance(pattern, str):
pattern = QtCore.QRegExp(
pattern, QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.FixedString)
super(SearchProxyModel, self).setFilterRegExp(pattern)
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(QtCore.Qt.DisplayRole)
if self.filterRegExp().indexIn(text) >= 0:
return True
for row in range(idx.model().rowCount(idx)):
if self._accept_index(idx.model().index(row, 0, idx)):
return True
return False
def filterAcceptsRow(self, sourceRow, sourceParent):
idx = self.sourceModel().index(sourceRow, 0, sourceParent)
return self._accept_index(idx)
and change the search method to this:
def search_text_changed(self, text=None):
self.tags_model.setFilterRegExp(self.ui_search.text())
if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
self.ui_tags.expandAll()
else:
self.ui_tags.collapseAll()
So now the SearchProxyModel has sole responsibilty for deciding how searches are performed via its setFilterRegExp method. The case-sensitivity is handled transparently, so there is no need to pre-process the input.
The method for getting a list of descendants, can be written like this:
def tag_double_clicked(self, idx):
text = []
while idx.isValid():
text.append(idx.data(QtCore.Qt.DisplayRole))
idx = idx.parent()
text.reverse()
self.clickedTag.emit(text)

Handling multiple QComboBox in one cell widget

Code:
class Example(QtGui.QWidget):
def __init__(self, parent):
super(Accounts, self).__init__()
self.initUI()
def initUI(self):
self.table = QtGui.QTableWidget()
self.table.setColumnCount(5)
database = 'Setting.db'
connection = sqlite3.connect(database)
cursor = connection.cursor()
cursor.execute('''
SELECT ID, User, Text
FROM Data ''')
data = cursor.fetchall()
num = 0
for row in data:
self.table.setRowCount(num+2)
self.table.setItem(num, 0, QtGui.QTableWidgetItem(str(row[0])))
self.table.setItem(num, 1, QtGui.QTableWidgetItem(str(row[1])))
self.table.setItem(num, 2, QtGui.QTableWidgetItem(str(row[2])))
self.table.setCellWidget(num, 3, Perms(self, row[0]))
save_user = QtGui.QPushButton("Save")
save_user.clicked.connect(self.save)
self.table.setCellWidget(num, 5, save_user)
num= num+1
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.table)
self.setLayout(main_layout)
def save(self):
button = self.sender()
index = self.table.indexAt(button.pos())
row = index.row()
a_id = str(self.table.item(row, 0).text())
data = Permissions(self, a_id).update_database()
class Perms(QtGui.QWidget):
def __init__(self, parent, a_id):
super(Perms, self).__init__()
self.a_id = a_id
self.init_ui()
def init_ui(self):
database = 'Settings.db'
conn = sqlite3.connect(database)
cursor = conn.cursor()
cursor.execute('''
SELECT control, sub
FROM Perms
WHERE ID= ?''', (self.a_id,))
row = cursor.fetchone()
control_label = QtGui.QLabel("Control")
sub_label = QtGui.QLabel("Sub")
self.control_options = QtGui.QComboBox()
self.control_options.addItem("Y")
self.control_options.addItem("N")
if str(row[0]) == "Y":
self.control_options.setCurrentIndex(0)
else:
self.control_options.setCurrentIndex(1)
self.sub_options = QtGui.QComboBox()
self.sub_options.addItem("Y")
self.sub_options.addItem("N")
if str(row[1]) == "Y":
self.sub_options.setCurrentIndex(0)
else:
self.sub_options.setCurrentIndex(1)
layout = QtGui.QGridLayout()
layout.addWidget(full_control_label, 1, 1)
layout.addWidget(self.full_control_options, 1, 2)
layout.addWidget(tills_label, 2, 1)
layout.addWidget(self.tills_options, 2, 2)
self.setLayout(layout)
def update_database(self):
control = str(self.control_options.currentText())
sub = str(self.sub_options.currentText())
return (control, sub)
Ignore any errors I don't specify because I wrote this quickly just for an example. The problem I'm facing is: the save button is created within the loop in order to be used multiple times. It connects to a function that gets the current text of the combo-boxes. But the problem is, if you change the current text in the combo-boxes and then click Save, it gets the text from the combo-boxes, but not the right text - you always get the same result.
For example:
QComboBox1: Y
QComboBox2: N
If we click Save, it'll return ('Y', 'N')
If we change the combobox values:
QComboBox1: N
QComboBox2: N
and click save, it'll return ('Y', 'N')
But it never returns the new values from the combo-boxes.
How can I fix it so I get the updated values rather than the old values?
The save method doesn't currently make much sense. Surely you need to get the Perm widget from the table, not make a new one every time:
def save(self):
button = self.sender()
index = self.table.indexAt(button.pos())
row = index.row()
# get the new values from the table
data = self.table.cellWidget(row, 3).update_database()

add different length columns to gtk TreeStore(Treeview)

I want to display two levels of hierarchical data using gtk Treeview(with model gtk Treestore)
The data is in the following format:
**First(parent)** level
col_a, col_b, col_c, col_d, col_e
val_a, val_b, val_c, val_d, val_e
**Second(child)** level
col_x, col_y, col_z
val_x, val_y, val_z
And the hierarchy of data is as follows:
> val_a1, val_b1, val_c1, val_d1, val_e1
val_x1, val_y1, val_z1
val_x2, val_y2, val_z2
> val_a2, val_b2, val_c2, val_s2, val_e2
val_x3, val_y3, val_z3
> val_a3, val_b3, val_c3, val_d3, val_e3
> val_a4, val_b4, val_c4, val_d4, val_e4
val_x4, val_y4, val_z4
val_x5, val_y5, val_z5
The following pygtk code is what I have tried(Modified the code from gtk tutorial)
import pygtk
pygtk.require('2.0')
import gtk
data = [
[('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('val_x1', 'val_y1', 'val_z1'), ('val_x2', 'val_y2', 'val_z2')],
[('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('val_x3', 'val_y3', 'val_z3')],
[('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
[('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('val_x4', 'val_y4', 'val_z4'), ('val_x5', 'val_y5', 'val_z5')],
]
class BasicTreeViewExample:
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Basic TreeView Example")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.treestore = gtk.TreeStore(str, str, str, str, str)
for detail in data:
for index, elem in enumerate(detail):
if index == 0:
piter = self.treestore.append(None, elem)
else:
self.treestore.append(piter, elem)
self.treeview = gtk.TreeView(self.treestore)
for i in range(5):
tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
self.treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', i)
self.window.add(self.treeview)
self.window.show_all()
def main():
gtk.main()
if __name__ == "__main__":
tvexample = BasicTreeViewExample()
main()
But, I'm getting the following error when I try running the above code:
Traceback (most recent call last):
File "test.py", line 55, in <module>
tvexample = BasicTreeViewExample()
File "test.py", line 33, in __init__
self.treestore.append(piter, detail[index])
ValueError: row sequence has wrong length
So my questions are:
How can I add data to gtk TreeStore with different number of columns in the different levels of hierarchy
Also, Is it possible to display column names for each row in the gtk treestore
i.e In the Treeview I want to see the output as follows:
col_a, col_b, col_c, col_d, col_e
> val_a1, val_b1, val_c1, val_d1, val_e1
col_x, col_y, col_z
val_x1, val_y1, val_z1
col_x, col_y, col_z
val_x2, val_y2, val_z2
col_a, col_b, col_c, col_d, col_e
> val_a2, val_b2, val_c2, val_s2, val_e2
col_x, col_y, col_z
val_x3, val_y3, val_z3
col_a, col_b, col_c, col_d, col_e
> val_a3, val_b3, val_c3, val_d3, val_e3
col_a, col_b, col_c, col_d, col_e
> val_a4, val_b4, val_c4, val_d4, val_e4
col_x, col_y, col_z
val_x4, val_y4, val_z4
col_x, col_y, col_z
val_x5, val_y5, val_z5
If this is not possible using the treeview, is there any alternative/workarounds using which I can achieve the above?
Short answers and introduction
How can I add data to gtk.TreeStore with different number of columns in the different levels of hierarchy?
Simple: you can't. GtkListStore as well as GtkTreeStore are designed to hold
data as a table. Columns are defined in a fixed way with an index and a data
type. The only difference between a ListStore and a TreeStore is that in a
TreeStore, rows have a hierarchy. Even worse, the GtkTreeView widget also
expects data to be stored as a table, as each row will unconditionally fetch
the cells using their column index, and expects to find something there.
Unless you write your own widget, but you probably don't want to
(God, this file is 16570 lines long...).
However, if you can't write your own widget, you still could write your own
model. And this will give you some flexibility.
Also, is it possible to display column names for each row in the gtk.TreeStore ?
Displaying data in the TreeView involve two components: the GtkTreeView itself,
that fetches data in the TreeStore and display them. The TreeView widget doesn't
have the feature of displaying headers for each row. But there are some tricks
to process data between the model and the view, which could end to the desired
effect, though not that nice probably.
Basics
So, the TreeView expects to work on a table of data, and we can't change that.
OK. But we still can trick it into thinking the data is a table, when actually
it isn't... Let's start with the view. We need at least five columns to display
the data of the parents. The children can then use only three columns out of
these five, so this is fine.
Note that columns of the model do not always map to a column in the tree view.
They actually map to some properties of cell renderers. For example, you can
have a column in the model that defines the background color of the row,
or a column that defines an icon to display. Columns in the view are just a way
to align groups of cell renderers, possibly under a header. But here, let's
assume all values are text that should go into a single CellRendererText in its
own column.
Parents will use all five columns while children will use only the columns 2, 3
and 4. We'll then trick the model to return an empty text when data isn't
available for the target cell.
Creating a new TreeModel
Some explainations about implementing a custom GtkTreeModel in PyGTK are
available in this tutorial.
This is a sample implementation of it:
import pygtk
pygtk.require('2.0')
import gtk
data = [
[('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('val_x1', 'val_y1', 'val_z1'), ('val_x2', 'val_y2', 'val_z2')],
[('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('val_x3', 'val_y3', 'val_z3')],
[('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
[('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('val_x4', 'val_y4', 'val_z4'), ('val_x5', 'val_y5', 'val_z5')],
]
class MyTreeModel(gtk.GenericTreeModel):
# The columns exposed by the model to the view
column_types = (str, str, str, str, str)
def __init__(self, data):
gtk.GenericTreeModel.__init__(self)
self.data = data
def on_get_flags(self):
"""
Get Model capabilities
"""
return gtk.TREE_MODEL_ITERS_PERSIST
def on_get_n_columns(self):
"""
Get number of columns in the model
"""
return len(self.column_types)
def on_get_column_type(self, n):
"""
Get data type of a specified column in the model
"""
return self.column_types[n]
def on_get_iter(self, path):
"""
Obtain a reference to the row at path. For us, this is a tuple that
contain the position of the row in the double list of data.
"""
if len(path) > 2:
return None # Invalid path
parent_idx = path[0]
if parent_idx >= len(self.data):
return None # Invalid path
first_level_list = self.data[parent_idx]
if len(path) == 1:
# Access the parent at index 0 in the first level list
return (parent_idx, 0)
else:
# Access a child, at index path[1] + 1 (0 is the parent)
child_idx = path[1] + 1
if child_idx >= len(first_level_list):
return None # Invalid path
else:
return (parent_idx, child_idx)
def on_get_path(self, iter_):
"""
Get a path from a rowref (this is the inverse of on_get_iter)
"""
parent_idx, child_idx = iter_
if child_idx == 0:
return (parent_idx, )
else:
(parent_idx, child_idx-1)
def on_get_value(self, iter_, column):
"""
This is where the view asks for values. This is thus where we
start mapping our data model to a fake table to present to the view
"""
parent_idx, child_idx = iter_
item = self.data[parent_idx][child_idx]
# For parents, map columns 1:1 to data
if child_idx == 0:
return item[column]
# For children, we have to fake some columns
else:
if column == 0 or column == 4:
return "" # Fake empty text
else:
return item[column-1] # map 1, 2, 3 to 0, 1, 2.
def on_iter_next(self, iter_):
"""
Get the next sibling of the item pointed by iter_
"""
parent_idx, child_idx = iter_
# For parents, point to the next parent
if child_idx == 0:
next_parent_idx = parent_idx + 1
if next_parent_idx < len(self.data):
return (next_parent_idx, 0)
else:
return None
# For children, get next tuple in the list
else:
next_child_idx = child_idx + 1
if next_child_idx < len(self.data[parent_idx]):
return (parent_idx, next_child_idx)
else:
return None
def on_iter_has_child(self, iter_):
"""
Tells if the row referenced by iter_ has children
"""
parent_idx, child_idx = iter_
if child_idx == 0 and len(self.data[parent_idx]) > 1:
return True
else:
return False
def on_iter_children(self, iter_):
"""
Return a row reference to the first child row of the row specified
by iter_. If iter_ is None, a reference to the first top level row
is returned. If there is no child row None is returned.
"""
if iter_ is None:
return (0, 0)
parent_idx, child_idx = iter_
if self.on_iter_has_child(iter_):
return (parent_idx, 1)
else:
return None
def on_iter_n_children(self, iter_):
"""
Return the number of child rows that the row specified by iter_
has. If iter_ is None, the number of top level rows is returned.
"""
if iter_ is None:
return len(self.data)
else:
parent_idx, child_idx = iter_
if child_idx == 0:
return len(self.data[parent_idx]) - 1
else:
return 0
def on_iter_nth_child(self, iter_, n):
"""
Return a row reference to the nth child row of the row specified by
iter_. If iter_ is None, a reference to the nth top level row is
returned.
"""
if iter_ is None:
if n < len(self.data):
return (n, 0)
else:
return None
else:
parent_idx, child_idx = iter_
if child_idx == 0:
if n+1 < len(self.data[parent_idx]):
return (parent_idx, n+1)
else:
return None
else:
return None
def on_iter_parent(self, iter_):
"""
Get a reference to the parent
"""
parent_idx, child_idx = iter_
if child_idx == 0:
return None
else:
return (parent_idx, 0)
class BasicTreeViewExample:
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Basic TreeView Example")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
# Create the model with data in it
self.model = MyTreeModel(data)
self.treeview = gtk.TreeView(self.model)
for i in range(5):
tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
self.treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'text', i)
self.window.add(self.treeview)
self.window.show_all()
def main():
gtk.main()
if __name__ == "__main__":
tvexample = BasicTreeViewExample()
main()
And the result:
Faking headers in cells
Let's now add some kind of title in each cell using the model to generate
the desired data. Full code is here.
class MyTreeModel(gtk.GenericTreeModel):
# The columns exposed by the model to the view
column_types = (str, str, str, str, str)
# Column headers
parent_headers = ("P.Col 1", "P.Col 2", "P.Col 3", "P.Col 4", "P.Col 5")
child_headers = ("C.Col 1", "C.Col 2", "C.Col 3")
...
def on_get_value(self, iter_, column):
"""
This is where the view asks for values. This is thus where we
start mapping our data model to a fake table to present to the view
"""
parent_idx, child_idx = iter_
item = self.data[parent_idx][child_idx]
# For parents, map columns 1:1 to data
if child_idx == 0:
return self.markup(item[column], column, False)
# For children, we have to fake some columns
else:
if column == 0 or column == 4:
return "" # Fake empty text
else:
# map 1, 2, 3 to 0, 1, 2.
return self.markup(item[column-1], column-1, True)
def markup(self, text, column, is_child):
"""
Produce a markup for a cell with a title and a text
"""
headers = self.child_headers if is_child else self.parent_headers
title = headers[column]
return "<b>%s</b>\n%s"%(title, text)
...
class BasicTreeViewExample:
def __init__(self):
...
self.treeview = gtk.TreeView(self.model)
self.treeview.set_headers_visible(False)
for i in range(5):
...
tvcolumn.pack_start(cell, True)
tvcolumn.add_attribute(cell, 'markup', i)
...
And the result:
Using set_cell_data_func or TreeModelFilter
Provided that you manage to fit your data into a ListStore or TreeStore, that
is to say you find a trick so that parents and children share the same amount
and type of columns, you can then manipulate data using either a
GtkTreeCellDataFunc
or a GtkTreeModelFilter.
PyGTK documentation provides examplefor Cell Data Functions
and Tree Model Filters.
Adding column headers using these concepts for example maybe easier than
creating a full custom model.
Here is the code using TreeCellDataFunc.
Note how the data input has been formatted so that children and parents have the
same amount of data. This is a condition to be able to use GtkTreeStore.
import pygtk
pygtk.require('2.0')
import gtk
data = [
[('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('', 'val_x1', 'val_y1', 'val_z1', ''), ('', 'val_x2', 'val_y2', 'val_z2', '')],
[('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('', 'val_x3', 'val_y3', 'val_z3', '')],
[('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
[('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('', 'val_x4', 'val_y4', 'val_z4', ''), ('', 'val_x5', 'val_y5', 'val_z5', '')],
]
class BasicTreeViewExample:
parent_headers = ("P.Col 1", "P.Col 2", "P.Col 3", "P.Col 4", "P.Col 5")
child_headers = ("C.Col 1", "C.Col 2", "C.Col 3")
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Basic TreeView Example")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.treestore = gtk.TreeStore(str, str, str, str, str)
for detail in data:
for index, elem in enumerate(detail):
if index == 0:
piter = self.treestore.append(None, elem)
else:
self.treestore.append(piter, elem)
self.treeview = gtk.TreeView(self.treestore)
for i in range(5):
tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
self.treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
tvcolumn.pack_start(cell, True)
# Delegate data fetching to callback
tvcolumn.set_cell_data_func(cell, self.cell_add_header, i)
self.window.add(self.treeview)
self.window.show_all()
def cell_add_header(self, treeviewcolumn, cell, model, iter_, column):
text = model.get_value(iter_, column)
if model.iter_parent(iter_) is None:
# This is a parent
title = self.parent_headers[column]
markup = "<b>%s</b>\n%s"%(title, text)
else:
# We have a child
if column == 0 or column == 4:
# Cell is not used by child, leave it empty
markup = ""
else:
title = self.child_headers[column-1]
markup = "<b>%s</b>\n%s"%(title, text)
cell.set_property('markup', markup)
def main():
gtk.main()
if __name__ == "__main__":
tvexample = BasicTreeViewExample()
main()
GtkTreeModelFilter leads to pretty much the same thing. The result is the same
than in Faking headers in cells (except that I forgot to set the headers invisible):
I hope this helped you, and the others that will have the same kind of question!

Sort items using listctrl in Python and wxpython

I am trying to sort items in a 3 column list but it's not quite working. I have it set up to populate a "table". When I first run the program, it sorts the columns no problem. However, I have an option to select another data source and the same table is populated with new data (mostly the 3rd column changes). However, now when I sort the items, it sorts based on the original data and not the newly shown data. Any help?
My code (some parts removed as not necessary):
class EditADP(wx.Dialog):
def __init__(self, parent, title):
super(EditADP, self).__init__(parent=parent,
title=title, size=(450, 700))
#Sizer Info Here
# This is where user selects new data source
try:
self.selectADP = wx.ComboBox(self,-1,tmp_default_ADP,choices=ADP_Options,style=wx.CB_READONLY)
except:
self.selectADP = wx.ComboBox(self,-1,default_ADP,choices=ADP_Options,style=wx.CB_READONLY)
#More Sizers
self.Bind(wx.EVT_COMBOBOX, self.OnselectADP, self.selectADP)
tmp_ADPselection = default_ADP
global adpdata
adpdata = {}
for i in Master_PlayerID:
adpdata[i] = (Master_1[i],Master_2[i],Master_3[tmp_ADPselection][i],i)
self.ADPlist = EditableListCtrl_editADP(self,-1,style=wx.LC_REPORT)
self.ADPlist.InsertColumn(1,'Col1',format=wx.LIST_FORMAT_CENTRE)
self.ADPlist.InsertColumn(2,'Col2',format=wx.LIST_FORMAT_CENTRE)
self.ADPlist.InsertColumn(3,'Col3',format=wx.LIST_FORMAT_CENTRE)
items = adpdata.items()
index = 0
for key, data in items:
self.ADPlist.InsertStringItem(index, Master_List[data[3]])
self.ADPlist.SetStringItem(index, 1, data[1])
self.ADPlist.SetStringItem(index, 2, str(data[2]))
self.ADPlist.SetItemData(index, key)
index += 1
self.itemDataMap = adpdata
# More sizer and button stuff here
def OnselectADP(self, event):
tmp_ADPselection = ADP_Options[event.GetSelection()]
self.ADPlist.DeleteAllItems()
global adpdata
adpdata = {}
for i in Master_PlayerID:
adpdata[i] = (Master_1[i],Master_2[i],Master_3[tmp_ADPselection][i],i)
items = adpdata.items()
index = 0
for key, data in items:
self.ADPlist.InsertStringItem(index, Master_List[data[3]])
self.ADPlist.SetStringItem(index, 1, data[1])
self.ADPlist.SetStringItem(index, 2, str(data[2]))
self.ADPlist.SetItemData(index, key)
index += 1
self.itemDataMap = adpdata
class EditableListCtrl_editADP(wx.ListCtrl, listmix.TextEditMixin, listmix.ColumnSorterMixin):
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)
listmix.ColumnSorterMixin.__init__(self, len(adpdata))
self.itemDataMap = adpdata
def GetListCtrl(self):
return self
Again, the 3-column "table" updates the data appropriately so no problem there. The only issue is when I try to sort the columns, it always sorts it based on the original "adpdata" that was used to fill in the table.
Any ideas?
EDIT:
Here's two example dicts:
Selection_A = {1:[A, no, 1],2:[B,no,2],3:[C,yes,3],4:[D,yes,4],5:[E,no,5]}
Selection_B = {1:[A, no, 3],2:[B,no,5],3:[C,yes,1],4:[D,yes,2],5:[E,no,4]}
Selection_A is my default dictionary that I use. When I build the lists, I can easily sort correctly the three columns (say column 3 would show in order 1,2,3,4,5. However, when I switch to Selection_B, it still sorts based on Selection_A values (but the lists shows Selection_B values) -- for example it shows 3,5,1,2,4 for column 3 and A,B,C,D,E for column 1 sorted (should be 1,2,3,4,5 and C,D,A,E,B). I don't know if this helps? I can't show my original dicts because there is so much data.
ALSO, I did check -- my self.itemDataMap does update appropriately (I printed the results) -- the sorting just doesn't take place. I guess I'm not completely sure where the "sort" values are "stored/updated"?
I'm not entirely sure why that doesn't work, but I think it might have something to do with your resetting the self.itemDataMap variable. I wrote the following quick and dirty script and it works:
import wx
import wx.lib.mixins.listctrl as listmix
########################################################################
class SortableListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.ListCtrl.__init__(self, parent, size=(-1,100),
style=wx.LC_REPORT|wx.BORDER_SUNKEN)
listmix.ListCtrlAutoWidthMixin.__init__(self)
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.index = 0
data = {1: ["Dr appointment", "05/06/2014", ""],
2: ["Meeting", "08/22/2013", "Board Room"]
}
self.list_ctrl = SortableListCtrl(panel)
self.list_ctrl.InsertColumn(0, 'Subject')
self.list_ctrl.InsertColumn(1, 'Due')
self.list_ctrl.InsertColumn(2, 'Location', width=125)
index = 0
for d in data:
print data[d][0]
print data[d][1]
self.list_ctrl.InsertStringItem(index, data[d][0]),
self.list_ctrl.SetStringItem(index, 1, data[d][1])
self.list_ctrl.SetStringItem(index, 2, data[d][2])
index += 1
btn = wx.Button(panel, label="Update")
btn.Bind(wx.EVT_BUTTON, self.updateListCtrl)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.CENTER|wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def updateListCtrl(self, event):
"""
"""
self.list_ctrl.DeleteAllItems()
data = {1: ["Tennis", "09/06/2013", ""],
2: ["Lunch", "08/15/2013", "Chick-fil-a"]
}
index = 0
for d in data:
print data[d][0]
print data[d][1]
self.list_ctrl.InsertStringItem(index, data[d][0]),
self.list_ctrl.SetStringItem(index, 1, data[d][1])
self.list_ctrl.SetStringItem(index, 2, data[d][2])
index += 1
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
Try commenting out the self.itemDataMap at the end of OnselectADP. If that doesn't work, then the next thing I would try is removing the global stuff.

checkboxCtrlMixin show selected values

I have a checkboxCtrlmixin, and what I want is to show the selected values of the checkboxCtrlmixin. Is there any special function in checkboxctrlmixin that shows what values are selected?
My code:
class TestListCtrl(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)
self.setResizeColumn(3)
class rulesFrame(wx.Frame):##open about frame
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "Choose Rules")
panel = wx.Panel(self)##create panel
prules=subprocess.check_output("perl ruleFinder.pl dataset24819.arff rules_test1Fold0w4_sample00ll1.dat", shell=True)
prules = prules.split()
ruleiden = [x for x in prules if x!='159']
ruleiden = list(set(ruleiden))
sortrule = [int(x) for x in ruleiden]
sortrule.sort()
with open('rules_test1Fold0w4_sample00ll1.dat') as fileobj:
lines = list(fileobj)
actualrules=''
##sortrule=[item+1 for item in sortrule]
##print sortrule
for index in sortrule:
actualrules += lines[index]
actualrules = actualrules.split('\n')
wx.Button(panel,label="Show Selected Rules",pos=(170,520),size=(200,25))
self.list = TestListCtrl(panel,size=(1000,500), style = wx.LC_REPORT)
self.list.InsertColumn(0,'Rules')
self.list.SetColumnWidth(0,500)
for i in actualrules:
self.list.InsertStringItem(sys.maxint,i)
To find out if an item is checked use the following method of your listctrl
IsChecked(self, index)
which will return True/False for given index.

Categories

Resources