- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
背景:我想使用 PySide2 实现一个 GUI 来控制一堆客户端(通过 RPC 调用与“服务器”控制电机、相机等硬件)。
以前的方法:通常,我要做的是创建 GUI 并将 UI 信号连接到客户端插槽,反之亦然。这对于更简单的应用程序来说非常有效。
问题:我希望我的 GUI 能够正确表示允许对客户端的调用。最简单的示例:执行 client1.doXY()
后,我想禁用执行该命令的按钮,并仅在 doZY()
完成后重新激活它。虽然通过上述方法这是完全可能的,但当事情变得更加复杂时,就会感觉错误:例如当 GUI 元素取决于多个客户端的状态时。
方法:因此,我认为使用有限状态机作为客户端和 GUI 之间的中间层是一个好主意,并遇到了 pytransitions ,看起来很有前途。然而,我正在努力寻找结合这两个世界的正确方法。
问题:
一般来说,这是拥有这样一个层的有效设计方法吗?
特别是如工作代码示例所示,我必须将客户端移至单独的线程,以避免客户端执行阻塞调用时 GUI 卡住。虽然我的代码工作正常,但在创建额外的 qt 信号来连接 ClientState
和 Client
对象时需要一些开销。这可以更优雅地完成吗(即没有额外的 xy_requested 信号,但以某种方式从 ClientState
直接调用 Client
函数,仍然调用 Client
code> 函数在 Client
线程中而不是主线程中?
工作示例:
代码:
import io
import logging
from time import sleep
import numpy as np
from PySide2 import QtSvg, QtWidgets
from PySide2.QtCore import Signal, Slot, QObject, QThread
from PySide2.QtWidgets import QWidget, QPushButton, QApplication
from transitions.extensions import GraphMachine
logging.basicConfig(level=logging.DEBUG)
class Client(QObject):
# Client signals
sig_move_done = Signal()
sig_disconnected = Signal()
sig_connected = Signal()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@Slot(int)
def client_move(self, dest):
print(f'Client moving to {dest}...')
sleep(3) # some blocking function
if np.random.rand() < 0.5:
print("Error occurred during movement...")
self.sig_disconnected.emit()
else:
print("Movement done...")
self.sig_move_done.emit()
@Slot()
def client_disconnect(self):
# do something then... on success do:
self.sig_disconnected.emit()
@Slot()
def client_connect(self):
# do something ... on success do:
self.sig_connected.emit()
# define states, transitions and extra args for transitions state machine:
states = ['ready', 'moving', 'unknown']
transitions = [
{'trigger': 'move', 'source': 'ready', 'dest': 'moving'},
{'trigger': 'stopped', 'source': 'moving', 'dest': 'ready'},
{'trigger': 'disconnect_', 'source': ['ready', 'moving'], 'dest': 'unknown'},
{'trigger': 'error', 'source': ['ready', 'moving'], 'dest': 'unknown'},
{'trigger': 'connect_', 'source': 'unknown', 'dest': 'ready'}
]
extra_args = dict(initial='unknown', title='Simple state machine',
show_conditions=True, show_state_attributes=True)
class ClientState(QObject):
# machine signals
sig_update_available = Signal()
sig_move_requested = Signal(int) # can this be avoided ? see self.on_enter_moving
sig_connect_requested = Signal() # can this be avoided ?
def __init__(self, client, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = client
# move client to seperate thread
self.worker_thread = QThread()
self.client.moveToThread(self.worker_thread)
self.worker_thread.start()
self.machine = GraphMachine(model=self, states=states, transitions=transitions,
show_auto_transitions=False, **extra_args, after_state_change="update_available",
send_event=True)
# connecting Client signals to state machine triggers
self.client.sig_disconnected.connect(self.disconnect_)
self.client.sig_connected.connect(self.connect_)
self.client.sig_move_done.connect(self.stopped)
self.update_available = lambda *args, **kwargs: self.sig_update_available.emit()
# can this be avoided ? see self.on_enter_moving
self.sig_move_requested.connect(self.client.client_move)
self.sig_connect_requested.connect(self.client.client_connect)
def on_enter_moving(self, event):
print(event.kwargs)
dest = event.kwargs.get('dest', 0)
# calling self.client_move() directly will cause self.client_move to be called from main thread...
# calling it via a helper signal instead:
self.sig_move_requested.emit(dest)
def show_graph(self, **kwargs):
stream = io.BytesIO()
self.get_graph(**kwargs).draw(stream, prog='dot', format='svg')
return stream.getvalue()
class GUI(QWidget):
def __init__(self, client_state):
super().__init__()
self.client_state = client_state
# setup UI
self.setWindowTitle("State")
self.svgWidget = QtSvg.QSvgWidget()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.svgWidget)
self.btn_move = QPushButton("move")
self.btn_connect = QPushButton("(re-)connect")
self.layout.addWidget(self.btn_move)
self.layout.addWidget(self.btn_connect)
self.setLayout(self.layout)
# Connect Slots/Signals
## machine -> GUI
self.client_state.sig_update_available.connect(self.update_gui)
## GUI --> machine
self.btn_move.clicked.connect(lambda: self.client_state.move(dest=np.random.randint(1, 100)))
self.btn_connect.clicked.connect(
self.client_state.connect_)
# update UI
self.update_gui()
def update_gui(self):
print("Update model graph and GUI...")
self.svgWidget.load(self.client_state.show_graph())
if self.client_state.is_ready():
self.btn_move.setEnabled(True)
self.btn_connect.setDisabled(True)
if self.client_state.is_moving():
self.btn_move.setDisabled(True)
self.btn_connect.setDisabled(True)
if self.client_state.is_unknown():
self.btn_move.setDisabled(True)
self.btn_connect.setEnabled(True)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
client = Client()
client_state = ClientState(client)
gui = GUI(client_state)
gui.show()
sys.exit(app.exec_())
最佳答案
是的,这是有效的,并且在复杂的应用程序中,FSM 的实现是因为它们简化了逻辑。
<小时/>关于恕我直言的简化,我更喜欢验证 Qt 中是否存在类似的工具,因为它们通过事件或信号与 Qt 的元素进行友好的交互。在这种情况下,至少有两个选择:
import time
from functools import partial
from PySide2 import QtCore, QtGui, QtWidgets
import numpy as np
class Client(QtCore.QObject):
# Client signals
sig_move_done = QtCore.Signal()
sig_disconnected = QtCore.Signal()
sig_connected = QtCore.Signal()
@QtCore.Slot(int)
def client_move(self, dest):
print(f"Client moving to {dest}...")
time.sleep(3) # some blocking function
if np.random.rand() < 0.5:
print("Error occurred during movement...")
self.sig_disconnected.emit()
else:
print("Movement done...")
self.sig_move_done.emit()
@QtCore.Slot()
def client_disconnect(self):
# do something then... on success do:
self.sig_disconnected.emit()
@QtCore.Slot()
def client_connect(self):
# do something ... on success do:
self.sig_connected.emit()
class GUI(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("State")
self.btn_move = QtWidgets.QPushButton("move")
self.btn_connect = QtWidgets.QPushButton("(re-)connect")
self.client = Client()
self._thread = QtCore.QThread(self)
self._thread.start()
self.client.moveToThread(self._thread)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.btn_move)
lay.addWidget(self.btn_connect)
self.resize(320, 120)
# states
self.unknown_state = QtCore.QState()
self.ready_state = QtCore.QState()
self.moving_state = QtCore.QState()
# transitions
self.ready_state.addTransition(self.btn_move.clicked, self.moving_state)
self.moving_state.addTransition(self.client.sig_move_done, self.ready_state)
self.ready_state.addTransition(self.client.sig_disconnected, self.unknown_state)
self.moving_state.addTransition(self.client.sig_disconnected, self.unknown_state)
self.unknown_state.addTransition(self.btn_connect.clicked, self.ready_state)
self.unknown_state.addTransition(self.client.sig_connected, self.ready_state)
self.unknown_state.entered.connect(self.on_unknown_state_enter)
self.ready_state.entered.connect(self.on_ready_state_enter)
self.moving_state.entered.connect(self.on_moving_state_enter)
state_machine = QtCore.QStateMachine(self)
state_machine.addState(self.ready_state)
state_machine.addState(self.moving_state)
state_machine.addState(self.unknown_state)
state_machine.setInitialState(self.unknown_state)
state_machine.start()
def on_unknown_state_enter(self):
print("unknown_state")
self.btn_move.setDisabled(True)
self.btn_connect.setEnabled(True)
def on_ready_state_enter(self):
print("ready_state")
self.btn_move.setEnabled(True)
self.btn_connect.setDisabled(True)
def on_moving_state_enter(self):
print("moving_state")
self.btn_move.setDisabled(True)
self.btn_connect.setDisabled(True)
dest = np.random.randint(1, 100)
wrapper = partial(self.client.client_move, dest)
QtCore.QTimer.singleShot(0, wrapper)
def closeEvent(self, event):
self._thread.quit()
self._thread.wait()
super().closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GUI()
w.show()
sys.exit(app.exec_())
Simple_State_Machine.scxml
<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" name="Simple_State_Machine" qt:editorversion="4.10.0" initial="unknown">
<qt:editorinfo initialGeometry="150.82;359.88;-20;-20;40;40"/>
<state id="ready">
<qt:editorinfo stateColor="#ff974f" geometry="425.83;190.46;-60;-50;120;100" scenegeometry="425.83;190.46;365.83;140.46;120;100"/>
<transition type="internal" event="move" target="moving">
<qt:editorinfo endTargetFactors="35.02;9.52" movePoint="-34.84;14.59" startTargetFactors="32.33;90.16"/>
</transition>
<transition type="internal" event="disconnect" target="unknown">
<qt:editorinfo endTargetFactors="91.87;60.92" movePoint="9.38;9.36" startTargetFactors="6.25;63.37"/>
</transition>
</state>
<state id="unknown">
<qt:editorinfo stateColor="#89725b" geometry="150.82;190.46;-60;-50;120;100" scenegeometry="150.82;190.46;90.82;140.46;120;100"/>
<transition type="internal" target="ready" event="connect">
<qt:editorinfo endTargetFactors="6.34;41.14" movePoint="0;7.30" startTargetFactors="91.13;39.41"/>
</transition>
</state>
<state id="moving">
<qt:editorinfo stateColor="#a508d0" geometry="425.83;344.53;-60;-50;120;100" scenegeometry="425.83;344.53;365.83;294.53;120;100"/>
<transition type="internal" event="disconnect" target="unknown">
<qt:editorinfo movePoint="2.08;17.72"/>
</transition>
<transition type="internal" event="stopped" target="ready">
<qt:editorinfo endTargetFactors="68.30;90.08" movePoint="62.50;10.32" startTargetFactors="68.69;5.74"/>
</transition>
</state>
</scxml>
import os
import time
from functools import partial
from PySide2 import QtCore, QtGui, QtWidgets, QtScxml
import numpy as np
class Client(QtCore.QObject):
# Client signals
sig_move_done = QtCore.Signal()
sig_disconnected = QtCore.Signal()
sig_connected = QtCore.Signal()
@QtCore.Slot(int)
def client_move(self, dest):
print(f"Client moving to {dest}...")
time.sleep(3) # some blocking function
if np.random.rand() < 0.5:
print("Error occurred during movement...")
self.sig_disconnected.emit()
else:
print("Movement done...")
self.sig_move_done.emit()
@QtCore.Slot()
def client_disconnect(self):
# do something then... on success do:
self.sig_disconnected.emit()
@QtCore.Slot()
def client_connect(self):
# do something ... on success do:
self.sig_connected.emit()
class GUI(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("State")
self.btn_move = QtWidgets.QPushButton("move")
self.btn_connect = QtWidgets.QPushButton("(re-)connect")
self.client = Client()
self._thread = QtCore.QThread(self)
self._thread.start()
self.client.moveToThread(self._thread)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.btn_move)
lay.addWidget(self.btn_connect)
self.resize(320, 120)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "Simple_State_Machine.scxml")
machine = QtScxml.QScxmlStateMachine.fromFile(filename)
machine.setParent(self)
for error in machine.parseErrors():
print(error.toString())
machine.connectToState("unknown", self, QtCore.SLOT("on_unknown_state_enter(bool)"))
machine.connectToState("ready", self, QtCore.SLOT("on_ready_state_enter(bool)"))
machine.connectToState("moving", self, QtCore.SLOT("on_moving_state_enter(bool)"))
self.btn_connect.clicked.connect(partial(machine.submitEvent, "connect"))
self.btn_move.clicked.connect(partial(machine.submitEvent, "move"))
self.client.sig_disconnected.connect(partial(machine.submitEvent, "disconnect"))
self.client.sig_connected.connect(partial(machine.submitEvent, "connect"))
self.client.sig_move_done.connect(partial(machine.submitEvent, "stopped"))
machine.start()
@QtCore.Slot(bool)
def on_unknown_state_enter(self, active):
if active:
print("unknown_state")
self.btn_move.setDisabled(True)
self.btn_connect.setEnabled(True)
@QtCore.Slot(bool)
def on_ready_state_enter(self, active):
if active:
print("ready_state")
self.btn_move.setEnabled(True)
self.btn_connect.setDisabled(True)
@QtCore.Slot(bool)
def on_moving_state_enter(self, active):
if active:
print("moving_state")
self.btn_move.setDisabled(True)
self.btn_connect.setDisabled(True)
dest = np.random.randint(1, 100)
wrapper = partial(self.client.client_move, dest)
QtCore.QTimer.singleShot(0, wrapper)
def closeEvent(self, event):
self._thread.quit()
self._thread.wait()
super().closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GUI()
w.show()
sys.exit(app.exec_())
关于python - 如何正确组合 PySide2 和 pytransitions 来实现 GUI 应用程序的状态机,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58177138/
我有一个无 GUI 的服务器(没有任何桌面环境或 Ubuntu 服务器的新鲜 Debian,没有 X 服务器,先验)。 我考虑安装 docker 并拉取一个基于官方 Ubuntu 的容器,并在其上添加
我正在构建一个带有临时用户名系统的简单聊天服务器。当屏幕弹出时,首先会出现一个简单的屏幕,询问您的用户名。你可以放入任何你想要的东西,这纯粹是暂时的(我也在尝试)。代码告诉程序继续,将用户名保存到代码
我想将来自其他类的图像显示到 QLabel 中,但要通知 GUI 有一个新的框架可用。我需要从非 GUI 类和非 GUI 线程发出信号。 有什么办法吗? 最佳答案 signal 可以从任何继承QObj
我正在用 Java 编写一个图形用户界面,它有一些按钮,其中一个按钮是选项。我想要它,所以当您单击选项时,它会将 gui 更改为我的选项 gui,而不是在另一个窗口中打开它。 我该怎么做? 最佳答案
标题说明了一切...我和我的 friend 正在这样做,我们不知道为什么 Ball.java 实际上没有在 gamePanel 中制作球,然后制作 GUI。顺便说一句,这是 8 球台球。这是代码: 驱
我正在使用 GUI 构建器,我想知道是否有一种简单的方法可以通过当前主窗口打开寄存器窗口(引用下面的页面)。我正在尝试通过菜单栏来执行此操作。 我一整天都在尝试,因为 GUI Builder 生成了一
我有一个程序使用了许多隐藏的 GUI 组件。例如,所有菜单项和打印机对话框/组件(仅占用至少 50 毫秒)。总的来说,我猜整个程序启动的大约 300 毫秒(或 40%)要归功于所有隐藏的东西。 我想做
我对 GUI 构建比较陌生。 我想制作一个带有按钮(我已经有了)的 GUI,用户可以按下该按钮并选择一个图像,然后动态地将该图像加载到面板中的 GUI 中。我希望每次用户浏览图像时图像都动态变化。 到
我有两年使用 Java 和 Visual Studio 进行企业应用程序编程的经验,而且我是 Python 和 wxPython 的新手。所以我的问题是:wxPython 能否为我提供足够丰富的 GU
这是我启动 mkvtoolnix-gui 时遇到的错误: mkvtoolnix-gui: symbol lookup error: mkvtoolnix-gui: undefined symbol:
我在初始屏幕上有一些最近使用的存储库,我想删除它们,因为我不再使用它们了。如何删除它们? 操作系统 = Windows 7 我查看了注册表并搜索了 git 目录,但找不到最近使用列表的存储位置。 最佳
我正在尝试在 matlab、GUI 中用户输入点作为输入和它们之间的连接。 我有 5 个 matlab 文件 - screen1.m、screen2.m、screen3.m、screen4.m、glo
我用java制作了一个客户端/服务器程序,我已经按照我想要的方式使用cmd完美地工作了,现在我正在尝试将代码的客户端转换为GUI,但是我在打印时遇到问题客户端消息并从文本字段和服务器消息读取客户端输入
我正在制作一种 CRUD 应用程序(Java GUI,MYSQL)我应该: 将数据从数据库加载到List(例如),然后将List加载到GUI 将数据从数据库加载到对象(具有 SQL 表等属性)和对象到
我正在开发一个有 5 个图形用户界面窗口的 Java 应用程序,其中一个是问候窗口或主窗口,我已经完成了所有逻辑部分的工作,我已经完成了 99.99%,唯一剩下的就是我如何以这种方式编码,当我点击一个
我目前正在开发 GUI。 我选择将我的 GUI 基于 bluej 项目 - Scribble。 当您创建 ScribbleGUI 对象时,DrawDemo 类会创建一个同时自动打开的 Canvas 。
在这里阅读了很多关于多进程、管道等的内容后,我还没有找到答案,但如果它已经存在,我深表歉意。 我有一个外围硬件,我正在尝试为其创建一个 GUI。我想让 GUI 使用来自外围设备的数据不断更新,同时仍保
我想做的是将 GUI 从一个单独文件中的类链接到另一个类。我的第一个类是一个主菜单,它将显示一些链接到另一个窗口的按钮。第二个类显示不同的窗口,但我现在遇到的问题是我不知道如何链接第一个类中的按钮来调
我的 GUI 代码中有一个奇怪的行为。如果用户在短时间内产生大量事件,则可能会发生正在运行的事件处理程序方法被另一个事件处理程序方法中断。由于一切都在同一个线程(GUI 线程)中运行,所以一切都应该按
这是一个涉及风格的问题。我正在寻找可以帮助我解决常见 GUI 设计问题 的想法。该应用程序是在 Winforms 中完成的,宁愿使用 WPF,该应用程序已经完成,但我是一个完美主义者,在与其他人合作时
我是一名优秀的程序员,十分优秀!