如何将 属性 从 python 公开到 QMLin 树视图

how to expose the property from python to QMLin tree view

我有一个要求,我必须将在 python 中编写的某些属性公开到树视图的 QML 端我无法这样做,因为我不太确定如何去做。

这是我为我的 treeview 后端编写的代码,以便我解析我的 treeviewModel class

中的 JSON 模型

tree_Model.py

import PySide2
from PySide2 import QtCore
from PySide2.QtCore import QAbstractItemModel, QByteArray
from PySide2.QtGui import Qt
from TreeItem import TreeItem


class tree_Model(QAbstractItemModel):
    SchoolNameRole = Qt.UserRole + 1
    FirstNameRole = Qt.UserRole + 2
    LastNameRole = Qt.UserRole + 3
    GenderRole = Qt.UserRole + 4
    AgeRole = Qt.UserRole + 5
    PhoneNumberRole = Qt.UserRole + 6

def __init__(self , data , parent = None):
    super(tree_Model, self).__init__(parent)
    print("wohoo came off pa")
    self.rootItem = TreeItem({"SchoolName","FirstName","LastName","Gender","Age","PhoneNumber"})
    self.setupModelData(data,self.rootItem)


def roleNames(self):
    roles = {
        tree_Model.SchoolNameRole: QByteArray(b'SchoolName'),
        tree_Model.FirstNameRole: QByteArray(b'FirstName'),
        tree_Model.LastNameRole: QByteArray(b'LastName'),
        tree_Model.GenderRole: QByteArray(b'Gender'),
        tree_Model.AgeRole: QByteArray(b'Age'),
        tree_Model.PhoneNumberRole: QByteArray(b'PhoneNumber')
        }
    return roles

def columnCount(self, parent: PySide2.QtCore.QModelIndex) -> int:
    if parent.isValid():
        return parent.internalPointer().columnCount()
    else:
        return self.rootItem.columnCount()

def data(self, index: PySide2.QtCore.QModelIndex, role: int):
    if not index.isValid():
        return None
    item: TreeItem = index.internalPointer()
    if role == tree_Model.SchoolNameRole:
        if item.isSchool_QProperty:
            return item

    if role == tree_Model.FirstNameRole:
        if not item.isSchool_QProperty:
            return item.StudentData['Firstname']

    if role == tree_Model.LastNameRole:

        if not item.isSchool_QProperty:
            return item.StudentData['LastName']

    if role == tree_Model.GenderRole:
        if not item.isSchool_QProperty:
            return item.StudentData['Gender']

    if role == tree_Model.AgeRole:
        if not item.isSchool_QProperty:
            return item.StudentData['Age']

    if role == tree_Model.PhoneNumberRole:
        if not item.isSchool_QProperty:
            return item.StudentData['PhoneNumber']

    if role == QtCore.Qt.EditRole:
        return item.value

    if role != QtCore.Qt.DisplayRole:
        return None

    return item.StudentData

def flags(self, index: PySide2.QtCore.QModelIndex) -> PySide2.QtCore.Qt.ItemFlags:

    if not index.isValid():
        return QtCore.Qt.NoItemFlags

    if index.internalPointer().isStudent:
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable
    else:
        return QtCore.Qt.NoItemFlags

def headerData(self, section: int, orientation: PySide2.QtCore.Qt.Orientation, role: int):
    if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
        return self.rootItem.data(section)
    return None

def index(self, row: int, column: int,
          parent: PySide2.QtCore.QModelIndex) -> PySide2.QtCore.QModelIndex:

    if not self.hasIndex(row, column, parent):
        return QtCore.QModelIndex()

    if not parent.isValid():
        parentItem = self.rootItem
    else:
        parentItem = parent.internalPointer()  # type: ignore[assignment] #[BLRFPGT-869]

    childItem = parentItem.child(row)
    if childItem:
        return self.createIndex(row, column, childItem)
    else:
        return QtCore.QModelIndex()
def parent(self, index: PySide2.QtCore.QModelIndex) -> PySide2.QtCore.QModelIndex:

    if not index.isValid():
        return QtCore.QModelIndex()

    childItem = index.internalPointer()
    parentItem: TreeItem = childItem.parent()

    if parentItem == self.rootItem:
        return QtCore.QModelIndex()
    return self.createIndex(parentItem.row(), 0, parentItem)

def rowCount(self, parent: PySide2.QtCore.QModelIndex) -> int:

    if parent.column() > 0:
        return 0

    if not parent.isValid():
        parentItem = self.rootItem
    else:
        parentItem = parent.internalPointer()

    return parentItem.childCount()

def setupModelData(self,jsondata, parent: TreeItem) -> None:
    parents = [parent]
    number = 0

    while number < len(jsondata):
        position = 0
        while position < len(jsondata[number]):
            if jsondata[number][position] != ' ':
                break
            position += 1

        json_value = jsondata[number][position:]
        if json_value:
            schoolItem = TreeItem(json_value[0], parents[-1])
            schoolItem.isSchool_QProperty = True
            schoolItem.isStudent_QProperty = False
            schoolItem.schoolName_QProperty = json_value[0]
            for students in json_value[1]:
                studentItem = TreeItem(students, schoolItem)
                schoolItem.appendChild(studentItem)
                studentItem.isStudent_QProperty = True
                studentItem.isSchool_QProperty = False
            parents[-1].appendChild(schoolItem)
        number += 1

TreeItem.py

class TreeItem(QObject):
    def __init__(self, data, parent = None):
        super().__init__(parent)
        self.parentItem = parent
        self.StudentData = data
        self.childItems = []
        self._schoolName = None
        self._isSchool = False
        self._isStudent = False
    def appendChild(self, item) ->None:
        self.childItems.append(item)

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

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

    def columnCount(self):
        return len(self.StudentData)

    def data(self, column: int):
        try:
            data = self.StudentData[column]
            return data
        except IndexError:
            return None

    def parent(self):
        return self.parentItem

    def row(self) -> int:
        if self.parentItem:
            return self.parentItem.childItems.index(self)
        return 0

    def schoolName(self) -> str:
        return self._schoolName

    def schoolName_set(self, schoolName: str):
        self._schoolName = schoolName

    schoolName_QProperty = Property(str, schoolName, schoolName_set)

    def isSchool(self) -> bool:
        return self._isSchool

    def isSchool_set(self, isSchool: bool) -> None:
        self._isSchool = isSchool

    isSchool_QProperty = Property(bool, isSchool, isSchool_set)

    def isStudent(self) -> bool:
        return self._isStudent

    def isStudent_set(self, isStudent: bool) -> None:
            self._isStudent = isStudent

    isStudent_QProperty = Property(bool, isStudent, isStudent_set)

我已经通过将它们设为 QProperty 将每个项目暴露给 qml。

main.py

import json
import os
from pathlib import Path
import sys

from PySide2.QtGui import QGuiApplication,Qt
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtQuick import QQuickView
from tree_Model import tree_Model


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    school_Student_Model = []
    with open("sample4.json") as f:
        data = json.load(f)
        for i in data['SchoolList']:
            print("the school names are", i['SchoolName'])
            school_name = i['SchoolName']
            students_info = i["Students"]
            school_Student_Model.append((school_name, students_info))
    model = tree_Model(school_Student_Model)
    model.setHeaderData(0, Qt.Horizontal, "ID")
    engine.rootContext().setContextProperty("schoolModel", model)
    engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

在 QML 中我如何访问这些 QProperty 这是我尝试过的但它似乎不起作用

main.qml

import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 1.4

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    TreeView{
        id: schoolTreeView
        model: schoolModel
        height: parent.height
        width: parent.width
        alternatingRowColors: true
        TableViewColumn{
            id:schoolName_id
            title: "SchoolName"
            role: "SchoolName"
            width: 100
            delegate: someDelegate

        }

        Component{
            id:someDelegate
            Item {
                id: schoolname_id
                Label{
                    text: styleData.value.schoolName_QProperty
                }
            }
        }
        TableViewColumn{
            id:student_first_Name_id
            title: "FirstName"
            role: "FirstName"
            width: 100

        }
        TableViewColumn{
            id:student_last_Name_id
            title: "LastName"
            role: "LastName"
            width: 100

        }
        TableViewColumn{
            id:student_gender_id
            title: "Gender"
            role: "Gender"
            width: 300
            delegate: gender_delegate
        }
        Component{
            id:gender_delegate
            Item {
                visible: model.Gender.isStudent_QProperty? false : true
               // visible: styleData.value.isStudent_QProperty? true : false
                Row{
                    spacing: 10
                    RadioButton{
                        id:male_button
                        text: "Male"
                        checked: styleData.value === "male"? true : false
                    }
                    RadioButton{
                        text: "Female"
                        checked: male_button.checked? false : true
                    }
                }
            }
        }

        TableViewColumn{
            id:student_age_id
            title: "Age"
            role: "Age"
            width: 100

        }
        TableViewColumn{
            id:student_ph_num_id
            title: "PhoneNumber"
            role: "PhoneNumber"
            width: 100

        }
    }
}

JSON文件是,放在项目的工作目录下 sample4.json

    {
"SchoolList": [
    {
    "SchoolName": "Faps",
    "Students": [
        {
            "Firstname": "abc",
            "LastName": "ask",
            "Age": 25,
            "Gender": "male",
            "PhoneNumber": "32423423"
        },
        {
            "Firstname": "def",
            "LastName": "ask",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "1278371267"
        },
        {
            "Firstname": "ijk",
            "LastName": "lmn",
            "Age": 25,
            "Gender": "Female",
            "PhoneNumber": "8372873187"
        },
        {
            "Firstname": "opq",
            "LastName": "rst",
            "Age": 25,
            "Gender": "Female",
            "PhoneNumber": "2434234234"
        },
        {
            "Firstname": "uvw",
            "LastName": "xyz",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "124331423"
        },
        {
            "Firstname": "abcd",
            "LastName": "efgh",
            "Age": 12,
            "Gender": "male",
            "PhoneNumber": "1231323424"
        }
    ]
    },
{
    "SchoolName": "Germains",
    "Students": [
        {
            "Firstname": "lmno",
            "LastName": "pqr",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "234234"
        },
        {
            "Firstname": "stuv",
            "LastName": "wxyz",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "123213"
        },
        {
            "Firstname": "sfadf",
            "LastName": "dfsdf",
            "Age": 26,
            "Gender": "Female",
            "PhoneNumber": "12323423"
        },
        {
            "Firstname": "dsfsd",
            "LastName": "sdfsd",
            "Age": 30,
            "Gender": "Female",
            "PhoneNumber": "1231323"
        },
        {
            "Firstname": "sdfsd",
            "LastName": "sdfsd",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "124331423"
        },
        {
            "Firstname": "awdas",
            "LastName": "cvfs",
            "Age": 26,
            "Gender": "male",
            "PhoneNumber": "1231323424"
        }
    ]
}   
]
}

当代码为 运行 时 pycharm 我们将看到这样的输出

有应用于父元素的男性女性单选按钮,我怎样才能确保删除父元素的那些单选按钮并仅将它们应用于子元素。

该结构令人困惑,因此我将从头开始实施该模型以使其可读。另一方面,与其改变项目的可见性,不如使用加载器,这样我们只在需要时构建项目。

注意:我稍微更改了 json 以便键与角色匹配:

from dataclasses import dataclass, field
from enum import IntEnum, auto
import json
import os
from pathlib import Path

from dataclasses_json import dataclass_json

from PySide2.QtCore import (
    QAbstractItemModel,
    QByteArray,
    QCoreApplication,
    QModelIndex,
    Qt,
    QUrl,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

CURRENT_DIRECTORY = Path(__file__).resolve().parent


@dataclass_json
@dataclass
class Student:
    FirstName: str
    LastName: str
    Age: int
    Gender: str
    PhoneNumber: str


@dataclass_json
@dataclass
class School:
    SchoolName: str
    Students: list[Student] = field(default_factory=list)


class SchoolRoles(IntEnum):
    SchoolNameRole = Qt.UserRole


class StudentRoles(IntEnum):
    FirstnameRole = SchoolRoles.SchoolNameRole + 1
    LastNameRole = auto()
    AgeRole = auto()
    GenderRole = auto()
    PhoneNumberRole = auto()


ROLES_MAPPPING = {
    SchoolRoles.SchoolNameRole: "SchoolName",
    StudentRoles.FirstnameRole: "FirstName",
    StudentRoles.LastNameRole: "LastName",
    StudentRoles.AgeRole: "Age",
    StudentRoles.AgeRole: "Age",
    StudentRoles.GenderRole: "Gender",
    StudentRoles.PhoneNumberRole: "PhoneNumber",
}


@dataclass
class Model(QAbstractItemModel):
    schools: list[School] = field(default_factory=list)

    def __post_init__(self):
        super().__init__()

        self._rolenames = dict()
        self._rolenames[Qt.DisplayRole] = QByteArray(b"display")
        for role, name in ROLES_MAPPPING.items():
            self._rolenames[role] = QByteArray(name.encode())

    def add_school(self, school):
        self.schools.append(school)

    def columnCount(self, index=QModelIndex()):
        return 1

    def roleNames(self):
        return self._rolenames

    def rowCount(self, parent):
        if parent.isValid():
            item = parent.internalPointer()
            if isinstance(item, School):
                return len(item.Students)
            return 0
        else:
            return len(self.schools)

    def index(self, row, column, parent=QModelIndex()):
        if column != 0:
            return QModelIndex()
        if not self.hasIndex(row, column, parent):
            return QModelIndex()
        if parent.isValid():
            item = parent.internalPointer()
            if isinstance(item, School):
                return self.createIndex(row, column, item.Students[row])
            return QModelIndex()
        else:
            return self.createIndex(row, column, self.schools[row])

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()
        item = index.internalPointer()
        if isinstance(item, School):
            return QModelIndex()
        elif isinstance(item, Student):
            row = -1
            parent_item = None
            for i, school in enumerate(self.schools):
                if item in school.Students:
                    row = i
                    parent_item = school
                    break
            if row >= 0:
                return self.createIndex(row, 0, parent_item)
        return QModelIndex()

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return
        item = index.internalPointer()
        if role == Qt.DisplayRole:
            return str(index.internalPointer())
        elif role == SchoolRoles.SchoolNameRole:
            if isinstance(item, School):
                return item.SchoolName
            elif isinstance(item, Student):
                return index.parent().internalPointer().SchoolName
            return
        elif isinstance(item, Student):
            prop = ROLES_MAPPPING.get(role)
            if prop is not None:
                return getattr(item, prop)


def main():
    app = QGuiApplication()

    model = Model()

    with open(CURRENT_DIRECTORY / "sample.json") as f:
        data = json.load(f)
        for dict_school in data.get("SchoolList", []):
            school = School.from_dict(dict_school)
            model.add_school(school)

    engine = QQmlApplicationEngine()

    engine.rootContext().setContextProperty("schoolModel", model)
    filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
    url = QUrl.fromLocalFile(filename)

    def handle_object_created(obj, obj_url):
        if obj is None and url == obj_url:
            QCoreApplication.exit(-1)

    engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
    engine.load(url)

    app.exec_()


if __name__ == "__main__":
    main()
import QtQuick 2.14
import QtQuick.Controls 1.4
import QtQuick.Window 2.14

Window {
    id: root

    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    TreeView {
        id: schoolTreeView

        model: schoolModel
        anchors.fill: parent
        alternatingRowColors: true

        TableViewColumn {
            id: schoolName_id

            title: "SchoolName"
            role: "SchoolName"
            width: 100
        }

        TableViewColumn {
            id: student_first_Name_id

            title: "FirstName"
            role: "FirstName"
            width: 100
        }

        TableViewColumn {
            id: student_last_Name_id

            title: "LastName"
            role: "LastName"
            width: 100
        }

        TableViewColumn {
            title: "Gender"
            role: "Gender"
            width: 150

            delegate: Loader {
                id: loader

                property string gender: styleData.value ? styleData.value : ""

                sourceComponent: styleData.value ? customDelegate : null
            }

        }

        TableViewColumn {
            id: student_age_id

            title: "Age"
            role: "Age"
            width: 100
        }

        TableViewColumn {
            id: student_ph_num_id

            title: "PhoneNumber"
            role: "PhoneNumber"
            width: 100
        }

    }

    property Component customDelegate: Row {
        spacing: 10

        RadioButton {
            id: male_button

            text: "Male"
            checked: gender === "male"
        }

        RadioButton {
            text: "Female"
            checked: gender === "Female"
        }

    }

}
{
    "SchoolList": [
        {
            "SchoolName": "Faps",
            "Students": [
                {
                    "FirstName": "abc",
                    "LastName": "ask",
                    "Age": 25,
                    "Gender": "male",
                    "PhoneNumber": "32423423"
                },
                {
                    "FirstName": "def",
                    "LastName": "ask",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "1278371267"
                },
                {
                    "FirstName": "ijk",
                    "LastName": "lmn",
                    "Age": 25,
                    "Gender": "Female",
                    "PhoneNumber": "8372873187"
                },
                {
                    "FirstName": "opq",
                    "LastName": "rst",
                    "Age": 25,
                    "Gender": "Female",
                    "PhoneNumber": "2434234234"
                },
                {
                    "FirstName": "uvw",
                    "LastName": "xyz",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "124331423"
                },
                {
                    "FirstName": "abcd",
                    "LastName": "efgh",
                    "Age": 12,
                    "Gender": "male",
                    "PhoneNumber": "1231323424"
                }
            ]
        },
        {
            "SchoolName": "Germains",
            "Students": [
                {
                    "FirstName": "lmno",
                    "LastName": "pqr",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "234234"
                },
                {
                    "FirstName": "stuv",
                    "LastName": "wxyz",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "123213"
                },
                {
                    "FirstName": "sfadf",
                    "LastName": "dfsdf",
                    "Age": 26,
                    "Gender": "Female",
                    "PhoneNumber": "12323423"
                },
                {
                    "FirstName": "dsfsd",
                    "LastName": "sdfsd",
                    "Age": 30,
                    "Gender": "Female",
                    "PhoneNumber": "1231323"
                },
                {
                    "FirstName": "sdfsd",
                    "LastName": "sdfsd",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "124331423"
                },
                {
                    "FirstName": "awdas",
                    "LastName": "cvfs",
                    "Age": 26,
                    "Gender": "male",
                    "PhoneNumber": "1231323424"
                }
            ]
        }
    ]
}