VBA一例:如何保持文本框焦点
- 缘起
在Excel的VBA编程中,设计一个用于录入的用户窗体,该窗体包含1个文本框和2个按钮,文本框用于扫描枪的录入。
要求扫描枪可以连续录入,即每扫描完一个条码,文本框自动清空,文本框继续获得焦点。
我们知道扫描枪录入实际上等同于往文本框录入一段字符串并回车, 那么刚才的要求理论上可以在文本框的KeyDown事件子过程中编程,先判断录入的字符是否回车键,如果是则清空文本框。
2 If KeyCode = 13 Then TextBox1.Value = ""
3 End Sub
至此,如果是VB编程,要求本应完成。但实际运行发现,扫描枪录入后,文本框虽然自动清空,但是焦点却跑到下一个按钮上去了。
- 求因
未及细想,手动用SetFocuse方法来设置文本框获得焦点。
2 If KeyCode = 13 Then
3 TextBox1.Value = ""
4 TextBox1.SetFocus
5 End If
6 End Sub
程序再运行,焦点还是跑到按钮上。不解,遂跟踪之,发现是在执行完KeyDown事件子过程后(End Sub语句后)发生的焦点转移,难怪SetFocus方法显得没有起作用。
为什么执行完KeyDown事件后会发生焦点自动转移呢?百思不得其解,遂清除代码反复测试,终于搞清楚一个结论:在Excel VBA编程中,文本框控件接收到回车键以后,会将焦点移到Tab键顺序的下一个对象。(这一结论通过参阅文本框控件的EnterKeyBehavior属性说明也可侧面印证)
之所以特别强调是在Excel VBA编程中,是因为我依稀仿佛记得在VB6里面,系统是不会“聪明的”帮文本框做焦点转移的。(多年不用VB6了,回忆的事情未必正确!但是,其它许多面向事件的UI编程里面,系统是不会自动帮忙文本框做焦点转移的。)
- 证果
既然查明原委,自然应找寻解决之道,且看我折腾。从文本框按下回车键到Button1按钮获得焦点,依次产生如下事件:
2
3 TextBox1_Exit( ByVal Cancel As MSForms.ReturnBoolean)
4
5 Button1_Enter( )
6
7 Button1_KeyPress( ByVal KeyANSI As MSForms.ReturnInteger)
8
9 Button1_KeyUp( ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As fmShiftState)
我的初步想法既然焦点转移是不可干预的系统行为,那就“事后”将它逆转回来,如此从Exit事件开始往后都可以用SetFocus方法将焦点重新设回文本框(此处说法不严谨,如果在Exit事件里面实现,不适用SetFocus方法,而是用Cancel=True,个中原因请自查VBA帮助)。但这样带来一个逻辑问题,从业务上判断,只有在文本框输入操作时,才需要让文本框保持焦点,其它时候不需要,否则会导致除了文本框之外的其它控件都得不到焦点,无法操作!因此干预焦点转移的代码逻辑只能在KeyDown事件中实现。
既然不能“事后”逆转,那就只能从中打断了。我又尝试了在KeyDown事件中用 Exit Sub 或 Call 跳出子过程,未果,系统仍然会自动做焦点转移。
灵光乍现间,我想到既然不能打断子过程,何不打断事件。方法是再做一个UserForm2窗体,当在文本框按回车时,隐藏现有窗体,显示
UserForm2窗体,这时会触发UserForm2的Activate事件, 同时窗体显示的切换也打断了文本框向按钮转移焦点,那么只要在Activate事件中再隐藏UserForm2窗体,显示原窗体,设置焦点到文本框就OK了。这个事件是在KeyDown事件子过程中通过代码人为产生的,是受控的,因此可以将其看作是KeyDown事件代码逻辑的一部分。
2
3 Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
4 If KeyCode = 13 Then
5 TextBox1.Value = ""
6 UserForm1.Hide
7 UserForm2.Show
8 End If
9 End Sub
2
3 Private Sub UserForm_Activate()
4 UserForm2.Hide
5 UserForm1.Show
6 UserForm1.TextBox1.SetFocus
7 End Sub
实测效果非常理想,窗体切换的速度肉眼根本察觉不到,感觉就是文本框保持焦点,用条码枪连续输入,不需要键盘和鼠标辅助定位。
这是我的解决之道,可能比较繁琐,如果你有更高明的方法,欢迎莅临指导!