I need to do some 3D transform matrix computation and I thought to re-use Qt3D Transform:
import QtQuick 2.0
import Qt3D.Core 2.12 as Qt3D
Item {
id: root
Qt3D.Transform {
id: transform1
}
function eulerToQuaternion(x, y, z) {
transform1.rotationX = x
transform1.rotationY = y
transform1.rotationZ = z
return transform1.rotation
}
function quaternionToEuler(q) {
transform1.rotation = q
return Qt.vector3d(transform1.rotationX, transform1.rotationY, transform1.rotationZ)
}
...
however it does not work. When I use eulerToQuaternion or quaternionToEuler I get:
ReferenceError: transform1 is not defined
Accessing objects by their id works for other objects, why not for this one?
Related
The json document is like this. I want to define a class for it in qml, just like the interface keyword in typescript does.
{
"ScannerID": "ID",
"Status": 1,
"SuccessRate": 0.999,
"Result": [{
"Codes": "result_11111",
"Positions": {
"CenterX": 10.0,
"CenterY": 10.0,
"Width": 100.0,
"Height": 100.0,
"Angle": 50
},
"Types": "QrCode"
},
//more items
]
}
I tried to define a qml class in file ScannerResult.qml
import QtQuick 2.4
QtObject {
property string pScannerID
property int pStatus
property real pSuccessRate
// how to define Result with strong type?
property ? pResult
function load(obj) {
pScannerID = obj.ScannerID
//......
}
}
then use it
...
ScannerResult {
id: scannerResult
}
function log(jsonstr) {
let obj = JSON.parse(jsonstr);
scannerResult.load(obj)
console.log(scannerResult.pScannerID) // it works
}
...
But it's hard to handle the vector of objects under "Result" key. Because qml doesn't allow to define class B under class A.
So does anyone have a good idea about how to define a strong-typed class to hold the object parsed from json in qml? Then I can use that class with auto-completion in qtcreator.
You can create a property var and assign it as a list/array/vector to hold pResult. To populate this list, you can create a "class B" Component, and dynamically create instances of this class them when parsing your JSON object.
Using your code structure, here is an updated version of ScannerResult.qml:
import QtQuick 2.4
QtObject {
property string pScannerID
property int pStatus
property real pSuccessRate
property var pResult: []
function load(obj) {
pScannerID = obj.ScannerID
//... etc
for (var element of obj.Result) {
let resultQtObject = resultComponent.createObject(this, {})
resultQtObject.pCodes = element.Codes
resultQtObject.pTypes = element.Types
resultQtObject.pPositions.pCenterX = element.Positions.CenterX
//... etc
pResult.push(resultQtObject)
}
}
property Component resultComponent: Component {
QtObject {
property string pCodes
property string pTypes
property QtObject pPositions: QtObject {
property real pCenterX
property real pCenterY
property real pWidth
property real pHeight
property int pAngle
}
}
}
}
Your updated log function works as follows:
function log(jsonstr) {
let obj = JSON.parse(jsonstr);
scannerResult.load(obj)
console.log(scannerResult.pScannerID) // it works
console.log(scannerResult.pResult[0].pPositions.pCenterX) // it works
}
Because you are dynamically creating instances of this Component at runtime, QtCreator cannot make auto-completion suggestions for it. As you mentioned your vector is unknown length, I believe that runtime instantiation is your only option, and thus there is no solution that can accommodate auto-completion.
One other thing to note is that if you are re-running log()/load() many times you will need to clean the pResult list and destroy() the dynamically created objects.
I am trying to get a class to have a property bound to another class's list property, where the 1st property is derived from a summarizing calculation over the objects in the list. The code below is a simplified version of my production code. (The production code is doing a summary over DateTime objects -- the essential part of the code below is the binding between a list and an object property (here, it is a String for simplicity).)
I have tried various things. One approach was using addListener on the list in the Summary class below but I was running into weird bugs with the listener callback making updates on the Summary object. After doing a bunch of reading I think that a binding between the summary string and the list is more appropriate but I don't know exactly how to hook up the binding to the property?
package com.example.demo.view
import javafx.beans.Observable
import javafx.beans.binding.StringBinding
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleListProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import tornadofx.View
import tornadofx.button
import tornadofx.label
import tornadofx.vbox
class Thing(x: Int) {
val xProperty = SimpleIntegerProperty(x)
val yProperty = SimpleStringProperty("xyz")
}
class Collection {
private var things = FXCollections.observableList(mutableListOf<Thing>()) {
arrayOf<Observable>(it.xProperty)
}
val thingsProperty = SimpleListProperty<Thing>(things)
fun addThing(thing: Thing) {
things.add(thing)
}
}
class Summary(var collection: Collection) {
val summaryBinding = object : StringBinding() {
// The real code is more practical but
// this is just a minimal example.
override fun computeValue(): String {
val sum = collection.thingsProperty.value
.map { it.xProperty.value }
.fold(0, { total, next -> total + next })
return "There are $sum things."
}
}
// How to make this property update when collection changes?
val summaryProperty = SimpleStringProperty("There are ? things.")
}
class MainView : View() {
val summary = Summary(Collection())
override val root = vbox {
label(summary.summaryProperty)
button("Add Thing") {
summary.collection.addThing(Thing(5))
}
}
}
Keep in mind that I made this answer based on your minimal example:
class Thing(x: Int) {
val xProperty = SimpleIntegerProperty(x)
var x by xProperty
val yProperty = SimpleStringProperty("xyz")
var y by yProperty
}
class MainView : View() {
val things = FXCollections.observableList(mutableListOf<Thing>()) {
arrayOf<Observable>(it.xProperty)
}
val thingsProperty = SimpleListProperty<Thing>(things)
val totalBinding = integerBinding(listProperty) {
value.map { it.x }.fold(0, { total, next -> total + next })
}
val phraseBinding = stringBinding(totalBinding) { "There are $value things." }
override val root = vbox {
label(phraseBinding)
button("Add Thing") {
action {
list.add(Thing(5))
}
}
}
}
I removed your other classes because I didn't see a reason for them based on the example. If the collection class has more functionality than holding a list property in your real project, then add just add it back in. If not, then there's no reason to give a list its own class. The summary class is really just two bindings (or one if you have no need to separate the total from the phrase). I don't see the need to give them their own class either unless you plan on using them in multiple views.
I think your biggest problem is that you didn't wrap your button's action in action {}. So your code just added a Thing(5) on init and had no action set.
P.S. The var x by xProperty stuff will only work if you import tornadofx.* for that file.
I have MyType registered as qmlRegisterSingletonType<MyType>("org.examples", 1, 0, "MyType") which has QmlListProperty<MyListElement> - myList.
What syntax i should use at QML side to assign something to MyType.myList?
Seems like that is incorrect:
Component.onCompleted: {
MyType.myList = [
MyListElement {
}
]
}
You have to create the MyListElement object using javascript and then add it to the list with push since QmlListProperty is translated to a list in QML.
In this case with Qt.createQmlObject(), so assuming MyListElement was created with:
qmlRegisterType<MyListElement>("org.examples", 1, 0, "MyListElement");
So the solution is:
Component.onCompleted: {
var o = Qt.createQmlObject('import org.examples 1.0; MyListElement {}', this)
// o.foo_property = foo_value;
MyType.myList.push(o);
}
I created a program that reads stock data (time series) from the internet and displays it in a QML ChartView. After I add all the line series that I want, I can delete them by clicking a button.
I would like to know if it is possible to delete the line series by clicking at ANY point in the line series?
I am adding the series dynamically like this:
// stockID is sent from C++ when the timeSeriesReady signal is emitted
var chartViewSeries = chartView.createSeries(ChartView.SeriesTypeLine, stockID, dateTimeAxis_chartView_xAxis, valueAxis_chartView_yAxis);
// Set chartViewSeries (AbstractSeries/LineSeries) properties
chartViewSeries.onClicked.connect(lineSeriesClicked);
chartViewSeries.onHovered.connect(lineSeriesHovered);
stockChart.setLineSeries(chartViewSeries);
I don't want to crowd too much my post so I won't post ALL the files, however:
dateTimeAxis_chartView_xAxis is a DateTimeAxis QML type inside the main ChartView QML typ with id: chartView
valueAxis_chartView_yAxis is a ValueAxis QML type inside the main ChartView QML typ with id: chartView
stockChart is the id of a StockChart QML Type imported from C++
lineSeriesClicked is this function:
function lineSeriesClicked(lineSeriesPoint){
console.log(lineSeriesPoint);
}
lineSeriesHovered is this function:
function lineSeriesHovered(lineSeriesPoint, isHovered){
if(isHovered){
var date = new Date(lineSeriesPoint.x);
console.log(date.getFullYear() + "-" + (((date.getMonth()+1) < 10) ? ("0" + (date.getMonth()+1).toString()) : (date.getMonth()+1)) + "-" + (((date.getDate()) < 10) ? ("0" + (date.getDate()).toString()) : (date.getDate())) + " -> " + lineSeriesPoint.y);
}
}
Now, in the log I see all the correct data, e.g., when hovered:
qml: 2017-08-29 -> 64.91115963918442
when clicked:
qml: QPointF(1.50432e+12, 65.0453)
Looking at XYSeries QML Type (https://doc.qt.io/qt-5/qml-qtcharts-xyseries.html#clicked-signal), the clicked signal that I am using only passes the a point.
Is there any way to get the name of the line series where the point data was obtained to be able to delete this series? Perhaps through some sort of context access or "this" keyword?
Thank you very much!!!
I don't know if you solve this, but just for documentation... I could solve it using model-view-delegate. In my example, I'm using a sensor data to generate a graphic and I wish I could connect different graphics (eg. pie and line) by the Series' label. I could do it using this model-view-delegate perspective. I figured this out like this:
import QtQuick 2.2
import QtQuick.Controls 2.3
import QtQuick.Window 2.10
import QtQuick.Layouts 1.3
import QtDataVisualization 1.2
import QtCharts 2.2
import Qt3D.Input 2.0
// I imported more than I used because it's a part of a big project...
Item{
id: mainWindow
ListModel{
id: jsonData
Component.onCompleted: // line
{
//create a request and tell it where the json that I want is
var req = new XMLHttpRequest();
var location = "URL-to-JSON-DATA"
//tell the request to go ahead and get the json
req.open("GET", location, true);
req.send(null);
//wait until the readyState is 4, which means the json is ready
req.onreadystatechange = function()
{
console.log("Req status:"+req.status+"("+req.readyState+")");
if (req.readyState == 4)
{
//turn the text in a javascript object while setting the ListView's model to it
if (req.responseText){
var result = JSON.parse(req.responseText);
for (var i =0; i < result.rows.length;i++){
// map the Json to a model
jsonData.append({"sensor":result['rows'][i]['value'][0],"activePower": result['rows'][i]['value'][1],"voltage":result['rows'][i]['value'][2], "current":result['rows'][i]['value'][3], "year":result['rows'][i]['key'][0],"month": result['rows'][i]['key'][1],"day":result['rows'][i]['key'][2], "hour":result['rows'][i]['key'][3], "minute":result['rows'][i]['key'][4] })
};
//Sucess string
console.log("JSON [Sensor,Date,Hour] loaded");
//Fail string :(
} else {console.log("Empty[Sensor,Date,Hour JSON");}
}
}
}
}
ChartView {
title: "Line"
id: mainChart
anchors.fill: parent
antialiasing: true
Repeater{
model: jsonData
Item {
Component.onCompleted: {
if( mainChart.series(sensor)) {
mainChart.series(sensor).append(10,activePower);
} else{
mainChart.createSeries(ChartView.SeriesTypeLine,sensor,lineXOrdinate,lineYOrdinate );
mainChart.series(sensor).append(hour+(minute*0.5)/30,activePower);
mainChart.series(sensor).hovered.connect(
function (point,state){
if (state){
console.log(">>>"+sensor); // print the Series label =D
}
});
}
if(activePower > lineYOrdinate.max){ // if the highest value is above the Y axis, reset it to value+10
lineYOrdinate.max = activePower +10;
}
}
}
}
}
ValueAxis {
id: lineYOrdinate
min:0
max:11
}
ValueAxis {
id: lineXOrdinate
min:0
max:24
}
}
The X axis is a 24 values long, because I'm mapping a day.
I hope this help =)
Completely new to QT and QML. I'm trying to set the color of a rectangle based on the relationship between the two propery doubles callValue and handRaiseXBB, but I get the error
unexpected token if"
and
expected a qualified name id
Could anyone tell me what I am doing wrong?
import QtQuick 2.0
Item{
id: hand
property double callValue: 0.0
property double handRaiseXBB: 100
property string handCallColor: "green"
property string handFoldColor: "grey"
Rectangle {
anchors.fill: hand
if (hand.callValue >= hand.handRaiseXBB) {
color: hand.handFoldColor
}
else {
color: hand.handCallColor
}
}
}
You can do it like this:
color: (hand.callValue >= hand.handRaiseXBB) ? hand.handFoldColor : hand.handCallColor
You could also make a function to calculate it and then assign the color property with the return value of the function:
function getHandColor()
{
var handColor = hand.handCallColor
if(hand.callValue >= hand.handRaiseXBB)
{
handColor = hand.handFoldColor
}
return handColor
}
color: getHandColor()
Another form to solve this is the following:
Rectangle {
...
color: {
color = hand.handCallColor
if(hand.callValue >= hand.handRaiseXBB)
color = hand.handFoldColor
}
...
}
But the form with ternary operator is a better form!
QML is "based" in javascript, then i belive that all itens are javascript objects, how to:
var Rectangle: {
color: "red",
id: "id",
//then we can do this
setColor: function(_color) {this.color = _color}
}