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)
}
}
}
}
Related
I have some issues changing the text of a QML window in Qt. I have a C++ file that calls a thread and from there I'm trying to change the value of the text label. The thread is running correctly but the text value from the QML is not changing. Below is part of my code:
main.cpp:
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///template.qml")));
QQuickItem *label = engine.rootObjects().at(0)->findChild<QQuickItem*>("myLabel");
thread2 thread(label);
thread.start();
}
Thread.cpp:
thread2::thread2(QQuickItem *label) {
this->label = label;
}
void thread2::run() {
int test = 0;
char buffer[10];
int speed = 100;
while(1) {
speed++;
sprintf(buffer,"%d km/h",speed);
this->label->setProperty("text", QString(buffer));
QThread::msleep(1000);
qDebug()<<"tic: "<<buffer<<endl;
}
template.qml:
Window {
id: window
visible: true
width: 360
height: 360
Component {
id: fruitDelegate
Row {
spacing: 10
Text { text: name }
Text { text: '$' + cost }
}
}
Text {
width: 99
height: 19
text: qsTr("Speed: ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 0
anchors.centerIn: parent
objectName: "lab"
}
Text {
width: 38
height: 19
text: qsTr(" 0 ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 46
anchors.centerIn: parent
objectName: "myLabel"
}
}
Can anyone tell me why is not working? Where is my mistake?
Thanks!
You have 2 errors:
You should not update the GUI from another thread, the run method is executed in another thread so Qt does not guarantee that it works correctly.
Do not export an element from QML to C++ because it brings several problems since it is many times impossible to obtain the object through the objectname, another inconvenient is that the life cycle of the Item is determined by QML so at a given moment it could be eliminated so that label could point to an unreserved memory making its use, etc. Instead it exports the C++ object to QML.
Considering the above the solution is:
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent=nullptr);
~Thread() override;
Q_SLOT void stop();
Q_SIGNAL void textChanged(const QString & text);
protected:
void run() override;
};
#endif // THREAD_H
thread.cpp
#include "thread.h"
#include <QDebug>
Thread::Thread(QObject *parent):
QThread(parent)
{
}
Thread::~Thread()
{
}
void Thread::stop()
{
requestInterruption();
wait();
}
void Thread::run()
{
int speed = 100;
QString text;
while(!isInterruptionRequested()) {
speed++;
text = QString("%1 km/h").arg(speed);
Q_EMIT textChanged(text);
QThread::msleep(1000);
qDebug()<<"tic: "<< text;
}
}
main.cpp
#include "thread.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Thread thread;
QObject::connect(&app, &QGuiApplication::aboutToQuit, &thread, &Thread::stop);
thread.start();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("thread", &thread);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
// ...
Text {
id: txt
width: 38
height: 19
text: qsTr(" 0 ")
anchors.verticalCenterOffset: 1
anchors.horizontalCenterOffset: 46
anchors.centerIn: parent
objectName: "myLabel"
}
Connections{
target: thread
onTextChanged: txt.text = text
}
// ...
For more information read:
https://doc.qt.io/qt-5/qtquick-bestpractices.html#interacting-with-qml-from-c
https://doc.qt.io/qt-5/thread-basics.html#gui-thread-and-worker-thread
You shouldn't modify the UI from another thread. Use signal/slot instead.
You should not create a child class from QThread, also (create a worker and move it in another thread).
Item {
id: rooItem
visible: true
anchors.fill: parent
signal change(string s);
onChange: foobar.text = s
Text {
id: foobar
text: "Empty"
}
}
class Worker: public QObject
{
Q_OBJECT
public slots:
void run()
{
while(1) {
QThread::msleep(1000);
emit changed(QDateTime::currentDateTime().toString());
}
}
signals:
void changed(QString);
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QThread* th = new QThread();
Worker* worker = new Worker();
worker->moveToThread(th);
QObject::connect(th, &QThread::started, worker, &Worker::run);
th->start();
QQuickView view(QStringLiteral("qrc:/Main.qml"));
QObject* o = view.rootObject();
QObject::connect(worker, SIGNAL(changed(QString)), o, SIGNAL(change(QString)));
view.showMaximized();
return app.exec();
}
You are using the wrong mechanism to update a qml property, take a look at QQmlProperty for the right way. You could also export a QObject instance into the qml engine and bind the labels text property to some property of that object. Always keep in mind that qml/qt quick are essentially hacks. There is a way to update the gui safely from a non-gui thread without using signals. instead you can use events to do the work.
I am trying to make the model QAbstractTableModel in cpp and connect to qml.
This code works well.
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
explicit MyModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
};
#endif // MYMODEL_H
MyModel.cpp
#include "MyModel.h"
#include <QDebug>
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
qDebug() << __FUNCTION__;
addAnimal(Animal("Wolf", "Medium"));
addAnimal(Animal("Polar bear", "Large"));
addAnimal(Animal("Quoll", "Small"));
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_animals.size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << __FUNCTION__ << index.row() << index.column() << role;
if (!index.isValid())
return QVariant();
const Animal &animal = m_animals[index.row()];
switch (role) {
case TypeRole:
return animal.type();
case SizeRole:
return animal.size();
default:
break;
}
return QVariant();
}
void MyModel::addAnimal(const Animal &animal)
{
qDebug() << __FUNCTION__;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
qDebug() << __FUNCTION__;
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyModel model;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main_test.qml
import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: main_view
width: 250
height: 600
visible: true
ListView {
id: list_view
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal Test: " + type + ", " + size }
}
TableView {
id: table_view
objectName: "tableView"
width: 250; height: 250
anchors.top: list_view.bottom
model: myModel
TableViewColumn {
id: type_col
role: "type"
title: "Type"
width: 100
}
TableViewColumn {
id: size_col
role: "size"
title: "Size"
width: 100
}
}
}
It looks like this
But, if I change the main.cpp a little bit, the list view dispaly as normal, but not the table view.
main.cpp
#include "MainView.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainView mainView;
return app.exec();
}
MainView.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
};
#endif // MAINVIEW_H
MainView.cpp
#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>
MainView::MainView(QObject *parent)
: QObject(parent)
{
initializeView();
}
void MainView::initializeView()
{
MyModel model;
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
It looks like this.
I really don't understand why this happens. What the difference between ListView and TableView in the second case? And how to fix it, i.e. make data display in the second case? Thank in advance.
The problem is in initializeView, model is a local variable in that scope, and every local variable is deleted from the memory when the function finishes executing. In the first case model will be eliminated when the application is closed, in the second case it will be eliminated when initializeView ends that is when the window is displayed, there are 2 possible solutions:
Create a pointer and to manage the memory we pass to MainView as a parent so that he removes it from memory (this last is a feature of the QObjects):
void MainView::initializeView()
{
MyModel *model = new MyModel(this);
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
Make the model member of the class.
*.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
MyModel model{this};
};
#endif // MAINVIEW_H
*.cpp
...
void MainView::initializeView()
{
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
To understand the behavior I have added more points of impression:
MyModel::~MyModel()
{
qDebug()<<"destructor";
}
TableView {
...
Component.onCompleted: {
console.log("completed table")
if(!timer.running)
timer.running = true
}
}
ListView {
...
Component.onCompleted: {
console.log("completed list")
if(!timer.running)
timer.running = true
}
}
Timer {
id: timer
interval: 0;
onTriggered: console.log(myModel, table_view.model, list_view.model)
}
And I get the following:
MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null
We note that ListView manages to load the data, whereas TableView in the middle of the load is called the destructor.
Possible explanation: I think that the ListView stores the data making a copy of them and only updates when the model notifies, should also be notified when the model is deleted to clean the data, it seems that this is a bug. On the other hand, TableView, being at the moment of loading and deleting the model, is null, so it is notified and cleans the data.
Doing another test:
void MainView::initializeView()
{
MyModel *model = new MyModel;
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleShot(1000, model, &MyModel::deleteLater);
}
It is observed that both load the data correctly, and after a second the model is destroyed, but the one that is only notified is the TableView since it is the only one that cleans the data shown, and ListView does not.
my conclusion that it is a ListView bug.
I'm trying to use QML TreeView Model. The example from Qt doesn't include how to create the model. I read this post and tried to use the code from #Tarod but the result is not what I expected.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animalmodel.h"
#include <qqmlcontext.h>
#include <qqml.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal("wolf", "Medium");
model.addAnimal("Bear", "Large");
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
animalmodel.h
#ifndef ANIMALMODEL_H
#define ANIMALMODEL_H
#include <QStandardItemModel>
class AnimalModel : public QStandardItemModel
{
Q_OBJECT //The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
Q_INVOKABLE void addAnimal(const QString &type, const QString &size);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
};
#endif // ANIMALMODEL_H
animalmodel.cpp
#include "animalmodel.h"
AnimalModel::AnimalModel(QObject *parent)
: QStandardItemModel(parent)
{
}
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
QStandardItem* entry = new QStandardItem();
entry->setData(type, TypeRole);
auto childEntry = new QStandardItem();
childEntry->setData(size, SizeRole);
entry->appendRow(childEntry);
appendRow(entry);
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
QStandardItem *myItem = itemFromIndex(index);
if (role == TypeRole)
return myItem->data(TypeRole);
else if (role == SizeRole) {
if (myItem->child(0) != 0)
{
return myItem->child(0)->data(SizeRole);
}
}
return QVariant();
}
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open")
onTriggered: messageDialog.show(qsTr("Open Action Triggered"));
}
MenuItem {
text: qsTr("&Exit")
onTriggered: Qt.quit();
}
}
}
TreeView {
anchors.fill: parent
model: myModel
TableViewColumn {
title: "Name"
role: "type"
width: 300
}
TableViewColumn {
title: "Size"
role: "size"
width: 300
}
}
}
What I got is something like this:
Result
What I want to have is the animal size as a child of animal type.
Model sub-classing is one of the worst minefields in Qt. The advice is always to have it go through the model test (https://wiki.qt.io/Model_Test) to see if everything was implemented correctly.
On the other hand, in 90% of the cases you do not need to subclass a model at all as the default models provided by Qt work quite well. What I'd do is just use QStandardItemModel using, on the C++ side, only the QAbstractItemModel interface (i.e. force yourself to use QAbstractItemModel* model = new QStandardItemModel(/*parent*/);) this way, if in the future you feel like you really need to reimplement the model (for efficiency) you'll just need to change 1 line in your existing code.
In your case:
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
if(columnCount()==0) insertColumn(0); // make sure there is at least 1 column
insertRow(rowCount()); // add a row to the root
const QModelIndex addedIdx = index(rowCount()-1,0);
setData(addedIdx, type, TypeRole); // set the root data
insertRow(rowCount(addedIdx),addedIdx ); // add 1 row ...
insertColumn(0,addedIdx ); // ... and 1 column to the added root row
setData(index(0,0,addedIdx), size, SizeRole); // set the data to the child
}
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();
}
I try to bind a QIcon provided by a class derived from QAbstractListModel to QML Image like this:
Component {
id: myDelegate
//...
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Image {
source: model.DecorationRole
}
Text {
text: model.DisplayRole
//...
}
But this results in this error:
Unable to assign QIcon to QUrl
How to do this correctly?
You can not assign a QIcon as a source of a QML Image.
What you need to do is choose a custom URL format for your icons, e.g.
images://myicons/<icon_id>
This string is what you set as a URL for Image.source.
Now you create and register an imageprovider, that delivers the icon when you send the requested URL from QML:
Create a subclass of QQuickImageProvider, e.g. MyIconProvider
Overwrite the function requestPixmap that takes the id as and argument and returns pixel data
Then you register the image provider in your main.cpp
MyIconProvider *mip = new MyIconProvider();
engine.addImageProvider("myicons", mip);
I figured out this solution. It works, but any suggestions or improvements are very welcome.
ImageProvider.h
class ImageProvider : public QQuickImageProvider
{
public:
explicit ImageProvider(myModel *myModel);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
signals:
public slots:
private:
myModel *m_myModel;
};
ImageProvider.cpp
ImageProvider::ImageProvider(myModel *myModel) :
QQuickImageProvider(QQuickImageProvider::Pixmap),
m_myModel(myModel)
{
}
QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
QModelIndex index;
bool foundId = false;
for(int row = 0; row < m_myModel->rowCount(); row++)
{
index = m_myModel->index(row, 0);
QString name = QVariant(m_myModel->data(index, Qt::DisplayRole)).toString();
if(name == id)
{
foundId = true;
break;
}
}
if(!foundId)
return QPixmap();
QIcon icon = m_myModel->data(index, Qt::DecorationRole).value<QIcon>();
QPixmap pixmap = icon.pixmap(128,128);
return pixmap;
}
register imageProvider...
ImageProvider *imageProvider = new ImageProvider(myModel);
view->engine()->addImageProvider(QLatin1String("provider"), imageProvider);
main.qml
Component {
id: myDelegate
//...
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 5
Image {
source: "image://provider/" + model.DisplayRole
}
Text {
text: model.DisplayRole
//...
}