can't get qtableview to update correctly
-
Hi @ all :) ,
I want to make a qtableview widget correctly updating. I'm working on a calibration applet, where i wanna fill cell by cell of an (e. g.) 100 x 100 x 4 array.If my hardware reaches position 1, 2, 3, and so on, I will trigger a voltage measurement and gather those values with an i2c-read out-function.
So issues a la "my qtableview is not updating" are omnipresent.
But so far, I'm not able to adapt examples I have read, to make my code behaving as I want.
So if you look at my screenshot:
..the problem is:
- when I'm clicking on row or col +/-, the yellow highlighting is not changing instantly (should move to indexed cell)
- when I'm clicking on store i²c, which is meant to put a dummy 0.0 in/on selected cell, this is also not changing instantly
- i only see updated values, when i switch widgets tab back and forth (A4, A5 and so on). Then the yellow highlight moved or stored values are displayed.
Several methods like telling the model that data has changed, I was not able to implement correctly so far.
Could some of you help me to add a few lines just to force applet to update correctly?
Following i will add simplified code, so you can run that applet, if you want to.
### libraries: import sys # to use e. g. exit function from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt import pandas as pd # to use pandas tables import numpy as np # to use numpy arrays ### user-defined header files / modules: from uLib_coloredWidget import Color # import user-defined functions.. from rndGen import i2c_read # .. see folder ### initial settings: # general np.random.seed(4) # if using np.random, then pseudo random values will occure ### globals: nRow = 5; nCol = 5; nSht = 4 # table dimensions rowIdx = colIdx = shtIdx = 0 # aux vars to index array rndArray = np.random.rand(nSht, nRow, nCol) * 4.3 # auxilliary before integrating i2c tabNames = ["A4", "A5","A6","A7"] # array (list) with tab names rowIdx = 1; colIdx = 1 # aux vars to index selected cell ### declarations / definitions: class TableModel(QtCore.QAbstractTableModel): def __init__(self, data): super(TableModel, self).__init__() self._data = data def data(self, index, role): if role == Qt.BackgroundRole and index.column() == colIdx and index.row() == rowIdx: # See below for the data structure. return QtGui.QColor('yellow') if role == Qt.DisplayRole: value = self._data.iloc[index.row(), index.column()] if isinstance(value, float): # to set fixed DISPLAYED precision of floats return "%.4f" % value return str(value) def rowCount(self, index): return self._data.shape[0] def columnCount(self, index): return self._data.shape[1] def headerData(self, section, orientation, role): # section is the index of the column/row. if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return str(self._data.columns[section]) if orientation == Qt.Vertical: return str(self._data.index[section]) class App(QtWidgets.QMainWindow): # local variable's declarations # init def __init__(self): super().__init__() # default one self.setup_main_window() # using helper function to increase readability (function call within self scope) # setup main window self.createLayout() # function call to create layouts with widgets self.post_main_window() # pass edited layouts to main window # declaration / methods / helper functions def setup_main_window(self): # to set window's / applet's properties self.centralwidget = QtWidgets.QWidget() self.setCentralWidget(self.centralwidget) self.resize( 800, 400 ) self.setWindowTitle( "# disposition calibration #" ) def post_main_window(self): # to publish edited layouts in app window self.centralwidget.setLayout(self.lyoOut) def createLayout(self): # to create layouts with widgets self.lyoOut = QtWidgets.QVBoxLayout() # declare different layouts self.lyoIn1 = QtWidgets.QHBoxLayout() self.lyoIn2 = QtWidgets.QGridLayout() self.createWidgets() # function call pass widgets to sub-layouts self.lyoOut.addLayout(self.lyoIn1) # inner layouts got widgets by self.createWidgets() self.lyoOut.addLayout(self.lyoIn2) # merge edited inner layout in/to outside layout here def createWidgets(self): # create master-layout's widgets (function calls) # fill 1st row of ouside layout self.lyoIn1 = self.createNestedTabs(self.lyoIn1) # function call to create master-tabs # fill 2nd row of outside layout self.lyoIn2 = self.createButtons(self.lyoIn2) # function call to create buttons def createNestedTabs(self, layout2modify): # create 1st tab layer self.MstTabs = QtWidgets.QTabWidget() # create tabs-widget self.MstTabs.setTabPosition(QtWidgets.QTabWidget.North) # set it's location self.MstTabs.addTab(self.createChildTabs(), "data") # add several sub-tab layouts to that widget self.MstTabs.addTab(Color("orange"), "plot") # stylesheet = """ QTabBar::tab:selected {background: lightgreen;} QTabBar::tab:!selected {background: lightyellow;} """ self.MstTabs.setStyleSheet(stylesheet) layout2modify.addWidget(self.MstTabs) # add this tabs-widget to passed-in layout return layout2modify # return edited layout def createChildTabs(self): # create 2nd tab layer self.ChdTabs = QtWidgets.QTabWidget() # create tabs-widget self.ChdTabs.setTabPosition(QtWidgets.QTabWidget.West) # set it's location self.ChdTabs.addTab(self.createPandasTables(0), "A4") self.ChdTabs.addTab(self.createPandasTables(1), "A5") self.ChdTabs.addTab(self.createPandasTables(2), "A6") self.ChdTabs.addTab(self.createPandasTables(3), "A7") return self.ChdTabs # return created widgets def createPandasTables(self, shtIdx): # to creating and editing pandas tables-widgets # use indexed (pandas)dataframe sheet values Lbl = ["a","b","c","d","e"] self.df = pd.DataFrame(rndArray[shtIdx], columns = Lbl, index = Lbl) # .. to create a widget self.table_widget = QtWidgets.QTableView() # create QTableView-Widget self.model = TableModel(self.df) # make df to user defined table model to use in widgets self.table_widget.setModel(self.model) # pass created model to created widget # certain formatings self.table_widget.resizeColumnsToContents() # set column width to content self.table_widget.horizontalHeader().setStretchLastSection(True) # strech last column to frame width self.table_widget.verticalHeader().setStretchLastSection(True) # strech last row to frame height self.table_widget.setAlternatingRowColors(True) # switch on alternating row highlighting return self.table_widget # return created widgets def createButtons(self, layout2modify): # helper function - to create layout's buttons bStoreI2C = QtWidgets.QPushButton("Store i²c") bStoreI2C.clicked.connect(lambda:self.storeVal()) bStoreI2C.setStyleSheet("QPushButton::hover" "{" "background-color : yellow;" "}") layout2modify.addWidget(bStoreI2C, 1, 3, 2, 1) self.lbl_1 = QtWidgets.QLabel() self.lbl_1.setText(str(rowIdx)) self.lbl_1.setAlignment(QtCore.Qt.AlignCenter) layout2modify.addWidget(self.lbl_1, 1, 5, 2, 1) bRowAdd = QtWidgets.QPushButton("row +") bRowAdd.clicked.connect(lambda:self.rowAdd()) layout2modify.addWidget(bRowAdd, 2, 6) bRowSub = QtWidgets.QPushButton("row -") bRowSub.clicked.connect(lambda:self.rowSub()) layout2modify.addWidget(bRowSub, 1, 6) return layout2modify # return edited layout def storeVal(self): #i2c_vals = get_i2c_values(i2c_addrs) for i in range (0,4): #self.tbData[i, rowIdx, colIdx] = i2c_vals[i] # change cell entries with imported value rndArray[i, rowIdx, colIdx] = 0 #self.tbData[sht, row, col] = 99 # change cell entry with imported value # try 1 #self.table_widget.update() #self.table_widget.repaint() #self.model.select() #self.table_widget.select() # try 2 # self.refreshModel() # not working so far #self.model = TableModel(self.df) # make df to user defined table model to use in widgets #self.table_widget.setModel(self.model) # print(rndArray) print('i²c-value(s) stored') def rowAdd(self): global rowIdx rowIdx = (rowIdx + 1) % nRow # increment and modulo to cycle self.lbl_1.setText(str(rowIdx)) # update label's text print('row is ', rowIdx) def rowSub(self): global rowIdx rowIdx = (rowIdx - 1) % nRow # increment and modulo to cycle self.lbl_1.setText(str(rowIdx)) # update label's text print('row is ', rowIdx) ### main: def main(): app = QtWidgets.QApplication(sys.argv) # instanciate app window = App() # instanciate window window.show() # show window app.exec_() # stuck here 'til window is closed print('# window will be terminated.. #') time.sleep(2) print('# ..app execution closed #') # make file executable if __name__ == '__main__': main()
rndGen.py: (is called in fillCSV_forum.py)
import numpy as np def i2c_read(): floats = np.random.rand(4,1,1) * 4.3 return floats
uLib_coloredWidget.py: (is called in fillCSV_forum.py)
from PyQt5.QtGui import QColor, QPalette from PyQt5.QtWidgets import QWidget class Color(QWidget): def __init__(self, color): super().__init__() self.setAutoFillBackground(True) palette = self.palette() palette.setColor(QPalette.Window, QColor(color)) self.setPalette(palette)
pip freeze --local-output of virtual enviroment:
numpy==1.23.0 pandas==1.4.3 PyQt5==5.15.7 PyQt5-Qt5==5.15.2 PyQt5-sip==12.11.0 python-dateutil==2.8.2 pytz==2022.1 six==1.16.0
-
Hi SGaist,
thy vm for your hint, but
- if you test my code OR like i mentioned in the beginning: technically it updates and display the changed data... but only if i am swapping the tabs widgets
- could you please give a corresponding code example, because i already had read such tips, but i was not able to implement it correctly without detailed examples so far
regards :)
-
The D'n'D documentation has an editable example.
-
hmpf, yah, thanks for that link. but this is c++ and i'm coding this in python. further i am not familiar at all to easily swap between.
i took another 3 h and i tried to adapt/integrate another python examples in my code... but jesus, i cannot get it working...
e. g. i tried update functions like this link text
isn't anybody out there, who had same problems with pandas dataframe, tab widgets and updates ^^ ?
grml
-
As part of your class, something like:
def setData(self, index: QModelIndex, value QVariant &value, role: int): if role == Qt.EditRole) if not self.checkIndex(index): return False; # update your pandas frame self.dataChanged.emit(index.row(), index.column()) return True return False
Just in case, there's a read-only example of a model in Qt's python documentation.
-
[... additionally many hours of trial and error....]
i think i finally got a dirty solution / work around..
the problem i could determining, was e. g. if i am clicking the col+/- or store button, the focus of recently selected tab is vanishing.
first when click again into any tab region or select another tabs those values are updating.so i tried to look for programmatically tab swap and did this as a dirty work around because i could not find a method like "reactivate tab again"
i added ... :
def storeVal(self): #i2c_vals = get_i2c_values(i2c_addrs) for i in range (0,nSht): self.df[i].iat[rowIdx, colIdx] = 99 print('i²c-value(s) stored') self.show_data() def show_data(self): x = self.ChdTabs.currentIndex() print(x) # debugging self.ChdTabs.setCurrentIndex(1) self.ChdTabs.setCurrentIndex(x)
... a show method and called it at the end of the store-method.
in this show method i programmatically swap the active tab back and forth. this is so fast, that i cannot see it
now my values are correctly shown
another tiny if else code is necessary to also swap if tab 1 is selected, but this is cosmetic thing
-
From what you wrote, it seems that you are modifying your data frame directly rather that through the model hence the model as no way to propagate the information to the upper layer.
Use the model as interface on top of your data frame.
-
yah i know,
and i would like to press your code snippets into a working model-will-change-data-interface-solution, but i didn't get it running for weeks. i am not familiar enough with object oriented coding and couldn't merge different code snippets to a working solution ...
unfortunately i m so busy at work, that i cannot enjoy weeks of youtube tutorials to learn this right.
so yeah, i change pandas data with a app/widget-class method directly and the model is correctly showing this but first when if give back the focus to tabs.
i would like to see a better / correct version with a model-class-method setData, but i was not able to...
=(
-
The short version would be to add a set_i2c_data method to your model, update the data frame in it and then emit the dataChanged signal as last step of that method.