I am looking to have a customized progress bar whose progress changes via a custom animation. I will have quite a number of instances of this widget and all of them should run smoothly and fast.
My first attempt was to use a regular QProgressBar, customize it by using a stylesheet and then using QPropertyAnimation to animate the change in status.
This works fine but is extremely slow. Say, I start my animation at a value of 0% and go up to 50% and want this to be accomplished during a duration of 500 msec. It is not smooth at all, but there are three clearly distinguishable steps. If I drop the stylesheet, it will work smoothly enough.
Well, what seems to work fine is using a derived class of QProgressBar, it is much faster than using stylesheets, although I have to custom-adjust the width and height:
void ColorBar::paintEvent(QPaintEvent *pe)
{
QRect region = pe->rect();
QPainter painter(this);
QColor borderColor;
borderColor.setNamedColor("#a0a0a0");
QColor lightColor = QColor(255, 255, 255);
QColor darkColor = QColor(225, 225, 225);
int barHeight = static_cast<int>(height() * 1. / 4. + 0.5);
QRect drawRect(0, static_cast<int>(height() / 2. - barHeight / 2. + 0.5), width() * .9 * value() / maximum(), barHeight);
QLinearGradient g(drawRect.topLeft(), drawRect.bottomLeft());
g.setColorAt(0., lightColor);
g.setColorAt(1., darkColor);
painter.setPen(QPen(borderColor));
painter.setBrush(QBrush(g));
painter.drawRect(drawRect);
}
Animating this bar is then straightforward and fast:
QPropertyAnimation* x = new QPropertyAnimation(percentageBar, "value");
x->setStartValue(percentageBar->value());
x->setEndValue(newValue);
x->setDuration(500);
x->start();
Still open for feedback or better solutions!
Related
I have a very simple gtkmm3 app with 2 widgets (a window and a dialog) that shows both in response to event on_activate via:
add_window(widget); widget.present();
Both the dialog and the window have exactly the same code, with the only difference being the base class (Gtk::ApplicationWindow vs Gtk::Dialog).
The code for them is following:
constructor()
{
set_decorated(false);
set_app_paintable();
set_default_size(640, 480);
// remove default child if present
auto child = get_child();
if (child) remove(), delete child;
}
bool on_draw(const ::Cairo::RefPtr< ::Cairo::Context> & cr)
{
// get the size of the window
int width, height;
get_size(width, height);
// semi-transparent grey background
cr->set_source_rgba(0.5,0.5,0.5,0.5);
// make round edges
const auto border_radius = 50;
cr->arc(width - border_radius, height - border_radius, border_radius, 0, M_PI_2);
cr->arc(border_radius, height - border_radius, border_radius, M_PI_2, M_PI);
cr->arc(border_radius, border_radius, border_radius, M_PI, 3 * M_PI_2);
cr->arc(width - border_radius, border_radius, border_radius, 3 * M_PI_2, 0);
cr->clip(); // clip the edges
cr->paint(); // paint the background
return false;
}
Surprisingly the result of both cases is different. The dialog gets the intended semi-transparent look, but the window has a black rectangular background.
I presume the window background must be drawn before on_draw, but I don't know when.
Q1: What is the difference between them?
Since Gtk::Dialog inherits from Gtk::Window, there must be a way to remove that black background.
Q2: How can I do that myself?
I already tried removing all CSS styles, by inserting * { all: unset; } in the Gtk inspector, with no visible effect (the inspector did showed all CSS nodes being reset). This makes me think that the black background may not be related to CSS, but I may be wrong.
This is what it looks like:
I posted before about text not displaying - I got that working but by a slightly different method - setting a stylesheet(around the constructor). The thing is - I am trying to set up a fadeout by setting the brush color to a calculated and decreasing fraction of the total color at initial display(#bbbbbb). What I see though because I use the stylesheet is the text appearing for 10 seconds (as that's the planned fadeout cycle) and then it disappears without fading out. But without the stylesheet setting color: #bbbbbb;, the text will not show. Here is my code for that text:
if((getFrame() >= 0) && (getFrame() < 10))
{
int iColorComponent = ((10 - getFrame()) / 10) * 0xbb;
painter.setBrush(QBrush(QColor(iColorComponent, iColorComponent, iColorComponent), Qt::SolidPattern));
painter.drawText(245, 330, 125, 15, Qt::AlignCenter, tr("Ready"));
}
getFrame() returns a qint64 so it initializes as -1 and when things start then, it is set to 0. There's a QTimer firing off every 250 ms and in that method it increments the frame field and calls update(). This is inside my paintEvent() protected method where this drawText is. I'm using QT 5.15 and C++ 11 if any of that makes a difference.
Any thoughts?
qt use QPen for drawing text. Set a pen instead of brush for QPainter:
painter.setPen(QPen(QColor(iColorComponent, iColorComponent, iColorComponent), Qt::SolidPattern));
painter.drawText(245, 330, 125, 15, Qt::AlignCenter, tr("Ready"));
I have a QPrinter that prints A4 either directly to a physical printer or a PDF. Now I'd like to use QPainter to draw in millimetres, but the current coordinate system seems to be the width and height of an A4 in inches times the resolution of the printer.
8.26 inch x 1200 res = 9912
11.69 inch x 1200 res = 14028
I have tried the following but text just ended up huge.
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(QRect(0, 0, page.width(), page.height()));
How do I change this so my QPainter can draw to 210 x 297 mm instead of the above system?
This is on Windows 10 and with Qt 5.10.
I tested this method on X11 (ubuntu linux) PDF print, using ScreenResolution printer mode:
painter.begin(printer);
int log_w = 210;
int log_h = 297;
painter.setWindow(0, 0, log_w, log_h);
int phys_w = printer->width();
int phys_h = printer->height();
painter.setViewport(0, 0, phys_w, phys_h);
Basically, set your logical size in mm using the painter window, and give the painter's viewport the printer's physical size.
This line should print a rectangle around the page with a border of 10 mm:
painter.drawRect(10, 10, log_w - 20, log_h -20);
Text should work accordingly. This code should print the word Ok at the top left corner of the rectangle:
QFont font = painter.font();
font.setPointSize(10); //1 cm height
painter.setFont(font);
painter.drawText(10, 20, "Ok");
painter.end();
Using HighResolution printer mode, font size must be set using
font.setPixelSize(10); //1 cm height
and a QPen must be set to the painter:
QPen pen(Qt::black);
pen.setWidthF(0.2);
painter.setPen(pen);
painter.drawRect(10, 10, log_w - 20, log_h - 20);
About loss of device dependency using setPixelSize, I'm aware that here is stated:
It is possible to set the height of characters shown on the screen to
a specified number of pixels with setPixelSize(); however using
setPointSize() has a similar effect and provides device independence.
but I think it refers to screen only, given that here is stated:
When rendering text on a QPrinter device, it is important to realize
that the size of text, when specified in points, is independent of the
resolution specified for the device itself. Therefore, it may be
useful to specify the font size in pixels when combining text with
graphics to ensure that their relative sizes are what you expect.
I think that you are looking for the QTransform class, according to the official doc:
The QTransform class specifies 2D transformations of a coordinate
system. A transformation specifies how to translate, scale, shear,
rotate or project the coordinate system, and is typically used when
rendering graphics.
You can initialise your custom transform class:
QTransform transform = QTransform::fromScale(painter.device()->physicalDpiX() / scale, painter.device()->physicalDpiY() / scale);
A think that this could be helpfull, the number of dots per militmeter:
const int dot_per_millimeter = qRound(qApp->primaryScreen()->physicalDotsPerInch() / 25.40);
Customise then your scale & apply it using a QPainter:
QPainter painter(parent);
painter.setWorldTransform(transform, false);
Your approach is correct. Here is an example of how to set up a printer/painter pair. I don't fiddle around with the transformation matrix since it's sufficient to specify a window/viewport pair. I don't even specify the viewport explicitly, since it is automatically set to the metrics of the paint device (in this case the QPrinter object).
#include <QPrinter>
#include <QPainter>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QPrinter printer(QPrinter::PrinterResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setResolution(300 /*dpi*/);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("ellipse.pdf");
QPainter painter(&printer);
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page.toRect());
// Draw a 5mm thick ellipse across the whole page.
painter.setPen(QPen(Qt::black, 5.0));
painter.drawEllipse(0, 0, 210, 297);
return 0;
}
It is hard to tell what goes wrong in your case without seeing the rest of the code
I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?
I try to draw a round rectangle with drawRoundedRect method directly in a QPixmap (no render engine involve here exept pure Qt one ...), I double check the size of the rectangle versus the size of my pixmap :
Pixmap : QSize(50, 73)
Rectangle: QRect(0,0 48x11)
See plenty of space ...
EDIT: some code
pixmap = QPixmap(50,73); //example size that match my case
QRectF rect(0,0,48,11);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setWorldMatrixEnabled(false);
painter.setPen(QPen()); //no pen
painter.setBrush(QBrush(color));
painter.drawRoundedRect(rect, 2.0, 2.0);
I disabled world transformation ...
I set set transformation to unity ...
I tried several radius (1.0,2.0,3.0,4.0) ...
I change pen width, brush color ...
But it always ends with a rectamgle with 4 diferent corners ! Like that :
I directly ouptut the pixmap to a file to be sure I wasn't scraping it during the display ... same shape.
Anyone know about Qt round rectangle with small radius ? I saw somthing about it a long time ago but I don't remenber how to deal with it !
It looks like you're not using anti-aliasing (i.e. the QPainter::Antialiasing render hint). This is a Qt quirk that occurs without it. From what I've seen/heard, the Qt devs aren't terribly concerned with fixing this (most people want anti-aliasing anyway).
The work-around (besides just using anti-aliasing) is to draw the rect yourself with QPainter::drawLine() and QPainter::drawArc(). You might have to play with numbers until it looks right -- straight calculations tend to come out a pixel or two off. Also, you might find that even with this method the lower right corner is never exactly the same as the other corners.
If you're feeling mildly ambitious, you could try fixing this and submitting a patch to Qt.
Update: Arc drawing results changed in Qt 5. In my experience, it's a big improvement.
I know this is an old problem but for Qt5 users calling setRenderHint(QPainter::Qt4CompatiblePainting); on the QPainter seems to solve the problem.
Edit:
I found a solution for generating a perfect rounded rectangle together with border color and it looks the same as the rounded rectangles used by QPushButton's border for example. This is how I implemented the paintEvent to achieve this:
void MyButtonGroup::paintEvent(QPaintEvent * e)
{
int borderSize = 5;
QColor borderColor = Qt::red;
QColor backgroundColor = Qt::blue;
int borderRadius = 3;
QPen pen;
pen.setWidth(borderSize);
pen.setColor(borderColor);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(pen);
QRectF rect(rect().x() + borderSize / 2,
rect().y() + borderSize / 2,
rect().width() - borderSize,
rect().height() - borderSize);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderSize,
borderSize);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QBrush brush(backgroundColor);
pen.setBrush(brush);
painter.setBrush(brush);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderRadius,
borderRadius);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QWidget::paintEvent(e);
}
I'm posting this because I found it a bit hard to achieve this result:
Try adding half a pixel offset (e.g.: rect.translated(0.5,0.5) ):
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,false);
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0, 2.0 );
I suppose this has to do with the coordinate system placing an integer value between two pixels.
If you draw with antialiasing and use a pen of 1 pixel width then drawing at exact integer coordinates results in lines of 2 pixel width instead.
Only with this 0.5 pixel offset you'll get lines that are exactly 1 pixel wide.
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setBrush(Qt::NoBrush);
painter.setPen( Qt::white );
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0,2.0 );
Best way do draw RoundRect is Path.
http://developer.nokia.com/community/wiki/Qt_rounded_rect_widget
void fillRoundRect(QPainter& painter, QRect r, int radius)
{
painter.setRenderHint(QPainter::Antialiasing,true);
QPainterPath rounded_rect;
rounded_rect.addRoundRect(r, radius, radius);
painter.setClipPath(rounded_rect);
painter.fillPath(rounded_rect,painter.brush());
painter.drawPath(rounded_rect);
}
try to play with render hints
1) disable antiAliasing;
2) enable SmoothPixmapTransform
but still no guarantee that it will help.
I have tried all tips from answers here but nothing works for me. But based on these code snippets I have found following solution:
As default set m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true) and only for rounded rectangles with width%2==0 disable it.
QRect rect = ConvertRectangle(rectangle);
int nPenWidth = m_pPainter->pen().width();
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, false);
m_pPainter->drawRoundedRect(rect, dbRadiusX, dbRadiusY);
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true);