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

问题描述

我有这样的东西: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,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_())