Model/View实现表格技术
一、简介
Model/View结构使数据管理与相应的数据显示相互独立,并提供了一系列标准的函数接口和用于Model模块与View模块之间的通信。它从MVC演化而来,MVC由三种对象组成,Model是应用程序对象,View是它的屏幕表示,Controller定义了用户界面如何对用户输入进行响应。把MVC中的View和Controller合在一起,就形成了Model/View结构。
二、运行图
(1)为了灵活对用户的输入进行处理,引入了Delegate,Model、View、Delegate三个模块之间通过信号与槽机制实现,当自身的状态发生改变时会发出信号通知其他模块。它们间的关系如下图1所示。
QAbstractItemModel是所有Model的基类,但一般不直接使用QAbstractItemModel,而是使用它的子类。Model模块本身并不存储数据,而是为View和Delegate访问数据提供标准的接口。
View模块从Model中获得数据项显示给用户,Qt提供了一些常用的View模型,如QTreeView、QTableView和QListView。
Delegate的基本接口在QAbstractItemDelegate类中定义,通过实现paint()和sizeHint()以达到渲染数据项的目的。
(2)程序运行图如下图2所示。
三、详解
1、表格中嵌入控件
利用Delegate的方式实现表格中嵌入各种不同控件的效果,控件在需要编辑数据项时才出现。
(1)插入日历编辑框QDateLineEdit
[cpp] view plain copy
- #ifndef DATEDELEGATE_H
- #define DATEDELEGATE_H
- #include <QtGui>
- class DateDelegate : public QItemDelegate
- {
- Q_OBJECT
- public:
- DateDelegate(QObject *parent = 0);
- QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
- const QModelIndex &index) const;
- void setEditorData(QWidget *editor, const QModelIndex &index) const;
- void setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const;
- void updateEditorGeometry(QWidget *editor,
- const QStyleOptionViewItem &option, const QModelIndex &index) const;
- };
- #endif
[cpp] view plain copy
- #include "datedelegate.h"
- DateDelegate::DateDelegate(QObject *parent)
- : QItemDelegate(parent)
- {
- }
- QWidget *DateDelegate::createEditor(QWidget *parent,
- const QStyleOptionViewItem &/* option */,
- const QModelIndex &/* index */) const
- {
- QDateTimeEdit *editor = new QDateTimeEdit(parent);
- editor->setDisplayFormat("yyyy-MM-dd");
- editor->setCalendarPopup(true);
- editor->installEventFilter(const_cast<DateDelegate*>(this));
- return editor;
- }
- void DateDelegate::setEditorData(QWidget *editor,
- const QModelIndex &index) const
- {
- QString dateStr = index.model()->data(index).toString();
- QDate date = QDate::fromString(dateStr,Qt::ISODate);
- QDateTimeEdit *edit = static_cast<QDateTimeEdit*>(editor);
- edit->setDate(date);
- }
- void DateDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- QDateTimeEdit *edit = static_cast<QDateTimeEdit*>(editor);
- QDate date = edit->date();
- model->setData(index, QVariant(date.toString(Qt::ISODate)));
- }
- void DateDelegate::updateEditorGeometry(QWidget *editor,
- const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
- {
- editor->setGeometry(option.rect);
- }
分析:DateDelegate继承QItemDelegate,一般需要重定义声明中的几个虚函数。createEditor()函数完成创建控件的工作;setEditorDate()设置控件显示的数据,把Model数据更新至Delegate,相当于初始化工作;setModelDate()把Delegate中对数据的更改更新至Model中;updateEditor()更析控件区的显示。
(2)插入下拉列表框QComboBox
[cpp] view plain copy
- #include "combodelegate.h"
- ComboDelegate::ComboDelegate(QObject *parent)
- : QItemDelegate(parent)
- {
- }
- QWidget *ComboDelegate::createEditor(QWidget *parent,
- const QStyleOptionViewItem &/* option */,
- const QModelIndex &/* index */) const
- {
- QComboBox *editor = new QComboBox(parent);
- editor->addItem(QString::fromLocal8Bit("工人"));
- editor->addItem(QString::fromLocal8Bit("农民"));
- editor->addItem(QString::fromLocal8Bit("医生"));
- editor->addItem(QString::fromLocal8Bit("律师"));
- editor->addItem(QString::fromLocal8Bit("军人"));
- editor->installEventFilter(const_cast<ComboDelegate*>(this));
- return editor;
- }
- void ComboDelegate::setEditorData(QWidget *editor,
- const QModelIndex &index) const
- {
- QString str = index.model()->data(index).toString();
- QComboBox *box = static_cast<QComboBox*>(editor);
- int i = box->findText(str);
- box->setCurrentIndex(i);
- }
- void ComboDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- QComboBox *box = static_cast<QComboBox*>(editor);
- QString str = box->currentText();
- model->setData(index, str);
- }
- void ComboDelegate::updateEditorGeometry(QWidget *editor,
- const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
- {
- editor->setGeometry(option.rect);
- }
(3)插入微调器QSpinBox
[cpp] view plain copy
- #include "spindelegate.h"
- SpinDelegate::SpinDelegate(QObject *parent)
- : QItemDelegate(parent)
- {
- }
- QWidget *SpinDelegate::createEditor(QWidget *parent,
- const QStyleOptionViewItem &/* option */,
- const QModelIndex &/* index */) const
- {
- QSpinBox *editor = new QSpinBox(parent);
- editor->setRange(1000,10000);
- editor->installEventFilter(const_cast<SpinDelegate*>(this));
- return editor;
- }
- void SpinDelegate::setEditorData(QWidget *editor,
- const QModelIndex &index) const
- {
- int value = index.model()->data(index).toInt();
- QSpinBox *spin = static_cast<QSpinBox*>(editor);
- spin->setValue(value);
- }
- void SpinDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
- const QModelIndex &index) const
- {
- QSpinBox *spin = static_cast<QSpinBox*>(editor);
- int value = spin->value();
- model->setData(index, value);
- }
- void SpinDelegate::updateEditorGeometry(QWidget *editor,
- const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
- {
- editor->setGeometry(option.rect);
- }
2、柱状统计图
自定义的View实现一个柱状统计图对TableModel的表格数据进行显示。
[cpp] view plain copy
- #ifndef HISTOGRAMVIEW_H
- #define HISTOGRAMVIEW_H
- #include <QtGui>
- class HistogramView : public QAbstractItemView
- {
- Q_OBJECT
- public:
- HistogramView(QWidget *parent=0);
- QRect visualRect(const QModelIndex &index)const;
- void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible);
- QModelIndex indexAt(const QPoint &point) const;
- void paintEvent(QPaintEvent *);
- void mousePressEvent(QMouseEvent *);
- void setSelectionModel(QItemSelectionModel * selectionModel);
- QRegion itemRegion(QModelIndex index);
- protected slots:
- void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
- void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected );
- protected:
- QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,
- Qt::KeyboardModifiers modifiers);
- int horizontalOffset() const;
- int verticalOffset() const;
- bool isIndexHidden(const QModelIndex &index) const;
- void setSelection ( const QRect&rect, QItemSelectionModel::SelectionFlags flags );
- QRegion visualRegionForSelection(const QItemSelection &selection) const;
- private:
- QItemSelectionModel *selections;
- QList<QRegion> listRegionM;
- QList<QRegion> listRegionF;
- QList<QRegion> listRegionS;
- };
- #endif
[cpp] view plain copy
- #include "histogramview.h"
- HistogramView::HistogramView(QWidget *parent)
- : QAbstractItemView(parent)
- {}
- void HistogramView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
- {
- QAbstractItemView::dataChanged(topLeft, bottomRight);
- viewport()->update();
- }
- QRect HistogramView::visualRect(const QModelIndex &index) const
- {}
- void HistogramView::scrollTo(const QModelIndex &index, ScrollHint hint)
- {}
- QModelIndex HistogramView::indexAt(const QPoint &point) const
- {
- QPoint newPoint(point.x(),point.y());
- QRegion region;
- foreach(region,listRegionM)
- {
- if (region.contains(newPoint))
- {
- int row = listRegionM.indexOf(region);
- QModelIndex index = model()->index(row, 3,rootIndex());
- return index;
- }
- }
- return QModelIndex();
- }
- QModelIndex HistogramView::moveCursor(QAbstractItemView::CursorAction cursorAction,
- Qt::KeyboardModifiers modifiers)
- {}
- int HistogramView::horizontalOffset() const
- {
- }
- int HistogramView::verticalOffset() const
- {}
- bool HistogramView::isIndexHidden(const QModelIndex &index) const
- {}
- void HistogramView::setSelectionModel(QItemSelectionModel * selectionModel)
- {
- selections = selectionModel;
- }
- void HistogramView::mousePressEvent(QMouseEvent *e)
- {
- QAbstractItemView::mousePressEvent(e);
- setSelection(QRect(e->pos().x(),e->pos().y(),1,1),QItemSelectionModel::SelectCurrent);
- }
- QRegion HistogramView::itemRegion(QModelIndex index)
- {
- QRegion region;
- if (index.column() == 3)
- region = listRegionM[index.row()];
- return region;
- }
- void HistogramView::setSelection ( const QRect &rect, QItemSelectionModel::SelectionFlags flags )
- {
- int rows = model()->rowCount(rootIndex());
- int columns = model()->columnCount(rootIndex());
- QModelIndex selectedIndex;
- for (int row = 0; row < rows; ++row)
- {
- for (int column = 1; column < columns; ++column)
- {
- QModelIndex index = model()->index(row, column, rootIndex());
- QRegion region = itemRegion(index);
- if (!region.intersected(rect).isEmpty())
- selectedIndex = index;
- }
- }
- if(selectedIndex.isValid())
- selections->select(selectedIndex,flags);
- else
- {
- QModelIndex noIndex;
- selections->select(noIndex, flags);
- }
- }
- QRegion HistogramView::visualRegionForSelection(const QItemSelection &selection) const
- {}
- void HistogramView::selectionChanged(const QItemSelection & selected, const QItemSelection & deselected )
- {
- viewport()->update();
- }
- void HistogramView::paintEvent(QPaintEvent *)
- {
- QPainter painter(viewport());
- painter.setPen(Qt::black);
- int x0 = 40;
- int y0 = 250;
- // draw coordinate
- painter.drawLine(x0, y0, 40, 30);
- painter.drawLine(38, 32, 40, 30);
- painter.drawLine(40, 30, 42, 32);
- painter.drawText(5, 45, tr("income"));
- for (int i=1; i<5; i++) {
- painter.drawLine(-1,-i*50,1,-i*50);
- painter.drawText(-20,-i*50,tr("%1").arg(i*5));
- }
- // x轴
- painter.drawLine(x0, y0, 540, 250);
- painter.drawLine(538, 248, 540, 250);
- painter.drawLine(540, 250, 538, 252);
- painter.drawText(500, 270, tr("name"));
- int row;
- // name
- int posD = x0+20;
- for (row = 0; row < model()->rowCount(rootIndex()); row++)
- {
- QModelIndex index = model()->index(row, 0, rootIndex());
- QString dep = model()->data(index).toString();
- painter.drawText(posD,y0+20,dep);
- posD += 50;
- }
- // income
- int posM = x0+20;
- for (row = 0; row < model()->rowCount(rootIndex()); row++)
- {
- QModelIndex index = model()->index(row, 3, rootIndex());
- int income = model()->data(index).toDouble();
- int width = 10;
- if (selections->isSelected(index))
- painter.setBrush(QBrush(Qt::darkBlue,Qt::SolidPattern));
- else
- painter.setBrush(Qt::blue);
- painter.drawRect(QRectF(posM + 10, y0-income/25, width, income/25));
- QRegion regionM(posM + 10, y0-income/25, width, income/25);
- listRegionM << regionM;
- posM += 50;
- }
- }
分析:对父类的QAbstractItemView中的所有纯虚函数都必须进行声明,纯虚函数包括visualRect()、scrollTo()、indexAt()、moveCursor()、horizontalOffset()、verticalOffset()、isIndexHidden()、setSelection()和visualRegionForSelection(),这些函数并不一定都要实现,根据功能要求选择实现。