您的位置:首页 > 编程语言 > Qt开发

Qt浅谈之十九:Model/View实现表格和统计图

2014-08-21 16:35 225 查看


一、简介

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

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

#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

#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的表格数据进行显示。

#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
#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(),这些函数并不一定都要实现,根据功能要求选择实现。


四、总结

(1)Mode/View结构比较难于理解,在此先作简单的介绍,以后再进行更加深入的研究。

(2)感兴趣的可以下载源码分析,并在其基础上进行自己的开发,本人水平有限,也只能提供代码供读者延伸。

(3)源码已经打包上传到csdn上可登录下载(http://download.csdn.net/detail/taiyang1987912/7797583)。

(4)若有更好的设计建议,也可发邮件沟通,在此先感谢!邮箱地址yang.ao@i-soft.com.cn。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: