QML hack/fiddle to get something to trigger change in state - qml

I'm pretty new to working with QML, we have some buttons that are enabled and disabled according to other content on the page. When the other controls are updated there is some JavaScript functions that can be used to determine if the buttons should be enabled or disabled. Originally these functions were assigned to the qml buttons enabled properties, however it seems this isn't enough and a fiddle to make it work is to have a property defined in QML (integer) which is incremented and this is then used by the enabled property to flag an update, this works but seems very hacky and not the correct way to do it.
Item {
id: root
property int qmlChangeFiddle: 0
...
Button {
id: prevImageButton
anchors.left: parent.left
anchors.leftMargin: 24
anchors.top: capturedAreaRect.top
enabled: (root.qmlChangeFiddle > 0)
height: cameraButtonBar.height
width: cameraButtonBar.width * 0.125
icon: "leftarrow.svg"
visible: capturedFrame.visible && (imageCaptureModel.capturableSelected.length > 1)
....
}
}
Incrementing "root.qmllChangeFiddle" will cause it to change and the button enabled, before I was calling a JavaScript routine which returns true or false but this didn't work.
Original qml which doesn't work:
Item {
id: root
...
Button {
id: prevImageButton
anchors.left: parent.left
anchors.leftMargin: 24
anchors.top: capturedAreaRect.top
enabled: images.moreThanOne()
height: cameraButtonBar.height
width: cameraButtonBar.width * 0.125
icon: "leftarrow.svg"
visible: capturedFrame.visible && (imageCaptureModel.capturableSelected.length > 1)
....
}
}

Function return values are not automatically re-evaluated in QML until at least one of the function arguments is updated AND the argument should be a QML object property. I can recommend two approaches:
You should bind the prevImageButton to moreThanOne function while the moreThanOne accepts a property of its own object (here image) where that property is being changed when the images object is updated
Images{
id:images
....
property var updateCounter : 0
function whichEverFunctionThatUpdatesImages(){
...
updateCounter++;
}
function moreThanOne(uc){
// you may want to use it or not
.... return count>0;
}
}
Button{
enabled : images.moreThanOne(images.updateCounter)
}
Define a property inside Images which is update by moreThanOne and bind it instead
Images{
id:images
....
property var updateCounter : 0
property var twoOrMore : false
function whichEverFunctionThatUpdatesImages(){
...
twoOrMore = iCount > 1 ;
}
}
Button{
enabled : images.twoOrMore
}
I choose the latter option.
EDIT:
The following example works quite well on btn2 without using the above methods. But if I use anything other than QML properties like an attribute from a javascript object like what is happening on moreThanOne2() method, then it stops to work out of the box and those two approaches for re-evaluation will work.
Item {
id : item
anchors.fill: parent
property var aCounter: 0
property var someObject: ({ counter: 0})
Button{
anchors.top: parent.top
text: 'btn1'
onClicked: {
parent.aCounter++
parent.someObject.counter++
}
}
Button{
anchors.bottom: parent.bottom
anchors.left: parent.left
text: 'btn2'
enabled: item.moreThanOne1()
}
Button{
anchors.bottom: parent.bottom
anchors.right: parent.right
text: 'btn3'
enabled: item.moreThanOne2()
}
function moreThanOne1(){
return aCounter > 1;
}
function moreThanOne2(){
return someObject.counter > 1;
}
}

The actual solution which works is to use Qt.binding:
Item {
id: root
...
Button {
id: prevImageButton
anchors.left: parent.left
anchors.leftMargin: 24
anchors.top: capturedAreaRect.top
enabled: Qt.binding(images.moreThanOne())
height: cameraButtonBar.height
width: cameraButtonBar.width * 0.125
icon: "leftarrow.svg"
visible: capturedFrame.visible && (imageCaptureModel.capturableSelected.length > 1)
....
}
}

Related

How to rotate only the right or left part of an image

I want to simulate the closure of one page of a book.
Any suggestions on how to do that in qml?
Thanks in advance
Perhaps Flipable, in combination with the fillMode property of Image:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: window
width: 640
height: 480
visible: true
Image {
id: backgroundImage
source: "http://www.thebookdesigner.com/wp-content/uploads/2013/04/pages-vs-spreads.png"
anchors.centerIn: parent
Flipable {
id: flipable
anchors.fill: parent
anchors.leftMargin: parent.width / 2
property bool flipped: false
front: Image {
id: foldImage
source: backgroundImage.source
fillMode: Image.Pad
width: foldImage.implicitWidth / 2
horizontalAlignment: Image.AlignRight
}
back: Image {
source: backgroundImage.source
width: foldImage.implicitWidth / 2
fillMode: Image.Pad
horizontalAlignment: Image.AlignLeft
}
transform: Rotation {
id: rotation
origin.x: 0
origin.y: flipable.height / 2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges {
target: rotation;
angle: -180
}
when: flipable.flipped
}
transitions: Transition {
NumberAnimation {
target: rotation
property: "angle"
duration: 1000
easing.type: Easing.InCubic
}
}
MouseArea {
anchors.fill: parent
onClicked: flipable.flipped = !flipable.flipped
}
}
}
}
Flipable does what its name suggests, and the fillMode property in combination with a width that is too small for the entire image allows you to "reposition" the contents of the image within the bounds of the item.
So, the front item contains the right side of the image, and the back item contains the left side. With an actual book that has many pages, you'd have to use the relevant pages instead of the same one.

(How) can i access relative position of qml element to main window

I have a qml element and want to show a (own) tooltip element as a new window right above this element. for this i need the absolute screen position to place the new window (AFAIK).
i got as far that the regular approach is to use "mapToItem" to get the relative position, but i cannot get to the "main window" - because the element in question is located within a "Loader" (which in this case is again located in another Loader).
So my question is: Is it possible to access the mainWindow from inside the dynamically loaded component, or is there maybe another easier way to anchor a new (tooltip) window right above an element ?
EDIT
mapToGlobal would probably work too, but i have to use qt 5.6.
i finally got it to work by setting the main window as a context property in c++:
this->qmlEngine->rootContext()->setContextProperty("mainWindow", this->root);
and in qml i can then access the main window position (on screen) and add the relative position the item has to the shown window like that:
tooltipWindow.setX(mainWindow.x +item1.mapToItem(item2,0,0).x )
The Window item has contentItem especially for that
[read-only] contentItem : Item
The invisible root item of the scene.
So you can refer to Window.contentItem as if it was Window:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: mainWindow
visible: true
width: 600
height: 300
Component {
id: testElement
Rectangle {
id: rect
width: 100
height: 100
color: "orange"
border { width: 1; color: "#999" }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: tooltip.show(true);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mapToItem(mainWindow.contentItem,mouse.x, mouse.y));
}
}
}
Item {
x: 40
y: 50
Item {
x: 80
y: 60
Loader {
sourceComponent: testElement
}
}
}
Rectangle {
id: tooltip
visible: false
width: 100
height: 20
color: "lightgreen"
border { width: 1; color: "#999" }
Text {
anchors.centerIn: parent
text: "I'm here"
}
function show(isShow) {
tooltip.visible = isShow;
}
function setPosition(point) {
tooltip.x = point.x - tooltip.width / 2;
tooltip.y = point.y - tooltip.height;
}
}
}
As for me I would reparent tooltip Item to hovered item itself at MouseArea.onEntered and so you can avoid position recalculation etc.:
onEntered: tooltip.show(true, rect);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mouse.x, mouse.y);
...
function show(isShow, obj) {
obj = (typeof obj !== 'undefined' ? obj : null);
if(obj !== null) {
tooltip.parent = obj;
}
tooltip.visible = isShow;
}
function setPosition(x, y) {
tooltip.x = x - tooltip.width / 2;
tooltip.y = y - tooltip.height;
}

Change calendar style on button click

I need to change the Calendar style when clicking a Button. Currently, in the code below, the style change only works when the object is created for the first time but I need to do style change manually whenever the Button is clicked.
Below is the QML code:
import QtQuick 2.0
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0
import QtQuick.Controls.Styles 1.1
ApplicationWindow {
visible: true
width: 640
height: 400
minimumWidth: 400
minimumHeight: 300
color: "#f4f4f4"
id: root
Calendar {
id: cal_panel
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter;
frameVisible:false
style: CalendarStyle {
gridVisible: false
dayDelegate: Rectangle {
color: styleData.selected ? "#FF2E7BD2" : (styleData.visibleMonth && styleData.valid ? "#191919" : "#191919");
Text {
id:day_txt
text: styleData.date.getDate()
font.bold: true
anchors.centerIn: parent
color: {
var color = "#dddddd";
if (styleData.valid) {
color = styleData.visibleMonth ? "#bbb" : "#444";
var sel = root.getHiglightDates();
for(var i=0;i<sel.length;i++){
if(sel[i]===Qt.formatDateTime(styleData.date,"dd:MM:yyyy"))
color="red"
}
if (styleData.selected) {
color = "black";
}
}
color;
}
}
}
}
}
Button{
anchors.top:cal_panel.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
text:"Higlight"
onClicked: {
console.log("Higlight here....")
}
}
function getHighlightDates(){
var sel = ["10:11:2015","12:11:2015","11:11:2015","08:11:2015","09:11:2015"];
return sel;
}
}
Edit:
The return value of the function getHighlightDates() changes each time. In the snippet above I've just returned a predefined array for testing. In that case I am conduced how to edit style element which is already created.
Here is the screen shot:
As a simple solution, you can reassign the style on click event, forcing an under the hood refresh of the Calendar item.
To do that you can use
cal_panel.style=cal_panel.style
Be aware that this solution is not exactly performance friendly. :-)
Based on the comments in the question and in #folibis's answer, it looks the question might just revolve around how to get the calendar style to reflect the updated list of selected dates (from getHiglightDates()) after a user has updated the list by clicking a button.
What about just adding a new property selectedDates to store the selected dates (previously held in getHighlightDates()) like in the code below. By making use of property binding, the appearance of selected dates will automatically be updated whenever selectedDates changes. In the code below, the color of the "day_txt" Text is updated when selectedData is updated (which in turn is updated when selectedDates is updated).
import QtQuick 2.0
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.1
ApplicationWindow {
visible: true
width: 640
height: 400
minimumWidth: 400
minimumHeight: 300
color: "#f4f4f4"
id: root
property variant selectedDates : ["10:11:2015","12:11:2015","11:11:2015","08:11:2015","09:11:2015"]
Calendar {
id: cal_panel
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter;
frameVisible:false
style: CalendarStyle {
gridVisible: false
dayDelegate: Rectangle {
property bool selectedDate: selectedDates.indexOf(Qt.formatDateTime(styleData.date,"dd:MM:yyyy")) > -1
color: styleData.selected ? "#FF2E7BD2" : (styleData.visibleMonth && styleData.valid ? "#191919" : "#191919");
Text {
id:day_txt
text: styleData.date.getDate()
font.bold: true
anchors.centerIn: parent
color: selectedDate ? "red" : (styleData.selected ? "black" : (styleData.visibleMonth ? "#bbb" : "#444"));
}
}
}
}
Button{
anchors.top:cal_panel.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
text:"Higlight"
onClicked: {
var updatedDates = selectedDates
updatedDates.push(Qt.formatDateTime(cal_panel.selectedDate,"dd:MM:yyyy"))
selectedDates = updatedDates
# See http://stackoverflow.com/questions/19583234/qml-binding-to-an-array-element for why its done this way...
}
}
}
as #skypjack already suggested, you just can assign a new style on click. The style property is a Component so there is no problem to do something like this:
Component {
id: style1
CalendarStyle {
background: Rectangle { color: "lightyellow" }
}
}
Component {
id: style2
CalendarStyle {
background: Rectangle { color: "orange" }
}
}
Calendar {
id: calendar
anchors.fill: parent
style: style1
onClicked: {
calendar.style = style2;
}
}

QML Dialog is broken?

I have this code:
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
standardButtons: StandardButton.Ok | StandardButton.Cancel
width: layout.implicitWidth
height: layout.implicitHeight
RowLayout {
id: layout
anchors.fill: parent
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Item {
width: 10
height: 1
}
}
}
When you run it it looks like this, and the dialog can be resized to any size. Also the RowLayout actually doesn't fill its parent as you can see.
How can I make it so that the dialog can't be resized below the minimum size of the layout, and so that the layout fills the dialog?
Unfortunately this is a bug in Qt. Currently the documentation is misleading and Dialog does not size itself correctly to the contents. Consider this working example, which I based on the DefaultFontDialog:
AbstractDialog {
title: "Hello"
id: root
// standardButtons: StandardButton.Ok | StandardButton.Cancel
modality: Qt.NonModal
Rectangle {
id: content
implicitWidth: mainLayout.implicitWidth + outerSpacing * 2
implicitHeight: mainLayout.implicitHeight + outerSpacing * 2
property real spacing: 6
property real outerSpacing: 12
color: "white"
GridLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
rowSpacing: content.spacing
columnSpacing: content.spacing
columns: 5
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" }
}
}
}
This works exactly as expected, though of course you don't get the buttons.
If you just change it to a Dialog and uncomment the standardButtons, then it stops working - you can resize the dialog to clip its contents (width-wise at least), and the contents do not expand to the dialog size.
The reason for the minimum width not working becomes clear when we look at the source code for Dialog (in qtquickcontrols/src/dialogs/DefaultDialogWrapper.qml):
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: Screen.pixelDensity * 50
implicitHeight: defaultContentItem.implicitHeight + spacing + outerSpacing * 2 + buttonsRight.implicitHeight
implicitWidth: Math.min(root.__maximumDimension, Math.max(
defaultContentItem.implicitWidth, buttonsRowImplicitWidth, Screen.pixelDensity * 50) + outerSpacing * 2);
minimumWidth is hardcoded to Screen.pixelDensity * 50!! There was never any hope that it would match the dialog contents. minimumHeight does work better (though not perfect, I believe because the spacing isn't considered).
I'm not sure why the defaultContentItem does not expand correctly, but anyway. It looks like the only solution at the moment is to use AbstractDialog and implement the buttons and accepted()/rejected()/etc. signals yourself. Bit of a pain.
Edit / Solution
I did some further investigation.
The reason the defaultContentItem doesn't expand is because it's bottom anchor isn't tied to the top of the button row:
Item {
id: defaultContentItem
anchors {
left: parent.left
right: parent.right
top: parent.top
margins: content.outerSpacing
}
implicitHeight: childrenRect.height
}
Minimum sizes just don't work that well with anchor-based layouts. They do with GridLayout-based layouts.
Unfortunately childrenRect has no implicitWidth/Height so we have to actually have the child items go into a ColumnLayout rather than be the ColumnLayout.
...
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
// A Dialog that resizes properly. The defualt dialog doesn't work very well for this purpose.
AbstractDialog {
id: root
default property alias data: defaultContentItem.data
onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus()
Rectangle {
id: content
property real spacing: 6
property real outerSpacing: 12
property real buttonsRowImplicitWidth: minimumWidth
property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth
property real minimumHeight: implicitHeight
property real minimumWidth: implicitWidth // Don't hard-code this.
implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitWidth + outerSpacing * 2))
implicitHeight: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitHeight + outerSpacing * 2))
color: palette.window
Keys.onPressed: {
event.accepted = true
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
reject()
break
case Qt.Key_Enter:
case Qt.Key_Return:
accept()
break
default:
event.accepted = false
}
}
SystemPalette { id: palette }
// We use layouts rather than anchors because there are no minimum widths/heights
// with the anchor system.
ColumnLayout {
id: mainLayout
anchors { fill: parent; margins: content.outerSpacing }
spacing: content.spacing
// We have to embed another item so that children don't go after the buttons.
ColumnLayout {
id: defaultContentItem
Layout.fillWidth: true
Layout.fillHeight: true
}
Flow {
Layout.fillWidth: true
id: buttonsLeft
spacing: content.spacing
Repeater {
id: buttonsLeftRepeater
Button {
text: (buttonsLeftRepeater.model && buttonsLeftRepeater.model[index] ? buttonsLeftRepeater.model[index].text : index)
onClicked: root.click(buttonsLeftRepeater.model[index].standardButton)
}
}
Button {
id: moreButton
text: qsTr("Show Details...")
visible: false
}
}
Flow {
Layout.fillWidth: true
id: buttonsRight
spacing: content.spacing
layoutDirection: Qt.RightToLeft
Repeater {
id: buttonsRightRepeater
// TODO maybe: insert gaps if the button requires it (destructive buttons only)
Button {
text: (buttonsRightRepeater.model && buttonsRightRepeater.model[index] ? buttonsRightRepeater.model[index].text : index)
onClicked: root.click(buttonsRightRepeater.model[index].standardButton)
}
}
}
}
}
function setupButtons() {
buttonsLeftRepeater.model = root.__standardButtonsLeftModel()
buttonsRightRepeater.model = root.__standardButtonsRightModel()
if (!buttonsRightRepeater.model || buttonsRightRepeater.model.length < 2)
return;
var calcWidth = 0;
function calculateForButton(i, b) {
var buttonWidth = b.implicitWidth;
if (buttonWidth > 0) {
if (i > 0)
buttonWidth += content.spacing
calcWidth += buttonWidth
}
}
for (var i = 0; i < buttonsRight.visibleChildren.length; ++i)
calculateForButton(i, buttonsRight.visibleChildren[i])
content.minimumWidth = calcWidth + content.outerSpacing * 2
for (i = 0; i < buttonsLeft.visibleChildren.length; ++i)
calculateForButton(i, buttonsLeft.visibleChildren[i])
content.buttonsRowImplicitWidth = calcWidth + content.spacing
}
onStandardButtonsChanged: setupButtons()
Component.onCompleted: setupButtons()
}
You have to use it a bit differently to a normal Dialog. Just imagine it is a ColumnLayout (this is a slightly different example to the original question):
ColumnLayoutDialog {
id: dialog1
standardButtons: StandardButton.Ok | StandardButton.Cancel
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
// Spacer.
Item {
Layout.fillHeight: true;
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
By the way you could change the ColumnLayout to a GridLayout and expose the columns property if you want. That might make more sense.
A small issue
It turns out a QWindow's minimum width and height only ensure that the dialog isn't actively resized to be less than its content. It doesn't ensure that the dialog is never smaller than its content, because the content can grow after the dialog is created (e.g. extra items added). To workaround this I added this function to my ColumnLayoutDialog:
// The minimumWidth/Height values of content are accessed by the C++ class, but they
// only ensure that the window isn't resized to be smaller than its content. They
// don't ensure that if the content grows the window grows with it.
function ensureMinimumSize()
{
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
It has to be called manually when you change the dialog contents. Or to do it automatically you can add this to the content rectangle:
onMinimumHeightChanged: {
if (root.height < content.minimumHeight)
root.height = content.minimumHeight;
}
onMinimumWidthChanged: {
if (root.width < content.minimumWidth)
root.width = content.minimumWidth;
}
This is a bug in QT up to version 5.6.0. Most likely the bug number 49058. The code from the question works as expected in QT 5.6.1 and 5.7.0.
A partial workaround for the old versions is to remove the lines
width: layout.implicitWidth
height: layout.implicitHeight
and replace
anchors.fill: parent
with
anchors.right: parent.right
anchors.left: parent.left
The dialog then respects the minimum height and the contents expand horizontally.
Here is also a complete workaround, but it relies on undocumented implementation details of Dialog, so it should be used with caution. It works in 5.5.1, 5.6.0, 5.6.1 and 5.7.0. Note also that the second Item is changed to a red Rectangle to make the behavior more apparent.
import QtQuick 2.3
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
Dialog {
visible: true
standardButtons: StandardButton.Ok | StandardButton.Cancel
RowLayout {
id: layout
// In the horizontal direction, expansion and shrinking can be achieved with anchors.
anchors.left: parent.left
anchors.right: parent.right
// Used only for guessing the height of the Dialog's standard buttons.
Button {
id: hiddenButton
visible: false
}
// Repeats until the relevant parts of the dialog (parent of the parent of the RowLayout)
// are complete, then overwrites the minimum width and implicit height and stops repeating.
Timer {
id: timer
interval: 50; running: true; repeat: true;
onTriggered: {
if(layout.parent.parent) {
var lp = layout.parent
var lpp = layout.parent.parent
lpp.minimumWidth = layout.implicitWidth + 2 * lpp.outerSpacing
layout.buttonHeight = 2 * lpp.outerSpacing + hiddenButton.implicitHeight + lpp.spacing
lp.implicitHeight = layout.implicitHeight + 2 * lpp.outerSpacing
running = false
}
}
}
// The guessed space needed for the Dialog's buttons.
property int buttonHeight: 80
// Expand and shrink vertically when the dialog is resized.
height: parent.parent ? Math.max(parent.parent.height-buttonHeight, implicitHeight) : implicitHeight
Item {
width: 10
height: 1
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.fillHeight: true
Layout.fillWidth: true
Text {
text: "Hello world? "
}
Text {
text: "Hello world!"
}
Text {
text: "Goodbye world? "
}
Text {
text: "Goodbye world!"
}
}
Rectangle {
Layout.fillHeight: true
color: 'red'
width: 10
}
}
}

How to get position (X,Y) of any visual node after page rendered?

I need to create a draggable button. The drag starts from multiple source and ends at a sink. Is there any way to get position of the sources and the sink after creation of the page.
Container {
horizontalAlignment: HorizontalAlignment.Fill
Label {
id: preId
preferredWidth: 700
text: rootContainer.data.part1
multiline: true
textStyle {
textAlign: TextAlign.Center
color: Color.Black
}
horizontalAlignment: HorizontalAlignment.Center
}
}
Container {
horizontalAlignment: HorizontalAlignment.Center
SinkButton {
id: sinkId
preferredHeight: 100
preferredWidth: 686
textColor: Color.Black
enabled: false
layoutProperties: AbsoluteLayoutProperties {
}
}
}
Container {
horizontalAlignment: HorizontalAlignment.Fill
topPadding: -5
bottomPadding: squareDisplay ? 10 : 50
Label {
id: postId
text: rootContainer.data.postData
multiline: true
textStyle {
textAlign: TextAlign.Center
color: Color.Black
}
horizontalAlignment: HorizontalAlignment.Center
}
}
}
I want to know the position of "sinkId" relative to the window. Its position varies depending on "preId" text length. LayoutUpdateHandler is a possible solution but layoutFrame.x or layoutFrame.y always comes zero on checking. Is there any way to get position of any visual node on/after page creation?
First, LayoutUpdateHandler reports layoutFrame properties quite well, not sure why it's not working in your case. Here is the working code demonstrating that if you want to utilise this class for your needs:
import bb.cascades 1.0
Page {
Container {
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
Button {
id: button
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
text: "click me"
onClicked: {
console.log("Button layout - x:" + layoutHandler.layoutFrame.x + ", y:" + layoutHandler.layoutFrame.y +
", width:"+ layoutHandler.layoutFrame.width + ", height: " + layoutHandler.layoutFrame.height);
}
attachedObjects: [
LayoutUpdateHandler {
id: layoutHandler
}
]
}
}
}
Also, if you want to respond to touch events of your widget which I guess draggable functionality means, the best bet here would be to use a series of touch-input reactions provided by touchBehaviors which could added to any VisualNode as via QML as via C++, doesn't matter. So in order to do this, you need to create a TouchBehavior object with a defined TouchReaction with respective properties.
So, for example, for tracking touch events of some VisualNode upon receiving a touch down event until a touch up one occurs you need to define a respective touchBehaviour object and connect to the onTouch() slot of that VisualNode where you get all the events captured by your touchBehaviour event filter. Here is the code demonstrating this approach:
import bb.cascades 1.0
Page {
Container {
layout: DockLayout {}
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
Button {
id: button
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
text: "click me"
touchBehaviors: [
TouchBehavior {
TouchReaction {
eventType: TouchType.Down
phase: PropagationPhase.AtTarget
response: TouchResponse.StartTracking
}
}
]
onTouch: {
console.log("===== got touch event of type " + event.touchType + " =====");
console.log("\twindowX - " + event.windowX);
console.log("\twindowX - " + event.windowX);
console.log("\twindowY - " + event.windowY);
console.log("\tlocalX - " + event.localX);
console.log("\tlocalY - " + event.localY);
console.log("\n");
}
}
}
}
Then you can add you logic in onTouch() slot handler.
Additional information on the subject:
TouchBehavior official documentation
TouchEvent official documentation
VisualNode touch event function official documentation
LayoutUpdateHandler will tell you the position of your element in relation to the parent, which in the case of SinkId is a Container wrapping it. LayoutUpdateHandler will always return you 0 in this case.
Either remove the container and set the horizontalAlignment: HorizontalAlignment.Center property on sinkId directly and use a LayoutUpdateHandler, or use a LayoutUpdateHandler on the container surrounding sinkId.