БлогNot. Очень простое приложение "модель-вид" с сигналами и слотами в классе модели

Очень простое приложение "модель-вид" с сигналами и слотами в классе модели

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

Напишем небольшое приложение, придерживающееся этих простых, в общем-то, правил отделения логики проекта (модели) от внешнего представления данных (вида).

Нашей моделью будет простейший конвертер, умеющий умножать некоторую величину value на коэффициент пересчёта rate, значения value и rate могут быть вещественными числами и работать с разными типами входных данных (как минимум, числом и строкой QString) с помощью механизма сигналов и слотов.

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

Ниже показан файл model.h, в котором описан класс модели.

#ifndef MODEL_H
#define MODEL_H
#include <QObject>

class Model : public QObject {
 Q_OBJECT
private:
 double value, //Пересчитываемая величина
        rate; //Коэффициент пересчёта
public:
 explicit Model (QObject *parent = 0, double value = 0, double rate = 0);
 double getInputValue(void);
 double getExchangeRate(void);
signals: //Один сигнал - "результат изменился"
 void resultChanged (double);
public slots:
 void setInputValue(double); //Установить величину для пересчёта
 void setInputValue(QString);
 void setExchangeRate(double); //Поменять коэффициент пересчёта
 void setExchangeRate(QString);
};
#endif // MODEL_H

В файле model.cpp реализуем логику приложения.

#include "model.h"

Model::Model (QObject *parent, double v, double r) :
     QObject(parent), value(v), rate(r) {}

double Model::getInputValue(void) { return value; }
double Model::getExchangeRate(void) { return rate; }

void Model::setInputValue(double v) {
 if (value == v) return; //Величина не менялась - выйти
 value = v;
 emit resultChanged(value*rate);
  //Иначе послать сигнал, что результат стал другим
}

void Model::setInputValue(QString v) {
 bool ok = false;
 double vs = v.toDouble(&ok);
 if (ok) this->setInputValue(vs);
}

void Model::setExchangeRate(double r) {
 if (rate == r) return;
 rate = r;
 emit resultChanged(value*rate);
}

void Model::setExchangeRate(QString r) {
 bool ok = false;
 double rs = r.toDouble(&ok);
 if (ok) this->setExchangeRate(rs);
}

Вид должен иметь, как минимум, конструктор, метод для подключения модели и слот, которым он подцепится к сигналу resultChanged. В этом слоте он проверит также, нет ли нужды обновить представление других данных модели (для простоты будем делать это всегда).

Напишем два альтернативных вида, первый из них (View) будет использовать компоненты QDoubleSpinBox для ввода и метку QLabel для вывода, а второй (SimpleView) и для ввода, и для вывода предназначит обычные текстовые поля QLineEdit.

Файл view.h
#ifndef VIEW_H
#define VIEW_H

#include <QWidget>
#include <QtWidgets>
#include "model.h"

class View : public QWidget {
 Q_OBJECT
private:
 QDoubleSpinBox *converted;
 QDoubleSpinBox *rate;
 QLabel *convertedValue;
 Model * model;
public:
 explicit View(QWidget *parent = 0);
 void setModel(Model *);
signals:

public slots:
 void convertedValueSlot(double);
};

#endif // VIEW_H
Файл view.cpp
#include "view.h"

void View::setModel(Model * model_) {
  model = model_;

  connect(converted, SIGNAL(valueChanged(double)), model, SLOT(setInputValue(double)));
  connect(rate, SIGNAL(valueChanged(double)), model, SLOT(setExchangeRate(double)));
  connect(model, SIGNAL(resultChanged(double)), this, SLOT(convertedValueSlot(double)));
/*
  //В "новом стиле" и с опцией проекта
  //QMAKE_CXXFLAGS += -std=gnu++11
  //эти строки в версиях QT младше 5.10 выглядели бы так:
  QObject::connect (converted,static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
   model, &Model::setInputValue);
  QObject::connect (rate, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
   model, &Model::setExchangeRate);
  QObject::connect (model, static_cast<void (Model::*)(double)>(&Model::resultChanged),
   this, &View::convertedValueSlot);
*/
 }

View::View(QWidget *parent) : QWidget(parent) {
 converted = new QDoubleSpinBox(this);
 converted->setDecimals(2);
 converted->setMaximum(1e6);
 converted->setSingleStep(0.5);

 rate = new QDoubleSpinBox(this);
 rate->setDecimals(2);
 rate->setMaximum(1000);
 rate->setSingleStep(0.5);

 convertedValue = new QLabel(this);

 QGridLayout *layout = new QGridLayout(this);
 layout->setSpacing(4);
 layout->addWidget(new QLabel(QString(tr("Сумма"))),0,0,1,1);
 layout->addWidget(converted,0,1,1,1);
 layout->addWidget(new QLabel(QString(tr("Коэффициент"))),1,0,1,1);
 layout->addWidget(rate,1,1,1,1);
 layout->addWidget(convertedValue,2,0,1,2);
 setLayout(layout);
}

void View::convertedValueSlot(double d) {
 converted->setValue(model->getInputValue());
 rate->setValue(model->getExchangeRate());
 convertedValue->setText("="+QString::number(d,'f',2));
}
Файл simpleview.h
#ifndef SIMPLEVIEW_H
#define SIMPLEVIEW_H

#include <QWidget>
#include <QtWidgets>
#include "model.h"

class SimpleView : public QWidget {
 Q_OBJECT
public:
 explicit SimpleView(QWidget *parent = 0);
 void setModel(Model *);
private:
 QLineEdit *converted, *rate, *convertedValue;
 Model * model;
public slots:
 void convertedValueSlot(double);
};

#endif // SIMPLEVIEW_H
Файл simpleview.cpp
#include "simpleview.h"

void SimpleView::setModel(Model * model_) {
 model = model_;
 connect(converted, SIGNAL(textChanged(QString)), model, SLOT(setInputValue(QString)));
 connect(rate, SIGNAL(textChanged(QString)), model, SLOT(setExchangeRate(QString)));
 connect(model, SIGNAL(resultChanged(double)), this, SLOT(convertedValueSlot(double)));
}

SimpleView::SimpleView(QWidget *parent) : QWidget(parent) {
 converted = new QLineEdit(QString("0"),this);
 rate = new QLineEdit(QString("0"),this);
 convertedValue = new QLineEdit(QString("0"),this);
 convertedValue->setReadOnly(true);

 QHBoxLayout *layout = new QHBoxLayout(this);
 layout->setSpacing(4);
 layout->addWidget(new QLabel(QString(tr("Сумма"))));
 layout->addWidget(converted);
 layout->addWidget(new QLabel(QString(tr("Коэффициент"))));
 layout->addWidget(rate);
 layout->addWidget(new QLabel(QString(tr("Результат"))));
 layout->addWidget(convertedValue);
 setLayout(layout);
}

void SimpleView::convertedValueSlot(double d) {
 QString str;
 converted->setText(str.setNum(model->getInputValue()));
 rate->setText(str.setNum(model->getExchangeRate()));
 convertedValue->setText(QString::number(d));
}

Главной программе останется создать экземпляр модели, экземпляры видов и запустить основное приложение, вот листинг файла main.cpp:

#include <QApplication>
#include "model.h"
#include "view.h"
#include "simpleview.h"

int main(int argc, char *argv[]) {
 QApplication a(argc, argv);

 Model *m = new Model();

 View *v = new View();
 v->setModel(m);
 v->show();

 SimpleView *w = new SimpleView();
 w->setModel(m);
 w->show();

 return a.exec();
}

Вот что получилось в работе:

скриншот работы приложения
скриншот работы приложения

Так как оба вида используют один и тот же экземпляр модели, они будут косвенно влиять на отображение данных модели друг другом, если мы изменим что-то в одном представлении, обновится и другое. Иногда это может приводить к мелким коллизиям, скажем, в текстовое поле ввода можно ввести большой коэффициент пересчёта, а в соответствующем QDoubleSpinBox стоит ограничение "1000" и результаты преобразования в двух видах будут выглядеть разными. Но это, в любом случае, проблемы представлений, а не модели :)

 Скачать этот проект QT5 в архиве .zip, папка уже создана внутри архива (4 Кб)


теги: программирование учебное c++ qt

21.04.2019, 14:45; рейтинг: 435