• 简易实现QT中的virtualkeyboard及问题总结


    前言:

    本文章主要包含四部分:

    1. 虚拟键盘的实现(基于Pyside2)
    2. 为什么选用QWidget而不适用QDialog实现键盘
    3. 关于从一个窗体a中拉起另一个窗体b后,窗体b闪退的问题探讨
    4. 关闭主窗口时子窗口未关闭的问题

    由于qt5官方的virtualkeyboard库无法适用于公司的应用场景,于是需要手写一个virtual keyboard,目前是初步实现了,但是没有办法全局监听,希望后续能有实现全局监听的办法。


    已实现功能:

    • 作为组件以keyboard.py 形式集成到项目中
    • 可切换大小写
    • 可按比例附着于屏幕下方
    • 项目内多组件共用(Keyboard需要作为单例类)
    • 符合真实键盘键位,支持部分特殊字符

    待实现功能:

    • 全局监听

    一、虚拟键盘的实现

    从虚拟键盘的交互可以感知其应当作为一个窗体与用户进行交互,通常窗体用的较多的是QDialog和QWidget,由于QDialog会阻塞其他窗体的交互事件(似乎可以通过setModal设置模态或非模态,但是笔者尝试后不起作用),故这里选择QWidget。
    —————
    实现思路:

    1. QWidget作为键盘窗体
    2. QPushButton作为虚拟键盘的每个按键
    3. 模拟真实键盘键位,为每一行提供一个QHBoxLayout,设置一个父layout即QVBoxLayout将所有QHBoxLayout添加进去。
    4. 通过setGeometry设置窗体位于指定位置,使键盘每次出现即附着于屏幕下方
    5. 设置该类为单例类
    6. 通过组件点击事件激活虚拟键盘,这里重写了QLineEdit的点击事件mouseclickevent
    7. keyboard工具类
    8. 单例类装饰器

    综合代码

    代码结构:

    • 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    未发生闪退问题的代码:

    class ×××(QWidget):
        def __init__():
            super().__init()
            self.w = None
    
        def ×××(self):
           if self.w is None:
               self.w = QWidget(self)
           self.w.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    推测:第一个代码由于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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    【云原生之Docker实战】使用Docker部署Owncloud开源个人云盘系统
    『现学现忘』Git基础 — 26、给Git命令设置别名
    一文速学-让神经网络不再神秘,一天速学神经网络基础(六)-基于数值微分的反向传播
    TGI 基准测试
    CTF —— 网络安全大赛(这不比王者好玩吗?)
    需要在html中加CSS,怎么加
    Springboot毕设项目动漫论坛5it8x(java+VUE+Mybatis+Maven+Mysql)
    HackTheBox---Starting Point-- Tier 0---Meow
    分布式之计算高性能
    鸿蒙开发学习笔记1
  • 原文地址:https://blog.csdn.net/qq_45933509/article/details/132643052