Qt Model-View-Controller

2019-02-20 00:57发布

问题:

First of all I would like to say that I' ve already read all the other questions about Qt and MVC, but I couldn't find what I'm looking for. So please, unless you find something in the old questions that actually answer my question, don't link them to me. I also searched in qt.digia.com and qt.project.com but again, no luck.

So now to my problem. I have to implement a simple image comparator that shows to image side by side so that they can be compared. I have to use MVC to do this. My problem is that I've never used Qt and I'm a bit confused on how to use it with MVC.

In particular, I am wondering what MainWindow should be subclassed from. It is the View or the Model, or both? This is what I thought. MainWindow is a View in my class diagram, but I'm not sure of that, because it also has elements of a model, since it actually stores data information. What do you suggest? Then how would design the other classes? Thank you.

回答1:

Qt does not implement the "standard" MVC pattern as a whole - you would need to reimplement such a framework from scratch. Qt offers a model-view framework that offers enough functionality for MVVM, but that's not MVC-by-the-book.

In Qt's implementation, the view and the controller are mingled together. The view is what shows the model to the user and what the user uses to interact with the model, with help of delegates.

Thus, the question of a separate controller is essentially moot, as there isn't one. In Qt, a concrete view is a stand-alone widget that you normally don't derive from. Instead, you integrate (has-a) the view into a larger widget that holds other controls.

Qt provides some standard views (a list view, a table view and a tree view). There's also the QDataWidgetMapper that lets you map one index from a model onto the user property of any widget. There are also several models to choose from. The abstract models are bases for your own implementations. Then there's the QStandardItemModel that provides a flexible tree/table storage of data. Finally, QSqlQueryModel and QSqlRelationalTableModel expose SQL databases as models.

In the example below, the comparator is implemented as a viewmodel - a proxy that amends the underlying image-providing model with results of the comparison. For the images to be displayed, they need to be provided under the Qt::DecorationRole. The main window is simply the view (is-a), and no subclassing is necessary.

#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"


回答2:

MainWindow should sit between your view and your data model as a view controller, I would move the data information storage out into a data model and interact with it that way.



回答3:

There are varying definitions of "MVC". If you mean Model-View-MEDIATING Controller, I think it is best to use the term MVA, i.e. Model-View-Adapter. Check this out: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93adapter

A class called MainWindow is absolutely part of a view per this model. The fact that the object is window makes it a type of view. I have a program for instance with three views: GuiView, TerminalView, and ServiceView. Those are radically different interfaces, which use the same underlying model, and have an adapter in between. Note: I also a virtual base class "View" which the adapter uses and thus doesn't care if I swap out derived view types.

There are plenty of ways to split up "views" other than the example I provided, but basically your view is how the user and/or client interacts with the program.

A window class should not be storing information, by any varied definition or interpretation of MVC.