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.
Related
i'm trying to create a box-shadow effect for my customs widget, but i can't find a way to do that!.
Using QGraphicsDropShadowEffect class generate a shadow that is the copy of the shape of the widget itself and put that copy behind the widget. So, when i set a opacity of 50% to my widget, the shadow is seeing trought the widget, and what i want to achive is something more like the box-shadow effect pressent in the web CSS styles, for example:
css generated:
https://i.ibb.co/mG1XXG2/box-shadow-qt-ask1.png
QGraphicsDropShadowEffect generated:
https://i.ibb.co/y680RKx/box-shadow-qt-ask2.png
As you can see, both of my elements has a shadow, and a opacity of 50%, the css generate haven't a shadow visible trought the semi transparent div element, but the shadow generated by QGraphicsDropShadowEffect can be seeing thounght the semi transparent widget, there's some way to achive to create a custom shadow that behave like css box-shadow but on my Qt/c++ widget?
Sorry if i'm not clear enoungh, i'm not an expert speaking english.
Thank you for your patience.
A cheat would be to group your object + drop shadow as a single item. Whilst making that entire object invisible, you copy the entire item with a dummy OpacityMask. Then, you apply the opacity to the OpacityMask.
For example:
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
Page {
background: Rectangle { color: "#ccc" }
Frame {
id: butterflyWithBoxShadow
anchors.centerIn: parent
visible: false
padding: 15
background: Item {
Rectangle {
anchors.fill: parent
anchors.leftMargin: butterflyWithBoxShadow.padding * 2
anchors.topMargin: butterflyWithBoxShadow.padding * 2
color: "grey"
opacity: 0.5
}
}
Frame {
id: frame
padding: 1
background: Rectangle {
border.color: "blue"
border.width: 1
color: "#ffe"
}
Image {
id: butterfly
fillMode: Image.PreserveAspectFit
source: "https://www.arcgis.com/sharing/rest/content/items/185841a46dd1440d87e6fbf464af7849/data"
smooth: true
}
}
}
OpacityMask {
anchors.fill: butterflyWithBoxShadow
source: butterflyWithBoxShadow
invert: true
opacity: slider.value
}
Frame {
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height * 8 / 10
background: Rectangle { }
Slider {
id: slider
from: 0
to: 1
value: 0.8
}
}
}
You can Try it Online!
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.
In one of my projects, using Qt Quick 2, I created a TableView view which draws custom items. The item template was defined in the delegate property of my table view. The qml code of the item template is something like that:
Component
{
id: itGridItem
Item
{
width: gvMessageGrid.width
height: itemTextID.height + 40
Rectangle
{
property int messageWidth: (gvMessageGrid.width / 2) - 50
id: itemRect
x: senderIsMyself ? 25 : gvMessageGrid.width - (25 + messageWidth)
y: 5
width: messageWidth
height: itemTextID.height + 20
color: senderIsMyself ? "#d5d5d5" : "#800b940e"
radius: 5
clip: true
Text
{
id: itemTextID
width: parent.width - 20
text: itemText
renderType: Text.NativeRendering
textFormat: Text.StyledText
wrapMode: Text.WordWrap
font.family: "Segoe UI Emoji"
font.pixelSize: 18
anchors.margins: 10
anchors.left: parent.left
anchors.top: parent.top
color: "#101010"
}
}
}
}
In my c++ code, I also created a class inheriting from QAbstractListModel, which I linked with my view using the modelproperty of my TableView view.
In this c++ class, I need to access several properties defined in the above shown item template, as an example I need to get the value of the font.pixelSize property declared in my item template's Text child component.
How can I achieve that?
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