In my application I'm having a table widget. I created a new class to implement delegate for the table cells alignment as center. The delegate works fine, but stylesheet is not working properly. Below is how I implement the delegate class:
//i create new class with "QStyledItemDelegate" as base class
//QAlignmentDelegate.h
#ifndef QALIGNMENTDELEGATE_H
#define QALIGNMENTDELEGATE_H
#include <QStyledItemDelegate>
class QAlignmentDelegate : public QStyledItemDelegate
{
public:
QAlignmentDelegate(Qt::Alignment alignment);
virtual void paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const;
private:
Qt::Alignment m_alignment;
};
#endif // QALIGNMENTDELEGATE_H
//QAlignmentDelegate.cpp
#include "qalignmentdelegate.h"
QAlignmentDelegate::QAlignmentDelegate(Qt::Alignment alignment)
{
m_alignment=alignment;
}
void QAlignmentDelegate::
paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItem alignedOption(option);
alignedOption.displayAlignment = m_alignment;
QStyledItemDelegate::paint(painter, alignedOption, index);
}
//main.cpp
QTableWidgetItem * protoitem = new QTableWidgetItem();
protoitem->setTextAlignment(Qt::AlignRight);
QTableWidgetItem * newitem = protoitem->clone();
tableWidget->setItemPrototype(newitem);
Now how to implement stylesheet in this delegate class?
Related
There are many good resources on dealing with models and views in the Qt doc, like: http://doc.qt.io/qt-5/model-view-programming.html, but I can't seem to find any that link to dealing with models in QtQuick. There are some basic chapters on extending qml with c++, like in http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html, and on using models: http://doc-snapshots.qt.io/qt5-5.11/qtquick-modelviewsdata-modelview.html, but I can't quite find a way to use an actual model in extended qml.
Currently I have this model:
class LayoutModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit LayoutModel(const QString &data, QObject *parent = 0);
~LayoutModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void setupModelData(const QStringList &lines, LayoutItem *parent);
LayoutItem *rootItem;
};
And I'm trying to access it from this view class:
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel & m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel & model() const;
void setModel(const LayoutModel & model);
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData & value) override;
void geometryChanged(const QRectF & newGeometry, const QRectF & oldGeometry) override;
signals:
void modelChanged();
};
But I can't find a way to actual use the model. I can't even properly set up read and write to the model, since QAbstractItemModels (and models in Qt in general) have their copy constructor deleted to enforce entity singularity. Here's my current broken implementation:
Layout::Layout(QQuickItem * parent) : QQuickItem(parent) {}
LayoutList & Layout::model() const
{
return m_model;
}
void Layout::setModel(const LayoutList & model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}
So, how can I make it so that I can use this extended qml class with my LayoutModel?
QObjects do not have a copy constructor, so you must use a pointer:
*.h
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel *model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel *m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel *model();
void setModel(LayoutModel * model);
...
signals:
void modelChanged();
};
*.cpp
...
LayoutModel *Layout::model()
{
return m_model;
}
void Layout::setModel(LayoutModel *model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}
I have a model with simple and complex data and I would like to have in-place widgets to edit simple data, but modal dialogs to edit complex data... How can I achieve that in a clean way?
(I would really prefer to do everything via a sub class of QItemDelegate, and no view specific hack)
I think you have to subclass the view and override the QAbstractItemView::edit() function to handle different editing paths. For example:
class MyView : public QTreeView
{
[..]
protected:
bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
if (index.row() > 5) { // Use your own criteria for simple/complex data
// Simple data with default editor.
return QTreeView::edit(index, trigger, event);
} else {
// Edit complex data.
QDialog dialog;
dialog.exec();
return false;
}
}
[..]
};
Try next delegate. I show main idea in the example:
Header:
#ifndef ITEMDELEGATE_H
#define ITEMDELEGATE_H
#include <QItemDelegate>
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = 0);
protected:
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;
void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
signals:
public slots:
};
#endif // ITEMDELEGATE_H
I show you only editorEvent because all another methods you can wrote by yourself, it will be custom delegate, but in editorEvent we create modal dialog.
bool ItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if(index.row()%2)//specific items, you can use it another options, it is just example
{
QInputDialog* dia = new QInputDialog;//create dialog, just example, it can be your QDialog subclass
dia->setInputMode(QInputDialog::TextInput);
//dia->setAttribute(Qt::WA_DeleteOnClose);
dia->setModal(true);
connect(dia, &QInputDialog::finished,[=]()//connection which will take data from dialog
{
model->setData(index,dia->textValue());//provide some method in your dialog to get user data and set it in model
delete dia;//we don't want memory leaks
});
dia->show();
}
return QItemDelegate::editorEvent(event,model,option,index);
}
I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots
This is an old question, but given that both answers are production-unsatisfactory (crash when returning false, nested event loops, etc)...
Please note I'm not faulting the posters for saying "it can't be done cleanly that way".
How about an entirely different, supported approach?
I have an implementation like this before (pseudo-code):
Disable edit triggers for "complex" items
.
auto item = model->item(row, column);
item->setFlags(item->flags() & ~Qt::ItemFlag::ItemIsEditable); // not editable
Connect to QAbstractItemView::activated
Ignore event for "simple" items
connect(m_ui.tableView,
&QAbstractItemView::activated,
this,
&QtGuiApplication2::onDataActivated);
//...
void QtGuiApplication2::onDataActivated(const QModelIndex &index) {
if(!is_complex)
return;
EditDialog dlg;
if(dlg.exec() == QDialog::DialogCode::Accepted) {
updatemodel();
}
}
Item Delegate:
Ignore for "complex" items.
QWidget* VariableEditorDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
if(is_complex)
return nullptr; // same as base class
return new EditorWidget(parent);
}
Now of course this isn't as nice as having just the delegate, but at least it doesn't skirt so closesly along crashes.
I have a QTableView which have set a QStandardItemModel. The user edits data in some index in the view and then the model emits the dataChanged() signal. In the SLOT where I am handling the SIGNAL I have the QModelIndex range of the user changes and thus I can get the new values the user have entered. How can I obtain the old values at that point?
After some research I figured out that there is no standard way to achive this behaviour. To solve the problem I had to inherit QStandardItemModel and reimplement setData() like this:
class RecallModel : public QStandardItemModel
{
public:
RecallModel (QObject * parent = 0) : QStandardItemModel(parent) {}
// Reimplemented
bool setData(const QModelIndex &index, const QVariant &value, int role= Qt::EditRole)
{
// backup the previous model data
if (role == Qt::EditRole || role == Qt::DisplayRole)
QStandardItemModel::setData(index, data(index), Qt::UserRole + 1);
return QStandardItemModel::setData(index, value, role);
}
};
And after that I can access the old data in the slot handling the dataChanged() signal:
void SomeObject::handleDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
...
const QVariant &vOldData = index.data(Qt::UserRole + 1); // here is the old data
const QVariant &vNewData = index.data(Qt::DisplayRole); // here is the new data
...
}
Since QStandardItemModel is a simple model, it does not have a signal with this feature. If you want such a feature you can subclass QAbstractItemModel and have your own custom class and implement setData and emit a custom signal which contains both the old and the new values.
As a workaround you can connect the itemChanged signal of QStandardItemModel to some slot :
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
And store the new value as a Qt::UserRole in model to use it as the old value when the slot is called next time :
void MyClass::onModelChanged(QStandardItem *item)
{
disconnect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
QVariant oldValue = item->data(Qt::UserRole);
item->setData(item->data(Qt::DisplayRole), Qt::UserRole); //Store the new value for next use
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
}
User can change data with delegate so possible solution is:
#ifndef ITEMDELEGATE_H
#define ITEMDELEGATE_H
#include <QItemDelegate>
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = 0);
protected:
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;
void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;
signals:
void dataChanged(QString oldValue,QString newValue) const;
public slots:
private:
mutable QString old;//we want change member data in const method
};
#endif // ITEMDELEGATE_H
As you can see many methods are const by default so I did some tricks (such as mutable) to avoid problems. Also in my edited answer I doesn't store old data in UserRole+1 , all done with old mutable variable.
cpp:
#include "itemdelegate.h"
#include <QLineEdit>
#include <QDebug>
ItemDelegate::ItemDelegate(QObject *parent) :
QItemDelegate(parent)
{
}
QWidget *ItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
void ItemDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
old = index.model()->data(index, Qt::EditRole).toString();//store old data
QLineEdit *line = qobject_cast<QLineEdit*>(editor);
line->setText(old);
}
void ItemDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index)const
{
QLineEdit *line = static_cast<QLineEdit*>(editor);
QString data = line->text();
emit dataChanged(old, line->text());
model->setData(index, data);
}
void ItemDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
Usage:
ItemDelegate * del = new ItemDelegate;
connect(del,&ItemDelegate::dataChanged,[=](QString oldValue,QString newValue) {
qDebug() << "old" << oldValue<< "new" <<newValue ;
});
ui->tableView->setItemDelegate(del);
I tested it and it works. It will work with different models. QTableView uses lineEdit as delegate by default, so user will not see any changes in view.
I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots, but of course you can use old syntax if you want.
All solutions before this answer rely on Qt-specific features. But you can move INSERT, UPDATE, DELETE (not SQL, but common) functionality outside Qt framework. Look at Unit Of Work Design Pattern, and related Domain Object pattern from "Patterns of Enterprise Application Architecture".
You can modify this patterns to hold old values and get it when you modify during setData().
I'm using a QComboxBox in a QTableView in Qt. When I add a new row then createEditor() of my delegate is called and I can instantiate the combobox with the correct set of choices available at that time. The problem is, that the user can load different files outside the table, and based on the content of the file the combobox would need to updated their items.
Is there a way that I can get the editor of a cell, so that I can update the choices accordingly? Since other cells of the table should not be destroyed, I can not simply recreate the table with the new data, I need only to update the comboboxes of certain cells.
I've been looking in the sourcecode of QAbstractItemView and there is a function editorForIndex() which would be exactly what I need, but this is implemented privately inside the view so it is not accessible even in a derived class.
Of course I can keep a record of the boxes that I create, so that I can update them accordingly later on, but I wonder if there is really no other way of doing this.
You can have the contents of your combobox as a class member of your delegate in a QStringList. Your item delegate can be like :
#include <QItemDelegate>
#include <QComboBox>
class ComboBoxDelegate: public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);
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;
void updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
QStringList comboItems;
mutable QComboBox *combo;
private slots:
void setData(int val);
};
ComboBoxDelegate::ComboBoxDelegate(QObject *parent ):QItemDelegate(parent)
{
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
combo = new QComboBox( parent );
QObject::connect(combo,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
combo->addItems(comboItems);
combo->setMaxVisibleItems(comboItems.count());
return combo;
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data( index, Qt::DisplayRole ).toString();
int comboIndex = comboItems.indexOf(QRegExp(text));
if(comboIndex>=0)
(static_cast<QComboBox*>( editor ))->setCurrentIndex(comboIndex);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
model->setData( index, static_cast<QComboBox*>( editor )->currentText() );
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry( option.rect );
}
void ComboBoxDelegate::setData(int val)
{
emit commitData(combo);
//emit closeEditor(combo);
}
When you want to update the items in combobox somewhere in your code just get a pointer to the item delegate by calling itemDelegateForColumn and access the comboItems member :
ComboBoxDelegate * itemDelegate = (ComboBoxDelegate *)ui->tableView->itemDelegateForColumn(columnIndex);
//Updating combobox items
itemDelegate->comboItems.append("newItem");
...
I am new to Qt. I have a table with a delegate combo box as the second column.
I wanted to detect click on the combo box.
I thought of one approach : add the combo box as a private variable in the ComboBoxDelegate and adding a public slot as
void on_cb_currentIndexChanged ( const QString & text );
However, since createEditor is const and I cannot re-assign the variable cb in this method.
Is there any alternative way this can be done?
class ComboBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(std::vector<std::string> values, QObject *parent = 0);
~ComboBoxDelegate();
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;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const;
private:
std::vector<std::string> values;
}
As you said, you can have the combobox as a member of delegate class and connect the currentIndexChanged signal of combobox to some slot when creating the combobox in createEditor. You should declare combobox as mutable for newing it in createEditor which is constant. If a data member is declared mutable, then it is legal to assign a value to this data member from a const member function :
#include <QItemDelegate>
#include <QComboBox>
class ComboBoxDelegate: public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);
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;
void updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
QStringList comboItems;
mutable QComboBox *combo;
private slots:
void setData(int val);
};
ComboBoxDelegate::ComboBoxDelegate(QObject *parent ):QItemDelegate(parent)
{
comboItems<<"Item 1"<<"Item 2"<<"Item 3";
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
combo = new QComboBox( parent );
QObject::connect(combo,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
combo->addItems(comboItems);
combo->setMaxVisibleItems(comboItems.count());
return combo;
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data( index, Qt::DisplayRole ).toString();
int comboIndex = comboItems.indexOf(QRegExp(text));
if(comboIndex>=0)
(static_cast<QComboBox*>( editor ))->setCurrentIndex(comboIndex);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
model->setData( index, static_cast<QComboBox*>( editor )->currentText() );
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry( option.rect );
}
void ComboBoxDelegate::setData(int val)
{
emit commitData(combo);
//emit closeEditor(combo);
}
Here i connect the currentIndexChanged signal of combobox to setData slot which commits the data to the model. You can also connect that signal to any slot you wish.