Toggle Switch in Qt

后端 未结 10 2220
臣服心动
臣服心动 2020-12-12 17:31

I am trying to use an element which is the equivalent of Android Switches in Qt. I have found a ToggleSwitch in QML, but nothing in the actual C++ Qt libs. Am I just missing

10条回答
  •  没有蜡笔的小新
    2020-12-12 17:54

    A few months ago I made an implementation whose visuals needed to be more consistent with common desktop styles (C++ and Python version available; the Python version was the prototype, i.e. they might work differently). Notice that the aesthetics is fully customized using paintEvent. Do not expect different visuals depending on system.


    C++ implementation

    NOTE: don't forget the includes (which are not in my example).

    Usage:

    SwitchButton* sbtn = new SwitchButton(this); // Default style is Style::ONOFF
    bool current = sbtn->value();
    sbtn->setValue(!current);
    

    (...).hpp

    class SwitchButton : public QWidget
    {
      Q_OBJECT
        Q_DISABLE_COPY(SwitchButton)
    
    public:
      enum Style
      {
        YESNO,
        ONOFF,
        BOOL,
        EMPTY
      };
    
    public:
      explicit SwitchButton(QWidget* parent = nullptr, Style style = Style::ONOFF);
      ~SwitchButton() override;
    
      //-- QWidget methods
      void mousePressEvent(QMouseEvent *) override;
      void paintEvent(QPaintEvent* event) override;
      void setEnabled(bool);
    
      //-- Setters
      void setDuration(int);
      void setValue(bool);
    
      //-- Getters
      bool value() const;
    
    signals:
      void valueChanged(bool newvalue);
    
    private:
      class SwitchCircle;
      class SwitchBackground;
      void _update();
    
    private:
      bool _value;
      int  _duration;
    
      QLinearGradient _lg;
      QLinearGradient _lg2;
      QLinearGradient _lg_disabled;
    
      QColor _pencolor;
      QColor _offcolor;
      QColor _oncolor;
      int    _tol;
      int    _borderradius;
    
      // This order for definition is important (these widgets overlap)
      QLabel*           _labeloff;
      SwitchBackground* _background;
      QLabel*           _labelon;
      SwitchCircle*     _circle;
    
      bool _enabled;
    
      QPropertyAnimation* __btn_move;
      QPropertyAnimation* __back_move;
    };
    
    class SwitchButton::SwitchBackground : public QWidget
    {
      Q_OBJECT
        Q_DISABLE_COPY(SwitchBackground)
    
    public:
      explicit SwitchBackground(QWidget* parent = nullptr, QColor color = QColor(154, 205, 50), bool rect = false);
      ~SwitchBackground() override;
    
      //-- QWidget methods
      void paintEvent(QPaintEvent* event) override;
      void setEnabled(bool);
    
    private:
      bool            _rect;
      int             _borderradius;
      QColor          _color;
      QColor          _pencolor;
      QLinearGradient _lg;
      QLinearGradient _lg_disabled;
    
      bool _enabled;
    };
    
    
    class SwitchButton::SwitchCircle : public QWidget
    {
      Q_OBJECT
        Q_DISABLE_COPY(SwitchCircle)
    
    public:
      explicit SwitchCircle(QWidget* parent = nullptr, QColor color = QColor(255, 255, 255), bool rect = false);
      ~SwitchCircle() override;
    
      //-- QWidget methods
      void paintEvent(QPaintEvent* event) override;
      void setEnabled(bool);
    
    private:
      bool            _rect;
      int             _borderradius;
      QColor          _color;
      QColor          _pencolor;
      QRadialGradient _rg;
      QLinearGradient _lg;
      QLinearGradient _lg_disabled;
    
      bool _enabled;
    };
    

    (...).cpp

    SwitchButton::SwitchButton(QWidget* parent, Style style)
      : QWidget(parent)
      , _value(false)
      , _duration(100)
      , _enabled(true)
    {
      _pencolor = QColor(120, 120, 120);
    
      _lg = QLinearGradient(35, 30, 35, 0);
      _lg.setColorAt(0, QColor(210, 210, 210));
      _lg.setColorAt(0.25, QColor(255, 255, 255));
      _lg.setColorAt(0.82, QColor(255, 255, 255));
      _lg.setColorAt(1, QColor(210, 210, 210));
    
      _lg2 = QLinearGradient(50, 30, 35, 0);
      _lg2.setColorAt(0, QColor(230, 230, 230));
      _lg2.setColorAt(0.25, QColor(255, 255, 255));
      _lg2.setColorAt(0.82, QColor(255, 255, 255));
      _lg2.setColorAt(1, QColor(230, 230, 230));
    
      _lg_disabled = QLinearGradient(50, 30, 35, 0);
      _lg_disabled.setColorAt(0, QColor(200, 200, 200));
      _lg_disabled.setColorAt(0.25, QColor(230, 230, 230));
      _lg_disabled.setColorAt(0.82, QColor(230, 230, 230));
      _lg_disabled.setColorAt(1, QColor(200, 200, 200));
    
      _offcolor = QColor(255, 255, 255);
      _oncolor = QColor(154, 205, 50);
      _tol = 0;
      _borderradius = 12;
      _labeloff = NEW QLabel(this);
      _background = NEW SwitchBackground(this, _oncolor);
      _labelon = NEW QLabel(this);
      _circle = NEW SwitchCircle(this, _offcolor);
      __btn_move = NEW QPropertyAnimation(this);
      __back_move = NEW QPropertyAnimation(this);
    
      __btn_move->setTargetObject(_circle);
      __btn_move->setPropertyName("pos");
      __back_move->setTargetObject(_background);
      __back_move->setPropertyName("size");
    
      setWindowFlags(Qt::FramelessWindowHint);
      setAttribute(Qt::WA_TranslucentBackground);
    
      _labeloff->setText("Off");
      _labelon->setText("On");
      _labeloff->move(31, 5);
      _labelon->move(15, 5);
      setFixedSize(QSize(60, 24));
      if (style == Style::YESNO)
      {
        _labeloff->setText("No");
        _labelon->setText("Yes");
        _labeloff->move(33, 5);
        _labelon->move(12, 5);
        setFixedSize(QSize(60, 24));
      }
      else if (style == Style::BOOL)
      {
        _labeloff->setText("False");
        _labelon->setText("True");
        _labeloff->move(37, 5);
        _labelon->move(12, 5);
        setFixedSize(QSize(75, 24));
      }
      if (style == Style::EMPTY)
      {
        _labeloff->setText("");
        _labelon->setText("");
        _labeloff->move(31, 5);
        _labelon->move(12, 5);
        setFixedSize(QSize(45, 24));
      }
    
      _labeloff->setStyleSheet("color: rgb(120, 120, 120); font-weight: bold;");
      _labelon->setStyleSheet("color: rgb(255, 255, 255); font-weight: bold;");
    
      _background->resize(20, 20);
    
      _background->move(2, 2);
      _circle->move(2, 2);
    }
    
    SwitchButton::~SwitchButton()
    {
      delete _circle;
      delete _background;
      delete _labeloff;
      delete _labelon;
      delete __btn_move;
      delete __back_move;
    }
    
    void SwitchButton::paintEvent(QPaintEvent*)
    {
      QPainter* painter = new QPainter;
      painter->begin(this);
      painter->setRenderHint(QPainter::Antialiasing, true);
    
      QPen pen(Qt::NoPen);
      painter->setPen(pen);
    
      painter->setBrush(_pencolor);
      painter->drawRoundedRect(0, 0
        , width(), height()
        , 12, 12);
    
      painter->setBrush(_lg);
      painter->drawRoundedRect(1, 1
        , width() - 2, height() - 2
        , 10, 10);
    
      painter->setBrush(QColor(210, 210, 210));
      painter->drawRoundedRect(2, 2
        , width() - 4, height() - 4
        , 10, 10);
    
      if (_enabled)
      {
        painter->setBrush(_lg2);
        painter->drawRoundedRect(3, 3
          , width() - 6, height() - 6
          , 7, 7);
      }
      else
      {
        painter->setBrush(_lg_disabled);
        painter->drawRoundedRect(3, 3
          , width() - 6, height() - 6
          , 7, 7);
      }
      painter->end();
    }
    
    void SwitchButton::mousePressEvent(QMouseEvent*)
    {
      if (!_enabled)
        return;
    
      __btn_move->stop();
      __back_move->stop();
    
      __btn_move->setDuration(_duration);
      __back_move->setDuration(_duration);
    
      int hback = 20;
      QSize initial_size(hback, hback);
      QSize final_size(width() - 4, hback);
    
      int xi = 2;
      int y  = 2;
      int xf = width() - 22;
    
      if (_value)
      {
        final_size = QSize(hback, hback);
        initial_size = QSize(width() - 4, hback);
    
        xi = xf;
        xf = 2;
      }
    
      __btn_move->setStartValue(QPoint(xi, y));
      __btn_move->setEndValue(QPoint(xf, y));
    
      __back_move->setStartValue(initial_size);
      __back_move->setEndValue(final_size);
    
      __btn_move->start();
      __back_move->start();
    
      // Assigning new current value
      _value = !_value;
      emit valueChanged(_value);
    }
    
    void SwitchButton::setEnabled(bool flag)
    {
      _enabled = flag;
      _circle->setEnabled(flag);
      _background->setEnabled(flag);
      if (flag)
        _labelon->show();
      else
      {
        if (value())
          _labelon->show();
        else
          _labelon->hide();
      }
      QWidget::setEnabled(flag);
    }
    
    void SwitchButton::setDuration(int time)
    {
      _duration = time;
    }
    
    void SwitchButton::setValue(bool flag)
    {
      if (flag == value())
        return;
      else
      {
        _value = flag;
        _update();
        setEnabled(_enabled);
      }
    }
    
    bool SwitchButton::value() const
    {
      return _value;
    }
    
    void SwitchButton::_update()
    {
      int hback = 20;
      QSize final_size(width() - 4, hback);
    
      int y = 2;
      int xf = width() - 22;
    
      if (_value)
      {
        final_size = QSize(hback, hback);
        xf = 2;
      }
    
      _circle->move(QPoint(xf, y));
      _background->resize(final_size);
    }
    
    SwitchButton::SwitchBackground::SwitchBackground(QWidget* parent, QColor color, bool rect)
      : QWidget(parent)
      , _rect(rect)
      , _borderradius(12)
      , _color(color)
      , _pencolor(QColor(170, 170, 170))
    {
      setFixedHeight(20);
    
      _lg = QLinearGradient(0, 25, 70, 0);
      _lg.setColorAt(0, QColor(154, 194, 50));
      _lg.setColorAt(0.25, QColor(154, 210, 50));
      _lg.setColorAt(0.95, QColor(154, 194, 50));
    
      _lg_disabled = QLinearGradient(0, 25, 70, 0);
      _lg_disabled.setColorAt(0, QColor(190, 190, 190));
      _lg_disabled.setColorAt(0.25, QColor(230, 230, 230));
      _lg_disabled.setColorAt(0.95, QColor(190, 190, 190));
    
      if (_rect)
        _borderradius = 0;
    
      _enabled = true;
    }
    SwitchButton::SwitchBackground::~SwitchBackground()
    {
    }
    void SwitchButton::SwitchBackground::paintEvent(QPaintEvent*)
    {
      QPainter* painter = new QPainter;
      painter->begin(this);
      painter->setRenderHint(QPainter::Antialiasing, true);
    
      QPen pen(Qt::NoPen);
      painter->setPen(pen);
      if (_enabled)
      {
        painter->setBrush(QColor(154, 190, 50));
        painter->drawRoundedRect(0, 0
          , width(), height()
          , 10, 10);
    
        painter->setBrush(_lg);
        painter->drawRoundedRect(1, 1, width()-2, height()-2, 8, 8);
      }
      else
      {
        painter->setBrush(QColor(150, 150, 150));
        painter->drawRoundedRect(0, 0
          , width(), height()
          , 10, 10);
    
        painter->setBrush(_lg_disabled);
        painter->drawRoundedRect(1, 1, width() - 2, height() - 2, 8, 8);
      }
      painter->end();
    }
    void SwitchButton::SwitchBackground::setEnabled(bool flag)
    {
      _enabled = flag;
    }
    
    SwitchButton::SwitchCircle::SwitchCircle(QWidget* parent, QColor color, bool rect)
      : QWidget(parent)
      , _rect(rect)
      , _borderradius(12)
      , _color(color)
      , _pencolor(QColor(120, 120, 120))
    {
      setFixedSize(20, 20);
    
      _rg = QRadialGradient(static_cast(width() / 2), static_cast(height() / 2), 12);
      _rg.setColorAt(0, QColor(255, 255, 255));
      _rg.setColorAt(0.6, QColor(255, 255, 255));
      _rg.setColorAt(1, QColor(205, 205, 205));
    
      _lg = QLinearGradient(3, 18, 20, 4);
      _lg.setColorAt(0, QColor(255, 255, 255));
      _lg.setColorAt(0.55, QColor(230, 230, 230));
      _lg.setColorAt(0.72, QColor(255, 255, 255));
      _lg.setColorAt(1, QColor(255, 255, 255));
    
      _lg_disabled = QLinearGradient(3, 18, 20, 4);
      _lg_disabled.setColorAt(0, QColor(230, 230, 230));
      _lg_disabled.setColorAt(0.55, QColor(210, 210, 210));
      _lg_disabled.setColorAt(0.72, QColor(230, 230, 230));
      _lg_disabled.setColorAt(1, QColor(230, 230, 230));
    
      _enabled = true;
    }
    SwitchButton::SwitchCircle::~SwitchCircle()
    {
    }
    void SwitchButton::SwitchCircle::paintEvent(QPaintEvent*)
    {
      QPainter* painter = new QPainter;
      painter->begin(this);
      painter->setRenderHint(QPainter::Antialiasing, true);
    
      QPen pen(Qt::NoPen);
      painter->setPen(pen);
      painter->setBrush(_pencolor);
    
      painter->drawEllipse(0, 0, 20, 20);
      painter->setBrush(_rg);
      painter->drawEllipse(1, 1, 18, 18);
    
      painter->setBrush(QColor(210, 210, 210));
      painter->drawEllipse(2, 2, 16, 16);
    
      if (_enabled)
      {
        painter->setBrush(_lg);
        painter->drawEllipse(3, 3, 14, 14);
      }
      else
      {
        painter->setBrush(_lg_disabled);
        painter->drawEllipse(3, 3, 14, 14);
      }
    
      painter->end();
    }
    void SwitchButton::SwitchCircle::setEnabled(bool flag)
    {
      _enabled = flag;
    }
    

    Python implementation (Prototype; PyQt5)

    Usage:

    switchbtn = SwitchButton(self, "On", 15, "Off", 31, 60)
    

    (...).py

    from PyQt5 import QtWidgets, QtCore, QtGui
    
    class SwitchButton(QtWidgets.QWidget):
        def __init__(self, parent=None, w1="Yes", l1=12, w2="No", l2=33, width=60):
            super(SwitchButton, self).__init__(parent)
            self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
            self.__labeloff = QtWidgets.QLabel(self)
            self.__labeloff.setText(w2)
            self.__labeloff.setStyleSheet("""color: rgb(120, 120, 120); font-weight: bold;""")
            self.__background  = Background(self)
            self.__labelon = QtWidgets.QLabel(self)
            self.__labelon.setText(w1)
            self.__labelon.setStyleSheet("""color: rgb(255, 255, 255); font-weight: bold;""")
            self.__circle      = Circle(self)
            self.__circlemove  = None
            self.__ellipsemove = None
            self.__enabled     = True
            self.__duration    = 100
            self.__value       = False
            self.setFixedSize(width, 24)
    
            self.__background.resize(20, 20)
            self.__background.move(2, 2)
            self.__circle.move(2, 2)
            self.__labelon.move(l1, 5)
            self.__labeloff.move(l2, 5)
    
        def setDuration(self, time):
            self.__duration = time
    
        def mousePressEvent(self, event):
            if not self.__enabled:
                return
    
            self.__circlemove = QtCore.QPropertyAnimation(self.__circle, b"pos")
            self.__circlemove.setDuration(self.__duration)
    
            self.__ellipsemove = QtCore.QPropertyAnimation(self.__background, b"size")
            self.__ellipsemove.setDuration(self.__duration)
    
            xs = 2
            y  = 2
            xf = self.width()-22
            hback = 20
            isize = QtCore.QSize(hback, hback)
            bsize = QtCore.QSize(self.width()-4, hback)
            if self.__value:
                xf = 2
                xs = self.width()-22
                bsize = QtCore.QSize(hback, hback)
                isize = QtCore.QSize(self.width()-4, hback)
    
            self.__circlemove.setStartValue(QtCore.QPoint(xs, y))
            self.__circlemove.setEndValue(QtCore.QPoint(xf, y))
    
            self.__ellipsemove.setStartValue(isize)
            self.__ellipsemove.setEndValue(bsize)
    
            self.__circlemove.start()
            self.__ellipsemove.start()
            self.__value = not self.__value
    
        def paintEvent(self, event):
            s = self.size()
            qp = QtGui.QPainter()
            qp.begin(self)
            qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
            pen = QtGui.QPen(QtCore.Qt.NoPen)
            qp.setPen(pen)
            qp.setBrush(QtGui.QColor(120, 120, 120))
            qp.drawRoundedRect(0, 0, s.width(), s.height(), 12, 12)
            lg = QtGui.QLinearGradient(35, 30, 35, 0)
            lg.setColorAt(0, QtGui.QColor(210, 210, 210, 255))
            lg.setColorAt(0.25, QtGui.QColor(255, 255, 255, 255))
            lg.setColorAt(0.82, QtGui.QColor(255, 255, 255, 255))
            lg.setColorAt(1, QtGui.QColor(210, 210, 210, 255))
            qp.setBrush(lg)
            qp.drawRoundedRect(1, 1, s.width()-2, s.height()-2, 10, 10)
    
            qp.setBrush(QtGui.QColor(210, 210, 210))
            qp.drawRoundedRect(2, 2, s.width() - 4, s.height() - 4, 10, 10)
    
            if self.__enabled:
                lg = QtGui.QLinearGradient(50, 30, 35, 0)
                lg.setColorAt(0, QtGui.QColor(230, 230, 230, 255))
                lg.setColorAt(0.25, QtGui.QColor(255, 255, 255, 255))
                lg.setColorAt(0.82, QtGui.QColor(255, 255, 255, 255))
                lg.setColorAt(1, QtGui.QColor(230, 230, 230, 255))
                qp.setBrush(lg)
                qp.drawRoundedRect(3, 3, s.width() - 6, s.height() - 6, 7, 7)
            else:
                lg = QtGui.QLinearGradient(50, 30, 35, 0)
                lg.setColorAt(0, QtGui.QColor(200, 200, 200, 255))
                lg.setColorAt(0.25, QtGui.QColor(230, 230, 230, 255))
                lg.setColorAt(0.82, QtGui.QColor(230, 230, 230, 255))
                lg.setColorAt(1, QtGui.QColor(200, 200, 200, 255))
                qp.setBrush(lg)
                qp.drawRoundedRect(3, 3, s.width() - 6, s.height() - 6, 7, 7)
            qp.end()
    
    class Circle(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Circle, self).__init__(parent)
            self.__enabled = True
            self.setFixedSize(20, 20)
    
        def paintEvent(self, event):
            s = self.size()
            qp = QtGui.QPainter()
            qp.begin(self)
            qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
            qp.setPen(QtCore.Qt.NoPen)
            qp.setBrush(QtGui.QColor(120, 120, 120))
            qp.drawEllipse(0, 0, 20, 20)
            rg = QtGui.QRadialGradient(int(self.width() / 2), int(self.height() / 2), 12)
            rg.setColorAt(0, QtGui.QColor(255, 255, 255))
            rg.setColorAt(0.6, QtGui.QColor(255, 255, 255))
            rg.setColorAt(1, QtGui.QColor(205, 205, 205))
            qp.setBrush(QtGui.QBrush(rg))
            qp.drawEllipse(1,1, 18, 18)
    
            qp.setBrush(QtGui.QColor(210, 210, 210))
            qp.drawEllipse(2, 2, 16, 16)
    
            if self.__enabled:
                lg = QtGui.QLinearGradient(3, 18,20, 4)
                lg.setColorAt(0, QtGui.QColor(255, 255, 255, 255))
                lg.setColorAt(0.55, QtGui.QColor(230, 230, 230, 255))
                lg.setColorAt(0.72, QtGui.QColor(255, 255, 255, 255))
                lg.setColorAt(1, QtGui.QColor(255, 255, 255, 255))
                qp.setBrush(lg)
                qp.drawEllipse(3,3, 14, 14)
            else:
                lg = QtGui.QLinearGradient(3, 18, 20, 4)
                lg.setColorAt(0, QtGui.QColor(230, 230, 230))
                lg.setColorAt(0.55, QtGui.QColor(210, 210, 210))
                lg.setColorAt(0.72, QtGui.QColor(230, 230, 230))
                lg.setColorAt(1, QtGui.QColor(230, 230, 230))
                qp.setBrush(lg)
                qp.drawEllipse(3, 3, 14, 14)
            qp.end()
    
    class Background(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Background, self).__init__(parent)
            self.__enabled = True
            self.setFixedHeight(20)
    
        def paintEvent(self, event):
            s = self.size()
            qp = QtGui.QPainter()
            qp.begin(self)
            qp.setRenderHint(QtGui.QPainter.Antialiasing, True)
            pen = QtGui.QPen(QtCore.Qt.NoPen)
            qp.setPen(pen)
            qp.setBrush(QtGui.QColor(154,205,50))
            if self.__enabled:
                qp.setBrush(QtGui.QColor(154, 190, 50))
                qp.drawRoundedRect(0, 0, s.width(), s.height(), 10, 10)
    
                lg = QtGui.QLinearGradient(0, 25, 70, 0)
                lg.setColorAt(0, QtGui.QColor(154, 184, 50))
                lg.setColorAt(0.35, QtGui.QColor(154, 210, 50))
                lg.setColorAt(0.85, QtGui.QColor(154, 184, 50))
                qp.setBrush(lg)
                qp.drawRoundedRect(1, 1, s.width() - 2, s.height() - 2, 8, 8)
            else:
                qp.setBrush(QtGui.QColor(150, 150, 150))
                qp.drawRoundedRect(0, 0, s.width(), s.height(), 10, 10)
    
                lg = QtGui.QLinearGradient(5, 25, 60, 0)
                lg.setColorAt(0, QtGui.QColor(190, 190, 190))
                lg.setColorAt(0.35, QtGui.QColor(230, 230, 230))
                lg.setColorAt(0.85, QtGui.QColor(190, 190, 190))
                qp.setBrush(lg)
                qp.drawRoundedRect(1, 1, s.width() - 2, s.height() - 2, 8, 8)
            qp.end()
    

提交回复
热议问题