I have a QListView which displays a list of items using PyQt in Python. How can I get it to return a qlistview specific item when searched for?
For example, if I have the following Qlistview with 4 items, how can I get the item which contains text = dan? or bring it to the top of the list. Also, the search doesn't need to be completely specific, If I type "da" I'd like it to return dan or items that starts with "da" and possibly bring it to the top of the list
My Qlistview is defined as follows:
from PyQt4 import QtCore, QtGui
import os
import sys
class AppView(QtGui.QDialog):
def __init__(self, parent=None):
super(AppView, self).__init__(parent)
self.resize(400, 400)
self.ShowItemsList()
def ShowItemsList(self):
self.setWindowTitle("List")
buttonBox = QtGui.QDialogButtonBox(self)
buttonBox.setOrientation(QtCore.Qt.Horizontal)
buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
listview = QtGui.QListView(self)
verticalLayout = QtGui.QVBoxLayout(self)
verticalLayout.addWidget(listview)
verticalLayout.addWidget(buttonBox)
buttonBox.accepted.connect(self.close)
model = QtGui.QStandardItemModel(listview)
with open("names-list.txt") as input:
if input is not None:
item = input.readlines()
for line in item:
item = QtGui.QStandardItem(line)
item.setCheckable(True)
item.setCheckState(QtCore.Qt.PartiallyChecked)
model.appendRow(item)
listview.setModel(model)
listview.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
view = AppView()
view.show()
sys.exit(app.exec_())
I fixed it like this. I made my model an instance variable beginning with self so that I can access it from another function.
def searchItem(self):
search_string = self.searchEditText.text() # Created a QlineEdit to input search strings
items = self.model.findItems(search_string, QtCore.Qt.MatchStartsWith)
if len(items) > 0:
for item in items:
if search_string:
self.model.takeRow(item.row()) #take row of item
self.model.insertRow(0, item) # and bring it to the top
else:
print "not found"
Related
I use PyQt5 and the QTreeview to show an xml tree (and some of its attributes as columns) loaded from a file. This works as expected. But I'm now struggling with two things:
The itemChange event. The tree node should be editable and I need to do some checks after editing. The itemChange is called but it is called N times when editing one node.
Starting at the edited item I need to go up and down in the tree and check the child and parent nodes. I expected this to be simple like getParent() for one and a recursive getChildren() for the other direction. But how do I get form the itemChange events QStandardItem to the parent and child items?
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file='data.xml')
class MainFrame(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainFrame, self).__init__(parent)
self.tree = QtWidgets.QTreeView(self)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.tree)
root_model = QtGui.QStandardItemModel()
root_model.setHorizontalHeaderLabels(['Label', 'privilege', 'uid', 'docId'])
self.tree.setModel(root_model)
self.tree.setUniformRowHeights(True)
root = tree.getroot()
self._populateTree(root, root_model.invisibleRootItem())
self.tree.expandAll()
self.tree.resizeColumnToContents(0)
self.tree.resizeColumnToContents(1)
self.tree.resizeColumnToContents(2)
self.move(0, 0)
self.resize(800, 1000)
#self.tree.itemChanged.connect(self.edit)
def _populateTree(self, root, parent):
child_item = QtGui.QStandardItem(root.tag)
child_item.setData(root)
privilege = ''
uid = ''
docId = ''
privilege_item = QtGui.QStandardItem(privilege)
uid_item = QtGui.QStandardItem(uid)
docId_item = QtGui.QStandardItem(docId)
parent.appendRow([child_item, privilege_item, uid_item, docId_item])
privilege_item.model().itemChanged.connect(self.change_privilege)
child_item.model().itemChanged.connect(self.change_privilege)
for elem in root.getchildren():
self._populateTree(elem, child_item)
def change_privilege(self, item):
print(item)
print(item.row(), item.column())
def check_parent(self, item):
# get parent of item and check value
# check_parent(...)
def check_child(self, item):
# get children of item and check value
# for child in item.children():
# check_child(child)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main = MainFrame()
main.show()
sys.exit(app.exec_())
To answer the first question: every time you call MainFrame._populateTree you create a signal-slot connection between the signals child_item.model().itemChanged and privilege_item.model().itemChanged and the slot MainFrame.change_privilege. However, since all child items and privelege item belong to the same model (i.e. root_model) you are effectively creating multiple connections between the same signal and slot. This means that when any item in the model is changed, the slot is called multiple times as well. Easiest way around this is to create one connection in __init__ or so.
To access the parent of an item you can indeed use item.parent(). The children of an item can be accessed one-by-one via item.child(row, column) where row is in the range range(item.rowCount()) and column in the range range(item.columnCount()), e.g.
def change_privilege(self, item):
print(item.row(), item.column(), item.text())
self.check_parent(item)
self.check_child(item)
def check_parent(self, item):
if item.parent():
print('Parent:', item.parent().text())
else:
print('No parent')
def check_child(self, item):
if item.hasChildren():
print('Children:')
for row in range(item.rowCount()):
print('\t', row, end='')
for col in range(item.columnCount()):
print('\t', item.child(row, col).text(), end='')
print()
else:
print('No children')
I'm creating an application where I need to make a checklist with a list given to me from another class. The user will check the checkboxes next to the items they want and then click a button. For those rows that were checked, I'd like to print "You checked number ___." in the neighboring QBoxGroup's QLabel.
(In my real application, I'm iterating through classes, where I will call a function within the class). I'm having a hard time iterating through my list since I have to check if the box is checked and whether or not the name is the name I'd like to print (the printed statement is different than the actual name because in my application I'm going to call a function within the class whose row I'm in).
I'm also not sure how to work this where I can print more than one statement if the user checks multiple boxes with QLabel. I might have to make it where the user can only select one row at a time, but this is a smaller issue than the one above.
from PyQt5.QtWidgets import (QApplication, QTabWidget, QVBoxLayout, QWidget, QGridLayout, QLabel, QGroupBox,
QListView, QPushButton)
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtCore import Qt
import sys, os
class MyPage(QWidget):
def __init__(self):
super().__init__()
self.app = QApplication(sys.argv)
self.screen = self.app.primaryScreen()
self.size = self.screen.size()
self.mydata = None
# These numbers are arbitrary and seemed
# to have the best balance
self.textWidth = self.size.width() * 0.64
self.chooseWidth = self.size.width() * 0.29
self.createTextGroup()
self.createStatsGroup()
self.layout = QGridLayout()
self.layout.addWidget(self.TextGroup, 0, 0, 0, 1)
self.layout.addWidget(self.StatsGroup, 0, 1)
self.setLayout(self.layout)
self.show()
# The left side of AnalysisTab containing the textbox
# where the analysis will be shown
def createTextGroup(self):
self.TextGroup = QGroupBox("Analysis")
self.TextGroup.setFixedWidth(self.textWidth)
self.setStyleSheet("font: 15pt Tw Cen MT")
# Here is where the analysis will go
self.analysis = QLabel()
self.layout = QGridLayout()
self.layout.addWidget(self.analysis)
self.TextGroup.setLayout(self.layout)
# The right side of AnalysisTab containing a checklist of tests that
# may be run on the data and a button to press to run the tests
def createStatsGroup(self):
self.StatsGroup = QGroupBox("Statistics Tests")
self.StatsGroup.setFixedWidth(self.chooseWidth)
self.setStyleSheet("font: 15pt Tw Cen MT")
# List widgets where users will choose what analysis
# they would like ran on their data
self.statsAnalysis = QListView()
self.model = QStandardItemModel(self.statsAnalysis)
for member in myList(self):
item = QStandardItem(member)
item.setCheckable(True)
check = Qt.Unchecked
item.setCheckState(check)
self.model.appendRow(item)
self.statsButton = QPushButton("Analyze")
self.statsButton.clicked.connect(self.statsButtonClicked)
self.statsAnalysis.setModel(self.model)
self.layout = QGridLayout()
self.layout.addWidget(self.statsAnalysis, 0, 0, 0, 1)
self.layout.addWidget(self.statsButton, 1, 1)
self.StatsGroup.setLayout(self.layout)
def statsButtonClicked(self):
model2 = self.model
for index in range(model2.rowCount()):
item = model2.item(index)
if item.checkState() == Qt.Unchecked and item == "One":
self.analysis.setText("You checked number 1")
elif item.checkState() == Qt.Unchecked and item == "One":
self.analysis.setText("You checked number 2")
elif item.checkState() == Qt.Unchecked and item == "One":
self.analysis.setText("You checked number 3")
elif item.checkState() == Qt.Unchecked and item == "One":
self.analysis.setText("You checked number 4")
elif item.checkState() == Qt.Unchecked and item == "One":
self.analysis.setText("You checked number 5")
else:
self.analysis.setText("You didn't check anything")
def myList(self):
thisList = ["One", "Two", "Three", "Four", "Five"]
return thisList
def runStatsWiz():
app = QApplication(sys.argv)
myPage = MyPage()
myPage.show()
app.exec_()
runStatsWiz()
Right now, the statsButtonClicked() function returns TypeError: 'QStandardItemModel' object is not callable
I've tried the information on these StackOverflow pages and I can't seem to figure out how to see the row's name and if the row is checked:
How do I get the Checked items in a Qlistview?
PyQt listView checkbox connect when checked/unchecked
How to iterate through a QStandardItemModel completely?
Iterating all items inside QListView using python
As I indicated in the comments you have a type since you have an attribute called self.model but you want to access using self.model().
On the other hand your logic is incorrect since for example it is not defined that it is row, on the other hand assuming that this error did not exist and you had 2 options checked with your logic you are writing "You checked number X" and then replace it with "You checked number Y".
The logic is to save the texts of the checked items in a list, and if that list is not empty then concatenate the information, otherwise indicate no items checked.
def statsButtonClicked(self):
checked_options = []
for index in range(self.model.rowCount()):
item = self.model.item(index)
if item is not None and item.checkState() == Qt.Checked:
checked_options.append(item.text())
if checked_options:
self.analysis.setText(
"\n".join(["You checked number {}".format(option) for option in checked_options])
)
else:
self.analysis.setText("You didn't check anything")
What I am currently trying to do is take a populated tree (qtreewidget) that has checkboxes at the bottom child level, and return the text of the path to the child if the box is checked. The reason I want to do this, is if a child is checked, it will then change a value in a key in a dictionary. (The "raw" dictionary the tree was created from). Here's a visual example of what I mean:
From user input and server directory crawling, we have populated a tree that looks something like this:
(Only the lowest level child items have checkboxes.) And sorry for the horrible tree diagram!! Hopefully it makes sense...
edited
study
-subject 1
--date
---[]c
---[]d
---[]e
-subject 2
--date
---[]g
---[]h
If someone checks (for example) the "g" levle child, is there anyway to then get the path to "g" in a form something like [1, B, g] or 1-B-g or 1/B/g, etc.?
One of the children levels (let's say in the example A and B) are also set to be user editable. So I'd need the info from the tree, not the info the tree was originally populated from.
I have tried printing self.ui.treeWidget indexes with no real luck in getting what I want. I feel as though there is an easy solution for this, but I can't seem to find it. Hopefully someone can help!
Actual Code Snippet:
for h,study in enumerate(tree_dict['study']):
study_name = study['study_name']
treeSTUDY = QtGui.QTreeWidgetItem(self.ui.treeWidget, [study_name])
treeSTUDY.setFlags(QtCore.Qt.ItemIsEnabled)
self.ui.treeWidget.expandItem(treeSTUDY)
for i,subject in enumerate(study['subject']):
subject = subject['ID']
treeSUBJECT = QtGui.QTreeWidgetItem(treeSTUDY, [subject_id])
treeSUBJECT.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled)
for j,visit in enumerate(subject['visit']):
scan_date = visit['date']
treeDATE = QtGui.QTreeWidgetItem(treeSUBJECT, [scan_date[4:6])
treeDATE.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled)
for k,ser in enumerate(visit['series']):
s_name = ser['name'] + '-' + ser['description']
count = str(ser['count'])
treeSCAN = QtGui.QTreeWidgetItem(treeDATE)
treeSCAN.setFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
treeSCAN.setCheckState(0, QtCore.Qt.Unchecked)
treeSCAN.setText(0, s_name)
treeSCAN.setText(1, ser['time'])
treeSCAN.setText(2, ser['number'])
treeSCAN.setText(3, 'count')
All you need is a method that walks up the parent/child chain grabbing the text of each item until the parent is None:
def getTreePath(self, item):
path = []
while item is not None:
path.append(str(item.text(0)))
item = item.parent()
return '/'.join(reversed(path))
UPDATE:
Here is a demo script that shows how to get the checked item and retrieve its path:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.tree = QtGui.QTreeWidget(self)
self.tree.setHeaderHidden(True)
for index in range(2):
parent = self.addItem(self.tree, 'Item%d' % index)
for color in 'Red Green Blue'.split():
subitem = self.addItem(parent, color)
for letter in 'ABC':
self.addItem(subitem, letter, True, False)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.tree)
self.tree.itemChanged.connect(self.handleItemChanged)
def addItem(self, parent, text, checkable=False, expanded=True):
item = QtGui.QTreeWidgetItem(parent, [text])
if checkable:
item.setCheckState(0, QtCore.Qt.Unchecked)
else:
item.setFlags(
item.flags() & ~QtCore.Qt.ItemIsUserCheckable)
item.setExpanded(expanded)
return item
def handleItemChanged(self, item, column):
if item.flags() & QtCore.Qt.ItemIsUserCheckable:
path = self.getTreePath(item)
if item.checkState(0) == QtCore.Qt.Checked:
print('%s: Checked' % path)
else:
print('%s: UnChecked' % path)
def getTreePath(self, item):
path = []
while item is not None:
path.append(str(item.text(0)))
item = item.parent()
return '/'.join(reversed(path))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 250, 450)
window.show()
sys.exit(app.exec_())
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.
I need to determine which rows are selected in a QTableView associated with a QStandardItemModel. From the view, I call the function selectionModel() to get the selection. This function returns a QSelectionModel object. From that object, I want to call isRowSelected() function. This function takes two arguments: the row I want to test, and a parent argument, which is a QModelIndex. This is where I'm lost. What is this parent argument for? Where does it come from? Conceptually, I don't understand why I'd need this parameter, and concretely, I don't know what value I should pass to the function to make it work.
You'll find the parent useful in a QTreeView, for example. For your use case, this are the relevant parts of the documentation:
The index is used by item views, delegates, and selection models to
locate an item in the model... Invalid indexes are often used as parent indexes when referring to top-level items in a model."
With QtCore.QModelIndex() you'll create an invalid index, that's the argument you are looking for. In this example, you can use the context menu to print the selection state of the rows:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QTableView):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.modelSource = QtGui.QStandardItemModel(self)
for rowNumber in range(3):
items = []
for columnNumber in range(3):
item = QtGui.QStandardItem()
item.setText("row: {0} column: {0}".format(rowNumber, columnNumber))
items.append(item)
self.modelSource.appendRow(items)
self.actionSelectedRows = QtGui.QAction(self)
self.actionSelectedRows.setText("Get Selected Rows")
self.actionSelectedRows.triggered.connect(self.on_actionSelectedRows_triggered)
self.contextMenu = QtGui.QMenu(self)
self.contextMenu.addAction(self.actionSelectedRows)
self.setModel(self.modelSource)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.horizontalHeader().setStretchLastSection(True)
self.customContextMenuRequested.connect(self.on_customContextMenuRequested)
#QtCore.pyqtSlot(bool)
def on_actionSelectedRows_triggered(self, state):
for rowNumber in range(self.model().rowCount()):
info = "Row {0} is ".format(rowNumber)
if self.selectionModel().isRowSelected(rowNumber, QtCore.QModelIndex()):
info += "selected"
else:
info += "not selected"
print info
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested(self, pos):
self.contextMenu.exec_(self.mapToGlobal(pos))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.resize(333, 222)
main.show()
sys.exit(app.exec_())