I have created an Image Viewer application that opens and saves an image and load the image on QLabel,then I created a ScrollArea to scroll for large images, in my second step I am trying to draw a selection rectangle to select a specific sub area , the steps I have taken to paint the selection rectangle as follows:
1- I used an PaintEvent to paint the rectangle.
2- I used MouseEvent to select the sub area.
The problem is, when I run my code, I can draw the rectangle on the QWidget but not on the ScrollArea.
Here is my code:
in imageviewer.h
class ImageViewer : public QWidget{
Q_OBJECT
public:
explicit ImageViewer(QWidget *parent = 0);
~ImageViewer();
private:
Ui::ImageViewer *ui;
private slots:
void on_openButton_pressed();
void on_saveButton_pressed();
private:
QPixmap image;
QImage *imageObject;
bool selectionStarted;
QRect selectionRect;
QMenu contextMenu;
protected:
void paintEvent(QPaintEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
};
this is imageviewer.cpp
ImageViewer::ImageViewer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ImageViewer)
{
ui->setupUi(this);
ui->scrollArea->setWidget(ui->imageLabel);
}
open & save functions:
void ImageViewer::on_openButton_pressed()
{
QString imagePath = QFileDialog::getOpenFileName(this, tr("Open File") , "" ,
tr("JPEG (*.jpg *.jpeg);;PNG (*.png);;BMP (*.bmp)"));
imageObject = new QImage();
imageObject->load(imagePath);
image = QPixmap::fromImage(*imageObject);
ui->imageLabel->setPixmap(image);
ui->imageLabel->adjustSize();
}
void ImageViewer::on_saveButton_pressed()
{
QString imagePath = QFileDialog::getSaveFileName(this, tr("Save File") , "" ,
tr("JPEG (*.jpg *.jpeg);;PNG (*.png);;BMP (*.bmp)"));
*imageObject = image.toImage();
imageObject->save(imagePath);
}
paint & mouse event functions:
void ImageViewer::paintEvent(QPaintEvent *e){
QWidget::paintEvent(e);
QPainter painter(this);
painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine));
painter.setBrush(QBrush(QColor(255,255,255,120)));
painter.drawRect(selectionRect);
}
void ImageViewer::mousePressEvent(QMouseEvent *e){
if(e->button() == Qt::RightButton){
if(selectionRect.contains(e->pos()))
contextMenu.exec(this->mapToGlobal(e->pos()));
}
else{
selectionStarted = true;
selectionRect.setTopLeft(e->pos());
selectionRect.setBottomRight(e->pos());
}
}
void ImageViewer::mouseMoveEvent(QMouseEvent *e){
if(selectionStarted){
selectionRect.setBottomRight(e->pos());
repaint();
}
}
void ImageViewer::mouseReleaseEvent(QMouseEvent *e){
selectionStarted = false;
}
and this is a screenshot for my application
How can I draw a selection rectangle on QScrollArea?
You need QRubberBand which supposed to be directly applied to ui->imageLabel widget you use to display an image you want to scroll. Whether or not you should overload QLabel for ui->imageLabel is a question of implementation. Please see the example for mouse events which, of course better be applied to your label image class.
I understand from the way the code looks you used Qt Designer? That complicates overriding that label class but you can either not use .ui file or use event filter for trapping all the events for the given object.
What you currently have can be finally polished enough to be good for actual use, of course. It just makes much more effort for you. You can avoid overloading paintEvent etc. and the key is to apply the right approach directly where it needs to be applied.
// the example applied to authors code as requested
class MyImageLabel : public QLabel
{
public:
explicit MyImageLabel(QWidget *parent = 0) : QLabel(parent), rubberBand(0) {}
private:
QRubberBand* rubberBand;
QPoint origin;
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event)
};
void MyImageLabel::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void MyImageLabel::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void MyImageLabel::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
ImageViewer::ImageViewer(QWidget *parent) :
QWidget(parent),
ui(new Ui::ImageViewer)
{
ui->setupUi(this);
// resolve this better than:
delete ui->imageLabel; // you already have the QLabel object here?
ui->imageLabel = new MyImageLabel;
ui->scrollArea->setWidget(ui->imageLabel);
}
Related
I am trying to create a round button by subclassing and setting the region mask so that I can reuse it in my project. I know we can override paintEvent method and draw a circle to show it as a round button. But the problem with this approach is that if user clicks outside the circle (but within button rect) it will be treated as a button click. This problem we don't see when set the region mask.
I tried to set the region by calling setmask method inside resizeEvent/paintEvent. In either of case, button will be blank. I am trying to figure out the place inside the subclass to set the region mask.
RoundAnimatingButton.h ->
#include <QPushButton>
namespace Ui {
class CRoundAnimatingBtn;
}
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr);
~CRoundAnimatingBtn();
void StartAnimation(QColor r);
void StopAnimation();
public slots:
void timerEvent(QTimerEvent *e);
private:
Ui::CRoundAnimatingBtn *ui;
bool m_Spinning;
// QWidget interface
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent * e) override;
};
#endif // ROUNDANIMATINGBTN_H
RoundAnimatingButton.cpp
CRoundAnimatingBtn::CRoundAnimatingBtn(QWidget *parent)
: QPushButton (parent)
, ui(new Ui::CRoundAnimatingBtn)
, m_Spinning(false)
{
ui->setupUi(this);
}
CRoundAnimatingBtn::~CRoundAnimatingBtn()
{
delete ui;
}
void CRoundAnimatingBtn::paintEvent(QPaintEvent *e)
{
QPushButton::paintEvent(e);
if(m_Spinning)
{
// Animating code
}
}
void CRoundAnimatingBtn::StartAnimation(QColor r)
{
m_Spinning=true;
startTimer(5);
}
void CRoundAnimatingBtn::StopAnimation()
{
m_Spinning=false;
this->update();
}
void CRoundAnimatingBtn::timerEvent(QTimerEvent *e)
{
if(m_Spinning)
this->update();
else
killTimer(e->timerId());
}
void CRoundAnimatingBtn::DrawRing()
{
}
void CRoundAnimatingBtn::resizeEvent(QResizeEvent *event)
{
// -----------------------------------
// This code didn't work
// -----------------------------------
QRect rect = this->geometry();
QRegion region(rect, QRegion::Ellipse);
qDebug() << "PaintEvent Reound button - " << region.boundingRect().size();
this->setMask(region);
// ----------------------------------
// ------------------------------------
// This code worked
// -------------------------------------
int side = qMin(width(), height());
QRegion maskedRegion(width() / 2 - side / 2, height() / 2 - side / 2, side,
side, QRegion::Ellipse);
setMask(maskedRegion);
}
Qt doc. provides a sample for “non-rectangular” widgets – Shaped Clock Example.
(Un-)Fortunately, I remembered this not before I got my own sample running.
I started in Qt doc. with
void QWidget::setMask(const QBitmap &bitmap)
Causes only the pixels of the widget for which bitmap has a corresponding 1 bit to be visible. If the region includes pixels outside the rect() of the widget, window system controls in that area may or may not be visible, depending on the platform.
Note that this effect can be slow if the region is particularly complex.
The following code shows how an image with an alpha channel can be used to generate a mask for a widget:
QLabel topLevelLabel;
QPixmap pixmap(":/images/tux.png");
topLevelLabel.setPixmap(pixmap);
topLevelLabel.setMask(pixmap.mask());
The label shown by this code is masked using the image it contains, giving the appearance that an irregularly-shaped image is being drawn directly onto the screen.
Masked widgets receive mouse events only on their visible portions.
See also mask(), clearMask(), windowOpacity(), and Shaped Clock Example.
(When reading this, I still missed the link to example.)
At first, I prepared a suitable pixmap for my purpose – dialog-error.png:
for which I converted an SVG from one of my applications.
I tried to apply it to a QPushButton as icon and as mask. This looked very strange. I'm not quite sure what exactly was the problem:
- using the resp. QPushButton as toplevel widget (i.e. main window)
- the fact that QPushButtons icon rendering and the mask may not match concerning position or size.
Without digging deeper, I changed the code and fixed both issues in next try:
making a derived button (like described by OP)
using the button as non-toplevel widget.
This worked soon. I added some code to make the effect more obvious:
a mouse press event handler for main window to show whether shape is considered correctly
a signal handler to show whether clicks on button (in shape) are received correctly.
So, I came to the following sample – testQPushButtonMask.cc:
#include <QtWidgets>
class MainWindow: public QWidget {
public:
explicit MainWindow(QWidget *pQParent = nullptr):
QWidget(pQParent)
{ }
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
protected:
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
};
void MainWindow::mousePressEvent(QMouseEvent *pQEvent)
{
qDebug() << "MainWindow::mousePressEvent:" << pQEvent->pos();
QWidget::mousePressEvent(pQEvent);
}
class RoundButton: public QPushButton {
private:
QPixmap _qPixmap;
public:
RoundButton(const QPixmap &qPixmap, QWidget *pQParent = nullptr):
QPushButton(pQParent),
_qPixmap(qPixmap)
{
setMask(_qPixmap.mask());
}
virtual ~RoundButton() = default;
RoundButton(const RoundButton&) = delete;
RoundButton& operator=(const RoundButton&) = delete;
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
QSize RoundButton::sizeHint() const { return _qPixmap.size(); }
void RoundButton::paintEvent(QPaintEvent*)
{
QPainter qPainter(this);
const int xy = isDown() * -2;
qPainter.drawPixmap(xy, xy, _qPixmap);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPixmap qPixmap("./dialog-error.png");
// setup GUI
MainWindow qWin;
qWin.setWindowTitle(QString::fromUtf8("QPushButton with Mask"));
QVBoxLayout qVBox;
RoundButton qBtn(qPixmap);
qVBox.addWidget(&qBtn);
qWin.setLayout(&qVBox);
qWin.show();
// install signal handlers
QObject::connect(&qBtn, &RoundButton::clicked,
[](bool) { qDebug() << "RoundButton::clicked()"; });
// runtime loop
return app.exec();
}
The corresponding Qt project file testQPushButtonMask.pro
SOURCES = testQPushButtonMask.cc
QT += widgets
Compiled and tested on cygwin64:
$ qmake-qt5 testQPushButtonMask.pro
$ make && ./testQPushButtonMask
Qt Version: 5.9.4
MainWindow::mousePressEvent: QPoint(23,22)
MainWindow::mousePressEvent: QPoint(62,24)
MainWindow::mousePressEvent: QPoint(62,61)
MainWindow::mousePressEvent: QPoint(22,60)
RoundButton::clicked()
Concerning the output:
I clicked into the four corners of button.
I clicked on the center of button.
There's a class inherited QQuickPaintedItem and included QwtPlot (in order to use it in QML):
class QmlQwtPlot : public QQuickPaintedItem
{
Q_OBJECT
public:
QmlQwtPlot(QQuickItem* parent = 0);
~QmlQwtPlot();
void paint(QPainter *painter) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent *event) override;
private:
QwtPlot* qwtPlot;
QwtPlotPanner* panner;
bool isDragging;
QPoint previousPosition;
QwtPlotPicker* picker;
};
For example, panning a plot was implemented by using mouse events handling and the QwtPlotPanner's signal :
void QmlQwtPlot::mouseMoveEvent(QMouseEvent* event) {
if (isDragging) { // if the mouse button has been pressed
QPoint currentPosition = event->pos();
QPoint diff = currentPosition - previousPosition;
emit panner->panned(diff.x(),diff.y());
previousPosition = event->pos();
update();
}
}
QwtPlotPicker is a Qwt library class providing selections on a plot canvas. QwtPlotPicker works well (a cursor with x,y coordinates of point and cross-line moving with cursor are appeared) with QWidget, but it doesn't work with QQuickPaintedItem because of different principles of work.
/* QWidget's child constructor (the same is in QQuickPaintedItem's one) */
PlottingWidget::PlottingWidget(QWidget *parent)
: QWidget(parent)
{
// ...
d_picker = new QwtPlotPicker(
QwtPlot::xBottom, QwtPlot::yLeft,
QwtPlotPicker::CrossRubberBand,
QwtPicker::AlwaysOn,
plot->canvas()
);
d_picker->setRubberBandPen( QColor( Qt::red ) );
d_picker->setTrackerPen( QColor( Qt::black ) );
d_picker->setStateMachine( new QwtPickerTrackerMachine() );
// ...
}
How to force QwtPlotPicker work with QQuickPaintedItem?
For legacy reasons I have to do opengl rendering in Qt without QGLWidgets and QGLContext. I did some tests in a simple qt project first:
In mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void paintEvent(QPaintEvent *);
void mouseReleaseEvent (QMouseEvent * event );
void mousePressEvent (QMouseEvent * event );
QPoint m_DownPoint;
QPaintEngine* paintEngine()const;
bool event(QEvent * evt);
void DrawView();
void resizeEvent(QResizeEvent * evt);
private:
Ui::MainWindow *ui;
};
mianwindow.cpp:
All OpenGL configuration and drawing commands are in drawView function including swapBuffers().
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
MainWindow::paintEngine();
setAttribute(Qt::WA_OpaquePaintEvent);
}
void MainWindow ::paintEvent(QPaintEvent *event)
{
DrawView();
QMainWindow::paintEvent(event);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::mouseReleaseEvent(QMouseEvent * event)
{
QPoint point = QPoint(event->pos().x(),event->pos().y());
int dx, dy;
if(event->button()== Qt::LeftButton)
{
dx = point.x() - m_DownPoint.x();
dy = point.y() - m_DownPoint.y();
}
DrawView();
}
QPaintEngine* MainWindow::paintEngine() const
{
return NULL;
}
void MainWindow::DrawView()
{
HDC hdc = getDC();
// m_pView->DrawView is a call to main opengl rendering function, which accepts a HDC .
m_pView->DrawView((_HDC)hdc);
releaseDC( hdc);
}
void MainWindow::resizeEvent(QResizeEvent * evt)
{
QSize size = evt->size();
SetViewSize(size.width(), size.height());
DrawView();
}
bool MainWindow::event(QEvent * evt)
{
if (evt->type() == QEvent::Paint)
{
// The follwing line is commented out to avoid flickering
// bool result = QMainWindow::event(evt);
DrawView();
return true;
}
else if (evt->type() == QEvent::UpdateRequest)
{
// The follwing line is commented out to avoid flickering
// bool result = QMainWindow::event(evt);
DrawView();
return true;
}
return QMainWindow::event(evt);
}
void MainWindow::mousePressEvent(QMouseEvent * event)
{
QPoint point = QPoint(event->pos().x(),event->pos().y());
if(event->button()== Qt::LeftButton)
{
m_DownPoint = point;
}
}
The problem is when I drag with a mouse to resize the widgets, the widgets area is blacked out, I can see the opengl rendered image flashes, but black out instantly. I steped into qt src code and find out that the reziseEvent was sent and my Drawview is called when a resize occurs, but after that Qt did a syncbackingstore function call somehow made the widgets black.
Newbie to Qt, any advices are welcome. Qt Version 4.8.5
EDIT: paintEvent() will not be called. It remains in the code because I used to call this->update() in MouseRelease or Resize event handling functions which caused flickering in the main widget.
I am fairly new to Qt and creating a simple application that initializes a number of custom QGraphicsItems in a custom QGraphicsScene. Each item is initialized with a random start position and a Weight value which is dependent on the position of the item. On a mouse move event, i want the Weight value of the items to update based on the position of the mouse cursor
I think my the mouseMoveEvent is not recognized within the graphicsScene, it seems to work fine in the main window where i implemented a label in the status bar to show the number of mouseMoveEvents and the X-Y position of the mouseMoveEvent
Here is the code:
Custom graphics Scene .h:
class ParticleScene : public QGraphicsScene
{
public:
ParticleScene();
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
private:
qreal WTotal;
Particle *particle;
}
Custom Graphics Scene .cpp:
ParticleScene::ParticleScene()
{
//this->setBackgroundBrush(Qt::gray);
this->setSceneRect(0,0,500,500);
WTotal=0;
int ParticleCount =5;
for (int i =0; i<ParticleCount; i++)
{
particle= new Particle();
particle->StartX= rand()%500;
particle->StartY= rand()%500;
particle->W= qSqrt(qPow(particle->StartX,2) + qPow(particle->StartY,2));
particle->setPos(particle->StartX,particle->StartY);
this->addItem(particle);
particle->setFocus();
WTotal+=particle->W;
}
}
void ParticleScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
update();
QGraphicsScene::mouseMoveEvent(event);
}
Particle.h:
I added the Keypress event function and this moved only the last item that was added to the scene, i assume only one item can get focus.
The mouseMove event on the other hand didn't do anything
class Particle :public QGraphicsItem
{
public:
Particle();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
int StartX;
int StartY;
qreal W;
protected:
//added keyPressEvent to test
virtual void keyPressEvent(QKeyEvent *event);
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
};
Particle.cpp:
Particle::Particle()
{
// setFlag(ItemIsMovable);
setFlag(ItemIsFocusable);
}
QRectF Particle::boundingRect() const
{
return QRect(0,0,120,30);
}
void Particle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRectF rec= boundingRect();
QBrush Brush(Qt::white);
painter->fillRect(rec,Brush);
painter->drawText(15,15,"Weight: "+QString::number(W));
painter->drawRect(rec);
}
void Particle::keyPressEvent(QKeyEvent *event)
{
switch(event->key()){
case Qt::Key_Right:{
moveBy(30,0);
break;}
case Qt::Key_Left:{
moveBy(-30,0);
break;}
case Qt::Key_Up:{
moveBy(0,-30);
break;}
case Qt::Key_Down:{
moveBy(0,30);
break;}
}
update();
}
void Particle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
this->W= this->W / qSqrt(qPow(event->pos().x(),2) + qPow(event->pos().y(),2));
moveBy(30,0);
update();
}
MainWindow .h and cpp: the status bar label here displays the mouse coordinates correctly i.e. mouseMoveEvent functions here
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void mouseMoveEvent(QMouseEvent *event);
protected:
private:
Ui::MainWindow *ui;
ParticleScene *scene;
QLabel *statlabel;
int moves;
};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
statlabel=new QLabel(this);
ui->statusBar->addWidget(statlabel);
statlabel->setText ("Mouse Coordinates");
setCentralWidget(ui->graphicsView);
centralWidget()->setAttribute(Qt::WA_TransparentForMouseEvents);
ui->graphicsView->setMouseTracking(true);
scene= new ParticleScene();
ui->graphicsView->setScene(scene);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
moves=0;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
moves+=1;
statlabel->setText("MouseMoves " +QString::number(moves)+ " X:"+QString::number(event->pos().x())+"-- Y:"+QString::number(event->pos().y()));
}
What am I missing in the program that causes the mousemoveevent to not function and Is there a way to focus all the items together? Would i need to perhaps, make them into QList?
In the Next step of the program, I would like the items to update their weight value based on the sum of all their weights and also move based on an algorithm that uses the new weight value to determine a new position.
You are missing some kind of container in which you can save all your Particles. In the current implementation ParticleScene has particle pointer as a private variable. You are creating multiple Particles in the loop (which are QGraphicsItem's) but only the last pointer is stored in you custom Scene. As you said, you can use, for example, QList to save your Particles.
Also I assume you would like to move you items around the scene. For that you can also make you own implementation for MousePress event (to catch when item was pressed and at what coordinate), MouseMove(for actual moving the item around..you have done this already, and mouseRelease (for instance, for computing weights or sending the signal which will trigger computation of weights for all the items)
Instead of keeping all the items inside of custom scene better create a new class, for instance, ItemsManager in which all the items will be stored and the pointer to the scene. In this class you can perform all the operations concerning computation of weights for items or other operations which are required.
I have this:
mapdialog2.h
namespace Ui {
class MapDialog2;
}
class MapDialog2 : public QDialog
{
Q_OBJECT
public:
explicit MapDialog2(QWidget *parent = 0);
~MapDialog2();
protected:
void closeEvent(QCloseEvent * event);
void reject();
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void wheelEvent(QWheelEvent* event);
private:
Ui::MapDialog2 *ui;
QGraphicsScene *scene;
};
and this:
mapdialog2.cpp
MapDialog2::MapDialog2(QWidget *parent) :
QDialog(parent),
ui(new Ui::MapDialog2)
{
ui->setupUi(this);
QGraphicsScene* scene = new QGraphicsScene(this);
scene->addPixmap(QPixmap(":/new/image/project_images/Screenshot from 2015-03-09 15:37:24.png"));
scene->activePanel();
ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
for(int i=0; i<10; i++) {
scene->addRect(50+i*30,100,50,100, QColor(0,200,0,100),QColor(20+i*20,0,0,100));
}
ui->graphicsView->setScene(scene);
}
void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Debug Message";
QPointF mousePos(event->buttonDownScenePos(Qt::LeftButton).x(),
event->buttonDownScenePos(Qt::LeftButton).y());
qDebug() << mousePos;
}
I want to capture the mouse position when I press the left mouse button. But that function is never called. No presses are ever captured. Why?
The zoom and panning is all working okay, but I can't capture mouse events on the scene. I Think the problem lies with the fact that I should capture within the scene and not the QGraphisView, or the QWidget for that matter.