I am a novice to c++, but I am doing my best to learn.
I am getting two errors and I don't know why, these are:
In constructor 'MyLabel::MyLabel(QWidget*)':
Qualified-id in declaration before '(' token -line 7
Qualified-id in declaration before '(' token -line20
My code is as follows:
mylabel.cpp:
#include "mylabel.h"
#include "ui_mainwindow.h"
MyLabel::MyLabel(QWidget *parent) :
QWidget(parent)
{
void MyLabel::MyLabel()
{
this->setAlignment(Qt::AlignCenter);
//Default Label Value
this->setText("No Value");
//set MouseTracking true to capture mouse event even its key is not pressed
this->setMouseTracking(true);
}
void MyLabel::mouseMoveEvent(QMouseEvent * event)
{
//Show x and y coordinate values of mouse cursor here
this->setText("X:" + QString::number(event->x()) + "-- Y:" + QString::number(event->y()));
}
}
mylabel.h:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QObject>
#include <QApplication>
#include <QMainWindow>
#include <QMouseEvent>
class MyLabel : public QWidget
{
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = 0);
~MyLabel();
void mouseMoveEvent(QMouseEvent * event);
signals:
};
#endif // MYLABEL_H
main.cpp
#include "mainwindow.h"
#include "mylabel.h"
#include <QApplication>
#include <QHBoxLayout>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
window->setWindowTitle(QString::fromUtf8("QT - Capture Mouse Move"));
window->resize(300, 250);
QWidget *centralWidget = new QWidget(window);
QHBoxLayout* layout = new QHBoxLayout(centralWidget);
MyLabel* CoordinateLabel = new MyLabel();
layout->addWidget(CoordinateLabel);
window->setCentralWidget(centralWidget);
window->show();
return app.exec();
}
mainwindow.cpp is blank
You are getting error are you are trying to define functions inside your constructor. MyLabel::MyLabel(QWidget *parent) so be
MyLabel::MyLabel(QWidget *parent) : QWidget(parent)
{
this->setAlignment(Qt::AlignCenter);
//Default Label Value
this->setText("No Value");
//set MouseTracking true to capture mouse event even its key is not pressed
this->setMouseTracking(true);
}
And then the definition for mouseMoveEvent should follow after the constructor
void MyLabel::mouseMoveEvent(QMouseEvent * event)
{
//Show x and y coordinate values of mouse cursor here
this->setText("X:" + QString::number(event->x()) + "-- Y:" + QString::number(event->y()));
}
EDIT:
As pointed out in the comments setAlignment and setText are not members of QWidget so if they are not members of MyLable then you will need to remove those otherwise it will not compile.
In order to implement your custom label you have to derive your class from Qt's standard QLabel class as:
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget *parent = 0);
~MyLabel();
protected:
void mouseMoveEvent(QMouseEvent * event);
};
Unfortunately in C++ you can not define a function inside another function as you did in MyLabel::MyLabel() constructor. Just write it in the following way:
MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
setAlignment(Qt::AlignCenter);
//Default Label Value
setText("No Value");
//set MouseTracking true to capture mouse event even its key is not pressed
setMouseTracking(true);
}
UPDATE
I would implement the handling of the mouse move event in this way:
void MyLabel::mouseMoveEvent(QMouseEvent * event)
{
// Show x and y coordinate values of mouse cursor here
QString txt = QString("X:%1 -- Y:%2").arg(event->x()).arg(event->y());
setText(txt);
}
Related
In normal screen mode, Qt widgets receive mouse events immediately after creation. In full-screen mode on macOS, however, it takes a while until the widget starts receiving those events. From my tests, it takes ~700ms on average until a widget receives its first mouse event versus ~30ms in normal screen mode. Why does it take so long for mouse events to stream in when using full-screen mode? Is there a way to force Qt to receive those events earlier? See my test in the GIF animation below.
The source code for my test follows below.
/**
* main.cpp
*/
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return QApplication::exec();
}
/**
* mainwindow.h
*/
#pragma once
#include "popupwindow.h"
#include <QWidget>
#include <QPushButton>
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
private slots:
void handleFullScreenButton();
void handleNormalScreenButton();
private:
PopupWindow* m_fullScreenWindow;
QPushButton* m_fullScreenButton;
QPushButton* m_normalScreenButton;
PopupWindow* m_normalScreenWindow;
};
/**
* popupwindow.h
*/
#pragma once
#include <QWidget>
#include <QLabel>
class PopupWindow : public QWidget
{
Q_OBJECT
public:
explicit PopupWindow(QWidget* parent = nullptr);
protected:
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
private:
void showText(const QString &text);
QLabel* m_text;
qint64 m_creationTimestamp;
bool m_eventReceived;
};
/**
* mainwindow.cpp
*/
#include "mainwindow.h"
#include <QDebug>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: m_normalScreenWindow(nullptr)
, m_fullScreenWindow(nullptr)
{
auto *layout = new QVBoxLayout(this);
m_normalScreenButton = new QPushButton("Popup screen");
m_fullScreenButton = new QPushButton("Popup full screen");
connect(m_normalScreenButton, &QPushButton::released, this, &MainWindow::handleNormalScreenButton);
connect(m_fullScreenButton, &QPushButton::released, this, &MainWindow::handleFullScreenButton);
layout->addWidget(m_normalScreenButton);
layout->addWidget(m_fullScreenButton);
}
void MainWindow::handleNormalScreenButton() {
m_normalScreenWindow = new PopupWindow(this);
m_normalScreenWindow->show();
m_normalScreenWindow->activateWindow();
m_normalScreenWindow->raise();
}
void MainWindow::handleFullScreenButton() {
m_fullScreenWindow = new PopupWindow(this);
m_fullScreenWindow->showFullScreen();
m_fullScreenWindow->activateWindow();
m_fullScreenWindow->raise();
}
/**
* popupwindow.cpp
*/
#include "popupwindow.h"
#include <QDateTime>
#include <QDebug>
#include <QHBoxLayout>
PopupWindow::PopupWindow(QWidget *parent)
: m_creationTimestamp(QDateTime::currentMSecsSinceEpoch())
, m_eventReceived(false)
, m_text(nullptr)
{
setAttribute(Qt::WA_DeleteOnClose);
setMouseTracking(true);
}
void PopupWindow::mouseMoveEvent(QMouseEvent *event) {
if (m_eventReceived) {
return;
}
auto deltaTimeMs = QDateTime::currentMSecsSinceEpoch() - m_creationTimestamp;
showText(QString("First mouse event received after %1 ms.").arg(deltaTimeMs));
m_eventReceived = true;
}
void PopupWindow::mousePressEvent(QMouseEvent *event) {
showNormal();
close();
}
void PopupWindow::showText(const QString &text) {
m_text = new QLabel(text);
m_text->setStyleSheet("font: 26pt;");
auto *layout = new QVBoxLayout(this);
layout->addWidget(m_text, Qt::AlignCenter);
}
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 having issues moving a QGraphicItem in a custom QGraphicView class. What I would like to be able to to do is select the item by a left mouse click and then move it to where I've done a right mouse click.
I stongly suspect that my problem is that QGraphicsItem::setPos() requires the coordinates to be in parent coordinates, and I'm unsure which for of QMouseEvent::*Pos() to use, and how to convert it to parent coordinates.
Screens shots of what is happening, versus what I what follow the code.
main.cpp: (simple main here, standard test harness)
#include "QtTest.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtTest w;
w.show();
return a.exec();
}
QtTest.h: (This defines the main application window)
#pragma once
#include <QtWidgets/QMainWindow>
class QGraphicsView;
class QGraphicsScene;
class QGraphicsItem;
class QMouseEvent;
class QtTest : public QMainWindow
{
Q_OBJECT
public:
QtTest(QWidget *parent = Q_NULLPTR);
private:
QGraphicsView* m_gv;
QGraphicsScene* m_pScene;
void setupUI();
};
QtTest.cpp: (implementation of the main application window)
#include "QtTest.h"
#include "testGV.h"
#include <QVariant>
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QMainWindow>
#include <QMouseEvent>
#include <QWidget>
QtTest::QtTest(QWidget *parent): QMainWindow(parent)
{
setupUI();
}
void QtTest::setupUI()
{
QWidget *centralWidget;
if (objectName().isEmpty())
setObjectName("QtTestClass");
resize(600, 400);
centralWidget = new QWidget(this);
centralWidget->setObjectName("centralWidget");
m_gv = new testGV(centralWidget);
m_gv->setObjectName("graphicsView");
m_gv->setGeometry(QRect(100, 10, 441, 331));
setCentralWidget(centralWidget);
}
testGV.h: (definition of custom widget)
#pragma once
#include <QGraphicsView>
#include <QGraphicsItem>
class testGV : public QGraphicsView
{
Q_OBJECT
public:
testGV(QWidget* parent);
protected:
void mousePressEvent(QMouseEvent*);
private:
QGraphicsScene* m_pScene;
QGraphicsItem* m_pItem;
void createScene();
};
testGV.cpp: (implementation of custom widget)
#include "testGV.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QMouseEvent>
testGV::testGV(QWidget* parent) : QGraphicsView(parent)
{
createScene();
}
void testGV::createScene()
{
m_pScene = new QGraphicsScene();
m_pScene->addRect(QRect(30, 30, 150, 150), QPen(Qt::black), QBrush(Qt::black, Qt::NoBrush));
QGraphicsEllipseItem* pTemp = m_pScene->addEllipse(QRect(0, 0, 15, 15), QPen(Qt::black), QBrush(Qt::red, Qt::SolidPattern));
pTemp->setFlag(QGraphicsItem::ItemIsMovable);
pTemp->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setScene(m_pScene);
}
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == 1) // left button click
{
m_pItem = itemAt(pEvent->pos());
}
else if (pEvent->button() == 2) // right button click
{
m_pItem->setPos(pEvent->pos());
m_pScene->update();
}
}
The image on the left is the initial display, when I right click on the red dot and then click in the square at about where the black dot is I get the image on the right. What I'm after is the red dot moving to where I clicked.
The cause of the problem is that the QMouseEvent has the position information in the coordinates of the view but the item uses the coordinates of the scene. The solution in that case is to map the coordinates of the view to the coordinates of the scene:
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == Qt::LeftButton)
m_pItem = itemAt(pEvent->pos());
else if (pEvent->button() == Qt::RightButton)
if(m_pItem)
m_pItem->setPos(mapToScene(pEvent->pos()));
}
But even so there is a problem, the position for your items is with respect to the topLeft so the displacement will have an error, if you want to avoid that deviation then you must consider the position of the left click:
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == Qt::LeftButton){
m_pItem = itemAt(pEvent->pos());
m_pItem->setData(0, mapToScene(pEvent->pos()));
}
else if (pEvent->button() == Qt::RightButton)
if(m_pItem){
QPointF p = m_pItem->data(0).toPointF();
QPointF sp = mapToScene(pEvent->pos());
m_pItem->setPos(m_pItem->pos() + sp - p);
m_pItem->setData(0, sp);
}
}
Note: When a pointer is created it points to any memory location so that can cause problems so I recommend initializing it to nullptr and also checking if the pointer is valid:
testGV::testGV(QWidget* parent) : QGraphicsView(parent), m_pItem(nullptr)
{
createScene();
}
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?
I'm working on an application where I need to be able to draw a line between two QWidget objects. I have tried quite a few things, but my current attempt (which I think is in the right direction I just think I'm missing something) is to have the containing widget (which I called DrawWidget and which holds the QGridLayout that the QWidget objects are added to) override the paintEvent method and call the QPainter::drawLine() function.
The issues I'm having are that:
No matter how I try to get the position of the widgets, the endpoints of the line are always in the wrong place
Whenever I try to draw a second line, the first line that I drew gets erased.
Here is the paintEvent function of the containing widget:
void paintEvent(QPaintEvent *)
{
if (!drewSinceUpdate){
drewSinceUpdate = true;
QPainter painter(this);
painter.setPen(QPen(Qt::black));
painter.drawLine(start->geometry().center(), end->geometry().center());
}
}
I have tried many different ways to get the correct position of the widgets in the last line of paintEvent, which I will post some of the ways (I can't remember all of them):
painter.drawLine(start->pos(), end->pos());
painter.drawLine(start->mapToGlobal(start->geometry().center()), end->mapToGlobal(end->geometry().center()));
painter.drawLine(this->mapToGlobal(start->geometry().center()), this->mapToGlobal(end->geometry().center()));
painter.drawLine(start->mapTo(this, start->pos()), end->mapTo(this, end->pos()));
painter.drawLine(this->mapFrom(start, start->pos()), this->mapFrom(end, end->pos()));
And just to make my question clear, here is an example of what I am looking for, taken from QT Diagram Scene Example:
But this is what I end up getting:
Thank you for any help you can provide.
NOTE:
-start and end are both QWidget objects which I passed in using another method
-The hierarchy relevant to DrawWidget is:
QMainWindow
->QScrollArea
->DrawWidget
->QGridLayout
->Items <-- These are the things I want to connect
EDIT: To make a Complete and Verifiable example, here is the entirety of the relevant code.
MainWindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QScrollBar>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
// Setting up the relevant hierarchy
ui->setupUi(this);
scrollArea = new QScrollArea();
setCentralWidget(scrollArea);
drawWidget = new DrawWidget();
gridLayout = new QGridLayout();
gridLayout->setSpacing(300);
drawWidget->setLayout(gridLayout);
scrollArea->setWidget(drawWidget);
scrollArea->setWidgetResizable(true);
AddItemSlot();
QApplication::connect(scrollArea->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(scrollHorizontal()));
}
// This is just creating a single one of the example widgets which I want to connect
QWidget* MainWindow::CreateNewItem(){
QWidget* itemWidget = new QWidget();
itemWidget->setStyleSheet("background-color: lightgray");
QHBoxLayout* singleItemLayout = new QHBoxLayout();
itemWidget->setLayout(singleItemLayout);
QTextEdit* textEdit = new QTextEdit(std::to_string(counter++).c_str());
textEdit->setStyleSheet("background-color:white;");
singleItemLayout->addWidget(textEdit);
QVBoxLayout* rightSidePanel = new QVBoxLayout();
rightSidePanel->setAlignment(Qt::AlignTop);
QPushButton* button1 = new QPushButton("Top Button");
QApplication::connect(button1, SIGNAL(clicked(bool)), this, SLOT(AddItemSlot()));
rightSidePanel->addWidget(button1);
QWidget* rightPanelWidget = new QWidget();
rightSidePanel->setMargin(0);
rightPanelWidget->setLayout(rightSidePanel);
singleItemLayout->addWidget(rightPanelWidget);
itemWidget->setLayout(singleItemLayout);
itemWidget->setMinimumWidth(400);
itemWidget->setFixedSize(400,200);
return itemWidget;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::scrollHorizontal()
{
scrollArea->ensureWidgetVisible(noteItems.back());
}
void MainWindow::AddItemSlot()
{
QWidget* w = CreateNewItem();
gridLayout->addWidget(w,currRow, currCol++);
if (!noteItems.empty()){
drawWidget->updateEndpoints(noteItems.back(), w);
}
noteItems.push_back(w);
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QGridLayout>
#include <QWidget>
#include <QMainWindow>
#include <QScrollArea>
#include <drawwidget.h>
#include "drawscrollarea.h"
#include <vector>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void scrollHorizontal();
void AddItemSlot();
private:
Ui::MainWindow *ui;
QWidget* CreateNewItem();
int counter = 0, currCol = 0, currRow = 0;
std::vector<QWidget*> noteItems;
QScrollArea* scrollArea;
DrawWidget* drawWidget;
QGridLayout* gridLayout;
};
#endif // MAINWINDOW_H
DrawWidget.cpp:
#include "drawwidget.h"
#include <QDebug>
#include <QRect>
DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
}
void DrawWidget::paintEvent(QPaintEvent *)
{
if (!drewSinceUpdate){
drewSinceUpdate = true;
QPainter painter(this);
painter.setPen(QPen(Qt::black));
for (ConnectedPair pair : items){
const QWidget* from = pair.from;
const QWidget* to =pair.to;
QPoint start = from->mapToGlobal(from->rect().topRight() + QPoint(0, from->height()/2));
QPoint end = to->mapToGlobal(to->rect().topLeft() + QPoint(0, to->height()/2));
painter.drawLine(mapFromGlobal(start), mapFromGlobal(end));
}
}
}
void DrawWidget::updateEndpoints(QWidget* startIn, QWidget* endIn){
drewSinceUpdate = false;
items.push_back(ConnectedPair{startIn, endIn});
}
DrawWidget.h
#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H
#include <QWidget>
#include <QPainter>
#include <QtCore>
#include <vector>
class DrawWidget : public QWidget
{
Q_OBJECT
public:
explicit DrawWidget(QWidget *parent = nullptr);
void updateEndpoints(QWidget* startIn, QWidget* endIn);
virtual void paintEvent(QPaintEvent *);
signals:
private:
struct ConnectedPair {
const QWidget* from;
const QWidget* to;
};
std::vector<ConnectedPair> items;
bool drewSinceUpdate = true;
};
#endif // DRAWWIDGET_H
For this case we use the function mapToGlobal() and mapfromGlobal(), since pos() returns a position with respect to the parent and this can cause problems if the widget has different parents.
drawwidget.h
#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H
#include <QWidget>
class DrawWidget : public QWidget
{
Q_OBJECT
public:
explicit DrawWidget(QWidget *parent = nullptr);
void addWidgets(const QWidget *from, const QWidget *to);
protected:
void paintEvent(QPaintEvent *);
private:
struct WidgetsConnected {
const QWidget* from;
const QWidget* to;
};
QList<WidgetsConnected> list;
};
#endif // DRAWWIDGET_H
drawwidget.cpp
#include "drawwidget.h"
#include <QPainter>
DrawWidget::DrawWidget(QWidget *parent) : QWidget(parent)
{
}
void DrawWidget::addWidgets(const QWidget * from, const QWidget * to)
{
list.append(WidgetsConnected{from , to});
update();
}
void DrawWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
for(const WidgetsConnected el: list){
const QWidget* from = el.from;
const QWidget* to = el.to;
QPoint start = from->mapToGlobal(from->rect().topRight() + QPoint(0, from->height()/2));
QPoint end = to->mapToGlobal(to->rect().topLeft() + QPoint(0, to->height()/2));
painter.drawLine(mapFromGlobal(start), mapFromGlobal(end));
}
}
The complete example can be found here.