Пишем модель для своего виджета - Работа с моделями в Qt5 для отображения данных в виджетах. - АлтунинВВ.Блог - всё об IT-технологиях!
Среда, 13 января 2021 19:00

Пишем модель для своего виджета - Работа с моделями в Qt5 для отображения данных в виджетах.

Россия
Оцените материал
(0 голосов)

Модели можно создавать не только для стандартных виджетов (QComboBox, QTableView и так далее), но и для тех, которые  создаём мы сами.

Сегодня будет рассмотрена реализация модели для виджета, который был создан в предыдущих статьях - простой лампочки. Модель позволит управлять состоянием лампочки – включать/отключать её.

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

Мы будем использовать проект из предыдущей статьи.

Данные и свойства

У вас может появится соблазн включить в модель все свойства лампочки – цвета, выравнивание и так далее. Но это неверный путь. Основная задача модели – предоставлять виджету данные.

Что такое данные? Для примера возьмем наши лампочки. Если мы создадим, к примеру, пять штук, то единственное что может часто изменяться во время выполнения программы – статус этой лампочки, т.е. она может включаться и отключаться. Таким образом всё что часто изменяется во время выполнения программы – это данные, то что описывает внешний вид и размер виджета это свойства.

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

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

Работа над ошибками

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

void QLampWidget::paintEvent(QPaintEvent *event)
{
    QString onColor = this->color;
    QColor mainColorOn = QColor(onColor);
    QColor subColorOn = QColor(onColor);
    subColorOn.setHsl(0,100,95,0);


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

    QPainter painter(this);

    painter.setFont(this->captionFont);

    QFontMetrics metrics(painter.font());

    int heightOfText  = 0;
    int widthOfText = 0;
    QRect bRect;

    if (this->getCaptionAlign() & Qt::TextWordWrap)
    {
        bRect = metrics.boundingRect(QRect(0,0,this->getLampSize(),32),this->getCaptionAlign(),this->getCaption());;

        heightOfText  = bRect.height();
        widthOfText = bRect.width();
    }
    else
    {
        heightOfText  = metrics.height();
        widthOfText = metrics.horizontalAdvance(this->getCaption());

    }


    this->setFixedHeight(this->getLampSize()+heightOfText);

    float xPos = 0;
    float textPosX = 0;

    if (widthOfText > this->getLampSize())
     {
        if (this->getCaptionAlign() & Qt::TextWordWrap)
        {
            this->setFixedWidth(bRect.width());
        }
        else
        {
            this->setFixedWidth(widthOfText);
        }
        xPos = widthOfText / 2 - (this->getLampSize()-8) / 2;
        textPosX = 0;

     }
     else
     {
         this->setFixedWidth(this->getLampSize());
         xPos = 4;
         textPosX = (this->getLampSize()-8) / 2 - widthOfText / 2;
     }


    qDebug() << widthOfText;
    QLinearGradient linearGrad(
                QPointF(xPos,
                    4),
                QPointF(xPos + this->getLampSize()-8,
                    this->getLampSize()-8));

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


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

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

    painter.drawEllipse(
                QRectF(xPos, 4,
                       this->getLampSize()-8, this->getLampSize()-8));

    painter.setBrush(QBrush(QColor(157,255,252,127)));
    QPen pen;
    pen.setStyle(Qt::DashLine);
    painter.setPen(pen);
    QRect border = QRect(textPosX,this->getLampSize(),
                         widthOfText, heightOfText);
    painter.drawRect(border);

    painter.setPen(QPen(QColor(this->captionColor), 1));
    painter.drawText(textPosX,this->getLampSize(),
                     widthOfText, heightOfText,
                     this->getCaptionAlign(),
                     this->getCaption(),
                     new QRect(0,this->getLampSize(),this->getLampSize(), heightOfText));

}

Я добавил дополнительные проверки и если используется флаг Qt::TextWordWrap то вычисляется высота и ширина, если флага нет, то вычисляется только ширина текста.

Так же вернул строчку

xPos = widthOfText / 2 - (this->getLampSize()-8) / 2;

для расчета середины.

И еще добавил переменную textPosX для вычисления смещения текста, в случае, если лампочка шире текста.

Создаем модель

Создадим простейшую модель для нашей лампочки – QlampWidgetModel

Заголовок:

#ifndef QLAMPWIDGETMODEL_H
	#define QLAMPWIDGETMODEL_H

#include <QAbstractListModel>
#include <QObject>



class QLampWidgetModel : public QAbstractListModel
{

    Q_OBJECT

public:
    QLampWidgetModel();
    int rowCount(const QModelIndex &) const;
    QVariant data(const QModelIndex &index, int role) const;

    void populate(QString *status);

private:
    QString *status;
};

#endif // QLAMPWIDGETMODEL_H

Реализация:

#include "qlampwidgetmodel.h"


QLampWidgetModel::QLampWidgetModel()
{

}

int QLampWidgetModel::rowCount(const QModelIndex &) const
{
    return 1;
}

QVariant QLampWidgetModel::data(const QModelIndex &index, int role) const
{
    QVariant value;

        switch ( role )
        {
            case Qt::DisplayRole: //string
            {
                value = this->status->toInt();
            }
            break;

            default:
                break;
        }

        return value;
}

void QLampWidgetModel::populate(QString *status)
{
    this->status = status;
}

Здесь всё просто, кроме одного момента – для хранения значения статуса используется QString. Я пошел на это по простой причине, чтобы не связываться с указателями *int.

Добавляем функционал модели в виджет

Чтобы виджет поддерживал модели, его нужно модернизировать:

Добавим поле для хранения указателя на модель:

private:
    QAbstractListModel *model;

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

Так же добавим геттер/сеттер

QAbstractListModel *QLampWidget::getModel() const
{
    return model;
} 

void QLampWidget::setModel(QAbstractListModel *value)
{
    model = value;
}

Правим главную форму

Добавим приватное поле, для хранения ссылки на нашу модель и поле для хранения статуса лампы:

private:
    QLampWidgetModel *model;
    QString lampStatus;

 Внесем изменения в конструктор главной формы:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QVBoxLayout *vlay = new QVBoxLayout();

    QHBoxLayout *hlay1 = new QHBoxLayout();

    lamp = new QLampWidget("#557d00","#F00",QLampWidget::on, 32, "2");

    lamp->setCaption("Lamp1");
    hlay1->addWidget(lamp);

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

    vlay->addStretch(1);

    ui->centralwidget->setLayout(vlay);
}

Добавим код для создания модели:

    model = new QLampWidgetModel();
    lamp->setModel(model);

    lampStatus = QString::number(QLampWidget::off);
    model->populate(&lampStatus);

перед строкой

    lamp->setCaption("Lamp1");

Запустим чтобы проверить, что всё работает.

Извлекаем данные из модели

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

Для этого добавим в метод QLampWidget::setModel() строку

    this->setStatus(model->data(QModelIndex(),Qt::DisplayRole).toInt());

Запустим – лампочка отключилась.

Что мы тут сделали? Мы вызвали метод модели data(), так как у нас всего одна ячейка в модели мы передаем методу пустой индекс QModelIndex(), так как роль у нас тоже одна мы передаем только её - Qt::DisplayRole.

Так как метод data() возвращает QVariant, мы возвращаем значение типа int с помощью метода toInt().

Минус такого подхода очевиден – состояние лампочки будет обновляться только при вызове метода setModel()

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

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

Добавим в модель сигнал

signals:
    void statusUpdated();

Обновим метод populate()

void QLampWidgetModel::populate(QString *status)
{
    this->status = status;
    emit statusUpdated();
}

Теперь при обновлении статуса всегда будет отправляться сигнал statusUpdated()

В виджет добавим приватный слот:

private slots:
    void onStatusUpdated();

Реализация

void QLampWidget::onStatusUpdated()
{
    this->setStatus(model->data(QModelIndex(),Qt::DisplayRole).toInt());
    qDebug() << "onStatusUpdated()";
}

Обновим метод setModel()

void QLampWidget::setModel(QAbstractListModel *value)
{
    model = value;
    QObject::connect(model, SIGNAL(statusUpdated()),this,SLOT(onStatusUpdated()));
}

Добавим на форму кнопку Toggle со слотом:

void MainWindow::on_pushButton_clicked()
{
    if (lamp->getStatus() == QLampWidget::on) {
        lampStatus = QString::number(QLampWidget::off);
    }
    else
    {
        lampStatus = QString::number(QLampWidget::on);
    }
    model->populate(&lampStatus);
}

Теперь при изменении статуса с помощью метода populate() модель будет отсылать сигнал statusUpdated, виджет будет считывать из модели новый статус и обновлять его значение с помощью метода setStatus(). А сам метод setStatus() помимо установки значения статуса еще и перерисовывает виджет.

Таким образом обновляется не только значение статуса, но и сам виджет.

Запустим – при нажатии на кнопку лампа меняет цвет.

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

Заключение

Сегодня мы рассмотрели добавление к виджету модели.

Создали класс для модели и внесли изменения в виджет, для реализации доступа к модели.

Добавили слоты и сигналы для оповещения об обновлении статуса.

Исходный код проекта вы моете найти на странице Github.

Прочитано 188 раз Последнее изменение Среда, 13 января 2021 20:11