QTableView - set the first column as "read only" - qt

I have a QTableView based on a QStandardItemModel.
I want to set the first column as "read only" and all the others columns editable.
I'm not an expert of QT and OOP, i searched around the web and in the QT-documentation and I've understand that I need to reimplement the flags(const QModelIndex &index) function of my model, but I don't know how and where do the re-implementation.
Thanks in advance!

You should create a new class inherited from QStandardItemModel, reimplement method flags and use your new class instead of the standard one.
class MyModel : public QStandardItemModel
{
public:
virtual Qt::ItemFlags flags(const QModelIndex& index) const override
{
Qt::ItemFlags result = QStandardItemModel::flags(index);
if (index.column() == 0) //0 is the first column!
{
result &= ~Qt::ItemIsEditable;
}
return result;
}
}
Another way to do the same:
- create a new class inherited from QStandardItem,
- reimplement flags in the same way
- call QStandardItemModel::setItemPrototype with an instance of the new class
This way is a little bit more complicated because you will need to reimplement method QStandardItem::clone as well.

Related

Sort a QAbstractListModel derived model by role in QML ListView

I've created a QAbstractListModel derived model based on an underlying QHash. Since I need to use the model in QML, I cannot make use of the sorting functionality Qt widgets and views have integrated.
I tried using a QSortFilterProxyModel but it doesn't seem to work with my model. Getting the model to properly work in QML wasn't tedious enough, and now I am stuck on sorting.
Any suggestions are appreciated.
Here is the model source:
typedef QHash<QString, uint> Data;
class NewModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
NewModel(QObject * parent = 0) : QAbstractListModel(parent) {}
enum Roles {WordRole = Qt::UserRole, CountRole};
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[WordRole] = "word";
roles[CountRole] = "count";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= m_data.size()) return QVariant();
Data::const_iterator iter = m_data.constBegin() + index.row();
switch (role) {
case WordRole:
return iter.key();
case CountRole:
return iter.value();
} return QVariant();
}
int rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent)
return m_data.size();
}
int count() const { return m_data.size(); }
public slots:
void append(const QString &word) {
bool alreadyThere = m_data.contains(word);
if (alreadyThere) m_data[word]++;
else m_data.insert(word, 1);
Data::const_iterator iter = m_data.find(word);
uint position = delta(iter);
if (alreadyThere) {
QModelIndex index = createIndex(position, 0);
emit dataChanged(index, index);
} else {
beginInsertRows(QModelIndex(), position, position);
endInsertRows();
emit countChanged();
}
}
void prepend(const QString &word) {
if (m_data.contains(word)) m_data[word]++;
else m_data.insert(word, 1);
}
signals:
void countChanged();
private:
uint delta(Data::const_iterator i) {
uint d = 0;
while (i != m_data.constBegin()) { ++d; --i; }
return d;
}
Data m_data;
};
Here is "trying" to sort it:
NewModel model;
QAbstractItemModel * pm = qobject_cast<QAbstractItemModel *>(&model);
QSortFilterProxyModel proxy;
proxy.setSourceModel(pm);
proxy.setSortRole(NewModel::WordRole);
proxy.setDynamicSortFilter(true);
Alas, the proxy works as a model, but it doesn't sort the entries.
If you enable QSortFilterProxyModel::setDynamicSortFilter(true), you need to call QSortFilterProxyModel::sort(...) function once to let the proxy know which way to sort.
With that, any time the model is updated the proxy will sort everything again just automatically.
proxy.setDynamicSortFilter(true);
proxy.sort(0);
First of all, There's no need for qobject_cast<QAbstractItemModel *> downcasting -- the NewModel is a derived class of the QAbstractItemModel and the polymorphism principle says that you can use a subclass everywhere where a parent class is applicable.
Second, your prepend method does not use beginInsertRows and endInsertRows. That's a violation of the MVC API. You'll get data corruption in the attached views and proxy models if you use it this way.
Third, you haven't mentioned whether you're actually using your proxy model as the model for the attached view :).
Finally, you are using QHash as a backing store of your data with QHash::iterator for insertion. That's an itneresting solution, but something which just cannot work -- an insertion or removal can cause the hash table to grow/shrink, which means changing all data you publish via your model indexes. This is just not going to work. Don't use QHash when you need a stable order. The O(n) complexity of your delta method should be interpreted as a warning; this is a wrong approach.
Have a Look at https://github.com/oKcerG/SortFilterProxyModel. This project exposes the functionality of QSortFilterProxyModel nicely to QML. I used it in different projects and it junst worked. However if you want to implement your own solution it's something to get your ideas.

QT - adding own column to QFileSystemModel

Can I extend QFileSystemModel and add new column with text / icon?
Regards
I would start by subclassing the model, providing the additional column and supplying the data to it.
So at the very least I would reimplement columnCount() and data() in both cases calling the base class and manipulating the results accordingly.
class yourSystemModel : public QFileSystemModel
{
Q_OBJECT
int columnCount(const QModelIndex& parent = QModelIndex()) const
{
return QFileSystemModel::columnCount()+1;
}
QVariant data(const QModelIndex& index,int role) const
{
if(!index.isValid()){return QFileSystemModel::data(index,role);}
if(index.column()==columnCount()-1)
{
switch(role)
{
case(Qt::DisplayRole):
{return QString("YourText");}
case(Qt::TextAlignmentRole):
{return Qt::AlignHCenter}
default:{}
}
}
return QFileSystemModel::data(index,role);
}
}
The official doc outline some basis as to minimal reimplementation for the abstract item model, but in this case you can run away with much less.
http://doc.qt.digia.com/stable/qabstractitemmodel.html - Subclassing section.

In Qt how to sort the immediate child indexes of a QModelIndex

I'm writing a C++ application that uses Qt classes to work with certain data models. For that purpose I inherited from QAbstractItemModel:
// the following is a class that represents the actual data used in my application
class EventFragment
{
....
private:
qint32 address;
QString memo;
QDateTime dateCreated;
QVector<EventFragment*> _children;
....
};
// the following is the model representation that used by my application to display the actual details to the user
class EventModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit EventModel (QObject *parent = 0);
....
private:
// the following is the root item within the model - I use a tree-like presentation to show my data
EventFragment* _rootFragment;
};
At some point I needed a sort/filter option in my application so I also created a class that inherits from QSortFilterProxyModel
class EventProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit EventProxyModel (QObject *parent = 0);
...
public:
// I had to add my custom implementation in the 'lessThan' method to achieve a
// more complex sort logic (not just comparing the actual values but using
// additional conditions to compare the two indexes)
virtual bool lessThan ( const QModelIndex & left, const QModelIndex & right ) const;
...
};
To achieve sorting, I used the default QSortFilterProxyModel::sort() method (I haven't reimplemented it in my proxy model class) and for a time it seemed to work.
At some point though, I noticed that the actual QSortFilterProxyModel::sort() method sorts the entire model and what I need is to sort only the immediate children of a certain index.
I tried to reimplement the sort() method of the EventModel class, but after a while I realized that QSortFilterProxyModel::sort() is not referring to it at all. On the other hand, I'm not sure how to rearrange the indexes in a safe way so that the view which displays the model does not crash.
I think there must be a way to sort only the immediate children of a certain QModelIndex, but I haven't found it yet.
Is there any tutorial/example that demonstrates a possible solution to my case, or some guidelines on how to do it?
Regards
If you want an optimized solution that doesn't do comparisons at all for the indexes you don't want to sort, I think you'd have to reimeplement your own QAbstractProxyModel, which is a non-trivial task. However, if you're fine with a non-optimized solution, I'd try this:
bool EventProxyModel::lessThan( const QModelIndex & left, const QModelIndex & right ) const {
if ( left.parent() == isTheOneToSortChildrenFor ) {
...apply custom comparison
} else {
return left.row() < right.row();
}
}
Comparing the rows in the source should leave everything other then indexes with that specific parent as they are.

Qt: Signal while a QTableView item data is being edited instead of after edit is done?

I have a QTableView which has some QString based items in its model. I implemented setData in my table model, so editing is working (I can change the data in the cell, setData is called to update the model, and the table is properly updated).
Currently setData is only called when the user is done with editing, e.g. after they hit Enter or click out of the text entry box to finalize the text entry. I want to update other other parts of the table while the user is typing/editing into the text edit control instead of after they're done and the edited contents are finalized.
A simple example of what I want to have is for the next table cell to display a count of how many characters have been entered into the cell being edited, but to do this as the user is typing/editing the cell contents, not just after the edit is finalized and setData is called.
Any pointers to what I should be looking for? Thanks!
You can subclass QStyledItemDelegate and commit the data whenever something changes, and then set that delegate for the view with QAbstractItemView::setItemDelegate.
class MyDelegate : public QStyledItemDelegate {
QSignalMapper *mapper;
public:
MyDelegate(QObject*parent = 0)
: QStyledItemDelegate(parent)
, mapper(new QSignalMapper(this))
{
connect(mapper, SIGNAL(mapped(QWidget*)), SIGNAL(commitData(QWidget*)));
}
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
if(qobject_cast<QLineEdit*>(editor)) {
connect(editor, SIGNAL(textChanged(QString)), mapper, SLOT(map()));
mapper->setMapping(editor, editor);
}
return editor;
}
};
The answer offered by #alexisdm did not work for me when I needed a persistent
editor enabled by QAbstractTableModel::setPersistentEditor(QModelIndex()).
The following solves this problem:
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
// ... omitted for brevity
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
auto *editor = static_cast<QLineEdit*>(
QStyledItemDelegate::createEditor(parent, option, index));
if (editor) {
connect(editor,
&QLineEdit::textChanged,
[=] (const QString &)
{
const_cast<Delegate*>(this)->commitData(editor);
});
}
return editor;
}
// ... omitted for brevity
};
We simply cast the constness from this and make it commit data for the editor.
Note that in the lambda we capture the editor variable by value [=] because otherwise, capturing with reference would make the value of editor undefined when the function runs out of scope.

cannot select QAbstractItemView item, when it's disabled

When I set flags of QAbstractItemModel selectable but not enabled, I can't select items by mouse click. However internally select() function selects objects.
Is this qt bug, or I do something wrong?
From what I understood, you want to "Disable" the item, but at the same time, be able to select it. it's fairly easy to fake that on the model.
if ( role == Qt::BackgroundRole ){
return QVariant(QApplication::palette()->color(QPalette::Inactive, QPalette::Window );
}
This will paint your item as grayed out, and you will still be able to select it.
You're doing something wrong. If you disable a widget it is greyed out and it doesn't receive user mouse clicks and keyboard input.
I just had similar problem (I need to copy disabled items). Here is solution that sets correct style for disabled items (without ignoring any style sheets).
Create custom item delegate for your model.
/// Returns false only if item needs to be rendered as disabled.
bool isIndexEnabled(const QModelIndex &index)
{
// Implement this function.
}
class ItemDelegate : public QStyledItemDelegate {
public:
explicit ItemDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent) {}
protected:
void initStyleOption(
QStyleOptionItemView *option, const QModelIndex &index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
if (!isIndexEnabled(index))
option->state &= ~QStyle::State_Enabled;
}
};
Set the new item delegate to your model.
auto itemDelegate = new ItemDelegate(model)
model->setItemDelegate(itemDelegate);

Resources