本节代码多处为了方便简洁,应该是用了些《QT creater 快速入门第三版 霍亚飞著》的一些代码,在此感谢。(尽管有些项目的代码已经改到六亲不认的地步了)。以前自己玩QT4的时候也是学了这本书,之后自己做的项目里很多代码都是直接在已经写了一些代码的文件上进行修改的。学习是一边学一边封装成自己的工具,然后再使用。在此也感谢一下《C++ GUI Qt4编程》,《QT5开发及实例》等书。但是通过一些注释记起原书里面的代码应该也是有点小毛病,后面的交互则对里面的问题进行了不少修改。大家可以对照着看一下。
终于写到最后部分的内容了。我们要实现鼠标拖动图元,以及限定图元的拖动范围。下面我们重新封装两个类:为了不冲突,我们定义为 MyGraphicsItem2 和 MyGraphicsView2 。然后开始实现这些类:
#pragma once
#ifndef __MyGraphicsView2_H__
#define __MyGraphicsView2_H__
#include <QGraphicsView>
class MyGraphicsView2 : public QGraphicsView {
Q_OBJECT
public:
MyGraphicsView2(QGraphicsView * parent = Q_NULLPTR);
~MyGraphicsView2();
protected:
void keyPressEvent(QKeyEvent *event);
private:
};
#endif
#include "mygraphicsview2.hpp"
#include <QKeyEvent>
MyGraphicsView2::MyGraphicsView2(QGraphicsView * parent) : QGraphicsView(parent) {
}
MyGraphicsView2::~MyGraphicsView2() {
}
void MyGraphicsView2::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_Plus:
scale(1.2, 1.2);
break;
case Qt::Key_Minus:
scale(1 / 1.2, 1 / 1.2);
break;
case Qt::Key_Right:
rotate(30);
break;
}
QGraphicsView::keyPressEvent(event);//最后一定要调用,不然就再也无法接收信息了
}
这个类就只定义了一个键盘响应事件,接收到键盘事件,然后就根据内容进行视口放大缩小,以及视口旋转,这应该都是书上的内容(毕竟我不会闲得无聊让视口来旋转。)
主要是MyGraphicsItem2这个类。
上首先我们先定义头文件:
#pragma once
#ifndef __MyGraphicsItem2_H__
#define __MyGraphicsItem2_H__
#include <QGraphicsItem>
class MyGraphicsItem2 : public QGraphicsItem {
//千万要注意!!! 这里不能添加Q_OBJECT
//否则会报一大堆错 因为它并不是继承自QObject
public:
MyGraphicsItem2(QGraphicsItem * parent = Q_NULLPTR);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
void setColor(const QColor &color) { brushColor = color; }
MyGraphicsItem2::~MyGraphicsItem2();
private:
QColor brushColor;
protected:
void keyPressEvent(QKeyEvent *event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void MyGraphicsItem2::mouseMoveEvent(QGraphicsSceneMouseEvent* pEvent);
};
#endif
注意里面有很多函数,首先先注意里面实现的两个虚函数。前面讲过了。然后是setColor,用来给一个变量名叫“刷子的颜色”的东西设置颜色。
然后我们定义了键盘事件,已经鼠标按下事件,鼠标松开事件,鼠标悬停在某个位置的事件,图形项鼠标右键时显示右键菜单的事件,以及鼠标移动事件,和图形项改变事件。
下面看各个函数的实现:
首先包含头文件:
#include "mygraphicsitem2.hpp"
#include <QPainter>
#include <QCursor>
#include <QKeyEvent>
#include <QGraphicsScene>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
然后定义构造和析构函数:
MyGraphicsItem2::MyGraphicsItem2(QGraphicsItem * parent) : QGraphicsItem(parent) {
brushColor = Qt::red;
setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges);
setAcceptHoverEvents(true);
}
MyGraphicsItem2::~MyGraphicsItem2() {
}
首先把刷子设置为红色作为初始颜色。然后设置Flag,设置为可设置为焦点,可选择,可移动,以及可以发送几何改变的信号(这个用于限制图元的运动范围)。之前好像没说过什么是焦点,这里提一句,所谓焦点,当你选中某个图形项(以及任何控件)的时候,它就被设置为焦点了。这个时候你操作鼠标,键盘,如果它自己有相应的事件响应函数,那么它就会先捕获。如果它没有相应的事件响应函数,那么它的父类会捕获事件响应。
下面再看:
QRectF MyGraphicsItem2::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-10 - adjust, -10 - adjust,
20 + adjust, 20 + adjust);
}
void MyGraphicsItem2::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
if (hasFocus()) {
painter->setPen(QPen(QColor(255, 255, 255, 200)));
}
else {
painter->setPen(QPen(QColor(100, 100, 100, 100)));
}
painter->setBrush(brushColor);
painter->drawRect(-10, -10, 20, 20);
}
首先看第一个函数,这样做也就是把正方形的中心设置为item原点了。
第二个函数,如果有焦点,则画的时候就把边界画亮,如果没有,就还是画成以前的灰色。
然后是:
// 鼠标按下事件处理函数,设置被点击的图形项获得焦点,并改变光标外观
void MyGraphicsItem2::mousePressEvent(QGraphicsSceneMouseEvent * pEvent)
{
QGraphicsItem::mousePressEvent(pEvent);
setFocus();
setCursor(Qt::ClosedHandCursor);
}
void MyGraphicsItem2::mouseReleaseEvent(QGraphicsSceneMouseEvent * pEvent)
{
QGraphicsItem::mouseReleaseEvent(pEvent);
clearFocus();
setCursor(Qt::OpenHandCursor);
}
也就是说点中的图形项会获得焦点(之前设置的图形项属性是可获得焦点的),以及设置手型工具变成抓握的。然后第二个函数是鼠标松开函数,会清除焦点。然后设置手型工具再把手伸开。
注意两个函数前面一定要设置
QGraphicsItem::mouseReleaseEvent(pEvent);
以及
QGraphicsItem::mousePressEvent(pEvent);
否则你会惊喜的发现图形项们会在视图里面玩“漂移大法”。大家可以试试体会一下(原书里因为只实现了QGraphicsItem::mousePressEvent(pEvent);函数,所以并没有什么影响,但是如果同时定义Press和Release函数就不行了)。
定义下面的函数:
// 键盘按下事件处理函数,判断是否是向下方向键,如果是,则向下移动图形项
void MyGraphicsItem2::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Down)
moveBy(0, 10);
}
void MyGraphicsItem2::mouseMoveEvent(QGraphicsSceneMouseEvent* pEvent)
{
QGraphicsItem::mouseMoveEvent(pEvent);
}
// 悬停事件处理函数,设置光标外观和提示
void MyGraphicsItem2::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
setCursor(Qt::OpenHandCursor);
setToolTip("I am item");
}
// 右键菜单事件处理函数,为图形项添加一个右键菜单
void MyGraphicsItem2::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
QMenu menu;
QAction *moveAction = menu.addAction("move back");
QAction *selectedAction = menu.exec(event->screenPos());
if (selectedAction == moveAction) {
setPos(0, 0);
}
}
这里没什么好解释的。注意鼠标移动事件的自调用。然后设置鼠标悬停的事件以及右键菜单。效果大家看看就好了,这是为了显得完整才搞上来的。
然后,看最后一个函数:
QVariant MyGraphicsItem2::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene())
{
QPointF newPos = value.toPointF();
QRectF rect(40, this->pos().y(), 300, 0);
if (!rect.contains(newPos))
{
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
我们定义一个函数,当你移动图形项的时候,这个函数就会被触发,首先判断场景是否改变,以及图形项位置是否改变,如果是,首先定义一个点,作为我们要移动过去的目标位置。注意定义了一个矩阵,这个矩阵设置了可以移动的矩阵范围。因为这里的高设置为了0,所以它不能向上移动。
之后再判断移动的目标位置,如果目标位置不在这个范围里面,那么就根据矩形范围来设置你能移动到的边界点。
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
首先比较当前坐标点和左边谁的大,然后针对大的再跟右边边界比,选个小的作为横坐标。第二行纵坐标也类似。(原谅我表达不行,这其实很好理解的,大家画画图理解一下就好了)
我们在主函数里,或者任意构造函数(要被主函数调用哦)里写如下代码:
//自定义图形项
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
QGraphicsScene *scene2 = new QGraphicsScene;
for (int i = 0; i < 5; ++i) {
MyGraphicsItem2 *item2 = new MyGraphicsItem2;
item2->setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
item2->setPos(i * 50 + 90, 50); //注意这个偏移是根据scene场景偏移的。
scene2->addItem(item2);
}
MyGraphicsView2 * view2 = new MyGraphicsView2;
view2->setScene(scene2);
view2->setBackgroundBrush(QPixmap("./ShowDebug/background.png"));
view2->resize(400, 300);
scene2->setSceneRect(0, 0, width(), height());
view2->show();
各位注意肯定是书上定义了一个随机数(我个人是不会这么无聊的~~),然后给图形项通过随机数赋颜色,之后再分配其在场景中的位置,之后把场景的坐标和视口(物理区域)坐标设置重合,然后显示如下:
左上角的五个小正方形只能在横坐标为40——340的区域运动。
现在我们已经万事俱备了,接下来,就开始我们的终极目标:传输函数控件的设计。