How to make a QWidget alpha-transparent - qt

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();
}

Related

How to Zoom / Fit Image to GraphicsView

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

Draw Rectangular overlay on QWidget at click

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();

Subclassed QWidget does not move correctly

I have subclassed QWidget as follows:
class myClass : public QWidget
{
public:
explicit myClass(QWidget *parent);
protected:
void paintEvent(QPaintEvent *event);
}
myWidget::myWidget(QWidget* parent) : QWidget(parent)
{
setGeometry(10,10,100,100);
}
void myWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bBlue(QColor::blue);
qp.fillRect(geometry(), bBlue);
}
What I wanted was to create a blue background QWidget placed onto the QWidget parent at 10,10 of size 100,100.
What I'm getting is a default size for myWidget of something like 100,50 at 0,0 with a black background (or transparent) and a blue rectangle starting at 10,10 within myWidget and clipped by myWidget.
It's like the setGeometry moved a rectangle within myWidget, not the myWidget itself.
Fairly new to Qt and would love an explanation and fix of above...
Thank you in advance.
Gary.
...here is actual code:
this is myWidget
class piTemplateWidget : public QWidget
{
public:
explicit piTemplateWidget(QWidget* parent);
static QColor* white;
static QColor* black;
static QColor* lightGrey;
static QColor* lightGreen;
piTemplate* tplt;
protected:
void paintEvent(QPaintEvent *event);
};
QColor* piTemplateWidget::white = new QColor(15,15,15);
QColor* piTemplateWidget::black = new QColor(250,250,250);
QColor* piTemplateWidget::lightGrey = new QColor(100,100,100);
QColor* piTemplateWidget::lightGreen = new QColor(250,15,250);
piTemplateWidget::piTemplateWidget(QWidget* parent) : QWidget(parent)
{
tplt = NULL;
move(100,100);
resize(300,240);
}
void piTemplateWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bWhite(*white);
qp.fillRect(this->geometry(), bWhite);
// if (tplt==NULL)
// return;
// tplt->render(&qp);
}
...and this is the parent widgets constructor which instantiates my widget
piTemplateEdit::piTemplateEdit(QWidget *parent) :
QWidget(parent),
ui(new Ui::piTemplateEdit)
{
ui->setupUi(this);
currentTemplate = NULL;
if (piTemplate::templates->count()>0)
{
currentTemplate = (piTemplate*)piTemplate::templates->atIndex(0);
}
templateWidget = new piTemplateWidget(this);
templateWidget->tplt = currentTemplate;
}
...I hopes this helps.
Thank you.
Setting the geometry during the constructor may get overridden by the show event that the parent widget calls on it.
A common main function can look like this:
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// w.showMaxmized(); // This line would trump the "setGeometry() call
// in the constructor
return a.exec();
}
The geometry rect stored in a QWidget is described here:
http://qt-project.org/doc/qt-4.8/application-windows.html
http://qt-project.org/doc/qt-4.8/qwidget.html#pos-prop
I would not use this internal QWidget setting as how you fill your widget. If you do want to store some setting, make a QRect member variable and use that instead.
If you want to fill the entire box of your QWidget with a color you should try something like this:
void myWidget::paintEvent(QPaintEvent *event)
{
QPainter qp(this);
QBrush bBlue(QColor::blue);
qp.fillRect(QRect(0,0, this->width(), this->height()), bBlue);
}
Inside paint functions, they are relative to paintable area you are in.
http://qt-project.org/doc/qt-4.8/qwidget.html#mapTo
And like #LaszloPapp was saying, you need to use resize() and move(). And it wouldn't hurt to throw in a update() call after either one of those.
Also be sure to check out the show() method and all of its "See Also" items.
http://qt-project.org/doc/qt-4.8/qwidget.html#show
http://qt-project.org/doc/qt-4.8/qshowevent.html
If you #include <QShowEvent>, and call resize() when the show event happens, you may be good to go. If you are nesting this widget inside another widget you should look into using the size hint and setFixedSize or using Layouts properly.
http://qt-project.org/doc/qt-4.8/layout.html
Hope that helps.

Sliding OSX Dock type "sidebar" rather embedded QDockWidget type?

I want to make a sliding "sidebar" similar to the functionality of the OSX "Dock" (e.g. mouse passes edge of screen and Dock slides out). I've been playing around with QDockWidget but since that is embedded in the window layout, it causes everything to shift when it becomes visible.
Can someone suggest a way to implement this?
Doesn't need to float (as a separate window/tool bar)
Should scale to window height (e.g. window can be fullscreen or default size)
Doesn't need to slide (animate) if that is complicated.
I'm new to Qt and so don't want to over-think this. Is this just a matter of a custom widget or should I be looking at a borderless window? The custom widget approach seems right but I don't know how to specify that it overlay other window content and also scale if the window scales.
QDockWidget has nothing to do with what you want - behaviorally. Just because it's called a Dock widget doesn't mean it's the same "Dock" concept as in OS X. It merely means that it docks somewhere. QDockWidget's documentation quite explicitly explains what is meant by the docking behavior.
The code below implements the behavior you seem to want. Whether it's good design or not is arguable. The reason the code is "convoluted" seems to hint that nobody is expected to come up with such a UI design. What's wrong with actually clicking a button somewhere to display the slider window?
The code works under both Qt 4.8 and 5.1.
Note: This begs to be implemented in Qt Quick 2. That's what it was designed for :) Of course Qt 4.6+ improved the behavior of the QWidget-moving animations, and Qt 5 does further tweaks, but really this code smells bad and there's a good reason it does: QWidget API, while powerful, ultimately encapsulates a set of APIs that date to 1984 when the original Macintosh was released. There's only so much you can do when you have to composite results from a bunch of stacked painters. In Qt Quick, the rendering is done by the GPU. The animation amounts to passing a couple of new floats to the GPU to update a single transformation matrix. That's it.
#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QGradient>
#include <QMouseEvent>
#include <QPropertyAnimation>
class Slider : public QWidget {
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
QPainter p(this);
QLinearGradient g(QPointF(0,0), QPointF(rect().bottomRight()));
g.setColorAt(0, Qt::blue);
g.setColorAt(1, Qt::gray);
p.setBackground(g);
p.eraseRect(rect());
p.setPen(Qt::yellow);
p.setFont(QFont("Helvetica", 48));
p.drawText(rect(), "Click Me To Hide");
}
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE {
hide();
}
public:
explicit Slider(QWidget *parent = 0) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
};
class Window : public QWidget {
QGridLayout m_layout;
Slider m_slider;
QLabel m_label;
QPropertyAnimation m_animation;
public:
explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f),
m_layout(this),
m_slider(this),
m_animation(&m_slider, "pos")
{
setMouseTracking(true);
m_layout.addWidget(&m_label);
m_slider.hide();
m_slider.setMouseTracking(false);
m_animation.setStartValue(QPoint(-width(), 0));
m_animation.setEndValue(QPoint(0, 0));
m_animation.setDuration(500);
m_animation.setEasingCurve(QEasingCurve::InCubic);
}
void leaveEvent(QEvent *) {
if (window() && QCursor::pos().x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
void childEvent(QChildEvent * ev) {
if (ev->added() && ev->child()->isWidgetType()) {
ev->child()->installEventFilter(this);
static_cast<QWidget*>(ev->child())->setMouseTracking(true);
}
}
bool event(QEvent * ev) {
eventFilter(this, ev);
return QWidget::event(ev);
}
bool eventFilter(QObject *, QEvent * ev) {
if (ev->type() == QEvent::MouseMove) {
auto pos = QCursor::pos();
if (window() && window()->isFullScreen()) {
if (pos.x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
m_label.setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
}
return false;
}
void resizeEvent(QResizeEvent *) {
m_slider.resize(size());
m_animation.setStartValue(QPoint(-width(), 0));
}
Q_SLOT void showSlider() {
if (m_slider.isVisible() || (window() && qApp->activeWindow() != window())) return;
m_slider.raise();
m_slider.show();
m_animation.start();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}

Increase button font size when button size is changing

I'm having a Qt application with a main window that has five buttons aligned in a vertical order.
They all have the same size.
All I want to do is to increase the font size of the button label when the app goes fullscreen.
I would really appreciate a solution that does not need too much code ... was hoping that this was something that could be done in Qt Designer, but I couldn't find a way how to.
Any suggestions?
Best,
guitarflow
I can't think of any way to do it in designer, but it's really not too much code. Here's a quick-and-dirty proof of concept. You'd want to take into account margins (using QStyle::pixelMetrics and the like), but you get the idea.
#include <QtGui>
class FontAdjustingButton : public QPushButton {
public:
explicit FontAdjustingButton(QWidget *parent = NULL) : QPushButton(parent) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
protected:
void resizeEvent(QResizeEvent *event) {
int button_margin = style()->pixelMetric(QStyle::PM_ButtonMargin);
QFont f = font();
f.setPixelSize(event->size().height() - button_margin * 2);
setFont(f);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QWidget w;
QVBoxLayout *layout = new QVBoxLayout;
for (int i = 0; i < 5; ++i) {
FontAdjustingButton *btn = new FontAdjustingButton;
btn->setText(QString("Hello, world %1").arg(i));
layout->addWidget(btn);
}
w.setLayout(layout);
w.show();
return app.exec();
}

Resources