why are different treeItems linked to the same data - qt

I have been trying to copy a treeitem to another location in the tree view along with its sub tree.
Finally i have been able to to move all of them successfully.
Header file for treeItem
class TreeItem
{
public:
explicit TreeItem( Container *data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
void appendChild(TreeItem *child);
TreeItem& operator = (const TreeItem &item);
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const ;
Container* GetContainer();
bool setData(Container* data , QVariant value);
void setContainer( Container* data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
void removeChild(int row);
void removeChild(TreeItem* itm);
QList<TreeItem*> children();
std::string getChildName(int row);
std::string getName();
int row() const;
void insertChild(int pos, TreeItem *child);
private:
QList<TreeItem*> childItems;
Container* itemData;
TreeItem* parentItem;
};
Cpp file for treeItem
TreeItem::TreeItem( Container *data, TreeItem *parent )
{
parentItem = parent;
itemData = new Container;
*itemData = *data;
}
TreeItem::~TreeItem()
{
if (itemData != nullptr)
{
delete itemData;
}
qDeleteAll(childItems);
}
TreeItem& TreeItem::operator = (const TreeItem &item)
{
qDebug() << "TreeItem operator called";
// if( this->itemData == nullptr)
// this->itemData = new Container;
*this->itemData = *item.itemData;
this->childItems = item.childItems;
this->parentItem = item.parentItem;
return *this;
}
TreeItem *TreeItem::parent()
{
return parentItem;
}
TreeItem *TreeItem::child(int iNumber)
{
return childItems.value(iNumber);
}
int TreeItem::childCount() const
{
return childItems.count();
}
int TreeItem::childNumber() const
{
if (parentItem)
return parentItem->childItems.indexOf(const_cast<TreeItem*> (this));
return 0;
}
Container TreeItem::data() const
{
return *itemData;
}
bool TreeItem::setData( Container* data , QVariant value )
{
//*itemData = *data; // Do Not !!!! uncomment this ///////////////////////////as it will set the value of default container constructor.
itemData->SetName(value.toString().toStdString() );
return true;
}
bool TreeItem::insertChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
Container cont;
TreeItem *item = new TreeItem(&cont, this);
childItems.insert(position, item);
return true;
}
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
for (int row = 0; row < count; ++row)
{
delete childItems.takeAt(position);
}
return true;
}
void TreeItem::setContainer( Container* cont)
{
*itemData = *cont;
}
void TreeItem::appendChild(TreeItem *node)
{
childItems.append( node );
}
int TreeItem::row() const
{
// qDebug() << "The child count = " << parentItem->childItems.indexOf(const_cast<TreeItem*>(this));
if (parentItem)
return parentItem->childItems.indexOf( const_cast<TreeItem*>(this) );
return 0;
}
void TreeItem::removeChild(int row)
{
// qDebug() << "The Row Number" << row;
// childItems.removeAt(row);
delete childItems.takeAt(row);
}
void TreeItem::insertChild(int pos, TreeItem *child)
{
childItems.insert(pos, child);
child->parentItem = this;
}
void TreeItem::removeChild(TreeItem* itm)
{
childItems.removeOne(itm);
}
std::string TreeItem::getChildName(int row)
{
return childItems.value(row)->getName();
}
std::string TreeItem::getName()
{
return itemData->GetName();
}
Container* TreeItem::GetContainer()
{
return itemData;
}
QList<TreeItem*> TreeItem::children()
{
return childItems;
}
This is my function to build the tree for subcontainers of the tree item in the tree structure.
void TreeModel::buildTree(TreeItem * pItem, QDataStream & ds) const
{
if (pItem == NULL)
return;
ds << reinterpret_cast<qlonglong>(pItem);
ds << pItem->childCount();
foreach(TreeItem* childItem, pItem->children())
{
buildTree(childItem, ds);
}
}
This is in the Drop mime data function where i restore the sub tree of the container.
// count is the number of child containers of the tree item
TreeItem *node;
//qDebug() << "The row" << row << parentNode->data().GetName().c_str() ;
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
int childCount;
stream >> nodePtr;
stream >> childCount;
node = reinterpret_cast<TreeItem *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parent())
--row;
TreeItem *nodeNew = new TreeItem(node->GetContainer(), node->parent());
nodeNew->setContainer(node->GetContainer());
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, nodeNew);
endInsertRows();
for (int k = 0; k < childCount; k++)
{
restoreTree(stream, nodeNew);
}
++row;
}
This is the function to restore the sub tree.
TreeItem * TreeModel::restoreTree(QDataStream & ds , TreeItem* parent) const
{
Container cont;
// Restore the info from the stream
int childCount;
qlonglong nodePtr;
ds >> nodePtr;
ds >> childCount;
TreeItem *currentItem = reinterpret_cast<TreeItem *>(nodePtr);
if (currentItem == nullptr)
return nullptr;
TreeItem *thisItem = new TreeItem(currentItem->GetContainer(), currentItem->parent() );
thisItem->setContainer(currentItem->GetContainer());
//*thisItem = *currentItem;
parent->appendChild(thisItem);
for (auto nChild = 0; nChild < childCount; ++nChild)
{
TreeItem* oldChild = restoreTree(ds, thisItem);
if (oldChild != nullptr)
{
TreeItem *pChild = new TreeItem( oldChild->GetContainer() , oldChild->parent() );
pChild->setContainer(oldChild->GetContainer());
thisItem->appendChild(pChild);
qDebug() << "Appending child";
}
}
return thisItem;
}
The tree gets restored in the correct manner.
But now i have a issue, the treeitem in the old position and the new position are linked together , if i make any changes to the tree item in the old position than it also affects the TreeItem in the new position.

Changing parent->appendChild(thisItem); to parent->insertChild(row, thisItem); solved the problem now the BuildTree function that works.
TreeItem * TreeModel::restoreTree(QDataStream & ds , TreeItem* parent , int row) const
{
Container cont;
// Restore the info from the stream
int childCount;
qlonglong nodePtr;
QModelIndex parentIndex;
ds >> nodePtr;
ds >> childCount;
TreeItem *currentItem = reinterpret_cast<TreeItem *>(nodePtr);
if (currentItem == nullptr)
return nullptr;
TreeItem *thisItem = new TreeItem(currentItem->GetContainer(), currentItem->parent() );
thisItem->setContainer(currentItem->GetContainer());
//*thisItem = *currentItem;
// parent->appendChild(thisItem);
parent->insertChild(row, thisItem);
for (auto nChild = 0; nChild < childCount; ++nChild)
{
restoreTree(ds, thisItem , nChild);
}
return thisItem;
}
It was more of hit and try to find the solution that worked , if someone can give a explanation on the difference between parent->appendChild(thisItem); to parent->insertChild(row, thisItem) that would be great.
Or Any alternative solution.

Related

QSortFilterProxyModel: crash sometimes when adding rows from the source model

I am writing a Qt application that includes a table view which displays the tasks performed by different persons. There is a column per person, and a row per day. The view is initialized with 26 rows from today minus 10 days to today +15 days. App overview When the vertical scrollbar is at min or max and that the user makes an action to move up/down, 10 new rows (i.e. days) are added on top/bottom. The problem is that sometimes it crashes at that time because of some internal QPersistentModelIndex issues, but only since I added a QSortFilterProxyModel between the source model and the view to display only the days where someone has a task to do.
The function calls propagate as follows:
the view (of type DashboardTableView) emits the signal "wantToGoMoreUp" or "wantToGoMoreDown"
-> DashboardWidget::slotAskDataUp or slotAskDataDown
-> DashboardTableModel::addDaysOnTop or addDaysOnBottom
I tried:
with a custom model instead of a QSortFilterProxyModel (by reimplementing "filterAcceptsRow")
to add "emit layoutAboutToBeChanged()" and "emit layoutChanged()" around rows additions/removals
... but with no success, it still crashes.
Sometimes the crash occurs after many manipulations (I was not able to understand what is the succession of events that makes it crash). The possible manipulations are:
scrolling of course (mouse wheel, mouse click on scroll bar buttons or page up/down keys)
jump to a given date (right click + select a date in the calendar pop-up)
enable/disable filtering using the toolbar button
I made a full working example on my GitHub account:
https://github.com/3noix/ForDebug
I am currently using Qt 5.11.0 with MinGW 5.3.0 32 bits.
So do you have any idea of what I am doing wrong?
Thanks by advance.
//DashboardWidget.h
#include <QWidget>
#include <QVector>
#include "../Task.h"
class QVBoxLayout;
class DashboardTableView;
class DashboardTableModel;
class DashboardFilterModel;
class QSortFilterProxyModel;
class DashboardTableDelegate;
class DashboardWidget : public QWidget
{
Q_OBJECT
public:
explicit DashboardWidget(QWidget *parent = nullptr);
DashboardWidget(const DashboardWidget &other) = delete;
DashboardWidget(DashboardWidget &&other) = delete;
DashboardWidget& operator=(const DashboardWidget &other) = delete;
DashboardWidget& operator=(DashboardWidget &&other) = delete;
virtual ~DashboardWidget() = default;
void toggleDaysFiltering();
void updateTasks(const QVector<Task> &tasks, const QDateTime &refreshDateTime);
public slots:
void slotAskDataUp();
void slotAskDataDown();
void slotShowDate(const QDate &date);
private slots:
void slotNewTimeRange(const QDate &from, const QDate &to);
private:
QVBoxLayout *m_layout;
DashboardTableView *m_view;
DashboardTableModel *m_model;
#ifdef CUSTOM_FILTER_MODEL
DashboardFilterModel *m_filter;
#else
QSortFilterProxyModel *m_filter;
#endif
DashboardTableDelegate *m_delegate;
};
//DashboardWidget.cpp
#include "DashboardWidget.h"
#include "DashboardTableView.h"
#include "DashboardTableModel.h"
#include "DashboardFilterModel.h"
#include <QSortFilterProxyModel>
#include "DashboardTableDelegate.h"
#include "DashboardGeometry.h"
#include "../DataInterface.h"
#include <QVBoxLayout>
///////////////////////////////////////////////////////////////////////////////
// RESUME :
//
// CONSTRUCTEUR
//
// TOGGLE DAYS FILTERING
// SLOT ASK DATA UP
// SLOT ASK DATA DOWN
// SLOT NEW TIME RANGE
// SLOT SHOW DATE
// UPDATE TASKS
///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTEUR ///////////////////////////////////////////////////////////////
DashboardWidget::DashboardWidget(QWidget *parent) : QWidget{parent}
{
m_layout = new QVBoxLayout{this};
this->setLayout(m_layout);
m_view = new DashboardTableView{this};
m_model = new DashboardTableModel{this};
m_delegate = new DashboardTableDelegate{this};
#ifdef CUSTOM_FILTER_MODEL
m_filter = new DashboardFilterModel{this};
#else
m_filter = new QSortFilterProxyModel{this};
m_filter->setFilterKeyColumn(0);
m_filter->setFilterRole(Qt::UserRole+1);
m_filter->setFilterRegExp(QRegExp{});
#endif
m_filter->setSourceModel(m_model);
m_view->setModel(m_filter);
m_view->setItemDelegate(m_delegate);
m_view->setupView();
m_layout->addWidget(m_view);
m_layout->setContentsMargins(0,0,0,0);
QObject::connect(m_view, SIGNAL(wantToGoMoreUp()), this, SLOT(slotAskDataUp()));
QObject::connect(m_view, SIGNAL(wantToGoMoreDown()), this, SLOT(slotAskDataDown()));
QObject::connect(m_view, SIGNAL(dateClicked(QDate)), this, SLOT(slotShowDate(QDate)));
QObject::connect(m_model, SIGNAL(newTimeRange(QDate,QDate)), this, SLOT(slotNewTimeRange(QDate,QDate)));
}
// TOGGLE DAYS FILTERING //////////////////////////////////////////////////////
void DashboardWidget::toggleDaysFiltering()
{
#ifdef CUSTOM_FILTER_MODEL
bool b = m_filter->filtersDays();
m_filter->setFilterOnDays(!b);
#else
if (m_filter->filterRegExp().isEmpty()) {m_filter->setFilterRegExp("Keep");}
else {m_filter->setFilterRegExp(QRegExp{});}
#endif
}
// SLOT ASK DATA UP ///////////////////////////////////////////////////////////
void DashboardWidget::slotAskDataUp()
{
int nbDays = 10;
QDate from = m_model->from();
m_model->addDaysOnTop(nbDays);
//if (m_model->daysVisible() > 100) {m_model->removeDaysOnBottom(nbDays);}
this->slotNewTimeRange(from.addDays(-nbDays),from);
}
// SLOT ASK DATA DOWN /////////////////////////////////////////////////////////
void DashboardWidget::slotAskDataDown()
{
int nbDays = 10;
QDate to = m_model->to();
m_model->addDaysOnBottom(nbDays);
//if (m_model->daysVisible() > 100) {m_model->removeDaysOnTop(nbDays);}
this->slotNewTimeRange(to,to.addDays(nbDays));
}
// SLOT SHOW DATE /////////////////////////////////////////////////////////////
void DashboardWidget::slotShowDate(const QDate &date)
{
int row = m_model->showDate(date);
QModelIndex indexSource = m_model->index(row,0);
QModelIndex indexView = m_filter->mapFromSource(indexSource);
m_view->scrollTo(indexView,QAbstractItemView::PositionAtTop);
}
// SLOT NEW TIME RANGE ////////////////////////////////////////////////////////
void DashboardWidget::slotNewTimeRange(const QDate &from, const QDate &to)
{
// list the tasks in this interval
QString errorMessage;
QVector<Task> tasks = DataInterface::instance().getTasks(from,to,&errorMessage);
if (errorMessage != "") {return;}
// update the table
this->updateTasks(tasks,QDateTime{});
}
// UPDATE TASKS ///////////////////////////////////////////////////////////////
void DashboardWidget::updateTasks(const QVector<Task> &tasks, const QDateTime &refreshDateTime)
{
m_model->updateTasks(tasks);
if (refreshDateTime.isValid()) {m_view->setUpdateTime(refreshDateTime);}
}
//DashboardTableModel.h
#include <QAbstractTableModel>
#include "../User.h"
#include "../Task.h"
#include <QDate>
class DashboardTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DashboardTableModel(QObject *parent = nullptr);
DashboardTableModel(const DashboardTableModel &other) = delete;
DashboardTableModel(DashboardTableModel &&other) = delete;
DashboardTableModel& operator=(const DashboardTableModel &other) = delete;
DashboardTableModel& operator=(DashboardTableModel &&other) = delete;
virtual ~DashboardTableModel() = default;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override final;
Qt::ItemFlags flags(const QModelIndex &index) const override final;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override final;
int columnCount(const QModelIndex &parent = QModelIndex{}) const override final;
int rowCount(const QModelIndex &parent = QModelIndex{}) const override final;
void updateTasks(const QVector<Task> &tasks);
int dateToRow(const QDate &date);
QDate from() const;
QDate to() const;
int daysVisible() const;
int showDate(const QDate &date);
void addDaysOnTop(int nbDays = 20);
void addDaysOnBottom(int nbDays = 20);
bool removeDaysOnTop(int nbDays = 20);
bool removeDaysOnBottom(int nbDays = 20);
void clear();
signals:
void newTimeRange(const QDate &from, const QDate &to);
private:
void initLines(const QDate &from, const QDate &to);
using Location = std::pair<QDate,int>;
struct DataLine
{
QDate date;
QVector<TaskGroup> data;
};
static int searchTask(const TaskGroup &g, int taskId);
int userIdToColumn(int userId) const;
QVector<User> m_users;
QList<DataLine> m_data;
QMap<int,Location> m_index; // key=taskId
};
//DashboardTableModel.cpp
#include "DashboardTableModel.h"
#include <QBrush>
const QStringList daysOfWeek{"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
const QColor greenToday{200,255,200};
const QColor grayWeekEnd{240,240,240};
const int initNbDaysBefore = -10;
const int initNbDaysAfter = 15;
const bool emitLayoutChange = false;
///////////////////////////////////////////////////////////////////////////////
// RESUME :
//
// CONSTRUCTEUR
// INIT LINES
//
// FROM
// TO
// DAYS VISIBLE
//
// DATE TO ROW
// SEARCH TASK
//
// SHOW DATE
// ADD DAYS ON TOP
// ADD DAYS ON BOTTOM
// REMOVE DAYS ON TOP
// REMOVE DAYS ON BOTTOM
// CLEAR
//
// DATA
// FLAGS
// HEADER DATA
// COLUMN COUNT
// ROW COUNT
//
// UPDATE TASKS
///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTEUR ///////////////////////////////////////////////////////////////
DashboardTableModel::DashboardTableModel(QObject *parent) : QAbstractTableModel{parent}
{
m_users << User{0,"Serge","Karamazov","SKZ"};
m_users << User{1,"Patrick","Biales","PBL"};
m_users << User{2,"Odile","Deray","ODR"};
m_users << User{3,"Mevatlaver","Kraspek","MLK"};
QDate from = QDate::currentDate().addDays(initNbDaysBefore);
QDate to = QDate::currentDate().addDays(initNbDaysAfter);
this->initLines(from,to);
}
// INIT LINES /////////////////////////////////////////////////////////////////
void DashboardTableModel::initLines(const QDate &from, const QDate &to)
{
this->clear();
int nbCols = this->columnCount();
int nbRowsInit = from.daysTo(to) + 1;
QDate date = from;
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},0,nbRowsInit-1);
for (int row=0; row<=nbRowsInit; ++row)
{
date = date.addDays(1);
m_data << DataLine{date,QVector<TaskGroup>(nbCols)};
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// FROM ///////////////////////////////////////////////////////////////////////
QDate DashboardTableModel::from() const
{
if (m_data.size() == 0) {return {};}
return m_data[0].date;
}
// TO /////////////////////////////////////////////////////////////////////////
QDate DashboardTableModel::to() const
{
if (m_data.size() == 0) {return {};}
return m_data.last().date;
}
// DAYS VISIBLE ///////////////////////////////////////////////////////////////
int DashboardTableModel::daysVisible() const
{
if (m_data.size() == 0) {return 0;}
QDate from = m_data[0].date;
QDate to = m_data.last().date;
return from.daysTo(to)+1;
}
// DATE TO ROW ////////////////////////////////////////////////////////////////
int DashboardTableModel::dateToRow(const QDate &date)
{
if (m_data.size() == 0) {return -1;}
QDate date1 = m_data[0].date;
QDate date2 = m_data.last().date;
if (date < date1 || date > date2) {return -1;}
return date1.daysTo(date);
}
// USER ID TO COLUMN //////////////////////////////////////////////////////////
int DashboardTableModel::userIdToColumn(int userId) const
{
return userId;
}
// SEARCH TASK ////////////////////////////////////////////////////////////////
int DashboardTableModel::searchTask(const TaskGroup &g, int taskId)
{
for (int i=0; i<g.size(); ++i)
{
if (g[i].id == taskId)
return i;
}
return -1;
}
// SHOW DATE //////////////////////////////////////////////////////////////////
int DashboardTableModel::showDate(const QDate &date)
{
if (m_data.size() == 0) {return -1;}
QDate from = this->from();
QDate to = this->to();
if (from <= date && date <= to) {return this->dateToRow(date);}
int fromToDate = from.daysTo(date);
int toToDate = to.daysTo(date);
if (fromToDate >= -15 && fromToDate < 0)
{
this->addDaysOnTop(15);
emit newTimeRange(date,from);
return this->dateToRow(date);
}
else if (toToDate > 0 && toToDate <= 15)
{
this->addDaysOnBottom(15);
emit newTimeRange(to,date);
return this->dateToRow(date);
}
// we discard the existing data and restart from nothing
this->clear();
QDate newFrom = date.addDays(initNbDaysBefore);
QDate newTo = date.addDays(initNbDaysAfter);
this->initLines(newFrom,newTo);
emit newTimeRange(newFrom,newTo);
return this->dateToRow(date);
}
// ADD DAYS ON TOP ////////////////////////////////////////////////////////////
void DashboardTableModel::addDaysOnTop(int nbDays)
{
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},0,nbDays-1);
QDate date = this->from();
int nbCol = this->columnCount();
for (int row=0; row<nbDays; ++row)
{
date = date.addDays(-1);
m_data.push_front(DataLine{date,QVector<TaskGroup>(nbCol)});
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// ADD DAYS ON BOTTOM /////////////////////////////////////////////////////////
void DashboardTableModel::addDaysOnBottom(int nbDays)
{
int nOld = m_data.size();
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginInsertRows(QModelIndex{},nOld,nOld+nbDays-1);
QDate date = this->to();
int nbCol = this->columnCount();
for (int row=0; row<nbDays; ++row)
{
date = date.addDays(1);
m_data << DataLine{date,QVector<TaskGroup>(nbCol)};
}
this->endInsertRows();
if (emitLayoutChange) emit layoutChanged();
}
// REMOVE DAYS ON TOP /////////////////////////////////////////////////////////
bool DashboardTableModel::removeDaysOnTop(int nbDays)
{
if (m_data.size() < nbDays) {return false;}
// remove tasks ids from index
for (int row=0; row<nbDays; ++row)
{
for (const TaskGroup &g : m_data[row].data)
{
for (const Task &t : g)
{m_index.remove(t.id);}
}
}
// removal
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},0,nbDays-1);
for (int i=0; i<nbDays; ++i) {m_data.removeFirst();}
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
return true;
}
// REMOVE DAYS ON BOTTOM //////////////////////////////////////////////////////
bool DashboardTableModel::removeDaysOnBottom(int nbDays)
{
if (m_data.size() < nbDays) {return false;}
int start = m_data.size() - nbDays;
int end = m_data.size() - 1;
// remove tasks ids from index
for (int row=start; row<end; ++row)
{
for (const TaskGroup &g : m_data[row].data)
{
for (const Task &t : g)
{m_index.remove(t.id);}
}
}
// removal
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},start,end);
for (int i=0; i<nbDays; ++i) {m_data.removeLast();}
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
return true;
}
// CLEAR //////////////////////////////////////////////////////////////////////
void DashboardTableModel::clear()
{
if (m_data.size() == 0) {return;}
// this->beginResetModel();
if (emitLayoutChange) emit layoutAboutToBeChanged();
this->beginRemoveRows(QModelIndex{},0,m_data.size()-1);
m_data.clear();
m_index.clear();
this->endRemoveRows();
if (emitLayoutChange) emit layoutChanged();
// this->endResetModel();
}
// DATA ///////////////////////////////////////////////////////////////////////
QVariant DashboardTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
if (!index.isValid() || row < 0 || row >= this->rowCount(QModelIndex{})) {return {};}
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
const TaskGroup &g = m_data[row].data[col];
return QVariant::fromValue(g);
}
else if (role == Qt::BackgroundRole)
{
QDate date = m_data[row].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return QBrush{greenToday};}
int d = date.dayOfWeek();
if (d == 6 || d == 7) {return QBrush{grayWeekEnd};}
return {};
}
else if (role == Qt::UserRole)
{
int nbPostTasks = 0;
for (const TaskGroup &g : m_data[row].data) {nbPostTasks += g.size();}
return nbPostTasks;
}
else if (role == Qt::UserRole+1)
{
int nbPostTasks = 0;
for (const TaskGroup &g : m_data[row].data) {nbPostTasks += g.size();}
return (nbPostTasks > 0 ? "Keep" : "Skip");
}
return {};
}
// FLAGS //////////////////////////////////////////////////////////////////////
Qt::ItemFlags DashboardTableModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
return (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
// HEADER DATA ////////////////////////////////////////////////////////////////
QVariant DashboardTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
if (section < 0 || section >= m_users.size()) {return {};}
//return m_users[section].trigramme;
const User &u = m_users[section];
return u.firstName + " " + u.lastName + "\n" + u.trigramme;
}
else if (orientation == Qt::Vertical && role == Qt::DisplayRole)
{
if (section >= m_data.size()) {return {};}
QDate date = m_data[section].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return "Today";}
QString str = daysOfWeek[date.dayOfWeek()-1] + "\n" + date.toString("dd/MM/yy");
if (date.dayOfWeek() == 1) {str += "\nW" + QString::number(date.weekNumber());}
return str;
}
else if (orientation == Qt::Horizontal && role == Qt::TextAlignmentRole)
{
return Qt::AlignCenter;
}
else if (orientation == Qt::Vertical && role == Qt::TextAlignmentRole)
{
return Qt::AlignCenter;
}
else if (orientation == Qt::Vertical && role == Qt::BackgroundRole)
{
if (section >= m_data.size()) {return {};}
QDate date = m_data[section].date;
if (!date.isValid()) {return {};}
if (date == QDate::currentDate()) {return QBrush{greenToday};}
int d = date.dayOfWeek();
if (d == 6 || d == 7) {return QBrush{grayWeekEnd};}
return {};
}
return QAbstractTableModel::headerData(section, orientation, role);
}
// COLUMN COUNT ///////////////////////////////////////////////////////////////
int DashboardTableModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {return 0;}
return m_users.size();
}
// ROW COUNT //////////////////////////////////////////////////////////////////
int DashboardTableModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {return 0;}
return m_data.size();
}
// UPDATE TASKS ///////////////////////////////////////////////////////////////
void DashboardTableModel::updateTasks(const QVector<Task> &tasks)
{
for (const Task &t : tasks)
{
int newRow = this->dateToRow(t.target);
int newCol = this->userIdToColumn(t.user_id);
bool bDisplayTask = (newRow != -1 && !t.deleted);
if (m_index.contains(t.id)) // the task is already displayed
{
Location loc = m_index[t.id];
int oldRow = this->dateToRow(loc.first);
int oldCol = loc.second;
if (bDisplayTask)
{
if (newRow != oldRow || newCol != oldCol)
{
// we move the post-it (and maybe update it)
TaskGroup &gOld = m_data[oldRow].data[oldCol];
int i = DashboardTableModel::searchTask(gOld,t.id);
if (i != -1) {gOld.removeAt(i);}
m_index.remove(t.id);
m_data[newRow].data[newCol] << t;
m_index.insert(t.id,std::make_pair(t.target,newCol));
QModelIndex oldIndex = this->index(oldRow,oldCol,{});
QModelIndex oldLineIndex = this->index(oldRow,0,{});
QModelIndex newLineIndex = this->index(newRow,0,{});
emit dataChanged(oldIndex,oldIndex);
emit dataChanged(oldLineIndex,oldLineIndex);
emit dataChanged(newLineIndex,newLineIndex);
}
else
{
// we only update the post-it
TaskGroup &g = m_data[newRow].data[newCol];
int i = DashboardTableModel::searchTask(g,t.id);
if (i != -1) {g[i] = t;}
}
QModelIndex newIndex = this->index(newRow,newCol,{});
emit dataChanged(newIndex,newIndex);
}
else
{
// we delete the post-it
TaskGroup &g = m_data[oldRow].data[oldCol];
int i = DashboardTableModel::searchTask(g,t.id);
if (i != -1) {g.removeAt(i);}
m_index.remove(t.id);
QModelIndex index = this->index(oldRow,oldCol,{});
QModelIndex oldLineIndex = this->index(oldRow,0,{});
emit dataChanged(index,index);
emit dataChanged(oldLineIndex,oldLineIndex);
}
}
else // the task is not displayed yet
{
if (bDisplayTask)
{
// we add the post-it
m_data[newRow].data[newCol] << t;
m_index.insert(t.id,std::make_pair(t.target,newCol));
QModelIndex index = this->index(newRow,newCol,{});
QModelIndex newLineIndex = this->index(newRow,0,{});
emit dataChanged(index,index);
emit dataChanged(newLineIndex,newLineIndex);
}
}
}
}

How to get keypressed event for table view

This is my code of DropMimeData for the Tree Model.
The code works as expected , the user can drag and drop a treeitem from one location in the table view to another location in the view..
I need to add a condition in mimeData function based on Cntrl KeyPressed.
1) How can i get to know if the cntrl key is pressed in the function.
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction
action, int row, int column, const QModelIndex &parent)
{
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
return false;
}
TreeItem *parentNode = getItem(parent);
int count;
stream >> count;
if (row == -1) {
if (parent.isValid())
row = 0;
else
row = rowCount(parent);
}
for (int i = 0; i < count; ++i) {
qlonglong nodePtr;
stream >> nodePtr;
TreeItem *node = reinterpret_cast<TreeItem *>(nodePtr);
if (node->row() < row && parentNode == node->parent())
--row;
TreeItem *nodeNew = new TreeItem(node->GetContainer(), parentNode);
nodeNew->setContainer(node->GetContainer());
parentNode->insertChild(row, nodeNew);
endInsertRows();
++row;
// if( ctrl key is pressed ) while dragging and dropping item the Cntrl key is pressed
// removeItem(node);
}
return true;
}
Try this.
if (QGuiApplication::keyboardModifiers() != Qt::ControlModifier)
removeItem(node);

when deleting Tree item destructor not being called

when i am deleting Treeitem from the the tree model the destructor for the tree item is not being called.
This is my code for deleting the tree item from the model.
void TreeModel::removeItem(TreeItem *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
TreeItem* itm = getItem(idx);
beginRemoveRows(idx.parent(), row, row);
node->parent()->removeChild(row);
endRemoveRows();
}
The code for Treeitem RemoveChild.
void TreeItem::removeChild(int row)
{
childItems.removeAt(row);
}
The code for tree item header file.
#include <QList>
#include <QVariant>
#include <QVector>
#include "Container.h"
class TreeItem
{
public:
explicit TreeItem( Container *data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
void appendChild(TreeItem *child);
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const ;
Container* GetContainer();
bool setData(Container* data , QVariant value);
void setContainer( Container* data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
void removeChild(int row);
void removeChild(TreeItem* itm);
std::string getChildName(int row);
std::string getName();
int row() const;
void insertChild(int pos, TreeItem *child);
private:
QList<TreeItem*> childItems;
Container* itemData;
TreeItem* parentItem;
};
The code for the tree item Cpp file.
/////////////////////////////////////////////
//////////////////////////////////////////////////////
TreeItem::TreeItem( Container *data, TreeItem *parent )
{
parentItem = parent;
itemData = new Container;
*itemData = *data;
}
TreeItem::~TreeItem()
{
qDebug() << itemData->GetName().c_str();
if (itemData != nullptr)
{
delete itemData;
qDebug() << "deleting Item Data";
}
qDeleteAll(childItems);
}
TreeItem *TreeItem::parent()
{
return parentItem;
}
TreeItem *TreeItem::child(int iNumber)
{
return childItems.value(iNumber);
}
int TreeItem::childCount() const
{
return childItems.count();
}
int TreeItem::childNumber() const
{
if (parentItem)
return parentItem->childItems.indexOf(const_cast<TreeItem*> (this));
return 0;
}
Container TreeItem::data() const
{
return *itemData;
}
bool TreeItem::setData( Container* data , QVariant value )
{
//*itemData = *data; // Do Not !!!! uncomment this as it will set the
value of default container constructor.
itemData->SetName(value.toString().toStdString() );
return true;
}
bool TreeItem::insertChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
Container cont;
TreeItem *item = new TreeItem(&cont, this);
childItems.insert(position, item);
return true;
}
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position > childItems.count())
return false;
for (int row = 0; row < count; ++row)
{
delete childItems.takeAt(position);
}
return true;
}
void TreeItem::setContainer( Container* cont)
{
*itemData = *cont;
}
void TreeItem::appendChild(TreeItem *node)
{
childItems.append( node );
}
int TreeItem::row() const
{
if (parentItem)
return parentItem->childItems.indexOf( const_cast<TreeItem*>(this) );
return 0;
}
void TreeItem::removeChild(int row)
{
childItems.removeAt(row);
}
void TreeItem::insertChild(int pos, TreeItem *child)
{
childItems.insert(pos, child);
child->parentItem = this;
}
void TreeItem::removeChild(TreeItem* itm)
{
childItems.removeOne(itm);
}
std::string TreeItem::getChildName(int row)
{
return childItems.value(row)->getName();
}
std::string TreeItem::getName()
{
return itemData->GetName();
}
Container* TreeItem::GetContainer()
{
return itemData;
}
The Header file for the TreeModel Class///////////////////////////////////
#pragma once
#include <QAbstractItemModel>
#include <QString>
#include <QMimedata.h>
#include <Qdatastream.h>
class TreeItem;
class Container;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
TreeModel(const QString &header, Container *data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role = Qt::EditRole) override;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &parent) const override;
bool insertRows(int position, int rows, const QModelIndex &parent);
// bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Container* GetContainer(const QModelIndex &index);
void SetContainer(const QModelIndex &index, Container* cont);
////////////////////// Drag And Drop Actions ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Qt::DropActions supportedDropActions() const override;
Qt::DropActions supportedDragActions() const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
void setupModelData(const QStringList &lines, TreeItem *parent);
void removeItem(TreeItem *item);
bool FindChild(std::string stdstrChildName);
TreeItem *getItem(const QModelIndex &index) const;
TreeItem *getRoot();
private:
//void setupModelData(const Container &cont, TreeItem *parent);
TreeItem *rootItem;
};
The Cpp file for the TreeModel
#include "TreeModel.h"
#include "TreeItem.h"
#include <qcoreapplication.h>
#include <qdebug.h>
#include "Container.h"
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if (item)
return item;
}
return rootItem;
}
TreeModel::TreeModel(const QString &header, Container *data, QObject
*parent) : QAbstractItemModel(parent)
{
qDebug() << "First level done";
rootItem = new TreeItem( data);
}
TreeModel::~TreeModel()
{
delete rootItem;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
TreeItem *item = getItem(index);
return QString::fromStdString(item->data().GetName());
//return QVariant::fromValue(item->data());
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return QVariant::fromValue(rootItem->data());
return QVariant();
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &val, int
role)
{
if (role != Qt::EditRole)
return false;
Container c = val.value<Container>();
TreeItem *item = getItem(index);
bool result = true;
item->setData(&c, val);
if (result)
emit dataChanged(index, index, { role });
return result;
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent)
const
{
if (parent.isValid() && parent.column() != 0)
return QModelIndex();
TreeItem *parentItem = getItem(parent);
TreeItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TreeItem *childItem = getItem(index);
TreeItem *parentItem = childItem->parent();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &val, int role)
{
if (role != Qt::EditRole || orientation != Qt::Horizontal)
return false;
Container c = val.value<Container>();
bool result = rootItem->setData(&c, val);
if (result)
emit headerDataChanged(orientation, section, section);
return result;
}
bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
TreeItem *parentItem = getItem(parent);
bool success;
beginInsertRows(parent, position, position + rows - 1);
success = parentItem->insertChildren(position, rows);
endInsertRows();
return success;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 1;
TreeItem *parentItem = getItem(parent);
//qDebug() << "the child count = " << parentItem->childCount() << parentItem->data().GetName().c_str();
return parentItem->childCount();
}
int TreeModel::columnCount(const QModelIndex & /* parent */) const
{
return 1;
}
Container* TreeModel::GetContainer(const QModelIndex &index)
{
TreeItem *item = getItem(index);
return item->GetContainer();
}
void TreeModel::SetContainer(const QModelIndex &index, Container* Cont)
{
TreeItem *item = getItem(index);
item->setContainer(Cont);
}
static const char s_treeNodeMimeType[] = "application/x-treenode";
QStringList TreeModel::mimeTypes() const
{
return QStringList() << s_treeNodeMimeType;
}
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray data; //a kind of RAW format for datas
//QDataStream is independant on the OS or proc architecture
//serialization of C++'s basic data types, like char, short, int, char *, etc.
//Serialization of more complex data is accomplished
//by breaking up the data into primitive units.
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeItem *> nodes;
//
foreach(const QModelIndex &index, indexes) {
TreeItem *node = getItem(index);
if (!nodes.contains(node))
nodes << node;
}
stream << QCoreApplication::applicationPid();
stream << nodes.count();
foreach(TreeItem *node, nodes) {
stream << reinterpret_cast<qlonglong>(node);
}
mimeData->setData(s_treeNodeMimeType, data);
return mimeData;
}
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction
action, int row, int column, const QModelIndex &parent)
{
//Q_ASSERT(action == Qt::MoveAction);
//Q_UNUSED(column);
//test if the data type is the good one
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
// Let's not cast pointers that come from another process...
return false;
}
TreeItem *parentNode = getItem(parent);
// Q_ASSERT(parentNode);
int count;
stream >> count;
if (row == -1) {
// valid index means: drop onto item. I chose that this should insert
// a child item, because this is the only way to create the first child
of an item...
// This explains why Qt calls it parent: unless you just support
replacing, this
// is really the future parent of the dropped items.
if (parent.isValid())
row = 0;
else
// invalid index means: append at bottom, after last toplevel
row = rowCount(parent);
}
//qDebug() << "The row" << row << parentNode->data().GetName().c_str() ;
for (int i = 0; i < count; ++i) {
// Decode data from the QMimeData
qlonglong nodePtr;
stream >> nodePtr;
TreeItem *node = reinterpret_cast<TreeItem *>(nodePtr);
// Adjust destination row for the case of moving an item
// within the same parent, to a position further down.
// Its own removal will reduce the final row number by one.
if (node->row() < row && parentNode == node->parent())
--row;
// Remove from old position
// qDebug() << "The remove item " << node->data().GetName().c_str();
removeItem(node);
// Insert at new position
//qDebug() << "Inserting into" << parent << row;
beginInsertRows(parent, row, row);
parentNode->insertChild(row, node);
endInsertRows();
++row;
}
return true;
}
void TreeModel::removeItem(TreeItem *node)
{
const int row = node->row();
QModelIndex idx = createIndex(row, 0, node);
TreeItem* itm = getItem(idx);
beginRemoveRows(idx.parent(), row, row);
node->parent()->removeChild(row);
endRemoveRows();
}
Qt::DropActions TreeModel::supportedDropActions() const
{
return Qt::MoveAction;
}
Qt::DropActions TreeModel::supportedDragActions() const
{
return Qt::MoveAction;
}
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
QList<TreeItem*> parents;
QList<int> indentations;
parents << parent;
indentations << 0;
int number = 0;
while (number < lines.count()) {
int position = 0;
while (position < lines[number].length()) {
if (lines[number].mid(position, 1) != " ")
break;
position++;
}
QString lineData = lines[number].mid(position).trimmed();
if (!lineData.isEmpty()) {
// Read the column data from the rest of the line.
QStringList columnStrings = lineData.split("\t", QString::SkipEmptyParts);
QList<QVariant> columnData;
for (int column = 0; column < columnStrings.count(); ++column)
columnData << columnStrings[column];
if (position > indentations.last()) {
// The last child of the current parent is now the new parent
// unless the current parent has no children.
if (parents.last()->childCount() > 0) {
parents << parents.last()->child(parents.last()->childCount() - 1);
indentations << position;
}
}
else {
while (position < indentations.last() && parents.count() > 0) {
parents.pop_back();
indentations.pop_back();
}
}
Container c;
// Append a new node to the current parent's list of children.
parents.last()->appendChild(new TreeItem(&c, parents.last()));
}
++number;
}
}
TreeItem *TreeModel::getRoot()
{
return rootItem;
}
childItems.removeOne(itm); and childItems.removeAt(row); just remove TreeItem * from your QList<TreeItem*> childItems; so memory is not freed.
You should explicitly call delete, once they are removed as you did with delete childItems.takeAt(position); for example.

How to select child's items checkBoxs in QTreeView when select their parent's checkbox

I want to select/unselect all child's items QCheckBoxs when I select/unselect their parent's item QCheckBox.
i inherit from QTreeView and detect when the QCheckBox is selected then i call function to do selecting/unselecting process.
here my code:
#ifndef MYQTREEVIEW_H
#define MYQTREEVIEW_H
#include <QTreeView>
#include <QMouseEvent>
#include <QDebug>
#include <QStandardItem>
class MyQTreeView: public QTreeView {
public:
MyQTreeView(QWidget* parent=0): QTreeView(parent){}
virtual ~MyQTreeView() {}
protected:
void resettingCheckBox (QModelIndex& parentIndex) {
if ( ! parentIndex.isValid() )
return;
QString text = parentIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "parent is: " << text;
if ( model()->hasChildren(parentIndex) ) {
for( int i = 0; i < model()->rowCount(parentIndex) ; i++ ) {
QModelIndex childIndex = model()->index( i, 0, parentIndex );
if ( model()->hasChildren(childIndex) )
resettingCheckBox(childIndex);
else {
QString text = childIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "child is: " << text;
QStandardItem *parentItem = static_cast<QStandardItem*> (parentIndex.internalPointer());
QStandardItem *childItem = static_cast<QStandardItem*> (childIndex.internalPointer());
if ( parentItem->checkState() == Qt::Checked ) {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Unchecked);
}
else {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Checked);
}
}
}
}
}
void mousePressEvent (QMouseEvent *event) {
QModelIndex index = indexAt(event->pos());
if(index.isValid()) {
QStyleOptionButton opt;
opt.QStyleOption::operator=(viewOptions());
opt.rect = visualRect(index);
QRect rect = style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt);
if (rect.contains(event->pos())) {
resettingCheckBox(index);
}
QTreeView::mousePressEvent(event);
}
}
};
#endif // MYQTREEVIEW_H
the code is not working probably when i select/unselect parent checkBox (subchilds is not selected/unselected).
Thanks in advance.
I believe the best way to manipulate treeview items is through the model. It looks like you're using QStandardItemModel; so you can override your model's setData method and reset child items values for the item index passed as a parameter to this method. Below is a small example:
class TestModel : public QStandardItemModel
{
public:
TestModel() {}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
QStandardItem *item = itemFromIndex(index);
for( int i = 0; i < item->rowCount() ; i++ )
{
QStandardItem *childItem = item->child(i);
setData(childItem->index(), value, role);
}
return QStandardItemModel::setData(index, value, role);
}
};
here's how this model gets initialized:
QStandardItemModel* tableModel = new TestModel();
QStandardItem* parentItem = tableModel->invisibleRootItem();
for (int i = 0; i < 4; ++i)
{
QStandardItem *item = new QStandardItem(QString("item %0").arg(i));
item->setCheckable(true);
parentItem->appendRow(item);
parentItem = item;
}
treeView->setModel(tableModel);
hope this helps, regards
It seems to me that you should call QTreeView::mousePressEvent(event) before resettingCheckBox(index), to let QTreeView update the checkState.
override mouseReleaseEvent() instead of mousePressEvent()!
because the checkState changes when mouse Release not mouse - press!

HowTo restore QTreeView last expanded state?

What I have:
QTreeView class with table data
And connected QAbstractTableModel model
Question: how to save expanded state of items? Is some one have finished solutions?
PS: I know, that I can do this code by myself, but I don't have much time, and this is not the major problem of our project, but still we need it, because app contain a lot of such tables, and every time expanding tree items is annoyed process...
First, thanks to Razi for persistentIndexList and isExpanded way.
Second, here is the code which works for me just fine :-)
dialog.h file:
class Dialog : public QDialog
{
Q_OBJECT;
TreeModel *model;
TreeView *view;
public:
Dialog(QWidget *parent = 0);
~Dialog(void);
void reload(void);
protected:
void createGUI(void);
void closeEvent(QCloseEvent *);
void saveState(void);
void restoreState(void);
};
dialog.cpp file:
Dialog::Dialog(QWidget *parent)
{
createGUI();
reload();
}
Dialog::~Dialog(void) {};
void Dialog::reload(void)
{
restoreState();
}
void Dialog::createGUI(void)
{
QFile file(":/Resources/default.txt");
file.open(QIODevice::ReadOnly);
model = new TreeModel(file.readAll());
file.close();
view = new TreeView(this);
view->setModel(model);
QVBoxLayout *mainVLayout = new QVBoxLayout;
mainVLayout->addWidget(view);
setLayout(mainVLayout);
}
void Dialog::closeEvent(QCloseEvent *event_)
{
saveState();
}
void Dialog::saveState(void)
{
QStringList List;
// prepare list
// PS: getPersistentIndexList() function is a simple `return this->persistentIndexList()` from TreeModel model class
foreach (QModelIndex index, model->getPersistentIndexList())
{
if (view->isExpanded(index))
{
List << index.data(Qt::DisplayRole).toString();
}
}
// save list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
settings.setValue("ExpandedItems", QVariant::fromValue(List));
settings.endGroup();
}
void Dialog::restoreState(void)
{
QStringList List;
// get list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
List = settings.value("ExpandedItems").toStringList();
settings.endGroup();
foreach (QString item, List)
{
// search `item` text in model
QModelIndexList Items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(item));
if (!Items.isEmpty())
{
// Information: with this code, expands ONLY first level in QTreeView
view->setExpanded(Items.first(), true);
}
}
}
Have a nice day!)
PS: this example based on C:\Qt\4.6.3\examples\itemviews\simpletreemodel code.
Thanks to Razi and mosg I was able to get this working. I made it restore the expanded state recursively so I thought I would share that part.
void applyExpandState_sub(QStringList& expandedItems,
QTreeView* treeView,
QAbstractItemModel* model,
QModelIndex startIndex)
{
foreach (QString item, expandedItems)
{
QModelIndexList matches = model->match( startIndex, Qt::UserRole, item );
foreach (QModelIndex index, matches)
{
treeView->setExpanded( index, true );
applyExpandState_sub(expandedItems,
treeView,
model,
model->index( 0, 0, index ) );
}
}
}
Then use like:
void myclass::applyExpandState()
{
m_treeView->setUpdatesEnabled(false);
applyExpandState_sub( m_expandedItems,
m_treeView,
m_model,
m_model->index( 0, 0, QModelIndex() ) );
m_treeView->setUpdatesEnabled(true);
}
I am using the Qt::UserRole here because multiple items in my model can have the same display name which would mess up the expand state restoration, so the UserRole provides a unique identifier for each item to avoid that problem.
These two function by using a loop should do that for you:
QModelIndexList QAbstractItemModel::persistentIndexList () const
bool isExpanded ( const QModelIndex & index ) const
Here is a general approach that should work with any QTreeView based widget, that uses some sort of ID system to identify elements (I am assuming the ID is an int, which is stored inside the Qt::UserRole):
void MyWidget::saveExpandedState()
{
for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
saveExpandedOnLevel(tree_view_->model()->index(row,0));
}
void Widget::restoreExpandedState()
{
tree_view_->setUpdatesEnabled(false);
for(int row = 0; row < tree_view_->model()->rowCount(); ++row)
restoreExpandedOnLevel(tree_view_->model()->index(row,0));
tree_view_->setUpdatesEnabled(true);
}
void MyWidget::saveExpandedOnLevel(const QModelIndex& index)
{
if(tree_view_->isExpanded(index)) {
if(index.isValid())
expanded_ids_.insert(index.data(Qt::UserRole).toInt());
for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
saveExpandedOnLevel(index.child(row,0));
}
}
void MyWidget::restoreExpandedOnLevel(const QModelIndex& index)
{
if(expanded_ids_.contains(index.data(Qt::UserRole).toInt())) {
tree_view_->setExpanded(index, true);
for(int row = 0; row < tree_view_->model()->rowCount(index); ++row)
restoreExpandedOnLevel(index.child(row,0));
}
}
Instead of MyWidget::saveExpandedState() and MyWidget::saveExpandedState() one could also directly call MyWidget::saveExpandedOnLevel(tree_view_->rootIndex()) and MyWidget::restoreExpandedOnLevel(tree_view_->rootIndex()). I only used the above implementation because the for loop will be called anyway and MyWidget::saveExpandedState() and MyWidget::saveExpandedState() looked cleaner with my SIGNAL and SLOT design.
I have reworked iforce2d's solution into this:
void ApplyExpandState(QStringList & nodes,
QTreeView * view,
QAbstractItemModel * model,
const QModelIndex startIndex,
QString path)
{
path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
for(int i(0); i < model->rowCount(startIndex); ++i)
{
QModelIndex nextIndex = model->index(i, 0, startIndex);
QString nextPath = path + QString::number(nextIndex.row()) + QString::number(nextIndex.column());
if(!nodes.contains(nextPath))
continue;
ApplyExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
}
if(nodes.contains(path))
view->setExpanded( startIndex.sibling(startIndex.row(), 0), true );
}
void StoreExpandState(QStringList & nodes,
QTreeView * view,
QAbstractItemModel * model,
const QModelIndex startIndex,
QString path)
{
path+=QString::number(startIndex.row()) + QString::number(startIndex.column());
for(int i(0); i < model->rowCount(startIndex); ++i)
{
if(!view->isExpanded(model->index(i, 0, startIndex)))
continue;
StoreExpandState(nodes, view, model, model->index(i, 0, startIndex), path);
}
if(view->isExpanded(startIndex))
nodes << path;
}
This way there is no need to match data. Obviously - for this approach to work, tree needs to stay relatively unchanged. If you somehow change the order of tree items - it will expand wrong nodes.
Here is a version which doesn't rely on nodes having a unique Qt::UserRole or Qt::DisplayRole - it just serialises the entire QModelIndex
header:
#pragma once
#include <QTreeView>
class TreeView : public QTreeView
{
Q_OBJECT
public:
using QTreeView::QTreeView;
QStringList saveExpandedState(const QModelIndexList&) const;
void restoreExpandedState(const QStringList&);
};
source:
#include "tree_view.h"
#include <QAbstractItemModel>
namespace
{
std::string toString(const QModelIndex& index)
{
std::string parent = index.parent().isValid() ? toString(index.parent()) : "X";
char buf[512];
sprintf(buf, "%d:%d[%s]", index.row(), index.column(), parent.c_str());
return buf;
}
QModelIndex fromString(const std::string& string, QAbstractItemModel& model)
{
int row, column;
char parent_str[512];
sscanf(string.c_str(), "%d:%d[%s]", &row, &column, parent_str);
QModelIndex parent = *parent_str == 'X' ? QModelIndex() : fromString(parent_str, model);
return model.index(row, column, parent);
}
}
QStringList TreeView::saveExpandedState(const QModelIndexList& indices) const
{
QStringList list;
for (const QModelIndex& index : indices)
{
if (isExpanded(index))
{
list << QString::fromStdString(toString(index));
}
}
return list;
}
void TreeView::restoreExpandedState(const QStringList& list)
{
setUpdatesEnabled(false);
for (const QString& string : list)
{
QModelIndex index = fromString(string.toStdString(), *model());
setExpanded(index, true);
}
setUpdatesEnabled(true);
};
For a QFileSystemModel, you can't use persistentIndexList().
Here is my work around. It works pretty well, even if I do say so myself. I haven't tested to see what happens if you have a slow loading filesystem, or if you remove the file or path.
// scrolling code connection in constructor
model = new QFileSystemModel();
QObject::connect(ui->treeView, &QTreeView::expanded, [=](const QModelIndex &index)
{
ui->treeView->scrollTo(index, QAbstractItemView::PositionAtTop);//PositionAtCenter);
});
// save state, probably in your closeEvent()
QSettings s;
s.setValue("header_state",ui->treeView->header()->saveState());
s.setValue("header_geometry",ui->treeView->header()->saveGeometry());
if(ui->treeView->currentIndex().isValid())
{
QFileInfo info = model->fileInfo(ui->treeView->currentIndex());
QString filename = info.absoluteFilePath();
s.setValue("last_directory",filename);
}
// restore state, probably in your showEvent()
QSettings s;
ui->treeView->header()->restoreState(s.value("header_state").toByteArray());
ui->treeView->header()->restoreGeometry(s.value("header_geometry").toByteArray());
QTimer::singleShot(1000, [=]() {
QSettings s;
QString filename = s.value("last_directory").toString();
QModelIndex index = model->index(filename);
if(index.isValid())
{
ui->treeView->expand(index);
ui->treeView->setCurrentIndex(index);
ui->treeView->scrollTo(index, QAbstractItemView::PositionAtCenter);
qDebug() << "Expanded" << filename;
}
else
qDebug() << "Invalid index" << filename;
} );
Hope that helps someone.
My approach was to save the list of expanded items (as pointers) and when restoring, only set as expanded only the items in this list.
In order to use the code below, you may need to replace TreeItem * to a constant pointer to your object (that doesn't change after a refresh).
.h
protected slots:
void restoreTreeViewState();
void saveTreeViewState();
protected:
QList<TargetObject*> expandedTreeViewItems;
.cpp
connect(view->model(), SIGNAL(modelAboutToBeReset()), this, SLOT(saveTreeViewState()));
connect(view->model(), SIGNAL(modelReset()), this, SLOT(restoreTreeViewState()));
...
void iterateTreeView(const QModelIndex & index, const QAbstractItemModel * model,
const std::function<void(const QModelIndex&, int)> & fun,
int depth=0)
{
if (index.isValid())
fun(index, depth);
if (!model->hasChildren(index) || (index.flags() & Qt::ItemNeverHasChildren)) return;
auto rows = model->rowCount(index);
auto cols = model->columnCount(index);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
iterateTreeView(model->index(i, j, index), model, fun, depth+1);
}
void MainWindow::saveTreeViewState()
{
expandedTreeViewItems.clear();
iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
if (!view->isExpanded(index))
{
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(item && item->getTarget())
expandedTreeViewItems.append(item->getTarget());
}
});
}
void MainWindow::restoreTreeViewState()
{
iterateTreeView(view->rootIndex(), view->model(), [&](const QModelIndex& index, int depth){
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if(item && item->getTarget())
view->setExpanded(index, expandedTreeViewItems.contains(item->getTarget()));
});
}
I think this implementation gives extra flexibility compared to some of the others here. At least, I could not make it work with my custom model.
If you want to keep new items expanded, change the code to save the collapsed items instead.

Resources