
Создаем слайдер - скользящий переключатель - Slider Button. Виджеты в Qt. Часть 9
Сегодня мы рассмотрим создание скользящего переключателя (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
Мы будет использовать верхнюю правую кнопку:
Сделаем её скриншот и вставим в Inkscape как подложку на отдельный слой, добавим направляющие по периметру рисунка и переместим изображение:
Теперь у нас есть изображение нужного размера.
Добавим прямоугольник, выделим его и снова нажмем на R. Потянув за указанный рычаг вниз сделаем из него овал:
Добавим окружность размером 18х18, поместим её на центральной направляющей и сдвинем влево.
Зальём окружность красным цветом, а овал белым:
Добавим направляющую слева от окружности и нарисуем прямоугольник:
Перетащим его вправо, добавим еще одну направляющую и удалим прямоугольник:
Теперь мы свободно может перемещать окружность вдоль направляющей, и она всегда будет правильно отцентрирована и находится на равном расстоянии от правого или левого краёв!
Давайте добавим дополнительные направляющие:
Мы подготовили макет, давайте теперь получим координаты точек, которые нам понадобятся, для отрисовки нашей кнопки:
Обратите внимание, в Qt при отрисовке окружностей и прямоугольников используются координаты левого верхнего угла, ширина и высота.
Посмотрим на координаты:
Теперь у нас есть все необходимые координаты для рисования!
Модифицируем:
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) );
}
Запустим:
Обратите внимание мне пришлось при отрисовке окружности немного подогнать координаты, так что вместо 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();
}
}
Запустим, при щелчке бегунок перемещаться вправо/влево:
Теперь давайте добавим возможность менять цвет бегунка и фона в зависимости от статуса.
Добавим приватные поля:
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");
Запустим:
Добавим еще строки:
sldBtn->setBg("#99f5aa");
sldBtn->setOffBg("#ffafaf");
Запустим, на этот раз изменяется и фон:
Добавляем слоты и сигналы
Добавим в заголовок класса виджета сигналы:
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 шаблон ношей кнопки.
Добавили в проект класс с нашим виджетом и реализовали базовый функционал для отображения кнопки.
Добавили возможность изменять положение бегунка по нажатию.
Добавили возможность менять цвет и фон виджета в зависимости от его состояния.
Добавили поддержку сигналов и слотов.
Добавили метод переключения кнопки.
Протестировали создание нескольких кнопок и использование слота одной для переключения статуса второй!
Добавить комментарий