Создаем слайдер - скользящий переключатель - Slider Button в Qt5

Россия
Создаем слайдер - скользящий переключатель - Slider Button в Qt5

Сегодня мы рассмотрим создание скользящего переключателя (Slider button) – кнопки представляющей собой переключатель аналогичный использующимся в мобильных телефонах.

Создадим новый проект Qt Widgets Application, назовем его

SliderButton

Уменьшим размер формы, сделав ее компактней.

Для нашей кнопки добавим класс:

QSliderButton

Заголовок:

#ifndef QSLIDERBUTTON_H
#define QSLIDERBUTTON_H

#include <QWidget>


class QSliderButton : public QWidget {
    Q_OBJECT
public:    
    explicit QSliderButton(QWidget* parent);
    QSliderButton();

protected:
    virtual void paintEvent(QPaintEvent *event);
    virtual QSize sizeHint() const;


};

#endif // QSLIDERBUTTON_H

Реализация: 

#include "qsliderbutton.h"
#include <QPainter>

QSliderButton::QSliderButton(QWidget *parent)
{
    this->setParent(parent);
}

QSliderButton::QSliderButton()
{
}

void QSliderButton::paintEvent(QPaintEvent *)
{

}

QSize QSliderButton::sizeHint() const {
    return QSize(50, 20);
}

Добавим в заголовок главной формы: 

private:
    QSliderButton *sldBtn;

Добавим в MainWindow::MainWindow после

ui->setupUi(this);

следующий код

    QVBoxLayout *vlay = new QVBoxLayout();
    QHBoxLayout *hlay1 = new QHBoxLayout();

    sldBtn =  new QSliderButton;
    hlay1->addWidget(sldBtn);

    hlay1->addStretch(1);
    vlay->addItem(hlay1);

    vlay->addStretch(1);
    ui->centralwidget->setLayout(vlay);

Запустим сборку Ctrl+R

Сборка успешно завершиться и будет открыта пустая форма. Это корректное поведение, так как виджет пока что ничего не отрисовывает!

Создаем дизайн переключателя

Для начала определимся с внешним видом переключателя.

Для этого нам понадобится Inkcscape.

Выставим в свойствах документа единицу измерения – px

Выставим размеры документа 50х20 px

Так же нам потребуется приблизительный шаблон дизайна, я остановился на вот такой картинке -  https://i.pinimg.com/originals/3d/16/f5/3d16f5908ad93653b0292dbdcd039067.png

Мы будет использовать верхнюю правую кнопку:

2022-02-07_10-50-57.png

Сделаем её скриншот и вставим в Inkscape как подложку на отдельный слой, добавим направляющие по периметру рисунка и переместим изображение: 

2022-02-07_11-10-51.png

Теперь у нас есть изображение нужного размера.

Добавим прямоугольник, выделим его и снова нажмем на R. Потянув за указанный рычаг вниз сделаем из него овал:

2022-02-07_11-15-53.png

2022-02-07_11-16-17.png

Добавим окружность размером 18х18, поместим её на центральной направляющей и сдвинем влево.

Зальём окружность красным цветом, а овал белым:

2022-02-07_11-24-35.png

Добавим направляющую слева от окружности и нарисуем прямоугольник:

2022-02-07_11-26-10.png

Перетащим его вправо, добавим еще одну направляющую и удалим прямоугольник:

2022-02-07_11-27-12.png

2022-02-07_11-27-29.png

2022-02-07_11-28-16.png

Теперь мы свободно может перемещать окружность вдоль направляющей, и она всегда будет правильно отцентрирована и находится на равном расстоянии от правого или левого краёв!

Давайте добавим дополнительные направляющие:

2022-02-07_11-30-27.png

Мы подготовили макет, давайте теперь получим координаты точек, которые нам понадобятся, для отрисовки нашей кнопки:

Обратите внимание, в QT5 при отрисовке окружностей и прямоугольников используются координаты левого верхнего угла, ширина и высота.

Посмотрим на координаты:

2022-02-07_12-42-26.png

Теперь у нас есть все необходимые координаты для рисования!

Модифицируем:

void QSliderButton::paintEvent(QPaintEvent *)

Реализация 

void QSliderButton::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(QColor("#fff"), 0.1));

    QString bgColorTxt = "#ffffff";
    QColor bgColor = QColor(bgColorTxt);
    painter.setBrush(bgColor);

    painter.drawRoundedRect(QRectF(0, 0, 50, 20),10,10);

    QLinearGradient linearGradBtn(QPointF(0, 0),QPointF(16, 16));

    QString onColor = "#444";
    QColor mainColorOn = QColor(onColor);
    QColor subColorOn = QColor(onColor);
    subColorOn.setHsl(0,100,95,0);

    QLinearGradient linearGrad(QPointF(0, 0), QPointF(16, 16));
    linearGrad.setColorAt(0, subColorOn);
    linearGrad.setColorAt(1, mainColorOn);

    painter.setBrush(linearGrad);
    painter.drawEllipse( QRectF(2, 2, 16, 16) );
}
 

Запустим:

2022-02-07_12-49-52.png

Обратите внимание мне пришлось при отрисовке окружности немного подогнать координаты, так что вместо 1,1 у нас координаты левого верхнего угла 2,2

Давайте добавим реакцию на нажатие мыши. Добавим в заголовок класса нашего виджета: 

    virtual void mousePressEvent(QMouseEvent * event);

Реализация 

void QSliderButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton ) {
        qDebug() << "Left Mouse button pressed";
        repaint();
    }
}

 Запустим и пощелкаем по переключателю, в консоли мы получим: 

Left Mouse button pressed
Left Mouse button pressed
Left Mouse button pressed

Изменяем статус кнопки по щелчку

Реализуем перемещение бегунка (окружности) вправо/влево по щелчку мышью.

В заголовок добавим приватное поле для хранения статуса: 

public:    
    int getStatus() const;
    void setStatus(int value);

    static const int off = 0;
    static const int on  = 1;

private:
    int status = 0;

Реализация: 

int QSliderButton::getStatus() const
{
    return status;
}

void QSliderButton::setStatus(int value)
{
    status = value;
    repaint();
}

Обратите внимание, мы вызываем метод:

repaint();

После обновления статуса, чтобы обновить содержимое виджета!

Обновим метод paintEvent 

void QSliderButton::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
         painter.setRenderHint(QPainter::Antialiasing, true);
    
         painter.setPen(QPen(QColor("#fff"), 0.1));
    
         QString bgColorTxt = "#ffffff";
         QColor bgColor = QColor(bgColorTxt);
    
         painter.setBrush(bgColor);
    
         painter.drawRoundedRect(QRectF(0, 0, 50, 20),10,10);
    
         QLinearGradient linearGradBtn(QPointF(0, 0),QPointF(16, 16));
    
         
    
    
         QString onColor = "#444";
         QColor mainColorOn = QColor(onColor);
         QColor subColorOn = QColor(onColor);
         subColorOn.setHsl(0,100,95,0);
    
    
         if (this->status==QSliderButton::on) {
             QLinearGradient linearGrad(QPointF(32, 2), QPointF(46, 16));
             linearGrad.setColorAt(0, subColorOn);
             linearGrad.setColorAt(1, mainColorOn);
    
             painter.setBrush(linearGrad);
    
            painter.drawEllipse( QRectF(30, 2, 17, 16) );
         } else {
             QLinearGradient linearGrad(QPointF(2, 2), QPointF(16, 16));
             linearGrad.setColorAt(0, subColorOn);
             linearGrad.setColorAt(1, mainColorOn);
    
             painter.setBrush(linearGrad);
    
             painter.drawEllipse( QRectF(2, 2, 16, 16) );
         }
}

Обновим метод: 

void QSliderButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton ) {
        if (this->status==QSliderButton::on) {
            this->status = QSliderButton::off;
        } else {
            this->status = QSliderButton::on;
        }
        repaint();
    }
}

Запустим, при щелчке бегунок перемещаться вправо/влево:

2022-02-07_14-18-16.png

2022-02-07_14-18-02.png

Теперь давайте добавим возможность менять цвет бегунка и фона в зависимости от статуса.

Добавим приватные поля: 

private:
    QString color = "#444";
    QString offColor = "#444";
    QString bg = "#ffffff";
    QString offBg = "#ffffff";

По умолчанию у нас кнопка серая, а цвет фона белый.

С помощью рефакторинга добавим геттеры и сеттеры для созданных полей. 

public:
    QString getColor() const;
    void setColor(const QString &value);
    
    QString getOffColor() const;
    void setOffColor(const QString &value);
    
    QString getBg() const;
    void setBg(const QString &value);
    
    QString getOffBg() const;
    void setOffBg(const QString &value);

В конец методов-сеттеров добавим строку: 

repaint();

Таким образом мы заставим виджет автоматически обновить содержимое каждый раз, когда цвет меняется: 

QString QSliderButton::getOffBg() const
{
    return offBg;
}

void QSliderButton::setOffBg(const QString &value)
{
    offBg = value;
    repaint();
}

QString QSliderButton::getBg() const
{
    return bg;
}

void QSliderButton::setBg(const QString &value)
{
    bg = value;
    repaint();
}

QString QSliderButton::getOffColor() const
{
    return offColor;
}

void QSliderButton::setOffColor(const QString &value)
{
    offColor = value;
    repaint();
}

QString QSliderButton::getColor() const
{
    return color;
}

void QSliderButton::setColor(const QString &value)
{
    color = value;
    repaint();
}

Обновим метод paintEvent

void QSliderButton::paintEvent(QPaintEvent *)
{
     QPainter painter(this);

     painter.setRenderHint(QPainter::Antialiasing, true);

     painter.setPen(QPen(QColor("#fff"), 0.1));

     if (this->status==QSliderButton::on) {

         painter.setPen(QPen(QColor("#fff"), 0.1));

         QString bgColorTxt = this->bg;
         QColor bgColor = QColor(bgColorTxt);
         painter.setBrush(bgColor);

         painter.drawRoundedRect(QRectF(0, 0, 50, 20),10,10);


         QString onColor = this->color;
         QColor mainColorOn = QColor(onColor);
         QColor subColorOn = QColor(onColor);
         subColorOn.setHsl(0,100,95,0);

         QLinearGradient linearGrad(QPointF(32, 2), QPointF(46, 16));
         linearGrad.setColorAt(0, subColorOn);
         linearGrad.setColorAt(1, mainColorOn);

         painter.setBrush(linearGrad);

        painter.drawEllipse( QRectF(30, 2, 16, 16) );

     } else {

         QString bgColorTxt = this->offBg;
         QColor bgColor = QColor(bgColorTxt);
         painter.setBrush(bgColor);

         painter.drawRoundedRect(QRectF(0, 0, 50, 20),10,10);


         QString aoffColor = this->offColor;
         QColor mainColorOff = QColor(aoffColor);
         QColor subColorOff = QColor(aoffColor);
         subColorOff.setHsl(0,100,95,0);

         QLinearGradient linearGrad(QPointF(2, 2), QPointF(16, 16));
         linearGrad.setColorAt(0, subColorOff);
         linearGrad.setColorAt(1, mainColorOff);

         painter.setBrush(linearGrad);

         painter.drawEllipse( QRectF(2, 2, 16, 16) );
     }
}

Запустим, все работает как раньше.

Теперь добавим в MainWindow::MainWindow после: 

sldBtn =  new QSliderButton();

Строки: 

    sldBtn->setColor("#557d00");
    sldBtn->setOffColor("#F00");

Запустим:

2022-02-07_14-49-30.png

2022-02-07_14-49-38.png

Добавим еще строки: 

    sldBtn->setBg("#99f5aa");
    sldBtn->setOffBg("#ffafaf");

Запустим, на этот раз изменяется и фон:

2022-02-07_14-53-09.png

2022-02-07_14-53-17.png

Добавляем слоты и сигналы

Добавим в заголовок класса виджета сигналы: 

signals:
    void clicked(QMouseEvent *event);
    void toggled(int status);

Обновим метод: 

void QSliderButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton ) {
        qDebug() << "Left Mouse button pressed";

        if (this->status==QSliderButton::on) {
            this->status = QSliderButton::off;
            emit toggled(this->status);
        } else {
            this->status = QSliderButton::on;
            emit toggled(this->status);
        }
        repaint();
    }
    emit this->clicked(event);
}

Наш переключатель по умолчанию реагирует только на левую кнопку мыши, но с помощью сигнала clicked мы можем добавить реакцию на щелчок любыми кнопками! 

Добавим в заголовок MainWindow: 

private slots:
    void btnToggled(int status);
    void clicked(QMouseEvent *event);

Реализация: 

void MainWindow::btnToggled(int status)
{
        qDebug() << "Button 1 toggled: " + QString::number(status);
}

void MainWindow::clicked(QMouseEvent *event)
{
        qDebug() << "Button " + QString::number(event->button()) + " clicked!";
}

Добавим в MainWindow::MainWindow

    QObject::connect(sldBtn, SIGNAL(toggled(int)),this,SLOT(btnToggled(int)));
    QObject::connect(sldBtn, SIGNAL(clicked(QMouseEvent*)),this,SLOT(clicked(QMouseEvent*)));
 

После:

sldBtn =  new QSliderButton();

Запустим, при щелчке на кнопке в консоли мы видим: 

Left Mouse button pressed
"Button 1 toggled: 1"
"Mouse button 1 clicked!"
Left Mouse button pressed
"Button 1 toggled: 0"
"Mouse button 1 clicked!"
Left Mouse button pressed
"Button 1 toggled: 1"
"Mouse button 1 clicked!"

Теперь мы можем управлять состоянием других кнопок по щелчку на одной из них.

Добавим в класс слайдера метод:

void QSliderButton::toggle()
{
    if (this->status == 0) {
        this->status = 1;
    } else {
        this->status = 0;
    }
    emit toggled(this->status);
    repaint();
}

Добавим еще одну кнопку: 

private:
    QSliderButton *sldBtn2;

private slots:
    void btnToggled2(int status);
    void clicked2(QMouseEvent *event);

Реализация:

   *sldBtn2 =  new QSliderButton();

    sldBtn2->setColor("#557d00");
    sldBtn2->setOffColor("#F00");
    sldBtn2->setBg("#99f5aa");
    sldBtn2->setOffBg("#ffafaf");

    QObject::connect(sldBtn2, SIGNAL(toggled(int)),this,SLOT(btnToggled2(int)));
    QObject::connect(sldBtn2, SIGNAL(clicked(QMouseEvent*)),this,SLOT(clicked2(QMouseEvent*)));

    hlay1->addWidget(sldBtn2);

 

void MainWindow::btnToggled2(int status)
{
        qDebug() << "Button 2 toggled: " + QString::number(status);
        sldBtn->toggle();
}

void MainWindow::clicked2(QMouseEvent *event)
{
        qDebug() << "Button " + QString::number(event->button()) + " clicked!";
}
 

Теперь при нажатии на вторую кнопку, первая будет менять свое состояние!

В консоли мы увидим: 

Left Mouse button pressed
"Button 2 toggled: 1"
"Button 1 toggled: 1"
"Mouse button 1 clicked!"

Заключение

Сегодня мы создали виджет реализующий скользящий переключатель (Slider button).

Создали новый проект.

Для получения необходимых координат нарисовали в Inkscape шаблон ношей кнопки.

Добавили в проект класс с нашим виджетом и реализовали базовый функционал для отображения кнопки.

Добавили возможность изменять положение бегунка по нажатию.

Добавили возможность менять цвет и фон виджета в зависимости от его состояния.

Добавили поддержку сигналов и слотов.

Добавили метод переключения кнопки.

Протестировали создание нескольких кнопок и использование слота одной для переключения статуса второй! 

Прочитано 477 раз Последнее изменение Вторник, 08 февраля 2022 19:00
Топ-100