I have a 'base' component which controls the aspect ratio of the item 'childItem':
//AspectRatio.qml
import QtQuick 2.12
Rectangle
{
property real targetAspectRatio: 16 / 9
color: "black"
anchors.fill: parent
onWidthChanged:
{
var _ratio = parent.width / parent.height
var _width = 0
if(_ratio > targetAspectRatio) //Too wide
{
_width = parent.height * targetAspectRatio
}
else
{
_width = parent.width
}
childItem.width = _width
}
onHeightChanged:
{
var _ratio = parent.width / parent.height
var _height = 0
if(_ratio > targetAspectRatio) //Too wide
{
_height = parent.height
}
else
{
_height = parent.width / targetAspectRatio
}
childItem.height = _height
}
Item
{
id: childItem
anchors.centerIn: parent
}
}
I want to use AspectRatio.qml as a generic component and override 'childItem', depending on the context the component is used. How can 'childItem' be overridden, like this?
//child.qml
AspectRatio
{
childItem : Rectangle
{
color: "red"
}
}
This mechanism is also used in standard qml components, like here. But it's unclear to me how to achieve this.
There are multiple solutions for your problem.
You could create a childItem property and manually put it in the children property of Item like so:
// AspectRatio.qml
Rectangle {
property real targetAspectRatio: 16 / 9
property Item childItem
children: [childItem]
color: "black"
anchors.fill: parent
onWidthChanged: // ...
onHeightChanged: // ...
}
// Usage
AspectRatio {
childItem : Rectangle {
color: "red"
}
}
Alternatively you could do it the other way and bind the childItem to the children and not have an explicit setter and your property just being a "view" into children:
readonly property Item childItem: children[0]
// Usage
AspectRatio {
Rectangle { /* ... */ }
}
To be able to use both syntax you could use a default property, overriding the Item's children default property:
default property Item childItem
children: [childItem]
// Usage
AspectRatio {
Rectangle { /* ... */ }
}
// OR
AspectRatio {
childItem: Rectangle { /* ... */ }
}
I'd say this one is my preferred solution.
You didn't ask but I would replace your imperative code in onWidth/heightChanged by declarative bindings (and move the anchors outside):
Rectangle {
id: root
property real targetAspectRatio: 16 / 9
default property Item childItem
children: [childItem]
color: "black"
Binding {
target: root.childItem
property: "width"
value: Math.min(root.height * root.targetAspectRatio, root.width)
}
Binding {
target: root.childItem
property: "height"
value: Math.min(root.width / root.targetAspectRatio, root.height)
}
}
You can't "override" an item, but you can create a property that points to the child list of an item. This is how things like the background property that you mentioned on controls work. By assigning to this property, you're essentially inserting an object as a child of whatever the containing Item is.
//AspectRatio.qml
Rectangle
{
property alias childItem: childContainer.data
Item
{
id: childContainer
anchors.centerIn: parent
}
}
//child.qml
AspectRatio
{
childItem : Rectangle
{
width: 100
height: 100
color: "red"
}
}
Related
Iam relatively new to qml and maybe my attempt is wrong, so please, if you have another idea for solving the issue, please do not hesitate to write it down.
Problem:
I need to Display varoius sizes of ListElemts (which can be dragged and dropped). The main Container is 200px x 180px. The smalles List element is 100px x 60px, the secound size should be 200px x 60px and the biggest should be 200px x 120 px. So I found an Qt example and modified it to my needs, but I have problems regarding the automatic placement of the ListElements.
It works perfect for only small ListElements like here:
Now, if I need a big one, GridView is placing them for Expample like this one:
In this Example, the View is much larger in width like 200px. (gragging the ListElements around may sort them other way, but not proper.)
In the last test, I removed CellWidth and Cell height:
The traget is to display all cells in a 200px x 180px Grid.
As said, maybe my attempt is wrong. Do anyone of you guys have a solution for me?
Here is the modified code. For testing, I commented out things and altered the size property:
Window {
visible: true
width: 480
height: 272
title: qsTr("Hello World")
GridView {
id: root
width: 200; height: 180
cellWidth: 100; cellHeight: 60
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
//! [0]
model: DelegateModel {
//! [0]
id: visualModel
model: ListModel {
id: colorModel
ListElement { color: "blue"; size:1}
ListElement { color: "green"; size:1 }
ListElement { color: "red"; size: 1 }
ListElement { color: "yellow"; size: 1 }
ListElement { color: "orange"; size: 1 }
ListElement { color: "purple"; size: 1 }
}
//! [1]
delegate: MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
width:{
switch (model.size) {
case 1:
return 100;
case 2:
return 200;
case 3:
return 200;
}
}
height: {
switch (model.size) {
case 1:
return 60;
case 2:
return 60;
case 3:
return 120;
}
}
drag.target: icon
Rectangle {
id: icon
width:{
switch (model.size) {
case 1:
return 100;
case 2:
return 200;
case 3:
return 200;
}
}
height: {
switch (model.size) {
case 1:
return 60;
case 2:
return 60;
case 3:
return 120;
}
}
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter
}
color: model.color
radius: 3
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: icon.width/2
Drag.hotSpot.y: icon.height/2
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
}
DropArea {
anchors { fill: parent; margins: 0 }
onEntered: visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
}
}
//! [1]
}
}
}
Thank you in advance for all your thoughts/suggestions!
Edit:
To clear up the questions, here a gif with showd correct rearrangement when drag and drop items:
This Picture shows the the problems I decribed. Some items are overlapping:
This picture is showing the problem with not setted cellWidth and cellHeight of GridView.
As far as I understand, GridView donĀ“t care about the size of its elements. Positioning Elements depends on the cellWith and cellHeight Values. But is it possible to configure GridView the way I need (do not overlap, let items not come outside bonds)?
Or is GradView maybe the wrong attempt to realize this?
If anyone need more information, I will be happy to provide!
I am animating the grid item to upwards on swiping up and removes the corresponding item on the animation stopped function.
But the problem is when trying to remove the 0th index item. when removing 0th index item with animation, the whole screen moves up instead of the corresponding item animation.
Is there any solution for this ?
Item {
id: fake_drag
x:0
y:0
width: 288*scaleFactor
height: 215*scaleFactor
property bool isReleased: false
NumberAnimation {
id: animationY
target: fake_drag
property: "y"
from: mouseYValue;
to: -200
duration: 500
onStopped: {
console.log("removed index : ", indexValue)
favouriteapp.remove(indexValue)
}
}
MouseArea {
id: mouseArea
property int difference: 0
width: 288*scaleFactor
height: 215*scaleFactor
anchors.centerIn: parent
onPressed : {
itemInitialYPosition = mouseY
indexValue = index
}
drag.target: fake_drag_image
drag.axis: Drag.YAxis
onReleased: {
onceloaded = false;
}
onMouseYChanged: {
mouseYValue = mouseY
difference = itemInitialYPosition - mouseY
if(!onceloaded) {
if(itemInitialYPosition >= mouseY){
if(difference >= 25){
console.log("swipe..")
animationY.start()
onceloaded = true;
}
}
}
}
Image {
id: fake_drag_image
width: 64; height: 64
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
source: "images/Feature_Galary.png"
states: State {
when: mouseArea.drag.active
ParentChange { target: fake_drag_image; parent: itemid }
AnchorChanges { target: fake_drag_image; anchors.verticalCenter: undefined; anchors.horizontalCenter: undefined }
}
}
}
This is the code used in grid item delegate.
I have 2 qml files.
I want to set 2nd qml fromperty from the 1st qml file.
**first.qml**
var cmponent = Qt.createComponent("second.qml");
var newObj = cmponent.createObject(swipeView);
newObj.pageIndex = i;
swipeView.insertItem(swipeView.currentIndex+i,newObj)
and insert into a SwipeView.
Where , 'pageIndex' is the integer property of 2nd qml.
In the second qml file I have a GridLayout with cells.
I need to display the cell content based on this dynamic pageIndex property
second.qml
declared the property.
property int pageIndex: 0
onPageIndexChanged:{
console.log("onPageIndexChanged :" +pageIndex)
home_grid.update()
}
The onPageIndexChanged method is triggered but, I want to set the Grid cells based on the property value.
The issue is
While initialization of the component
var cmponent = Qt.createComponent("second.qml");
the cells are loaded into the GridLayout.
How can I relead/ solve this issue.
I think what you want is this:
(I used my example for your fist question)
It should illustrate my comments.
Of course you can put the different views in different files. You just need to pass the same model to both, when creating the objects.
ListModel {
id: lm
ListElement { width: 40; height: 40 }
[...]
ListElement { width: 40; height: 40 }
}
SwipeView {
width: 200
height: 800
clip: true
currentIndex: 0
Repeater {
model: Math.ceil(lm.count / 6)
delegate: ListView {
width: 200
height: 800
property int viewIndex: index
model: DelegateModel {
model: lm
groups: DelegateModelGroup { name: 'filter' }
Component.onCompleted: {
for (var i = viewIndex * 6; i < lm.count && i < (viewIndex * 6) + 6; i++) {
items.setGroups(i, 1, ['items', 'filter'])
}
}
filterOnGroup: 'filter'
delegate: Rectangle {
width: 180
height: 30
border.width: 1
Text {
anchors.centerIn: parent
text: index
}
}
}
}
}
}
GridView {
clip: true
x: 300
width: 600
height: 600
model: lm
delegate: TestObj {
}
}
Here is the Code for the delegate TestObj
Rectangle {
width: model.width
height: model.height
property alias text: myText.text
Text {
id: myText
anchors.centerIn: parent
text: index
}
}
Of course you could also write:
delegate: TestObj {
width: model.width; height: model.height; text: index
}
which would minimize the dependencies of your second QML-File.
I want to have a single (vertical) ListView with (horizontal) ListView delegates.
The horizontal delegates should scroll synchronously. To do so, I put a Flickable on top of the ListViews and bind the contentX of the horizontal ListView to the contentX of the Flickable (and the same for the contentY of the vertical ListView) (Note: Here a different approach was described for the synchronous ListView scrolling but this seems to have performance issues on mobile devices)
The code below kind of works but still has the following issues
I don't get the onClicked in the Rectangle (I do get it when I remove the top Flickable)
I want either horizontal flicking or vertical flicking but not both at the same time. I can restrict the flicking of the top Flickable by setting flickableDirection: Flickable.HorizontalFlick but then I can't flick vertically anymore (I was hoping that the Flickable would pass on unused mouse events to the vertical ListView but this doesn't seem to happen)
Suggestions on how to fix these issues?
Any help appreciated!
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
ListView {
id: verticalList
width: parent.width;
height: parent.height;
contentY : flickable.contentY
anchors.fill: parent
spacing: 10
orientation: ListView.Vertical
model: 100
delegate:
ListView {
id: horizontalList
width: parent.width;
height: 100;
contentX : flickable.contentX
spacing: 10
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
delegate:
Rectangle
{
property var colors : ['red', 'green', 'blue']
property var widths : [100, 200, 300]
height: 100
width: widths[(verticalIndex + model.index) % widths.length]
color: colors[model.index % colors.length]
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
}
}
}
}
//on top a Flickable
Flickable {
id: flickable
height: parent.height
width: parent.width
contentHeight: 100*100 //nrOfRows * rowHeight
contentWidth: 20*300 //nrOfEvent * max/averageEventWidth
}
}
I'm not giving you a perfect solution, but it's working. When you are using Flickable on the top of the ListView, you are not able to interact with it. So, I've used Flickable bellow the ListView and bounded the contentX of Flickable and ListView, but this is causing a loop and I'm getting the following output, but we're getting the desired behavior.
QML Binding: Binding loop detected for property "value"
EDIT
So, instead of using ListView for vertical list, I just used a Repeater and Column and used property binding. It's working well now.
Following is the updated version.
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
property bool virticalFlick: false //To get either vertical or horizontal flicking
Flickable {
anchors.fill: parent
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
flickableDirection: Flickable.VerticalFlick
interactive: (virticalFlick === true)?true:false
Column {
id: column
spacing: 10
Repeater {
id: repeater
model: 20
ListView {
id: horizontalList
width: 600;
height: 100;
clip: true
interactive: (virticalFlick === true)?false:true
spacing: 10
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
onMovingChanged: {
if(moving == true) {
for(var i=0; i<repeater.count ; i++) {
/* If the property is later assigned a static value from a JavaScript statement,
this will remove the binding.
However if the intention is to create a new binding then the property
must be assigned a Qt.binding() value instead. This is done by passing a function to
Qt.binding() that returns the desired result */
if (i !== index)
repeater.itemAt(i).contentX = Qt.binding(function() { return contentX });
}
}
else {
for(var i=0; i<repeater.count ; i++) {
if (i !== index)
repeater.itemAt(i).contentX = contentX; // This will remove binding
}
}
}
delegate: Rectangle {
property var colors : ['red', 'green', 'blue']
property var widths : [100, 200, 300]
height: 100
width: widths[(ListView.view.verticalIndex + model.index) % widths.length]
color: colors[model.index % colors.length]
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
}
}
}
}
}
}
}
The following does work, however the initial attempt seemed more elegant.
I still need to compare the performance (fps) when flicking, especially on a mobile device. I also get "Binding loop" warnings but I think they are false positives.
import QtQuick 2.0
Item {
id: main
visible: true
width: 600
height: 600
ListView {
id: verticalList
width: parent.width;
height: parent.height;
anchors.fill: parent
spacing: 10
cacheBuffer: 500 // in pixels
orientation: ListView.Vertical
model: 100
property var activeIndex : 0
property var lastContentX : 0
delegate:
ListView {
id: horizontalList
width: parent.width;
height: 100;
spacing: 10
cacheBuffer: 500 // in pixels
orientation: ListView.Horizontal
model: 20
property var verticalIndex : index
delegate:
Rectangle
{
property var colors : ['red', 'green', 'blue']
color: colors[model.index % colors.length]
height: 100
property var widths : [100, 200, 300]
width: widths[(verticalIndex + model.index ) % widths.length]
MouseArea {
z:10
anchors.fill: parent
onClicked: {
console.log("Rectangle.onClicked")
}
onPressed: {
console.log("Rectangle.onPressed")
}
onReleased: {
console.log("Rectangle.onReleased")
}
}
}
onContentXChanged: {
if(model.index === verticalList.activeIndex)
{
verticalList.lastContentX = contentX
}
}
onMovementStarted: {
verticalList.activeIndex = model.index
unbind();
}
onMovementEnded: {
bind();
}
Component.onCompleted: {
bind();
}
function bind()
{
contentX = Qt.binding(function() { return verticalList.lastContentX })
}
function unbind()
{
contentX = contentX ;
}
}
}
}
The following modifications were needed to my initial attempt
limit the Flickable to flickableDirection : Flickable.HorizontalFlick and remove the contentY : flickable.contentY on the verticalList
by doing so, there is no vertical scrolling anymore. This can be fixed by moving the Flickable inside the ListView
onClicked events are received by adding the following MouseArea to the Flickable
eg.
MouseArea {
anchors.fill: parent
//see http://stackoverflow.com/questions/29236762/mousearea-inside-flickable-is-preventing-it-from-flicking
onReleased: {
if (!propagateComposedEvents) {
propagateComposedEvents = true
}
}
}
I have a 4x4 grid and I want to associate arrow key presses with the movement of items within the grid. How does one do that?
Here is a sample QML:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
Grid {
id: grid16;
x: 5; y: 5;
width: 490; height: 490;
rows: 4; columns: 4; spacing: 5;
Repeater {
model: 1;
Rectangle {
width: 118; height: 118; color: "darkblue";
}
}
}
Keys.onRightPressed: pressRight();
function pressRight() {
console.log("Left key pressed");
}
focus: true;
}
Update 1: Thanks to sebasgo and alexisdm for the answers. If moving within a grid is not that easy why we have the move transition property [http://qt-project.org/doc/qt-4.8/qml-grid.html#move-prop]
You'd better use a GridView Item instead of your Grid approach.
This way you can use it's currentIndex property to choose which item to move like this:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
GridView {
id: grid16;
x: 5; y: 5;
width: 490; height: 490;
model: gridModel
delegate: Component{
Rectangle {
width: 118; height: 118; color: "darkblue";
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: value
}
}
}
}
ListModel {
id: gridModel
ListElement {value: 1}
ListElement {value: 2}
ListElement {value: 3}
ListElement {value: 4}
}
Keys.onRightPressed: {
gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1)
}
Keys.onLeftPressed: {
gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1)
}
focus: true;
}
Grids give you no way to manipulate the position of the contained items directly. Instead their position is directly derived from the physically order of the child items of the grid. There is no easy way to to manipulate child items in QML dynamically, so I think you should abandon the Grid item and specify the position of the child items explicitly with the x and y properties. Applied to your code this could look like:
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
Item {
x: 5; y: 5;
width: 490; height: 490;
Repeater {
id: pieces
model: 1;
Rectangle {
property int column: 0
property int row: 0
x: column * 123
y: row * 123
width: 118; height: 118; color: "darkblue";
}
}
}
Keys.onRightPressed: pressRight();
function pressRight() {
console.log("Left key pressed");
pieces.itemAt(0).column++
}
focus: true;
}
Update 1:
Grids (in combination with a Repeater) can be used to visualize models, e.g., a XmlListModel item or an QAbstractItemModel descendent.
With move property it's easy to react to layout changes in the model (if an entry is removed/added) in an animated way. Still, the items in the Grid are laid out strictly in the order of the entries of the model.
So if you want have manual control over the position of your items, even in cellular layout, use of a Grid is not advisable.
You can change the number of items before the item you want to move to change its position:
import QtQuick 1.1
Rectangle {
id: main;
width: 500; height: 500;
color: "darkgreen";
property int emptyBlock: 16;
property int posX: 0;
property int posY: 0;
Grid {
id: grid;
x: 5; y: 5;
width: 490; height: 490;
rows: 4; columns: 4; spacing: 5;
Repeater {
id: beforeTheItem
model: main.posX + parent.columns * main.posY
Rectangle {
width: 118; height: 118; color: "transparent";
}
}
Rectangle {
id:theItem
width: 118; height: 118; color: "darkblue";
}
}
Keys.onPressed: {
// To avoid flickering, the item is hidden before the change
// and made visible again after
theItem.visible = false;
switch(event.key) {
case Qt.Key_Left: if(posX > 0) posX--;
break;
case Qt.Key_Right: if(posX < grid.columns - 1) posX++;
break;
case Qt.Key_Up: if(posY > 0) posY--;
break;
case Qt.Key_Down: if(posY < grid.rows - 1) posY++;
break;
}
theItem.visible = true;
}
focus: true;
}
Now, by using Qt 5.1 or above and GridLayout you can move your items without hassle:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window
{
visible: true
MainForm
{
GridLayout {
id: gridLayout
columns: 3
property int oneRow: 0
property int oneCol: 0
Text { id: one; Layout.row :grid.oneRow; Layout.column: grid.oneCol; text: "My"; font.bold: true; }
Text { text: "name"; color: "red" }
Text { text: "is"; font.underline: true }
Text { text: "not"; font.pixelSize: 20 }
Text { text: "Ravan"; font.strikeout: true }
}
Component.onCompleted:
{
gridLayout.oneRow = 1
gridLayout.oneCol = 2
}
}
}
The GridView is a very confusing monster. It just populates one row from a given model, which leads to confusion since it is called GRID. But it can still be used as a fixed size grid, as I show in the example below. A single square can be moved with arrow keys on a 4x4 sized grid.
GridView {
id: grid16;
anchors.fill: parent
cellWidth: parent.width / 2
cellHeight: parent.height / 2
model: gridModel
delegate:
Rectangle {
Component.onCompleted: if( index >= 1 ) visible = false
width: grid16.cellWidth ; height: grid16.cellHeight ; color: "yellow";
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: value
}
}
move: Transition {
NumberAnimation { properties: "x,y"; duration: 1000 }
}
}
ListModel {
id: gridModel
ListElement {value: 1}
//Necessary, otherwise the grid will have the dimension 1x1
ListElement {value: 2}
ListElement {value: 3}
ListElement {value: 4}
}
Keys.onRightPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1) }
Keys.onLeftPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1) }
Keys.onUpPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex-2, 1) }
Keys.onDownPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex+2, 1) }
focus: true;
}