QML object does not update on display - c++

I created a QML object in C++. In my main method, I can set various properties that I have defined in my custom QML object. When set from the main method, the various properties are displayed as expected. When I set properties from within my update method, the display never updates. I have verified that the associated methods within my custom QML object are receiving the values that I am passing to it. Why wouldn't my display be updating?
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "horizontalBarGraph.h"
#include "speedometer.h"
#include "test.h"
#include <QTimer>
#include <QDebug>
//QObject *item = NULL;
static HorizontalBarGraph *ptrOilTemp = nullptr;
static int value = 0;
int update()
{
//qInfo() << "update() called.";
if(value > ptrOilTemp->getMaxValue())
{
value = 0;
}
ptrOilTemp->setActualValue(value);
qInfo() << "value = " << value;
value++;
//ptrOilTemp->setUnits("Test");
return 0;
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Speedometer>("com.ulasdikme.speedometer",1,0,"Speedometer");
qmlRegisterType<HorizontalBarGraph>("com.kubie.horizontalBarGraph", 1, 0, "HorizontalBarGraph");
qmlRegisterType<Test>("com.kubie.test", 1, 0, "Test");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
//item = engine.rootObjects().at(0)->findChild<QQuickItem*>("testing");
QObject *object = engine.rootObjects()[0];
QObject *oilTemp = object->findChild<QObject*>("oilTemp");
QObject *oilPressure = object->findChild<QObject*>("oilPressure");
QObject *coolantTemp = object->findChild<QObject*>("coolantTemp");
QObject *coolantPressure = object->findChild<QObject*>("coolantPressure");
QObject *intakeAirTemp = object->findChild<QObject*>("intakeAirTemp");
QObject *engineRPM = object->findChild<QObject*>("engineRPM");
//HorizontalBarGraph *ptrOilTemp = qobject_cast<HorizontalBarGraph*>(oilTemp);
ptrOilTemp = qobject_cast<HorizontalBarGraph*>(oilTemp);
HorizontalBarGraph *ptrOilPressure = qobject_cast<HorizontalBarGraph*>(oilPressure);
HorizontalBarGraph *ptrCoolantTemp = qobject_cast<HorizontalBarGraph*>(coolantTemp);
HorizontalBarGraph *ptrCoolantPressure = qobject_cast<HorizontalBarGraph*>(coolantPressure);
HorizontalBarGraph *ptrIntakeAirTemp = qobject_cast<HorizontalBarGraph*>(intakeAirTemp);
HorizontalBarGraph *ptrEngineRPM = qobject_cast<HorizontalBarGraph*>(engineRPM);
qreal val1 = 0;
//qreal val2 = 25;
qreal val3 = 100;
QString degF(QChar(0x00b0));
degF += "F";
ptrOilTemp->setSensorName("Oil Temp");
ptrOilTemp->setUnits(degF);
ptrOilTemp->setMinValue(val1);
ptrOilTemp->setMaxValue(val3);
//ptrOilTemp->setActualValue(val2);
ptrOilPressure->setSensorName("Oil Press");
ptrOilPressure->setUnits("PSI");
ptrCoolantTemp->setSensorName("Coolant Temp");
ptrCoolantTemp->setUnits(degF);
ptrCoolantPressure->setSensorName("Coolant Press");
ptrCoolantPressure->setUnits("PSI");
ptrIntakeAirTemp->setSensorName("Intake Air Temp");
ptrIntakeAirTemp->setUnits(degF);
ptrEngineRPM->setSensorName("");
ptrEngineRPM->setUnits("RPM");
qInfo() << "Initializing timer.";
QTimer timer;// = new QTimer(this);
QObject::connect(&timer, &QTimer::timeout, update);
timer.start(100);
return app.exec();
}
horizontalBarGraph.cpp
#include <QPainter>
#include "horizontalBarGraph.h"
HorizontalBarGraph::HorizontalBarGraph(QQuickItem *parent)
:QQuickPaintedItem(parent),
_horizontalBarGraphWidth(260),
_horizontalBarGraphHeight(75),
_minValue(0),
_maxValue(0),
_actualValue(0),
_sensorName("Sensor Name"),
_units("Units")
{
}
void HorizontalBarGraph::paint(QPainter *painter)
{
QRectF rect = this->boundingRect();
painter->setRenderHint(QPainter::Antialiasing);
QPen pen = painter->pen();
pen.setCapStyle(Qt::FlatCap);
QFont bottomFont("Arial", 14, QFont::Bold);
QFont topFont("Arial", 16, QFont::Bold);
QColor gray1(225, 225, 225);
QColor gray2(200, 200, 200);
QColor gray3(175, 175, 175);
QColor gray4(100, 100, 100);
//minValue, actualValue, maxValue
painter->save();
painter->setFont(bottomFont);
pen.setColor(gray1);
painter->setPen(pen);
painter->drawText(rect.adjusted(0, 50, -210, 0), Qt::AlignHCenter | Qt::AlignBottom, QString::number((_minValue), 'f', 0)); //Draws minValue
painter->drawText(rect.adjusted(210, 50, 0, 0), Qt::AlignHCenter | Qt::AlignBottom, QString::number((_maxValue), 'f', 0)); //Draws maxValue
painter->drawText(rect.adjusted(0, 50, 0, 0), Qt::AlignHCenter | Qt::AlignBottom, QString::number((_actualValue), 'f', 1));
//pen.setStyle(Qt::DotLine); //Next 3 lines were used to show the bounding rectangle for a drawText object
//painter->setPen(pen);
//painter->drawRect(rect.adjusted(0, 50, -150, 0));
painter->restore();
//Bar chart background
painter->save();
painter->fillRect(rect.adjusted(25, 35, -25, -25), gray1);
painter->restore();
//Lo
painter->save();
painter->fillRect(rect.adjusted(25, 35, -185, -25), gray2);
painter->restore();
//LoLo
painter->save();
painter->fillRect(rect.adjusted(25, 35, -210, -25), gray3);
painter->restore();
//Hi
painter->save();
painter->fillRect(rect.adjusted(185, 35, -25, -25), gray2);
painter->restore();
//HiHi
painter->save();
painter->fillRect(rect.adjusted(210, 35, -25, -25), gray3);
painter->restore();
//Sensor name, Units
painter->save();
painter->setFont(topFont);
pen.setColor(gray1);
painter->setPen(pen);
painter->drawText(rect.adjusted(25, 0, -50, -40), Qt::AlignLeft | Qt::AlignTop, _sensorName); //Draws sensor name
painter->drawText(rect.adjusted(50, 0, -25, -40), Qt::AlignRight | Qt::AlignTop, _units); //Draws units
painter->restore();
//Arrow
// painter->save();
// static const QPointF points[3] = {
// QPointF(17.5, 27.5),
// QPointF(32.5, 27.5),
// QPointF(25.0, 42.5)
// };
// painter->drawPolygon(points, 3);
// painter->restore();
painter->save();
QPainterPath path;
qreal x = scale(_actualValue, _minValue, _maxValue, 25, 235);
//path.moveTo(17.5, 27.5);
//path.lineTo(32.5, 27.5);
//path.lineTo(25.0, 42.5);
path.moveTo(x - 7.5, 27.5);
path.lineTo(x + 7.5, 27.5);
path.lineTo(x, 42.5);
path.closeSubpath();
painter->fillPath(path, gray4);
painter->restore();
}
qreal HorizontalBarGraph::getHorizontalBarGraphWidth()
{
return _horizontalBarGraphWidth;
}
qreal HorizontalBarGraph::getHorizontalBarGraphHeight()
{
return _horizontalBarGraphHeight;
}
qreal HorizontalBarGraph::getMinValue()
{
return _minValue;
}
qreal HorizontalBarGraph::getMaxValue()
{
return _maxValue;
}
qreal HorizontalBarGraph::getActualValue()
{
return _actualValue;
}
QString HorizontalBarGraph::getSensorName()
{
return _sensorName;
}
QString HorizontalBarGraph::getUnits()
{
return _units;
}
void HorizontalBarGraph::setHorizontalBarGraphWidth(qreal width)
{
if(_horizontalBarGraphWidth == width)
return;
_horizontalBarGraphWidth = width;
emit widthChanged();
}
void HorizontalBarGraph::setHorizontalBarGraphHeight(qreal height)
{
if(_horizontalBarGraphHeight == height)
return;
_horizontalBarGraphHeight = height;
emit heightChanged();
}
void HorizontalBarGraph::setMinValue(qreal minValue)
{
if(_minValue == minValue)
return;
_minValue = minValue;
emit minValueChanged();
}
void HorizontalBarGraph::setMaxValue(qreal maxValue)
{
if(_maxValue == maxValue)
return;
_maxValue = maxValue;
emit maxValueChanged();
}
void HorizontalBarGraph::setActualValue(qreal actualValue)
{
if(_actualValue == actualValue)
return;
_actualValue = actualValue;
emit actualValueChanged();
}
void HorizontalBarGraph::setSensorName(QString sensorName)
{
if(_sensorName == sensorName)
return;
_sensorName = sensorName;
emit sensorNameChanged();
}
void HorizontalBarGraph::setUnits(QString units)
{
if(_units == units)
return;
_units = units;
emit unitsChanged();
}
qreal HorizontalBarGraph::scale(qreal input, qreal inputMin, qreal inputMax, qreal outputMin, qreal outputMax)
{
qreal output = (outputMax - outputMin) * (input - inputMin) / (inputMax - inputMin) + outputMin;
if(output > outputMax)
output = outputMax;
if(output < outputMin)
output = outputMin;
return output;
}
horizontalBarGraph.h
#ifndef HORIZONTALBARGRAPH_H
#define HORIZONTALBARGRAPH_H
#include <QObject>
#include <QQuickPaintedItem>
class HorizontalBarGraph : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(qreal horizontalBarGraphWidth READ getHorizontalBarGraphWidth WRITE setHorizontalBarGraphWidth NOTIFY horizontalBarGraphWidthChanged)
Q_PROPERTY(qreal horizontalBarGraphHeight READ getHorizontalBarGraphHeight WRITE setHorizontalBarGraphHeight NOTIFY horizontalBarGraphHeightChanged)
Q_PROPERTY(qreal minValue READ getMinValue WRITE setMinValue NOTIFY minValueChanged)
Q_PROPERTY(qreal maxValue READ getMaxValue WRITE setMaxValue NOTIFY maxValueChanged)
Q_PROPERTY(qreal actualValue READ getActualValue WRITE setActualValue NOTIFY actualValueChanged)
Q_PROPERTY(QString sensorName READ getSensorName WRITE setSensorName NOTIFY sensorNameChanged)
Q_PROPERTY(QString units READ getUnits WRITE setUnits NOTIFY unitsChanged)
public:
HorizontalBarGraph(QQuickItem *parent = 0);
virtual void paint(QPainter *painter);
qreal getHorizontalBarGraphWidth();
qreal getHorizontalBarGraphHeight();
qreal getMinValue();
qreal getMaxValue();
qreal getActualValue();
QString getSensorName();
QString getUnits();
void setHorizontalBarGraphWidth(qreal width);
void setHorizontalBarGraphHeight(qreal height);
void setMinValue(qreal minValue);
void setMaxValue(qreal maxValue);
void setActualValue(qreal actualValue);
void setSensorName(QString sensorName);
void setUnits(QString units);
signals:
void horizontalBarGraphWidthChanged();
void horizontalBarGraphHeightChanged();
void minValueChanged();
void maxValueChanged();
void actualValueChanged();
void sensorNameChanged();
void unitsChanged();
private:
qreal _horizontalBarGraphWidth;
qreal _horizontalBarGraphHeight;
qreal _minValue;
qreal _maxValue;
qreal _actualValue;
QString _sensorName;
QString _units;
qreal scale(qreal input, qreal inputMin, qreal inputMax, qreal outputMin, qreal outputMax);
};
#endif // HORIZONTALBARGRAPH_H
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
import com.kubie.horizontalBarGraph 1.0
Window {
id: window
visible: true
width: 800
height: 480
title: qsTr("REDNEKSLDHLR")
color: "black"
// Rectangle
// {
// width: 260
// height: 75
// color: "#7a0505"
// anchors.bottom: parent.bottom
// anchors.bottomMargin: 0
// anchors.left: parent.left
// anchors.leftMargin: 0
// }
HorizontalBarGraph {
objectName: "oilTemp"
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
HorizontalBarGraph {
objectName: "oilPressure"
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 75
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
HorizontalBarGraph {
objectName: "coolantTemp"
anchors.right: parent.right
anchors.rightMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
HorizontalBarGraph {
objectName: "coolantPressure"
anchors.right: parent.right
anchors.rightMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 75
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
HorizontalBarGraph {
objectName: "intakeAirTemp"
anchors.left: parent.left
anchors.leftMargin: parent.width / 2 - width / 2;
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
HorizontalBarGraph {
objectName: "engineRPM"
anchors.left: parent.left
anchors.leftMargin: parent.width / 2 - width / 2;
anchors.bottom: parent.bottom
anchors.bottomMargin: 75
width: horizontalBarGraphWidth
height: horizontalBarGraphHeight
}
}

You have to use the update() method so that the GUI is repainted, so you must invoke it every time a property that is used to do the painting is modified. By example:
void HorizontalBarGraph::setActualValue(qreal actualValue) {
if(_actualValue == actualValue)
return;
_actualValue = actualValue;
update();
emit actualValueChanged();
}

Related

QQuickPaintedItem not visible in QML designer

I have created QQuickPaintedItem class that is responsible for painting UI stuff with overridable paint() method . The component is placed in Qml but i am not able to see it in designer.But i am able to see the result on runtime.
I am following https://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html
Nothing works , i really need to see the output in qml designer not on runtime.
As i am following https://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html
here is the source code for header file
class TextBalloon : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool rightAligned READ isRightAligned WRITE setRightAligned NOTIFY rightAlignedChanged)
public:
TextBalloon(QQuickItem *parent = 0);
void paint(QPainter *painter);
bool isRightAligned();
void setRightAligned(bool rightAligned);
private:
bool rightAligned;
signals:
void rightAlignedChanged();
};
and definition
TextBalloon::TextBalloon(QQuickItem *parent)
: QQuickPaintedItem(parent)
, rightAligned(false){}
void TextBalloon::paint(QPainter *painter){
QBrush brush(QColor("#007430"));
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing);
QSizeF itemSize = size();
painter->drawRoundedRect(0, 0, itemSize.width(), itemSize.height() - 10, 10, 10);
if (rightAligned)
{
const QPointF points[3] = {
QPointF(itemSize.width() - 10.0, itemSize.height() - 10.0),
QPointF(itemSize.width() - 20.0, itemSize.height()),
QPointF(itemSize.width() - 30.0, itemSize.height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
else
{
const QPointF points[3] = {
QPointF(10.0, itemSize.height() - 10.0),
QPointF(20.0, itemSize.height()),
QPointF(30.0, itemSize.height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
}
and here is qml file
ListModel {
id: balloonModel
ListElement {
balloonWidth: 200
}
ListElement {
balloonWidth: 120
}
}
ListView {
anchors.bottom: controls.top
anchors.bottomMargin: 2
anchors.top: parent.top
id: balloonView
delegate: TextBalloon {
anchors.right: index % 2 == 0 ? undefined : parent.right
height: 60
rightAligned: index % 2 == 0 ? false : true
width: balloonWidth
}
model: balloonModel
spacing: 5
width: parent.width
}
Please help with this i really need to see the painted output in qml designer
or please suggest another painting way by which i can see the output in qml designer.
thanks:)

Inserting/Deleting Items in a drag&drop QML listView with cpp model

I tried to add to a ListView in QML of N Items a way to add and delete a new Item at a given index.
I did the following example, but the problem is that when I move some Items, when I try to insert a new one, the position might be incorrect and I have no clue why. When I check my DataList in my cpp model, positions are correct, however, new or deleted items won't be inserted/deleted at the right position.
It seems that the error occurs when I insert a new Item, then I move it , and then I try to delete this Item or insert an Item next to this New Item.
Here is a simple example (you can run it if you need). I called my Items Data : Blocks
#include "mainwindow.h"
#include <QApplication>
#include <QtQml>
#include <QQuickView>
#include <QQuickWidget>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
main.cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "model.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void addItem(int index);
~MainWindow();
private slots:
private:
QList<QObject*> dataList;
Ui::MainWindow *ui;
BlockModel model;
int cpt = 0;
};
#endif // MAINWINDOW_H
mainwindow.h
#include <QtQml>
#include <QQuickView>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QQuickWidget"
#include <QStringList>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
int nbItems = 5;
for(; cpt < nbItems; cpt ++) {
Block a = Block(QString("Item ")+QString::number(cpt));
model.addBlock(a);
}
ui->setupUi(this);
QQuickWidget *view = new QQuickWidget;
QQmlContext *ctxt = view->rootContext();
ctxt->setContextProperty("myModel", &model);
view->setSource(QUrl::fromLocalFile("main.qml"));
view->setGeometry(0, 200, 600, 400);
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
ui->dockWidget_3->setWidget(view);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.cpp
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
#include <QDebug>
#include <QStringList>
//![0]
class Block
{
public:
Block(){
}
Block(const QString &name);
QString nameBlock() const;
void setName(QString n) {
m_name = n;
}
private:
QString m_name;
};
class BlockModel : public QAbstractListModel
{
Q_OBJECT
public:
Block* getBlock(QString name);
Q_INVOKABLE void moveBlock(int from,int to);
Q_INVOKABLE void insertBlock(int index);
Q_INVOKABLE void deleteBlock(int index);
enum BlockRoles {
nameRole = Qt::UserRole + 1,
};
BlockModel(QObject *parent = 0);
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void setName(const QString &name);
void addBlock(const Block &Block);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<Block> m_blocks;
QQmlContext* m_ctx;
int cpt = 0;
};
mode.h
#include "model.h"
#include "qDebug"
Block::Block(const QString &name)
: m_name(name)
{
}
QString Block::nameBlock() const
{
return m_name;
}
BlockModel::BlockModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void BlockModel::addBlock(const Block &Block)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_blocks << Block;
endInsertRows();
}
int BlockModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_blocks.count();
}
void BlockModel::moveBlock(int from, int to) {
m_blocks.move(from,to);
}
void BlockModel::insertBlock(int index) {
Block b =(Block(QString("New Item ")+QString::number(cpt)));
beginInsertRows(QModelIndex(),index+1,index+1);
m_blocks.insert(index+1,b);
endInsertRows();
cpt++;
}
void BlockModel::deleteBlock(int index) {
beginRemoveRows(QModelIndex(),index,index);
m_blocks.removeAt(index);
endRemoveRows();
}
QVariant BlockModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_blocks.count())
return QVariant();
const Block &Block = m_blocks[index.row()];
if (role == nameRole)
return Block.nameBlock();
return QVariant();
}
//![0]
QHash<int, QByteArray> BlockModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[nameRole] = "nameBlock";
return roles;
}
model.cpp
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls.Styles 1.4
Rectangle {
id : rootRectangle
visible: true
ScrollView {
anchors.fill:parent
ListView{
id: root
width: parent.width; height: parent.height
property int visualIndex: -1
displaced: Transition {
NumberAnimation { properties: "y"; easing.type: Easing.OutQuad }
}
model: DelegateModel {
id: visualModel
model: myModel
delegate: Component {
MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
cursorShape: Qt.PointingHandCursor
width: root.width; height: 100
drag.target: icon
drag.axis: Drag.YAxis
Behavior on height {
PropertyAnimation { duration: 100 }
}
Rectangle {
anchors.top: delegateRoot.top
anchors.left: delegateRoot.left
id: icon
objectName: nameBlock
width: root.width-5; height: 100
color: "skyblue"
radius: 3
Text {
objectName: "rect"
id: title
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: nameBlock
}
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: 36
Drag.hotSpot.y: 36
Button {
id : buttonAdd
text: "Add Block"
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.insertBlock(visualIndex)
}
}
Button {
id : buttonDelete
text: "Delete Block"
anchors{
right: buttonAdd.left
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.deleteBlock(visualIndex)
}
}
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
transitions: Transition {
// Make the state changes smooth
ParallelAnimation {
ColorAnimation { property: "color"; duration: 500 }
NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width,font.pixelSize,font.bold,visible" }
}
}
}
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualInde)
}
}
}
}
}
}
}
}
main.qml
Do you have any idea of what I am doing wrong ?
Thanks a lot and have a good day !
There are two bugs when moving items. In DropArea.onEntered, if you print out both drag.source.visualIndex and delegateRoot.visualIndex before and after visualModel.items.move, you'll see that values are modified after moving. That means you are moving wrong rows when calling myModel.moveBlock. To fix the problem, save the value before moving items:
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
var from = drag.source.visualIndex;
var to = delegateRoot.visualIndex;
myModel.moveBlock(from, to);
}
}
When moving items in C++ model, QAbstractItemModel::beginMoveRows should be called just like insert/remove items. Otherwise the QML DelegateModel cannot correctly display your model. Remember that when implementing BlockModel::moveBlock, the destination row for the model is different from the one for your source list m_blocks. See the last example in QAbstractItemModel::beginMoveRows documentation for detail.
void BlockModel::moveBlock(int from, int to) {
if (from == to)
return;
auto modelFrom = from;
auto modelTo = to + (from < to ? 1 : 0);
beginMoveRows(QModelIndex(), modelFrom, modelFrom, QModelIndex(), modelTo);
m_blocks.move(from,to);
endMoveRows();
}

QML GridView doesn't reflect changes in C++ model

I follow the Using C++ Models with Qt Quick Views and AbstractItemModel example project in Qt 5.5. Screen class is the model data; ScreenManager is derived from QAbstractListModel acting as the model source in QML GridView. In the GridView, I also add a MouseArea and some animation in delegate to implement items drag and drop.
My expected result is when dragging a screen around, whenever it's on top of another screen, the target screen will be moved to the last position of dragged screen until button release to drop the dragged screen. All the movement should use the animation declared in QML.
Now it could show screens correctly and recognize the selected screen. But it fails to swap the dragged and dropped screens. The underlying list has swapped the element but it doesn't reflect to the view.
A similar question is this, I try to use beginMoveRows and endMoveRows. But my program crashes on calling endMoveRows. layoutChanged would rearrange the whole model. Because I have animation on grid item movement. layoutChanged would cause non affected screens shifting from top left to their original position.
Edit: endMoveRows crash is caused by invalid operation described here.
Edit: the GridView has a 3 * 5 items. Since it's in a QList, I assume I only need to move on rows.
Screen.h
class Screen
{
public:
Screen(QString name, int gridId, bool active = false);
QString name() const;
int gridId() const;
bool active() const;
void setActive(bool a);
private:
QString m_name;
int m_gridId;
bool m_active;
};
ScreenManager.h
#include "Screen.h"
#include <QAbstractListModel>
class ScreenManager : public QAbstractListModel
{
Q_OBJECT
public:
enum ScreenRoles {
NameRole = Qt::UserRole + 1,
GridIDRole,
ActiveRole
};
ScreenManager();
void addScreen(const Screen& screen);
int rowCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
Q_INVOKABLE int getScreenGridId(int index);
Q_INVOKABLE bool getScreenActive(int index);
Q_INVOKABLE void swapScreens(int index1, int index2);
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Screen> m_screens;
};
ScreenManager.cpp
#include "ScreenManager.h"
#include "Screen.h"
ScreenManager::ScreenManager()
{
int index = 0;
for (;index < 15; index++) {
addScreen(Screen(QString ("Screen%1").arg(index), index, true));
}
}
void ScreenManager::addScreen(const Screen& screen)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_screens << screen;
endInsertRows();
}
int ScreenManager::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_screens.count();
}
QVariant ScreenManager::data(const QModelIndex& index, int role) const
{
if (index.row() < 0 || index.row() >= m_screens.count())
return QVariant();
const Screen& screen = m_screens[index.row()];
if (role == NameRole)
return screen.name();
else if (role == GridIDRole)
return screen.gridId();
else if (role == ActiveRole)
return screen.active();
return QVariant();
}
QHash<int, QByteArray> ScreenManager::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[GridIDRole] = "gridId";
roles[ActiveRole] = "active";
return roles;
}
int ScreenManager::getScreenGridId(int index)
{
return m_screens.at(index).gridId();
}
bool ScreenManager::getScreenActive(int index)
{
return m_screens.at(index).active();
}
void ScreenManager::swapScreens(int index1, int index2)
{
int min = index1 < index2 ? index1 : index2;
int max = index1 > index2 ? index1 : index2;
bool r = beginMoveRows(QModelIndex(), min, min, QModelIndex(), max);
r = beginMoveRows(QModelIndex(), max-1, max-1, QModelIndex(), min);
m_screens.swap(index1, index2);
endMoveRows();
}
QML GridView related code
ScreenManager {
id : screenManager
}
GridView {
id: gridView
x: 82
y: 113
width: cellWidth * 5
height: cellHeight * 3
clip: true
anchors.bottom: parent.bottom
anchors.bottomMargin: 70
anchors.topMargin: 100
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
flickableDirection: Flickable.HorizontalAndVerticalFlick
cellWidth: 90; cellHeight: 90;
property bool ignoreMovementAnimation: true
MouseArea {
id: gridViewMouseArea
hoverEnabled: true
preventStealing : true
property int currentGridId: -1
property int preIndex
property int index: gridView.indexAt(mouseX, mouseY)
anchors.fill: parent
onPressAndHold: {
currentGridId = screenManager.getScreenGridId(index)
preIndex = index
preIndexBackup = preIndex
gridView.ignoreMovementAnimation = false
}
onReleased: currentGridId = -1
onPositionChanged: {
if (currentGridId != -1 && index != -1 && index != preIndex) {
if (screenManager.getScreenActive(index)) {
screenManager.swapScreens(preIndex, index)
preIndex = index
}
}
}
}
model: screenManager
delegate: Component {
Item {
id: gridViewDelegate
width: gridView.cellWidth; height: gridView.cellHeight
Image {
id: itemImage
parent: gridView
x: gridViewDelegate.x + 5
y: gridViewDelegate.y + 5
width: gridViewDelegate.width - 10
height: gridViewDelegate.height - 10;
fillMode: Image.PreserveAspectFit
smooth: true
source: "qrc:/res/image/screen_icon.png"
visible: active
Text {
text: name
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
anchors.fill: parent;
border.color: "grey"
border.width: 6
color: "transparent"; radius: 5
visible: itemImage.state === "active"
}
// specify the movement's animation for non-active screen icons
Behavior on x {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
// specify the shaking animation for non-active screen icons when hold one icon
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: gridViewMouseArea.currentGridId != -1 && itemImage.state !== "active"
loops: Animation.Infinite
alwaysRunToEnd: true
}
// specify the active screen's new position and size
states: State {
name: "active"
when: gridViewMouseArea.currentGridId == gridId
PropertyChanges {
target: itemImage
x: gridViewMouseArea.mouseX - width/2
y: gridViewMouseArea.mouseY - height/2
scale: 0.5
z: 10
}
}
// specify the scale speed for the active screen icon
transitions: Transition {
NumberAnimation { property: "scale"; duration: 200}
}
}
}
}
}
main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<ScreenManager>("com.gui", 1, 0, "ScreenManager");
QQmlApplicationEngine engine(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
It turns out I need to deal some corner cases to avoid no-op or invalid move operation.
void ScreenManager::swapScreens(int index1, int index2)
{
int min = index1 < index2 ? index1 : index2;
int max = index1 > index2 ? index1 : index2;
m_screens.swap(index1, index2);
beginMoveRows(QModelIndex(), max, max, QModelIndex(), min);
endMoveRows();
if (max - min > 1) {
beginMoveRows(QModelIndex(), min + 1, min + 1, QModelIndex(), max + 1);
endMoveRows();
}
}

Override QQuickImageProvider requestImage()

I am using QQuickImageProvider and have taken a class object(PageBuffer) in requestimage function:
class ImageProvider : public QQuickImageProvider{
public:
explicit ImageProvider();
virtual QImage requestImage(int id, QSize *size, const QSize& requestedSize,PageBuffer p);
~ImageProvider();
};
I want to set QImage of ImageProvider using the variable saved in the PageBuffer object and use the id as index
Something like this:
QImage ImageProvider:: requestImage(int id, QSize *size, const QSize &requestedSize,PageBuffer p) {
return p.imagelist[id];
}
Here is my QML file where i want to call the Image Provider:
Image{
x: 4
y: 4
height : imagerec.height
visible: true
width : imagerec.width
anchors.fill: imagerec
source:fileUrl
Text{
id:txt
x: 0
y: 71
text:"Sketch"+(index+1)
horizontalAlignment: txt.AlignHCenter
font.family: "Tahoma"
color:"#ffffff"
}
MouseArea {
anchors.rightMargin: -59
anchors.bottomMargin: -39
anchors.fill: parent
onClicked: {
p.index=index;
p.image=mod.get(index).fileUrl
main.source="image://image/"+index
}
}
}
Ok guys, you can begin from this example by Qt.
Also below the piece of code demonstrates using Image Provider to load random images from Internet, although I think that it is very trivial.
myimageprovider.h
class MyImageProvider : public QQuickImageProvider
{
public:
MyImageProvider(ImageType type, Flags flags = 0);
~MyImageProvider();
QImage requestImage(const QString & id, QSize * size, const QSize & requestedSize);
protected:
QNetworkAccessManager *manager;
};
myimageprovider.cpp
MyImageProvider::MyImageProvider(ImageType type, Flags flags) :
QQuickImageProvider(type,flags)
{
manager = new QNetworkAccessManager;
}
MyImageProvider::~MyImageProvider()
{
delete manager;
}
QImage MyImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
QUrl url("http://lorempixel.com/" + id);
QNetworkReply* reply = manager->get(QNetworkRequest(url));
QEventLoop eventLoop;
QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
eventLoop.exec();
if (reply->error() != QNetworkReply::NoError)
return QImage();
QImage image = QImage::fromData(reply->readAll());
size->setWidth(image.width());
size->setHeight(image.height());
return image;
}
Registering our image provider in main.cpp:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyImageProvider *imageProvider = new MyImageProvider(QQmlImageProviderBase::Image);
engine.addImageProvider("myprovider",imageProvider);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
And finally using the provider in QML:
Window {
visible: true
width: 800
height: 600
Image {
id: img
anchors.centerIn: parent
source: "image://myprovider/500/500/"
onStatusChanged: {
if(status == Image.Ready)
indicator.running = false;
}
BusyIndicator {
id: indicator
anchors.centerIn: parent
running: false
}
MouseArea {
anchors.fill: parent
onClicked: {
indicator.running = true;
img.source = "image://myprovider/500/500/?seed=" + Math.random(1000)
}
}
}
}

QML ListView and Segmentation fault

I'm using a QML ListView with a SectionScroller & QAbstractListModel. I noticed that I get a segmentation fault in memcpy (never called explicitly) when I'm normally scrolling (without usage of SectionScroller)
Do you have any idea why it's happening?
I tried to reproduce it, and now the segmentation fault is
0x402f9c3a in ?? () from /usr/lib/libQtScript.so.4
0x402f9c3a: ldrh r1, [r7, r3]
The debug symbols are there, though no valuable info is dumped. The other time the Segfault was
0x0000cab8 in QBasicAtomicInt::ref (this=0x0)
at /usr/include/QtCore/qatomic_armv6.h: 119
It's strange since AFAIK N900's processor is armv7 /edit: on N950 it uses the same and in Qt sources are only for ARM qatomic_arm.h and qatomic_armv6.h so it should be ok.
ListView{
id: irrview
width: parent.width
model: irregulars
anchors.top: caption.bottom
anchors.bottom: parent.bottom
spacing: 5
clip: true
section.criteria: ViewSection.FirstCharacter
section.property: "form0"
delegate: Rectangle{
id: del
property int fontSize: 20
height: 60
width: parent.width
color: "#E0E1E2"
Row{
height: parent.height
width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter
property real columnWidth: (width - 10) / 3
property int rad: 10
spacing: 5
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form0
font.pointSize: del.fontSize
}
}
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form1
font.pointSize: del.fontSize
}
}
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form2
font.pointSize: del.fontSize
}
}
}
}
}
The model is:
#ifndef IRREGULARLISTWRAPPER_H
#define IRREGULARLISTWRAPPER_H
#include <QObject>
#include <QList>
#include <QAbstractListModel>
#include <QMap>
#include "IrregularVerb.h"
#include "AbstractIrregularList.h"
class IrregularListWrapper : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString langName READ getLangName NOTIFY langChanged)
Q_PROPERTY(int count READ rowCount NOTIFY langChanged)
Q_ENUMS(Language)
public:
Q_INVOKABLE int rowCount(const QModelIndex& = QModelIndex()) const { return db->count(); }
Q_INVOKABLE QObject* get(int index) const {return db->at(index);}
QVariant data(const QModelIndex &index, int role) const;
enum Language
{
English = 0,
German = 1
};
enum IrregularVerbRoles
{
Form0Role = Qt::UserRole + 1,
Form1Role,
Form2Role
};
IrregularListWrapper();
// ~IrregularListWrapper() { delete db; }
// QList<QObject*> getdb() const { return *db; }
QString getLangName() const { return langName; }
Q_INVOKABLE void changeLang(Language l) { beginResetModel(); db = 0; /*QList<IrregularVerb*>();*/ setLang(l); endResetModel(); }
static QMap<Language, QString> plugins;
signals:
void langChanged();
protected:
void setLang(Language);
//QList<IrregularVerb*> db;
QString langName;
AbstractIrregularList * db;
};
#endif // IRREGULARLISTWRAPPER_H
QMap<IrregularListWrapper::Language, QString> IrregularListWrapper::plugins;
IrregularListWrapper::IrregularListWrapper()
{
QHash<int, QByteArray> roles;
roles[Form0Role] = "form0";
roles[Form1Role] = "form1";
roles[Form2Role] = "form2";
const QString pluginPath = "/opt/MeeIrregulars/share/lib%1.so";
plugins[English] = pluginPath.arg("english");
plugins[German] = pluginPath.arg("german");
setRoleNames(roles);
setLang(German);
}
QVariant IrregularListWrapper::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
const IrregularVerb* verb = db->at(index.row());
switch (role)
{
case Form0Role:
return verb->getForm0();
break;
case Form1Role:
return verb->getForm1();
break;
case Form2Role:
return verb->getForm2();
break;
}
return QVariant();
}
void IrregularListWrapper::setLang(Language l)
{
QPluginLoader loader(plugins[l]);
db = qobject_cast<AbstractIrregularList*>(loader.instance());
if (db == 0) db = new AbstractIrregularList;
switch (l)
{
case English:
langName = "English";
break;
case German:
langName = "German";
break;
}
emit langChanged();
}
class IrregularVerb : public QObject
{
Q_OBJECT
Q_PROPERTY(QString form0 READ getForm0 NOTIFY formChanged)
Q_PROPERTY(QString form1 READ getForm1 NOTIFY formChanged)
Q_PROPERTY(QString form2 READ getForm2 NOTIFY formChanged)
public:
QString forms[3];
QString getForm0() const { return forms[0]; }
QString getForm1() const { return forms[1]; }
QString getForm2() const { return forms[2]; }
IrregularVerb(QString a, QString b, QString c) { forms[0] = a; forms[1] = b; forms[2] = c; }
signals:
void formChanged();
};
Backtrace:
#0 QBasicAtomicInt::ref (this=0x18)
#1 QString (this=0xbe88d2a0, other=...)
#2 IrregularVerb::getForm2 (this=0x9e6de8)
#3 IrregularVerbWrapper::data(this=0x9e31b8, index=..., role=35) // the model// some calls to libQtDeclarative
Thanks.
It was a problem with ownership. The elements returned by get were owned by JS and automatically destroyed. See this answer for more information.