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.
Related
We are using a QTableView with a custom QAbstractTableModel. Some data is too long to display in the cells directly or we would like to show additional information.
In the model we use the following code:
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
return "Short Content";
}
if (role == Qt::ToolTipRole) {
return "Super long content which contains line\nbreaks, tabs\t and more.";
}
return QVariant();
}
This works and when hovering the cells the tooltip is displayed. However, this takes a few seconds for the tooltip to appear and sometimes some mouse-wiggling.
Is there a built-in way in Qt 5 to disable the timeout and always display the tooltip?
As they point out in the Qt forum the delay depends on the style and is returned through SH_ToolTip_WakeUpDelay which is 700 ms by default.
Considering the above, a possible solution is to use the QProxyStyle method override:
class ProxyStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
if (hint == QStyle::SH_ToolTip_WakeUpDelay)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
tableview->setStyle(new ProxyStyle(tableview->style()));
I have a QTreeView rendering QAbstractItemModel, where I would like to set the background of certain cells based on the data in the model. I return QBrush from model::data(Qt::BackgroundColorRole) and it works until I apply a style to an item.
Setting any style to the item (even something that has nothing to do with background color, e.g. styling the border) overrides the color I return from the model (the calls to the model querying the background color are made). I.e. the view behaves as if the model never returns any color.
I am using Qt 4.8 and I cannot upgrade to a later version.
Is there a way to make the color returned from the model take precedence over the style?
Why does the Qt behave in such a strange way - model has way more granularity and knows way more than a style can possibly know, why does the style take precedence - after all, the model doesn't have to return the color for every single cell - only a few specific ones?
I assume it is a bug in Qt, I have opened a bug report, which is reproducible on this code:
#include <QtCore/qabstractitemmodel.h>
#include <QtGui/qtreeview.h>
#include <QtGui/qtableview.h>
#include <QtGui/qapplication.h>
class MyModel : public QAbstractItemModel
{
public:
MyModel(QObject *parent) :QAbstractItemModel(parent){}
int rowCount(const QModelIndex &parent = QModelIndex()) const
{ return 2; }
int columnCount(const QModelIndex &parent = QModelIndex()) const
{ return parent.isValid() ? 0 : 2; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
if(index.row() >= 0 && index.column() >= 0)
{
switch(role)
{
case Qt::DisplayRole:
return QString("a");
case Qt::BackgroundRole:
return QBrush(QColor(255 * index.row(), 255 * index.column(), 0));
default:
break;
}
}
return QVariant();
}
virtual QModelIndex index(int pos, int column, const QModelIndex &parent = QModelIndex()) const
{ return createIndex(pos, column, 0); }
virtual QModelIndex parent(const QModelIndex &child) const
{ return QModelIndex(); }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView view;
MyModel myModel(0);
view.setModel(&myModel);
view.show();
//a.setStyleSheet("QTreeView::item { border: 1px solid black; }");
return a.exec();
}
If you uncomment the line before return, all the backgrounds are gone.
I found a workaround - it still is some extra code and IMHO it should have been handled by the Qt itself, but at least there is a way to overlay the background with the default rendering, i.e. I don't need to reimplement everything the default delegate (QStyledItemDelegate) does:
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant bg = index.data(Qt::BackgroundRole);
if(bg.isValid()) // workaround for Qt bug https://bugreports.qt.io/browse/QTBUG-46216
painter->fillRect(option.rect, bg.value<QBrush>());
QStyledItemDelegate::paint(painter, option, index);
}
If the background returned by the model is somewhat transparent (I used alpha-value of 50), it is nicely overlaid over the style's background, so that alternate-row coloring or similar stuff is still visible.
Interesting, though, I also applied the same trick to the header (in reimplemented QHeaderView::paintSection) and it didn't work - my background is visible as long as I don't call QHeaderView::paintSection - if I do, my painter->fillRect call is ignored. I'll post it as a separate question.
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.
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.
Is there any way to determine if a QTableView has an open editor in the current cell? I need to handle the following situation:
A user double-clicks a cell and edits the data, but leaves the cell in the "edit" state.
On another part of the UI, an action is taken that changes the selected row of the underlying model.
Back on my view, I want to determine if the newly selected row is the same as the open row. If not, I need to take an action. (Prompt the user? Commit automatically? Revert?)
I see how to get the current item, and can get the delegate on that item, but I don't see any isEditMode() property I was hoping to find.
Can someone point me in the right direction?
Just check whether the return value of
State QAbstractItemView::state () const
is
QTableView::EditingState
Connect to underlying model dataChanged signal
void QAbstractItemModel::dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight )
You can check if the cell where data has changed is the same than the currentIndex
QModelIndex QAbstractItemView::currentIndex () const
You cannot know if the current cell had an open editor straight, but can check if the view is in QAbstractItemView::EditingState
State QAbstractItemView::state () const
It should be enough to do what you want.
You can subclass QTableView in order to be able to access the state() function, which is unfortunately protected. However, I did not try that.
If you already have an QStyledItemDelegate subclass, you can use it to track whether an editor is currently open. However, you can't just use setEditorData/setModelData, because setModelData won't be called, when the user cancels editing. Instead, you can track the creation and destruction of the editor itself.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate( QObject* parent = nullptr );
~MyItemDelegate();
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void setEditorData( QWidget* editor, const QModelIndex& index ) const;
void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
bool isEditorOpen() const { return *m_editorCount > 0; }
protected:
int* m_editorCount;
protected slots:
void onEditorDestroyed( QObject* obj );
};
Implementation:
MyItemDelegate::MyItemDelegate( QObject* parent ) :
QStyledItemDelegate( parent )
{
m_editorCount = new int;
*m_editorCount = 0;
}
MyItemDelegate::~MyItemDelegate()
{
delete m_editorCount;
}
QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// create an editor, can be changed as needed
QWidget* editor = QStyledItemDelegate::createEditor( parent, option, index );
connect( editor, SIGNAL(destroyed(QObject*)), SLOT(onEditorDestroyed(QObject*)));
printf( "editor %p created\n", (void*) editor );
(*m_editorCount)++;
return editor;
}
void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
...
}
void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
...
}
void MyItemDelegate::onEditorDestroyed( QObject* obj )
{
printf( "editor %p destroyed\n", (void*) obj );
(*m_editorCount)--;
}
On some occasions, e.g. when moving to the next item in the tree using the cursor keys, Qt will create the new editor first and then destroy the old one. Hence, m_editorCount must be an integer instead of a bool.
Unfortunately, createEditor() is a const function. Therefore, you cannot create an int-member. Instead, create a pointer to an int and use that.
Subclass your delegate so that it includes an accessor that tells you when it's editing:
void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const {
// _isEditing will have to be mutable because this method is const
_isEditing = true;
QStyledItemDelegate::setEditorData(editor, index);
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const {
QStyledItemDelegate::setModelData(editor, model, index);
_isEditing = false;
}
bool MyDelegate::isEditing() const { return _isEditing; }
Then you can just check the delegate to see what's going on. Alternatively and/or if you don't like the mutable, you can emit signals so you know what state the delegate is in.
If you know the index of the item being edited, you can call indexWidget() and attempt to cast it. If it's valid, you not only know you're editing, but you also have your editor widget handy.
EditWidget *editWidget = qobject_cast<EditWidget*>(tableView->indexWidget(tableView->currentIndex()));
if(editWidget)
{
//yep, ur editing bro
}
Here is an idea, its even helpful to get the edit/combo widget before the edit begins...
just emit a signal and consume it in the mainwindow... this is what I used one to get combo box in QTableWidget before editing...
first create a signal in ComoBoxItemDelegate...
signals:
void OnComboEdit(QComboBox* pCombo) const;
then emit the signal in the createEditor method...
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Create the combobox and populate it
QComboBox* cb = new QComboBox(parent);
emit OnComboEdit(cb);
return cb;
}
and in the MainWindow declare a function to receive the singal...
void MainWindow::OnComboEidt(QComboBox *pCB) const
{
qDebug() << "Combo Eidt Singal Received";
}
Then finally in the constructor of MainWindow connect it...
ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(ui->tableWidget);
connect(cbid, &ComboBoxItemDelegate::OnComboEdit, this, &MainWindow::OnComboEidt);
ui->tableWidget->setItemDelegateForColumn(0, cbid);