I am implementing a GUI for a python programme using Qt5 and PySide2. I have no problem understanding the C++ side of Qt, so feel free to point out Qt references not related with python.
I have some data that I display in a QTableView using a subclass of QAbstractTableModel. I also use a subclass of QSortFilterProxyModel to filter my table to only display a subset of the underlying data because it's a very large dataset. I offer the user the possibility to display only part of this data according to some criteria. This all works really well.
Then I have configured the QTableView so that the user can only select complete rows:
self.ui.candidatesTable.setSelectionBehavior(QTableView.SelectRows)
And in the object handling the UI I have implemented a slot that is called when the selection in the table changes:
#Slot(QItemSelection)
def handleSelectionChanged(self, item):
hasSelection = self.ui.candidatesTable.selectionModel().hasSelection()
if hasSelection:
selectedRows = self.ui.candidatesTable.selectionModel().selectedRows()
for row in selectedRows:
print(row.row())
My problem is that the value printed by print(row.row()) shows the row index in the currently displayed rows. If the user has selected filtering criteria that only displays 5 rows out of the several thousands, and then selects the first row, print(row.row()) will return 0 and not the original index in the underlying QAbstractTableModel.
My question is therefore the following: how can I access the original index in this situation?
You have to map the QModelIndex of the proxy model to the source model using the mapToSource() method:
#Slot(QItemSelection)
def handleSelectionChanged(self, item):
indexes = self.ui.candidatesTable.selectedIndexes()
proxy_model = self.ui.candidatesTable.model()
rows = set()
for index in indexes:
si = proxy_model.mapToSource(index)
rows.add(si.row())
for row in rows:
print(row)
Based on the previous answer by #eyllanesc, I implemented a solution using selectedRows() instead of selectedIndexes(). The latter returns indices for all selected columns and rows, while I was only interested in rows:
#Slot(QItemSelection)
def handleSelectionChanged(self, item):
hasSelection = self.ui.candidatesTable.selectionModel().hasSelection()
if hasSelection:
selectedRows = self.ui.candidatesTable.selectionModel().selectedRows()
for row in selectedRows:
proxy_model = self.ui.candidatesTable.model()
row_index = proxy_model.mapToSource(row).row()
Related
So I have created a CSV database with PyQT5.The user Has the option to delete last row and selected row
So I have a problem with selecting and deleting selected row:
(I apologize if my problem is hard to understand I don't know how to explain it properly, so here is a small visual representation)
1.data1 1.data1
2.data2 -> user selects 2.data2 row and deletes it-> 2.data3
3.data3 3.data4
4.data4
-> but if the user decides to delete last row without selecting the row this is what happens
1.data1 1.data1
2.data3 2.data4
3.data4 -> user presses Delete row button, without selecting 3.data4 row ->
-> but the last row doesn't get deleted instead the index of previously selected row gets passed
and the last selected row gets deleted
I have managed to locate the problem somewhere in this block of code, My guess is the problem comes from the .currentIndex(), but I don't know how to set the currentIndex to be None or empty so that it doesn't carry a number.
def deleteSelectedRow(self):
self.chosenRow = self.table_Database.currentIndex().row()
print(self.chosenRow)
if self.chosenRow < 0:
return
else:
self.table_Database.removeRow(self.chosenRow)
I had tried setting it to a negative number but that caused the same effect the index of the selected row prevailed the same
The currentIndex() of an item view doesn't indicate a selected index: while normally using setCurrentIndex() also selects the index, you can have a current index even if none is selected.
When an index that was the current is removed from the view, Qt automatically sets (but does not select!) the new current to the previous row and/or column depending on what was removed, unless there is no available index (rowCount() or columnCount() return 0) or the deleted index was in the first row or column (in this case, it sets the first available row and/or column).
The simple solution is to check whether there's a selected index or not:
def deleteSelectedRow(self):
if self.table_Database.selectedIndexes():
row = self.table_Database.currentIndex().row()
else:
row = self.table_Database.rowCount() - 1
if row >= 0:
self.table_Database.removeRow(row)
Considering what explained above, you could also have extended selections which don't show what the "current index" is; if you're supporting selections upon more than one row, you should create a list of rows based on the selected indexes, and remove rows in reversed order:
def deleteSelectedRow(self):
selected = self.table_Database.selectedIndexes()
if selected:
# create a set of *unique* rows
rows = set(i.row() for i in selected)
else:
rows = [self.table_Database.rowCount() - 1]
for row in sorted(rows, reverse=True):
self.table_Database.removeRow(row)
I am having a trouble removing several rows on my QAbstractItemModel.
My problem is that if I select several rows, say row 1 and 3.
I then loop over my selected rows, and delete them. However after I deleted the row 1, then row 3 becomes the row 2, so I actually delete the row 4.
Here is my method to retrieve the selected rows :
def get_selected_rows(self):
view = self._view
selection = view.selectionModel()
return selection.selectedRows()
Here is my method to delete a specific row :
def delete_obj(self, row):
self._model.removeRow(row.row())
And here is my method in my controller :
def on_delete_clicked(self):
for selected_index in self._view.get_selected_rows():
self._view.delete_obj(selected_index)
Is there any way to avoid recalculating all the indexes after each iteration of my loop ?
I found a very simple solution, I just iterate on the reversed of my list, and it's fine :)
def on_delete_clicked(self):
for selected_index in reversed(self._view.get_selected_rows()):
self._view.delete_obj(selected_index)
I have a custom QTableView with a custom QAbstractTableModel. I update every row where data has changed. The class that manages the data has a dirty flag which works well to help cut down the number of updates.
When I have a large number of rows, 1000 or more, the table gets a little less responsive. Instead of a for loop for each row to check it is dirty, I'd like to just loop over the 20 or so rows visible to the user, but I can't seem to determine how to get that information.
Is there a method or a convenient way to determine what rows are visible to a QAbstractTableModel?
The following will update only the rows visible to the user:
minRow = treeView.rowAt(0) # very top of scrollable area
if minRow >= 0: # ensure there is at least one row
maxRow = treeView.rowAt(treeView.height()) # very bottom...
# there may not be enough rows to fill scrollable area
if maxRow < 0: maxRow = model.rowCount() - 1
for row in range(minRow, maxRow + 1):
model.dataChanged.emit(model.index(row, 0), model.index(row, model.columnCount()))
I currently load the results of an SQL query into a TableView.
self.projectModel = QSqlQueryModel()
self.projectModel.setQuery(sql,db)
I then need to select a specific cell based on the header label (geometry). This column will move position depending on the different table that is search.
When the user clicks anywhere on the row (NOT the cell of geometry column) I would like to select the geometry column cell.
At the moment I have a this associated with the tableView
self.dlg.tableView.clicked.connect(self.cellClicked)
And in that function I have
row = self.projectModel.currentIndex()
If I use QTableView.model(row, column) to select the index, I have to speciify the row and column number. However, this would vary so I would want to do QTableView.model(row, 'geometry') however the model expects integers.
Any solutions?
thanks
So it seems all you need is a method to find a column from its header label, i.e. something like:
def columnFromLabel(self, label):
model = self.table.horizontalHeader().model()
for column in range(model.columnCount()):
if model.headerData(column, QtCore.Qt.Horizontal) == label:
return column
return -1
I'm hoping to duplicate my techniques for looping through tables in R using python in the ArcGIS/arcpy framework. Specifically, is there a practical way to loop through the rows of an attribute table using python and copy that data based on the values from previous table values?
For example, using R I would use code similar to the following to copy rows of data from one table that have unique values for a specific variable:
## table name: data
## variable of interest: variable
## new table: new.data
for (i in 1:nrow(data))
{
if (data$variable[i] != data$variable[i-1])
{
rbind(new.data,data[i,])
}
}
If I've written the above code correctly then in words, this for-loop simply checks to see if the current value in a table is different from the previous value and adds all column values for that row to the new table if it is in fact a new value. Any help with this thought process would be great.
Thanks!
To just get the unique values in a table in a field in arcpy:
import arcpy
table = "mytable"
field = "my_field"
# ArcGIS 10.0
unique_values = set(row.getValue(field) for row in iter(arcpy.SearchCursor(table).next, None))
# ArcGIS 10.1+
unique_values = {row[0] for row in arcpy.da.SearchCursor(table, field)}
Yes to loop through values in table using arcpy you want to use a cursor. Its been a while since I've used arcpy, but if I recall correctly the one you want is a search cursor. In its simplest form this is what it would look like:
import arcpy
curObj = arcpy.SearchCursor(r"C:/shape.shp")
row = curObj.next()
while row:
columnValue = row.getValue("columnName")
row = curObj.next()
As of version 10 (i think) they introduced a data access cursor which is orders of magnitude faster. Data access or DA cursors require you to declare what columns you want to have returned when you create the cursor. Example:
import arcpy
columns = ['column1', 'something', 'someothercolumn']
curObj = arcpy.da.SearchCursor(r"C:/somefile.shp", columns)
for row in curObj:
print 'column1 is', row[0]
print 'someothercolumn is', row[2]