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.