Supported with:
- Engine
- ArcView
- ArcEditor
- ArcInfo
- Server
Library dependencies: System, SystemUI, GeometryAdditional library information: Contents,
Object Model Diagram
The Display library contains objects used for the display of GIS data. In addition to the main display objects that are responsible for the actual output of the image, the library contains objects that represent symbols and colors, controlling the properties of entities drawn on the display. The library also contains objects that provide the user with visual feedback when interacting with the display.
The objects that implement this functionality are grouped into a number of library subsystems. These library subsystems are: Display
The Display object abstracts a drawing surface. A drawing surface is any hardware device, export file, or memory stream that can be represented by a Windows Device Context. Each display manages its own Transform object which handles the conversion of coordinates from real-world space to device space and back. The following standard displays are provided: ScreenDisplay abstracts a normal application window. It implements scrolling and backing store (multiple backing layers are possible). SimpleDisplay abstracts all other devices that can be rendered to using a Windows Device Context, such as printers, metafiles, bitmaps, and secondary windows.
The display objects allow application developers to easily draw graphics on a variety of output devices. These objects allow you to render shapes stored in real-world coordinates to the screen, the printer, and export files. Application features such as scrolling, backing store, print "tiling", and printing to a frame can be easily implemented. If some desired behavior is not supported by the standard objects, custom objects can be created by implementing one or more of the standard display interfaces.
In general, you must have a device context to do any drawing in windows. The HDC (handle to a device context) defines the device that you are drawing on. Some example devices are windows, printers, bitmaps, and metafiles. In ArcObjects, a display is simply a wrapper for a windows device context.
Use a SimpleDisplay Component when you want to draw on a printer, export file, or simple preview window. Specify the HDC that you want to use to StartDrawing. This tells the display which window, printer, bitmap, or metafile to draw on. The HDC is created outside of ArcObjects using a Windows GDI function call.
Use a ScreenDisplay Component when you want to draw maps to the main window of your application. This Class handles advanced application features such as display caching and scroll bars. Specify the HDC for the associated window to StartDrawing. Normally, this is the HDC returned when you call the Windows GDI BeginPaint function in your application's WM_PAINT handler. Alternatively, you may specify 0 as the HDC parameter for StartDrawing, and an HDC for the associated window is automatically created. Normally, a ScreenDisplay uses internal display caches to boost drawing performance. During a drawing sequence, output is directed to the active cache and once per second, the window (i.e., the HDC specified to StartDrawing) is progressively updated from the active cache. If you want to prevent progressive updates (i.e., if you would rather update the window once when drawing has completed), specify the recording cache HDC (IScreenDisplay::CacheMemDC(esriScreenRecording)) to StartDrawing.
Use the IDisplay interface to draw points, lines, polygons, rectangles, and text on a device. Access to the display object's DisplayTransformation object is provided by this interface.
DisplayTransformation – This object defines how real-world coordinates are mapped to a output device. Three rectangles define the transformation. The Bounds specifies the full extent in real-world coordinates. The VisibleBounds specifies what extent is currently visible. And the DeviceFrame specifies where the VisibleBounds appears on the output device. Since the aspect ratio of the DeviceFrame may not always match the aspect ratio of the specified VisibleBounds, the transformation calculates the actual visible bounds that fits the DeviceFrame. This is called the FittedBounds and is in real-world coordinates. All coordinates can be rotated about the center of the visible bounds by simply setting the transformation’s Rotation property.
Display Caching
Here's the basic idea behind display caching. The main application window is controlled by a view (IActiveView). There are currently two view Classes implemented: Map (data view) and PageLayout (layout view). ScreenDisplay makes it possible for clients to create any number of caches (a cache is just a device dependent bitmap). When a cache is created, the client gets a cacheID. The id is used to specify the active cache (last argument of StartDrawing, i.e., where output is directed), invalidate the cache, or draw the cache to a destination HDC. In addition to dynamic caches, the ScreenDisplay also provides a recording cache that accumulates all drawing that happens on the display. Clients manage recording using the StartRecording and FinishRecording methods.
To see how caches are implemented within ArcObjects the Map Class will be examined. In the simplest case, Map creates one cache for all the layers, another cache if there are annotation or graphics, and a third cache if there's a feature selection. It also records all output. (In addition to these caches, it's also possible for individual layers to request a private cache by returning true for their Cached property. If a layer requests a cache, the Map creates a separate cache for the layer and groups the layers above and below it into different caches.) The IActiveView::PartialRefresh method uses it's knowledge of the cache layout to invalidate as little as possible so that we can draw as much as possible from cache. Given these caches, all of the following scenarios are possible:
- Use the recording cache to redraw when the application is moved or exposed or when drawing editing rubberbanding. This is very efficient since only one BitBlt is needed.
- Select a new set of features, invalidate only the selection cache. Features draw from cache. Graphics and annotation draw from cache. Only the feature selection draws from scratch.
- Move a graphic element or annotation over some features. Only invalidate the annotation cache. Features draw from cache. Feature selection draws from cache. Only the annotation draws from scratch.
- Create a new kind of layer called a tracking layer. Always return true for it's cached property. To show vehicle movement when new GPS signals are received, move the markers in the layer and only invalidate the tracking layer. All other layers draw from cache. Only the vehicle layer draws from scratch. This makes it possible to animate a layer.
- Create a basemap by moving several layers into a group layer and setting the group layer's Cached property to true. Now you can edit and interact with layers that draw on top of the basemap without having to redraw the basemap from scratch.
- The concept of a display filter allows raster operations to be performed on any kind of layer including feature layers that use custom symbols. It will be possible to create a slider dialog that attaches to a layer. It sets the layer's Cached property to true and uses a transparency display filter to interactively control the layer's transparency using the slider. Other display filters can be created to implement clipping, contrast, brightness, etc.
Recording Cache
The ScreenDisplay can record what gets drawn. Use StartRecording() and StopRecording() to let the display know what exactly should be recorded. Use DrawCache(esriScreenRecording) to display what was recorded. Use get_CacheMemDC(esriScreenRecording) to get a handle to the memory device context for the recording bitmap. This functionality has several important uses.
- First, a single bitmap backing store is simple to implement, just use a drawing sequence like the following:
[VB.NET]
If (m_pScreenDisplay.IsCacheDirty(CShort(esriScreenCache.esriScreenRecording))) Then
m_pScreenDisplay.StartRecording()
m_pDraw.StartDrawing(hDC, CShort(esriScreenCache.esriNoScreenCache))
DrawContents()
m_pDraw.FinishDrawing()
m_pScreenDisplay.StopRecording()
Else
m_pScreenDisplay.DrawCache(Picture1.hDC, CShort(esriScreenCache.esriScreenRecording, 0, 0))
End If
[C#]
if ((m_pScreenDisplay.IsCacheDirty((short)esriScreenCache.esriScreenRecording)))
{
m_pScreenDisplay.StartRecording();
m_pDraw.StartDrawing(hDC, (short)esriScreenCache.esriNoScreenCache);
DrawContents();
m_pDraw.FinishDrawing();
m_pScreenDisplay.StopRecording();
}
else
{
m_pScreenDisplay.DrawCache(Picture1.hDC, (short)
esriScreenCache.esriScreenRecording, 0, 0);
}
- Second, clients can use allocated display caches (created with IScreenDisplay::AddCache) to cache different phases of their view drawing while still having a single bitmap (the recording) to use for quick refreshes. Finally, you can access the recording bitmap while drawing to implement interesting advanced rendering techniques such as translucency.
Caveats
If any part of your map contains transparency it will affect how things get refreshed. When a transparent layer draws, everything below it, becomes part of the layer's rendering. As a result, the transparent layer must redraw from scratch every time that something below it changes.
Text also has transparency if the anti-aliasing setting is turned on in Microsoft Windows. This means that the text uses the layers that draw below it to carry out the anti-aliasing (the edges of the text are blended into the background). As a result, the annotation or auto-labels must draw whenever a layer changes.
How to cache layers
Set the cached flag on the layers you want to have their own display cache. Then reactivate the view.
[VB.NET]
Private Sub EnableLayerCaches()
Dim i As Integer
For i = 0 To m_pMap.LayerCount - 1 Step i + 1
m_pMap.get_Layer(i).Cached = (chkCustomCaches.Value = IIf(1 , True , False))
Next
...
pActiveView.Deactivate()
pActiveView.Activate(pActiveView.ScreenDisplay.hWnd)
pActiveView.Refresh()
End Sub
[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
Understanding how rotatation is implemented within the display objects is important since it affects all entities that are displayed. Rotation happens below the transformation level so clients of DisplayTransformation always deal with unrotated shapes. For example, when you get a shape back from one of the transform routines, it's in unrotated space. Also, when you specify an extent to the transform, the extent is also in unrotated space. When working with polygons everything just works. When working with envelopes, things are more complicated because rotated rectangles cannont be represented. This is best illustrated with 2 examples:
- Getting a rectangle from the transform
- Specifying a rectangle to the transform
Getting a rectangle from the transform. For example, let's say you want a rectangle representing the client area of the window. Since the user is seeing rotated space on the display, it's not possible to represent the requested area as an Envelope. The four corners of the rectangle have unique x and y values in map space. The internal representation of the Envelope Class assumes shared x and y values for the sides. As a result the envelope returned by DisplayTransformation.FittedBounds isn't really what you want because a rectangular polygon is needed to accurately represent the client area in unrotated map space. Currently there's a bug that causes FittedBounds to return the same envelope that is returned when there is no rotation. When this is fixed, it should return a slightly expanded envelope as you expected. Most clients avoid using an envelope when there is rotation and implement code like the following to find the rectangular polygon that matches some rectangle on the user's display:
[VB.NET]
Private Sub ToUnrotatedMap(ByVal r As tagRECT, ByVal pBounds As IGeomeTry, ByVal pTransform As IDisplayTransformation)
Dim mapPoints() As WKSPoint = New WKSPoint(5)
Dim rectCorners() As tagPOINT = 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( mapPoints(0), rectCorners(0), 4, 4 | 1)
pTransform.TransformCoords( mapPoints(1), rectCorners(1), 4, 4 | 1)
pTransform.TransformCoords( mapPoints(2), rectCorners(2), 4, 4 | 1)
pTransform.TransformCoords( mapPoints(3), rectCorners(3), 4, 4 | 1)
' build polygon from mapPoints
mapPoints(4) = mapPoints(0)
Dim pBoundsPointCollection As IPointCollection
Dim pBoundsTopologicalOperator2 As ITopologicalOperator2
pBoundsPointCollection = CType(pBounds, IPointCollection)
pBoundsTopologicalOperator2 = CType(pBounds, ITopologicalOperator2)
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
End Sub
[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;
}
Specifying a rectangle to the transform. Remember that the client needs to work in unrotated space and let the transform handle the rotation just before displaying. What does this mean in the simple case of dragging a zoom rectangle. First, the user sees rotation space. The rectangle they drag is in rotation space. (Note: the tool uses code like above to create a rectangular polygon representing the area selected by the user.) The tool needs to convert the rectangle to unrotated space before specifying it to the transform. The following code shows how to do this (pRotatedExtent is a rectangular polygon that exactly matches the rectangle dragged by the user):
[VB.NET]
Dim pArea As IArea = pRotatedExtent
Dim pCenter As IPoint = pArea.Centroid
Dim pTrans As ITransform2D = pRotatedExtent
pTrans.Rotate(pCenter, (90 * (3.1416 / 180)))
[C#]
IArea pArea = pRotatedExtent;
IPoint pCenter = pArea.Centroid;
ITransform2D pTrans = pRotatedExtent;
pTrans.Rotate(pCenter, (90 * (3.1416 / 180)));
Refreshing versus Invalidation
In order to cause a display to redraw, the Invalidate routine must be called. Most clients never use IScreenDisplay::Invalidate however. The reason is that if there is a view being used in your application, i.e., the Map or PageLayout Class, the view should be used for refreshing the screen, i.e., Refresh, PartialRefresh. The view manages the display's caches and knows the best way to carry out invalidation. Just make sure PartialRefresh is called using the most specific arguments possible. Also, only call Refresh when absolutely necessary since this is usually a very time consuming operation.
One Stop Invalidation - In order to allow views (Map and PageLayout) to completely manage display caching, all invalidation must go through the view. Calling IActiveView::Refresh always draws everything. This is very inefficient. The method called PartialRefresh should be used whenever possible. It lets you specify what part of the view to redraw and allows the view to work with display caches in a way the allows drawing to be quick and efficient.
|
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 |
Arguments for PartialRefresh
The table below shows some example calls to the partial refresh method; note the use of optional arguments:
|
Action
|
VB.Net/C# Method Call
|
| Refresh Layer |
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeography, pLayer, Nothing)
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeography, pLayer, null);
|
| Refresh All Layers |
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeography, Nothing, Nothing)
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
|
| Refresh Selection |
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, Nothing, Nothing)
pMap.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null);
|
| Refresh Labels |
pMap.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
pMap.PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, null);
|
| Refresh Element |
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphics, pElement, Nothing)
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphics, pElement, null);
|
| Refresh All Elements |
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, null);
|
| Refresh Selection |
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphicSelection, Nothing, Nothing)
pLayout.PartialRefresh(esriViewDrawPhase.esriViewGraphicSelection, null, null);
|
Example uses of PartialRefresh
Note: invalidating any phase will cause the recording cache to be invalidated. To force a redraw from the recording cache, use the following call:
[VB.NET]
pScreenDisplay.Invalidate(Nothing, False, CShort(esriScreenCache.esriNoScreenCache))
[C#]
pScreenDisplay.Invalidate(null, FALSE, (short)esriScreenCache.esriNoScreenCache)
;
Display Events
This section describes the events that are fired when a map draws. To provide better insight into drawing events, drawing order and display caching are discussed as well.
Drawing Order
To better understand the events that are fired when a map draws, the following section describes the order that objects are drawn for each kind of view.
Map (data view) – The following shows top to bottom order, i.e., each item is drawn above the items below it:
|
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 Class Drawing Order
PageLayout – The following shows top to bottom order, i.e., each item is drawn above the items below it:
|
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 class drawing order
Drawing Events
The following IActiveViewEvents events can be used to add custom drawing to your application:
- 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)
The AfterDraw event is fired after every phase of drawing. To draw a graphic into a cache, use the following design:
- Create an object that connects to the active view (map). For example “Events”.
- Pick the draw phase that you want to draw after. The phase you pick determines what gets drawn above and below you.
- Draw in response to IActiveViewEvents::AfterDraw
Not all of the views fire all of the events. Additionally, if a view is partially refreshed, the phases that draw from cache do not fire their AfterDraw event. For example, if the selection is refreshed, all of the layers draw from cache. As a result, the AfterDraw(esriViewGeography) event does not fire. However, there is an exception, in the case of esriViewForeground, the event is fired everytime the view draws. Even if everything in the map draws from the recording cache, the foreground event still fires.
How to Enable item events with VerboseEvents
The AfterItemDraw is fired after each feature or graphic is displayed and can seriously impact drawing performance if a connected handler is not efficient. Normally clients connect to the AfterDraw event. Note that it's important to check the second argument and respond to the appropriate phase of drawing since the AfterDraw routine will be called several times when the map is drawn.
For efficiency, IActiveView has a property called VerboseEvents. It can be used to limit the number of events that are fired. If VerboseEvents=false, AfterItemDraw is not fired. This is the default setting.
Events and Display Caching
The following tables shows the device context that is active when each of the AfterDraw events is fired:
|
Event
|
Active HDC
|
| esriViewForeground |
window |
| esriViewGraphics |
annotation cache |
| esriViewGeoSelection |
selection cache |
| esriViewGeography |
top layer cache |
| esriViewBackground |
bottom layer cache |
Map class AfterDraw Device Contexts
|
Event
|
Active HDC
|
| esriViewForeground |
window |
| esriViewGraphicSelection |
selection cache |
| esriViewGraphics |
element cache |
| esriViewBackground |
element cache |
PageLayout class AfterDraw Device Contexts
Create a private cache
For drawing graphics (events), you probably want to use esriViewGraphics. AfterDraw has two arguments, pDisplay and drawPhase. It gets called for each of the phases so make sure you only draw when your phase is specified. Draw directly to the display and don't worry about caches. The StartDrawing and FinishDrawing methods are called by Map. If the phase you are drawing after is cached, your drawing will automatically be cached.
- Create the cache in response to IDocumentEvents::ActiveViewChanged. Map creates it's caches in response to Activate and throws away all caches in response to Deactivate. The ActiveViewChanged event is fired after Map creates its caches so if there's not enough memory, the map will get its caches but the private cache will not.
[VB.NET]
Dim pActiveView As IActiveView = pMap
Dim pScreen As IScreenDisplay = pActiveView.ScreenDisplay
m_myCacheID = pScreen.AddCache()
[C#]
IActiveView pActiveView = (IActiveView)pMap;
IScreenDisplay pScreen = pActiveView.ScreenDisplay;
m_myCacheID = pScreen.AddCache();
-
AfterDraw should look something like this:
[VB.NET]
If phase <> esriViewXXX Then
Return
End If
Dim pScreen As IScreenDisplay = pDisplay
If (Not (pScreen = Nothing)) Then
' Draw directly to output device
DrawMyStuff(pDisplay)
Return
End If
' Draw to screen using cache if possible
Dim hWindowDC As Long
WindowDC = pScreen.WindowDC
Dim bDirty As Boolean
pScreen.IsCacheDirty(m_myCacheID, bDirty)
If (bDirty) Then
' draw from scratch
pScreen.FinishDrawing()
pScreen.StartDrawing(hWindowDC, m_myCacheID)
DrawMyStuff(pDisplay)
Else
' draw from cache
pScreen.DrawCache(hWindowDC, m_myCacheID, Nothing, Nothing)
End If
[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);
}
Transparency
Both symbols and images can make use of transparent colors. The transparency (alpha blend) algorithm is raster based. Vector drawing must first be converted to raster in order to support it. Transparent objects are drawn to a source bitmap. The background that the objects are drawn on must be stored in a background bitmap. Transparency is accomplished by blending the source bitmap into the background bitmap using either a single transparency value for all the bits or a mask bitmap containing transparency values for each individual pixel. To support transparency, IDisplay provides the BackgroundDC attribute that can be used to get a bitmap containing all of the drawing that has happened in the current drawing sequence.
Display Transparency
The transparency algorithm is encapsulated into the display filter object: TransparencyDisplayFilter. The same filter Class can be used by feature layers, raster layers, elements, third parties, etc.
To draw with transparency, do the following:
[VB.NET]
Dim pFilter As ITransparencyDisplayFilter = New TransparencyDisplayFilterClass()
pFilter.Transparency = 100
pDisplay.StartDrawing(hdc, cacheID)
pDisplay.Filter = pFilter
DrawToDisplay()
pDisplay.Filter = 0
pDisplay.FinishDrawing()
[C#]
ITransparencyDisplayFilter pFilter = new TransparencyDisplayFilterClass();
pFilter.Transparency = 100;
pDisplay.StartDrawing(hdc, cacheID);
pDisplay.Filter = pFilter;
DrawToDisplay();
pDisplay.Filter = 0;
pDisplay.FinishDrawing();
For raster layers, DrawToDisplay() means get the destination DC from the display and BitBlt the raster image to it.
When the filter is specified, the display creates an internal filter cache that is used along with the recording cache to provide the necessary raster info to the filter. Output is routed to the filter cache so that when the raster layer asks for the destination DC, it gets the filter cache DC. The display applies the filter when pDisplay.Filter is set to 0. Apply receives the current background bitmap (recording cache), the opaque raster layer image (filter cache), and the destination (window). The filter knows the transparency value is 100 so it does the blending and sends the results to the window.
Symbol Transparency
How do we technically make symbols and images support transparency. The view objects (Data and Layout) handle drawing maps both to output devices (windows and printers) and export files (bitmap and metafiles). All graphic elements and layers need to report whether or not they are using transparent colors. So that when the view starts to generate output, it can check if there are any transparent colors. If there are, it uses the following algorithm to produce output:
- Divide the display surface up into bands so that the in-memory bitmaps required to create transparent colors do not consume too much memory.
- Create a BackingDisplay the size of one band. A BackingDisplay has an internal device independent bitmap that has the same color depth and resolution as the output device. Drawing is directed to the bitmap then when drawing is finished, the bitmap is copied to the actual output device (or file). The layers and symbols treat this BackingDisplay just like any other display so no special coding is required for them.
- For each band: adjust the display's transform to reference the current band and draw the view. The renderers will automatically clip to the band since the transform's visible bounds equals the band rectangle.
This will result in a series of bitmaps (the bands) being copied to the output device or export file. If there are no transparent colors in the map, the metafile will be generated normally, i.e., it will contain series of vector graphics.
Rapid Display
Often times it is necessary to quickly update the display to show movement of live objects. For example, movements of commercial assets tracked by GPS. There are two aspects to the problem:
- How to store the live data
- How to rapidly draw it to the display.
Design Patterns
There are many ways to store and draw event data in your application. The most common methods are as follows:
- Create a feature class and add and remove features as necessary. Use standard renderers or custom symbols to draw features. Draw above or below any other layers. Wrap with feature layer that has its own display cache for best performance (i.e., set ILayer::Cached to true).
- Create a custom layer. Use either a proprietary data base or a feature class to store features. Implement custom rendering in layer (i.e., use GDI+ directly). Draw above or below any other layers. Give feature layer its own display cache for best performance.
- Create a graphics layer. Store data as graphic elements. Render using standard or custom symbol. Draws above all other layers.
- Draw in response to IActiveViewEvents::AfterDraw(esriViewForeground). Store data in proprietary data base. Draw directly using either GDI or IDisplay. Draws on top of everything else. Fast! GPS extension uses this approach.
Note the three common ways to store custom data:
- Features in a GeoDatabase
- Elements in a graphics layer
- Proprietary data structure.
The best choice depends on where in the drawing order your events need to draw, whether you want to use standard rendering objects, and whether or not you need to support a proprietary data format.
In all cases, the standard Invalidation model of drawing should be used. That is, create an object that draws (i.e., layer, graphic element, or event handler), plug it into your map, and call IActiveView::PartialRefresh when you want it to draw.
Animation Support
At version 9 of ArcObjects, a new interface, IViewRefresh makes it simpler to quickly refresh the display to show live objects. Clients should use the AnimationRefresh routine in place of PartialRefresh to invalidate their custom drawing object. For example, you may store “live” features using a layer with its own display cache. Animation is accomplished by moving features, invalidating the layer (with AnimationRefresh), and letting redraw happen naturally. When AnimationRefresh is used instead of PartialRefresh, the following optimizations / tradeoffs are enabled:
Text Anti-aliasing is temporarily disabled. This prevents labels from having to be redrawn every time the animation layer is invalidated. Remember that anti-aliased text uses the background as part of its rendering so normally when anything below it changes, the text also needs to draw from scratch.
Transparent layers above the animation layer are not automatically invalidated along with the animation layer. This speeds up the redraw with the following limitation: Features in the animation layer will no longer show through the transparent layer above it.
All drawing is directed to the recording cache HDC rather than the window. This causes all drawing to happen behind the scenes. When drawing is complete, the window is updated once from the completed recording cache. The result is less flashing.
To avoid excessive CPU consumption during rapid drawing you can make a call to UpdateWindow between invalidating old location and invalidating new location.
Display Design Patterns
To help you understand how the various display objects work together to solve common development requirements, several application scenarios are given along with details on their implementation. Use these patterns as a starting point for working with the display objects.
The application window
One of the most common tasks is to draw maps in the client area of an application window with support for scrolling and backing store. The display objects are used as follows to make this possible.
Initialization
Start by creating a
ScreenDisplay when the window is created. You'll also need to create one or more symbols to use for drawing shapes. Forward the application's hWnd to
pScreenDisplay.hWnd. Obtain from the
ScreenDisplay its
IDisplayTransformation interface and set the full and visible map extents using
pTransformation.Bounds and
pDisplayTransform.VisibleBounds. The visible bounds determines the current zoom level.
ScreenDisplay takes care of updating the display transformation's
DeviceFrame. The
ScreenDisplay monitors the window's messages and automatically handles common events such as window resizing or scrolling.
[VB.NET]
Private m_pScreenDisplay As IScreenDisplay
Private m_pFillSymbol As ISimpleFillSymbol
Private Sub Form_Load(ByVal sender As Object, ByVal e As System.EventArgs)
m_pScreenDisplay = New ScreenDisplayClass()
m_pScreenDisplay.hWnd = Picture1.hWnd
m_pFillSymbol = New SimpleFillSymbolClass()
Dim pEnv As IEnvelope = New EnvelopeClass()
pEnv.PutCoords(0, 0, 50, 50)
m_pScreenDisplay.DisplayTransformation.bounds = pEnv
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv
End Sub
[C#]
private IScreenDisplay m_pScreenDisplay;
private ISimpleFillSymbol m_pFillSymbol;
private void Form_Load(object sender, System.EventArgs e)
{
m_pScreenDisplay = new ScreenDisplayClass();
m_pScreenDisplay.hWnd = Picture1.hWnd;
m_pFillSymbol = new SimpleFillSymbolClass();
IEnvelope pEnv = new EnvelopeClass();
pEnv.PutCoords(0, 0, 50, 50);
m_pScreenDisplay.DisplayTransformation.bounds = pEnv;
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv;
}
Drawing
The display objects define a generic IDraw interface, which makes it easy to draw to any display. As long as you use IDraw or IDisplay to implement your drawing code, you don't have to worry about what kind of device you're drawing to. A drawing sequence starts with StartDrawing and ends with FinishDrawing. For example, create a routine that builds one polygon in the center of the screen and draws it. The shape is drawn using the default symbol. Here are the sample routines:
[VB.NET]
Private Function GetPolygon() As IPolygon
Dim GetPolygon As IPolygon
GetPolygon = New PolygonClass()
Dim pPointCollection As IPointCollection
pPointCollection = GetPolygon As IPointCollection
Dim pPoint As IPoint = New PointClass()
pPoInteger.PutCoords(20, 20)
pPointCollection.AddPoints(1, ref pPoint)
pPoInteger.PutCoords(30, 20)
pPointCollection.AddPoints(1, ref pPoint)
pPoInteger.PutCoords(30, 30)
pPointCollection.AddPoints(1, ref pPoint)
pPoInteger.PutCoords(20, 30)
pPointCollection.AddPoints(1, ref pPoint)
GetPolygon.Close()
Return GetPolygon
End Function
Private Sub MyDraw(ByVal pDisplay As IDisplay, ByVal hDC As esriSystem.OLE_HANDLE)
'Draw from Scratch
Dim pDraw As IDraw
pDraw = pDisplay
pDraw.StartDrawing(hDC, CShort(esriScreenCache.esriNoScreenCache))
Dim pPoly As IPolygon
pPoly = GetPolygon()
pDraw.SetSymbol(m_pFillSymbol)
pDraw.Draw(pPoly)
pDraw.FinishDrawing()
End Sub
[C#]
private IPolygon GetPolygon()
{
IPolygon GetPolygon;
GetPolygon = new PolygonClass();
IPointCollection pPointCollection;
pPointCollection = GetPolygon as IPointCollection;
IPoint pPoint = new PointClass();
pPoint.PutCoords(20, 20);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(30, 20);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(30, 30);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(20, 30);
pPointCollection.AddPoints(1, ref pPoint);
GetPolygon.Close();
return GetPolygon;
}
private void MyDraw(IDisplay pDisplay, esriSystem.OLE_HANDLE hDC)
{
// Draw from Scratch
IDraw pDraw;
pDraw = pDisplay;
pDraw.StartDrawing(hDC, (short)esriScreenCache.esriNoScreenCache);
IPolygon pPoly;
pPoly = GetPolygon();
pDraw.SetSymbol(m_pFillSymbol);
pDraw.Draw(pPoly);
pDraw.FinishDrawing();
}
This routine can be used to draw polygons to any device context. The first place we need to draw, however, is to a window. To handle this, write some code in the
Paint method of the Picture Box that passes the application's
ScreenDisplay pointer and Picture Box
HDC to the
MyDraw routine. Notice that the routine takes both a display pointer and a Windows device context.
[VB.NET]
Private Sub Picture1_Paint()
MyDraw(m_pScreenDisplay, Picture1.hDC)
End Sub
[C#]
private void Picture1_Paint()
{
MyDraw(m_pScreenDisplay, Picture1.hDC);
}
Forwarding the DC allows the display to honor the clipping regions that Windows sets into the paint HDC.
Adding Display Caching
Some drawing sequences can take a while to complete. A simple way to optimize your application is to enable display caching. This refers to ScreenDisplay's ability to record your drawing sequence into a bitmap and then use the bitmap to refresh the picture box's window whenever Paint method is called. The cache is used until your data changes and you call IScreenDisplay::Invalidate to indicate that the cache is invalid. There are two kinds of caches: recording caches and user-allocated caches. Use recording to implement a display cache in the sample application's Paint method.
[VB.NET]
Private Sub Picture1_Paint()
If (m_pScreenDisplay.IsCacheDirty(CShort(esriScreenCache.esriScreenRecording))) Then
m_pScreenDisplay.StartRecording()
MyDraw(m_pScreenDisplay, Picture1.hDC)
m_pScreenDisplay.StopRecording()
Else
Dim rect As tagRECT
m_pScreenDisplay.DrawCache(Picture1.hDC, CShort(esriScreenCache.esriScreenRecording), rect, rect)
End If
End Sub
[C#]
private void Picture1_Paint()
{
if ((m_pScreenDisplay.IsCacheDirty((short)esriScreenCache.esriScreenRecording)
))
{
m_pScreenDisplay.StartRecording();
MyDraw(m_pScreenDisplay, Picture1.hDC);
m_pScreenDisplay.StopRecording();
}
else
{
tagRECT rect;
m_pScreenDisplay.DrawCache(Picture1.hDC, (short)
esriScreenCache.esriScreenRecording, rect, rect);
}
}
When you execute this code, you will see that nothing is drawn on the screen. This is due to the
ScreenRecording cache not having its dirty flag set. To ensure that the
MyDraw function is called when the first paint message is received, you must invalidate the cache. Add the following line at the end of the
Form_Load method.
[VB.NET]
m_pScreenDisplay.Invalidate(Nothing, True, CShort(esriScreenCache.esriScreenRecording))
[C#]
m_pScreenDisplay.Invalidate(null, true, (short)
esriScreenCache.esriScreenRecording);
Some applications, ArcMap for example, may require multiple display caches. To utilize multiple caches, follow these steps:
- Add a new cache using IScreenDisplay::AddCache. Save the cache ID that is returned.
- To draw to your cache, specify the cache ID to StartDrawing.
- To invalidate your cache, specify the cache ID to Invalidate.
- To draw from your cache, specify the cache ID to DrawCache.
To change the sample application to support its own cache, make the following changes:
- Add a member variable to hold the new cache.
[VB.NET]
Private m_lCacheID As Long
[C#]
private long m_lCacheID;
- Create the cache in the Form_Load method.
[VB.NET]
m_lCacheID = m_pScreenDisplay.AddCache
[C#]
m_lCacheID = m_pScreenDisplay.AddCache;
- Change the appropriate calls to use the m_lCacheID variable and remove the start and stop recording from the Paint method.
Pan, zoom, and rotate
A powerful feature of the display objects is the ability to zoom in and out on your drawing. It's easy to implement tools that let users zoom in and out or pan. Scrolling is handled automatically. To zoom in and out on your drawing, simply set your display's visible extent. For example, add a command button to the form and place the following code, which zooms the screen by a fixed amount, in the Click event of the button.
[VB.NET]
Private Sub Command1_Click()
Dim pEnv As IEnvelope = m_pScreenDisplay.DisplayTransformation.VisibleBounds
pEnv.Expand(0.75, 0.75, True)
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv
m_pScreenDisplay.Invalidate(Nothing, True, CShort(esriScreenCache.esriAllScreenCaches))
End Sub
[C#]
private void Command1_Click()
{
IEnvelope pEnv = m_pScreenDisplay.DisplayTransformation.VisibleBounds;
pEnv.Expand(0.75, 0.75, true);
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv;
m_pScreenDisplay.Invalidate(null, true, (short)
esriScreenCache.esriAllScreenCaches);
}
ScreenDisplay implements TrackPan, which can be called in response to a mouse down event to let users pan the display. You can also rotate the entire drawing about the center of the screen by setting the DisplayTransformation's Rotation property to a nonzero value. Rotation is specified in degrees. ScreenDisplay implements TrackRotate, which can be called in response to a mouse down event to let users interactively rotate the display.
Printing
Printing is very similar to drawing to the screen. Since you don't have to worry about caching or scrolling when drawing to the printer, a
SimpleDisplay can be used. Create a
SimpleDisplay object and initialize its transform by copying the
ScreenDisplay's transform. Set the printer transformation's
DeviceFrame to the pixel bounds of the printer page. Finally, draw from scratch using the
SimpleDisplay and the printer's
HDC.
Output to a metafile
The
GDIDisplay object can be used to represent a metafile. There's little difference between creating a metafile and printing. If you specify 0 as the
lpBounds parameter to
CreateEnhMetaFile, the
MyDraw routine can be used. Just substitute
hMetafileDC for
hPrinterDC. If you want to specify a bounds to
CreateEnhMetafFile (in
HIMETRIC units), set the
DisplayTransformation's
DeviceFrame to the pixel version of the same rectangle.
Print to a frame
Some projects may require output to be directed to some subrectangle of the output device. It's easy to handle this by setting the
DisplayTransformation's device frame to a pixel bounds that is less than the full device extent.
Filters
Very advanced drawing effects, such as color transparency, can be accomplished using display filters. Filters work along with a display cache to allow a rasterized version of your drawing to be manipulated. When a filter is specified to the display (using IDisplay::putref_DisplayFilter), the display creates an internal filter cache that is used along with the recording cache to provide raster info to the filter. Output is routed to the filter cache until the filter is cleared (that is, putref_DisplayFilter(0)). At that point, the display calls IDisplayFilter::Apply. Apply receives the current background bitmap (recording cache), the drawing cache (containing all of the drawing that happened since the filter was specified), and the destination HDC. The transparency filter performs alpha blending on these bitmaps and draws them to the destination HDC to achieve color transparency. New filters can be created to realize other effects.
Dynamic Display
The ArcGIS Display subsystem manages map display and refresh. Intensive map rendering and unsynchronized display refresh can lead to CPU overloading. CPU overloading can delay or even prevent display refresh and user interaction with the display. The Dynamic Display technology moves graphics intensive rendering from the CPU to the graphics hardware and significantly improves performance.
The following is a brief description of the Dynamic Display objects used in the Display library:
|
IDynamicDisplay
|
DynamicDisplay provides the mechanism to draw different geographical geometries on the map, in addition to provide the access to the DynamicGlyphFactory.
|
|
IDynamicDrawScreen
|
The DynamicDrawScreen is used to draw geometries on the map using screen coordinates. The DynamicDrawScreen sould use similar to the DynamicDisplay where you should first set the symbol’s properties using DynamicSymbolProperties and then use DynamicDrawScreen in order to draw the geometry.
|
|
IDynamicGlyphFactory
|
The DynamicGlyphFactory is used to manage DynamicGlyphs. Using the DynamicGlyphFactory you can get, create and delete DynamicGlyphs. This means that you can either use standard ArcObjects symbols (such as character marker symbols, text symbols, image symbols etc.) or use external resources such as icons and bitmaps. In addition, DynamicGlyphs can be created from dynamic glyph groups which are actually a mosaic image of several glyphs where each glyph is mapped to a specific location in the mosaic. Using DynamicGlyphs from a glyphs group would result in better drawing performance since there will be only one OpenGL texture created for an entire group. The dynamic display includes internal default glyphs group; however you can use your own glyphs groups, load them and unload them using the DynamicGlyphFactory.
|
|
IDynamicSymbolProperties
|
DynamicSymbolProperties allow you to set varies properties of the active symbol, such as color, scale, heading and glyph. The idea behind the DynamicDisplay is that at any given time it has an active symbol for points, lines and text. Using the DynamicSymbolProperties you can control this symbol before drawing it to the map.
|
|
IDynamicCompoundMarker
|
In order to draw marker symbols with labels (or attributes) surrounding it you should use interface IDynamicCompoundMarker which allow you to draw both the marker symbol and its attributes at the same call. It provides you the option to draw one up to 10 attributes around the marker symbol and allow you to control over the offset from the marker to the text.
|
|
IDynamicGlyph
|
A dynamic glyph handles the resource needed to be rendered by the DynamicSymbol. This might be a resource for a line, marker or text. DynamicGlyphs are being managed by the DynamicGlyphFactory.
|
|
IDynamicMap
|
The IDynamicMap interface is used for controlling the Dynamic Display. The interface is used for enabling and disabling the Dynamic Display and controlling global properties of the dynamic map.
|
The Dynamic Map
The DynamicMap can be enabled using the IDynamicMap Interface that is implemented by the Map CoClass. Use the interface IDyanmicMap to enable or disable the dynamic display. When enabled, the dynamic display creates an OpenGL rendering context to draw the basic map layers. As well as the dynamic layers that are drawn on top of the base layers.
Drawing a Dynamic Layer
A dynamic layer has two primary draw phases: Immediate and Compiled. The compiled draw phase stores draw commands in a list. Dynamic display uses this list to draw the commands. The list is recreated when the dirty flag has been set. The immediate draw phase does not store draw commands. The draw commands of the immediate draw phase are immediately drawn.
When redrawing the display, the Dynamic Display iterates through the Dynamic Layers and does the following for each:
- Checks if recompile is needed by checking the DynamicLayerDirty with the immediate Phase. If any dynamic layer has set the dirty flag for the immediate phase, all dynamic layers DrawDynamicLayer method will be called with the immediate draw phase.
- Draws the immediate phase and calls the DrawDynamicLayer method with the immediate phase.
- Checks if recompile is needed by checking the DynamicLayerDirty (with the compiled Phase) and the DynamicRecompileRate property.
- If needed, recompile the Dynamic Layer by calling the DrawDynamicLayer method with the compiled phase.
- Draw the compiled phase, by drawing the Display List.
A layer will be recompiled (in compiled phase) when any refresh events or when the following conditions are met:
- The DynamicLayerDirty property is set to true
- The DynamicRecompileRate time form the previous recompile time has elapsed
The layer’s DrawDynamicLayer method will be called:
- With the Compiled Phase – when the layer needs to be recompiled
- With the Immediate Phase – when the layer needs to be redrawn
Note: When redrawing the Compiled phase of a dynamic layer, it is not necessary to call its DrawDynamicLayer method but only redraw the already compiled list. the DrawDynamicLayer method (of the Compiled Phase) will be called only when needed to recompile. On the other hand, when redrawing the Immediate Phase of a layer the DrawDynamicLayer method (of the Immediate Phase) is being called.
Draw Cycle Sequence (including DynamicMapEvents)
There are two DynamicMapEvents methods that can be used to draw objects. The two draw phases are esriDMDPLayers and esriDMDPDynamicLayers. During every draw cycle, the layers are being drawn and the DynamicMap events are being fired in the following sequence:
-
BeforeDynamicDraw(esriDMDPLayers) event is being fired.
- Layers are drawn. These are the layers that do not support IDynamicLayers (static layers)
-
AfterDynamicDraw(esriDMDPLayers) event is being fired.
-
BeforeDynamicDraw(esriDMDPDynamicLayers) event is being fired.
- Dynamic layers are drawn.
- Immediate phase (esriDDPImmediate)
- Compiled phase (esriDDPCompiled)
-
AfterDynamicDraw(esriDMDPDynamicLayers) event is being fired.
Drawing Objects API
The dynamic display has the following methods for drawing into the OpenGL rendering context:
-
IDynamicLayer::DrawDynamicLayer
-
IDynamicMapEvents::BeforeDynamicDraw event
-
IDynamicMapEvents::AfterDynamicDraw event
In these methods, the user can draw using the OpenGL API or the Dynamic Display Draw methods:
- Use the IDynamicDisplay interface to draw objects in map coordintates.
- Use the IDynamicScreen interface to draw objects to the screen. The origin is the bottom left of the screen.
- Use the IDynamiGlyphFactory interface to create and delete dynamic glyphs. To get to the interface IDynamicGlyphFactory use the IDynamicDisplay method.
- Use the IDynamicSymbolProperties interface to control the dynamic symbols properties (Dynamic marker, line, text and fill).
- Use the IDynamiGlyph interface to access properties of Dynamic Glyphs. A dynamic Glyph is a handle to a resource.
- Use the IDynamicMap interface to set the subpixelrendering mode.
General usage
Using the compiled phase is recommended for drawing most layers. In the compiled phase it is not recommended to create dynamic glyphs or textures. Use the Immediate phase to create dynamic glyphs and textures. For example, if you have one layer with many features that update only every 20 seconds. In this case it is recommended to redraw this in the compiled phase. If you have a layer you need to update at sub-second rates, you would draw this in the immediate phase.
When drawing dynamic layer using the DrawDynamicLayer method or when listening to DynamicMapEvents, it is recommended to initially first Cache (keep it as a class member) the DynamicGlyphFactory, the DynamicSymbolProperties and the DynamicCompoundMarker. This is an efficient method considering the repetition of draw cycle sequences.
For more information on working with Dynamic Layers, please see the samples and component help in this help system.
Colors
Color is used in many places in ArcGIS applications—in feature and graphics symbols, as properties set in renderers, as the background for ArcMap or ArcCatalog windows, and as properties of a raster image. The type of color model used in each of these circumstances will vary. For example, a window background would be defined in terms of an RGB color because display monitors are based on the RGB model. A map made ready for offset-press publication could have CMYK colors to match printer's inks.
Color can be represented using a number of different models, which often reflect the ways in which colors can be created in the real world. You may be familiar with the RGB color model, which is based on the primary colors of light—red, green, and blue. When red, green, and blue rays of light coincide, white light is created. The RGB color model is therefore termed additive, as adding the components together creates light. By displaying pixels of red, green, and blue light, your computer monitor is able to portray hundreds, thousands, and even millions of different colors. To define a color as an RGB value, you give a separate value to the red, green, and blue components of the light. A value of 0 indicates no light, and 255 indicates the maximum light intensity.
Red, green, blue (RGB) color model
Here are a few rules for RGB values:
- If all RGB values are equal, then the color is a gray tone.
- If all RGB values are 0, the color is black (an absence of light).
- If all RGB values are 255, the color is white.
Another common way to represent color, the CMYK model, is modeled on the creation of colors by spot printing. Cyan, magenta, yellow, and black inks are mingled on paper to create new colors. The CMYK model, unlike RGB, is termed subtractive, as adding all the components together creates an absence of light (black).
Cyan, magenta, yellow (CMY) color model
Cyan, magenta, and yellow are the primary colors of pigments—in theory you can create any color by mixing different amounts of cyan, magenta, and yellow. In practice, you also need black, which adds definition to darker colors and is better for creating precise black lines. HSV, or the hue, saturation, and value color model, describes colors based around a color wheel that arranges colors in a spectrum. The hue value indicates where the color lies on this color wheel and is given in degrees. For example, a color with a hue of 0 will be a shade of red, whereas a hue of 180 will indicate a shade of cyan.
Color wheel for hue, saturation, and value (HSV) color model
Saturation describes the purity of a color. Saturation ranges from 0 to 100; therefore, a saturation of 20 would indicate a neutral shade, whereas a saturation of 100 would indicate the strongest, brightest color possible. The value of a color determines its brightness, with a range of 0 to 100. A value of 0 always indicates black; however, a value of 100 does not indicate white, it just indicates the brightest color possible. Hue is simple to understand, but saturation and value can be confusing. It may help to remember these rules:
- If value = 0, the color is black.
- If saturation = 0, the color is a shade of gray.
- If value = 255 and saturation = 0, the color is white.
The HLS, or hue, lightness, and saturation model, has similarities with the HSV model. Hue again is based on the spectrum color wheel, with a value of 0 to 360. Saturation again indicates the purity of a color, from 0 to 100. However, instead of value, a lightness indicator is used, again with a range of 0 to 100. If lightness is 100, white is produced, and if lightness is 0, black is produced. The last color model is grayscale. 256 shades of pure gray are indicated by a single value. A grayscale value of 0 indicates black, and a value of 255 indicates white.
Grayscale color model
The CIELAB color model is used internally by ArcObjects, as it is device independent. The model, based on a theory known as opponent process theory, describes color in terms of three “opponent channels”. The first channel, known as the 1 channel, traverses from black to white. The second, or 2 channel, traverses red to green hues. The last channel, or 3 channel, traverses hues from blue to yellow.
Sample Color Values
Objects that support the IColor interface allow precise control over any color used within the ArcObjects model. You can get and set colors using a variety of standard color models—RGB, CMYK, HSV, HLS, and Grayscale.
The properties available on the IColor interface define the common functionality of all color objects. Representations of colors are held internally as CIELAB colors, described in the color theory topic. The CIELAB color model is device independent, providing a frame of reference to allow faithful translation of colors between one color model and another. You can use the GetCIELAB and SetCIELAB methods of the IColor interface to interact directly with a color object using the CIELAB model.Although colors are held internally as CIELAB colors, you don't need to deal directly with the CIELAB color model—you can use the IColor interface to simply read and define colors. For example, the RGB property can be used to read or write a Long integer representing the red, green, and blue values for any color object.
[VB.NET]
Public Function RGBToLong(ByVal lngRed As Long, ByVal lngGreen As Long, ByVal lngBlue As Long) As Long
Return lngRed + (256 * lngGreen) + (65536 * lngBlue)
End Function
[C#]
public long RGBToLong(long lngRed, long lngGreen, long lngBlue)
{
return lngRed + (256 * lngGreen) + (65536 * lngBlue);
}
If you are reading the
RGB property, you can break down the RGB value into its component red, green, and blue values with an inverse function of the previously defined
RGBToLong function, as follows:
[VB.NET]
Public Function ReturnRGBBytes(ByVal lngRGB As Long) As Byte()
Dim bytArray(2) As Byte
bytArray(0) = CType((lngRGB Mod 256), Byte)
bytArray(1) = CType(((lngRGB / 256) Mod 256), Byte)
bytArray(2) = CType(((lngRGB / 65536) Mod 256), Byte)
Return bytArray
End Function
[C#]
public byte[] ReturnRGBBytes(long lngRGB)
{
byte[] bytArray = new byte[2];
bytArray[0] = (byte)(lngRGB % 256);
bytArray[1] = (byte)((lngRGB / 256) % 256);
bytArray[2] = (byte)((lngRGB / 65536) % 256);
return bytArray;
}
One important point to note when reading the
RGB property: the
UseWindowsDithering property should generally be set to
True. If
UseWindowsDithering is
False, the
RGB property returns a number with a high byte of 2, indicating the use of a system color, and the
RGB property will return a value outside of the range you would expect. If you write to the
RGB property, the
UseWindowsDithering property will be set to
True for you. For more information on converting individual byte values to long integer representation, look for topics on color models and hexadecimal numbering in your development environment's online Help system. The
IColor interface also provides access to colors through another color model—CMYK. The
CMYK property can be used in a similar way as
RGB to read or write a
Long integer representing the cyan, magenta, yellow, and black components of a particular color—the difference being that the CMYK color model requires four values to define a color. The
RGBToLong function can be adapted as shown.
[VB.NET]
Public Function CMYKToLong(ByVal lngBlack As Long, ByVal lngYellow As Long, ByVal lngMagenta As Long, ByVal lngCyan As Long) As Long
Return (lngBlack + (256 * lngYellow) + (65536 * lngMagenta) + (16777216 * lngCyan))
End Function
[C#]
public long CMYKToLong(long lngBlack, long lngYellow, long lngMagenta, long
lngCyan)
{
return (lngBlack + (256 * lngYellow) + (65536 * lngMagenta) + (16777216 *
lngCyan));
}
Setting the
NullColor property to
True will result in the set color being nullified. All items with color set to
Null will not appear on the display. This only applies to the specific color objects—not all items with the same apparent color; therefore, you can have different null colors in one
Map or
PageLayout.
IColor also has two methods to convert colors to and from specific CIELAB colors, using the parameters of the CIELAB color model. You can set a color object to a specific CIELAB color by using
SetCIELab, or read CIELAB parameters from an existing color by using
GetCIELab. See also the
CieLabConversion Class. Color transparency does not get used by the feature renderers; instead, a display filter is used. Setting the transparency on a color has no effect, unless the objects using the color honor this setting. The
Color class is only an abstract class—when dealing with a color object, you always interact with one of the color Classes, which are described below.
RGBColor,
CMYKColor,
GrayColor Class,
HSVColor, and
HLSColor are all creatable classes, allowing new colors to be created programmatically according to the most appropriate color model.
Color Ramps
The objects supporting the IColorRamp interface offer a simple way to define a series of colors for use elsewhere in ArcObjects. For example, you can set a color ramp directly onto the ColorRamp property of the IGradientFillSymbol interface of a FillSymbol, or you might wish to create a color ramp to define the colors used in a ClassBreaksRenderer. The individual ColorRamp objects offer different ways of defining the criteria that determine which colors will comprise the ColorRamp. Random colors can be created using the RandomColorRamp, and sequential colors can be created using the AlgorithmicColorRamp. The PresetColorRamp Class contains 13 colors, allowing the creation of ramps mimicking ArcView GIS 3.x color ramps. In addition, the MultiPartColorRamp allows you to create a single color ramp that concatenates other color ramps, providing unlimited color ramp capabilities.
ColorRamps are used in two different ways in ArcObjects: by accessing the individual colors in a ramp or by using the ramp object directly as a property or, in a method, of another object. First, a color ramp can be set up and its individual colors accessed. For example, when a UniqueValueRenderer is created, each symbol in its symbol array should be set individually, perhaps using colors from a color ramp. To retrieve individual colors from a color ramp, first set the Size property according to the number of Color objects you wish to retrieve from the ramp. The CreateRamp method should then be called, which populates both the Color and the Colors properties. The Color property holds a read-only, zero-based array of Color objects, returned by index. The code fragment below shows the creation of a RandomColorRamp and the generation of 10 color objects from that ramp. Note that the Boolean parameter used in the CreateRamp method is checked after the method is called to ensure the colors were generated correctly.
[VB.NET]
Dim pColorRamp As IRandomColorRamp = New RandomColorRampClass()
pColorRamp.Size = 10
Dim bOK As Boolean
pColorRamp.CreateRamp( bOK)
If (bOK) Then
' Access the Color array here, for example, set colors
' for an array of symbols, or map layers etc...
Dim i As Integer
For i = 0 To pColorRamp.Size - 1 Step i + 1
Next
End If
[C#]
IRandomColorRamp pColorRamp = new RandomColorRampClass();
pColorRamp.Size = 10;
bool bOK;
pColorRamp.CreateRamp(out bOK);
if ((bOK))
{
// Access the Color array here, for example, set colors
// for an array of symbols, or map layers etc...
for (int i = 0; i <= pColorRamp.Size - 1; i++){}
}
The
Colors property returns an enumeration of colors and is useful as a lightweight object to pass around between procedures.Second, a color ramp object may be used directly—for example, the
ColorRamp property of the
IGradientFillSymbol can be set to a specific color ramp object. The
MultiPartColorRamp also uses color ramp objects directly by passing the object as a parameter in the
AddRamp method. Below you can see a
GradientFillSymbol object being created with an
AlgorithmicColorRamp as its fill. The
IntervalCount is set, which decides the amount of colors in the gradient fill.
[VB.NET]
Dim pAlgoRamp As IAlgorithmicColorRamp = New AlgorithmicColorRampClass()
pAlgoRamp.FromColor = myFromColorObject
pAlgoRamp.ToColor = myToColorObject
Dim pGFill As IGradientFillSymbol = New GradientFillSymbolClass()
pGFill.ColorRamp = pAlgoRamp
pGFill.IntervalCount = 5
[C#]
IAlgorithmicColorRamp pAlgoRamp = new AlgorithmicColorRampClass();
pAlgoRamp.FromColor = myFromColorObject;
pAlgoRamp.ToColor = myToColorObject;
IGradientFillSymbol pGFill = new GradientFillSymbolClass();
pGFill.ColorRamp = pAlgoRamp;
pGFill.IntervalCount = 5;
If the ramp will be used directly, as above, it is not necessary to set the
Size property or to call the
CreateRamp method yourself. In these cases, the parent object uses the information contained in the color ramp object to generate the number of colors it requires. The
Size property will be ignored. The name property simply stores a string, which you may want to use to keep track of your color ramps—it is not used internally by ArcObjects.
Symbols
ArcObjects uses three categories of symbols to draw geographic features: marker symbols, line symbols, and fill symbols. These same basic symbols are also used to draw graphic elements, such as neatlines and North arrows, on a Map or PageLayout. A fourth symbol, the TextSymbol, is used to draw labels and other textual items. A fifth symbol, the 3DChartSymbol, is used for drawing charts. In the case of a graphic element, a symbol is set as a property of each element. Layers, however, are drawn with a renderer, which has one or more symbols associated with it. The size of a symbol is always specified in points (such as the width of a line), but the size of their geometry (such as the path of a line) is determined by the item they are used to draw. Most items, when created, have a default symbol, so instead of creating a new symbol for every item, you can modify the existing one. Another way to get a symbol is to use a style file. ArcObjects uses style files, which are distributable databases, to store and access symbols and colors. Many standard styles, offering thousands of predefined symbols, are available during the installation process. Using the StyleGallery and StyleGalleryItem classes, you can retrieve and edit existing symbols, which may be more efficient than creating symbols from scratch. You might also wish to use the standard symbol editors found in ArcMap, which can be opened programmatically using the SymbolEditor Class. The following pages describe how to create all the different symbols from first principles. The ISymbol interface provides high-level functionality for all symbols. It allows you to draw any symbol directly to a device context (DC). A device context is an internal Windows structure—each window has a device context handle, or hDC.
The
SetupDC, Draw, and
ResetDC methods can be used in conjunction with the
ROP2 property to draw a symbol to a device context, providing a familiar procedure for those who have worked with device context drawing before. Calling the
SetupDC method selects the
Symbol into the specified DC, and setting the
ROP2 property to one of the
esriRaster-OpCodes specifies how the
Symbol is drawn (see below). Subsequently calling the
Draw method will draw the
Symbol, using the
Geometry parameter from the
Draw method, to the DC. The following code demonstrates drawing to a device context, where
pDisplay is a valid
Display object,
pPoint is a valid
Point in display coordinates, and
pSymbol is any valid
Symbol. There are two important points to note:
- Call StartDrawing on the Display before using the Draw method, as this sets up the Display's device context. Always ensure you call FinishDrawing on the Display after you have finished.
- Always make sure you call ResetDC after you finish drawing with a particular symbol, which restores the DC to its original state.
[VB.NET]
Private Sub DrawSymbol(ByVal pDisplay As IDisplay, ByVal pSymbol As ISymbol)
pDisplay.StartDrawing(pDisplay.hDC, CShort(esriScreenCache.esriNoScreenCache))
pSymbol.SetupDC(pDisplay.hDC, pDisplay.DisplayTransformation)
pSymbol.Draw(pPoint)
pSymbol.ResetDC()
pDisplay.FinishDrawing()
End Sub
[C#]
private void DrawSymbol(IDisplay pDisplay, ISymbol pSymbol)
{
pDisplay.StartDrawing(pDisplay.hDC, (short)esriScreenCache.esriNoScreenCache);
pSymbol.SetupDC(pDisplay.hDC, pDisplay.DisplayTransformation);
pSymbol.Draw(pPoint);
pSymbol.ResetDC();
pDisplay.FinishDrawing();
}
Symbol Level Drawing
You can use symbol level drawing to alter the draw order of features within feature layers. By using symbol level drawing you can control the draw order of features on a symbol by symbol basis. This means that features do not necessarily need to be drawn in the same order that feature layers appear in the ArcMap table of contents. With symbol level drawing you can control when a feature draws by controlling when the feature's symbol draws. Furthermore, when multi layer symbols are used, you can control the drawing order of individual symbol layers.
Using symbol level drawing is most useful for maps with cased lines because it can be used to create overpass and underpass effects where the line features cross, which is a good way to show connectivity. Symbol level drawing can be used to achieve other advanced effects as well.
IMapLevel is the interface that you use to assign a map level (or levels if the symbol is multi layer) to a symbol, thus preparing it to be used with symbol level drawing. Not all symbols support this interface. Note that if you assign a symbol with map levels to a graphic element, then the levels will be ignored. Also, ISymbol::Draw ignores levels as well.
To use symbol level drawing:
- Turn on symbol level drawing, either for a layer using ISymbolLevels::UseSymbolLevels, or for your entire map using IMap::UseSymbolLevels.
Note: Prior to ArcGIS 9.0 symbol levels were referred to in the ArcMap GUI as 'Advanced Drawing Options' and they were only available at the map level. In ArcGIS 9.0 and later versions, symbol level support was added for individual FeatureLayers and GroupLayers. In the GUI this is called 'Symbol Level Drawing'. The preferred use of symbol levels is at the layer level since in ArcGIS 9.0 and later there is no GUI for map-based symbol level drawing and layer-based symbol level drawing is more efficient.
- For each layer in your map that you want to use symbol levels for, access the layer's renderer using IGeoFeatureLayer::Renderer.
- Access your layer's symbols through the renderer.
- Using IMapLevel, set symbol levels on your layer's symbols. Symbols with MapLevel = 0 draw first, then symbols with MapLevel = 1, continuing until the highest MapLevel is reached. If two symbols have the same MapLevel, then the features drawn with these symbols are drawn in the normal layer order. A MapLevel of -1 for a multilayer symbol (MultiLayerMarkerSymbol, MultiLayerLineSymbol, MultiLayerFillSymbol) indicates that each of the symbol's individual layers are drawn with their individual MapLevel.
Join and Merge
The graphics below show the effect of joining a symbol, which makes features with the same symbol appear to 'connect' to each other. Merge makes features with different symbols appear to 'connect'. Both of these effects are implemented behind the scenes using the symbol level objects and interfaces. You can toggle symbol level drawing on or off, either using ISymbolLevels::UseSymbolLevels for a layer, or for your entire map using IMap::UseSymbolLevels.
Symbols drawing using Map Levels
The following code turns on symbol level drawing for the layer in the map and sets up the multi-layer symbol assigned to this layer to be joined.
[VB.NET]
' ...
If pMap.get_LayerCType(Is IGeoFeatureLayer, 0) Then
Dim pFeatLyr As IGeoFeatureLayer
pFeatLyr = New FeatureLayerClass()
pFeatLyr = pMap.get_Layer(0) As IGeoFeatureLayer
Dim pSymbolLevels As ISymbolLevels
pSymbolLevels = pFeatLyr As ISymbolLevels
pSymbolLevels.UseSymbolLevels = True
If TypeOf pFeatLyr.Renderer Is ISimpleRenderer Then
Dim pSimpleRend As ISimpleRenderer
pSimpleRend = New SimpleRendererClass()
If TypeOf pSimpleRend.Symbol Is IMultiLayerLineSymbol Then
Dim pMulti As IMultiLayerLineSymbol
pMulti = New MultiLayerLineSymbolClass()
pMulti = pSimpleRend.Symbol As IMultiLayerLineSymbol
SetMapLevel(pMulti As IMapLevel, -1)
Dim i As Integer
For i = 0 To pMulti.LayerCount - 1 Step i + 1
SetMapLevel(pMulti.get_LayerCType(As IMapLevel, pMulti.LayerCount - (i + 1), i))
Next
End If
End If
End If
' ...
Public Sub SetMapLevel(ByVal pMapLevel As IMapLevel, ByVal iLevel As Integer)
If Not (pMapLevel Is Nothing Then
pMapLevel.MapLevel = iLevel
End If
End Sub
[C#]
// ...
if (pMap.get_Layer(0)is IGeoFeatureLayer)
{
IGeoFeatureLayer pFeatLyr;
pFeatLyr = new FeatureLayerClass();
pFeatLyr = pMap.get_Layer(0)as IGeoFeatureLayer;
ISymbolLevels pSymbolLevels;
pSymbolLevels = pFeatLyr as ISymbolLevels;
pSymbolLevels.UseSymbolLevels = true;
if (pFeatLyr.Renderer is ISimpleRenderer)
{
ISimpleRenderer pSimpleRend;
pSimpleRend = new SimpleRendererClass();
if (pSimpleRend.Symbol is IMultiLayerLineSymbol)
{
IMultiLayerLineSymbol pMulti;
pMulti = new MultiLayerLineSymbolClass();
pMulti = pSimpleRend.Symbol as IMultiLayerLineSymbol;
SetMapLevel(pMulti as IMapLevel, - 1);
for (int i = 0; i < pMulti.LayerCount; i++)
{
SetMapLevel(pMulti.get_Layer(i)as IMapLevel, pMulti.LayerCount - (i + 1)
);
}
}
}
}
// ...
public void SetMapLevel(IMapLevel pMapLevel, int iLevel)
{
if (!(pMapLevel == null)
{
pMapLevel.MapLevel = iLevel;
}
}
Marker Symbols
The
IMarkerSymbol interface represents the properties all types of
MarkerSymbol have in common. These are
Angle, Color, Size XOffset, and
YOffset.
IMarkerSymbol is the primary interface for all marker symbols. All other marker symbol interfaces inherit the properties and methods of IMarkerSymbol. The interface has five read_write properties that allow you to get and set the basic properties of any MarkerSymbol. The Color property can be set to any IColor object, and its effects will be dependent on the type of Class you are using.
Marker Symbol Color Properties
The Size property sets the overall height of the symbol if the symbol is a SimpleMarkerSymbol, CharacterMarkerSymbol, PictureMarkerSymbol, or MultiLayerMarkerSymbol. For an ArrowMarkerSymbol, Size sets the length. The units are points. The default size is eight for all marker symbols except the PictureMarkerSymbol—its default size is 12. The Angle property sets the angle in degrees to which the symbol is rotated counterclockwise from the horizontal axis; and its default is 0. The XOffset and YOffset properties determine the distance to which the symbol is drawn offset from the actual location of the feature. The properties are both in printer's points, both have a default of zero, and both can be negative or positive; positive numbers indicate an offset above and to the right of the feature, and negative numbers indicate an offset below and to the left. Below, you create an ArrowMarkerSymbol and set only the properties inherited from IMarkerSymbol.
[VB.NET]
Dim pArrow As IMarkerSymbol = New ArrowMarkerSymbolClass()
pArrow.Angle = 60
pArrow.Size = 50
pArrow.XOffset = 20
pArrow.YOffset = 30
pArrow.Color = pColor
[C#]
IMarkerSymbol pArrow = new ArrowMarkerSymbolClass();
pArrow.Angle = 60;
pArrow.Size = 50;
pArrow.XOffset = 20;
pArrow.YOffset = 30;
pArrow.Color = pColor;
The Size, XOffset, and YOffset of a marker symbol is in printer's points—1/72 of an inch.
The types of marker symbols
The rotation of a marker symbol is specified in mathematical notation.
Marker Symbol Rotation
Below are some examples of each of the marker symbol types.