Decrease time until onPressAndHold is emitted on ItemDelegate - qt

I have the following example Qt Quick application:
import QtQuick 2.12
import QtQuick.Controls 2.15
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
ListView {
id: list
anchors.fill: parent
model: ListModel {
id: listView
ListElement { name: "Element 1" }
ListElement { name: "Element 2" }
ListElement { name: "Element 3" }
ListElement { name: "Element 4" }
}
delegate: ItemDelegate {
property string textcolor: "black";
contentItem: Text {
id: content
width: parent.width
text: name
color: textcolor
}
onPressAndHold: { textcolor = "red"; }
}
}
}
I'm changing the text color on a single element trough the onPressAndHold event. How can I decrease the duration until the event is emited?
I tried to use pressAndHoldInterval but it seems like it does not exist for ItemDelegate.

The interval is established through the mousePressAndHoldInterval() of QStyleHints, in this case the solution is to use the setter that seems to be undocumented.
QGuiApplication app(argc, argv);
app.styleHints()->setMousePressAndHoldInterval(10);

Related

How can I use QML Layouts to Size Nested Layouts in ScrollView?

I am trying to use QML layouts to size sections of a UI within a ScrollView and having trouble understanding why one of the nested GridLayouts is being clipped. To understand it better, please see the picture below and look specifically at the bottom of "Section 1":
I think, basically, the issue is that "Section 2" implicit height(the smaller of the two side-by-side sections) is driving the sizing for that row in the magenta GridLayout instead of the other way around, as I would hope. I'm trying to get the code down to something allowed in the length of the question and will edit as soon as I do.
Instead of using ScrollView. I highly recommend you look at components that have a ScrollBar property first such as Flickable and ListView.
To demonstrate, I mocked up the following example demonstrating how you can use ListView with the ScrollBar.vertical property set to recreate the UI/UX shown in your question.
For the delegate I use a DelegateChooser so each row can have a different delegate. For the model I use a ListModel which is used to populate the different input types.
For each delegate, I add the following code to ensure that when that control is being edited, its focus is forced to be visible. i.e. it avoids the control being clipped.
onActiveFocusChanged: listView.currentIndex = index
Here's a full working example:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
background: Rectangle { color: "black" }
Frame {
anchors.fill: parent
anchors.margins: 10
background: Rectangle {
color: "black"
border.color: "#f0f"
border.width: 2
}
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
SectionInput {
title: "SECTION 1"
model: Data1 { }
}
SectionInput {
title: "SECTION 2"
model: Data2 { }
}
}
SectionInput {
title: "SECTION 3"
model: Data3 { }
}
}
}
}
// SectionInput.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels
Frame {
id: sectionInput
property string title
property ListModel model
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
color: "black"
border.color: "yellow"
border.width: 2
}
ColumnLayout {
anchors.fill: parent
Text {
text: title
color: "cyan"
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: sectionInput.model
clip: true
spacing: 10
snapMode: ListView.SnapOneItem
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
delegate: DelegateChooser {
id: chooser
role: "typ"
DelegateChoice { roleValue: "combo"; MyCombo { } }
DelegateChoice { roleValue: "text"; MyText { } }
DelegateChoice { roleValue: "switch"; MySwitch { } }
}
}
}
}
// MyCombo.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RowLayout {
property ListView listView: ListView.view
width: listView.width - 20
Text {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: lab
color: "white"
horizontalAlignment: Qt.AlignRight
}
ComboBox {
Layout.fillWidth: true
Layout.preferredWidth: 100
model: dat.split(",")
onAccepted: val = currentText
onActiveFocusChanged: listView.currentIndex = index
Component.onCompleted: currentIndex = find(val)
}
}
// MyText.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RowLayout {
property ListView listView: ListView.view
width: listView.width - 20
Text {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: lab
color: "white"
horizontalAlignment: Qt.AlignRight
}
TextField {
Layout.fillWidth: true
Layout.preferredWidth: 100
background: Rectangle { color: "#ddd" }
text: val
placeholderText: pla
onAccepted: val = text
onActiveFocusChanged: listView.currentIndex = index
}
}
// MySwitch.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RowLayout {
property ListView listView: ListView.view
width: listView.width - 20
Text {
Layout.fillWidth: true
Layout.preferredWidth: 100
text: lab
color: "white"
horizontalAlignment: Qt.AlignRight
}
Frame {
Layout.fillWidth: true
Layout.preferredWidth: 100
background: Item { }
Switch {
checked: val === "true"
onToggled: val = JSON.stringify(checked)
onActiveFocusChanged: listView.currentIndex = index
}
}
}
// Data1.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { lab: "Field 1"; typ: "combo"; val: "F1"; dat: "F0,F1,F2,F3" }
ListElement { lab: "Field 2"; typ: "text"; pla: "xxx.xxx.xxx.xxx" }
ListElement { lab: "Field 3"; typ: "text"; pla: "xx,xxx" }
ListElement { lab: "Field 4"; typ: "text"; pla: "x,xxx" }
ListElement { lab: "Field 5"; typ: "text"; pla: "xxx" }
ListElement { lab: "Field 6"; typ: "text"; pla: "xx" }
ListElement { lab: "Field 7"; typ: "text" }
}
// Data2.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { lab: "Field 1"; typ: "combo"; val: "TTL"; dat: "TTL,XYZ,ABC" }
ListElement { lab: "Field 2"; typ: "combo"; val: "50"; dat: "5,50,500" }
ListElement { lab: "Field 3"; typ: "combo"; val: "50"; dat: "40,50,60" }
ListElement { lab: "Field 4"; typ: "combo"; val: "Normal"; dat: "Normal,Not Normal" }
ListElement { lab: "Field 5"; typ: "combo"; val: "Normal"; dat: "Normal,Not Normal" }
}
// Data3.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { lab: "Field 1"; typ: "switch"; val: "true" }
ListElement { lab: "Field 2"; typ: "combo"; val: "OFF"; dat: "ON,OFF" }
ListElement { lab: "Field 3"; typ: "switch"; val: "false" }
ListElement { lab: "Field 4"; typ: "text" }
ListElement { lab: "Field 5"; typ: "text" }
ListElement { lab: "Field 6"; typ: "text" }
}
You can Try it Online!
I ended up resolving the sizing issue with a test for which "Section" Rectangle was vertically larger (implicitHeight) before setting the Layout.preferredHeight for both of the "Sections".
Rectangle { //Section 1 rectangle
color: "black"
clip: true
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: widthMainControlCommon
Layout.preferredHeight: { layoutSec1.implicitHeight > layoutSec2.implicitHeight ?
layoutSec1.implicitHeight : layoutSec2.implicitHeight}
GridLayout {
id: layoutSec1
columns: 2
anchors.fill: parent
}
//..
Rectangle { //Section 2 rectangle
color: "black"
clip: true
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: widthMainControlCommon
Layout.preferredHeight: { layoutSec1.implicitHeight > layoutSec2.implicitHeight ?
layoutSec1.implicitHeight : layoutSec2.implicitHeight}
GridLayout {
id: layoutSec2
columns: 2
anchors.fill: parent
}
I don't understand the benefits of the Layouts like I thought I did, or the logic that drives sizing when they're nested. Needing to set a preferredHeight in order to get the Layout to size to make it visible seems strange to me.

ListView doesn't populate values from the ListModel

I'm trying to make a list of buttons, where the user can select one of the options. I can't seem to display the text from the listmodel, but I can't figure out why. It seems like even the button is not correctly placed. Does anyone have ideas as to how to fix this? Thanks!
import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Controls 2.15
Window {
id: window
visible: true
height: 400
width: 400
Rectangle {
id: rect
width: 400; height: 400
ListView {
id: listview
anchors.fill: parent
model: timeList
delegate: Item {
anchors.top: parent.top
height: 30
Button {
anchors.top: parent.top
anchors.left: parent.left
id: textButton
text: text
width: parent.width
height: 30
flat: true
onClicked: {
console.log(value)
}
}
Rectangle {
height: (index === timeList.count - 1 ? 0 : 1)
color: '#E1E6E9'
width: 400
anchors {
left: textButton.left
top: textButton.bottom
}
}
}
ListModel {
id: timeList
ListElement { text: "15 minutes"; value: 15 }
ListElement { text: "1 hour"; value: 60 }
ListElement { text: "3 hours"; value: 180 }
ListElement { text: "6 hours"; value: 360 }
ListElement { text: "12 hours"; value: 720 }
}
}
}
}
There are several issues here:
don't name ListElement properties using keywords, text: text looks confusing.
don't wrap everything with Rectangle/Item with no need, use Layout instead if needed
never use anchors for delegates
pay attention that all containers have a size, the delegate's Item has no with for example (or probably 0)
actually, that should work:
import QtQuick
import QtQuick.Window
import QtQuick.Controls
Window {
id: window
visible: true
height: 400
width: 400
ListView {
anchors.fill: parent
model: timeList
delegate: Button {
id: textButton
text: name
width: parent.width
height: 30
flat: true
onClicked: {
console.log(value)
}
}
ListModel {
id: timeList
ListElement { name: "15 minutes"; value: 15 }
ListElement { name: "1 hour"; value: 60 }
ListElement { name: "3 hours"; value: 180 }
ListElement { name: "6 hours"; value: 360 }
ListElement { name: "12 hours"; value: 720 }
}
}
}

how to read custom property of parent ListView from external delegate?

I have a ListView and its delegate defined in other qml-file.
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement {
name: "Bill Smith"
}
ListElement {
name: "John Brown"
}
ListElement {
name: "Sam Wise"
}
}
ListView {
width: 180; height: 200
property color delegateColor: "green"
model: listModel
delegate: ExternalDelegate {}
}
}
ExternalDelegate.qml:
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
background: Rectangle {
color: ListView.view.delegateColor
}
Text {
text: model.name
}
}
Parent ListView has a custom property delegateColor. I need to read this property from the delegate. But if I try to access it by attached property ListView.view it does not work. And I see messages in console:
qrc:/ExternalDelegate.qml:7: TypeError: Cannot read property 'delegateColor' of null
How to read custom property of ListView from an external delegate?
I need to set this property in ListView (not in delegate) because I also want to access this property from header, footer and section delegates.
In this case it is better that the components do not know what they are used for but rather to expose through properties of the root element, for example an alias of the color of the rectangle can be created, and in the case of the text the ItemDelegate component already has that property.
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
id: root
property alias color: bg.color
background: Rectangle {
id: bg
}
contentItem: Text {
text: root.text
}
}
delegate: ExternalDelegate {
text: model.name
color: ListView.view.delegateColor
}
Another solution is to only change the alias for a new property:
import QtQuick 2.12
import QtQuick.Controls 2.12
ItemDelegate {
id: root
property color color : "white"
background: Rectangle {
color: root.color
}
contentItem: Text {
text: root.text
}
}

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.

Different delegates for QML ListView

I would like to know if it's possible to use (several) different delegates for a QML ListView.
Depending on the individual object in the ListView model, I would like to visualize the objects with different delegates.
This piece of code explains what I want to achieve:
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
ListModel {
id: contactsModel
ListElement {
name: "Bill Smith"
position: "Engineer"
}
ListElement {
name: "John Brown"
position: "Engineer"
}
ListElement {
name: "Sam Wise"
position: "Manager"
}
}
ListView {
id: contactsView
anchors.left: parent.left
anchors.top: parent.top
width: parent.width
height: parent.height
orientation: Qt.Vertical
spacing: 10
model: contactsModel
delegate: {
if (position == "Engineer") return Employee; //<--- depending on condition, load Contact{}
else if (position == "Manager") return Manager; //<--- depending on condition, load Person{}
}
}
}
Employee.qml (One possible Component which I would like to use as a delegate)
import QtQuick 2.4
Rectangle{
width: 200
height: 50
color: ListView.isCurrentItem ? "#003366" : "#585858"
border.color: "gray"
border.width: 1
Text{
anchors.centerIn: parent
color: "white"
text: name
}
}
Manager.qml (other Component I would like to use as a delegate)
import QtQuick 2.4
Rectangle{
width: 200
height: 50
color: "red"
border.color: "blue"
border.width: 1
Text{
anchors.centerIn: parent
color: "white"
text: name
}
}
I would appreciate any advice!
Thanks!
I've had the same problem, the Qt documentation is providing a pretty good answer: http://doc.qt.io/qt-5/qml-qtquick-loader.html#using-a-loader-within-a-view-delegate
The easiest solution is an inline Component with a Loader to set a source file:
ListView {
id: contactsView
anchors.left: parent.left
anchors.top: parent.top
width: parent.width
height: parent.height
orientation: Qt.Vertical
spacing: 10
model: contactsModel
delegate: Component {
Loader {
source: switch(position) {
case "Engineer": return "Employee.qml"
case "Manager": return "Manager.qml"
}
}
}
}
Any attempt to use Loader.srcComponent will result in missing any variable from the model (including index). The only way for the variables to be present is the children Component to be inside the main Component, but then only one can be present, so it is useless.
I believe it would be better to implement one base delegate for all kind of position which loads concrete implementation depending on position or any other data properties using Loader
BaseDelegate {
property var position
Loader {
sourceComponent: {
switch(position) {
case "Engineer": return engineerDelegate
}
}
}
Component {
id: engineerDelegate
Rectangle {
Text { }
}
}
}
I implemented it as follow:
ListView {
id: iranCitiesList
model: sampleModel
delegate: Loader {
height: childrenRect.height
width: parent.width
sourceComponent: {
switch(itemType) {
case "image" :
return imageDel;
case "video":
return videoDel;
}
}
}
ImageDelegate { id: imageDel }
VideoDelegate { id: videoDel }
}
ImageDelegate.qml
Component {
Image { /*...*/ }
}
VideoDelegate.qml
Component {
Item { /*....*/ }
}
Last note, check width and height of delegates. In my case, I had to set width and height of my delegate in Loader again.
Good luck - Mousavi
The simplest way to do this now is using DelegateChooser. This also allows you to edit the properties of the delegates, which is something that is more difficult to do with Loader!
Example inspired from the docs:
import QtQuick 2.14
import QtQuick.Controls 2.14
import Qt.labs.qmlmodels 1.0
ListView {
width: 640; height: 480
ListModel {
id: contactsModel
ListElement {
name: "Bill Smith"
position: "Engineer"
}
ListElement {
name: "John Brown"
position: "Engineer"
}
ListElement {
name: "Sam Wise"
position: "Manager"
}
}
DelegateChooser {
id: chooser
role: "position"
DelegateChoice { roleValue: "Manager"; Manager { ... } }
DelegateChoice { roleValue: "Employee"; Employee { ... } }
}
model: contractsModel
delegate: chooser
}
Sure, it's possible. ListView.delegate is a kind of pointer to a Component which will draw the items so you can change it.
For example:
Employee { id: delegateEmployee }
Manager { id: delegateManager}
...
ListView {
property string position
delegate: position == "Engineer" ? delegateEmployee : delegateManager
}
As far as you have only two types, the following code is as easy to maintain as easy to understand:
delegate: Item {
Employee { visible = position === "Engineer" }
Manager { visible = position === "Manager" }
}
In case the number of types will grow, it is not a suitable solution for it easily leads to an hell of if statement.
Because position is either "Manager" or "Engineer" and the delegates are saved in Manager.qml or Engineer.qml we can use a clever expression for Loader.source:
Loader {
source: position + ".qml"
}
Here's the full source:
import QtQuick
import QtQuick.Controls
Page {
ListModel {
id: contactsModel
ListElement { name: "Bill Smith"; position: "Engineer" }
ListElement { name: "John Brown"; position: "Engineer" }
ListElement { name: "Sam Wise"; position: "Manager" }
}
ListView {
id: listView
anchors.fill: parent
model: contactsModel
delegate: Loader {
width: ListView.view.width
source: position + ".qml"
}
}
}
//Engineer.qml
import QtQuick
import QtQuick.Controls
Rectangle {
property bool isCurrentItem: listView.currentIndex === index
height: 50
color: isCurrentItem ? "#0033cc" : "#585858"
border.color: "gray"
border.width: 1
Text {
anchors.centerIn: parent
color: "white"
text: name
}
}
//Manager.qml
import QtQuick
import QtQuick.Controls
Rectangle {
property bool isCurrentItem: listView.currentIndex === index
height: 50
color: isCurrentItem ? "#cc3300" : "#661100"
border.color: "blue"
border.width: 1
Text {
anchors.centerIn: parent
color: "white"
text: name
}
}
You can Try it Online!

Resources