前話
Qt的圖形視圖框架,最核心的三個類為:QGraphicsScene、QGraphicsItem與QGraphicsView。
基於圖形框架的高級白板軟件Demo
QGraphicsScene類提供了一個用於管理大量二維圖形項的面。
該類用作QGraphicsItems的容器。它與QGraphicsView一起用於在二維面上可視化圖形項,例如線條、矩形、文本,甚至自定義項。QGraphicsScene是圖形視圖框架的一部分。
QGraphicScene還提供了一些功能,可以讓有效地確定項目的位置,以及確定在場景中任意區域內哪些項目可見。使用QGraphicsView小部件,可以可視化整個場景,或者放大並只查看場景的部分。
示例:
QGraphicsScene *pScene = new QGraphicsScene();
pScene->addText("Hello, world!");
QGraphicsView *pView = new QGraphicsView(pScene, this);
請注意,QGraphicScene本身沒有視覺外觀;它只管理項目。需要創建一個QGraphicsView小部件來可視化場景(設置它的父類為可視窗口,如QWidget)。
要將項目添加到場景中,首先要構造一個QGraphicsScene對象。然后,有兩個選項:要么通過調用addIitem()添加現有的QGraphicsItem對象(主要是自定義繼承的QGraphicsItem),要么調用便利函數addEllipse()、addLine()、addPath()、addPixmap()、addPolygon()、addRect()或addText(),這些函數都返回指向新添加項的指針。使用這些函數添加的項目的尺寸是相對於項目的坐標系的,並且項目位置在場景中初始化為(0,0)。
然后可以使用QGraphicsView可視化場景。當場景更改時(例如,當項目移動或轉換時),QGraphicScene會發出changed()信號。若要刪除項,請調用RemoveItem()。
QGraphicScene使用索引算法來有效地管理項目的位置。默認情況下,使用BSP(二進制空間分區)樹;該算法適用於大多數項目保持靜態(即不移動)的大型場景。可以通過調用setItemIndexMethod()來選擇禁用此索引。有關可用索引算法的詳細信息,請參閱ItemIndexMethod屬性。
場景的邊界矩形是通過調用setSceneRect()設置的。項目可以放置在場景的任何位置,默認情況下,場景的大小是無限的。場景矩形僅用於內部記賬,維護場景的項索引。如果場景rect未設置,QGraphicScene將使用itemsBoundingRect()返回的所有項的邊界區域作為場景矩形。但是,itemsBoundingRect()是一個相對耗時的函數,因為它通過收集場景中每個項目的位置信息來操作。因此,在大型場景上操作時,應始終設置場景矩形。
QGraphicScene最大的優點之一是它能夠有效地確定項目的位置。即使現場有數百萬個項目,items()函數也可以在幾毫秒內確定項目的位置。items()有幾個重載:一個在特定位置查找項目,一個在多邊形或矩形內或與多邊形或矩形相交的項目,等等。返回項目的列表按堆疊順序排序,最上面的項目是列表中的第一個項目。為了方便起見,還有一個itemAt()函數在給定位置返回最上面的項。
QGraphicScene維護場景的選擇信息。要選擇項,請調用setSelectionArea(),要清除當前選擇,請調用clearSelection()。調用selectedItems()以獲取所有選定項的列表。
QGraphicsScene的另一個職責是從QGraphicsView傳播事件。要將事件發送到場景,可以構造繼承QEvent的事件,然后使用QApplication::SendEvent()發送它。event()負責將事件分派給各個項目。一些常見事件由方便的事件處理程序處理。例如,按鍵事件由keypressEvent()處理,鼠標按鍵事件由mousePressEvent()處理。
關鍵事件傳遞到焦點項目。要設置焦點項,可以調用setFocusItem(),傳遞接受焦點的項,或者項本身可以調用QGraphicsItem::setFocus()。調用focusItem()以獲取當前焦點項。為了與小部件兼容,場景還維護自己的焦點信息。默認情況下,場景沒有焦點,所有關鍵事件都將被丟棄。如果調用了setFocus(),或者場景中的某個項目獲得焦點,則場景將自動獲得焦點。如果場景有焦點,hasFocus()將返回true,並且關鍵事件將轉發到焦點項(如果有的話)。如果場景失去焦點(即有人調用clearFocus()),而某個項目有焦點,則該場景將保持其項目焦點信息,並且一旦場景恢復焦點,它將確保最后一個焦點項目恢復焦點。
對於鼠標懸停效果,QGraphicsScene會發送懸停事件。如果一個項接受懸停事件(請參見QGraphicsItem::acceptHoverEvents()),則當鼠標進入其區域時,它將收到一個QGaphicsSceneHoverCenter事件。當鼠標繼續在項目區域內移動時,QGraphicsScene將向其發送GraphicsSceneHoverMove事件。當鼠標離開項目區域時,項目將收到一個GraphicsSceneHoverLeave事件。
所有鼠標事件都將傳遞到當前鼠標抓取器項。如果一個項目接受鼠標事件(請參見QGraphicsItem::acceptedMouseButtons())並接收到鼠標按下,它將成為場景的鼠標抓取器。它保持鼠標抓取器,直到在沒有其他鼠標按鈕被按下時收到鼠標釋放為止。可以調用mouseGramberItem()來確定當前正在獲取鼠標的項。
QGraphicsView類提供一個窗口,用於顯示QGraphicsScene的內容。
在QGraphicsView將可視內容滾動的視口中。幾何項創建場景的步驟參考,QGraphicsScene的文檔,QGraphicsView圖形視圖框架的一部分。
可視化一個場景,通過建構QGraphicsView通過對象的地址,可看QGraphicsView的構造函數,或者也可以隨后調用setScene()顯示。在你調用show(),視窗將默認將滾動到視圖中心並顯示可見的所有項目。例如:
QGraphicsScene *pScene = new QGraphicsScene();
pScene->addText("Hello, world!");
QGraphicsView *pView = new QGraphicsView(pScene, this);
可以使用滾動條或調用centerOn()顯式滾動到場景中的任何位置。通過將點傳遞給centerOn(),QGraphicsView將滾動其視區以確保該點在視圖中居中。提供了一個用於滾動到QGraphicsItem的重載,在這種情況下,QGraphicsView將看到該項的中心在視圖中居中。如果只想確保某個區域是可見的(但不一定是居中的),那么可以調用ensureVisible()。
QGraphicsView可以用來可視化整個場景,或者只顯示其中的一部分。默認情況下,在首次顯示視圖時自動檢測可視化區域(通過調用QGraphicsScene::itemsBoundingRect())。要自己設置可視化區域矩形,可以調用setScenRect()。這將適當調整滾動條的范圍。請注意,盡管場景支持幾乎不受限制的大小,但滾動條的范圍永遠不會超過整數的范圍(INT_MIN,INT_MAX)。
QGraphicsView通過調用render()可視化場景。默認情況下,通過使用常規的QPainer和默認的渲染提示將項目繪制到視區上。要在繪制項時更改QGraphicsView傳遞給QPainter的默認渲染提示,可以調用setRenderHints()。
默認情況下,QGraphicsView為viewport小部件提供常規的QWidget。可以通過調用viewport()來訪問這個小部件,也可以通過調用setViewport()來替換它。要使用OpenGL進行渲染,只需調用setViewPort(new QGLWidget)。QGraphicsView擁有viewport小部件的所有權。
QGraphicsView使用QTransform支持仿射轉換。可以將矩陣傳遞給setTransform(),也可以調用便利函數rotate()、scale()、translate()或shear()。最常見的兩種轉換是縮放,用於實現縮放和旋轉。QGraphicsView在轉換期間保持視圖中心不變。由於場景對齊(setAligment()),轉換視圖不會產生視覺影響。
可以使用鼠標和鍵盤與場景中的項目進行交互。QGraphicsView將鼠標和鍵事件轉換為場景事件(繼承QGraphicsSceneEvent的事件),並將它們轉發到可視化的場景。最后,處理事件並對其做出反應的是單個項目。例如,如果單擊一個可選擇的項,該項通常會讓場景知道它已被選中,並且它也會重新繪制自己以顯示一個選擇矩形。類似地,如果你點擊並拖動鼠標來移動一個可移動的項目,它就是處理鼠標移動和移動自己的項目。項交互在默認情況下是啟用的,可以通過調用setInteractive()來切換它。
還可以通過創建QGraphicsView的子類並重新實現鼠標和鍵事件處理程序來提供自己的自定義場景交互。為了簡化如何以編程方式與視圖中的項進行交互,QGraphicsView提供了映射函數mapToScene()和mapFromScene(),以及項訪問器items()和itemAt()。這些函數允許在視圖坐標和場景坐標之間映射點、矩形、多邊形和路徑,並使用視圖坐標在場景中查找項目。
QGraphicsItem類是QGraphicsScene中所有圖形項的基類。
它為編寫自己的自定義項目提供了輕量級的基礎。這包括通過事件處理程序定義項的幾何體、沖突檢測、其繪制實現和項交互。QGraphicsItem是圖形視圖框架的一部分。
為了方便起見,Qt為最常見的形狀提供了一組標准圖形項。這些是:
- QGraphicsEllipseItem 提供橢圓項
- QGraphicsLineItem 提供直線項
- QGraphicsPathItem 提供任意路徑項
- QGraphicsPixmapItem 提供pixmap項
- QGraphicsPolygonItem 提供多邊形項
- QGraphicsRectItem 提供矩形項
- QGraphicsSimpleTextItem 提供簡單的文本標簽項
- QGraphicsTextItem 提供高級文本瀏覽器項
(補充:若是為了深度使用,建議全部重寫一個公用積累,然后所有元素item自己寫,不使用自帶的。)
項目的所有幾何信息都基於其局部坐標系。項的位置pos()是唯一不在本地坐標中操作的函數,因為它返回父坐標中的位置。圖形視圖坐標系詳細描述了坐標系。
可以通過調用setVisible()來設置項是否應可見(即,繪制和接受事件)。隱藏項目也會隱藏其子項。同樣,可以通過調用setEnabled()來啟用或禁用項。如果禁用某個項目,它的所有子項也將被禁用。默認情況下,項目既可見又啟用。若要切換是否選擇項,請首先通過設置itemIsSelectable標志啟用選擇,然后調用setSelected()。通常,選擇由場景切換,這是用戶交互的結果。
要編寫自己的圖形項,首先要創建QGraphicsItem的子類,然后從實現它的兩個純虛擬公共函數開始:boundingRect(),它返回該項繪制的區域的估計值,paint()實現實際繪制。
示例:
class SimpleItem : public QGraphicsItem
{
public:
QRectF boundingRect() const
{
qreal penWidth = 1;
return QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->drawRoundedRect(-10, -10, 20, 20, 5, 5);
}
};
boundingRect()函數有許多不同的用途。QGraphicsScene基於boundingRect()建立其項索引,QGraphicsView使用它來剔除不可見項,以及確定繪制重疊項時需要重新編譯的區域。此外,QGraphicsItem的碰撞檢測機制使用boundingRect()提供有效的截止點。collistsWithItem()中的細粒度碰撞算法基於調用shape(),它以QPainterPath的形式返回項目形狀的准確輪廓。
QGraphicScene希望所有boundingRect()和shape()項保持不變,除非通知它。如果要以任何方式更改項的幾何圖形,必須首先調用prepareGeometryChange()以允許QgraphicsScene更新其記賬。
碰撞檢測有兩種方式:
- 重載shape()返回項的准確形狀,並依賴collapsWithItem()的默認實現來進行形狀交集。如果形狀復雜的話,這可能會很貴(指性能消耗)。
- 重載collistWithItem()提供自己的自定義項和形狀沖突算法。
可以調用contains()函數來確定項是否包含點。此函數也可以由項重新實現。contains()的默認行為基於調用shape()。
項可以包含其他項,也可以包含在其他項中。所有項都可以有父項和子項列表。除非該項沒有父項,否則它的位置在父坐標(即父項的局部坐標)中。父項將它們的位置及其轉換傳播到所有子項。
除了基礎位置pos()之外,QGraphicsItem還支持投影轉換。有幾種方法可以更改項的轉換。對於簡單的轉換,可以調用方便函數setRotation()或setScale(),也可以將任何轉換矩陣傳遞給setTransform()。對於高級轉換控制,還可以通過調用setTransforms()來設置多個組合轉換。
項目轉換從父項到子項疊加,因此如果父項和子項都旋轉90度,子項的總轉換將為180度。類似的,如果項目的父級縮放到原始大小的2倍,則其子級也將是原始大小的兩倍。項的轉換不會影響其自身的局部幾何圖形;所有幾何圖形函數(例如,contains()、update()和所有映射函數)仍在局部坐標中操作。為了方便起見,QGraphicsItem提供函數sceneTransform(),它返回項的總轉換矩陣(包括其位置以及所有父級位置和轉換),scenePos()返回其在場景坐標中的位置。若要重置項的矩陣,請調用resetTransform()。
某些轉換操作根據應用順序產生不同的結果。例如,如果縮放一個變換,然后旋轉它,可能會得到與先旋轉變換不同的結果。但是,在QGraphicsItem上設置轉換屬性的順序不會影響生成的轉換;QGraphicsItem始終以固定的、定義的順序應用這些屬性:
- 應用項的基礎轉換(transform())
- 按順序應用項的轉換列表(transforms())
- 該項相對於其轉換原點旋轉(rotation(),transformOriginPoint())
- 該項相對於其轉換原點進行縮放(scale(),transformOriginPoint())
QGraphicsView調用paint()函數來繪制項的內容。該項本身沒有背景或默認填充;該項后面的內容將在該函數中未顯式繪制的所有區域中發光。可以調用update()來計划重新繪制,也可以選擇傳遞需要重新繪制的矩形。根據該項在視圖中是否可見,可以重新繪制該項,也可以不重新繪制;在QGraphicsItem中沒有與QWidget::repaint()等效的項。
項由視圖繪制,從父項開始,然后按升序堆疊子項。可以通過調用setZValue()來設置項的堆棧順序,並通過調用zValue()來測試它,其中z值較低的項在z值較高的項之前繪制。堆疊順序適用於兄弟項;父項總是在其子項之前繪制。
排序
所有項目都以定義的、穩定的順序繪制,並且這個順序決定了當單擊場景時,哪些項目將首先接收鼠標輸入。通常,不必擔心排序,因為項目遵循“自然順序”,遵循場景的邏輯結構。
項目的子項堆疊在父項的頂部,兄弟項按插入順序堆疊(即,以添加到場景或添加到同一父項的相同順序)。如果添加了項目A,然后添加了B,那么B將位於A的頂部。如果添加了C,那么項目的堆疊順序將是A,然后添加了B,然后添加了C。
這個例子顯示了從拖放機器人的例子中機器人所有肢體的堆疊順序。軀干是根項(所有其他項都是軀干的子項或子項),因此首先繪制軀干。接下來,畫出頭部,因為它是軀干列表中的第一個項目。然后畫出左上臂。由於下臂是上臂的子對象,因此將繪制下臂,然后繪制上臂的下一個兄弟姐妹,即右上臂,依此類推。
對於高級用戶,有一些方法可以更改項目的排序方式:
- 可以對一個項調用setZValue()來顯式地將它堆疊在其他同級項的頂部或下方。項的默認Z值為0。具有相同Z值的項按插入順序堆疊。
- 可以調用stackBefore()對子項列表重新排序。這將直接修改插入順序。
- 可以將ItemStacksBhindParant標志設置為將子項堆疊在其父項之后。
兩個同級項的堆疊順序也計算每個項的子項和子項。因此,如果一個項目位於另一個項目之上,那么它的所有子項也將位於其他項目的所有子項之上。
QGraphicsItem通過虛擬函數sceneEvent()從QGraphicsScene接收事件。此函數將最常見的事件分發給一組方便的事件處理程序:
- contextMenuEvent()處理上下文菜單事件。
- focusInEvent()和focusOutEvent()處理焦點進入和退出事件。
- hoverEnterEvent()、hoverMoveEvent()和hoverLeaveEvent()處理hoverEnter、move和leave事件。
- inputMethodEvent()處理輸入事件,以獲得輔助功能支持,如中文輸入法獲取輸入內容。
- keyPressEvent()和keyReleaseEvent()處理按鍵和釋放事件。
- mousePressEvent()、mouseMoveEvent()、mouseReleaseEvent()和mouseDoubleClickEvent()處理鼠標按下、移動、釋放、單擊和雙擊事件。
可以通過安裝事件篩選器來篩選任何其他項目的事件。此功能與Qt的常規事件篩選器(請參閱QObject::InstallEventFilter())不同,后者只在QObject的子類上工作。通過調用InstallSceneEventFilter()將項目安裝為另一個項目的事件篩選器后,虛擬函數sceneEventFilter()將接收篩選的事件。可以通過調用removeSceneEventFilter()刪除項事件篩選器。
有時,將自定義數據注冊到一個項、自定義項或標准項是很有用的。可以對任何項調用setData()以使用鍵值對(鍵為整數,值為QVariant)來存儲其中的數據。要從項中獲取自定義數據,請調用data()。此功能完全不受qt本身的影響;它是為方便用戶而提供的。
此枚舉描述QGraphicsItem的緩存模式。緩存用於通過分配和渲染到非屏幕像素緩沖區來加快渲染速度,當項需要重新繪制時,可以重用該緩沖區。對於某些繪制設備,緩存直接存儲在圖形內存中,這使得渲染非常快速。
此枚舉描述由QGraphicsItem::itemChange()通知的狀態更改。通知將作為狀態更改發送,在某些情況下,可以進行調整(有關詳細信息,請參閱每個更改的文檔)。
注意:在itemChange()內小心調用QGraphicsItem本身的函數,因為某些函數調用可能導致不需要的遞歸。例如,不能對itemPositionChange通知調用itemChange()中的setPos(),因為setPos()函數將再次調用itemChange(itemPositionChange)。相反,可以從itemChange()返回新的已調整位置。
此枚舉描述可以在項上設置的不同標志,以切換項行為中的不同功能。默認情況下禁用所有標志。
注意:此標志類似於ItemContainChildrenInShape,但另外還通過剪切子項來強制包含。
注意:設置此標志后,仍然可以縮放項目本身,並且縮放轉換將影響項目的子項。
注意:如果同時設置了此標志和ItemClipsChildrenToShape,則將強制執行剪輯。這相當於只設置ItemClipsChildrenToShape。
GRaphicsItemFlags類型是QFlags的typedef。它存儲一個或多個GraphicsItemFlag值。
此枚舉指定模式面板的行為。模態面板是阻止輸入到其他面板的面板。請注意,屬於模式面板子級的項不會被阻止。
可取值為:
如果item接受懸停事件(QGraphicsSceneHoverEvent)將會返回true;否則,返回false。默認情況下,圖元項沒有接受hover事件。
這個功能是在QT 4.4引入的。
如果項接受觸摸事件,則返回true;否則返回false。默認情況下,項目不接受觸摸事件。
Qt4.6中引入了該功能。
返回此項接受鼠標事件的鼠標按鈕。默認情況下,接受所有鼠標按鈕。
如果一個項目接受鼠標按鈕,當鼠標按鈕的鼠標按下事件被傳遞時,它將成為鼠標抓取項目。但是,如果項目不接受按鈕,QGraphicsScene將把鼠標事件轉發到它下面的第一個項目。
此純虛擬函數將項的外部邊界定義為一個矩形;所有繪制必須限制在項的邊界矩形內。QGraphicsView使用它來確定項是否需要重新繪制。
雖然項目的形狀可以是任意的,但是邊界矩形始終是矩形的,並且不受項目轉換的影響。
如果要更改項的邊框,必須首先調用PrepareGeteryChange()。這將通知場景即將發生的更改,以便它可以更新其項幾何索引;否則,場景將不知道項的新幾何,並且結果未定義(通常,渲染工件留在視圖中)。
重新實現這個函數,讓QGraphicsView確定需要重新繪制小部件的哪些部分(如果有的話)。
注意:對於繪制輪廓/筆划的形狀,在邊界矩形中包含一半的筆寬是很重要的。但是,不需要補償抗鋸齒。
示例:
返回此項的邊界區域。返回區域的坐標空間取決於itemToDeviceTransform。如果將標識QTransform作為參數傳遞,則此函數將返回局部坐標區域。
邊界區域描述了項目視覺內容的粗略輪廓。雖然計算起來很昂貴,但它也比boundingrect()更精確,並且有助於避免在項目更新時不必要的重新繪制。這對於薄項目(如直線或簡單多邊形)尤其有效。可以通過調用 setBoundingRegionGranularity()來調整邊界區域的粒度。默認粒度為0;其中項的邊界區域與其邊界矩形相同。
itemTodeviceTransform是從項坐標到設備坐標的轉換。如果希望此函數返回場景坐標中的QRegion,可以將sceneTransform()作為參數傳遞。
這個功能是在QT 4.4引入的。
對於事件事件,可以重新實現此事件處理程序,以接收此項目的拖曳輸入事件。當光標進入項目區域時,將生成“拖動輸入”事件。
通過接受事件(即,通過調用qEvent:::accept()),項目將接受放置事件,除了接收拖動移動和拖動離開。否則,事件將被忽略並傳播到下面的項。如果該事件被接受,則在控件返回事件循環之前,該項將收到一個拖動移動事件。
DragEnterEvent的常見實現根據事件中關聯的MIME數據接受或忽略事件。例子:
對於事件事件,此事件處理程序可以重新實現以接收此項目的拖動移動事件。當光標在項目區域內移動時,會生成拖動移動事件。大多數情況下,不需要重新實現此函數;它用於指示只有項目的部分可以接受放置。
在事件上調用QEvent::Ignore()或QEvent::Accept()將切換該項是否接受從事件中刪除的位置。默認情況下,接受事件,表示項目允許在指定位置放置。
默認情況下,項目不接收拖放事件;若要啟用此功能,請調用setAcceptDrops(true)。
默認實現什么也不做。
對於事件事件,可以重新實現此事件處理程序以接收此項的放置事件。只有在最后一個拖動移動事件被接受時,項才能接收放置事件。
對事件調用QEvent::Ignore()或QEvent::Accept()無效。
默認情況下,項不接收拖放事件;若要啟用此功能,請調用setAcceptDrops(true)。
默認實現什么也不做。
對於事件事件,可以重新實現此事件處理程序以接收此項目的鼠標雙擊事件。
當雙擊某個項目時,該項目將首先收到一個鼠標按下事件,然后是一個釋放事件(即單擊),然后是一個雙擊事件,最后是一個釋放事件。
對事件調用QEvent::Ignore()或QEvent::Accept()無效。
默認實現調用mousePressEvent()。如果要在重新實現此函數時保留基本實現,請在重新實現中調用QGraphicsItem:: MouseDoubleClickevent()。
請注意,如果一個項目既不可選擇也不可移動,它將不會接收雙擊事件(在這種情況下,鼠標單擊將被忽略,並停止生成雙擊)。
返回此項的形狀作為本地坐標中的QPainterPath。該形狀用於許多事情,包括碰撞檢測、命中測試和QGraphicsScene::items()函數。
默認的實現調用boundingRect()返回一個簡單的矩形,但是子類可以重新實現這個函數來為非矩形項返回一個更精確的形狀。例如,圓形項目可以選擇返回橢圓形狀以更好地檢測碰撞。例如:
形狀的輪廓可以根據繪圖時使用的筆的寬度和樣式而變化。如果要將此輪廓包含在項的形狀中,可以使用QPainterPathStroker從筆划創建形狀。
此函數由contains()和collipswithpath()的默認實現調用。
上一篇:《Qt開發技術:圖形視圖框架(一)基本介紹》
下一篇:敬請期待