I am trying to write custom delegate and custom model as a part of learning Qt.
I made a simple custom model based on QAbstractTableModel. I did not do anything complicated. It only generates the data in its constructor as well as minimally implement the pure virtual function.
I made a custom delegate which display numerical data in terms of bars. I also implemented a spin box as an editor to edit data.
The program works well. I can view, edit and modify data through a QTableView with the delegate set.
But there is a small problem. When I call the editor, the data bar persists, which means I see the data bar at the background and the spin box on top.
Initially, I think it is because the Qt::EditRole in the QAbstractTableModel::data() has not been set properly. But, surprisingly, I find that the Qt::EditRole has never been called.
So, there are two question:
How to remove the data bar when I am having the spin box editor?
Why is the EditRole never been called in my custom model?
Here is part of my code:
My Custom Model:
MyModel::MyModel(QObject* parent):QAbstractTableModel(parent)
{
for (int i = 0; i < 10; ++i)
localData.push_back(i*i);
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::EditRole:
qDebug() << "EditRole"; //Never Print Out
return 0;
case Qt::DisplayRole :
if (index.column() == 0)
return (index.row());
if (index.column() == 1)
return (localData.at(index.row()));
default:
return QVariant();
}
}
My Custom Delegate:
void MyDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setPen(Qt::red);
painter->setBrush(Qt::red);
double factor = 0;
if (index.data().toDouble() > 100)
factor = 1;
else
factor = index.data().toDouble() / (double) (100.0);
painter->drawRect(option.rect.x()+5, option.rect.y()+3, (option.rect.width()-10)*factor, option.rect.height()-6);
painter->restore();
}
QWidget* MyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox* box = new QSpinBox(parent);
box->setMinimum(0);
box->setMaximum(100);
return box;
}
Try to set setAutoFillBackground(true) for your view
Editrole is not called because your custom editor does not query the model for that data. You don not set any value for your spin box. Try to set it as:
box->setValue(model.data(Qt::EditRole));
in the MyDelegate::createEditor() function.
Related
I'm using the following code to try to change the background color of a cell at a given QModelIndex.
ui->TreeView->model()->setData(index, QVariant(QBrush (QColor(Qt::green))) , Qt::BackgroundRole);
where index is given by the dataChanged() signal.
This isn't working. Any ideas why?
Here's my reimplemented setData function.
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = getItem(index); //gets item a given index
bool result = item->setData(index.column(), value);
if (result)
emit dataChanged(index, index);
return result;
}
And here is the setData method for the underlying item:
bool TreeItem::setData(int column, const QVariant &value)
{
if (column < 0 || column >= itemData.size())
return false;
itemData[column] = value;
return true;
}
Apologies for the vague question. I've managed to solve it by myself so I will post here in case anyone is ever stuck on a similar issue.
The problem for me was that I hadn't reimplemented QAbstractItemView's data() method to account for the new role.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
TreeItem *item = getItem(index);
if (role == Qt::BackgroundRole)
return item->data(index.column());
//and so on...
AFAIK the data() method gives the treeview the data out of the model that it needs to present. Within this method I hadn't accounted for the case when role == Qt::BackgroundRole so the view was never given the appropriate information out of the model.
I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:
As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.
I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.
By the way, the lis of QDir is a vector, but it can be easily modified to anything else.
Thanks!
You can try to make proxy model for QFileSystemModel, override flags() with Qt::ItemIsUserCheckable, override setData() and apply the model to QTreeView. Full example can be found at https://github.com/em2er/filesysmodel. This code is just a concept, i have not tested it thoroughly, but you can take some ideas from it. It will look smth like on the screenshot:
.
Also you can combine it with Merged Proxy Model to display multiple starting paths at one view.
You might want to consider the QTreeWidget, or it's a tad more advanced version - QTreeView and an appropriate data model.
As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.
First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.
class FileSelector : public QFileSystemModel
{
public:
FileSelector(const char *rootPath, QObject *parent = nullptr);
~FileSelector();
bool setData(const QModelIndex& index, const QVariant& value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
private:
QObject *parent_;
/* checklist_ stores all the elements which have been marked as checked */
QSet<QPersistentModelIndex> checklist_;
};
When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:
Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}
When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :
bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == 0) {
QModelIndexList list;
getAllChildren(index, list); // this function gets all children
// given the index and saves them into list (also saves index in the head)
if(value == Qt::Checked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.insert(list[i]);
// signals that a change has been made
emit dataChanged(list[i], list[i]);
}
}
else if(value == Qt::Unchecked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.remove(list[i]);
emit dataChanged(list[i], list[i]);
}
}
return true;
}
return QFileSystemModel::setData(index, value, role);
}
When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.
QVariant FileSelector::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == 0) {
if(checklist_.contains(index)) return Qt::Checked;
else return Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}
The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.
Hope this can help someone who has the same problem as I did!
I'm using this code to query sqlite and put the results in a QTableView.
//MainWindow.cpp
void MainWindow::on_pushButton_clicked()
{
QSqlQueryModel * modal=new QSqlQueryModel();
connOpen();
QSqlQuery* qry=new QSqlQuery(mydb);
qry->prepare("select * from database");
qry->exec();
modal->setQuery(*qry);
//from stack
modal->insertColumn(0);
ui->tableView->setModel(modal);
//from stack
ui->tableView->resizeColumnsToContents();
int p;
for(p=0; p<modal->rowCount(); p++)
{
ui->tableView->setIndexWidget(modal->index(p,0),new QCheckBox());
}
connClose();
qDebug() <<(modal->rowCount());
}
I've seen several examples of the web for adding checkboxes to a column, but I'm not quite sure what to use for my simple example.
This answer suggests a few lines that doesn't seem standard.
There are more examples like this and this one that appear to outline what I need, but it's unclear to where you place the code.
What I intend to do is to have column 1 checkable. On next btn press, If checked those rows of data get written to a file.
I still need to understand how to loop thru the selected data, or perhaps I need to get the ids of the checked rows and do another query.
Questions:
How do you add 1 column of editable checkboxes to QTableView?
How do you loop through values in the QTableView data, so values of the checked rows can be accessed?
How do you check all/none?
I think the best way to have a column of checkable cells is to create your item model, e.g. by subclassing the QSqlQueryModel.
You must reimplement the flags() method to make checkable the cells.
Also you need to reimplement the data() method to return the check state and the setData() method and to set the check state. You must implement your own logic to keep track of the check state of every rows (e.g. using an array of Qt::CheckState that you must initialize and resize when the model data changes).
Yuo can start with something like this:
class MyModel : public QSqlQueryModel
{
public:
Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.column() == 0)
return QSqlQueryModel::flags(index) | Qt::ItemIsUserCheckable;
return QSqlQueryModel::flags(index);
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to return the check state
//....
}
else
return QSqlQueryModel::data(index, role);
}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to set the check state
//....
}
else
QSqlQueryModel::setData(index, value, role);
}
};
Se also:
Model Subclassing
QAbstractItemModel documentation
I have a QTableView with 4 Rows and 4 columns each representing their data's in it. By default the QTableView is editable. Now I want to make any particular column as non editable in my QTableView.
How can I do it?
Thanks in Advance.
You can use the setItemDelegateForColumn() function. Implement a read-only delegate, and set it for the column you need.
You can also use the flags inside your model, and remove the Qt::ItemIsEditable flag for a specific column.
Something like that may also do it:
class NotEditableDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit NotEditableDelegate(QObject *parent = 0)
: QItemDelegate(parent)
{}
protected:
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{ return false; }
QWidget* createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const
{ return Q_NULLPTR; }
};
In use:
// Make all the columns except the second read only
for(int c = 0; c < view->model()->columnCount(); c++)
{
if(c != 1)
view->setItemDelegateForColumn(c, new NotEditableDelegate(view));
}
The easiest way is settting the flag of the item you don't want to be editable in this way:
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
You can also check this thread: Qt How to make a column in QTableWidget read only
May be this late, but for future reference. You should set the table view to NoEditTrigger like this:
myTableView->setModel(model);
myTableView->setEditTriggers(QAbstractItemView::NoEditTriggers)
You need to override the 'flags' method and specify the editability parameters of the element for the selected column
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::NoItemFlags;
if(index.column() == SELECTED_COLUMN_NUM)
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
In the overide method just change it to if(!(index.column() == 0) and change the Flag value as Flag |= Qt::ItemisEditable.This Works Fine.
Guys I've implemented my delegate class based on QStyledItemDelegate and the problem I have is that it doesn't display checkboxes next to the text which is displayed in the listView.
Before I've used my delegate I've those checkboxes displayed in my listView so I know that's the problem lays in this delegate class.
Any thoughts?
EDIT
void Display_Delegate::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex &index) const
{
QString model_data = index.data().toString();
QFontMetrics metrics = view_->fontMetrics();
int view_width = view_->width();
auto modified_str = adjust_text(metrics,model_data,view_width);//this just makes the string to fit into view, don't bother about it.
QStyleOptionViewItemV4 style_option = option;
initStyleOption(&style_option,index);
QPalette::ColorGroup color_group = style_option.state & QStyle::State_Selected ? QPalette::Active : QPalette::Inactive;
if (style_option.state & QStyle::State_Selected)
{
// painter->setPen(style_option.palette.color(color_group, QPalette::Highlight));
painter->setBackgroundMode(Qt::OpaqueMode);
QColor color(148,231,245,100);
painter->setBackground(QBrush(color));
}
else
{
painter->setPen(style_option.palette.color(color_group, QPalette::Text));
}
painter->drawText(option.rect,modified_str);
}
Qt::CheckState QStyleOptionViewItemV4::checkState
If this view item is checkable, i.e., ViewItemFeature::HasCheckIndicator is true, checkState is true if the item is checked; otherwise, it is false.
I found in the methods this fairly obscure reference to having a check indicator. It says that if you want to make the item "checkable" then set this style option. So try something like:
style_option.ViewItemFeatures = QStyleOptionViewItemV2::HasCheckIndicator;