How to have QTreeWidgetItems of different heights in a QTreeWidget utilizing QStyledItemDelegate? - c++

NOTE: it turned out that the problem was not due to the implementation of QStyledItemDelegate, but it was the fact that in the constructor of MyTreeWidget I was calling setUniformRowHeights(true). The code below and the solution posted by #scopchanov are valid and working
QTreeWidget has a protected method called itemFromIndex() and this is how I am making it accessible:
class MyTreeWidget : public QTreeWidget {
Q_OBJECT
public:
MyTreeWidget(QWidget *parent) : QTreeWidget(parent) {
setItemDelegate(new MyItemDelegate(this));
}
QTreeWidgetItem treeWidgetItemFromIndex(const QModelIndex& index) {
return itemFromIndex(index);
}
}
In my QStyledItemDelegate, I am storing a pointer to MyTreeWidget and then overriding its virtual sizeHint() method and based on the type of the QTreeWidgetItem adding a padding.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
_myTreeWidget = dynamic_cast<MyTreeWidget*>(parent);
}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
auto treeWidgetItem = _myTreeWidget->treeWidgetItemFromIndex(index);
QSize padding;
if (dynamic_cast<MyCustomTreeWidgetItem1*>(treeWidgetItem) {
padding = {0, 5};
} else if (dynamic_cast<MyCustomTreeWidgetItem2*>(treeWidgetItem) {
padding = {0, 10};
}
return QStyledItemDelegate::sizeHint(option, index) + padding;
}
}
This doesn't work, since sizeHint() of the delegate doesn't get called for every single QTreeWidgetItem.
So my text options to call setSizeHint() in the constructor of MyCustomTreeWidgetItem1, and that didn't seem to have any effect either. Is Qt ignoring it because there is a delegate?
Another option was to set a minimum height of a QWidget that is contained in MyCustomTreeWidgetItem which is made possible via the QTreeWidget::setItemWidget().
So it looks like the moment I use the delegate, I am confined to only size. Is my option to get rid of the delegate or there's something else I can try?
I know many people would say switch from a QTreeWidget to a QTreeView, but it's not possible at the moment.

Solution
I would approach this problem in a different (simpler) manner:
Define an enumeration for the different item sizes, e.g.:
enum ItemType : int {
IT_ItemWithRegularPadding,
IT_ItemWithBigPadding
};
When creating an item, set the desired size in its user data, depending on its type, e.g.:
switch (type) {
case IT_ItemWithRegularPadding:
item->setData(0, Qt::UserRole, QSize(0, 5));
break;
case IT_ItemWithBigPadding:
item->setData(0, Qt::UserRole, QSize(0, 10));
break;
}
In the reimplementation of sizeHint retrieve the desired size from the index's data, e.g.:
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
return QStyledItemDelegate::sizeHint(option, index)
+ index.data(Qt::UserRole).toSize();
}
Example
Here is an example I wrote for you to demonstrate how the proposed solution could be implemented:
#include <QApplication>
#include <QStyledItemDelegate>
#include <QTreeWidget>
#include <QBoxLayout>
class Delegate : public QStyledItemDelegate
{
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent){
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
return QStyledItemDelegate::sizeHint(option, index)
+ index.data(Qt::UserRole).toSize();
}
};
class MainWindow : public QWidget
{
public:
enum ItemType : int {
IT_ItemWithRegularPadding,
IT_ItemWithBigPadding
};
MainWindow(QWidget *parent = nullptr) :
QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
QList<QTreeWidgetItem *> items;
for (int i = 0; i < 10; ++i)
items.append(createItem(QString("item: %1").arg(i),
0.5*i == i/2 ? IT_ItemWithRegularPadding
: IT_ItemWithBigPadding));
treeWidget->setColumnCount(1);
treeWidget->setItemDelegate(new Delegate(this));
treeWidget->insertTopLevelItems(0, items);
l->addWidget(treeWidget);
resize(300, 400);
setWindowTitle(tr("Different Sizes"));
}
private:
QTreeWidgetItem *createItem(const QString &text, int type) {
auto *item = new QTreeWidgetItem(QStringList(text));
switch (type) {
case IT_ItemWithRegularPadding:
item->setData(0, Qt::UserRole, QSize(0, 5));
break;
case IT_ItemWithBigPadding:
item->setData(0, Qt::UserRole, QSize(0, 10));
break;
}
return item;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Note: This example sets the item's size depending on its index - odd or even. Feel free to change this by implementing the logic you need to differentiate between the items.
Result
The given example produces the following result:
The even and odd items are of a different height.

Related

How to show selected text different from list text in QComboBox?

I have a QComboBox with a popup list (a QAbstractItemView) showing different items (QStandardItems). Now, I want the item in the list to show a different text than if the item is selected.
Background:
I am creating a word-processor like style chooser where one can choose, say, "1.1 Heading 2" from the list, indicating the numbering and the style name, but when an item is chosen the combobox should only show the style name, say "Heading 2".
I thought the following question was exactly about what I was asking for but apparently an answer was chosen that does not work (apparently even according to the person asking the question): Can a QComboBox display a different value than whats in it's list?
Solution
Since QComboBox uses a list view to display the values, probably the "Qt'iest" way to achieve the desired effect, is to use a custom delegate and modify the text within its paint method, using a hash map (QHash) to get the corresponding string.
Example
Here is a simple example I have prepared for you to demonstrate how the proposed solution could be implemented:
Delegate.h this is where the magic is happening
#include <QStyledItemDelegate>
#include <QApplication>
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent) {}
void setHash(const QHash<int, QString> &hash) {
m_hash = hash;
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
if (!index.isValid())
return;
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text = m_hash.value(index.row());
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}
private:
QHash<int, QString> m_hash;
};
MainWindow.h only for demo purposes
#include <QWidget>
#include <QBoxLayout>
#include <QComboBox>
#include <QStandardItemModel>
#include "Delegate.h"
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr)
: QWidget(parent)
{
auto *l = new QVBoxLayout(this);
auto *cmbBox = new QComboBox(this);
auto *model = new QStandardItemModel(this);
auto *delegate = new Delegate(this);
QHash<int, QString> hash;
for (int n = 0; n < 5; n++) {
// For demo purposes I am using "it#" and "item #"
// Feel free to set those strings to whatever you need
model->appendRow(new QStandardItem(tr("it%1").arg(QString::number(n))));
hash.insert(n, tr("item %1").arg(QString::number(n)));
}
delegate->setHash(hash);
cmbBox->setModel(model);
cmbBox->setItemDelegate(delegate);
l->addWidget(cmbBox);
resize(600, 480);
}
};
Result
The example produces the following result:
The easiest way to do is to set the ComboBox as editable and then when the current item changes, you change the text to whatever you want. Example:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStringList a = {"Red", "Green", "Blue"};
aModel.setStringList(a);
ui->comboBox->setModel(&aModel);
ui->comboBox->setEditable(true);
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
if (arg1 == "Green") {
ui->comboBox->setCurrentText("Green on");
} else if (arg1 == "Red") {
ui->comboBox->setCurrentText("Red on");
}
}
ui->comboBox->setCurrentText("Green on"); will only change the text when the item is selected, when you reopen the combobox, the text will be reverted back to original. This is somewhat similar to my answer here.
Another way to do this would be to inherit the QComboBox class and then reimplement the mousePressEvent to change the model whenever the mouse is pressed, and switch it back after releasing. This will probably be more difficult to get right or may not even work as I have not tried it myself

How to pass images on QGraphicsView to QTableView programmatically using QPushButton

I have been researching for a while how to store images loaded on a QGraphicsView into rows of a QTableView using a QPushButton in a programmatic way but the information I found so far are not that many.
I have 1 QGraphicsView, a QPushButton (Send button) and a QTableView and a QLineEdit. When I upload images using the load button I show them both on the QGraphicsView and on the QLineEidt (I show the path of the image), if I click the Send button, the text of the QLineEdit should be added in the first row of the QTableView (which is happening) and the image should be stored inside the QTableView.
However, the image on the QGraphicsView is not being stored to the QTableView and nothing is being passed.
Currently this is what happens:
The expected behavior would be:
I created an ItemDelegate class that takes care of the resizing of the image on the QGraphicsView to be stored inside the QTableView
That part is shown below:
This is the mainwindow.h
#include <QGraphicsView>
#include <QGraphicsScene>
#include "imagedelegate.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void addData();
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
private slots:
void on_sendBtn_clicked();
void on_loadBtn_clicked();
private:
Ui::MainWindow *ui;
QStandardItemModel *model;
QGraphicsScene *leftScene;
};
#endif // MAINWINDOW_H
and here is the mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "imagedelegate.h"
#include <QGraphicsPixmapItem>
#include <QBuffer>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
leftScene = new QGraphicsScene(this);
ui->graphicsView->setScene(leftScene);
ui->graphicsView->show();
model = new QStandardItemModel();
ui->tableView->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addData()
{
// Path on the first column
QStandardItem *pathAItem = new QStandardItem(ui->lineEdit->text());
// Image on the second column - not working yet
//QStandardItem *image1 = new QStandardItem(/*ui->graphicsView->*/);
QPixmap image1;
QByteArray img1Array;
QBuffer buffer1(&img1Array);
buffer1.open(QIODevice::WriteOnly);
image1.save(&buffer1, "PNG");
QList<QStandardItem*> row;
row << pathAItem;
model->setColumnCount(1);
model->appendRow(row);
}
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
// QStyledItemDelegate::paint(painter, option, index);
}
}
The entire code will compile if you copy and paste so that you can see the issue I have.
Please shed light on this matter.
I wanted to answer to this question hoping that could also be useful to others. As suggested by Jeremy Friesner, the best (and fast in comparison to a QItemDelegate) way to send images into a QTableView using a QPushButton is to modify the void MainWindow::addData() function by using a QImage and pass it to a setData(QVariant(QPixmap::fromImage), Qt::DecorationRole) so that the entire function can be written as follows:
FIRST OPTION:
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::addData()
{
QStandardItem *pathAItem = new QStandardItem(ui->pathLineEdit_A->text());
QStandardItem *pathBItem = new QStandardItem(ui->pathLineEdit_B->text());
QImage image1(ui->graphicsViewLeft->grab().toImage());
QStandardItem *item1 = new QStandardItem();
item1->setData(QVariant(QPixmap::fromImage(image1.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QImage image2(ui->graphicsViewRight->grab().toImage());
QStandardItem *item2 = new QStandardItem();
item2->setData(QVariant(QPixmap::fromImage(image2.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QList<QStandardItem*> row;
row << pathAItem << pathBItem << item1 << item2;
model->appendRow(row);
}
SECOND OPTION
If it is necessary to use a QItemDelgate I am posting that part of the code too (it is working as I already tried it):
In the imagedelegate.h is necessary to provide a QSize as follows:
class ImageDelegate : public QStyledItemDelegate
{
public:
ImageDelegate(QObject * parent = nullptr);
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
After that on your imagedelegate.cpp the implementation is:
#include "imagedelegate.h"
ImageDelegate::ImageDelegate(QObject * parent) : QStyledItemDelegate(parent)
{}
QSize ImageDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return QSize(32,32);
Q_UNUSED(option);
Q_UNUSED(index);
}
void ImageDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
qDebug() << (index.model()->headerData(index.column(), Qt::Horizontal).toString());
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1" || colName == "image2")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
In my case I had two columns in which I needed to save the images, so you can expand it for how many columns as you like and I also set a QSize of (32,32) but this is up to the developer.
I hope this will save your programming time and this is the final result! :)

How to show QAbstractTableModel's status in a QStatusBar?

I have my custom implementation of QAbstractTableModel and of QSortFilterProxyModel, used for filtering. The table is shown in a QTableView.
The parent dialog of my QTableView has a QStatusBar, with a read-only QLineEdit widget.
In my overriding data() method of QAbstractTableModel, I'm setting the pertinent values for the Qt::StatusTipRole role.
Now I'm missing part of the plumbing: how do I get my per-cell StatusTipRole data to show up in my widget inside QStatusBar?
There is no need to override the view widget. Qt provides built-in support to show status tips for items in a model.
Normally, You just need to return a QString from your model's data() when role is QStatusTipRole, and that QString will be shown in the status bar when you hover your item.
You also need to turn on mouse tracking for the QTableView so that you get status bar updates without the mouse button being pressed. This is because when mouse tracking is disabled (by default), the widget receives mouse move events only while a mouse button is pressed.
Now, in order to display those status tips in your QLineEdit instead of the default status bar, You can override your main window's event function, intercept QStatusTipEvents, and show tips in your QLineEdit.
Here is an example implementation:
#include <QtWidgets>
//model to provide dummy data
class MyModel : public QAbstractTableModel{
public:
explicit MyModel(QObject* parent= nullptr):QAbstractTableModel(parent){}
~MyModel() = default;
int columnCount(const QModelIndex &parent) const{
if(parent.isValid()) return 0;
return 4;
}
int rowCount(const QModelIndex &parent) const{
if(parent.isValid()) return 0;
return 20;
}
QVariant data(const QModelIndex &index, int role) const{
QVariant val;
switch(role){
case Qt::DisplayRole: case Qt::EditRole:
val= QString("Display (%1, %2)")
.arg(index.row(), 2, 10, QChar('0'))
.arg(index.column(), 2, 10, QChar('0'));
break;
case Qt::ToolTipRole:
val= QString("Tooltip (%1, %2)")
.arg(index.row(), 2, 10, QChar('0'))
.arg(index.column(), 2, 10, QChar('0'));
break;
case Qt::StatusTipRole:
val= QString("StatusTip (%1, %2)")
.arg(index.row(), 2, 10, QChar('0'))
.arg(index.column(), 2, 10, QChar('0'));
break;
}
return val;
}
};
class MainWindow : public QMainWindow{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent= nullptr):QMainWindow(parent){
//set up GUI
layout.addWidget(&lineEditFilter);
layout.addWidget(&tableView);
setCentralWidget(&cw);
lineEditStatusBar.setReadOnly(true);
statusBar()->addPermanentWidget(&lineEditStatusBar);
//set up models
filterModel.setSourceModel(&model);
tableView.setModel(&filterModel);
connect(&lineEditFilter, &QLineEdit::textChanged, this, &MainWindow::updateFilter);
//turn on mouse tracking for the table view
tableView.setMouseTracking(true);
}
~MainWindow()= default;
Q_SLOT void updateFilter(const QString& text){
filterModel.setFilterFixedString(text);
}
protected:
//in order to intercept QStatusTipEvents
//and show tips in the line edit instead of the normal status bar
bool event(QEvent *event){
if(event->type() != QEvent::StatusTip) return QMainWindow::event(event);
QStatusTipEvent* statusTipEvent= static_cast<QStatusTipEvent*>(event);
lineEditStatusBar.setText(statusTipEvent->tip());
statusTipEvent->ignore();
return true;
}
private:
QWidget cw;
QVBoxLayout layout{&cw};
QLineEdit lineEditFilter;
QTableView tableView;
MyModel model;
QSortFilterProxyModel filterModel;
QLineEdit lineEditStatusBar;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow mw;
mw.show();
return a.exec();
}
#include "main.moc"

Link two QListWidget

I want to link two QListWidget, but I don't know how to do with code. Here is what I did :
We can see two QListWidget. With the left QListWidget, I add (by example : "Bonjour", "Hello", "Tag") three QListWidgetItem. I want that if I click on one of three QListWidgetItem of the left QListWidget that I can add QListWidgetItem with the right QListWidget (by example, for "Bonjour" : "Tu", "Vas", "Bien"). If I don't click on one of three QListWidgetItem, I can't add QListWidgetItem with the right QListWidget. If I did for "Bonjour" : "Tu", "Vas", "Bien" and I click on "Hello" (obviously, "Hello" contains nothing), there is nothing in the right QListWidget. That's just an example of what I want to do. Below, I write my code if it's helpful :
- secondwindow.cpp -
#include "secondwindow.h"
#include "ui_secondwindow.h"
#include "thirdwindow.h"
#include "ui_thirdwindow.h"
SecondWindow::SecondWindow(QWidget *parent) :
QWidget(parent),
ui(new Ui::SecondWindow)
{
ui->setupUi(this);
ui->button_1->setIcon(QIcon(":/Images/Images/Haut.png"));
ui->button_2->setIcon(QIcon(":/Images/Images/Bas.png"));
ui->button_5->setIcon(QIcon(":/Images/Images/Haut.png"));
ui->button_6->setIcon(QIcon(":/Images/Images/Bas.png"));
connect(ui->button_1, SIGNAL(clicked()), this, SLOT(UpForLeft()));
connect(ui->button_2, SIGNAL(clicked()), this, SLOT(DownForLeft()));
connect(ui->button_3, SIGNAL(clicked()), this, SLOT(DeleteForLeft()));
connect(ui->button_4, SIGNAL(clicked()), this, SLOT(AddForLeft()));
connect(ui->button_9, SIGNAL(clicked()), this, SLOT(ShowThirdWindow()));
connect(ui->button_10, SIGNAL(clicked()), this, SLOT(close()));
connect(ui->table_1, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
this, SLOT(EditForLeft(QListWidgetItem *)));
}
SecondWindow::~SecondWindow()
{
delete ui;
}
void SecondWindow::ShowThirdWindow()
{
ThirdWindow *window = new ThirdWindow;
window->setWindowTitle("B");
window->setWindowIcon(QIcon(":/Images/Images/Bouclier.png"));
window->setFixedSize(820, 440);
window->show();
}
void SecondWindow::UpForLeft()
{
QListWidgetItem *item;
int i;
i = ui->table_1->currentRow();
item = ui->table_1->takeItem(i);
ui->table_1->insertItem(i - 1, item);
ui->table_1->setCurrentRow(i - 1);
}
void SecondWindow::DownForLeft()
{
QListWidgetItem *item;
int i;
i = ui->table_1->currentRow();
item = ui->table_1->takeItem(i);
ui->table_1->insertItem(i + 1, item);
ui->table_1->setCurrentRow(i + 1);
}
void SecondWindow::UpForRight()
{
QListWidgetItem *item;
int i;
i = ui->table_2->currentRow();
item = ui->table_2->takeItem(i);
ui->table_1->insertItem(i - 1, item);
ui->table_1->setCurrentRow(i - 1);
}
void SecondWindow::DownForRight()
{
QListWidgetItem *item;
int i;
i = ui->table_2->currentRow();
item = ui->table_2->takeItem(i);
ui->table_1->insertItem(i + 1, item);
ui->table_1->setCurrentRow(i + 1);
}
void SecondWindow::AddForLeft()
{
QString string;
string = ui->line_1->text();
ui->table_1->addItem(string);
ui->line_1->clear();
}
void SecondWindow::DeleteForLeft()
{
QListWidgetItem *item;
int i;
i = ui->table_1->currentRow();
item = ui->table_1->takeItem(i);
delete item;
}
void SecondWindow::EditForLeft(QListWidgetItem *item)
{
item->setFlags(item->flags() | Qt::ItemIsEditable);
item = ui->table_1->currentItem();
ui->table_1->editItem(item);
}
- secondwindow.h -
#ifndef SECONDWINDOW_H
#define SECONDWINDOW_H
#include <QListWidgetItem>
#include <QWidget>
#include <QString>
#include <QIcon>
#include "thirdwindow.h"
#include "ui_thirdwindow.h"
namespace Ui {
class SecondWindow;
}
class SecondWindow : public QWidget
{
Q_OBJECT
public:
explicit SecondWindow(QWidget *parent = 0);
~SecondWindow();
public slots:
void ShowThirdWindow();
void UpForLeft();
void DownForLeft();
void UpForRight();
void DownForRight();
void AddForLeft();
void DeleteForLeft();
void EditForLeft(QListWidgetItem *item);
private:
Ui::SecondWindow *ui;
ThirdWindow *window;
};
#endif // SECONDWINDOW_H
Thank you for help.
A QListWidget is a convenience widget that mixes a model with a view. This makes things a bit harder than necessary as you need to keep replenishing the models with data from your top-level model that represents the tree-structured data.
Instead, you could use a QStandardItemModel, and expose it via QListView. The view only shows one column at a given level of the tree, without any children. To view the children, select an appropriate root index.
A key missing feature of QStandardItemModel is moveRows, needed to move items up/down. That's easy enough to remedy with a limited implementation that supports moving a single item within the same parent. Thus the views can be completely model-agnostic by using moveRow. We shall implement moveRows first:
// https://github.com/KubaO/stackoverflown/tree/master/questions/list-widgets-40403640
#include <QtWidgets>
class StandardItemModel : public QStandardItemModel {
bool moveRows(const QModelIndex &srcParent, int srcRow, int count,
const QModelIndex &dstParent, int dstRow) override {
if (count == 0) return true;
if (count != 1 || srcParent != dstParent) return false;
if (srcRow == dstRow) return true;
if (abs(srcRow - dstRow) != 1) return false;
auto root = srcParent.isValid() ? itemFromIndex(srcParent) : invisibleRootItem();
if (!root) return false;
auto srcItem = root->takeChild(srcRow);
auto dstItem = root->takeChild(dstRow);
if (!srcItem || !dstItem) return false;
root->setChild(srcRow, dstItem);
root->setChild(dstRow, srcItem);
return true;
}
public:
using QStandardItemModel::QStandardItemModel;
};
Subsequently, a ListUi widget implements the view, without any knowledge of how the model might work. For the purpose of this example, new items are edited in-place, instead of using a separate control.
class ListUi : public QWidget {
Q_OBJECT
QGridLayout m_layout{this};
QVBoxLayout m_column;
QPushButton m_up{"⬆"}, m_down{"⬇"}, m_remove{"−"}, m_add{"+"};
QLabel m_caption;
QListView m_list;
inline QAbstractItemModel * model() const { return m_list.model(); }
inline QModelIndex root() const { return m_list.rootIndex(); }
inline QModelIndex index(int row) const { return model()->index(row, 0, root()); }
public:
ListUi(const QString & caption, QWidget * parent = nullptr) :
QWidget{parent},
m_caption{caption}
{
m_layout.addWidget(&m_up, 0, 0);
m_layout.addWidget(&m_down, 1, 0, 1, 1, Qt::AlignTop);
m_layout.addLayout(&m_column, 0, 1, 3, 1);
m_column.addWidget(&m_caption);
m_column.addWidget(&m_list);
m_layout.addWidget(&m_remove, 0, 2);
m_layout.addWidget(&m_add, 2, 2);
m_caption.setAlignment(Qt::AlignCenter);
connect(&m_add, &QPushButton::clicked, [this]{
int row = model()->rowCount(root());
if (model()->columnCount(root()) == 0)
model()->insertColumn(0, root());
if (model()->insertRow(row, root())) {
m_list.setCurrentIndex(index(row));
m_list.edit(index(row));
}
});
connect(&m_remove, &QPushButton::clicked, [this]{
if (m_list.currentIndex().isValid())
model()->removeRow(m_list.currentIndex().row(), root());
});
connect(&m_up, &QPushButton::clicked, [this]{
auto row = m_list.currentIndex().row();
if (row > 0 && model()->moveRow(root(), row, root(), row - 1))
m_list.setCurrentIndex(index(row-1));
});
connect(&m_down, &QPushButton::clicked, [this]{
auto row = m_list.currentIndex().row();
if (row >= 0 && row < (model()->rowCount(root()) - 1) &&
model()->moveRow(root(), row, root(), row + 1))
m_list.setCurrentIndex(index(row+1));
});
}
void setModel(QAbstractItemModel * model) {
m_list.setModel(model);
connect(m_list.selectionModel(), &QItemSelectionModel::currentChanged, this, &ListUi::currentIndexChanged);
}
void setRootIndex(const QModelIndex & index) {
m_list.setRootIndex(index);
}
Q_SIGNAL void currentIndexChanged(const QModelIndex &);
};
A simple test harness instantiates two ListUis, a StandardItemModel, populates the model from a JSON source, and links them to obtain the desired functionality.
class Window : public QWidget {
Q_OBJECT
QGridLayout m_layout{this};
ListUi m_programs{"Liste des programmes"};
ListUi m_sessions{"Liste des sessions"};
QPushButton m_generate{"Générer les données"};
StandardItemModel m_model;
public:
explicit Window(QWidget * parent = nullptr) : QWidget{parent}
{
m_layout.addWidget(&m_programs, 0, 0);
m_layout.addWidget(&m_sessions, 0, 1);
m_layout.addWidget(&m_generate, 1, 1, 1, 1, Qt::AlignRight);
m_programs.setModel(&m_model);
m_sessions.setModel(&m_model);
m_sessions.setDisabled(true);
connect(&m_programs, &ListUi::currentIndexChanged, [this](const QModelIndex & root){
m_sessions.setEnabled(true);
m_sessions.setRootIndex(root);
});
connect(&m_generate, &QPushButton::clicked, this, &Window::generateData);
}
Q_SIGNAL void generateData();
void setJson(const QJsonDocument & doc) {
m_model.clear();
auto object = doc.object();
for (auto it = object.begin(); it != object.end(); ++it) {
if (!m_model.columnCount()) m_model.appendColumn({});
auto root = new QStandardItem(it.key());
m_model.appendRow(root);
if (it.value().isArray()) {
auto array = it.value().toArray();
for (auto const & value : array) {
if (!root->columnCount()) root->appendColumn({});
root->appendRow(new QStandardItem{value.toString()});
}
}
}
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
auto json = R"--({
"Foo":["Foo-1", "Foo-2", "Foo-3"],
"Bar":["Bar-1", "Bar-2"]
})--";
Window ui;
ui.connect(&ui, &Window::generateData, [&]{ ui.setJson(QJsonDocument::fromJson(json)); });
ui.show();
return app.exec();
}
#include "main.moc"
It is a simple matter to iterate the standard item model to regenerate the JSON representation.
This concludes the example.

Qt: Space between individual cells in QTableWidget

(Using Qt 5.5.1 on Windows 8.1)
I have a table displaying images selected by the user.
I know their are many other ways to display multiple images on a GUI, but I'm new to Qt, so I didn't understand how to use QGraphicsView and found the following to be the easiest way.
But this forms a table in which images are not separated. I want some space b/w them. See how the next images starts right where the first ends.
How can I do it so that next image starts after leaving some space?
I need those checkboxes too (they came bu default by using QTableWidget) because after adding images, I want user to select them too for further processing.
main.cpp
QFileDialog dialog(this);
dialog.setNameFilter(tr("Images (*.jpg)"));
dialog.setFileMode(QFileDialog::ExistingFiles);
QStringList all_filenames = dialog.selectedFiles();
int maxCol = 3;
int maxRows = all_filenames.size() / maxCol;
ui->tableWidget->setColumnCount(maxCol);
ui->tableWidget->setRowCount(maxRows);
int remainder = all_filenames.size() % maxCol;
if (remainder != 0)
{
maxRows +=1;
}
ui->tableWidget->horizontalHeader()->setDefaultSectionSize(200);
ui->tableWidget->verticalHeader()->setDefaultSectionSize(200);
if(all_filenames.isEmpty() == 0)
{
for( int i = 0; i < all_filenames.size() ; ++i)
{
QPixmap map(all_filenames.at(i));
map = map.scaled(200,200,Qt::IgnoreAspectRatio,Q::FastTransformation);
QBrush brush(map);
QTableWidgetItem* item = new QTableWidgetItem();
item->setCheckState(Qt::CheckState());
item->setBackground(brush);
ui->tableWidget->setItem(j,k,item);
k++;
if ( k == maxCol )
{
j++;
k = 0;
}
}
}
EDIT
cmargindelegate.cpp
#ifndef CMARGINDELEGATE_H
#define CMARGINDELEGATE_H
#include <QItemDelegate>
class CMarginDelegate : public QItemDelegate
{
public:
explicit CMarginDelegate(int margin, QObject* parent);
~CMarginDelegate();
private:
int m_margin;
public slots:
void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index);
};
#endif // CMARGINDELEGATE_H
cmargindelegate.cpp
#include "cmargindelegate.h"
#include <QItemDelegate>
CMarginDelegate::CMarginDelegate(int margin, QObject*parent):QItemDelegate(parent),m_margin(margin)
{}
CMarginDelegate ::~CMarginDelegate()
{}
void CMarginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
QStyleOptionViewItem itemOption(option);
// Make the 'drawing rectangle' smaller.
itemOption.rect.adjust(m_margin, m_margin, -m_margin, -m_margin);
QItemDelegate::paint(painter, itemOption, index);
}
Implement a custom delegate to draw your table cells. You can start with this one:
class CMarginDelegate : public QItemDelegate
{
public:
CMarginDelegate(int margin, QObject* parent)
: QItemDelegate(parent),
m_margin(margin)
{}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem itemOption(option);
// Make the 'drawing rectangle' smaller.
itemOption.rect.adjust(m_margin, m_margin, -m_margin, -m_margin);
QItemDelegate::paint(painter, itemOption, index);
}
private:
int m_margin;
};
And this is how to use it:
ui->tableWidget->setItemDelegate(new CMarginDelegate(5, ui->tableWidget));