从 QML ListView 委托调用 JavaScript 对象方法

Posted

技术标签:

【中文标题】从 QML ListView 委托调用 JavaScript 对象方法【英文标题】:Calling JavaScript object method from QML ListView delegate 【发布时间】:2018-05-31 11:10:04 【问题描述】:

我有一个以 javascript 对象列表作为模型的 ListView。委托需要响应点击,我想存储附加到模型项的点击处理程序(action 属性):

ListView 
    id: idListView
    model: [
        
            name: "Item 1",
            icon: "icon1.svg",
            action: function() 
                //do stuff
            
        ,
        /* other items */
    ]
    delegate: MyDelegate 
        name: modelData.name
        icon: modelData.icon

        MouseArea 
            anchors.fill: parent
            onClicked 
                modelData.action()
            
        
    

但是当我点击一个项目时,我得到了

TypeError: 对象 [object Object] 的属性“动作”不是函数

将函数附加到对象并调用它的正确方法是什么?

【问题讨论】:

【参考方案1】:

您应该将函数定义为 QML 属性。 Object 不允许这样做,因此您可以改用 ListModel

import QtQuick 2.11
import QtQuick.Window 2.11

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

    ListView 
        anchors.fill: parent
        spacing: 2
        model: ListModel 
            ListElement 
                name: "Item 1"
                property var func: function() console.log("Item 1 clicked"); 
            
            ListElement 
                name: "Item 2"
                property var func: function() console.log("Item 2 clicked"); 
            
        

        delegate: Rectangle 
            height: 30
            color: "#EFEFEF"
            border  width: 1; color: "#CCC" 
            width: parent.width
            Text 
                text: name
                anchors.centerIn: parent
            
            MouseArea 
                anchors.fill: parent
                onClicked: 
                    if(typeof func === "function")
                        func();
                    else
                        console.error("Click handler not defined");
                
            
        
    

另一个但有点被欺骗的解决方案:

import QtQuick 2.11
import QtQuick.Window 2.11

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

    ListView 
        anchors.fill: parent
        spacing: 2
        property list<QtObject> arr: [
            QtObject 
                property string name: "Item 1"
                property var func: function() console.log("Item 1 clicked"); 
            ,
            QtObject 
                property string name: "Item 2"
                property var func: function() console.log("Item 2 clicked"); 
            
        ]
        model: arr

        delegate: Rectangle 
            height: 30
            color: "#EFEFEF"
            border  width: 1; color: "#CCC" 
            width: parent.width
            Text 
                text: modelData.name ? modelData.name : "Undefined item"
                anchors.centerIn: parent
            
            MouseArea 
                anchors.fill: parent
                onClicked: 
                    if(typeof modelData.func === "function")
                        modelData.func();
                    else
                        console.error("Click handler not defined");
                
            
        
    

【讨论】:

你测试了第一个sn-p吗?我不认为函数可以在ListElement 中使用。 是的,在 5.11 中测试。拳头我已经发布了第二个例子,QtObjects 然后我决定用ListElement 尝试同样的事情,并惊讶地发现它有效。 ListElement 只能有简单类型,但没有提到 property :-) 直接使用 ListElement 对我不起作用,因为它不处理 QmlEngine 动态重新翻译。第二个选项看起来不错(并且有效),尽管更冗长 - 我正在寻找一种将处理程序附加到模型项的方法。 这确实是 Qt 5.11 中的新功能:doc.qt.io/qt-5/qml-qtqml-models-listelement.html - “从 Qt 5.11 开始,ListElement 还允许将函数声明分配给角色。这允许使用可调用操作定义 ListElements。” 第一个变体在 5.12 中仍然返回错误:ListElement: cannot use script for property value。这里应该有什么正确的语法?文档中没有示例。【参考方案2】:

很遗憾,无法在ListElement 中存储函数:

值必须是简单的常量;任一字符串(带引号和可选 在对 QT_TR_NOOP 的调用中)、布尔值(真、假)、数字、 或枚举值(如 AlignText.AlignHCenter)。

从委托中调用函数的一种简单方法是将函数保留在模型之外并在模型中引用其名称:

ListView 
    id: idListView

    readonly property var actions: 
        "action1": function() 
            console.log("called action 1!");
        ,
        "action2": function() 
            console.log("called action 2!");
        
    

    model: [
        
            name: "Item 1",
            icon: "icon1.svg",
            action: "action1"
        ,
        
            name: "Item 2",
            icon: "icon2.svg",
            action: "action2"
        ,
        /* other items */
    ]
    delegate: MyDelegate 
        name: modelData.name
        icon: modelData.icon

        MouseArea 
            anchors.fill: parent
            onClicked: 
                if (typeof idListView.actions[modelData.action] === "function") 
                    idListView.actions[modelData.action]()
                
            
        
    

【讨论】:

感谢您的建议!那么,不管模型类型如何,模型项都会转换为 ListElements? 不,我不这么认为。我认为 augre 指的是 folibis 的回答。 我不确定。我使用了ListElement 的文档,因为我相信它与使用模型对象的行为相同(具有相同的可能值)。但我并不真正了解模型是如何使用对象 VS ListModel 管理的。无论如何,我没想过使用属性,如果您不介意使用 ListModel,我发现 fobilis 的答案是一个不错的选择,因为您不必依赖函数名称! 非常感谢!以上是QTBUG-80041 的唯一解决方法。【参考方案3】:

我遇到了类似的问题。我想要一个包含ListViewActions 的Drawer。一些尝试和错误最终让它运行(Qt 5.11)。愿我的代码对其他人有用。

Action 
    id: aboutAction
    text: "About"
    icon.source: "icon_info.png"


Drawer 
    edge: Qt.LeftEdge
    width: Screen.pixelDensity * 100  // mm
    height: parent.height

    ListView 
        id: listView
        anchors.fill: parent

        model: ListModel 

        delegate: ItemDelegate 
            width: parent.width
            action: action_
        
    

    Component.onCompleted: 
        listView.model.append(action_: aboutAction);
    

【讨论】:

以上是关于从 QML ListView 委托调用 JavaScript 对象方法的主要内容,如果未能解决你的问题,请参考以下文章

如何从 C++ 访问 QML ListView 委托项目?

如何从 QML 中的 GridView 或 ListView 获取实例化的委托组件

QML 阻止 ListView 委托一直更新

QMLProfiler 中的 QML Listview 报告两次委托创建

如何从QML中的GridView或ListView获取实例化的委托组件

QML ListView隐藏的委托项