Issue
I have a custom class derived from QCombobox
with an underlying custom list model (derived from QAbstractListModel
). The model allows the user make a multiple selection from the options (by checking members). The combobox is created from a delegate (derived from QItemDelegate
) that works over a QAbstractTableModel
derived instance. The idea is that from the selection, the table model will store the selection as list containing the selection members, and display it as a string representation of the list.
So far my implementation works, but I havent been able to accomplish two things:
- After the user inserts text on the completer, the resulting list shown is not alphabetically sorted (on the completer's popup).
- Every time that I click on the completer's output list (the popup) the view hides/closes (I'm not sure which one) which prevents selecting multiple items at a time.
Here is a short (pyside) example of my implementation:
from PySide.QtCore import *
from PySide.QtGui import *
class MyCombobox(QComboBox):
def __init__(self, parent = None):
super(MyCombobox, self).__init__(parent)
self.setFocusPolicy(Qt.StrongFocus)
self.setEditable(True)
self.filter = QSortFilterProxyModel(self)
self.filter.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.completer = QCompleter(self.filter, self)
self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
self.setCompleter(self.completer)
#signals
self.activated.connect(self._comboActivated)
self.lineEdit().textEdited[unicode].connect(self.filter.setFilterFixedString)
self.completer.activated['QModelIndex'].connect(self._completerActivated)
self._pressed = True
def _completerActivated(self, index):
if index.isValid():
self._itemPressed(index)
def setModel(self, model):
super(MyCombobox, self).setModel(model)
self.filter.setSourceModel(model)
self.completer.setModel(self.filter)
def _itemPressed(self, index):
row = index.row()
col = index.column()
index2 = self.filter.index(row, col)
index = self.filter.mapToSource(index2)
model = self.model()
state = model.data(index, role = Qt.CheckStateRole)
if state == Qt.Checked:
model.setData(index, Qt.Unchecked, Qt.CheckStateRole)
self._pressed = True
return
elif state == Qt.Unchecked:
model.setData(index, Qt.Checked, Qt.CheckStateRole)
self._pressed = True
return
else:
self._pressed = False
return
def _comboActivated(self, pos):
model = self.model()
index = model.index(pos, 0)
state = model.data(index, role = Qt.CheckStateRole)
if state == Qt.Checked:
model.setData(index, Qt.Unchecked, Qt.CheckStateRole)
self._pressed = True
return
elif state == Qt.Unchecked:
model.setData(index, Qt.Checked, Qt.CheckStateRole)
self._pressed = True
return
else:
self._pressed = False
return
def hidePopup(self):
if not self._pressed:
super(MyCombobox, self).hidePopup()
self._pressed = True
else:
self._pressed = False
class MyDelegate(QItemDelegate):
def __init__(self, parent = None):
super(MyDelegate, self).__init__(parent)
def createEditor(self, parent, option, index):
cb = MyCombobox(parent)
tableModel = index.model()
sel = tableModel._table[0][0]
model = MyModel(parent)
for k, name in enumerate(model._base):
model._checked[k] = name in sel
cb.setModel(model)
return cb
def setModelData(self, editor, model, index):
mymodel = editor.model()
sel = mymodel._checked
base = mymodel._base
myselection = [ name for k, name in enumerate(base) if sel[k] ]
model.setData(index, myselection, role = Qt.DisplayRole)
class MyModel(QAbstractListModel):
def __init__(self, parent = None):
super(MyModel, self).__init__(parent)
self._base = ["one", "two", "three", "four", "five"]
self._checked = [False for k in range(len(self._base))]
def rowCount(self, index = None):
return len(self._base)
def data(self, index, role = Qt.DisplayRole):
i = index.row()
if 0 <= i < self.rowCount():
if role == Qt.DisplayRole:
return self._base[i]
elif role == Qt.CheckStateRole:
if self._checked[i]:
return Qt.Checked
else:
return Qt.Unchecked
else:
pass
else:
pass
def setData(self, index, value, role = Qt.CheckStateRole):
i = index.row()
if 0 <= i < self.rowCount():
if role == Qt.CheckStateRole:
self._checked[i] = value == Qt.Checked
self.dataChanged.emit(index, index)
return True
else:
return super(MyModel, self).setData(index, value, role)
else:
return False
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
class MyTableModel(QAbstractTableModel):
def __init__(self, table, parent = None):
super(MyTableModel, self).__init__(parent)
self._table = table
def rowCount(self, index = None):
return len(self._table)
def columnCount(self, index = None):
if self._table: return len(self._table[0])
return 0
def data(self, index, role = Qt.DisplayRole):
i, j = index.row(), index.column()
if role == Qt.DisplayRole:
return unicode(self._table[i][j])
def setData(self, index, value, role = Qt.DisplayRole):
i, j = index.row(), index.column()
if role == Qt.DisplayRole:
self._table[i][j] = value
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if __name__ == '__main__':
app = QApplication("test")
tableView = QTableView()
model = MyTableModel([[["numbers"]]])
tableView.setModel(model)
delegate = MyDelegate(tableView)
tableView.setItemDelegate(delegate)
tableView.show();
app.exec_()
Solution
Ok, so I found solutions for both problems.
First, for the sorting on the completer output: I connected the QLineEdit.textEdited
signal to a custom slot in the combobox. This slot forces the QSortFilterProxyModel
instance to sort the underlying model by column 0. Not pretty, but works.
Second, for the "clicking" issue on the completer popup: This was a headcache. After hours playing around events and filters I realized that the mouse realease event on the view (the popup) was emitting the clicked signal (thanks to the docs for that) and, I believe, this signal was connected to the "hide" slot of the view. So my solution is an ugly hack, but again, it works. I wrote my own view derived from QListView
and override the mouseReleaseEvent
method on which I block all signals an then handle the event. In order to achieve this I had to pass the combobox instance my view class.
Here are the classes that were modified or added to the initial posted code in order to work:
class MyCombobox(QComboBox):
def __init__(self, parent=None):
super(MyCombobox, self).__init__(parent)
self.setFocusPolicy(Qt.StrongFocus)
self.setEditable(True)
self.filter = QSortFilterProxyModel(self)
self.filter.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.completer = QCompleter(self.filter, self)
self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
self.setCompleter(self.completer)
# signals
self.activated.connect(self._comboActivated)
self.lineEdit().textEdited[unicode].connect(self.filter.setFilterFixedString)
self.lineEdit().textEdited[unicode].connect(self._sort)
self.completer.activated['QModelIndex'].connect(self._completerActivated)
mlview = MyListView(self)
mlview.setEditTriggers(self.view().editTriggers())
self.completer.setPopup(mlview)
self._pressed = True
def _completerActivated(self, index):
if index.isValid():
self._itemPressed(index)
def setModel(self, model):
super(MyCombobox, self).setModel(model)
self.filter.setSourceModel(model)
self.completer.setModel(self.filter)
def _itemPressed(self, index):
row = index.row()
col = index.column()
index2 = self.filter.index(row, col)
index = self.filter.mapToSource(index2)
model = self.model()
state = model.data(index, role = Qt.CheckStateRole)
if state == Qt.Checked:
model.setData(index, Qt.Unchecked, Qt.CheckStateRole)
self._pressed = True
return
elif state == Qt.Unchecked:
model.setData(index, Qt.Checked, Qt.CheckStateRole)
self._pressed = True
return
else:
self._pressed = False
return
def _comboActivated(self, pos):
model = self.model()
index = model.index(pos, 0)
state = model.data(index, role = Qt.CheckStateRole)
if state == Qt.Checked:
model.setData(index, Qt.Unchecked, Qt.CheckStateRole)
self._pressed = True
return
elif state == Qt.Unchecked:
model.setData(index, Qt.Checked, Qt.CheckStateRole)
self._pressed = True
return
else:
self._pressed = False
return
def _sort(self, arg = None):
self.filter.sort(0)
def hidePopup(self):
if not self._pressed:
super(MyCombobox, self).hidePopup()
self._pressed = True
else:
self._pressed = False
class MyListView(QListView):
def __init__(self, widget, parent = None):
super(MyListView, self).__init__(parent)
self._widget = widget
self.setSelectionMode(QListView.SingleSelection)
self.setFocusPolicy(Qt.StrongFocus)
self.setSelectionBehavior(QListView.SelectItems)
def mouseReleaseEvent(self, event):
self.blockSignals(True)
super(MyListView, self).mouseReleaseEvent(event)
self.blockSignals(False)
index = self.currentIndex()
if not index.isValid(): return
self._widget._itemPressed(index)
Answered By - user20679
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.