欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

在Qt4中自定义View控件实现甘特图效果

程序员文章站 2022-05-22 12:41:42
...

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

详细代码

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSplitter>
#include <QTableView>
#include "GanttView.h"
#include <QStandardItemModel>
#include <QTableView>

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 <QAbstractItemView>
#include <QFont>
#include <QItemSelection>
#include <QItemSelectionModel>
#include <QStandardItemModel>
#include <QModelIndex>
#include <QRect>
#include <QSize>
#include <QPoint>
#include <QWidget>
#include <QDate>

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 <QtGui>
#include <QDate>

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()&&cx<rd.x()&&cy<rd.y())
        {
            return model()->index(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;
    }
}

执行效果

在Qt4中自定义View控件实现甘特图效果

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

相关标签: 甘特图 Qt