How to make a Qt widget invisible without changing the position of the other Qt widgets? - qt

I've got a window full of QPushButtons and QLabels and various other fun QWidgets, all layed out dynamically using various QLayout objects... and what I'd like to do is occasionally make some of those widgets become invisible. That is, the invisible widgets would still take up their normal space in the window's layout, but they wouldn't be rendered: instead, the user would just see the window's background color in the widget's rectangle/area.
hide() and/or setVisible(false) won't do the trick because they cause the widget to be removed from the layout entirely, allowing other widgets to expand to take up the "newly available" space; an effect that I want to avoid.
I suppose I could make a subclass of every QWidget type that override paintEvent() (and mousePressEvent() and etc) to be a no-op (when appropriate), but I'd prefer a solution that doesn't require me to create three dozen different QWidget subclasses.

This problem was solved in Qt 5.2. The cute solution is:
QSizePolicy sp_retain = widget->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
widget->setSizePolicy(sp_retain);
http://doc.qt.io/qt-5/qsizepolicy.html#setRetainSizeWhenHidden

The only decent way I know of is to attach an event filter to the widget, and filter out repaint events. It will work no matter how complex the widget is - it can have child widgets.
Below is a complete stand-alone example. It comes with some caveats, though, and would need further development to make it complete. Only the paint event is overridden, thus you can still interact with the widget, you just won't see any effects.
Mouse clicks, mouse enter/leave events, focus events, etc. will still get to the widget. If the widget depends on certain things being done upon an a repaint, perhaps due to an update() triggered upon those events, there may be trouble.
At a minimum you'd need a case statement to block more events -- say mouse move and click events. Handling focus is a concern: you'd need to move focus over to the next widget in the chain should the widget be hidden while it's focused, and whenever it'd reacquire focus.
The mouse tracking poses some concerns too, you'd want to pretend that the widget lost mouse tracking if it was tracking before. Properly emulating this would require some research, I don't know off the top of my head what is the exact mouse tracking event protocol that Qt presents to the widgets.
//main.cpp
#include <QEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QApplication>
class Hider : public QObject
{
Q_OBJECT
public:
Hider(QObject * parent = 0) : QObject(parent) {}
bool eventFilter(QObject *, QEvent * ev) {
return ev->type() == QEvent::Paint;
}
void hide(QWidget * w) {
w->installEventFilter(this);
w->update();
}
void unhide(QWidget * w) {
w->removeEventFilter(this);
w->update();
}
Q_SLOT void hideWidget()
{
QObject * s = sender();
if (s->isWidgetType()) { hide(qobject_cast<QWidget*>(s)); }
}
};
class Window : public QWidget
{
Q_OBJECT
Hider m_hider;
QDialogButtonBox m_buttons;
QWidget * m_widget;
Q_SLOT void on_hide_clicked() { m_hider.hide(m_widget); }
Q_SLOT void on_show_clicked() { m_hider.unhide(m_widget); }
public:
Window() {
QGridLayout * lt = new QGridLayout(this);
lt->addWidget(new QLabel("label1"), 0, 0);
lt->addWidget(m_widget = new QLabel("hiding label2"), 0, 1);
lt->addWidget(new QLabel("label3"), 0, 2);
lt->addWidget(&m_buttons, 1, 0, 1, 3);
QWidget * b;
b = m_buttons.addButton("&Hide", QDialogButtonBox::ActionRole);
b->setObjectName("hide");
b = m_buttons.addButton("&Show", QDialogButtonBox::ActionRole);
b->setObjectName("show");
b = m_buttons.addButton("Hide &Self", QDialogButtonBox::ActionRole);
connect(b, SIGNAL(clicked()), &m_hider, SLOT(hideWidget()));
QMetaObject::connectSlotsByName(this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"

You can use a QStackedWidget. Put your button on the first page, a blank QWidget on the second, and change the page index to make your button vanish while retaining its original space.

I've 3 solutions in my mind:
1) Subclass your QWidget and use a special/own setVisible() replacement method witch turns on/off the painting of the widget (if the widget should be invisible simply ignore the painting with an overridden paintEvent() method). This is a dirty solution, don't use it if you can do it other ways.
2) Use a QSpacerItem as a placeholder and set it's visibility to the opposite of the QWidget you want to hide but preserve it's position+size in the layout.
3) You can use a special container widget (inherit from QWidget) which gets/synchronizes it's size based on it's child/children widgets' size.

I had a similar problem and I ended up putting a spacer next to my control with a size of 0 in the dimension I cared about and an Expanding sizeType. Then I marked the control itself with an Expanding sizeType and set its stretch to 1. That way, when it's visible it takes priority over the spacer, but when it's invisible the spacer expands to fill the space normally occupied by the control.

May be QWidget::setWindowOpacity(0.0) is what you want? But this method doesn't work everywhere.

One option is to implement a new subclass of QWidgetItem that always returns false for QLayoutItem::isEmpty. I suspect that will work due to Qt's QLayout example subclass documentation:
We ignore QLayoutItem::isEmpty(); this means that the layout will treat hidden widgets as visible.
However, you may find that adding items to your layout is a little annoying that way. In particular, I'm not sure you can easily specify layouts in UI files if you were to do it that way.

Here's a PyQt version of the C++ Hider class from Kuba Ober's answer.
class Hider(QObject):
"""
Hides a widget by blocking its paint event. This is useful if a
widget is in a layout that you do not want to change when the
widget is hidden.
"""
def __init__(self, parent=None):
super(Hider, self).__init__(parent)
def eventFilter(self, obj, ev):
return ev.type() == QEvent.Paint
def hide(self, widget):
widget.installEventFilter(self)
widget.update()
def unhide(self, widget):
widget.removeEventFilter(self)
widget.update()
def hideWidget(self, sender):
if sender.isWidgetType():
self.hide(sender)

I believe you could use a QFrame as a wrapper. Although there might be a better idea.

Try void QWidget::erase (). It works on Qt 3.

Related

How to access QTextEdit "layout" to add "status bar"?

This a modified question I have posted on this forum.
It is not a repost, for two reasons - I cannot edit the other post and I am trying to
resolve this issue from another angle. Besides the other post got derailed by people who mean well but did not really read the post.
I have a working C++ code which is using (QT) QTextEdit class to collect and process text data.
The class - as its name suggest - was designed to collect and analyze text.
The text is displayed in "view " area.
My task is to select ONE word of text and drag it to another GUI object.
I like to put the text being dragged into QT standard "status bar".
Normal QT GUI widgets are designed using QTDesigner. Integral part of such design process
is "layout".
As it stands , QTextEdit DOES NOT HAVE / USE "layout" or use it but it is NOT visible / accessible when QTextEdit is implemented - there is no need for it.
In order to add "status bar" I need to MODIFY the view to hold the current text editing
"layout" and add "status bar " layout.
I am unable to figure out how to get access to the QTextEdit class GUI layout.
I am asking for help to accomplish that- how to add "status bar" to EXISTING QTextEdit.
Please read the post carefully _ I need help with how to add "status bar" to EXISTING QTextEdit.
I do have an option to replace the QTextEdit with basic "widget" class but it "breaks " the working code and I rather not do that.
I did look into setting multiple inheritance - Qwidget and QTextEdit but did not work.
PLEASE Mr Higgins , editing my post for proper English grammar and composition DOES NOT solve the problem. So , please - don't.
I'm not entirely sure but your question seems to imply that inheriting from QTextEdit would be acceptable. If that's the case then you can probably make use of the fact that QTextEdit itself inherits QAbstractScrollArea and use the viewport margins to create an area in which to show a status bar of some sort.
Consider the following code...
#include <QApplication>
#include <QScrollBar>
#include <QStatusBar>
#include <QTextEdit>
namespace {
class text_edit: public QTextEdit {
using super = QTextEdit;
public:
explicit text_edit (QWidget *parent = nullptr)
: super(parent)
, m_status(this)
{
m_status.setStyleSheet("background-color: gray;");
m_status.showMessage("Status text goes here...");
show_status(true);
horizontalScrollBar()->installEventFilter(this);
verticalScrollBar()->installEventFilter(this);
setLineWrapMode(QTextEdit::NoWrap);
}
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
if (event->type() == QEvent::Show || event->type() == QEvent::Hide)
update_status_geometry();
return super::eventFilter(obj, event);
}
virtual void resizeEvent (QResizeEvent *event) override
{
super::resizeEvent(event);
update_status_geometry();
}
private:
void show_status (bool on)
{
if (on) {
setViewportMargins(0, 0, 0, m_status.height());
m_status.show();
} else {
setViewportMargins(0, 0, 0, 0);
m_status.hide();
}
}
void update_status_geometry ()
{
/*
* Calculate initial geometry of the QStatusBar based on its size hint.
*/
auto s = m_status.sizeHint();
s.rwidth() = width();
QRect geom(QPoint(0, 0), s);
geom.moveTop(height() - s.height());
/*
* Adjust the status bar geometry to allow for the scroll bars.
*/
if (horizontalScrollBar()->isVisible())
geom.moveTop(geom.top() - horizontalScrollBar()->height());
if (verticalScrollBar()->isVisible())
geom.setRight(geom.right() - verticalScrollBar()->width());
m_status.setGeometry(geom);
}
QStatusBar m_status;
};
}
int
main (int argc, char **argv)
{
QApplication app(argc, argv);
text_edit te;
te.show();
return app.exec();
}
Running the code above results in the following widget...

How to make QToolButton go beyond the edge of QToolbar?

How can I make the button go beyond the edge of QToolbar?
Below is the code as I create the toolbar:
mainwindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
private:
QToolBar* _toolBar;
};
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
_toolBar = new QToolBar;
QAction *actionAdd = new QAction(QIcon(":/images/add.png"), "", this);
_toolBar->addAction(actionAdd);
addToolBar(Qt::ToolBarArea::TopToolBarArea, _toolBar);
}
style.qss
QToolBar {
background: #018ac4;
height: 150px;
}
As said before, it is not possible to solve this correctly using QtWidgets.
However I see two options to visually create that effect:
Take the button out of the tool bar and add it to the main window instead, but do not add it to a layout. Usually i would say reposition it on resize events, but since it is in the top left, you might as well just call setGeometry() once on startup and not worry about it later. You probably have to add last, or call rise() though.
Make it look like the button sticks out, while it really doesn't. Make the toolbar as large as the button, but paint the lower part of the toolbar in the brighter blue, so that it looks like it is part of the widget below it.
It is not possible with widgets. A QWidget can not paint outside of its area. See this answer : https://stackoverflow.com/a/48302076/6165833.
However, the QToolBar is not really the parent of the QAction because addAction(QAction *action) does not take the ownership. So maybe the QMainWindow could paint your QAction the way you want but AFAIK this is not doable through the public API of Qt.
What you could do is use QML (but you would need to use QML for the whole window then).

Overriding QLabel to be able to draw graphs

I want to draw a graph on my main form, so I figured I'd use a QLabel and Override that. Like this:
// drawlabel.h
class DrawLabel : public QLabel
{
Q_OBJECT
public:
DrawLabel(QWidget *parent = 0);
private:
void paintEvent(QPaintEvent *);
};
// drawlabel.cpp
DrawLabel::DrawLabel(QWidget *parent)
: QLabel(parent)
{
}
void DrawLabel::paintEvent(QPaintEvent *)
{
qDebug() << "paint event" ;
QPainter painter(this);
painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine));
painter.setBrush(QBrush(QColor(255,255,255,120)));
QRect selectionRect(10, 10, 100, 101);
painter.drawRect(selectionRect);
}
On my main window I droppde a QLabel, sized it to about 500x200 and promoted it to DrawLLabel. When the application is run, a dashed square is drawn on the form.
All good so far.
If I add the line:
this->setText("123456");
into the DrawLabel constructor, or add it into the paintEvent() I don't see the text. I'd also like to be able to have a border around the DrawLabel, but
this->setFrameShape(QFrame::Box);
in the constructor doesn't work either.
What should I be doing to get these to work?
Well, I think you should call paintEvent of base class. Add parameter name e to method:
void DrawLabel::paintEvent(QPaintEvent *e)
And then at end of method add
QLabel::paintEvent (e);
The second option do all painting by yourself directly at paintEvent.
If you want something custom, then implement a custom widget inheriting QWidget. Then you get to draw whatever you want and have whatever members you want.
Your problem is you have overridden the label's paint event, so the code to draw the label text is not executed.
You could call the method from QLabel as Evgeny suggested, but it is better to implement a custom widget instead.
Calling the method from the base class might for example corrupt any previous drawing, unless the method was implemented with calling form derived classes in mind. I don't expect that is the case for stock widgets. I haven't tried doing it with QLabel re-implementations in particular, but I have tried it with other stock widgets and it did not work as expected.

Hide QLineEdit blinking cursor

I am working on QT v5.2
I need to hide the blinking cursor (caret) of QLineEdit permanently.
But at the same time, I want the QLineEdit to be editable (so readOnly and/or setting editable false is not an option for me).
I am already changing the Background color of the QLineEdit when it is in focus, so I will know which QLineEdit widget is getting edited.
For my requirement, cursor (the blinking text cursor) display should not be there.
I have tried styleSheets, but I can't get the cursor hidden ( {color:transparent; text-shadow:0px 0px 0px black;} )
Can someone please let me know how can I achieve this?
There is no standard way to do that, but you can use setReadOnly method which hides the cursor. When you call this method it disables processing of keys so you'll need to force it.
Inherit from QLineEdit and reimplement keyPressEvent.
LineEdit::LineEdit(QWidget* parent)
: QLineEdit(parent)
{
setReadOnly(true);
}
void LineEdit::keyPressEvent(QKeyEvent* e)
{
setReadOnly(false);
__super::keyPressEvent(e);
setReadOnly(true);
}
As a workaround you can create a single line QTextEdit and set the width of the cursor to zero by setCursorWidth.
For a single line QTextEdit you should subclass QTextEdit and do the following:
Disable word wrap.
Disable the scroll bars (AlwaysOff).
setTabChangesFocus(true).
Set the sizePolicy to (QSizePolicy::Expanding, QSizePolicy::Fixed)
Reimplement keyPressEvent() to ignore the event when Enter/Return is hit
Reimplement sizeHint to return size depending on the font.
The implementation is :
#include <QTextEdit>
#include <QKeyEvent>
#include <QStyleOption>
#include <QApplication>
class TextEdit : public QTextEdit
{
public:
TextEdit()
{
setTabChangesFocus(true);
setWordWrapMode(QTextOption::NoWrap);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setFixedHeight(sizeHint().height());
}
void keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
event->ignore();
else
QTextEdit::keyPressEvent(event);
}
QSize sizeHint() const
{
QFontMetrics fm(font());
int h = qMax(fm.height(), 14) + 4;
int w = fm.width(QLatin1Char('x')) * 17 + 4;
QStyleOptionFrameV2 opt;
opt.initFrom(this);
return (style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(w, h).
expandedTo(QApplication::globalStrut()), this));
}
};
Now you can create an instance of TextEdit and set the cursor width to zero :
textEdit->setCursorWidth(0);
Most straight forward thing I found was stolen from this github repo:
https://github.com/igogo/qt5noblink/blob/master/qt5noblink.cpp
Basically you just want to disable the internal "blink timer" Qt thinks is somehow good UX (hint blinking cursors never were good UX and never will be - maybe try color or highlighting there eh design peeps).
So the code is pretty simple:
from PyQt5 import QtGui
app = QtGui.QApplication.instance()
app.setCursorFlashTime(0)
voilĂ .
Solution in python:
# somelibraries
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.setFocus() # this is what you need!!!
container = QWidget()
container.setLayout(self.layout)
# Set the central widget of the Window.
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
I ran into the same problem but setReadOnly is not a viable option because it alters the UI behavior in other places too.
Somewhere in a Qt-forum I found the following solution that actually solves the problem exactly where it occurs without having impact on other parts.
In the first step you need to derive from QProxyStyle and overwrite the pixelMetric member function:
class CustomLineEditProxyStyle : public QProxyStyle
{
public:
virtual int pixelMetric(PixelMetric metric, const QStyleOption* option = 0, const QWidget* widget = 0) const
{
if (metric == QStyle::PM_TextCursorWidth)
return 0;
return QProxyStyle::pixelMetric(metric, option, widget);
}
};
The custom function simply handles QStyle::PM_TextCursorWidth and forwards otherwise.
In your custom LineEdit class constructor you can then use the new Style like this:
m_pCustomLineEditStyle = new CustomLineEditProxyStyle();
setStyle(m_pCustomLineEditStyle);
And don't forget to delete it in the destructor since the ownership of the style is not transferred (see documentation). You can, of course, hand the style form outside to your LineEdit instance if you wish.
Don't get complicated:
In QtDesigner ,
1.Go the the lineEdit 's property tab
2.Change focusPolicy to ClickFocus
That's it...

Subclassing QLabel to show native 'Mouse Hover Button indicator'

I have a QLabel with a 'StyledPanel, raised' frame.
It is clickable, by subclassing QLabel;
class InteractiveLabel(QtGui.QLabel):
def __init__(self, parent):
QtGui.QLabel.__init__(self, parent)
def mouseReleaseEvent(self, event):
self.emit(QtCore.SIGNAL('clicked()'))
However, a general opinion is that this 'Box' is not easily recognised as clickable.
In an effort toward usability, I'd like the 'Box' to show it is clickable when the mouse is hovered over it.
Obviously a reaction to a mouse hover is easily achieved by connecting the mouseHoverEvent.
However, the 'button indicator' must be natively inherited, since my Qt application allows the User to change the style (out of Windows XP, Windows 7, plastique, motif, cde).
This image shows the particular widget (bottom right corner) and the mouseHover aesthetics I desire in two different styles.
When a mouse is hovered over 'Box', I'd like it to respond like the combobox has in the top, middle.
(The 'response' is aesthetically native and occurs with all Qt buttons, except in 'CDE' and 'motif'styles.).
Is there a way to implement this with PyQt4?
(I suppose non-native solutions would involve QGradient and checking the native style, but that's yucky.)
UPDATE:
lol4t0's idea of a QLabel over a QPushButton.
Here's my pythonic implementation with signals working properly and all the appropriate button aesthetics.
The RichTextBox is the widget you would embed into the program.
from PyQt4 import QtCore, QtGui
class RichTextButton(QtGui.QPushButton):
def __init__(self, parent=None):
QtGui.QPushButton.__init__(self, parent)
self.UnitText = QtGui.QLabel(self)
self.UnitText.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
self.UnitText.setAlignment(QtCore.Qt.AlignCenter)
self.UnitText.setMouseTracking(False)
self.setLayout(QtGui.QVBoxLayout())
self.layout().setMargin(0)
self.layout().addWidget(self.UnitText)
Thanks!
Specs:
- python 2.7.2
- Windows 7
- PyQt4
Main idea
You can add QLabelabove QPushButton (make QLabel child of QPushButton) and show rich text in label, while clicks and decorations can be processed with QPushButton
Experiment
Well, I am a C++ programmer, but there is nothing complicated, I hope, you understand the code
Implementing main idea:
QLabel * label = new QLabel(pushButton);
label->setText("<strong>sss</strong>");
label->setAlignment(Qt::AlignCenter);
label->setMouseTracking(false);
pushButton->setLayout(new QVBoxLayout(pushButton));
pushButton->layout()->setMargin(0);
pushButton->layout()->addWidget(label);
And this almost works! The only one silly bug (or my global misunderstanding) is that when you press button with mouse and then release it, it remains pressed.
- So, it seems we need to reimplement mouseReleaseEvent in our label to fix always pressed issue:
I'm pretty sure, there is a bit more elegant solution, but I'm too lazy to find it now, so, I made following:
class TransperentLabel: public QLabel
{
public:
TransperentLabel(QWidget* parent):QLabel(parent) {}
protected:
void mouseReleaseEvent(QMouseEvent *ev)
{
/*
QApplication::sendEvent(parent(), ev); -- does not help :(
*/
static_cast<QPushButton*>(parent())->setDown(false);
static_cast<QPushButton*>(parent())->click(); //fixing click signal issues
}
};
As #Roku said, to fix that issue, we have to add
label->setTextInteractionFlags(Qt::NoTextInteraction);
#Lol4t0, i have some improvements for your method...
This is my header file:
#ifndef QLABELEDPUSHBUTTON_H
#define QLABELEDPUSHBUTTON_H
#include <QPushButton>
class QLabel;
class QLabeledPushButton : public QPushButton
{
Q_OBJECT
QLabel * m_label;
public:
QLabeledPushButton(QWidget * parent = 0);
QString text() const;
void setText(const QString & text);
protected:
void resizeEvent(QResizeEvent * event);
};
#endif // QLABELEDPUSHBUTTON_H
And there is my cpp file:
#include <QLabel>
#include <QVBoxLayout>
#include <QResizeEvent>
#include "QLabeledPushButton.h"
QLabeledPushButton::QLabeledPushButton(QWidget * parent)
: QPushButton(parent)
, m_label(new QLabel(this))
{
m_label->setWordWrap(true);
m_label->setMouseTracking(false);
m_label->setAlignment(Qt::AlignCenter);
m_label->setTextInteractionFlags(Qt::NoTextInteraction);
m_label->setGeometry(QRect(4, 4, width()-8, height()-8));
}
QString QLabeledPushButton::text() const
{
return m_label->text();
}
void QLabeledPushButton::setText(const QString & text)
{
m_label->setText(text);
}
void QLabeledPushButton::resizeEvent(QResizeEvent * event)
{
if (width()-8 < m_label->sizeHint().width())
setMinimumWidth(event->oldSize().width());
if (height()-8 < m_label->sizeHint().height())
setMinimumHeight(event->oldSize().height());
m_label->setGeometry(QRect(4, 4, width()-8, height()-8));
}
So text on QLabel is always visible. QPushButton can't be too small to hide part of text. I think this way is more comfortable to use...

Resources