in my project i use a EventFilter for widgets, that are in a QHBoxLayout.
If i clicked on an a widget, i want to draw a transparent overlay with blue color over the clicked widget.
Is there a way to implement this?
greetings
This answer is in a series of my overlay-related answers: first, second, third.
One way of doing it is:
Have a semi-transparent overlay widget that is also transparent to mouse events.
In the event filter, track the clicks and the resizing of the objects by adjusting the overlay's geometry to match that of the target widget.
The self-contained example below works under both Qt 4 and Qt 5 and does what you want.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-19199863
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class Overlay : public QWidget {
public:
explicit Overlay(QWidget *parent = nullptr) : QWidget(parent) {
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter(this).fillRect(rect(), {80, 80, 255, 128});
}
};
class OverlayFactoryFilter : public QObject {
QPointer<Overlay> m_overlay;
public:
explicit OverlayFactoryFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *ev) override {
if (!obj->isWidgetType()) return false;
auto w = static_cast<QWidget*>(obj);
if (ev->type() == QEvent::MouseButtonPress) {
if (!m_overlay) m_overlay = new Overlay;
m_overlay->setParent(w);
m_overlay->resize(w->size());
m_overlay->show();
}
else if (ev->type() == QEvent::Resize) {
if (m_overlay && m_overlay->parentWidget() == w)
m_overlay->resize(w->size());
}
return false;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
OverlayFactoryFilter factory;
QWidget window;
QHBoxLayout layout(&window);
for (auto text : { "Foo", "Bar", "Baz "}) {
auto label = new QLabel{text};
layout.addWidget(label);
label->installEventFilter(&factory);
}
window.setMinimumSize(300, 250);
window.show();
return a.exec();
}
In the overlay widget constructor:
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint | Qt::ToolTip | Qt::WindowStaysOnTopHint);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
In the window that owns that widget:
overlay_ = new RtspOverlay(this);
overlay_->show();
Related
I'm trying to create a program in which you can connect points together with lines. I instantiate QGraphicsEllipseItem into a QGraphicsScene and I use HoverEnterEvent and HoverLeaveEvent to change the color and the size of the ellipses when the mouse is over them. To draw a temporary line between the point clicked and the mouse cursor I have to use MouseMoveEvent into the scene. However, when I do that, the HoverEvents of the items don't work anymore ! How can I use both MouseMoveEvent of the scene and HoverEvents of the items ?
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
for (int i = 0; i < pointList.size(); ++i) {
if (pointList[i]->over == 1){
pointList[i]->press();
lineActivated=true;
tempLine.setLine(pointList[i]->x(),pointList[i]->y(),pointList[i]->x(),pointList[i]->y());
}
}
}
void GraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(lineActivated){
const QPointF pos = event->scenePos();
tempLine.setP2(pos);
}
}
void Point::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
pen.setColor(Qt::green);
pen.setWidth(2);
this->setPen(pen);
over=true;
qDebug("enter");
update();
}
void Point::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
if(isClicked==false){
pen.setColor(Qt::lightGray);
pen.setWidth(1);
over=false;
this->setPen(pen);
qDebug("leave");
update();
}
}
By default QGraphicsScene::mouseMoveEvent sends the necessary information to handle the hover event of the items but override that method you eliminate that behavior. The solution is to call the parent method.
#include <QtWidgets>
class GraphicsScene: public QGraphicsScene{
public:
using QGraphicsScene::QGraphicsScene;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event){
if(!m_lineitem){
m_lineitem = new QGraphicsLineItem;
addItem(m_lineitem);
}
QLineF l(event->scenePos(), event->scenePos());
m_lineitem->setLine(l);
QGraphicsScene::mousePressEvent(event);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event){
if(m_lineitem){
QLineF l(m_lineitem->line().p1(), event->scenePos());
m_lineitem->setLine(l);
}
QGraphicsScene::mouseMoveEvent(event);
}
private:
QGraphicsLineItem *m_lineitem = nullptr;
};
class Point: public QGraphicsEllipseItem{
public:
Point(QGraphicsItem *parent=nullptr): QGraphicsEllipseItem(parent){
setRect(QRectF(-5, -5, 10, 10));
QPen pen;
pen.setColor(Qt::lightGray);
pen.setWidth(1);
setPen(pen);
setAcceptHoverEvents(true);
}
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event){
QPen pen;
pen.setColor(Qt::green);
pen.setWidth(2);
setPen(pen);
QGraphicsEllipseItem::hoverEnterEvent(event);
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event){
QPen pen;
pen.setColor(Qt::lightGray);
pen.setWidth(1);
setPen(pen);
QGraphicsEllipseItem::hoverLeaveEvent(event);
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
GraphicsScene *scene = new GraphicsScene;
QGraphicsView w(scene);
w.setRenderHint(QPainter::Antialiasing, true);
w.fitInView(QRectF(0, 0, 100, 100), Qt::KeepAspectRatio);
for(const QPointF & p: {QPointF(10.0, 10.0), QPointF(90.0, 20.0), QPointF(30.0, 40.0)}){
Point *it = new Point();
scene->addItem(it);
it->setPos(p);
}
w.resize(640, 480);
w.show();
return a.exec();
}
I read a lot of posts/threads but I can't get it to work.
I'd like to fit every Image to a GraphicsView regardless if it is smaller or bigger then the view.
What's wrong?
void frmMain::on_btLoadImage_clicked()
{
QGraphicsScene *scene;
QPixmap image;
QString imgPath = "O:/IMG_0001.JPG";
QRectF sceneRect = ui->imgMain->sceneRect();
image.load(imgPath);
image.scaled (sceneRect.width (),sceneRect.height (), Qt::KeepAspectRatio, Qt::SmoothTransformation);
scene = new QGraphicsScene(this);
scene->addPixmap(image);
scene->setSceneRect(sceneRect); //image.rect());
//ui->imgMain->fitInView (scene->itemsBoundingRect(), Qt::KeepAspectRatio); //ui->imgMain->width (), ui->imgMain->height ());
ui->imgMain->setScene(scene);
}
Here is a basic custom QGraphicsView implementation which displays one image and keeps it sized/scaled to fit the available viewport space. Note that the image needs to be rescaled every time the viewport size changes, which is why it is simplest to reimplement the QGraphicsView itself and change the scaling in resizeEvent(). Although it could be done inside a custom QGraphicsScene instead. (Or, really, a number of other ways depending on the exact needs.)
The same technique could be used to keep a QGraphicsWidget as the root item in the scene to always take up the full space. Then a layout could be used in the widget to keep children aligned/resized/positioned/etc.
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
class GrpahicsImageView : public QGraphicsView
{
Q_OBJECT
public:
using QGraphicsView::QGraphicsView;
public slots:
void setImage(const QString &imageFile)
{
if (m_imageFile != imageFile) {
m_imageFile = imageFile;
loadImage(viewport()->contentsRect().size());
}
}
void setImageScaleMode(int mode)
{
if (m_scaleMode != Qt::AspectRatioMode(mode)) {
m_scaleMode = Qt::AspectRatioMode(mode);
if (m_item)
loadImage(viewport()->contentsRect().size());
}
}
void loadImage(const QSize &size)
{
if (!scene())
return;
if (m_imageFile.isEmpty()) {
// remove existing image, if any
removeItem();
return;
}
// Load image at original size
QPixmap pm(m_imageFile);
if (pm.isNull()) {
// file not found/other error
removeItem();
return;
}
// Resize the image here.
pm = pm.scaled(size, m_scaleMode, Qt::SmoothTransformation);
if (createItem())
m_item->setPixmap(pm);
}
protected:
void resizeEvent(QResizeEvent *e) override
{
QGraphicsView::resizeEvent(e);
if (!scene())
return;
// Set scene size to fill the available viewport size;
const QRect sceneRect(viewport()->contentsRect());
scene()->setSceneRect(sceneRect);
// Keep the root item sized to fill the viewport and scene;
if (m_item)
loadImage(sceneRect.size());
}
private:
bool createItem() {
if (m_item)
return true;
if (!m_item && scene()) {
m_item = new QGraphicsPixmapItem();
scene()->addItem(m_item);
return true;
}
return false;
}
void removeItem()
{
if (m_item) {
if (scene())
scene()->removeItem(m_item);
delete m_item;
m_item = nullptr;
}
}
Qt::AspectRatioMode m_scaleMode = Qt::KeepAspectRatio;
QString m_imageFile;
QGraphicsPixmapItem *m_item = nullptr;
};
Usage example:
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog d;
d.setLayout(new QVBoxLayout);
d.resize(350, 350);
GrpahicsImageView *view = new GrpahicsImageView(new QGraphicsScene, &d);
QComboBox *imgCb = new QComboBox(&d);
imgCb->addItems({
"./so-logo.png",
"./se-logo.png",
"./su-logo.png"
});
QComboBox *scaleCb = new QComboBox(&d);
scaleCb->addItems({
"IgnoreAspectRatio",
"KeepAspectRatio",
"KeepAspectRatioByExpanding"
});
QHBoxLayout *cbLayout = new QHBoxLayout;
cbLayout->setSpacing(9);
cbLayout->addWidget(imgCb);
cbLayout->addWidget(scaleCb);
d.layout()->addItem(cbLayout);
d.layout()->addWidget(view);
QObject::connect(imgCb, QOverload<const QString &>::of(&QComboBox::currentIndexChanged), view, &GrpahicsImageView::setImage);
QObject::connect(scaleCb, QOverload<int>::of(&QComboBox::currentIndexChanged), view, &GrpahicsImageView::setImageScaleMode);
view->setImageScaleMode(scaleCb->currentIndex());
view->setImage(imgCb->currentText());
return d.exec();
}
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/se-logo.png
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/su-logo.png
I want to install an event filter on an object of a custom class in Qt. So I created a project such as QtGuiApplication1 on the Qt Designer and created a simple class as myClass as which has a widget and a QGraphicsView for drawing a colored rectangle.
in header file:
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtGuiApplication1.h"
#include "myClass.h"
class QtGuiApplication1 : public QMainWindow
{
Q_OBJECT
public:
QtGuiApplication1(QWidget *parent = Q_NULLPTR);
private:
Ui::QtGuiApplication1Class ui;
bool eventFilter(QObject *obj, QEvent *ev);
myClass* myClass_;
};
in .cpp
#include "QtGuiApplication1.h"
QtGuiApplication1::QtGuiApplication1(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
myClass_ = new myClass(this, QRect(100, 100, 200, 200));
myClass_->installEventFilter(this);
}
bool QtGuiApplication1::eventFilter(QObject * obj, QEvent * ev)
{
if (obj == myClass_)
{
bool hi = true;
}
return false;
}
and the myClass code is here:
header file of myClass:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QGraphicsView>
class myClass : public QObject
{
Q_OBJECT
public:
explicit myClass(QObject *parent = 0);
myClass();
myClass(QWidget* parent, QRect inRect);
private:
QWidget * widget;
QGraphicsView* qGraph_back;
QGraphicsScene* scene_back;
};
#endif /
cpp file of myClass:
#include "myClass.h"
myClass::myClass(QObject *parent) :
QObject(parent)
{
}
myClass::myClass()
{
}
myClass::myClass(QWidget* parent, QRect inRect)
{
widget = new QWidget(parent);
qGraph_back = new QGraphicsView(widget);
scene_back = new QGraphicsScene(qGraph_back);
widget->setGeometry(inRect);
scene_back->setSceneRect(0,0,inRect.width(),inRect.height());
qGraph_back->setBackgroundBrush(QColor(0, 0, 255, 80));
qGraph_back->setScene(scene_back);
qGraph_back->show();
}
I want to get all the events of myClass_ object such as mouse event, But I can't and the eventfilter doesn't work. how to install eventfilter on the object?
The event filter will work only for events in your MyClass instance, only. Not for its children.
So, events, such as a mouse click or move, in your qGraph_back will be not visible in your eventFilter method.
When you add a child in a widget, an QChildEvent event is raised. You can use it to install the event filter on all children (and grandchildren, etc.). But, you have to install the event filter on your MyClass before adding the children.
A quick example:
class Listener: public QObject
{
public:
Listener(): QObject()
{}
bool eventFilter(QObject* object, QEvent* event)
{
qDebug() << Q_FUNC_INFO << object << event;
if (event->type() == QEvent::ChildAdded)
{
QChildEvent* ev = dynamic_cast<QChildEvent*>(event);
ev->child()->installEventFilter(this);
}
return false;
}
};
class Widget: public QWidget
{
public:
Widget(QObject* parent) : QWidget()
{
installEventFilter(parent);
QGraphicsView* view = new QGraphicsView(this);
auto layout = new QHBoxLayout(this);
layout->addWidget(view);
layout->addWidget(new QLabel("Foobar"))
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Listener* listener = new Listener();
Widget* w = new Widget(listener);
w->show();
return app.exec();
}
As you can see, the events in the QLabel are now sent to the listener. But, you can't see the events from the view because they are caught by the viewport widget in the QGraphicsView...
You have to handle the case where the added child has a viewport (inherits from QAbstractItemView, etc.) and it becomes more complicated.
So, if you have to know when the user clicks on your view, it would be easier to use signals/slots and not an event filter.
I have a QDialog for beginning of my game.in this class I have a QGraphicsTextItem. I want to it is clickable. When user clicked play game start. I do this but not work.
class Mydialog_start:public QDialog
{
Q_OBJECT
public:
explicit Mydialog_start(QWidget *parent = 0);
signals:
public slots:
void on_play_clicked();
void on_exit_clicked();
private:
QGraphicsScene* scene;
QGraphicsView* view;
QPixmap image;
QBrush brush;
QGraphicsTextItem* text;
QFont font;
const int x_size;
const int y_size;
};
Mydialog_start::Mydialog_start(QWidget *parent) :
QDialog(parent),x_size(400),y_size(400)
{
scene=new QGraphicsScene(this);
view=new QGraphicsView(this);
view->setScene(scene);
scene->setSceneRect(0,0,x_size,y_size);
image.load(":picture/image/background.jpg");
image=image.scaled(x_size,y_size);
brush.setTexture(image);
scene->setBackgroundBrush(brush);
font.setBold(true);
font.setPointSize(40);
font.setItalic(true);
text=scene->addText("play",font);
text->setDefaultTextColor(QColor("red"));
text->setPos(100,300);
this->setFixedSize(400,400);
connect(text,SIGNAL(linkActivated(QString("play"))),this,SLOT(on_play_clicked()));
}
void Mydialog_start::on_play_clicked()
{
accept();
}
void Mydialog_start::on_exit_clicked()
{
reject();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
Mydialog_start dialog;
dialog.exec();
if( dialog.exec()==QDialog::Accepted)
{
w.show();
}
else
{
w.close();
}
}
Not quite sure whether you needed your text item to be "editable" - see Mitch's comment...
It seems that you need your item to be "clickable" - then all you need are some flags:
text->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
I need to create an alpha transparent widget, it's basically a navigation bar with a shadow and the widgets below need to be partially visible through the shadow. The widget loads a PNG then draws it on the paint event. The problem is that the shadow is all black and is not alpha-transparent.
This is the code I'm currently using:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
setAttribute(Qt::WA_NoBackground, true); // This is supposed to remove the background but there's still a (black) background
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, backgroundPixmap_->width(), backgroundPixmap_->height(), *backgroundPixmap_);
x += backgroundPixmap_->width();
}
}
Does anybody know what I need to change to make sure the widget is really transparent?
You're doing too much work :-)
The setAttribute call is not necessary. By default, a widget will not draw anything on its background (assuming Qt >= 4.1). Calling QWidget::paintEvent is also unnecessary - you don't want it to do anything.
Rather than doing the pattern fill yourself, let Qt do it with a QBrush:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
// debug check here:
if (!backgroundPixmap_->hasAlphaChannel()) {
// won't work
}
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), QBrush(*backgroundPixmap));
}
Adjust the height parameter if you don't want the pattern to repeat vertically.
Are you sure your PNG file is actually transparent? The following (which is essentially what you are doing) is working for me. If this fails on your machine, perhaps include what version of Qt you are using, and what platform.
#include <QtGui>
class TransparentWidget : public QWidget {
public:
TransparentWidget()
: QWidget(),
background_pixmap_(":/semi_transparent.png") {
setFixedSize(400, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, background_pixmap_);
x += background_pixmap_.width();
}
}
private:
QPixmap background_pixmap_;
};
class ParentWidget : public QWidget {
public:
ParentWidget() : QWidget() {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(new TransparentWidget);
layout->addWidget(new QPushButton("Button"));
setLayout(layout);
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
ParentWidget w;
w.show();
return app.exec();
}