Drag and drop of QTreeWidgetItem does not work properly - c++

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;
}

Related

How to force a model to update QComboBox when data changed?

I created model for QComboBox:
#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H
#include <QModelIndex>
class QComboBoxModel : public QAbstractListModel
{
public:
QComboBoxModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
QVariant data(const QModelIndex &index, int role) const;
void populate(const QList<QPair<int,QString>> &values);
private:
QList<QPair<int,QString>> values;
};
#endif // QCOMBOBOXMODEL_H
code
#include "qcomboboxmodel.h"
#include <QModelIndex>
QComboBoxModel::QComboBoxModel(QObject *parent)
:QAbstractListModel(parent)
{
}
int QComboBoxModel::rowCount(const QModelIndex &) const
{
return values.count();
}
QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->values.value(index.row()).second;
}
break;
case Qt::UserRole: //data
{
value = this->values.value(index.row()).first;
}
break;
default:
break;
}
return value;
}
void QComboBoxModel::populate(const QList<QPair<int,QString>> &values)
{
this->values = values;
}
Now i use it
values.append(QPair<int,QString>(-1,"Select item"));
values.append(QPair<int,QString>(10,"item1(0)"));
values.append(QPair<int,QString>(11,"item1(1)"));
values.append(QPair<int,QString>(21,"item1(2)"));
values.append(QPair<int,QString>(32,"item1(3)"));
values.append(QPair<int,QString>(44,"item1(4)"));
newidx = 50;
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
and on button click i add new item to combobox
newidx++;
QString strIdx = QString().number(newidx);
values.append(QPair<int,QString>(newidx,"New item("+strIdx+")"));
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
Its all seems works just fine, but problem here that i need to recreate model every time i add new item to combobox data
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
Is that a proper way to do so? Or there are another way to force model update combobox when data updated?
According to "Model Subclassing Reference" in the documentation you have to do many more things to make an editable model. Is there a reason why you don't use a ready made model like QStandardItemModel?
comboModel = new QStandardItemModel(0, 2, this);
ui->comboBox1->setModel(comboModel);
comboModel->insertRow(0);
comboModel->setData(comboModel->index(0, 0), -1);
comboModel->setData(comboModel->index(0, 1), "Select item");
//and so on
//and the data is available as
int number = comboModel->data(comboModel->index(0, 0)).toInt();
QString itemtext = comboModel->data(comboModel->index(0, 1)).toString();
I find solution!
First i add new method to model
void QComboBoxModel::append(int index, QString value)
{
int newRow = this->values.count();
this->beginInsertRows(QModelIndex(), newRow, newRow);
values.append(QPair<int,QString>(index,value));
endInsertRows();
}
Now on button click method changed to this
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
model->append(newidx,"new item " + strIdx );
}
Point is to use beginInsertRows and endInsertRows to notify model that data actually changed!
Now all worked as expected!
Now you also can modify append method to batch add rows to it, but i think if you add many rows it better just recreate model and reassign it to combobox.
Update 1:
Also keep in mind, that you update values QList inside model, so if you add
qDebug() << values;
in on_pushButton_clicked() method, you always see
(QPair(-1,"Select item"), QPair(10,"item1(0)"), QPair(11,"item1(1)"), QPair(21,"item1(2)"), QPair(32,"item1(3)"), QPair(44,"item1(4)"))
Update 2:
Also i update populate method
void QComboBoxModel::populate(const QList<QPair<int,QString>> &newValues)
{
int oldIdx = this->values.count();
int newIdx = newValues.count();
this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
this->values = newValues;
endInsertRows();
}
Now you can just work with values list
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
values.append(QPair<int,QString>(newidx,"new item " + strIdx));
model->populate(values);
qDebug() << values;
}
Update 3:
Now, i find one big problem - i did not use pointer inside model, so when i pass QList to model it just create copy instead use already created, so i rewrite model and other code:
Model
#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H
#include <QModelIndex>
class QComboBoxModel : public QAbstractListModel
{
public:
QComboBoxModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
QVariant data(const QModelIndex &index, int role) const;
void populate(QList<QPair<int,QString>> *newValues);
void append(int index, QString value);
private:
QList<QPair<int,QString>> *values;
};
#endif // QCOMBOBOXMODEL_H
#include "qcomboboxmodel.h"
#include <QModelIndex>
#include <QDebug>
QComboBoxModel::QComboBoxModel(QObject *parent)
:QAbstractListModel(parent)
{
values = new QList<QPair<int,QString>>();
}
int QComboBoxModel::rowCount(const QModelIndex &) const
{
return values->count();
}
QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->values->value(index.row()).second;
}
break;
case Qt::UserRole: //data
{
value = this->values->value(index.row()).first;
}
break;
default:
break;
}
return value;
}
void QComboBoxModel::populate(QList<QPair<int,QString>> *newValues)
{
int oldIdx = this->values->count();
int newIdx = newValues->count();
this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
this->values = newValues;
endInsertRows();
}
void QComboBoxModel::append(int index, QString value)
{
int newRow = this->values->count();
this->beginInsertRows(QModelIndex(), newRow, newRow);
values->append(QPair<int,QString>(index,value));
endInsertRows();
}
Main form
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "qcomboboxmodel.h"
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_comboBox_currentIndexChanged(int index);
void on_comboBox_currentIndexChanged(const QString &arg1);
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
int newidx;
QList<QPair<int,QString>> *values;
QComboBoxModel *model;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "qcomboboxmodel.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
values = new QList<QPair<int,QString>>();
values->append(QPair<int,QString>(-1,"Select item"));
values->append(QPair<int,QString>(10,"item1(0)"));
values->append(QPair<int,QString>(11,"item1(1)"));
values->append(QPair<int,QString>(21,"item1(2)"));
values->append(QPair<int,QString>(32,"item1(3)"));
values->append(QPair<int,QString>(44,"item1(4)"));
newidx = 50;
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_comboBox_currentIndexChanged(int index)
{
qDebug() << ui->comboBox->itemData(index).value<int>();
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
qDebug() << arg1;
}
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
values->append(QPair<int,QString>(newidx,"new item " + strIdx));
model->populate(values);
qDebug() << values->toStdList();
}
Now all looks just fine and works as intended!
Why don't you request the current model via "QAbstractItemModel * model() const" from the QComboBox; alter it and assign it again (via "void QComboBox::setModel(QAbstractItemModel *model)")?

Qt C++ Drag QHeaderView between tables

I want to copy the selected column of a QTableWidget to another one.
So I tried to make selected columns draggable by adding this code:
void makeDraggable(QTableWidget *table)
{
table->setDragEnabled(true);
table->setAcceptDrops(true);
table->setSelectionBehavior(QAbstractItemView::SelectColumns);
}
Result I got:
But I want to drag a whole column (horizontal and vertical headers) by clicking on headers only, not on cells, and copy its data to another table including the header text.
Dragging between different tables inside one application can be done with reimplementing custom QHeaderView and QTableWidget. In my example I generate text with indecies of table and column for drag event. Custom header:
#include <QHeaderView>
class ITableManager;
class DraggableHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit DraggableHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
int tag() const;
void setTag(const int tag);
void setTableManager(ITableManager* manager);
protected:
void mouseMoveEvent(QMouseEvent *e);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
signals:
public slots:
private:
int m_tag; //internal index of table
ITableManager *m_tableManager; //manager will convert table index into pointer
};
Custom header cpp
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QDebug>
#include <QTableWidget>
#include <ITableManager.h>
DraggableHeaderView::DraggableHeaderView(Qt::Orientation orientation, QWidget *parent) :
QHeaderView(orientation, parent)
{
m_tag = 0;
m_tableManager = 0;
setAcceptDrops(true);
}
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
//custom drag text with indecies inside
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec();
}
}
int DraggableHeaderView::tag() const
{
return m_tag;
}
void DraggableHeaderView::setTag(const int tag)
{
m_tag = tag;
}
void DraggableHeaderView::dragEnterEvent(QDragEnterEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QString dragText = event->mimeData()->text();
int index = dragText.indexOf("MoveHeader;");
if (index == 0)
{
event->accept();
}
else
{
event->ignore();
}
}
void DraggableHeaderView::dropEvent(QDropEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QStringList dragText = event->mimeData()->text().split(';');
if (dragText.count() < 3 || dragText.at(0) != "MoveHeader")
{
event->ignore();
return;
}
int tableIndex = dragText.at(1).mid(6).toInt();//6 - length 'Table:'
QTableWidget* tableSrc = m_tableManager->getTableFromIndex(tableIndex);
if (!tableSrc)
{
event->ignore();
return;
}
//dst table as parent for header view
QTableWidget *tableDst = qobject_cast<QTableWidget*> (this->parentWidget());
if (!tableDst)
{
event->ignore();
return;
}
//move column: modify for your needs
//now moves only items text
int columnIndex = logicalIndexAt(event->pos());
int srcColumnIndex = dragText.at(2).mid(6).toInt(); //6 - length of 'Index:'
tableDst->insertColumn(columnIndex);
for (int iRow = 0; iRow < tableDst->rowCount() && iRow < tableSrc->rowCount(); ++iRow)
{
if (tableSrc->item(iRow, srcColumnIndex))
{
tableDst->setItem(iRow, columnIndex,
new QTableWidgetItem(tableSrc->item(iRow, srcColumnIndex)->text()));
}
else
{
tableDst->setItem(iRow, columnIndex, new QTableWidgetItem());
}
}
tableSrc->removeColumn(srcColumnIndex);
}
void DraggableHeaderView::setTableManager(ITableManager *manager)
{
m_tableManager = manager;
}
Now create custom QTableWidget with DraggableHeaderView inside
class CustomTableWidget : public QTableWidget
{
Q_OBJECT
public:
explicit CustomTableWidget(QWidget *parent = 0);
void setTag(const int tag);
void setTableManager(ITableManager* manager);
};
CustomTableWidget::CustomTableWidget(QWidget *parent) :
QTableWidget(parent)
{
DraggableHeaderView *headerView = new DraggableHeaderView(Qt::Horizontal, this);
setHorizontalHeader(headerView);
setAcceptDrops(true);
}
void CustomTableWidget::setTag(const int tag)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTag(tag);
}
}
void CustomTableWidget::setTableManager(ITableManager *manager)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTableManager(manager);
}
}
For converting table index to pointer I use ITableManager
class ITableManager
{
public:
virtual QTableWidget* getTableFromIndex(const int index) = 0;
};
And implement it in QMainWindow
class MainWindow : public QMainWindow, ITableManager
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QTableWidget* getTableFromIndex(const int index);
}
QTableWidget * MainWindow::getTableFromIndex(const int index)
{
switch (index)
{
case 1:
return ui->tableWidget;
case 2:
return ui->tableWidget_2;
default:
return nullptr;
}
}
Dont forget setup tags (indecies) and table manager for tables (in main window constructor)
ui->tableWidget->setTag(1);
ui->tableWidget_2->setTag(2);
ui->tableWidget->setTableManager(this);
ui->tableWidget_2->setTableManager(this);
EDIT: If you want change custom pixmap for dragging just set QDrag::setPixmap
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
drag->setPixmap(pixmapForDrag(index));
Qt::DropAction dropAction = drag->exec();
}
}
And method for taking pixmap of column can be like this
QPixmap DraggableHeaderView::pixmapForDrag(const int columnIndex) const
{
QTableWidget *table = qobject_cast<QTableWidget*> (this->parentWidget());
if (!table)
{
return QPixmap();
}
//image for first 5 row
int height = table->horizontalHeader()->height();
for (int iRow = 0; iRow < 5 && iRow < table->rowCount(); ++iRow)
{
height += table->rowHeight(iRow);
}
//clip maximum size
if (height > 200)
{
height = 200;
}
QRect rect(table->columnViewportPosition(columnIndex) + table->verticalHeader()->width(),
table->rowViewportPosition(0),
table->columnWidth(columnIndex),
height);
QPixmap pixmap(rect.size());
table->render(&pixmap, QPoint(), QRegion(rect));
return pixmap;
}

QDockWidget does not resize properly when interacting with GUI commands of QMainWindow

I have a problem regarding the the non correct resize of the QDockWidget. Specifically when I launch the GUI, the QDockWidget appears like in the image below which is wrong. Also I adjust the size of the QDockWidget during the use of the .ui, however as soon as I interact with the .ui (e.g. using a QPushButton or using a QCheckBox) the QDockWidget gets bigger again:
The expected behavior is the one below, which it does not increase dimension abruptly during the interaction with the .ui but rather remains in position as below:
Below is the most important part of the code I am using for this project and I signed the 3 debugging errors notified by the compiler with // <-- ERROR HERE if that can be useful:
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mNewText = new QPlainTextEdit;
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
mDockWidget_A->installEventFilter(new QDockResizeEventFilter(mNewText,dynamic_cast<QFluidGridLayout*>(mNewText->layout())));
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);
}
qdockresizeeventfilter.h
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include <QCoreApplication>
#include <QMouseEvent>
#include "qfluidgridlayout.h"
#include "mainwindow.h"
class QDockResizeEventFilter : public QObject
{
public:
friend QMainWindow;
friend QLayoutPrivate;
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = nullptr)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height()); // <-- ERROR HERE
if (dock->size().width() != fixedSize.width())
{
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
// cause mainWindow dock layout recalculation
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
// adding dock widgets causes the separator move event to end
// restart it by synthesizing a mouse press event
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom());
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// Do nothing
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h_
and finally qfluidgridlayout.h
#ifndef QFluidGridLayout_h_
#define QFluidGridLayout_h_
#include <QLayout>
#include <QGridLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>
class QFluidGridLayout : public QLayout
{
public:
enum Direction { downToUp, UpToDown };
QFluidGridLayout(QWidget *parent = nullptr)
: QLayout(parent)
{
setContentsMargins(8,8,8,8);
setSizeConstraint(QLayout::SetMinAndMaxSize);
}
~QFluidGridLayout() {
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void addItem(QLayoutItem *item) {
itemList.append(item);
}
Qt::Orientations expandingDirections() const {
return nullptr;
}
bool hasHeightForWidth() const {
return false;
}
int heightForWidth(int width) const {
int height = doLayout(QRect(0, 0, width, 0), true, true);
return height;
}
bool hasWidthForHeight() const {
return true;
}
int widthForHeight(int height) const { // <-- ERROR HERE
int width = doLayout(QRect(0, 0, 0, height), true, false);
return width;
}
int count() const {
return itemList.size();
}
QLayoutItem *itemAt(int index) const {
return itemList.value(index);
}
QSize minimumSize() const {
QSize size;
QLayoutItem *item;
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2*margin(), 2*margin());
return size;
}
void setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
doLayout(rect);
}
QSize sizeHint() const {
return minimumSize();
}
QLayoutItem *takeAt(int index) {
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return nullptr; }
private:
int doLayout(const QRect &rect, bool testOnly = false, bool width = false) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom); // <-- ERROR HERE
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
int lineWidth = 0;
QLayoutItem* item;
foreach(item,itemList)
{
QWidget* widget = item->widget();
if (y + item->sizeHint().height() > effectiveRect.bottom() && lineWidth > 0) {
y = effectiveRect.y();
x += lineWidth + right;
lineWidth = 0;
}
if (!testOnly) {
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
y += item->sizeHint().height() + top;
lineHeight = qMax(lineHeight, item->sizeHint().height());
lineWidth = qMax(lineWidth, item->sizeHint().width());
}
if (width) {
return y + lineHeight - rect.y() + bottom;
}
else {
return x + lineWidth - rect.x() + right;
}
}
QList<QLayoutItem *> itemList;
Direction dir;
};
#endif // QFluidGridLayout_h_
I have been reading about this issue frequently here and in this post. However I have been reading about the possibility that this specific object may have some bugs and it was advised to overwrite a resiveEvent. However none of this worked.
I finally, after doing a good amount of research, found this useful post which almost replicates the problem I have and that carries the majority of the two classes above class QFluidGridLayout and class QDockResizeEventFilter.
Although I am using the same approach I am still not able to achieve a normal behavior of this object.
I am also including a snapshot of the debugger:
Can someone explain what am I doing wrong? Thanks so much for shedding light on this issue.
#Emanuele, The post you saw it is mainly for sub-classing the QDockWidget as a child and therefore that solution had to be implemented manually. I think that if you look at this alternative solution you will find it useful.
Try to modify on your constructor adding resizeDocks({dock}, {100}, Qt::Horizontal); as in the post so to have:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mNewText = new QPlainTextEdit;
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
mDockWidget_A->installEventFilter(new QDockResizeEventFilter(mNewText,dynamic_cast<QFluidGridLayout*>(mNewText->layout())));
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);
resizeDocks({mDockWidget_A}, {100}, Qt::Horizontal);
}

Subclass of QPlainText does not expand to fill the layout

I do not understand why the CodeEditor example from the Qt website does not appear to work as expected. Every time I run the code it displays it like this, really small and not expanding to take up all the available space. Does anyone have any idea why? I have even tried to set a fixed size, sizepolicy and minimum sizing. Not sure what I am missing here.
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QVBoxLayout>
#include <QLabel>
#include "codeeditor.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// controls
auto *widget_main = new QWidget(this);
auto *lay_main = new QVBoxLayout(widget_main);
auto *label = new QLabel("Test");
auto *editor = new CodeEditor();
// layout
lay_main->addWidget(label);
lay_main->addWidget(editor);
lay_main->setContentsMargins(5, 5, 5, 5);
}
MainWindow::~MainWindow()
{
}
codeeditor.h:
#ifndef CODEEDITOR_H
#define CODEEDITOR_H
#include <QPlainTextEdit>
#include <QObject>
class QPaintEvent;
class QResizeEvent;
class QSize;
class QWidget;
class LineNumberArea;
// Main text editor
class CodeEditor : public QPlainTextEdit
{
Q_OBJECT
public:
CodeEditor(QWidget *parent = 0);
void lineNumberAreaPaintEvent(QPaintEvent *event);
int lineNumberAreaWidth();
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void highlightCurrentLine();
void updateLineNumberArea(const QRect &, int);
private:
QWidget *lineNumberArea;
};
// Line number gutter
class LineNumberArea : public QWidget
{
public:
LineNumberArea(CodeEditor *editor) : QWidget(editor) {
codeEditor = editor;
}
QSize sizeHint() const override {
return QSize(codeEditor->lineNumberAreaWidth(), 0);
}
protected:
void paintEvent(QPaintEvent *event) override {
codeEditor->lineNumberAreaPaintEvent(event);
}
private:
CodeEditor *codeEditor;
};
#endif
codeeditor.cpp:
#include "codeeditor.h"
#include <QtWidgets>
#include <QFontMetrics>
CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
lineNumberArea = new LineNumberArea(this);
connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
updateLineNumberAreaWidth(0);
highlightCurrentLine();
setFixedSize(200, 200);
setMinimumSize(200,200);
}
int CodeEditor::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
//int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
int space = 3 + 12 * digits;
return space;
}
void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
if (dy)
lineNumberArea->scroll(0, dy);
else
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineNumberAreaWidth(0);
}
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
void CodeEditor::highlightCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!isReadOnly()) {
QTextEdit::ExtraSelection selection;
QColor lineColor = QColor(Qt::yellow).lighter(160);
selection.format.setBackground(lineColor);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
setExtraSelections(extraSelections);
}
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
int bottom = top + (int) blockBoundingRect(block).height();
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + (int) blockBoundingRect(block).height();
++blockNumber;
}
}
QMainWindow is a special widget since it has a preset layout
So you must set the main widget through setCentralWidget():
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto *widget_main = new QWidget;
auto *lay_main = new QVBoxLayout(widget_main);
auto *label = new QLabel("Test");
auto *editor = new CodeEditor();
setCentralWidget(widget_main); // <-- +++
// layout
lay_main->addWidget(label);
lay_main->addWidget(editor);
lay_main->setContentsMargins(5, 5, 5, 5);
}
On the other hand if you are going to use layouts then you should not set a fixed size to the widget, in your case remove setFixedSize(200, 200) on the other hand it is recommended that you make the connections with the new syntax:
CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
lineNumberArea = new LineNumberArea(this);
connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::updateLineNumberArea);
connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
updateLineNumberAreaWidth(0);
highlightCurrentLine();
// setFixedSize(200, 200); <-- ---
setMinimumSize(200,200);
}

How do I print QStringListModel content?

I need to print the content of a QStringListModel to a printer, in 'void MainWindow::on_pbImprime_clicked()' It's printing any Qstring with no problems, but I don't know how to put the data of QStringListModel to my QString text, anyone have an idea?
Here is my code:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
model = new QStringListModel(this);
ui->lbItens->setModel(model);
ui->lbItens->setEditTriggers(QAbstractItemView::AnyKeyPressed |
QAbstractItemView::DoubleClicked);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pbAdd_clicked()
{
int row = model->rowCount();
model->insertRows(row,1);
QModelIndex index = model->index(row);
ui->lbItens->setCurrentIndex(index);
ui->lbItens->edit(index);
}
void MainWindow::on_pbRemove_clicked()
{
model->removeRows(ui->lbItens->currentIndex().row(),1);
}
void MainWindow::on_pbImprime_clicked()
{
QPrinter printer;
QPainter p(&printer);
int x_pos = 20;
int y_pos = 20;
int row = model->rowCount();
int i;
for(i=0; i<row; i++){
QString text = ;
p.drawText(x_pos, y_pos, text);
y_pos += p.fontMetrics().height();
}
}
Sorry for my bad english and thanks for the help.
You can get QStringList from your model:
QStringList list = model->stringList();
From QStringList get your QString using join():
QString str = list.join(" ");
In join you may choose separator you need.
Alternatively, you can try this
void MainWindow::on_pbImprime_clicked()
{
QPrinter printer;
...
for(i=0; i<row; i++){
QString text = model->data(model->index(row, 0)).toString();
p.drawText(x_pos, y_pos, text);
y_pos += p.fontMetrics().height();
}
}
As a matter of fact,
QVariant QAbstractItemModel::data(const QModelIndex & index, int role = Qt::DisplayRole) const
is the prefered way to get data out of model.