【问题标题】:How do I toggle 'always on top' for a QMainWindow in Qt without causing a flicker or a flash?如何在 Qt 中为 QMainWindow 切换“始终位于顶部”而不会导致闪烁或闪烁?
【发布时间】:2010-05-18 09:30:22
【问题描述】:
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();
    }
}

上述解决方案有效,但由于 setWindowFlags 隐藏了窗口,因此需要重新显示它,当然这看起来不太优雅。那么如何在没有“闪烁”副作用的情况下为 QMainWindow 切换“始终在顶部”?

【问题讨论】:

    标签: c++ qt window-managers


    【解决方案1】:

    诺基亚says no:

    一旦创建了窗口就不能更改窗口标志而不引起闪烁。闪烁是不可避免的,因为需要重新创建窗口。

    但有时,如果您遇到像这样丑陋的闪烁效果,您可以故意将其拖出,让它看起来像是刚刚发生的“很酷”的事情。

    可能会弹出一个不在窗口中的小进度条,说“正在调整窗口属性!”...使窗口不存在然后重新进入,然后关闭进度条弹出窗口。

    【讨论】:

    • 对于“始终处于领先地位”的功能,这必须是可能的。很多应用程序都可以做到这一点而不会闪烁;也许我只需要使用一些本机 Windows 功能?
    • 如果您想深入或回避 Qt 实现(销毁窗口并重新创建),那么可能。但至少其中一些是“窗口类”IIRC 的属性,而不是 HWND ;并且您可能无法在创建窗口后更改它的类。无论哪种方式,我认为在不破坏 Qt 抽象层的情况下提出一个您可以接受的答案的价值比陷入 Win32 hacks 更好。这就是 Qt 的乐趣所在……
    • @ereOn:是的,我有一些东西可以设置一个进度条并且(无论任务是否完成)都不会删除它,除非自该条设置以来经过了最短闪烁期。所以计算机在某种意义上“让你慢下来”,但体验更好,因为它没有立即发出令人震惊的信号。那里有很多进度条心理学。 :) scribd.com/doc/2226848/Rethinking-The-Progress-Bar
    • @HostileFork 我实际上会接受你的答案而不是我自己的答案,因为我要求一个 Qt 解决方案,而你给出了正确的答案。
    • 谢谢杰克!好吧,两个答案都还在,所以人们可以做出自己的选择。祝你提交补丁好运! ;)
    【解决方案2】:

    嗯,对于解决方案,我认为我会查看 Mono 源,因为我知道 .NET Form 类 (System.Windows.Forms) 具有 TopMost 属性。

    我为我的 Qt 程序找到的解决方案是:

    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
    }
    

    【讨论】:

    • 如果您真的认为值得(!)但我建议至少将其作为单独的方法分开,例如void setWindowStaysOnTopHint(bool windowStaysOnTopHint)。然后,您的 ifdef 不会跨越您的按钮,它会更准确地反映您“想要”的例程,如果您提交适用于所有平台的补丁,诺基亚可能会在某一天添加​​该例程。在那个例程中,我还会确保留下一个指向这个 SO 问题的链接,这样有人想知道它为什么会在那里找到......
    • 我已经这样做了,在一个单独的类中有一些方法可以处理它。如果其他人曾经看过这个问题,我只是为了方便起见将它发布在这里。不过,将其提交给诺基亚的想法很有趣,一旦我实施了可行的 Carbon 和 X11 解决方案,我肯定会考虑这一点。
    • 答案是正确的,但是对于 Qt5 需要将 reinterpret_cast 添加到 HWNDthis-&gt;winId()
    • @Predelnik 是正确的(关于reinterpret_cast&lt;HWND&gt;(this-&gt;winId()) [Qt6.1])...此外,使用异或,“not-onTop”代码可能会产生意外结果。您可能更喜欢这种更“防御性”的清除位的方法:flags &amp; ~(x | y)
    【解决方案3】:

    因为我最近遇到了同样的问题:

    您可以通过绕过 Qt 来做到这一点。对于 Windows 部分,请参阅@JakePetroules 答案。
    我使用的 XCB (X11) 版本:

    #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
    

    像这样使用: xcb_update_prop(true, window()-&gt;winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP");

    它有点老套,但它在 Mate、KDE、GNOME3、XFCE 和 openbox 上运行良好。

    【讨论】:

      【解决方案4】:

      经过测试

      • Windows XP 上的 Qt 5.2.1
      • OS X 10.9 上的 Qt 5.2
      无效 ConsoleUI::onAllwaysTop(布尔检查) { Qt::WindowFlags 标志 = windowFlags(); 如果(选中) { 标志 ^= Qt::WindowStaysOnBottomHint; 标志 |= Qt::WindowStaysOnTopHint; } 别的 { 标志 ^= Qt::WindowStaysOnTopHint; 标志 |= Qt::WindowStaysOnBottomHint; } 设置窗口标志(标志); 显示(); }

      【讨论】:

      • 这是完全错误的。首先,它将窗口设置为始终位于底部,这是您几乎从不想要的(当然在这个问题中也不需要)。其次,它没有回答如何做到不闪烁的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-24
      • 2012-07-23
      相关资源
      最近更新 更多