Qt Model-View-Controller

Прежде всего я хотел бы сказать, что я уже прочитал все другие вопросы о Qt и MVC, но я не смог найти то, что я ищу. Поэтому, пожалуйста, если вы не найдете в старых вопросах что-то, что действительно отвечает на мой вопрос, не связывайте их со мной. Я также искал в qt.digia.com и qt.project.com, но опять же не повезло.

Так что теперь к моей проблеме. Я должен реализовать простой компаратор изображений, который показывает изображение рядом, чтобы их можно было сравнивать. Я должен использовать MVC, чтобы сделать это. Моя проблема в том, что я никогда не использовал Qt, и я немного запутался в том, как использовать его с MVC.

В частности, мне интересно, из чего MainCindow должен быть подкласс. Это View или Модель, или оба? Это то, что я думал. MainWindow - это View в моей диаграмме классов, но я не уверен в этом, потому что он также имеет элементы модели, так как на самом деле хранит информацию о данных. Что ты предлагаешь? Тогда как бы спроектировать другие классы? Спасибо.

3 ответа

Решение

Qt не реализует "стандартный" шаблон MVC в целом - вам потребуется переопределить такую ​​среду с нуля. Qt предлагает структуру представления модели, которая предлагает достаточно функциональности для MVVM, но это не MVC для каждой книги.

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

Таким образом, вопрос об отдельном контроллере по сути спорный, так как его нет. В Qt конкретное представление - это автономный виджет, который вы обычно не выводите. Вместо этого вы интегрируете (has-a) представление в большой виджет, который содержит другие элементы управления.

Qt предоставляет несколько стандартных представлений (представление списка, представление таблицы и представление дерева). Там также QDataWidgetMapper это позволяет сопоставить один индекс из модели с пользовательским свойством любого виджета. Есть также несколько моделей на выбор. Абстрактные модели являются основой для ваших собственных реализаций. Тогда есть QStandardItemModel это обеспечивает гибкое хранение данных в виде дерева / таблицы. В заключение, QSqlQueryModel а также QSqlRelationalTableModel выставлять базы данных SQL как модели.

В приведенном ниже примере компаратор реализован в виде модели представления - прокси-сервера, который дополняет базовую модель предоставления изображений результатами сравнения. Для отображения изображений они должны быть предоставлены под Qt::DecorationRole, Главное окно - это просто представление (is-a), и подклассы не нужны.

Скриншот

#include <QApplication>
#include <QTableView>
#include <QIdentityProxyModel>
#include <QStandardItemModel>
#include <QPainter>

/** Adds image comparison results to a table/tree with pairs of images in each row.
 *
 * This is a viewmodel that expects a table or tree with row containing pairs of images in the
 * first two columns. Comparison results are visualized as the added last column.
 * A null result is provided for rows that don't have two images as their first two columns.
 */
class Comparator : public QIdentityProxyModel {
   Q_OBJECT
   bool isLastColumn(const QModelIndex & proxyIndex) const {
      return proxyIndex.column() == columnCount(proxyIndex.parent()) - 1;
   }
   QModelIndex indexInColumn(int column, const QModelIndex & proxyIndex) const {
      return index(proxyIndex.row(), column, proxyIndex.parent());
   }
   /** Compares the two images, returning their difference..
    * Both images are expanded to the larger of their sizes. Missing data is filled with
    * transparent pixels. The images can be in any format. The difference is in ARGB32. */
   QImage compare(const QImage & left, const QImage & right) const {
      QImage delta(left.size().expandedTo(right.size()), QImage::Format_ARGB32);
      delta.fill(Qt::transparent);
      QPainter p(&delta);
      p.setRenderHint(QPainter::Antialiasing);
      p.drawImage(0, 0, left);
      p.setCompositionMode(QPainter::CompositionMode_Difference);
      p.drawImage(0, 0, right);
      return delta;
   }
public:
   Comparator(QObject * parent = 0) : QIdentityProxyModel(parent) {}
   QModelIndex index(int row, int column, const QModelIndex &parent) const Q_DECL_OVERRIDE {
      if (column != columnCount(parent) - 1)
         return QIdentityProxyModel::index(row, column, parent);
      return createIndex(row, column, parent.internalPointer());
   }
   int columnCount(const QModelIndex &parent) const Q_DECL_OVERRIDE {
      return sourceModel()->columnCount(mapToSource(parent)) + 1;
   }
   QVariant data(const QModelIndex &proxyIndex, int role) const Q_DECL_OVERRIDE {
      if (isLastColumn(proxyIndex)) {
         QVariant left = data(indexInColumn(0, proxyIndex), role);
         QVariant right = data(indexInColumn(1, proxyIndex), role);
         if (!left.canConvert<QImage>() || !right.canConvert<QImage>()) return QVariant();
         return QVariant::fromValue(compare(left.value<QImage>(), right.value<QImage>()));
      }
      return QAbstractProxyModel::data(proxyIndex, role);
   }
};

QImage sector(qreal diameter, qreal size, qreal start, qreal end, const QColor & color)
{
   QImage image(size, size, QImage::Format_ARGB32_Premultiplied);
   image.fill(Qt::transparent);
   QPainter p(&image);
   p.setRenderHint(QPainter::Antialiasing);
   p.setPen(Qt::NoPen);
   p.setBrush(color);
   p.drawPie(QRectF(size-diameter, size-diameter, diameter, diameter),
             qRound(start*16), qRound((end-start)*16));
   return image;
}

QStandardItem * imageItem(const QImage & image) {
   QScopedPointer<QStandardItem> item(new QStandardItem);
   item->setEditable(false);
   item->setSelectable(false);
   item->setData(QVariant::fromValue(image), Qt::DecorationRole);
   item->setSizeHint(image.size());
   return item.take();
}

typedef QList<QStandardItem*> QStandardItemList;

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QStandardItemModel images;
   Comparator comparator;
   QTableView view;
   comparator.setSourceModel(&images);
   view.setModel(&comparator);

   images.appendRow(QStandardItemList()
                    << imageItem(sector(150, 160, 30, 100, Qt::red))
                    << imageItem(sector(150, 160, 60, 120, Qt::blue)));
   images.appendRow(QStandardItemList()
                    << imageItem(sector(40, 45, 0, 180, Qt::darkCyan))
                    << imageItem(sector(40, 45, 180, 360, Qt::cyan)));

   view.resizeColumnsToContents();
   view.resizeRowsToContents();
   view.adjustSize();
   view.show();

   return a.exec();
}

#include "main.moc"

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

Существуют различные определения "MVC". Если вы имеете в виду Контроллер Model-View-MEDIATING, я думаю, что лучше использовать термин MVA, то есть Model-View-Adapter. Проверьте это: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93adapter

Класс с именем MainWindow является абсолютно частью представления этой модели. Тот факт, что объект является окном, делает его тип просмотра. У меня есть программа, например, с тремя представлениями: GuiView, TerminalView и ServiceView. Это радикально разные интерфейсы, которые используют одну и ту же базовую модель и имеют адаптер между ними. Примечание: я также виртуальный базовый класс "View", который использует адаптер, и поэтому не волнует, если я поменяю производные типы представлений.

Есть много способов разделить "представления", кроме приведенного мною примера, но в основном вы видите, как пользователь и / или клиент взаимодействуют с программой.

Класс окна не должен хранить информацию, согласно любому различному определению или интерпретации MVC.

Другие вопросы по тегам