Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make a immediate change to model using QDataWidgetMapper

I have a QTableView that has a column with True/False values. To change value in this column I use a QDataWidgetMapper checkBox.

I wish to change a value immediately after clicking a checkbox. Usually a change is made after checkbox looses focus (in other words you need to make extra click somewhere else in application).

I have tried to do it like this: checkBox.stateChanged.connect(lambda: data_mapper.submit()), but I don't like that using this method too many signals are emitted: first it emits signals that all mapped items were changed, and after checkbox looses focus - a signal that only one item was changed.

So the goal is to update model immediately after clicking a checkbox and have only one signal emitted.

Code:

from PyQt5 import QtCore, QtGui, QtWidgets
import sys

class Mainwindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()
        self.widget_1 = Widget_1(self)

        self.layout_1 = QtWidgets.QHBoxLayout()
        self.layout_1.addWidget(self.table)
        self.layout_1.addWidget(self.widget_1)

        self.setLayout(self.layout_1)

        headers = ['State', 'Prorerty_1', 'Prorerty_2']
        data = [
        [True, '1', '2'],
        [False, '3', '4']
        ]

        self.model = ListModel(data, headers)
        self.table.setModel(self.model)
        self.table.setSelectionBehavior(self.table.SelectRows)

        self.table.clicked.connect(self.set_data_mapper)

        self.model.dataChanged.connect(lambda value: print(value.row(), value.column(), value.data()))

    def set_data_mapper(self):
        position = self.table.selectionModel().selectedIndexes()[0].row()

        self.widget_1.data_mapper.setModel(self.model)
        self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
        self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
        self.widget_1.data_mapper.setCurrentIndex(position)

        self.widget_1.checkBox.stateChanged.connect(lambda: self.widget_1.data_mapper.submit())

class Widget_1(QtWidgets.QWidget):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent

        self.layout_1 = QtWidgets.QGridLayout()
        self.state = QtWidgets.QLabel('State')
        self.checkBox = QtWidgets.QCheckBox()
        self.property = QtWidgets.QLabel('Prop.1')
        self.lineEdit = QtWidgets.QLineEdit()

        self.layout_1.addWidget(self.state, 0, 0)
        self.layout_1.addWidget(self.checkBox, 0, 1)
        self.layout_1.addWidget(self.property, 1, 0)
        self.layout_1.addWidget(self.lineEdit, 1, 1)

        self.setLayout(self.layout_1)

        self.data_mapper = QtWidgets.QDataWidgetMapper()

class ListModel(QtCore.QAbstractTableModel):

    def __init__(self, data_list = [[]], headers = [], parent = None):
        super(ListModel, self).__init__()
        self.data_list = data_list
        self.headers = headers

    def rowCount(self, parent):
        return len(self.data_list)

    def columnCount(self, parent):
        return len(self.headers)

    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.data_list[row][column]
            return value

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            value = self.data_list[row][column]
            return value

        if role == QtCore.Qt.FontRole:
            if index.column() == 0:
                boldfont = QtGui.QFont()
                boldfont.setBold(True)
                return boldfont

    def setData(self, index, value, role = QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.data_list[row][column] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.headers[section]

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    application = Mainwindow()
    application.show()


    sys.exit(app.exec())
like image 724
Nasty_gri Avatar asked Mar 04 '26 15:03

Nasty_gri


1 Answers

I have figured out this problem: now only one signal is emitted immediately after change.

First, I used "ManualSubmit" policy for QDataWidgetMapper , as @eyllanesc told.

Second, I wrote my own custom function for both lineEdit and a checkBox instead data_mapper.submit().

Manual connection between mapped widgets and model is done once in 'for position in range(len(self.model.data_list))' loop.

Also, I want to pay attention that inside 'data_mapper_settings' function I used 'clicked' instead 'stateChanged' for checBox and 'textEdited' instead 'textChanged' for lineEdit to avoid emitting extra signals when other row is chosen.

Still I wonder if there's more elegant way to solve this problem

from PyQt5 import QtCore, QtGui, QtWidgets
import sys

class Mainwindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()
        self.widget_1 = Widget_1(self)

        self.layout_1 = QtWidgets.QHBoxLayout()
        self.layout_1.addWidget(self.table)
        self.layout_1.addWidget(self.widget_1)

        self.setLayout(self.layout_1)

        headers = ['State', 'Prorerty_1', 'Prorerty_2']
        data = [
        [True, '1', '2'],
        [False, '3', '4']
        ]

        self.model = ListModel(data, headers)
        self.table.setModel(self.model)
        self.table.setSelectionBehavior(self.table.SelectRows)

        self.table.clicked.connect(lambda index: self.set_data_mapper(index))

        for position in range(len(self.model.data_list)):
            self.data_mapper_settings(position)


        self.model.dataChanged.connect(lambda index: print(index.row(), index.column(), index.data()))


    def set_data_mapper(self, index):
        position = index.row()

        self.widget_1.data_mapper.setModel(self.model)
        self.widget_1.data_mapper.addMapping(self.widget_1.checkBox, 0)
        self.widget_1.data_mapper.addMapping(self.widget_1.lineEdit, 1)
        self.widget_1.data_mapper.setCurrentIndex(position)

    def data_mapper_settings(self, position):

        self.widget_1.checkBox.clicked.connect(lambda value: self.submit_checkbox(value, position))
        self.widget_1.lineEdit.textEdited.connect(lambda value: self.submit_lineEdit(value, position))

    def submit_checkbox(self, value, position):
        if position == self.table.selectionModel().selectedIndexes()[0].row():
            self.model.setData(self.model.index(position, 0), value)

    def submit_lineEdit(self, value, position):
        if position == self.table.selectionModel().selectedIndexes()[0].row():
            self.model.setData(self.model.index(position, 1), value)

class Widget_1(QtWidgets.QWidget):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent

        self.layout_1 = QtWidgets.QGridLayout()
        self.state = QtWidgets.QLabel('State')
        self.checkBox = QtWidgets.QCheckBox()
        self.property = QtWidgets.QLabel('Prop.1')
        self.lineEdit = QtWidgets.QLineEdit()

        self.layout_1.addWidget(self.state, 0, 0)
        self.layout_1.addWidget(self.checkBox, 0, 1)
        self.layout_1.addWidget(self.property, 1, 0)
        self.layout_1.addWidget(self.lineEdit, 1, 1)

        self.setLayout(self.layout_1)

        self.data_mapper = QtWidgets.QDataWidgetMapper()
        self.data_mapper.setSubmitPolicy(QtWidgets.QDataWidgetMapper.ManualSubmit)

class ListModel(QtCore.QAbstractTableModel):

    def __init__(self, data_list = [[]], headers = [], parent = None):
        super(ListModel, self).__init__()
        self.data_list = data_list
        self.headers = headers

    def rowCount(self, parent):
        return len(self.data_list)

    def columnCount(self, parent):
        return len(self.headers)

    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.data_list[row][column]
            return value

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            value = self.data_list[row][column]
            return value

        if role == QtCore.Qt.FontRole:
            if index.column() == 0:
                boldfont = QtGui.QFont()
                boldfont.setBold(True)
                return boldfont

    def setData(self, index, value, role = QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.data_list[row][column] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.headers[section]

    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsUserCheckable

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    application = Mainwindow()
    application.show()


    sys.exit(app.exec())
like image 87
Nasty_gri Avatar answered Mar 06 '26 04:03

Nasty_gri



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!