Dynamic component inside a scrollview can't work - qt

If I replace component with Rectangle, then the scrollview works.....
Any idea? How to include dynamic parts in ScrollView?
Thanks in advance
ScrollView {
id:template1
contentHeight: 2000
....
Component {
id: routeComp
CruiseRouteButton {
}
}
function createTab() {
}
Component.onCompleted: {
for(var i = 0; i < mainWnd.config.routes.length; i++) {
....
let t = routeComp.createObject(template1, cfg);
routes[i] = t
}
createTab()
}
This component is used to dynamically set some routes for user to choose

Related

Qt QML change item of a page dynamically

I have BasePage.qml like this:
Item {
property alias content: loader.sourceComponent
signal topBarLeftButtonClicked()
TopBar {
...
}
Loader {
id: loader
}
BottomBar {
...
}
}
This way I can change dynamically the content of the page, but I must use Component, and I can't read properties of the content in a DerivedPage.
For example:
DerivedPage.qml
BasePage {
onTopBarLeftIconClicked: item.text //error, item is not defined
content: Component {
TextField {
id: item
}
}
}
Any solution?
You can define an alias to the Loader's item property inside BasePage, like that:
property alias contentItem: loader.item
And refer to it instead of content item within DerivedPage.
Putting it all together:
// BasePage.qml
Item {
property alias content: loader.sourceComponent
property alias contentItem: loader.item
signal topBarLeftButtonClicked()
Loader { id: loader }
}
// DerivedPage.qml
BasePage {
onTopBarLeftIconClicked: { contentItem.text = "clicked" }
content: Component { TextField { } }
}
I found a way to replace loader and components:
//BasePage.qml
Item {
default property alias data: item.data
signal topBarLeftButtonClicked()
TopBar {
...
}
Item{
id: item
}
BottomBar {
...
}
}
//DerivedPage.qml
BasePage {
onTopBarLeftIconClicked: textField.text = "string"
TextField {
anchors.fill: parent
id: textField
}
}
This way textField replace item in BasePage.qml

How to dynamically update a ListView in QML

I have a ListView that displays a list of all notifications for a user. Right now since I'm using Component.onCompleted, if the list updates, the new list is not displayed, but the one which existed during instantiation. How could we solve this? Would using a Loader with a separate component instead help?
property int numNotifications: backend_service.num_notifications
property var notifications: []
onNumNotificationsChanged: {
for(var x=0; x<numNotifications; x++) {
var notif = backend_service.notifications.get(x);
notifications.push(notif)
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
color: "black"
height: 500
width: 0.95 * parent.width
ListView {
anchors.fill: parent
model: notifModel
delegate: notifDelegate
}
}
ListModel {
id: notifModel
Component.onCompleted: {
for(var i in notifications) {
notifModel.append({"name": notifications[i]})
}
}
}
Component {
id: notifDelegate
Row {
spacing: 10
Text { text: name; color: "white" }
}
}
Component.onCompleted only runs when the object is built and never again. So using that method to add items to the model is useless, instead you should use the function that reports the new data:
onNumNotificationsChanged: {
for(var x=0; x<numNotifications; x++) {
var notif = backend_service.notifications.get(x);
notifModel.append({"name": notif})
}
}

Qt QML ComboBox override wheel events

Is there any way to override ComboBox MouseArea to ignore wheel event instead of changing current index? ComboBox itself has no option to change wheel focus behaviour. So far I've tried to override onWheel from CB MouseArea with code like this:
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
console.log(combobox_ctrl.children[i])
console.log(combobox_ctrl.children[i].hasOwnProperty('onWheel'))
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')) {
console.log(combobox_ctrl.children[i]['onWheel'])
combobox_ctrl.children[i]['onWheel'] = function() { console.log("CB on wheel!") }
//combobox_ctrl.children[i]onWheel = function() { console.log("CB on wheel!")
//combobox_ctrl.children[i].destroy()
}
}
}
}
But I get
TypeError: Cannot assign to read-only property "wheel"
Did anyone was able to disable wheel events on ComboBox in Qml?
// EDIT
for example in Slider control I was able to remove wheel event handling like this:
Slider {
Component.onCompleted: {
for (var i = 0; i < slider.children.length; ++i) {
console.log(slider.children[i])
if (slider.children[i].hasOwnProperty("onVerticalWheelMoved") && slider.children[i].hasOwnProperty("onHorizontalWheelMoved")) {
console.log("Found wheel area!")
slider.children[i].destroy()
}
}
}
}
But in slider WheelArea is not responsible for handling "click" events.
You can place MouseArea over ComboBox and steel wheel event.
ComboBox {
anchors.centerIn: parent
model: [ "Banana", "Apple", "Coconut" ]
MouseArea {
anchors.fill: parent
onWheel: {
// do nothing
}
onPressed: {
// propogate to ComboBox
mouse.accepted = false;
}
onReleased: {
// propogate to ComboBox
mouse.accepted = false;
}
}
}
It's not currently possible, as ComboBox is not derived from MouseArea, but FocusScope, which has no support for these kinds of events.
A similar problem was mentioned in a suggestion recently:
Disable mouse wheel scroll event on QtQuick.Controls
If you're after a hacky way of doing it, it seems like the only option you have left is to apply a patch to ComboBox.qml that removes the onWheel handler:
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 4e29dfe..3413cac 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
## -407,13 +407,6 ## Control {
popup.toggleShow()
overridePressed = false
}
- onWheel: {
- if (wheel.angleDelta.y > 0) {
- __selectPrevItem();
- } else if (wheel.angleDelta.y < 0){
- __selectNextItem();
- }
- }
}
Another alternative that doesn't involve modifying Qt code would be to add an intermediate MouseArea above ComboBox's, and then somehow only forward specific events through to ComboBox's MouseArea. Or, create a custom C++ item that does the equivalent. You may have more control that way.
Ok. After hacking around I've managed to come with solution that is acceptable for me but may introduce some regressions in some situations. pressed and hovered properties are no longer usable
import QtQuick.Controls.Private 1.0
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel') && combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
if (!Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
if (Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
}
}
This way we can mimic mouse area that was originaly inside the ComboBox. Popup is shown as it was (at least I didn't see any regresion in it yet). However two properties are inaccesible right now
I created a separate file called NonScrollingComboBox.qml with the following code following this post: https://stackoverflow.com/a/33080217/969016
Now I can just use NonScrollingComboBox as a component instead of ComboBox on places where I don't want the mouse scroll to change the value
import QtQuick 2.0
import QtQuick.Controls 1.4
ComboBox {
id: combobox_ctrl
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')
&& combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
combobox_ctrl.__popup.toggleShow()
}
}
}
usage:
NonScrollingComboBox {
anchors.verticalCenter: parent.verticalCenter
model: ["item one", "item 2"]
}
This seems to apply only to Qt Quick Controls 1 ComboBox. On Qt Quick Controls 2 ComboBox the wheel mouse event is not enabled by default and can be enabled manually by setting to true the property wheelEnabled (documented in the base class Control). Also the combobox won't keep a "focus" on mouse events so you can freely use the wheel on other mouse areas by just entering them.

Items inside TabView/Tab are not accessible

Suppose we have a QML file like the following:
Window {
Component.onCompleted: rect.color ="green"
TabView {
Tab {
Rectangle {
id: rect
color: "white"
}
}
}
}
When I run this code, for some reason, it gives me this error:
ReferenceError: rect is not defined
Somebody can say it's a scope problem but the following code works fine:
Window {
Component.onCompleted: rect.color ="green"
Item {
Item {
Rectangle {
id: rect
color: "white"
}
}
}
}
In my case I have a big form with tabs and controls inside it and I pass all the controls to several functions to validate the form, i.e. some code like this:
function onClose() {
validate(control1);
validate(control2);
// etc
}
but while accessing controls by id I get the above error.
How about just binding the rectangle to a color parameter instead of a hardcoded value?
This wil separate your Model and View code further to make it more readable elsewhere in your project as well...try:
Window {
property string myColor: "White"
Component.onCompleted: myColor = "Green"
TabView {
Tab {
Rectangle {
id: rect
color: myColor
}
}
}
}
To view the QML object tree, you need to start your project in debug mode. Then split your code window so that the "Locals and Expressions" view is showing (checkbox ticked on the right side). You will see your root item and all the other controls nested in a tree and now you can directly edit the values of their properties, and the changes will render immediately. There is a youtube video tutorial on debugging: https://youtu.be/mPXn6L2Wftc?t=19m55s
I'm not sure if the tree would give you access to the controls at runtime, but it might help you with debugging.
Ok, since Tab item cannot be accessed from outside I think it can be done in this way:
TabView {
id: tabView
Tab {
title: "tab1"
function validate() { /* validate all the controls related to tab1 only */ }
Item { id: item1 }
Item { id: item2 }
}
Tab {
title: "tab2"
function validate() { /* validate all the controls related to tab2 only */ }
Item { id: item3 }
Item { id: item4 }
}
function validateTabs() {
for(var i = 0; i < tabView.count;i ++) {
var tab = tabView.getTab(i);
if(tab && tab.active && tab.item.validate) {
if(!tab.item.validate())
return false;
}
}
return true;
}
}
The good point that if some Tab wasn't opened and so not changed it will not be validated.

QML - tracking global position of a component

I would like to track a global position of an object (or relative to one of it's ancestors) and bind it to some other item's position.
I was thinking about using mapFromItem as follows:
SomeObject {
x: ancestor.mapFromItem(trackedObject, trackedObject.x, 0).x
y: ancestor.mapFromItem(trackedObject, 0, trackedObject.y).y
}
The problem with this approach is that the mapFromItem is evaluated once and doesn't update as one of it's arguments gets updated. Moreover the mapping sometimes returns the new position altered by an offset I'm unable to track in the code (but that's not the matter at hand).
My second idea was to calculate the global position by implementing a function that would recursively sum the offsets, stopping at the provided ancestor (something like calculateOffsetFrom(ancestor)). Still this is just a function and as far as I'm concerned it won't get re-evaluated as one of the ancestors position changes (unless, in that function, I'll bind calling it to the onXChanged signal for each one of the ancestors along the way, which seems like a dirty solution).
So in the end I've added properties to the object I intend to track and then I bind to them:
TrackedObject {
property real offsetX: x + parent.x + parent.parent.x + parent.parent.parent.x ...
property real offsetY: y + parent.y + parent.parent.y + parent.parent.parent.y ...
}
SomeObject {
x: trackedObject.globalX
y: trackedObject.globalY
}
But well... yeah... this one doesn't scale at all and is as ugly as it gets.
Does anyone have any idea how this problem might be solved in a cleaner way?
Edit:
As far as I'm concerned I can't use anchors in this case. The SomeObject component is a custom component drawing a bezier curve from one point to another (it will connect two TrackedObjects). For that I need the difference between the coordinates. If I'm correct anchors don't provide any way of calculating the distance between them.
This is a hard point, but here is the hack i used in one of my projects : to make blue rect which is in another parent than green rect move, to stay aligned with it, when green rect moves but also when yellow rect (green rect parent) moves :
import QtQuick 2.0;
Rectangle {
id: window;
width: 800;
height: 480;
property bool globalBit : true;
function updatePos (item_orig, item_dest, bit) {
var pos_abs = window.mapFromItem (item_orig.parent, item_orig.x, item_orig.y);
return window.mapToItem (item_dest.parent, pos_abs.x, pos_abs.y);
}
Rectangle {
id: rectYellow;
width: 400;
height: 300;
x: 300;
y: 200;
color: "yellow";
onXChanged: { globalBit = !globalBit; }
onYChanged: { globalBit = !globalBit; }
MouseArea {
drag {
target: rectYellow;
minimumX: 0;
minimumY: 0;
maximumX: (rectYellow.parent.width - rectYellow.width);
maximumY: (rectYellow.parent.height - rectYellow.height);
}
anchors.fill: parent;
}
Rectangle {
id: rectGreen;
x: 100;
y: 100;
width: 50;
height: 50;
color: "green";
MouseArea {
drag {
target: rectGreen;
minimumX: 0;
minimumY: 0;
maximumX: (rectGreen.parent.width - rectGreen.width);
maximumY: (rectGreen.parent.height - rectGreen.height);
}
anchors.fill: parent;
}
}
}
Rectangle {
id: rectBlue;
x: pos.x + 50;
y: pos.y + 50;
width: 50;
height: 50;
color: "blue";
property var pos : updatePos (rectGreen, rectBlue, globalBit);
}
}
The trick is to bring all coordinates back to the first common ancestor, using both mapfromItem and mapToItem, and to force the function to be re-evaluated, just put a global boolean flag that you pass to the computing function, and that you invert each time a movable element on your map moves... You don't have to put it every where, just on parents of items that can move and are inside the ancestor item.
So it works, your positions will always be right, and it's quite scalable and doesn't add much code.
The solution below will trigger the qmlElementToTrack.onPropertyNameXChanged() and qmlElementToTrack.onPropertyNameYChanged() events each time one of its parents 'x' or 'y' values change.
It does this by attaching to each parent's onXChanged() and onYChanged() signals. When one of those values changes, it recalculates the propertyNameX or propertyNameY values by traversing all of qmlElementToTrack's parents.
To make it 'relative' positioned (instead of 'absolute'), add current !== qmlElementToStopAt to each while() condition.
ElementToTrack {
id: qmlElementToTrack
property real propertyNameX: 0
property real propertyNameY: 0
}
setPositionChangedToParents(qmlElementToTrack);
/**
Connect to each parent's 'onXChanged' and 'onYChanged' signals.
*/
setPositionChangedToParents = function(current) {
while (current && current.parent) {
current.onXChanged.connect(calculatePropertyNameX);
current.onYChanged.connect(calculatePropertyNameY);
current = current.parent;
}
};
/**
Disconnects the signals set to all parents.
*/
removePositionChangedFromParents = function(current) {
while (current && current.parent) {
current.onXChanged.disconnect(calculatePropertyNameX);
current.onYChanged.disconnect(calculatePropertyNameY);
current = current.parent;
}
};
/**
When any parent's 'x' changes, recalculate the 'x' value for the 'property name'.
*/
calculatePropertyNameX = function() {
var calculatedX, current;
calculatedX = 0;
current = qmlElementToTrack;
while (current && current.parent) {
calculatedX += current.x;
current = current.parent;
}
propertyNameX = calculatedX;
};
/**
When any parent's 'y' changes, recalculate the 'y' value for the 'property name'.
*/
calculatePropertyNameY = function() {
var calculatedY, current;
calculatedY = 0;
current = qmlElementToTrack;
while (current && current.parent) {
calculatedY += current.y;
current = current.parent;
}
propertyNameY = calculatedY;
};
I don't know whether this will help, but for the above yellow rect,blue rect and green rect problem mentioned by TheBootroo, I used the below code to solve the problem
import QtQuick 2.0;
Rectangle {
id: window;
width: 800;
height: 480;
Rectangle {
id: rectYellow;
width: 400;
height: 300;
x: 300;
y: 200;
color: "yellow";
MouseArea {
drag {
target: rectYellow;
minimumX: 0;
minimumY: 0;
maximumX: (rectYellow.parent.width - rectYellow.width);
maximumY: (rectYellow.parent.height - rectYellow.height);
}
anchors.fill: parent;
}
Rectangle {
id: rectGreen;
x: 100;
y: 100;
width: 50;
height: 50;
color: "green";
MouseArea {
drag {
target: rectGreen;
minimumX: 0;
minimumY: 0;
maximumX: (rectGreen.parent.width - rectGreen.width);
maximumY: (rectGreen.parent.height - rectGreen.height);
}
anchors.fill: parent;
}
}
}
Rectangle {
id: rectBlue;
//Need to acheive the below behvior(commented)
//x: window.x+rectYellow.x+rectGreen.x+50
//y: window.y + rectYellow.y +rectGreen.y+50
width: 50;
height: 50;
color: "blue";
}
Component.onCompleted: {
rectBlue.x =Qt.binding(
function()
{
//Returns window.x+rectYellow.x+rectGreen.x+rectGreen.width
var docRoot = null;
var x=rectGreen.x;
if(!docRoot)
{
docRoot = rectGreen.parent;
x+=docRoot.x;
while(docRoot.parent)
{
docRoot = docRoot.parent;
x+=docRoot.x
}
}
return x+rectGreen.width;
}
)
rectBlue.y = Qt.binding(
function()
{
//Returns window.y+rectYellow.y+rectGreen.y+rectGreen.height
var docRoot = null;
var y=rectGreen.y
if(!docRoot)
{
docRoot = rectGreen.parent;
y+=docRoot.y;
while(docRoot.parent)
{
docRoot = docRoot.parent;
y+=docRoot.y
}
}
return y+rectGreen.height;
}
)
}
}
The idea is to calculate the position of blue rectangle relative to the green rectangle, by
calculating the position of green rectangle and its visual ancestors.
The inspiration behind this solution is -> http://developer.nokia.com/Community/Wiki/How_to_create_a_Context_Menu_with_QML
Tracking certain Item's global positions seems like an important problem if developing some complex graphics interaction. I came up with a relatively simple & graceful solution. Here is my core codes:
Item{
id: globalRoot
signal globalPositionChanged(Item item, real newX, real newY);
function tracking(item){
var obj = item;
var objN;
function onGlobalXYChanged(){
var pt = mapFromItem(item, item.x, item.y);
globalRoot.globalPositionChanged(item, pt.x, pt.y);
}
do{
objN = obj.objectName;
obj.xChanged.connect(onGlobalXYChanged);
obj.yChanged.connect(onGlobalXYChanged);
obj = obj.parent;
}while(objN !== "furthestAncestorObjectName");
}
}
The core idea is: what essentially makes an Item's global position change? It maybe itself, its parent or its parent's parent etc. So make a traverse back to its furthest parent and connect each of its ancestor's x/y change signal to a function, within which we get the item's global position and broadcast outside.
I have tried to improve on #Shubhanga's answer a bit by moving the code into its own ItemPositionTracker.qml file:
import QtQuick 2.3
Item {
id: root
property Item trackedItem
property Item movedItem
Component.onCompleted: {
movedItem.x =
Qt.binding(
function()
{
if (trackedItem === null) return 0;
var docRoot = trackedItem;
var x = trackedItem.x;
while(docRoot.parent)
{
docRoot = docRoot.parent;
x += docRoot.x
}
return x;
}
)
movedItem.y =
Qt.binding(
function()
{
if (trackedItem === null) return 0;
var docRoot = trackedItem;
var y = trackedItem.y
while(docRoot.parent)
{
docRoot = docRoot.parent;
y += docRoot.y
}
return y;
}
)
}
}
The code can now be added to any QML object like this:
ItemPositionTracker {
trackedItem: rectGreen
movedItem: rectBlue
}
Which makes rectBlue follow rectGreen.

Resources