文章目录
- 前言:
- 一、虚拟键盘的实现
- 综合代码
- 二、为什么选用QWidget而不适用QDialog实现键盘
- 三、从窗体a拉起窗体b后,窗体b闪退问题的探讨
- 四、关闭主窗口时子窗口未关闭的问题
前言:
本文章主要包含四部分:
- 虚拟键盘的实现(基于Pyside2)
- 为什么选用QWidget而不适用QDialog实现键盘
- 关于从一个窗体a中拉起另一个窗体b后,窗体b闪退的问题探讨
- 关闭主窗口时子窗口未关闭的问题
由于qt5官方的virtualkeyboard库无法适用于公司的应用场景,于是需要手写一个virtual keyboard,目前是初步实现了,但是没有办法全局监听,希望后续能有实现全局监听的办法。
已实现功能:
- 作为组件以keyboard.py 形式集成到项目中
- 可切换大小写
- 可按比例附着于屏幕下方
- 项目内多组件共用(Keyboard需要作为单例类)
- 符合真实键盘键位,支持部分特殊字符
待实现功能:
- 全局监听
一、虚拟键盘的实现
从虚拟键盘的交互可以感知其应当作为一个窗体与用户进行交互,通常窗体用的较多的是QDialog和QWidget,由于QDialog会阻塞其他窗体的交互事件(似乎可以通过setModal设置模态或非模态,但是笔者尝试后不起作用),故这里选择QWidget。
—————
实现思路:
- QWidget作为键盘窗体
- QPushButton作为虚拟键盘的每个按键
- 模拟真实键盘键位,为每一行提供一个QHBoxLayout,设置一个父layout即QVBoxLayout将所有QHBoxLayout添加进去。
- 通过setGeometry设置窗体位于指定位置,使键盘每次出现即附着于屏幕下方
- 设置该类为单例类
- 通过组件点击事件激活虚拟键盘,这里重写了QLineEdit的点击事件mouseclickevent
- keyboard工具类
- 单例类装饰器
综合代码
代码结构:
- keyboard.py 键盘实现
- keyboard_util.py 键盘工具类
- singleton_util.py 单例类装饰器类
- enjoy_edit.py QLineEdit示例组件
- main.py 主程序入口
注:代码虽使用PySide2,但修改PySide2为PyQt5也可运行
代码已放在github:
https://github.com/qilin02811/VirtualKeyboard
clone后即可运行,通过python3 main.py即可运行,需安装PySide2环境,如果是PyQt5环境,应将代码中的依赖修改为PyQt5
示意图:
点击文本框后即可拉起键盘,并且键盘附着于最下方
二、为什么选用QWidget而不适用QDialog实现键盘
由于QT程序一般有多个窗体,我们希望键盘不会被其他窗体阻塞,且我们也不希望键盘阻塞其他窗体,故上面代码所有窗体均为QWidget,而未使用QDialog。 使用QDialog要通过dialog.exec_()来拉起,否则不会处理窗口的事件。
并且,如果通过exec拉起QDialog,必须关闭该dialog后才会响应其他窗体,这是实际运行过程中应该避免的。
通过该键盘程序的编写,我更倾向于使用QWidget而不是QDialog。
三、从窗体a拉起窗体b后,窗体b闪退问题的探讨
由于在窗体a中,我通过w = QWidget() ,w.show()闪退,故考虑使用QDialog: q = QDialog() ,q.exec_()
但这样会产生一个问题,当处理键盘点击事件完成后,想要点击主窗口或其他子窗口事件需要先关闭键盘,这不符合我们平常的使用场景,我们不希望键盘阻塞其他窗口,于是又放弃了QDialog,回到QWidget研究闪退问题。
这里给出一段解决闪退问题的示例:
发生闪退问题的代码:
class ×××(QWidget):
def __init__():
super().__init()
def ×××(self):
w = QWidget()
w.show()
未发生闪退问题的代码:
class ×××(QWidget):
def __init__():
super().__init()
self.w = None
def ×××(self):
if self.w is None:
self.w = QWidget(self)
self.w.show()
推测:第一个代码由于w.show()后,整个解释器中就不存在w的引用,则w.show()后会清除w的实例
第二个代码由于有self.w = None的引用,故self.w.show()后不会清除self.w的实例
四、关闭主窗口时子窗口未关闭的问题
编写窗体程序时,经常出现多个窗体需要逐一关闭的情况,我们希望关闭主窗口时能够关闭所有子窗口,可以通过重写closeEvent实现:
# 逐个关闭所有子widget
def closeEvent(self, event):
for widget in QApplication.instance().allWidgets():
if widget != self and isinstance(widget, QWidget):
widget.close()
event.accept()