本片博客中代码仅使用于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;
}
}
以上就是在Qt4中自定义甘特图视图的全部内容。如有不明白的地方欢迎留言交流,若帖子中有错误的地方同样欢迎留言批评指正,在此谢过,欢迎骚扰。