How to skip drawing an item when a Grid is populated via an Repeater in qml? - qml

I want to fill a Grid with a Repeater but not all cells should get an Item. How to do that?
Rectangle {
id: base
Grid {
id: grid
anchors.fill: parent
columns: 30
rows: 20
Repeater {
model: grid.columns * grid.rows
delegate: Item {
// How to skip this at row 5, column 3, for example
width: grid.width / grid.columns
height: grid.height / grid.rows
}
}
}
}

This is basically the same problem, as how can I have different delegates in a Repeater depending on the model's data with the special case: The delegate is empty.
For this more general approach, you can use dynamic object creation like this:
Window {
id: root
visible: true
width: 400; height: 400
// delegate 0 is skipped, 1 is circle, 2 is quadrangle
ListModel {
id: lm
ListElement { delegate: 0 }
ListElement { delegate: 1; color: 'red' }
ListElement { delegate: 2; color: 'green' }
ListElement { delegate: 1; color: 'blue' }
ListElement { delegate: 0 }
ListElement { delegate: 0 }
ListElement { delegate: 1; color: 'orchid' }
ListElement { delegate: 0 }
ListElement { delegate: 1; color: 'red' }
ListElement { delegate: 2; color: 'green' }
ListElement { delegate: 1; color: 'blue' }
ListElement { delegate: 0 }
ListElement { delegate: 0 }
ListElement { delegate: 1; color: 'orchid' }
}
Grid {
anchors.fill: parent
columns: 5
Repeater {
id: rep
property var delegates: [function() { console.log('skipped'); return null }, circ.createObject, rect.createObject ]
model: lm
delegate: Item {
width: (delegate ? delegate.width : 1)
height: (delegate ? delegate.height : 1)
property var delegate: rep.delegates[model.delegate](this, { color: model.color })
}
}
}
Component {
id: circ
Rectangle {
width: 20
height: 20
radius: 10
}
}
Component {
id: rect
Rectangle {
width: 20
height: 20
}
}
}
Of course you can use any cirterium and method to chose, whether or which delegate should be used. The ListModel and list of functions just seems to be convenient in lots of cases. You can also just use a switch-statement or anything else.
Instead of using the JS-function you could also use a Loader as toplevel element, and set or not set a source. You only need to make sure the toplevel delegate has dimensions (at least width and height of 1) otherwise the positioners (Grid/Column/Row) will ignore their existence.
The advantage of the dynamic creation is, that if the delegate is complex, it won't be created and just turned invisible, as it would be with the solution of Tony Clifton

You can't really "skip" a delegate's creation. You can use index to determine which item(s) to hide. If you want an "empty" gap in the grid set opacity to 0. If you want it "completely" gone, set visible to false.
Repeater {
model: grid.columns * grid.rows
delegate: Rectangle {
// How to skip this at row 5, column 3
// 4 * columns + 2 = 122 // 4 & 3 because of zero based counting.
color: "red"
opacity: index !== 122 ? 1 : 0
width: (grid.width / grid.columns) - 1
height: (grid.height / grid.rows) - 1
Text {
anchors.centerIn: parent
text: index
}
}
}

Related

How can I make a custom delegate for my ListView?

I'm trying to make a custom ListView (AppDataListView) that can be used for data manipulation (essentially having a bunch of inputs). The problem I'm having is finding a way to tell AppDataListView what input types to use in the delegate of the ListView.
I currently try and do this by creating a loader in the ListView delegate, setting its source to a property of type Component, and then when I create an instance of AppDataListView, I specify the Component... However, I don't have any access to the model data, so it's kind of pointless.
Does anyone know how I can accomplish this?
Main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick .Controls 2.12
import QtQuick.Layouts 1.12
Window {
width: 640
height: 480
visible: true
ListModel {
id: sampleData
ListElement {
itemId: 1
name: "Name1"
}
ListElement {
itemId: 2
name: "Name2"
}
ListElement {
itemId: 3
name: "Name3"
}
ListElement {
itemId: 4
name: "Name4"
}
}
AppDataListView {
anchors.fill: parent
headers: ["ID", "Name"]
model: sampleData
delegate: Component {
RowLayout {
anchors.fill: parent
TextArea {
// I can't access itemId from here even though this is loaded into the delegate.
text: itemId
}
TextArea {
// I can't access name from here even though this is loaded into the delegate.
text: name
}
}
}
}
}
AppDataListView.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Rectangle {
property var headers: []
property alias model: listView.model
property alias listView: listView
required property Component delegate;
id: root
color: "#bdbdbd"
ColumnLayout {
anchors.fill: parent
anchors.margins: 1
spacing:0
RowLayout {
spacing: 1
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Item {
Layout.minimumWidth: 30
Layout.maximumWidth: 30
}
Repeater {
id: headerRepeater
model: headers
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
gradient: Gradient {
GradientStop { position: 0.0; color: "#FFFFFF" }
GradientStop { position: 0.5; color: "#F1F1F1" }
GradientStop { position: 1.0; color: "#FFFFFF" }
}
Label {
text: modelData
padding: 5
anchors.centerIn: parent
}
}
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
interactive: true
clip: true
boundsBehavior: Flickable.StopAtBounds
spacing: 1
ScrollBar.vertical: ScrollBar {
active: true
policy: ScrollBar.AlwaysOn
}
delegate: RowLayout {
width: parent.width
spacing: 1
Button {
id: rowBtn
Layout.minimumWidth: 30
Layout.maximumWidth: 30
background: Rectangle {
gradient: Gradient {
GradientStop { position: 0.0; color: (rowBtn.down ? "#56aff5" : rowBtn.hovered ? "#d9ebf9" : "#FFFFFF") }
GradientStop { position: 0.5; color: (rowBtn.down ? "#1b93f1" : rowBtn.hovered ? "#a4b2bd" : "#F1F1F1") }
GradientStop { position: 1.0; color: (rowBtn.down ? "#56aff5" : rowBtn.hovered ? "#d9ebf9" : "#FFFFFF") }
}
}
}
Loader {
sourceComponent: root.delegate
}
}
}
}
}
Capturing #Amfasis excellent comments to the question and adding some more detail....
Change your Loader reference to this:
Loader {
sourceComponent: root.delegate
property int itemId: model.itemId
property string name: model.name
}
fixes it. Here's why....
A Component declaration is put into the QML namespace hierarchy where it is declared not where it is instantiated at runtime. In other words, by declaring the delegate within main.qml, it can only see that namespace regardless of where it is instantiated at (in this case in AppDataListView).
More info here:
https://doc.qt.io/qt-5/qml-qtqml-component.html#creation-context
As #Amfasis pointed out, the workaround in this case is to declare properties on the Loader to pass in the model references you need. In this case the Loader acts as a bridge of sorts from the ListView namespace over to the delegate Component's namespace in main.qml.
More info on that here:
https://doc.qt.io/qt-5/qml-qtquick-loader.html#using-a-loader-within-a-view-delegate

flickableItem.atYEnd is always false when scrolling QML

I have a ListView which is inside a ScrollView and I want to know or listen when the scroll bar is scrolled to the bottom,
ScrollView {
id: moderatorMessagesScrollViewID
anchors.fill: parent
function update()
{
console.debug("<< moderatorMessagesScrollViewID ScrollView::update ", flickableItem.atYEnd);
}
flickableItem.onAtYBeginningChanged: {
update();
}
flickableItem.onAtYEndChanged: {
update();
}
flickableItem.onContentYChanged: {
update();
}
ListView {
id: moderatorMessagesList
anchors.leftMargin: 15
anchors.rightMargin: 15
anchors.bottomMargin: 5
layoutDirection: Qt.LeftToRight
orientation: ListView.Vertical
verticalLayoutDirection: ListView.BottomToTop
cacheBuffer: (chatPanel.height <= 0) ? 1000 : (chatPanel.height * 1000)
spacing: 0
focus: true
Component.onCompleted: {
updateView();
}
delegate: messageListDelegate
}
style: ScrollViewStyle {
incrementControl: Rectangle {
visible: false
implicitWidth: 0
implicitHeight: 0
}
decrementControl: Rectangle {
visible: false
implicitWidth: 0
implicitHeight: 0
}
corner: Rectangle {
color: "white"
visible: true
rotation: 180
}
handle: Rectangle {
implicitWidth: 5 * MyStyle.props.scrollSizeFactor()
implicitHeight: 7 * MyStyle.props.scrollSizeFactor()
color: Qt.rgba(237/255, 237/255, 237/255, 1)
radius: 2
border.width: 1
border.color: "#C3C3C3"
}
scrollToClickedPosition: true
handleOverlap: 1
scrollBarBackground: Rectangle {
width: 5 * MyStyle.props.scrollSizeFactor()
height: 10 * MyStyle.props.scrollSizeFactor()
color: MyStyle.props.color("chatChatPanelBackground")
}
transientScrollBars: false
}
}
but my issues here are
flickableItem.atYEnd is always false
flickableItem.onAtYEndChanged is not triggered even if I scroll at the bottom
update() is triggered by onContentYChanged() instead when i try to scroll using scrollbar.
What is wrong with this and what areas I need to look into?
Here's a minimum working example depicting the detection of Vertical scroll bar at its bottom:
ListModel {
id: contactModel
ListElement {
name: "Bill Smith"
number: "555 3264"
}
ListElement {
name: "John Brown"
number: "555 8426"
}
ListElement {
name: "Sam Wise"
number: "555 0473"
}
ListElement {
name: "Bill Smith"
number: "555 3264"
}
ListElement {
name: "John Brown"
number: "555 8426"
}
ListElement {
name: "Sam Wise"
number: "555 0473"
}
}
Rectangle {
width: 180; height: 50
Component {
id: contactDelegate
Item {
width: 180; height: 40
Column {
Text { text: '<b>Name:</b> ' + name }
Text { text: '<b>Number:</b> ' + number }
}
}
}
}
ScrollView {
width: 200
height: 60
ListView {
model: contactModel
delegate: contactDelegate
onAtYEndChanged: console.log("Vertical Scroll Bar's bottom reached = ", atYEnd)
}
}

GridLayout Arrangement

Following is my main.qml:
Window {
id: window
visible: true
width: 800
height: 480
title: qsTr("Hello World")
ListModel {
id: _listModel
ListElement {
textData: "E1"
isEnabled: false
}
ListElement {
textData: "E2"
isEnabled: false
}
ListElement {
textData: "E3"
isEnabled: false
}
ListElement {
textData: "E4"
isEnabled: false
}
ListElement {
textData: "E5"
isEnabled: false
}
ListElement {
textData: "E6"
isEnabled: false
}
}
ListView {
id: _listview
model: _listModel
width: 100
height: parent.height
anchors.right: parent.right
delegate: Item {
width: parent.width
height: 50
anchors.right: parent.right
Component.onCompleted:
{
if (isEnabled)
visibleRecs++;
}
RowLayout {
Text {
id: itemText
text: qsTr(textData)
}
CheckBox {
height: 30
width: height
checked: isEnabled
onCheckedChanged: {
isEnabled = checked
}
}
}
}
}
ScrollView {
id: _scrollView
width: parent.width / 2
height: parent.height
clip: true
GridLayout {
id: _gridLayout
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
columnSpacing: 10
rowSpacing: 10
columns: 2
Repeater {
model: _listModel
Loader {
id: _loader
sourceComponent: isEnabled ? _recComponent : null
onLoaded: {
item.text = textData
}
}
}
}
}
Component {
id: _recComponent
Rectangle {
property alias text : _txt.text
id: _rec
width: 100
height: 50
radius: 5
color: "yellow"
Text {
id: _txt
anchors.centerIn: parent
}
}
}
}
The above code creates following (when all check boxes are ticked):
When all checkboxes are ticked:
When checkbox E3 is unchecked:
I want the items to be rearranged in the gridlayout if any of the item goes invisible.
E.g. in the above case when E3 is unchecked, I want my view to be something like this:
Please let me know, if it is possible to achieve. Thanks in advance.
The problem is that you still instantiate the Loader, you just set the sourceComponent to null. You have to make the item invisible to not use space in the GridLayout (or put width/height to 0)
ScrollView {
id: _scrollView
width: parent.width / 2
height: parent.height
clip: true
GridLayout {
id: _gridLayout
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
columnSpacing: 10
rowSpacing: 10
columns: 2
Repeater {
model: _listModel
Loader {
id: _loader
visible: isEnabled
sourceComponent: isEnabled ? _recComponent : null
onLoaded: {
item.text = textData
}
}
}
}
}

How to highlight the clicked (by mouse) element of a delegate w.r.t FolderListModel?

import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate: Text { id: h; text: fileName }
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
This works only when I use keyboard. How to make it work on mouse clicks?
To react on mouse events you need to place MouseArea item.
In the sample below (being an expanded version of the code you provided) I have added a MouseArea to the delegate item that upon being clicked sets the ListView's currentIndex to the delegate's index (a special property visible in the ListView's delegate).
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate:
Text {
id: h;
text: fileName
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
As an alternative approach you might try placing a single MouseArea filling the whole ListView and use ListView's indexAt(int x, int y) method to check which delegate was clicked. However, you would need to care about more edge-conditions in such case.

Scale Element in StateELement

I can write lines below and got it work:
states: State {
name: "active"; when:myitem.activeFocus;
PropertyChanges { target: myitem; z:1000; scale: 1.2 }
}
transitions: Transition {
NumberAnimation { properties: scale; duration: 1000 }
}
But in these lines i can not give specific origin to scale property!
I found Scale Element
transform: Scale { origin.x: 25; origin.y: 25; xScale: 3}
How can i inject this into state property above, because i want to use "when" property of state,
i want scaling to run on that "when" condition.
Or is there any other way to scale in a condition with specifying origins?
Thanks for any idea.
You should set an id for the Scale element. Then you can change its properties in the "active" state.
Here a minimal working example:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale { id: scaleTransform; origin.x: 25; origin.y: 25 }
states: State {
name: "active"; when: mouseArea.pressed
PropertyChanges { target: scaleTransform; xScale: 3 }
}
transitions: Transition {
NumberAnimation { property: "xScale"; duration: 1000 }
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
Another possibility without states could be:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale {
id: scaleTransform
origin.x: 25; origin.y: 25
xScale: mouseArea.pressed ? 3 : 1 // <-
Behavior on xScale { // for animation
NumberAnimation { duration: 1000 }
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
xScale is set to 3 when mouse is pressed, else to 1 (if you don't know the " ternary operation" look here).