I created an interface has a ListView and two Buttons. When click on Scan button it will call to C++ and make change to the model of ListView. After that C++ will emit signal to inform model is changed therefore ListView in QML will update with new model. I want to make BusyIndicator running during that process. How can i do that ?.
I saw a few solutions on stackoverflow like this one: BusyIndicator does not show up but none of them worked for my case.
Can anyone help me ? Thanks.
Here is my qml code:
import QtQuick 2.5
import QtQuick.Layouts 1.3
import Qt.labs.controls 1.0
Rectangle
{
objectName: "bluetoothPage"
anchors.fill: parent
property var bluetoothDataModel: messageFromApp.bluetoothData
onBluetoothDataModelChanged: listView.update()
signal qmlScanButtonSignal()
signal qmlDisconnectButtonSignal()
ColumnLayout
{
anchors.fill: parent
spacing: 6
RowLayout
{
Layout.fillWidth: true
Text
{
Layout.fillWidth: true
text: "Connect with ECU"
font.bold: true
font.pixelSize: 20
}
BusyIndicator
{
id: busyIndicator
Layout.preferredWidth: 30
Layout.preferredHeight: 30
running: false
visible: false
}
}
GroupBox
{
Layout.fillHeight: true
Layout.fillWidth: true
title: qsTr("Available device:")
ListView
{
id: listView
anchors.fill: parent
model: bluetoothDataModel
delegate: Component
{
Item
{
width: parent.width
height: 40
Column
{
Text { text: "Name:" + model.modelData.name }
Text { text: "Number:" + model.modelData.macAddress }
}
MouseArea
{
anchors.fill: parent
onClicked: listView.currentIndex = index
}
}
}
highlight: Rectangle
{
color: "blue"
}
}
}
RowLayout
{
Layout.fillWidth: true
Layout.preferredHeight: 10
Button
{
Layout.fillHeight: true
Layout.fillWidth: true
text: "Scan"
onClicked: qmlScanButtonSignal()
}
Button
{
Layout.fillHeight: true
Layout.fillWidth: true
text: "Disconnect"
onClicked: qmlDisconnectButtonSignal()
}
}
}
}
Only this solution worked for me in my case. However, like everybody said using QCoreApplication::processEvents()
is really bad practice. I also try to using QThread but it got crash when emitted signal inside thread. If you guy have any futher solutions, please let me now. I'm really appreciate. Thanks.
QML
BusyIndicator {
running: CPPModule.busy
}
CPP
void CPPModule::setBusy(const bool &busy)
{
m_busy = busy;
emit busyChanged();
}
void CPPModule::InsertIntoDB()
{
setBusy(true);
QThread::msleep(50);
QCoreApplication::processEvents();
/*
very Long Operation
*/
setBusy(false);
}
Another solution is this:
Timer {
id: myTimer
interval: 1
onTriggered: {
app.someLongRunningFunction();
myActivityIndicator.visible = false;
}
}
Butoon{
....
onClicked: {
myActivityIndicator.visible=true;
myTimer.start();
}
}
Related
We are having an ApplicationWindow based main.qml which is connected to our python backend via QmlElement Bridge. We have a view Slot-methods which directly return values to the qml frontend to change textfields which are children of the ApplicationWindow like the following:
ApplicationWindow {
id: mainFrame
width: 1280
height: 720
visible: true
title: qsTr("Test")
StackView {
id: stack
initialItem: loginFrame
anchors.fill: parent
}
Bridge {
id: bridge
}
Component{
id: loginFrame
ColumnLayout {
anchors.margins: 3
spacing: 3
Layout.columnSpan: 1
Layout.alignment: Qt.AlignHCenter
Text {
id: title
Layout.alignment: Qt.AlignHCenter
font.pointSize: 16
text: "Login Screen"
Layout.preferredHeight: 100
}
Button {
id: loginButton
Layout.alignment: Qt.AlignHCenter
text: "login"
highlighted: true
Material.accent: Material.Red
onClicked: {
title.text = bridge.login(username.text, password.text)
}
}
}
}
}
To reduce the size of our main.qml we decided to load the other Layouts, Components etc from different files with
Loader {
id: otherLoader
source: "other.qml"
}
How to access the Text Object inside of other.qml to update the text property from main.qml because the value is provided by the Bridge?
I already tried Accessing TextField from Another QML File but this hasn't worked.
The Loader creates items in not the same context as the statically create item use so cannot access the loaded item. You have several ways to access such an item.
The first and the most correct way is to use a declarative style:
Item {
id: container
anchors.fill: parent
property string someText: "press again"
Loader {
id: loader
active: false
sourceComponent: Text {
id: txt
text: container.someText
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if(loader.active)
container.someText = "some text"
else
loader.active = true
}
}
}
You can create a binding in a Javascript code whenever you want:
Loader {
id: loader
active: false
sourceComponent: Text {
id: txt
Component.onCompleted: {
txt.text = Qt.binding(function() { return container.someText; })
}
}
}
Another option is using Loader.item property:
Item {
id: container
anchors.fill: parent
property string someText: "some text"
Loader {
id: loader
active: false
sourceComponent: Text {
id: txt
text: "press again"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if(loader.active)
loader.item.text = "some text"
else
loader.active = true
}
}
}
After successfully designing the layout of a small application from my previous post I am adding the logic of the events. I almost completed it but some events are not happening as I am planning. Below the logic and the full source code here in case would like to verify:
1) As soon as I chose the robot to connect to as show below, It does show I am connecting, but I am not able to interact with the QML page at all and all actions are blocked. I think this could be due to the fact that I have 2 ColumnLayout and I think that one is overwriting the other but I am not sure why that is happening as I thought the logic was complete:
The expected result would be that when I am connecting to the robot, the entire page works instead of being (or looking) disable.
Below the most important part of the code that composes the Minimal Reproducible Example with the problem:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtWebEngine 1.8
import QtQuick.Controls.Styles 1.4
ApplicationWindow {
id: root
visible: true
width: 440
height: 630
title: qsTr("Conn")
property Page1 page1: Page1 {}
property Page2 page2: Page2 {}
Component.onCompleted: {
page1.selectDialog.connect(function() {
mystackview.push(page2);
});
page2.onButtonClicked.connect(function(buttonId) {
page1.dialogId = buttonId;
mystackview.pop();
});
}
StackView {
id: mystackview
anchors.fill: parent
initialItem: page1
}
}
Page1.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.impl 2.12 // for IconLabel
import QtWebEngine 1.8
Page {
property int dialogId: -1
signal selectDialog()
function buttonClick(button)
{
button.text = qsTr("Connecting to %1...").arg(button.text);
button.enabled = false;
if (button.background && button.background instanceof Rectangle) {
button.background.color = "green";
button.background.gradient = null;
button.background.visible = true;
}
if (button.contentItem && button.contentItem instanceof IconLabel) {
button.contentItem.color = "white";
button.contentItem.font.bold = true;
button.contentItem.font.pointSize = 20;
}
}
function buttonClearList(buttonClear)
{
buttonClear.text = qsTr("Clear List").arg(buttonClear.text);
buttonClear.enabled = true;
if (buttonClear.background && buttonClear.background instanceof Rectangle) {
buttonClear.background.color = "red";
buttonClear.background.gradient = null;
buttonClear.background.visible = true;
}
if (buttonClear.contentItem && buttonClear.contentItem instanceof IconLabel) {
buttonClear.contentItem.color = "white";
buttonClear.contentItem.font.bold = true;
buttonClear.contentItem.font.pointSize = 20;
}
}
ColumnLayout {
// anchors.fill: parent
// anchors.topMargin: 0 // margin from top of the page
Layout.fillWidth: true
width: parent.width
spacing: 5
Button {
id: button1
text: "Select Robot"
width: parent.width
onClicked: selectDialog()
Layout.fillWidth: true
font.pointSize: 20
}
Button {
id: dialogA
text: "Freddie Mercury: Connected"
visible: dialogId === 1
Layout.fillWidth: true
font.pointSize: 20
spacing: 10
onClicked: {
buttonClick(this)
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: 50 // margin from top of the page
Layout.fillWidth: true
spacing: 10
GroupBox {
id: box1
width: parent.width
title: "Connection"
font.pointSize: 20
Layout.fillWidth: parent
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
id: row1
spacing: 200
Layout.fillWidth: true
Layout.fillHeight: false
Label {
id: textField
text: "Connection:"
font.pointSize: 15
Layout.fillWidth: true
}
Text {
id: connected
text: "Not-Connected"
color: "red"
font.pointSize: 15
horizontalAlignment: Text.AlignRight
Layout.fillWidth: true
}
}
}
}
Button {
id: clist
text: "Clear List";
Layout.fillWidth: true
font.pointSize: 20
width: parent.width
onClicked: {
buttonClearList(this)
}
}
}
}
}
}
Page2.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Page {
signal onButtonClicked(var buttonId)
Component.onCompleted: {
button1.clicked.connect(function() {
onButtonClicked(1);
});
}
ColumnLayout {
id: mybuttons
anchors.fill: parent
spacing: 5
Button {
id: button1
Layout.fillWidth: true
Layout.fillHeight: true
text: "Freddie Mercury"
font.pointSize: 20
}
}
}
So far I have been trying very different combinations of locating the ColumnLayout in different places. But my doubt i: I already have a ColumnLayout and after that I have another ColumnLayout, and I think that they are overwriting each other.
However, from the official documentation and also consulting other sources re is no problem in using it in a nested loop.
The same post talks about how a Column is a Positioner, while a ColumnLayout is a Layout.
I was sure I was using in the right way but something is missing.
Please point out in the right direction to solve this problem.
Basic design rule: If the parent item is disabled the children too.
Explanation:
In your case, the ColumnLayout is the child of the Button and this is the container of the other items that are your children, so if Button is disabled by the previous ColumnLayout rule, it will also be, and consequently also the entire contents of the ColumnLayout.
Solution:
In your case it is not necessary for ColumnLayout to be the children of Button but it can be on the same level.
On the other hand you have other errors:
If you are going to use Layout.XXX then you should not use the widths.YYY since they fulfill the same task but if you use both you can have problems since it can have an indefinite behavior since they will compete with each other.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.impl 2.12 // for IconLabel
Page {
property int dialogId: -1
signal selectDialog()
function buttonClick(button)
{
button.text = qsTr("Connecting to %1...").arg(button.text);
button.enabled = false;
if (button.background && button.background instanceof Rectangle) {
button.background.color = "green";
button.background.gradient = null;
button.background.visible = true;
}
if (button.contentItem && button.contentItem instanceof IconLabel) {
button.contentItem.color = "white";
button.contentItem.font.bold = true;
button.contentItem.font.pointSize = 20;
}
}
function buttonClearList(buttonClear)
{
buttonClear.text = qsTr("Clear List").arg(buttonClear.text);
buttonClear.enabled = true;
if (buttonClear.background && buttonClear.background instanceof Rectangle) {
buttonClear.background.color = "red";
buttonClear.background.gradient = null;
buttonClear.background.visible = true;
}
if (buttonClear.contentItem && buttonClear.contentItem instanceof IconLabel) {
buttonClear.contentItem.color = "white";
buttonClear.contentItem.font.bold = true;
buttonClear.contentItem.font.pointSize = 20;
}
}
ColumnLayout {
Layout.fillWidth: true
width: parent.width
spacing: 5
Button {
id: button1
text: "Select Robot"
width: parent.width
onClicked: selectDialog()
Layout.fillWidth: true
font.pointSize: 20
}
Button {
id: dialogA
text: "Freddie Mercury: Connected"
visible: dialogId === 1
Layout.fillWidth: true
font.pointSize: 20
spacing: 10
onClicked: {
buttonClick(this)
}
}
ColumnLayout {
id: layout
visible: dialogId === 1
Layout.fillWidth: true
spacing: 10
GroupBox {
id: box1
width: parent.width
title: "Connection"
font.pointSize: 20
Layout.fillWidth: parent
spacing: 10
GridLayout {
width: parent.width
columns: 1
RowLayout {
id: row1
spacing: 200
Layout.fillWidth: true
Layout.fillHeight: false
Label {
id: textField
text: "Connection:"
font.pointSize: 15
Layout.fillWidth: true
}
Text {
id: connected
text: "Not-Connected"
color: "red"
font.pointSize: 15
horizontalAlignment: Text.AlignRight
Layout.fillWidth: true
}
}
}
}
Button {
id: clist
visible: dialogId === 1
text: "Clear List";
Layout.fillWidth: true
font.pointSize: 20
width: parent.width
onClicked: {
buttonClearList(this)
}
}
}
}
}
I am using QtQuick TableView to show data from a database through QSqlTableModel and QSortFilterProxyModel.
The remove row operation doesn't work as it should. I have implemented a method in a class derived from QSortFilterProxyModel to call removeRows methods of QSortFilterProxyModel.
Everything works correctly as long as I have a filter setted in QSortFilterProxyModel ( i set it through a text box ). But when the filter is empty, the TableView rowCount property doesn't decrement and, after each delete, the currentRow property is set to rowCount-2. Why? To me it looks like a bug. Why it works when the filter is not empty?
Q_INVOKABLE void eliminaCliente(int row) {
removeRows(row,1);
}
import QtQuick 2.6
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import Material 0.2
import Material.ListItems 0.1
ApplicationWindow {
id: root
visible: true
width: 1024
height: 640
title: qsTr("assiBase")
Page {
id: pLayout
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Toolbar {
id: aBar
Layout.fillWidth: true
page: pLayout
backgroundColor: "#eeeeee"
RowLayout {
anchors.fill: parent
ActionButton {
id: addButton
Layout.leftMargin: 10
iconName: "content/add_circle"
backgroundColor: "#4CAF50"
onClicked: modalDialog.show()
isMiniSize: true
}
ActionButton {
id: editButton
iconName: "content/create"
isMiniSize: true
}
ActionButton {
id: deleteButton
iconName: "action/delete"
isMiniSize: true
backgroundColor: "#FF0000"
onClicked: {
if (dataView.currentRow != -1) {
var r = dataView.currentRow
console.log(dataView.currentRow)
sqlSortedData.eliminaCliente(dataView.currentRow)
console.log(dataView.rowCount)
//dataView.currentRow = r
}
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Icon {
name: "action/search"
Layout.alignment: Qt.AlignBottom
}
TextField {
id: searchBox
Layout.rightMargin: 20
Layout.minimumWidth: 400
Layout.preferredWidth: 500
placeholderText: qsTr("cerca...")
onTextChanged: sqlSortedData.setFilterWildcard(searchBox.text)
font.capitalization: Font.MixedCase
}
}
}
}
TableView {
anchors.top: aBar.bottom
anchors.topMargin: 3
sortIndicatorVisible: true
frameVisible: false
Layout.fillWidth: true
Layout.fillHeight: true
onSortIndicatorColumnChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
onSortIndicatorOrderChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
id: dataView
TableViewColumn {
role: "ID"
visible: false
}
TableViewColumn {
role: "Nome"
title: "Nome"
width: 200
}
TableViewColumn {
role: "Residenza"
title: "Residenza"
width: 200
}
TableViewColumn {
role: "Assicurazione"
title: "Assicurazione"
width: 200
}
TableViewColumn {
width: 128
resizable: false
delegate: RowLayout {
anchors.fill: parent
clip: true
IconButton {
iconName: "content/create"
onClicked: console.log(styleData.row)
}
IconButton {
iconName: "action/delete"
onClicked: {
console.log(styleData.row)
sqlSortedData.eliminaCliente(styleData.row)
console.log(dataView.rowCount)
}
}
}
}
model: sqlSortedData
}
}
}
Take a look at here. There is an workaround suggestion.
It seems like QSortFilterProxyModel needs some love for a long time.
I'm using Qt 5.6
I want ListView and its items all receive MouseArea onEntered, onClicked signals.
I tried the examples and changed:
ListView {
anchors.fill: parent
model: searchModel
delegate: Component {
Row {
spacing: 5
Marker { height: parent.height }
Column {
Text { text: title; font.bold: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: console.log("eeee");
}
}
Text { text: place.location.address.text }
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: console.log("entered");
}
}
Only ListView can accept onEntered signal, there is no response from its items.
How to enable items receive MouseArea events ?
To propagate clicked events, you should set propagateComposedEvent to true to the outermost MouseArea.
Guess if the same applies to the entered event.
I created my own Item with signal clicked, that contatins MouseArea. I want to emit signal clicked, when MouseArea is clicked. But nothing works.
Here is my .qml code:
import QtQuick 2.4
Item {
id: baseButton
property alias text: txt.text
width: txt.width
height: txt.height
signal clicked
onClicked : console.log("Clicked!")
Text {
id: txt
color: "white"
font.pointSize: 8
anchors.centerIn: parent
}
MouseArea {
id: mousearea
anchors.fill: parent
hoverEnabled: true
onEntered: {
txt.color = "yellow"
txt.font.pointSize = 15
}
onExited: {
txt.color = "white"
txt.font.pointSize = 8
}
onClicked: baseButton.clicked
}
}
I'll be very grateful for your help!
Functions (which signals are) are first class objects in JS, so it is not an error to refer to them without parentheses. But you need them in order to execute the function (i.e. emit the signal).
So just change this line:
onClicked: baseButton.clicked()