HowTo draw border for QTableWidget row? - qt

I'm trying to make a border for rows in QTableWidget with different ways, but all solutions don't respond my requirements. All that I want, is to draw a rectangle around a whole row. I had try QStyledItemDelegate class, but that is not my way, because delegates are used only for item[ row, column ], not for the whole rows or columns.
Here is wrong solution:
/// #brief Рисуем границу вокруг строки.
class DrawBorderDelegate : public QStyledItemDelegate
{
public:
DrawBorderDelegate( QObject* parent = 0 ) : QStyledItemDelegate( parent ) {}
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
}; // DrawBorderDelegate
void DrawBorderDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QStyleOptionViewItem opt = option;
painter->drawRect( opt.rect );
QStyledItemDelegate::paint( painter, opt, index );
}
And somewhere in code:
tableWidget->setItemDelegateForRow( row, new DrawBorderDelegate( this ) );
Thanks for help!

Your solution was not far wrong. You just need to be a bit more selective about which edges of the rectangle you draw:
void DrawBorderDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
const QRect rect( option.rect );
painter->drawLine( rect.topLeft(), rect.topRight() );
painter->drawLine( rect.bottomLeft(), rect.bottomRight() );
// Draw left edge of left-most cell
if ( index.column() == 0 )
painter->drawLine( rect.topLeft(), rect.bottomLeft() );
// Draw right edge of right-most cell
if ( index.column() == index.model()->columnCount() - 1 )
painter->drawLine( rect.topRight(), rect.bottomRight() );
QStyledItemDelegate::paint( painter, option, index );
}
Hope this helps!

#include <QTableWidget>
QTableWidget* table = new QTableWidget();
table->resize(400, 250);
table->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
table->setColumnCount(3);
table->setRowCount(2);
//Set Header Label Texts Here
table->verticalHeader ()->hide();
table->horizontalHeader()->hide();
table->setItem(0,0,new QTableWidgetItem("CELL 1"));
table->setItem(0,1,new QTableWidgetItem("CELL 2"));
table->setItem(0,2,new QTableWidgetItem("CELL 3"));
table->setItem(1,0,new QTableWidgetItem("CELL 4"));
table->setItem(1,1,new QTableWidgetItem("CELL 5"));
table->setItem(1,2,new QTableWidgetItem("CELL 6"));
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setFocusPolicy(Qt::NoFocus);
table->setSelectionMode(QAbstractItemView::NoSelection);
table-> setObjectName (QString :: fromUtf8 ("table_"));
table->show();
Stylesheet:
QTableWidget
{
background-color : none;
gridline-color: white; // this border for rows and columns
color:#ffffff;
}
QTableWidget#table_{
border:1px solid #ffffff; // this border for total table
}
sample output
Hope this simple way to helps!!!

Related

How to display bold text in a specific QHeaderView section in combination with a stylesheet

I'm using application-wide stylesheets to alter my QTableView's look. At the same time, I want certain column headers to have bold text, depending on the header text. For this I derived from QHeaderView and implemented the paintSection function:
class BoldHeaderView : public QHeaderView
{
Q_OBJECT
public:
BoldHeaderView( Qt::Orientation orientation, QWidget* parent = 0 ) : QHeaderView( orientation, parent ) { }
void addBoldColumn( QString column_name )
{
if ( !m_bold_columns.contains( column_name ) )
m_bold_columns.append( column_name );
}
protected:
void paintSection( QPainter* p_painter, const QRect& rect, int logicalIndex ) const
{
QFont bold_font = p_painter->font();
QString column_name = model()->headerData( logicalIndex, Qt::Horizontal, Qt::DisplayRole ).toString();
if ( m_bold_columns.contains( column_name ) )
bold_font.setBold( true );
p_painter->setFont( bold_font );
QHeaderView::paintSection( p_painter, rect, logicalIndex );
}
private:
QList<QString> m_bold_columns;
};
Then I set this as the QTableView's horizontalHeader :
BoldHeaderView* p_bold_header = new BoldHeaderView( Qt::Horizontal );
p_bold_header->addBoldColumn( "Foo" );
m_p_table_view->setHorizontalHeader( p_bold_header );
My stylesheet looks like this:
QTableView QHeaderView::section {
font-family: "Segoe UI";
background-color: white;
border-style: none;
}
And it is applied application-wide in the main-function:
QApplication app(argc, argv);
[...]
app.setStyleSheet( style_sheet );
Thanks to eyllanesc I found out that this conflicts with the stylesheet. The bold font will always be overwritten with whatever is specified there. I need to find a way to combine both methods.
How about using the model of the data to be in charge of that?
If the model of the data derives from QAbstractItemModel, you can change the headerData method of it. Something like:
QVariant MyDerivedModel::headerData(int section, Qt::Orientation orientation, int role) const
{
//... things
if(role == Qt::FontRole)
{
//if the column (the section) is one of the "special" ones, set it to bold
//return the desired QFont in bold
}
}

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))

HowTo draw pixmap in QHeaderView section using delegate?

Problem: I need to draw small pixmap to QHeaderView section like presented on this figure (pixmap located on the right corner of the section, marked with red square):
As I understand, there is two ways to do that:
reimplement QHeaderView's paintSection() method.
create an delegate from QStyledItemDelegate class and reimplement paint() method.
If I tried (1) variant with this code below, filter pixmap are not shown at all:
void DecorativeHeaderView::paintSection( QPainter* painter, const QRect& rect, int logicalIndex ) const
{
if( !rect.isValid() )
{
return;
}
// get the state of the section
QStyleOptionHeader option;
initStyleOption( &option );
// setup the style options structure
option.rect = rect;
option.section = logicalIndex;
option.iconAlignment = Qt::AlignVCenter | Qt::AlignHCenter;
QVariant variant = model()->headerData( logicalIndex, orientation(), Qt::DecorationRole );
option.icon = qvariant_cast< QIcon >( variant );
if( option.icon.isNull() )
{
option.icon = qvariant_cast< QPixmap >( variant );
}
// draw the section
if( !option.icon.isNull() )
{
style()->drawControl( QStyle::CE_Header, &option, painter, this );
}
else
{
QHeaderView::paintSection( painter, rect, logicalIndex );
// HERE is where I'm trying to draw my filter picture!!!
if( logicalIndex == filteredLogicalIndex_ )
{
QPixmap pixmap( ":/spreadsheet/images/spreadsheet/filter_icon_table.png" );
int x = rect.right() - pixmap.width();
int y = rect.top() + ( rect.height() - pixmap.height() ) / 2;
painter->drawPixmap( QPoint( x, y ), pixmap );
}
}
}
The (2) variant is this:
class HeaderDelegate : public QStyledItemDelegate
{
Q_OBJECT
Q_DISABLE_COPY( HeaderDelegate )
public:
HeaderDelegate( QObject* parent = 0 ) : QStyledItemDelegate( parent ) {}
virtual ~HeaderDelegate() {}
virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
}; // HeaderDelegate
void HeaderDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// THIS method never starts!!!
if( index.column() == 2 )
{
QPixmap pixmap( ":/spreadsheet/images/spreadsheet/filter_icon_table.png" );
int x = option.rect.right() - pixmap.width();
int y = option.rect.top() + ( option.rect.height() - pixmap.height() ) / 2;
painter->save();
painter->drawPixmap( QPoint( x, y ), pixmap );
painter->restore();
}
QStyledItemDelegate::paint( painter, option, index );
}
DecorativeHeaderView::DecorativeHeaderView( Qt::Orientation orientation, QWidget* parent /* = 0 */ )
: QHeaderView( orientation, parent )
, filteredLogicalIndex_( -1 )
{
setItemDelegate( new HeaderDelegate( this ) );
}
Delegate created, but function did not start the paint() method!
Any help?
Thanks!
It Seems like it isn't possible at this time.
From Qt documentation:
Note: Each header renders the data for each section itself, and does not rely on a delegate. As a result, calling a header's setItemDelegate() function will have no effect.
How to have dynamic pixmap in a QHeaderView item
But you may override QHeaderView::paintSection method.
subclassing-QHeaderView
class HeaderView : public QHeaderView {
Q_OBJECT
public:
HeaderView(Qt::Orientation orientation, QWidget * parent = 0)
: QHeaderView(orientation, parent), p("333222.jpeg") {
}
int sectionSizeHint ( int /*logicalIndex*/ ) const { return p.width(); }
protected:
void paintSection(QPainter * painter, const QRect & rect, int /*logicalIndex*/) const {
painter->drawPixmap(rect, p);
}
private:
QPixmap p;
};

QStyledItemDelegate - How does updateEditorGeometry works?

I'm using Qt 4.7.
I have a model that I display in a QTableView in two columns, and my goal is to provide inline editing of this model in my QTableView.
+-----------------+----------------+
| Axis position | Axis range |
+-----------------+----------------+
| Left | Fixed [0,1] |
| Left | Source: SRC1 |
| Right | Source: SRC2 |
| Left | Fixed [5,10] |
+-----------------+----------------+
The first column is editable by using a simple QComboxBox to switch between Right and Left, and it works quite well. The problem is with my second column, which is editable using a custom widget.
This widget is kind of simple, it describes a range. So there is a QComboBox to select the kind of range ("Fixed": the values are given by the user, "Source": the value are adjusted dynamically from the min/max of a source).
Here is the source code of my custom widget:
class RangeEditor : public QWidget
{
Q_OBJECT
public:
RangeEditor( ... );
~RangeEditor();
public:
CurveView::ConfigAxes::Range range () const;
QVariant minimum() const;
QVariant maximum() const;
DataModel* model () const;
void range ( CurveView::ConfigAxes::Range range );
void minimum( QVariant minimum );
void maximum( QVariant maximum );
void model ( DataModel* model );
public slots:
void rangeTypeChanged( int type );
private: // --- External editors
QComboBox* editRange_;
QSpinBox* editMinimum_;
QSpinBox* editMaximum_;
QComboBox* editModel_;
};
RangeEditor::RangeEditor( ... ) : QWidget(parent)
{
editRange_ = new QComboBox(this);
editMinimum_ = new QSpinBox (this);
editMaximum_ = new QSpinBox (this);
editModel_ = new QComboBox(this);
QHBoxLayout* layout = new QHBoxLayout();
setLayout(layout);
layout->addWidget( editRange_ );
layout->addWidget( editMinimum_ );
layout->addWidget( editMaximum_ );
layout->addWidget( editModel_ );
editRange_->addItem( "Fixed" );
editRange_->addItem( "Source" );
editModel_->setCurrentIndex(0);
editModel_->hide();
QObject::connect( editRange_, SIGNAL(currentIndexChanged(int)),
this, SLOT (rangeTypeChanged(int)) );
}
void RangeEditor::rangeTypeChanged( int type )
{
if ( type==CurveView::ConfigAxes::FIXED )
{
editMinimum_->show();
editMaximum_->show();
editModel_->hide();
}
else if ( type==CurveView::ConfigAxes::SOURCE )
{
editMinimum_->hide();
editMaximum_->hide();
editModel_->show();
}
}
Okay, so now, I created a QStyledItemDelegate to provide the view a custom editor for my columns. Here is how I did it:
class ConfigAxesDelegate : public QStyledItemDelegate
{
public:
ConfigAxesDelegate( ... );
~ConfigAxesDelegate();
public:
virtual QWidget* createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
virtual void setEditorData ( QWidget* editor, const QModelIndex& index ) const;
virtual void setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
};
QWidget* ConfigAxesDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
if ( index.column()==0 ) // Position
{
PositionEditor* editor = new PositionEditor(parent);
return editor;
}
else if ( index.column()==1 ) // Range
{
RangeEditor* editor = new RangeEditor(parent);
return editor;
}
else
{
return QStyledItemDelegate::createEditor(parent,option,index);
}
}
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// WHAT TO DO HERE?
editor->setGeometry( option.rect );
}
Basically, what I get is a single pixel height editor.
Here is a screenshot of the result:
I tried to changed updateEditorGeometry to the following:
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QRect r = option.rect;
r.setSize( editor->sizeHint() );
editor->setGeometry( r );
}
Which seems to fix the size problem, but not the position:
I feel kind of lost since I don't know if the problem comes from my custom widget (not providing enough information for Qt to compute its position properly), or the view (maybe some margins that would crush the editor size), or the updateEditorGeometry() method.
Any help greatly appreciated, thanks for reading!
I would say setting editor's geometry by calling:
editor->setGeometry(rect);
should work correctly; What happens in your case is that your editor is built using QHBoxLayout which has default margins and spacing set. Default height of your tableview rows is less then editor's height and this makes your editor to resize; one pixel row on your screen shot would be: top margin + what's left from controls + bottom margin.
By enabling the vertical header for your tableview you would be able to resize row height to make your editor controls completely visible.
What you could possibly do:
1.Remove\decrease spacing and margins for the layout:
QHBoxLayout* layout = new QHBoxLayout();
layout->setSpacing(1);
layout->setMargin(1);
setLayout(layout);
in this case, updating editor's geometry this way:
QRect rect = option.rect;
QSize sizeHint = editor->sizeHint();
if (rect.width()<sizeHint.width()) rect.setWidth(sizeHint.width());
if (rect.height()<sizeHint.height()) rect.setHeight(sizeHint.height());
editor->setGeometry(rect);
or just
editor->setGeometry(rect);
should work fine for you
2.You can also consider using popup editors for your rows\cells values
3.Resize widget's row heights to fit cell editors.
hope this helps, regards

Resources