python - 是否可以使用 PYQT5 中的元素创建一个类

标签 python oop pycharm pyqt5 qpushbutton

我正在尝试创建一个 GUI 键盘。我正在使用 QPushButton 来复制按键。我还没有专注于将它链接到 MIDI。但是,我想知道是否可以使用 QPushButton 的属性创建一个类。我指的是 QPushButton.move/resize/setStyleSheet 等函数。

就目前而言,我的代码非常长(如下所示)。这是由于不断重复代码来制作每个按钮。然而,我注意到大小、样式表和位置(仅在 y 轴上)等属性是相同的,主要区别在于黑键和白键之间的 x 轴和 y 轴。如果我要创建一个类,我将如何创建具有各种属性的对象?

再次强调,如果我的逻辑有缺陷或者有任何错误,请随时告诉我。

import sys

from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QAction, QLineEdit, QMessageBox, QLabel \
    ,QDialog
from PyQt5.QtGui import QIcon, QHideEvent, QFont, QPixmap
from PyQt5.QtCore import pyqtSlot, QObject, QSize


class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'Piano '
        self.left = 620
        self.top =250
        self.width = 1920
        self.height = 1080
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.setFixedWidth(self.width)
        self.setFixedHeight(self.height)
        self.setStyleSheet("background-color:lightgrey")
        self.setWindowIcon(QIcon('img.png'))

        # Create a button in the window
        self.button1 = QPushButton('C', self)
        self.button1.move(10, 650)
        self.button1.setFont(QFont('Arial', 14))
        self.button1.resize(100,400)
        self.button1.setStyleSheet("background-color:white; border :1px solid ;") #Black Not Working

        self.button2 = QPushButton('D', self)
        self.button2.move(110,650)
        self.button2.setFont(QFont('Arial', 14))
        self.button2.resize(100, 400)
        self.button2.setStyleSheet("background-color:white; border :1px solid ;")

        self.button3 = QPushButton('E', self)
        self.button3.move(210, 650)
        self.button3.setFont(QFont('Arial', 14))
        self.button3.resize(100, 400)
        self.button3.setStyleSheet("background-color:white; border :1px solid ;")

        self.button4 = QPushButton('F', self)
        self.button4.move(310, 650)
        self.button4.setFont(QFont('Arial', 14))
        self.button4.resize(100, 400)
        self.button4.setStyleSheet("background-color:white; border :1px solid ;")

        self.button5 = QPushButton('G', self)
        self.button5.move(410, 650)
        self.button5.setFont(QFont('Arial', 14))
        self.button5.resize(100, 400)
        self.button5.setStyleSheet("background-color:white; border :1px solid ;")

        self.button6 = QPushButton('A', self)
        self.button6.move(510, 650)
        self.button6.setFont(QFont('Arial', 14))
        self.button6.resize(100, 400)
        self.button6.setStyleSheet("background-color:white; border :1px solid ;")

        self.button7 = QPushButton('B', self)
        self.button7.move(610, 650)
        self.button7.setFont(QFont('Arial', 14))
        self.button7.resize(100, 400)
        self.button7.setStyleSheet("background-color:white; border :1px solid ;")

        self.button8 = QPushButton('C2', self)
        self.button8.move(710, 650)
        self.button8.setFont(QFont('Arial', 14))
        self.button8.resize(100, 400)
        self.button8.setStyleSheet("background-color:white; border :1px solid ;")

        self.button2 = QPushButton('D2', self)
        self.button2.move(810, 650)
        self.button2.setFont(QFont('Arial', 14))
        self.button2.resize(100, 400)
        self.button2.setStyleSheet("background-color:white; border :1px solid ;")

        self.button3 = QPushButton('E2', self)
        self.button3.move(910, 650)
        self.button3.setFont(QFont('Arial', 14))
        self.button3.resize(100, 400)
        self.button3.setStyleSheet("background-color:white; border :1px solid ;")

        self.button4 = QPushButton('F2', self)
        self.button4.move(1010, 650)
        self.button4.setFont(QFont('Arial', 14))
        self.button4.resize(100, 400)
        self.button4.setStyleSheet("background-color:white; border :1px solid ;")

        self.button5 = QPushButton('G2', self)
        self.button5.move(1110, 650)
        self.button5.setFont(QFont('Arial', 14))
        self.button5.resize(100, 400)
        self.button5.setStyleSheet("background-color:white; border :1px solid ;")

        self.button6 = QPushButton('A2', self)
        self.button6.move(1210, 650)
        self.button6.setFont(QFont('Arial', 14))
        self.button6.resize(100, 400)
        self.button6.setStyleSheet("background-color:white; border :1px solid ;")

        self.button7 = QPushButton('B2', self)
        self.button7.move(1310, 650)
        self.button7.setFont(QFont('Arial', 14))
        self.button7.resize(100, 400)
        self.button7.setStyleSheet("background-color:white; border :1px solid ;")

        self.button8 = QPushButton('C3', self)
        self.button8.move(1410, 650)
        self.button8.setFont(QFont('Arial', 14))
        self.button8.resize(100, 400)
        self.button8.setStyleSheet("background-color:white; border :1px solid ;")

        self.button1up = QPushButton('C#', self)
        self.button1up.move(85, 650)
        self.button1up.setFont(QFont('Arial', 14))
        self.button1up.resize(50, 300)
        self.button1up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button2up = QPushButton('D#', self)
        self.button2up.move(185, 650)
        self.button2up.setFont(QFont('Arial', 14))
        self.button2up.resize(50, 300)
        self.button2up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button3up = QPushButton('F#', self)
        self.button3up.move(385, 650)
        self.button3up.setFont(QFont('Arial', 14))
        self.button3up.resize(50, 300)
        self.button3up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button4up = QPushButton('G#', self)
        self.button4up.move(485, 650)
        self.button4up.setFont(QFont('Arial', 14))
        self.button4up.resize(50, 300)
        self.button4up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button5up = QPushButton('A#', self)
        self.button5up.move(585, 650)
        self.button5up.setFont(QFont('Arial', 14))
        self.button5up.resize(50, 300)
        self.button5up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button6up = QPushButton(self)
        self.button6up.move(785, 650)
        self.button6up.setFont(QFont('Arial', 14))
        self.button6up.resize(50, 300)
        self.button6up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button7up = QPushButton(self)
        self.button7up.move(885, 650)
        self.button7up.setFont(QFont('Arial', 14))
        self.button7up.resize(50, 300)
        self.button7up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button8up = QPushButton(self)
        self.button8up.move(1085, 650)
        self.button8up.setFont(QFont('Arial', 14))
        self.button8up.resize(50, 300)
        self.button8up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button9up = QPushButton(self)
        self.button9up.move(1185, 650)
        self.button9up.setFont(QFont('Arial', 14))
        self.button9up.resize(50, 300)
        self.button9up.setStyleSheet("background-color:black; border :1px solid ;")

        self.button10up = QPushButton(self)
        self.button10up.move(1285, 650)
        self.button10up.setFont(QFont('Arial', 14))
        self.button10up.resize(50, 300)
        self.button10up.setStyleSheet("background-color:black; border :1px solid ;")

        # connect button to function on_click
        self.button1.clicked.connect(self.b1)

        self.show()

    @pyqtSlot()
    def b1(self):
       pass


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

最佳答案

第一个逻辑解决方案是为每个键创建一个数据列表,如下所示:

KeyMap = (
    # key name, x, y, width, height, color
    ('C', 10, 650, 100, 400, 'white'),
    # ...
    ('C#', 85, 650, 50, 300, 'black'),
)

然后循环浏览列表:

    baseStyle = 'background-color: {}; border :1px solid;'
    for key, x, y, width, height, color in KeyMap:
        button = QPushButton('key', self)
        button.setGeometry(x, y, width, height)
        button.setFont(QFont('Arial', 14))
        button.setStyleSheet(baseStyle.format(color)

不幸的是,虽然上面的方法对于简单的例子来说可能工作得很好,但我可以根据经验告诉你,它对于这种情况(类似钢琴的键盘)来说效果不太好,主要是由于以下两个原因:

  • 固定几何形状很少是一个好主意,如果您出于任何原因需要调整键盘大小(包括使其适合屏幕,特别是如果它很小),这将使手动编码变得更加困难;
  • 您可能需要将键盘扩展到更多 Octave 或想要更改按键的宽度和高度之间的比率;

我在尝试创建 UI 键盘方面拥有丰富的经验,最终得到了一个完全自定义的小部件(基于 QGraphicsView),但对于简单的情况,以下内容可能会满足您的需求。

诀窍是使用 Qt 网格布局,它有一个有趣的“功能”,即如果放置在其“单元格”之间,则允许项目重叠。

以下代码使用 2 行(顶部为白键和黑键,底部为白键),每个白键使用 3 列:白键使用全部三列,黑键使用第三列前一个白键,以及下一个白键的第一个;想象一下这样的表格:

+---+---+---+---+---+---+---+---+---+
|   ·   |   ·   |   |   ·   |   ·   |
|   ·   |   C   |   |   D   |   ·   |
|   ·   | sharp |   | sharp |   ·   |
|   ·   |   ·   |   |   ·   |   ·   |
+ · · · +---+---+ · +---+---+ · · · +
|   ·   ·   |   ·   ·   |   ·   ·   |
|   · C ·   |   · D ·   |   · E ·   |
|   ·   ·   |   ·   ·   |   ·   ·   |
+---+---+---+---+---+---+---+---+---+

然后,您可以为按键创建一个基本类(包括按下释放时按键触发的信号),并使用简单的列表轻松获取按键是白色还是黑色并放置/相应地设计它们。

可选参数显然用于 Octave 范围和 Octave 开始(因为您可能会使用 MIDI,这很重要,因为您将自动获取键值)。

nice keyboard

from PyQt5 import QtCore, QtWidgets

BlackIdx = 1, 3, -1, 6, 8, 10
WhiteIdx = 0, 2, 4, 5, 7, 9, 11
KeyNames = 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'


class KeyButton(QtWidgets.QPushButton):
    triggered = QtCore.pyqtSignal(int, bool)
    def __init__(self, key, isBlack=False):
        super().__init__()
        self.key = key
        # this will be used by the stylesheet
        self.setProperty('isBlack', isBlack)

        octave, keyIdx = divmod(key, 12)
        self.setText('{}{}'.format(KeyNames[keyIdx], octave))

        self.setMinimumWidth(25)
        self.setMinimumSize(25, 80 if isBlack else 120)
        
        # ensure that the key expands vertically
        self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, 
            QtWidgets.QSizePolicy.Expanding)

        # connect the pressed and released (not clicked!) signals to our custom one
        self.pressed.connect(lambda: self.triggered.emit(key, True))
        self.released.connect(lambda: self.triggered.emit(key, False))


class Keyboard(QtWidgets.QWidget):
    def __init__(self, octaves=2, octaveStart=3):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)

        # the ratio between key heights: white keys are 1/3 longer than black ones
        layout.setRowStretch(0, 2)
        layout.setRowStretch(1, 1)
        layout.setSpacing(0)

        blackKeys = []
        for octave in range(octaves):
            for k in range(12):
                isBlack = k in BlackIdx
                keyButton = KeyButton(k + (octaveStart + octave) * 12, isBlack)
                keyButton.triggered.connect(self.keyTriggered)
                if isBlack:
                    keyPos = BlackIdx.index(k)
                    # column based on the index of the key list, plus 2 "cells"
                    col = keyPos * 3 + 2
                    # only one row in the layout
                    vSpan = 1
                    # only two columns
                    hSpan = 2
                    blackKeys.append(keyButton)
                else:
                    keyPos = WhiteIdx.index(k)
                    col = keyPos * 3
                    # two rows
                    vSpan = 2
                    # three columns
                    hSpan = 3
                col += octave * 21
                layout.addWidget(keyButton, 0, col, vSpan, hSpan)

            # "blank" spacers between E-F and B-C, to keep the spacings homogeneous
            efSpacer = QtWidgets.QWidget()
            efSpacer.setMinimumWidth(25)
            layout.addWidget(efSpacer, 0, octave * 21 + 8, 1, 2)
            efSpacer.lower()
            baSpacer = QtWidgets.QWidget()
            baSpacer.setMinimumWidth(25)
            layout.addWidget(baSpacer, 0, octave * 21 + 20, 1, 2)
            baSpacer.lower()

        # the last C note, with a minimum width a bit bigger
        octave += 1
        lastButton = KeyButton((octaveStart + octave) * 12)
        lastButton.setMinimumWidth(32)
        lastButton.triggered.connect(self.keyTriggered)
        layout.addWidget(lastButton, 0, octave * 21, 2, 3)

        # raise all black keys on top of everything else
        for keyButton in blackKeys:
            keyButton.raise_()

        # set the stretch of layout cells, if it's in the middle, it's bigger
        for col in range(layout.columnCount()):
            if col % 3 == 1:
                layout.setColumnStretch(col, 4)
            else:
                layout.setColumnStretch(col, 3)

        self.setStyleSheet('''
            KeyButton {
                color: rgb(50, 50, 50);
                border: 1px outset rgb(128, 128, 128);
                border-radius: 2px;
                background: white;
            }
            KeyButton:pressed {
                border-style: inset;
            }
            KeyButton[isBlack=true] {
                color: rgb(250, 250, 250);
                background: black;
            }
            KeyButton[isBlack=true]:pressed {
                background: rgb(50, 50, 50);
            }
        ''')

    def keyTriggered(self, key, pressed):
        octave, keyIdx = divmod(key, 12)
        keyName = '{}{}'.format(KeyNames[keyIdx], octave)
        state = 'pressed' if pressed else 'released'
        print('Key {} ({}) {}'.format(key, keyName, state))


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    keyboard = Keyboard()
    keyboard.show()
    sys.exit(app.exec_())

PS:我用几年前制作的键盘创建了一个要点。请注意,这段代码相当旧,当时我还没有经验(您可能需要更改 import 语句,因为它的目标是 Qt.py 模块,该模块允许透明集成PyQt4/5 和 PySide):
https://gist.github.com/MaurizioB/43a053575f17eae371a9d7394e66a46e

关于python - 是否可以使用 PYQT5 中的元素创建一个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66139144/

相关文章:

python - pandas 应用正则表达式替换值

oop - 域驱动设计中多个表的单个存储库与多个存储库

django - 这是PyCharm 4.0.5中的错误吗?

python - 有什么方法可以用一个衬垫查看 PyCharm 控制台中定义的变量吗?

python - 数据库访问生成器是否返回使用它们时添加的新行?

python - 3D 轴上填充等高线图中的伪影

python - 如何有条件地将数据从数组添加到字典

c++ - 为什么在接口(interface)中定义私有(private)成员/方法?

java - Method 和 Constructor<?> 都继承自 Member,都具有 getExceptionTypes() 方法。在这种情况下如何避免代码重复?

python - PyCharm 自动导入和自动完成