建立自己的 QGraphicsItem

延續上一篇《Qt Graphics View Framework 簡介》,在大概介紹過整個 Qt Graphics View Framework 後,這一篇先大概簡單介紹一下,要怎麼建立一個可以在 Qt Graphics View 架構裡使用的 QGraphicsItem

基本上,在 Qt Graphics View 的架構下,所有要畫出來、要做互動的東西,都是要透過「場景」(Scene),也就是 QGraphicsScene 來做管理和操作的;而他能使用的物件,都必須要是型別為 QGraphicsItem 的物件。所以如果我們需要自己訂一個新的、可以在 Qt Graphics View 中使用的物體,就必須要繼承 QGraphicsItem、來寫出自己的物件類別。

而要寫出一個這樣的類別呢,除了要繼承 QGraphicsItem 之外,還必須要實作兩個必要的函式:boundingRect()paint()。下面的 CItem 就是一個最簡單的形式:

class CItem : public QGraphicsItem
{
public:
QRectF boundingRect() const
{
//...
}

void paint( QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget )
{
//...
}
};

其中,paint() 這個函式就是實際用來畫這個物件的函式,希望這個物件顯示什麼樣子,就是在這個函式裡時做了∼而基本上,這邊就是用所給的 QPainter、在 item 座標系統上來進行繪製了∼QPainter 基本上提供了相當多繪製用的函式,應該是可以滿足絕大部分的需求的;這部分的細節,就請參考 QPainter 官方的說明吧。

除了用來繪圖的 QPainter 外,這個函式會取得的參數還有 QStyleOptionGraphicsItemQWidget。前者是一些在 graphics view 架構下的特殊參數,包括了 LOD(Level-of-detail)的參數、會顯示出來的區域等細節資訊,如果在物件複雜的時候,透過這些參數來做繪圖的內容控制,可以有效地增進效率;而後者則是代表要被拿來畫的 widget,不過一般情況下應該都會是 0(NULL)、可以不需要理他。

另外,boundingRect() 這個函式必須要傳回 QRectF,代表這個物件所佔的矩形範圍、讓 QGraphicsScene 可以在空間上做物件的管理,也可以讓 QGraphicsView 知道這個物件是否需要重新繪製、更新。而他所使用的座標系統,就是它自身的 Item 座標系統,Qt 在內部處理的時候,會自動處理他在場景裡的 transformation。

由於 boundingRect() 回傳的資料只是一個矩形的範圍(rectangle)、概略性地代表這個物件所佔的範圍,所以並不能完整地代表這個物體實際的範圍;而在某些應用上可能會需要物件的實際邊界(例如碰撞偵測),這時候就還需要另外實作 shape() 這個函式來回傳完整的輪廓資料。

而上面的程式碼只是一個空殼,並不能實際拿來用;下面的程式碼,則就是把必要的元素補齊、可以拿來用的一個 item 的類別了∼

class CItem : public QGraphicsItem
{
public:
CItem()
{
m_qBrush.setColor( QColor::fromRgb( 255, 0, 255 ) );
m_qBrush.setStyle( Qt::SolidPattern );
QVector<QPointF> v;
{
v.push_back( QPointF(  20,   0 ) );
v.push_back( QPointF(   0,  20 ) );
v.push_back( QPointF( -20,   0 ) );
v.push_back( QPointF(   0, -20 ) );
}
m_qPath.addPolygon( QPolygonF( v ) );
}

QRectF boundingRect() const
{
return m_qPath.boundingRect();
}

QPainterPath shape() const
{
return m_qPath;
}

virtual void paint( QPainter *painter,
                      const QStyleOptionGraphicsItem *option, QWidget *widget )
{
painter->fillPath( m_qPath, m_qBrush );
}

protected:
QBrush        m_qBrush;
QPainterPath m_qPath;
};

在上面的程式碼裡,CItem 這個類別裡有型別分別為 QBrushQPainterPath 的兩個資料:m_Brushm_qPath,分別代表了要繪製的畫筆、以及要畫的路徑。而這兩個資料,則是在 CItem 的建構子裡進行設定;基本上,Heresy 這邊是將 CItem 設定為一個紫色的菱形(右圖,或者說轉 45 度的正方形)。

而在建立好資料後,在 paint() 裡,就可以透過 QPainterfillPath() 這個函式,來用指定的畫筆(m_Brush)、填滿指定的路徑(m_qPath)。另外,在 boundingRect()shape() 裡,也就可以使用已經設定好的路徑,還回傳需要的資料了∼

所以,在這個簡單的例子裡,除了建構子裡的程式碼比較複雜外,其他幾個必須要實作的函式,其實內容都相當地簡單。不過實際上,也不一定要這樣都把東西寫在建構子裡,像 Qt 官方的範例(頁面)就是沒有建構子、直接把相關的程式碼寫在 paint()boundingRect() 裡;所以,要怎麼寫就是看需求了∼

如此一來,這個 CItem 就是一個可以在 Qt Graphics View 架構下、讓 Scene 使用的 item 了∼下面,就是一個簡單的使用範例:

// 2a. add customize item 1
CItem* pItem1 = new CItem();
scene.addItem( pItem1 );

// 2b. add customize item 2
CItem* pItem2 = new CItem();
scene.addItem( pItem2 );
pItem2->translate( 50, 50 );
pItem2->rotate( 45 );

右圖,就是這樣的 scene 畫出來的樣子了∼

而從上面的程式碼的「2b」部分,應該可以發發現,由於 CItem 是繼承 QGraphicsItem 的,所以也可以直接用 QGraphicsItem 所定義好的各種函式,包括這邊所使用的 translate()rotate() 等等。所以,在 Qt Graphics View 的架構下,要定義並使用自訂的 item,算是相當方便的∼


到這邊為止,就是建立一個可以一般使用、並畫出東西的 item 了。而實際上,在 Qt 的架構下,也還可以透過重新時做各式各樣的 event 函式,來賦予它更多的功能;例如 mousePressEvent()mouseReleaseEvent()mouseDoubleClickEvent() 就都是滑鼠相關的事件函式,不過在這邊就先不多提了,之後有空再寫吧。

4 thoughts on “建立自己的 QGraphicsItem”

  1. 您好,我现在想在qgraphicview中绘制一个骨骼点的运动轨迹(点的数据从用户骨骼点坐标获取)。您这篇中讲的这个方法我觉得可行,但是我现在是想画一条线,实现起来有什么不同呢?

    顺便提一点,实现这个类的头文件是什么?有没完整的代码贴出来呢?

  2. QRectF boundingRect() const
    {
    QRectF qRect( m_aJoints[0].X, m_aJoints[0].Y, 0, 0 );//可以解释下这句么?难道矩形区域就用一个点表示?最后两个参数为什么是0?
    for( unsigned int i = 1; i < 15; i ) { if( m_aJoints[i].X < qRect.left() ) qRect.setLeft( m_aJoints[i].X ); if( m_aJoints[i].X > qRect.right() )
    qRect.setRight( m_aJoints[i].X );

    if( m_aJoints[i].Y < qRect.top() ) qRect.setTop( m_aJoints[i].Y ); if( m_aJoints[i].Y > qRect.bottom() )
    qRect.setBottom( m_aJoints[i].Y );
    }
    return qRect;
    }

    for循环里也简要解释下,谢谢。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。