从一个槽返回一个自定义的Python模型?

huangapple go评论73阅读模式
英文:

Returning a custom Python model from a slot?

问题

我正在尝试为一个 List 设置详情窗口。

我已经设置了一个点击项目会创建一个新 Window 并将点击项目的信息传递到这个新 Window 的功能,我然后尝试将嵌套的 List 的模型分配给一个 Python 函数的返回值(使用 @Slot 装饰器)。这个函数基于传入的信息实例化一个新的自定义模型(从 QAbstractListModel 继承),然后返回它。

我的问题是,当我点击一个项目以打开窗口时,会抛出以下错误:

file:///home/cassidy/Projects/portmod/portmod/_gui/Manage/PackageDetailsWindow.qml:50: TypeError: Property 'get_local_flags_model' of object Config(0x55907ace9e70) is not a function

这是我的Python函数,它存储在传递给 QML 上下文作为 configConfig 类中:

@QtCore.Slot(str, result=QtCore.QObject)  # type: ignore
def get_local_flags_model(self, atom: str) -> FlagListModel:
    return FlagListModel(get_local_flags(Atom(atom)))

这是我的自定义模型:

from typing import Any, Dict, Tuple, Union

from PySide6.QtCore import QAbstractListModel, QModelIndex, QPersistentModelIndex, Qt

class FlagListModel(QAbstractListModel):
    def __init__(self, use_flags: Dict[str, Tuple[bool, str]]):
        super().__init__()

        self._data = use_flags

    def rowCount(self) -> int:
        return len(self._data)

    def data(
        self,
        index: Union[QModelIndex, QPersistentModelIndex],
        role: int,
    ) -> Any:
        if role == Qt.ItemDataRole.DisplayRole:
            return list(self._data.keys())[index.row()]
        elif role == Qt.ItemDataRole.CheckStateRole:
            state = list(self._data.values())[index.row()][0]
            if state:
                return Qt.CheckState.Checked
            else:
                return Qt.CheckState.Unchecked

我的详情窗口:

import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0

Window {
    id: detailsWindow

    property string atom

    title: detailsWindow.atom
    height: root.height / 1.5
    width: root.width / 1.5

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10

        // TODO: 如果没有USE flags,则不显示
        GroupBox {
            title: "USE Flags"
            Layout.fillWidth: true
            Layout.fillHeight: true
            ListView {
                id: useListView
                clip: true
                model: config.get_local_flags_model(detailsWindow.atom);
                width: parent.width
                height: parent.height
                delegate: CheckDelegate {
                    text: modelData
                    checkState: model.check
                    width: ListView.view.width - 20
                }
                
                ScrollBar.vertical: ScrollBar {}            
            }
        }
    }
}
英文:

I'm trying to setup up a details window for a List.

I've already set something up where clicking on an item creates a new Window with Qt.createComponent, and information from the clicked item is passed to this new Window. I then try to assign the model of a nested List to the return value of a Python function (decorated with @Slot). This function instantiates a new custom model (subclassed from QAbstractListModel) based on the information passed in, and returns it.

My problem is, when I click on an item to launch the window, it throws this error:

file:///home/cassidy/Projects/portmod/portmod/_gui/Manage/PackageDetailsWindow.qml:50: TypeError: Property 'get_local_flags_model' of object Config(0x55907ace9e70) is not a function

Here's my Python function. This is stored in a Config class that's passed to the QML context as config:

@QtCore.Slot(str, result=QtCore.QObject)  # type: ignore
def get_local_flags_model(self, atom: str) -> FlagListModel:
    return FlagListModel(get_local_flags(Atom(atom)))

Here's my custom model:

from typing import Any, Dict, Tuple, Union

from PySide6.QtCore import QAbstractListModel, QModelIndex, QPersistentModelIndex, Qt


class FlagListModel(QAbstractListModel):
    def __init__(self, use_flags: Dict[str, Tuple[bool, str]]):
        super().__init__()

        self._data = use_flags

    def rowCount(self) -> int:
        return len(self._data)

    def data(
        self,
        index: Union[QModelIndex, QPersistentModelIndex],
        role: int,
    ) -> Any:
        if role == Qt.ItemDataRole.DisplayRole:
            return list(self._data.keys())[index.row()]
        elif role == Qt.ItemDataRole.CheckStateRole:
            state = list(self._data.values())[index.row()][0]
            if state:
                return Qt.CheckState.Checked
            else:
                return Qt.CheckState.Unchecked

My details window:

import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0

Window {
    id: detailsWindow

    property string atom

    title: detailsWindow.atom
    height: root.height / 1.5
    width: root.width / 1.5

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10

        // TODO: Don't show if there are no USE flags
        GroupBox {
            title: "USE Flags"
            Layout.fillWidth: true
            Layout.fillHeight: true
            ListView {
                id: useListView
                clip: true
                model: config.get_local_flags_model(detailsWindow.atom);
                width: parent.width
                height: parent.height
                delegate: CheckDelegate {
                    text: modelData
                    checkState: model.check
                    width: ListView.view.width - 20
                }
                
                ScrollBar.vertical: ScrollBar {}            
            }
        }
    }
}

答案1

得分: 1

  1. 似乎你将config实例注册为上下文属性。这不是最佳实践,而是使用qmlRegisterSingletonInstance

  2. 你尝试“property bind”一个Slot,据我所知,这是不可行的,你可以在组件完成时设置模型属性,即

ListView {
    id: useListView
    Component.onCompleted: {useListView.model = config.get_local_flags_model(detailsWindow.atom);}
}

请注意,通常情况下,你会在模型中进行预初始化,但我不知道你的用例是什么,所以很难说。

附注:我看到你使用了注解,如果你感兴趣,我有一个库基于注解创建插槽(以及许多其他技巧),兼容mypy。

英文:
  1. Seems like you registered config instance as a context property.
    This is not best-practice instead use qmlRegisterSingletonInstance

  2. You tried to "property bind" a Slot, AFAIK this is not feasible,you can set the model property when to component is completed. i.e

ListView{
    id: useListView
    Component.onCompleted: {useListView.model = config.get_local_flags_model(detailsWindow.atom);}

}

Note that generaly you would have pre-initialized it in the model, but I don't know what is your use-case so it is hard to tell.

sidenote: I see that you use annotations, if you are interested I have a library that creates slots based on annotations (and many other hacks), that is mypy compatible.

huangapple
  • 本文由 发表于 2023年2月16日 03:16:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/75464503.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定