Toggle Switch in Qt

后端 未结 10 2183
臣服心动
臣服心动 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:55

    Here is a C++ implementation of @Stefan Scherfke’s answer which is Python 3 / PyQt5 implementation of @IMAN4K’s answer. In addition to the improvements added by Stefan Scherfke, I have also added a few improvements.

    Toggle button has 2 different design according to the parameters given to the constructor function.

    explicit ToggleButton(int trackRadius, int thumbRadius, QWidget* parent = nullptr);
    

    In the first design(trackRadius>thumbRadius), ball (thumb) radius is smaller than the slide (track) radius, thus ball moves inside the slide according to the on/off status and includes the check “✔” and uncheck “✕” marks.

    In the second design(trackRadius), ball (thumb) radius is larger than the slide (track) radius, a flat fat ball slides over the track and changes color depending on the on/off situation.

    I used the U+2714 Heavy Check and U+2715 unCheck marks as UTF-16 in QChar type in order not to create encoding problems in the source code.

    I drew Heavy Check “✔” and Uncheck “✕” marks with the DrawPath function due to the problems of anti aliasing provided by the drawText function.

    DrawPath:

    DrawText:

    But since the drawPath function is not capable of aligment, I calculated Pixel offsets manually based on this picture to provide aligment.


    ToggleButton.h

    #ifndef TOGGLEBUTTON_H
    #define TOGGLEBUTTON_H
    
    #include 
    
    
    class QPropertyAnimation;
    
    class ToggleButton :public QAbstractButton
    {
        Q_OBJECT
            Q_PROPERTY(int mOffset READ offset WRITE setOffset);
    public:
        explicit ToggleButton(int trackRadius, int thumbRadius, QWidget* parent = nullptr);
        ~ToggleButton();
    
        QSize sizeHint() const override;
    
    protected:
        void paintEvent(QPaintEvent *) override;
        void resizeEvent(QResizeEvent*) override;
        void mouseReleaseEvent(QMouseEvent  *) override;
        void enterEvent(QEvent *event) override;
        void setChecked(bool checked);
    
        int offset();
        void setOffset(int value);
    
    private:
        qreal mOffset;
        qreal mBaseOffset;
        qreal mMargin;
        qreal mTrackRadius;
        qreal mThumbRadius;
        qreal mOpacity;
        QPropertyAnimation* mAnimation;
    
        QHash mEndOffset;
        QHash mTrackColor;
        QHash mThumbColor;
        QHash mTextColor;
        QHash mThumbText;
    
    };
    
    #endif // TOGGLEBUTTON_H
    

    ToggleButton.cpp

    #include "toggleButton.h"
    #include 
    #include 
    #include  
    #include  
    #include  
    
    ///
    /// Toggle button has 2 different design. In the first design, if the ball (thumb) radius is 
    /// larger than the slide (track) radius, a flat ball slides and colors from the slide according to 
    /// the on / off situation. In the second design, if the ball radius is smaller than the slide radius, 
    /// the ball moves according to the on / off status inside the slide and includes the check and uncheck marks.
    ///
    ToggleButton::ToggleButton(int trackRadius, int thumbRadius, QWidget* parent)
        : QAbstractButton(parent)
    {
        setCheckable(true);
        setSizePolicy(QSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed));
        mTrackRadius = trackRadius;
        mThumbRadius = thumbRadius;
        mAnimation = new QPropertyAnimation(this);
        mAnimation->setTargetObject(this);
    
        mMargin = 0 > (mThumbRadius - mTrackRadius) ? 0 : (mThumbRadius - mTrackRadius);
        mBaseOffset = mThumbRadius > mTrackRadius ? mThumbRadius : mTrackRadius;
        mEndOffset.insert(true, 4 * mTrackRadius + 2 * mMargin - mBaseOffset); // width - offset
        mEndOffset.insert(false, mBaseOffset);
        mOffset = mBaseOffset;
        QPalette palette = this->palette();
    
        if (mThumbRadius > mTrackRadius)
        {
            mTrackColor.insert(true, palette.highlight());
            mTrackColor.insert(false, palette.dark());
            mThumbColor.insert(true, palette.highlight());
            mThumbColor.insert(false, palette.light());
            mTextColor.insert(true, palette.highlightedText().color());
            mTextColor.insert(false, palette.dark().color());
            mThumbText.insert(true, "");
            mThumbText.insert(false, "");
            mOpacity = 0.5;
        }
        else
        {
            mTrackColor.insert(true, palette.highlight());
            mTrackColor.insert(false, palette.dark());
            mThumbColor.insert(true, palette.highlightedText());
            mThumbColor.insert(false, palette.light());
            mTextColor.insert(true, palette.highlight().color());
            mTextColor.insert(false, palette.dark().color());
            mThumbText.insert(true, QChar(0x2714)); // check character
            mThumbText.insert(false, QChar(0x2715)); // uncheck character
            mOpacity = 1.0;
        }
    }
    
    
    ToggleButton::~ToggleButton()
    {
        delete mAnimation;
    }
    
    QSize ToggleButton::sizeHint() const
    {
        int w = 4 * mTrackRadius + 2 * mMargin;
        int h = 2 * mTrackRadius + 2 * mMargin;
    
        return QSize(w, h);
    }
    
    void ToggleButton::paintEvent(QPaintEvent *)
    {
        QPainter p(this);
        QPainter::RenderHints m_paintFlags = QPainter::RenderHints(QPainter::Antialiasing |
            QPainter::TextAntialiasing);
        p.setRenderHints(m_paintFlags, true);
        p.setPen(Qt::NoPen);
        bool check = isChecked();
        qreal trackOpacity = mOpacity;
        qreal textOpacity = 1.0;
        qreal thumbOpacity = 1.0;
        QBrush trackBrush;
        QBrush thumbBrush;
        QColor textColor;
    
        if (this->isEnabled())
        {
    
            trackBrush = mTrackColor[check];
            thumbBrush = mThumbColor[check];
            textColor = mTextColor[check];
        }
        else
        {
            trackOpacity *= 0.8;
            trackBrush = this->palette().shadow();
            thumbBrush = this->palette().mid();
            textColor = this->palette().shadow().color();
        }
    
        p.setBrush(trackBrush);
        p.setOpacity(trackOpacity);
        p.drawRoundedRect(mMargin, mMargin, width() - 2 * mMargin, height() - 2 * mMargin, mTrackRadius, mTrackRadius);
    
        p.setBrush(thumbBrush);
        p.setOpacity(thumbOpacity);
        p.drawEllipse(mOffset - mThumbRadius, mBaseOffset - mThumbRadius, 2 * mThumbRadius, 2 * mThumbRadius);
    
        p.setPen(textColor);
        p.setOpacity(textOpacity);
        QFont font = p.font();
        font.setPixelSize(1.5*mThumbRadius);
        p.setFont(font);
    
    
        // Since the antialiasasing provided by the drawText function is incompetent,
        // DrawPath function preferred. But since the drawPath function is not capable of aligment,
        // Pixel offsets calculated to provide aligment.
        QPainterPath textPath;
        qreal pixelOffset = (qreal)mThumbRadius * (1 - 1 / 1.414);
        textPath.addText(mOffset - mThumbRadius + pixelOffset, mBaseOffset + mThumbRadius - pixelOffset, font, mThumbText.value(check));
        p.drawPath(textPath);
    
    
        /*p.drawText(QRectF(mOffset - mThumbRadius,
            mBaseOffset - mThumbRadius,
            2 * mThumbRadius,
            2 * mThumbRadius),
            Qt::AlignCenter,
            mThumbText.value(check));*/
    }
    
    void ToggleButton::resizeEvent(QResizeEvent* e)
    {
        QAbstractButton::resizeEvent(e);
        mOffset = mEndOffset.value(isChecked());
    }
    
    void ToggleButton::mouseReleaseEvent(QMouseEvent  *e)
    {
        QAbstractButton::mouseReleaseEvent(e);
        if (e->button() == Qt::LeftButton)
        {
            mAnimation->setDuration(120);
            mAnimation->setPropertyName("mOffset");
            mAnimation->setStartValue(mOffset);
            mAnimation->setEndValue(mEndOffset[isChecked()]);
            mAnimation->start();
        }
    }
    
    void ToggleButton::enterEvent(QEvent * event)
    {
        setCursor(Qt::PointingHandCursor);
        QAbstractButton::enterEvent(event);
    }
    
    void ToggleButton::setChecked(bool checked)
    {
        QAbstractButton::setChecked(checked);
        mOffset = mEndOffset.value(checked);
    }
    
    int ToggleButton::offset()
    {
        return mOffset;
    }
    
    void ToggleButton::setOffset(int value)
    {
        mOffset = value;
        update();
    }
    

    main.cpp

    #include "toggleButton.h"
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QWidget *widget = new QWidget;
        widget->setWindowFlags(Qt::FramelessWindowHint);
        QHBoxLayout layout;
        widget->setLayout(&layout);
        ToggleButton *toggleButton1 = new ToggleButton(10, 8);
        ToggleButton *toggleButton2 = new ToggleButton(10, 12);
        layout.addWidget(toggleButton1);
        layout.addWidget(toggleButton2);
        widget->show();
        return a.exec();
    }
    

提交回复
热议问题