1

我有一个用 PyQt 创建的 GUI 应用程序,我希望能够通过一种内部 API 从 python 终端控制它。

想法:

  • 使用主终端:不可能,因为它被 QApplication 阻止(通过 app.exec_())
  • 在另一个线程中启动 GUI 以释放主线程:不可能,QApplications 必须在主线程中执行。
  • ???

我不想要一个“应用内”终端。

你还有其他建议吗 ?

4

2 回答 2

2

使用主终端:不可能,因为它被 QApplication 阻止(通过 app.exec_())

app.exec()如果您在 Python 终端中并且它不会阻塞,则可以省略。正如这里所解释的,这很有效,因为......

PyQt5 安装了一个输入钩子(使用 PyOS_InputHook),它在交互式解释器等待用户输入时处理事件。这意味着您可以,例如,从 Python shell 提示符创建小部件,与它们交互,并且仍然能够输入其他 Python 命令。

例如,在 Python shell 中输入以下内容以同时拥有一个工作的 Qt 小部件和一个非阻塞 REPL。

$> python
Python 3.7.6 | packaged by conda-forge | (default, Jan  7 2020, 22:05:27)
[Clang 9.0.1 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt5.QtWidgets import QApplication, QWidget
>>> a = QApplication([])
>>> w = QWidget()
>>> w.show()
>>> w.raise_()

IPython 具有类似的功能。如果您以 开头ipython --gui=qt,或%gui qt在终端中键入,您将获得相同的效果...

$> ipython
Python 3.7.6 | packaged by conda-forge | (default, Jan  7 2020, 22:05:27)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.11.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: %gui qt

In [2]: from PyQt5 import QtWidgets

In [3]: win = QtWidgets.QPushButton("click me")

In [4]: win.show()

In [5]: win.raise_()

我建议使用 IPython,因为它更适合交互式工作,并且可以与 PySide 一起使用(也许普通的 Python 和 PySide 也可以;我没有检查)。

另请参阅我之前的答案

最后,即使这可行,我也不知道性能有多好。对于业余爱好项目来说,这是一个很好的解决方案,但如果您有很多用户,我会考虑实施“应用内”终端或某种形式的进程间通信。

于 2020-01-24T12:41:43.500 回答
2

根据您的要求,您想要实现类似于 Native Messaging Protocol ( Chrome , Mozilla )的东西,如果是这样,那么您应该使用QWinEventNotifierQSocketNotifier依赖于操作系统来检测它是否是在控制台上编写的。

根据我之前的回答,我创建了以下示例,其中用户在控制台中写了一些短语,然后按Enter然后该短语显示在 QLabel 中(我只在 Linux 中测试了我的示例)。

import platform
import sys

from PyQt5 import QtCore, QtGui, QtWidgets


class NativeMessenger(QtCore.QObject):
    messageChanged = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.m_qin = QtCore.QFile()

        self.m_qin.open(
            sys.stdin.fileno(), QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Unbuffered
        )

        if platform.system() == "Windows":
            import win32api

            if sys.platform == "win32":
                import os
                import msvcrt

                if platform.python_implementation() == "PyPy":
                    os.fdopen(fh.fileno(), "wb", 0)
                else:
                    msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)

            self.m_notifier = QtCore.QWinEventNotifier(
                win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
            )

        else:
            self.m_notifier = QtCore.QSocketNotifier(
                sys.stdin.fileno(), QtCore.QSocketNotifier.Read, self
            )

        self.m_notifier.activated.connect(self.readyRead)

    @QtCore.pyqtSlot()
    def readyRead(self):
        line = self.m_qin.readLine().data().decode().strip()
        self.messageChanged.emit(line)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    w = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
    w.resize(640, 480)
    w.show()

    messenger = NativeMessenger()
    messenger.messageChanged.connect(w.setText)

    sys.exit(app.exec_())

输出:

Stack Overflow

在此处输入图像描述

以上可以作为实现你的API的基础。


虽然另一种方法是有 2 个应用程序,其中 CLI 通过套接字和其他协议(例如 IPC(dbus 等)、ZeroMQ、MQTT 等)进行通信来控制 GUI。

于 2020-01-24T11:51:31.853 回答