QT自定義圖形項中的boundingRect()和shape()函數的理解
實現自定義圖形項經常需要重繪的函數有boundingRect()、paint()、shape()。
針對霍亞飛的Qt creator中所說,boundingRect()函數具有以下特點:
1.paint繪制的圖像必須在boundingRect()函數之中。
2.用來確定哪些區域需要重構(repaint)。
3.用來檢測碰撞
其中第二個功能在幫助文檔中沒有看到(可能英語水平不過關),故而通過一次小測試借以理解以上函數:
對第一點和第二點的理解:為什么圖像的繪制必須在boundingRect()函數所確定的Rect之中。
第一個測試:我們把圖像畫到boundingRect()的所設置的矩形外邊,並且想辦法觀察到重繪的情況
項目建立之類的就不說了,自行參照書本。
首先搭建個測試的框架:
添加一個繼承自QGraphicsItem的MyIetm和繼承自QGraphicssView的MyView,兩個類的內容如下:
//myitem.h內容如下 #ifndef MYITEM_H #define MYITEM_H #include <QGraphicsItem> class MyItem : public QGraphicsItem { public: MyItem(); QRectF boundingRect()const override; void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget)override; private: void drawRectPath(QPainter *painter); }; #endif // MYITEM_H
//myitem.cpp內容如下: #include "myitem.h" #include <QPainter> MyItem::MyItem() { } QRectF MyItem::boundingRect()const { qreal penwidth=1; return QRectF(-50-penwidth/2,-50-penwidth/2,100+penwidth,100+penwidth); } void MyItem::drawRectPath(QPainter *painter){ QPainterPath rectPath; rectPath.moveTo(-50,-50); rectPath.lineTo(50,-50); rectPath.lineTo(50,50); rectPath.lineTo(-50,50); rectPath.closeSubpath();//返回繪圖開始點 painter->setPen(QPen(Qt::red,20,Qt::SolidLine,Qt::SquareCap,Qt::MiterJoin));//pen參數別設置錯了,要不不好看出來 painter->drawPath(rectPath); //在之前的繪圖上我們繪制出QboundingRect的虛線方框 painter->setPen(QPen(Qt::black,1,Qt::DotLine,Qt::SquareCap,Qt::MiterJoin)); painter->drawRect(-50,-50,100,100); } void MyItem::paint(QPainter *painter ,const QStyleOptionGraphicsItem *,QWidget *){ drawRectPath(painter); }
//myview.h什么也不需要改動 #ifndef MYVIEW_H #define MYVIEW_H #include <QGraphicsView> class MyView : public QGraphicsView { public: MyView(); }; #endif // MYVIEW_H
//myview.cpp內容如下: #include "myview.h" MyView::MyView() { }
//main.cpp文件 #include <QApplication> #include "myitem.h" #include <QGraphicsScene> #include "myview.h" int main(int argv,char* argc[]){ QApplication app(argv,argc); MyItem *item1=new MyItem; MyItem *item2=new MyItem; item1->setPos(0,0); item2->setPos(150,150); QGraphicsScene scene; scene.addItem(item1); scene.addItem(item2); MyView view; view.setScene(&scene); view.resize(600,600); view.show(); return app.exec(); }
繪制的圖形如下:
由上圖,雖然我們繪制的圖像和boundingRect返回的QRect是一樣大的,但是因為我們的pen寬度,繪制的圖形已經超出了boudingRect.
為了看到視圖更新的效果,我們為圖形項添加移動效果:
//在myitem.h中: //添加 protected: void keyPressEvent(QKeyEvent *event)override; void mousePressEvent(QGraphicsSceneMouseEvent *event)override;
//在myitem.cpp中: //更改MyItem()函數為: MyItem::MyItem() { //設置可以被移動以及獲得焦點,缺一不可 setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsFocusable); } //添加 //上下左右移動圖形項 void MyItem::keyPressEvent(QKeyEvent *event){ switch (event->key()) { case Qt::Key_Left:{ moveBy(-1,0); break; } case Qt::Key_Up:{ moveBy(0,-1); break; } case Qt::Key_Right:{ moveBy(1,0); break; } case Qt::Key_Down:{ moveBy(0,1); break; } } } //鼠標點擊獲得焦點 void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *){ setFocus(); }
好了,點中方框圖形(點擊在虛線里邊),就可以上下左右鍵移動了,試一試吧,也許我們已經可以猜到結果了:
理解這一點基礎在於QGraphicsView的一個屬性ViewportUpdateMode,可以通過
void setViewportUpdateMode(QGraphicsView::ViewportUpdateMode mode)
進行設定:
總共有五種模式,很容易理解:
QGraphicsView::FullViewportUpdate 全視口更新,整體都更新的意思啦
QGraphicsView::MinimalViewportUpdate 最小更新,哪里有變動更新哪里
QGraphicsView::SmartViewportUpdate 智能選擇,它要自己選
QGraphicsView::BoundingRectViewportUpdate 來了,來了,它就是我們要注意的。
QGraphicsView::NoViewportUpdate 不更新
其中默認為QGraphicsView::MinimalViewportUpdate,也就是上例中我們沒有進行設置的情況。事實上除了設置為FullViewportUpdate 其余四種皆會出現問題,不妨試一試。
我們可以通過在MyView的構造函數中設置為FullViewportUpdate 的全視口更新得到我們想要的結果,但是卻是以犧牲性能為代價的。
理解第三點,用來檢測碰撞
第二個測試:
在第一個測試基礎上做以下更改:
MyItem.h中做添加
private: void drawRectPath(QPainter *painter);//繪制矩形不在使用 void drawtTriangle(QPainter *painter);//添加繪制三角形
在MyItem.cpp中更改三處
//第一處,實現drawtTriangle(QPainter *painter) void MyItem::drawtTriangle(QPainter *painter){ QPainterPath trianglePath; trianglePath.moveTo(0,-50); trianglePath.lineTo(50,50); trianglePath.lineTo(-50,50); trianglePath.closeSubpath(); painter->setPen(QPen(Qt::red,1,Qt::SolidLine,Qt::SquareCap,Qt::MiterJoin)); painter->drawPath(trianglePath); }
//第二處 重新實現paint void MyItem::paint(QPainter *painter ,const QStyleOptionGraphicsItem *,QWidget *){ //繪制boundingRect的QRect以方便查看 painter->setPen(QPen(Qt::black,1,Qt::DotLine,Qt::SquareCap,Qt::MiterJoin)); painter->drawRect(-50,-50,100,100); if(hasFocus()&&!collidingItems().isEmpty()){ //判斷是否有在獲得焦點的同時有碰撞 painter->setBrush(QColor(Qt::black)); //若有碰撞則繪制的圖形將以黑色作為畫刷填充 } drawtTriangle(painter);//這次是三角形 }
//第三處:在keyPressEvent()函數中添加一個旋轉的響應(按下R鍵) void MyItem::keyPressEvent(QKeyEvent *event){ switch (event->key()) { case Qt::Key_Left:{ moveBy(-1,0); break; } case Qt::Key_Up:{ moveBy(0,-1); break; } case Qt::Key_Right:{ moveBy(1,0); break; } case Qt::Key_Down:{ moveBy(0,1); break; } case Qt::Key_R:{ //按下R鍵旋轉90度 setRotation(90); break; } } }
結果如下:
虛線接觸時發生了碰撞,為了避免繪制的虛線對結果的影響,我們注釋掉虛線部分
void MyItem::paint(QPainter *painter ,const QStyleOptionGraphicsItem *,QWidget *){ // painter->setPen(QPen(Qt::black,1,Qt::DotLine,Qt::SquareCap,Qt::MiterJoin)); // painter->drawRect(-50,-50,100,100); if(hasFocus()&&!collidingItems().isEmpty()){ painter->setBrush(QColor(Qt::black)); .............
結果如下:
還沒碰到呢,就已經變黑了(檢測到碰撞了),可以得出結論了:
boundingRect與碰撞檢測很明顯是相關的 ;
但boundingRect返回的是矩形框,很明顯不符合我們三角形碰撞的需求;
這就到了shape函數了,它返回的是QPainterPath故而可以是任何形狀
myItem.h函數中添加新函數shape
//myitem.h ......... protected: void keyPressEvent(QKeyEvent *event)override; void mousePressEvent(QGraphicsSceneMouseEvent *event)override; QPainterPath shape()const override;//重寫shape函數
shape函數的實現
//myitem.cpp ...... QPainterPath MyItem::shape()const{ //shape()函數返回一個一樣的三角形路徑 QPainterPath trianglePath; trianglePath.moveTo(0,-50); trianglePath.lineTo(50,50); trianglePath.lineTo(-50,50); trianglePath.closeSubpath(); return trianglePath; }
結果正如我們所需的: