QComboBox with NULL support - qt

I'm using QComboBox to select unit (an arbitrary property of the object) from Units table. The problem is that object in my data model can have no unit (NULL in the table), in which case QComboBox shows value happened on top of the list. Select 'none' is not possible.
What do you suggest to add NULL support? I have few versions:
Insert special record in Units table named '--' or 'N/A'. Not exactly NULL - will have its own id.
Set items in QComboBox and update model manually. Possible but tedious - goodbye automatic update of unit list.
What else is possible - subclassing QComboBox (overriding what)? I don't see anything similar to setEditorData/setModelData like in QAbstractItemDelegate to control items.

You can subclass the model, so that data will return a special value for NULL and then setData will check for the special value and substitute a NULL.
Example sketch of code:
class MyModel : public QSqlTableModel
{
Q_OBJECT
public:
MyModel();
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
};
QVariant MyModel::data(const QModelIndex& idx, int role)
{
QVariant var = QSqlTableModel::data(idx, role);
if (var.isNull())
var = QVariant(QString("NULL"));
return var;
}
bool MyModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
QVariant var(value);
if (var == QString("NULL"))
var == QVariant(record().field(idx.column()).type());
return QSqlTableModel::setData(idx, var, role);
}

Related

QListView internal move without serialization

I'm trying to support rearranging a list of large objects in a QListView. It seems that the model/view framework insists on serializing the items which, in this case, is highly inefficient. I've tried to shorten things by using shared_ptr's, so only one bit of data needs passing, but even so, it feels very messy and is not at all reliable.
Worse still, during the move I notice the serialization functions are called repeatedly (dozens/hundreds of times). Surely they should only be called once each.
I'm sure I'm missing a trick, what's the correct way to achieve simple, internal move, list ordering without heavy serialize overhead?
class Big
{
public:
Big();
~Big();
...
};
using BigPtr = std::shared_ptr<Big>;
Q_DECLARE_METATYPE(BigPtr) // and qRegisterMetaTypeStreamOperators
// Required serialization operators
// This is very ugly, and what I want rid of...
QDataStream &operator<<(QDataStream &out, const BigPtr &big)
{
qDebug() << "Out <<";
out << (quint64)big.get();
return out;
}
// ...This too...
QDataStream &operator>>(QDataStream &in, BigPtr &big)
{
qDebug() << "In >>";
quint64 raw = 0;
in >> raw;
li.reset((LayerPtr::element_type *)raw);
return in;
}
class BigModel : public QAbstractListModel
{
Q_OBJECT
public:
BigModel (std::vector<BigPtr> &bigs, QObject *parent = Q_NULLPTR);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const; // Qt::ItemIsDragEnabled or Qt::ItemIsDropEnabled depending on valid index
Qt::DropActions supportedDropActions() const; // return Qt::MoveAction
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
private:
std::vector<BigPtr> &_bigs;
};
...
// dialog setup...
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->listView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked);
ui->listView->setDragEnabled(true);
ui->listView->setDragDropMode(QAbstractItemView::InternalMove);
ui->listView->setDropIndicatorShown(true);
ui->listView->viewport()->setAcceptDrops(true);
EDIT:
After more head scratching, I decided to overload the mimeData and dropMimeData functions and sub-class QMimeData to carry the BigPtr directly.
class BigMimeData : public QMimeData
{
Q_OBJECT
public:
BigMimeData(const QVariant &_big)
: big(_big)
{ }
bool hasFormat(const QString &/*mimeType*/) const { return true; }
QVariant big;
};
...
QMimeData *BigModel::mimeData(const QModelIndexList &indexes) const
{
if(indexes.count() > 0) {
return new BigMimeData(indexes.at(0).data());
}
return Q_NULLPTR;
}
bool BigModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int /*column*/, const QModelIndex &parent)
{
if(!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) {
return false;
}
// start of list
if(row == -1) {
row = rowCount(parent);
}
auto dragged = static_cast<const BigMimeData *>(data);
// setData with stored value
insertRow(row, parent);
setData(index(row, 0, parent), dragged->big);
return true;
}
Which works, but seriously!? All that just to enable simple list reordering in the UI? It can't be this complex surely?
BTW, a lot of these answer seem to be focusing on the underlying data structure, that's not the question. The problem is that simple internal move drag/drop rearrangement in a list control seem to be ridiculously complex in QListView because it requires costly serialization. It's a UI/Widgets/MVC question.

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.

Why is MyModel::data() not being called (subclassing QSqlQueryModel)

Hi I can't figure out why my data() function is never called when populating QTableView
I subclassed QSqlQueryModel. The header is like:
class TicketModel : public QSqlQueryModel
{
Q_OBJECT
public:
explicit TicketModel(QObject *parent = 0);
QVariant data(const QModelIndex &index, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
};
In the main window I set my model to the table
TicketModel *model = new TicketModel();
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
QSqlQuery *query = _tf->search(1);
model->setQuery(*query);
_ui->dashTable->setModel(proxyModel); // <<<<<<<<<<<<<< I setting model here too, didn't work
_ui->dashTable->setSortingEnabled(true);
_ui->dashTable->horizontalHeader()->setVisible(true);
_ui->dashTable->setSelectionBehavior(QAbstractItemView::SelectRows);
The TicketModel::headerData(...) is called but TicketModel::data(...) is never called when the table is created. Why? How can I get it to be called?
I hope I just overlooked something simple but I have been trying for a few hours to figure it out.
Thanks for the help!
You've got the signature wrong. You need a const.
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const

Set color to a QTableView row

void MyWindow::initializeModelBySQL(QSqlQueryModel *model,QTableView *table,QString sql){
model = new QSqlQueryModel(this);
model->setQuery(sql);
}
With this method i can set a QSQlQueryModels to my QTableviews.
But How i can set color to a row based on a cell value?
The view draws the background based on the Qt::BackgroundRole role of the cell which is the QBrush value returned by QAbstractItemModel::data(index, role) for that role.
You can subclass the QSqlQueryModel to redefine data() to return your calculated color, or if you have Qt > 4.8, you can use a QIdentityProxyModel:
class MyModel : public QIdentityProxyModel
{
QColor calculateColorForRow(int row) const {
...
}
QVariant data(const QModelIndex &index, int role)
{
if (role == Qt::BackgroundRole) {
int row = index.row();
QColor color = calculateColorForRow(row);
return QBrush(color);
}
return QIdentityProxyModel::data(index, role);
}
};
And use that model in the view, with the sql model set as source with QIdentityProxyModel::setSourceModel.
OR
You can keep the model unchanged and modify the background with a delegate set on the view with QAbstractItemView::setItemDelegate:
class BackgroundColorDelegate : public QStyledItemDelegate {
public:
BackgroundColorDelegate(QObject *parent = 0)
: QStyledItemDelegate(parent)
{
}
QColor calculateColorForRow(int row) const;
void initStyleOption(QStyleOptionViewItem *option,
const QModelIndex &index) const
{
QStyledItemDelegate::initStyleOption(option, index);
QStyleOptionViewItemV4 *optionV4 =
qstyleoption_cast<QStyleOptionViewItemV4*>(option);
optionV4->backgroundBrush = QBrush(calculateColorForRow(index.row()));
}
};
As the last method is not always obvious to translate from C++ code, here is the equivalent in python:
def initStyleOption(self, option, index):
super(BackgroundColorDelegate,self).initStyleOption(option, index)
option.backgroundBrush = calculateColorForRow(index.row())
Your best bet is to define a custom model (QAbstractTableModel subclass). You probably want to have a QSqlQueryModel as a member in this custom class.
If it's a read-only model, you need to implement at least these methods:
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
and for well behaved models also
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
If you need the model to be able to edit/submit data, things get a bit more involved and you will also need to implement these methods:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole);
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex());
What will actually change a row appearance lies in the return value of this method:
QVariant data(const QModelIndex &index, int role) const;
A dumb example:
QVariant MyCustomModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid() )
return QVariant();
int row = index.row();
int col = index.column();
switch ( role )
{
case Qt::BackgroundRole:
{
if(somecondition){
// background for this row,col is blue
return QVariant(QBrush (QColor(Qt::blue)));
}
// otherwise background is white
return QVariant(QBrush (QColor(Qt::white)));
}
case Qt::DisplayRole:
{
// return actual content for row,col here, ie. text, numbers
}
case Qt::TextAlignmentRole:
{
if (1==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignLeft );
if (2==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignTrailing );
return QVariant ( Qt::AlignVCenter | Qt::AlignHCenter );
}
}
}

How to associate QModelIndex with a new row?

I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data. I add the data like so:
void PointListModel::addPoint(int frameNumber, QPoint const& pos)
{
PointItem *pointItem = new PointItem( frameNumber, pos );
QModelIndex newRow = this->createIndex( m_points.count(), 0, pointItem );
qDebug() << newRow.internalPointer();
beginInsertRows( newRow, m_points.count(), m_points.count() );
m_points.insert( m_points.count( ), pointItem );
endInsertRows();
emit pointAdded( pointItem, pos );
}
It was only later that I realized that the argument to beginInsertRows is asking for the parent model index of the new row, not the new row's actual model index.
So, at this point in time, Qt has given me no way of supplying a QModelIndex to associate with this particular row. How do I create my own model index for this new row?
Okay, I'm rewriting my answer as after some research I've found out that I got it wrong.
You shouldn't do anything special to create a new index when you add new data. You code should look like this:
PointItem *pointItem = new PointItem( frameNumber, pos );
// assume you insert a top level row, having no parent
beginInsertRows( QModelIndex(), m_points.count(), m_points.count() );
m_points.insert( m_points.count( ), pointItem );
endInsertRows();
Then you should implement the index() method which will create indexes on demand and the parent() method which will determine the parent of some index, but since you have a list model, it should probably always just return QModelIndex(). Here is a good article about creating custom models.
Here is a complete example of a working QAbstractListModel:
class MyModel: public QAbstractListModel {
Q_OBJECT
public:
virtual QModelIndex index(int row, int column = 0,
const QModelIndex &parent = QModelIndex()) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
void add(int i);
private:
QList<int> list;
};
void MyModel::add(int i)
{
beginInsertRows(QModelIndex(), list.size(), list.size());
list.append(i);
endInsertRows();
}
QModelIndex MyModel::index(int row, int column,
const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column, (void*)&list[row])
: QModelIndex();
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return list.size();
}
QVariant MyModel::data(const QModelIndex &index,
int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return QVariant(QString::number(*static_cast<int*>(index.internalPointer())));
}
I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data.
If you start with wrong requirements, you end up with wrong solutions :)
A list model is simple enough so that you don't need more than the QModelIndex's row() to uniquely define the data the index addresses.
So, given a QModelIndex mi, when you before did
PointItem * item = static_cast<PointItem*>(mi.internalPointer());
you can instead do
PointItem * item = plm->pointItemFromIndex(mi);
where plm is your PointListModel. If you don't have a pointer to it lying around when you need to access the PointItem, you can reconstruct it like this:
PointItemModel * plm = qobject_cast<PointItemModel*>(mi.model());
// check for !plm here (!mi.isValid() || qobject_cast fails)
In turn, PointListMode::pointItemFromIndex() would do the actual work:
PointItem * PointListMode::pointItemFromindex(const QModelIndex &mi) const {
return mi.isValid() ? m_points[mi.row()] : 0 ;
}
This is the most important thing to realize when working with QAbstractListModel in Qt: Mentally replace QModelIndex with int row, ignore everything else it has (an invalid QModelIndex has row() == -1).
Same for QAbstractTableModel: mentally reduce the QModelIndex to int row, int column. Forget everything else.
The only time you need the full QModelIndex (including its internalPointer() or internalId() is when you implement a tree model (QAbstractItemModel).

Resources