I am new at Qt and I want to write my custom QGraphicsItem which contains a rectangle and couple of buttons. I want to write a single custom component that could be easily added to QGraphicsScene and moved or resized with contents(buttons and rectangles) in it. In the end I want to add multiple customized QGraphicsItem to my QGraphicsScene. My question is how can I write this customized QGraphicsItem that contains buttons and rectangles which relative positions to each other are constant.
In this drawing green colored rectangles represent buttons and their relative position to each other always stays same (as if they are placed using qlayouts)
Thanks to #replete, from the example at http://doc.qt.io/qt-5/qtwidgets-graphicsview-dragdroprobot-example.html I was able to create a custom QGraphicsItem with clickable sub-parts in it. In code below BboxItem represents container QGraphicsItem and BboxItemContent represents childs of it. By emitting signals whith mause click events I was able to implement button like features. And I can move the BboxItem by setting its bounding rectangle.
BboxItem related source code:
BboxItemContent::BboxItemContent(QGraphicsItem *parent, int type, QColor color,QRectF *rect)
: QGraphicsObject(parent)
{
content_rectangle = rect;
content_type = type;
switch (type)
{
case 0:
rectangle_color = color;
icon = 0;
break;
case 1:
icon = new QImage(":/resource/assets/info_btn.png");
break;
case 2:
icon = new QImage(":/resource/assets/close_btn.png");
break;
}
}
BboxItemContent::~BboxItemContent()
{
delete icon;
}
QRectF BboxItemContent::boundingRect() const
{
return QRectF(content_rectangle->x(), content_rectangle->y(), content_rectangle->width(), content_rectangle->height());
}
void BboxItemContent::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (icon == 0)
{
QPen pen(rectangle_color, 3);
painter->setPen(pen);
painter->drawRect(*content_rectangle);
}
else
{
painter->drawImage(*content_rectangle, *icon);
}
}
void BboxItemContent::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
emit bboxContentClickedSignal();
}
void BboxItemContent::setRect(QRectF *rect)
{
content_rectangle = rect;
update();
}
BboxItem::BboxItem(QGraphicsItem *parent,QRectF *itemRect) : BboxItemContent(parent,0,Qt::red, itemRect)
{
setFlag(ItemHasNoContents);
bbox_area = new BboxItemContent(this, 0, Qt::red, itemRect);
info_btn = new BboxItemContent(this, 1, Qt::red, new QRectF(itemRect->x() - 30, itemRect->y(), 30, 30));
connect(info_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onInfoClickedSlot);
delete_btn= new BboxItemContent(this, 2, Qt::red, new QRectF((itemRect->x()+itemRect->width()), itemRect->y(), 30, 30));
connect(delete_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onDeleteClickedSlot);
}
void BboxItem::onDeleteClickedSlot()
{
//delete clicked actions
}
void BboxItem::onInfoClickedSlot()
{
//info clicked actions
}
void BboxItem::setRect(QRectF *rect)
{
bbox_area->setRect(rect);
info_btn->setRect(new QRectF(rect->x() - 30, rect->y(), 30, 30));
delete_btn->setRect(new QRectF((rect->x() + rect->width()), rect->y(), 30, 30));
}
Related Headers:
class BboxItemContent : public QGraphicsObject
{
Q_OBJECT
public:
BboxItemContent(QGraphicsItem *parent = 0, int type = 0, QColor color = Qt::red, QRectF *rect=nullptr);
~BboxItemContent();
// Inherited from QGraphicsItem
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
void setRect(QRectF *rect);
signals:
void bboxContentClickedSignal();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
private:
QImage *icon;
QColor rectangle_color;
QRectF *content_rectangle;
int content_type;
};
class BboxItem : public BboxItemContent {
Q_OBJECT
public:
BboxItem(QGraphicsItem *parent = 0,QRectF *itemRect=nullptr);
void setRect(QRectF *rect);
private slots:
void onDeleteClickedSlot();
void onInfoClickedSlot();
private:
BboxItemContent *delete_btn;
BboxItemContent *bbox_area;
BboxItemContent *info_btn;
};
Related
In my code below, qDebug() inside of the paintSection is being called, however, the QPixmap is not being drawn into the header column.
Reproducible example:
class HeaderView : public QHeaderView
{
Q_OBJECT
public:
HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr)
: QHeaderView(orientation, parent)
{
}
QPixmap pixmap = QPixmap(":/files/icon.png");
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
{
switch (logicalIndex)
{
case 0:
{
painter->drawPixmap(rect.x(), rect.y(), rect.width(), rect.height()
, pixmap);
//painter->fillRect(rect, QColor(255, 255, 255));
qDebug() << rect;
break;
}
default:
break;
}
QHeaderView::paintSection(painter, rect, logicalIndex);
}
};
class TreeView : public QTreeView
{
Q_OBJECT
public:
QStandardItemModel model;
HeaderView* headerView;
TreeView(QWidget* parent = 0) : QTreeView(parent)
{
setModel(&model);
setIndentation(0);
setUniformRowHeights(true);
setRootIsDecorated(false);
setSortingEnabled(true);
headerView = new HeaderView(Qt::Horizontal);
setHeader(headerView);
model.setHorizontalHeaderItem(0, new QStandardItem("column0"));
model.setHorizontalHeaderItem(1, new QStandardItem("column1"));
model.setHorizontalHeaderItem(2, new QStandardItem("column2"));
header()->setDefaultAlignment(Qt::AlignCenter);
header()->setMinimumSectionSize(100);
}
};
#include "treeview.h"
Application::Application(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
TreeView* treeView = new TreeView(this);
}
While searching about, I found that the painter needs to be saved/restored to make it work:
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
switch (logicalIndex)
{
case 0:
{
painter->drawPixmap(rect, pixmap);
break;
}
default:
break;
}
}
I am trying to achieve rubber band rectangle zoom in. But here I need to zoom in only the part which comes under rubber band rectangle. I do not want to zoom in the whole view.
For that I was told to create rubber band rectangle whose point I will know. And then use
void QGraphicsView::ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50)
or
void QGraphicsView::centerOn(qreal x, qreal y)
So just to understand, what this ensureVisible() does, I called it with some points which are present in my design. But just a pop up windows appeared but nothing was there to display. So I left that task and concentrated on, how to get rubber band rectagle points. So I tried to add some mouse events in my existing sample project but I got following error.
schematicdesign.cpp:75:47: error: non-static member 'mapToScene' found
in multiple base-class subobjects of type 'QGraphicsItem': class
SchematicDesign -> class QGraphicsRectItem -> class
QAbstractGraphicsShapeItem -> class QGraphicsItem class
SchematicDesign -> class QGraphicsPathItem -> class
QAbstractGraphicsShapeItem -> class QGraphicsItem
qgraphicsitem.h:360:13: note: member found by ambiguous name lookup
Such error is not only for mapToScene() but also for scene() and transform().
What I understood from the error is :
I have inherited schematicDesign class from multiple classes (
QGraphicsRectItem and QGraphicsPathItem ) so when it tries to find the
definition of mapToScene() or scene() or transform(), it finds in
multiple classes so gets confused in which one to pick. ( please
correct if I am wrong )
Here is my code:
Widget.h
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_schematicButoon_clicked();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
view = new QGraphicsView(this);
view->setScene(scene);
ui->verticalLayout_2->addWidget(view);
}
void Widget::on_schematicButoon_clicked()
{
SchematicDesign* sd1 = new SchematicDesign();
QGraphicsRectItem* clkPin = sd1->createRect(20,160,20,20);
scene->addItem(clkPin);
// some more createRect() method
QPen mPen;
mPen.setWidth(3);
mPen.setBrush(Qt::yellow);
QPolygonF clknet;
clknet<< QPointF (40,170) << QPointF (186,170);
QGraphicsPathItem* clk = sd1->drawPolyline(clknet);
scene->addItem(clk);
// some more drawPolyline() method.
QGraphicsPathItem* text = sd1->addText(i1Instance,275.0,138.0,"I1");
scene->addItem(text);
// some more addText() method
}
SchematicDesign.h
class SchematicDesign : public QGraphicsRectItem,QGraphicsPathItem
{
public:
explicit SchematicDesign();
explicit SchematicDesign(qreal x, qreal y, qreal width, qreal height,QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(x, y, width, height, parent)
{}
explicit SchematicDesign(QPainterPath pPath);
signals:
public:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
QGraphicsRectItem* createRect(qreal x, qreal y, qreal width, qreal height);
QGraphicsPathItem* drawPolyline(QPolygonF poly);
QGraphicsPathItem* addText(QGraphicsRectItem *rect, double d1, double d2, QString s);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
private:
QPointF selectTopLeft;
bool drawingSelection;
QGraphicsRectItem* lastRect;
public:
QPoint rubberBandOrigin;
bool rubberBandActive;
};
#endif // SCHEMATICDESIGN_H
SchematicDesign.cpp
SchematicDesign::SchematicDesign(){}
SchematicDesign::SchematicDesign(QPainterPath pPath)
{}
void SchematicDesign::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
QGraphicsRectItem::paint(painter, &copied_option, widget);
QGraphicsPathItem::paint(painter, &copied_option, widget);
if (selected)
{
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(QGraphicsRectItem::shape());
painter->drawPath(QGraphicsPathItem::shape());
painter->restore();
}
}
QGraphicsRectItem *SchematicDesign::createRect(qreal x, qreal y, qreal width, qreal height)
{...... }
QGraphicsPathItem* SchematicDesign::drawPolyline(QPolygonF poly)
{....}
QGraphicsPathItem* SchematicDesign::addText(QGraphicsRectItem *rect, double d1, double d2, QString s)
{.....}
void SchematicDesign::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem * sceneItem = scene()->itemAt(mapToScene(event->pos()),transform()); //error
if(!sceneItem){
selectTopLeft = event->pos();
event->pos();
drawingSelection = true;
}
QGraphicsRectItem::mousePressEvent(event);
}
void SchematicDesign::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(drawingSelection){
QGraphicsItem * itemToRemove = lastRect;
if(itemToRemove){
fitInView(itemToRemove,Qt::KeepAspectRatio); //error at fitInView : Use of undeclared identifier
scene()->removeItem(itemToRemove); //error at scene()
delete itemToRemove;
lastRect = nullptr;
}
}
drawingSelection = false;
QGraphicsRectItem::mouseReleaseEvent(event);
}
void SchematicDesign::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(drawingSelection){
//Selection region
int x = selectTopLeft.rx();
int y = selectTopLeft.ry();
int h = event->pos().rx();
int w = event->pos().ry();
//QRect selectRegion = QRect(selectTopLeft,event->pos());
QRect selectRegion = QRect(x,y,h,w);
QPainterPath path;
path.addRect(selectRegion);
scene()->setSelectionArea(mapToScene(path)); //error at scene()
//Draw visual feedback for the user
QGraphicsItem * itemToRemove = lastRect;
scene()->removeItem(itemToRemove); // error at scene()
lastRect = scene()->addRect(QRectF(mapToScene(selectTopLeft),
mapToScene(event->pos())).normalized()); // error at scene() and mapToScene()
lastRect->setBrush(QBrush(QColor(255, 0, 0, 50)));
delete itemToRemove;
}
QGraphicsRectItem::mouseMoveEvent(event);
}
How to tackle such type of error ? Should not I inherit my class from multiple classes ?
I'd like to create a vertical button in Qt (using C++, not Python), with text rotated 90ยบ either clockwise or counterclockwise. It doesn't seem to be possible with a standard QPushButton.
How could I do it?
In order to create a vertical button in Qt, you can subclass QPushButton so that the dimensions reported by the widget are transposed, and also modify the drawing event to paint the button with the proper alignment.
Here's a class called OrientablePushButton that can be used as a drop-in replacement of the traditional QPushButton but also supports vertical orientation through the usage of setOrientation.
Aspect:
Sample usage:
auto anotherButton = new OrientablePushButton("Hello world world world world", this);
anotherButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
anotherButton->setOrientation(OrientablePushButton::VerticalTopToBottom);
Header file:
class OrientablePushButton : public QPushButton
{
Q_OBJECT
public:
enum Orientation {
Horizontal,
VerticalTopToBottom,
VerticalBottomToTop
};
OrientablePushButton(QWidget * parent = nullptr);
OrientablePushButton(const QString & text, QWidget *parent = nullptr);
OrientablePushButton(const QIcon & icon, const QString & text, QWidget *parent = nullptr);
QSize sizeHint() const;
OrientablePushButton::Orientation orientation() const;
void setOrientation(const OrientablePushButton::Orientation &orientation);
protected:
void paintEvent(QPaintEvent *event);
private:
Orientation mOrientation = Horizontal;
};
Source file:
#include <QPainter>
#include <QStyleOptionButton>
#include <QDebug>
#include <QStylePainter>
OrientablePushButton::OrientablePushButton(QWidget *parent)
: QPushButton(parent)
{ }
OrientablePushButton::OrientablePushButton(const QString &text, QWidget *parent)
: QPushButton(text, parent)
{ }
OrientablePushButton::OrientablePushButton(const QIcon &icon, const QString &text, QWidget *parent)
: QPushButton(icon, text, parent)
{ }
QSize OrientablePushButton::sizeHint() const
{
QSize sh = QPushButton::sizeHint();
if (mOrientation != OrientablePushButton::Horizontal)
{
sh.transpose();
}
return sh;
}
void OrientablePushButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStylePainter painter(this);
QStyleOptionButton option;
initStyleOption(&option);
if (mOrientation == OrientablePushButton::VerticalTopToBottom)
{
painter.rotate(90);
painter.translate(0, -1 * width());
option.rect = option.rect.transposed();
}
else if (mOrientation == OrientablePushButton::VerticalBottomToTop)
{
painter.rotate(-90);
painter.translate(-1 * height(), 0);
option.rect = option.rect.transposed();
}
painter.drawControl(QStyle::CE_PushButton, option);
}
OrientablePushButton::Orientation OrientablePushButton::orientation() const
{
return mOrientation;
}
void OrientablePushButton::setOrientation(const OrientablePushButton::Orientation &orientation)
{
mOrientation = orientation;
}
I was trying to do a simple animation on QGrapichScene. I implemented void QGraphicsItem::advance(int) in class, that inherites QGraphicsItem, but after calling advance() my item not redrawn. In colliding mice example it works.
What have I done wrong?
Here is my code:
widget.h:
class Widget : public QWidget
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsView *view;
QHBoxLayout *layout;
QTimer t;
public:
Widget(QWidget *parent = 0);
~Widget();
};
widget.cpp:
Widget::Widget(QWidget *parent) : QWidget(parent)
{
layout = new QHBoxLayout(this);
view = new QGraphicsView(this);
scene = new QGraphicsScene(0, 0, 400, 400, view);
scene->addItem(new MyItem());
view->setScene(scene);
layout->addWidget(view);
setLayout(layout);
connect(&t, SIGNAL(timeout()), scene, SLOT(advance()));
t.start(100);
}
Widget::~Widget()
{
}
my_item.h:
class MyItem : public QGraphicsItem
{
private:
QRect bRect;
enum directon { left, right };
directon currentDir;
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
public:
MyItem(int w = 20);
virtual void advance(int phase);
virtual QRectF boundingRect() const
{ return QRectF(bRect); }
};
my_item.cpp:
MyItem::MyItem(int w)
{
currentDir = right;
bRect = QRect(0, 0, w, w);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qDebug() << "In void MyItem::paint(QPainter*, "
"const QStyleOptionGraphicsItem*, "
"QWidget*)";
painter->fillRect(bRect, Qt::red);
}
void MyItem::advance(int phase)
{
qDebug() << "In void MyItem::advance(int);"
<< "Phase =" << phase;
if(!phase)
return;
// Than move item to new positon...
}
It's on you to inform the graphics scene that your item's contents have changed. You have to call update() at the end of advance(). If you're simply moving the item, without changing its contents, then you don't need to call update() of course - the scene will detect such changes automatically.
I found something strange:
connect(&timer, SIGNAL(timeout()), scene, SLOT(advance()));
The slot need to combined after the Widget is shown, or the slot will never been called!
As the Qt example shown, you can combine it in the main function.
This is the same question as in:
How to change text alignment in QTabWidget?
I tried to port that python code into C++ but it doesn't seem to work.
Here is header file:
#include <QTabBar>
class HorizontalTabWidget : public QTabBar
{
Q_OBJECT
public:
explicit HorizontalTabWidget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
QSize sizeHint() const;
};
Here is source file:
void HorizontalTabWidget::paintEvent(QPaintEvent *)
{
for(int index = 0; index < count(); index++)
{
QPainter * painter = new QPainter(this);
painter->begin(this);
painter->setPen(Qt::blue);
painter->setFont(QFont("Arial", 10));
QRect tabrect = tabRect(index);
painter->drawText(tabrect, Qt::AlignVCenter | Qt::TextDontClip, tabText(index));
painter->end();
}
}
QSize HorizontalTabWidget::sizeHint() const
{
return QSize(130, 130);
}
I use it by creating NewTabWidget class that inherits QTabWidget. In the constructor of NewTabWidget I use:
setTabBar(new HorizontalTabWidget);
This is done just to be able to use that tabWidget because setTabBar is protected. Here is what I get:
What am I missing?
Edit:
What I want to create is this but with icons on the top and the labels under the icons (as in Qt Creator):
the problem should be in the paint method; check if an example below would work for you, it should draw tabs the same way QTCreator does. I've reused the original tab style QStyleOptionTabV3 to do majority of paint work and then just rendered icon and tab's text on top of the image it produced:
class TestTabBar : public QTabBar
{
public:
explicit TestTabBar(QWidget* parent=0) : QTabBar(parent)
{
setIconSize(QSize(80, 80));
}
protected:
QSize tabSizeHint(int) const
{
return QSize(80, 80);
}
void paintEvent(QPaintEvent *)
{
QStylePainter p(this);
for (int index = 0; index < count(); index++)
{
QStyleOptionTabV3 tab;
initStyleOption(&tab, index);
QIcon tempIcon = tab.icon;
QString tempText = tab.text;
tab.icon = QIcon();
tab.text = QString();
p.drawControl(QStyle::CE_TabBarTab, tab);
QPainter painter;
painter.begin(this);
QRect tabrect = tabRect(index);
tabrect.adjust(0, 8, 0, -8);
painter.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tempText);
tempIcon.paint(&painter, 0, tabrect.top(), tab.iconSize.width(), tab.iconSize.height(), Qt::AlignTop | Qt::AlignHCenter);
painter.end();
}
}
};
class TestTabWidget : public QTabWidget
{
public:
explicit TestTabWidget(QWidget *parent = 0) : QTabWidget(parent)
{
setTabBar(new TestTabBar());
}
};
tabwidget init:
TestTabWidget* test = new TestTabWidget(this);
test->setGeometry(20, 20, 300, 200);
test->addTab(new QWidget(), QIcon("icon0.png"), "test0");
test->addTab(new QWidget(), QIcon("icon1.png"), "test1");
test->setTabPosition(QTabWidget::West);
this worked fine on my ubuntu, hope it gonna work for you,
regards
Vasiliy, thanks for fixing the double QPainter bug.
However, calling setTabIcon() and setTabText() from within paintEvent() leads to an infinite recursion. Remember that tab is a local object, so
tab.text = QString();
does not affect tabText().
So, the example can also be written without making temporary copies and do
p.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tabText(index));
tabIcon(index).paint(&p, tabrect, Qt::AlignTop | Qt::AlignHCenter);
This example does not work. leads to a fall program.
bring your own example with minor edits
- my system qt 4.6.3 for Windows and VS2008
class TestTabBar : public QTabBar
{
public:
explicit TestTabBar(QWidget* parent=0) : QTabBar(parent)
{
setIconSize(QSize(58, 68));
}
protected:
QSize tabSizeHint(int) const
{
return QSize(58, 68);
}
void paintEvent(QPaintEvent *)
{
QStylePainter p(this);
for (int index = 0; index < count(); index++)
{
QStyleOptionTabV3 tab;
initStyleOption(&tab, index);
QIcon tempIcon = tabIcon(index);
QString tempText = this->tabText(index);
QRect tabrect = tabRect(index);
tab.icon = QIcon();
tab.text = QString();
p.drawControl(QStyle::CE_TabBarTab, tab);
tabrect.adjust(0, 3, 0, -3);
p.setPen(Qt::black);
p.setFont(QFont("Arial", 7));
p.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tempText );
tempIcon.paint(&p, tabrect, Qt::AlignTop | Qt::AlignHCenter);
this->setTabIcon(index, tempIcon );
this->setTabText( index, tempText);
}
}
};
class TestTabWidget : public QTabWidget
{
public:
explicit TestTabWidget(QWidget *parent = 0) : QTabWidget(parent)
{
setTabBar(new TestTabBar());
}
};