How to make a menu with a submenu in qml? - qt

I'm trying to do a menu that might have submenus for some of its options.
The behavior i want it to have is the same we see on most of websites.
When we hover an option that has a submenu, that submenu it will happear, if the mouse arrow goes anywhere else that is not the submenu the submenu will close.
I'm going to illustrate with images.
When we enter the submenu we have this:
Now we hover the Language option its submenu will happear
Now what is not done is the behavior i want. If we are hovering on the Languageoption the submenu is visible. If i go directly from language to the language's submenu it will remain there as intented.
The code of this example is provided below:
main.qml
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.3
ApplicationWindow {
visible: true
width: 640
height: 500
title: qsTr("Tabbars")
Button{
id: button
text: "Menu"
onClicked: contextMenu.open()
anchors.top:parent.top
anchors.left:parent.left
height: 20
width: 100
}
Menu {
id: contextMenu
y: button.height
padding: 1
background: Rectangle {
implicitWidth: 200
border.color: "#fff"
color: "#000"
}
Button {
id: languageMenuItem
text: qsTr("Language")
width:parent.width
height: 35
background: Item {
Rectangle {
anchors.fill: parent
color: "#555"
opacity: mouseArea1.pressed ? 1 : mouseArea1.containsMouse ? 0.6 : 0.0
MouseArea {
id: mouseArea1
anchors.fill: parent
hoverEnabled: true
onEntered: function() {
submenuLanguage.open()
}
onExited: function() {
}
}
}
}
contentItem: Text {
text: languageMenuItem.text
color: "#fff"
font.pointSize: 12
font.bold: true
}
Rectangle {
z: 1
color: "#000"
opacity: 0.5
anchors.fill: parent
visible: !parent.enabled
}
Component.onCompleted: {
mouseArea1.clicked.connect(clicked)
}
}
CMenuItem{
text: qsTr("Exit")
width: parent.width
onClicked: close()
}
}
Menu {
id:submenuLanguage
x: contextMenu.width
background: Rectangle {
implicitWidth: 200
border.color: "#fff"
color: "#000"
}
Connections {
target: mouseArea1
onExited: {
console.log("mouseArea leaving")
}
}
CMenuItem{
id:btlingen
width: parent.width
text: qsTr("English")
onClicked: {
contextMenu.close()
console.log("English")
}
}
CMenuItem{
id:btlingpt
width: parent.width
text: qsTr("Português")
onClicked: {
contextMenu.close()
console.log("Português")
}
}
CMenuItem{
id:btlinges
width: parent.width
text: qsTr("Español")
onClicked: {
contextMenu.close()
console.log("Español")
}
}
CMenuItem{
id:btlingit
width: parent.width
text: qsTr("Italiano")
onClicked: {
contextMenu.close()
console.log("Italiano")
}
}
CMenuItem{
id:btlingde
width: parent.width
text: qsTr("Deutsch")
onClicked: {
contextMenu.close()
console.log("Deutsch")
}
}
}
}
CMenuItem.qml
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Controls.Styles 1.4
MenuItem {
id: mainMenuItem
background: Item {
Rectangle {
anchors.fill: parent
color: "#555"
opacity: mouseArea.pressed ? 1 : mouseArea.containsMouse ? 0.6 : 0.0
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
}
}
contentItem: Text {
text: mainMenuItem.text
color: "#fff"
font.pointSize: 12
font.bold: true
}
Rectangle {
z: 1
color: "#000"
opacity: 0.5
anchors.fill: parent
visible: !parent.enabled
}
Component.onCompleted: {
mouseArea.clicked.connect(clicked)
}
}
How can i do this?

Use the cascade property to create a nested menu:
ApplicationWindow {
id: window
width: 320
height: 260
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("&Foo")
Menu {
cascade: true // Nested menu
title: qsTr("&Bar")
Action { text: qsTr("A1") }
Action { text: qsTr("A2") }
Action { text: qsTr("A3") }
}
}
}
}

Related

How to change row selection colour of TreeView in QML?

The following code results in this:
I don't know where from the blue colour is coming up on selection. How do I set the background colour of a selected row in TreeView?
import QtQuick 2.2
import QtQuick.Controls 1.5
import QtQuick.Controls.Styles 1.4
import QtQml.Models 2.2
import filesystem_browser 1.0
ApplicationWindow
{
visible: true
width: 740
height: 740
Rectangle
{
anchors.centerIn: parent
color: "#292A38"
border.color: "#373848"
height: 600; width: 600
ItemSelectionModel
{
// This model is comming from C++' class DisplayFileSystemModel.
model: treeViewModel
}
TreeView
{
id: view
anchors.fill: parent
anchors.margins: 2 * 12
model: treeViewModel
rootIndex: root
selection:
ItemSelectionModel
{
model: treeViewModel
onSelectionChanged:
{
console.log( treeViewModel.data( view.currentIndex ))
}
}
style:
TreeViewStyle
{
backgroundColor: "#14161C"
highlightedTextColor: "red"
alternateBackgroundColor: "#14161C"
}
TableViewColumn
{
title: "Name"
role: "display"
resizable: true
}
itemDelegate:
Rectangle
{
color: "transparent"
height: 20
Rectangle
{
height: 20; width: 40; color: styleData.depth ? "green":"transparent";
anchors.right: parent.right
border.width: 1
}
Text
{
color: styleData.depth ? "green":"black";
anchors.verticalCenter: parent.verticalCenter
text: styleData.value
}
}
}
}
}
Add a rowDelegate to the TreeView to specify how to draw the row:
TreeView {
// ...
rowDelegate: Rectangle {
color: styleData.selected ? "red" : "transparent"
}
}

Dragging from Listview to a droparea

I am following this example here Using drag and drop with ListView to create an inventory UI. However, I have broken down the components into a main section, Listview and a droparea. Everything is working except, I cannot get the ReferenceError: listView is not defined. Please help
Main area
import QtQuick 2.15
import QtQuick.Controls 2.15
Page{
id:dragRect
Rectangle {
id: root
width: 400
height: 400
ListViewElem{
}
DropAreaElem{}
}
}
ListViewElem
import QtQuick 2.15
ListViewElem {
id: listView
width: parent.width / 2
height: parent.height
property int dragItemIndex: -1
model: ListModel {
Component.onCompleted: {
for (var i = 0; i < 10; ++i) {
append({value: i});
}
}
}
delegate: Item {
id: delegateItem
width: listView.width
height: 50
Rectangle {
id: dragRect2
width: listView.width
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "salmon"
border.color: Qt.darker(color)
Text {
anchors.centerIn: parent
text: modelData
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: dragRect2
drag.onActiveChanged: {
if (mouseArea.drag.active) {
listView.dragItemIndex = index;
}
dragRect2.Drag.drop();
}
}
states: [
State {
when: dragRect2.Drag.active
ParentChange {
target: dragRect2
parent: root
}
AnchorChanges {
target: dragRect2
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
Drag.active: mouseArea.drag.active
Drag.hotSpot.x: dragRect2.width / 2
Drag.hotSpot.y: dragRect2.height / 2
}
}
}
DropAreaElem
import QtQuick 2.15
Rectangle {
width: parent.width / 2
height: parent.height
anchors.right: parent.right
color: "#aaff0011"
DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
listView.model.remove(listView.dragItemIndex);
listView.dragItemIndex = -1;
}
}
}
You should provide id to your components
import QtQuick 2.15
import QtQuick.Controls 2.15
Page{
id:dragRect
Rectangle {
id: root
width: 400
height: 400
ListViewElem{
id: listView
}
DropAreaElem{
id: dropArea
}
}
}

QML DelegateModel: Access DelegateModel from delegate

I'm trying to create a ListView with different delegates and a drag'n'drop functionality. The delegates shall be loaded with a Loader.
The QML Documentation provides a working example for a ListView without a Loader:
http://doc.qt.io/qt-5/qtquick-tutorials-dynamicview-dynamicview3-example.html
However, using the Loader I get the error: Cannot read property 'DelegateModel' of undefined
I do not understand how I can access the DelegateModel from the Loader.
A hint to the solution is highly appreciated!
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.1
import QtQml.Models 2.3
Window {
id: mainroot
visible: true
width: 640
height: 480
Rectangle{
id:viewContainer
anchors.fill: parent
DelegateModel {
id: visualModel
model: ListModel{
id:m_model
ListElement{
type:1
m_text :"Text1"
}
ListElement{
type:1
m_text :"Text2"
}
}
delegate: Loader{
id:idLoader
width: view.width
height: childrenRect.height
Component.onCompleted: {
switch(type){
case 1:
idLoader.setSource("TestDelegate.qml", {"m_text": m_text})
break;
}
}
}
}
ListView{
id: view
anchors.fill: parent
spacing: 5
model: visualModel
}
}
}
TestDelegate.qml:
import QtQuick 2.7
MouseArea {
id: dragArea
property bool held: false
property string m_text
anchors { left: parent.left; right: parent.right }
height: 50
width: view.width
drag.target: held ? content : undefined
drag.axis: Drag.YAxis
onPressAndHold: held = true
onReleased: held = false
Rectangle {
id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea.width
height: textfield.implicitHeight
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
border.width: 1
border.color: "lightsteelblue"
color: dragArea.held ? "lightsteelblue" : "white"
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
states: State {
when: dragArea.held
ParentChange { target: content; parent: viewContainer }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Text{
id: textfield
anchors.centerIn: parent
text: m_text
}
}
DropArea {
anchors { fill: parent; margins: 10 }
onEntered: {
visualModel.items.move(
idLoader.item.drag.source.DelegateModel.itemsIndex,
idLoader.item.dragArea.DelegateModel.itemsIndex)
}
}
}
The items defined in the file loaded with Loader or in general with any other .qml file that is imported should not depend directly on the main file since the ids have a scope, it is better to expose properties, in your case:
╭------------------------------------------╮
| bool held ------┿--->
| TestDelegate string m_text ------┿--->
| ============ DelegateModel md ------┿--->
| int index ------┿--->
╰------------------------------------------╯
Considering the above, the solution is the following:
main.qml
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQml.Models 2.3
Window {
id: mainroot
visible: true
width: 640
height: 480
Rectangle{
id:viewContainer
anchors.fill: parent
DelegateModel {
id: visualModel
model: ListModel{
id:m_model
Component.onCompleted: {
for(var i=0; i< 40; i++){
m_model.append({"type": 1, "m_text": "Text" + i})
}
}
}
delegate:
Loader{
id: idLoader
width: view.width
height: childrenRect.height
property int index: DelegateModel.itemsIndex
onIndexChanged: if(status == Loader.Ready) idLoader.item.index = index
Component.onCompleted: {
switch(type){
case 1:
idLoader.setSource("TestDelegate.qml", {
"m_text": m_text,
"index": index,
"md": visualModel
})
break;
}
}
}
}
ListView{
id: view
anchors.fill: parent
spacing: 5
model: visualModel
}
}
}
TestDelegate.qml
import QtQuick 2.7
import QtQml.Models 2.3
MouseArea {
id: dragArea
property bool held: false
property string m_text
property DelegateModel md: null
property int index : -1;
anchors { left: parent.left; right: parent.right }
height: 50
width: view.width
drag.target: held ? content : undefined
drag.axis: Drag.YAxis
onPressAndHold: held = true
onReleased: held = false
Rectangle {
id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea.width
height: textfield.implicitHeight
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
border.width: 1
border.color: "lightsteelblue"
color: dragArea.held ? "lightsteelblue" : "white"
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
states: State {
when: dragArea.held
ParentChange { target: content; parent: viewContainer }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Text{
id: textfield
anchors.centerIn: parent
text: m_text
}
}
DropArea {
anchors { fill: parent; margins: 10 }
onEntered: {
if(md !== null)
md.items.move(drag.source.index, dragArea.index)
}
}
}

Choreography in qml

Is there possible to use choreography animations in qml (in a REUSABLE manner)?
for example in StackView transitions from page1 to page2.
I tried following code, but ParentChange does not work as expected. This code just changes red rectangle's position.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
StackView {
id: stack
anchors.fill: parent
initialItem: Page {
id: page1
Label {
text: qsTr("First page")
anchors.centerIn: parent
}
Rectangle {
id: rect
width: 50
height: 50
x: 600
y: 100
color: "red"
states: [
State {
when: stack.depth > 1
ParentChange { target: rect; parent: stack.currentItem; x: 100; y: 100; }
}
]
transitions: Transition {
ParentAnimation {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
}
}
MouseArea {
anchors.fill: parent
onClicked: stack.push(page2)
}
}
Component {
id: page2
Page {
background: Rectangle { color: "yellow"; anchors.fill: parent }
Label {
text: qsTr("Second page")
anchors.centerIn: parent
}
}
}
}
Rectangle {
id: back
color: "blue"
width: 50
height: 50
radius: 25
MouseArea {
anchors.fill: parent
onClicked: stack.pop()
}
}
}
but the usage is not limited to StackView. It can be used in many other situations. (just like above link)

QML ListView, SwipeView etc. - avoid overlapping of other UI components

I'm trying to avoid this annoying overlapping that both SideView and ListView seem to fancy. Here is an example which demonstrates the issue:
Note: Look at the green rectangle on the left when you swipe the SwipeView and also the tabs when you scroll down the ListView
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.2
Window {
id: window
visible: true
width: 600
height: 480
title: "Demo"
RowLayout {
id: layoutTopLevel
anchors.fill: parent
spacing: 0
Rectangle {
id: sidebarView
Layout.preferredWidth: layoutTopLevel.width * .3
Layout.fillHeight: true
color: "#453"
border.width: 1
}
ColumnLayout {
id: sideViewLayout
spacing: 0
SwipeView {
id: sideView
currentIndex: sideViewPageIndicator.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: layoutTopLevel.height * .9
Page {
id: page1
header: Text {
text: "Page 1"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
}
Page {
id: page2
header: Text {
text: "Page 2"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
TabView {
id: page2TabView
width: parent.width
height: parent.height
anchors.margins: 4
tabPosition: Qt.BottomEdge
Tab {
title: qsTr("Tab 1")
}
Tab {
title: qsTr("Tab 2")
ColumnLayout {
Text {
text: "Text 1"
Layout.alignment: Qt.AlignCenter
}
Text {
text: "Text 2"
Layout.alignment: Qt.AlignCenter
}
ListView {
width: parent.width
height: parent.height
model: ListModel {
ListElement {
name: "Element 1"
}
ListElement {
name: "Element 2"
}
ListElement {
name: "Element 3"
}
ListElement {
name: "Element 4"
}
ListElement {
name: "Element 5"
}
ListElement {
name: "Element 6"
}
}
delegate: Text {
text: name
}
}
}
}
style: TabViewStyle {
tabsAlignment: Qt.AlignHCenter
frameOverlap: 1
tab: Rectangle {
border.width: styleData.selected
implicitWidth: Math.max(text.width + 4, 80)
implicitHeight: 20
radius: 10
Text {
id: text
anchors.centerIn: parent
text: styleData.title
color: styleData.selected ? "white" : "black"
}
color: styleData.selected ? "#654" : "white"
}
frame: Rectangle {
color: "white"
}
}
}
}
}
PageIndicator {
id: sideViewPageIndicator
count: sideView.count
interactive: true
anchors.bottom: sideView.bottom
anchors.bottomMargin: -45
anchors.horizontalCenter: parent.horizontalCenter
delegate: Rectangle {
height: 30
width: 30
antialiasing: true
color: "#654"
radius: 10
opacity: index === sideView.currentIndex ? 0.95 : pressed ? 0.7 : 0.45
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
}
}
}
}
}
Use clip: true
Which clips the content which goes out of its boundaries.
I accidentally came across an example of a ListView while looking into another problem I had and I saw the clip property there. I have completely missed it while looking into the docs of both SideView and ListView. Basically when you set it to true the view no longer covers other components and this is exactly what I want. See comment by #Mitch on why this is not enabled by default.

Resources