QListView and delegate display unintended item - qt

I've a problem with my QListView, it paint an unintended item on the top left of the QListView :
http://s4.postimage.org/64orbk5kd/Screen_Shot_2013_02_14_at_20_23_14.png
I use a QStyledItemDelegate in my QListView :
m_stringList.push_back("FIRST");
m_stringList.push_back("SECOND");
m_stringList.push_back("THIRD");
m_model.setStringList(m_stringList);
ui->processesListView->setFlow(QListView::LeftToRight);
ui->processesListView->setModel(&m_model);
ui->processesListView->setItemDelegate(new ProcessItemDelegate(this, ui->processesListView));
The delegate (ProcessItemDelegate) paint method use a custom QWidget to display the information :
void ProcessItemDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex &inIndex ) const
{
_listItem->setContent(_listView->model()->data(inIndex).toString());
painter->save();
painter->translate(option.rect.center());
_listItem->render(painter);
painter->restore();
}
The setContent method of the QWidget is very simple :
void ProcessItem::setContent(const QString &s)
{
ui->processId->setText(s);
}

I have another way to add a widget to some list using a QListWidget.
For example knowing that ui->historyView is a QListWidget element and HistoryElementView a subclass of QWidget.
void View::onHistoryChanged(const QList<HistoryElement> &history)
{
clearHistory();
foreach(HistoryElement elt, history)
{
HistoryElementView *historyViewElement = new HistoryElementView(elt.getDateTime("dd/MM/yyyy - hh:mm"), elt.getFilename());
QListWidgetItem *item = new QListWidgetItem();
ui->historyView->addItem(item);
ui->historyView->setItemWidget(item, historyViewElement);
}
}
void View::clearHistory()
{
QListWidgetItem *item;
while (ui->historyView->count() != 0)
{
item = ui->historyView->takeItem(0);
delete item;
}
}
You do not need to delete the widgets inside your QListWidgetItem, it will be handle by Qt.
Once your widgets are inside the list, you can retrieve them using :
// Using index
QListWidgetItem *item = ui->historyView->item(0);
HistoryElementView *elt = qobject_cast<HistoryElementView *>(ui->historyView->itemWidget(item));
// Using position
QListWidgetItem *item = ui->historyView->itemAt(pos);
HistoryElementView *historyElement = qobject_cast<HistoryElementView *>(ui->historyView->itemWidget(item));
Hope it helps.

Related

Painting QPixmap in the center of QTableView cell

I have a QTableView that works very well, the first column holds some thumbnails, in each cell of this column the thumbnails are vertically centered, but not horizontally centered.
Do I really need to use a delegate?
If yes, How to center them horizontally using QStyledItemDelegate?
Construct your own delegate and inherit QStyledItemDelegate. Override the paint method.
Then do something like this:
void
MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QPixmap pixmap;
pixmap.load("Your pixmap file path");
pixmap = pixmap.scaled(option.rect.width(), option.rect.height(), Qt::KeepAspectRatio);
// Position our pixmap
const int x = option.rect.center().x() - pixmap.rect().width() / 2;
const int y = option.rect.center().y() - pixmap.rect().height() / 2;
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
painter->drawPixmap(QRect(x, y, pixmap.rect().width(), pixmap.rect().height()), pixmap);
}
Drawing by yourself is not necessary, but a custom delegate - is. The styled item delegate uses the style's control element drawing code to draw a CE_ItemViewItem - see the source code for Qt 5.5.0. The drawing code takes the style option's decorationAlignment member into account. Unfortunately, there's no data role that would pass that alignment to the styles's implementation. Instead, you have to override the alignment in your delegate:
class DecorationAligningDelegate : public QStyledItemDelegate {
Q_OBJECT
Qt::Alignment const m_alignment;
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject * parent = 0) :
QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
auto opt = option;
opt.decorationAlignment = m_alignment;
QStyledItemDelegate::paint(painter, opt, index);
}
};
Then, to center the thumbnails:
view.setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, &view));
//or
view->setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, view));
If you really wished to paint it all yourself, even though it's unnecessary, the rectangle of the item to be painted is given in the style option (option.rect). To draw the pixmap centered in the item's rectangle, you could do as follows:
QStyleOption option;
QPixmap pix;
QPainter painter;
...
painter.save();
auto loc = option.rect.center() - pix.rect().center()
painter.drawPixmap(loc, pix);
painter.restore();
I will just leave my version that literary is a combination of the two answers.
class DecorationAligningDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject *parent = nullptr)
: QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QIcon icon = QIcon(qvariant_cast<QIcon>(index.data(Qt::DecorationRole)));
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
icon.paint(painter, option.rect, m_alignment);
}
private:
Q_DISABLE_COPY(VDecorationAligningDelegate)
Qt::Alignment const m_alignment;
};
I assume you define your item like this:
auto *item = new QTableWidgetItem();
item->setIcon(QIcon("Your pixmap file path"));
Don't forget about setting a delegate.

Set QItemDelegate on a particular QTreeWidgetItem

Is it possible to set a QItemDelegate on a particular QTreeWidgetItem? I need to color some of the QTreeWidgetItems with a particular color.
I assume it is possible as we have QAbstractItemView::setItemDelegateForRow but I can't figure out how. I can't use QAbstractItemView::setItemDelegateForRow because I need to set a custom delegate on a child row inside the QTreeWidget.
Does anyone know a solution for that?
You can't use QTreeWidgetItem in delegate directly (probably you can store list of this items inside delegates but I think that it is not efficient), because delegates works with QModelIndex and data inside different roles. You can set data to Qt::UserRole+1 and access it inside delegate. For example:
QTreeWidgetItem *cities = new QTreeWidgetItem(ui->treeWidget);
//...
cities->setData(0,Qt::UserRole+1,"chosen one");
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
//...
QTreeWidgetItem *berlinItem = new QTreeWidgetItem(cities);
//...
berlinItem->setData(0,Qt::UserRole+1,"chosen one");
Inside delegate (just example):
void ItemDelegatePaint::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QString txt = index.model()->data( index, Qt::DisplayRole ).toString();
if( option.state & QStyle::State_Selected )
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::green );
else
painter->fillRect( option.rect, option.palette.highlight() );
}else
if(option.state & QStyle::State_MouseOver)
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::yellow );
else
painter->fillRect( option.rect, Qt::transparent );
}
else
{
QStyledItemDelegate::paint(painter,option,index);
}
}
You can access the QTreeWidget from you delegate's paint routine to check if a condition for painting the background is met
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const QTreeWidget* tree_widget = qobject_cast<const QTreeWidget*>(qstyleoption_cast<const QStyleOptionViewItemV3*>(&option)->widget);
....
}
or you store something in the QModelIndex UserData as Chernobyl suggested. In that case I would however create an enum for flags (if this is applicable in your case):
enum custom_painter_flags{
paint_default = 0,
paint_background = 1
};
void somewhere_creating_the_items()
{
QTreeWidgetItem* newitem = new QTreeWidgetItem(...);
newitem->setData(0, Qt::UserRole, QVariant::fromValue<int>(custom_painter_flags::paint_background));
}
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
custom_painter_flags painter_flags = static_cast<painter>(index.data(Qt::UserRole).value<int>());
if(painter_flags & paint_background){
....
}
}
Unfortunately I have not much time right now so this is thrown together pretty quick. Feel free to edit if you find any errors.
You can use qss on the QTreeWidgetItem to change color or background color.
I have done it for a QTableWidget, You must check the value of all your QTreeWidgetItem and set a background color / color.
For example, for my QTableWidget i have done something like this in a loop :
if(good item):
MyQTableItem.setBackground(QtGui.QColor(255,255,255))

QT Custom QStyle for QTabBar and QIcon

I have customize a Qtabbar's QMdiarea to get horizontal tab instead of vertical tab like this :
http://www.qtcentre.org/wiki/index.php?title=Customizing_QTabWidget%27s_QTabBar
But i want to insert icon in my QTabBar.
If I apply my custom style to my QTabBar my icons doesn't appear.
If I don't apply, my icon appear.
here my custom style :
class CustomTabStyle : public QPlastiqueStyle
{
Q_OBJECT
public:
QSize sizeFromContents(ContentsType type, const QStyleOption *option,const QSize &size, const QWidget *widget) const
{
QSize s = QPlastiqueStyle::sizeFromContents(type, option, size, widget);
if (type == QStyle::CT_TabBarTab)
s.transpose();
return s;
}
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
if (element == CE_TabBarTabLabel)
{
if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option))
{
QStyleOptionTab opt(*tab);
opt.shape = QTabBar::RoundedNorth;
QPlastiqueStyle::drawControl(element, &opt, painter, widget);
return;
}
}
QPlastiqueStyle::drawControl(element, option, painter, widget);
}
};
and i apply my style like this :
mMdiAreaDock=aMdiArea;
m_pMdiAreaTabBar = NULL;
QObjectList listChildren = mMdiAreaDock->children();
for (QObjectList::Iterator i = listChildren.begin(); i != listChildren.end(); ++i)
{
if (QString((*i)->metaObject()->className()) == "QTabBar")
{
m_pMdiAreaTabBar = dynamic_cast<QTabBar*>(*i);
break;
}
}
m_pMdiAreaTabBar->setStyle(new CustomTabStyle());
return 0;
where mMdiAreaDock is an QMdiArea
and m_pMdiAreaTabBar is a QTabBar
Look at this code:
const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option));
QStyleOptionTab opt(*tab);
option is an instance of QStyleOptionTabV2.
When you create a new object QStyleOptionTab opt with copy constructor, you lose some important data which extended QStyleOptionTabV2 contains including information about an icon.
Use this code instead:
if (const QStyleOptionTabV2 *tab = qstyleoption_cast<const QStyleOptionTabV2 *>(option))
{
QStyleOptionTabV2 opt(*tab);
opt.shape = QTabBar::RoundedNorth;
QPlastiqueStyle::drawControl(element, &opt, painter, widget);
return;
}
ps. I used this code to assign an icon for a specific tab:
m_pMdiAreaTabBar->setTabIcon(0, icon);

QStandardItemModel header with widget and text

i need to use checkbox with text, like this "Check all":
in header of QStanndardItemModel. I tried like this
QStandardItem* item0 = new QStandardItem("some text");
item0->setCheckable(true);
item0->setCheckState(Qt::Checked);
item0->setText("some text");
_model->setHorizontalHeaderItem(1, item0);
This way only works for items not for header, i mean for items if i use
_model->setItem(new QStandardItem(some_item);
I heard about writing my own class which inherit QHeaderView but i dont know if this can help in my problem. I would ask if there is a easy way to achieve this?
Regards
i did something like this:
QStandardItemModel *model = new QStandardItemModel(this);
ui->tableView->setModel(model);
QtCheckHeaderView *header = new QtCheckHeaderView(Qt::Horizontal, ui->tableView);
QtCheckHeaderView *vheader = new QtCheckHeaderView(Qt::Vertical, ui->tableView);
ui->tableView->setHorizontalHeader(header);
ui->tableView->setVerticalHeader(vheader);
QStandardItem *root = model->invisibleRootItem();
QList<QList<QStandardItem *> > items;
for (int i = 0; i < 10; ++i)
{
QList<QStandardItem *> res;
for (int j = 0; j < 1000; ++j)
{
res.append(new QStandardItem(QString("%1;%2").arg(j).arg(i)));
vheader->addCheckable(j);
}
items.append(res);
root->appendColumn(res);
header->addCheckable(i);
}
this works great. Draws checkbox with text in vertical and horizontal header.
But this works great only if i put this code to MainWindow constructor. If i put this code to for example pushbutton function this wont work. Datas are fine but headers are invisible.
ui->tableView->repaint();
wont work. Someone maybe knows the answer why this is happening and/or how to solve this?
thanks for answer
Qt models don't support item flags for headers. Setting item flags on items that are used as headers has no effect. QHeaderView doesn't support drawing checkboxes. I'm afraid subclassing QHeaderView is the simpliest way.
I wrote an example of how it can be implemented based on this FAQ page. This class assumes that you use QStandardItemModel and uses flags of its header items. If someone would want to use other model class, subclassing QAbstractItemModel and implementing missing interface and changing headerview implementation would be needed.
class MyHeader : public QHeaderView
{
public:
MyHeader(Qt::Orientation orientation, QWidget * parent = 0);
protected:
QStandardItemModel* standard_model;
virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
virtual void mousePressEvent(QMouseEvent *event);
virtual void setModel(QAbstractItemModel* model);
};
MyHeader::MyHeader(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent)
{
standard_model = 0;
}
void MyHeader::paintSection(QPainter *painter, const QRect &rect, int logical_index) const {
painter->save();
QHeaderView::paintSection(painter, rect, logical_index);
painter->restore();
if (standard_model && orientation() == Qt::Horizontal) {
QStandardItem* item = standard_model->horizontalHeaderItem(logical_index);
if (item && item->isCheckable()) {
int offset = (height() - style()->pixelMetric(QStyle::PM_IndicatorHeight))/2;
int pos = sectionViewportPosition(logical_index);
QStyleOptionButton option;
option.rect = QRect(offset + pos, offset,
style()->pixelMetric(QStyle::PM_IndicatorWidth),
style()->pixelMetric(QStyle::PM_IndicatorHeight));
if (item->checkState() == Qt::Checked) {
option.state = QStyle::State_On;
} else {
option.state = QStyle::State_Off;
}
option.state |= QStyle::State_Enabled;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
}
}
}
void MyHeader::mousePressEvent(QMouseEvent *event) {
int offset = (height() - style()->pixelMetric(QStyle::PM_IndicatorHeight))/2;
if (standard_model && orientation() == Qt::Horizontal) {
for(int logical_index = 0; logical_index < count(); logical_index++) {
int pos = sectionViewportPosition(logical_index);
QRect rect(offset + pos, offset,
style()->pixelMetric(QStyle::PM_IndicatorWidth),
style()->pixelMetric(QStyle::PM_IndicatorHeight));
if (rect.contains(event->pos())) {
QStandardItem* item = standard_model->horizontalHeaderItem(logical_index);
if (item && item->isCheckable()) {
item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
return;
}
}
}
}
QHeaderView::mousePressEvent(event);
}
void MyHeader::setModel(QAbstractItemModel *model) {
QHeaderView::setModel(model);
standard_model = qobject_cast<QStandardItemModel*>(model);
}
//usage
QTableView view;
QStandardItemModel model;
model.setColumnCount(5);
QStandardItem* item0 = new QStandardItem("some text");
item0->setCheckable(true);
item0->setCheckState(Qt::Checked);
item0->setText("some text");
model.setHorizontalHeaderItem(0, item0);
view.setModel(&model);
MyHeader *myHeader = new MyHeader(Qt::Horizontal, &view);
view.setHorizontalHeader(myHeader);
view.show();

Position of icon in QTreeWidgetItem

My QTreeWidget has a single column. Its items have a check box, an icon, and text. If the user clicks inside an item, I want to know whether the icon was clicked. How can I find the position and size of the icon in a QTreeWidgetItem?
Updated to add: Here is the code for my eventual solution, as requested by webclectic.
First, I sub-classed QItemDelegate so that I could access the coordinates of each part of a QTreeWidgetItem (check box, icon, and text). Here is the header file:
#include <QItemDelegate>
class MyItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit MyItemDelegate (MyTreeWidget *parent)
: QItemDelegate (parent), ParentView (parent) { }
~MyItemDelegate() { }
void GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const ;
private:
MyTreeWidget* ParentView ;
} ;
And here is the source file:
void MyItemDelegate::GetRects (const QModelIndex &index, QRect& CheckBox, QRect& Icon, QRect& Text) const
{
QStyleOptionViewItem option = ParentView -> viewOptions() ;
CheckBox = rect (option, index, Qt::CheckStateRole) ;
Icon = rect (option, index, Qt::DecorationRole) ;
Text = rect (option, index, Qt::DisplayRole) ;
doLayout (option, &CheckBox, &Icon, &Text, true) ;
QRect VisualRect = ParentView -> visualRect (index) ;
CheckBox.translate (VisualRect.topLeft()) ;
Icon.translate (VisualRect.topLeft()) ;
Text.translate (VisualRect.topLeft()) ;
}
Then I added a MyItemDelegate* member to MyTreeWidget, and set it as the item view's delegate. In the header:
class MyTreeWidget : public QTreeWidget
{
...
MyItemDelegate* Delegate ;
...
} ;
In the source :
MyTreeWidget::MyTreeWidget (QObject* parent)
{
...
Delegate = new MyItemDelegate (this) ;
setItemDelegate (ItemDelegate) ;
}
Now, to get the coordinates of each part of a QTreeWidgetItem:
QTreeWidgetItem* item ;
...
QModelIndex ModelIndex = indexFromItem (item) ;
QRect CheckBoxRect, IconRect, TextRect ;
ItemDelegate -> GetRects (ModelIndex, &CheckBoxRect, &IconRect, &TextRect) ;
Unfortunately there is no simple way to achieve what you want. The problem is that QTreeWidget is responsible for painting its items so the item itself has no information about the position of its elements in the view.
First of all you have to subclass QTreeWidget and reimplement the mousePressEvent (or mouseReleaseEvent if you prefer). Inside the event you should calculate the position of the icon and handle it correspondingly.
Sample code (but untested) follows:
void mousePressEvent(QMouseEvent *event)
{
QModelIndex clickedIndex = indexAt(event->pos());
// make sure the event was on a valid item
if (clickedIndex.isValid() == false)
return;
// Get the tree widget's x position
int treeX = header()->sectionViewportPosition(0);
// Get the x coordinate of the root item. It is required in order to calculate
// the identation of the item
int rootX = visualRect(rootIndex()).x();
// Get the rectangle of the viewport occupied by the pressed item
QRect vrect = visualRect(clickedIndex);
// Now we can easily calculate the x coordinate of the item
int itemX = treeX + vrect.x() - rootX;
// The item is a checkbox, then an icon and finally the text.
// 1. Get the rect surrounding the checkbox
QRect checkboxRect = QRect(itemX,
vrect.y(),
style()->pixelMetric(QStyle::PM_IndicatorWidth),
vrect.height());
// 2. Get the rect surrounding the icon
QRect iconRect = QRect(itemX + checkboxRect.width(),
vrect.y(),
iconSize().width(),
vrect.height());
// 3. Finally get the rect surrounding the text
QRect textRect = QRect(itemX + checkboxRect.width() + iconRect.width(),
vrect.y(),
vrect.width() - checkboxRect.width() - iconRect.width(),
vrect.height());
// Now check where the press event took place and handle it correspondingly
if(checkboxRect.contains(event->pos()))
{
qDebug() << "Checkbox pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else if (iconRect.contains(event->pos()))
{
qDebug() << "Icon pressed";
QTreeWidget::mousePressEvent(event);
return;
}
else
{
qDebug() << "Text pressed";
QTreeWidget::mousePressEvent(event);
return;
}
}
I repeat that the code is untested but you get the idea about how to achieve what you want.

Resources