Custom Qt designer widget : a scroll Area containing a custom vertical layout - qt

I want to do something fairly simple : add a custom widget to Qt designer that would basically be a scrollArea containing a custom vertical layout(I added some code to the vertical layout in order to handle its objects for my projects).
The idea would be to represent a vertical menu that would be on the side of my screen
What I have done so far
I created the custom widget plugin and my custom layout.
My custom widget codes looks like this:
#include "menuwidget.h"
MenuWidget::MenuWidget(QWidget *parent) :
QScrollArea(parent)
{
this->setWidgetResizable(true);
QWidget* layoutHoldingWidget= new QWidget(this);
layout= new MenuLayout();
layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
layout->addStretch(1);
layoutHoldingWidget->setLayout(layout);
this->setWidget(layoutHoldingWidget);
}
If I add manually to the layout (in the constructor code) some buttons
for(int i =0;i<20;i++)
layout->addWidget(new QPushButton(this));
It does work and I can see my scrollArea containing some buttons, which is almost what I want.
What I want
I would like to be able to add these buttons directly via Qt designer: the user would first drag the empty MenuWidget on the main window, then would drag QPushButtons on my custom widget exactly like he would do on a regular vertical layout.
Is that possible?How could I do such a thing?
Thank you ! :)
Edit 1
What I was missing was the "scrollAreaWidgetContents" widget that is always created when you drag and drop a QScrollArea. I did a similar thing by adding a widget (let's call it containerWidget) to my custom scrollArea in its domXml function, which enables me to drag and drop widgets on my scroll Area like I wanted to do.
BUT there's still something I can't figure out : I want the containerWidget to have a customLayout (myCustomLayout) . If I add it in the domXml function, I get the following line in the terminal :
Designer:The layout type 'MyCustomLayout' is not supported,
defaulting to grid.
So it means that I can't tell designer to use my custom layout to place my widgets, which is kind of sad :D
Is there any way to "cheat" here?

There are two things to consider:
1) Overwrite in the class you derive from QDesignerCustomWidgetInterface the function to return true
bool isContainer() const { return true; }
This tells QtDesigner that the widget can contain children. (In Qt nearly any Widget can contain any widget as child, but QtDesigner tries to restrict it in a sensible way - e.g. you cant add children to a QLabel in QtDesigner)
2) Implement childEvent of your Widget. Probably in your case it would add Widgets added in QtDesigner to a layout.
Here is a core I've implemented to try this out. I've created a skeleton using "Qt Widget Plugin" Wizard in QtCreator and modified a little bit.
Don't forget to build as release, for the compiler/Qt-version of your QtDesigner , to copy the .dll and .lib files in \plugins\designer directory and to restart QtDesigner!
verticalplugin.cpp
//all other functions remained as created by QtCreator wizard
bool VerticalMenuPlugin::isContainer() const
{
return true;
}
VerticalMenu.h
#ifndef VERTICALMENU_H
#define VERTICALMENU_H
#include <QtGui/QWidget>
#include <QtGui/QVBoxLayout>
class VerticalMenu : public QWidget
{
Q_OBJECT
protected:
virtual void childEvent ( QChildEvent * event );
public:
VerticalMenu(QWidget *parent = 0);
};
#endif
VerticalMenu.cpp
#include "verticalmenu.h"
#include <QChildEvent>
VerticalMenu::VerticalMenu(QWidget *parent) :
QWidget(parent)
{
setLayout (new QVBoxLayout);
}
void VerticalMenu::childEvent ( QChildEvent * event )
{
if ( event->added() )
{
QWidget * newChild = qobject_cast<QWidget *>(event->child());
if ( newChild )
{
layout()->addWidget( newChild );
}
}
}
I hope' it would help as a starting point.

Qt 4 does not support custom layout plugins for designer, so I couldn't achieve what I wanted to do. I will instead use a Vertical Layout and try to implement the additional features that were supposed to be in the custom layout code in the widget code.

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).

Promote custom widget with custom constructor in QT Creator

I know, this is basically the same question, but my problem goes further.
The following tree explains my structure:
QWidget
|
CustomWidget
| |
MyTable MyWidgetAroundIt
I have promoted MyTable in Qt Designer. So, I can add it to MyWidgetAroundIt. That worked quite well. The only problem is, CustomWidget requires it's parent to be a CustomWidget too, its constructor looks like:
CustomWidget(CustomWidget* parent) : QWidget(parent), _specialValue(parent->getSpecialValue)
This causes compile errors, as the designer generated code tries to initialize MyTable with a QWidget*, instead of the CustomWidget*. What could/should I do to prevent this and/or give the designer a hint about this requirement?
A widget whose parent can't be a QWidget is not a widget anymore. Your design breaks the Liskov Substitution Principle and has to be fixed.
You're free to enable special functionality if the widget happens to be of a certain type, but a widget must be usable with any widget for a parent.
Thus:
CustomWidget(QWidget* parent = nullptr) :
QWidget(parent)
{
auto customParent = qobject_cast<CustomWidget*>(parent);
if (customParent)
_specialValue = customParent->specialValue();
}
or:
class CustomWidget : public QWidget {
Q_OBJECT
CustomWidget *_customParent = qobject_cast<CustomWidget*>(parent());
SpecialType _specialValue = _customParent ? _customParent->specialValue() : SpecialType();
SpecialType specialValue() const { return _specialValue; }
public:
CustomWidget(QWidget * parent = nullptr) : QWidget(parent) {}
};

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

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.

How to create a scrollable QVBoxLayout?

I'm trying to put a QVBoxLayout inside a QScrollArea in order for it to be scrollable vertically. However items don't seem to be added to it.
I saw a suggestion that I ought to create an inner widget that the ScrollArea uses and to place the layout inside that, although it doesn't seem to have worked. My structure is supposed to look like this:
+-------------------------------
| QScrollArea(realmScroll)
| +----------------------------
| | QWidget(realmScrollInner)
| | +-------------------------
| | | QVBoxLayout(realmLayout)
And the code to do this:
# Irrelevant, added for context (this works)
centralWidget = QWidget(self)
self.container = QVBoxLayout(centralWidget)
centralWidget.setLayout(self.container)
self.setCentralWidget(centralWidget)
# Where trouble starts
self.realmScroll = QScrollArea(self.container.widget())
self.realmScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.realmLayout = QVBoxLayout(self.container.widget())
self.realmScrollInner = QWidget(self.realmScroll)
self.realmScrollInner.setLayout(self.realmLayout)
self.realmScroll.setWidget(self.realmScrollInner)
self.container.addWidget(self.realmScroll)
# Doesn't add to realmLayout
self.realmLayout.addWidget(QLabel("test"))
I'm still learning Qt (2 days in), so in-depth answers to where I'm going wrong would be appreciated.
Update:
It seems that the addWidget(QLabel()) works right up until the realmScrollInner has been set as realmScroll's widget. Since I'd like to add elements after the UI has been displayed I have to do this, which I'm not sure is really correct:
self.realmLayout.addWidget(QLabel("test"))
# realmScrollInner bound to realmScroll
realmScroll.setWidget(realmScrollInner)
self.container.addWidget(realmScroll)
# Access realmScroll's widget and then layout to add
realmScroll.widget().layout().addWidget(QLabel("test"))
But if you remove that first call to addWidget before the widget has been bound (so the layout has no widgets), then bind to the ScrollArea widgets added afterwards are not displayed. Perhaps the ScrollArea needs repainting (although I don't see a method for that)?
Update 2: Calling repaint() on realmScroll or its contained widget does nothing, as does calling activate/update() on the layout.
It turned out that I was lead down a wrong path by putting the layout as the layout of a widget. The actual way to do this is as simple as:
scrollarea = QScrollArea(parent.widget())
layout = QVBoxLayout(scrollarea)
realmScroll.setWidget(layout.widget())
layout.addWidget(QLabel("Test"))
Which I'm pretty sure I tried originally, but hey it's working.
However this adds an issue that the layout's items are shrunk vertically instead of causing the scrollarea to add a scrollbar.
OK, I just got done fighting with this. Here's a widget that can go into a scroll area (scrollarea->setWidget) and work correctly. It contains a QVBoxLayout and a list of label/listwidget pairs, each in their own little horizontal layout, and it does pretty much what you'd want.
The important thing was reading the QScrollArea docs section on Size Hints and Layouts, and finding the bit where having the sizeContraint QLayout::SetMinAndMaxSize on the layout would be necessary.
class MappingDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit MappingDisplayWidget(QWidget *parent = 0);
void addFile(QString name);
private:
QVBoxLayout *m_layout;
QMap<QString, QListWidget *> m_mappings;
};
MappingDisplayWidget::MappingDisplayWidget(QWidget *parent) :
QWidget(parent)
{
m_layout = new QVBoxLayout;
m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
setLayout(m_layout);
}
void MappingDisplayWidget::addFile(QString name) {
if (m_mappings.find(name) == m_mappings.end()) {
QWidget *widg = new QWidget;
QHBoxLayout *lay = new QHBoxLayout;
widg->setLayout(lay);
QLabel *nlab = new QLabel(name);
lay->addWidget(nlab);
QListWidget *list = new QListWidget;
lay->addWidget(list);
m_layout->addWidget(widg);
m_mappings[name] = list;
}
}
I keep pointers to the list widgets so that I can add stuff to them later, and that works fine.
Try calling
self.realmScroll.setWidgetResizable(True)

Resources