Qt:甘特图控件

本片博客中代码仅使用于Qt4,Qt5中已经提供了QChart类无需在造轮子实现类似效果。

在这片博客中,我将详细讲述如何是通过QAbstractItemView定制一个GanttView。本篇博文中的代码参考自Qt4例子chart,有兴趣的同学可以查看chart代码深入理解如何定制自己的view控件。(注:在写这篇博客的代码时犯了懒癌,不想写注释……)

详细代码

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include "GanttView.h"
#include 
#include 

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    void initModel();
    void initView();

private:
    Ui::MainWindow *ui;

    QSplitter *_splitter;
    QAbstractItemModel *_model;
    QTableView *_table;
    GanttView *_ganttView;
    QItemSelectionModel *_selectionModel;
};

#endif // MAINWINDOW_H

MainWindow.cpp

#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    initModel();
    initView();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initModel()
{
    _model = new QStandardItemModel(5,3,this);
    _model->setHeaderData(0,Qt::Horizontal,QString("event"));
    _model->setHeaderData(1,Qt::Horizontal,QString("StartDate"));
    _model->setHeaderData(2,Qt::Horizontal,QString("EndDate"));

    QDate startDate = QDate::currentDate();
    for (int row = 0; row < _model->rowCount(); ++row)
    {
        _model->setData(_model->index(row,0),QString::number(row+1));
        _model->setData(_model->index(row,1),startDate.addDays(row));
        _model->setData(_model->index(row,2),startDate.addDays(row+5));
    }
}

void MainWindow::initView()
{
    _splitter = new QSplitter;

    _table = new QTableView;
    _table->setModel(_model);
    _table->setSelectionMode(QAbstractItemView::SingleSelection);
    _table->setSelectionBehavior(QAbstractItemView::SelectRows);
    _table->horizontalHeader()->setStretchLastSection(true);
    _table->horizontalHeader()->setDefaultSectionSize(100);
    _table->horizontalHeader()->setMinimumWidth(100);
    _table->verticalHeader()->setVisible(false);

    _ganttView = new GanttView;
    _ganttView->setModel(_model);

    QItemSelectionModel *selectionModel = new QItemSelectionModel(_model);
    _table->setSelectionModel(selectionModel);
    _ganttView->setSelectionModel(selectionModel);

    _splitter->addWidget(_table);
    _splitter->addWidget(_ganttView);

    _splitter->setStretchFactor(0, 2);
    _splitter->setStretchFactor(1, 3);

    setCentralWidget(_splitter);
}

GanttView.h

#ifndef GanttView_H
#define GanttView_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class GanttView : public QAbstractItemView
{
    Q_OBJECT
public:
    explicit GanttView(QWidget *parent = 0);
    ~GanttView(void);

    QRect visualRect(const QModelIndex &index) const;
    void scrollTo(const QModelIndex &, ScrollHint = EnsureVisible);
    QModelIndex indexAt(const QPoint &point) const;

protected slots:
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
    void rowsInserted(const QModelIndex &parent, int start, int end);
    void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);

signals:
    void sgCurrentSerialNumberChanged(const int sn) const;

protected:
    bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event);
    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers);

    int horizontalOffset() const;
    int verticalOffset() const;

    bool isIndexHidden(const QModelIndex &) const;

    void setSelection(const QRect&, QItemSelectionModel::SelectionFlags command);

    void mousePressEvent(QMouseEvent *event);

    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);

    void paintEvent(QPaintEvent *event);
    void resizeEvent(QResizeEvent *);
    void scrollContentsBy(int dx, int dy);

    QRegion visualRegionForSelection(const QItemSelection &selection) const;

private:
    QRect itemRect(const QModelIndex &item) const;
    QRegion itemRegion(const QModelIndex &index) const;
    int rows(const QModelIndex &index = QModelIndex()) const;
    void updateGeometries();
    void updateMetaData();

private:
    int _rectHeight;
    int _space;
    int _lengthX;
    int _lengthY;
    QPoint _origin;
    QRubberBand *_rubberBand;

    QDate _startDate;
    int _pixPerDay;

};

#endif // GanttView_H

GanttView.cpp

#include "GanttView.h"
#include 
#include 

GanttView::GanttView(QWidget *parent) : QAbstractItemView(parent)
{
    horizontalScrollBar()->setRange(0, 0);
    verticalScrollBar()->setRange(0, 0);

    _rubberBand = 0;
    _rectHeight = 20;
    _space = 0;
    _pixPerDay = 10;
    _lengthX = 100;
    _lengthY = 100;

    setEditTriggers(QAbstractItemView::NoEditTriggers);
}

GanttView::~GanttView()
{}

QRect GanttView::visualRect(const QModelIndex &index) const
{
    QRect rect = itemRect(index);
    if (rect.isValid())
        return QRect(rect.left() - horizontalScrollBar()->value(), rect.top() - verticalScrollBar()->value(), rect.width(), rect.height());
    else
        return rect;
}

void GanttView::scrollTo(const QModelIndex &/*index*/, QAbstractItemView::ScrollHint /*hint*/)
{
    update();
}

QModelIndex GanttView::indexAt(const QPoint &point) const
{
    int wx = point.x() + horizontalScrollBar()->value();
    int wy = point.y() + verticalScrollBar()->value();

    double cx = wx - 50;
    double cy = wy - 50;

    for (int row = 0; row < model()->rowCount(rootIndex()); ++row)
    {
        QDate currentStartDate = model()->data(model()->index(row,1)).toDate();
        QDate currentEndDate = model()->data(model()->index(row,2)).toDate();

        QPoint lt = QPoint(_startDate.daysTo(currentStartDate)*_pixPerDay,(_rectHeight+_space)*row);
        QPoint rd = QPoint(_startDate.daysTo(currentEndDate)*_pixPerDay,(_rectHeight+_space)*row+_rectHeight);

        if (cx>lt.x()&&cy>lt.y()&&cxindex(row, 0, rootIndex());
        }
    }

    return QModelIndex();
}

void GanttView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    QAbstractItemView::dataChanged(topLeft, bottomRight);
    updateMetaData();
    viewport()->update();
}

void GanttView::rowsInserted(const QModelIndex &parent, int start, int end)
{
    QAbstractItemView::rowsInserted(parent, start, end);
}

void GanttView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
}

bool GanttView::edit(const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event)
{
    if (index.column() == 0)
        return QAbstractItemView::edit(index, trigger, event);
    else
        return false;
}

QModelIndex GanttView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
    QModelIndex current = currentIndex();
    if (model() == NULL) return current;

    switch (cursorAction) {
    case MoveLeft:
    case MoveUp:
        if (current.row() > 0)
            current = model()->index(current.row() - 1, current.column(), rootIndex());
        else
            current = model()->index(0, current.column(), rootIndex());
        break;
    case MoveRight:
    case MoveDown:
        if (current.row() < rows(current) - 1)
            current = model()->index(current.row() + 1, current.column(), rootIndex());
        else
            current = model()->index(rows(current) - 1, current.column(), rootIndex());
        break;
    default:
        break;
    }

    viewport()->update();
    return current;
}

int GanttView::horizontalOffset() const
{
    return horizontalScrollBar()->value();
}

int GanttView::verticalOffset() const
{
    return verticalScrollBar()->value();
}

bool GanttView::isIndexHidden(const QModelIndex &/*index*/) const
{
    return false;
}

void GanttView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
    // Use content widget coordinates because we will use the itemRegion()
    // function to check for intersections.
    if (model() == NULL) return;

    QRect contentsRect = rect.translated( horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized();

    int rows = model()->rowCount(rootIndex());
    QModelIndexList indexes;

    for (int row = 0; row < rows; ++row)
    {
        QModelIndex index = model()->index(row, 0, rootIndex());
        QRegion region = itemRegion(index);
        if (!region.intersect(contentsRect).isEmpty())
            indexes.append(index);
    }

    if (indexes.size() > 0)
    {
        int firstRow = indexes[0].row();
        int lastRow = indexes[0].row();
        int firstColumn = indexes[0].column();
        int lastColumn = indexes[0].column();

        for (int i = 0; i < indexes.size(); ++i)
        {
            firstRow = qMin(firstRow, indexes[i].row());
            lastRow = qMax(lastRow, indexes[i].row());
            firstColumn = qMin(firstColumn, indexes[i].column());
            lastColumn = qMax(lastColumn, indexes[i].column());
        }

        QItemSelection selection( model()->index(firstRow, firstColumn, rootIndex()), model()->index(lastRow, lastColumn, rootIndex()));
        selectionModel()->select(selection, command);
    }
    else
    {
        QModelIndex noIndex;
        QItemSelection selection(noIndex, noIndex);
        selectionModel()->select(selection, command);
    }

    update();
}

void GanttView::mousePressEvent(QMouseEvent *event)
{
    QAbstractItemView::mousePressEvent(event);
    _origin = event->pos();
    if (!_rubberBand)
        _rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
    _rubberBand->setGeometry(QRect(_origin, QSize()));
    _rubberBand->show();
}

void GanttView::mouseMoveEvent(QMouseEvent *event)
{
    if (_rubberBand)
        _rubberBand->setGeometry(QRect(_origin, event->pos()).normalized());
    QAbstractItemView::mouseMoveEvent(event);
}

void GanttView::mouseReleaseEvent(QMouseEvent *event)
{
    QAbstractItemView::mouseReleaseEvent(event);
    if (_rubberBand)
        _rubberBand->hide();
    viewport()->update();
}

void GanttView::paintEvent(QPaintEvent *event)
{
    if (model() == NULL) return;

    QItemSelectionModel *selections = selectionModel();
    QStyleOptionViewItem option = viewOptions();

    QBrush background = option.palette.base();
    QPen foreground(option.palette.color(QPalette::WindowText));

    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing);

    painter.fillRect(event->rect(), background);
    painter.setPen(foreground);

    painter.save();
    painter.translate(50 - horizontalScrollBar()->value(),50-verticalScrollBar()->value());
    painter.drawLine(0,0,0,_lengthY);
    painter.drawLine(0,0,_lengthX,0);
    painter.drawText(-6,_lengthY+13,QString("sn"));
    painter.drawText(_lengthX+3,3,QString("day"));

    ///画Y轴箭头
    painter.translate(0,_lengthY);
    painter.drawLine(1,1,6,-8);
    painter.drawLine(-1,1,-6,-8);

    ///画X轴箭头
    painter.translate(_lengthX,-_lengthY);
    painter.drawLine(-1,-1,-8,-6);
    painter.drawLine(-1,1,-8,6);

    painter.translate(-_lengthX,0);

    /// 画X轴刻度
    for (int i = 0; i < _lengthX; i+=50)
    {
        painter.drawLine(i,0,i,5);
        painter.drawText(i - 7,-5,QString::number(i/10));
    }

    /// 画Y轴刻度
    for (int i = 1; i < model()->rowCount()+1; ++i)
    {
        painter.drawLine(0,(_rectHeight+_space)*i,5,(_rectHeight+_space)*i);
        painter.drawText(-15,(_rectHeight+_space)*i - 10,QString::number(i));
    }

    for (int row = 0; row < model()->rowCount(); ++row)
    {
        QDate startDate = model()->data(model()->index(row,1)).toDate();
        QDate endDate = model()->data(model()->index(row,2)).toDate();
        int duration = startDate.daysTo(endDate);

        QModelIndex index = model()->index(row, 0);

        if (currentIndex() == index && selections->isSelected(index))
            painter.setBrush(QBrush(QColor::fromHsvF(qreal(row)/qreal(model()->rowCount()),1.0,1.0), Qt::Dense4Pattern));
        else
            painter.setBrush(QBrush(QColor::fromHsvF(qreal(row)/qreal(model()->rowCount()),1.0,1.0)));

        painter.drawRect(_startDate.daysTo(startDate)*_pixPerDay,(_rectHeight+_space)*(row),duration*_pixPerDay,_rectHeight);
    }
    painter.restore();
}

void GanttView::resizeEvent(QResizeEvent */*event*/)
{
    updateGeometries();
}

void GanttView::scrollContentsBy(int dx, int dy)
{
    viewport()->scroll(dx, dy);
}

QRegion GanttView::visualRegionForSelection(const QItemSelection &selection) const
{
    if (model() == NULL) return QRect();;
    int ranges = selection.count();

    if (ranges == 0)
        return QRect();

    QRegion region;
    for (int i = 0; i < ranges; ++i) {
        QItemSelectionRange range = selection.at(i);
        for (int row = range.top(); row <= range.bottom(); ++row) {
            for (int col = range.left(); col <= range.right(); ++col) {
                QModelIndex index = model()->index(row, col, rootIndex());
                region += visualRect(index);
            }
        }
    }
    return region;
}

QRect GanttView::itemRect(const QModelIndex &index) const
{
    if (model() == NULL) return QRect();

    if (!index.isValid())
        return QRect();

    if (index.column() == 0)
    {
        return viewport()->rect();
    }

    return QRect();
}

QRegion GanttView::itemRegion(const QModelIndex &index) const
{
    if (!index.isValid())
        return QRegion();

    if (index.column() != 0)
        return itemRect(index);

    for (int row = 0; row < model()->rowCount(rootIndex()); ++row)
    {
        QModelIndex sliceIndex = model()->index(row, 0, rootIndex());

        QDate currentStartDate = model()->data(model()->index(row,1)).toDate();
        QDate currentEndDate = model()->data(model()->index(row,2)).toDate();
        int duration = currentStartDate.daysTo(currentEndDate)*_pixPerDay;

        if (sliceIndex == index)
        {
            QPainterPath path;
            path.addRect(_startDate.daysTo(currentStartDate)*_pixPerDay+50,(_rectHeight+_space)*row+50,duration,_rectHeight);
            return QRegion(path.toFillPolygon().toPolygon());
        }
    }

    return QRegion();
}

int GanttView::rows(const QModelIndex &index) const
{
    return model()->rowCount(model()->parent(index));
}

void GanttView::updateGeometries()
{
    if (NULL == model()) return;

    updateMetaData();

    horizontalScrollBar()->setPageStep(viewport()->width());
    horizontalScrollBar()->setRange(0, qMax(0, _lengthX - viewport()->width()));
    verticalScrollBar()->setPageStep(viewport()->height());
    verticalScrollBar()->setRange(0, qMax(0, _lengthY - viewport()->height()));
}

void GanttView::updateMetaData()
{
    _lengthX = 5;
    _lengthY = (_rectHeight+_space)*(model()->rowCount()+1);
    if (model())
    {
        _startDate = model()->data(model()->index(0,1)).toDate();

        for (int row = 0; row < model()->rowCount(); ++row)
        {
            QDate currentStartDate = model()->data(model()->index(row,1)).toDate();
            QDate currentEndDate = model()->data(model()->index(row,2)).toDate();
            _startDate = qMin(currentStartDate,_startDate);
            _lengthX = qMax(_lengthX,_startDate.daysTo(currentEndDate));
        }
        _lengthX *=10;
        _lengthX += 50;
    }
}

效果图

Qt:甘特图控件_第1张图片

以上就是在Qt4中自定义甘特图视图的全部内容。如有不明白的地方欢迎留言交流,若帖子中有错误的地方同样欢迎留言批评指正,在此谢过,欢迎骚扰。

你可能感兴趣的:(Qt)