【问题标题】:Make a immediate change to model using QDataWidgetMapper使用 QDataWidgetMapper 立即更改模型
【发布时间】:2020-11-16 01:25:17
【问题描述】:

我有一个 QTableView,它有一列包含 True/False 值。要更改此列中的值,我使用 QDataWidgetMapper 复选框。

我希望在单击复选框后立即更改值。通常在复选框失去焦点后进行更改(换句话说,您需要在应用程序的其他地方进行额外的点击)。

我曾尝试这样做:checkBox.stateChanged.connect(lambda: data_mapper.submit()),但我不喜欢使用这种方法发出太多信号:首先它发出所有映射的信号项目已更改,并且在复选框失去焦点后 - 仅更改一项的信号。

所以目标是在单击复选框后立即更新模型并且只发出一个信号。

代码:

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())

【问题讨论】:

    标签: python python-3.x pyqt pyqt5 datamapper


    【解决方案1】:

    我已经解决了这个问题:现在只在更改后立即发出一个信号。

    首先,正如@eyllanesc 所说,我对 QDataWidgetMapper 使用了“ManualSubmit”策略。

    其次,我为 lineEdit 和 checkBox 编写了自己的自定义函数,而不是 data_mapper.submit()。

    映射小部件和模型之间的手动连接在“for position in range(len(self.model.data_list))”循环中完成一次。

    另外,我想注意的是,在 'data_mapper_settings' 函数中,我使用 'clicked' 而不是 'stateChanged' 来表示 checBox 和 'textEdited' 而不是 'textChanged' 来表示 lineEdit 以避免在选择其他行时发出额外的信号。

    我还是想知道是否有更优雅的方法来解决这个问题

    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())
    

    【讨论】:

      【解决方案2】:

      默认情况下,QDataWidgetMapper 策略是“AutoSubmit”,它会在任何小部件失去焦点时更新模型,因此如果您希望在失去焦点时不发出它,那么您必须使用“ManualSubmit”策略。另一方面,连接必须只进行一次,因为如果你这样做 n 次,则会发出 n 个信号,在你的情况下,每次按下按钮时都会调用“set_data_mapper”。

      class Mainwindow(QtWidgets.QWidget):
          def __init__(self):
              super().__init__()
      
              self.table = QtWidgets.QTableView(
                  selectionBehavior=QtWidgets.QAbstractItemView.SelectRows
              )
              self.widget_1 = Widget_1(self)
      
              lay = QtWidgets.QHBoxLayout(self)
              lay.addWidget(self.table)
              lay.addWidget(self.widget_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.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.setSubmitPolicy(
                  QtWidgets.QDataWidgetMapper.ManualSubmit
              )
      
              self.widget_1.checkBox.stateChanged.connect(self.widget_1.data_mapper.submit)
              self.widget_1.lineEdit.textChanged.connect(self.widget_1.data_mapper.submit)
      
              self.table.clicked.connect(self.update_current_index)
      
              self.model.dataChanged.connect(
                  lambda value: print(value.row(), value.column(), value.data())
              )
      
          def update_current_index(self, index):
              position = index.row()
              self.widget_1.data_mapper.setCurrentIndex(position)
      

      【讨论】:

      • 我仍然很困惑:使用这种方法,我仍然会发出额外的信号。我希望我的应用程序只发出一个信号,例如:如果第 0 行中的复选框被激活 - 输出应该是“0 0 False”,但现在是三个信号“0 0 False”、“0 1 1”和“0” 0 False',尽管没有对 lineEdit 进行任何更改。当我只选择其他行时也会发出信号(但实际上我的模型没有改变)。我在想也许 submit() 函数不是在这里使用的最佳变体
      猜你喜欢
      • 1970-01-01
      • 2012-10-03
      • 1970-01-01
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      • 2018-06-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多