I have a QTreeView that is filled using an AbstractItemModel. I want to highlight some entries by showing a red border based on an internal state.
Currently my code looks like this:
QVariant MyAbstractItemModel::data(QModelIndex const& index, int role)
...
else if (Qt::BackgroundRole == role)
{
if (someMethod(index))
return QColor(255,0,0);
return QVariant();
} ...
Obviously this codes sets the background color to red and not the border color.
How can I set the border color of the item?
So thanks to SaZ the solution is the QStyledItemDelegate.
My code looks now like this:
QVariant MyAbstractItemModel::data(QmodelIndex const&, int role)
{
...
else if (Qt::UserRole == role)
{
return someMethod(index);
}
...
}
and I have a delegate:
class MyDelegate
: public QStyledItemDelegate
{
virtual void paint(QPainter* painter, QStyleOptionViewItem const& option, QModelIndex const& index) const override
{
QStyledItemDelegate::paint(painter, option, index);
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
if (index.data(Qt::UserRole))
{
QRect rect(opt.rect.x(), opt.rect.y(), opt.rect.width(), opt.rect.height());
painter.save();
painter.setPen(Qt::red);
painter.drawRect(rect);
painter->restore();
}
}
}
...
MyDelegate _delegate;
_ui.myTreeView->setItemDelegate(&_delegate);
Related
i have QListView I assigned him my modeland delegate in which I redefined the method paint(..):
void PlainDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect rect = option.rect;
QLinearGradient gradient(0,0,rect.width(),rect.height());
if (option.state & QStyle::State_Selected)
{
gradient.setColorAt(1,Qt::black); //not work
}
else if(option.state & QStyle::State_MouseOver&& !isEditorOpen)
{
//set gradient
}
else
{
//set gradient
}
painter->fillRect(option.rect, gradient);
painter->setPen(Qt::NoPen);
painter->setBrush(gradient);
painter->drawRect(rect);
QStyledItemDelegate::paint(painter,option,index);
}
it works like that
As you can see, the elements are overlap by a standard blue window.
How to remove this window?
You can do it via CSS.
Look at this (in file css or QWidget::setStyle(QStyle *style)):
QListView::item:selected
{
border: 1.2px;
border-color: #273e51;
border-style: outset;
...etc
}
QListView::item:selected:!active
{
}
QListView::item:selected:active
{
}
QListView::item:hover
{
}
look at this: https://doc.qt.io/qt-5/stylesheet-reference.html
I have QTreeView with QAbstractItemModel. Some particular columns are supposed to have user defined checkboxes. I have done so by overriding QAbstractItemModel::data() function and by sending check state for Qt::CheckStateRole role as shown in the code.
I am getting checkboxes and am able to check and uncheck them successfully.
But the requirement is to customize some of these checkboxes. Basically I need to differentiate some checkboxes from the others by any method for eg: fill the checkbox with blue, make the boundary of the checkbox blue or any other method. But I am not sure how to change checkbox styling as I am creating checkbox via model.
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::CheckStateRole && index.column() == COLUMN_WITH_CHECKBOX)
{
//return Qt::Checked or Qt::Unchecked here
}
//...
}
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
if (role == Qt::CheckStateRole)
{
if ((Qt::CheckState)value.toInt() == Qt::Checked)
{
//user has checked item
return true;
}
else
{
//user has unchecked item
return true;
}
}
return false;
}
First you need is implement your own ItemDelegate
class CheckedDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
CheckedDelegate(QObject *parent = nullptr);
~CheckedDelegate();
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;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const;
};
In this delegate you must implement custom editor and custom item painting. To create custom editor:
QWidget *CheckedDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QCheckBox *chBox = new QCheckBox(parent);
//customize editor checkbox
QString strQss = "QCheckBox::indicator:checked { image: url(:/icons/pic/checkboxChecked.png); } ";
strQss.append("QCheckBox::indicator:unchecked { image: url(:/icons/pic/checkboxUnchecked.png); }");
chBox->setStyleSheet(strQss);
return chBox;
}
void CheckedDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked)
{
chBox->setChecked(true);
}
else
{
chBox->setChecked(false);
}
}
void CheckedDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QCheckBox *chBox = dynamic_cast<QCheckBox*> (editor);
model->setData(index, chBox->isChecked() ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
}
void CheckedDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(GetCheckboxRect(option));
}
To calculate checkbox geometry use
QRect GetCheckboxRect(const QStyleOptionViewItem &option)
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
double dx = (r.width() - sz.width()) / 2;
double dy = (r.height()- sz.height()) / 2;
r.setTopLeft(r.topLeft() + QPoint(qRound(dx),qRound(dy)));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
Then implement custom painting. In this example I use pixmaps to customize checkbox so I also paint only pixmaps.
void CheckedDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
if (index.data(Qt::CheckStateRole).toInt() == Qt::Checked) {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxChecked.png"));
} else {
QApplication::style()->drawItemPixmap(painter, GetCheckboxRect(option), Qt::AlignLeft | Qt::AlignVCenter, QPixmap(":/icons/pic/checkboxUnchecked.png"));
}
}
And set your delegate (in my example I have TableTiew not TreeView)
CheckedDelegate *chDel = new CheckedDelegate(this);
ui->tableView->setItemDelegateForColumn(1, chDel);
I've been struggling for a few days trying to figure out how to display some pretty simple data in a QML window. I realize there are many ways to accomplish this task, and in this case, I'd prefer to find out how to use QAbstractTableModel.
I have a row of data, each row containing two items, a name and a value (name/value or key/value pair). I've subclassed the QAbstractTableModel to pass this data to the QML. Here is the code I have so far; it is mostly based on the tutorial that can be found here (which is also very old): https://doc.qt.io/archives/4.6/itemviews-addressbook.html.
databridge.h
#include <QObject>
#include <QAbstractTableModel>
#include <QPair>
class DataBridge : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DataBridge();
explicit DataBridge(QList<QPair<QString, QString>> listOfPairs);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool insertRows(int row, int count, const QModelIndex &parent);
bool removeRows(int row, int count, const QModelIndex &parent);
Qt::ItemFlags flags(const QModelIndex &index) const;
QList<QPair<QString, QString>> getList();
signals:
public slots:
private:
QList<QPair<QString, QString>> m_listOfPairs;
};
#endif // DATABRIDGE_H
databridge.cpp
#include "databridge.h"
DataBridge::DataBridge()
{}
DataBridge::DataBridge(QList<QPair<QString, QString> > listOfPairs)
{ m_listOfPairs = listOfPairs; }
int DataBridge::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_listOfPairs.size();
}
int DataBridge::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
//Number of columns is always 2 in this kata
return 2;
}
QVariant DataBridge::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() >= m_listOfPairs.size() || index.row() < 0)
return QVariant();
if(role == Qt::DisplayRole)
{
QPair<QString, QString> pair = m_listOfPairs.at(index.row());
if(index.column() == 0)
return pair.first;
else
return pair.second;
}
return QVariant();
}
bool DataBridge::setData(const QModelIndex &index, const QVariant &value, int role)
{
//TODO
return QVariant();
}
QVariant DataBridge::headerData(int section, Qt::Orientation orientation, int role) const
{
//TODO
return QVariant();
}
bool DataBridge::insertRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
bool DataBridge::removeRows(int row, int count, const QModelIndex &parent)
{
//TODO
return true;
}
Qt::ItemFlags DataBridge::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QPair<QString, QString> > DataBridge::getList()
{
return m_listOfPairs;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QPair>
#include "databridge.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QList<QPair<QString, QString>> inserter;
inserter.append(QPair<QString, QString>("0", "zero"));
inserter.append(QPair<QString, QString>("1", "one"));
inserter.append(QPair<QString, QString>("2", "two"));
inserter.append(QPair<QString, QString>("3", "three"));
inserter.append(QPair<QString, QString>("4", "four"));
DataBridge * bridge = new DataBridge(inserter);
engine.rootContext()->setContextProperty("bridge", bridge);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.data(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.data(index, 0)
}
}
}
Column{
id: thisColumn
anchors.horizontalCenter: parent.horizontalCenter
Repeater{
id: myRepeater
delegate: myComponent
model: bridge
}
}
Component.onCompleted: {
console.log("DEBUG main.qml");
}
}
Please ignore the functions with TODO in them in the databridge.cpp. I didn't include the bodies for the sake of brevity.
The first problem I'm facing is that when my repeater in the main.qml calls bridge.data(index, 0), the index is determined to be invalid in the first if statement of the data function (if(!index.isValid()). I'm not sure why this is happening. The second issue I can see, though I haven't gotten that far yet, is how can I tell the data function if I'm calling column 0 or column 1? It checks for this later in the function, and returns the pair relevant to the column requested. I'm guessing that in myComponent, I need something more specific to request the data from whichever column, but I'm not sure what that would be?
Any help with this would be immensely appreciated. Thank you in advance!
QModelIndex Documentation
Another link to the example referenced at the top
QAbstractTableModel Documentation
Qt Role Enum Documentation
I was able to find a solution, but I'm not sure if it's the correct one. I've added a function to the databridge class that takes integer input from the QML, finds a corresponding QModelIndex, and pulls the data from the model that way. This is what the code looks like:
From databridge.cpp
QString DataBridge::getThisItem(int index, int column)
{
QModelIndex currentIndex = QAbstractTableModel::index(index, column);
QString retVal = data(currentIndex, 0).toString();
return retVal;
}
Corresponding QML Repeater Change
Component{
id: myComponent
Row{
id: thisRow
Text{
text: bridge.getThisItem(index, 0)
}
Text{
text: "\t.\t.\t.\t"
}
Text{
text: bridge.getThisItem(index, 1)
}
}
}
This produces the desired output, that being:
I am interested to know if this is a good implementation, or if there is a better way to do this. Thanks again!
I have a custom Delegate, subclassed from QItemDelegate, that provides a QComboBox in the very first column and a QLineEdit in all other columns.
SensorDisplayDelegate::SensorDisplayDelegate(QObject *parent) :
QItemDelegate(parent)
{}
QWidget *SensorDisplayDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = new QComboBox(parent);
connect(comboBox, SIGNAL(activated(int)), this, SLOT(setData(int)));
comboBox->setEditable(false);
//comboBox->setMaximumSize(editorSize);
comboBox->setInsertPolicy(QComboBox::NoInsert);
currentComboBox = comboBox;
return comboBox;
}
else
{
QLineEdit *lineEdit = new QLineEdit(parent);
return lineEdit;
}
return NULL;
}
void SensorDisplayDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = static_cast<QComboBox*>(editor);
QStringList comboItems = index.data(Qt::EditRole).toStringList();
comboBox->addItem("Add New Sensor");
comboBox->addItems(comboItems);
QCompleter *completer = new QCompleter(comboItems);
completer->setCaseSensitivity(Qt::CaseInsensitive);
comboBox->setCompleter(completer);
comboBox->showPopup();
}
else
{
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
lineEdit->setText(index.data(Qt::EditRole).toString());
lineEdit->show();
}
}
void SensorDisplayDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
int col = index.column();
if(col == 0)
{
QComboBox *comboBox = static_cast<QComboBox*>(editor);
if(comboBox->currentIndex() == 0)
emit addNewSensor();
else
emit populateSensorView(comboBox->currentText());
}
else
{
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
model->setData(index, QVariant(lineEdit->text()));
}
}
void SensorDisplayDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
QSize SensorDisplayDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
return editorSize;
}
void SensorDisplayDelegate::setData(int option)
{
emit commitData(currentComboBox);
emit closeEditor(currentComboBox);
}
The editTrigger has been set to selectClicked. I want the combo box to cover the entire cell in the QTableView. However, now it just appears as blip on the left hand corner. I tried setting the minimum size by passing the cell size through an event filter that listens for mousePressed on QTableView. However, the corresponding slot in the delegate is never called. Here's the code:
MultiEventFilter.cpp :
bool MultiEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
{
if(obj->objectName() == "sensorlocationTableView")
{
QTableView *sensorView = static_cast<QTableView*>(obj);
QModelIndexList idxs = sensorView->selectionModel()->selectedIndexes();
if(!idxs.empty())
{
QModelIndex idx = idxs.at(0);
emit passCellSize(QSize(sensorView->columnWidth(idx.column()),
sensorView->rowHeight(idx.row())));
}
}
}
return false;
}
installed on qApp.
MainWindow.cpp:
eFilter = new MultiEventFilter();
connect(eFilter, SIGNAL(passCellSize(QSize)),
sensor_display_delegate, SLOT(setEditorSize(QSize)));
SensorDisplayDelegate.cpp slot:
void SensorDisplayDelegate::setEditorSize(const QSize &size)
{
editorSize = size;
}
where QSize editorSize is a private member.
How can I set the size of the editor correctly? I need something general that can be applied to the QLineEdit editors as well.
Also, is it necessary to explicitly emit commitData() when the editor is closed? I have not seen this done in any example codes involving QComboBox.
I suspect your eventFilter is intercepting the click events before the selection indexes have been set. So, you're effectively always hitting an empty idxs IndexList?
Try replacing that loop with something like:
bool MultiEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
{
if(obj->objectName() == "sensorlocationTableView")
{
emit locationTableViewClicked();
}
}
return false;
}
....
connect(eFilter, SIGNAL(locationTableViewClicked()),
sensor_display_delegate, SLOT(setEditorSize()));
...
void SensorDisplayDelegate::setEditorSize()
{
QModelIndexList idxs = sensorView->selectionModel()->selectedIndexes();
if(!idxs.empty())
{
QModelIndex idx = idxs.at(0);
editorSize = QSize(sensorView->columnWidth(idx.column()),
sensorView->rowHeight(idx.row()));
}
}
I would like to set a widget in a treeviews child row using a QStyledItemDelegate.
The widget is shown as intended but not clickable. It seems like it is not "active".
This is my paint method:
ProjectSpecificDelegate::ProjectSpecificDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}
void ProjectSpecificDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
if(index.data(SpecialTreatmentsArchiv::ParentRowRol).toString() != "parent")
{
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
QPaintDevice* original_pdev_ptr = painter->device();
QList<ProjectSpecificArchivItem> item_list = index.data(SpecialTreatmentsArchiv::ChildRowRole).value<QList<ProjectSpecificArchivItem> >();
ProjectSpecificArchiv expand_widget(item_list);
painter->end();
expand_widget.render(painter->device(), QPoint(option.rect.x(), option.rect.y()), QRegion(0, 0, option.rect.width(), option.rect.height()), QWidget::DrawChildren);
painter->begin(original_pdev_ptr);
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
My question is: What do I have to change so that it is possible to to interact with the widget?