Python PyQt:QDataWidgetMapper自定义属性映射

Python PyQt:QDataWidgetMapper自定义属性映射,python,pyqt,pyqt5,qabstractitemmodel,Python,Pyqt,Pyqt5,Qabstractitemmodel,在PyQt中,我想使用QAbstractItemModel和QDataWidgetMapper将小部件映射到模型数据。 对于QLineEdit,它可以正常工作,但我希望在QButtonGroup(填充了几个QRadioButton)和模型之间有一个映射。 因此,我将QGroupBox子类化,并添加了一个自定义属性selectedOption: class ButtonGroupWidget(QGroupBox): def __init__(self, parent=None):

在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.\u selectedOption设置为当前值,并更新单选按钮。 但是如果我点击另一个单选按钮,我不知道如何更新模型

QWidget是否有类似于数据更改事件的事件,将更新模型

有没有更好的方法将QAbstractItemModel映射到具有多个单选按钮的GroupBox

完整代码(最少的示例,使用Python 3.4和PyQt 5.4测试):

导入系统 从PyQt5.qtwidts导入QWidget、QLineEdit、QRadioButton、QButtonGroup、QApplication、QVBoxLayout、QGroupBox、QTreeView、QDataWidgetMapper 从PyQt5.QtCore导入qabstracttemmodel、QModelIndex、Qt、pyqtProperty “”“基类为树提供一些节点 目前,每个节点包含两个成员:名称和选项“” 类节点(对象): def uuu init uuu(self,name,parent=None): self.\u name=name self._children=[] self.\u parent=父 自选项=2 如果父项不是无: parent.addChild(self) def名称(自我): 返回self.\u name def setName(自我,名称): self.\u name=name def选项(自): 返回自我。\u选项 def设置选项(自身,选项): self.\u option=选项 def addChild(self,child): self.\u children.append(child) def insertChild(自身、位置、子项): 如果位置<0或位置>len(self.\u儿童): 返回错误 self.\u children.insert(位置,子对象) 孩子。_父母=自己 返回真值 def removeChild(自身,位置): 如果位置<0或位置>len(self.\u儿童): 返回错误 child=self.\u children.pop(位置) 子项。\u父项=无 返回真值 def子项(自身,行): 返回自我。\u子对象[行] def儿童计数(自我): 返回len(自身儿童) def父级(自我): 返回自我。\u家长 def行(自身): 如果self.\u parent不是None: 返回self.\u父项.\u子项索引(self) 定义报告(自我): 返回self.log() 类按钮组小部件(QGroupBox): def uuu init uuu(self,parent=None): super(按钮组小部件,self)。\uuuu init\uuuuu(父级) self.\u selectedOption=-1 self.buttonGroup=QButtonGroup() self.layoutGroupBox=QVBoxLayout(self) def ADDRIOBUTTON(自身、optionText、optionId): 打印(optionText) radioButton=QRadioButton(optionText) radioButton.clicked.connect(self.updateOptionSelected) self.layoutGroupBox.addWidget(radioButton) self.buttonGroup.addButton(单选按钮,optionId) def updateOptionSelected(自): 打印(self.buttonGroup.checkedId())#用于测试 self.selectedOption=self.buttonGroup.checkedd() 打印(自选选项)#用于测试 def getSelectedOption(自): 打印(“调用get selectedOption”) 返回自我。\u选择选项 def SETS SELECTED(自定义,SELECTED)选项: 打印(“调用设置选定选项”) self.\u selectedOption=selectedOption self.buttonGroup.button(selectedOption).setChecked(True) selectedOption=pyqtProperty(int、getSelectedOption、setSelectedOption) 类数据模型(QAbstracteModel): def uuu init uuu(self,parent=None): 超级(数据模型,自).\uuuu初始化\uuuuu(父级) self.\u rootNode=节点(“根节点”) childNode1=节点(“子节点1”,self.\u rootNode) childNode2=节点(“子节点2”,self.\u rootNode) childNode3=节点(“子3”,self.\u rootNode) 定义(自身、其他): 返回self.\uu dict.\uuu==其他.\uu dict__ def isNull(自身): nullObject=DataModel() 如果self.\uuuuu eq\uuuuu(空对象): 返回真值 其他: 返回错误 def行数(自身、父级): 如果不是parent.isValid(): parentNode=self.\u rootNode 其他: parentNode=parent.internalPointer() 返回parentNode.childCount() def列数(自身、父项): 返回1 def数据(自身、索引、角色): 如果不是index.isValid(): 一无所获 node=index.internalPointer() 如果角色==Qt.DisplayRole或角色==Qt.EditRole: 如果index.column()==0: 返回node.name() 如果index.column()==1: 返回node.option() def setData(self、index、value、role=Qt.EditRole): if index.isValid(): node=index.internalPointer() 如果角色==Qt.EditRole: 如果index.column()==0: node.setName(值) 如果index.column()==1: node.setOption(值) self.dataChanged.emit(索引,索引) 返回真值 返回错误 def headerData(自身、部门、方向、角色): 如果角色==Qt.DisplayRole: 如果节==0: 返回“选择子对象” def标志(自、索引): 返回Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable def父级(自我,索引): node=index.internalPointer() parentNode=node.parent() 如果parentNode==self.\u rootNode: 返回QModelIndex() 返回self.createIndex(parentNode.row(),0,parentNode) def索引(自身、行、列、父项):
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() 
    def updateOptionSelected(self):
        self.selectedOption = self.buttonGroup.checkedId()
        QApplication.postEvent(
            self, QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier))