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.
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!
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 have a QML GridView with a scrollbar and hover effect, when I move the curso to the page's bottom the hover make an automatic scrolling, I want to stop it. I tried to set the interactive property of Flickable to false "interactive: false" but it didn't work. How can I stop this behavior?
Obs: When I remove the hover effect the scroll behave in the expected way, just moving through the scrollbar.
GridView{
id: grid
anchors.margins: 20
anchors.fill: parent
cellHeight: 80
cellWidth: 80
model: MyModel{}
highlight: Rectangle {
color: "lightsteelblue"
height: parent.cellHeight
width: parent.cellWidth
z:2
opacity: 0.7
}
delegate: Column {
Rectangle {
color: myColor;
height: grid.cellHeight * 0.7
width: grid.cellWidth * 0.7
border.color: "white"
anchors.margins: 5
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
id: mouseRegion
anchors.fill: parent
hoverEnabled: true
onEntered: grid.currentIndex = model.index
}
}
Text {
text: name;
anchors.horizontalCenter: parent.horizontalCenter }
}
}
Here is the Link to a simple project that reproduces the behavior: https://drive.google.com/open?id=0B7WUSCDDdwtIbWVDWVFvMjM1djA
My workmate help me to figured out a solution to my problem. I was using currentIndex property to set the hover position in the gridview, as described on documentation this property will smoothly scroll the GridView in the way that the current item becomes visible if the highlightFollowsCurrentItemis set to true (default value) and will not scroll if highlightFollowsCurrentItem set to false.
However, after set highlightFollowsCurrentItem property to false, the automatic scroll stopped together with my hover effect, which is clearly unwanted. I couldn't figure out what was wrong and we came out with a different approach.
To make the hover work without the automatic scrolling provided by currentIndex behavior, we remove it from gridview and used onEntered and onExited to control hover behavior, changing the rectangle visibility (id:selectedItem) used to simulate the hover effect as showed below.
GridView{
id: grid
anchors.margins: 20
anchors.fill: parent
cellHeight: 80
cellWidth: 80
model: MyModel{}
delegate: Item{
height: grid.cellHeight * 0.9
width: grid.cellWidth * 0.7
Rectangle {
id: selectedItem
color: "lightsteelblue"
height: parent.height
width: parent.width
z:12
opacity: 0.7
visible: false
}
Rectangle {
id:rect
color: myColor;
height: parent.height-textName.height
width: parent.width
border.color: "white"
anchors.margins: 5
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
id: mouseRegion
anchors.fill: parent
hoverEnabled: true
onEntered: selectedItem.visible = true
onExited: selectedItem.visible = false
}
}
Text {
id: textName
text: name;
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
}
}
}
It allowed me to avoid automatic scroll and keep the hover effect work properly.
I have made an Item in QT QML which contains a MouseArea element.
Here is the code,
import QtQuick 1.0
Rectangle {
id: base
width: 240
height: 320
x:0; y:0
color: "#323138"
/////////////////////// MAIN FOCUSSCOPE //////////////////////
FocusScope {
id: mainfocus
height: base.height; width: base.width
focus: true
/////////////////////// MAIN GRID ///////////////////////////
GridView {
id: maingrid
width: base.width-10; height: base.height-titlebar.height-10
x: 5; y: titlebar.height+5;
cellHeight: maingrid.height/3; cellWidth: maingrid.width/3-1
Component {
id: myicon
Rectangle {
id: wrapper
height: maingrid.cellHeight-10; width: maingrid.cellWidth-10
radius: 8; smooth: true
color: GridView.isCurrentItem ? "#c0d0c0" : "transparent"
focus: true
MouseArea {
id: clickable
anchors.fill: wrapper
hoverEnabled: true
//onClicked: func()
}
Image {
id: iconpic
source: "./ui6.svg"
anchors.centerIn: wrapper
}
Text {
id: iconname
color: wrapper.GridView.isCurrentItem ? "black" : "#c8dbc8"
anchors.top: iconpic.bottom; anchors.horizontalCenter: iconpic.horizontalCenter
text: name
}
}
}
model: 4
delegate: myicon
focus: true
}
}
//////////////////////// TITLEBAR ///////////////////////
Rectangle {
id: titlebar
x:base.x
y:base.y
height: 25; width: base.width
color : "#356f47"
Text {
color: "#fdfdfd"
anchors.centerIn: titlebar
text: "title"
}
}
}
I want to make a grid of such Items so that it gives me a grid of custom made click-able Items that I created which I can use for different functions.
Using the GridView element, I was able to make such a grid which used number of my custom made Items as a template.
The problem is that when I click anyone of these Items it executes a single function since there was only one MouseArea element in my Item. I am able to detect a click on an Item, but not able to uniquely determine which Item was clicked. How do I achieve this ?
Of course, I might be doing it wrong, so other suggestions are also welcome.
Thanks
When the GridView item creates the instances they inherit the index variable. This identifies the unique item.
MouseArea {
id: clickable
anchors.fill: wrapper
hoverEnabled: true
onClicked: {
maingrid.currentIndex=index;
switch (index)
{
case 0:
//Function or method to call when clicked on first item
break;
case 1:
//Function or method to call when clicked on second item
break;
default:
//Function or method to call when clicked on another item
break;
}
}
}
I hope it helps you!