Qt QML: Get reference to object emitting a signal - qt

I have a screen with some rectangles which can contain text. The text content of these rectangles should be allowed to change through clicking on buttons in the screen where this component is used. The problem I am having is how to know in the screen which uses this component which instance is selected. I thought about solving this via emitting a signal, which transmits the id of the instance as reference, but it seems this does not work. How could this be accomplished? Here my custom rectangle component
Rectangle {
id: root
width: 50
height: 50
color: "#000000"
anchors.verticalCenter: parent.verticalCenter
border.color: "#555555"
property int value: 0
signal sendId(Item)
Text {
id: displayed_text
color: "#ffffff"
text: root.value
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 15
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
root.border.color="#222222"
root.sendId(root.id)
}
}
}
and here the file where other buttons should change the content of the custom component:
property Item selected: myRectangle
function changeSelected(value) {
selected.value=5
}
function setSelected(it) {
root.selected=it
}
MyRectangle {
id: myRectangle
Component.onCompleted: {
myRectangle.sendId.connect(tempNumber.setSelected)
}
}
MyRectangle {
id: myRectangle1
Component.onCompleted: {
myRectangle1.sendId.connect(tempNumber.setSelected)
}
}
MyRectangle {
id: myRectangle2
Component.onCompleted: {
myRectangle2.sendId.connect(tempNumber.setSelected)
}
}

root.sendId(root.id)
The id is not a regular property. Don't use it as such. The purpose of the id is to get you a reference, with which you can refer to a particular object, so all you really need is:
root.sendId(root)
And if root is your qml file root object, sendId(root) would work too as long as sendId is not shadowed, root members can be referenced directly, keep in mind this only applies to the root object, it won't work for a object that is a direct or indirect parent but not root.
It is recomended practice to abstain from giving everything an id - only use ids when you really need to reference a particular object and no other way exists.
Another thing you are missing is that unlike JS functions, you do have to provide some typing for a signal. You can still use var and pass anything, but usually it is more efficient to narrow down the scope. So you need type and identifier:
signal sendId(Item item)
This way you can access item in the signal handlers, so you can avoid the awkward imperative connection syntax, so instead you can simply:
MyRectangle {
onSendId: tempNumber.setSelected(item)
}
However, I'd say your design is not optional. Signals are supposed to be employed when you aim for generality and reuse. Your usage scenario is more specific, thus the usage of signals can be avoided altogether:
// Rect.qml
Rectangle {
width: 50
height: 50
color: manager.selected === this ? "red" : "blue"
MouseArea {
anchors.fill: parent
onClicked: manager.selected = parent
}
}
// main.qml
Window {
id: manager
visible: true
width: 600
height: 300
property Item selected: null
Row {
spacing: 2
Repeater {
model: 10
delegate: Rect {}
}
}
}
As the following example shows, you can directly access objects by id as long as they can be found down the object tree. The same applies to properties, however while the id will work for any object down the tree, properties will only work if they are declared in the root object of the particular qml file.

Related

How to reuse a nested listview's delegate?

I have a nested listview structure where the delegate listview will contain another listview. I would like the nested listview's delegate to refer to itself because the nested listview will contain the same type of item as itself, but this doesn't appear to work.
Component {
id: subSequenceComponent
ItemDelegate {
id: subSequenceItemDelegate
property var id: edit.id
ColumnLayout {
Text{
text: edit.name
}
ListView {
width: 180; height: 200
model: items.subModelFromId(subSequenceItemDelegate.id)
delegate: subSequenceComponent
}
}
}
}
This works:
Component {
id: subSequenceComponent
ItemDelegate {
id: subSequenceItemDelegate
property var id: edit.id
ColumnLayout {
Text{
text: edit.name
}
ListView {
width: 180; height: 200
model: items.subModelFromId(subSequenceItemDelegate.id)
delegate: Text{
text: edit.name
}
}
}
}
}
Is there a way to reuse the same delegate you are a part of?
It may be connected to this bug. Basically, QML has some checks that are supposed to prevent accidental infinite recursions, but they are not particularly well implemented, and trigger false positives even for scenario where nesting is intended and there is no danger of infinite recursion.
If that is the case, then you can trick that check by using an additional Loader that will load the component from a string, which will not catch the nesting recursion.

How to resize QML TableView column to its contents on app start?

I am using Qt 5.9.4 on Ubuntu 18.04.
When my application starts, I want to automatically adjust TableView's column size to its contents. A model have some data on start.
I know about the resizeColumnToContents function, but I have no idea where to call it.
onDataChange does not work in TableView: The QML engine says that this signal does not exist. But intellitype allows me to type it in the code.
How to accomplish that?
EDIT 18/09/18
If you use a StackView or else you can preload your TableView
// main.qml
Loader {
id: tableViewLoader
active: true
sourceComponent: TableView { id: tableView }
}
StackView {
id: stackView
initialItem: listViewLoader
function onContentReceived()
{
stackView.push(tableViewLoader);
tableViewLoader.item.resizeColumnsToContents()
}
function onContentClosed()
{
swipeView.pop()
}
}
EDIT 17/09/18
You are right Danil.
There is specified in TableView.qml that
Depending on how the model is populated, the model may not be ready when
TableView Component.onCompleted is called. In that case you may need to
delay the call to positionViewAtRow by using a \l {QtQml::Timer}{Timer}
For me this is working
Component.onCompleted: resizeColumnsToContentsTimer.start()
Timer {
id: resizeColumnsToContentsTimer
interval: 50
running: false
repeat: false
onTriggered: parent.resizeColumnsToContents()
}
You can also see this discussion about it
http://lists.qt-project.org/pipermail/interest/2016-June/023018.html
Maybe, you can call it in onModelChanged which called when you set your model (your model must be populated before).
onModelChanged: tableView.resizeColumnToContents()
Otherwise, you can use signals/slots when your data ready.
But beware with this function : if you have delegate you must specify implicitWidth in, or this is will not to work.
headerDelegate: Rectangle {
id: headerDelegate
height: 36
implicitWidth: textItem.implicitWidth + textItem.padding * 2
color: Style.lightColor
Text {
id: textItem
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
padding: 10
text: styleData.value
elide: Text.ElideRight
color: Style.darkColor
font.pixelSize: Style.bigFontPixelSize
}
}

Page Navigation in QML

I'm trying to implement the following GUI in QML and having trouble understanding how to properly navigate through different pages of the application.
There are 3 buttons in the main menu. When the user clicks on the 'actor' button the UI switches to 'actor view' where the user can toggle between Thumbnail view and List View. When the user clicks on one of the actors the UI switches to Actor Detail view: A view that has a movie view 'nested in it' which lists all the actors movies.
I'm trying to implement this using StackView.
So my StackView lives in the main menu screen (main.qml) when the user clicks one of the buttons the onClicked event pushes the correct view on to the stack.
ActorsView.qml consists of an internal StackView (Most likely a bad idea) and 2 buttons that switch between Thumb and Detail view. This is done by pushing either Thumb or Detail view onto the local stack.
DetailView.qml and ThumbView.qml function exactly the same though look different. Here is where I ran into trouble. I want the main view to be notified when a click event occurs in either Detail or Thumb view. So that it could (based on the event passed information) know what view push onto the main stack. For example when the user clicks on Actor1, the main menu could push 'actor detail view for actor 1' onto the stack.
Sadly I don't know how to 'catch' events that are firing in nested components in the parent element.
I've started playing around with QML and QT just a few weeks ago, and would be happy to hear that my approach is all wrong and that there is a much better way to achieve what I want. Sadly this is the only viable option I found this far.
main.qml:
ApplicationWindow {
title: qsTr("Hello World")
width: 1280
height: 720
visible: true
id: mainWindow
Component{
id: homeScreen
Rectangle{
height: 500
width: 500
color:"blue"
anchors.centerIn: mainWindow
Text {
anchors.centerIn: parent
text: qsTr("Home")
font.pixelSize: 40
}
}
}
Component{
id: actorsView
ActorsView{
view: stack
}
}
Component{
id: moviesView
MoviesView{
view: stack
}
}
ColumnLayout{
RowLayout{
Layout.fillWidth: true
Button{
text: "Back"
onClicked: stack.pop()
}
Button{
text: "actor view"
onClicked: stack.push(actorView)
}
Button{
text: "movie view"
onClicked: stack.push(moviesView)
}
}
StackView {
id: stack
initialItem: homeScreen
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ActorsView.qml:
Item {
property StackView view
Component {
id: actorDetailView
DetailView {
name: "actorDetailView"
text: "Actor"
}
}
Component {
id: actorThumbView
ThumbView {
name: "actorThumbView"
text: "Actor"
}
}
ColumnLayout {
RowLayout {
Text {
text: "Actor view"
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Button {
text: "Detail"
onClicked: internalStack.push(actorDetailView)
}
Button {
text: "Thumb"
onClicked: internalStack.push(actorThumbView)
}
Button {
text: "back"
onClicked: internalStack.pop()
}
Button {
text: "depth: " + internalStack.depth
}
}
StackView {
id: internalStack
initialItem: {
console.log(internalStack.depth)
internalStack.initialItem = actorThumbView
}
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ThumbView.qml:
Item {
property string name: "thumbView"
property string text
property int counter: 0
id:thumbView
signal thumbPressed (string pressedName)
GridLayout {
columnSpacing: 10
rowSpacing: 10
width: parent.width
Repeater {
model: 16
Rectangle {
width: 200
height: 300
color: "grey"
Text {
id: lable
text: text
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
var tag = lable.text
console.log("You have clicked " + tag)
thumbView.thumbPressed(tag)
}
}
Component.onCompleted: {
counter = counter + 1
lable.text = text + " " + counter
}
}
}
}
}
That's actually a common approach to structure a QML application, so no it's not all bad ! Nested StackViews are a powerful way to manage sub-content of a page, but surely add a level in your app structure. It's made easier by creating your own Page item, redefining the navigation and interaction as you wish.
There's different ways to handle signal in nested components. The easiest: call an identified item up in hierarchy. Local and parent elements in QML are accessible from their id directly, even if those are not in the same QML file. Which allowThis of course has the drawback of inducing coupling between your pages or components and the rest of your application.
ApplicationWindow {
id: mainWindow
function pushPage(page) {
stack.push(page)
}
function showActor(id) {
...
}
// ...
}
In your page simply...
MouseArea {
onClicked: {
mainWindow.showActor(index)
}
}
To achieve something more modular, you can rely StackView currentItem, signals, Connections and Binding elements to name a few, or implement an interface in QML and/or C++ to manage your navigation.
There's definitely a lot of possibilities depending on your goal architecture, trying & learning makes it perfect !

changing property of element from other qml file

I know that there is tons of topic similar like this, I try to implement answer from them and I still have no results.
I take some sample project from qt creator to play with this. I play with changing visibility of qml files ( treat every file as other screen). After lunching 3rd screen I want to make the second one invisible.
Here Is the code where I want change property in it:
MyLuncherList.qml
import QtQuick 2.0
Rectangle {
Item
{
id:ei
visible:false
clip: true
property url itemUrl
onItemUrlChanged:
{
visible = (itemUrl== '' ? false : true);
}
anchors.fill: parent
anchors.bottomMargin: 40
Rectangle
{
id:bg
anchors.fill: parent
color: "white"
}
MouseArea
{
anchors.fill: parent
enabled: ei.visible
//takes mouse events
}
Loader
{
focus:true
source: ei.itemUrl
anchors.fill: parent
}
}
}
and here is the code where I want to make a action
View2.qml
import QtQuick 2.0
Rectangle {
width: 100
height: 62
Text
{
text: "second screen"
}
MyLuncherList
{
id:luncherList
}
Rectangle
{
x: 50
y: 30
width: 120
height: 60
color: "red"
MouseArea
{
anchors.fill: parent
id: mouseAreaWhichHides
onClicked:
{
luncherList.ei.itemUrl = '';
}
}
}
}
and I got the error: qrc:///View2.qml:29: TypeError: Type error
which point on this line luncherList.ei.itemUrl = '';
Type error says that I make some mismatch with Type, but I’m not even sure, if I do this access process in properly way, so I’m asking how to change property of
ei.itemUrl
from
View2.qml
in working way.
The ei element won't be available directly in other QML file.
You can use an alias to do it.
property alias callUrl: ei.itemUrl
and call it from other QML file
luncherList.callUrl='file:///home/user/file.jpg'

Unable to access QML variable / id globally

I have QtQuick 1.0
I use the following code:
Rectangle {
Component {
id: appDelegate
MouseArea{
id:myMouseArea
hoverEnabled: true
onClicked:{
onClicked: load.source = page;
}
}
Loader {
id: load
}
}
GridView {
id: view
// I am unable to access myMouseArea here.
highlight: myMouseArea.containsMouse ? appHighlight : !appHighlight
delegate: appDelegate
}
}
It gives me the following error:
ReferenceError: Can't find variable: myMouseArea
/usr/lib/i386-linux-gnu/qt4/bin/qmlviewer exited with code 0
I don't know if the details I provided are sufficient, please let me know if theres anything else I am missing.
I am using this code as an example:
http://docs.knobbits.org/qt4/declarative-modelviews-gridview-qml-gridview-example-gridview-example-qml.html
You cannot access myMouseArea because it's created inside delegate context. You cannot access delegate other then currentItem. But you can freely access view inside the context of delegate, to set currentIndex to attached property index.
This is a corrected code:
Rectangle {
width: 360
height: 360
Component { // It must be a component, if we want use it as delegate
id: appDelegate
// its not possible to have more than one element inside component
Rectangle
{
// need to set size of item, anchors wont work here
// could use view.cellWidth and view.cellHeight to keep it DRY
width: 96
height: 66
color: "green" // color only to see the place of MouseArea
MouseArea {
id:myMouseArea
anchors.fill: parent // this setup the size to whole rectangle
// it this item have the size 0,0 it will simple do not work
hoverEnabled: true
onEntered: {
// we know the mouse is inside this region
// setting this value will show the highlight rectangle
view.currentIndex = index;
}
onClicked:{
onClicked: load.source = page;
}
}
Loader {
// this is not needed but it's wise to not keep zero size
anchors.fill: parent
id: load
}
}
}
GridView {
id: view
// the size of GridView must be set,
// as otherwise no delegate will not show
anchors.fill: parent
anchors.margins: 5
cellWidth: 100
cellHeight: 70
// Rectangle will act as a border.
// Size and position is set by GridView
// to the size and position of currentItem.
// This is no a item, this makes a Component
// as highlight property needs one.
// You can create a Component like appDelegate.
highlight : Rectangle {
border.width: 2
border.color: "blue"
}
// some ListModel to setup the page variable inside delegate context
model: ListModel {
ListElement { page: "test1.qml"; }
ListElement { page: "test2.qml"; }
ListElement { page: "test3.qml"; }
}
delegate: appDelegate
}
}

Resources