I'm trying to create a navigation drawer like the image below:
First I tried it with ListView
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
ApplicationWindow{
id: window
visible: true
width: 640
height: 480
title: "Dashboard"
Material.theme: Material.Light
Material.primary: "#1de9b6"
Material.accent: "#3d5afe"
header: ToolBar{
RowLayout{
anchors.fill: parent
ToolButton{
id: btnDrawer
icon.source: "qrc:/icons/icons/ic_drawer.svg"
onClicked: {
if(!navDrawer.opened)
navDrawer.open()
if(navDrawer.opened)
navDrawer.close()
}
}
Item{
Layout.fillWidth: true
}
ToolButton{
id: btnUsuario
icon.source: "qrc:/icons/icons/ic_bullet_menu.svg"
}
}
}
Drawer{
id: navDrawer
y: header.height
width: window.width / 3
height: window.height - header.height
ListView{
model: ListModel{
ListElement{
icon: "qrc:/icons/icons/ic_people.svg"
name: "Hopsede"
}
}
delegate: Item{
Image{
source: icon
}
Text{
text: name
}
}
}
}
}
And this is the result:
Not cool. Now i tried using Layouts
Almost good, but still not clickable and not hoverable effect, now finaly i tried used buttons:
This time it was far from looking like a navigation drawer, I intend to make the entire desktop application using material design for having a good appearance, but I'm not able to replicate some components like this
Your root delegate item has no size, which is why everything is jumbled together. The docs for ListView say:
The ListView will lay out the items based on the size of the root item
in the delegate.
So you need to either give it a size, or use an item that has an implicit size, like ItemDelegate:
delegate: ItemDelegate {
text: model.name
width: parent.width
icon.source: model.icon
}
Note that in that example, the width is set so that it takes up the entire ListView. If you don't do that, it will still have a valid size, it will just likely be too small.
You probably also want to make the ListView fill the Drawer:
anchors.fill: parent
Related
I have a file called "SingleTile.qml" with following content
import QtQuick 2.15
Item {
width: 100; height: 100
Rectangle {
anchors.centerIn: parent
color: "green"
}
}
On a button click, i do the following to create an instance of SingleTile.qml
QQmlEngine engine;
QQmlComponent component(&engine,
QUrl::fromLocalFile("SingleTile.qml"));
QQuickItem *object = qobject_cast<QQuickItem*>(component.create());
object->setProperty("color", "blue");
But this does not show any rectangle on the screen with either green or blue color. Why?
Note there are bugs in your SingleTile component. Firstly the green Rectangle { } actually has zero width and height. To correct that bug, you need to change anchors.centerIn: parent to anchors.fill: parent. The other problem is the color cannot be changed because the parent Item has no color property. To fix that you should bubble up that property from your Rectangle with something like a property alias.
//SingleTile.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
width: 100; height: 100
property alias color: rect.color
Rectangle {
id: rect
anchors.fill: parent
color: "green"
}
}
Do you require to dynamically create your components in c++? If the button click is done in QML, you can handle the dynamic creation and deletion of your components completely in QML.
There is a Qt page that documents the approach https://doc.qt.io/qt-6/qtqml-javascript-dynamicobjectcreation.html
However, if you want to avoid such complexity, you can consider reworking the dynamic creation as a matter of ListModel and delegates as follows. As you add to the ListModel the delegate will instantiate new instance of your component automatically. As you delete from the ListModel, instances of your component will be deleted automatically.
I use this QML online service which demonstrates the dynamic creation and deletion of SingleTile. Since I am using this QML online service it requires me to put everything in one file, so, I have implemented SingleTile as an inline component instead of a separate file.
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
Repeater {
model: listModel
delegate: SingleTile {
x: tileX
y: tileY
color: tileColor
Text {
text: "X"
anchors.right: parent.right
color: "white"
MouseArea {
anchors.fill: parent
onClicked: listModel.remove(index)
}
}
}
}
ListModel {
id: listModel
}
Button {
text: qsTr("Create")
onPressed: {
let tileX = Math.floor(Math.random() * parent.width);
let tileY = Math.floor(Math.random() * parent.height);
let tileColor = ["green", "blue"][Math.floor(Math.random() * 2)];
listModel.append( { tileX, tileY, tileColor } );
}
}
}
//SingleTile.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
width: 100; height: 100
property alias color: rect.color
Rectangle {
id: rect
anchors.fill: parent
color: "green"
}
}
You can Try it Online!
Sorry i don't know what your problem is exactly, because you haven't provided enough information about the issue.
But here are some stuff that maybe causing the issue:
QUrl::fromLocalFile creates an absolute path to the given file at you application's location. In your case i would rather just use the constructor with a "qrc:/SingleTile.qml" this will create a relative path to the Qt resource system.
You should be checking for errors after constructing your QQmlComponent by calling "isError" and "errorString".
You need to specify a visual parent for your created item by setting the "parent" property through "setProperty" or "setParentItem", otherwise there is nowhere that the item can be visualized.
I'm developping an app using QT 5.15 LTS (5.15.2). I have the following QML item that I use to handle virtual keyboard interactions :
//InputScreen.qml
import QtQuick 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import QtQuick.VirtualKeyboard 2.3
Rectangle
{
id: inputScreen
property var target: undefined
width: app.width
height: app.height
color: "#44000000"
z: 200
onVisibleChanged:
{
if (inputScreen.visible == true)
{
fld.text = target.text;
inputPanel.forceActiveFocus();
fld.forceActiveFocus();
fld.clicked(); //Tried to force a click here. Explanation below
}
}
Rectangle
{
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
height: 40
width: 600
z: 201
TextField
{
id: fld
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 400
height: 40
font.pixelSize: 24
focus: true
}
Button
{
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 200
height: 40
font.pixelSize: 24
text: "Valider"
onClicked:
{
if (target != undefined)
{
target.text = fld.text;
inputScreen.visible = false;
}
}
}
}
InputPanel
{
id: inputPanel
height: 400
width: app.width - 40
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
focus: true
z: 201
}
MouseArea
{
anchors.fill: parent
onClicked:
{
target.text = fld.text;
inputScreen.visible = false;
}
}
}
When my item becomes visible, the textField gets the focus correctly and I can start typing through the InputPanel virtual keyboard. There's however one single problem : the shift key does not work !
In order to make that shift key enable itself, I need to click on the TextField, which beats the purpose of forcing the active focus on it to begin with.
Is there any way to fix that problem ? Or at the very least, is there any kind of workaround I can use to enable that shift key ?
I have already tried to force a click inside my TextField (as seen in the code above), but it didn't work.
For context : here's the content of main.qml as well, in order to give out a working example :
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4
import QtQuick.VirtualKeyboard 2.3
Window
{
id: app
visible: true
width: 640
height: 480
title: qsTr("Example")
InputScreen
{
id: inputScreen
visible: false
}
Rectangle
{
id: rect
height: parent.height * 0.8
width: parent.width * 0.8
anchors.centerIn: parent
border.color: "#FF0000"
z: 1
TextField
{
id: myField
anchors.centerIn: parent
width: 200
height: 24
MouseArea
{
anchors.fill: parent
onClicked:
{
inputScreen.target = myField;
inputScreen.visible = true;
}
}
}
}
}
I finally managed to find a trick that enabled that shift key without requiring to click on my already focused TextField, and it's actually very simple.
I simply added the following line in the onClicked event of the MouseArea inside the "myField" TextField :
MouseArea
{
anchors.fill: parent
onClicked:
{
parent.focus = true; //This line solved the issue !
//...
}
}
I have no idea why, but somehow this was enough to actually unlock the shift key from my InputPanel virtual keyboard without requiring me to click a second time on the TextField from my InputScreen item. I suppose it has probably something to do with the fact that my MouseArea is hijacking the focus of the "myField" TextField, which triggers some sort of odd behaviour on the InputPanel item despite having another TextField item focused.
In any case, I hope this will help someone.
I'm having some troubles getting the QML type TableView to behave correctly when wrapping it inside another item. The problem is that creating a reuseable type basically forces one to use an Item wrapper to have the *HeaderView types in the same .qml file. Here is the rather simple code, a test-model for some data can be taken from the official TableView documentation.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import TableModel
Window {
width: 600
height: 480
visible: true
// This item wrapper changes TableView behavior
Item {
width: 600
height: 250
// --------------------------------------------
TableView {
id: tableView
anchors.fill: parent
topMargin: horizontalHeader.implicitHeight
leftMargin: verticalHeader.implicitWidth
columnSpacing: 1
rowSpacing: 1
clip: true
model: TableModel {}
delegate: Rectangle {
implicitWidth: 150
implicitHeight: 25
Text {
text: display
}
}
}
HorizontalHeaderView {
id: horizontalHeader
syncView: tableView
}
VerticalHeaderView {
id: verticalHeader
syncView: tableView
}
// --------------------------------------------
}
// --------------------------------------------
}
Without the Item wrapper (see comments in code) my TableView looks as expected:
But once wrapped inside an Item the horizontal and vertical headers get placed over the actual table.
For some odd reason this displacement is only relevant for the very first rendering of the table. Once I drag the data from the table around a little (I guess activating the "Flickable" inherited type?) the data suddenly snaps into position and is displayed correctly outside of the headers.
Apparently it wasn't a good idea to use anchors.fill: parent when trying to attach the *HeaderViews. Once I got rid of that line and simply anchored all views to each other (horizontal to top, vertical to left) it works.
Item {
implicitWidth: 600
implicitHeight: 250
TableView {
id: tableView
implicitWidth: parent.implicitWidth
implicitHeight: parent.implicitHeight
anchors.top: horizontalHeader.bottom
anchors.left: verticalHeader.right
columnSpacing: 1
rowSpacing: 1
clip: true
model: TableModel {}
delegate: Rectangle {
implicitWidth: 150
implicitHeight: 25
Text {
text: display
}
}
}
HorizontalHeaderView {
id: horizontalHeader
anchors.left: verticalHeader.right
clip: true
syncView: tableView
}
VerticalHeaderView {
id: verticalHeader
anchors.top: horizontalHeader.bottom
clip: true
syncView: tableView
}
}
Note thougt that in the official Qt docs using anchors inside layouts is explicitly listed as "Don'ts". My guess is that they mean don't use anchors between layout elements or parents of the layout, not don't anchor two elements inside a single layout cell.
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 am writing a project that is using Qt5-QML. In order to shrink the problem I prepared a small example that replicates the problem.
1) I have an application on a Page1. That carries a Button as shown in the below print screen below:
2) After the user pushes the button goes on Page2 shown below which carries different Buttons. Let's say Button A it is the user's choice:
3) The final screen represented by Page1 is the correct choice, which is the the button selected
The problem I have is that after the user selects the choice on point 2) and goes back on Page1 a Button should appear on Page1 in the middle of the Page1 that opens a dialog window, in my case a WebEngineView only related to that Button.
Of course if the user selcts Button 3 on Page2, back on Page1 there should be another Button that opens another dialog related to Button 3 and that is always a WebEngineView.
I can't see the Button and the related dialog.
The expected result is the print screen below:
Below the snippet of code that carries the problem:
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Conn")
property Page1 page1: Page1 {}
property Page2 page2: Page2 {}
Component.onCompleted: {
page2.onButtonClicked.connect(function(buttonId, buttonName) {
page1.mytext.text = buttonId;
page1.mytext.text = buttonName;
mystackview.pop();
});
}
// These buttons should appear only after the user selects the choices on `Page2`
Button {
id: dialogA
Layout.fillWidth: true
text: "Open Dialog A"
}
Button {
id: dialogB
Layout.fillWidth: true
text: "Open Dialog B"
}
Button {
id: dialogC
Layout.fillWidth: true
text: "Open Dialog C"
}
StackView {
id: mystackview
anchors.fill: parent
initialItem: page1
}
}
page1.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
Page {
property alias mytext: mytext
Button {
id: button1
text: "Select"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
onClicked: {
mystackview.push(page2);
}
}
Text {
id: mytext
anchors.top: button1.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
font.bold: true
font.pointSize: 30
}
}
page2.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Page {
signal onButtonClicked(var buttonId, var buttonName)
Component.onCompleted: {
button1.clicked.connect(function() {
onButtonClicked(1, "A");
});
button2.clicked.connect(function() {
onButtonClicked(2, "B");
});
button3.clicked.connect(function() {
onButtonClicked(3, "C");
});
}
ColumnLayout {
id: mybuttons
anchors.fill: parent
spacing: 5
Button {
id: button1
Layout.fillWidth: true
Layout.fillHeight: true
text: "A"
}
Button {
id: button2
Layout.fillWidth: true
Layout.fillHeight: true
text: "B"
}
Button {
id: button3
Layout.fillWidth: true
Layout.fillHeight: true
text: "C"
}
}
What I tried so far:
1) I came across the following source which was useful to understand the style of a component. And that is for this reason that I used Component.onCompleted: on my code. Which did a good job in triggering the buttons at Page2. I thought that I could use the same philosophy also for the others but they are not showing up.
2) This source was useful for me to understand the properties of a components, in particular in the example how to highlight on a click. I think this is the most close that I can find for what I am doing.
3) Digging more in what I am trying to achieve I went ahead and went through the official documentation which seems to push for the use of the property onCurrentItemChanged which could be a choice as triggers the emit properties of a signal.
UPDATES
The following part in the main.qml is used to show that once the user triggers one of the three buttons, then on page1 it is shown the letter of the Button that was clicked, in fact it is possible to see it on point 3) of the procedure.
Component.onCompleted: {
page2.onButtonClicked.connect(function(buttonId, buttonName) {
page1.mytext.text = buttonId;
page1.mytext.text = buttonName;
mystackview.pop();
});
This is connected to the signal on the page2.qml as shown below:
signal onButtonClicked(var buttonId, var buttonName)
Why I can't see the Button after I go back on Page1 of my QML application?
Thank you very much for pointing in the right direction to solve this problem.
As mentioned in my comment, the "Open Dialog" buttons are always hidden by the stack view anyway. If they weren't hidden by that, they'd in any case need some flag to indicate whether they should be visible or not, based on the selection made on Page2.
Keeping with the existing structure of your MRE, here's one way it could work. I've moved the "Open Dialog" buttons to Page1 because that seems to make the most sense, but they could also be on some separate page, or incorporated into main.qml if there were some layout there which allowed that. Also I made a signal/slot connection from Page1 to trigger the Page2 load in main.qml (instead of directly trying to access objects in main.qml, which is bad practice). I also removed the text label from Page1 to simplify the code. Page2 code remains unchanged so I do not include it.
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
visible: true
width: 640
height: 480
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, buttonName) {
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
Page {
property int dialogId: -1;
signal selectDialog()
ColumnLayout {
anchors.fill: parent
spacing: 5
Button {
id: button1
text: "Select"
onClicked: selectDialog()
Layout.fillWidth: true
}
// These buttons should appear only after the user selects the choices on `Page2`
Button {
id: dialogA
text: "Open Dialog A"
visible: dialogId === 1
Layout.fillWidth: true
}
Button {
id: dialogB
text: "Open Dialog B"
visible: dialogId === 2
Layout.fillWidth: true
}
Button {
id: dialogC
text: "Open Dialog C"
visible: dialogId === 3
Layout.fillWidth: true
}
}
}