Qt中某些小部件切换“windowFlags”后如何恢复tabOrder?

Adr*_*ire 6 c++ qt focus drop-down-menu

语境:

对于自定义小部件,类似于菜单,当空间不足时,我在“下拉”框架内放置一些内容:

在此输入图像描述

对于此下拉功能,我将这些按钮的框架设置为frame.setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);. 这工作得很好,只是它扰乱了制表顺序。

小部件的结构如下所示:

MenuWidget
 |- Tab1
     |- Group_<without_name>
         ...
     |- Group_File
         ...
     |- Group_Export
         |-Frame (can be either standard sub-widget, or a popup widgets)
             |- Button 1
             |- Button 2
                 ....
Run Code Online (Sandbox Code Playgroud)

您可以在那里查看当前的 WIP: https: //github.com/Escain/QTopMenu


我将问题简化为以下代码,该代码在开始时具有正确的制表顺序,并且在我设置Qt::Popup并返回到Qt::Widget. 任何恢复正确的制表顺序的尝试都会失败。

设置前Qt::Popup

在此输入图像描述

设置Qt::Popup和设置后Qt::Widget

在此输入图像描述


这是重现该问题的最小示例。
免责声明:这不是生产代码,只是旨在重现相同问题的快速完成的代码。

#include <QApplication>
#include <QDebug>
#include <QWidget>
#include <QPushButton>

class TestWidget: public QWidget
{
    Q_OBJECT
public:
    explicit TestWidget( QWidget* parent=nullptr)
        : QWidget(parent)
        , frame(this)
        , but1(&frame)
        , but2(&frame)
    {
        but1.setGeometry(10, 10, 80, 30);
        but2.setGeometry(110, 10, 80, 30);
        frame.setGeometry(0,0,200,50);
        resize(200,50);
        setStyleSheet("background-color: rgba(0,128,128,32);");

        setFocusProxy(&frame);
        frame.setFocusProxy(&but1);
        setFocusPolicy(Qt::FocusPolicy::StrongFocus);

        connect(&but1, &QPushButton::clicked, [this]()
        {
            const auto flag = frame.windowFlags();

            // In some circumstances, the widget needs to show it content as drop-down
            frame.setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);

            //try to recover here
            frame.setWindowFlags(flag);
            frame.show();
            emit needsTabOrder();
        });
    }

    QWidget* orderTabs( QWidget* first)
    {
        setTabOrder(first, &but1);
        setTabOrder(&but1, &but2);
        return &but2;
    }

signals:
    void needsTabOrder();
private:

    QWidget frame;
    QPushButton but1, but2;
};
#include "main.moc"


auto main (int argn, char* args[])-> int
{
    QApplication app(argn, args);

    QWidget window;

    TestWidget t1(&window);
    TestWidget t3(&window); //Intentionally out of order
    TestWidget t2(&window);
    TestWidget t4(&window);
    t1.move(0,10);
    t2.move(200,10);
    t3.move(400,10);
    t4.move(600,10);

    QWidget::setTabOrder(&t1, &t2);
    QWidget::setTabOrder(&t2, &t3);
    QWidget::setTabOrder(&t3, &t4);

    auto tryRecoverThisMess = [&t1, &t2, &t3, &t4]()
    {
        qDebug() << "Trying to recover this mess";

        // Attempt 1: not working
        QWidget::setTabOrder(&t1, &t2);
        QWidget::setTabOrder(&t2, &t3);
        QWidget::setTabOrder(&t3, &t4);

        // Attempt 2: very hacky and not working
        QWidget* w=nullptr;
        w = t1.orderTabs( w);
        w = t2.orderTabs( w);
        w = t3.orderTabs( w);
        w = t4.orderTabs( w);
    };

    QWidget::connect(&t1, &TestWidget::needsTabOrder, tryRecoverThisMess);
    QWidget::connect(&t2, &TestWidget::needsTabOrder, tryRecoverThisMess);
    QWidget::connect(&t3, &TestWidget::needsTabOrder, tryRecoverThisMess);
    QWidget::connect(&t4, &TestWidget::needsTabOrder, tryRecoverThisMess);

    window.resize(800,200);
    window.show();

    return app.exec();
}
Run Code Online (Sandbox Code Playgroud)

使用 Qt 5.15.2

CMakeLists 看起来像:

cmake_minimum_required(VERSION 3.13)
cmake_policy( SET CMP0076 NEW )

project("QtTest")

set(CMAKE_AUTOMOC ON)

find_package(Qt5Widgets REQUIRED)
include_directories(${Qt5Widgets_INCLUDE_DIRS})
add_definitions(${Qt5Widgets_DEFINITIONS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")


set(TARGET "QtTest")
add_executable(${TARGET})

target_compile_options( ${TARGET} PRIVATE -Wall -pedantic -Wno-padded -Werror=return-type)
target_compile_features( ${TARGET} PUBLIC cxx_std_17)

target_sources( ${TARGET} PRIVATE "main.cpp")
target_link_libraries(${TARGET} "Qt5::Widgets" )
Run Code Online (Sandbox Code Playgroud)

我尝试解决这个问题:

  1. 我尝试调用setTabOrder父小部件以及每个按钮(hacky)。没有成功。

  2. 要打印制表符顺序,它确实不会按照setTabOrder

  3. 不同的setWindowFlags价值观

  4. setFocusProxy和的几种组合setFocusPolicy

  5. 让框架始终为“弹出”,并将其上的所有小部件在框架和主小部件之间移动。

问题:

设置标志后如何恢复 Tab 键顺序Qt::Popup

use*_*148 -1

  1. 切勿使用自动

  2. 仅在枪口下使用 Lambda

    auto tryRecoverThisMess = [&window, &t1, &t2, &t3, &t4]()
    {
        qDebug() << "Trying to recover this mess";
    
        // Attempt 1: not working
        window.setTabOrder( &t1, &t2 );
        window.setTabOrder( &t2, &t3 );
        window.setTabOrder( &t3, &t4 );
    
    Run Code Online (Sandbox Code Playgroud)

    #if 0 // 尝试 2:非常 hacky 并且不起作用 QWidget *w=nullptr; w = t1.orderTabs( w ); w = t2.orderTabs( w ); w = t3.orderTabs( w ); w = t4.orderTabs( w ); #万一

    };
    
    Run Code Online (Sandbox Code Playgroud)

您是否创建了一个从 QWidget 派生的类 MyClass 并给悲伤的类一个插槽 void tryRecoverThisMess();

您不需要参数,因为 TestWidget 项目将是成员变量。更重要的是,只需要插槽。

setTabOrder( &t1, &t2 );
setTabOrder( &t2, &t3 );
setTabOrder( &t3, &t4 );
Run Code Online (Sandbox Code Playgroud)

因为它将在小部件内执行。

您正在使用 auto 类型在 Lambda 内调用 QTabWidget::setTabOrder。编译器可以自由地确定此 Lambda 是 QWidget 并为其设置 Tab 键顺序,或者创建一个临时 QWidget,设置其 Tab 键顺序,然后在 Lambda 生命周期结束时将其丢弃。

您的代码中没有任何内容表明您想要更改“窗口”的选项卡顺序。

诚然,我在使用 Qt 5.12.8 的 Ubuntu 20.04 LTS 上尝试过此操作,但我确实看到单击后内容跳转到第三个按钮。我修复后他们没有这样做。“正确”的修复方法是将这些东西放入一个类中,并将 SIGNAL 连接到该类中的一个插槽。

我怀疑您的其他代码中也存在类似的育儿问题。

重组您的代码以删除所有 Lambda 并删除所有 auto。

Lambda 的 as slot 确实很危险。 当涉及创建然后移动到另一个线程的对象时,它们变得极其危险。Lambda 不是对象的一部分,它不必移动。这意味着您可能有一个排队连接,并且该 Lambda 槽尝试返回的对象可能会被删除。

年轻的开发人员喜欢使用 auto 和 Lambda,因为这可以让他们走捷径。

任意两点之间的最长距离就是捷径。

当你长大后,你会意识到这是 IT 领域的事实。

欢迎来到奇妙的编程世界!

根据您的要求,我“修补”了您的代码。它需要从头开始重新设计才能“修复”。

#include <QApplication>
#include <QDebug>
#include <QWidget>
#include <QPushButton>

class TestWidget: public QWidget
{
    Q_OBJECT
public:
    explicit TestWidget( QString txt, QWidget *parent=nullptr )
        : QWidget( parent )
        , button1( nullptr )
        , button2( nullptr )
    {
        // don't create widgets on the stack
        //
        QString lbl1 = QString( "%1-%2" ).arg( txt ).arg( 1 );
        QString lbl2 = QString( "%1-%2" ).arg( txt ).arg( 2 );
        button1 = new QPushButton( lbl1, this );
        button2 = new QPushButton( lbl2, this );

        button1->setGeometry( 10, 10, 80, 30 );
        button2->setGeometry( 110, 10, 80, 30 );

        setGeometry( 0,0,200,50 );

        resize( 200,50 );  // ????

        setStyleSheet( "QPushButton{ background-color: rgba(170, 176, 81, 200); }"
                       "QPushButton:focus{ background-color: rgba(72, 75, 224, 200); } "
                       "QPushButton:focus:pressed{ background-color: rgba(229, 141, 29, 200); } "
                       "QPushButton:pressed{ background-color: rgba(241, 5, 10, 250); } " );

        setFocusProxy( this );
        setFocusProxy( button1 );
        setFocusPolicy( Qt::FocusPolicy::StrongFocus );

        setInternalTabOrder();
        connect( button1, &QPushButton::clicked, [this]()
        {
            const auto flag = this->windowFlags();

            // In some circumstances, the widget needs to show it content as drop-down
            this->setWindowFlags( Qt::Popup | Qt::FramelessWindowHint );

            //try to recover here
            this->setWindowFlags( flag );
            this->show();
            emit needsTabOrder();
            this->setFocus( );  // without this clicked button won't grab
            // focus but a clicked button2 will
        } );
    }

    ~TestWidget()
    {
        if ( button1 != nullptr )
        {
            delete button1;
            button1 = nullptr;
        }

        if ( button2 != nullptr )
        {
            delete button2;
            button2 = nullptr;
        }
    }

    void setInternalTabOrder()
    {
        setTabOrder( button1, button2 );
    }
signals:
    void needsTabOrder();
private:

    QPushButton *button1;
    QPushButton *button2;
};
#include "main.moc"


auto main ( int argn, char *args[] )-> int
{
    QApplication app( argn, args );

    // don't create widgets on the stack
    //
    QWidget *window = new QWidget();

    TestWidget *t1 = new TestWidget( "Fred", window );
    TestWidget *t3 = new TestWidget( "Ethyl", window ); //Intentionally out of order
    TestWidget *t2 = new TestWidget( "Lucy", window );
    TestWidget *t4 = new TestWidget( "Ricky", window );

    t1->move( 0,10 );
    t2->move( 200,10 );
    t3->move( 400,10 );
    t4->move( 600,10 );

    QWidget::setTabOrder( t1, t2 );
    QWidget::setTabOrder( t2, t3 );
    QWidget::setTabOrder( t3, t4 );

    auto tryRecoverThisMess = [window, t1, t2, t3, t4]()
    {
        qDebug() << "Trying to recover this mess";

        window->setTabOrder( t1, t2 );
        window->setTabOrder( t2, t3 );
        window->setTabOrder( t3, t4 );

    };

    QWidget::connect( t1, &TestWidget::needsTabOrder, tryRecoverThisMess );
    QWidget::connect( t2, &TestWidget::needsTabOrder, tryRecoverThisMess );
    QWidget::connect( t3, &TestWidget::needsTabOrder, tryRecoverThisMess );
    QWidget::connect( t4, &TestWidget::needsTabOrder, tryRecoverThisMess );

    window->resize( 800,200 );
    window->show();

    return app.exec();
}
Run Code Online (Sandbox Code Playgroud)

尽管所有想要在任何地方使用 auto 和 lambda 的仇恨者都投了反对票,但这是唯一已发布的工作代码,并且已经过测试。没有其他人花时间实际编写工作代码。