i'm trying to get two coordinates from QML component Map. I tried to use standard Component.onCompleted. I am trying to get the coordinate that is at the top left and the coordinate that is at the bottom right.
I'm using Map function toCoordinate with Qt.Point() as a parameter.
Problem occurs when function is called, because output of this function is empty.
I am using Qt 5.12.3.
Output:
qml:
qml:
My code
import QtQuick 2.12
import QtQuick.Window 2.12
import QtLocation 5.9
import QtPositioning 5.3
Window {
visible: true
width: 640
height: 480
Map {
id: map
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: parent.height + 20
plugin: mapPlugin
gesture.acceptedGestures: {
MapGestureArea.PanGesture |
MapGestureArea.PinchGesture
}
Component.onCompleted: {
console.log(map.toCoordinate(Qt.point(0,0)))
console.log(map.toCoordinate(Qt.point(map.width, map.height)))
}
}
Plugin {
id: mapPlugin
name: "osm"
PluginParameter {
name: "osm.mapping.cache.directory"
value: "./cache/"
}
}
}
Is there any solution for this problem? Did anyone have similiar problem?
Thank you for your help.
//Edit:
When I use map.toCoordinate(Qt.point(0,0)) in Map.onCenterChanged and then move with Map, it returns valid coordinate.
As the docs points out:
mapReady : bool
This property holds whether the map has been successfully initialized
and is ready to be used. Some methods, such as fromCoordinate and
toCoordinate, will not work before the map is ready. Due to the
architecture of the Map, it's advised to use the signal emitted for
this property in place of Component.onCompleted, to make sure that
everything behaves as expected.
(The emphasis is mine)
Component.onCompleted does not guarantee that the map is rendered or has the information provided by the API, instead you must use the mapReady property:
Map {
id: map
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: parent.height + 20
plugin: mapPlugin
gesture.acceptedGestures: {
MapGestureArea.PanGesture |
MapGestureArea.PinchGesture
}
onMapReadyChanged: {
if(mapReady){
console.log(map.toCoordinate(Qt.point(0,0)))
console.log(map.toCoordinate(Qt.point(map.width, map.height)))
}
}
}
Output:
qml: 51° 32' 29.3" N, 1° 53' 7.8" W, 0m
qml: 51° 28' 23.1" N, 1° 37' 48.4" E, 0m
Related
working on an application using qml for the interface. I have a stack view in the main qml file which will push the qml file which will be a page to the stack view. Because there are multiple pages and buttons in these pages that when clicked may push a different page onto the stack view, I created a function in the main qml file which i will call from the pages when buttons are clicke. The pages folder which contain many different pages is a subdirectory of main.qml folder. The functionis supposed to push the pages unto the stack view. However, on click of the button, the satck view does not push the new page.
This is the code below
main qml file
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import "pages"
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property url source: ""
function changeView(source){
stackView.push(Qt.resolvedUrl(source))
}
Rectangle {
id: rectangle
color: "#391199"
anchors.fill: parent
StackView {
id: stackView
anchors.fill: parent
initialItem: Qt.resolvedUrl("pages/homepage.qml")
}
}
}
/*##^##
Designer {
D{i:0;formeditorZoom:0.75}
}
##^##*/
homePage.qml
import QtQuick 2.0
import QtQuick.Controls 2.15
import "qrc:../main.qml" as Main
Item {
Rectangle {
id: rectangle
color: "#08630f"
anchors.fill: parent
Button {
id: button
x: 478
y: 255
text: qsTr("Change ")
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 10
anchors.bottomMargin: 10
onClicked: {
source = "pages/nextPage.qml"
Main.changeView(source)
}
}
}
}
nextPage.qml
import QtQuick 2.0
import QtQuick.Controls 2.15
import "../main.qml" as Main
Item {
Rectangle {
id: rectangle
color: "#08404b"
anchors.fill: parent
Button {
id: button
x: 478
y: 255
text: qsTr("Change ")
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: 10
anchors.bottomMargin: 10
onClicked: {
Main.changeView("pages/homePage.qml")
}
}
}
}
I suspect the problem is coming from the calling of the function but i have a little expertise in qml. I get this error: Property 'changeView' of object [object Object] is not a function
You will want to call the changeView function on the actual main window, not on the import.
This involves a bit of "magic", you can give the main Window an id, which will be available in all the children of it (so, magic because if looking at a single page file, you can wonder what that id is doing there). So, make sure to pick a wise name which won't collide with other id (i.e. root would be quite bad)
//main.qml
Window {
id: main_window
function changeView(source) { ... }
}
//homePage.qml
Item {
...
Button {
...
onClicked: main_window.changeView("pages/nextPage.qml")
}
}
I try to save index of ComboBox for choosing supported map types of Open Street Map. When opening the app again, the last chosen map index should be displayed. Qt.labs.settings didn't work as the example below:
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtLocation 5.12
import QtPositioning 5.12
import Qt.labs.settings 1.0
ApplicationWindow{
id: root
width: 500
height: 500
visible: true
Settings{
id:mycombo
property alias maptype: selectmap.currentIndex
}
Flickable {
height: parent.height
width: parent.width
clip: true
contentHeight: Math.max(mapColumn.implicitHeight, height)
Column{
anchors.horizontalCenter: parent.horizontalCenter
id:mapColumn
spacing: 5
anchors.fill : parent
Row{
anchors.horizontalCenter: parent.horizontalCenter
spacing:25
Rectangle{
width:mapColumn.width
height:mapColumn.height-80
Map {
id:map
anchors.fill: parent
plugin: Plugin {
name: "osm"
}
}
}
}
Column{
id: combos
spacing: 10
width: parent.width
anchors.verticalCenter: root.verticalCenter
Row{
anchors.horizontalCenter: parent.horizontalCenter
spacing:1
Label{ text:"Map Type: "; height: selectmap.height; verticalAlignment: Text.AlignVCenter; }
// Map Types
ComboBox {
id: selectmap
width: 200
model:map.supportedMapTypes
textRole:"description"
onCurrentIndexChanged: map.activeMapType = map.supportedMapTypes[currentIndex]
}
}
}
}
}
}
Is it possible to save the current index of ComboBox for maps?
By saying "opening the app again" it is possible that the app has been killed, and in that case all values are lost. You have to store the index value in the file system, like config data, which is read when application loads. I guess there will be several of these data, including last typed places and history(for which cache is better). However for config data you may use JSON format.
Make a JSON file where you store all data, and make a controller like GuiConfigurationController which parses the file with QJsonValue. You may also consider to make a GuiConfiguration Singletonn class which will have all the values loaded as getter method. To write to the JSON file from QML use the Q_PROPERTY WRITE method which calls the setter method for the variable.
Q_PROPERTY(quint32 savedIndex READ savedIndex WRITE setSavedIndex NOTIFY savedIndexChanged)
quint32 savedIndex() const {return m_savedIndex;}
void setSavedIndex(quint32 index) {m_savedIndex = index;}
QML:
_someController.savedIndex = currentIndex
make sure _someController is set as rootContext -> ContextProperty to QQuickView
so when index changes the value is set in someController which saved the value to JSON. When app is loading read JSON and get the value of index. Update QtQuick using the onLoaded method
onLoaded: {currentindex = _someController.savedIndex}
Could anyone point me in the right direction on how to best organise my QML?
Currently for common single components I make a new QML file and add it to a Common directory under my resources.
E.g. My Label.qml
Text{
width: parent.width * 0.5
height: parent.height * 0.1
color: "#ffffff"
font.underline: true
font.pointSize: 16
verticalAlignment: Text.AlignBottom
horizontalAlignment: Text.AlignLeft
}
And then in my Form.qml I can import and use it like this:
import "Common"
Page {
Label{
id: username_lbl
text: "Username"
anchors.topMargin: parent.height * 0.1
}
...
}
But how would I do the above if I want to group together multiple components and reference them for use with connections?
For example I'd like a pair of buttons that sit at the bottom of a page (below is just an example and doesn't work):
So I'd like to have a ButtonPair.qml which would like a bit like this:
Button {
id: left_btn
width: parent.width * 0.5
height: parent.height * 0.1
anchors.bottom: parent.bottom
anchors.right: parent.right
}
Button {
id: right_btn
width: parent.width * 0.5
height: parent.height * 0.1
anchors.bottom: parent.bottom
anchors.left: parent.left
}
And then in my Form.qml I'd like to use these buttons and add an event handler to each:
import "Common"
Page {
ButtonPair{id: back_forward_buttons}
Connections {
target: back_forward_buttons.left_btn
onClicked: {
stackView.pop();
}
Connections {
target: back_forward_buttons.right_btn
onClicked: {
stackView.push("AnotherPage.qml");
}
}
Do I need to wrap my ButtonPair in a Component and use a Loader on my page and if so how do I then get to the individual left/right buttons in order to bind to onClicked?
When a component is designed, it is considered a black box that has properties and signals that must be viewed from the outside.
For example in your case ButtonPair must expose 2 signals: one when the left button is pressed and another when the right button is pressed, another thing that I have added are 2 properties to be able to establish the name of the buttons.
I see that you have set the height of the buttons as 10% of the height of the father and should be in the lower part and if you want to use that same component on the top? I would have to create another topButtonPair component, and if I want them to be on the right, etc. For the size should be established when the component is created not in the implementation. In this case each button must occupy half of the parent item.
Using the above we obtain the following:
ButtonPair.qml
import QtQuick 2.0
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
Item{
// expose properties and signals
property string leftname: ""
property string rightname: ""
signal leftClicked()
signal rightClicked()
// internals
RowLayout{
anchors.fill: parent
spacing: 0
Button {
text: leftname
onClicked: leftClicked()
Layout.fillWidth: true
Layout.fillHeight: true
}
Button {
text: rightname
onClicked: rightClicked()
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
Note: The use of the layout is optional, you could use the anchors.
Now used on the page:
Page {
// other components
ButtonPair{
anchors.bottom: parent.bottom
height: 0.1*parent.height // <--- Here the height is established
anchors.left: parent.left
anchors.right: parent.right
leftname: "left text"
rightname: "right text"
onLeftClicked: console.log("left clicked")
onRightClicked: console.log("right clicked")
}
}
Generally, the black-box appraoch taken by #eyllanesc is the better one and should be preferred whenever possible. However, if you really need to access child items from the outside, you can:
ButtonPair.qml:
Item {
property alias leftButton: left_btn
property alias rightButton: right_btn
// … declarations of left_btn and right_btn as in your question
}
Usage:
ButtonPair {
leftButton {
onClicked: {
stackView.pop();
}
}
rightButton {
onClicked: {
stackView.push("AnotherPage.qml");
}
}
}
You can also use it in Connections. However, in 95% of cases you should forward properties and signals as in #eyllanesc’s approach, which leads to a much cleaner and readable interface.
I've created a QML UI that has a dial and a custom control. The custom control is basically a progress bar with a MouseArea to allow the user to set the value by clicking it. As Qt's property binding docs point out, as soon as I assign to the custom control's value from Javascript in the MouseArea click handler, I lose the declarative binding between it and the dial.
Is it possible to make this binding bidirectional, or even better, to link the values of both controls to a single value above both of them in the QML hierarchy? And is it possible to do this with declarative syntax so I don't have complex event handler code in every control?
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0
Window {
id: window
visible: true
width: 800
height: 200
readonly property int range: 10
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Dial {
id: dial1
live: true
from: 0
to: window.range
stepSize: 1
snapMode: Dial.SnapAlways
}
Control {
id: dut
implicitWidth: 200
implicitHeight: 50
property int range: window.range
property int value: dial1.value
onValueChanged: {
console.log("New value: " + value);
}
Rectangle {
width: parent.width
height: parent.height
color: Qt.rgba(0,0,0,0)
border.color: Qt.rgba(0,0,0,1)
border.width: 1
}
Rectangle {
width: parent.width * dut.value/dut.range
height: parent.height
color: Qt.rgba(0,0,0,1)
}
MouseArea {
anchors.fill: parent
onClicked: {
dut.value = Math.round(mouseX/width * dut.range);
}
}
}
}
}
Note that if I reverse the relationship ie. have dial1.value: dut.value, then the binding isn't broken (although it's not quite bidirectional).
I realise that this example basically reinvents the scrollbar, but I'm trying to work my way up to more complex controls, for which declarative relationships between values would make life much easier.
Elaboration from a comment: What I don't understand, but want to, is how it's done for other QML components. For example, with a Dial I can set its value property to be bound to some other component's property, and clicking on the dial doesn't remove that binding. I don't have to hook into its mouse events to do that. Despite looking through the source for how this is done, I'm not really any closer to understanding it.
There are other questions about bidirectional property bindings in QML, but I haven't been able to apply them to my problem because (a) I really, really want something declarative, and (b) the MouseArea properties and events don't seem to work well with Binding objects (as in, I can't figure out how to integrate the two things).
I would have done this:
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Shapes 1.0
Window {
id: window
visible: true
width: 800
height: 200
readonly property int range: 10
property int commonValue
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Dial {
id: dial1
live: true
from: 0
to: window.range
stepSize: 1
snapMode: Dial.SnapAlways
onValueChanged: {
commonValue = dial1.value
console.log("New value: " + value);
}
}
Rectangle {
width: 200
height: 50
color: Qt.rgba(0,0,0,0)
border.color: Qt.rgba(0,0,0,1)
border.width: 1
MouseArea {
anchors.fill: parent
onClicked: {
commonValue = Math.round(mouseX/width * window.range)
dial1.value = commonValue
}
}
Rectangle {
width: parent.width * window.commonValue/window.range
height: parent.height
color: Qt.rgba(0,0,0,1)
}
}
}
}
Use a Binding QML Type:
MouseArea {
id: mouseArea
anchors.fill: dut
}
Binding {
target: dut
property: 'value'
value: Math.round(mouseArea.mouseX/mouseArea.width * dut.range);
when: mouseArea.pressed && mouseArea.containsMouse
}
Note that the when property on the Binding means it's only active as a binding when those conditions are fulfilled ie. the mouse is over the area and one of the "accepted buttons" is pressed.
This does not mean that the value reverts when the conditions aren't met, just that the value stops updating when they're not met. However, if you have another binding active somewhere else, that one may cause the the value to "snap back" because it will "take over" when this Binding ceases to apply.
Depending on the other components you use, this might not even be enough, and you might need to implement your properties in C++ to get them to work as you expect.
I am trying to communicate between 2 QML pages.
In my page Main.qml I receive a signal from my C++ code. On receiving this signal I want text on InputPage.qml to change. This page is shown within Main.qml using a Loader. The only way I could find so far is to set up another signal between the 2 pages. However, I think there is a much easier way to do this. I already tried this way but I could not get it to work. So before I proceed I would like to know if this is the right method or not.
Any ideas on how to do this, and if the method described above is the correct one?
My code:
Main.qml
Item {
id: screen_InputPage
width: 1920
height: 930
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 100
visible: false
opacity: 1
Loader {//Loads the pages
id: pageLoader_ID2
source: "inputPage.qml"
}
}
And i would like to access the text(and maybe functions) placed on inputPage.qml
Text {
id: text_volume_perc_ID1
height: 48
text: qsTr("50")
anchors.right: parent.right
anchors.rightMargin: 0
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.top: parent.top
anchors.topMargin: 126
anchors.left: parent.left
anchors.leftMargin: 0
font.pixelSize: 42
}
To access the created object, you can use
idLoader.item.idInputpage.idText.text
I propose that you load objects dynamically to improve performance. To do so you can create your own CustomLoader.qml:
CustomLoader.qml
import QtQuick 2.0
Item {
id: idRoot
width: childrenRect.width
height: childrenRect.height
property Item createdObject
property string source
function fnSourceChange() {
if (""!== source){
var component
// create component
component = Qt.createComponent(source)
if (Component.Ready === component.status) {
createdObject= component.createObject(idRoot)
if(!createdObject)
console.log("Loader::Could not create the object ")
}
else {
console.log("Loader::Could not create panel", component.errorString(), "component has errors")
}
}
else {
createdObject.destroy();
createdObject = null
// unComment this line if you want to force the garbage collector
//gc()
}
}
onSourceChanged: {
fnSourceChange()
}
// even without that it should detect the source change and create it
// you can unComment this line if you want, but like that it will parse the function
// two times one on the sourceChanged signal and on on in this handler
// print the source or somthing in the function and you'll see
// Component.onCompleted: fnSourceChange()
}
main.qml
import QtQuick 2.0
Item {
id: screen_InputPage
width: 1920
height: 930
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 100
visible: false
opacity: 1
CustomLoader{
id: pageLoader_ID2
source: "inputPage.qml"
}
}
InputPage.qml
import QtQuick 2.0
Item {
width: 800
height: 480
property alias text: idText.text
property alias label: idText
property alias rect: idRect
Text{
id: idText
}
Rectangle{
id: idRect
width: 100
height: 200
}
}
In your main add :
//or another scope like click button
Component.onCompleted: {
pageLoader_ID2.createdObject.text = "hello"
pageLoader_ID2.createdObject.rect.color = "red"
}