Qt模型 - 视图 - 控制器

时间:2014-07-23 09:48:28

标签: c++ qt model-view-controller

首先我想说的是我'我已经阅读了关于Qt和MVC的所有其他问题,但我无法找到我正在寻找的东西。所以,除非您在旧问题中找到实际回答我问题的内容,否则请不要将它们与我联系起来。我也在qt.digia.com和qt.project.com上搜索过,但又没有运气。

所以现在我的问题。我必须实现一个简单的图像比较器,它可以并排显示图像,以便对它们进行比较。我必须使用MVC来做到这一点。我的问题是我从未使用过Qt,而且我对如何将它与MVC一起使用感到有些困惑。

特别是,我想知道什么是MainWindow应该被分类。它是视图还是模型,还是两者兼而有之?这就是我的想法。 enter image description here MainWindow是我的类图中的一个视图,但我不确定,因为它还包含模型的元素,因为它实际上存储了数据信息。你有什么建议? 那么如何设计其他类呢?谢谢。

3 个答案:

答案 0 :(得分:2)

MainWindow应该作为视图控制器位于您的视图和数据模型之间,我会将数据信息存储移动到数据模型中并以这种方式与其进行交互。

答案 1 :(得分:2)

Qt没有实施"标准" MVC模式作为一个整体 - 您需要从头开始重新实现这样的框架。 Qt提供了一个模型视图框架,为MVVM提供了足够的功能,但这不是MVC-by-the-book。

在Qt的实现中,视图和控制器混合在一起。视图是在用户的帮助下向用户显示模型用户与模型交互的内容。

因此,单独控制器的问题基本上没有实际意义,因为没有。在Qt中,具体视图是您通常不会从中派生出来的独立窗口小部件。相反,您将视图集成(has-a)一个包含其他控件的更大的小部件。

Qt提供了一些标准视图(列表视图,表视图和树视图)。还有QDataWidgetMapper,它允许您将模型中的一个索引映射到任何窗口小部件的用户属性。还有几种型号可供选择。抽象模型是您自己实现的基础。然后是QStandardItemModel提供灵活的数据树/表存储。最后,QSqlQueryModelQSqlRelationalTableModel将SQL数据库公开为模型。

在下面的示例中,比较器实现为视图模型 - 一个代理,它修改基础图像提供模型以及比较结果。对于要显示的图像,需要在Qt::DecorationRole下提供。主窗口只是 视图(is-a),不需要子类化。

screenshot

#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 :(得分:0)

“MVC”有不同的定义。如果您的意思是模型 - 视图 - MEDIATING控制器,我认为最好使用术语MVA,即模型 - 视图 - 适配器。看看这个:https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93adapter

一个名为MainWindow的类绝对是每个模型的视图的一部分。对象是窗口的事实使它成为一种视图。我有一个程序,例如有三个视图:GuiView,TerminalView和ServiceView。这些是完全不同的接口,它们使用相同的底层模型,并且介于两者之间。注意:我也是适配器使用的虚拟基类“View”,因此我不关心是否换出派生视图类型。

除了我提供的示例之外,还有很多方法可以拆分“视图”,但基本上你的观点是用户和/或客户端如何与程序交互。

窗口类不应该通过MVC的任何不同定义或解释来存储信息。