Reusing a delegate component in QML - qt

I came across a weird problem when designing my application in QML. The following code works:
TableView {
itemDelegate: Item {
function a() {}
Loader {}
}
}
I have a bunch of functions, properties, and loaders in this item delegate which is an Item object. Problems arise when I try to reuse this delegate in a ListView. I can't reference it like this:
Item {
id: myitem
function a() {}
Loader {}
}
TableView {
itemDelegate: myitem
}
error: Unable to assign QQuickItem to QQmlComponent
This is because itemDelegate is a Component:
http://qt-project.org/doc/qt-5/qml-qtquick-controls-tableview.html#itemDelegate-prop
So QML can convert an Item to Component when it's embedded, but not when it's referenced.
And I can't make it a component, since components can't have functions, loaders, properties etc.
How can I reuse my delegate?

Solved by wrapping the item inside the component:
Component {
Item{...}
}

Related

MapQuickItem loaded with Loader does not display

I am building a mapping program that eventually wants to display a different item on the map based on a value within the model. To facilitate this, I'm using Loader in my MapItemView delegate.
Unfortunately, when I tested this, the Loader method displayed nothing.
MapItemDelegate.qml
MapQuickItem {
id: waypoint
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: task.waypoint //task extends QObject with members taskname and waypoint
sourceItem: Grid: {
columns: 1
rows:2
horizontalItemAlignment: Grid.AlignHCenter
Image {
id: image
source: "Waypoint.png"
height: 32
width: 32
}
Text {
id: text
text: task.taskname
}
}
}
main.qml
MapItemView {
id: taskview
model: tasklistmodel //Extends AbstractListModel to contain task objects
delegate: Component {
//Desired method, fails to display.
Loader {
source:"MapItemDelegate.qml"
// Will eventually be:
// source: task.typename+"MapItemDelegate.qml" to facilitate several types.
}
//Control method. Works fine, but does not meet my requirements.
MapItemDelegate {
//Nothing further needed.
}
}
}
How do I get MapItemView to actually display MapQuickItems loaded with Loader? Is there another method of dynamically loading delegates for display?
As it turns out, using a Loader within a delegate of MapItemView is not possible, as the API demands the Component contain a single mapping object. While Loader did load a mapping object, it is not in itself a mapping object, which caused the object to fail to display.
What I ended up doing is implementing a custom QSortFilterProxyModel that overrides filterAcceptsRow to filter tasklistmodel to match the task.typename property, then editing my MapItemView as follows:
MapItemView {
id:taskview
model: TaskListFilterModel { // custom extension of QSortFilterProxyModel
acceptType:"dothisthingtype"
sourceModel: tasklistmodel
}
delegate:MapItemDelegate {
}
}
With that, I can add a MapItemView for each type of task I plan to add in the future. While it's far less elegant and way more work than I had hoped to achieve with Loader, it does at least meet my requirements.

Re-using Component of TextField responding to wheel events, with signal handling

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.
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.

QML pass QtObject from one qml to another

I've been trying to wrap my head around this one for a while and came up with a few hacks, but none of them seem to be the Right Way. Hopefully this makes sense. Say I have three qml files.
First QML:
...
ListView {
id: patientList
model: patientModel
delegate: E3DPatientListItem {
onClicked: {
if (patientList.currentIndex !== index)
{
patientList.currentIndex = index
detailStackView.push("DetailPanel.qml",
{ "view": view, "ptInfo": model })
...
DetailPanel:
...
Page {
property QtObject ptInfo
Timeline {
ptInfo: ptInfo // <- how do I pass this to Timeline?
...
Timeline.qml
...
Item {
property QtObject ptInfo // <- always null :(
...
...
Page {
property QtObject ptInfo
Timeline {
ptInfo: ptInfo // <- how do I pass this to Timeline?
...
What do you suppose ptInfo: ptInfo is achieving? You are binding the property to its own identifier.
Maybe try not using the same identifier to avoid shadowing, or give the Page and id and then ptInfo: pageid.ptInfo. parent.ptInfo will also work as long as the object has the parent property exposed, keep in mind that QtObject does not.
However, you don't really need to have property QtObject ptInfo in Timeline.qml, as long as a timeline is always instantiated inside a page, you can access ptInfo directly from inside the timeline because of dynamic scoping. Note that dynamic scope properties will only work for properties that are defined in the root element of the particular qml file:
// Obj1.qml
Item {
property int test: 7
Obj2{}
}
// Obj2.qml
Item {
Component.onCompleted: console.log(test) // it will work
}

how to access properties of item inside loader object

I am using a qml Loader component to push a page dynamically into view. Now the page I push has various properties and I would like to access them from the Loader component itself. However, I am unable to create aliases to these properties. So, I have something like:
Loader {
id: loginLoader
source: "qrc:/pages/IdPinLoginPage.qml"
property alias hasNavBar: loginLoader.item.hasNavBar
property alias hasStatusBar: loginLoader.item.hasStatusBar
}
This results in Invalid alias target location. How can I redirect these properties to the parent level in the Loader component?
I recommended you to use sourceComponent instead of source property of the Loader for assign/bind value to loaded item easily.
Also for access loaded item, you can use item property of the loader:
Loader {
id: loader
sourceComponent: Component {
SampleItem {
foo: 13 //assign values to loaded item properties
bar: "aleyk!"
}
}
}
Text {
text: loader.item.bar //Access to loaded item properties
}
SampleItem.qml
import QtQuick 2.6
Item {
property int foo: 0
property string bar: "salam";
}

Set the parent context separately from the parent in QML components

When I define a Component in QML I can define a alias property to expose properties of QtObjects that are otherwise hidden by the Components root.
Especially interesting is the default property alias to build powerful reusable and adaptable Components. But there is no generic way to have alter the nameresolution, which is bad for properties and signals.
Example: MyComp1.qml
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
}
}
main.qml
MyComp {
id: myObj
signal sig1()
onSig1: console.log('here')
Button {
id: button
onClicked: {
parent.sig()
parent.sig1()
}
}
}
Here button is now a child of childItem, but both signals: sig() and sig1() are defined in rootItem, which leads to an error. To the user of of my nicely designed Component it is not transparent. He would need to look in the code of the Component to realize, why the buttons parent is not myObj/rootItem but in fact of childItem. This might lead to bugs.
Therefore I am looking for a way to prevent it.
For signal sig() this would be possible by the modifying the MyComp and manually forward the signal from childItem to rootItem:
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
signal sig()
onSig: rootItem.sig()
}
}
But is there a way to shortcut this, so that the user does not need to us myObj.sig1() but might use parent.sig1().
Same goes for properties, that are added uppon instantiation:
MyComp {
signal sig1()
property int num: 5
Button {
text: parent.num // <--- Error
}
}
It would be nice, if the properties and signals defined uppon instantiation would be available as parent.propertyName to the Objects added by a default-property aswell, I think.
Is there a way (to maybe hack this)?
There is no way to do this.
What you can do is stop assuming the outer scope in QML code is always the parent.
You could either access it directly by id:
MyComp {
id: myComp
signal sig1()
property int num: 5
Button {
text: myComp.num
onClicked: myComp.sig1()
}
}
Or if the object you want to access is the root item, you can access its properties implicitely:
MyComp {
signal sig1()
property int num: 5
Button {
text: num
onClicked: sig1()
}
}
One good practice (albeit maybe too extreme for most case) that can be deduced from this situation, is to only use parent when you actually want to access the parent Item in the visual hierarchy, for things like anchors or width and height.

Resources