I have subclassed QTreeWidget (called it ToolsSelectorWidget) and enabled reordering in it by overriding QTreeWidget::dropEvent()
void ToolsSelectorWidget::dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid()) {
return;
}
QTreeWidget::dropEvent(event);
}
Also, I am adding QWidgets (QPushButton, QLineEdit) to top level items of QTreeWidget:
ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QTreeWidgetItem *part = new QTreeWidgetItem(this);
part->setFlags(part->flags() & Qt::ItemFlag((~Qt::ItemIsDropEnabled)));
setItemWidget(part, 0, new QLabel("Part" + QString::number(i) + " Preview", this));
setItemWidget(part, 1, new QLineEdit("Part" + QString::number(i) + " Name", this));
setItemWidget(part, 2, new QCheckBox("Part" + QString::number(i) + " Visible", this));
setItemWidget(part, 3, new QCheckBox("Part" + QString::number(i) + " Locked", this));
}
}
So now I have 4 top level items each containing 4 QWidgets. It's populating them fine, but when I rearrange them by drag and drop the QWidgets disappear and I end up having an empty row. What should I do to preserve them?
Before:
After Part2 has been moved and is under Part4, it's children have been preserved, but it's conents, which are QWidgets, are gone:
Why are widgets deleted?
When the drag and drop is performed, the data of the selected items is coded (roles and associated values) and saved in a QMimeData. When the drop is accepted, the source items are deleted and new items are created with the information stored in the QMimeData, inside the saved information there is no widgets information since this does not have relation with the model. And since the items are deleted, their widgets are also deleted.
To check it we can use the following example
#include <QApplication>
#include <QLabel>
#include <QTreeWidget>
#include <QDebug>
static void on_destroyed(){
qDebug()<<"destroyed";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeWidget w;
w.setSelectionMode(QAbstractItemView::SingleSelection);
w.setDragEnabled(true);
w.viewport()->setAcceptDrops(true);
w.setDropIndicatorShown(true);
w.setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i< 5; i++){
QTreeWidgetItem *it = new QTreeWidgetItem(&w);
QLabel *lbl = new QLabel(QString::number(i));
QObject::connect(lbl, &QObject::destroyed, on_destroyed);
w.setItemWidget(it, 0, lbl);
}
w.show();
return a.exec();
}
It shows that the widgets will emit the signal they destroy when you drag and drop the items.
Possible workaround:
One possible solution is to remove the widgets before accepting the drop and set them in the new items which I have not implemented.
I have explored another solution, it is to change the QTreeWidget for a QTreeView + QStandardItemModel. In the case of the QCheckBox, the checkboxes with the Qt::ItemIsUserCheckable flag are enabled, in the case of the QLineEdit a delegate will be used and to always be shown, the openPersistentEditor() method must be used.
#include <QApplication>
#include <QStandardItemModel>
#include <QTreeView>
#include <QHeaderView>
#include <QDropEvent>
#include <QStyledItemDelegate>
#include <QLineEdit>
class ToolsSelectorDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const{
QLineEdit *le = new QLineEdit(parent);
return le;
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const{
QRect r(option.rect);
r.adjust(2, 2, -2, -2);
editor->setGeometry(r);
}
};
class ToolsSelectorWidget: public QTreeView{
QStandardItemModel model;
public:
ToolsSelectorWidget(QWidget *parent=nullptr): QTreeView(parent){
setItemDelegate(new ToolsSelectorDelegate(this));
setModel(&model);
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QList<QStandardItem *> items;
for(const QString & text: {"Preview", "Name", "Visible", "Locked"}){
QStandardItem *it = new QStandardItem(QString("Part%1 %2").arg(i).arg(text));
it->setFlags(it->flags() & ~Qt::ItemIsDropEnabled & ~Qt::ItemIsEditable);
items.append(it);
if(text == "Visible" || text == "Locked"){
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
it->setCheckState(Qt::Unchecked);
}
else if (text == "Name") {
it->setFlags(it->flags() | Qt::ItemIsEditable);
}
}
for(const QString & children: {"The", "quick", "Brown", "fox", "jump...", "over", "the", "lazy", "dog"})
items.first()->appendRow(new QStandardItem(children));
model.invisibleRootItem()->appendRow(items);
for( int i = 0; i < model.rowCount(); ++i )
openPersistentEditor(model.index(i, 1));
}
}
protected:
void dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid())
return;
QTreeView::dropEvent(event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ToolsSelectorWidget w;
w.show();
return a.exec();
}
Related
My model designed to add row in the begining of table. Problem is that if I select second row (for example) selection still will be on this row (second) after adding new row, but original row moved down.
I figured out that QStandardItemModel provides propely behaviour. How to provide same one in my inherited QAbstractTableModel?
Now there is exapmle code for QStandardItemModel. You can select row and see what's going on.
#include <QApplication>
#include <QMainWindow>
#include <QStandardItemModel>
#include <QTableView>
#include <QTimer>
void timeout(QTimer &timer, QStandardItemModel &tblModel)
{
static int step = 0;
tblModel.insertRow(0);
QStandardItem *pItem = new QStandardItem(QString("row %0").arg(step));
tblModel.setItem(0, 1, pItem);
step++;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QMainWindow win;
QStandardItemModel tblModel(0, 1);
QTableView tblView;
tblView.setModel(&tblModel);
win.setCentralWidget(&tblView);
win.show();
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout,
[&timer, &tblModel]() { timeout(timer, tblModel); });
timer.start();
return app.exec();
}
And simplified version of my model. You can see that in my version selected row doesen't move.
#include <QApplication>
#include <QMainWindow>
#include <QStandardItemModel>
#include <QTableView>
#include <QTimer>
#include <QString>
class MyModel : public QAbstractTableModel
{
public:
MyModel(QObject *parent = nullptr);
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;
void insertRow(int data);
private:
QVector<int> table;
};
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent) {}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return table.count();
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 1;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return table[table.count() - 1 - index.row()];
}
return QVariant();
}
void MyModel::insertRow(int data) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
table.push_back(data);
endInsertRows();
}
void timeout(QTimer &timer, MyModel * tblModel)
{
static int step = 0;
tblModel->insertRow(step);
step++;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QMainWindow win;
MyModel * model = new MyModel();
QTableView tblView;
tblView.setModel(model);
win.setCentralWidget(&tblView);
win.show();
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout,
[&timer, model]() { timeout(timer, model); });
timer.start();
return app.exec();
}
I didn't find any information in QAbstractTableModel documentation. After that I tried to read QStandardItemModel source code but it's really difficult for understanding.
I have a treeview which is modeling a file tree. Each item in the TreeView is a QstandardItem that holds a file path. I would like to be able to get the file it referres to and drag the item into another application. The files are all video files so I would like to add the ability to drag and drop into VLC, Adobe Premier etc.
minialistic code :
main.cpp
drag d:/1.txt drop to notepad
#include <QtWidgets/QApplication>
#include <QMimeData>
#include <QTreeView>
#include <QDrag>
#include <QStandardItemModel>
#include<QUrl>
class Myodel :public QStandardItemModel
{
Q_OBJECT
public:
QStringList mimeTypes() const override
{
return QStringList(QLatin1String("text/uri-list"));
}
QMimeData* mimeData(const QModelIndexList& indexes) const override
{
QList<QUrl> urls;
QList<QModelIndex>::const_iterator it = indexes.begin();
for (; it != indexes.end(); ++it)
if ((*it).column() == 0)
urls << QUrl::fromLocalFile("d:/1.txt");
QMimeData* data = new QMimeData();
data->setUrls(urls);
return data;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Myodel* model = new Myodel;
QStandardItem* parentItem = model->invisibleRootItem();
for (int i = 0; i < 4; ++i) {
QStandardItem* item = new QStandardItem(QString("item %0").arg(i));
parentItem->appendRow(item);
parentItem = item;
}
QTreeView* tree = new QTreeView();
tree->setModel(model);
tree->setDragEnabled(true);
tree->show();
return a.exec();
}
#include"main.moc"
I subclassed a QTreeWidget in order to reimplement the protected functionalities of drag and drop in order to drag drop parents and children of a QTreeWidget.
It almost works and the problem is that as soon as I try to drag and drop either children or parents, they are erased as soon as I drop them.
Source code can be found here in case you need to see what is going on.
Also if I try to drag-drop the parent, it unfortunately does not move and nothing happens.
See below the strange effect for the children:
I drag the child "Original" to drop it right under "Sample" and this procedure seems to work correctly:
After dropping "Original" as you can see the index seems to be there but it seems to be partially erased:
I am not sure exactly of what is going on and why the children seems to not completely be dropped correctly and why the parents are literally not moving.
Below the code of the minimal verifiable example:
mainwindow.h
#include <QMainWindow>
class QTreeWidget;
class QTreeWidgetItem;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
QTreeWidgetItem *createItem(const QString &name, const QString &iconPath);
private:
Ui::MainWindow *ui;
QTreeWidget *widget;
QString m_Name;
QString m_Path;
};
#endif // MAINWINDOW_H
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui->treeWidget->setDragEnabled(true);
ui->treeWidget->viewport()->setAcceptDrops(true);
ui->treeWidget->setDropIndicatorShown(true);
ui->treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");
top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));
ui->treeWidget->addTopLevelItems({ top1, top2 });
// Below I am assigning to each parent/children a checkbox
const int n_tops = ui->treeWidget->topLevelItemCount();
for(int a = 0; a<n_tops; ++a) {
const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
for(int b = 0; b<n_children; ++b) {
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
}
}
}
MainWindow::~MainWindow()
{
delete ui;
}
QTreeWidgetItem *MainWindow::createItem(const QString &name, const QString &iconPath)
{
auto *item = new QTreeWidgetItem(QStringList{name});
item->setIcon(0, QIcon(iconPath));
return item;
}
maphelpers.h
#include <QTreeWidget>
#include <QTreeWidgetItem>
void qtreewidgetitem_assign_qcheckbox(QTreeWidget *tree_widget, QTreeWidgetItem *tree_item);
#endif // MAPHELPERS_H
maphelpers.cpp
#include <QTreeWidget>
void qtreewidgetitem_assign_qcheckbox(QTreeWidget *tree_widget, QTreeWidgetItem *tree_item)
{
MyCheckBoxMap *myCheckBoxMap = new MyCheckBoxMap(tree_item);
tree_widget->setItemWidget(tree_item, MAP_COLUMN, myCheckBoxMap);
}
Below how I subclassed QTreeWidget:
customtreewidget.h
#include <QTreeWidget>
class CustomTreeWidget : public QTreeWidget
{
public:
CustomTreeWidget(QWidget *parent = Q_NULLPTR);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
QTreeWidgetItem *draggedItem;
};
#endif // CUSTOMTREEWIDGET_H
customtreewidget.cpp
CustomTreeWidget::CustomTreeWidget(QWidget *parent)
{
QTreeWidgetItem* parentItem = new QTreeWidgetItem();
parentItem->setText(0, "Test");
parentItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled);
int num_rows = topLevelItemCount();
for(int i = 0; i < num_rows; ++i)
{
int nChildren = topLevelItem(i)->childCount();
QTreeWidgetItem* pItem = new QTreeWidgetItem(parentItem);
for(int j = 0; j < nChildren; ++j) {
pItem->setText(0, QString("Number %1").arg(i) );
pItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
pItem->addChild(pItem);
topLevelItem(i)->child(j);
}
}
}
void CustomTreeWidget::dragEnterEvent(QDragEnterEvent *event)
{
draggedItem = currentItem();
QTreeWidget::dragEnterEvent(event);
}
void CustomTreeWidget::dropEvent(QDropEvent *event)
{
QModelIndex droppedIndex = indexAt(event->pos());
if(!droppedIndex.isValid()) {
return;
}
if(draggedItem) {
QTreeWidgetItem *mParent = draggedItem->parent();
if(mParent) {
if(itemFromIndex(droppedIndex.parent()) != mParent)
return;
mParent->removeChild(draggedItem);
mParent->insertChild(droppedIndex.row(), draggedItem);
}
}
}
What I have done so far and what I have tried:
1) According to official documentation it is possible to use QTreeWidgetItemIterator Class and i tried to go that way and in fact you can see below my initial implementation idea, but this didn't really bring me anywhere:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");
top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));
ui->treeWidget->addTopLevelItems({ top1, top2 });
const int n_tops = ui->treeWidget->topLevelItemCount();
// Below I am assigning to each parent/children a checkbox
for(int a = 0; a<n_tops; ++a) {
const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
for(int b = 0; b<n_children; ++b) {
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
}
}
QTreeWidgetItem *parentItem = Q_NULLPTR;
QTreeWidgetItem *childItem = Q_NULLPTR;
QTreeWidgetItemIterator it(ui->treeWidget);
while (*it) {
parentItem = new QTreeWidgetItem(ui->treeWidget);
//parentItem->setText(0, it.key());
foreach (const auto &str, it) {
childItem = new QTreeWidgetItem;
childItem->setText(0, str);
parentItem->addChild(childItem);
}
}
}
2) After further research in the official documentation I decided to approach the problem applying the QMapIterator Class I wanted to keep the constructor as I structured initially and figured that passing through a QMap meant to rewrite the constructor in a different way, basically the implementation idea below, but didn't want to pass that way:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto *top1 = createItem("Images", "/home/ui/qrc/laserscan.png");
auto *top2 = createItem("Path", "/home/ui/qrc/laserscan.png");
top1->addChild(createItem("Original", "/home/ui/qrc/laserscan.png"));
top1->addChild(createItem("Sample", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Left Side", "/home/ui/qrc/laserscan.png"));
top2->addChild(createItem("Right Side", "/home/ui/qrc/laserscan.png"));
ui->treeWidget->addTopLevelItems({ top1, top2 });
ui->treeWidget->addTopLevelItems({ top1, top2 });
const int n_tops = ui->treeWidget->topLevelItemCount();
// Below I am assigning to each parent/children a checkbox
for(int a = 0; a<n_tops; ++a) {
const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
for(int b = 0; b<n_children; ++b) {
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
}
}
QTreeWidgetItem *parentItem = Q_NULLPTR;
QTreeWidgetItem *childItem = Q_NULLPTR;
QMapIterator<QString, QStringList> i(treeMap);
while (i.hasNext()) {
i.next();
parentItem = new QTreeWidgetItem(widget);
parentItem->setText(0, i.key());
foreach (const auto &str, i.value()) {
childItem = new QTreeWidgetItem;
childItem->setText(0, str);
parentItem->addChild(childItem);
}
}
}
3) After more research I cam across this source, and this other source which was useful (in particular the last post) as guidance to the implementation of the subclassing of the QTreeWidget.
Now I am stuck as I am trying to figure out why, despite all the things I tried, I achieved a 90% working application and, therefore, what is missing about it?
Thanks for providing guidance on how to solve this problem.
I think the checkboxes are handling the mouse events and don't let the events go through to the item. To have the desired behavior, you should remove this part:
const int n_tops = ui->treeWidget->topLevelItemCount();
for(int a = 0; a<n_tops; ++a) {
const int n_children = ui->treeWidget->topLevelItem(a)->childCount();
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a));
for(int b = 0; b<n_children; ++b) {
qtreewidgetitem_assign_qcheckbox(ui->treeWidget, ui->treeWidget->topLevelItem(a)->child(b));
}
}
And in the createItem() function:
QTreeWidgetItem *MainWindow::createItem(const QString &name, const QString &iconPath)
{
auto *item = new QTreeWidgetItem(QStringList{name});
item->setCheckState(0, Qt::Unchecked);
item->setIcon(0, QIcon(iconPath));
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
return item;
}
I have a SQLite database with three tables:
graph(ID int primary key, name varchar(64));
vertex(ID int primary key, graphID int references graph(ID), name varchar(64), x int default 0, y int default 0);
edge(ID int primary key, graphID int references graph(ID), sourceID int references vertex(ID), targetID int references vertex(ID), weight real default 1);
In my desktop app I'm using custom classes for model/view
MyTableView : public QTableView
VertexTableModel : public QSqlTableModel
EdgeTableModel : public QSqlRelationalTableModel
I'm setting them up like this:
GraphyEditor::GraphyEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::GraphyEditor),
vertexModel(new VertexTableModel(parent)),
edgeModel(new EdgeTableModel(parent)) {
ui->setupUi(this);
vertexModel->setTable("vertex");
ui->vertices->setModel(vertexModel); // ui->vertices is *MyTableView
edgeModel->setTable("edge");
//TODO find fix to the issue
// edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
// edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name"));
ui->edges->setModel(edgeModel); // ui->egdes is *MyTableView
}
This code works and displays the data correctly, but I would like to substitute columns 2 and 3 (sourceID and targetID) in edgeModel from vertex.ID to vertex.name
I did some searching and found the setRelation method (the same I commented out in my code), but when I use it the edgeModel table shows no edges.
Is it because of my tables schemas or is there something wrong in my code?
How do I achieve this?
EDIT:
Here are implementations of classes I'm using:
MyTableModel.h/cpp
#include <QtSql/QSqlTableModel>
class MyTableModel : public QSqlTableModel {
Q_OBJECT
public:
explicit MyTableModel(QObject *parent = nullptr);
void refresh();
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override = 0;
bool setData(const QModelIndex &index, const QVariant &value, int role) override = 0;
signals:
void databaseUpdated();
};
#endif
#include "MyTableModel.h"
#include <QDebug>
#include <utility>
#include <database/DBManager.h>
MyTableModel::MyTableModel(QObject *parent) :
QSqlTableModel(parent) {}
void MyTableModel::refresh() { select(); }
VertexTableModel.h/cpp
#include <model/MyTableModel.h>
class VertexTableModel : public MyTableModel {
Q_OBJECT
public:
explicit VertexTableModel(QObject *parent = nullptr);
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
};
#endif
bool VertexTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
// checks if value is valid and updates database
}
Qt::ItemFlags VertexTableModel::flags(const QModelIndex &index) const {
auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (index.column() == 2) flags |= Qt::ItemIsEditable;
return flags;
}
VertexTableModel::VertexTableModel(QObject *parent) : MyTableModel(parent) {}
EdgeTableModel.h/cpp
class EdgeTableModel : public QSqlRelationalTableModel {
Q_OBJECT
public:
explicit EdgeTableModel(QObject *parent = nullptr);
void refresh();
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
signals:
void databaseUpdated();
};
#endif
EdgeTableModel::EdgeTableModel(QObject *parent) : QSqlRelationalTableModel(parent) {
refresh();
}
void EdgeTableModel::refresh() { select(); }
bool EdgeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
// checks if value is valid and updates the database
}
Qt::ItemFlags EdgeTableModel::flags(const QModelIndex &index) const {
auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (index.column() == 4) flags |= Qt::ItemIsEditable;
return flags;
}
GraphyEditor.h/cpp
#ifndef GRAPHY_EDITOR_H
#define GRAPHY_EDITOR_H
#include <QMainWindow>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtGui/QRegExpValidator>
#include <QtSql/QSqlRelationalDelegate>
QT_BEGIN_NAMESPACE
namespace Ui { class GraphyEditor; }
QT_END_NAMESPACE
class GraphyEditor : public QMainWindow {
Q_OBJECT
Ui::GraphyEditor *ui;
QSqlTableModel *vertexModel;
QSqlRelationalTableModel *edgeModel;
QSqlRelationalDelegate *delegate;
QString graphID = "";
public:
explicit GraphyEditor(QWidget *parent = nullptr);
void setGraphID(const QString &newGraphID);
~GraphyEditor() override;
};
#endif
#include <QtWidgets/QWidget>
#include "GraphyEditor.h"
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtSql/QSqlRelationalDelegate>
GraphyEditor::GraphyEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::GraphyEditor),
// vertexModel(new VertexTableModel(parent)),
vertexModel(new QSqlTableModel(parent)),
// edgeModel(new EdgeTableModel(parent)) {
edgeModel(new QSqlRelationalTableModel(parent)) {
ui->setupUi(this);
vertexModel->setTable("vertex");
vertexModel->setHeaderData(1, Qt::Horizontal, "Vertex ID");
vertexModel->setHeaderData(2, Qt::Horizontal, "Vertex Name");
ui->vertices->setModel(vertexModel);
// ui->vertices->hideColumn(0);
ui->vertices->hideColumn(1);
ui->vertices->hideColumn(3);
ui->vertices->hideColumn(4);
edgeModel->setTable("edge");
//TODO find fix to the issue
// edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
// edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name as targetName"));
edgeModel->setHeaderData(2, Qt::Horizontal, "Source Vertex", Qt::DisplayRole);
edgeModel->setHeaderData(3, Qt::Horizontal, "Target Vertex", Qt::DisplayRole);
edgeModel->setHeaderData(4, Qt::Horizontal, "Weight");
delegate = new QSqlRelationalDelegate(this);
ui->edges->setModel(edgeModel);
ui->edges->setItemDelegate(delegate);
// ui->edges->setItemDelegateForColumn(2, delegate);
// ui->edges->setItemDelegateForColumn(3, delegate);
ui->edges->hideColumn(0);
ui->edges->hideColumn(1);
ui->canvas->setVertices(vertexModel);
ui->canvas->setEdges(edgeModel);
}
GraphyEditor::~GraphyEditor() {
delete ui;
delete vertexModel;
delete edgeModel;
delete delegate;
}
void GraphyEditor::setGraphID(const QString &newGraphID) {
GraphyEditor::graphID = newGraphID;
vertexModel->setFilter("graphID = " + newGraphID);
edgeModel->setFilter("graphID = " + newGraphID);
ui->canvas->setGraphID(newGraphID);
ui->canvas->refresh();
}
GraphyCanvas.h/cpp
#ifndef GRAPHY_CANVAS_H
#define GRAPHY_CANVAS_H
#include <QWidget>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
class GraphyCanvas : public QWidget {
Q_OBJECT
QSqlTableModel *vertexModel = nullptr;
QSqlRelationalTableModel *edgeModel = nullptr;
QString graphID = "";
public:
void setGraphID(const QString &newGraphID);
explicit GraphyCanvas(QWidget *parent = nullptr);
void setVertices(QSqlTableModel *vertexTableModel);
void setEdges(QSqlRelationalTableModel *edgeTableModel);
public slots:
void refresh();
};
#endif
#include <QPainter>
#include <QPen>
#include <QDebug>
#include <QtWidgets/QtWidgets>
#include <cmath>
#include "GraphyCanvas.h"
GraphyCanvas::GraphyCanvas(QWidget *parent) : QWidget(parent) {
QPalette newPalette = palette();
newPalette.setColor(QPalette::Window, Qt::white);
setPalette(newPalette);
}
void GraphyCanvas::refresh() {
vertexModel->select();
edgeModel->select();
for (auto child : children()) child->deleteLater();
int edgesCount = edgeModel->rowCount();
for (int e = 0; e < edgesCount; ++e) {
//paints edge objects
}
int verticesCount = vertexModel->rowCount();
for (int v = 0; v < verticesCount; ++v) {
//paints vertex objects
}
}
void GraphyCanvas::setVertices(QSqlTableModel *vertexTableModel) {
GraphyCanvas::vertexModel = vertexTableModel;
connect(vertexModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}
void GraphyCanvas::setEdges(QSqlRelationalTableModel *edgeTableModel) {
GraphyCanvas::edgeModel = edgeTableModel;
connect(edgeModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}
void GraphyCanvas::setGraphID(const QString &newGraphID) { GraphyCanvas::graphID = newGraphID; }
main.cpp
#include "setup/GraphySetup.h"
#include <QApplication>
#include <database/DBManager.h>
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
DBManager::initialize();
GraphyEditor editor;
editor.setGraphID("1");
editor.showMaximized();
return QApplication::exec();
}
DBManager is a helper class that's responsible for initializing and accessing the database
In GraphyEditor.cpp, function GraphyEditor::setGraphID, this line
edgeModel->setFilter("graphID = " + newGraphID);
should be
edgeModel->setFilter("edge.graphID = " + newGraphID);
The underlying query is a join, where the field name graphID belongs to more than one table, so the table name has to be specified along with the field name.
Your problem is probably hidden in some other place in your project that you don't provide in your question, but not in the code using QSqlRelationalTableModel.
There is only a minimal problem in your (commented) code: the two replaced columns would be having the same name: "name", but you can rename both columns while defining the QSqlRelation.
Here is a m.r.e. to illustrate how to deal with your two tables and a QSqlRelationalTableModel, just in case someone else comes to stackoverflow asking a similar question.
SQLite database dump:
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE `vertex` (
`ID` INTEGER,
`name` TEXT,
PRIMARY KEY(`ID`)
);
INSERT INTO vertex VALUES(1,'one');
INSERT INTO vertex VALUES(2,'two');
INSERT INTO vertex VALUES(3,'three');
INSERT INTO vertex VALUES(4,'four');
INSERT INTO vertex VALUES(5,'five');
INSERT INTO vertex VALUES(6,'six');
INSERT INTO vertex VALUES(7,'seven');
INSERT INTO vertex VALUES(8,'eight');
INSERT INTO vertex VALUES(9,'nine');
CREATE TABLE IF NOT EXISTS "edge" (
"ID" INTEGER,
"sourceID" INTEGER,
"targetID" INTEGER,
FOREIGN KEY("targetID") REFERENCES "vertex"("ID"),
PRIMARY KEY("ID"),
FOREIGN KEY("sourceID") REFERENCES "vertex"("ID")
);
INSERT INTO edge VALUES(1,1,4);
INSERT INTO edge VALUES(2,2,5);
INSERT INTO edge VALUES(3,3,6);
INSERT INTO edge VALUES(4,4,7);
INSERT INTO edge VALUES(5,5,8);
INSERT INTO edge VALUES(6,6,9);
COMMIT;
test.pro
QT = core sql
CONFIG += c++11 console
SOURCES += main.cpp
main.cpp
#include <QCoreApplication>
#include <QTextStream>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlRelationalTableModel>
#include <QSqlRecord>
#include <QSqlField>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream cout(stdout, QIODevice::WriteOnly);
QTextStream cerr(stderr, QIODevice::WriteOnly);
auto db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("test.db");
if (!db.open()) {
cerr << db.lastError().text() << endl;
return 1;
}
QSqlRelationalTableModel model;
model.setTable("edge");
model.setRelation(1, QSqlRelation("vertex", "ID", "name as sourceName"));
model.setRelation(2, QSqlRelation("vertex", "ID", "name as targetName"));
model.select();
auto rec = model.record();
// headers output
cout << qSetFieldWidth(15);
for(int i=0; i<rec.count(); ++i) {
cout << rec.field(i).name();
}
cout << endl;
// rows output
for(int i=0; i<model.rowCount(); ++i) {
rec = model.record(i);
cout << rec.value("ID").toInt() << rec.value("sourceName").toString() << rec.value("targetName").toString() << endl;
}
}
And this is the output of the program:
ID sourceName targetName
1 one four
2 two five
3 three six
4 four seven
5 five eight
6 six nine
Maybe modify selectStatement:
QString QSqlRelationalTableModel::selectStatement() const
......
//!!! my
fList.append(QLatin1String(", "));
fList.append(relTableAlias);
fList.append(QLatin1String("."));
fList.append(relation.indexColumn());
fList.append(QLatin1String(" as "));
fList.append(relation.tableName());
fList.append(QLatin1String("_"));
fList.append(relation.indexColumn());
Maybe you can look our open Qt project:
[github][1]
This project contains a wrapper above QSqlTableModel + QTableView and realize PblTableDlg class with basic table functionaliy.
We use a new variant of QSqlRelationalTableModel = PblSqkRelationalTableModel.
There is PblTableView (inherited by QTableView) and PblTableDlg that contains a db table view with all controls.
[1]:https://github.com/PavelDorofeev/Fork-Sql-Qt-4.8.1--SQLite-3--relations--calc-fields
I am using C++ Qt5. Currently I have a QStandardItemModel being displayed as a QTreeView with multiple rows and columns. I am aware of using setStyleSheet(), but the issue there is that every row and column that the mouse hovers is highlighted.
I would only like specific rows of the first column to be highlighted, and then have a function called for each cell highlighted that I would then use to manipulate my game.
The solution for a personalized painting is to use a custom delegate, and to indicate which item should change the color a role should be used, in the following code I show an example:
#include <QApplication>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTreeView>
class StyledItemDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const{
QStyledItemDelegate::initStyleOption(option, index);
if(index.data(Qt::UserRole +1).toBool())
option->backgroundBrush = QBrush(Qt::red);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView w;
StyledItemDelegate delegate(&w);
w.setItemDelegate(&delegate);
QStandardItemModel model;
model.setColumnCount(4);
w.setModel(&model);
for(int i=0; i<4; i++){
auto it = new QStandardItem(QString::number(i));
model.appendRow(it);
for(int j=0; j<3; j++){
it->appendRow(new QStandardItem(QString("%1-%2").arg(i).arg(j)));
}
}
QObject::connect(&w, &QTreeView::clicked, [&](const QModelIndex & index){
bool last_state = model.data(index, Qt::UserRole +1).toBool();
model.setData(index, !last_state, Qt::UserRole +1);
});
w.expandAll();
w.show();
return a.exec();
}