How do I toggle 'always on top' for a QMainWindow in Qt without causing a flicker or a flash?
Asked Answered
S

4

26
void MainWindow::on_actionAlways_on_Top_triggered(bool checked)
{
    Qt::WindowFlags flags = this->windowFlags();
    if (checked)
    {
        this->setWindowFlags(flags | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint);
        this->show();
    }
    else
    {
        this->setWindowFlags(flags ^ (Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint));
        this->show();
    }
}

The above solution works but because setWindowFlags hides the window, it needs to be re-shown and of course that doesn't look very elegant. So how do I toggle "always on top" for a QMainWindow without that "flashing" side effect?

Swoop answered 18/5, 2010 at 9:30 Comment(0)
R
20

Nokia says no:

It is not possible to make changes to the window flags once the window has been created without causing flicker. Flicker is unavoidable since the window needs to be recreated.

But sometimes if you're stuck with a flashing effect that's kind of ugly like this, you can intentionally drag it out to make it seem like something "cool" just happened.

Maybe pop up a little progress bar that's not in the window, say "Adjusting Window Properties!"...fade the window out of existence and then back in, and close the progress bar popup.

Role answered 18/5, 2010 at 9:41 Comment(6)
For an "always on top" feature, this must be possible. Plenty of applications do it without flicker; perhaps I'll just need to use some native Windows functions?Swoop
If you want to go beneath or sidestep the Qt implementation (of destroying the window and making it again) then possibly. But at least some of these things are properties of the "Window Class" IIRC and not the HWND ; and you may not be able to change a window's class after it has been created. Either way, I think the value of coming up with an answer you can accept without breaking the Qt abstraction layer is better than getting mired in Win32 hacks. That's the joy of Qt in the first place...Role
@ereOn: Yeah, I have some stuff that puts up a progress bar and (regardless of task completion) won't take it down unless a minimum flicker period has passed since the bar was put up. So the computer is "slowing you down" in a sense but the experience is better because it didn't make an alarming blip that went right away. Lots of progress bar psychology out there. :) scribd.com/doc/2226848/Rethinking-The-Progress-BarRole
@HostileFork I'm actually going to accept your answer rather than my own, since I asked for a Qt solution, to which you gave the correct answer.Swoop
Thanks Jake! Well, both the answers are still here, so people can make their own choice. Good luck with submitting the patch! ;)Role
Get the HWND via winId() & use SetWindowLongPtr on that. Check the win32 docs, there are also APIs like ShowWindow, that won't cause flickering. Also, fire a bug report about that someday :)Wickiup
S
20

Well, for a solution I figured I'd look in the Mono sources, since I know the .NET Form class (System.Windows.Forms) has a TopMost property.

The solution I found for my Qt program was:

void MainWindow::on_actionAlways_on_Top_triggered(bool checked)
{
#ifdef Q_OS_WIN
    // #include <windows.h>
    if (checked)
    {
        SetWindowPos(this->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    }
    else
    {
        SetWindowPos(this->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
    }
#else
    Qt::WindowFlags flags = this->windowFlags();
    if (checked)
    {
        this->setWindowFlags(flags | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint);
        this->show();
    }
    else
    {
        this->setWindowFlags(flags ^ (Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint));
        this->show();
    }
#endif
}
Swoop answered 18/5, 2010 at 20:12 Comment(4)
If you really think it's worth it (!) But I would suggest at least breaking it out as a separate method, e.g. void setWindowStaysOnTopHint(bool windowStaysOnTopHint). Then your ifdef wouldn't be spanning your button and it would more accurately reflect the routine that you "wanted", which Nokia may add someday if you submitted a patch for all platforms. In that routine I'd also make sure to leave a link behind to this SO question so someone wondering why it's there will be able to find out...Role
I already did that, there's a few methods in a separate class to handle it. I just posted it this way on here for convenience if anyone else ever looked at this question. Interesting idea on submitting it to Nokia, though, I'll definitely consider that once I implement working Carbon and X11 solutions.Swoop
The answer is correct, however for Qt5 there's a need to add reinterpret_cast to HWND for this->winId()Refrigerator
@Refrigerator is correct (about reinterpret_cast<HWND>(this->winId()) [Qt6.1]) ...also, using xor, it is possible for the 'not-onTop' code to produce unexpected results. You may prefer this more "defensive" method of clearing bits: flags & ~(x | y)Sanasanabria
C
0

Since i recently ran into the same issue:

You can do it by bypassing Qt. For the windows part see @JakePetroules answer.
XCB (X11) version i use:

#ifdef Q_OS_LINUX
#include <QX11Info>
#include <xcb/xcb.h>

// Just a simple atom cache helper
xcb_atom_t xcb_get_atom(const char *name){
    if (!QX11Info::isPlatformX11()){
        return XCB_ATOM_NONE;
    }
    auto key = QString(name);
    if(_xcb_atom_cache.contains(key)){
        return _xcb_atom_cache[key];
    }
    xcb_connection_t *connection = QX11Info::connection();
    xcb_intern_atom_cookie_t request = xcb_intern_atom(connection, 1, strlen(name), name);
    xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, request, NULL);
    if(!reply){
        return XCB_ATOM_NONE;
    }
    xcb_atom_t atom = reply->atom;
    if(atom == XCB_ATOM_NONE){
        DEBUG("Unknown Atom response from XServer: " << name);
    } else {
        _xcb_atom_cache.insert(key, atom);
    }
    free(reply);
    return atom;
}

void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2)
{
    auto connection = QX11Info::connection();
    xcb_atom_t type_atom = xcb_get_atom(type);
    xcb_atom_t prop_atom = xcb_get_atom(prop);
    xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.sequence = 0;
    event.window = window;
    event.type = type_atom;
    event.data.data32[0] = set ? 1 : 0;
    event.data.data32[1] = prop_atom;
    event.data.data32[2] = prop2 ? xcb_get_atom(prop2) : 0;
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;

    xcb_send_event(connection, 0, window,
                   XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE,
                   (const char *)&event);
    xcb_flush(connection);
}
#endif

Use like: xcb_update_prop(true, window()->winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");

Its a bit hackish, but it worked fine on Mate, KDE, GNOME3, XFCE and openbox.

Chez answered 25/8, 2020 at 2:44 Comment(0)
L
-2

Tested with

  • Qt 5.2.1 on windows XP
  • Qt 5.2 on OS X 10.9

    void ConsoleUI::onAllwaysTop(bool checked)
    {
        Qt::WindowFlags flags = windowFlags();
        if (checked)
        {
            flags ^= Qt::WindowStaysOnBottomHint;
            flags |= Qt::WindowStaysOnTopHint;
        }
        else
        {
            flags ^= Qt::WindowStaysOnTopHint;
            flags |= Qt::WindowStaysOnBottomHint;
        }
        setWindowFlags(flags);
        show();
    }

Luker answered 24/3, 2014 at 6:3 Comment(1)
This is totally wrong. First of all it sets the window to be always on bottom which you almost never want (and certainly isn't wanted in this question). Secondly it doesn't answer the question of how to do it without flicker.Portcullis

© 2022 - 2024 — McMap. All rights reserved.