Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

tableView not updated after calling dataChanged



  • Hi

    I'm doing a toy project to understand the QAbstractTableModel and display it's data into a tableView widget. I have been reading some of the old posts here but most of them aren't solved.

    Expected:
    At the begginning i expect the string '0x0800C000' being displayed on column 0, row 0, after pressing the button I remove all the rows to clean the view and after that have the following list displayed on column 0, each element on it's own row: '0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014'.

    What I get:
    The first string is properly displayed, the rows are being removed but the view isn't being updated.

    Here's what I have tried so far:

    #!/usr/bin/env python3
    
    from fbs_runtime.application_context.PySide2 import ApplicationContext
    
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import QIntValidator
    from PySide2.QtUiTools import QUiLoader
    
    import sys
    
    class TestModel(QAbstractTableModel):
        def __init__(self):
            QAbstractTableModel.__init__(self)
            
            # Here we keep the data
            self.display = []
    
        def rowCount(self, parent=QModelIndex()):
            return len(self.display)
    
        def columnCount(self, parent=QModelIndex()):
            return 1
    
        def setData(self, index, value, role=Qt.EditRole):
            '''
            Adjust the data (set it to value <value>) depending on the given index
            and role
            '''
    
            if role != Qt.EditRole:
                return False
    
            if index.isValid() and 0 <= index.row():
                print(f'[SET_DATA] DATA: {value} index row: {index.row()}; column{index.column()}')
                if index.column() == 0:
                    self.display.append(value)
                else:
                    return False
                
                print(f'data changed signal')
                # Let Qt know there's new data to be displayed
                self.dataChanged.emit(index, index)
                return True
    
            return False
    
        def flags(self, index):
            '''
            Set the item flags at the given index. Seems like we're implementing
            this function just to see ho it's done, as we manually adjust each
            tableView to have NoEditTriggers.
            '''
            if not index.isValid():
                return Qt.ItemIsEnabled
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)
    
        def insertRows(self, position, rows=1, index=QModelIndex()):
            '''
            Insert a row from the model.
            '''
            
            self.beginInsertRows(QModelIndex(), position, position + rows - 1)
    
            self.endInsertRows()
    
            return True
    
        def removeRows(self, position, rows=1, index=QModelIndex()):
            '''
            Remove a row from the model.
            '''
            
            self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
    
            print(self.display)
            
            print(f'Removing {rows} rows from {position} to {position+rows}')
            del self.display[position:position+rows]
    
            print(self.display)
    
            self.endRemoveRows()
    
            return True
    
        def insertColumns(self, column, count, parent):
            pass
    
        def removeColumns(self):
            pass
    
        def data(self, index, role=Qt.DisplayRole):
    
            if not index.isValid():
                return None
            
            if role != Qt.DisplayRole and role != Qt.EditRole:
                return None
    
            column = index.column()
            row = index.row()
    
            print(f'[DATA] Column: {column}; Row: {row}')
            
            if column == 0:
                return self.display[index.row()]
            else:
                return None
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole and orientation == Qt.Horizontal:
                if section == 0:
                    return 'Address'
    
    class AppContext(ApplicationContext):
        def run(self):
    
            self.app.setStyle('Fusion')
    
            ui_file = self.get_resource("mainwindow.ui")
            self.file = QFile(ui_file)
            self.file.open(QFile.ReadOnly)
            self.loader = QUiLoader()
            self.window = self.loader.load(self.file)
    
            self.window.updateView.clicked.connect(self.onUpdateView)
    
            self.address_list = ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    
            self.model = TestModel()
    
            first_index = self.model.createIndex(0, 0)
    
            self.model.setData(first_index, self.address_list[0], Qt.EditRole)
    
            self.window.tableView.setModel(self.model)
            self.window.tableView.show()
            
            print(self.model.rowCount())
    
            # Show the application to the user        
            self.window.show()
            return self.app.exec_()
    
        def onUpdateView(self):
    
            current_rows = self.model.rowCount()
    
            print(f'Updating view, removing {current_rows} rows')
            self.model.removeRows(0, current_rows)
    
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
            
            row = 0
            for address in self.address_list:
                idx = self.model.createIndex(row, 0)
                self.model.setData(idx, address, Qt.EditRole)
                row += 1
            
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
    
    if __name__ == '__main__':
        appctxt = AppContext()
        exit_code = appctxt.run()
        sys.exit(exit_code)
    

    The tableView starts like this:
    Table view when launching the application: https://imgur.com/eIfwwxm

    After pressing the button i get the following log data:

    [SET_DATA] DATA: 0x0800C000 index row: 0; column0
    data changed signal
    [SET_DATA] DATA: 0x0800C004 index row: 1; column0
    data changed signal
    [SET_DATA] DATA: 0x0800C008 index row: 2; column0
    data changed signal
    [SET_DATA] DATA: 0x0800C00C index row: 3; column0
    data changed signal
    [SET_DATA] DATA: 0x0800C010 index row: 4; column0
    data changed signal
    [SET_DATA] DATA: 0x0800C014 index row: 5; column0
    data changed signal
    Currently we have 6 rows
    

    So the data is being inserted in the self.display list.
    But the widget looks still empty: https://imgur.com/moPD7x3

    You can find the complete repo here, I'm using fbs for the application so you should clone the repo, enter the cloned directory and do fbs run to get it running.

    Regards



  • @Carlos-Diaz
    As I said above.

    I believe you will find your problem is because: having removed all rows via removeRows() you simply use setData() to put stuff into the model rows. You do not call insertRows(). If that is not called, the view won't get the signal and know there are any new rows.

    Put a debug in to show it's not being called. Then put in calls during your address population loop, and see how the view looks.



  • @Carlos-Diaz said in tableView not updated after calling dataChanged:

            current_rows = self.model.rowCount()
    
            print(f'Updating view, removing {current_rows} rows')
            self.model.removeRows(0, current_rows)
    
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
            
            row = 0
            for address in self.address_list:
                idx = self.model.createIndex(row, 0)
                self.model.setData(idx, address, Qt.EditRole)
                row += 1
    

    Why doesn't your output show those print lines?

    Don't they show that after removing all rows from the model, you just go setData() over all the rows which no longer exist? What does the bool returned by your self.model.setData() return?



  • After taking a closer look at the log we can see the method data isn't being called after setData.

    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0 # Here i load the first item of the list into the model
    data changed signal
    ['0x0800C000']
    Setting model
    Showing model
    [DATA] Column: 0; Row: 0 # Here the data method is being called
    0x0800C000
    [DATA] Column: 0; Row: 0
    0x0800C000
    Updating view, removing 1 rows
    ['0x0800C000']
    Removing 1 rows from 0 to 1
    []
    Currently we have 0 rows
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0 # Updating the data in the model, but the data method isn't being called after this
    data changed signal
    ['0x0800C000']
    [SET_DATA] DATA: 0x0800C004 index row: 1; column 0
    data changed signal
    ['0x0800C000', '0x0800C004']
    [SET_DATA] DATA: 0x0800C008 index row: 2; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008']
    [SET_DATA] DATA: 0x0800C00C index row: 3; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C']
    [SET_DATA] DATA: 0x0800C010 index row: 4; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010']
    [SET_DATA] DATA: 0x0800C014 index row: 5; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    Currently we have 6 rows
    


  • @Carlos-Diaz
    As I said above.

    I believe you will find your problem is because: having removed all rows via removeRows() you simply use setData() to put stuff into the model rows. You do not call insertRows(). If that is not called, the view won't get the signal and know there are any new rows.

    Put a debug in to show it's not being called. Then put in calls during your address population loop, and see how the view looks.



  • Hi @JonB,

    Here's the updated test code with the print statements updated:

    #!/usr/bin/env python3
    
    from fbs_runtime.application_context.PySide2 import ApplicationContext
    
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import QIntValidator
    from PySide2.QtUiTools import QUiLoader
    
    import sys
    
    class TestModel(QAbstractTableModel):
        def __init__(self):
            QAbstractTableModel.__init__(self)
            
            # Here we keep the data
            self.display = []
    
        def rowCount(self, parent=QModelIndex()):
            return len(self.display)
    
        def columnCount(self, parent=QModelIndex()):
            COLUMNS_WIDTH_SIZE = 1
            return COLUMNS_WIDTH_SIZE
    
        def setData(self, index, value, role=Qt.EditRole):
            '''
            Adjust the data (set it to value <value>) depending on the given index
            and role
            '''
    
            if role != Qt.EditRole:
                return False
    
            if index.isValid() and 0 <= index.row(): # < len(self.addresses):
                
                print(f'[SET_DATA] DATA: {value} index row: {index.row()}; column {index.column()}')
                if index.column() == 0:
                    self.display.append(value)
                else:
                    return False
                
                print(f'data changed signal')
                print(self.display)
                # self.layoutChanged.emit()
                self.dataChanged.emit(index, index)
                return True
    
            return False
    
        def flags(self, index):
            '''
            Set the item flags at the given index. Seems like we're implementing
            this function just to see ho it's done, as we manually adjust each
            tableView to have NoEditTriggers.
            '''
    
            if not index.isValid():
                return Qt.ItemIsEnabled
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)
    
        def insertRows(self, position, rows=1, index=QModelIndex()):
            '''
            Insert a row from the model.
            '''
            
            self.beginInsertRows(QModelIndex(), position, position + rows - 1)
    
            self.display.append(' ')
            print(f'insert {rows} rows at {position}')
    
            self.endInsertRows()
    
            return True
    
        def removeRows(self, position, rows=1, index=QModelIndex()):
            '''
            Remove a row from the model.
            '''
            
            self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
    
            print(self.display)
            
            print(f'Removing {rows} rows from {position} to {position+rows}')
            del self.display[position:position+rows]
    
            print(self.display)
    
            self.endRemoveRows()
    
            return True
    
        def insertColumns(self, column, count, parent):
            print(f'insert {count} columns at {column}')
    
        def removeColumns(self):
            pass
    
        def data(self, index, role=Qt.DisplayRole):
    
            if not index.isValid():
                print(f'invalid index {index}')
                return None
            
            if role != Qt.DisplayRole and role != Qt.EditRole:
                # print(f'invalid role {role}')
                return None
    
            column = index.column()
            row = index.row()
    
            print(f'[DATA] Column: {column}; Row: {row}')
            
            if column == 0:
                tmp = self.display[index.row()]
                print(tmp)
                return tmp
            else:
                return None
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole and orientation == Qt.Horizontal:
                if section == 0:
                    return 'Address'
    
    class AppContext(ApplicationContext):
        def run(self):
    
            self.app.setStyle('Fusion')
    
            ui_file = self.get_resource("mainwindow.ui")
            self.file = QFile(ui_file)
            self.file.open(QFile.ReadOnly)
            self.loader = QUiLoader()
            self.window = self.loader.load(self.file)
    
            self.window.updateView.clicked.connect(self.onUpdateView)
    
            self.address_list = ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    
            self.model = TestModel()
    
            first_index = self.model.createIndex(0, 0)
    
            self.model.setData(first_index, self.address_list[0], Qt.EditRole)
    
            print(f'Setting model')
            self.window.tableView.setModel(self.model)
            print(f'Showing model')
            self.window.tableView.show()
            
            # Show the application to the user        
            self.window.show()
            return self.app.exec_()
    
        def onUpdateView(self):
    
            current_rows = self.model.rowCount()
    
            print(f'Updating view, removing {current_rows} rows')
            self.model.removeRows(0, current_rows)
    
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
            
            for row, address in enumerate(self.address_list):
                idx = self.model.createIndex(row, 0)
                tmp = self.model.setData(idx, address, Qt.EditRole)
                print(f'Result of setData: {tmp}')
            
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
    
    if __name__ == '__main__':
        appctxt = AppContext()
        exit_code = appctxt.run()
        sys.exit(exit_code)
    

    The return value from setData is True, here's the log output:

    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000']
    Setting model
    Showing model
    [DATA] Column: 0; Row: 0
    0x0800C000
    [DATA] Column: 0; Row: 0
    0x0800C000
    Updating view, removing 1 rows
    ['0x0800C000']
    Removing 1 rows from 0 to 1
    []
    Currently we have 0 rows
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C004 index row: 1; column 0
    data changed signal
    ['0x0800C000', '0x0800C004']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C008 index row: 2; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C00C index row: 3; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C010 index row: 4; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C014 index row: 5; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    Result of setData: True
    Currently we have 6 rows
    

    If in setData we uncomment the self.layoutChanged.emit() line the view works as expected:

        def setData(self, index, value, role=Qt.EditRole):
            '''
            Adjust the data (set it to value <value>) depending on the given index
            and role
            '''
    
            if role != Qt.EditRole:
                return False
    
            if index.isValid() and 0 <= index.row(): # < len(self.addresses):
                
                print(f'[SET_DATA] DATA: {value} index row: {index.row()}; column {index.column()}')
                if index.column() == 0:
                    self.display.append(value)
                else:
                    return False
                
                print(f'data changed signal')
                print(self.display)
                self.layoutChanged.emit()
                self.dataChanged.emit(index, index)
                return True
    
            return False
    

    log:

    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000']
    Setting model
    Showing model
    [DATA] Column: 0; Row: 0
    0x0800C000
    [DATA] Column: 0; Row: 0
    0x0800C000
    Updating view, removing 1 rows
    ['0x0800C000']
    Removing 1 rows from 0 to 1
    []
    Currently we have 0 rows
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C004 index row: 1; column 0
    data changed signal
    ['0x0800C000', '0x0800C004']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C008 index row: 2; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C00C index row: 3; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C010 index row: 4; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C014 index row: 5; column 0
    data changed signal
    ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    Result of setData: True
    Currently we have 6 rows
    [DATA] Column: 0; Row: 0
    0x0800C000
    [DATA] Column: 0; Row: 1
    0x0800C004
    [DATA] Column: 0; Row: 2
    0x0800C008
    [DATA] Column: 0; Row: 3
    0x0800C00C
    [DATA] Column: 0; Row: 4
    0x0800C010
    [DATA] Column: 0; Row: 5
    0x0800C014
    

    Updated tableView: https://imgur.com/zvQgS1s

    I guess adding self.layoutChanged.emit() is what i was missing or it isn't the proper way to solve it?



  • @Carlos-Diaz
    Our posts keep crossing! Please read the answer I have just posted above your new post! I hope you will find it is the reason/solution.



  • @Carlos-Diaz said in tableView not updated after calling dataChanged:

    if index.isValid() and 0 <= index.row(): # < len(self.addresses):

    I think your commented-out bit suggests you were onto the problem :) I admit I don't know why the index is valid, but you should check its row is less than the number of items/rows currently in the model. Then I think it would return false, the setData()s would fail, and you will have to call insertRows() as you should? And then it all works :) ?



  • @JonB

    Sorry about that, I also was going to mention our posts were crossing. I now cleaned up the code, I have been working with it for a couple of days and kept garbage code.

    I did a tiny experiment by removing the line that remove rows on the table and:

    1. Keeping the line self.layoutChanged.emit()
    2. Removing the line self.layoutChanged.emit()

    Option 1 the tableView works as expected, i can see the rows being updated.
    Option 2 the tableView is not being updated.

    So I ended up with the following working code:

    #!/usr/bin/env python3
    
    from fbs_runtime.application_context.PySide2 import ApplicationContext
    
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import QIntValidator
    from PySide2.QtUiTools import QUiLoader
    
    import sys
    
    class TestModel(QAbstractTableModel):
        def __init__(self):
            QAbstractTableModel.__init__(self)
            
            # Here we keep the data
            self.display = []
    
        def rowCount(self, parent=QModelIndex()):
            return len(self.display)
    
        def columnCount(self, parent=QModelIndex()):
            COLUMNS_WIDTH_SIZE = 1
            return COLUMNS_WIDTH_SIZE
    
        def setData(self, index, value, role=Qt.EditRole):
            '''
            Adjust the data (set it to value <value>) depending on the given index
            and role
            '''
    
            if role != Qt.EditRole:
                return False
    
            if index.isValid() and 0 <= index.row():
                
                print(f'[SET_DATA] DATA: {value} index row: {index.row()}; column {index.column()}')
                if index.column() == 0:
                    self.display.append(value)
                else:
                    return False
                
                print(f'data changed signal')
                print(self.display)
                self.layoutChanged.emit()
                self.dataChanged.emit(index, index)
                return True
    
            return False
    
        def flags(self, index):
            '''
            Set the item flags at the given index. Seems like we're implementing
            this function just to see ho it's done, as we manually adjust each
            tableView to have NoEditTriggers.
            '''
    
            if not index.isValid():
                return Qt.ItemIsEnabled
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)
    
        def insertRows(self, position, rows=1, index=QModelIndex()):
            '''
            Insert a row from the model.
            '''
            
            self.beginInsertRows(QModelIndex(), position, position + rows - 1)
    
            self.display.append(' ')
            print(f'insert {rows} rows at {position}')
    
            self.endInsertRows()
    
            return True
    
        def removeRows(self, position, rows=1, index=QModelIndex()):
            '''
            Remove a row from the model.
            '''
            
            self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
    
            print(self.display)
            
            print(f'Removing {rows} rows from {position} to {position+rows}')
            del self.display[position:position+rows]
    
            print(self.display)
    
            self.endRemoveRows()
    
            return True
    
        def insertColumns(self, column, count, parent):
            print(f'insert {count} columns at {column}')
    
        def removeColumns(self):
            pass
    
        def data(self, index, role=Qt.DisplayRole):
    
            if not index.isValid():
                print(f'invalid index {index}')
                return None
            
            if role != Qt.DisplayRole and role != Qt.EditRole:
                # print(f'invalid role {role}')
                return None
    
            column = index.column()
            row = index.row()
    
            print(f'[DATA] Column: {column}; Row: {row}')
            
            if column == 0:
                tmp = self.display[index.row()]
                print(tmp)
                return tmp
            else:
                return None
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole and orientation == Qt.Horizontal:
                if section == 0:
                    return 'Address'
    
    class AppContext(ApplicationContext):
        def run(self):
    
            self.app.setStyle('Fusion')
    
            ui_file = self.get_resource("mainwindow.ui")
            self.file = QFile(ui_file)
            self.file.open(QFile.ReadOnly)
            self.loader = QUiLoader()
            self.window = self.loader.load(self.file)
    
            self.window.updateView.clicked.connect(self.onUpdateView)
    
            self.address_list = ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    
            self.model = TestModel()
    
            first_index = self.model.createIndex(0, 0)
    
            self.model.setData(first_index, self.address_list[0], Qt.EditRole)
    
            print(f'Setting model')
            self.window.tableView.setModel(self.model)
            print(f'Showing model')
            self.window.tableView.show()
            
            # Show the application to the user        
            self.window.show()
            return self.app.exec_()
    
        def onUpdateView(self):
    
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
            
            for row, address in enumerate(self.address_list):
                idx = self.model.createIndex(row, 0)
                tmp = self.model.setData(idx, address, Qt.EditRole)
                print(f'Result of setData: {tmp}')
            
            current_rows = self.model.rowCount()
            print(f'Currently we have {current_rows} rows')
    
    if __name__ == '__main__':
        appctxt = AppContext()
        exit_code = appctxt.run()
        sys.exit(exit_code)
    
    

    Working log:

    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000']
    Setting model
    Showing model
    [DATA] Column: 0; Row: 0
    0x0800C000
    [DATA] Column: 0; Row: 0
    0x0800C000
    Currently we have 1 rows
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    data changed signal
    ['0x0800C000', '0x0800C000']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C004 index row: 1; column 0
    data changed signal
    ['0x0800C000', '0x0800C000', '0x0800C004']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C008 index row: 2; column 0
    data changed signal
    ['0x0800C000', '0x0800C000', '0x0800C004', '0x0800C008']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C00C index row: 3; column 0
    data changed signal
    ['0x0800C000', '0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C010 index row: 4; column 0
    data changed signal
    ['0x0800C000', '0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C014 index row: 5; column 0
    data changed signal
    ['0x0800C000', '0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    Result of setData: True
    Currently we have 7 rows
    [DATA] Column: 0; Row: 0
    0x0800C000
    [DATA] Column: 0; Row: 1
    0x0800C000
    [DATA] Column: 0; Row: 2
    0x0800C004
    [DATA] Column: 0; Row: 3
    0x0800C008
    [DATA] Column: 0; Row: 4
    0x0800C00C
    [DATA] Column: 0; Row: 5
    0x0800C010
    [DATA] Column: 0; Row: 6
    0x0800C014
    

    We can see the data function is being called and returning valid data so the view is updated. Updated tableView: https://imgur.com/zvQgS1s



  • @Carlos-Diaz said in tableView not updated after calling dataChanged:

    print(f'insert {rows} rows at {position}')

    Where is that called in the output? You are not calling insertRows(). Your code for setData() is wrong. As I have said in above posts.

    Your call to index.isValid() is insufficient. That only says:

    Returns true if this model index is valid; otherwise returns false.

    A valid index belongs to a model, and has non-negative row and column numbers.That's what I think. It would be interesting to hear your comment?

    You have to check for number of rows in model in setData(), your line needs to be:

     if index.isValid() and 0 <= index.row() < self.rowCount():
    

    Your self.layoutChanged.emit() hides the incorrect code by causing the whole view to refresh.

    I really think that is your problem. It would be nice to hear your comment on this specific area?



  • @JonB Thanks for being so patient with my mistakes.

    I think I had added everything you suggested and removed the self.layoutChanged.emit() call and the view keeps working!

    I don't know if it's possible to add the new rows inside the setData function, so it's not handled on the application level?

    Here's the updated code:

    #!/usr/bin/env python3
    
    from fbs_runtime.application_context.PySide2 import ApplicationContext
    
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import QIntValidator
    from PySide2.QtUiTools import QUiLoader
    
    import sys
    
    '''
    QAbstractTableModel provides a standard interface for models that represent their
    data as a two-dimensional array of items. It is not used directly, but must be
    subclassed.
    
    Since the model provides a more especialized interface than QAbstractItemModel,
    it is not suitable for use with tree views, although it can be used to provide
    data to a QListView.
    If you need to represent a simple list of items, and only need a model to contain
    a single column of data, subclassing the QAbstractListModel may be more appropiate.
    
    The rowCount() and columnCount() functions return the dimensions of the table.
    To retrieve a model index corresponding to an item in the model, use index()
    and provide only the row and column numbers.
    
    Editable models need to implement setData(), and implement flags() to return a value
    containing ItemsIsEditable.
    
    Models that provide interfaces to resizable data structures can provide implementations of:
     - insertRows()
     - removeRows()
     - insertColumns()
     - removeColumns()
     
    When implementing these functions, it is important to call the appropiate functions
    so that all connected views are aware of any changes.
    
    Role enum:
    https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum
    
    Useful links:
    https://doc.qt.io/qtforpython/PySide2/QtCore/QAbstractTableModel.html
    https://doc.qt.io/qtforpython/tutorials/datavisualize/index.html
    https://stackoverflow.com/questions/50391050/how-to-remove-row-from-qtreeview-using-qabstractitemmodel
    https://github.com/pyside/Examples/blob/master/examples/itemviews/editabletreemodel/editabletreemodel.py
    '''
    class TestModel(QAbstractTableModel):
        def __init__(self):
            QAbstractTableModel.__init__(self)
            
            # Here we keep the data
            self.display = []
    
        def rowCount(self, parent=QModelIndex()):
            return len(self.display)
    
        def columnCount(self, parent=QModelIndex()):
            COLUMNS_WIDTH_SIZE = 1
            return COLUMNS_WIDTH_SIZE
    
        def setData(self, index, value, role=Qt.EditRole):
            '''
            Adjust the data (set it to value <value>) depending on the given index
            and role
            '''
    
            if role != Qt.EditRole:
                print(f'[SET_DATA] Role is not EditRole')
                return False
    
            # We must be sure we add the new value into a valid index of the model
            # So we validate the index, we make sure the index row isn't 0 or less
            # and that the amount of current rows is more than the index row we are
            # adding the value to.
            if index.isValid() and 0 <= index.row() < self.rowCount():
                
                print(f'[SET_DATA] DATA: {value} index row: {index.row()}; column {index.column()}')
                if index.column() == 0:
                    print(f'Before setting the new value {self.display}')
                    self.display[index.row()] = value
                    print(f'After setting the new value {self.display}')
                else:
                    return False
                
                self.dataChanged.emit(index, index)
                return True
    
            return False
    
        def flags(self, index):
            '''
            Set the item flags at the given index. Seems like we're implementing
            this function just to see ho it's done, as we manually adjust each
            tableView to have NoEditTriggers.
            '''
    
            if not index.isValid():
                return Qt.ItemIsEnabled
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)
    
        def insertRows(self, position, rows=1, index=QModelIndex()):
            '''
            Insert a row from the model.
            '''
            
            self.beginInsertRows(QModelIndex(), position, position + rows - 1)
    
            print(f'Inserting {rows} rows at {position}')
    
            # We extend the list with rows elements
            self.display.extend([''] * rows)
    
            self.endInsertRows()
    
            return True
    
        def removeRows(self, position, rows=1, index=QModelIndex()):
            '''
            Remove a row from the model.
            '''
            
            self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
    
            print(self.display)
            
            print(f'Removing {rows} rows from {position} to {position+rows}')
            del self.display[position:position+rows]
    
            print(self.display)
    
            self.endRemoveRows()
    
            return True
    
        def insertColumns(self, column, count, parent):
            print(f'insert {count} columns at {column}')
    
        def removeColumns(self):
            pass
    
        def data(self, index, role=Qt.DisplayRole):
    
            if not index.isValid():
                print(f'invalid index {index}')
                return None
            
            if role != Qt.DisplayRole and role != Qt.EditRole:
                return None
    
            column = index.column()
            row = index.row()
    
            print(f'Fetching data from column: {column}; row: {row}')
            
            if column == 0:
                tmp = self.display[index.row()]
                print(f'Data: {tmp}')
                return tmp
            else:
                return None
    
        def headerData(self, section, orientation, role):
            if role == Qt.DisplayRole and orientation == Qt.Horizontal:
                if section == 0:
                    return 'Address'
    
    class AppContext(ApplicationContext):
        def run(self):
    
            self.app.setStyle('Fusion')
    
            ui_file = self.get_resource("mainwindow.ui")
            self.file = QFile(ui_file)
            self.file.open(QFile.ReadOnly)
            self.loader = QUiLoader()
            self.window = self.loader.load(self.file)
    
            self.window.updateView.clicked.connect(self.onUpdateView)
    
            self.address_list = ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    
            self.model = TestModel()
            self.window.tableView.setModel(self.model)
    
            # Add a row for the default data..
            self.model.insertRows(0)
            first_index = self.model.createIndex(0, 0)
            self.model.setData(first_index, self.address_list[0], Qt.EditRole)
            
            self.window.tableView.show()
            
            # Show the application to the user        
            self.window.show()
            return self.app.exec_()
    
        def onUpdateView(self):
            print(f'Updating view...')
            
            print(f'We have {self.model.rowCount()} rows')
    
            # Insert rows for new data
            rows_to_add = len(self.address_list)
            current_rows = self.model.rowCount()
    
            rows_to_add -= current_rows
            print(f'Inserting {rows_to_add} new rows for the new data')
            self.model.insertRows(0, rows_to_add)
            print(f'We now have {self.model.rowCount()} rows')
    
            print(f'Inserting new data into the model...')
            for row, address in enumerate(self.address_list):
                column = 0
                idx = self.model.createIndex(row, column)
                tmp = self.model.setData(idx, address, Qt.EditRole)
                print(f'Result of setData: {tmp}')
    
            print('Done')
    
    if __name__ == '__main__':
        appctxt = AppContext()
        exit_code = appctxt.run()
        sys.exit(exit_code)
    

    Output log:

    Inserting 1 rows at 0
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    Before setting the new value ['']
    After setting the new value ['0x0800C000']
    Fetching data from column: 0; row: 0
    Data: 0x0800C000
    Fetching data from column: 0; row: 0
    Data: 0x0800C000
    Updating view...
    We have 1 rows
    Inserting 5 new rows for the new data
    Inserting 5 rows at 0
    We now have 6 rows
    Inserting new data into the model...
    [SET_DATA] DATA: 0x0800C000 index row: 0; column 0
    Before setting the new value ['0x0800C000', '', '', '', '', '']
    After setting the new value ['0x0800C000', '', '', '', '', '']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C004 index row: 1; column 0
    Before setting the new value ['0x0800C000', '', '', '', '', '']
    After setting the new value ['0x0800C000', '0x0800C004', '', '', '', '']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C008 index row: 2; column 0
    Before setting the new value ['0x0800C000', '0x0800C004', '', '', '', '']
    After setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '', '', '']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C00C index row: 3; column 0
    Before setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '', '', '']
    After setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '', '']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C010 index row: 4; column 0
    Before setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '', '']
    After setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '']
    Result of setData: True
    [SET_DATA] DATA: 0x0800C014 index row: 5; column 0
    Before setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '']
    After setting the new value ['0x0800C000', '0x0800C004', '0x0800C008', '0x0800C00C', '0x0800C010', '0x0800C014']
    Result of setData: True
    Done
    Fetching data from column: 0; row: 0
    Data: 0x0800C000
    Fetching data from column: 0; row: 1
    Data: 0x0800C004
    Fetching data from column: 0; row: 2
    Data: 0x0800C008
    Fetching data from column: 0; row: 3
    Data: 0x0800C00C
    Fetching data from column: 0; row: 4
    Data: 0x0800C010
    Fetching data from column: 0; row: 5
    Data: 0x0800C014
    

    Current tableView: https://imgur.com/5HMxhrx

    What do you think?



  • @Carlos-Diaz
    Please no more log output, it's not telling us anything! :)

    You need have the following:

    In setData() you must return False if the row is greater than the number of rows currently in the model:

    if index.isValid() and 0 <= index.row() < self.rowCount():
        ....
    else:
        return False
    

    I see you have that now.

    In onUpdateView you must call insertRows(), either to insert at the start or at the end or the existing rows, and then setData() on each of the newly inserted rows.

    You also seem to have that now.

    So I would say what you have looks correct!

    It may or may not be possible to call the insertRows() from within setData(), but I don't think that is setData()'s job anyway. It is correct that it should instead return False, and the coder must explicitly call insertRows() outside himself to add new rows. This is the way the model is designed to behave. That is why you are now able to say

    removed the self.layoutChanged.emit() call and the view keeps working!

    :)



  • @JonB Thanks for the help, now I need to migrate this into the final application.
    I will mark the issue as solved.


Log in to reply