如何在功能仍然 运行 时单击按钮两次?

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 == 4clicked() 将在循环结束后关闭 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_())