想要扩展性更好的UI

  热键工具一开始的目的是为了解决写代码的时候经常会为了移动光标而将手指移出主键盘区去按那上下左右四个箭头按键,就目前情况来看热键工具已经完全能够满足使用要求,让双手时刻保持在主键盘区。但是经过一段时间的使用之后我又有了更多的想法,希望增加更多的快捷功能来提高日常的工作效率,那么这个时候没有UI的热键工具就显得力不从心了。
  首先需要添加的是两个功能。其一,我经常会需要在谷歌或者某些代码库的文档上面搜索特定的关键词,但是每一次当我需要搜索的时候都必须先打开谷歌搜索的主页面,这个动作就非常的多余,所以希望能直接一步到位的跳转到搜索结果页面;其二,我在本地经常会需要执行一些脚本,例如git提交代码什么的,这些脚本可能是python、nodeJS或者powershell写的,每一次需要调用这些脚本的时候,都需要先打开命令行窗口,然后再执行再输入指令才能调用这些脚本,非常的不方便。所以为了解决这两个问题这两个功能需求,我准备增加两种快捷的窗口界面。

写一个demo看看

  翻看AHK的文档,发现AHK本身也有GUI功能,打算先用它试试。写个半透明的ahk原生搜索框看看:
ahk原生   优点很明显,ahk原生的UI组件是通过Windows的接口实现的,兼容性自然没得说,而且画布大小是可以随时改变的。
  缺点也很明显,可以用的UI组件较少。
  考虑到未来的工具升级,还是得选择更加成熟的GUI框架才行,而且最好把UI作为前端,让前后端分离开。

常用UI框架选型

  综合GUI框架的跨平台性和当前应用的广泛程度,打算从QT、electron或flutter中选一个。   QT不用多说应用太广了,amd的驱动管理UI界面就是用它写的,主要语言是c++;electron那就更有名了,VScode就是它最知名的应用,主要语言是js;flutter是谷歌推的跨平台框架,目前国内应用并不广泛,闲鱼app用的就是flutter,主要语言是dart。
  flutter我以前用来写过手机app,但是太冷门,放弃;electron用来写过小工具,写起来快,但是打包后自带浏览器和js解释器,体积太大,放弃;QT以前我用过,主要用的QWidget,走cpu渲染,这次就尝试一下QML吧,毕竟QT官方也在强推QML,QML通过GPU渲染,下周工作中也恰好要用QML,那么决定就是QML了。
  将来要是yue的1.0版推出了(electron的创造者的新GUI项目),我一定要体验一下😄。

开始动手敲代码

  用QML创建UI窗口App.qml

import QtQuick 6.5
import QtWebSockets
Window {
    id: ourRoot
    width: Screen.desktopAvailableWidth * 0.9
    height: Screen.desktopAvailableHeight * 0.9
    x: (Screen.desktopAvailableWidth - width) / 2
    y: (Screen.desktopAvailableHeight - height) / 2
    visible: false
    color: "#00000000"
    ......
    ......
    ......
}

  界面的描述几乎全都可以由QML完成,QML创建的窗口需要在初始化的时候就确定其大小。QML的使用感受还行,但是QML的组件生态远不如浏览器前端的生态那么繁荣,只是QtQuick的话,不太够用,需要自己封装一些UI组件。QML的语法类似于js,但差异还是不小,例如定时器的写法完全不同。

想要快速搞定功能

  Qt官方提供了Python版本叫做PySide6,所以为了快速将界面跑起来,看看效果,我选择先用Python来启动这个Qt项目。main.py

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    QQuickStyle.setStyle("Material")
    engine = QQmlApplicationEngine()
    qml_file = Path(__file__).resolve().parent / "App.qml"
    engine.load(qml_file)
    if not engine.rootObjects():
        sys.exit(-1)
    rootWindows=engine.rootObjects()[0]
    rootItem=engine.rootObjects()[0].findChild(QObject, "rootItem")
    sys.exit(app.exec())

  上面是main.py中引入我们的App.qml的部分。
  为了让各种扩展功能更具灵活性,增加了一个json文件作为启动配置文件。config.json
json配置文件
  看看启动效果:
Q搜索示例
  网站快捷搜索功能:
Q快捷搜索
  脚本快捷调用功能:
快捷脚本
  快捷菜单界面被呼出的时候始终保持在所有窗口的顶层,并且整个菜单都是半透明的,目的就是为了保证当按下capslock + Q呼出快捷菜单界面的时候不会挡住背后当前屏幕上的信息。

追求更小的打包体积

  PySide6我使用pyinstaller打包,打包出来体积都超过100MB了,所以要想打包体积小,估计还是得用原版Qt,既然决定用原版Qt了,那顺便再加个快捷翻译功能算了,这样以后看英文论文的时候速度会更快一些。
  Qt慢慢也放弃了qmake,改用cmake了,CMakeLists.txt大概长这样:

cmake_minimum_required(VERSION 3.16)
project(CapslockShortcutProject VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INCLUDE_CURRENT_DIR YES)
find_package(Qt6 6.5 REQUIRED COMPONENTS Quick)
set_source_files_properties(GlobalDataSingleton.qml PROPERTIES
    QT_QML_SINGLETON_TYPE TRUE
)
qt_standard_project_setup(REQUIRES 6.5)
qt_add_executable(appCapslockShortcutProject
    main.cpp
)
qt_add_qml_module(appCapslockShortcutProject
    URI CapslockShortcutProject
    VERSION 1.0
    QML_FILES Main.qml
    QML_FILES GlobalDataSingleton.qml
    QML_FILES GreenBox.qml GridItem.qml MyQSearchListItem.qml SearchPage.qml TabPage.qml
    QML_FILES ListPane.qml
    SOURCES tmtTranslation.cpp
    SOURCES write.cpp
    QML_FILES MyComboBox.qml
    QML_FILES TranslatePage.qml
    QML_FILES StartPage.qml
)
......
......
......

  打包完成差不多20MB,感觉很满意。
  看看翻译界面的效果:
翻译界面

总结

  结合自己曾经使用过的多种GUI框架,我越发的感觉到当今的GUI框架逐渐呈现出某种趋同的性质,就像react框架他希望用JSX把HTML和JS统一起来,也就是all in js,而qt推出的QML,它的目的也是想用QML来实现HTML和JS的功能,Flutter则是all in dart。Flutter的那种组件套组件的写法跟QML的写法其实也非常相似,感觉就像HTML去掉多余标签并把尖括号换成了大括号或小括号,本质上都是想用一套语言实现GUI界面的静态骨架结构和动态更新能力,这种核心思路的趋同,也许正是经过无数工程实践,去粗取精,进化迭代,所形成的“当前最优解”。