How to propertly connect signal in Loader's item? - qt

I want connect one signal from QObject to various pages, loaded by the "Loader" qml element. My problem similar Dead QML elements receiving signals? but loaded items destroyed before calling the "onDestruction" method.
For example below, if switch from page1 to page2 in console writed:
"QML: Loading status: 1 Item: QDeclarativeRectangle(0x8dcd408, "page2")
QML Item: Loaded QDeclarativeRectangle(0x8dcd408, "page2") 1
qrc:/page1.qml:12: TypeError: Result of expression 'parent' [null] is not an object.
qrc:/page1.qml:15: ReferenceError: Can't find variable: page1text"
every second. So there can't disconnect from signal because parent object is destroyed.
How to handle signals from QObject (root) in loaded items? or How to disconnect signal from unloaded page?
main.qml
import QtQuick 1.1
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
Item {
width: parent.width
height: parent.height/2
anchors.top: parent.top
Loader {
id: pageLoader
objectName: "pageLoader"
anchors.fill: parent
anchors.centerIn: parent
signal textMsg(variant params)
onStatusChanged: console.log("QML: Loading status: ", status, " Item: ", item)
onLoaded: { console.log("QML Item: Loaded",item,status); }
}
}
states: [
State {
name: "page1"
PropertyChanges { target: pageLoader; source: "qrc:/page1.qml"}
}
,State {
name: "page2"
PropertyChanges { target: pageLoader; source: "qrc:/page2.qml"}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: pageLoader.textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
page1.qml
import QtQuick 1.1
Rectangle {
id: page1
objectName: "page1"
color: "yellow"
Component.onCompleted: {
parent.textMsg.connect(msgHandler);
}
Component.onDestruction: {
parent.textMsg.disconnect(msgHandler);
}
function msgHandler(params) {
page1text.text += " "+params.msg2page1;
}
Text {
id: page1text
anchors.fill: parent
wrapMode: Text.WordWrap
text: "page1"
}
}
page2.qml
import QtQuick 1.1
Rectangle {
id: page2
objectName: "page2"
color: "red"
}

That's nicely described in Loader documenation. It reads:
Any signals emitted from the loaded item can be received using the Connections element.
There is also an example, I copy it below for the sake of clarity:
// Application.qml
import QtQuick 1.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
onMessage: console.log(msg)
}
}
// MyItem.qml
import QtQuick 1.0
Rectangle {
id: myItem
signal message(string msg)
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.message("clicked!")
}
}
Clearly, if item is destroyed, any signal handlers are ignored until the target is recreated again.

My answer is: Don't use the "Loader", create child object by JS and destroy it as no needed, for example:
main.qml
import QtQuick 1.1
import "qrc:/pageloader.js" as Pageloader
Rectangle {
id: root
objectName: "root"
width: 360
height: 360
state: "page1"
color: "white"
signal textMsg (variant params)
states: [
State {
name: "page1"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page1.qml");}
}
,State {
name: "page2"
StateChangeScript{ script: Pageloader.createPageObject("qrc:/page2.qml");}
}
]
Timer {
// simulate signals from QObject
interval: 1000; running: true; repeat: true
onTriggered: textMsg({"msg2page1":"test","msg2page2":"test"})
}
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "yellow"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 1"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page1";
}
}
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width/2
height: parent.height/2
border {
color: "black"
width: 1
}
color: "red"
Text{
anchors.fill: parent
anchors.centerIn: parent
text: "Set Page 2"
}
MouseArea {
anchors.fill: parent
onClicked: {
root.state = "page2";
}
}
}
}
pageloader.js
var component;
var sprite;
function createPageObject(path) {
if(sprite){
console.log("sprite.destroy() ",typeof sprite);
sprite.destroy();
console.log("component.destroy() ",typeof component);
component.destroy();
}
component = Qt.createComponent(path);
if (component.status === Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(root);
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else{
if (component.status === Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}else{
console.log("Component status changed:", component.status);
}
}
}
page1.qml and page2.qml not changed.

I got it. My setup:
qml file to display ListViews
Several qml files defining Listviews, each takes a different column of different SQL tables. The model comes from C++
So here is the shortened code:
Dialog {
id: dialog
width: 1000; height: 400
property Component listViewItem
signal newDatabaseEntry( string text ) [1]
contentItem: Rectangle {
[...]
TextInputWithButton { [3]
id: newRecords
onInputAccepted: { newDatabaseEntry( text ) } [1]
}
}
[...]
Loader {
id: listViewPlaceholder
anchors.fill: parent
sourceComponent: dialog.listViewItem
onLoaded: {
if( typeof listViewPlaceholder.item.insertRecord === "function" )
// newRecords.inputAccepted.connect( listViewPlaceholder.item.insertRecord ) [1]
dialog.newDatabaseEntry.connect( listViewPlaceholder.item.insertRecord ) [2]
}
The above code is the general view of ListViews. The signal roundtrip [1] is necessary, otherwise no data is passed. How to chain signals is described here:
http://doc.qt.io/qt-5/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals
The input button [3] delivers the confirmed data to be inserted into the db.
A ListView passed to the above function looks like this:
DialogSqlSingleColumnEdit {
listViewItem: ListView {
function insertRecord( text ) {
console.log( "done:" + text )
sqlModel.insertRecord( text )
}
[...]
The insertRecord is called forwards the text to the sql-C++ model.

Related

How to use Qt MediaDevices type to switch AudioOutput device

I think I'm missing something here, but I can't figure out how to set a AudioOutput device from MediaDevices audioOutputs list with the QML Components
audioOutput: AudioOutput {
id: playerOutput
device: mediaDevices.defaultAudioOutput
onDeviceChanged: {
console.log("Output device changed " + device)
}
Component.onCompleted: {
console.log(mediaDevices.audioOutputs)
}
}
MediaDevices {
id: mediaDevices
}
The 2 devices are showing:
QAudioDevice(2, Built in earpiece (IN2023), false, Output),
QAudioDevice(3, Built in speaker (IN2023), false, Output)
but I don't understand how to change it, I've tried playerOutput.setDevice() description and id but it wants a QAudioDevice (it only provides description, id and mode, the documentation for both is very vague:
This property can be used to select an output device from the
QtMultimedia::MediaDevices::audioOutputs() list.
https://doc.qt.io/qt-6/qml-qtmultimedia-mediadevices.html
https://doc.qt.io/qt-6/qml-qtmultimedia-audiooutput.html#device-prop
Any help would be appreciated!
Thanks
You can assign an object from mediaDevices.audioOutputs to the devices property of your AudioOutput.
playerOutput.device = mediaDevices.audioOutputs[index]
where index is a valid index in the audioOutputs list.
import QtQuick
import QtQuick.Window
import QtMultimedia
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello AudioOutput")
MediaDevices {
id: mediaDevices
}
MediaPlayer {
id: mediaPlayer
source: "qrc:/Test_ogg_mp3_48kbps.wav"
audioOutput: AudioOutput {
id: playerOutput
device: mediaDevices.defaultAudioOutput
onDeviceChanged: { console.log("Output device changed " + device) }
Component.onCompleted: { console.log(mediaDevices.audioOutputs) }
}
}
component MediaPlayerButton : Rectangle {
id: button
property alias text: label.text
property bool active: false
signal clicked
width: 100; height: 40; radius: 10
color: button.active ? "tomato" : "ghostwhite"
border.color: "gainsboro"
border.width: buttonMouseArea.containsMouse ? 4 : 2
Text {
id: label
anchors.centerIn: parent
font.pointSize: 18
}
MouseArea {
id: buttonMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: button.clicked()
}
}
Column {
spacing: 10
anchors.centerIn: parent
Row {
spacing: 10
MediaPlayerButton {
text: qsTr("Play")
active: mediaPlayer.playbackState === MediaPlayer.PlayingState
onClicked: { mediaPlayer.play() }
}
MediaPlayerButton {
text: qsTr("Pause")
active: mediaPlayer.playbackState === MediaPlayer.PausedState
onClicked: { mediaPlayer.pause() }
}
MediaPlayerButton {
text: qsTr("Stop")
active: mediaPlayer.playbackState === MediaPlayer.StoppedState
onClicked: { mediaPlayer.stop() }
}
}
Repeater {
model: mediaDevices.audioOutputs
Row {
spacing: 10
Rectangle {
id: r
width: 40; height: 40; radius: 20
color: mediaPlayer.audioOutput.device === mediaDevices.audioOutputs[index] ? "tomato" : "ghostwhite"
border.color: "gainsboro"
border.width: jackMouseArea.containsMouse ? 4 : 1
MouseArea {
id: jackMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: playerOutput.device = mediaDevices.audioOutputs[index]
}
}
Text {
anchors.verticalCenter: r.verticalCenter
font.pointSize: 12
text: mediaDevices.audioOutputs[index].description
}
}
}
}
}

How to have the components comunicate between them in an accordion like Component? [duplicate]

I'm trying to create accordion qml control like this.
First I thought that I can use combobox and customize it but now I think it is impossible.
Is there any standerd control that I can use? If not can you help me with controls structure?
Just playing with QML
PanelItem.qml
import QtQuick 2.7
import QtQuick.Layouts 1.2
Item {
default property var contentItem: null
property string title: "panel"
id: root
Layout.fillWidth: true
height: 30
Layout.fillHeight: current
property bool current: false
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: bar
Layout.fillWidth: true
height: 30
color: root.current ? "#81BEF7" : "#CEECF5"
Text {
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: root.title
}
Text {
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 10
}
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: "^"
rotation: root.current ? "180" : 0
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null)
root.parent.currentItem.current = false;
root.parent.currentItem = root;
}
}
}
Rectangle {
id: container
Layout.fillWidth: true
anchors.top: bar.bottom
implicitHeight: root.height - bar.height
clip: true
Behavior on implicitHeight {
PropertyAnimation { duration: 100 }
}
}
Component.onCompleted: {
if(root.contentItem !== null)
root.contentItem.parent = container;
}
}
}
usage:
import QtQuick 2.7
import QtQuick.Layouts 1.2
import QtQuick.Window 2.0
Window {
visible: true
width: 400
height: 400
ColumnLayout {
anchors.fill: parent
spacing: 1
property var currentItem: null
PanelItem {
title: "Panel 1"
Rectangle {
color: "orange"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 2"
Rectangle {
color: "lightgreen"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 3"
Rectangle {
color: "lightblue"
anchors.fill: parent
}
}
PanelItem {
title: "Panel 4"
Rectangle {
color: "yellow"
anchors.fill: parent
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
What about using this open source component which I did here
Accordion component and used here Accordion component example.
You only need to initialize:
Components.Accordion {
id: acordion
anchors.fill: parent
anchors.margins: 10
}
And create the data dynamically like this:
propertyAcordion.model = [
{
'menuTitle': value,
'children': [
{
'menuTitle': value,
'children': [
...
Updating the MouseArea click part as below with some added condition. Thanks to folibis for this qml accordian menu.
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
root.current = !root.current;
if(root.parent.currentItem !== null) {
if(root.parent.currentItem !== root)
root.parent.currentItem.current = false;
}
root.parent.currentItem = root;
}
}

How to show the QML window before the program is completed?

The problem is simple: window doesn't rendering and refresh, until the program is finished. It just doesn't show anything.
And I want to see the window even if the long cycle is not finished yet.
I will be very grateful for any help!
#include <QtGui>
#include <QtQml>
int main(int _nArgCount, char * _pArgValues[]) {
QApplication app(_nArgCount, _pArgValues);
//QMLblock
QString strQmlPath = "../main.qml";
QQmlApplicationEngine engine;
QQmlComponent pComponent(&engine, strQmlPath);
if( pComponent.status()==QQmlComponent::Error )
{ qDebug()<<"Error:"<<pComponent.errorString();
return app.exec();
}
QObject * pQmlObject = pComponent.create();
QObject * pWindow = pQmlObject->findChild<QObject*>("initStateGui");
QObject * pWindowNext = pQmlObject->findChild<QObject*>("searchRemovableGui");
pWindow->setProperty("visible","false");
pWindowNext->setProperty("visible","true");
QObject * pList = pQmlObject->findChild<QObject*>("devicesList");
QStringList s;
QString str;
s.append("3");
pList->setProperty("model",s);
for (int i=0; i<5; i++){
s.append(str.number(i));
pList->setProperty("model",s);
}
return app.exec();
}
And my QML (I don't think it's needed, but anyway):
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
QtObject {
property real defaultSpacing: 10
property SystemPalette palette: SystemPalette { }
property var controlWindow: Window {
width: 500
height: 500
color: palette.window
title: "Updater"
visible: true
//init state
Column {
id: initStateGui
objectName: "initStateGui"
anchors.fill: parent
anchors.margins: defaultSpacing
spacing: defaultSpacing
property real cellWidth: initStateGui.width / 3 - spacing
visible: true
Text { text: "Init state" }
Grid {
id: grid
columns: 3
spacing: defaultSpacing
width: parent.width
Button {
id: showButton
width: initStateGui.cellWidth
text: "Cancel"
onClicked: Qt.quit()
}
Button {
id: initStateContinue
objectName: "initStateContinue"
width: initStateGui.cellWidth
text: "Continue"
signal sigInitStateContinue()
onClicked: initStateContinue.sigInitStateContinue()
}
}
Text {
id: textLabel
text: "Welcome to the updater!"
}
Rectangle {
id: horizontalRule
color: "black"
width: parent.width
height: 1
}
}
//updater update state
Column {
id: updaterUpdateGui
objectName: "updaterUpdateGui"
anchors.fill: parent
anchors.margins: defaultSpacing
spacing: defaultSpacing
visible: false
property real cellWidth: initStateGui.width / 3 - spacing
Text { text: "UpdaterUpdate State" }
Grid {
id: grid1
columns: 3
spacing: defaultSpacing
width: parent.width
Button {
id: showButton1
width: initStateGui.cellWidth
text: "Cancel"
onClicked: Qt.quit()
}
Button {
id: updaterUpdateContinue
objectName: "updaterUpdateContinue"
width: initStateGui.cellWidth
text: "Continue"
signal sigUpdaterUpdateContinue()
onClicked: updaterUpdateContinue.sigUpdaterUpdateContinue()
}
}
Text {
text: "Update is started!"
}
Rectangle {
id: horizontalRule1
color: "black"
width: parent.width
height: 1
}
}
//removable Search gui
Column {
id:searchRemovableGui
objectName: "searchRemovableGui"
anchors.fill: parent
anchors.margins: defaultSpacing
spacing: defaultSpacing
visible: false
property real cellWidth: initStateGui.width / 3 - spacing
Text { text: "Removable search State" }
Grid {
id: grid2
columns: 3
spacing: defaultSpacing
width: parent.width
Button {
id: showButton2
width: initStateGui.cellWidth
text: "Cancel"
onClicked: Qt.quit()
}
}
Text {
text: "Searching for removable, please wait...!"
}
ListView {
id:devicesList
objectName:"devicesList"
width: 100; height: 500
model: myModel
delegate: Rectangle {
height: 15
width: 100
Text { text: modelData }
}
}
}
}
}
Addition: i don't need threads, i need to see the freezed window with the caption.
I see it if i add the button, and begin cycle after the button is pressed.
Without the button the window doesn't rendering, and i can't find how to do it.
It's impossible to realize in one thread.
Only moving long process to another thread allows to render GUI.

How to access the property of a GridView - or ListView - element

Here is the code, I create 4 buttons. When one is clicked I wanna that its color changes to red and the color of all the others change to black.
But looks like I could not access the color property.
Rectangle {
id: root
width: 200; height: 100
DelegateModel {
id: visualModel
model: ListModel {
ListElement { my_color: "red" }
ListElement { my_color: "black" }
ListElement { my_color: "black" }
ListElement { my_color: "black" }
}
groups: [
DelegateModelGroup { name: "selected" }
]
delegate: Rectangle {
id: item
height: 25
width: 200
color:my_color
MouseArea {
anchors.fill: parent
onClicked: {
console.log(visualModel.items.get(index).color)
for (var i = 0; i < root.count; i++){
if(index == i)
visualModel.items.get(i).color = "red";
else
visualModel.items.get(i).color = "black";
}
}
}
}
}
ListView {
anchors.fill: parent
model: visualModel
}
}
I advice you to use ExclusiveGroup from QML controls. Usually it is used for Action but it's possible to use it for any other Item. From the Qt docs:
It is possible to add support for ExclusiveGroup for an object or
control. It should have a checked property, and either a
checkedChanged, toggled(), or toggled(bool) signal.
So all we need is to add suitable property. Small example:
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
Window {
width: 200
height: 400
ExclusiveGroup { id: exclusiveGroup }
ListView {
anchors.fill: parent
anchors.margins: 5
spacing: 2
model: 10
delegate: Rectangle {
id: myItem
property bool checked: false // <-- this is necessary
height: 30
width: parent.width
color: myItem.checked ? "lightblue" : "#DEDEDE"
border { width: 1; color: "#999" }
radius: 5
Text { text: "item" + (index + 1); anchors.centerIn: parent}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: myItem.checked = !myItem.checked;
}
Component.onCompleted: {
exclusiveGroup.bindCheckable(myItem);
}
}
}
}

How to switch back between two windows in QML

I Create qml file like this :
main.qml
Item {
width: 200; height: 200
Loader { id: pageLoader }
MouseArea {
anchors.fill: parent
onClicked: pageLoader.source = "Page1.qml"
}
}
I want to back to main page from Page1.qml.
How I can do this Work?
just set the source of pageLoader in Page1 to nothing:
Main Page :
import QtQuick 1.0
Rectangle {
width: 360
height: 360
Text {
anchors.centerIn: parent
text: "Main Page"
}
MouseArea {
anchors.fill: parent
onClicked: {
ld.source="Test.qml"
}
}
Loader{
id:ld;
anchors.fill: parent;
}
}
Test.qml
Rectangle {
width: 100
height: 62
color: "navy";
Text {
anchors.centerIn: parent
text: "Page 1"
}
MouseArea{
anchors.fill: parent;
onClicked: ld.source="";
}
}
Don't you have to instanciate the loader once again in Test.qml to access it via var

Resources