如何在功能仍然 运行 时单击按钮两次?
How to click pushbutton twice while function is still running?
我有这样的东西:self.pushButton.clicked.connect(self.clicked)
我点击我的按钮,它调用函数 clicked()
,目前一切正常。
Clicked()
函数播放一个 midi 音符然后执行 time.sleep()
但是我并没有实际使用 time.sleep()
我调用了另一个函数 loop()
来执行 time.sleep
对我来说:
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
这将执行 1 秒 time.sleep
除非 a == 4
。clicked()
将在循环结束后关闭 midi 音符。
现在我想要它,所以如果我在循环 运行 时再次单击同一个按钮,那么 a = 4 并且循环将被打破,原始音符将在 1 秒过去之前停止。例如如果我在循环的第 5 个循环中单击按钮,它将中断,返回 clicked()
并提前关闭音符。
我希望能够在我第二次单击该按钮后立即打破此循环。(同一个按钮)
问题是,虽然这个循环是 运行,但它拒绝接受第二次按钮按下。一旦整个 Clicked()
完成 运行,它就会对第二次点击进行查询,但我不想要这个。我不想等待 Clicked
完成。我想在 clicked()
为 运行 时单击按钮,然后按下第二个按钮使 a = 4 以便循环停止。
我已经尝试使用线程来执行此操作,但它似乎没有什么不同,第二次单击按钮将不会注册,直到 clicked()
完成。
我想看的:
我点击按钮,它播放了 1 秒的 MIDI 音符。如果我在 1 秒之前再次按下相同的按钮,那么它会提前切断音符并再次启动音符。
基本上,如果我再次单击按钮,我想更改循环的长度。
这是我的实际代码:
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame
import pygame.midi
import time
import threading
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
a = 0
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(998, 759)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(430, 80, 121, 71))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 998, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
'THIS IS THE BUTTON'
self.pushButton.clicked.connect(self.clicked)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "C"))
'during each loop of loop() check to see if a == 4 and if it does then break'
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
def clicked(self):
print('clicked')
global player
global a
a = a + 2
player.note_on(60, 127)
self.loop()
player.note_off(60, 127)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
我假设答案与线程有关,但是当我尝试这个时,它没有任何区别,因为我在 loop()
中有一个线程,在 clicked()
中有一个线程,但这没有区别,因为只要 clicked()
是 运行,第二次点击就永远不会被识别,因为 clicked()
是按钮实际调用的函数。我让 clicked()
执行 a = a + 2 以便在第二次单击时我知道 a == 4 并想用它来结束循环。
您的代码有几个问题:
不要在事件循环中使用 time.sleep,即使是在很小的间隔内,因为它们仍然会阻塞事件循环并生成例如:GUI 冻结,与信号关联的槽不会被调用,等等
不要使用全局变量,因为它们很难调试。
在这种情况下,最好使用 QTimer 并在控制器中实现逻辑 class,另一方面,不要修改 pyuic 生成的代码,因此在这种情况下,您将不得不恢复它代码并将其保存在 ui.py 文件中。
from functools import cached_property
from dataclasses import dataclass
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame.midi
from ui import Ui_MainWindow
@dataclass
class MidiController(QtCore.QObject):
note: int = 60
velocity: int = 127
channel: int = 0
@cached_property
def timer(self):
timer = QtCore.QTimer(singleShot=True)
timer.timeout.connect(self.off)
return timer
@cached_property
def player(self):
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
return player
def start(self, dt=1 * 1000):
self.timer.setInterval(dt)
self.timer.start()
self.on()
@property
def running(self):
return self.timer.isActive()
def stop(self):
self.timer.stop()
self.off()
def on(self):
self.player.note_on(self.note, self.velocity, self.channel)
def off(self):
self.player.note_off(self.note, self.velocity, self.channel)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.handle_clicked)
@cached_property
def midi_controller(self):
return MidiController()
def handle_clicked(self):
# update parameters
self.midi_controller.note = 60
# self.midi_controller.velocity = 127
if self.midi_controller.running:
self.midi_controller.stop()
else:
self.midi_controller.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
我有这样的东西:self.pushButton.clicked.connect(self.clicked)
我点击我的按钮,它调用函数 clicked()
,目前一切正常。
Clicked()
函数播放一个 midi 音符然后执行 time.sleep()
但是我并没有实际使用 time.sleep()
我调用了另一个函数 loop()
来执行 time.sleep
对我来说:
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
这将执行 1 秒 time.sleep
除非 a == 4
。clicked()
将在循环结束后关闭 midi 音符。
现在我想要它,所以如果我在循环 运行 时再次单击同一个按钮,那么 a = 4 并且循环将被打破,原始音符将在 1 秒过去之前停止。例如如果我在循环的第 5 个循环中单击按钮,它将中断,返回 clicked()
并提前关闭音符。
我希望能够在我第二次单击该按钮后立即打破此循环。(同一个按钮)
问题是,虽然这个循环是 运行,但它拒绝接受第二次按钮按下。一旦整个 Clicked()
完成 运行,它就会对第二次点击进行查询,但我不想要这个。我不想等待 Clicked
完成。我想在 clicked()
为 运行 时单击按钮,然后按下第二个按钮使 a = 4 以便循环停止。
我已经尝试使用线程来执行此操作,但它似乎没有什么不同,第二次单击按钮将不会注册,直到 clicked()
完成。
我想看的:
我点击按钮,它播放了 1 秒的 MIDI 音符。如果我在 1 秒之前再次按下相同的按钮,那么它会提前切断音符并再次启动音符。
基本上,如果我再次单击按钮,我想更改循环的长度。
这是我的实际代码:
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame
import pygame.midi
import time
import threading
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
a = 0
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(998, 759)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(430, 80, 121, 71))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 998, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
'THIS IS THE BUTTON'
self.pushButton.clicked.connect(self.clicked)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "C"))
'during each loop of loop() check to see if a == 4 and if it does then break'
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
def clicked(self):
print('clicked')
global player
global a
a = a + 2
player.note_on(60, 127)
self.loop()
player.note_off(60, 127)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
我假设答案与线程有关,但是当我尝试这个时,它没有任何区别,因为我在 loop()
中有一个线程,在 clicked()
中有一个线程,但这没有区别,因为只要 clicked()
是 运行,第二次点击就永远不会被识别,因为 clicked()
是按钮实际调用的函数。我让 clicked()
执行 a = a + 2 以便在第二次单击时我知道 a == 4 并想用它来结束循环。
您的代码有几个问题:
不要在事件循环中使用 time.sleep,即使是在很小的间隔内,因为它们仍然会阻塞事件循环并生成例如:GUI 冻结,与信号关联的槽不会被调用,等等
不要使用全局变量,因为它们很难调试。
在这种情况下,最好使用 QTimer 并在控制器中实现逻辑 class,另一方面,不要修改 pyuic 生成的代码,因此在这种情况下,您将不得不恢复它代码并将其保存在 ui.py 文件中。
from functools import cached_property
from dataclasses import dataclass
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame.midi
from ui import Ui_MainWindow
@dataclass
class MidiController(QtCore.QObject):
note: int = 60
velocity: int = 127
channel: int = 0
@cached_property
def timer(self):
timer = QtCore.QTimer(singleShot=True)
timer.timeout.connect(self.off)
return timer
@cached_property
def player(self):
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
return player
def start(self, dt=1 * 1000):
self.timer.setInterval(dt)
self.timer.start()
self.on()
@property
def running(self):
return self.timer.isActive()
def stop(self):
self.timer.stop()
self.off()
def on(self):
self.player.note_on(self.note, self.velocity, self.channel)
def off(self):
self.player.note_off(self.note, self.velocity, self.channel)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.handle_clicked)
@cached_property
def midi_controller(self):
return MidiController()
def handle_clicked(self):
# update parameters
self.midi_controller.note = 60
# self.midi_controller.velocity = 127
if self.midi_controller.running:
self.midi_controller.stop()
else:
self.midi_controller.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())