How can I achieve functionality similar to WPF's IValueConverter - qt

In my ListView I've these:
ListView{
id: listView
Layout.fillHeight: true
Layout.fillWidth: true
model: addContext.newLease.receivables
delegate: ItemDelegate{
width: parent.width
topPadding: 0
bottomPadding: 0
contentItem: RowLayout{
Text{
text: modelData.headId //mainContext.receivableHeads....name
Layout.preferredWidth: parent.width / 2
}
Text{
text: modelData.amount
horizontalAlignment: Text.AlignRight
}
PathButton{
pathData: C.minusIcon
Layout.alignment: Qt.AlignRight
hasToolTip: false
onClicked: addContext.removeReceivable(listView.currentIndex)
}
}
}
}
With this I've some Layout issue BUT I'll ask about that later. Here in the contentItem's first Text, I've a number headId and it presents that number in the ListView with that modelData.headId BUT I want to present the name instead. The model, addContext.newLease.receivables, doesn't contain the name, it only has these properties:
class Receivable : public QObject{
Q_OBJECT
Property(int, leaseId)
Property(int, headId)
Property(int, amount)
};
name corresponding to the headId is in mainContext.receivableHeads which has these properties:
class Head : public QObject{
Q_OBJECT
Property(int, id)
Property(int, controlId)
Property(QString, name)
Property(QString, description)
};
I want to get the name from mainContext.receivableHeads by matching the headId of modelData to the id of mainContext.receivableHeads.
EDIT
This:
Q_INVOKABLE QString headName(int headId){
foreach(auto head, mvm->receivableHeads()){
if(head->id() == headId)
return head->name();
}
}
along with this:
text: addContext.headName(modelData.headId) //modelData.headId //mainContext.receivableHeads.indexOf(modelData).name
as suggested by Thomenson, works. My receivableHeads is a tiny list, so it's a good candidate for javascript, is there any built-in javascript function that does this?
Well, I didn't know that javascript function like this:
text: {
for(var i =0; i < mainContext.receivableHeads.length; i++){
if(mainContext.receivableHeads[i].id === modelData.headId)
return mainContext.receivableHeads[i].name
}
}
also works!

Related

Are QML var properties passed by reference or by copy?

I have two QML files as below:
//Page.qml
SelectionPage {
model: localizationPageProxy.vehicleTypes //QObject* class exposed by property
currentKey: localizationPageProxy.vehicleTypes.currentDataKey //QVariant property
}
//SelectionPage.qml
Item {
property var model
property var currentKey
id: page
ColumnLayout {
height: parent.height
width: parent.width * 0.9
anchors.horizontalCenter: parent.horizontalCenter
ListView {
id: listView
anchors.fill: parent
ScrollBar.vertical: ScrollBar {}
clip: true
model: page.model.data
spacing: Number.EPSILON // I don't know why the data loading is faster with that
delegate: Item {
height: listView.height * 0.12
width: listView.width
RadioButtonItem {
height: parent.height * 0.85
width: parent.width
anchors.centerIn: parent
text: modelData.value
checked: modelData.key === page.currentKey
onClicked: page.currentKey = modelData.key //here the c++ property is changed
}
}
}
}
}
So, is currentKey property of SelectionPage.qml passed by reference?
If that was a copy I should not see the c++ model change.
Thank you for your help
Rather than discussing copy vs reference, we should really be talking about bindings. When you do this:
currentKey: localizationPageProxy.vehicleTypes.currentDataKey
You're creating a binding. Whenever currentDataKey's value changes, currentKey will be updated too. But it is not a two-way binding. So changing currentKey does not update currentDataKey. Two-way bindings are pretty difficult to achieve, but there are posts on SO about them.
To actually solve what you're trying to achieve, I recommend adding a Q_INVOKABLE function to your QObject called updateCurrentKey or something. Then in your onClicked handler, do something like this:
onClicked: page.model.updateCurrentKey(modelData.key)

Unexpected binding in QML with Q_GADGET

I have a gadget:
class ZScoreParams
{
Q_GADGET
Q_PROPERTY(quint64 lag MEMBER lag);
Q_PROPERTY(qreal threshold MEMBER threshold);
Q_PROPERTY(qreal influence MEMBER influence);
public:
std::size_t lag;
double threshold;
double influence;
};
Q_DECLARE_METATYPE(ZScoreParams)
and a property of an object of type QVariant:
Q_PROPERTY(QVariant zscoreParams READ zscoreParams WRITE setZscoreParams NOTIFY zscoreParamsChanged)
its getter returns either QVariant{} that is null in QML or QVariant::fromValue(gadget_val), the setter has the same logic.
Can't figure out how do I create a dialog with three SpinBox-es for editing my ZScoreParams. What is the right way to create it?
I attempted this ZParamsEdit.qml:
GridLayout
{
property var params
property var refreshChart: function () {}
signal zparamsChanged()
Component.onCompleted:
{
refreshChart = function ()
{
params.lag = lagEdit.value
params.threshold = thresholdEdit.value
params.influence = influenceEdit.realValue
logTable.debug("qml", JSON.stringify(params))
zparamsChanged()
}
}
Layout.margins: 5
columns: 2
flow: GridLayout.LeftToRight
Label {
text: qsTr("Lag:")
Layout.alignment: Qt.AlignRight
}
SpinBox {
id: lagEdit
value: params.lag
from: 100
to: 1000000
stepSize: 100
editable: true
Layout.alignment: Qt.AlignLeft
onValueChanged: refreshChart()
}
Label {
text: qsTr("Threshold:")
Layout.alignment: Qt.AlignRight
}
SpinBox {
id: thresholdEdit
value: params.threshold
from: 0
to: 100
stepSize: 1
editable: true
Layout.alignment: Qt.AlignLeft
onValueChanged: refreshChart()
}
Label {
text: qsTr("Influence:")
Layout.alignment: Qt.AlignRight
}
RealEdit
{
Layout.alignment: Qt.AlignLeft
id: influenceEdit
decimals: 2
realValue: params.influence
realFrom: 0
realTo: 1.0
realStepSize: 0.1
onValueChanged: refreshChart()
}
}
and then in main.qml:
Dialog
{
id: zdlg
ZParamsEdit
{
id: zedit
}
}
IconButton {
icon.source: "images/analyze.svg"
onClicked: {
zedit.params = market.zscoreParams
zdlg.open()
}
}
and got an effect that I did not expect. Each time an edit box changes, market.zscoreParams property setter is called.
How to make ZParamsEdit store a copy of my gadget and set my property value only once (with the gadget as a whole) when 'OK' button is pressed in the dialog?
Also tried
JSON.parse(JSON.stringify(market.zscoreParams))
but got also something unexpected.
EDIT1:
I probably found an ugly solution with an explicitly defined function that creates a copy of the gadget:
Q_INVOKABLE QVariant cloneZScoreParams(QVariant v)
{
std::optional<ZScoreParams> val;
util::FromVariant(v, val);
return util::ToVariant(val);
}
and then
globalSettings.cloneZScoreParams(market.zscoreParams)
in QML.
Is there something better in QT?
If I understand your problem correctly, you just need some sort of local property/properties to store the copy of your data, and then you need some function that writes those copies back to data source.
Here's how I would change your code:
GridLayout
{
property var params
// Copy the data into local properties
property lag: params.lag
property threshold: params.threshold
property influence: params.influence
// Only update the local properties when spinboxes change
function refreshChart()
{
lag = lagEdit.value
threshold = thresholdEdit.value
influence = influenceEdit.realValue
zparamsChanged()
}
// Call this when OK button is clicked
function save()
{
params.lag = lag
params.threshold = threshold
params.influence = influence
}
signal zparamsChanged()
...
SpinBox {
id: lagEdit
value: lag
...
onValueChanged: refreshChart()
}
SpinBox {
id: thresholdEdit
value: threshold
...
onValueChanged: refreshChart()
}
RealEdit
{
id: influenceEdit
realValue: influence
...
onValueChanged: refreshChart()
}
}
UPDATE:
To avoid calling setter 3 times, you can provide an invokable function in your C++ class:
Q_INVOKABLE void save(quint64 l, qreal t, qreal i)
{
lag = l;
threshold = t;
influence = i;
}
Now the save() function would look like this:
function save()
{
params.save(lag, threshold, influence)
}

Qml: How to set a property to all GridView items

I am trying to make a change to all items of a GridView.
I have tried to iterate through either the model or the grid, I looked at similar examples on the web, but everything I try ends with Cannot read property 'buttonText' of undefined.
It seems to me that the problem is that the interpreter can't figure out that the item from the grid or model is a Button. But I don't know how to cast it.
If I change the log to only display the item, not any property, (see code snippet), it seems that it knows it is an item... see my experiments below.
The only thing I can make work is set a property (or call a signal, or a function) from the delegate. But that only affects one grid item, not all.
How can I set a property on every item of the grid ? Alternatively, how can I send a signal, or call a function, on every item?
My experiments are in function changeEverythingFunction()
file: Button.qml
Item
{
id: itemButton
signal changeEverything
property int buttonIndex
property string buttonText
...
}
file: Model.qml
Item
{
id: modelItem
ListModel
{
id: listModel
}
property int buttonCount: listModel.count
function changeEverythingFunction()
{
// for (var i = 0; i < buttonCount; i++)
// listModel.setProperty(i, buttonText, "abc")
for(var childIndex in gridItems.contentItem.children)
{
console.log(listModel.get(childIndex).buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex].buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex]["buttonText"]) // undefined (I saw this in a SO example)
var item = gridItems.contentItem.children[childIndex]
console.log(item) // qml: QQuickItem(0xe496370)
}
}
MouseArea
{
....
Rectangle
{
...
GridView
{
id: gridItems
anchors.fill: parent
clip: true
model: listModel
delegate: Item
{
id: buttonDelegate
Button
{
buttonIndex: gridId
buttonText: itemText
onChangeEverything:
{
changeEverythingFunction();
}
}
}
}
}
}
}
Your approach is in the opposite direction: Your approach is to obtain the item of the view and modify it, but the approach that Qt points out is that the view reflects the information of the model and modifies it when necessary.
The following is a simple example where every time you press on the button with "change me" text increasing the number it shows, but if you press the button with "change all" text it will change all the numbers. As it is observed everything is done through the model, not through the view that are only used to display information or receive user interaction.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
Window {
visible: true
width: 640
height: 480
ListModel{
id: listmodel
}
function changeAll(){
for(var i=0; i< listmodel.count; ++i){
listmodel.setProperty(i, "number", listmodel.get(i).number + 1)
}
}
GridView{
id: grid
anchors.fill: parent
clip: true
model: listmodel
cellHeight: 120
cellWidth: 120
delegate: Item {
width: grid.cellWidth; height: grid.cellHeight
Column {
anchors.fill: parent
Text { text: model.number; anchors.horizontalCenter: parent.horizontalCenter }
Button{text: "change me"; onClicked: model.number +=1}
Button{text: "change all"; onClicked: changeAll()}
}
}
}
Component.onCompleted: {
for(var i=0; i < 10; ++i){
listmodel.append({"number": 0});
}
}
}

read string from Qt application by reverse engineering

I want to programmatically read a string generated in runtime by a basic Qt GUI application.
The string appears on the screen but I don't have the source and I want to pass this string to another script.
Here's the relevant .qml file:
import QtQuick 1.0
Rectangle {
id: tagCloud
SystemPalette { id: palette } //we get the system default colors from this
//public API
property variant model
property color baseColor: palette.base
property color textColor: palette.text
property int textFontSize: 16
color: baseColor
Flow {
id: flow
width: parent.width
spacing: 15
anchors.margins: 4
anchors.verticalCenter: parent.verticalCenter
//property int maxHeight:0
Repeater {
id: repeater
model: tagCloud.model
Text {
id: textBlock
text: category
font.pointSize: tagCloud.textFontSize;
}
}
}
}
Is there an easy way to get the "category" string as it's being generated?
Edit: This is the link to the application,
http://cybertron.cg.tu-berlin.de/eitz/projects/classifysketch/sketchpad_win.zip

QML Access object property by property name string

I want to create a QML binding in a repeated component. I want to bind the value of one of the elements to a property from a known object. My problem is that the name of said property that I want to bind to will be provided as a string in the component. How can I resolve the property name to an actual property that can be used as the value in a binding?
PS. If possible I guess I could pass the property directly to the repeater but then I would like to be able to convert the property to a string because I need both and don't want to pass both.
EDIT:
Here's what I want:
ListModel {
id: settingsModel
ListElement { title: "Bed Width"; setting: "bedWidth"; }
ListElement { title: "Bed Length"; setting: "bedLength"; }
}
Component {
id: settingsDelegate
Item {
width: parent.width
height: childrenRect.height
Label {
id: setLabel
text: title + ":"
width: parent.width
}
TextBox {
id: setTBox
anchors.top: setLabel.bottom
anchors.topMargin: 5
width: parent.width
Binding on text {
when: !setTBox.isActive
value: settings.setting
}
Binding {
target: settings
property: setting
value: setTBox.text
}
}
}
}
Column {
id: settingsColumn
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.top: txtSave.bottom
anchors.topMargin: 15
Repeater {
model: settingsModel
delegate: settingsDelegate
}
}
My problem is that the name of said property that I want to bind to
will be provided as a string in the component. How can I resolve the
property name to an actual property that can be used as the value in a
binding?
If you look at the documentation for Binding you will discover that the property property expects a string - property : string
So you don't have anything to resolve, that happens internally.
my problem is the "value: settings.setting" line
You could try something like settings[setting]

Resources