QT 指定焦点的遍历顺序

QT 指定焦点的遍历顺序

前言

QT 切换焦点有很多种方式,比较常用的是通过Tab键, 那么按下Tab键后,界面的器件焦点切换顺序是怎么样的,又该怎么指定呢?

1 焦点切换的方式

继承自Qwidget的Qt控件可用setFocusPolicy()函数设置切换焦点的策略,有如下几种方式

Qt::TabFocus(0x1): 通过Tab键接受焦点

Qt::ClickFocus(0x2): 通过单击接受焦点

Qt::StrongFocus(TabFocus | ClickFocus | 0x8): 通过 Tab键和单击接收焦点,如果是macOS的系统则 表明会在‘Text/List focus mode’ (文本/列表焦点式) 下接受Tab 焦点。

Qt::WheelFocus(StrongFocus | 0x4): StrongFocus增强版,使用滑轮切换焦点

Qt::NoFocus: 不接受焦点

下面我们讨论在使用 “Tab” 或者 “shift+Tab” 双向切换焦点时如何控制焦点遍历顺序。

2. Tab Focus 遍历顺序指定

2.1 setTabOrder方式

在使用 “Tab” 或者 “shift+Tab” 切换焦点时,我们希望能够控制切换顺序,这是必然的需求。常规方法是使用designer设计UI时时指定。如下图所示

在这里插入图片描述

亦或直接接口指定顺序

 QWidget::setTabOrder(pushButton1, pushButton2);
 QWidget::setTabOrder(pushButton2, pushButton3);
 QWidget::setTabOrder(pushButton3, pushButton4);

然而在界面是由多个UI组合的情况下遍历顺序如何控制?

比如下图,该界面由多个区域组成,这些区域都是独立的UI文件,并且可能交由不同人员开发,同时每个UI下又有很多的内部节点,UI内部的节点遍历顺序由UI自己管理。

在这里插入图片描述

我们希望遍历完区域1所有的节点后,遍历区域3然后到区域2、4。

如何将指定多个UI的遍历顺序呢?setTabOrder()函数的帮助文档没有描述这种情况下怎么处理,我们可以看看setTabOrder()函数的源码:

void QWidget::setTabOrder(QWidget* first, QWidget *second)
{
    ......		//省略部分代码
        
    //记录second节点的前后节点
    QWidget *sp = second->d_func()->focus_prev;
    QWidget *sn = second->d_func()->focus_next;		

    //将second节点加入到新的位置上(first节点的后面)
    fn->d_func()->focus_prev = second;			
    first->d_func()->focus_next = second;		
    
    second->d_func()->focus_next = fn;
    second->d_func()->focus_prev = first;
    
	//将second节点从原先所在的位置中删除
    sp->d_func()->focus_next = sn;
    sn->d_func()->focus_prev = sp;			

    Q_ASSERT(first->d_func()->focus_next->d_func()->focus_prev == first);
    Q_ASSERT(first->d_func()->focus_prev->d_func()->focus_next == first);

    Q_ASSERT(second->d_func()->focus_next->d_func()->focus_prev == second);
    Q_ASSERT(second->d_func()->focus_prev->d_func()->focus_next == second);
}

可见该函数只是调整了某个节点在双向循环链表中的位置,显然在调用setTabOrder之前,该节点已经被加入到了某个双向链表中,并且似乎所有UI的节点都已经添加到同一个链表中去了。

那么该节点是何时被自动加入到链表中的?

同时我们的目的是将某个UI下的所有节点统一前移或后后移,逐个调整显然费时费力,并且我们并不希望暴露UI内的所有节点控件的指针,有没有方法将一个UI下的所有节点统一前移或后移?

2.2 默认的遍历顺序

先回答第一个问题,界面器件节点何时被自动加入到双向链表中的?

查看QWidget构造函数源码,可知焦点遍历链表相关的初始化调用栈:

QWidget::QWidget()

QWidgetPrivate::init()

QWidget::setParent()

QWidgetPrivate::reparentFocusWidgets()

//setParent 的实现
void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
	QWidget *oldtlw = window();
	d->reparentFocusWidgets(oldtlw);
}
//reparentFocusWidget函数的注释
/*!\internal  
  Moves the relevant subwidgets of this widget from the \a oldtlw's
  tab chain to that of the new parent, if there's anything to move and
  we're really moving

  This function is called from QWidget::reparent() *after* the widget
  has been reparented.

  \sa reparent()
*/
void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw){}

//析构时对链表的操作
QWidget::~QWidget()
{
    Q_D(QWidget);

    //QWidget析构时,从链表中删除本身节点
    if (d->focus_next != this) {
        d->focus_next->d_func()->focus_prev = d->focus_prev;
        d->focus_prev->d_func()->focus_next = d->focus_next;
        d->focus_next = d->focus_prev = 0;
    }
}

可见,当QWidget创建时,和QWidget相关联的所有子器件会被移动到window()主窗体的 tab 链表(tab chain)中,可见QWidget创建时,即使没有显式调用setTabOrder函数,所有可以接受焦点的子器件都会按其创建顺序添加到焦点链表中, 也就是所谓的默认顺序。

所以在存在多个UI的一个界面中,我们可以通过控制 UI 或者子器件的创建顺序来改变Tab Focus的遍历顺序。

并且界面销毁时,界面所有节点自动从遍历链表中删除,这要求我们的界面必须是常驻内存的。

//!setupUi中已经通过setTabOrder或默认方式指定了其子器件的遍历顺序
//这里则指定了各UI的遍历顺序: ui1->ui2->ui3->ui4
ui1->setupUi(this);
ui2->setupUi(this);
ui3->setupUi(this);
ui4->setupUi(this);

3 关于动态调整

显然通过创建顺序决定遍历顺序,顺序只能指定一次,即遍历顺序在界面创建后就是固定的!界面创建完成后,要想重新指定顺序,必须删除所有界面并重新创建,否则后创建的界面的所有器件节点默认加入遍历链表尾。

3.1 多个UI下的顺序调整

当界面的节点对象或个数发生改变(stackedWidget切换不同模式界面、界面动态调整等),新创建的UI要使用setTabOrder()逐个调整新节点的顺序,否则默认加入到链表的尾部,这种情况下可能不是我们期待的遍历顺序。

3.2 一个UI下的顺序调整

一个UI下的内部节点可以通过setTabOrder()函数任意调整,同时不会影响UI外面其他UI的节点的内部遍历顺序。

posted @ 2020-08-23 14:37  HL棣  阅读(63)  评论(0编辑  收藏  举报  来源