How to immediately detect checkbox state change in a QTreeView? - qt

I'm using Qt 5.9 on linux. I have a QTreeView where I set a custom model which is derived from QAbstractItemModel and I fill the tree with several plain classes for the tree items. Each treeview item has a checkbox next to it.
I have the following attribute set on the QTreeView ...
treeView->viewport()->setAttribute(Qt::WA_Hover);
so that when the mouse hovers over a tree item, I can capture the event via my delegates paint method.
The problem is that I also have a checkbox in each tree item and I'd like to be able to capture when the state of the checkbox changes, but my delegate doesn't seem to capture that. I can tell what state the checkbox is in when I hover the mouse over the item, but what I want is to be able to immediately know when the state of the checkbox changes w/o having to move the mouse any further.
Any thoughts on how to immediately detect when the state of the checkbox changes in a tree item?

A possible solution is to track the state change of the checkbox using the editorEvent method:
#include <QtWidgets>
class CheckboxDelegate: public QStyledItemDelegate{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
bool editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index) override
{
Qt::CheckState last = static_cast<Qt::CheckState>(model->data(index, Qt::CheckStateRole).toInt());
bool res = QStyledItemDelegate::editorEvent(event, model, option, index);
Qt::CheckState current = static_cast<Qt::CheckState>(model->data(index, Qt::CheckStateRole).toInt());
if(last != current)
Q_EMIT stateChanged(index);
return res;
}
Q_SIGNALS:
void stateChanged(const QModelIndex & index);
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model;
model.setColumnCount(2);
for(int i=0; i<4; ++i){
QList<QStandardItem *> l;
for (int c=0; c< model.columnCount(); ++c) {
QStandardItem *parent = new QStandardItem(QString("%1-%2").arg(i).arg(c));
parent->setCheckable(true);
l << parent;
for (int j=0; j<4; ++j) {
QList<QStandardItem *> ll;
for (int c=0; c< model.columnCount(); ++c) {
QStandardItem *child = new QStandardItem(QString("%1-%2-%3").arg(i).arg(j).arg(c));
child->setCheckable(true);
ll << child;
}
parent->appendRow(ll);
}
}
model.appendRow(l);
}
QTreeView w;
w.viewport()->setAttribute(Qt::WA_Hover);
CheckboxDelegate *delegate = new CheckboxDelegate(&w);
w.setItemDelegate(delegate);
QObject::connect(delegate, &CheckboxDelegate::stateChanged, [](const QModelIndex & index){
QString text = index.data().toString();
Qt::CheckState state = static_cast<Qt::CheckState>(index.data(Qt::CheckStateRole).toInt());
qDebug() << text << state;
});
w.setModel(&model);
w.resize(640, 480);
w.expandAll();
w.show();
return a.exec();
}

Related

QListView max number of items in view

I need to calculate max number of items in current view of QListView.
I wrote code like this:
void MyListView::resizeEvent(QResizeEvent *event)
{
QListView::resizeEvent(event);
QFontMetrics fm (this->font());
int fontHeight = fm.lineSpacing();
QRect cr = contentsRect();
int windowHeight = cr.bottom() - cr.top();
int maxItemsCount = windowHeight / fontHeight;
qDebug()<<"max items in view: "<< maxItemsCount;
}
but calculated max number of items is is incorrect.
E.g. in case of my window height and font height I get 32 max items in view when in fact current view has 28 items. Perhaps someone can suggest something, how to calculate it properly?
My idea is to use QListView::indexAt() (inherited from QAbstractView) to obtain the row index for
the top-left corner
the bottom-left corner
of the list view viewport and determining the number of visible items by difference of them.
To check this out, I made an MCVE testQListViewNumVisibleItems.cc:
// Qt header:
#include <QtWidgets>
class ListWidget: public QListWidget {
public:
ListWidget(QWidget *pQParent = nullptr): QListWidget(pQParent) { }
virtual ~ListWidget() = default;
ListWidget(const ListWidget&) = delete;
ListWidget& operator=(const ListWidget&) = delete;
int getNumVisibleItems() const
{
const QSize size = viewport()->size();
const QModelIndex qMIdx0 = indexAt(QPoint(0, 0));
const QModelIndex qMIdx1 = indexAt(QPoint(0, size.height() - 1));
//qDebug() << "qMIdx0:" << qMIdx0 << "qMIdx1:" << qMIdx1;
return qMIdx0.isValid()
? (qMIdx1.isValid() ? qMIdx1.row() : count()) - qMIdx0.row()
: 0;
}
};
const int MaxItems = 20;
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
ListWidget qLst;
qLst.resize(200, 200);
qLst.show();
// timer to populate list view
using namespace std::chrono_literals;
QTimer qTimer;
qTimer.setInterval(1000ms);
// install signal handlers
int n = 0;
QObject::connect(&qTimer, &QTimer::timeout,
[&]() {
qLst.addItem(QString("item %0").arg(++n));
qDebug() << "Visible items:" << qLst.getNumVisibleItems();
if (n >= MaxItems) qTimer.stop();
});
// runtime loop
qTimer.start();
return app.exec();
}
and a CMakeLists.txt:
project(QListViewNumVisibleItems)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
add_executable(testQListViewNumVisibleItems testQListViewNumVisibleItems.cc)
target_link_libraries(testQListViewNumVisibleItems Qt5::Widgets)
built and tested in VS2017 on Windows 10:
After having implemented what came in my mind, I googled a bit to possibly see other approaches. (I admit I should've done before.)
Thereby I found the following possible duplicate:
SO: Simple way to get all visible items in the QListView
The accepted answer doesn't contain much more than the hint for indexAt and a link to a Qt-FAQ article:
How can i get hold of all of the visible items in my QListView?
In order to get hold of the visible items in a QListView http://doc.qt.io/qt-5/qlistview.html, then you can iterate over them using indexAt() http://doc.qt.io/qt-5/qlistview.html#indexAt. You can get hold of the first visible item using indexAt(QPoint(0, 0)), then in order to get the index at the next position then use visualRect() http://doc.qt.io/qt-5/qlistview.html#visualRect to find out what your next call to itemAt() should be. This position would be:
visualRect.y() + visualRect.height() + 1 effectively.
See the following example for an illustration:
#include <QtGui>
QList <QModelIndex>myList;
class ListView : public QListView
{
Q_OBJECT
public:
ListView()
{
QStringListModel *myModel = new QStringListModel(this);
QStringList list;
list << "a" << "b" <<"c" <<"d" <<"e" <<"f" <<"g" <<"h" <<"i" <<"j" <<"k";
myModel->setStringList(list);
setModel(myModel);
QTimer::singleShot(3000, this, SLOT(test1()));
}
public slots:
void test1()
{
QModelIndex firstIndex = indexAt(QPoint(0, 0));
if (firstIndex.isValid()) {
myList << firstIndex;
} while (viewport()->rect().contains(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ))) {
firstIndex = indexAt(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ));
myList << firstIndex;
}
qDebug() << myList.count() << "are visible";
}
};
#include "main.moc"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
ListView window;
window.resize(100, 50);
window.show();
return app.exec();
}

Qt update TableView every x seconds

I have a QAbstractTableModel that has a list of custom items it displays, this TableModel is the model of a TableView. How can i refresh the TableView every x seconds? I tried beginInsertRows and endInsertRows, but this caused laggs because im editing too many items per second, so i only want to refresh it every x seconds.
use QTimer
for example,
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(processOneThing()));
timer->start(1000);
in processOneThing , you can write the code of refreshing data and set the timer again using timer->start(1000);
Using beginInsertRows and endInsertRows could potentially cause a whole lot of reorgansation internally that is not necessary. If the structure of the model (i.e. ordering, number of items,etc.) doesn't change, only the display content, you're far better off emitting the dataChanged signal.
That signal tells the connected views to refresh, and they'll only redraw the items that are visible within the viewport, they don't process items that are hidden.
#include <QtWidgets/QApplication>
#include <QtCore/qtimer.h>
#include <QtWidgets/qtableview.h>
#include <QtCore/QAbstractTableModel>
class TableModel : public QAbstractTableModel {
public:
TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
connect(&timer, &QTimer::timeout, [=]() {
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
});
timer.start(1000);
}
virtual int rowCount(QModelIndex const &index = QModelIndex()) const { return index.parent().isValid() ? 0 : 5; }
virtual int columnCount(QModelIndex const &index = QModelIndex()) const { return index.parent().isValid() ? 0 : 10; }
virtual QVariant data(QModelIndex const &index, int role = Qt::DisplayRole) const {
QVariant value;
if (index.isValid() && role == Qt::DisplayRole) {
value = QString("X %1; Y: %2").arg(qrand()).arg(qrand());
}
return value;
}
private:
QTimer timer;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TableModel model;
QTableView view;
view.setModel(&model);
view.show();
return a.exec();
}

How to remove QTreeView indentation

I want to have a QTreeView without an indentation on the left side increasing at each nesting level. I tried setting QTreeView::setIndentation(0). It removes the indentations just as I want, however it also hides the tree arrows.
Default behavior:
With indentations ✗
With arrows ✔
After setIndentation(0):
Without indentations ✔
Without arrows ✗
Desired behavior:
Without indentations ✔
With arrows ✔
So how can I achieve the result shown in the third example? Is there any standard way of doing it, or I will have to reimplement the QTreeView::paintEvent(), QTreeView::drawBranches(), etc.?
To solve the problem I used a delegate to translate the paint of the items, and paint the arrows.
#include <QtWidgets>
class BranchDelegate: public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override{
QStyleOptionViewItem opt(option);
if(index.column() == 0)
opt.rect.adjust(opt.rect.height(), 0, 0, 0);
QStyledItemDelegate::paint(painter, opt, index);
if(index.column() == 0){
QStyleOptionViewItem branch;
branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height());
branch.state = option.state;
const QWidget *widget = option.widget;
QStyle *style = widget ? widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_IndicatorBranch, &branch, painter, widget);
}
}
};
class TreeView: public QTreeView
{
public:
TreeView(QWidget *parent=nullptr):QTreeView(parent)
{
BranchDelegate *delegate = new BranchDelegate(this);
setItemDelegate(delegate);
setIndentation(0);
}
protected:
void mousePressEvent(QMouseEvent *event) override{
QModelIndex index = indexAt(event->pos());
bool last_state = isExpanded(index);
QTreeView::mousePressEvent(event);
if(index.isValid() && last_state == isExpanded(index))
setExpanded(index, !last_state);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TreeView w;
QFileSystemModel model;
model.setRootPath(QDir::rootPath());
w.setModel(&model);
w.setRootIndex(model.index(QDir::homePath()));
/*for (int i = 1; i< model.columnCount() ; ++i) {
w.hideColumn(i);
}*/
w.expandAll();
w.resize(640, 480);
w.show();
return a.exec();
}
eyllanesc's falls appart if there is horizontal scrolling. Also usually the view only expands/collapses when clicking the branch-indicator, not the index.
My Solution: Only change the Rect of indices which have a parent but no children. Also dont set the indentation to 0. No need to subclass QTreeView.
#include <QtWidgets>
class BranchDelegate: public QStyledItemDelegate
{
public:
mIndent = 50;
using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem opt(option);
if(index.parent().isValid && (!index.model() || !index.model()->index(0, 0, index).isValid()))
{
opt.rect.adjust(-mIndent, 0, 0, 0);
}
QStyledItemDelegate::paint(painter, opt, index);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView apView;
BranchDelegate* const apDelegate = new BranchDelegate(apView);
apDelegate->mIndent = 50;
apView->setIndentation(apDelegate->mIndent);
apView->setItemDelegateForColumn(0, apDelegate);
QFileSystemModel model;
model.setRootPath(QDir::rootPath());
apView.setModel(&model);
apView.setRootIndex(model.index(QDir::homePath()));
/*for (int i = 1; i< model.columnCount() ; ++i) {
apView.hideColumn(i);
}*/
apView.expandAll();
apView.resize(640, 480);
apView.show();
return a.exec();
}
ellyanesc's answer works, but is incorrect by one small detail in the line:
branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height());
The reason is because when the view is horizontally scrolled, option.rect.x() becomes negative. If branch.rect.x() is 0 (as in ellyanesc's answer), the branch indicator will always be shown, which also causes artifacts during scroll:
https://i.stack.imgur.com/O518A.png
https://i.stack.imgur.com/qYJMj.png
To solve this, replace the above line with:
branch.rect = QRect(option.rect.x(), opt.rect.y(), opt.rect.height(), opt.rect.height());
(I would have just pointed that out as a comment in ellyanesc's answer, but I don't have enough reputation for that.)

qstyleditemdelegate subclassing paint method not working right

I extended the qstyleditemview class. When I am in the editing mode for the qtreeview item, the paint method seems not to be executing right. When I change state to QStyle::State_Selected it works - it paints the selected row (text) in the qtreeview.
Any idea why it is not working in editing mode?
void myQItemDelegate::paint(QPainter *painter,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QStyleOptionViewItem s = *qstyleoption_cast<const QStyleOptionViewItem*>(&option);
if(s.state & QStyle::State_Editing)
{
painter->fillRect(s.rect, s.palette.highlight());
s.palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
QStyledItemDelegate::paint(painter, s, index);
}
In the State_Editing state the editor that is a widget created in createEditor() method is opened so that it will not be affected by the QStyleOptionViewItem palette.
Also instead of overwriting the paint method, use the initStyleOption() method:
#include <QtWidgets>
class StyledItemDelegate: public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QWidget * widget = QStyledItemDelegate::createEditor(parent, option, index);
QPalette pal(widget->palette());
pal.setColor(QPalette::HighlightedText, QColor(Qt::blue));
pal.setBrush(QPalette::Highlight, option.palette.highlight());
widget->setPalette(pal);
return widget;
}
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{
QStyledItemDelegate::initStyleOption(option, index);
option->palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView w;
QStandardItemModel model;
w.setModel(&model);
w.setItemDelegate(new StyledItemDelegate);
for(int i=0; i<3; ++i){
auto it = new QStandardItem(QString::number(i));
model.appendRow(it);
for (int j=0; j<4; ++j) {
it->appendRow(new QStandardItem(QString("%1-%2").arg(i).arg(j)));
}
}
w.expandAll();
w.show();
return a.exec();
}
Thanks for helping me out.
I understand now. I added code in QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const metod to style QLineEdit. I am trying to get the same background color for QLineEdit when it is created- when I am editing qtreeview item. The problem is when I select the item in qtreeview the whole row gets colored. That's ok. Now when I edit the item for example to change the text in qtreeview item, only the text part gets selected and colored with the same color as previous row selection color. The rest of the QLineEdit is white. In editing mode I would like to color the whole row which is edited with the same color. I could as obviously from my code color it with RGB but I don't know the exact RGB values. Is there any way to get the exact RGB color from item selection and then use it in
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
my code:
QWidget* myQItemDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QLineEdit *lineEdit = new QLineEdit(parent);
connect(lineEdit,SIGNAL(editingFinished()),this,SLOT(commitAndCloseEditor()));
QPalette pal;
pal.setColor(QPalette::HighlightedText, QColor(Qt::white));
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
lineEdit->setPalette(pal);
lineEdit->setFrame(false);
return lineEdit;
}
Thanks, Tom

How to exclude one row from sort by column on QSortFilterProxyModel

I'm using QTableView with QSortFilterProxyModel.
I have emty row for manually input:
Example of table
Now i want to sort by Date
But i don't wanna move the last row
Example of sort table
I will be grateful for the help
proxyModel->sort(2);
You can use a custom QSortFilterProxyModel that does not sort the last row. In the following example you will see on the left side the original table and on the right side the table with the special order.
#include <QtWidgets>
class SortFilterProxyModel: public QSortFilterProxyModel
{
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
bool lessThan(const QModelIndex &left,
const QModelIndex &right) const
{
int row_max = std::max(left.row(), right.row());
if(row_max >= sourceModel()->rowCount()-1)
return left.row() < right.row();
return QSortFilterProxyModel::lessThan(left, right);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QTableView *left_view = new QTableView;
QTableView *right_view = new QTableView;
QHBoxLayout *hlay = new QHBoxLayout(&w);
hlay->addWidget(left_view);
hlay->addWidget(right_view);
QStandardItemModel model(12, 3);
SortFilterProxyModel proxy;
proxy.setSourceModel(&model);
left_view->setModel(&model);
right_view->setModel(&proxy);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(100.0, 200.0);
for (int i=0; i< model.rowCount(); ++i) {
for(int j=0; j<model.columnCount(); ++j){
QStandardItem *it = new QStandardItem;
it->setData(dis(gen), Qt::DisplayRole);
model.setItem(i, j, it);
}
}
w.resize(640, 480);
proxy.sort(2);
w.show();
return a.exec();
}

Resources