问题描述
我有这样的东西: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,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()
正在运行,click 就永远不会被识别,因为 clicked()
是按钮实际调用的函数。我让 clicked()
do a = a + 2 以便在第二次点击时我知道 a == 4 并想用它来结束循环。
解决方法
您的代码有几个问题:
-
即使在很小的时间间隔内也不要在 eventloop 中使用 time.sleep,因为它们仍然会阻塞 eventloop 并生成例如:GUI 冻结、与信号关联的插槽未被调用等。
-
不要使用全局变量,因为它们很难调试。
在这种情况下,最好使用 QTimer 并在控制器类中实现逻辑,另一方面不要修改 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.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_())