How do I print QStringListModel content? - c++

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.

Related

How to visualize a .txt or .csv file on a QTableView after triggering currentRowChanged(int currentRow) on QListWidget?

Problem: How to visualize a .txt or .csv file on a QTableView after triggering currentRowChanged(int currentRow) on QListWidget?
I have a very simple interface that I designed as a minimal verifiable example that look like below with a QPushButton a QLineEdit a QListWidget and a QTableView.
If I upload a .csv file using the QPushButton there are no problem and the content of the file is correctly shown on a QTableView:
However, and here is the problem:
When I drag and drop (functionality correctly implemented) multiple files and I click on one of them I have problem visualizing the file because the content does not change. It seems that the slot currentRowChanged(int currentRow) in the QListWidget is never triggered despite the files exists.
Below is the desired result:
Here is the example I implemented. If you copy/paste on your computer it will work no problem:
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.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QListWidgetItem>
#include <QStandardItemModel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
enum fileFormat {
CSV,
TXT,
OTHER
};
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void showFilesDetails(QListWidgetItem *item);
void readFile(QString path, fileFormat format);
void setValueAt(int ix, int jx, const QString &value);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
private slots:
void on_listWidget_currentRowChanged(int currentRow);
void on_loadBtn_clicked();
private:
Ui::MainWindow *ui;
fileFormat *format;
QStandardItemModel *model;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>
#include <QFileDialog>
namespace constants
{
const QStringList HEADERS = {
"tax_id", "Org_name", "GeneID", "CurrentID", "Status"};
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setAcceptDrops(true);
model = new QStandardItemModel(this);
ui->tableView->setModel(model);
model->setHorizontalHeaderLabels(constants::HEADERS);
model->setColumnCount(constants::HEADERS.length());
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, ui->tableView);
horizontal->setSectionsClickable(true);
horizontal->setHighlightSections(true);
ui->tableView->setHorizontalHeader(horizontal);
}
MainWindow::~MainWindow(){ delete ui; }
void MainWindow::dragEnterEvent(QDragEnterEvent *event) { event->accept(); }
void MainWindow::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); }
void MainWindow::dragMoveEvent(QDragMoveEvent *event) { event->accept(); }
void MainWindow::dropEvent(QDropEvent *event)
{
QString name;
QList<QUrl> urls;
QList<QUrl>::Iterator i;
urls = event->mimeData()->urls();
for(i = urls.begin(); i != urls.end(); ++i) {
name = i->path();
ui->listWidget->addItem(name);
}
}
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
QListWidgetItem *item = nullptr;
if(ui->listWidget->count() > 0)
ui->listWidget->setCurrentRow(currentRow);
showFilesDetails(item);
}
void MainWindow::readFile(QString path, fileFormat format)
{
QListWidgetItem *item = nullptr;
QFile file(path);
if(!file.exists()){
qDebug() << "File does not exist "<<path;
}else{
qDebug() << path<< "File exist...";
}
if (file.fileName().endsWith(".txt", Qt::CaseInsensitive))
{
qDebug() << "File does not exist "<<file.fileName();
} else if(file.fileName().endsWith(".csv", Qt::CaseInsensitive))
{
qDebug() << "File does not exist "<<file.fileName();
}
QString line;
if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
QTextStream stream(&file);
while (!stream.atEnd()){
line = stream.readLine();
ui->listWidget->setCurrentItem(item);
qDebug() << "line: "<<line;
}
}
file.close();
}
void MainWindow::showFilesDetails(QListWidgetItem *item)
{
QString path;
fileFormat frmt;
readFile(path, frmt);
ui->listWidget->setCurrentItem(item);
}
void MainWindow::on_loadBtn_clicked()
{
auto filename = QFileDialog::getOpenFileName(this, "Open", QDir::rootPath(), "CSV file (*.csv)");
if(filename.isEmpty()) {
ui->statusbar->showMessage("File empty");
return;
}
QFile file(filename);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
ui->statusbar->showMessage("File did not open");
return;
}
QTextStream xin(&file);
int ix = 0;
while (!xin.atEnd()) {
model->setRowCount(ix);
auto line = xin.readLine();
auto values = line.split(",");
const int colCount = values.size();
model->setColumnCount(colCount);
for(int jx = 0; jx < colCount; ++jx) {
setValueAt(ix, jx, values.at(jx));
}
++ix;
ui->lineEdit->setText(filename);
ui->statusbar->showMessage(filename);
}
ui->listWidget->addItem(filename);
file.close();
}
void MainWindow::setValueAt(int ix, int jx, const QString &value)
{
if (!model->item(ix, jx)) {
model->setItem(ix, jx, new QStandardItem(value));
} else {
model->item(ix, jx)->setText(value);
}
}
In order to solve the problem I read very useful sources and in particular I came across this one, this one and also I found this post useful. However, I am trying to figure out where the problem might be.
The drag/drop functionality is properly implemented but how do I upload the files in the QTableView and properly trigger the QListWidget event?
Thank to anyone who would have time to read and point to a potential solution.
afaik,funtion on_listWidget_currentRowChanged triggered properly.
function showFilesDetails passes an empty string to readFile,this may be the problem.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>
#include <QFileDialog>
namespace constants
{
const QStringList HEADERS = {
"tax_id", "Org_name", "GeneID", "CurrentID", "Status"};
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setAcceptDrops(true);
model = new QStandardItemModel(this);
ui->tableView->setModel(model);
model->setHorizontalHeaderLabels(constants::HEADERS);
model->setColumnCount(constants::HEADERS.length());
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, ui->tableView);
horizontal->setSectionsClickable(true);
horizontal->setHighlightSections(true);
ui->tableView->setHorizontalHeader(horizontal);
}
MainWindow::~MainWindow(){ delete ui; }
void MainWindow::dragEnterEvent(QDragEnterEvent *event) { event->accept(); }
void MainWindow::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); }
void MainWindow::dragMoveEvent(QDragMoveEvent *event) { event->accept(); }
void MainWindow::dropEvent(QDropEvent *event)
{
QString name;
QList<QUrl> urls;
QList<QUrl>::Iterator i;
urls = event->mimeData()->urls();
for(i = urls.begin(); i != urls.end(); ++i) {
name = i->toLocalFile();
ui->listWidget->addItem(name);
}
}
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
QListWidgetItem *item = nullptr;
if(ui->listWidget->count() > 0)
ui->listWidget->setCurrentRow(currentRow);
showFilesDetails(item);
}
void MainWindow::readFile(QString path, fileFormat format)
{
QListWidgetItem *item = nullptr;
QFile file(path);
if(!file.exists()){
qDebug() << "File does not exist "<<path;
}else{
qDebug() << path<< "File exist...";
}
if (file.fileName().endsWith(".txt", Qt::CaseInsensitive))
{
qDebug() << "File does not exist "<<file.fileName();
} else if(file.fileName().endsWith(".csv", Qt::CaseInsensitive))
{
qDebug() << "File does not exist "<<file.fileName();
}file.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream xin(&file);
int ix = 0;
while (!xin.atEnd()) {
model->setRowCount(ix);
auto line = xin.readLine();
auto values = line.split(",");
const int colCount = values.size();
model->setColumnCount(colCount);
for(int jx = 0; jx < colCount; ++jx) {
setValueAt(ix, jx, values.at(jx));
}
++ix;
ui->lineEdit->setText(path);
ui->statusbar->showMessage(path);
}
file.close();
}
void MainWindow::showFilesDetails(QListWidgetItem *item)
{
item=ui->listWidget->currentItem();
QString path=
item->data(Qt::DisplayRole).toString();;
fileFormat frmt;
readFile(path, frmt);
ui->listWidget->setCurrentItem(item);
}
void MainWindow::on_loadBtn_clicked()
{
auto filename = QFileDialog::getOpenFileName(this, "Open", QDir::rootPath(), "CSV file (*.csv)");
if(filename.isEmpty()) {
ui->statusbar->showMessage("File empty");
return;
}
QFile file(filename);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
ui->statusbar->showMessage("File did not open");
return;
}
QTextStream xin(&file);
int ix = 0;
while (!xin.atEnd()) {
model->setRowCount(ix);
auto line = xin.readLine();
auto values = line.split(",");
const int colCount = values.size();
model->setColumnCount(colCount);
for(int jx = 0; jx < colCount; ++jx) {
setValueAt(ix, jx, values.at(jx));
}
++ix;
ui->lineEdit->setText(filename);
ui->statusbar->showMessage(filename);
}
ui->listWidget->addItem(filename);
file.close();
}
void MainWindow::setValueAt(int ix, int jx, const QString &value)
{
if (!model->item(ix, jx)) {
model->setItem(ix, jx, new QStandardItem(value));
} else {
model->item(ix, jx)->setText(value);
}
}

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)")?

Drag and drop of QTreeWidgetItem does not work properly

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

use QModelIndex to set background of a QTreeWidgetItem

I have MainWindow with a qTreeWidget. To add elements to this widget I've implemented this functions:
QTreeWidgetItem *MainWindow::prepareIt(QTreeWidgetItem *it, const QString &name, const QString &descr)
{
it->setText(0, name);
it->setText(1, descr);
return it;
}
QTreeWidgetItem *MainWindow::addRoot(const QString &name, const QString &descr)
{
QTreeWidgetItem *it = prepareIt(new QTreeWidgetItem(ui->treeWidget), name, descr);
it->setBackground( 0, QColor{112, 77, 75} );
it->setBackground( 1, QColor{78, 90, 110} );
return it;
}
QTreeWidgetItem *MainWindow::addChild(QTreeWidgetItem *parent, const QString &name, const QString &descr)
{
auto child = new QTreeWidgetItem(parent);
child->setBackground( 0, QColor{102, 67, 65} );
child->setBackground( 1, QColor{68, 80, 99} );
parent->addChild(prepareIt(child, name, descr));
return child;
}
...
addRoot(...);
addChild(parent,...);
...
It works as expected. Now I want to highlight some entrys in this qTreeWidget with a right mouse click. In the C-Tor of my MainWindow I implemented:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
...
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeWidget, SIGNAL (customContextMenuRequested(const QPoint &)), this, SLOT (rightClick(const QPoint &)));
...
}
and the slot is
void MainWindow::rightClick(const QPoint &pt)
{
QModelIndex idx = ui->treeWidget->indexAt(pt);
if (idx.isValid())
{
qDebug() << idx.data().toString();
qDebug() << idx << "index.row()" << idx.row() << " index.column()" << idx.column();
}
}
The slot is called as expected (the qDebug works), but how can I get from the QModelIndex idx to the corresponding QTreeWidgetItem for the highlighting? Or is there another way to make the highligt / change the color of the element?
Thanks in advance!!
Change the color of the cell:
A possible solution is to use the setData() method:
void MainWindow::rightClick(const QPoint &pt)
{
QModelIndex idx = ui->treeWidget->indexAt(pt);
if (idx.isValid())
{
ui->treeWidget->model()->setData(idx, QColor("red"), Qt::BackgroundRole);
}
}
But that will only change one cell, if you want to change the color then the entire row will have to be traversed:
Change the row color:
void MainWindow::rightClick(const QPoint &pt)
{
QModelIndex idx = ui->treeWidget->indexAt(pt);
if (idx.isValid())
{
for(int c=0; c < ui->treeWidget->columnCount(); ++c){
QModelIndex ix = idx.sibling(idx.row(), c);
ui->treeWidget->model()->setData(ix, QColor("red"), Qt::BackgroundRole);
}
}
}
Or:
void MainWindow::rightClick(const QPoint &pt)
{
QTreeWidgetItem *it = ui->treeWidget->itemAt(pt);
if (it)
{
for(int c=0; c < ui->treeWidget->columnCount(); ++c){
it->setBackground(c, QColor("red"));
}
}
}

Implementations of buttons in Qt / C++

Im looking to implement a sudoku gui in Qt. First things first im trying to generate a grid of QToolButtons, which when pressed will increment their value by one.
Here is my header for window.h
class Window : public QWidget
{
Q_OBJECT
public:
Window(QWidget *parent = nullptr);
private slots:
void digitClicked();
private:
Button *createButton(const QString &text, const char *member);
QString *readFileChar(const QString file_name, QString digit[] );
QGridLayout *previewLayout;
void setSudokuChar(const int digit, const char *member);
enum { NumDigitButtons = 81 };
Button *digitButtons[NumDigitButtons];
QString digit[NumDigitButtons];
protected:
void paintEvent(QPaintEvent *event) override;
void mouseReleaseEvent(QMouseEvent * event) override;
};
And the corresponding source file window.cpp
Window::Window(QWidget *parent) : QWidget(parent) //constrcutor
{
//button creation
QString* current_digit = readFileChar("/home/pi/Documents/ELEC1204/P6/sudoku.txt", digit);
for(int i = 0; i < NumDigitButtons; i++){
digitButtons[i] = createButton(*(current_digit++), SLOT(digitClicked())); //initialize sudoku
}
// set form size
QGridLayout *mainLayout = new QGridLayout;
setMaximumSize(600,600);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(tr("button game"));
// place each digit button
for(int i = 1; i < NumDigitButtons; i++){
int row = (81 - i) / 9 ; // y position for button
int col = (i - 1) % 9 ; // x position for button
mainLayout->addWidget(digitButtons[i], row, col);
}
}
void Window::digitClicked()
{
Button *clickedButton = qobject_cast<Button*>(sender()); //clicked button = object button that was pressed
int val = ++(clickedButton->text().toInt()); //incrememt digit value of the button which was pressed
setSudokuChar(val, clickedButton); //updates sudoku with new value
}
QString* Window::readFileChar(const QString file_name, QString digit[]) //reads in file to initialize buttons
{
QFile file(file_name);
int i = 0;
if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
QTextStream in(&file);
while(!in.atEnd()){
digit[i] = in.read(1); //read each character into an array of QStrings
i++ ;
}
}
else{
QTextStream(stdout) << "could not open file, program terminating" << endl;
exit(1);
}
return digit;
}
Button *Window::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
void setSudokuChar(const int digit, Button* clickedButton) //takes in a digit, and assigns its value to the char that was pass
{
}
Please note that the function to update the button's value when pressed 'setSudokuChar' is yet to be implemented, but that pretty irrelevant for now.
Here is the button class header button.h, it's only a small modification of the QToolButton class to aid in resizing.
class Button : public QToolButton
{
Q_OBJECT
public:
explicit Button(const QString &text, QWidget *parent = nullptr);
QSize sizeHint() const override;
};
And finally, here is the button.cpp source file
Button::Button(const QString &text, QWidget *parent) : QToolButton(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
setText(text);
}
QSize Button::sizeHint() const
{
QSize size = QToolButton::sizeHint();
size.rheight() += 20;
size.rwidth() = qMax(size.width(), size.height());
return size;
}
Hopefully thats not too much to digest, I'm still a beginner!
**My problem is ... ** when I run the code, a window is produced but its blank, which means the compilation is fine, but the program is not working as intended. Could anyone by chance spot what is going wrong? All that i do in the main function is create a window as follows :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
Any and all help is thoroughly appreciated.