如何在Qt Designer中创建切换开关按钮?

9

我正在尝试在qt designer中创建切换按钮。我在网上搜索了相关内容,但无法找到如何实现的方法。有人知道如何制作切换开关按钮吗?我附上了一个示例按钮图像。

toggle button

编辑

我在文本框下面创建了一个想要放置这个切换按钮的区域。但是当我尝试添加它时,会出现错误。怎样将按钮添加到文本区域下面?我还附上了代码片段。

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPropertyAnimation, QRectF, QSize, Qt, pyqtProperty
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import (
    QAbstractButton,
    QApplication,
    QHBoxLayout,
    QSizePolicy,
    QWidget,
)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
       MainWindow.setObjectName("MainWindow")
       MainWindow.resize(472, 180)
       self.centralwidget = QtWidgets.QWidget(MainWindow)
       self.centralwidget.setObjectName("centralwidget")
       self.url = QtWidgets.QLineEdit(self.centralwidget)
       self.url.setGeometry(QtCore.QRect(30, 20, 411, 31))
       font = QtGui.QFont()
       font.setFamily("MS Shell Dlg 2")
       font.setPointSize(10)
       font.setBold(True)
       font.setWeight(75)
       self.url.setFont(font)
       self.url.setAutoFillBackground(False)
       self.url.setStyleSheet("border-radius:10px;")
       self.url.setAlignment(QtCore.Qt.AlignCenter)
       self.url.setCursorMoveStyle(QtCore.Qt.LogicalMoveStyle)
       self.url.setObjectName("url")
       MainWindow.setCentralWidget(self.centralwidget)
       self.menubar = QtWidgets.QMenuBar(MainWindow)
       self.menubar.setGeometry(QtCore.QRect(0, 0, 472, 21))
       self.menubar.setObjectName("menubar")
       MainWindow.setMenuBar(self.menubar)
       self.statusbar = QtWidgets.QStatusBar(MainWindow)
       self.statusbar.setObjectName("statusbar")
       MainWindow.setStatusBar(self.statusbar)

       self.retranslateUi(MainWindow)
       QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
       _translate = QtCore.QCoreApplication.translate
       MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
       self.url.setPlaceholderText(_translate("MainWindow", "Playlist URL"))

if __name__ == "__main__":
    import sys
    main()
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())
3个回答

6

使用Qt Designer设置窗口中小部件的位置和初始化一些属性,但无法创建自定义绘制开关等小部件,因此需要使用Python实现。很久以前,为一个项目实现了一个开关,以下是该代码:

from PyQt5.QtCore import QObject, QSize, QPointF, QPropertyAnimation, QEasingCurve, pyqtProperty, pyqtSlot, Qt
from PyQt5.QtGui import  QPainter, QPalette, QLinearGradient, QGradient
from PyQt5.QtWidgets import QAbstractButton, QApplication, QWidget, QHBoxLayout, QLabel


class SwitchPrivate(QObject):
    def __init__(self, q, parent=None):
        QObject.__init__(self, parent=parent)
        self.mPointer = q
        self.mPosition = 0.0
        self.mGradient = QLinearGradient()
        self.mGradient.setSpread(QGradient.PadSpread)

        self.animation = QPropertyAnimation(self)
        self.animation.setTargetObject(self)
        self.animation.setPropertyName(b'position')
        self.animation.setStartValue(0.0)
        self.animation.setEndValue(1.0)
        self.animation.setDuration(200)
        self.animation.setEasingCurve(QEasingCurve.InOutExpo)

        self.animation.finished.connect(self.mPointer.update)

    @pyqtProperty(float)
    def position(self):
        return self.mPosition

    @position.setter
    def position(self, value):
        self.mPosition = value
        self.mPointer.update()

    def draw(self, painter):
        r = self.mPointer.rect()
        margin = r.height()/10
        shadow = self.mPointer.palette().color(QPalette.Dark)
        light = self.mPointer.palette().color(QPalette.Light)
        button = self.mPointer.palette().color(QPalette.Button)
        painter.setPen(Qt.NoPen)

        self.mGradient.setColorAt(0, shadow.darker(130))
        self.mGradient.setColorAt(1, light.darker(130))
        self.mGradient.setStart(0, r.height())
        self.mGradient.setFinalStop(0, 0)
        painter.setBrush(self.mGradient)
        painter.drawRoundedRect(r, r.height()/2, r.height()/2)

        self.mGradient.setColorAt(0, shadow.darker(140))
        self.mGradient.setColorAt(1, light.darker(160))
        self.mGradient.setStart(0, 0)
        self.mGradient.setFinalStop(0, r.height())
        painter.setBrush(self.mGradient)
        painter.drawRoundedRect(r.adjusted(margin, margin, -margin, -margin), r.height()/2, r.height()/2)

        self.mGradient.setColorAt(0, button.darker(130))
        self.mGradient.setColorAt(1, button)

        painter.setBrush(self.mGradient)

        x = r.height()/2.0 + self.mPosition*(r.width()-r.height())
        painter.drawEllipse(QPointF(x, r.height()/2), r.height()/2-margin, r.height()/2-margin)

    @pyqtSlot(bool, name='animate')
    def animate(self, checked):
        self.animation.setDirection(QPropertyAnimation.Forward if checked else QPropertyAnimation.Backward)
        self.animation.start()


class Switch(QAbstractButton):
    def __init__(self, parent=None):
        QAbstractButton.__init__(self, parent=parent)
        self.dPtr = SwitchPrivate(self)
        self.setCheckable(True)
        self.clicked.connect(self.dPtr.animate)

    def sizeHint(self):
        return QSize(84, 42)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        self.dPtr.draw(painter)

    def resizeEvent(self, event):
        self.update()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    w = Switch()
    w.show()
    sys.exit(app.exec_())

注意: 在Qt世界中,另一个可能的解决方案是使用QML Switch1, 2组件。

注意:这篇文章中,我指出了如何将自定义小部件添加到.ui文件中。


  1. 我已经纠正了我的代码。
  2. 问题是如何将上面的片段添加到我的代码中。
  3. 我正在尝试为一个应用程序创建GUI,因此它需要按钮。我想我解答了你的问题。
- Anonymous
@eyllanesc,你能告诉我如何在“ON”状态下更改背景颜色(最好是蓝色)吗?目前只有按钮从左到右和相反移动。 - Voldemort
@deepanshu 绘制方法是绘画的地方,所以我建议你在那里更改颜色。 - eyllanesc
@eyllanesc,我已经在绘制方法中更改了颜色,但问题是颜色对于“ON”和“OFF”状态都是固定的。 我想让“ON”具有蓝色背景,“OFF”具有灰色背景。 你能帮我吗? - Voldemort
@deepanshu 然后使用 color = QtGui.Color("blue") if self.mPointer.isChecked() else QtGui.Color("grey") - eyllanesc
显示剩余4条评论

2
一个相当简单的切换开关的实现可以在 Python 的 qtwidgets 模块中找到(pip install qtwidgets)。在我的情况下,我需要一个更定制化的版本来适应我的整体应用设计。

enter image description here

这段话的意思是:“代码非常简单。我基于原作者的 git hub 仓库 Martin Fitzpatrick: qtwidgets/toggle 创建了一个修改过的切换开关小部件。真正的功劳应归于 M. Fitzpatrick。这是我的修改(添加了“ON”文本,设置了垂直和水平缩放,一些默认颜色)。”
class ToggleSwitch(QCheckBox):

    _transparent_pen = QPen(Qt.transparent)
    _light_grey_pen = QPen(Qt.lightGray)
    _black_pen = QPen(Qt.black)

    def __init__(self, 
                 parent=None, 
                 bar_color=Qt.gray, 
                 checked_color="#00B0FF",
                 handle_color=Qt.white, 
                 h_scale=1.0,
                 v_scale=1.0,
                 fontSize=10):
             
        super().__init__(parent)

        # Save our properties on the object via self, so we can access them later
        # in the paintEvent.
        self._bar_brush = QBrush(bar_color)
        self._bar_checked_brush = QBrush(QColor(checked_color).lighter())

        self._handle_brush = QBrush(handle_color)
        self._handle_checked_brush = QBrush(QColor(checked_color))

        # Setup the rest of the widget.

        self.setContentsMargins(8, 0, 8, 0)
        self._handle_position = 0
        self._h_scale = h_scale
        self._v_scale = v_scale
        self._fontSize = fontSize

        self.stateChanged.connect(self.handle_state_change)

    def sizeHint(self):
        return QSize(58, 45)

    def hitButton(self, pos: QPoint):
        return self.contentsRect().contains(pos)

    def paintEvent(self, e: QPaintEvent):

        contRect = self.contentsRect()
        width =  contRect.width() * self._h_scale
        height = contRect.height() * self._v_scale
        handleRadius = round(0.24 * height)

        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)

        p.setPen(self._transparent_pen)
        barRect = QRectF(0, 0, width - handleRadius, 0.40 * height)
        barRect.moveCenter(contRect.center())
        rounding = barRect.height() / 2

        # the handle will move along this line
        trailLength = contRect.width()*self._h_scale - 2 * handleRadius
        xLeft = contRect.center().x() - (trailLength + handleRadius)/2 
        xPos = xLeft + handleRadius + trailLength * self._handle_position

        if self.isChecked():
            p.setBrush(self._bar_checked_brush)
            p.drawRoundedRect(barRect, rounding, rounding)
            p.setBrush(self._handle_checked_brush)

            p.setPen(self._black_pen)
            p.setFont(QFont('Helvetica', self._fontSize, 75))
            p.drawText(xLeft + handleRadius / 2, contRect.center().y() + 
                       handleRadius / 2,"ON")

        else:
            p.setBrush(self._bar_brush)
            p.drawRoundedRect(barRect, rounding, rounding)
            p.setPen(self._light_grey_pen)
            p.setBrush(self._handle_brush)

        p.setPen(self._light_grey_pen)
        p.drawEllipse(
            QPointF(xPos, barRect.center().y()),
            handleRadius, handleRadius)

        p.end()

    @Slot(int)
    def handle_state_change(self, value):
        self._handle_position = 1 if value else 0

    @Property(float)
    def handle_position(self):
        return self._handle_position

    @handle_position.setter
    def handle_position(self, pos):
        """change the property
           we need to trigger QWidget.update() method, either by:
           1- calling it here [ what we're doing ].
           2- connecting the QPropertyAnimation.valueChanged() signal to it.
        """
        self._handle_position = pos
        self.update()

    def setH_scale(self,value):
        self._h_scale = value
        self.update()

    def setV_scale(self,value):
        self._v_scale = value
        self.update()

    def setFontSize(self,value):
        self._fontSize = value
        self.update()

1

一个可能的解决方案是使用带有QCheckBox的样式表。只需使用以下代码编辑复选框的样式表:

    QCheckBox::indicator:unchecked {
        image: url(switch_off.png);
    }
    QCheckBox::indicator:checked {
        image: url(switch_on.png);
    }

最小运行示例:

from PyQt5 import QtWidgets
import sys

app = QtWidgets.QApplication(sys.argv)
switch = QtWidgets.QCheckBox()
switch.setStyleSheet('''
    QCheckBox::indicator:unchecked {
        image: url(switch_off.png);
    }
    QCheckBox::indicator:checked {
        image: url(switch_on.png);
    }
''')
switch.show()
sys.exit(app.exec_())

很不幸,如果您需要调整大小并希望复选框调整外观,则这通常不起作用。
唯一的选择是子类化QPushButton/QAbstractButton(将checkable()属性设置为True),并自己实现paintEvent,如 eyllanesc的答案所建议的那样。

那么没有办法自动缩小图像到合适的尺寸吗? - yem
1
@yem 指示器的大小是由样式固定定义的,因此您无法“自动缩放”它。另一种选择是设置 indicatorwidthheight,并使用 border-image 而不是 image,但这仅允许在样式表中明确定义的显式大小,没有自动缩放可用。 - musicamante

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接