0

I'm trying to use a QQuickWidget as an overlay over a QWidget. The QQuickWidget contains some buttons and a couple other widgets laid out along the bottom.

I've found that I can get the QQuickWidget to properly overlay the QWidget by using a combination of setAttribute(Qt::WA_AlwaysStackOnTop) and setClearColor(Qt::transparent). But the problem I'm having is that all the mouse events are being directed to the main window instead of the underlying QWidget.

I setup a sample project to reproduce my problem.

OverlayItem.qml is defined as follows:

import QtQuick 2.6
import QtQuick.Controls 1.4

Item {
    width: parent.width
    height: parent.height

    Row {
        anchors { 
            horizontalCenter: parent.horizontalCenter; 
            bottom: parent.bottom; 
            bottomMargin: 15 
        }
        spacing: 15
        Button {
            text: "Button1"
            onClicked: {
                console.log("Button1 CLICKED!")
            }
        }
        Button {
            text: "Button2"
            onClicked: {
                console.log("Button2 CLICKED!")
            }
        }
        Button {
            text: "Button3"
            onClicked: {
                console.log("Button3 CLICKED!")
            }
        }
    }
}

The QQuickWidget is created and the QML loaded when MainWindow is created:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    qw(NULL)
{
    ui->setupUi(this);

    qw = new QQuickWidget(ui->frame);
    qw->setResizeMode(QQuickWidget::SizeRootObjectToView);
    qw->setAttribute(Qt::WA_AlwaysStackOnTop);
    qw->setClearColor(Qt::transparent);
    qw->setSource(QUrl("OverlayItem.qml"));
}

void MainWindow::showEvent(QShowEvent *event)
{
    if (auto pw = qw->parentWidget())
    {
        qw->resize(pw->size());
    }
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
    if (auto pw = qw->parentWidget())
    {
        qw->resize(pw->size());
    }
}

And here's the MainWindow UI code:

class Ui_MainWindow
{
public:
    QWidget *centralWidget;
    QGridLayout *gridLayout_2;
    QFrame *frame;
    QGridLayout *gridLayout;
    QTextBrowser *textBrowser;

    void setupUi(QMainWindow *MainWindow)
    {
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QStringLiteral("MainWindow"));
        MainWindow->resize(800, 600);

        QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
        sizePolicy.setHorizontalStretch(0);
        sizePolicy.setVerticalStretch(0);
        sizePolicy.setHeightForWidth(MainWindow->sizePolicy().hasHeightForWidth());
        MainWindow->setSizePolicy(sizePolicy);

        centralWidget = new QWidget(MainWindow);
        centralWidget->setObjectName(QStringLiteral("centralWidget"));

        gridLayout_2 = new QGridLayout(centralWidget);
        gridLayout_2->setSpacing(6);
        gridLayout_2->setContentsMargins(11, 11, 11, 11);
        gridLayout_2->setObjectName(QStringLiteral("gridLayout_2"));

        frame = new QFrame(centralWidget);
        frame->setObjectName(QStringLiteral("frame"));
        frame->setFrameShape(QFrame::StyledPanel);
        frame->setFrameShadow(QFrame::Raised);

        gridLayout = new QGridLayout(frame);
        gridLayout->setSpacing(6);
        gridLayout->setContentsMargins(11, 11, 11, 11);
        gridLayout->setObjectName(QStringLiteral("gridLayout"));

        textBrowser = new QTextBrowser(frame);
        textBrowser->setObjectName(QStringLiteral("textBrowser"));
        textBrowser->setReadOnly(false);

        gridLayout->addWidget(textBrowser, 0, 0, 1, 1);

        gridLayout_2->addWidget(frame, 0, 0, 1, 1);

        MainWindow->setCentralWidget(centralWidget);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
    } // setupUi
};

As you can see, the CentralWidget contains a QFrame which, in turn, contains a QTextBrowser. The desired behavior of this example code is for the QTextBrowser to remain functional while 3 buttons are overlaid at the bottom. However, when I run this code, I'm unable to click on the QTextBrowser in order to give it focus. Instead, MainWindow receives all of the non-button-click mouse events.

I've played around with event filters and reparenting, but I don't seem to be getting anywhere. Has anyone had success trying something like this?

Knute Knudsen
  • 1,049
  • 1
  • 12
  • 24
  • Wrong application design leads to such problems. Actually you should not mix `QtQuick` and `QWidgets`. Use one for GUI and another one for logics. – folibis Dec 03 '17 at 11:44
  • Why do you want to put qml contents over the QTextBrowser rather than placing QQuickWidget below it using a layout manager? The qml buttons will potentially cover the text preventing the user positioning the cursor behind them anyway. – absolute.madness Dec 03 '17 at 16:36
  • It's "wrong" but I've got a lot of QGraphicsWidget code that is going to take a long time to move over to QML. Until then, I'd like to take advantage of some of the QML features (e.g., animations) for the overlay widget. – Knute Knudsen Dec 03 '17 at 16:46
  • Re: putting qml over the QTextBrowser -- this is just a dummy example to illustrate the problem. My real application has a much richer interface, the buttons and other widgets are semi-transparent, they auto-hide, etc. Good point, though. I wish that would work in my case. – Knute Knudsen Dec 03 '17 at 16:50
  • 1
    @KnuteKnudsen The main problem as I see it is how to figure out which mouse events for QQuickWidget should be propagated to its QWidget-siblings and which should be handled inside qml. I don't know an elegant solution for QQuickWidget. The only thing I could suggest is an obvious idea to make QQuickWidget more compact to overlap as little as possible and hide it on timeout of no mouse moving. Perhaps it's not an option for your case. – absolute.madness Dec 03 '17 at 18:56
  • @absolute.madness The funny thing is that the underlying QWidget doesn’t receive the mouse events in the first place. It seems like it has to do with using the AlwaysStackOnTop attribute. But your idea about hiding the QQuickWidget sounds promising. In fact I bet I could replace the QQuickWidget with a bitmap after a short timeout and it would be undetectable. A workaround, for sure, but might get the job done. – Knute Knudsen Dec 03 '17 at 20:00
  • 1
    @KnuteKnudsen The fact that the underlying widget doesn't receive mouse events is predictable. QApplication sends a mouse event to the child under mouse. If it cannot handle it (QWidget::event() returns false) the event goes up to its parent then to the parent's parent etc. In order for the underlying widgets to receive mouse events you could use [Qt::WA_TransparentForMouseEvents](http://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum) but that will make your QQuickWidget miss the mouse events... – absolute.madness Dec 03 '17 at 20:29
  • @absolute.madness Thank you and what you’ve described makes sense. However, the TextBrowser and the QQuickWidget are siblings. I was expecting the mouse events would pass through to the sibling before being routed to the parent after taking into account the stacking order. Regardless, when I make TextBrowser the parent of the QQuickWidget (qw = new QQuickWidget(ui->textBrowser);) I get the same behavior. Is that not contrary to the routing order you’ve described? – Knute Knudsen Dec 04 '17 at 01:02
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160395/discussion-between-knute-knudsen-and-absolute-madness). – Knute Knudsen Dec 04 '17 at 05:57

0 Answers0