How to make a floating widget in Qt

How to make a floating widget in Qt

Table of contents

I tend to make the applications I develop to be as customizable as possible because I personally enjoy customizable software. Not everyone has the same workflow.

But If you decide to accommodate a large number of workflows, you run into the risk of making your application too complicated, messy and crammed.

I encounter this problem especially when making a toolbar. Adding widgets such as combo/spin boxes would cost a lot of space, and tool buttons do not offer a lot of interaction, which made me think of using the latter as an anchor instead. A way to display and hide a larger set of settings and widgets.

Take a look:

Floating widget displayed in the top left corner of the screen

Basic template

A floating widget is not contained in any layout, it…well…floats, meaning its position is relative to the screen, not the application’s window.

To achieve that, we could use Qt::popup window flag:

setWindowFlag(Qt::Popup,true);

This would take care of making the widget a top-level one, modal and hide when clicked outside of it. Now remains controlling where it appears.

Let’s use a QToolButton in QToolBar, and make the widget popup where its center would align with the tool button’s center.

We could implement that by calculating its (x,y) coordinates in showEvent:

Calculating x:

  • Get the x coordinate of the tool button’s center.

  • Map it to global.

  • Subtract half of the widget width from it.

int parentGlobalX = parentWidget()->mapToGlobal( parentWidget()->rect().center() ).x();
//push it to the left by half of its width
int x = parentGlobalX - width() / 2;

Calculating y:

Use the tool button’s height as the y coordinate of a QPoint, so it can be mapped to global.

int y = parentWidget()->mapToGlobal( QPoint(0, parentWidget()->geometry().height() ) ).y();

Setting new Position :

Use QWidget::move, and pass it the new (x,y) coordinates:

move(x,y);

Here’s the resulting showEvent:

void showEvent(QShowEvent *event)
{
    int parentGlobalX = parentWidget()->mapToGlobal( parentWidget()->rect().center() ).x();
    //push it to the left by half of its width
    int x = parentGlobalX - width() / 2;
    int y = parentWidget()->mapToGlobal( QPoint(0, parentWidget()->geometry().height() ) ).y();

    move(x,y);
}

Here’s how it looks:

Basic floating widget preview

Usage

Full Class:

class FloatWidget : public QWidget
{
public:
    FloatWidget(QWidget *parent) : QWidget(parent)
    {
        setWindowFlag(Qt::Popup,true);
        setStyleSheet("background: white;");
    }

protected:
    void showEvent(QShowEvent *event) 
    {
        int parentGlobalX = parentWidget()->mapToGlobal( parentWidget()->rect().center() ).x();
        //push it to the left by half of its width
        int x = parentGlobalX - width() / 2;
        int y = parentWidget()->mapToGlobal( QPoint(0, parentWidget()->geometry().height() ) ).y();

        move(x,y);
    }
};

Here’s an example of how to use it in a QMainWindow:

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

    floating_widgetA = new FloatWidget(ui->toolBar->widgetForAction(ui->actionA));
}

//use QAction triggered signal and connect it to a slot
//to call the floating widget's show in
void MainWindow::on_actionA_triggered()
{
    floating_widgetA->show();
}

From this, you can customize it however you like, I made mine animated and wrote a custom paintEvent for it; I’ve also added a pointer widget to help identify which widget it belongs to.

You can take a look at my public repository on Bitbucket to get some ideas.