QT自定義圖形項中的boundingRect()和shape()函數的理解


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;
}

結果正如我們所需的:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM