I'm writing a desktop application on Windows 10 that does something similar to "share your screen/app window", and I've got the classic problem of trying to highlight the region of interest on the screen. So I need to draw a bright and thick rectangle, have the rectangle always be visible and 'on top', and have no interference with user input, mouse movement, etc. (i.e. all pass-through).
I can't make it work properly with Qt v5.7. I either get an opaque window (I can't see what's "below" it) with the right border, or a transparent window with only a black 1-pixel border.
I vaguely know that if I were to use Windows-specific APIs, I could create a window with WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVE style etc.
(using something like a "chrome window" - example in C# here), but besides the fact that I haven't tried yet (and need to do in C++ and not C#), I'd rather do it with Qt is possible.
Case A I'm still a novice with Qt, but I thought that using a QFrame was the way to go, so I wrote some code:
//
// A- Displays a completely transparent rectangle with a black 1-pixel border.
//
QFrame *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
frame->setAttribute(Qt::WA_TranslucentBackground, true);
frame->setGeometry(1000,500,600,300); // Just some fixed values to test
frame->show();
That gives me this, a rectangle with a black 1-pixel thick border:
It's great, in that the rectangle stays on top of everything else, is transparent, pass-through for mouse input etc., cannot get focus, be resized or moved, and doesn't show up on the task bar.
Case B I thought the only problem left was to draw a thick bright border, so I coded this just before the call to frame->show():
// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");
.. but that gave me exactly, er, nothing. The QFrame was not showing at all!
Case C So as a test, I removed the setting of Qt::WA_TranslucentBackground. Here's the code:
//
// C- Displays an opaque pass-through window with a green 5-pixel border.
//
QFrame *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
// Disabled Qt::WA_TranslucentBackground
//frame->setAttribute(Qt::WA_TranslucentBackground, true);
frame->setGeometry(1000,500,600,300); // Just some fixed values to test
// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");
frame->show();
That window is still completely pass-through, stay-on-top, etc. and has the right border, but of course it's opaque.
Now, the fact that:
with Qt::WA_TranslucentBackground set and without changing the style sheet, I get a black 1-pixel border/frame;
with Qt::WA_TranslucentBackground set and with changing the style sheet, I don't get a border/frame at all (the window becomes invisible);
without Qt::WA_TranslucentBackground set and with changing the style sheet, I get the expected border/frame;
.. seems to imply that there's some style set for getting the black 1-pixel outside border when the window is transparent. When I changed the border style myself, this stays fully inside the window frame, and thus disappears when the window is transparent (case B) - I think.
Does anybody know what the right style sheet settings should be so that I get the window fully transparent with its 5-pixel green border?
Also, I couldn't find any documentation that tells me exactly what styles could apply to what type of widget/window (and QFrame in particular, for my test case). Does that exist anywhere? It would be handy to know, as I'd also like to use gradient colours for the border, and possibly other effects.
Try replacing the QFrame in your code with something like...
class rubber_band: public QRubberBand {
using super = QRubberBand;
public:
template<typename... Types>
explicit rubber_band (const Types &... args)
: super(args...)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
QPen pen(Qt::green);
pen.setWidth(10);
painter.setPen(pen);
painter.drawLine(rect().topLeft(), rect().topRight());
painter.drawLine(rect().topRight(), rect().bottomRight());
painter.drawLine(rect().bottomRight(), rect().bottomLeft());
painter.drawLine(rect().bottomLeft(), rect().topLeft());
}
};
An instance of the above rubber_band class should display as a green border with completely transparent body. Use as...
rubber_band *frame = new rubber_band(QRubberBand::Rectangle);
frame->setGeometry(1000, 500, 600, 300);
frame->show();
(Note: On platforms using X11 the above will require that a composite manager such as xcompmgr be running. That's usually not an issue.)
I found a solution, which is to:
set a mask on the QFrame to exclude the inside of the frame - so that becomes completely transparent.
not set Qt::WA_TranslucentBackground (otherwise nothing is visible, as per case B in the question).
I've also set the opacity to 0.5 so that the border that gets rendered is partly transparent.
The final code is:
//
// D- Displays a completely transparent pass-through window with a green 5-pixel translucent border.
//
QFrame *frame = new QFrame();
frame->setFrameStyle(QFrame::Box | QFrame::Plain);
frame->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint);
frame->setGeometry(1000,500,600,300); // Just some fixed values to test
// Set a solid green thick border.
frame->setObjectName("testframe");
frame->setStyleSheet("#testframe {border: 5px solid green;}");
// IMPORTANT: A QRegion's coordinates are relative to the widget it's used in. This is not documented.
QRegion wholeFrameRegion(0,0,600,300);
QRegion innerFrameRegion = wholeFrameRegion.subtracted(QRegion(5,5,590,290));
frame->setMask(innerFrameRegion);
frame->setWindowOpacity(0.5);
frame->show();
What we end up with is this:
I worried somewhat that all this masking would lead to a not very optimal solution, but I realised afterwards that the C# example using Windows APIs actually does something very similar (a rectangle within a rectangle to exclude the region to stay transparent).. so maybe this is the right way to go.
Using stylesheets, if I set background-color of a QDialog, I don't seem to be able to control the width of the visible colour - the gap between the content and the actual border. For example if I create a QDialog with only a QListWidget on it, in a QGridLayout, I see the background-color appear as a border around the QListWidget. I would like to make this thinner.
How can I reduce this "border"? It looks as thought background-clip would work if QDialog supported the box model.
I am on 4.7 if it makes any difference
That's the layout border. You can reduce it from the design editor by selecting yout QDialog then adjusting the layoutLeftMargin/layoutTopMargin/layoutRightMargin/layoutBottomMargin properties.
You can also set the border width by code by calling setContentMargins on the layout. For example:
ui->gridLayout->setContentsMargins(3,3,3,3); // sets the qdialog border width to 3px.
How can I get the pressed background color of a QPushButton?
if (isDown())
_BGColor = <pressed background color>; // how to get this???
else
_BGColor = palette().color(QPalette::Background); // this is to get the idle backcolor
Thanks in advance!!!
It's very difficult (if not impossible) to find a way to get the backgound color of a button when it's pressed, because it depends on the style, and it's not guaranteed that the style respects the palette.
However I suggest two different approaches:
You can set you own background color using style sheets (simpler) or implement the painting of the button yourself, using styles or reimplementing paintEvent(). See Customizing QPushButton
To paint over the button with the inverse color, you can set a composition mode to the painter in order to get the inverse color.
For example:
painter.setPen(QColor(255, 255, 255));
painter.setCompositionMode(QPainter::RasterOp_SourceAndNotDestination);
(note that using this example, the inverse color of middle grey (128, 128, 128) is exactly the same color)
See QPainter::CompositionMode
I have a QDockWidget:
I would like to alert the user to certain events by setting the background color of the title bar.
I have achieved this by setting the style sheet for my DockWidget:
void DockWidget::setCriticalAlert()
{
setStyleSheet("QDockWidget { background-color:red; }");
}
The result is this:
The problem is that the background-color doesn't get applied when the QDockWidget is docked:
How can I get the background color to be applied when the QDockWidget is docked?
This is a bug in Qt.
Issue 10537
Quoting from the linked issue:
The problem is that in QDockWidget::paintEvent, there is a
isFloating() condition before drawing PE_FrameDockWidget. We cannot
jsut remove this condition as it would break the other style (that
does not whish to draw frame when the dockwidget is docked) We cannot
either use PE_Widget to draw the frame as then it goes over the
dockwidget's title The solution is maybe to introduce a new
PE_FrameDockWidgetDocked primitive element. Or some
SH_DockWidget_DrawDockedFrame stylehint to draw the frame in every
cases.
a valid workaround seems to be to set the stylesheet of the parent, and use the class-and-id selector. Forgive the python formatted code but the concept is the same - in this case, 'dock' is a QDockWidget which has been given an object name using setObjectName(), and its parent, the QMainWindow, is 'self':
self.setStyleSheet("QDockWidget#"+str(dock.objectName())+"::title {background-color:red}")
In PyQt5.5, this works at runtime, i.e., can be changed on the fly.
I find a solution like this:
Firstly put a frame behind all the widgets of dockwidget's center widget, as the background.
Then set stylesheet for the frame.
By this way, we could change the background color of dockwidget.
Or you can extend the dockwidget and overwrite the function
void QDockWidget::setWidget(QWidget *widget)
using private/qdockwidget_h. and add a frame as this widget's father.
In Stringray grid, there is the ability to use a transparent background which allows the background of the dialog to be shown through the grid.
In the documentation it states:
But be careful; you should disable scrolling or you have to redraw the grid each time it is scrolled (by overriding DoScroll).
I have a scrollable gird and override the DoScroll and make sure I call Redraw and also tried Invalidate, however the grid is still not completely erasing and redrawing.
I also tried using the old drawing method by setting m_bForceOldDrawing to TRUE.
How can I create a grid that has a transparent background that paint correctly after a scroll without leaving artifacts?
Yes you have to redraw the grid by overriding DoScroll because it is no longer using ScrollWindow to scroll contents because the background is transparent.
However you now have artifacts of the grid over your background.
This is because the background behind the grid is not getting redrawn.
Do you have clipchildren set for the parent?
Another potential problem is that the background is not being drawn because it doesn't realize it has been exposed.
Try calling the parent with the following.
Parent.Invalidate();
Parent.UpdateWindow();
before calling...
Invalidate();