建立自己的 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() 就都是滑鼠相關的事件函式,不過在這邊就先不多提了,之後有空再寫吧。


Nokia Qt 相關文章目錄

對「建立自己的 QGraphicsItem」的想法

  1. […] 之前在《建立自己的 QGraphicsItem》一篇文章裡,已經有大概提過要怎麼建立一個屬於自己、可以在 Qt 的 Graphics View 裡面使用的 QGraphicsItem 了。而這一篇,則算是一種延伸的特殊需要,主要是希望可以透過在 paint() 這個函式裡面做額外的控制,避免使用 QPainter 進行繪製的時候,會有畫筆(QPen)和筆刷(QBrush)一起受到 Graphics view 的 transformation(主要是放大和縮小)影響的問題。 […]

  2. […] 延續之前的和《建立自己的 QGraphicsItem》,在建立出自己的 Graphics Item 後,接下來來研究一下在 Graphics View 這個架構下的事件處理吧~基本上,在《Qt Graphics View Framework 簡介》一文中已經有很簡單地提到了,Qt 在 Graphics View 這個 Framework 下,處理事件的方式,是會由 View 來做最外層的接收、然後傳遞給 Scene,再視需要傳遞給個別的 Item。而要把事件處理的程式碼寫在哪一層呢?這就是要看自己的需求了~ […]

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料