编程语言
首页 > 编程语言> > python – PyQt:自定义属性的QDataWidgetMapper映射

python – PyQt:自定义属性的QDataWidgetMapper映射

作者:互联网

在PyQt中,我想使用QAbstractItemModel和QDataWidgetMapper将小部件映射到模型数据.
对于QLineEdit,它工作正常,但我想在QButtonGroup(填充了几个QRadioButton)和模型之间进行映射.
因此我将QGroupBox子类化并添加了一个自定义属性selectedOption:

class ButtonGroupWidget(QGroupBox):
    def __init__(self, parent=None):
        super(ButtonGroupWidget, self).__init__(parent)
        self._selectedOption = -1
        self.buttonGroup = QButtonGroup()
        self.layoutGroupBox = QVBoxLayout(self)

    def addRadioButton(self,optionText,optionId):
        print(optionText)
        radioButton = QRadioButton(optionText)
        radioButton.clicked.connect(self.updateOptionSelected)
        self.layoutGroupBox.addWidget(radioButton)
        self.buttonGroup.addButton(radioButton,optionId)

    def updateOptionSelected(self):
        print(self.buttonGroup.checkedId()) # for test purpose
        self.selectedOption = self.buttonGroup.checkedId()
        print(self._selectedOption) # for test purpose

    def getSelectedOption(self):
        print("get selectedOption is called")
        return self._selectedOption

    def setSelectedOption(self,selectedOption):
        print("set selectedOption is called")
        self._selectedOption = selectedOption
        self.buttonGroup.button(selectedOption).setChecked(True)

    selectedOption = pyqtProperty(int,getSelectedOption,setSelectedOption)

在主窗口小部件中,我创建了一个ButtonGroupWidget的实例:

class MainWidget(QWidget):

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

        ...

        # insert a button group widget
        self.buttonGroupWidget1 = ButtonGroupWidget(self)
        self.buttonGroupWidget1.setTitle("Select option")
        self.buttonGroupWidget1.addRadioButton("Option 1", 1)
        self.buttonGroupWidget1.addRadioButton("Option 2", 2)
        self.buttonGroupWidget1.addRadioButton("Option 3", 3)
        ...

        # Create the data model and map the model to the widgets.
        self._model = DataModel()
        ...

        self._dataMapper = QDataWidgetMapper()
        self._dataMapper.setModel(self._model)
        ...
        # mapping to custom property
        self._dataMapper.addMapping(self.buttonGroupWidget1,1,"selectedOption")

完整代码(工作最小示例)如下.
我的问题如下:
如果模型已更改(例如,您在完整代码中看到的lineEdit),则调用setSelectedOption方法并将self._selectedOption设置为当前值并更新radioButtons.
但如果我点击另一个radioButton,我不知道如何更新模型.

是否有像QWidget的数据更改事件,这将更新模型?

有没有更好的方法将QAbstractItemModel映射到具有多个radioButtons的GroupBox?

完整代码(最小的例子,用Python 3.4和PyQt 5.4测试):

import sys
from PyQt5.QtWidgets import QWidget, QLineEdit, QRadioButton, QButtonGroup, QApplication, QVBoxLayout, QGroupBox, QTreeView,QDataWidgetMapper
from PyQt5.QtCore import QAbstractItemModel,QModelIndex,Qt,pyqtProperty

"""Base class to provide some nodes fro a tree
At the moment each node contains two members: name and option"""
class Node(object):

    def __init__(self,name,parent=None):

        self._name = name
        self._children = []
        self._parent = parent
        self._option = 2

        if parent is not None:
            parent.addChild(self)

    def name(self):
        return self._name

    def setName(self, name):
        self._name = name

    def option(self):
        return self._option

    def setOption(self,option):
        self._option = option  

    def addChild(self,child):
        self._children.append(child)

    def insertChild(self, position, child):
        if position < 0 or position > len(self._children):
            return False
        self._children.insert(position, child)
        child._parent = self
        return True

    def removeChild(self, position):
        if position < 0 or position > len(self._children):
            return False
        child = self._children.pop(position)
        child._parent = None
        return True

    def child(self,row):
        return self._children[row]

    def childCount(self):
        return len(self._children)

    def parent(self):
        return self._parent

    def row(self):
        if self._parent is not None:
            return self._parent._children.index(self)

    def __repr__(self):
        return  self.log()


class ButtonGroupWidget(QGroupBox):
    def __init__(self, parent=None):
        super(ButtonGroupWidget, self).__init__(parent)
        self._selectedOption = -1
        self.buttonGroup = QButtonGroup()
        self.layoutGroupBox = QVBoxLayout(self)

    def addRadioButton(self,optionText,optionId):
        print(optionText)
        radioButton = QRadioButton(optionText)
        radioButton.clicked.connect(self.updateOptionSelected)
        self.layoutGroupBox.addWidget(radioButton)
        self.buttonGroup.addButton(radioButton,optionId)

    def updateOptionSelected(self):
        print(self.buttonGroup.checkedId()) # for test purpose
        self.selectedOption = self.buttonGroup.checkedId()
        print(self._selectedOption) # for test purpose

    def getSelectedOption(self):
        print("get selectedOption is called")
        return self._selectedOption

    def setSelectedOption(self,selectedOption):
        print("set selectedOption is called")
        self._selectedOption = selectedOption
        self.buttonGroup.button(selectedOption).setChecked(True)

    selectedOption = pyqtProperty(int,getSelectedOption,setSelectedOption)


class DataModel(QAbstractItemModel):

    def __init__(self, parent=None):
        super(DataModel, self).__init__(parent)
        self._rootNode = Node("Root Node")
        childNode1 = Node("Child 1",self._rootNode)
        childNode2 = Node("Child 2",self._rootNode)
        childNode3 = Node("Child 3",self._rootNode)

    def __eq__(self, other): 
        return self.__dict__ == other.__dict__

    def isNull(self):
        nullObject = DataModel()
        if self.__eq__(nullObject):
            return True
        else:
            return False

    def rowCount(self, parent):
        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()
        return parentNode.childCount()

    def columnCount(self, parent):
        return 1

    def data(self,index,role):
        if not index.isValid():
            return None
        node = index.internalPointer()
        if role == Qt.DisplayRole or role == Qt.EditRole:
            if index.column() == 0:
                return node.name()
            if index.column() == 1:
                return node.option()

    def setData(self,index,value, role=Qt.EditRole):
        if index.isValid():
            node = index.internalPointer()
            if role == Qt.EditRole:
                if index.column() == 0:
                    node.setName(value)
                if index.column() == 1:
                    node.setOption(value)
                self.dataChanged.emit(index,index)
                return True            
        return False   

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if section == 0:
                return "Select Child"

    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable

    def parent(self, index):
        node = index.internalPointer()
        parentNode = node.parent()
        if parentNode == self._rootNode:
            return QModelIndex()
        return self.createIndex(parentNode.row(), 0, parentNode)

    def index(self, row, column, parent):

        if not parent.isValid():
            parentNode = self._rootNode
        else:
            parentNode = parent.internalPointer()

        childItem = parentNode.child(row)

        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QModelIndex()

    def getNode(self, index):
        if index.isValid():
            node = index.internalPointer()
            if node:
                return node

        return self._rootNode

class MainWidget(QWidget):

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

        # define tree view
        self.treeView = QTreeView(self)

        # insert line edit
        self.lineEdit1 = QLineEdit(self)
        self.lineEdit2 = QLineEdit(self)

        # insert a button group widget
        self.buttonGroupWidget1 = ButtonGroupWidget(self)
        self.buttonGroupWidget1.setTitle("Select option")
        self.buttonGroupWidget1.addRadioButton("Option 1", 1)
        self.buttonGroupWidget1.addRadioButton("Option 2", 2)
        self.buttonGroupWidget1.addRadioButton("Option 3", 3)

        layoutMain = QVBoxLayout(self)
        layoutMain.addWidget(self.treeView)
        layoutMain.addWidget(self.lineEdit1)
        layoutMain.addWidget(self.lineEdit2)
        layoutMain.addWidget(self.buttonGroupWidget1)

        # Create the data model and map the model to the widgets.
        self._model = DataModel()
        self.treeView.setModel(self._model)

        self._dataMapper = QDataWidgetMapper()
        self._dataMapper.setModel(self._model)
        # the mapping works fine for line edits and combo boxes
        self._dataMapper.addMapping(self.lineEdit1, 0)
        self._dataMapper.addMapping(self.lineEdit2, 1)
        # mapping to custom property
        self._dataMapper.addMapping(self.buttonGroupWidget1,1,"selectedOption")

        self.treeView.selectionModel().currentChanged.connect(self.setSelection)

    def setSelection(self, current):

        parent = current.parent()
        self._dataMapper.setRootIndex(parent)
        self._dataMapper.setCurrentModelIndex(current) 


def main():
    app = QApplication(sys.argv)
    form = MainWidget()
    form.show()
    app.exec_()

main() 

解决方法:

当单选按钮选项更改时,模型将更新,但仅在按下return或enter时才会更新.

原因是数据映射器使用item-delegate来通知模型的更改,而item-delegate会在映射的每个widget上安装事件过滤器.但事件过滤器仅监视按键和焦点更改,而不是鼠标单击,并且在任何情况下,它都无法接收来自子窗口小部件的事件.

解决此限制的一种温和的hackish方法是在单击单选按钮时模拟适当的按键:

    def updateOptionSelected(self):
        self.selectedOption = self.buttonGroup.checkedId()
        QApplication.postEvent(
            self, QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier))

标签:python,pyqt,pyqt5,qabstractitemmodel
来源: https://codeday.me/bug/20190708/1400135.html