Using QT Tree Model with Custom data - qt

This is my Header file for Container object.
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
I use Container as the datatype in TreeItem.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
};
The problem is that when i add rows to the Tree Structure the rows gets added but with no text on them.
I want to set the data in stdstrContainerName as the data on the row.
if i try to write something it does not stay.
i think the problem is with my functions which set the data to TreeItem object
Container TreeItem::data() const
{
return itemData;
}
bool TreeItem::setData(const Container &data , QVariant value )
{
itemData = data;
itemData.SetName(value.toString().toStdString() );
return true;
}
/
Data Function for Tree Model
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());
}
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 = item->setData(c);
if (result)
emit dataChanged(index, index, { role });
return result;
}
I can also post any additional code of the project.

What's missing here is the code of your view.
A QAbstractItemView derived view cannot display Container QVariants by itself.
Either you implement your view in such a way that it
interprets Container-QVariants and displays e.g. its GetName()
provides an editor that can manipulate Container-QVariants
or (simpler method) you shift the concretion layer into your model and
return item->GetName(), ->GetType() depending on role and index.column()
manipulate the Container instance in the TreeItem from the text passed in QVariant val

Related

Mime type for custom data in tree view

The items in the tree view hold a instance of class container.
I want to implement drag and drop functionality in the view.
According to the QT tutorial for the data to copy i need specify the mime type and than write the Mimedata and dropMimeData functions.
The QT Example is dealing with a simple string so i am totally clueless of how to implement these function in case of custom objects.
1) What should be the mime type in my case ?
2) How to implement the current mimedata function for Container object data?
3) How to implement the current dropmimedata function for Container object data?
/////////////////////////////////////////
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
////////////////////////////////////////////////////////////
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes)
const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
foreach (const QModelIndex &index, indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
// I have a GetContainer function which returns the Container
//object and i can use the GetContainer instead of data function.
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
//////////////////////////////////////////////////////////////////
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex
&parent)
{
if (action == Qt::IgnoreAction)
return true;
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
int beginRow;
if (row != -1)
beginRow = row;
else if (parent.isValid())
beginRow = parent.row();
else
beginRow = rowCount(QModelIndex());
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
insertRows(beginRow, rows, QModelIndex());
foreach (const QString &text, newItems) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}
The header file for TreeItem.
class TreeItem
{
public:
explicit TreeItem( const 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;
bool setData(const Container &data , QVariant value);
void setContainer(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
}
You can add your custom mime types to specify the type of container you want to drag/drop. See this post for details.
The QDrag object constructed by the source contains a list of MIME types that it uses to represent the data (ordered from most appropriate to least appropriate), and the drop target uses one of these to access the data.
First of all, try to find a compatible standard mime type. Those are the most common one assigned by the IANA.
If the one you are looking for is not in the list, then you can label your custom one and serialize your data into a QByteArray to share it.
QByteArray output;
// do whatever
mimeData->setData("my-awesome-mime-type", output);
Now, in your custom widget, don't forget to accept the drops of this mime type:
void Window::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasFormat("my-awesome-mime-type"))
event->acceptProposedAction();
}
You can find a complete example in this project.

Using Custom data type in QT TreeModel

This is my Container class defination
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
std::string stdstrPluginName;
int iSegments;
float fRadius;
public:
Container();
explicit Container(std::string strContainerName , std::string
strPluginName , std::string strPluginType, int segments , float
radius );
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
I want the nodes of the TreeView to hold object of Container class as data.
This is the header file for the TreeItem class.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
};
The problem i am facing is while implementing the TreeModel functions.
how can i use Container as the data type instead of QVariant.
QVariant data(const QModelIndex &undex, 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;
You can't use Container instead of QVariant, but you can use Container inside a QVariant. Have a look at Q_DECLARE_METATYPE.
Add it in the header file after the declaration of Container:
class Container
{
private:
// ...
public:
Container();
// ...
};
Q_DECLARE_METATYPE(Container); // You only need this once, so here is a good place
When you created the Container meta type, you can return it like this:
QVariant data(const QModelIndex &index, int role) const override {
TreeItem *item = ...;
return QVariant::fromValue(item->data());
}
And in your view you retrieve the Container like this:
QVariant v = model()->data(index, role);
Container c = v.value<Container>();
Beware that this only works within the same thread, else you'd need to register Container with qRegisterMetaType() and maybe even write a converter function from / to QVariant.
This doesn't apply to your situation, though, because model and view are not designed to live in different threads.

Extending QFileSystemModel by a bool variable

I would like to extend the QFileSystemModel. I would like to have a custom role IsSelectedRole where I can store whether a bool value which stores whether a file got selected in a QML TreeView. More precisely, I don't know how to set the setData and data function of my QFileSystemModel derived class, like where to store the data. I guess making my own tree of bool variables should work, but I hope that there is an easier way.
Qt already has a model that stores item selections: QItemSelectionModel. This model is used by the widget views as well as the QML views. All that's left for you to do is to overlay the data from that model on top of the data from QFileSystemModel, in a viewmodel.
You definitely shouldn't be deriving from QFileSystemModel. The viewmodel maintains the state for a single view only, and should be a proxy that overlays your role on top of the underlying model.
The proxy doesn't have to assume anything about the underlying model, and can work on any model, not just the QFileSystemModel.
For example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/filesystem-model-select-50132273
#include <QtWidgets>
#include <algorithm>
class SelectionProxy : public QIdentityProxyModel {
Q_OBJECT
Q_PROPERTY(QItemSelectionModel* selectionModel
READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
Q_PROPERTY(QVector<int> roles READ roles WRITE setRoles)
using self_t = SelectionProxy;
using base_t = QIdentityProxyModel;
using model_t = QItemSelectionModel;
QPointer<QItemSelectionModel> m_model;
QVector<QMetaObject::Connection> m_modelConnections;
QVector<int> m_roles{IsSelectedRole};
QModelIndex topLeft() const {
return sourceModel()->index(0, 0);
}
QModelIndex bottomRight() const {
return sourceModel()->index(sourceModel()->rowCount()-1, sourceModel()->columnCount()-1);
}
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
auto sel = selected;
sel.merge(deselected, QItemSelectionModel::Select);
for (auto &range : qAsConst(sel)) {
auto topLeft = mapFromSource(range.topLeft());
auto bottomRight = mapFromSource(range.bottomRight());
emit dataChanged(topLeft, bottomRight, m_roles);
}
}
void onModelChanged(QAbstractItemModel *model) {
setSourceModel(model);
}
bool check(const QModelIndex &index, int role) const {
return index.isValid() && m_model && m_roles.contains(role);
}
public:
static constexpr int IsSelectedRole = Qt::UserRole + 44;
SelectionProxy(QObject *parent = {}) : QIdentityProxyModel(parent) {}
QItemSelectionModel *selectionModel() const { return m_model; }
virtual void setSelectionModel(QItemSelectionModel *model) {
if (model == m_model) return;
for (auto &conn : m_modelConnections)
disconnect(conn);
m_model = model;
m_modelConnections = {
connect(m_model, &model_t::selectionChanged, this, &self_t::onSelectionChanged),
connect(m_model, &model_t::modelChanged, this, &self_t::onModelChanged) };
setSourceModel(model->model());
emit selectionModelChanged(m_model);
}
Q_SIGNAL void selectionModelChanged(QItemSelectionModel *);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!check(index, role))
return base_t::data(index, role);
return m_model->isSelected(mapToSource(index));
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!check(index, role))
return base_t::setData(index, value, role);
using Sel = QItemSelectionModel;
m_model->select(mapToSource(index), value.toBool() ? Sel::SelectCurrent : (Sel::Deselect | Sel::Current));
return true;
}
QVector<int> roles() const { return m_roles; }
void setRoles(QVector<int> roles) {
std::sort(roles.begin(), roles.end());
if (roles == m_roles)
return;
std::swap(roles, m_roles);
if (!m_model)
return;
QVector<int> allRoles;
std::merge(roles.begin(), roles.end(), m_roles.begin(), m_roles.end(), std::back_inserter(allRoles));
emit dataChanged(topLeft(), bottomRight(), allRoles);
}
void setRole(int role) {
setRoles({role});
}
};
#include "main.moc"
A simple demonstrator sets up two views of the filesystem model; the bottom view shows true/false indicating the selection status of the first view.
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
QTreeView left, right;
layout.addWidget(&left);
layout.addWidget(&right);
QFileSystemModel model;
SelectionProxy selProxy;
model.setRootPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
auto rootPathIndex = model.index(model.rootPath());
left.setModel(&model);
left.setSelectionMode(QAbstractItemView::MultiSelection);
left.setRootIndex(rootPathIndex);
selProxy.setRole(Qt::DisplayRole);
selProxy.setSelectionModel(left.selectionModel());
right.setModel(&selProxy);
right.setRootIndex(selProxy.mapFromSource(rootPathIndex));
for (int col : {1,2,3})
right.hideColumn(col);
win.show();
return app.exec();
}

Qt QSortfilterproxy model virtual column

A related question has been asked by ymoreau here - but there is no decisive solution. I have subclassed QSortFilterProxyModel with the purpose to display some data in the virtual column on addition to QSqlRelationalTableModel (sourceModel) whose data is from MYSQL database.
Below is my code:
class vModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit vModel(QObject *parent): QSortFilterProxyModel(parent)
{
}
virtual QModelIndex index(int row, int column) const
{
if(column >= columnCount()-1){
return createIndex(row,column);
}
else
return QSortFilterProxyModel::index(row, column);
}
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const
{
Q_UNUSED(parent);
return sourceModel() ? (sourceModel()->columnCount() + 1) : 0;
}
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
{
if (index.isValid() && role == Qt::EditRole)
{
QSortFilterProxyModel::setData(index,value,role);
emit dataChanged(index,index);
return true;
}
else
return false;
}
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
{
int amtCol = columnCount()-1;
if (orientation == Qt::Horizontal
&& section == amtCol
&& role == Qt::DisplayRole){
return QString("Amount");
}
else
return QSortFilterProxyModel::headerData(section, orientation, role);
}
virtual QModelIndex parent(const QModelIndex &index) const {
Q_UNUSED(index);
return QModelIndex();
}
virtual QVariant data(const QModelIndex &index, int role) const
{
int amtCol = columnCount()-1;
if(index.column() == amtCol ){
if (role != Qt::DisplayRole)
return QSortFilterProxyModel::data(index, role);
else{
QString val = QString("Amount Index(%1,%2)").arg(index.row()).arg(index.column());
return val;
}
}
return QSortFilterProxyModel::data(index, role);
}
virtual QModelIndex mapFromSource(const QModelIndex &source) const
{
return index(source.row(), source.column());
}
virtual QModelIndex mapToSource(const QModelIndex &proxy) const
{
return (sourceModel()&&proxy.isValid())
? sourceModel()->index(proxy.row(), proxy.column(), proxy.parent())
: QModelIndex();
}
protected:
Qt::ItemFlags flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QSortFilterProxyModel::flags(index);
if (index.isValid())
{
flags |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
return flags;
}
};
The view draws the extra column with correct header as I specified, but the content is empty even alternating row background is not painted. The model returns the correct data when I query the extra column indexes.The delegates returns false when I check the validity of any index in the virtual column yet the my subclassed model returns true.
Where's my problem...Is it the model or the delegates? Is there any additional function that I need to include?
Subclassing the Sourcemodel yielded my expectations so easily. I believe QIdentityProxyModel or any other proxy can do the same task but you have to man-handle so many issues. My Sourcemodel (the QSqlRelationalTableModel) responds to data changes (calculated values in more than 4 virtual columns) and communicates with the default relational-delegate so easily.
If there's away to make proxymodels do this task, I still welcome those suggestions. Am using Qt5.9

How to access Items stored in a QAbstractListmodel in QML(by delegates) otherwise than using item roles?

I just want to display elements from list using QML, but not using item roles.
For ex. I want to call a method getName() that returns the name of the item to be displayed.
Is it possible? I didn't find nothing clear reffering to this.
You can use one special role to return the whole item as you can see below:
template<typename T>
class List : public QAbstractListModel
{
public:
explicit List(const QString &itemRoleName, QObject *parent = 0)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole] = QByteArray(itemRoleName.toAscii());
setRoleNames(roles);
}
void insert(int where, T *item) {
Q_ASSERT(item);
if (!item) return;
// This is very important to prevent items to be garbage collected in JS!!!
QDeclarativeEngine::setObjectOwnership(item, QDeclarativeEngine::CppOwnership);
item->setParent(this);
beginInsertRows(QModelIndex(), where, where);
items_.insert(where, item);
endInsertRows();
}
public: // QAbstractItemModel
int rowCount(const QModelIndex &parent = QModelIndex()) const {
Q_UNUSED(parent);
return items_.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= items_.count()) {
return QVariant();
}
if (Qt::UserRole == role) {
QObject *item = items_[index.row()];
return QVariant::fromValue(item);
}
return QVariant();
}
protected:
QList<T*> items_;
};
Do not forget to use QDeclarativeEngine::setObjectOwnership in all your insert methods. Otherwise all your objects returned from data method will be garbage collected on QML side.

Resources