QML connect signal to function - qt

I was getting my self familiar with QML in QT5. So i was trying to make the code from the follwing video: video
In this video the code:send.connect(target.receive()); is used. But This part does not work. I am getting the error:
qrc:/Receiver.qml:8: Error: Cannot assign [undefined] to QString
Is this method deprecated or am i doing something wrong?
main.qml
Window {
visible: true
width: 640
height: 480
title: qsTr("Jurassic World")
Background {
id: background
anchors.fill: parent
target: sender
Sender {
id: sender
y: 145
displayText: "Sender"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 50
target: receiver
}
Receiver {
id: receiver
x: 376
y: 158
displayText: "Receiver"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 50
width: sender.width
}
}
}
Receiver.qml
Cirkel {
id: receiveButton
function receive(value)
{
displayText = value
clicknotify.running = true
}
SequentialAnimation on buttonColor
{
id: clicknotify
running: false
ColorAnimation {
from: "#afc4cb"
to: "red"
duration: 200
}
ColorAnimation {
from: "red"
to: "#afc4cb"
duration: 200
}
}
}
Sender.qml
Cirkel {
id: sendButton
property int counter: 0
property Receiver target: null
signal send(string value)
onTargetChanged: {
send.connect(target.receive());
}
MouseArea {
anchors.fill: parent
onClicked: {
counter++
parent.send(counter)
displayText = counter
}
onPressed: parent.buttonColor = "green"
onReleased: parent.buttonColor = "#afc4cb"
}
}
Question:
How can i link a signal from one qml to a other qml function?

You write:
send.connect(target.receive());
where you call target.receive() and try to connect its return value to send.
What you want is:
send.connect(target.receive);
where you connect send to the function receive itself.
Recommendation:
Also try the declarative way, using a Connection-object.

I'd say, in Sender.qml just do:
signal send(int value)
...
sendButton.send(counter)
and in main.qml just do
Sender {
id: sender
...
onSend: receiver.receive(value)
}
That's all. No need for the whole target assignment.
And by the way, you have a data type issue in the receive function (integer to string)
Fix with this:
function receive(value)
{
displayText = "" + value
clicknotify.running = true
}
In general, a signal in QML can be handled as a function by adding "on" and uppercase first letter.
For example, a Button exposes the clicked signal, and when you handle it you use onClicked

Related

QML disable button after other button_click event and enable again after timer timeout

I would disable button after other button_click event and enable again after timer timeout. I tried with Timer and States with PropertyChanged, but it doesn't work.
How I can send a qml signal to reload/refresh the page?
When i click on id_button_add I would disable Id_button_edit and start a timer for 3 seconds. When the timer timeouts I enable already the id_button_edit.
Here is my code.
import QtQuick 2.0
import QtQuick.Layouts 1.3
import "../items"
import ch.example 1.0
Item {
id: id_root
z: 2
property bool prepare_delete: false
property string button_edit_enable_state: "enabled"
anchors.left: parent ? parent.left : undefined
anchors.right: parent ? parent.right : undefined
Connections {
target: id_root.ListView.view
onCurrentIndexChanged: {
prepare_delete = false
}
}
function update_remove_enable() {
id_button_remove.button_enabled = ui_resident_model.sourceModel.rowCount() > 1
}
Component.onCompleted: {
ui_resident_model.sourceModel.onRowsInserted.connect(update_remove_enable)
ui_resident_model.sourceModel.onRowsRemoved.connect(update_remove_enable)
update_remove_enable()
}
Rectangle {
anchors.fill: parent
color: "transparent"
border {
color: touchpanel_style.foreground_color
width: touchpanel_style.line_width
}
}
RowLayout {
anchors.left: parent.left
anchors.margins: touchpanel_style.margin
anchors.verticalCenter: parent.verticalCenter
.
.
.
.
}
RowLayout {
id: id_content
anchors.right: parent.right
anchors.margins: touchpanel_style.margin
anchors.verticalCenter: parent.verticalCenter
state: button_edit_enable_state
states: [
State {
name: "enabled"
PropertyChanges { target: id_button_edit; enabled: true }
},
State {
name: "disabled"
PropertyChanges { target: id_button_edit; enabled: false }
}
]
Timer {
id: serviceListItemTimer
interval: 3000
running: false
repeat: false
onTriggered: {
console.log("onTriggered")
button_edit_enable_state = "enabled"
}
}
IconButton {
id: id_button_edit
objectName: "button_edit"
Layout.fillHeight: true
width: height
icon_factor: 1.35
icon_source: "../icons/edit.png"
onButtonClick: {
prepare_delete = false
loader.source = "../service_menu/ResidentEdit.qml";
loader.item.onCancel.connect(cancel_edit)
loader.item.onTimeout.connect(timeout)
loader.item.model = id_root.ListView.view.currentItem.resident_model
}
function cancel_edit() {
loader.source = ""
}
}
IconButton {
id: id_button_add
objectName: "button_add"
Layout.fillHeight: true
width: height
icon_factor: 1.35
icon_source: "../icons/resident_add.svg"
onButtonClick: {
console.log("btn_add")
button_edit_enable_state = "disabled"
serviceListItemTimer.running = true
var index = id_root.ListView.view.currentIndex;
id_root.ListView.view.model.createItem(index);
set_index(index);
}
}
}
}
If you use enabled property of the item, you can disable the button.
For example:
IconButton {
id: id_button_edit
...
enabled: true
...
onButtonClick: {
id_button_edit.enabled = false;
...
}
Then the timer expire, you return to enable the button with id_button_edit.enabled = true.
Maybe an another solution can be the signal.
Here is more info:
https://doc.qt.io/qt-5/qtqml-syntax-signals.html
You can do it in a simple an declarative way by binding the enabled property of your edit button to the running property of the timer, no need to deal with states and such:
IconButton {
id: id_button_add
// ...
onButtonClick: {
serviceListItemTimer.start();
var index = id_root.ListView.view.currentIndex;
id_root.ListView.view.model.createItem(index);
set_index(index);
}
}
IconButton {
id: id_button_edit
enabled: !serviceListItemTimer.running
// ...
}
I found a solution --> bind qml property with cpp model and emit also in cpp module valueChanged() signal.
see http://imaginativethinking.ca/bi-directional-data-binding-qt-quick/

Closing qml dialog properly

I've been playing around with dialogs and there is something that bothers me.
I have the following code:
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: dlgTest.open()
}
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: dlgTest.close()
text: "Close"
}
}
}
}
When I open it the first time I add some text to the TextField and then I close it. However, If I open it again, the text will still be there. What I want is to "reset" the dialog to it's original state when I opened it the first time (with an empty TextField). It seems that calling the method "close" is exactly the same as changing visible to false.
Is there a way of doing this "reset"?
I have an other dialog with a lot of controls and it's annoying having to restore everything manually.
In your code you create the Dialog once, as a child of the ApplicationWindow.
To 'reset' it, you have two options:
Have a reset-function, that you call, and restores everything. You can use this to set it up in the first place as well
Create a new Object with everything set in place.
For the latter you can either use JavaScript Dynamic Object Creation or a Loader.
JavaScript Dynamic Object Creation:
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
var d = diaComp.createObject(null)
d.open()
}
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
dlgTest.destroy()
}
text: "Close"
}
}
}
}
However, as you destroyed the Object, the contents of your properties are lost, and you can't access them anymore. So you need to make sure, to copy them (not bind them) to some property that is not destroyed, first.
With the Loader you have the posibility to unload the Dialog right before you load it again, which basically resets it. But until you unloaded it, you can still access it's values, as you can see in the Buttons onClicked-handler.
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
console.log((dlgLoad.status === Loader.Ready ? dlgLoad.item.value : 'was not loaded yet'))
dlgLoad.active = false
dlgLoad.active = true
dlgLoad.item.open()
}
}
Loader {
id: dlgLoad
sourceComponent: diaComp
active: false
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
property alias value: tfText.text
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
}
text: "Close"
}
}
}
}
Of course, you could also copy the values from the Loader's item as well, and then unload it earlier, to possible free the memory.
But if the Dialog is frequently (most of the time) shown, it might be the wisest to avoid the creation and destruction of the objects, by reusing it and resetting it manually.

How to limit the size of drop-down of a ComboBox in QML

I am using a ComboBox in QML and when populated with a lot of data it exceeds my main windows bottom boarder. From googling I have learned that the drop-down list of a ComboBox is put on top of the current application window and therefore it does not respect its boundaries.
Ideally I would want the ComboBox to never exceed the main applications boundary, but I can not find any property in the documentation.
A different approach would be to limit the number of visible items of the drop-down list so that it do not exceed the window limits for a given window geometry. I was not able to find this in the documentation either and I have run out of ideas.
Take a look to the ComboBox source code, the popup is of a Menu type and it doesn't have any property to limit its size. Moreover, the z property of the Menu is infinite, i.e. it's always on top.
If you Find no way but to use the ComboBox of Qt you can create two models one for visual purpose, I will call it visual model, you will show it in your ComboBox and the complete one , it will be the reference model. Items count in your VisualModel wil be equal to some int property maximumComboBoxItemsCount that you declare . you'll need o find a way that onHovered find the index under the mouse in the visualmodel if it's === to maximumComboBoxIemsCount you do visualModel.remove(0) et visualModel.add(referenceModel.get(maximum.. + 1) and you'll need another property minimumComboBoxIemsCount, same logic but for Scroll Up , I dont know if it will work. but it's an idea
I think there is no solution using the built-in component and you should create your own comboBox. You can start from the following code.
ComboBox.qml
import QtQuick 2.0
Item {
id: comboBox
property string initialText
property int maxHeight
property int selectedItem:0
property variant listModel
signal expanded
signal closed
// signal sgnSelectedChoice(var choice)
width: 100
height: 40
ComboBoxButton {
id: comboBoxButton
width: comboBox.width
height: 40
borderColor: "#fff"
radius: 10
margin: 5
borderWidth: 2
text: initialText
textSize: 12
onClicked: {
if (listView.height == 0)
{
listView.height = Math.min(maxHeight, listModel.count*comboBoxButton.height)
comboBox.expanded()
source = "qrc:/Images/iconUp.png"
}
else
{
listView.height = 0
comboBox.closed()
source = "qrc:/Images/iconDown.png"
}
}
}
Component {
id: comboBoxDelegate
Rectangle {
id: delegateRectangle
width: comboBoxButton.width
height: comboBoxButton.height
color: "#00000000"
radius: comboBoxButton.radius
border.width: comboBoxButton.borderWidth
border.color: comboBoxButton.borderColor
Text {
color: index == listView.currentIndex ? "#ffff00" : "#ffffff"
anchors.centerIn: parent
anchors.margins: 3
font.pixelSize: 12
text: value
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.height = 0
listView.currentIndex = index
comboBox.selectedItem = index
tools.writePersistence(index,5)
comboBoxButton.text = value
comboBox.closed()
}
}
}
}
ListView {
id: listView
anchors.top: comboBoxButton.bottom
anchors.left: comboBoxButton.left
width: parent.width
height: 0
clip: true
model: listModel
delegate: comboBoxDelegate
currentIndex: selectedItem
}
onClosed: comboBoxButton.source = "qrc:/Images/iconDown.png"
Component.onCompleted: {
var cacheChoice = tools.getPersistence(5);
listView.currentIndex = tools.toInt(cacheChoice)
selectedItem = listView.currentIndex
comboBoxButton.text = cacheModel.get(selectedItem).value
}
}
ComboBoxButton.qml
import QtQuick 2.0
Item {
id: container
signal clicked
property string text
property alias source : iconDownUp.source
property string color: "#ffffff"
property int textSize: 12
property string borderColor: "#00000000"
property int borderWidth: 0
property int radius: 0
property int margin: 0
Rectangle {
id: buttonRectangle
anchors.fill: parent
color: "#00000000"
radius: container.radius
border.width: container.borderWidth
border.color: container.borderColor
Image {
id: image
anchors.fill: parent
source: "qrc:/Images/buttonBackground.png"
Image {
id: iconDownUp
source: "qrc:/Images/iconDown.png"
sourceSize.height:20
sourceSize.width: 20
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
id:label
color: container.color
anchors.centerIn: parent
font.pixelSize: 10
text: container.text
font.bold: true
}
MouseArea {
id: mouseArea;
anchors.fill: parent
onClicked: {
container.clicked()
buttonRectangle.state = "pressed"
startTimer.start()
}
}
Timer{
id:startTimer
interval: 200
running: false;
repeat: false
onTriggered: buttonRectangle.state = ""
}
states: State {
name: "pressed"
when: mouseArea.pressed
PropertyChanges { target: image; scale: 0.7 }
PropertyChanges { target: label; scale: 0.7 }
}
transitions: Transition {
NumberAnimation { properties: "scale"; duration: 200; easing.type: Easing.InOutQuad }
}
}
}
I've used it in some software of mine, hence it is possible that It could not work "out of the box". I use it like this:
ComboBox{
id:cacheChoice
initialText: "None"
anchors.top: baseContainer.top
anchors.topMargin: 2
anchors.right: baseContainer.right
maxHeight: 500
listModel: cacheModel
onExpanded: {
cacheChoice.height = 500
}
onClosed: {
cacheChoice.height = 20
}
}
In case you are working with ComboBox from Qt Quick Controls 2, here's the source code for it:
https://github.com/qt/qtquickcontrols2/blob/5.12/src/imports/controls/ComboBox.qml
Based on that, this override of the behavior works to limit the height to something reasonable:
myComboBox.popup.contentItem.implicitHeight = Qt.binding(function () {
return Math.min(250, myComboBox.popup.contentItem.contentHeight);
});
It is possible to access the hidden MenuStyle within the ComboBoxStyle component. There you can use all the things and hidden things you have within a MenuStyle, including its maximum height.
The thing looks roughly like this.
Not pretty but it works well enough.
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
ComboBox {
id: comboBox
style: ComboBoxStyle {
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: 400
__menuItemType: "comboboxitem" //not 100% sure if this is needed
}
}
As it came up resonantly in our team, here is a updated version of the idea shown above. The new version restricts the size automatically to the size of your application.
ComboBox {
id: root
style: ComboBoxStyle {
id: comboBoxStyle
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: Math.max(55, //min value to keep it to a functional size even if it would not look nice
Math.min(400,
//limit the max size so the menu is inside the application bounds
comboBoxStyle.control.Window.height
- mapFromItem(comboBoxStyle.control, 0,0).y
- comboBoxStyle.control.height))
__menuItemType: "comboboxitem" //not 100% sure if this is needed
} //Component __dropDownStyle: MenuStyle
} //style: ComboBoxStyle
} //ComboBox

Qml: Adding content dynamically to a SequentialAnimation

I have an Qml component with a SequentialAnimation containing a static sequence of PropertyAction components:
SequentialAnimation {
id: anim
running: true
PropertyAction { target: icon; property: "iconid"; value: propStore.anim1 }
PropertyAction { target: icon; property: "iconid"; value: propStore.anim2 }
PropertyAction { target: icon; property: "iconid"; value: propStore.anim3 }
}
This accomplishes that a specific icon is animated. However now I'd like to make it a little bit dynamic by building the sequence dynamically. The reason is that the propStore isn't under my control, and users adding new images to the animation sequence require me to make changes to the Qml :(
How should I go about doing this?
My first thought was to dynamically add components to anim.animations, but that doesn't work (it seems to be a read-only property of SequentialAnimation.)
My next thought was to add a ListModel to the outer component, and in its Component.onCompleted slot I append objects of the shape { image: "animN" } (I get the "animN" strings using introspection on propStore.) Then I use the ListModel to populate a Repeater. However, the SequentialAnimation doesn't seem to accept a Repeater object.
You can't append directly to anim.animations, but you can reaffect it to a new value. Build a JS array from anim.animation, append a dynamically created animation to it, then reaffect it to anim.animations.
Here's a simple example.
Component
{
id: component
SequentialAnimation
{
id: seq
property string color
PropertyAction {target: rect; property: "color"; value: seq.color}
PauseAnimation { duration: 500 }
}
}
function addAnim(color)
{
var listAnim = []
for(var i=0; i<anim.animations.length; i++)
listAnim.push(anim.animations[i])
var temp = component.createObject(root, {"color":color})
listAnim.push(temp)
anim.animations = listAnim
}
Rectangle
{
id: rect
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: row.top
anchors.margins: 40
border.width: 1
SequentialAnimation
{
id: anim
}
}
Row
{
id: row
anchors.bottom: parent.bottom
anchors.bottomMargin: 50
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Button {
text: qsTr("Play")
onClicked: anim.start()
}
Repeater
{
model: ["red", "green", "blue", "cyan", "magenta", "yellow"]
Button {
text: qsTr("Add %1").arg(modelData[0])
onClicked: addAnim(modelData)
}
}
}

QML: how to fire an event on item param chenge (and pass to it its value)?

I have an item with value param. I wonder how to catch its change event?
Say having a RectComp.qml:
Item{
property alias currentX: rect.x
Rectangle {
id: rect
x: 617
y: 450
}
}
handle its currentX change from application that creates its instance say how to
Rectangle {
id: host
x: 617
y: 450
RectComp{ id: MyRC}
OnMyRCcurrentXChange(int){log("hello!")}
}
As per Qt documentation "Adding a property to an item automatically adds a value changed signal handler to the item."
So your code will changes to following
Rectangle {
id: host
x: 617
y: 450
RectComp{ id: MyRC
onCurrentXChanged:{
console.log("Hello");
}
}
}
Or you can use Connection element
Connections {
target: MyRC
onCurrentXChanged: {
console.log("Hello");
}
}

Resources