简体   繁体   English

如何从 Python 中的命令行控制/驱动/交互 PyQt GUI

[英]How to control/drive/interact a PyQt GUI from command line in Python

I have a GUI application created with PyQt and I would like to be able to control it also from the python terminal through a kind of internal API.我有一个用 PyQt 创建的 GUI 应用程序,我希望也能够通过一种内部 API 从 python 终端控制它。

Ideas :想法:

  • Using the main terminal : impossible since that it is blocked by the QApplication (by app.exec_())使用主终端:不可能,因为它被 QApplication 阻止(通过 app.exec_())
  • Starting the GUI in another thread to free the main one : impossible, the QApplications have to be executed in the main one.在另一个线程中启动 GUI 以释放主线程:不可能,必须在主线程中执行 QApplications。
  • ??? ???

I don't want an 'in-app' terminal.我不想要“应用内”终端。

Do you have any other ideas ?你还有其他建议吗 ?

Depending on what you seem to ask, you want to implement something similar to Native Messaging Protocol ( Chrome , Mozilla ) , if so, then you should use QWinEventNotifier or QSocketNotifier depending on the OS to detect if it was written on the console.根据您的要求,您想要实现类似于 Native Messaging Protocol ( Chrome , Mozilla ) 的内容,如果是这样,那么您应该根据操作系统使用QWinEventNotifierQSocketNotifier来检测它是否写在控制台上。

Based on my previous answer I created the following example where the user writes in the console some phrase and press Enter then that phrase is shown in the QLabel (I have only tested my example in Linux).根据我之前的回答,我创建了以下示例,其中用户在控制台中写入一些短语并按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_())

Output:输出:

Stack Overflow

在此处输入图片说明

The above can be taken as the basis for implementing your API.以上可以作为实现你的 API 的基础。


Although another approach is to have 2 applications where a CLI controls the GUI by communicating through sockets and other protocols such as IPC (dbus, etc.), ZeroMQ, MQTT, etc.尽管另一种方法是拥有 2 个应用程序,其中 CLI 通过套接字和其他协议(如 IPC(dbus 等)、ZeroMQ、MQTT 等)进行通信来控制 GUI。

Using the main terminal : impossible since that it is blocked by the QApplication (by app.exec_())使用主终端:不可能,因为它被 QApplication 阻止(通过 app.exec_())

You can omit app.exec() if you are within a Python terminal and it will not be blocking.如果您在 Python 终端内,则可以省略app.exec()并且它不会阻塞。 As explained here this works because...正如这里所解释的这是有效的,因为......

PyQt5 installs an input hook (using PyOS_InputHook) that processes events when an interactive interpreter is waiting for user input. PyQt5 安装了一个输入钩子(使用 PyOS_InputHook),它在交互式解释器等待用户输入时处理事件。 This means that you can, for example, create widgets from the Python shell prompt, interact with them, and still being able to enter other Python commands.这意味着,例如,您可以从 Python shell 提示创建小部件,与它们交互,并且仍然能够输入其他 Python 命令。

For example, enter the following in the Python shell to have a working Qt widget and a non-blocking REPL simultaniously.例如,在 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 has similar functionality. IPython 具有类似的功能。 If you start it with ipython --gui=qt , or type %gui qt in the terminal you get the same effect...如果你用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_()

I recommend to use IPython because it's better suited for interactive work and it will work with PySide (perhaps regular Python and PySide will also work; I didn't check).我建议使用 IPython,因为它更适合交互式工作,并且可以与 PySide 一起使用(也许常规 Python 和 PySide 也可以;我没有检查)。

See also my earlier answer here另请参阅我之前的回答here

Finally, even though this works I don't know how good the performance is.最后,即使这有效,我也不知道性能有多好。 It's a good solution for a hobby project but if you have many users I would consider to implement an 'in-app' terminal or some form of inter-process communication.对于业余爱好项目来说,这是一个很好的解决方案,但如果您有很多用户,我会考虑实施“应用内”终端或某种形式的进程间通信。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM