web-dev-qa-db-ja.com

Qtの切り替えスイッチ

Qtで Android Switches に相当する要素を使用しようとしています。 QMLでToggleSwitchを見つけましたが、実際のC++ Qtライブラリには何もありません。私は何か不足しているのですか、それともこのウィジェットを自分で再実装する必要がありますか?

24
ElCraneo

@piccyの提案は、以前にそのようなトグルスイッチに対して行ったことです。いくつかの微調整で。

IOSのオン/オフスイッチと同様の動作をエミュレートする必要がありました。つまり、外部アニメーションなしでスライダーを0〜1の制限に設定すると、緩やかな動きが必要になります。

したがって、スライダーの値の範囲をスライダーの最大幅と同じになるように設定しました。

次に、スライダーリリース信号を接続し、値が最大値の半分未満であるかどうかを確認します。そうである場合は、スライダー値を0に設定し、それ以外の場合はスライダー値を最大にします。

これにより、マウスを離したときに優れたドラッグエフェクトと極端なクリップが得られます。

スライダーをドラッグせずに反対側をクリックしたときにトグルするだけの場合は、スライダーの値が変更された信号を接続し、新しい値をチェックしてどちらかの端に近づけ、スライダーの値として設定しますスライダーがそうでない場合)ダウン状態。スライダーが下にある場合は、スライダーの値を変更しないでください。前のドラッグモーションが中断する可能性があります。

14
Viv

次に例を示します。

switch.h

#pragma once
#include <QtWidgets>

class Switch : public QAbstractButton {
    Q_OBJECT
    Q_PROPERTY(int offset READ offset WRITE setOffset)
    Q_PROPERTY(QBrush brush READ brush WRITE setBrush)

public:
    Switch(QWidget* parent = nullptr);
    Switch(const QBrush& brush, QWidget* parent = nullptr);

    QSize sizeHint() const override;

    QBrush brush() const {
        return _brush;
    }
    void setBrush(const QBrush &brsh) {
        _brush = brsh;
    }

    int offset() const {
        return _x;
    }
    void setOffset(int o) {
        _x = o;
        update();
    }

protected:
    void paintEvent(QPaintEvent*) override;
    void mouseReleaseEvent(QMouseEvent*) override;
    void enterEvent(QEvent*) override;

private:
    bool _switch;
    qreal _opacity;
    int _x, _y, _height, _margin;
    QBrush _thumb, _track, _brush;
    QPropertyAnimation *_anim = nullptr;
};

switch.cpp

Switch::Switch(QWidget *parent) : QAbstractButton(parent),
_height(16),
_opacity(0.000),
_switch(false),
_margin(3),
_thumb("#d5d5d5"),
_anim(new QPropertyAnimation(this, "offset", this))
{
    setOffset(_height / 2);
    _y = _height / 2;
    setBrush(QColor("#009688"));
}

Switch::Switch(const QBrush &brush, QWidget *parent) : QAbstractButton(parent),
_height(16),
_switch(false),
_opacity(0.000),
_margin(3),
_thumb("#d5d5d5"),
_anim(new QPropertyAnimation(this, "offset", this))
{
    setOffset(_height / 2);
    _y = _height / 2;
    setBrush(brush);
}

void Switch::paintEvent(QPaintEvent *e) {
    QPainter p(this);
    p.setPen(Qt::NoPen);
    if (isEnabled()) {
        p.setBrush(_switch ? brush() : Qt::black);
        p.setOpacity(_switch ? 0.5 : 0.38);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.drawRoundedRect(QRect(_margin, _margin, width() - 2 * _margin, height() - 2 * _margin), 8.0, 8.0);
        p.setBrush(_thumb);
        p.setOpacity(1.0);
        p.drawEllipse(QRectF(offset() - (_height / 2), _y - (_height / 2), height(), height()));
    } else {
        p.setBrush(Qt::black);
        p.setOpacity(0.12);
        p.drawRoundedRect(QRect(_margin, _margin, width() - 2 * _margin, height() - 2 * _margin), 8.0, 8.0);
        p.setOpacity(1.0);
        p.setBrush(QColor("#BDBDBD"));
        p.drawEllipse(QRectF(offset() - (_height / 2), _y - (_height / 2), height(), height()));
    }
}

void Switch::mouseReleaseEvent(QMouseEvent *e) {
    if (e->button() & Qt::LeftButton) {
        _switch = _switch ? false : true;
        _thumb = _switch ? _brush : QBrush("#d5d5d5");
        if (_switch) {
            _anim->setStartValue(_height / 2);
            _anim->setEndValue(width() - _height);
            _anim->setDuration(120);
            _anim->start();
        } else {
            _anim->setStartValue(offset());
            _anim->setEndValue(_height / 2);
            _anim->setDuration(120);
            _anim->start();
        }
    }
    QAbstractButton::mouseReleaseEvent(e);
}

void Switch::enterEvent(QEvent *e) {
    setCursor(Qt::PointingHandCursor);
    QAbstractButton::enterEvent(e);
}

QSize Switch::sizeHint() const {
    return QSize(2 * (_height + _margin), _height + 2 * _margin);
}

main.cpp

#include "switch.h"

    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
        QWidget *widget = new QWidget;
        widget->setWindowFlags(Qt::FramelessWindowHint);
        QHBoxLayout layout;
        widget->setLayout(&layout);
        Switch *_switch = new Switch;
        Switch *_switch2 = new Switch;
        _switch2->setDisabled(true);
        layout.addWidget(_switch);
        layout.addWidget(_switch2);
        widget->show();
        return a.exec();
    }

enter image description here

2018年8月20日更新

新しいマテリアルスイッチウィジェット!

style.h

/*
 * This is nearly complete Material design Switch widget implementation in qtwidgets module.
 * More info: https://material.io/design/components/selection-controls.html#switches
 * Copyright (C) 2018 Iman Ahmadvand
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
*/

#ifndef STYLE_H
#define STYLE_H

#include <QtCore/qeasingcurve.h>

#define cyan500 QColor("#00bcd4")
#define gray50 QColor("#fafafa")
#define black QColor("#000000")
#define gray400 QColor("#bdbdbd")

Q_DECL_IMPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); // src/widgets/effects/qpixmapfilter.cpp

namespace Style {

    using Type = QEasingCurve::Type;

    struct Animation {
        Animation() = default;
        Animation(Type _easing, int _duration) :easing{ _easing }, duration{ _duration } {

        }

        Type easing;
        int duration;
    };

    struct Switch {
        Switch() :
            height{ 36 },
            font{ QFont("Roboto medium", 13) },
            indicatorMargin{ QMargins(8, 8, 8, 8) },
            thumbOnBrush{ cyan500 },
            thumbOnOpacity{ 1 },
            trackOnBrush{ cyan500 },
            trackOnOpacity{ 0.5 },
            thumbOffBrush{ gray50 },
            thumbOffOpacity{ 1 },
            trackOffBrush{ black },
            trackOffOpacity{ 0.38 },
            thumbDisabled{ gray400 },
            thumbDisabledOpacity{ 1 },
            trackDisabled{ black },
            trackDisabledOpacity{ 0.12 },
            textColor{ black },
            disabledTextOpacity{ 0.26 },
            thumbBrushAnimation{ Animation(Type::Linear, 150) },
            trackBrushAnimation{ Animation(Type::Linear, 150) },
            thumbPosAniamtion{ Animation(Type::InOutQuad, 150) } {

        }

        int height;
        QFont font;
        QMargins indicatorMargin;
        QColor thumbOnBrush;
        double thumbOnOpacity;
        QColor trackOnBrush;
        double trackOnOpacity;
        QColor thumbOffBrush;
        double thumbOffOpacity;
        QColor trackOffBrush;
        double trackOffOpacity;
        QColor thumbDisabled;
        double thumbDisabledOpacity;
        QColor trackDisabled;
        double trackDisabledOpacity;
        QColor textColor;
        double disabledTextOpacity;
        Animation thumbBrushAnimation;
        Animation trackBrushAnimation;
        Animation thumbPosAniamtion;
    };

    inline QPixmap drawShadowEllipse(qreal radius, qreal elevation, const QColor& color) {
        auto px = QPixmap(radius * 2, radius * 2);
        px.fill(Qt::transparent);

        { // draw ellipes
            QPainter p(&px);
            p.setBrush(color);
            p.setPen(Qt::NoPen);
            p.setRenderHint(QPainter::Antialiasing, true);
            p.drawEllipse(QRectF(0, 0, px.size().width(), px.size().height()).center(), radius - elevation, radius - elevation);
        }

        QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied);
        tmp.setDevicePixelRatio(px.devicePixelRatioF());
        tmp.fill(0);
        QPainter tmpPainter(&tmp);
        tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
        tmpPainter.drawPixmap(QPointF(), px);
        tmpPainter.end();

        // blur the alpha channel
        QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
        blurred.setDevicePixelRatio(px.devicePixelRatioF());
        blurred.fill(0);
        {
            QPainter blurPainter(&blurred);
            qt_blurImage(&blurPainter, tmp, elevation * 4., true, false);
        }

        tmp = blurred;

        return QPixmap::fromImage(tmp);
    }

} // namespace Style

#endif // STYLE_H

switch.h

/*
 * This is nearly complete Material design Switch widget implementation in qtwidgets module.
 * More info: https://material.io/design/components/selection-controls.html#switches
 * Copyright (C) 2018 Iman Ahmadvand
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
*/

#ifndef SWITCH_H
#define SWITCH_H

#include <QtWidgets>
#include "style.h"

class Animator : public QVariantAnimation {
    Q_OBJECT
    Q_PROPERTY(QObject* targetObject READ targetObject WRITE setTargetObject)

public:
    explicit Animator(QObject* target, QObject* parent = nullptr);
    ~Animator();

    QObject* targetObject() const;
    void setTargetObject(QObject* target);

    bool isRunning() const {
        return state() == Running;
    }

    void setup(int duration, QEasingCurve easing = QEasingCurve::Linear);
    void interpolate(const QVariant& start, const QVariant& end);

protected:
    void updateCurrentValue(const QVariant& value) override;
    void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) override;

private:
    QPointer<QObject> target;
};

class SelectionControl :public QAbstractButton {
    Q_OBJECT

public:
    explicit SelectionControl(QWidget* parent = nullptr);
    ~SelectionControl();

    Qt::CheckState checkState() const;

Q_SIGNALS:
    void stateChanged(int);

protected:
    void enterEvent(QEvent*) override;
    void checkStateSet() override;
    void nextCheckState() override;
    virtual void toggle(Qt::CheckState state) = 0;
};

class Switch :public SelectionControl {
    Q_OBJECT

public:
    explicit Switch(QWidget* parent = nullptr);
    explicit Switch(const QString& text, QWidget* parent = nullptr);
    Switch(const QString& text, const QBrush&, QWidget* parent = nullptr);
    ~Switch();

    QSize sizeHint() const override;

protected:
    void paintEvent(QPaintEvent *) override;
    void resizeEvent(QResizeEvent*) override;
    void toggle(Qt::CheckState) override;

    void init();
    QRect indicatorRect();
    QRect textRect();

    static inline QColor colorFromOpacity(const QColor& c, qreal opacity) {
        return QColor(c.red(), c.green(), c.blue(), opacity * 255.0);
    }
    static inline bool ltr(QWidget* w) {
        if (w)
            return w->layoutDirection() == Qt::LeftToRight;
        return false;
    }

private:
    Style::Switch style;
    QPixmap shadowPixmap;
    QPointer<Animator> thumbBrushAnimation;
    QPointer<Animator> trackBrushAnimation;
    QPointer<Animator> thumbPosAniamtion;
};

#endif // SWITCH_H

switch.cpp

/*
 * This is nearly complete Material design Switch widget implementation in qtwidgets module.
 * More info: https://material.io/design/components/selection-controls.html#switches
 * Copyright (C) 2018 Iman Ahmadvand
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
*/

#include "switch.h"

#define CORNER_RADIUS 8.0
#define THUMB_RADIUS 14.5
#define SHADOW_ELEVATION 2.0

Animator::Animator(QObject* target, QObject* parent) : QVariantAnimation(parent) {
    setTargetObject(target);
}

Animator::~Animator() {
    stop();
}

QObject* Animator::targetObject() const {
    return target.data();
}

void Animator::setTargetObject(QObject *_target) {
    if (target.data() == _target)
        return;

    if (isRunning()) {
        qWarning("Animation::setTargetObject: you can't change the target of a running animation");
        return;
    }

    target = _target;
}

void Animator::updateCurrentValue(const QVariant& value) {
    Q_UNUSED(value);

    if (!target.isNull()) {
        auto update = QEvent(QEvent::StyleAnimationUpdate);
        update.setAccepted(false);
        QCoreApplication::sendEvent(target.data(), &update);
        if (!update.isAccepted())
            stop();
    }
}

void Animator::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) {
    if (target.isNull() && oldState == Stopped) {
        qWarning("Animation::updateState: Changing state of an animation without target");
        return;
    }

    QVariantAnimation::updateState(newState, oldState);

    if (!endValue().isValid() && direction() == Forward) {
        qWarning("Animation::updateState (%s): starting an animation without end value", targetObject()->metaObject()->className());
    }
}

void Animator::setup(int duration, QEasingCurve easing) {
    setDuration(duration);
    setEasingCurve(easing);
}

void Animator::interpolate(const QVariant& _start, const QVariant& end) {
    setStartValue(_start);
    setEndValue(end);
    start();
}



SelectionControl::SelectionControl(QWidget * parent) :QAbstractButton(parent) {
    setObjectName("SelectionControl");
    setCheckable(true);
}

SelectionControl::~SelectionControl() {

}

void SelectionControl::enterEvent(QEvent* e) {
    setCursor(Qt::PointingHandCursor);
    QAbstractButton::enterEvent(e);
}

Qt::CheckState SelectionControl::checkState() const {
    return isChecked() ? Qt::Checked : Qt::Unchecked;
}

void SelectionControl::checkStateSet() {
    const auto state = checkState();
    emit stateChanged(state);
    toggle(state);
}

void SelectionControl::nextCheckState() {
    QAbstractButton::nextCheckState();
    SelectionControl::checkStateSet();
}



void Switch::init() {
    setFont(style.font);
    setObjectName("Switch");
    /* setup animations */
    thumbBrushAnimation = new Animator{ this, this };
    trackBrushAnimation = new Animator{ this, this };
    thumbPosAniamtion = new Animator{ this, this };
    thumbPosAniamtion->setup(style.thumbPosAniamtion.duration, style.thumbPosAniamtion.easing);
    trackBrushAnimation->setup(style.trackBrushAnimation.duration, style.trackBrushAnimation.easing);
    thumbBrushAnimation->setup(style.thumbBrushAnimation.duration, style.thumbBrushAnimation.easing);   
    /* set init values */
    trackBrushAnimation->setStartValue(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity));
    trackBrushAnimation->setEndValue(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity));
    thumbBrushAnimation->setStartValue(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity));
    thumbBrushAnimation->setEndValue(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity));
    /* set standard palettes */
    auto p = palette();
    p.setColor(QPalette::Active, QPalette::ButtonText, style.textColor);
    p.setColor(QPalette::Disabled, QPalette::ButtonText, style.textColor);
    setPalette(p);
    setSizePolicy(QSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Fixed));
}

QRect Switch::indicatorRect() {
    const auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right();
    return ltr(this) ? QRect(0, 0, w, style.height) : QRect(width() - w, 0, w, style.height);
}

QRect Switch::textRect() {
    const auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right();
    return ltr(this) ? rect().marginsRemoved(QMargins(w, 0, 0, 0)) : rect().marginsRemoved(QMargins(0, 0, w, 0));
}

Switch::Switch(QWidget* parent) : SelectionControl(parent) {
    init();
}

Switch::Switch(const QString& text, QWidget* parent) : Switch(parent) {
    setText(text);
}

Switch::Switch(const QString& text, const QBrush& brush, QWidget* parent) : Switch(text, parent) {
    style.thumbOnBrush = brush.color();
    style.trackOnBrush = brush.color();
}

Switch::~Switch() {

}

QSize Switch::sizeHint() const {
    auto h = style.height;
    auto w = style.indicatorMargin.left() + style.height + style.indicatorMargin.right() + fontMetrics().width(text());

    return QSize(w, h);
}

void Switch::paintEvent(QPaintEvent *) {
    /* for desktop usage we do not need Radial reaction */

    QPainter p(this);

    const auto _indicatorRect = indicatorRect();
    const auto _textRect = textRect();
    auto trackMargin = style.indicatorMargin;
    trackMargin.setTop(trackMargin.top() + 2);
    trackMargin.setBottom(trackMargin.bottom() + 2);
    QRectF trackRect = _indicatorRect.marginsRemoved(trackMargin);

    if (isEnabled()) {
        p.setOpacity(1.0);
        p.setPen(Qt::NoPen);
        /* draw track */
        p.setBrush(trackBrushAnimation->currentValue().value<QColor>());
        p.setRenderHint(QPainter::Antialiasing, true);
        p.drawRoundedRect(trackRect, CORNER_RADIUS, CORNER_RADIUS);
        p.setRenderHint(QPainter::Antialiasing, false);
        /* draw thumb */
        trackRect.setX(trackRect.x() - trackMargin.left() - trackMargin.right() - 2 + thumbPosAniamtion->currentValue().toInt());
        auto thumbRect = trackRect;

        if (!shadowPixmap.isNull())
            p.drawPixmap(thumbRect.center() - QPointF(THUMB_RADIUS, THUMB_RADIUS - 1.0), shadowPixmap);
        p.setBrush(thumbBrushAnimation->currentValue().value<QColor>());
        p.setRenderHint(QPainter::Antialiasing, true);
        p.drawEllipse(thumbRect.center(), THUMB_RADIUS - SHADOW_ELEVATION - 1.0, THUMB_RADIUS - SHADOW_ELEVATION - 1.0);
        p.setRenderHint(QPainter::Antialiasing, false);

        /* draw text */
        if (text().isEmpty())
            return;
        p.setOpacity(1.0);
        p.setPen(palette().color(QPalette::Active, QPalette::ButtonText));
        p.setFont(font());
        p.drawText(_textRect, Qt::AlignLeft | Qt::AlignVCenter, text());
    } else {
        p.setOpacity(style.trackDisabledOpacity);
        p.setPen(Qt::NoPen);
        // draw track
        p.setBrush(style.trackDisabled);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.drawRoundedRect(trackRect, CORNER_RADIUS, CORNER_RADIUS);
        p.setRenderHint(QPainter::Antialiasing, false);
        // draw thumb
        p.setOpacity(1.0);
        if (!isChecked())
            trackRect.setX(trackRect.x() - trackMargin.left() - trackMargin.right() - 2);
        else
            trackRect.setX(trackRect.x() + trackMargin.left() + trackMargin.right() + 2);
        auto thumbRect = trackRect;
        if (!shadowPixmap.isNull())
            p.drawPixmap(thumbRect.center() - QPointF(THUMB_RADIUS, THUMB_RADIUS - 1.0), shadowPixmap);
        p.setOpacity(1.0);
        p.setBrush(style.thumbDisabled);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.drawEllipse(thumbRect.center(), THUMB_RADIUS - SHADOW_ELEVATION - 1.0, THUMB_RADIUS - SHADOW_ELEVATION - 1.0);

        if (text().isEmpty())
            return;
        p.setOpacity(style.disabledTextOpacity);
        p.setPen(palette().color(QPalette::Disabled, QPalette::ButtonText));
        p.setFont(font());
        p.drawText(_textRect, Qt::AlignLeft | Qt::AlignVCenter, text());
    }
}

void Switch::resizeEvent(QResizeEvent* e) {
    shadowPixmap = Style::drawShadowEllipse(THUMB_RADIUS, SHADOW_ELEVATION, QColor(0, 0, 0, 70));
    SelectionControl::resizeEvent(e);
}

void Switch::toggle(Qt::CheckState state) {
    if (state == Qt::Checked) {
        thumbPosAniamtion->interpolate(0, (style.indicatorMargin.left() + style.indicatorMargin.right() + 2) * 2);
        thumbBrushAnimation->interpolate(colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity), colorFromOpacity(style.thumbOnBrush, style.thumbOnOpacity));
        trackBrushAnimation->interpolate(colorFromOpacity(style.trackOffBrush, style.trackOffOpacity), colorFromOpacity(style.trackOnBrush, style.trackOnOpacity));
    } else { // Qt::Unchecked
        thumbPosAniamtion->interpolate(thumbPosAniamtion->currentValue().toInt(), 0);
        thumbBrushAnimation->interpolate(colorFromOpacity(style.thumbOnBrush, style.thumbOnOpacity), colorFromOpacity(style.thumbOffBrush, style.thumbOffOpacity));
        trackBrushAnimation->interpolate(colorFromOpacity(style.trackOnBrush, style.trackOnOpacity), colorFromOpacity(style.trackOffBrush, style.trackOffOpacity));
    }
}

main.cpp

#include "switch.h"

int main(int argc, char *argv[]) {
    QApplication application(argc, argv);
    QWidget container;
    QVBoxLayout mainLayout;
    container.setLayout(&mainLayout);

    Switch* switch1 = new Switch("SWITCH");
    mainLayout.addWidget(switch1);
    Switch* switch2 = new Switch("SWITCH");
    mainLayout.addWidget(switch2);
    switch2->setDisabled(true);
    Switch* switch3 = new Switch("SWITCH");
    mainLayout.addWidget(switch3);
    switch3->setLayoutDirection(Qt::RightToLeft);
    Switch* switch4 = new Switch("SWITCH");
    mainLayout.addWidget(switch4);
    switch4->setLayoutDirection(Qt::RightToLeft);
    switch4->setChecked(true);
    switch4->setDisabled(true);

    QButtonGroup bg;
    Switch* item1 = new Switch("ITEM1");
    Switch* item2 = new Switch("ITEM2");
    bg.addButton(item1);
    bg.addButton(item2);
    mainLayout.addWidget(item1);
    mainLayout.addWidget(item2);
    mainLayout.setMargin(100);

    container.show();
    return application.exec();
}

結果:

enter image description here

28
IMAN4K

数か月前に、一般的なデスクトップスタイル(C++とPython利用可能なバージョン; Pythonバージョンがプロトタイプでした、つまり、動作が異なる可能性があります。paintEventを使用して美学が完全にカスタマイズされていることに注意してください。システムに応じて異なるビジュアルを期待しないでください。

A switch button implementation in Qt (C++;Python)


C++の実装

注:インクルード(私の例にはありません)を忘れないでください。

用途:

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<int>(width() / 2), static_cast<int>(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実装(プロトタイプ; PyQt5)

用途:

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()
12
armatita

これはPython 3/@ IMAN4KのPyQt5実装 answer です。

元の実装に対する改善:

  • 親指のサイズは、トラックのサイズより大きく/小さくすることができます
  • 現在のアプリのパレットを使用して色を付ける
  • クリック時にトグル/クリックされた信号を送信する
  • その他のさまざまな修正

enter image description here

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


class Switch(QAbstractButton):
    def __init__(self, parent=None, track_radius=10, thumb_radius=8):
        super().__init__(parent=parent)
        self.setCheckable(True)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        self._track_radius = track_radius
        self._thumb_radius = thumb_radius

        self._margin = max(0, self._thumb_radius - self._track_radius)
        self._base_offset = max(self._thumb_radius, self._track_radius)
        self._end_offset = {
            True: lambda: self.width() - self._base_offset,
            False: lambda: self._base_offset,
        }
        self._offset = self._base_offset

        palette = self.palette()
        if self._thumb_radius > self._track_radius:
            self._track_color = {
                True: palette.highlight(),
                False: palette.dark(),
            }
            self._thumb_color = {
                True: palette.highlight(),
                False: palette.light(),
            }
            self._text_color = {
                True: palette.highlightedText().color(),
                False: palette.dark().color(),
            }
            self._thumb_text = {
                True: '',
                False: '',
            }
            self._track_opacity = 0.5
        else:
            self._thumb_color = {
                True: palette.highlightedText(),
                False: palette.light(),
            }
            self._track_color = {
                True: palette.highlight(),
                False: palette.dark(),
            }
            self._text_color = {
                True: palette.highlight().color(),
                False: palette.dark().color(),
            }
            self._thumb_text = {
                True: '✔',
                False: '✕',
            }
            self._track_opacity = 1

    @pyqtProperty(int)
    def offset(self):
        return self._offset

    @offset.setter
    def offset(self, value):
        self._offset = value
        self.update()

    def sizeHint(self):  # pylint: disable=invalid-name
        return QSize(
            4 * self._track_radius + 2 * self._margin,
            2 * self._track_radius + 2 * self._margin,
        )

    def setChecked(self, checked):
        super().setChecked(checked)
        self.offset = self._end_offset[checked]()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.offset = self._end_offset[self.isChecked()]()

    def paintEvent(self, event):  # pylint: disable=invalid-name, unused-argument
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing, True)
        p.setPen(Qt.NoPen)
        track_opacity = self._track_opacity
        thumb_opacity = 1.0
        text_opacity = 1.0
        if self.isEnabled():
            track_brush = self._track_color[self.isChecked()]
            thumb_brush = self._thumb_color[self.isChecked()]
            text_color = self._text_color[self.isChecked()]
        else:
            track_opacity *= 0.8
            track_brush = self.palette().shadow()
            thumb_brush = self.palette().mid()
            text_color = self.palette().shadow().color()

        p.setBrush(track_brush)
        p.setOpacity(track_opacity)
        p.drawRoundedRect(
            self._margin,
            self._margin,
            self.width() - 2 * self._margin,
            self.height() - 2 * self._margin,
            self._track_radius,
            self._track_radius,
        )
        p.setBrush(thumb_brush)
        p.setOpacity(thumb_opacity)
        p.drawEllipse(
            self.offset - self._thumb_radius,
            self._base_offset - self._thumb_radius,
            2 * self._thumb_radius,
            2 * self._thumb_radius,
        )
        p.setPen(text_color)
        p.setOpacity(text_opacity)
        font = p.font()
        font.setPixelSize(1.5 * self._thumb_radius)
        p.setFont(font)
        p.drawText(
            QRectF(
                self.offset - self._thumb_radius,
                self._base_offset - self._thumb_radius,
                2 * self._thumb_radius,
                2 * self._thumb_radius,
            ),
            Qt.AlignCenter,
            self._thumb_text[self.isChecked()],
        )

    def mouseReleaseEvent(self, event):  # pylint: disable=invalid-name
        super().mouseReleaseEvent(event)
        if event.button() == Qt.LeftButton:
            anim = QPropertyAnimation(self, b'offset', self)
            anim.setDuration(120)
            anim.setStartValue(self.offset)
            anim.setEndValue(self._end_offset[self.isChecked()]())
            anim.start()

    def enterEvent(self, event):  # pylint: disable=invalid-name
        self.setCursor(Qt.PointingHandCursor)
        super().enterEvent(event)


def main():
    app = QApplication([])

    # Thumb size < track size (Gitlab style)
    s1 = Switch()
    s1.toggled.connect(lambda c: print('toggled', c))
    s1.clicked.connect(lambda c: print('clicked', c))
    s1.pressed.connect(lambda: print('pressed'))
    s1.released.connect(lambda: print('released'))
    s2 = Switch()
    s2.setEnabled(False)

    # Thumb size > track size (Android style)
    s3 = Switch(thumb_radius=11, track_radius=8)
    s4 = Switch(thumb_radius=11, track_radius=8)
    s4.setEnabled(False)

    l = QHBoxLayout()
    l.addWidget(s1)
    l.addWidget(s2)
    l.addWidget(s3)
    l.addWidget(s4)
    w = QWidget()
    w.setLayout(l)
    w.show()

    app.exec()


if __name__ == '__main__':
    main()
7
Stefan Scherfke

0から1の範囲の水平方向のQSliderコントロールを使用してこれを行うこともできます。ダイアログの幅を超えないように、最大​​幅を50程度に設定することをお勧めします。 。次に、スタイルシートで微調整して外観を改善したり、サブクラス化してコントロールを自分で描画したりできます。見栄えを良くするためにコードを多く取りすぎないかもしれません。

4
piccy

まあ、あなたは QCheckBox を使わなければならないでしょう。これはトグルスイッチではありませんが、同じことを行います。本当に異なるビジュアルが必要な場合は、カスタムウィジェットを作成する必要があります

3
Davita

ダビタは正しい 彼の答えでは チェックボックスに関するところ。ただし、3番目の例(オン/オフスイッチ)に似たものを探している場合は、2つの QPushButton sを使用して、それらを checkable に設定します。それらを autoexclusive と同時に作成すれば、問題はありません。

少し視覚的なスタイリング スタイルシートを使用 で、スポットにならなくても近づくことができるはずです。

3
Bart

QRadioButton および QPushButton とともに checkable も参照してください。一部のスタイルシートまたはカスタム描画は、「オン/オフスイッチの切り替え」のように作成できます。

1
Zlatomir

私はこのスレッドが古いことを知っていますが、Vivから非常に良いヒントを与えられたにもかかわらず、この特定の問題にかなり苦労しました。

とにかく、私がここで思いついた解決策を共有したいと思いました。おそらくそれは途中で誰かを助けるでしょう。

_void Switch::on_sldSwitch_actionTriggered(int action) {
    if(action != 7) ui->sldSwitch->setValue((action%2) ? 100 : 0);
}

void Switch::on_sldSwitch_sliderReleased() {
    ui->sldSwitch->setValue((ui->sldSwitch->sliderPosition() >= 50) ? 100 : 0);
}
_

簡単な説明:actionTriggeredは、スライダーがキーボードでクリックまたは移動されるたびに呼び出されます。ドラッグすると、信号「7」が発生します。すぐにスナップしないように、アクション7はブロックされます。

右に移動すると、キーボードで3クリックしてクリックし、キーボードの「右」(または「下」)を押すと1を放出します。そのため、偶数ではないときに右にスナップしています。

左に移動すると、2または4を放出します。

sliderReleased()は、ドラッグ後にマウスボタンを離すと呼び出されますが、この時点では、スライダーはまだ古い値を持っています(これにより、かなりの動作が発生しました)。したがって、スナップする正しい位置を取得するために、sliderPositionではなくvalueを照会しました。

これが誰かの役に立つことを願っています。

0