i simply try to decide to display a different page in my QML-Projecet with respect to a certain condition. Right now im doing it like this:
Page {
id: pageMain
visible: true
width: 1024
height: 600
if (condition) {
Page1{}
}
else Page2{}
}
But unfortunately this isnt working as expected. How can i solve my problem?
Thanks in advance :)
You're mixing the syntax of javascript and QML. You probably want to use a Loader like this:
Page {
id: pageMain
visible: true
width: 1024
height: 600
Component {
id: page1
Page1 {}
}
Component {
id: page2
Page2 {}
}
Loader {
sourceComponent: condition ? page1 : page2
}
}
Related
Right now, there is a button for activating the function, like:
Window {
id: window
width: Screen.width
height: Screen.height
visible: true
property alias originalImage: originalImage
title: qsTr("Window1")
Button{
id: startButton
x: 50
y:100
text: "Open"
onClicked: {
VideoStreamer.openVideoCamera()
}
}
But I want to activate this method just after the window is created, not with a button click. Like
Window {
id: window
width: Screen.width
height: Screen.height
visible: true
property alias originalImage: originalImage
title: qsTr("Window1")
VideoStreamer.openVideoCamera()
}
But it doesn't work like that. I get expected token ":" error. It expects something like somecondition: How am I going to manage this without a button, or without an external user-needed condition like onclicked:, etc. ?
You can use Component.onCompleted signal to do things right after component is created.
The onCompleted signal handler can be declared on any object. The order of running the handlers is undefined.
Window {
id: window
width: Screen.width
height: Screen.height
visible: true
property alias originalImage: originalImage
title: qsTr("Window1")
Component.onCompleted:
{
VideoStreamer.openVideoCamera()
}
}
P.S.: if your VideoStreamer is an QML object maybe it is a better to use this signal directly there.
Summary
QML Swipeview has problems updating when model of repeater is reset. The Qt version is 5.12.
Description
Following the doc in (https://doc.qt.io/qt-5/qml-qtquick-controls2-swipeview.html) a SwipeView is implemented. Difference with the example stated in the link, is that no Loader is used. The model of the Repeater is injected by the parent view in form of a QAbstractListModel.
When the viewModel is reset, the SwipeView has problems to update. It causes several repaints of the first page. For the user this appears as if the views is flickering. In the following the QML file and update function of the QAbstractListModel is described.
QML File:
Item {
id: root
/**
Set the model (is of type QAbstractListModel
**/
property var viewModel
height: 30 * Screen.pixelDensity
width: 60 * Screen.pixelDensity
Rectangle {
id: wrapper
anchors.fill: parent
color: "grey"
SwipeView {
id: swipe
clip: true
anchors.fill: parent
Repeater {
model: root.viewModel
Rectangle {
width: 20
height: 20
border.color: "black"
border.width: 1
Text {
text: index
}
}
}
}
}
}
Update Function:
void DerivedQAbstractList::setPages(const std::vector<Pages> &pages)
{
beginResetModel();
// create internal data structure
endResetModel();
}
Observations:
Update problem does not appear when the QAbstractList model is initially empty.
Using the Repeater without a SwipeView works fine (e.g. Column {Repeater {...}}
Do you have any ideas what could be the problem. Or give me an alternative approach to have a swiping view with dynamical model?
Edit 1.0. Using a Instantiator
As asked in the comment, we tried to replace the Repeater with an Instantiator. We can see from the console log, that the components are created. However they do not appear on the screen. What do we miss?
Rectangle {
id: wrapper
anchors.fill: parent
color: "grey"
SwipeView {
id: swipe
clip: true
anchors.fill: parent
Instantiator {
model: root.viewModel
delegate: Rectangle {
Component.onCompleted: console.log("Created")
width: 20
height: 20
border.color: "black"
border.width: 1
Text {
text: index
}
}
}
}
}
I am currently using QML/C++ for mobile development and facing a problem that, for sure demonstrates my poor ability to design QML/C++ applications. Hope you can help me here.
This is my main.qml:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: qsTr("(Nome da Aplicação)")
header: ToolBar{
RowLayout{
anchors.fill: parent
ToolButton {
id: toolButton
text: stackView.depth > 1 ? "\u25C0" : "\u2630"
onClicked: drawer.open()
}
Label {
text: stackView.currentItem.title
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
}
}
Drawer {
id: drawer
width: window.width * 0.33
height: window.height
Column{
anchors.fill: parent
ItemDelegate {
text: qsTr("Operações")
width: parent.width
onClicked: {
stackView.push("Operacoes.qml")
drawer.close()
}
}
ItemDelegate {
text: qsTr("Produtos")
width: parent.width
onClicked: {
stackView.push("Produtos.qml")
drawer.close()
}
}
ItemDelegate {
text: qsTr("Configurações")
width: parent.width
onClicked: {
stackView.push("Configuracoes.qml")
drawer.close()
}
}
}
}
StackView {
id: stackView
initialItem: "Operacoes.qml"
anchors.fill: parent
}
}
The combo box whose value I need to access from C++ is defined in Operacoes.qml which consists of
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import QtCharts 2.3
Item {
objectName: "janelaResumo"
property alias title: paginaOperacoes.title
property alias primeiroGraf: primeiroGraf
property alias segundoGraf: segundoGraf
property alias terceiroGraf: terceiroGraf
property alias quartoGraf: quartoGraf
property alias combo_periodoFaturacao_ID: combo_periodoFaturacao_ID
Page {
id: paginaOperacoes
anchors.fill: parent
title: "Resumo de Operações"
ScrollView {
anchors.fill: parent
clip: true
GridLayout {
id: grid_BaseLayout
columns: paginaOperacoes.width < 400 ? 1 : 2
rows: paginaOperacoes.width < 400 ? 4 : 2
anchors.fill: parent
ColumnLayout {
Label {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Faturação")
font.bold: true
}
RowLayout {
ChartView {
id: primeiroGraf
width: 350
height: 350
antialiasing: true
PieSeries {
name: "PieSeries"
PieSlice {
value: 13.5
label: "Slice1"
}
PieSlice {
value: 10.9
label: "Slice2"
}
PieSlice {
value: 8.6
label: "Slice3"
}
}
}
ComboBox {
objectName: "combo_periodoFaturacao"
model: ListModel{
ListElement {
text:"7 dias"
}
ListElement {
text:"Mensal"
}
ListElement {
text:"Anual"
}
}
id: combo_periodoFaturacao_ID
}
}
}
// segundo gráfico
ColumnLayout {
Label {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Tesouraria")
font.bold: true
}
ChartView {
id: segundoGraf
width: 350
height: 350
antialiasing: true
PieSeries {
name: "PieSeries"
PieSlice {
value: 13.5
label: "Slice1"
}
PieSlice {
value: 10.9
label: "Slice2"
}
PieSlice {
value: 8.6
label: "Slice3"
}
}
}
}
}
}
}
Then, C++ ClassX class implements a method to load data that should start by reading qml interface values in order to use them as arguments for some future processig.
void classX::loadData(){
if(mDbStatus == true){
QQuickView view;
const QUrl url(QStringLiteral("qrc:/Operacoes.qml"));
view.setSource(url);
QObject *OperacoesObject = view.rootObject();
QObject *comboFaturacao_t = OperacoesObject->findChild<QObject ("combo_periodoFaturacao");
qDebug() << comboFaturacao_t->property("currentText");
No matter what value lives in the combobox combo_periodoFaturacao depending on user selection, I always get the same value(first element of the respective combobox model) in comboFaturacao_t->property("currentText");
I am aware that I must avoid referring explicitly my UI from C++!
I also understand that, for each loadData() call, I am instantiating a new QQuickView object, but how can I simply collect a few UI values to serve as parameters for the execution of loadData() without implement a cpp class "binded" to my fileX.qml?
No matter what value lives in the combobox comboFaturacao depending on user selection, I always get the same value(first element of the combobox model)
Based on the code you posted, and except if I missed something, you are reading the value of "currentText" immediately after creating your view, without waiting for the user to select anything. So this will return the initial value when your view is created.
but how can I simply collect a few UI values to serve as parameters for the execution of loadData() without implement a cpp class "binded" to my fileX.qml
Exposing C++ to the UI is really the way to go, and a good practice, which forces to avoid high level logic to GUI dependencies. Which means not depending on implementation detail (GUI in this case). That said, if this is what you want, you can read properties from C++, but still need to wait for the user to be "done", which can be done by:
Creating the view on the heap instead of the stack and saving it somewhere
Connecting a slot like onLoadDataUserSettingsReady to a QML signal, using connect (probably the older SIGNAL/SLOT syntax to allow connecting to an arbitrary signal)
Return from loadData, as you will need to wait for the user to interact with the UI without blocking the main thread
And whenever you emit your QML signal that says "The user is done", your onLoadDataUserSettingsReady slot will be executed, allowing you to read the QML properties you are interested with (or directly pass them in the signal/slot)
But as you can see, this is a bit complex, and forces you to make loadData asynchronous, which may not be what you want. You could potentially make it synchronous using a thread that's not the main thread, and a QSignalSpy or other to wait for a signal, but again, not a great solution. I would still recommend exposing a C++ instance with setContextProperty, and reading from this object in your loadData method, whenever needed.
I have a simple QML code like this:
GridView {
id: viewId
anchors.fill: parent
delegate: delegateId
cellWidth: 100
cellHeight: 100
}
Component {
id: delegateId
Rectangle {
id: rectId
width: 100
height: 100
MyCystomItem {
id: imageId
anchors.centerIn: parent
height: parent.height
width: parent.width
myCustomProperty: myCustomRole
}
}
}
In delegate I use MyCustomItem which is defined on c++ side of my project.
When MyCustomItem::paint() is called for the first time I paint some 'wait' indicator,
and using value passed to myCustomProperty I begin some calculations on a thread.
When calculations are done, I call MyCustomItem::update() and I paint result of calculations.
I'd like to experiment with QtQuick's transitions to make the grid more alive so I though
about adding some transition between 'wait' and 'final result` states. It is not important which one,
the problem is I do not know what would be the right way of doing this.
I'd like to avoid any timer based attempts in my c++ code. I'd like to place transitions
directly in qml file so I can easliy experiment with various effects.
Next step would be to add transitions to item when 'wait' state is active.
What would be the most qml-like approach for this?
It's not entirely clear what exactly you want to animate. But I'll try to give some idea's
Option 1
You could expose the internal state of the custom item, with properties like waiting (or better working) and done. In QML you can bind to these properties in State objects and Transition objects:
MyCystomItem {
id: imageId
anchors.centerIn: parent
height: parent.height
width: parent.width
myCustomProperty: myCustomRole
states: [
State {
name: "working"
when: imageId.working && !imageId.done
//you could set properties
},
State {
name: "done"
when: !imageId.working && imageId.done
//you could set properties
}
],
transitions: [
Transition {
from: "*"
to: "working" //here goes the name from the state
NumberAnimation {
properties: "x,y";
easing.type: Easing.InOutQuad;
duration: 200;
}
}
]
}
Option 2
Depending on what the calculations are you could add one or more properties that represent the results and use Behavior objects. Since you id'd the custom item imageId, I don't think you are really interested in this option, but just putting it here for completenes.
Note also, that this option exposes a bit of an issue with mixed responsibilities (see wikipedia); The calculations are done in the same class as the representation. So in the code below I'm assuming the MyCustomItem is only doing the calculation (as soon as it's instantiated)
Rectangle {
id: tempView
anchors.centerIn: parent
height: parent.height
width: parent.width
color: imageId.working ? "transparent"
: imageId.temperature < 20 ? "blue" : "red"
Behavior on color { ColorAnimation { duration: 500 } }
MyCystomItem {
id: imageId
}
}
Option 3
Lastly, you could make the grid a bit more alive with ProgressBars. For this it is needed to have an idea on how far the calculation is, and of course expose that value
import QtQuick.Controls 2.3
MyCystomItem {
id: imageId
anchors.centerIn: parent
height: parent.height
width: parent.width
ProgressBar {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
value: imageId.progress
visible: imageId.progress < 100
}
}
Conclusion
I think the most valuable part in this answer it to expose your internal calculation state to the QML side with Q_PROPERTY's. Since you can only control what is visible.
I have 3 files. main.qml, Guide.qml and ChannelViewer.qml
my main class contains 2 components and a loader here is the code
main.qml
import QtQuick 2.0
Rectangle {
id:loader
color: "black"
property string channelName
property string channelURL
Component{
id:tv
ChannelViewer{}
}
Component{
id:guide
Guide{}
}
Loader
{
id: pageLoader
anchors.fill:parent
focus:true
sourceComponent: tv
}
Connections{
target:pageLoader.item
onChangeChannel:{
channelName=name
channelURL=url
}
}
Keys.onPressed: {
event.accepted = true;
if (event.key === Qt.Key_I) {
pageLoader.sourceComponent = tv;
}
else if(event.key === Qt.Key_G) {
pageLoader.sourceComponent = guide;
}
}
}
Now if I press "G" I would be moved to the guide file without any issues In my guide page I am able to send signal to main.qml and update the name property in main.
Guide.qml
Item {
signal changeChannel(string url, string name)
Loader {
id: pageLoader
anchors.fill:parent
sourceComponent: guide
focus:true
}
Keys.onPressed: {
if(event.key === Qt.Key_Escape) {
pageLoader.source = "main.qml";
}
event.accepted = true;
}
Component {
id:guide
Rectangle {
color:"lightblue"
Keys.onPressed: {
if(event.key === Qt.Key_Return) {
changeChannel(menuContent.currentItem.ch_url, menuContent.currentItem.ch_name)
pageLoader.source = "main.qml";
}
event.accepted = true;
}
}
}
}
However now when I press "Return" in my Guide.qml i will be taken back to main.qml (Channelname and ChannelURL will be updated successfully), and my main.qml will now take me to ChannelViewer.qml and here is the problem my ChannelViewer.qml will not receive the updated channelName and channelURL. And I am not sure what am I doing wrong.
ChannelViewer.qml
import QtQuick 2.0
import VLCQt 1.0
Rectangle {
id:root
width: 640
height: 480
color: "black"
focus:true
Loader
{
id: pageLoader
anchors.fill:parent
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(channelURL)
}
}
Keys.onPressed: {
if (event.key === Qt.Key_I) {
event.accepted = true;
if(channelInfo.visible === true) {
channelInfo.visible=false;
}
else {
channelInfo.visible=true;
}
}
}
VlcVideoPlayer {
id: vidwidget
anchors.fill: parent
url:channelURL
ChannelInfo{
id:channelInfo
anchors.bottom: parent.bottom
anchors.bottomMargin: ((parent.height*5)/100)
anchors.horizontalCenter: parent.horizontalCenter
width:parent.width - ((parent.width*10)/100)
height: (parent.height*20)/100
backgroundOpacity: 0.7
radius:10
channelNameProp: channelName
channelNumberProp: "1"
headerIcon: "imgs/television_32x32.png"
}
}
}
EDIT:
Code for my ChannelInfo.qml
import QtQuick 2.0
Item {
id:channelinfo
property color backgroundColor: "blue"
property color headerBackgroundColor: "lightblue"
property color headerNameColor: "black"
property color borderColor: "black"
property color channelNameColor: "white"
property color channelNumberColor: "white"
property real borderWidth:0
property real radius:0
property real backgroundOpacity: 0.5
property string menuTitle : "TV Channels"
property string channelNameProp
property string channelNumberProp
property url headerIcon: "imgs/television.png"
visible:false
Rectangle{
id:root
width:channelinfo.width
height:channelinfo.height
color:channelinfo.backgroundColor
border.color:channelinfo.borderColor
border.width: channelinfo.borderWidth
radius:channelinfo.radius
opacity:channelinfo.backgroundOpacity
visible: parent.visible
Rectangle{
id:header
anchors.top:parent.top
// width:(parent.width*40)/100
width: parent.width
height: (parent.height*30)/100
radius: channelinfo.radius
color:channelinfo.headerBackgroundColor
Image{
source:channelinfo.headerIcon
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -4
}
Text{
id:headerTitle
anchors.left: parent.left
anchors.leftMargin: 50
anchors.verticalCenter: parent.verticalCenter
width:parent.width
wrapMode: Text.WordWrap
color:channelinfo.headerNameColor
text:menuTitle
font.pixelSize: Math.round(parent.height/2)
font.bold: true
}
}
Rectangle{
id:content
anchors.bottom: parent.bottom
width:parent.width
height:parent.height-header.height
color:"transparent"
Text{
id:channelName
anchors.left: parent.left
anchors.leftMargin: 50
anchors.verticalCenter: parent.verticalCenter
color:channelinfo.channelNameColor
text:channelNameProp
font.pixelSize: Math.round(parent.height/4)
font.bold: true
}
Text{
id:channelNumber
anchors.right: parent.right
anchors.rightMargin: 20
anchors.verticalCenter: parent.verticalCenter
color:channelinfo.channelNumberColor
text:channelNumberProp
font.pixelSize: Math.round(parent.height/4)
font.bold: true
}
}
}
}
Github Page for VLCPlayer
https://github.com/vlc-qt/
If you are going to have such a fixed structure, why even bother with the signal, you can simply:
Keys.onPressed: {
if(event.key === Qt.Key_Return) {
channelName = menuContent.currentItem.ch_name
channelURL = menuContent.currentItem.ch_url
pageLoader.source = "main.qml";
}
event.accepted = true;
}
And then remove the unnecessary part:
Connections{
target:pageLoader.item
onChangeChannel:{
channelName=name
channelURL=url
}
}
Since channelName and channelURL are declared in the root object of the qml file, they should be accessible from within objects that are nested further up the tree because of dynamic scoping.
So after you posted the relevant code, you have an:
Text{
id:channelName
in your ChannelInfo object, which is shadowing the channelName property, declared in main.qml. It is a good idea to develop the habit of consistent naming conventions. For example, since this is an id, I personally would have used id: _cName, this way you minimize the odds of getting such collisions.
Update:
The only other reason I can think why it doesn't work is that you are somewhere breaking the channelNameProp: channelName binding by doing something like channelNameProp = something.
Here is a quick example to illustrate that dynamic scoping just works (as long as you don't shadow anything), even in situations that involve dynamically changing Loader items:
// main.qml
ApplicationWindow {
id: _cName
visible: true
width: 640
height: 480
property int value: 0
Loader {
id: loader
source: "Obj.qml"
}
}
// Rect.qml
Rectangle {
id: rectangle
width: 50; height: 100
color: "red"
Text {
anchors.centerIn: parent
text: value
}
MouseArea {
anchors.fill: parent
onClicked: {
loader.source = "Obj.qml"
}
}
}
// Obj.qml
Rectangle {
id: rectangle
width: 50; height: 100
color: "blue"
MouseArea {
anchors.fill: parent
onClicked: {
value++
loader.source = "Rect.qml"
}
}
}
As the properties
property string channelName
property string channelURL
have change signals and therefore support property binding, I think the easiest way would be to change line 9-17 to
Component{
id:tv
ChannelViewer {
id: channelViewer
channelName: loader.channelName
channelURL: loader.channelURL
}
}
Component{
id:guide
Guide {
id: guide
channelName: loader.channelName
channelURL: loader.channelURL
}
}
If Guide changes the channelName you need to make sure that you change it in the loader. You might use Binding-objects to make the bindings survive assignments (=).
So this works, you need to create the properties channelName and channelURL in the root nodes of your Guide.qml abd your ChannelViewer.qml. Then, in each place inside those files, you use fully qualified identifiers: id.propertyName, which would be e.g channelinfo.channelName in the ChannelInfo.qml, root.channelName in the ChannelViewer.qml and an id you will need to set (e.g. root again) in your Guid.qml -> root.channelName.
Usage of fully qualified identifiers for bindings, which always include the idOfTheObject.propertyName helps to avoid problems. In some cases (positioning, anchoring, sizing) parent is ok, but you might not know what exactly is the parent)
Dynamic scoping is a blessing if you know exactly how and where the code will be used, e.g. if it is essentially a partial definition of a larger object, and will never be used in another context. But here you need to know that if the parent file changes the internal api, you need to adapt the child file accordingly. If you think, the file might be for reuse, avoid dynamic scoping and only reference what is defined inside the file.