7
votes

While developing Qt UIs, I often want to display a notification or other display tool on top of an existing UI.

However, I have only discovered two ways of doing this, but neither completely solves the problem.

Option 1

Make the notification widget a sibling of the displayed widgets, but don't add it to any layout and raise it above the other one.

Problem: The widget can't be in a layout, and so you have to manually size it to fit it's contents, and you have to move it to the correct position whenever the parent window is resized.

Option 2

Add the widget to the layout right alongside the displayed widgets.

Problem: Since the widget is part of the layout, it moves the other displayed widgets around when it is visible, rather than on top of them.

Example - here the notification pushes the "v0.3b" label upwards rather than appearing on top of it. On the upside, since it is part of the layout with the spacer on its left, it is sized appropriately to its contents. enter image description here

Solution

Is there something like a QStackedWidget that displays multiple pages at once, with the empty area showing the widgets beneath? Is there another option for this kind of UI feature?

3

3 Answers

1
votes

You are right, QStackedWidget/QStackedLayout is your friend. Make a custom widget with stacked layout, when message comes in, you set active widget to the one with warning message, with a timer, you flip back to normal one. This custom widget can be placed in the main window in bottom right corner. With spacer, this can be done fairly easy, right?

0
votes

One way I might do it is to create the notification widget as its own custom widget with a size that matches that of the widget it will sit on. Make the background of the custom widget transparent.

NotificationWidget::paintEvent(QPaintEvent* event) {
  QPainter painter(this);
  QColor bg;
  bg.setAlpha(0);

  painter.fillRect(rect(), QBrush(bg));
  QWidget::paintEvent(event);
}

Only the notification part of that widget is drawn. It can be put into a layout as normal. Now, whenever you resize the parent, the custom notification widget will be resized as well and the visible part of this widget will be in the correct spot. You'll need to do the following in your constructor as well:

NotificationWidget::NotificationWidget(QWidget* parent = 0) : QWidget(parent) {
  setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
  setAutoFillBackground(false);

  // add whatever widgets/layouts you want
}
0
votes

You can use your option 1 with some tweaks:

  • Create the popup widget, give it a relevant parent widget, but don't add it to any layout.
  • Give the popup widget itself a layout and any child elements to make it as complex as you want.
  • Call setSizeConstraint(QLayout::SetFixedSize); on the popup widget layout.
  • Use move() on the popup widget to position it (relative to its parent widget)
  • Use show() and hide() on the popup widget to control its visibility.

Contrary to the naming, the SetFixedSize option doesn't require you to actually hard-code a fixed size. It just means the widget will always use its (dynamic) sizeHint() as the size it should be. All other size constraint options (expanding etc...) rely on a combination of sizeHint() and a parent layout, which you don't have in this case.

Example:

HoverWidget::HoverWidget(QWidget *parent)
    : QFrame(parent)
    , label1(new QLabel(this))
    , label2(new QLabel(this))
{
    auto vlayout = new QVBoxLayout(this);
    vlayout->addWidget(label1);
    vlayout->addWidget(label2);
    vlayout->setSizeConstraint(QLayout::SetFixedSize);
    hide();
}

HoverWidget::showNear(int left, int top)
{
    auto const hoverSize = sizeHint();   // don't use size() here.
    auto const parentSize = parentWidget()->size();
    // TODO: make this code a bit more complex to make sure the widget stays
    //       within the parent, by using hoverSize and parentSize to edit
    //       the left, top parameters.
    move(left, top);
    show();
}