9

I am trying to create toggle button in qt designer. I refer on internet also but i couldn't find how to do that. Can anyone know how to do toggle switch button. I have attached a sample button image.

toggle button

EDIT

I created a text box below that i want this toggle button. When i try to add it throws me error. How to add the button below the text area? i have attached the code snippet also.

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_())
Anonymous
  • 113
  • 1
  • 1
  • 10

3 Answers3

6

Qt Designer to set the position and initialize some properties of the widgets in a window, but it does not work to create widgets with custom painted as the switch so you will have to implement with python. Long ago for a project implement a switch so it will show that code:

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_())

Note: Another possible solution within the Qt world is to use the QML Switch1, 2 component.

Note: In this post I point out how to add custom widgets to a .ui file.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • thanks for your time i have edited my question please help me out to reach this – Anonymous Jun 13 '20 at 19:47
  • @Anonymous 1) Correct the indentation of your code, 2) Run in the terminal so that you get the error message and show it in your question, and 3) What does that code have to do with switch? – eyllanesc Jun 13 '20 at 19:50
  • 1) i corrected my code, 2) the thing is how to add the above snippet into my code, 3)I am trying to create gui for an application so it needs buttton. I think i cleared your question – Anonymous Jun 13 '20 at 20:09
  • @Anonymous If you want to insert widgets in Qt Designer then that has already been answered in a previous question as I already pointed out at the end of my updated answer. For you to use it easily since I do not want to put the same from the other post in my current answer I pass the code in the following link https://gist.github.com/eyllanesc/01d513a746f5d86f6708ba6f31d8a023 – eyllanesc Jun 13 '20 at 20:26
  • i have commented on your link. please see that – Anonymous Jun 13 '20 at 21:03
  • @eyllanesc, could you tell me how to change the background color (preferbably to blue) in "ON" state? Currently, only the button moves from left to right and vice-versa. – Voldemort Nov 25 '20 at 16:30
  • @deepanshu The draw method is where the painting is done, so I recommend you change the colors there – eyllanesc Nov 25 '20 at 17:10
  • @eyllanesc, I changed the color in the draw method but the issue is that the color gets fixed for both "ON" and "OFF" states. I would like to have blue background for "ON" and grey for "OFF". Could you help me out? – Voldemort Nov 25 '20 at 19:43
  • @deepanshu then use `color = QtGui.Color("blue") if self.mPointer.isChecked() else QtGui.Color("grey")` – eyllanesc Nov 25 '20 at 19:49
2

A fairly simple implementation of a Toggle Switch is given in the qtwidgets python module (pip install qtwidgets). In my case, I needed a more customized version to fit with my overall app design.

enter image description here

The code is quite straight forward. I created a modified toggle switch widget based upon the original author's git hub repo: Martin Fitzpatrick: qtwidgets/toggle. The real credit here goes to M. Fitzpatrick. This is with my modifications (added "ON" text, set vertical and horizontal scaling, some default colors):

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()
RexBarker
  • 1,456
  • 16
  • 14
1

A possible solution is to use a stylesheet with a QCheckBox. Just edit the stylesheet for the check box with the following code:

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

Minimal running example:

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_())

Unfortunately, this doesn't always work well if you need some resizing and want the checkbox to adjust its appearance.

The only alternative is to subclass a QPushButton/QAbstractButton (with the checkable() property set to True) and implement the paintEvent on your own, as already suggested by the answer from eyllanesc.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • so there's no way to auto scale the image down to the right size? – yem May 03 '22 at 08:22
  • 1
    @yem the size of the indicator is fixed and defined by the style, so you cannot "auto scale" it. An alternative is to set the `width` and `height` of the `indicator` and use `border-image` instead of `image`, but this only allows for explicit size defined in the stylesheet, there is no automatic scaling available. – musicamante May 06 '22 at 17:30