changing property of element from other qml file - qt

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'

Related

How to recreate Android's Pull to Refresh icon in QML?

This is the pull to refresh icon used to refresh views in Android.
I've been trying to bring that to qml but it is not so easy.
There are so many transitions that it quickly becomes very complex.
How difficult this should be to recreated in QML?
Is using canvas the better solution?
As i have first seen, the swipe brings down the arrow in a different pace of the swipe, while the arrow rotates. If this arrow comes from a canvas how can it relate to outside events, that is the swipe?
I used something like this:
//
// Slot called when the flick has started
//
onFlickStarted: {
refreshFlik = atYBeginning
}
//
// Slot called when the flick has finished
//
onFlickEnded: {
if ( atYBeginning && refreshFlik )
{
refresh()
}
}
It seems to work as expected and it is easy to implement
The problem is that Flickable and the derived ListView don't really provide any over-drag or over-shoot information in the cases where the visual behavior is disabled.
If dragging the visual over the beginning is not a problem for you, you can simply use the negated value of contentY which goes into the negative if the view is dragged before its beginning.
The only solution I can think of to not have any visual over-dragging but still get the over-drag information in order to drive your refresher is to set the view interactive property to false, and put another mouse area on top of that, and redirect drags and flicks manually to the now non-interactive view.
That last part might sound complex, but it isn't that complex, and I happen to know for a fact that it works well, because I have already used this approach and the source code is already here on SO.
So once you have access to the mouse area that controls the view, you can track how much you are in the negative, and use that information to drive the logic and animation of the refresher.
The notable difference between the implementation in the linked answer and what you need is that the linked answer has the mouse area in each delegate, due to the requirements of the specific problem I wanted to solve. You don't need that, you only need one single mouse area that covers the view.
I did like this recently.
Basically I use the position of a ScrollBar and if it goes negative I show a spinner and refresh. So I don't need to mess with the flick stuff.
import QtQuick.Controls 6.0
import QtQuick 6.0
ListView {
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
property bool negativescroll: scrollbar.position < 0
onNegativescrollChanged: {
if (spinner.visible) {
refresh()
}
spinner.visible = !spinner.visible
}
BusyIndicator {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
visible: false
running: visible
id: spinner
}
width: 180; height: 200
model: model
delegate: Text {
text: name + ": " + number
}
ListModel {
id: model
ListElement {
name: "Bill Smith"
number: "555 3264"
}
ListElement {
name: "John Brown"
number: "555 8426"
}
ListElement {
name: "Sam Wise"
number: "555 0473"
}
}
}
I came to a simpler solution based on dtech's experience involving multiple Flickable elements, which basically consists on filling the Flickable with a MouseArea, setting its boundsBehavior property to Flickable.StopAtBounds, and from there, if it's at the top, do things based on mouseY values.
The better approximation i could get is in the following code. A possible drawback is that diagonal swiping also counts as a refresh intention. It could be improved with GestureArea, but i'm too lazy to get my hands on this at the moment.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
ApplicationWindow {
property real mm: Screen.pixelDensity
property real margins: 2 * mm
id: mainWindow
visible: true
width: 60 * mm
height: 120 * mm
title: qsTr("Hello World")
ListModel {
id: myModel
Component.onCompleted: {
for(var i = 0; i <= 100; ++i) {
myModel.append({num: i})
}
}
}
ListView {
id: view
boundsBehavior: Flickable.StopAtBounds
interactive: true
anchors.fill: parent
model: myModel
spacing: 4
delegate: Rectangle {
width: parent.width
height: 25 * mm
border.color: 'red'
Text {
id: name
text: num
anchors.centerIn: parent
}
}
Rectangle {
signal follow
id: swatch
width: 15 * mm
height: width
radius: width / 2
color: 'lightgray'
anchors.horizontalCenter: parent.horizontalCenter
y: - height
}
MouseArea {
property int mouseYSart
property int biggerMouseY
anchors.fill: view
onPressed: {
mouseYSart = mouseY
biggerMouseY = 0
}
onMouseYChanged: {
if(view.contentY == 0) {
var currentMouseY = mouseY
if(currentMouseY > biggerMouseY) {
biggerMouseY = currentMouseY
swatch.y += 1
}
if(currentMouseY < biggerMouseY) {
biggerMouseY = currentMouseY
swatch.y -= 1
}
}
}
onReleased: swatch.y = - swatch.height
}
}
}

Open and close additional window (QML)

Currently I have a window openning in the following way:
property variant win
Button {
id: testButton
MouseArea {
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.show();
}
}
}
Is it ok to create a window like this or there is a better way to do it (from QML, not from C++)?
When I close this additional window (just by clicking "x" button), I want to connect it to another event (for example, changing color of the button). How to do it?
Thanks.
It is usually nicer to have it more declarative. If you want your button to only open one window, the usage of a Loader might be right for you.
I think this is what you want, as you store it in one variable, and if you click the button multiple times, you would lose access to your instance. If you need a larger amount of Windows created by the same Button, you might use a ListModel and a Instantiator to create the instances.
With the Loader this might look like this:
Button {
id: ldbutton
onClicked: winld.active = true
Rectangle {
id: ldindic
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: height
color: winld.active ? 'green' : 'red'
}
Loader {
id: winld
active: false
sourceComponent: Window {
width: 100
height: 100
color: 'green'
visible: true
onClosing: winld.active = false
}
}
}
In this code is also already the answer to your second question: The signal you are looking for is called closing - connect to it to do what ever is necessary.
In the case of the Loader it is necessary to unload the window, so it can be loaded again later, maybe. If you have the window created by a Instantiator, you need to remove the corresponding index from the Instantiator's ListModel.
This might look like this:
Button {
id: rpbutton
onClicked: rpmodel.append({})
text: 'Open Windows ' + rpmodel.count
ListModel {
id: rpmodel
}
Instantiator { // from QtQml 2.0
model: rpmodel
delegate: Window {
width: 100
height: 100
color: 'blue'
visible: true
onClosing: rpmodel.remove(index)
}
}
}
In your code you could connect to it, either by using a Connection-object, that connects to your property win, or by changing the JS onClicked like so:
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.closing.connect(function() { console.log('do something') })
win.show();
}

Qt QML: Get reference to object emitting a signal

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.

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

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