2

I'm trying to implement some sort of custom Menu with custom elements. The ultimate goal is to create some sort of popup menu with text and icons. But during creation I faced with some issues. I can show 2 primary problems:

  1. There is a strange menu element with title Hello world at the first position (looks like it's read title of application window):

Hello World issue

  1. From time to time I'm getting errors like qrc:/BreezeQuickMenu.qml:45: TypeError: Property 'clicked' of object QQuickListView(0x1120830) is not a function

Here is my actual code:

main.qml

import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.2

ApplicationWindow {
    title: qsTr("Hello World")
    width: Screen.width
    height: Screen.height
    visible: true
    id: win
    color: brPalette.normalBackground
BreezeQuickMenu{
    id: brMenu
    x: 490
    y: 199
    width: 128
    height: 256
    palette: brPalette
    menuFont.pointSize: 16
    BreezeQuickMenuItem{
        title: "Item 1"
        onClicked: mbox.show()
    }
    BreezeQuickMenuItem{
        title: "Item 2"
    }
    BreezeQuickMenuItem{
        title: "Item 3"
    }
  }
}

BreezeQuickMenu.qml

import QtQuick 2.4

Item {
    id: root
    property BreezeQuickPalette palette: BreezeQuickPalette
    property alias currentIndex: menuList.currentIndex
    property font menuFont
    property bool menuVisible: false
    implicitWidth: 128
    implicitHeight: menuList.height
    ListView{
        id: menuList
        anchors.fill: parent
        model: root.children
        clip: true
        delegate: Component {
            id: menuItem
            Rectangle {
                id: menuElement
                property bool isCurrentItem: ListView.isCurrentItem
                anchors {
                    left: parent.left
                    right: parent.right
                }
                color: palette.normalBackground
                height: menuText.font.pixelSize*1.2
                Text {
                    id: menuText
                    anchors.fill: parent
                    text: title
                    color: palette.normalText
                    font: menuFont
                }
                MouseArea {
                    anchors.fill: parent
                    hoverEnabled: true
                    onClicked: {
                        menuList.currentIndex = index
                        menuList.model[index].clicked()
                    }
                }
            }
        }
    }
}

BreezeQuickMenuItem.qml

import QtQuick 2.4

Item {
    id: root
    property string title: "Menu Element"
    signal clicked
}

As you can see I'm trying to implement menu list and menu items with their own signals. I have 2 questions:

  • how can I properly get rid of using title property of parent element, since I need to read title property of childrens

  • what is the correct approach of using signals and slots in menu elements to avoid above error?

Please help me to understand. Full project can be pulled here:

git clone git://git.code.sf.net/p/breezequick/code breezequick-code

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
user3417815
  • 615
  • 6
  • 28
  • 2
    You are missing the `()` for the signal declaration, see [Signal to Signal Connect](http://doc.qt.io/qt-5/qtqml-syntax-signals.html). Avoid using names as similar as the built-in signals, that could generate strange bugs. It is NOT your case but it would be better to avoid potential sources of problems. As for the `ListView`, consider using either [`VisualItemModel`](http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html#visualitemmodel) or [Object Instanced as models](http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html#object-instances-as-models). – BaCaRoZzo Jun 30 '15 at 11:18
  • Thanks, really miss this thing. Used signal name without `()` in other elements (saw this in few Qt tutorials for QML). Will fix that for others. Do you mean I can use `VisualItemModel` without delegate? But how can I create menu elements as childrens in this case? (as I show in main.qml) – user3417815 Jun 30 '15 at 12:31
  • Using children seems strange to me since also `ListView` itself is a child of `root`, isn't it? That sounds Awkward to me. – BaCaRoZzo Jun 30 '15 at 12:38
  • Understood, thanks for suggestions. Will try to use other models. – user3417815 Jun 30 '15 at 12:47
  • You can e.g. set the `BreezeQuickMenuItem` has children of `VisualItemModel`, or wrap them in a children `Item` and use that as a model. – BaCaRoZzo Jun 30 '15 at 12:57
  • Can you show me code snippet? I don't know too much yet about data models, simple example will be good beginning for me. – user3417815 Jun 30 '15 at 13:03
  • I'm out of office now, sorry. Maybe someone else can provide an answer with some code. – BaCaRoZzo Jun 30 '15 at 13:13
  • 1
    No problems, anyway thanks for your tips. – user3417815 Jun 30 '15 at 13:14
  • Since no answer has provided an answer, here is my two cents. Hope it helps shedding some lights on your problems. – BaCaRoZzo Jun 30 '15 at 21:44

1 Answers1

3

The problem with the signal is related to its declaration. Signals are always declared as a function would be: with a signature. In other words, a signal without parameters has the form

signal <signal_name>()

That's also why you got the error "is not a function". Apart from that, the usage of signals/signal handlers is correct. Anyhow, reading carefully the documentation wouldn't hurt. This page covers in detail the argument.

Coming to the other problem, you made the wrong assumption: anything that is declared inside a component is part of the children of the component itself. Here you declared a BreezeQuickMenu which has a child ListView. When you use it and add the BreezeQuickMenuItems, you add them to the same set to which the ListView belongs. In the end you have four elements in the children property. Also, by adding the ListView to itself through the model you mess up things to the point that a totally unrelated string is rendered.

There are several ways to handle Items as model members for a view, inclusing VisualItemModel and using object Instanced as models. However, by skimming your code, it is clear that you want to define a component which adds menu items in a declarative fashion. Using children is not sufficient in this case. You also need the default property:

An object definition can have a single default property. A default property is the property to which a value is assigned if an object is declared within another object's definition without declaring it as a value for a particular property.

Hence you can define the default property for your BreezeQuickMenu and exploit it to obtain the desired children for your list. A common approach would be the following (code simplified):

import QtQuick 2.4

Item {
    id: root
    property BreezeQuickPalette palette: BreezeQuickPalette
    property alias currentIndex: menuList.currentIndex

    // default declaration  (1)
    default property alias contents: addItem.children

    // Item to which the inner declared meantime will belong  (2)
    Item {
        id: addItem
    }

    property font menuFont
    property bool menuVisible: false

    implicitWidth: 128
    implicitHeight: menuList.height
    ListView{
        id: menuList
        anchors.fill: parent
        model: contents                 // usage of the default property  (3)
        clip: true
        delegate: Rectangle {
            // your current delegate code
        }
    }
}

The basic idea is to exploit also property alias: basically in (1) we are saying that "all the Items declared inside BreezeQuickMenu are automatically children of addItem which is an inner declared Item (2). In this way the ListView is kept apart whereas all the BreezeQuickMenuItem are gathered together, under addItem children property. At this point, it is sufficient to use the same children property as the model (3) for the ListView and that's it.

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
  • 1
    Thanks, really great example! Didn't know about `contents` property usage, other things made this problem clear too. – user3417815 Jul 01 '15 at 09:00