Create Objects of inner Component of Object of outer Component - qt

Is it possible to do something like this:
import QtQuick 2.7
import QtQuick.Window 2.2
Window{
id: root_
visible: true
width: 300
height: 300
Component {
id:compouter
Column{
anchors.fill: parent
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
Component.onCompleted: {
var c = compouter.createObject(this)
//var d = c.compinner.createObject(c, {"color": "green"})
//var e = c.compinner.createObject(c, {"color": "red"})
}
}
That is, I want to create multiple Objects inside the outer object (after the outer object was created). However this isn't possible, as I get the error:
TypeError: Cannot call method 'createObject' of undefined
Is there any workaround for this? Is it maybe only possible to instantiate all inner objects during the instantiation of the outer object?

The ids are out of the scope for your function call.
You can either add a function to your outer component, that creates your objects:
Component {
id:compouter
Column{
anchors.fill: parent
function createCompinner(arg) { compinner.createObject(this, arg) }
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
or you expose the inner Component as a property:
Component {
id:compouter
Column{
property alias compinner: compinnerComponent
anchors.fill: parent
Component {
id: compinnerComponent
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
and access it like that.

Ok, I found a workaround through sending a signal to the outter object:
import QtQuick 2.7
import QtQuick.Window 2.2
Window{
id: root_
visible: true
width: 300
height: 300
Component {
id:compouter
Column{
anchors.fill: parent
signal createRect(var color)
onCreateRect: {
compinner.createObject(this, {"color": color})
}
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
Component.onCompleted: {
var c = compouter.createObject(this)
c.createRect("green")
c.createRect("red")
}
It's working, but maybe there is some more generic approach to this (see the accepted answer). As the poster suggested, calling a function would be semantically more clean than sending a signal

Related

QML How to import ItemGrabResult?

I try to use ItemGrabResult {} element. But I got error
"QQmlApplicationEngine failed to load component"
"ItemGrabResult is not a type"
import QtQuick 2.15
Item {
id: root
ItemGrabResult {
}
Rectangle {
id: rect
color: "red"
anchors.fill: parent
}
ItemGrabResult is inactive
In documentation https://doc.qt.io/qt-5/qml-qtquick-itemgrabresult.html#details
I can not find any information how to import this element in CMake (or qmake) file
As iam_peter indicated, the ItemGrabResult comes from a call to Item.grabToImage().
In the following example, you need to click on the red Rectangle and it will initiate a grabToImage() call. The callback will have an instance of the ItemGrabResult which has properties and methods for accessing the grabbed image:
import QtQuick
import QtQuick.Controls
Page {
Item {
id: frame
width: 200
height: 200
Rectangle {
id: rect
color: "red"
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
frame.grabToImage( function (itemGrabResult) {
// itemGrabResult: QQuickItemGrabResult(0x254f9a0)
// itemGrabResult.image: QVariant(QImage, QImage(QSize(200, 200),format=QImage::Format_RGBA8888_Premultiplied,depth=32,devicePixelRatio=1,bytesPerLine=800,sizeInBytes=160000))
// itemGrabResult.saveToFile: function() { [native code] }
dbg.text = `
itemGrabResult: ${itemGrabResult.toString()}
itemGrabResult.image: ${itemGrabResult.image.toString()}
itemGrabResult.saveToFile: ${itemGrabResult.saveToFile.toString()}
`
} );
}
}
}
footer: TextEdit { id: dbg; wrapMode: Text.WordWrap }
}
You can Try it Online!
https://doc.qt.io/qt-6/qml-qtquick-item.html#grabToImage-method

QML TypeError: Property 'xx' of object yy is not a function

I have this main.qml:
import QtQuick 2.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.5
ApplicationWindow {
...
Item {
// This Item is to provide needed properties for functionality in the original app that was elided out in this example.
// It was left in in case it's relevant to the problem.
...
Column {
...
Text {
text: qsTr("Masked text")
SlidingMask {
id: testMask
anchors.fill: parent
...
}
}
Row {
Button {
id: btnRevealText
text: qsTr("Reveal")
...
}
Button {
id: btnHideText
text: qsTr("Hide")
...
}
}
}
}
Connections {
target: btnRevealText
onPressed: testMask.reveal()
}
Connections {
target: btnHideText
onPressed: testMask.hide()
}
}
And this SlidingMask.qml that's registered in the qml.qrc:
import QtQuick 2.0
Rectangle {
...
function hide() {
...
}
function reveal() {
...
}
}
When I run the app and try to press the buttons, I get the following errors:
TypeError: Property 'hide' of object SlidingMask_QMLTYPE_7(0x19991132c50) is not a function
TypeError: Property 'reveal' of object SlidingMask_QMLTYPE_7(0x19991132c50) is not a function
However, if I try changing the Connections to alter a property of the SlidingMask instead of calling a function, it works fine.
I've also tested this component previously and didn't run into any problems then, although I wasn't using Connections in that test.
I've searched here and on Google for an answer, but nothing I've found seems relevant to my situation. How would I fix this?
Here is a simple example which works properly:
//main.qml
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
Column{
ItemWithFunction{
id: sc
width: 100
height: 100
}
Button{
id: btn1
text: 'Test Connection'
}
}
Connections{
target: btn1
onPressed: sc.testFunction();
}
}
//ItemWithFunction.qml
Rectangle{
color: 'red'
function testFunction(){
console.log("SOMETHING HAPPENED")
}
}
It seems that you are not putting your functions in SlidingMask root but in one of its child components.

Load SwipeView pages dynamically

I have created the following MWE (Qt 5.13.0):
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 3;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Loader
{
sourceComponent: theSingleComp;
Component
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " +
(theSwipeView.currentIndex - 1);
}
}
}
}
}
}
}
In my program, I have an unique component (theSingleComp) and multiple components behind him (theMultiComp). As for now, I need to implement the following functionality:
In case the model used for theMultiComp has only 1 item, display only this item and not the theSingleComp. In case the are more theMultiComp items, display it like now. It seems to me that there is no possibility for this to work if I keep the items defined statically. But on the other hand, I don't know how to do this dynamically, since there is a case in which one of the components should not be displayed at all. I tried an approach like this:
sourceComponent: (itemsNo > 1) ? theSingleComp : null;
But then the page for this null component is still created.
Your problem is that Loader is an Item and SwipeView creates a page for it even if it doesn't have a source component.
To solve this problem you can use Repeater instead with a model of 1 (or 0 to disable it). Repeater is also an Item but it has some special code under the hood to be ignored by containers.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
id: window
property int itemsNo: 0
visible: true
width: 480
height: 480
SwipeView {
id: theSwipeView
anchors.fill: parent
Repeater {
model: window.itemsNo > 1 ? 1 : 0
Page {
Text {
text: "The single one"
}
}
}
Repeater {
model: window.itemsNo
Page {
Text {
text: "The multi one " + model.index
}
}
}
}
}
(I've simplified your code to remove the explicit Components and the Loaders)
I have come up with the following solution but I am not happy with it. It's very hacky and the user can see how the page index changes.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 2;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Component.onCompleted:
{
if (itemsNo > 1)
insertItem(0, theSingleComp);
set0IndexTimer.start();
}
Timer
{
id: set0IndexTimer;
interval: 1;
running: false;
repeat: false;
onTriggered: theSwipeView.setCurrentIndex(0);
}
onCurrentIndexChanged: console.log("page: ", currentIndex);
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " + theSwipeView.currentIndex;
}
}
}
}
}
}
Item
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
I am still seeking some other examples.

How to setup my button component to open a window

Here is the code of the window I wanna be opened in file PopUpFreeCoins.qml:
import QtQuick 2.0
import QtQuick.Controls 2.1
Item {
property int t
property int c
ListModel{
id:ff
ListElement {
name: "ByFollow"
s: "Images/follow.png"
}
ListElement {
name: "ByLike"
s: "Images/care.png"
}
ListElement {
name: "ByComment"
s: "Images/chat.png"
}
}
ListView{
width:t-t/10
height: c/5
layoutDirection:Qt.LeftToRight
orientation: ListView.Horizontal
model: ff
spacing:50
delegate: Button{
contentItem: Image{
source: s
}}
}
}
property t is set equal to window width in main file and property c is set to window height. This is code of my Button.qml:
Button{//Below Right
width:profilePicture.width/2
height:profilePicture.width/2
x:profilePicture.x+profilePicture.width
y:profilePicture.y+profilePicture.height
contentItem: Image {
source: "Images/freecoins.png"
anchors.fill: parent
}
onClicked: PopUp{height:100;width:300;PopUpFreeCoins{t:a;c:b;}}
}
property a is window width and b is window height.
this line onClicked: PopUp{height:100;width:300;PopUpFreeCoins{t:a;c:b;}} has an error I don't know how to handle!
Here is the error:
Cannot assign object type PopUpFreeCoins_QMLTYPE_0 with no default
method
You need to create the Object somehow. You have multiple ways for dynamically create Objects. One way is to use Component.createObject(parent) which requires you to have a Component instantiated in your file.
Here you can also pass a Object ({property0 : value, property1:value ... }) as second argument, to set the properties of the Component to be instantiated. You should not set the parent to null as it might happen, that the JS-garbage collector is too aggressive once again.
Alternatively you can use the Loader to load it from either a source (QML-file) or sourceComponent. Here you won't have problems with the garbage collector.
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
width: 1024
height: 800
visible: true
Button {
text: 'create'
onClicked: test.createObject(this)
}
Button {
x: 200
text: 'load'
onClicked: loader.active = !loader.active
}
Loader {
id: loader
source: 'TestObj.qml'
active: false
}
Component {
id: test
TestObj {}
}
}
TestObj.qml includes the Window to be opened.
Alternatively you can have the Window created from the beginning, and just change the visible to true or false.

How to make some reusable QML object, which can inject another QML object?

How to make some reusable QML object, which can inject another object?
I've ever tried to use Component & Loader , but seems not what I want. (It still encapsulate the whole QML type and lacks of elasticity, hard to reuse)
Usage example:
Card.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
default property var innerObject
property string titleText: "[Hello Untitled Title]"
id: root
color: "#fff"
ColumnLayout {
anchors.fill: parent
Rectangle {
id: header
height: 10
width: parent.width
color: "#666"
RowLayout {
Text { text: titleText; color: "#fff" }
}
}
// How to inject innerObject in here ?
}
}
main.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Card {
titleText: "Image Information"
ColumnLayout { /* .......*/ } // innerObject
}
Card {
titleText: "Image Viewer"
Rectangle { /* .......*/ } // innerObject
}
The answer I linked works like this:
Main.qml
Card {
titleText: "Image Viewer"
innerObject: Rectangle {
Component.onCompleted: {
console.log(parent.objectName)
}
}
}
Card.qml
Rectangle {
property string titleText: "[Hello Untitled Title]"
default property alias innerObject : innercolumn.children
id: root
color: "#fff"
ColumnLayout {
id: innercolumn
objectName: "column"
anchors.fill: parent
Rectangle {
id: header
height: 10
width: parent.width
color: "#666"
RowLayout {
Text { text: titleText; color: "#fff" }
}
}
}
}
I also want to suggest a solution based on default property and reparenting:
The Item which can embed another Item:
MyItem.qml
import QtQuick 2.7
import QtQuick.Layouts 1.2
Rectangle {
id: root
default property Item contentItem: null
border {
width: 1
color: "#999"
}
ColumnLayout {
anchors.fill: parent
Rectangle {
Layout.fillWidth: true
height: 30
color: "lightgreen"
}
Item {
id: container
Layout.fillWidth: true
Layout.fillHeight: true
}
}
onContentItemChanged: {
if(root.contentItem !== null)
root.contentItem.parent = container;
}
}
Can be used as below:
main.qml
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
visible: true
width: 600
height: 600
MyItem{
width: 400
height: 400
anchors.centerIn: parent
Text {
text: "Hello!"
anchors.centerIn: parent
}
}
}
But I still agree with #ddriver that Loader is the best solution for this case
It is not mandatory that you use a Loader with a component. You can just go:
Loader {
source: "Something.qml"
}
When the source is something that can be loaded synchronously, you can directly use the loader's item for stuff like bindings, without worrying about whether or not it is created. If you load over network, you have to delay the bindings until the item is completed and use either a Binding element or Qt.binding() to do it respectively in a declarative or imperative manner.
In your case, a loader would be appropriate, and the property for the inner dynamic object outta be a Component. This way you can populate it either with an inline component, or with Qt.createComponent() from existing source.
property Component innerObject
...
innerObject: Component { stuff }
...
innerObject: Qt.CreateComponent(source)
Of course, there are even more advanced ways to do it, for example, the "generic QML model object" I have outlined here. It allows to quickly and easily create arbitrary data structure trees both declaratively and imperatively, and since the object is also a model, you can directly use listviews or positioner elements with repeaters to layout the gui without actually writing the UI code each and every time.
Also, from your main.qml code example - you cannot have more than one root element in a qml file.
Edit: The default property approach actually works if the element is moved to its own qml file, so also basically you could just:
default property alias innerObject: innerColumn.children
where innerColumn is the id of your ColumnLayout. Also, innerObject could be whatever legal name, since as a default property, it will not actually be used.
There is also the option to not use a default property, which is useful when the root item still needs to have its own children, but still have the ability to redirect declarative objects to be children of a sub-object:
property alias content: innerColumn.children
// and then
content: [ Obj1{}, Obj2{}, Obj3{} ] // will become children of innerColumn

Resources