问题描述
当用户需要在对话窗口输入多个参数的时候,比如说在下图中,用户可以手动输入LineEdit和Spinbox以及时间框中的值。很多用户会在输入完某一个参数后,习惯性地敲下Enter或者Return键(后文会讲到这两个键的区别),如果在编写程序中不注意,用户的这种行为很容易会关闭这个窗口,即便是用户并没有完成所有参数的输入。这种行为对用户来说是不友好的,甚至是危险的(可能会丧失用户用户之前输入的数据。但需要数据量很大的时候,因为不小心多敲下回车键导致窗口关闭会让用户抓狂的)。
图1
在给出解决方案之前,我们先来搞懂出现这种问题的原因以及Qt背后的机制。首先我们将给出QWidget的focus policy和Qdialog的default button的解释。
QWidget Focus Policy
此属性主要表示widget接受键盘焦点的方式,其主要有五种模式。
Qt::TabFocus | 其表示部件接受tab键更改焦点 |
Qt::ClickFocus | 其表示部件接受鼠标点击更改焦点 |
Qt::StrongFocus | 其为上面两种方式的集合 |
Qt::WheelFocus | 其表示StrongFocus和鼠标滚轮更改焦点的集合 |
Qt::NoFocus | 其表示部件不接受任何焦点 |
注意:如果小部件需要处理键盘事件,则必须为小部件启用键盘焦点。
QDialog Default Button
此属性表示该按钮是否为默认(default)按钮,此属性的默认值为 false。在对话框中,一次只有一个按钮可以是默认按钮。该按钮随后会显示一个附加框架(比如上图中MacOS样式下,按钮显示为蓝色)。
默认(default)和自动默认(autoDefault)按钮仅在对话框(QDialog)中起作用,当用户在对话框中按下 Enter 键时,默认按钮属性将决定Enter键会触发什么。此属性设置为 true 的按钮(即对话框的默认按钮)将在用户按下 Enter 键时自动按下。但有一个例外:如果 autoDefault 按钮当前具有焦点,则按下 autoDefault 按钮。当对话框有 autoDefault 按钮但没有默认按钮时,按 Enter 键将按下当前具有焦点的 autoDefault 按钮,但是如果没有按钮具有焦点,则按下焦点链中的下一个 autoDefault 按钮。
上面解释来自于Qt官方文档,听起来有些许绕,下面我们将给出一个程序框图帮助大家更好的理解。
图2
焦点链:focus chain
注意:如果在创建Dialog的时候,添加了QDialogButtonBox的控件,那么假定我们将按钮的default和autoDefault属性都设置为false,其仍具有autoDefault的属性。
在上图代码中,我们将QDiaogButtonBox的OK按钮和cancel按钮的default和autoDefault属性都设置为false,将updateTime按钮的autoDefault属性设置为true。在此种情境下,
没有default按钮,当焦点没有落在updateTime按钮(auto Default属性为true),用户按下Enter键,则会触发该焦点链中的下一个autoDefault按钮--会触发到OK按钮(QDialog Button Box中的焦点链中的顺序为standard buttons中的各个button的顺序)。
问题解决
回到我们的问题:如何避免用户在输入数据时,输入完成键入回车会将窗口意外关闭?
->解决方法一:将dialog中的default button设置为安全按钮。
理论上QDialog中都需要设置一个default button,但是尽量要设置为安全按钮,也就是说将default按钮按下并不会关闭对话框或者丧失用户的输入(这样会被产品经理打啦)。一般来说,可以设置为保存按钮。 比如说我们上面程序中将default button设置为update time。
如果对话框中没有设置安全按钮时,这个时候需要怎么办呢?
->解决方法二:将会导致比如说关闭窗口的按钮default属性设置为false,并把其焦点策略(focus policy)设置为NoFocus或者将此类按钮的autoDefault属性也设置为false
当default属性设置为false的时候,我们假设此刻对话框中没有任何default按钮。我们可以参考上面的框图,可以发现有两种情况
A. 有autoDefault按钮-->分为有焦点和没焦点两种情况
B. 没有autoDefault按钮-->不会触发任何按钮按下的事件
针对A情况,我们认为可能比较危险的按钮仍然具有autoDefault属性,其仍然处于焦点链(focus order)中,仍然有可能被触发到(参考上面的程序框图)。此时我们将此类按钮的焦点策略改为NoFocus,此时将此类按钮从焦点链中移除,则其不会在用户按下enter键时被触发。
针对B情况,我们将此类按钮的autoDefault属性设置为false,则自然将此类按钮从焦点链中移除。可以达到同样的效果。
值得注意的是,上述AB两种情况下的解决方法(见下图中的代码)对QDialogButtonBox是无效的。
那么问题来了,如果该对话框中没有可供选择的安全按钮,又使用了QDialogButtonBox,这个时候应该要怎么办呢?
->解决方法三:复写该对话框的键盘事件
因为QDialog会将enter键或者return键按下的键盘事件触发default或者autoDefault的按钮,因此我们可以复写(override)此键盘事件,具体实现逻辑可以看下图代码。
注:
Qt::Key_Return对应标准键盘的主按键区(字母区)的Enter键。
Qt::Key_Enter 对应标准键盘的次按键区(数字区)的Enter键。
参考图1,假设用户在SpinBox中输入整数,在输入完成后,按下回车键,此时对话框的焦点仍然在SpinBox输入框中,然后在QDialog基类在处理“键盘按下事件(keyPressEvent)”前,我们先检查此时焦点有没有在OK或者Cancel按钮上。如果不在的话,我们将直接返回,不将此键盘事件传递给基类的QDialog进行处理。
当然假设此对话框中只有lineEdit和spinbox两个输入框(用户习惯性会在键盘输入后敲入回车键),其他情况下更多的是鼠标的勾选。在此种情况下我们可以只检查用户在敲入回车时的焦点是否在此两个输入框之一。假设此对话框的焦点在某个QComboBox,则我们可以假定用户此时键入回车键表示其想关闭窗口。因此我们可以采用上图代码中注释部分的写法。(此时仍要注意,默认为保存或接受按钮。如果使用QDialogButtonBox的话,则默认第一个焦点为OK)。
Source Code: