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()))
Related
I have a table with 4 rows.First 2 rows, column 0 must be spanned, last 2 rows, column 0 must be spanned too.
This is the piece of code I use but at the time of the second span I get
QTableView :: setSpan: span cannot overlap
table.setRowCount(0)
new_row = 0
table.insertRow(new_row)
table.setRowHeight(new_row, 20)
table.setSpan(0, 0, 2, 1) ### <------
cell = QTableWidgetItem(str('AA'))
table.setItem(new_row, 0, cell)
a = 1
while a <= 10:
cell = QTableWidgetItem(str(a))
table.setItem(new_row, a, cell)
a += 1
new_row += 1
table.insertRow(new_row)
table.setRowHeight(new_row, 20)
a = 1
while a <= 10:
cell = QTableWidgetItem(str('CC'))
table.setItem(new_row, a, cell)
a += 1
new_row += 1
table.insertRow(new_row)
table.setRowHeight(new_row, 20)
table.setSpan(2, 0, 2, 1) ### <------
cell = QTableWidgetItem(str('BB'))
table.setItem(new_row, 0, cell)
The problem is that you are inserting a row within the previously set span. Spanning is dynamic, not fixed: if you insert elements between cells that include the spanning, their span is expanded.
If the model already had 2 rows, setting a 2-row span would make the first row span to the second one. Now, consider: what you should happen if you inserted a row between the first and the second? You would expect that the spanning should then extend to include all three rows, wouldn't you?
When you create the first row and set the first span, although there is only one row, the internal "span collection" will still keep that reference. When you insert a row at index 1 (meaning, between 0 and 1), the span is expanded as explained before: including rows 0 to 2, the first three rows. And that's even if there are still only two rows.
The error, then, comes from the fact that you're finally adding that third row (which is included in the span above), and when you try to set the second span, it won't work because it would overlap the previous one, because you want it to start from the third row.
The solution is simple: only set spans as soon as their rows or columns exist, not before.
In your case, move the first setSpan() after adding the second row. Consequentially, you should do the same for the second setSpan() only when its "spanned" rows are actually added (as right now, you're doing the same mistake).
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 created a table using plotly to calculate some financials, I would like to show the whole table in the graph interface (not just a few rows):
As you can see in the image, only 11 of my 30 rows are shown. I would like to show all the data of the table (all 30 rows with no scrollbar).
The code for the table is the following:
fig6 = go.Figure(data=[go.Table(
header=dict(values=list(df_table.columns),
fill_color='#d3d3d3',
align='left'),
cells=dict(values=[df_table['date'],
df_table['P/E_Ratio'],
df_table['Stock Price']],
fill_color='white',
align='left'))
])
As Juan correctly stated, adding height to fig6.update_layout() will do the trick. If you are however looking for a more dynamic workaround, you can use this function to calculate the height when input with a dataframe-
def calc_table_height(df, base=208, height_per_row=20, char_limit=30, height_padding=16.5):
'''
df: The dataframe with only the columns you want to plot
base: The base height of the table (header without any rows)
height_per_row: The height that one row requires
char_limit: If the length of a value crosses this limit, the row's height needs to be expanded to fit the value
height_padding: Extra height in a row when a length of value exceeds char_limit
'''
total_height = 0 + base
for x in range(df.shape[0]):
total_height += height_per_row
for y in range(df.shape[1]):
if len(str(df.iloc[x][y])) > char_limit:
total_height += height_padding
return total_height
You might have to play around with the other features if you have a different font_size than the default, or if you change the margin from the default. Also, the char_limit argument of the function is the other weakpoint as some characters take up more space than others, capital characters take up more space, and a single word if long can force a row to be extended. It should also be increased if the number or columns are less and vice versa. The function is written taking 4 table columns into consideration.
You would have to add fig6.update_layout(height=calc_table_height(df_table)) to make it work.
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 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