1

I enriched a TextField (displaying a float) so that it can be changed by mouse wheel, all while being still editable by hand.

I found the quirk with forceActiveFocus and onClicked here (I wish I could just let all events pass down the widget stack like in Qt) and use onWheel to change the value (please let me know if this is not the best way to do this):

TextField{
   text: cxxObject.floatAttribute.toString()
   onEditingFinished: { cxxObject.floatAttribute=parseFloat(text); }
   MouseArea{
      anchors.fill: parent
      propagateComposedEvent: true
      onClicked: { parent.forceActiveFocus(); }
      onWheel: { parent.text=parseFloat(parent.text)-.5*wheel.angleDelta.y/120;
         parent.editingFinished();
      }
   }

I would like to re-use this component instead of TextField in several places (without copy&paste), so I tried to declare the component like this:

Component{
    id: wheeledFloatTextField
    property real initValue: 0.
    property real dWheel: 0.5
    signal editingFinished(real value);
    TextField{
        text: parent.initValue.toString();
        // re-emit signal to the component
        // so that user-defined slot can be defined when re-used
        onEditingFinished: parent.editingFinished(parseFloat(text));
        // validator: ...
        MouseArea{
            anchors.fill: parent
            propagateComposedEvents: true
            onClicked: { parent.forceActiveFocus();  }
            onWheel: {
                parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
                parent.editingFinished();
            }
        }
    }
}

and re-use:

Loader{
    sourceComponent: wheeledFloatTextField
    initValue: cxxObject.floatAttribute;
    onEditingFinished: { cxxObject.floatAttribute=value; }
}

I am however getting (at the line where Component is used):

Component objects cannot declare new properties.

What is wrong? I was some posts (like How do you assign a QML Item to a component property in QML and then use that object inside the component? and https://developer.blackberry.com/native/documentation/dev/custom_components/index.html) from which I am gathering I might need to wrap the inside of Component (which is as-if it were a separate .qml file and does not define a scope) in something like Item or Container but I am not sure what to do. Any hint?

I would like to keep the definition inline first, later move to a separate file.

eudoxos
  • 18,545
  • 10
  • 61
  • 110

1 Answers1

1

If you have the component declared in a separate file, you can (should) omit the top-level Component. For maximum reusability of components, it is reccomended to declare them in a separate file.
A Component can not have any properties declared. It is basically stopping the object creation in a prototypical state. That is useful if you want to configure the object, for later creation, for example lazy initialization (delegates).
If you have a property of type Component and you use the myProp: SomeType {...} syntax, it will automatically just create a component from that.

I think the best solution is to put your TextField in a seperate file, and add the properties to the root-node so it is customizable.

File1 (e.g. "CustomTextField.qml")

TextField{
    property real initValue: 0.
    property real dWheel: 0.5
    signal editingFinished(real value);
    text: initValue.toString();
    // re-emit signal to the component
    // so that user-defined slot can be defined when re-used
    onEditingFinished: editingFinished(parseFloat(text));
    // validator: ...
    MouseArea{
        anchors.fill: parent
        propagateComposedEvents: true
        onClicked: { parent.forceActiveFocus();  }
        onWheel: {
            parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
            parent.editingFinished();
        }
    }
}

You can then reuse the Component in all known ways like in a Loader:

Loader {
    sourceComponent: CustomTextField { // Property type is component, so it automatically creates a Component instead of the full-blown object, until it is loaded.
        initValue: 12
        dWheel: 42
    }
    ...
}

or without Loader

CustomTextField {
    ...
}

Of course you can keep it inline, but even then, you have to add the properties to the root-element inside the Component.

Component {
    id: componentId // only thing you can set besides one Object in a Component
    TextField{
        id: componentRoot // You can't reference this id from outside the Component!!!
        property real initValue: 0.
        property real dWheel: 0.5
        signal editingFinished(real value);
        text: initValue.toString();
        // re-emit signal to the component
        // so that user-defined slot can be defined when re-used
        onEditingFinished: editingFinished(parseFloat(text));
        // validator: ...
        MouseArea{
            anchors.fill: parent
            propagateComposedEvents: true
            onClicked: { parent.forceActiveFocus();  }
            onWheel: {
                parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
                parent.editingFinished();
            }
        }
    }
}

This has the down-side that you will always need a separate object to instantiate the Component, like a Loader which adds overhead and complicates communication in the file between the objects, since to address it, you will need to use: loaderId.item.property which might be expensive in lookup, you need to ensure that item is defined e.t.c.