-1

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:

indPosApplet.png

the problem is:

  • when I'm clicking on row or col +/-, the yellow highlighting is not changing instantly
  • 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

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?

fillCSV_forum.py:

### 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
El_Mo
  • 1
  • 2
  • Sorry but questions should always be self contained, so they cannot use external resources for code as they can become unaccessible and then make the question invalid. Please take your time and try to provide a [mre]. – musicamante Jul 06 '22 at 09:48
  • you are absolutely right. sorry for that and thy vm =) – El_Mo Jul 06 '22 at 10:43
  • Sorry, but that is certainly *not* minimal. Also, please don't add unnecessary text (thanks, "edit/update", etc). Take your time, rushing a question or its code won't help nobody. Ensure that your question *synthesizes* the problem (without providing *too* many and unnecessary details) and that the code is short enough while still able to reproduce the issue: anything that is ***not*** relevant to the question, should not be included. – musicamante Jul 06 '22 at 11:17
  • Ok, sorry again. is it now more compliant? I further shortened the text and code. – El_Mo Jul 07 '22 at 16:59

1 Answers1

0

[... 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

El_Mo
  • 1
  • 2