I want to make a simple QWidget which is a simple rectangle fade away. The main problem is that the paint event paint at the same place every time in fact making the effect opposite, it make the colour stronger. Is there any way of achieving this functionality? Could you maybe provide some simple example?
My code:
`
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
void paintEvent(QPaintEvent*);
~Widget();
private:
Ui::Widget *ui;
int alfa;
int i;
QTimer time;
};
#endif // WIDGET_H
`
and the cpp:
#include "widget.h"
#include "ui_widget.h"
#include <QColor>
#include <QPainter>
#include <QBrush>
#include <QTimer>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent,Qt::Popup | Qt::FramelessWindowHint),
ui(new Ui::Widget),
time()
{
ui->setupUi(this);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_PaintOnScreen);
time.setInterval(500);
time.start();
connect(&time,SIGNAL(timeout()),this,SLOT(update()));
alfa=100;
i=0;
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
int rectBase = height();
QColor c(255,0,255);
alfa=alfa-(i*10);
c.setAlpha(alfa);
qDebug()<<c.alpha();
i++;
painter.setBrush(QBrush(c));
painter.drawRect(0, 0, width(),height());
}
Widget::~Widget()
{
delete ui;
}
You shouldn't rely on QWidget::paintEvent() to change your alpha level, since it can be called less or more than you want (multiple update() calls may result in only one paintEvent() and paintEvent() may be called when you don't expect it).
So a more reliable way to get to the result, is have a separate slot where you decrease the alpha level and then call update(). Your class definition might look like this:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget( QWidget * inParent );
private:
void paintEvent(QPaintEvent *);
private slots:
void animate();
private:
QTimer * mTimer;
int mAlpha;
};
And the declaration:
Widget::Widget( QWidget * inParent )
:
QWidget( inParent ),
mTimer( new QTimer( this ) ),
mAlpha( 255 )
{
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_PaintOnScreen);
mTimer->setInterval( 40 );
mTimer->setSingleShot( false );
connect( mTimer, SIGNAL(timeout()), this, SLOT(animate()) );
mTimer->start();
}
void
Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setBrush( QColor(255,0,255,mAlpha ) );
painter.drawRect(rect());
}
void
Widget::animate()
{
if ( mAlpha > 0 )
{
mAlpha -= 3;
}
else
{
mTimer->stop();
}
update();
}
Notice that I did decrease the interval of the timer. You only called an update() every half a second. That typically does not result in a smooth animation.
I get a warning with this code on Kubuntu Linux, under Qt5:
QWidget::paintEngine: should no longer be called
The originating line is in qWarning("QWidget::paintEngine: Should no longer be called");, in src/widgets/kernel/qwidget_qpa.cpp and discussed a bit in this ticket:
https://qt.gitorious.org/qt/qtbase-harmattan/commit/3037525
You can get the warning to stop by removing the setAttribute(Qt::WA_PaintOnScreen);, so I did that. After taking that line out, it works for me--although your subtraction model is strange. You are modifying the alpha as well as changing the subtraction value on each iteration; you probably didn't intend both. So either change it to:
QColor c (255, 0, 255);
alfa = alfa - 10;
if (alfa >= 0) {
c.setAlpha(alfa);
} else {
time.stop();
}
...or:
QColor c(255,0,255);
if (alfa - i * 10 >= 0) {
c.setAlpha(alfa - i * 10);
i++;
} else {
time.stop();
}
Etc. (See also #PrisonMonkeys note on your timer not necessarily being the only source of update() calls.) Regarding getting these warnings to be more vocal so you don't miss them, you might look at The Essential Noisy Debug Hook For Qt, which I should update.
If with the change, an alpha blended window doesn't work on your platform at all, you should mention explicitly what your circumstance is...as it is working for me.
Related
.h
class myButton : public QPushButton
{
Q_OBJECT
public:
QPropertyAnimation* anim;
struct WidgetPos { int x = 0; int y = 0; int w = 0; int h = 0; };
WidgetPos wp;
void CreateAnimation(QByteArray propertyName)
{
if (propertyName == "geometry")
{
anim = new QPropertyAnimation(this, propertyName);
this->anim->setDuration(100);
this->anim->setEasingCurve(QEasingCurve::Linear);
this->wp.x = this->x();
this->wp.y = this->y();
this->wp.w = this->width();
this->wp.h = this->height();
}
}
myButton(QWidget* parent = 0) : QPushButton(parent) {}
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Enter)
{
if (!this->wp.x)
this->CreateAnimation("geometry");
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), this->width(), this->height()));
this->anim->setEndValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->start();
}
else if (event->type() == QEvent::Leave)
{
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->setEndValue(
QRect(this->wp.x, this->wp.x, this->wp.w, this->wp.h));
this->anim->start();
}
return QWidget::eventFilter(obj, event);
}
};
.cpp
QtWidgetsApplication::QtWidgetsApplication(QWidget * parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QPushButton* btn = new myButton(this);
btn->setGeometry(100, 100, 50, 40);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/tutorial.png);
background-repeat: no-repeat; }
)");
QLabel* labl = new QLabel(btn);
labl->setObjectName("label");
labl->setGeometry(32, 0, btn->width() + 32, btn->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
btn->installEventFilter(btn);
return;
}
So far what I did result on:
If I move the mouse on it so fast it becomes messy, and the "closing" animation <= isn't working.
I'm struggling with the calculation of the animation QRect and handling it when there's an animation already running.
The goal is to create a smooth animation effect similar to see in this gif:
I think the reason for the issue you are having is because when you are leaving the widget you set the start animation to the maximum width the button could take instead of starting it from the current width. I've implemented my own QPushButton subclass in the following way which seems to achieve the result you need. Instead of creating an event filter, I'll just override the enter and leave event. We'll also need to update the initial geometry every time the widget is moved or resized (outside of the animation), so I'm overriding the move and resize event as well.
// MyButton.h
class MyButton : public QPushButton
{
public:
MyButton(QWidget* parent = nullptr);
~MyButton() = default;
protected:
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent* event) override;
void moveEvent(QMoveEvent *event) override;
void resizeEvent(QResizeEvent* event) override;
private:
QPropertyAnimation* m_animation;
QRect m_init_geometry;
double m_duration;
double m_extension;
};
Here is the implementation:
// MyButton.cpp
MyButton::MyButton(QWidget* parent)
: QPushButton(parent)
, m_animation(nullptr)
, m_init_geometry()
, m_duration(200)
, m_extension(100)
{
m_animation = new QPropertyAnimation(this, "geometry", this);
m_animation->setDuration(m_duration);
m_animation->setEasingCurve(QEasingCurve::Linear);
m_init_geometry = geometry();
}
void MyButton::enterEvent(QEvent *event)
{
QPushButton::enterEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((m_init_geometry.width() + m_extension - width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(QRectF(m_init_geometry.x(), m_init_geometry.y(), m_init_geometry.width() + m_extension, m_init_geometry.height()));
m_animation->start();
}
void MyButton::leaveEvent(QEvent *event)
{
QPushButton::leaveEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((width() - m_init_geometry.width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(m_init_geometry);
m_animation->start();
}
void MyButton::moveEvent(QMoveEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::moveEvent(event);
m_init_geometry.setTopLeft(event->pos());
}
void MyButton::resizeEvent(QResizeEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::resizeEvent(event);
m_init_geometry.setSize(event->size());
}
Notice that the start value of the closing animation is the current geometry and not the initial geometry plus the extended width. I'm updating reducing the duration of the opening animation linearly depending on how close the current width is to the full extended width; similarly for the closing animation. The rest now is very similar to your code:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* btn = new MyButton(this);
btn->setGeometry(100, 100, 60, 80);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/ubuntu.png);
background-repeat: no-repeat;
background-origin: content;
background-position: left center;}
)");
auto* labl = new QLabel("Hello World", btn);
labl->setAlignment(Qt::AlignCenter);
labl->setGeometry(btn->width(), 0, labl->width(), btn->height());
}
The result looks like this
I try this way:
in mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QLabel>
#include <QPushButton>
class MyButton : public QPushButton
{
Q_OBJECT
public:
MyButton(QWidget* parent = nullptr);
// QObject interface
public:
bool eventFilter(QObject *watched, QEvent *event);
signals:
void mouseEnter();
void mouseLeave();
private:
};
#endif // MYBUTTON_H
in mybutton.cpp
#include "mybutton.h"
#include <QEvent>
#include <QLabel>
MyButton::MyButton(QWidget *parent):
QPushButton(parent)
{
}
bool MyButton::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::HoverEnter)
{
emit mouseEnter();
}
else if (event->type() == QEvent::HoverLeave)
{
emit mouseLeave();
}
}
I use signal and in MainWindow class UI I add widget and layouts:
in mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *labl;
QLabel *labl2;
};
#endif // MAINWINDOW_H
in mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <mybutton.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
MyButton* btn = new MyButton(this);
btn->setObjectName("button 1 ");
btn->setText("btn 1");
btn->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn);
// QLabel
labl= new QLabel(btn,Qt::ToolTip);
connect(btn,&MyButton::mouseEnter,this,[btn,this](){
labl->setObjectName("label");
labl->setGeometry(btn->x()+this->x()+btn->width()+10, btn->y()+this->y()+btn->height()+15,
labl->width(), labl->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
});
connect(btn,&MyButton::mouseLeave,this,[this](){
labl->hide();
});
btn->installEventFilter(btn);
MyButton* btn2 = new MyButton(this);
btn2->setObjectName("button 2 ");
btn2->setText("btn 2");
btn2->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn2);
// QLabel
labl2= new QLabel(btn2,Qt::ToolTip);
connect(btn2,&MyButton::mouseEnter,this,[btn2,this](){
labl2->setObjectName("label");
labl2->setGeometry(btn2->x()+this->x()+btn2->width()+10, btn2->y()+this->y()+btn2->height()+15,
labl2->width(), labl2->height());
labl2->setText("Hello World 2");
labl2->setAlignment(Qt::AlignCenter);
labl2->show();
});
connect(btn2,&MyButton::mouseLeave,this,[this](){
labl2->hide();
});
btn->installEventFilter(btn);
btn2->installEventFilter(btn2);
}
MainWindow::~MainWindow()
{
delete ui;
}
and this is my result:
By using the same color in the stylesheet you can have what you show in your Gif.
You can try this approach:
Calculate the start and end rect only once and set in the animation object.
On enter you start the animation as before.
On leave you can change the direction of the animation from play forward to backward.
You can run the animation backwards with
this->anim->setDirection( QAbstractAnimation::Backward );
I don't know if you must surround it with
this->anim->pause();
//[...]
this->anim->resume();
Maybe you must experience with it a little.
Also, maybe you must keep track of
1.) Did you start a animation in forward and backward already for not start it twice or even more often, e.g. use an enum to safe state in a member:
enum class eState { Stopped, Forward, Backward };
2.) Test if the animation is still running or finished already for eventually start a new animation in either forward or backward direction, e.g. test with
this->anim->currentTime() < this->anim->totalDuration();
// or just query the state
this->anim->state() == QAbstractAnimation::Stopped;
I hope I could help you to solve your problem.
EDIT My point 1.) you can also solve with the methods of the animation class by testing:
this->anim->direction(); // and...
this->anim->state();
I am new to QT.
How can I press and release a button in Qt ?
In java I do the below program to control key events ?
Robot r = new Robot();
r.keyPress(KeyEvent.VK_ENTER);
r.keyRelease(KeyEvent.VK_ENTER);
I want to press a key in keyboard programatically.
But , How can I do the same thing in QT ?
You can either create QKeyEvent and send them to the application using QApplication::sendEvent(). Or if you want higher level API, you can build your application with QtTest module and use keyClick functions. See https://doc.qt.io/qt-6/qtest.html
In Qt, key presses are handled by the Event System. Like other languages/frameworks events are encapsulated in an object, in Qt's case, QEvent. All subclasses of QObject have a virtual QObject::event(QEvent *e) method to handle event objects sent to it. This method does not directly handle the event, but calls the object's appropriate event handler based on the QEvent::Type enum. In the case of key presses, the QEvent::type() method returns QEvent::KeyPress.
While most events are handled internally without programmer intervention, you may send events manually using the QCoreApplication class or its subclass QGuiApplication. An instance of one of these classes is typically instantiated in the boilerplate main.cpp file created when you generate a new project with Qt Creator. These classes have access to the methods QCoreApplication::sendEvent(QObject *receiver, QEvent *event), which sends an event directly to receiver, and QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority), which sends the event to Qt's event queue to be processed later.
I've created a project to demonstrate this functionality. This app just displays a plain rectangle which can be either red or blue. The rectangle only switches colors when it receives a QKeyEvent indicating that the C key was pressed. Below the rectangle is a button which programmatically produces this event and sends it to the rectangle's widget. The project went on a bit long and is a bit messy, but I hope it helps some.
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Here I modify the boilerplate code to allow MainWindow w to have access
// to QApplication a so that a widget in MainWindow w can use postEvent()
MainWindow w(nullptr, &a);
w.show();
return a.exec();
}
mainwindow.h
#include <QCoreApplication>
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr, QCoreApplication* app = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QGridLayout>
#include <QLabel>
#include "keypressacceptor.h"
#include "keypressgenerator.h"
MainWindow::MainWindow(QWidget *parent, QCoreApplication *app)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGridLayout* layout = new QGridLayout(this);
KeyPressAcceptor* kpa = new KeyPressAcceptor(this);
KeyPressGenerator* kpg = new KeyPressGenerator();
kpg->registerReceiver(kpa);
kpg->registerApp(app);
layout->addWidget(kpa);
layout->addWidget(kpg);
centralWidget()->setLayout(layout);
}
MainWindow::~MainWindow()
{
delete ui;
}
keypressacceptor.h
#include <QWidget>
class KeyPressAcceptor : public QWidget
{
Q_OBJECT
public:
explicit KeyPressAcceptor(QWidget *parent = nullptr);
bool handleKeyPress(const int &key);
protected:
bool event(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
private:
QColor m_color;
};
keypressacceptor.cpp
#include "keypressacceptor.h"
#include <QEvent>
#include <QKeyEvent>
#include <QPainter>
KeyPressAcceptor::KeyPressAcceptor(QWidget *parent)
: QWidget{parent}
, m_color(QColor(220, 20, 20))
{
// Setting focus policy so that the widget can accept focus.
// This is necessary to process key press events.
setFocusPolicy(Qt::StrongFocus);
}
bool KeyPressAcceptor::handleKeyPress(const int &key)
{
// This method performs some arbitrary action, in this case changing a
// color, to indicate that a key press has been processed.
switch (key) {
case Qt::Key_C:
// If the "C" key was pressed, switch m_color
if (m_color == QColor(220, 20, 20)) {
m_color = QColor(20, 20, 220);
} else {
m_color = QColor(220, 20, 20);
}
// Call update() to tell Qt to repaint this widget once Qt has entered
// the main event loop
update();
return true;
default:
return false;
}
}
bool KeyPressAcceptor::event(QEvent *event)
{
switch (event->type()) {
case QEvent::KeyPress:
// If the received event is of type QEvent::KeyPress, then cast the
// variable event to type QKeyEvent*, then use the event's key()
// method to pass as an argument to this class's handleKeyPress()
// method.
return handleKeyPress(static_cast<QKeyEvent*>(event)->key());
// Note! This overrides QWidget's default behavior upon receiving a
// QKeyEvent event
default:
// Otherwise, be sure to use the class's superclass to process any
// other events.
return QWidget::event(event);
}
}
void KeyPressAcceptor::paintEvent(QPaintEvent *event)
{
// Don't need to use the event parameter in this implementation.
Q_UNUSED(event)
// Want to draw a rectangle centered in the widget whose height is half
// the widget's height and whose width is half the widget's width.
// The color of the rectangle is determined by m_color.
// First define the rectangle using the height and width properties of
// QWidget to determine the rectangle's height, width, and coordinates of
// top left corner.
QRect rect(width() / 4, height() / 4, // Coordinates of top left corner
width() / 2, height() / 2); // Width and height
// Create a QPainter object to paint with
QPainter painter(this);
// Set pen and brush for rectangle's outline and fill respectively.
painter.setPen(QColor(0,0,0)); // Black 1px pen
painter.setBrush(QBrush(m_color)); // Solid fill of color m_color
// Draw the rectangle
painter.drawRect(rect);
}
keypressgenerator.h
#include <QCoreApplication>
#include <QPushButton>
#include <QObject>
class KeyPressGenerator : public QPushButton
{
Q_OBJECT
public:
explicit KeyPressGenerator(QWidget *parent = nullptr);
void registerApp(QCoreApplication* app);
void registerReceiver(QObject* receiver);
public slots:
void generateKeyPress();
private:
QCoreApplication* m_app;
QObject* m_receiver;
};
keypressgenerator.cpp
#include "keypressgenerator.h"
#include <QCoreApplication>
#include <QKeyEvent>
KeyPressGenerator::KeyPressGenerator(QWidget *parent)
: QPushButton{parent}
, m_app(nullptr)
, m_receiver(nullptr)
{
setText("Push Button to Send C Key Press");
// Connect clicked signal to generateKeyPress so when button is clicked
// a programmatically generated keypress is sent to m_receiver
connect(this, &KeyPressGenerator::clicked,
this, &KeyPressGenerator::generateKeyPress);
}
void KeyPressGenerator::registerApp(QCoreApplication *app)
{
m_app = app;
}
void KeyPressGenerator::registerReceiver(QObject *receiver)
{
m_receiver = receiver;
}
void KeyPressGenerator::generateKeyPress()
{
if (m_app == nullptr || m_receiver == nullptr) return;
// Generate the key press event. Check documentation for an explanation of
// the constructor's parameters.
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_C, Qt::NoModifier);
m_app->postEvent(m_receiver, event);
}
I am working on Falling ball application,
About the application: the balls keep on falling at regular intervals, and there is a basket that catches the balls. If caught then we earn a point if not then lose one life.
For this application, I am using qt and CPP.
I have created one class Sprite with specifications regarding the initial position of the ball(x and y) and speed (dx and dy).
I am using one timer for the speed of the ball, but I need to another timer for ball creation at regular intervals. Could anyone help me with how to implement the ball creation at regular intervals?
sprite.h
--------
#ifndef SPRITE_H
#define SPRITE_H
#include <QTimer>
#include <QPainter>
#include <QWidget>
/**
* Header class for Sprite
*/
class Sprite : public QTimer
{
public:
Sprite(QWidget *parent);
//draw() to draw a sprite
void draw(QPainter &painter);
protected:
//This timerEvent will be called after certain time prescribed.
virtual void timerEvent(QTimerEvent *e) override;
int x;//position of sprite in x-direction.
int y;//position of sprite in y-direction.
int dx;//difference in x-direction position.
int dy;//difference in y-direction position.
int x1;//position of the basket in x-direction
int y1;//position of the basket in y-direction.
QWidget *parent;//parent class for all widgets.
};
#endif // SPRITE_H
sprite.cpp
-----------
#include "sprite.h"
#include <QDebug>
Sprite::Sprite(QWidget *parent):parent(parent)
{
QRect rct = parent->rect();
x = rand() % rct.width();//randomly initialize the x-position for the sprite.
y=rct.height()*0.05;//start position for the sprite is about 5% after the top of the menu bar.
dx = rand() % 10;//the speed is randomly set in x-direction.
// dy = rand() % 10;//the speed is randomly set in y-direction.
dy = 4;
x1=rct.width()/2;
y1 = rct.height()-80;
start(10);
}
void Sprite::draw(QPainter &painter)
{
qDebug() <<"Sprite::draw() called";
painter.drawEllipse(x, y, 15, 15);//ball
painter.drawRect( x1, y1, 80, 30);//basket
}
void Sprite::timerEvent(QTimerEvent *)
{
qDebug("timerEvent called");
QRect rct = parent->rect();
if ( x > rct.width() || x < 0)
dx *= -1;
if ( y > rct.height() || y < 0){}
x += dx;
y += dy;
parent->update();
}
mainwindow.cpp
-----------------
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QKeyEvent>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() <<"mainWindow constructor";
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, this,&MainWindow::onTimer);
timer.start(1000);
}
MainWindow::~MainWindow()
{
qDebug() <<"mainWindow destructor";
delete ui;
}
void MainWindow::onTimer()
{
std::cout << "Tick!-----------------------------------------------------------------------------" << std::endl;
}
void MainWindow::paintEvent(QPaintEvent *)
{
qDebug() <<"painEvent() called";
QPainter painter(this);
painter.fillRect(rect(), QBrush(QColor(Qt::white)));
painter.setPen(Qt::black);
painter.setBrush(QBrush(QColor(Qt::darkBlue)));
emit draw(painter);
}
MainWindow.h
-----------
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "sprite.h"
#include <balls.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void draw(QPainter &painter);
void move(int nDirection);
protected:
//This event handler captures all the paint events request to repaint all or part of the widget.
void paintEvent(QPaintEvent* event)override;
public slots:
void onTimer();
private slots:
void on_actionStart_triggered();
void on_actionStop_triggered();
private:
Ui::MainWindow *ui;
Sprite *sprite;
};
#endif // MAINWINDOW_H
How can I create balls at regular intervals, any sudo code will help.
Or any better way to implement this is also welcome.
Calling QObject::startTimer twice, will run two timers simultaneously:
int timerId1 = startTimer(2000);
int timerId2 = startTimer(10);
void MyObject::timerEvent(QTimerEvent *event)
{
if (event->timerId() == timerId1)
; // 2000ms timer is fired
else if (event->timerId() == timerId2)
; // 10ms timer is fired
}
Also the Qt documentation itself contains a clear example of this use case.
An alternative is to use two QTimer objects, and connect a slot to the timeout signal of each QTimer object.
How to bufferswap between two textures, to achieve drawing on the next VBL?
Currently I'm using two QPixmap which I set to a QLabel. This results in a unstable delay between 18ms and 45ms measured against parallelport, using a 85Hz CRT monitor (no buffers there which add any delays).
My goal would be to issue a flip command which leads to bufferswap on the next VBL (Vertical Blanking Interrupt).
What I have tried now:
I setup a full screen window in the main.cpp (different problem: can't open a window at runtime) and initialize there two QPixmaps.
With a signal created by a timer I trigger the change:
myVisualStimuli = new VisualStimuli(0);
//Start Timer for Stimulus Presenation
StimulusThread = new QThread(this);
StimulusTimer = new QTimer(0);
StimulusTimer->setInterval(500);
StimulusTimer->moveToThread(StimulusThread);
myVisualStimuli->moveToThread(StimulusThread);
myVisualStimuli->connect(StimulusTimer, SIGNAL(timeout()), SLOT(SetStimulus()), Qt::DirectConnection);
this->connect(StimulusTimer, SIGNAL(timeout()), SLOT(SetStimulus()), Qt::DirectConnection);
// Make sure the timer gets started from m_thread.
StimulusTimer->connect(StimulusThread, SIGNAL(started()), SLOT(start()));
StimulusThread->start();
And the Drawing: Header / Code
#ifndef VISUALSTIMULI_H
#define VISUALSTIMULI_H
#include <QtCore>
#include <QObject>
#include <QThread>
#include <QLabel>
class VisualStimuli: public QThread
{
Q_OBJECT
public:
explicit VisualStimuli(QObject *parent = 0);
int Setup();
bool StimulusToggle = true;
QRect *screenres;
QLabel* StimulusWindow;
QPixmap *pix1;
QPixmap *pix2;
public slots:
void SetStimulus();
private:
protected:
void run();
};
#endif // VISUALSTIMULI_H
#include "visualstimuli.h"
VisualStimuli::VisualStimuli(QObject *parent) : QThread(parent)
{
}
int VisualStimuli::Setup()
{
return 0;
}
void VisualStimuli::SetStimulus()
{
start();
}
void VisualStimuli::run()
{
if(StimulusToggle){
this->StimulusWindow->setPixmap(*pix1);
StimulusToggle=false;
}else{
this->StimulusWindow->setPixmap(*pix2);
StimulusToggle=true;
}
}
Due to Hardware I'm bound to Windows 10 64Bit, so I 'don’t mind to use any platform dependent code/library for the stimulus presentation.
I'm trying to create a simple frame in Qt with a tick and some text. I made two new label implementations because I wanted the labels to dynamically fill all the available space but when I resize the window the sizes are off, as shown by the qDebug output, which represents the size of the image label:
Resized: 244 , 244 <-- Window first created
Resized: 305 , 305 <-- Window maximized
Resized: 135 , 135 <-- Window restored to original size
As you can see, when the window is restored to its original size the image is not. The last size should be 244, 244.
The code which describes the behaviour of the two widgets is the following:
"widgets.h":
/*
* This file includes many custom widgets.
*/
#ifndef APOCRYPHA_WIDGETS
#define APOCRYPHA_WIDGETS
#include <QWidget>
#include <QLabel>
#include <QTimer>
#include <QPixmap>
#include <QResizeEvent>
#include <QPaintEvent>
class AutoTextLabel : public QLabel {
Q_OBJECT
public:
explicit AutoTextLabel(QWidget* parent);
AutoTextLabel(QWidget* parent, QString text);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
QTimer* resizeTimer;
private slots:
void onResizeEnd();
};
class AutoImageLabel : public QLabel {
Q_OBJECT
public:
explicit AutoImageLabel(QWidget* parent);
AutoImageLabel(QWidget* parent, const QPixmap& pixmap);
void setFillOrientation(int orientation);
QSize sizeHint() const override;
public slots:
void setPixmap(const QPixmap &newPix);
void resizeEvent(QResizeEvent* event) override;
protected:
// void paintEvent(QPaintEvent* event) override;
private:
int fillOrientation;
int widthForHeight(int h) const;
int heightForWidth(int w) const override;
QPixmap scaledPixmap() const;
QPixmap labelPixmap;
};
#endif //APOCRYPHA_WIDGETS
"widgets.cpp":
/*
* This file includes many custom widgets.
*/
#include "widgets.h"
#include <QPainter>
#include <QDebug>
AutoTextLabel::AutoTextLabel(QWidget *parent, QString text) : QLabel(text, parent){
// Enable antialiasing
QFont aaFont(font());
aaFont.setStyleStrategy(QFont::PreferAntialias);
setFont(aaFont);
// This timer is used to fire a slot when a window is resized
resizeTimer = new QTimer();
resizeTimer->setSingleShot(true);
connect(resizeTimer, SIGNAL(timeout()), SLOT(onResizeEnd()));
}
AutoTextLabel::AutoTextLabel(QWidget *parent) : AutoTextLabel(parent, "") {}
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
void AutoTextLabel::onResizeEnd() {
QFont updatedFont(font());
// Resize Text
if (!text().isEmpty()){
int fontSize = 1;
updatedFont.setPixelSize(fontSize);
QRect boundingRectangle;
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
while (boundingRectangle.height() <= contentsRect().height()) {
fontSize++;
updatedFont.setPixelSize(fontSize);
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
}
updatedFont.setPixelSize(fontSize - 1);
setFont(updatedFont);
}
}
/* Auto Image Label */
AutoImageLabel::AutoImageLabel(QWidget *parent, const QPixmap &pixmap) : QLabel(parent) {
setMinimumSize(1, 1);
setScaledContents(false);
setPixmap(pixmap);
}
AutoImageLabel::AutoImageLabel(QWidget *parent) : QLabel(parent) {
setScaledContents(false);
}
void AutoImageLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
if(!labelPixmap.isNull())
QLabel::setPixmap(scaledPixmap());
qDebug() << "Resized: " << scaledPixmap().width() << ", " << scaledPixmap().height();
}
int AutoImageLabel::widthForHeight(int h) const {
return labelPixmap.isNull() ? width() : (labelPixmap.width() * h) / labelPixmap.height();
}
int AutoImageLabel::heightForWidth(int w) const {
return labelPixmap.isNull() ? height() : (labelPixmap.height() * w) / labelPixmap.width();
}
void AutoImageLabel::setFillOrientation(int orientation) {
this->fillOrientation = orientation;
}
QSize AutoImageLabel::sizeHint() const {
if (fillOrientation == Qt::Horizontal)
return QSize(width(), heightForWidth(width()));
else
return QSize(widthForHeight(height()), height());
}
QPixmap AutoImageLabel::scaledPixmap() const {
return labelPixmap.scaled(sizeHint(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AutoImageLabel::setPixmap(const QPixmap &newPix) {
labelPixmap = newPix;
QLabel::setPixmap(scaledPixmap());
}
"other_frames.h":
//
// Created by Riccardo on 18/09/2017.
//
#ifndef APOCRYPHA_OTHER_FRAMES_H
#define APOCRYPHA_OTHER_FRAMES_H
#include <QFrame>
#include <QLabel>
#include <QGridLayout>
#include <QWidget>
#include <QResizeEvent>
#include <QPixmap>
#include <QTimer>
#include "widgets.h"
class ConfirmationFrame : public QFrame {
Q_OBJECT
public:
explicit ConfirmationFrame(QWidget* parent);
ConfirmationFrame(QWidget* parent, const QString& text);
private:
QGridLayout* layout;
AutoImageLabel* imageLabel;
AutoTextLabel* textLabel;
};
#endif //APOCRYPHA_OTHER_FRAMES_H
"other_frames.cpp":
//
// Created by Riccardo on 18/09/2017.
//
#include "other_frames.h"
#include <QDebug>
ConfirmationFrame::ConfirmationFrame(QWidget* parent, const QString &text) : QFrame(parent) {
textLabel = new AutoTextLabel(this, text);
QPixmap pix(":/images/check-tick.png");
imageLabel = new AutoImageLabel(this, pix);
textLabel->setAlignment(Qt::AlignCenter);
imageLabel->setAlignment(Qt::AlignCenter);
textLabel->setWordWrap(true);
// Green Background
setStyleSheet("background-color: rgba(106, 242, 94, 1);");
layout = new QGridLayout();
layout->setSpacing(0);
layout->setContentsMargins(32, 32, 32, 32);
layout->setRowStretch(0, 1);
layout->setRowStretch(1, 1);
layout->addWidget(imageLabel, 0, 1);
layout->addWidget(textLabel, 1, 1);
setLayout(layout);
}
ConfirmationFrame::ConfirmationFrame(QWidget *parent) : ConfirmationFrame(parent, "") {
}
"window_main.h":
#ifndef WINDOW_MAIN_H
#define WINDOW_MAIN_H
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QFrame>
#include <QScreen>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
QFrame *mainFrame;
void center(QScreen* screen);
void autoSetSize(QScreen* screen);
private:
void createMenu();
// Components
QGridLayout *mainLayout;
QMenuBar *menuBar;
QMenu *fileMenu;
};
#endif // WINDOW_MAIN
"window_main.cpp":
#include "window_main.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
mainFrame = new QFrame();
mainLayout = new QGridLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
createMenu();
mainFrame->setStyleSheet("background-color: red;");
mainFrame->setLayout(mainLayout);
setCentralWidget(mainFrame);
}
void MainWindow::createMenu(){
menuBar = new QMenuBar;
fileMenu = new QMenu(tr("&File"), this);
menuBar->addMenu(fileMenu);
setMenuBar(menuBar);
}
void MainWindow::center(QScreen *screen) {
QSize size = screen->availableSize();
int x = size.width() / 2 - width() / 2;
int y = size.height() / 2 - height() / 2;
move(x, y);
}
void MainWindow::autoSetSize(QScreen *screen) {
QSize screenSize = screen->availableSize();
// TODO Math.round
setMinimumSize(QSize((int)(screenSize.width() / 1.25), (int)(screenSize.height() / 1.25)));
}
"main.cpp":
#include <QApplication>
#include <iostream>
#include <QFile>
#include "quiz/choice.h"
#include "quiz/question.h"
#include "quiz/quizmaker.h"
#include <QSettings>
#include <QStandardPaths>
#include <QDebug>
#include <src/user_interface/other_frames.h>
#include "user_interface/window_main.h"
#include <QScreen>
#include <QFontDatabase>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// Set Application Parameters
QCoreApplication::setOrganizationName("Riccardo Fagiolo");
QCoreApplication::setOrganizationDomain("kopharex.me");
QCoreApplication::setApplicationName("Apocrypha");
// Set application font
const int id = QFontDatabase::addApplicationFont(":/fonts/montserrat/Montserrat-Regular.otf");
QString family = QFontDatabase::applicationFontFamilies(id).at(0);
QFont font(family);
font.setStyleStrategy(QFont::PreferAntialias);
a.setFont(font);
// App Settings
QSettings settings;
settings.setValue("data_dir", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
// Create UI
auto* window = new MainWindow();
ConfirmationFrame* cframe = new ConfirmationFrame(window, "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?");
window->mainFrame->layout()->addWidget(cframe);
window->autoSetSize(a.primaryScreen());
//cframe->updateTextLabel();
window->show();
window->center(a.primaryScreen());
// [...] - Nothing related to user interface.
return a.exec();
}
Here is a screenshot of the current MainWindow and ConfirmationFrame to give you an idea of what i'm trying to accomplish:
Window Screenshot
All comments regarding the code are welcome.
Thanks for any help,
Riccardo
Hello I tried to fix the resizing issue with an hack.
Before starting the timer to resize the text, just reduce its font to a 1 pixel font:
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// set a very small font, then start the timer
QFont updatedFont(font());
updatedFont.setPixelSize(1);
setFont(updatedFont);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
Can the effect be acceptable in your opinion?