ArcGIS engine中Display類庫——Display
轉自原文 ArcGIS engine中Display類庫——Display
Display類庫包括了用於顯示GIS數據的對象。除了負責實際輸出圖像的主要顯示對象(display object)外,這個類庫還包含了表示符號和顏色的對象,用於控制在顯示(display)中繪制時實體的屬性。這個類庫也包含了用戶與顯示(display)交互時的可視化反饋的對象。完成這些功能的對象被歸並到一組類庫子系統中。
這些類庫子系統是:
n Display
n Dynamic Display
n Colors
n Color Ramps
n Symbols
n Display Feedbacks
n Rubber Bands
n Trackers
n Representations
顯示(Display)
Display對象是對制圖表面的一種抽象。這個制圖表面是可以被Windows設備環境所表示的任何硬件設備、輸出文件或內存流。每個顯示(Display)都管理著自己的轉換對象,這些對象操縱著從現實世界空間到設備空間的坐標轉換,或從設備空間到現實世界空間的坐標轉換。下面提供了這些標準的顯示(Display):ScreenDisplay
顯示對象(the display object)使應用程序的開發人員很容易地在各種輸出設備上繪制圖形。這些對象使我們能夠把按現實世界坐標存儲的圖形渲染到屏幕、打印機和輸出文件中。應用程序的特征,如滾動,後備存儲,打印輸出,都能很容易的實現。如果某些需要的行為沒有被這些標準的對象所支持,我們可以通過實現一個或多個這些標準的顯示接口(the standard display interfaces)來生成自定義對象。
一般來說,窗口中任何繪圖都需要設備環境。HDC(設備環境句柄)就定義了我們繪圖下的設備環境。有許多設備,如窗口、打印機、位圖和元文件。在ArcObjects中,顯示(display)是對窗口設備環境的一種簡單的封裝。
當你想在打印機、輸出文件或簡單的預覽窗口中繪制圖形時,使用SimpleDisplay組件。如果你想使用StartDrawing,就需要指明HDC。這告訴顯示(display)繪制的環境是窗口、打印機、位圖還是元文件。HDC是通過調用ArcObjects以外的Windows GDI函數來生成的。
當你想繪制地圖到應用程序的主窗口時,使用ScreenDisplay組件。這個類用於處理高級的應用程序特征,如顯示緩存(Display Caching)和滾動條。記得指定相關窗口的HDC到StartDrawing。 正常情況下,當在應用程序的WM_PAINT處理中調用Windows GDI的BeginPaint函數時,會返回HDC。另外,我們也可以指定StartDrawing的參數HDC為0,相關窗口的HDC會自動生成。正常情況下,ScreenDisplay會使用內部的顯示緩存來提高制圖的性能。在繪制的過程中,輸出指向活動的緩存。每隔一秒,窗口(如StartDrawing的某個HDC)會持續地從活動緩存裏更新。如果你不希望持續地更新(例如,當繪制完成時,你只想更新窗口一次),可以為StartDrawing指定記錄緩存HDC(IScreenDisplay::CacheMemDC(esriScreenRecording))。
使用 IDisplay接口可以在設備上繪制點、線、多邊形、矩形和文本。這個接口也提供了訪問Display對象的DisplayTransformation對象。
DisplayTransformation ——這個對象定義了現實世界坐標如何映射到輸出設備裏。三個矩形區域定義了這個轉換。Bounds指定了真實世界坐標中的整個範圍。VisibleBounds指定了當前的可見範圍。DeviceFrame指定了輸出設備中VisibleBounds顯示的位置。既然DeviceFrame的屏幕高寬比不一定總是和VisibleBounds的屏幕高寬比相匹配,通過轉換計算出實際的可見bounds以滿足DeviceFrame。這被稱之為FittedBounds,它是現實世界坐標系。通過簡單地設置變換的Rotation屬性,所有的坐標系都能以可見區域的中心點進行旋轉。
顯示緩存—— Display Caching
這裏是顯示緩存(display caching)的基本原理。視圖(IActiveView)控制著主應用程序窗口。當前實現的視圖類有兩個:Map(數據視圖)和PageLayout(布局視圖)。ScreenDisplay使客戶生成任意數量的緩存(緩存其實只是獨立於設備的位圖)成為可能。當一個緩存生成時,客戶就獲得一個cacheID。這個ID能用來指明活動緩存(StartDrawing的最後一個參數,例如輸出的位置),是緩存無效,或者繪制緩存到目標HDC中。除了動態緩存外,ScreenDisplay也提供了一個記錄緩存來累計發生在Display上的所有繪制。客戶利用StartRecording和FinishRecording方法來管理記錄。
ArcObjects是如何來實現緩存的呢?讓我們來考察Map類。Map為所有的圖層生成一個緩存,如果存在註記或圖形的話會有另一緩存,如果存在要素選擇則會有第三個緩存。它也記錄了所有的輸出。(除了這些緩存外,還可以通過設置Cached屬性為True來為單個圖層請求一個私有的緩存。如果一個圖層請求了一個緩存,Map會為這個圖層分配一個單獨的緩存,並且根據圖層的上下位置把這個緩存歸並到不同的緩存中。)IActiveView::PartialRefresh利用對緩存布局的認知盡可能少地無效化緩存,便於我們盡可能多地從緩存裏繪圖。
利用緩存,實現下面的場景都是可能的:
l 當應用程序被移動或暴露(exposed),或者繪制圖形編輯的橡皮條(rubberbanding)時,使用記錄緩存來重繪。因為BitBlt只需使用一次,這是非常有效率的。
l 選擇一組新的要素,只使所選的緩存無效。要素、圖形和註記都從緩存裏繪制。只有所選的要素需從頭開始繪制。
l 從要素上挪開圖形元素或註記。只是使註記緩存無效。要素和要素選擇都從緩存裏繪制,只有註記從頭開始繪制。
l 生成一種叫動態圖層的新圖層。它的緩存屬性總是返回True。為了顯示車載GPS的運動軌跡,在圖層裏移動標記,只需要使動態圖層無效。所有其他的圖層都從緩存裏繪制。只有車輛圖層需要從頭繪制。這就可使得圖層有動態的效果。
l 通過移動幾個圖層到一個圖層組裏來生成一個基本地圖,並且設置這個圖層組的Cached屬性為Ture。現在,我們可以編輯和交互那些繪制在基本地圖頂端的圖層,而不必要從頭開始繪制這個基本地圖。
l 顯示過濾器(display filter)的概念是允許任何圖層執行柵格操作,這些圖層包括使用了自定義符號的要素圖層。這也可能生成一個依附於圖層的slider對話框。設置這個圖層的Cached屬性為True,使用透明的顯示過濾器和slider來交互地控制圖層的透明度。其他的顯示過濾器也能被用來實現裁剪、對比度和亮度等。
記錄緩存——Recording Cache
ScreenDisplay能夠記錄它要繪制的。利用StartRecording() 和 StopRecording()讓顯示(Display)知道什麽正是需要記錄的。利用DrawCache(esriScreenRecording)來顯示它所記錄的。對於記錄位圖(recording bitmap)利用get_CacheMemDC(esriScreenRecording)來獲取內存設備環境句柄。這個功能有幾個非常重要的用途。
- 首先,單一位圖的後備存儲很容易實現,其繪制過程如下:
[C#]
if ((m_pScreenDisplay.IsCacheDirty(esriScreenRecording)))
{
m_pScreenDisplay.StartRecording();
m_pDraw.StartDrawing(hDC, esriScreenCache.esriNoScreenCache);
DrawContents();
m_pDraw.FinishDrawing();
m_pScreenDisplay.StopRecording();
}
else
{
m_pScreenDisplay.DrawCache(Picture1.hDC, esriScreenCache.esriScreenRecording, 0, 0);
}
- 其次,客戶可以使用已分配好的顯示緩存(利用IScreenDisplay::AddCach來創建)來緩存視圖繪制的不同階段,然而為了快速地刷新,仍然可以使用單一的位圖(記錄)。
- 最後,當有興趣完成一些高級的渲染技術(如半透明)的繪制,我們可以使用記錄位圖。
Caveats
如果地圖的任何部分包含了透明,其刷新就都會受到影響。當繪制一個透明圖層時,該圖層下的一切都屬於其渲染的部分。因此,每當透明圖層的下面發生變化時,該圖層都必須重新開始繪制。
對Microsoft窗口,如果開啟了anti-aliasing設置,文本也會有透明度。這意味是文本使用在它下面繪制的圖層來實現anti_aliasing(文本的邊緣被混入到背景中)。因此,當圖層改變是,註記或自動標記都必須重新繪制。
How to cache layers
通過設置圖層的緩存標識來建立自己的顯示緩存。然後重新激活視圖。
[C#]
private void EnableLayerCaches()
{
int i;
for (i = 0; i <= m_pMap.LayerCount - 1; i++)
{
m_pMap.get_Layer(i).Cached = (chkCustomCaches.Value == 1 ? true : false);
}
...
pActiveView.Deactivate();
pActiveView.Activate(pActiveView.ScreenDisplay.hWnd);
pActiveView.Refresh();
}
旋轉——Rotation
理解顯示對象的旋轉如何實現是很重要的,因為它關系到所有實體的顯示。旋轉是發生在變換層級之後的,這樣DisplayTransformation客戶總是處理未旋轉的圖形。例如,當我們從一個變換的例行程序中取回一個圖形時,它是位於一個未旋轉的空間。同樣,當我們指定一個變換的範圍時,這個範圍也是位於未旋轉的空間。多邊形的旋轉所有的都執行正常。但對於封裝邊界(envelope),事情就復雜了,因為矩形的旋轉不可能表示出來。這能通過兩個例子很好地闡明:
l 從變換中獲取一個矩形
l 為變換指定一個矩形
從變換中獲取一個矩形。例如,假設你想獲取一個代表窗口客戶區域的矩形。既然用戶一直註視著旋轉的空間,請求的區域就不可能表示成一個Envelope。矩形的四個角在地圖空間裏都有唯一的x,y值。Envelope類內部的表示假定邊共享了x、y的值。因此,由DisplayTransformation來返回封裝邊界。FittedBounds不是我們所想要的,因為矩形多邊形被要求精確地表示出未旋轉的地圖空間下的客戶區域。當前存在一個bug,使得FittedBounds返回一個沒有旋轉的封裝邊界。當這個bug修復後,它返回的envelpe要比我們想象的稍微大一點。在旋轉的情況下,大部分客戶要避免使用封裝邊界,下面的代碼實現了通過匹配用戶顯示下的某個矩形來發現矩形多邊形:
[C#]
private void ToUnrotatedMap(tagRECT r, IGeometry pBounds, IDisplayTransformation pTransform)
{
WKSPoint[] mapPoints = new WKSPoint[5];
tagPOINT[] rectCorners = new tagPOINT[4];
rectCorners[0].x = r.left;
rectCorners[0].y = r.bottom;
rectCorners[1].x = r.left;
rectCorners[1].y = r.top;
rectCorners[2].x = r.right;
rectCorners[2].y = r.top;
rectCorners[3].x = r.right;
rectCorners[3].y = r.bottom;
//transform all 4 points.
pTransform.TransformCoords(ref mapPoints[0], ref rectCorners[0], 4, 4 | 1);
pTransform.TransformCoords(ref mapPoints[1], ref rectCorners[1], 4, 4 | 1);
pTransform.TransformCoords(ref mapPoints[2], ref rectCorners[2], 4, 4 | 1);
pTransform.TransformCoords(ref mapPoints[3], ref rectCorners[3], 4, 4 | 1);
// build polygon from mapPoints
mapPoints[4] = mapPoints[0];
IPointCollection pBoundsPointCollection;
ITopologicalOperator2 pBoundsTopologicalOperator2;
pBoundsPointCollection = (IPointCollection)pBounds;
pBoundsTopologicalOperator2 = (ITopologicalOperator2)pBounds;
pBoundsPointCollection.SetWKSPoints(1, ref mapPoints[0]);
pBoundsPointCollection.SetWKSPoints(1, ref mapPoints[1]);
pBoundsPointCollection.SetWKSPoints(1, ref mapPoints[2]);
pBoundsPointCollection.SetWKSPoints(1, ref mapPoints[3]);
pBoundsPointCollection.SetWKSPoints(1, ref mapPoints[4]);
pBoundsTopologicalOperator2.IsKnownSimple_2 = true;
}
為變換指定一個矩形。記得客戶需要在未旋轉的空間下工作,讓變換在顯示前來處理旋轉。一個簡單的例子如拖拽出縮放的矩形就是這種情況。首先,用戶看到變換的空間,拖放出的矩形是在旋轉的空間。(註意:這些工具使用像上面的代碼來生成直角多邊形,這種多邊形代表了用戶所選的區域)。在指定變換之前,需要利用工具轉換矩形到未旋轉的空間上。下面的代碼顯示了如何這樣做(pRotatedExtent是一個直角多邊形,它正好匹配用戶所拖拽出的矩形)。
[C#]
IArea pArea = pRotatedExtent;
IPoint pCenter = pArea.Centroid;
ITransform2D pTrans = pRotatedExtent;
pTrans.Rotate(pCenter, (90 * (3.1416 / 180)));
Refreshing versus Invalidation
為了促使顯示(display)的重繪,需要調用無效化(Invalidation)的操作。然而,大部分客戶從來不使用IScreenDisplay::Invalidate。原因在於,我們的應用程序中存在這樣的視圖,如Map或PageLayout類,由它來負責屏幕的刷新,如Refresh, PartialRefresh。這種視圖管理著顯示的緩存(the display‘s caches),知道執行無效的最好方式。只是盡可能地使用大部分指定的參數來確保PartialRefresh被調用。只有在絕對必要的情況下,我們才調用Refresh,因為這經常是一個耗時的操作。
為了讓視圖(Map和PageLayout)能完全管理顯示緩存(display caching),所有的無效都必須通過視圖(來調用)。調用IActiveView::Refresh總是繪制所有的,這種做法是非常沒有效率。所有應該盡可能對使用PartialRefresh方法。它讓我們指定部分視圖來重繪,使視圖結合顯示緩存來運作,這種方式就使得繪圖迅速和高效。
Draw Phase |
Map |
PageLayout |
esriViewBackground |
unused |
page/snap grid |
esriViewGeography |
layers |
unused |
esriViewGeoSelection |
feature selection |
unused |
esriViewGraphics |
labels/graphics |
graphics |
esriViewGraphicSelection |
graphic selection |
element selection |
esriViewForeground |
unused |
snap guides |
PartialRefresh的參數
下面的表格顯示了調用PartialRefresh方法的例子;註意參數選項的用法:
Action
|
C# Method Call
|
pMap.PartialRefresh(esriViewGeography, pLayer, null); |
|
pMap.PartialRefresh(esriViewGeography, null, null); |
|
pMap.PartialRefresh(esriViewGeoSelection, null, null); |
|
pMap.PartialRefresh(esriViewGraphics, null, null); |
|
pLayout.PartialRefresh(esriViewGraphics, pElement, null); |
|
pLayout.PartialRefresh(esriViewGraphics, null, null); |
|
pLayout.PartialRefresh(esriViewGraphicSelection, null, null); |
使用PartialRefresh的例子
註意:使任何階段(phase)無效化都會促使記錄緩存的無效。為了強制從記錄緩存中重繪,使用下面的調用:
[C#]
pScreenDisplay.Invalidate(null, FALSE, esriNoScreenCache);
Display Events
這節描述了地圖制圖的事件觸發。為了更好地理解繪圖事件,也會討論繪圖順序和顯示緩存。
Drawing Order
為了更好地理解繪圖的事件觸發,下面將描述每種視圖的繪圖順序。
Map(數據視圖)——下面顯示了從頂部到底部的順序,例如,上面的每一項都比下面的要先繪。
Object | Phase | Cache |
Graphic Selection
|
esriViewForeground
|
none
|
Clip Border
|
esriViewForeground
|
none
|
Feature Selection
|
esriViewGeoSelection
|
selection
|
Auto Labels
|
esriViewGraphics
|
annotation
|
Graphics
|
esriViewGraphics
|
annotation
|
Layer Annotation
|
esriViewGraphics
|
annotation
|
Layers
|
esriViewGeography
|
layer(s)
|
Background
|
esriViewBackground
|
bottom layer
|
Map類的繪制順序
PageLayout(布局視圖)——下面顯示了從頂部到底部的順序,例如,上面的每一項都比下面的要先繪。
Object | Phase | Cache |
Snap Guides
|
esriViewForeground
|
none
|
Selection
|
esriViewGraphicSelection
|
selection
|
Elements
|
esriViewGraphics
|
element
|
Snap Grid
|
esriViewBackground
|
element
|
Print Margins
|
esriViewBackground
|
element
|
Paper
|
esriViewBackground
|
element
|
PageLayout類的繪制順序
Drawing Events
利用下面的IActiveViewEvents事件向應用程序添加自定義的和繪制。
- AfterDraw(display, esriViewBackground)
- AfterDraw(display, esriViewGeography)
- AfterDraw(display, esriViewGeoSelection)
- AfterDraw(display, esriViewGraphics)
- AfterDraw(display, esriViewGraphicSelection)
- AfterDraw(display, esriViewForeground)
- AfterItemDraw(display, idx, esriDPGeography)
- AfterItemDraw(display, idx, esriDPAnnotation)
- AfterItemDraw(display, idx, esriDPSelection)
每個繪圖階段之後都會有AfterDraw事件的觸發。使用下面的方法繪制圖形到緩存中:
- 生成連接活動視圖(地圖)的對象。例如“Events”。
- 選擇繪制之後的繪圖階段(phase)。你選擇的這個階段(phase)決定了其繪制的先後順序。
- IActiveViewEvents::AfterDraw負責繪制。
不是所有的視圖都觸發所有的事件。此外,如果一個視圖被部分刷新,那麽從緩存裏繪圖的繪制階段就不會觸發AfterDraw事件。例如,如果選擇(要素)被刷新,那麽所有的圖層都從緩存裏繪制。這樣,AfterDraw(esriViewGeography)事件就不會被觸發。然而也有例外,對esriViewForeground而言,每次只要視圖繪制這個事件都會被觸發。即使從記錄緩存裏繪制,這個背景事件也會被觸發。
How to Enable item events with VerboseEvents
每個要素或圖形顯示時都會觸發AfterItemDraw事件,如果相連的句柄沒有效率的話就會嚴重影響制圖的性能。一般情況下客戶會連接AfterDraw事件。要註意檢查第二個參數是否是合適的繪制階段,因為當地圖繪制時,AfterDraw過程會被調用好幾次。
為了效率考慮,IActiveView 有一個叫VerboseEvents的屬性。它用來限制事件觸發的數目。如果VerboseEvents為false,AfterItemDraw就不會觸發。這是默認的設置。
Events and Display Caching
下面的表格顯示了當AfterDraw事件發生時的活動設備環境:
Event
|
Active HDC
|
window |
|
annotation cache |
|
selection cache |
|
top layer cache |
|
bottom layer cache |
Map類AfterDraw的設備環境
Event
|
Active HDC
|
window |
|
selection cache |
|
element cache |
|
element cache |
PageLayout類AfterDraw的設備環境
Create a private cache
在繪圖(events)時,我們可能想使用esriViewGraphics。AfterDraw有兩個參數,pDisplay和drawPhase。每個指定的階段都會調用AfterDraw來確保繪制。直接繪制到顯示(display)上,而不需擔心緩存。方法StartDrawing和FinishDrawing由Map來調用。如果我們繪制之後的階段被緩存了,那麽我們的繪制就自動緩存了。.
- 響應IDocumentEvents::ActiveViewChanged來生成緩存。Map生成它的緩存通過響應Activate,釋放它的緩存通過響應Deactivate。ActiveViewChanged事件是在Map生成完緩存之後觸發的,這樣如果內存不夠,地圖將獲取這個緩存,不過私有緩存不行。
[C#]
IActiveView pActiveView = pMap As IActiveView;
IScreenDisplay pScreen = pActiveView.ScreenDisplay;
pScreen.AddCache(m_myCacheID);
- AfterDraw 應該像這樣:
[C#]
if (phase != esriViewXXX)
{
return;
}
IScreenDisplay pScreen = pDisplay;
if ((!(pScreen == null)))
{
// Draw directly to output device
DrawMyStuff(pDisplay);
return;
}
// Draw to screen using cache if possible
long hWindowDC;
WindowDC = pScreen.WindowDC;
bool bDirty;
pScreen.IsCacheDirty(m_myCacheID, bDirty);
if ((bDirty))
{
// draw from scratch
pScreen.FinishDrawing();
pScreen.StartDrawing(hWindowDC, m_myCacheID);
DrawMyStuff(pDisplay);
}
else
{
// draw from cache
pScreen.DrawCache(hWindowDC, m_myCacheID, null, null);
}
ArcGIS engine中Display類庫——Display