Пишем модель для своего виджета - Работа с моделями в Qt для отображения данных в виджетах
Модели можно создавать не только для стандартных виджетов (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() помимо установки значения статуса еще и перерисовывает виджет.
Таким образом обновляется не только значение статуса, но и сам виджет.
Запустим – при нажатии на кнопку лампа меняет цвет.
Таким образом мы добавили для нашего виджета модель.
Заключение
Сегодня мы рассмотрели добавление к виджету модели.
Создали класс для модели и внесли изменения в виджет, для реализации доступа к модели.
Добавили слоты и сигналы для оповещения об обновлении статуса.
Исходный код проекта вы моете найти на странице GitFlic
Добавить комментарий