Info about items and signal in QTreeView - PyQt5 - python

There are rows in QTreeView, each row has two columns.
The first column carries a name, second column carries a preview.
Questions:
How to get click on row signal? If row is selected by single mouse click, how to get signal? I use QTreeView.doubleClicked.connect() now, but is there way how to do?
I follow up on the first question. If I choose by clicking on the item in the second column how to get item name from the first column? (if is not possible to perform the action with a single mouse click, how to do it with QTreeView.doubleClicked.connect()? )
How to get information about category, subcategories, row, column of item?
Thanks for hints and help.
Code preview:
QTreeView
self.tree = QtWidgets.QTreeView()
self.model = QtGui.QStandardItemModel(self.tree)
self.tree.setDragEnabled(True)
self.tree.doubleClicked.connect(self.getValue)
Filling QTreeView
self.model.setHorizontalHeaderLabels(('Category / Subcategories / Name', 'Preview'))
self.tree.setModel(self.model)
self.tree.setColumnWidth(0, 200)
#root = self.tree.rootIndex()
for row, (keys, vals) in enumerate(self.dataStatObj.items()):
if row == 0:
kategorie = keys[0]
podkategorie = keys[1]
categoryTV = QtGui.QStandardItem(keys[0])
subcategoriesTV = QtGui.QStandardItem(keys[1])
self.model.appendRow(categoryTV)
for iimg in listImagesNameSuffix:
if iimg[1] == keys[2]:
print(iimg[1])
print(keys[2])
imgName = iimg[0]
imgTV = StandardItemObr()
imgTV.setIcon(QtGui.QIcon(pathToImages + imgName))
imgTV.setEditable(False)
nameImgTV = QStandardItem(keys[2])
nameImgTV.setEditable(False)
categoryTV.appendRow(subcategoriesTV)
categoryTV.setEditable(False)
subcategoriesTV.appendRow([nameImgTV, imgTV])
subcategoriesTV.setEditable(False)
...
...
self.tree.expandAll()
self.tree.setIconSize(QtCore.QSize(100, 100))
getValue (self.tree.doubleClicked.connect(self.getValue))
def getValue(self, val):
print(val.data(), 'If I click on the first column, the text will appear.'
' When I click on the second column I need to return the same as for the first column ')
if val.data() is None:
print('None')
print(val.parent(), "PARENT")
Preview

QTreeView, like other views such as QListView or QTableView, inherits from QAbstractItemView, and if you look at the Signals section you'll see that there are other signals other than doubleClicked. What you need, obviously, is the clicked signal.
That signal, like most of the others, returns the model index
(a QModelIndex instance) that has been clicked, which is an object that is used to locate the data within the model, including its "coordinates": row, column and parent (the last is fundamental for tree models).
The QModelIndex class has various convenience functions that allow easy access to the relation with the model:
data(), which you're already using, that returns the value(s) associated to that specific index in the model; it's actually a "shortcut" to model.data(index, role);
parent() is pretty explanatory; same as model.parent(index);
sibling(), returns an index at the specified row or column that shares the same parent of the given one; it's the same as model.sibling(row, column, index);
Considering the above, if you want to get data about the first column after a click, you need to connect the function to the clicked signal; then, in order to get information about any possible parent, you can use a basic recursive function:
def getValue(self, index):
if index.column():
index = index.sibling(index.row(), 0)
# since Qt 5.11 you can use `index.siblingAtColumn(0)`
def getValueRecursive(index):
info = ['"{}" (row {}, col {})'.format(
index.data(), index.row(), index.column())]
if index.parent().isValid():
info.extend(getValueRecursive(index.parent()))
return info
print('Index clicked!')
for indexInfo in reversed(getValueRecursive(index)):
print(indexInfo)
Note that the clicked signal will obviously only work for mouse clicks: if the current index is changed using the keyboard, it will not be emitted, so you should better use the selectionModel of the view and use the selectionChanged or currentChanged signals instead.
I strongly recommend you to carefully and patiently study the classes you're using, including the inherited ones; for instance, if you're using QTreeView, you should consider studying all the documentation about: QAbstractItemView, QAbstractScrollArea, QFrame, QWidget and, finally, QObject; since you're using models, you also need to know more about QModelIndex, QAbstractItemModel and read the documentation about the model/view framework.

Related

How to add a second column in a Combobox using Pyqt

I want two columns to be displayed in my Combobox. How could I do it without going too far into code. This code works but I have only achieved it with one column.
sqlcom2="SELECT cod,name FROM product"
data2 = cur.execute(sqlcom2)
result2 = cur.fetchall()
result22 = [i[0] for i in result2]
self.comboBox.addItems(result22)
QComboBox doesn't normally allow to show more than one column of items as its view() is normally a QListView. In order to show more contents, you can use a QTableView instead, but this requires adding more items for each row, so the basic addItem() or addItems() cannot be used, so items must be added to the combobox model instead.
Even if QComboBox currently uses a QStandardItemModel, this might change in the future, so it's better to create your own QStandardItemModel and set that model for the combo.
Then, QComboBox is able to use models with multiple columns (the default is column 0) and the currently used column can be changed using setModelColumn(), but you need to ensure that whenever the user selects an item in a different column the combobox column is update accordingly. In order to do that, a subclass of QTableView is usually the best approach, as it allows to override the methods required to notify the combo about the column change.
There's also another problem: QComboBox uses its own width to automatically resize the popup width. While for single column popup this is not a big problem, if multiple columns are going to be shown, it's possible that only the first column will be shown.
To solve this issue you can set a minimum size for the table view within the showEvent of the table; note that this is generally not a suggested approach, but it is acceptable for this specific case and for the sake of simplicity. Also consider that I'm using the defaultSectionSize() property, and you might prefer to ensure that the columns resize themselves according to the contents, but since that is a more and slightly unrelated topic that might add more complexity to the implementation, I'll not address it in this answer.
class ComboTableView(QtWidgets.QTableView):
def __init__(self, comboBox):
super().__init__()
self.comboBox = comboBox
self.horizontalHeader().hide()
self.verticalHeader().hide()
self.activated.connect(self.updateComboColumn)
def updateComboColumn(self, index):
if (index.isValid() and
index.flags() & QtCore.Qt.ItemIsEnabled and
index.flags() & QtCore.Qt.ItemIsSelectable):
self.comboBox.setModelColumn(index.column())
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.updateComboColumn(self.indexAt(event.pos()))
super().mousePressEvent(event)
def showEvent(self, event):
w = self.horizontalHeader().defaultSectionSize() * self.model().columnCount()
if self.model().rowCount() > self.comboBox.maxVisibleItems():
w += self.verticalScrollBar().sizeHint().width()
self.setMinimumWidth(w)
class SomeWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.comboTableView = ComboTableView(self.comboBox)
self.comboBox.setView(self.comboTableView)
comboModel = QtGui.QStandardItemModel()
self.comboBox.setModel(comboModel)
# ...
sqlcom2="SELECT cod,name FROM product"
data2 = cur.execute(sqlcom2)
result2 = cur.fetchall()
for rowValues in result2:
items = []
for value in rowValues:
items.append(QtGui.QStandardItem(value))
comboModel.appendRow(items)
Finally, you can also implement "horizontal" keyboard navigation within the combo so that the user can switch between columns using the arrow keys, and that can be done by installing an event filter that manages arrow keys.
class SomeWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.comboBox.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.comboBox and event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Right:
column = min(
self.comboBox.modelColumn() + 1,
self.comboBox.model().columnCount() - 1
)
self.comboBox.setModelColumn(column)
elif event.key() == QtCore.Qt.Key_Left:
column = max(0, self.comboBox.modelColumn() - 1)
self.comboBox.setModelColumn(column)
return super().eventFilter(source, event)

How to make particular cell editable and leave the rest non-editable in QTableWidget?

I have QTableWidget which is non editable.(i had setup noEditTriggers while creating Ui file). I want to make particular cell editable from each row. how i can get this done?
I looked into several answers on SO and other platforms but didn't get anything working for me.
currently I am using this piece of code. it doesnt give an error but i still could not edit that cell value.
self.item = QTableWidgetItem('Hi')
flags = self.item.flags()
flags ^= QtCore.Qt.ItemIsEditable
self.item.setFlags(flags)
self.table.setItem(row, column, self.item)
EDIT::
Using the same fundament for the #musicamante answer is to create a delegate that only returns one editor in the specific column, the advantage is that you don't need to subclassify QTableWidget and the logic can be used in other types of views:
class Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
if index.column() == 2:
return super(Delegate, self).createEditor(parent, option, index)
delegate = Delegate(self.table)
self.table.setItemDelegate(delegate)
Update:
If you want the cells with NN to be editable then you must return the editor when it meets that condition: index.data() == "NN"
import random
import sys
from PyQt5 import QtWidgets
class Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
if index.data() == "NN":
return super(Delegate, self).createEditor(parent, option, index)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
texts = ["Hello", "Stack", "Overflow", "NN"]
table = QtWidgets.QTableWidget(10, 5)
delegate = Delegate(table)
table.setItemDelegate(delegate)
for i in range(table.rowCount()):
for j in range(table.columnCount()):
text = random.choice(texts)
it = QtWidgets.QTableWidgetItem(text)
table.setItem(i, j, it)
table.resize(640, 480)
table.show()
sys.exit(app.exec_())
You could set the flags for each item, while leaving the default edit triggers, but this is not very good approach, since you could have a very large table, some items could be changed/added/removed and you might forget to set/reset the flags.
A better approach could be to override the edit() method, and execute the default implementation (which creates the item editor and starts the editing) by manually setting the edit trigger.
This requires to leave the default edit triggers (or at least one trigger method) set.
class TableWidget(QtWidgets.QTableWidget):
def edit(self, index, trigger, event):
# editing is allowed only for the third column
if index.column() != 2:
trigger = self.NoEditTriggers
return super().edit(index, trigger, event)

QGraphicsScene changing objects when selected

I have a QGraphicsScene containing some simple objects (in this simplified example circles) that I want to change into other objects (here squares) when selected. More specifically I'd like to have parent objects which don't draw themselves, they are drawn by their child objects, and under various circumstances, but in particular when the parent objects are selected, I'd like the set of child objects to change. This is a nice conceptual framework for the overall app I am working on.
So I've implemented this in PySide and I thought it was working fine: the circles change nicely into squares when you click on them.
Until I use RubberBandDrag selection in the view. This causes an instant segfault when the rubber band selection reaches the parent object and the selection changes. Presumably this is being triggered because the rubber band selection in QT is somehow keeping a pointer to the child item which is disappearing before the rubber band selection action is complete.
Simplified code below - test it by first clicking on the object (it changes nicely) then dragging over the object - segfault:
from PySide import QtCore,QtGui
class SceneObject(QtGui.QGraphicsItem):
def __init__(self, scene):
QtGui.QGraphicsItem.__init__(self, scene = scene)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtGui.QGraphicsItem.ItemHasNoContents, True)
self.updateContents()
def updateContents(self):
self.prepareGeometryChange()
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
def itemChange(self, change, value):
if self.scene() != None:
if change == QtGui.QGraphicsItem.ItemSelectedHasChanged:
self.updateContents()
return
return super(SceneObject,self).itemChange(change, value)
def boundingRect(self):
return self.childrenBoundingRect()
class Visualiser(QtGui.QMainWindow):
def __init__(self):
super(Visualiser,self).__init__()
self.viewer = QtGui.QGraphicsView(self)
self.viewer.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
self.setCentralWidget(self.viewer)
self.viewer.setScene(QtGui.QGraphicsScene())
parent_item = SceneObject(self.viewer.scene())
parent_item.setPos(50,50)
app = QtGui.QApplication([])
mainwindow = Visualiser()
mainwindow.show()
app.exec_()
So questions:
Have I just made a mistake that can be straightforwardly fixed?
Or is removing objects from the scene not allowed when handling an ItemSelectedHasChanged event?
Is there a handy workaround? Or what's a good alternative approach? I could replace the QGraphicsRectItem with a custom item which can be drawn either as a square or a circle but that doesn't conveniently cover all my use cases. I can see that I could make that work but it will certainly not be as straightforward.
EDIT - Workaround:
It is possible to prevent this failing by preserving the about-to-be-deleted object for a while. This can be done by something like this:
def updateContents(self):
self.prepareGeometryChange()
self._temp_store = self.childItems()
for c in self.childItems():
self.scene().removeItem(c)
...
However, this is ugly code and increases the memory usage for no real benefit. Instead I have moved to using the QGraphicsScene.selectionChanged signal as suggested in this answer.
I've debugged it. Reproduced on Lunix
1 qFatal(const char *, ...) *plt 0x7f05d4e81c40
2 qt_assert qglobal.cpp 2054 0x7f05d4ea197e
3 QScopedPointer<QGraphicsItemPrivate, QScopedPointerDeleter<QGraphicsItemPrivate>>::operator-> qscopedpointer.h 112 0x7f05d2c767ec
4 QGraphicsItem::flags qgraphicsitem.cpp 1799 0x7f05d2c573b8
5 QGraphicsScene::setSelectionArea qgraphicsscene.cpp 2381 0x7f05d2c94893
6 QGraphicsView::mouseMoveEvent qgraphicsview.cpp 3257 0x7f05d2cca553
7 QGraphicsViewWrapper::mouseMoveEvent qgraphicsview_wrapper.cpp 1023 0x7f05d362be83
8 QWidget::event qwidget.cpp 8374 0x7f05d2570371
qt-everywhere-opensource-src-4.8.6/src/gui/graphicsview/qgraphicsscene.cpp:2381
void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode,
const QTransform &deviceTransform)
{
...
// Set all items in path to selected.
foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) {
if (item->flags() & QGraphicsItem::ItemIsSelectable) { // item is invalid here
if (!item->isSelected())
changed = true;
unselectItems.remove(item);
item->setSelected(true);
}
}
They are using items() function to find a list of items under the rubber band selection. But if one item while processing deletes something the item pointer just becomes invalid. And next call to item->flags() causes the crash.
As alternative you could use QGraphicsScene::selectionChanged signal. It's emitted only once per selection change.
Looks like it's not expected by Qt to have some major changes in itemChange
Behind of this here is common mistake you have with prepareGeometryChange() call.
It's designed to be called right before changing boundingRect. Bounding rect should be the old one when prepareGeometryChange called and new one right after.
So that's could happen:
In updateContents:
self.prepareGeometryChange(); # calls boundingRect. old value returned
...
shape_item.setParentItem(self); # could call the boundingRect. but still old value returned!
After child added it calls boundingRect again but value unexpected different.
As a solution you can add a variable
def updateContents(self):
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
self.prepareGeometryChange();
self._childRect = self.childrenBoundingRect()
def boundingRect(self):
return self._childRect

How to undo an edit of a QListWidgetItem in PySide/PyQt?

Short version
How do you implement undo functionality for edits made on QListWidgetItems in PySide/PyQt?
Hint from a Qt tutorial?
The following tutorial written for Qt users (c++) likely has the answer, but I am not a c++ person, so get a bit lost: Using Undo/Redo with Item Views
Longer version
I am using a QListWidget to learn my way around PyQt's Undo Framework (with the help of an article on the topic). I am fine with undo/redo when I implement a command myself (like deleting an item from the list).
I also want to make the QListWidgetItems in the widget editable. This is easy enough: just add the ItemIsEditable flag to each item. The problem is, how can I push such edits onto the undo stack, so I can then undo/redo them?
Below is a simple working example that shows a list, lets you delete items,and undo/redo such deletions. The application displays both the list and the the undo stack. What needs to be done to get edits onto that stack?
Simple working example
from PySide import QtGui, QtCore
class TodoList(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.initUI()
self.show()
def initUI(self):
self.todoList = self.makeTodoList()
self.undoStack = QtGui.QUndoStack(self)
undoView = QtGui.QUndoView(self.undoStack)
buttonLayout = self.buttonSetup()
mainLayout = QtGui.QHBoxLayout(self)
mainLayout.addWidget(undoView)
mainLayout.addWidget(self.todoList)
mainLayout.addLayout(buttonLayout)
self.setLayout(mainLayout)
self.makeConnections()
def buttonSetup(self):
#Make buttons
self.deleteButton = QtGui.QPushButton("Delete")
self.undoButton = QtGui.QPushButton("Undo")
self.redoButton = QtGui.QPushButton("Redo")
self.quitButton = QtGui.QPushButton("Quit")
#Lay them out
buttonLayout = QtGui.QVBoxLayout()
buttonLayout.addWidget(self.deleteButton)
buttonLayout.addStretch()
buttonLayout.addWidget(self.undoButton)
buttonLayout.addWidget(self.redoButton)
buttonLayout.addStretch()
buttonLayout.addWidget(self.quitButton)
return buttonLayout
def makeConnections(self):
self.deleteButton.clicked.connect(self.deleteItem)
self.quitButton.clicked.connect(self.close)
self.undoButton.clicked.connect(self.undoStack.undo)
self.redoButton.clicked.connect(self.undoStack.redo)
def deleteItem(self):
rowSelected=self.todoList.currentRow()
rowItem = self.todoList.item(rowSelected)
if rowItem is None:
return
command = CommandDelete(self.todoList, rowItem, rowSelected,
"Delete item '{0}'".format(rowItem.text()))
self.undoStack.push(command)
def makeTodoList(self):
todoList = QtGui.QListWidget()
allTasks = ('Fix door', 'Make dinner', 'Read',
'Program in PySide', 'Be nice to everyone')
for task in allTasks:
todoItem=QtGui.QListWidgetItem(task)
todoList.addItem(todoItem)
todoItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
return todoList
class CommandDelete(QtGui.QUndoCommand):
def __init__(self, listWidget, item, row, description):
super(CommandDelete, self).__init__(description)
self.listWidget = listWidget
self.string = item.text()
self.row = row
def redo(self):
self.listWidget.takeItem(self.row)
def undo(self):
addItem = QtGui.QListWidgetItem(self.string)
addItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.listWidget.insertItem(self.row, addItem)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
myList=TodoList()
sys.exit(app.exec_())
Note I posted an earlier version of this question at QtCentre.
That tutorial you mentioned is really not very helpful. There are indeed many approaches to undo-redo implementation for views, we just need to choose the simplest one. If you deal with small lists, the simpliest way is to save all data on each change and restore full list from scratch on each undo or redo operation.
If you still want atomic changes list, you can track user-made edits with QListWidget::itemChanged signal. There are two problems with that:
Any other item change in the list will also trigger this signal, so you need to wrap any code that changes items into QObject::blockSignals calls to block unwanted signals.
There is no way to get previous text, you can only get new text. The solution is either save all list data to variable, use and update it on change or save the edited item's text before it's edited. QListWidget is pretty reticent about its internal editor state, so I decided to use QListWidget::currentItemChanged assuming that user won't find a way to edit an item without making is current first.
So this is the changes that will make it work (besides adding ItemIsEditable flag in two places):
def __init__(self):
#...
self.todoList.itemChanged.connect(self.itemChanged)
self.todoList.currentItemChanged.connect(self.currentItemChanged)
self.textBeforeEdit = ""
def itemChanged(self, item):
command = CommandEdit(self.todoList, item, self.todoList.row(item),
self.textBeforeEdit,
"Rename item '{0}' to '{1}'".format(self.textBeforeEdit, item.text()))
self.undoStack.push(command)
def currentItemChanged(self, item):
self.textBeforeEdit = item.text()
And the new change class:
class CommandEdit(QtGui.QUndoCommand):
def __init__(self, listWidget, item, row, textBeforeEdit, description):
super(CommandEdit, self).__init__(description)
self.listWidget = listWidget
self.textBeforeEdit = textBeforeEdit
self.textAfterEdit = item.text()
self.row = row
def redo(self):
self.listWidget.blockSignals(True)
self.listWidget.item(self.row).setText(self.textAfterEdit)
self.listWidget.blockSignals(False)
def undo(self):
self.listWidget.blockSignals(True)
self.listWidget.item(self.row).setText(self.textBeforeEdit)
self.listWidget.blockSignals(False)
I would do it like this:
Create a custom QItemDelegate and use these two signals:
editorEvent
closeEditor
On editorEvent: Save current state
On closeEditor: Get new state and create a QUndoCommand that set the new state for Redo and the old state for Undo.
Each time you verify and accept the new text of the item, save it as list item data. Quasi-semi-pseudo-code:
OnItemEdited(Item* item)
{
int dataRole{ 32 }; //or greater (see ItemDataRole documentation)
if (Validate(item->text()) {
item->setData(dataRole, item->text());
} else { //Restore previous value
item->setText(item->data(dataRole).toString());
}
}
I'm sorry if it looks too much like C++.

Highlight/select first visible item in QListView when filtering with QSortFilterProxyModel

If a list selection does not exist for filtered results then I would like to automatically highlight the first item. I created the method force_selection() that highlight's the first item if nothing is selected. I am using the QListView.selectionModel() to determine the selection index. I have tried connecting force_selection() to the QLineEdit slots: textEdited(QString) and textChanged(QString). However, it appears that there is a timing issue between textChanged and the proxy refreshing the QListView. Sometimes the selection is made while other times it disappears.
So how would I go about forcing a selection (blue highlight) during a proxy filter if the user has not made a selection yet? The idea behind my code is that the user searches for an item, the top item is the best result so it is selected (unless they manually select another item in the filter view).
You can find an image of the problem here.
Recreate issue:
Execute sample script with Python 2.7
Do not select anything in the list (QLineEdit should have focus)
Search for 'Red2', slowly type 'R', 'e', 'd' --> Red1 and Red2 are visible and Red1 is highlighted
Finish the search by typing the number '2' --> Red2 is no longer highlighted/selected
Final solution:
from PySide import QtCore
from PySide import QtGui
class SimpleListModel(QtCore.QAbstractListModel):
def __init__(self, contents):
super(SimpleListModel, self).__init__()
self.contents = contents
def rowCount(self, parent):
return len(self.contents)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return str(self.contents[index.row()])
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
data = ['Red1', 'Red2', 'Blue', 'Yellow']
self.model = SimpleListModel(data)
self.view = QtGui.QListView(self)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.proxy.setDynamicSortFilter(True)
self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.view.setModel(self.proxy)
self.search = QtGui.QLineEdit(self)
self.search.setFocus()
layout = QtGui.QGridLayout()
layout.addWidget(self.search, 0, 0)
layout.addWidget(self.view, 1, 0)
self.setLayout(layout)
# Connect search to proxy model
self.connect(self.search, QtCore.SIGNAL('textChanged(QString)'),
self.proxy.setFilterFixedString)
# Moved after connect for self.proxy.setFilterFixedString
self.connect(self.search, QtCore.SIGNAL('textChanged(QString)'),
self.force_selection)
self.connect(self.search, QtCore.SIGNAL('returnPressed()'),
self.output_index)
# #QtCore.Slot(QtCore.QModelIndex)
#QtCore.Slot(str)
def force_selection(self, ignore):
""" If user has not made a selection, then automatically select top item.
"""
selection_model = self.view.selectionModel()
indexes = selection_model.selectedIndexes()
if not indexes:
index = self.proxy.index(0, 0)
selection_model.select(index, QtGui.QItemSelectionModel.Select)
def output_index(self):
print 'View Index:',self.view.currentIndex().row()
print 'Selected Model Current Index:',self.view.selectionModel().currentIndex()
print 'Selected Model Selected Index:',self.view.selectionModel().selectedIndexes()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
The problem is the order of connect calls. You connect textChanged to force_selection first, so it's called first. But at that time, filter is not processed and proxy is not updated. So you select an item that might soon be removed by filtering.
Just switch the order of connect calls.
By the way, you might want to reconsider your logic in force_selection. currentIndex doesn't necessarily correspond to selected indexes. You can observe that by typing red2 and deleting 2. You'll get both Red1 and Red2 selected. If you want to deal with currentIndex use setCurrentIndex instead of select. If you want to deal with selected indexes, then your condition should be based on selectedRows or selectedIndexes.

Categories