Воскресенье, 27.12.2020 08:26

Пишем простой виджет - Виджеты (компоненты) в Qt.

Пишем простой виджет - Виджеты (компоненты) в Qt. Часть 1

Виджеты в Qt5 предоставляют широкий простор для творчества, позволяя создавать любые по сложности компоненты. Виджет может просто отрисовывать некую информацию и даже служить «контейнером» для других виджетов. Всё зависит от решаемой задачи и фантазии программиста и дизайнера.

Сегодня мы напишем простой виджет – лампочку (круг), которая будет у нас загораться выбранным цветом.

Создаем простой виджет

Создадим проект Qt Widgets ApplicationLampWidget

Добавим новый класс для нашего виджета – QLampWidget

Заголовок:

#ifndef QLAMPWIDGET_H
#define QLAMPWIDGET_H

#include <QWidget>

class QLampWidget  : public QWidget
{    
    Q_OBJECT
protected:
    virtual void paintEvent(QPaintEvent *event);
public:
    explicit QLampWidget(QWidget *parent);
    QLampWidget();
};
#endif // QLAMPWIDGET_H

 Реализация:

Реализация:
#include "qlampwidget.h"

#include <QPainter>
#include <QPen>

void QLampWidget::paintEvent(QPaintEvent *event)
{    
	QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(QColor("#f00"), 4));
    painter.drawEllipse(QRectF(0,0,40,40));

}
QLampWidget::QLampWidget()
{
}

Теперь нам нужно добавить виджет на на главную форму, а также добавить компоновку или разметку (layout), далее я буду использовать термин Разметка. Что такое компоновка и зачем она нужна мы поговорим в другой статье.

Добавим в конструктор главной формы следующий код: 

    QVBoxLayout *vlay = new QVBoxLayout();

    QLampWidget *lamp = new QLampWidget();
    vlay->addWidget(lamp);

    ui->centralwidget->setLayout(vlay);

Запустим:

2020-12-26_10-58-18.png

Вот мы и создали простой виджет.

Задаем размеры виджета

У каждого виджета есть своя ширина и высота. У виджета, который мы создали нет четких размеров, давайте в этом убедимся.

В метод paintEvent() добавим строку:

painter.setPen(QPen(QColor("#00f"), 4));
painter.drawRect(0,0,this->rect().width(),this->rect().height());

Запустим:

2020-12-26_11-02-40.png

Как видите, рамка заняла всё окно. Это происходит потому что созданная нами Разметка занимает всю форму и всё помещенные в нее виджеты автоматически стремятся занять всё доступное пространство, давайте добавим еще один виджет:

 QLampWidget *lamp1 = new QLampWidget();    
vlay->addWidget(lamp1);

 Запустим:

2020-12-26_11-07-07.png

Как видите виджеты равномерно заполнили всё доступное пространство формы.

Давайте зададим виджету размер. В нашем случаем наша лампочка будет размером 32х32 пикселя.

Изменим конструктор виджета:

QLampWidget::QLampWidget()
{
    this->setFixedWidth(32);
    this->setFixedHeight(32);
}

 Так же добавим объявление метода:

protected:
    virtual QSize sizeHint() const;

QSize QLampWidget::sizeHint() const {
    return QSize(32, 32);
}

Этот метод необходим, для того, чтобы при масштабировании и прочих преобразованиях Qt5 точно знал размеры виджета. В данный момент они фиксированы.

Запустим:

2020-12-26_11-14-05.png

Как видите размер виджета уменьшился согласно заданным размерам.

Удалим из конструктора формы объявление второго виджета, он нам не понадобиться.

Рисуем лампочку

Нарисуем круг – основу нашей лампочки, так же уменьшим толщину линии границы виджета:

void QLampWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(QColor("#f00"), 4));
    painter.drawEllipse(QRectF(4,4,24,24));

    painter.setPen(QPen(QColor("#00f"), 1));
    painter.drawRect(0,0,this->rect().width(),this->rect().height());
}

Запустим:

2020-12-26_11-20-51.png

Добавим фон для лампочки:

void QLampWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(QColor("#f00"), 4));
    painter.setBrush(QColor("#f00"));
    painter.drawEllipse(QRectF(4,4,24,24));


    painter.setBrush(QColor(Qt::transparent));
    painter.setPen(QPen(QColor("#00f"), 1));
    painter.drawRect(0,0,this->rect().width(),this->rect().height());
}

Добавляем градиент

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

void QLampWidget::paintEvent(QPaintEvent *event)
{
    QLinearGradient linearGrad(QPointF(4, 4), QPointF(28, 28));
    linearGrad.setColorAt(0, QColor("#fed9d9"));
    linearGrad.setColorAt(1, Qt::red);


    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    
    painter.setPen(QPen(QColor("#f00"), 0.1));
    painter.setBrush(linearGrad);

    painter.drawEllipse(QRectF(4,4,24,24));



    painter.setBrush(QColor(Qt::transparent));
    painter.setPen(QPen(QColor("#00f"), 1));
    painter.drawRect(0,0,this->rect().width(),this->rect().height());
}

Запустим:

2020-12-26_11-47-56.png

Теперь наша лампочка выглядит лучше.

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

Наша лампочка может быть в двух состояниях – включена или выключена. Во включенном состоянии она будет красной, в выключенной – серой.

Добавим поле отвечающее за хранение состояния виджета.

private:    
	int status;

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

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

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

Внесем изменения в метод paintEvent(), для реализации функционала:

void QLampWidget::paintEvent(QPaintEvent *event)
{
    QString onColor = "#f00";
    QColor mainColorOn = QColor(onColor);
    QColor subColorOn = QColor(onColor);
    subColorOn.setHsl(0,100,95,0);


    QString offColor = "#808080";
    QColor mainColorOff = QColor(offColor);
    QColor subColorOff = QColor(offColor);
    subColorOff.setHsl(0,100,95,0);

    QLinearGradient linearGrad(QPointF(4, 4), QPointF(28, 28));

    if (this->getStatus()==QLampWidget::on)
    {
        linearGrad.setColorAt(0, subColorOn);
        linearGrad.setColorAt(1, mainColorOn);
    } else  {
        linearGrad.setColorAt(0, subColorOff);
        linearGrad.setColorAt(1, mainColorOff);
    }

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

    painter.setPen(QPen(QColor("#f00"), 0.1));
    painter.setBrush(linearGrad);

    painter.drawEllipse(QRectF(4,4,24,24));

    painter.setBrush(QColor(Qt::transparent));
    painter.setPen(QPen(QColor("#00f"), 1));
    painter.drawRect(0,0,this->rect().width(),this->rect().height());
}

Добавим в конструктор виджета строку:

this->setStatus(QLampWidget::on);

По умолчанию лампочка всегда будет включена.

Добавим константы для статуса лампочки:

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

Запустим:

2020-12-26_14-18-28.png

Как видите ничего не изменилось.

Добавим в конструктор формы

lamp->setStatus(QLampWidget::off);

Запустим:

2020-12-26_14-19-42.png

Лампа отключена.

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

В переменную onColor мы записываем базовый цвет кнопки.

Обратите внимание – цвет должен быть не очень ярким, даже наоборот - темным, но за счет градиента он будет ярче!

От основного цвета мы вычисляем цвет начала градиента, с помощью HSL увеличив яркость цвета, таким образом, для любого цвета внешний вид кнопки у нас не измениться!

Далее мы задаем градиент для лампы в отключенном состоянии. Этот цвет у нас будет всегда один - #808080

Далее просто проверяем статус лампы и задаем соответствующий градиент.

Управляем лампой

Давайте добавим на форму кнопку и добавим слот, чтобы включать/выключать нашу лампу.

Добавим на форму кнопку QPushButton, установим у нее свойства checkable и flat и checked.

Перенесем объявление

QLampWidget *lamp;

В заголовок главной формы, чтобы он был доступен из метода слота.

Добавим слот:

void MainWindow::on_pushButton_toggled(bool checked)
{
    if (checked)
    {
        lamp->setStatus(QLampWidget::on);
    } else {
        lamp->setStatus(QLampWidget::off);
    }
}

Запускаем и… Ничего, при нажатии на кнопку, статус лампочки не меняется.

Если мы добавим в слот отладку:

    qDebug() << checked;

То в консоли увидим:

false 
true 
false 
true

Так что слот отрабатывает, но наш виджет не обновляется. Но если вы нажмете на кнопку и свернете/развернете окно – цвет поменяется!

Исправить данное поведение довольно просто, изменим метод setStatus()

void QLampWidget::setStatus(int value)
{
    status = value;
    this->update();
}

Метод update() уведомляет виджет, что нужно заново перерисовать свое содержимое, таким образом вызывается метод paintEvent() и цвет кнопки меняется. 

Меняем цвет лампочки

Красная лампочка это хорошо, но нам может потребоваться лампочки разных цветов. Чтобы не плодить виджеты, давайте сделаем виджет более универсальным.

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

QString color;

Добавим конструкторы для инициализации и обновим конструктор по умолчанию:

QLampWidget::QLampWidget()
{
    this->setFixedWidth(32);
    this->setFixedHeight(32);
    this->setStatus(QLampWidget::on);
    this->color = "#f00";
}

QLampWidget::QLampWidget(QString color)
{
    this->setFixedWidth(32);
    this->setFixedHeight(32);
    this->color = color;
    this->setStatus(QLampWidget::on);
}


QLampWidget::QLampWidget(QString color, int status)
{
    this->setFixedWidth(32);
    this->setFixedHeight(32);
    this->color = color;
    this->setStatus(status);
}

В методе paintEvent() исправим первую строку на

QString onColor = this->color;

Добавим еще 2 лампочки в конструкторе формы

    QLampWidget *lamp1 = new QLampWidget("#557d00");
    vlay->addWidget(lamp1);
    QLampWidget *lamp2 = new QLampWidget("#557d00",QLampWidget::off);
    vlay->addWidget(lamp2);

Запустим:

2020-12-26_15-00-04.png

Уберем границу вокруг виджета, удалив или закомментировав, в методе paintEvent() строки:

    painter.setBrush(QColor(Qt::transparent));
    painter.setPen(QPen(QColor("#00f"), 1));
    painter.drawRect(0,0,this->rect().width(),this->rect().height()); 

 В заключение я приведу более сложный пример - добавлю еще кнопок и лампочек, этот пример мы отдельно рассмотрим в одной из будущих статей.

2020-12-27_19-30-02.png

Исходный код доступен на GitFlic.

Заключение

Сегодня мы создали простой виджет – лампочку.

Мы добавили возможность задавать цвет включенной лампочки.

А также возможность переключать её состояние с помощью метода.

В следующей статье мы усовершенствуем класс - добавим возможность менять цвет в отключенном состоянии, а так же размер лампочки.

Категория Виджеты

Добавить комментарий

Простой текст

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
Просмотров: 248