I'm new to Qt Quck and Qt5/PyQt, and now I've faced a strange problem. I'm trying to find an object with objectName "test" in the below QML definition like this:
self.rootObject().findChild(QObject, "test")
But the call returns None. However, if I move the objectName: "test" property to the parent Tab element, then it's found successfully. It's only not found whem inside the child Item. Similarly, addChannel, modifyChannel and removeChannel objects are also not found by findChild().
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import "TouchStyles"
Item {
ListModel { }
TouchButtonFlatStyle { id: touchButtonFlat }
TouchTabViewStyle { id: touchTabView }
Rectangle {
width: 480
height: 230
TabView {
currentIndex: 0
tabPosition: 1
anchors.fill: parent
style: touchTabView
Tab {
title: "Play"
Item {
anchors.fill: parent
PianoKeyboard { anchors.centerIn: parent }
}
}
Tab {
title: "Channels"
Item {
objectName: "test"
ListView {
anchors.fill: parent
model: listModel
delegate: Channel {}
}
BorderImage {
border.bottom: 8
anchors.bottom: parent.bottom
source: "images/toolbar.png"
width: parent.width
height: 50
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "Add"; objectName: "addChannel" }
Button { text: "Modify"; objectName: "modifyChannel" }
Button { text: "Remove"; objectName: "removeChannel" }
}
}
}
}
}
}
}
What am I doing wrong? The Qt documentation says that the search is performed recursively. Why doesn't it traverse the entire object tree?
The problem is related to the fact that tabs are "instantiated" only on demand. The first tab is always instantiated, so if you put the objectName there it will be found.
It will be found in the second tab only if you instantiate the second tab (select it). Similarly, using findChild on the TabView probably instantiates each tab (since it looking for them), so after that a findChild works even if second tab was not selected.
Conclusion: instantiate all tabs first (doing a findChild on the TabView is one way but may be a hack), then do the findChild for the item.
Related
i want to add a Component dynamically to an ColumnLayout in a TabView/Tab. But i've not found a possibility to do this. The problem is that i have no correct parent reference to my ColumnLayout for the createObject call.
Because the Tab dynamically loads a Component I've encapsulated the ColumnLayout in a Component.
In the QML Debugger i can solve the following path: objForm.tabView.tabStatus.tabStatusLoader.colLayout, but i cant use this as an correct parent.
It seems not be in the scope.
TypeError: Cannot read property 'tabStatus' of undefined
ObjForm.ui.qml
Item {
id: item1
width: 400
height: 400
property QtObject object
property Component compLayout
TabView {
id: tabView
anchors.fill: parent
Tab {
id: tabStatus
title: "status"
Loader {
id: tabStatusLoader
sourceComponent: compLayout
}
}
}
}
ObjForm.qml
ObjectViewForm {
id: objForm
anchors.fill: parent
object: someObj
compLayout: Component {
id: layoutComp
ColumnLayout {
id: colLayout
spacing: 2
}
}
onObjectChanged: {
// Here i want to add the someLabel Component to the ColumnLayout
someLabel.createObject(*PARENT*)
}
Component {
id: someLabel
Row {
property string text
property string label
spacing: 5
Label {
text: parent.label
}
Label {
text: parent.text
}
}
}
Does anyone know how to solve this or can make a better suggestion?
ok I've found a solution by myself. Instead to include the ColumnLayout into the Component, i've pulled it out and made an alias property to publish it. With this it was possible to add objects to my ColumnLayout. But the ColumnLayout got the wrong parent(objForm) instead of the Component.
A Component cant be a parent because a QQuickItem* is expected instead of a QObject* and additional can't include properties except for 'id'. Therefore a dummy Item was needed.
To reparent the ColumnLayout the Item needs a Component.onCompleted function where the parent will be set.
ObjectViewForm {
id: objForm
anchors.fill: parent
object: someObj
property alias componentLayout: colLayout
compLayout: Component {
id: layoutComp
Item {
id: dummy
Component.onCompleted: {
colLayout.parent = dummy
}
}
}
ColumnLayout {
id: colLayout
spacing: 2
}
The following code works and shows my items correctly, but I get the warning
qrc:/TableDelegate.qml:24: ReferenceError: name is not defined
I think it is because the ListView tries to access the model when it is empty and can not reference the item properties. I assume I am not doing to it correctly but I do not know how to do it better.
So my question is: how to get rid of the warning by doing it the right way?
TableDelegate.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.1
Item {
property color bgcolor: 'transparent'
property alias box: rowBox
height: 40
width: parent.width
Rectangle {
id: rowBox
anchors.fill: parent
color: bgcolor
RowLayout {
anchors.fill: parent
Rectangle {
id: tableNameColumn
color: 'transparent'
Layout.fillHeight: true
Layout.fillWidth: true
Text {
anchors.centerIn: parent
color: textcolor
text: name // <--- here is `name`
}
}
// More Columns ...
}
}
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
}
}
}
And I use it like this
TableView.qml:
// ...
ListModel {
id: model
}
ListView {
id: view
model: model
anchors.fill: parent
highlight: delegate_highlighted
highlightFollowsCurrentItem: true
delegate: delegate
}
Component {
id: delegate
TableDelegate {
bgcolor: 'transparent';
}
}
Component {
id: delegate_highlighted
TableDelegate {
bgcolor: 'lightsteelblue'
box.border.color: 'black'
box.radius: 3
}
}
// ...
You use a TableDelegate for the highlight. That is wrong.
The ListView creates 1 instance of the highlight item, that will be drawn as a background for the currently selected item, It may also move between items as transition when the current item changes. It should only be a rectangle or whatever you want to use.
In your example, the highlight item is a full delegate, that wants to access model data, which it cannot.
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.
I'm trying to make custom component for editable tables in QML, like this:
// BaseTableView.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
Item {
signal addActionPerformed()
signal editActionPerformed(int id)
signal deleteActionPerformed(int id)
property var model
ToolBar {
id: toolBar
anchors.left: parent.left
anchors.right: parent.right
Row {
ToolButton {
id: addButton
iconSource: "qrc:/icons/actions/add.png"
onClicked: addActionPerformed()
}
ToolButton {
id: editButton
enabled: false
iconSource: "qrc:/icons/actions/edit.png"
}
ToolButton {
id: deleteButton
enabled: false
iconSource: "qrc:/icons/actions/delete.png"
}
}
}
TableView {
id: tableView
model: parent.model
anchors.left: parent.left
anchors.right: parent.right
anchors.top: toolBar.bottom
anchors.bottom: parent.bottom
onCurrentRowChanged: {
editButton.enabled = currentRow !== null
deleteButton.enabled = currentRow !== null
}
}
}
and use this component in another file like this:
// Another.qml file
import QtQuick 2.3
import QtQuick.Controls 1.2
import "../common" // Here is BaseTableView.qml
BaseTableView {
TableViewColumn {
role: "id"
title: qsTr("Id")
}
TableViewColumn {
role: "object_expression"
title: qsTr("Expression")
}
}
So, problem is how i can pass table view columns from usage to underlying TableView?
I've tried to make property list in BaseTableView and assign a list of objects to this property in Aother.qml? but unsuccessfully.
Use default properties:
An object definition can have a single default property. A default property is the property to which a value is assigned if an object is declared within another object's definition without declaring it as a value for a particular property.
More relevant for your scenario:
You will notice that child objects can be added to any Item-based type without explicitly adding them to the children property. This is because the default property of Item is its data property, and any items added to this list for an Item are automatically added to its list of children.
Default properties can be useful for reassigning the children of an item. See the TabWidget Example, which uses a default property to automatically reassign children of the TabWidget as children of an inner ListView.
If you take a look at the TabWidget example that the last paragraph refers to, you should have all you need:
import QtQuick 2.0
Item {
id: tabWidget
// Setting the default property to stack.children means any child items
// of the TabWidget are actually added to the 'stack' item's children.
// See the "Property Binding"
// documentation for details on default properties.
default property alias content: stack.children
property int current: 0
onCurrentChanged: setOpacities()
Component.onCompleted: setOpacities()
function setOpacities() {
for (var i = 0; i < stack.children.length; ++i) {
stack.children[i].opacity = (i == current ? 1 : 0)
}
}
Row {
id: header
Repeater {
model: stack.children.length
delegate: Rectangle {
width: tabWidget.width / stack.children.length; height: 36
Rectangle {
width: parent.width; height: 1
anchors { bottom: parent.bottom; bottomMargin: 1 }
color: "#acb2c2"
}
BorderImage {
anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 }
border { left: 7; right: 7 }
source: "tab.png"
visible: tabWidget.current == index
}
Text {
horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter
anchors.fill: parent
text: stack.children[index].title
elide: Text.ElideRight
font.bold: tabWidget.current == index
}
MouseArea {
anchors.fill: parent
onClicked: tabWidget.current = index
}
}
}
}
Item {
id: stack
width: tabWidget.width
anchors.top: header.bottom; anchors.bottom: tabWidget.bottom
}
}
In cases like this, where you want to replicate something that is done by an item offered by Qt, it can also be helpful to take a look at the source code of what you're trying to replicate. However, the documentation is a bit easier to read. :)
I am working with QML and I want to add elements to SplitView dynamically eg. onMouseClick, but so far I didn't find the answer.
What I've found out so far is that the SplitView has it's default property set to it's first child's data property. So I guess I should try and add new dynamically created components with the parent set to that child (splitView1.children[0]). Unfortunately that doesn't work either. What is more the number of children of that first child is zero after the component has finished loading (seems like the SplitLayout's Component.onCompleted event calls a function that moves those children somewhere else). Thus the added children do not render (and do not respond to any of the Layout attached properties).
Please see the following code snippet:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
// Rectangle { //I want to add Rectangle to splitView1 like this one, but dynamicly eg.onMouseClick
// color: "blue"
// }
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length); // __contents is the SplitView's default property - an alias to the first child's data property
var newObject = Qt.createQmlObject('import QtQuick 2.1; Rectangle {color: "blue"}',
splitView1, "dynamicSnippet1"); //no effect
// var newObject = Qt.createQmlObject('import QtQuick 2.1; import QtQuick.Layouts 1.0; Rectangle {color: "blue"; width: 50; height: 50}',
// splitView1, "dynamicSnippet1"); //rectangle visible, but not in layout(?) - not resizeable
}
}
}
Is there any way I can make the dynamically created components render properly in the SplitView as the statically added ones?
It appears that the API does not provide support for dynamic insertion of new elements. Even if you do get it to work it would be a hack and might break with future releases. You may need to roll your own control to mimic the behavior you want. Ideally it should be backed by some sort of model.
As of QtQuick Controls 1.3, SplitView has an addItem(item) method.
you have to use the Loader for load dinamicaly objects. in onClicked handle you have to declare sourceComponent property to change the source of the Loader, something like this:
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
Loader {
id:rect
}
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length) // __contents is the SplitView's default property - an alias to the first child's data property
rect.sourceComponent = algo
}
}
Component {
id:algo
Rectangle {
anchors.fill: parent
color: "blue"
}
}
}
I saw the source code of SplitView, it calculate each split region when Component.onCompleted signal. So I think that is a key point. No matter how you do (insert, dynamic create). The region won't be reset after you insert a new region for split.