对于QT自定义窗口部件,那么绘制一个或者几个项来说,QPainter肯定是最理想的啦,
那么,但是如果需要处理从几个到几万的项时,而且用户能够单击,拖动和选取这些项的时候QPainter就变得不再适用了、那么QT视图类提供了这一解决方案。
我们先看下一些宏的定义以及成员函数的实现吧。
Q_DECLARE_TR_FUNCTIONS()
这是一个宏定义,主要用来完成tr() 和 trUtf8()这两个功能。
Foreach(Link *link, myLinks)
Delete link;
Foreach(variable, container);
Foreach也是一个宏,用来完成foreach的循环,查找variable参数中与container参数类型一致的variable,并且通过delete来删除这个节点。
PrepareGeometryChange()在影响项的变化修改之前立即调用。
Node::Node()
{
myTextColor = Qt::darkGreen;
myOutlineColor = Qt::darkBlue;
myBackgroundColor = Qt::white;
setFlags(ItemIsMovable | ItemIsSelectable);
}
Node构造函数用于初始化颜色,ITemIsMovable 和ITemIsSelectbale用来支持鼠标移动、单击或者拖动项 和 选择项
Link::Link(Node *fromNode, Node *toNode)
{
myFromNode = fromNode;
myToNode = toNode;
myFromNode->addLink(this);
myToNode->addLink(this);
setFlags(QGraphicsItem::ItemIsSelectable);
setZValue(-1);
setColor(Qt::darkRed);
trackNodes();
}
这里通过函数指针传递函数,下面就直接使用指针变量来调用函数了。
QRectF Node::outlineRect() const
{
const int Padding = 8;
QFontMetricsF metrics = qApp->font();
QRectF rect = metrics.boundingRect(myText);
rect.adjust(-Padding, -Padding, +Padding, +Padding);
rect.translate(-rect.center());
return rect;
}
QPainterPath path;
path.addRoundRect(rect, roundness(rect.width()),
roundness(rect.height()));
增加一个圆角矩形到QPainterPath对象中,roundness返回一个合适的圆角率,圆角率的范围必须是在0到99之间。
void Node::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget * /* widget */)
{
QPen pen(myOutlineColor);
if (option->state & QStyle::State_Selected) {
pen.setStyle(Qt::DotLine);
pen.setWidth(2);
}
painter->setPen(pen);
painter->setBrush(myBackgroundColor);
QRectF rect = outlineRect();
painter->drawRoundRect(rect, roundness(rect.width()),
roundness(rect.height()));
painter->setPen(myTextColor);
painter->drawText(rect, Qt::AlignCenter, myText);
}
Paint函数就是绘制项的地方, QStyleOptionGraphicsItem继承于QStyleOption,state返回QStyle::State默认状态下指明widget没有状态,这里判断项是否被选中,如果被选中,则将笔画的风格设置为点线型,下面是点线型的风格图,并且设置线宽为2个像素, ,然后绘制一个与边缘矩形大小相等的圆角矩形,最后,在圆角矩形的上面绘制文字,使其居于边缘矩形。
QVariant Node::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == ItemPositionHasChanged) {
foreach (Link *link, myLinks)
link->trackNodes();
}
return QGraphicsItem::itemChange(change, value);
}
一旦用户拖动一个节点,就会调用itemChange()处理器,由ItemPositionChanged作为第一个参数,如果,发生改变,遍历节点的Link集合,通知每一条线更新他们的端点,最后,调用基类的实现以确保基类也得到了通知。
void Node::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
QString text = QInputDialog::getText(event->widget(),
tr("Edit Text"), tr("Enter new text:"),
QLineEdit::Normal, myText);
if (!text.isEmpty())
setText(text);
}
如果用户双击该节点,就会弹出显示当前文字的对话框,并且允许用户修改它,如果用户点击了Cancle,将会返回一个空字符串,因此,当字符串不为空时修改才会有效。
我们定义了项类的实现,我们就来使用它吧
DiagramWindow::DiagramWindow()
{
scene = new QGraphicsScene(0, 0, 600, 500);
view = new QGraphicsView;
view->setScene(scene);
view->setDragMode(QGraphicsView::RubberBandDrag);
view->setRenderHints(QPainter::Antialiasing
| QPainter::TextAntialiasing);
view->setContextMenuPolicy(Qt::ActionsContextMenu);
setCentralWidget(view);
minZ = 0;
maxZ = 0;
seqNumber = 0;
createActions();
createMenus();
createToolBars();
connect(scene, SIGNAL(selectionChanged()),
this, SLOT(updateActions()));
setWindowTitle(tr("Diagram"));
updateActions();
}
我们从创建一个图形场景开始,QGraphicsScene提供2D表面图形的管理,和QGraphicsView配合使用,QGraphicsView用来显示QGraphicsScene中的场景。
setScene(scene)设置当前的场景到scene, setDragMode当鼠标按下左键并且拖动时产生相应的动作,QGraphicsView::RubberBandDrag意味着可以圈选它们。
setRenderHints让边沿显得更加平滑。
setContextMenuPolicy指出这个Widget怎么显示菜单。
minZ 和maxZ数字用在sendToBack() 和 bringToFront()函数中,序号用来为用户添加每一个节点提供一个唯一的初始化文字。
接下来我们就看看到底怎么去使用这些定义好的项,当然我们需要创建一个菜单栏,工具栏。createActions();createMenus();createToolBars();这里完成这些工作我们不再讨论,我们看添加新节点:
void DiagramWindow::addNode()
{
Node *node = new Node;
node->setText(tr("Node %1").arg(seqNumber + 1));
setupNode(node);
}
创建一个Node对象来产生一个新的节点,用setupNode来定位和选取它。
void DiagramWindow::setupNode(Node *node)
{
node->setPos(QPoint(80 + (100 * (seqNumber % 5)),
80 + (50 * ((seqNumber / 5) % 7))));
scene->addItem(node);
++seqNumber;
scene->clearSelection();
node->setSelected(true);
bringToFront();
}
Node继承于QGraphicsItem,当然QGraphicsItem类中的成员函数也是可以使用的,用来给node定位,用addItem来增加项和所以子项到场景当中,每次新建Node 都要用clearSelection清除当前的选中状态,否则会出现每次新建的node都处于选中状态,并且用setSelected来选中当前新建的那个Node节点,