Menu / sidebar with interactive buttons inside item - qt

A picture is worth thousand words. This is menu / sidebar which I want to get:
Note that it is fake. This is just static QTreeWidget preparation with 3 columns, colspan and header hidden.
Nedded features:
Grayed upper case items should not be possible to select (partly could be solved by QTreeWidgetItem::setFlags(ItemIsSelectable))
Icons on the right side (open, eject) should be interactive. I mean click signal and cursor should change to "hand" when mouse is over these icons
When resizing menu / sidebar using QSplitter then icons on the right side should anchor to the right edge
I'm beginner in Qt Framework but not such lame. I studied examples to find interesting solutions but I'm not sure which will be the easiest:
Play with QGraphicsView
Create QToolButton descendant and try to add child buttons. Then put everything into QWidget, add labels, layout and prepare stylesheet to imitate QTreeWidget
Create QTreeWidget descendant and play with painter, mouse move event etc.
Any suggestions or other solutions? Never tried QML / QtQuick, had no time to learn it but maybe I could use QDeclarativeView or QQuickWidget

Use Drawers and Listview in conjunction to get your results. QML Drawers are the slidable widgets which you can program to open() on a click or a press event.
Here is the official write up on QML Drawer
https://doc-snapshots.qt.io/qt5-5.7/qml-qtquick-controls2-drawer.html
The following example from Qt runs exactly what is mentioned in the question.
http://doc.qt.io/qt-5/qtquickcontrols2-gallery-example.html
Taken straight from the code just to give you an Idea :
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import Qt.labs.settings 1.0
ApplicationWindow {
id: window
width: 360
height: 520
visible: true
title: "Qt Quick Controls 2"
Settings {
id: settings
property string style: "Default"
}
header: ToolBar {
Material.foreground: "white"
RowLayout {
spacing: 20
anchors.fill: parent
ToolButton {
contentItem: Image {
fillMode: Image.Pad
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
source: "qrc:/images/drawer.png"
}
onClicked: drawer.open()
}
Label {
id: titleLabel
text: "Gallery"
font.pixelSize: 20
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
ToolButton {
contentItem: Image {
fillMode: Image.Pad
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
source: "qrc:/images/menu.png"
}
onClicked: optionsMenu.open()
Menu {
id: optionsMenu
x: parent.width - width
transformOrigin: Menu.TopRight
MenuItem {
text: "Settings"
onTriggered: settingsPopup.open()
}
MenuItem {
text: "About"
onTriggered: aboutDialog.open()
}
}
}
}
}
Drawer {
id: drawer
width: Math.min(window.width, window.height) / 3 * 2
height: window.height
ListView {
id: listView
currentIndex: -1
anchors.fill: parent
delegate: ItemDelegate {
width: parent.width
text: model.title
highlighted: ListView.isCurrentItem
onClicked: {
if (listView.currentIndex != index) {
listView.currentIndex = index
titleLabel.text = model.title
stackView.replace(model.source)
}
drawer.close()
}
}
model: ListModel {
ListElement { title: "BusyIndicator"; source: "qrc:/pages/BusyIndicatorPage.qml" }
ListElement { title: "Button"; source: "qrc:/pages/ButtonPage.qml" }
ListElement { title: "CheckBox"; source: "qrc:/pages/CheckBoxPage.qml" }
ListElement { title: "ComboBox"; source: "qrc:/pages/ComboBoxPage.qml" }
ListElement { title: "Dial"; source: "qrc:/pages/DialPage.qml" }
ListElement { title: "Delegates"; source: "qrc:/pages/DelegatePage.qml" }
ListElement { title: "Drawer"; source: "qrc:/pages/DrawerPage.qml" }
ListElement { title: "Frame"; source: "qrc:/pages/FramePage.qml" }
ListElement { title: "GroupBox"; source: "qrc:/pages/GroupBoxPage.qml" }
ListElement { title: "Menu"; source: "qrc:/pages/MenuPage.qml" }
ListElement { title: "PageIndicator"; source: "qrc:/pages/PageIndicatorPage.qml" }
ListElement { title: "Popup"; source: "qrc:/pages/PopupPage.qml" }
ListElement { title: "ProgressBar"; source: "qrc:/pages/ProgressBarPage.qml" }
ListElement { title: "RadioButton"; source: "qrc:/pages/RadioButtonPage.qml" }
ListElement { title: "RangeSlider"; source: "qrc:/pages/RangeSliderPage.qml" }
ListElement { title: "ScrollBar"; source: "qrc:/pages/ScrollBarPage.qml" }
ListElement { title: "ScrollIndicator"; source: "qrc:/pages/ScrollIndicatorPage.qml" }
ListElement { title: "Slider"; source: "qrc:/pages/SliderPage.qml" }
ListElement { title: "SpinBox"; source: "qrc:/pages/SpinBoxPage.qml" }
ListElement { title: "StackView"; source: "qrc:/pages/StackViewPage.qml" }
ListElement { title: "SwipeView"; source: "qrc:/pages/SwipeViewPage.qml" }
ListElement { title: "Switch"; source: "qrc:/pages/SwitchPage.qml" }
ListElement { title: "TabBar"; source: "qrc:/pages/TabBarPage.qml" }
ListElement { title: "TextArea"; source: "qrc:/pages/TextAreaPage.qml" }
ListElement { title: "TextField"; source: "qrc:/pages/TextFieldPage.qml" }
ListElement { title: "ToolTip"; source: "qrc:/pages/ToolTipPage.qml" }
ListElement { title: "Tumbler"; source: "qrc:/pages/TumblerPage.qml" }
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}
ToolButton with drawer.png as Image, is programmed to open and close the sidebar.
I hope this helps.

if you decide to go QML (which I would recommend, unless you have specific reasons not to) here's what you could do.
Step one: Use a ListView with section headers: http://qmlbook.github.io/en/ch06/index.html#lists-with-sections
Step two: Create an item delegate with multiple actions / subitems.
Just take a look at the Qml docs and examples or the QML Book linked above to understand the basics of the model, ListView and delegate concepts:
http://qmlbook.github.io/en/ch06/index.html#
As to anchoring, you can do that pretty easily too by using the anchors property and the many layout options QML gives you:
http://qmlbook.github.io/en/ch04/index.html#positioning-elements
Hope this helps.

Related

ListView with section, Remove Animation not working for the top item

I'm using a QML ListView with section, click on item to remove with animation. Here the code:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
visible: true
width: 400
height: 400
ListView {
id: list
anchors.fill: parent
clip: true
spacing: 0
onContentYChanged: console.log("onContentYChanged: " + contentY)
onContentHeightChanged: console.log("onContentHeightChanged: " + contentHeight)
model: ListModel {
id: myModel
ListElement {name: "Item 1";type: "A"}
ListElement {name: "Item 2";type: "A"}
ListElement {name: "Item 3";type: "B"}
}
delegate: Rectangle {
width: parent.width
height: 50
color: (index % 2 == 1) ? "#5678a2" : "#88a345"
Text {
anchors.verticalCenter: parent.verticalCenter
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log("remove: " + index + ", contentY:" + list.contentY)
myModel.remove(index)
}
}
}
section.property: "type"
section.delegate: Rectangle {
height: 30
Text {
anchors.verticalCenter: parent.verticalCenter
text: section
}
}
displaced: Transition {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutCubic }
}
remove: Transition {
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 500 }
NumberAnimation { property: "scale"; from: 1.0; to: 0; duration: 500 }
}
}
}
When I clicked on the first item(Item 1), it got deleted, but the Item 2 was flying up to outside the window. The ListView displayed the remaining items in wrong positions. ContentY changed to 80 (which was the y position of Item 2 before) instead of remaining at 0.
qml: onContentHeightChanged: 300
qml: onContentHeightChanged: 240
qml: onContentHeightChanged: 210
qml: remove: 0, contentY:0
qml: onContentYChanged: 80
qml: onContentHeightChanged: 160
It will work correctly if:
Delete other items except the top one.
Disable either the section or animation.
I tried your code with Qt 5.13.1. And currently downloading Qt 5.15. For now it looks like it is a bug with section, because I found a lot of not not closed bug reports on bugtracker. I can suggest 2 ways of solving your problem.
Performing animation while locking removal.
Using model with categories.
1st solution I tested by meself. Here is what you need to change to try it:
Delete ListView's removal animations. Add following code to your delegate
ListView.onRemove: SequentialAnimation {
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: true }
ParallelAnimation {
NumberAnimation { target: wrapper; property: "opacity"; to: 0; duration: 500 }
NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 500 }
}
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: false }
}
What is this? ListView has a signal remove() which is called BEFORE removing an item from the view. It is described in documentation It is also noted, that
If a remove transition has been specified, it is applied after this signal is handled, providing that delayRemove is false.
So in delegate you simply block removal from view, do you animation and unblock it. I suppose it won't be as clean and beautiful as you want it to be simply because view doesn't andjust it's size in this case.
2nd solution
I didn't try to implement it, but I can imagine having a model like this:
ListModel {
id: myModel
ListElement { type: "category"; name: "cat1" }
ListElement { name: "delegate1"; type: "delegate"; catrgory: "cat1"}
ListElement { name: "delegate2"; type: "delegate"; catrgory: "cat1"}
ListElement { name: "delegate3"; type: "delegate"; catrgory: "cat1"}
ListElement { type: "category"; name: "cat2" }
ListElement { name: "delegate4"; type: "delegate"; catrgory: "cat2"}
To use this as you want, you will need to castomize your delegate accordingly and removal function accordingly, which will lead to much more complex code in comparison to what it would be if section would work properly.
UPD: Same problem in 5.15

In 'AppListView' are empty places where Loader should load Component with 'SimpleRow'

I have a code application written in QT/QML and V-PLAY on the github here:
My problem:
I want to use AppListView to display different elements (like Button or SwitchApp) in 'Ustawienia' (Settings) page dependent on elements in array:
property var typeOfElementsInSettings: ['switch','switch','button','switch']
I use 'delegete: Loader' to do It, I inspired in this thread. I load component from other file, one will have Button inside, other AppSwitcher. Loader inserts SimpleRow to AppListView, I know It because variable myIndex should increment when SimpleRow is added and It was incremented but I can't see anything. I mean that I see empty space in place where should be displayed SimpleRow.
See screenshot:
Android Theme:
iOS Theme:
This is my code in Main.qml
NavigationItem{
title: "Ustawienia"
icon: IconType.cogs
NavigationStack{
Page{
title: "Ustawienia"
AppButton{
id: przy
text: "abba"
}
AppListView{
anchors.top: przy.bottom
model: ListModel{
ListElement{
type: "kategoria 1"; name: "opcja 1"
}
ListElement{
type: "kategoria 1"; name: "opcja 2"
}
ListElement{
type: "kategoria 2"; name: "opcja 3"
}
ListElement{
type: "Opcje programisty"; name: "Czyszczenie ustawień aplikacji"
}
}
section.property: "type";
section.delegate: SimpleSection {
title: section
}
delegate: Loader{
sourceComponent: {
switch(typeOfElementsInSettings[myIndex]){
case "switch":
console.log(typeOfElementsInSettings[myIndex])
console.log("s")
return imageDel;
case "button":
console.log(typeOfElementsInSettings[myIndex])
console.log("b")
return imageDel;
}
}
}
SimpleRowSwitch { id: imageDel }
VideoDelegate { id: videoDel }
}
}
}
onSelected: {
//console.log("selected")
}
Component.onCompleted: {
//console.log("Zrobiono")
}
}
This my code in SimpleRowSwitch.qml:
import VPlayApps 1.0
import QtQuick 2.9
Component{
SimpleRow {
x: 100
y: 200
text: name;
AppSwitch{
property int indexOfElementInSettings: 0
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: dp(10)
Component.onCompleted: {
indexOfElementInSettings=myIndex
console.log(myIndex)
if(switchsSettingsLogicArray[myIndex]===1){
checked=true
} else {
checked=false
}
//myIndex++;
}
onToggled: {
console.log(indexOfElementInSettings)
}
}
Component.onCompleted: {
console.log(x)
console.log(y)
console.log(typeOfElementsInSettings[myIndex])
console.log(myIndex)
myIndex++
}
onSelected: {
console.log("abba")
}
}
}

How to properly popup and resize Dialog from another .qml file

Learning qml and trying to separate main window and settings in different files. I have a SettingsView.qml with simple Dialog and Have a main.qml where I call menu and call my settings dialog to popup. When I had a Dialog in main.qml it was fine and it had been resizing with whole window properly. But after I had moved it to different file the behaviour changed. Now also I recieve a message: "refSettingsDialog is not defined". I would be gratefull for any advices.
upd: Closed. No need in properties here etc just basics. And do not call id from another file. Atleast, as I understand it by now
SettingsView.qml
Dialog{
id: settingsDialog
modal: true
focus: true
title: "Settings"
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: {
settingsDialog.close()
}
onRejected: {
settingsDialog.close()
}
}
main.qml
ApplicationWindow {
visible: true
id: screen
property alias mainScreen: screen
width: 640
height: 480
property alias screenWidth: screen.width
property alias screenHeight: screen.height
title: qsTr("McdtViewer")
Material.theme: Material.Dark
Material.accent: Material.Yellow
SystemPalette { id: activePalette }
//toolbar
header: ToolBar {
RowLayout {
spacing: 20
anchors.fill: parent
ToolButton {
icon.name: contentSwiper.currentIndex === 1 ? "Back" : "пустой"
onClicked: {
if (contentSwiper.currentIndex === 1){
contentSwiper.pop()
}
}
}
Label {
id: titleLabel
text: contentSwiper.currentIndex === 0? "ExpWatcher": "ExpView"
font.pixelSize: 20
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
ToolButton {
icon.name: "menu"
onClicked: optionsMenu.open()
Menu {
id: optionsMenu
x: parent.width - width
transformOrigin: Menu.TopRight
MenuItem {
text: "Settings"
//calling the instance of settingView which we declared in the bottom
onTriggered: {
settingsView.open()
}
}
}
}
}
}
}
// making instance of settingsDialog here so the width will be calculated properly.
SettingsView{
id: settingsView
x: Math.round((screenWidth - width) / 2)
y: Math.round(screenHeight / 6)
width: Math.round(Math.min(screenWidth, screenHeight) / 3 * 2)
}
}

How do I correctly handle mouse events in a QML TableView with overlapping mouse areas?

I've got a delegate attached to my TableViewColumn that contains a MouseArea. I use the MouseArea to detect double clicks on individual cells in the table, which allows me to show a TextField for editing purposes.
The problem is the delegate MouseArea blocks mouse events from propagating through to TableView. This means that the selection behaviour of TableView no longer works. Specifically, I have SelectionMode.ExtendedSelection enabled.
The MouseArea child item is simple and originally looked like this:
MouseArea{
id: mousearea
anchors.fill: parent
onDoubleClicked: {
showTextField()
}
}
After consulting the documentation, it looked like this should work:
MouseArea{
id: mousearea
anchors.fill: parent
propagateComposedEvents: true // new
onDoubleClicked: {
showTextField()
}
onPressed: mouse.accepted = false // new
}
Which it does, except now I cannot pick up double click events anymore (in MouseArea)! Which makes sense, as it states later in the documentation:
pressed(MouseEvent mouse)
When handling this signal, use the accepted property of the mouse parameter to control whether this MouseArea handles the press and all future mouse events until release. The default is to accept the event and not allow other MouseAreas beneath this one to handle the event. If accepted is set to false, no further events will be sent to this MouseArea until the button is next pressed.
There does not seem to be a way to capture mouse events for individual cells at the TableView level. It's my first day playing around with QML, so I might have missed something obvious here, but what are my options? Note I'm using PyQt.
If it is only the the selection you want to achive you can set the selection manually:
TableView {
id: tv
itemDelegate: Item {
Text {
anchors.centerIn: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
MouseArea {
id: ma
anchors.fill: parent
onPressed: {
tv.currentRow = styleData.row
tv.selection.select(styleData.row) // <-- select here.
}
onClicked: {
console.log(styleData.value)
}
}
}
TableViewColumn {
role: 'c1'
title: 'hey'
width: 100
}
TableViewColumn {
role: 'c2'
title: 'tschau'
width: 100
}
model: lm
}
Right now I only select. But you can write your very own selection/deselection-logic.
You might also map from the TableView.__mouseArea to the delegate.
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow {
id: appWindow
width: 1024
height: 800
visible: true
ListModel {
id: lm
ListElement { c1: 'hallo1'; c2: 'bye' }
ListElement { c1: 'hallo2'; c2: 'bye' }
ListElement { c1: 'hallo3'; c2: 'bye' }
ListElement { c1: 'hallo4'; c2: 'bye' }
ListElement { c1: 'hallo5'; c2: 'bye' }
ListElement { c1: 'hallo6'; c2: 'bye' }
ListElement { c1: 'hallo7'; c2: 'bye' }
ListElement { c1: 'hallo8'; c2: 'bye' }
ListElement { c1: 'hallo9'; c2: 'bye' }
}
TableView {
id: tv
itemDelegate: Item {
id: mydelegate
signal doubleclicked()
onDoubleclicked: console.log(styleData.value)
Text {
anchors.centerIn: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
Connections {
target: tv.__mouseArea
onDoubleClicked: {
// Map to the clickposition to the delegate
var pos = mydelegate.mapFromItem(tv.__mouseArea, mouse.x, mouse.y)
// Check whether the click was within the delegate
if (mydelegate.contains(pos)) mydelegate.doubleclicked()
}
}
}
TableViewColumn {
role: 'c1'
title: 'hey'
width: 100
}
TableViewColumn {
role: 'c2'
title: 'tschau'
width: 100
}
model: lm
}
}

TableView not the correct size with QML

I am trying to create a TableView with QML where I have a checkbox, an image and a text field. The table column definitions are as follows:
// TableViewCheckBoxColumn.qml
TableViewColumn {
title: ""
role: "check"
delegate: CheckBox {
anchors.fill: parent
checked: styleData.value
}
}
//TableViewImageColumn.qml
TableViewColumn {
title: ""
role: "thumbnail"
delegate: Image {
anchors.fill: parent
source: styleData.value
width: 30
height: 30
}
}
Now the data model and the table itself is defined as a QML component as follows:
Item {
ListModel {
id: sourceModel
ListElement {
check: false
thumbnail: "file:///Users/xargon/alignment.png"
length: "10:22"
}
}
// Table view
TableView {
anchors.centerIn: parent
alternatingRowColors: false
TableViewCheckBoxColumn {
id: checkedColumn
}
TableViewImageColumn {
id: thumbColumn
}
TableViewColumn {
id: lengthColumn
role: "length"
title: "Length"
}
model: sourceModel
}
}
Now, this is embedded in a ColumnLayout and a StackView as:
ColumnLayout {
anchors.fill: parent
MyTable {
id: reviewScreen
Layout.fillWidth: true
}
StackView {
id: options
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: reviewScreen
}
}
Now I was expecting the table to fill the entire width of the parent control and I also was expecting the image to be drawn as a 30 x 30 image but what I see is the attached screenshot where the horizontal scrollbar is there to move between controls and the table is small and the image is very distorted as well.
yes, only you need declare heigh and weight of every TableViewColumn, for example:
TableViewColumn { id: lengthColumn; role: "length"; title: "Length"; height: parent.height/8; width:parent.width*0.25}

Resources