I have a QGraphicsPixmapItem that rotates through different pixmaps to simulate animation. I need to accurately implement the shape() function so the scene can properly determine collision with other objects. Each pixmap obviously has slightly different collision paths. Is there a simple way to create a QPainterPath from a pixmap by outlining the colored pixels of the actual image that border the alpha background of the bounding rect without having to write my own complex algorithm that tries to create that path manually?
I plan on having these paths pre-drawn and cycle through them the same way I do as the pixmaps.
You can use QGraphicsPixmapItem::setShapeMode() with either QGraphicsPixmapItem::MaskShape or QGraphicsPixmapItem::HeuristicMaskShape for this:
#include <QtGui>
#include <QtWidgets>
class Item : public QGraphicsPixmapItem
{
public:
Item() {
setShapeMode(QGraphicsPixmapItem::MaskShape);
QPixmap pixmap(100, 100);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setBrush(Qt::gray);
painter.setPen(Qt::NoPen);
painter.drawEllipse(0, 0, 100 - painter.pen().width(), 100 - painter.pen().width());
setPixmap(pixmap);
}
enum { Type = QGraphicsItem::UserType };
int type() const {
return Type;
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsView view;
view.setScene(new QGraphicsScene());
Item *item = new Item();
view.scene()->addItem(item);
// Comment out to see the item.
QGraphicsPathItem *shapeItem = view.scene()->addPath(item->shape());
shapeItem->setBrush(Qt::red);
shapeItem->setPen(Qt::NoPen);
view.show();
return app.exec();
}
Related
The question is simple ! I want something like this. Either using QPainter class or using Qt Graphics Framework:
There are several ways to do this using a QPainterPath specified here.
Here is the second example from that page:
#include <QtGui>
#include <cmath>
class Widget : public QWidget
{
public:
Widget ()
: QWidget() { }
private:
void paintEvent ( QPaintEvent *)
{
QString hw("hello world");
int drawWidth = width() / 100;
QPainter painter(this);
QPen pen = painter.pen();
pen.setWidth(drawWidth);
pen.setColor(Qt::darkGreen);
painter.setPen(pen);
QPainterPath path(QPointF(0.0, 0.0));
QPointF c1(width()*0.2,height()*0.8);
QPointF c2(width()*0.8,height()*0.2);
path.cubicTo(c1,c2,QPointF(width(),height()));
//draw the bezier curve
painter.drawPath(path);
//Make the painter ready to draw chars
QFont font = painter.font();
font.setPixelSize(drawWidth*2);
painter.setFont(font);
pen.setColor(Qt::red);
painter.setPen(pen);
qreal percentIncrease = (qreal) 1/(hw.size()+1);
qreal percent = 0;
for ( int i = 0; i < hw.size(); i++ ) {
percent += percentIncrease;
QPointF point = path.pointAtPercent(percent);
qreal angle = path.angleAtPercent(percent); // Clockwise is negative
painter.save();
// Move the virtual origin to the point on the curve
painter.translate(point);
// Rotate to match the angle of the curve
// Clockwise is positive so we negate the angle from above
painter.rotate(-angle);
// Draw a line width above the origin to move the text above the line
// and let Qt do the transformations
painter.drawText(QPoint(0, -pen.width()),QString(hw[i]));
painter.restore();
}
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Widget widget;
widget.show();
return app.exec();
}
How would I go about drawing a rectangle?
I have tried two different ways;
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Which works fine (even though the parameter is not named nor used), but I don't want to use the QPaintEvent * I have no use for it.
So I tried just renaming my function;
void MyWidget::draw()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
QRect rect = QRect(290, 20, 70, 40);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
This doesn't display anything (yet has no errors).
Why would it not work if I don't use QPaintEvent * ??
The paint event is the method that is called by the paint system when a widget needs to be redrawn. That is why simply naming your own method does not work. It is never called by the paint system.
You really should be using the QPaintEvent. It gives you the rect that needs to be drawn. This rect will be based upon the size of the widget, so instead of using an explicit rect in your paint event, set your widget to the right size. A paint event will be generated should your widget ever move, resize, etc.
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
Now if you want to separate your paint logic into another method, that is fine. But you would need to have it called from the paint event:
void MyWidget::paintEvent(QPaintEvent *event)
{
QRect rect = event->rect();
draw(rect);
}
void MyWidget::draw(QRect &rect)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawText(rect, Qt::AlignCenter,
"Data");
painter.drawRect(rect);
}
If you want to completely bypass the paint events as you said, and only want to create a static rectangle to display, one way is to just draw it once to a pixmap and display it in a QLabel:
QPixMap pix(200,100);
QPainter painter(&pix);
// do paint operations
painter.end()
someLabel.setPixmap(pix)
Any data that your paintEvent() needs should be accessible as fields of the containing class, in your case, private fields of MyWidget. These private fields can be exposed to clients of MyWidget via "setters" which would set the data values before calling update() on MyWidget which will trigger a call to paintEvent().
This playlist contains the best Qt tutorials , starting tutorial 74 would be useful for you (Qpainter and QPen), tutorial 75 is how to draw rectangles using QRect.
As well #Mat told you: the "event" is the correct way to launch a painter.
QPainter can only be evoked after a QPaintEvent event, which carries the safe region where the object may be drawn.
So you must find another strategy to transport your data, to help
I will propose a method simple, which can be adjusted to many cases.
widget.cpp
#include <QtGui>
#include "widget.h"
#define MIN_DCX (0.1)
#define MAX_DCX (5.0)
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
dcx=MIN_DCX;
setFixedSize(170, 100);
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
pcx=dcx*2;
QRect rect = QRect(50-dcx,25-dcx,60+pcx,40+pcx);
painter.drawText(rect, Qt::AlignCenter,printData);
painter.drawRect(rect);
painter.end();
}
void Widget::setPrintData(QString value){
printData = value;
dcx=(dcx>MAX_DCX)?MIN_DCX:dcx+MIN_DCX;
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent);
void setPrintData(QString value);
protected:
void paintEvent(QPaintEvent *event);
private:
QString printData;
float dcx;
float pcx;
};
#endif
window.cpp
#include <QtGui>
#include "widget.h"
#include "window.h"
#define MAX_SDCX 20
Window::Window()
: QWidget()
{
gobject = new Widget(this);
textMode=1;
rectMode=1;
gobject->setPrintData(msgs[textMode]);
QGridLayout *layout = new QGridLayout;
layout->addWidget(gobject, 0, 0);
setLayout(layout);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(dataOnAir()));
timer->start(10);
setWindowTitle(tr("Rect Shaking"));
}
void Window::dataOnAir(){
if((++rectMode)>MAX_SDCX){
rectMode=0;
textMode^=1;
}
gobject->setPrintData(msgs[textMode]);
gobject->repaint();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "widget.h"
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private slots:
void dataOnAir();
private:
Widget *gobject;
const QString msgs[2] = {"Hello","World"};
int textMode;
int rectMode;
};
#endif
main.cpp
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
As you can see in the code is executed a timer, outside the object "widget"
every 10ms sends a repaint the widget to redraw a "rect" with a different size and every 20 cycles (200ms) changes the text "hello" for "world"
In this example you can see that in any way need overwrite the QPainterDevice architecture.
You may also notice that the "event" within the "paintEvent" is silenced and not used directly, but it is essential to execute a sequence QPainter.
Overriding the paintEvent() function of a widget enables you to customize the widget and this function is called periodically to redraw the widget. Therefore any drawing should be made in this function. However overriding paintEvent() may cause some performance issues. I would prefer using a QGraphicsScene and QGraphicsView then I would add a rectangle to the scene which is the common way of doing this kind of drawing stuff. Please check the GraphicsView Framework
http://qt-project.org/doc/qt-4.8/graphicsview.html
I want to draw polygons in a QGraphicsScene but where the polygons has latitude/longitude positions. In a equirectangular projection the coordinates goes from:
^
90
|
|
-180----------------------------------->180
|
|
-90
How can I set the QGraphicsScene / QGraphicsView to such projection?
Many thanks,
Carlos.
Use QGraphicsScene::setSceneRect() like so:
scene->setSceneRect(-180, -90, 360, 180);
If you're concerned about the vertical axis being incorrectly flipped, you have a few options for how to deal with this. One way is to simply multiply by -1 whenever you make any calculation involving the y coordinate. Another way is to vertically flip the QGraphicsView, using view->scale(1, -1) so that the scene is displayed correctly.
Below is a working example that uses the latter technique. In the example, I've subclassed QGraphicsScene so that you can click in the view, and the custom scene will display the click position using qDebug(). In practice, you don't actually need to subclass QGraphicsScene.
#include <QtGui>
class CustomScene : public QGraphicsScene
{
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << event->scenePos();
}
};
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
QGraphicsScene *scene = new CustomScene;
QGraphicsView *view = new QGraphicsView(this);
scene->setSceneRect(-180, -90, 360, 180);
view->setScene(scene);
view->scale(1, -1);
setCentralWidget(view);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
I have a QPainter, and a rectangle.
i'd like to draw a QLineEdit control, empty. Just to draw it, not to have a live control. How do I do that? I have tried QStyle::drawPrimitive to no avail. nothing gets drawn.
QStyleOption option1;
option1.init(contactsView); // contactView is the parent QListView
option1.rect = option.rect; // option.rect is the rectangle to be drawn on.
contactsView->style()->drawPrimitive(QStyle::PE_FrameLineEdit, &option1, painter, contactsView);
Naturally, i'd like the drawn dummy to look native in Windows and OSX.
Your code is pretty close, but you would have to initialize the style from a fake QLineEdit. The following is based on QLineEdit::paintEvent and QLineEdit::initStyleOption.
#include <QtGui>
class FakeLineEditWidget : public QWidget {
public:
explicit FakeLineEditWidget(QWidget *parent = NULL) : QWidget(parent) {}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
QLineEdit dummy;
QStyleOptionFrameV2 panel;
panel.initFrom(&dummy);
panel.rect = QRect(10, 10, 100, 30); // QFontMetric could provide height.
panel.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth,
&panel,
&dummy);
panel.midLineWidth = 0;
panel.state |= QStyle::State_Sunken;
panel.features = QStyleOptionFrameV2::None;
style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &painter, this);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
FakeLineEditWidget w;
w.setFixedSize(300, 100);
w.show();
return app.exec();
}
I have a Qt dialog and there is a slider in it, when the dialog is initialized the slider will be set a value. In order to remind the user what is the default value, I want to add a mark to the slider, just draw a line or a triangle above the handle. Here, the slider should be of QSlider type, that means I can't implement a customized control derived from QSlider. Is there any way to realize it ?
I'm not clear why you can't derive a control from QSlider. You can still treat it like a QSlider, just override the paintEvent method. The example below is pretty cheesy, visually speaking, but you could use the methods from QStyle to make it look more natural:
#include <QtGui>
class DefaultValueSlider : public QSlider {
Q_OBJECT
public:
DefaultValueSlider(Qt::Orientation orientation, QWidget *parent = NULL)
: QSlider(orientation, parent),
default_value_(-1) {
connect(this, SIGNAL(valueChanged(int)), SLOT(VerifyDefaultValue(int)));
}
protected:
void paintEvent(QPaintEvent *ev) {
int position = QStyle::sliderPositionFromValue(minimum(),
maximum(),
default_value_,
width());
QPainter painter(this);
painter.drawLine(position, 0, position, height());
QSlider::paintEvent(ev);
}
private slots:
void VerifyDefaultValue(int value){
if (default_value_ == -1) {
default_value_ = value;
update();
}
}
private:
int default_value_;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
DefaultValueSlider *slider = new DefaultValueSlider(Qt::Horizontal);
slider->setValue(30);
QWidget *w = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(slider);
layout->addStretch(1);
w->setLayout(layout);
QMainWindow window;
window.setCentralWidget(w);
window.show();
return app.exec();
}
#include "main.moc"
Easiest way I can think off is:
Add QSlider to QSlider (like you do it with layouts and QFrames). Slider above will be your current slider (clickable one). Slider below will be your "default tick position" value.
#include <QApplication>
#include <QSlider>
#include <QVBoxLayout>
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
QSlider * defaultValueSlider = new QSlider();
QSlider * valueSlider = new QSlider(defaultValueSlider);
QVBoxLayout * lay = new QVBoxLayout(defaultValueSlider);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
lay->addWidget(valueSlider);
defaultValueSlider->setRange(0, 100);
valueSlider->setRange(0, 100);
defaultValueSlider->setValue(30);
defaultValueSlider->show();
return app.exec();
}
Why do you need to inherit a QSlider to access its public methods?
http://doc.trolltech.com/4.7/qslider.html
You can just call its setTickPosition() in your app.