WPF Graphics Rendering Overview
This topic provides an overview of the WPF visual layer. It focuses on the role of the Visual class for rendering support in the WPF model.
Role of the Visual Object
The Visual class is the basic abstraction from which every FrameworkElement object derives. It also serves as the entry point for writing new controls in WPF, and in many ways can be thought of as the window handle (HWND) in the Win32 application model.
The Visual object is a core WPF object, whose primary role is to provide rendering support. User interface controls, such as Button and TextBox, derive from the Visual class, and use it for persisting their rendering data. The Visual object provides support for:
Output display: Rendering the persisted, serialized drawing content of a visual.
Transformations: Performing transformations on a visual.
Clipping: Providing clipping region support for a visual.
Hit testing: Determining whether a coordinate or geometry is contained within the bounds of a visual.
Bounding box calculations: Determining the bounding rectangle of a visual.
However, the Visual object does not include support for non-rendering features, such as:
Event handling
Layout
Styles
Data binding
Globalization
Visual is exposed as a public abstract class from which child classes must be derived. The following illustration shows the hierarchy of the visual objects that are exposed in WPF.
DrawingVisual Class
The DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout or event handling, which improves its runtime performance. For this reason, drawings are ideal for backgrounds and clip art. The DrawingVisual can be used to create a custom visual object. For more information, see Using DrawingVisual Objects.
Viewport3DVisual Class
The Viewport3DVisual provides a bridge between 2D Visual and Visual3D objects. The Visual3D class is the base class for all 3D visual elements. The Viewport3DVisual requires that you define a Camera value and a Viewport value. The camera allows you to view the scene. The viewport establishes where the projection maps onto the 2D surface. For more information on 3D in WPF, see 3D Graphics Overview.
ContainerVisual Class
The ContainerVisual class is used as a container for a collection of Visual objects. The DrawingVisual class derives from the ContainerVisual class, allowing it to contain a collection of visual objects.
Drawing Content in Visual Objects
A Visual object stores its render data as a vector graphics instruction list. Each item in the instruction list represents a low-level set of graphics data and associated resources in a serialized format. There are four different types of render data that can contain drawing content.
Drawing content type | Description |
---|---|
Vector graphics | Represents vector graphics data, and any associated Brush and Pen information. |
Image | Represents an image within a region defined by a Rect. |
Glyph | Represents a drawing that renders a GlyphRun, which is a sequence of glyphs from a specified font resource. This is how text is represented. |
Video | Represents a drawing that renders video. |
The DrawingContext allows you to populate a Visual with visual content. When you use a DrawingContext object's draw commands, you are actually storing a set of render data that will later be used by the graphics system; you are not drawing to the screen in real-time.
When you create a WPF control, such as a Button, the control implicitly generates render data for drawing itself. For example, setting the Content property of the Button causes the control to store a rendering representation of a glyph.
A Visual describes its content as one or more Drawing objects contained within a DrawingGroup. A DrawingGroup also describes opacity masks, transforms, bitmap effects, and other operations that are applied to its contents. DrawingGroup operations are applied in the following order when content is rendered: OpacityMask, Opacity, BitmapEffect, ClipGeometry, GuidelineSet, and then Transform.
The following illustration shows the order in which DrawingGroup operations are applied during the rendering sequence.
Order of DrawingGroup operations
For more information, see Drawing Objects Overview.
Drawing Content at the Visual Layer
You never directly instantiate a DrawingContext; you can, however, acquire a drawing context from certain methods, such as DrawingGroup.Open and DrawingVisual.RenderOpen. The following example retrieves a DrawingContext from a DrawingVisual and uses it to draw a rectangle.
// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle()
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
' Create a DrawingVisual that contains a rectangle.
Private Function CreateDrawingVisualRectangle() As DrawingVisual
Dim drawingVisual As New DrawingVisual()
' Retrieve the DrawingContext in order to create new drawing content.
Dim drawingContext As DrawingContext = drawingVisual.RenderOpen()
' Create a rectangle and draw it in the DrawingContext.
Dim rect As New Rect(New Point(160, 100), New Size(320, 80))
drawingContext.DrawRectangle(Brushes.LightBlue, CType(Nothing, Pen), rect)
' Persist the drawing content.
drawingContext.Close()
Return drawingVisual
End Function
Enumerating Drawing Content at the Visual Layer
In addition to their other uses, Drawing objects also provide an object model for enumerating the contents of a Visual.
Note
When you are enumerating the contents of the visual, you are retrieving Drawing objects, and not the underlying representation of the render data as a vector graphics instruction list.
The following example uses the GetDrawing method to retrieve the DrawingGroup value of a Visual and enumerate it.
public void RetrieveDrawing(Visual v)
{
DrawingGroup drawingGroup = VisualTreeHelper.GetDrawing(v);
EnumDrawingGroup(drawingGroup);
}
// Enumerate the drawings in the DrawingGroup.
public void EnumDrawingGroup(DrawingGroup drawingGroup)
{
DrawingCollection dc = drawingGroup.Children;
// Enumerate the drawings in the DrawingCollection.
foreach (Drawing drawing in dc)
{
// If the drawing is a DrawingGroup, call the function recursively.
if (drawing is DrawingGroup group)
{
EnumDrawingGroup(group);
}
else if (drawing is GeometryDrawing)
{
// Perform action based on drawing type.
}
else if (drawing is ImageDrawing)
{
// Perform action based on drawing type.
}
else if (drawing is GlyphRunDrawing)
{
// Perform action based on drawing type.
}
else if (drawing is VideoDrawing)
{
// Perform action based on drawing type.
}
}
}
How Visual Objects are Used to Build Controls
Many of the objects in WPF are composed of other visual objects, meaning they can contain varying hierarchies of descendant objects. Many of the user interface elements in WPF, such as controls, are composed of multiple visual objects, representing different types of rendering elements. For example, the Button control can contain a number of other objects, including ClassicBorderDecorator, ContentPresenter, and TextBlock.
The following code shows a Button control defined in markup.
<Button Click="OnClick">OK</Button>
If you were to enumerate the visual objects that comprise the default Button control, you would find the hierarchy of visual objects illustrated below:
The Button control contains a ClassicBorderDecorator element, which in turn, contains a ContentPresenter element. The ClassicBorderDecorator element is responsible for drawing a border and a background for the Button. The ContentPresenter element is responsible for displaying the contents of the Button. In this case, since you are displaying text, the ContentPresenter element contains a TextBlock element. The fact that the Button control uses a ContentPresenter means that the content could be represented by other elements, such as an Image or a geometry, such as an EllipseGeometry.
Control Templates
The key to the expansion of a control into a hierarchy of controls is the ControlTemplate. A control template specifies the default visual hierarchy for a control. When you explicitly reference a control, you implicitly reference its visual hierarchy. You can override the default values for a control template to create a customized visual appearance for a control. For example, you could modify the background color value of the Button control so that it uses a linear gradient color value instead of a solid color value. For more information, see Button Styles and Templates.
A user interface element, such as a Button control, contains several vector graphics instruction lists that describe the entire rendering definition of a control. The following code shows a Button control defined in markup.
<Button Click="OnClick">
<Image Source="images\greenlight.jpg"></Image>
</Button>
If you were to enumerate the visual objects and vector graphics instruction lists that comprise the Button control, you would find the hierarchy of objects illustrated below:
The Button control contains a ClassicBorderDecorator element, which in turn, contains a ContentPresenter element. The ClassicBorderDecorator element is responsible for drawing all the discrete graphic elements that make up the border and background of a button. The ContentPresenter element is responsible for displaying the contents of the Button. In this case, since you are displaying an image, the ContentPresenter element contains a Image element.
There are a number of points to note about the hierarchy of visual objects and vector graphics instruction lists:
The ordering in the hierarchy represents the rendering order of the drawing information. From the root visual element, child elements are traversed, left to right, top to bottom. If an element has visual child elements, they are traversed before the element’s siblings.
Non-leaf node elements in the hierarchy, such as ContentPresenter, are used to contain child elements—they do not contain instruction lists.
If a visual element contains both a vector graphics instruction list and visual children, the instruction list in the parent visual element is rendered before drawings in any of the visual child objects.
The items in the vector graphics instruction list are rendered left to right.
Visual Tree
The visual tree contains all visual elements used in an application's user interface. Since a visual element contains persisted drawing information, you can think of the visual tree as a scene graph, containing all the rendering information needed to compose the output to the display device. This tree is the accumulation of all visual elements created directly by the application, whether in code or in markup. The visual tree also contains all visual elements created by the template expansion of elements such as controls and data objects.
The following code shows a StackPanel element defined in markup.
<StackPanel>
<Label>User name:</Label>
<TextBox />
<Button Click="OnClick">OK</Button>
</StackPanel>
If you were to enumerate the visual objects that comprise the StackPanel element in the markup example, you would find the hierarchy of visual objects illustrated below:
Rendering Order
The visual tree determines the rendering order of WPF visual and drawing objects. The order of traversal starts with the root visual, which is the top-most node in the visual tree. The root visual’s children are then traversed, left to right. If a visual has children, its children are traversed before the visual’s siblings. This means that the content of a child visual is rendered in front of the visual's own content.
Root Visual
The root visual is the top-most element in a visual tree hierarchy. In most applications, the base class of the root visual is either Window or NavigationWindow. However, if you were hosting visual objects in a Win32 application, the root visual would be the top-most visual you were hosting in the Win32 window. For more information, see Tutorial: Hosting Visual Objects in a Win32 Application.
Relationship to the Logical Tree
The logical tree in WPF represents the elements of an application at run time. Although you do not manipulate this tree directly, this view of the application is useful for understanding property inheritance and event routing. Unlike the visual tree, the logical tree can represent non-visual data objects, such as ListItem. In many cases, the logical tree maps very closely to an application's markup definitions. The following code shows a DockPanel element defined in markup.
<DockPanel>
<ListBox>
<ListBoxItem>Dog</ListBoxItem>
<ListBoxItem>Cat</ListBoxItem>
<ListBoxItem>Fish</ListBoxItem>
</ListBox>
<Button Click="OnClick">OK</Button>
</DockPanel>
If you were to enumerate the logical objects that comprise the DockPanel element in the markup example, you would find the hierarchy of logical objects illustrated below:
Diagram of logical tree
Both the visual tree and logical tree are synchronized with the current set of application elements, reflecting any addition, deletion, or modification of elements. However, the trees present different views of the application. Unlike the visual tree, the logical tree does not expand a control's ContentPresenter element. This means there is not a direct one-to-one correspondence between a logical tree and a visual tree for the same set of objects. In fact, invoking the LogicalTreeHelper object's GetChildren method and the VisualTreeHelper object's GetChild method using the same element as a parameter yields differing results.
For more information on the logical tree, see Trees in WPF.
Viewing the Visual Tree with XamlPad
The WPF tool, XamlPad, provides an option for viewing and exploring the visual tree that corresponds to the currently defined XAML content. Click the Show Visual Tree button on the menu bar to display the visual tree. The following illustrates the expansion of XAML content into visual tree nodes in the Visual Tree Explorer panel of XamlPad:
Notice how the Label, TextBox, and Button controls each display a separate visual object hierarchy in the Visual Tree Explorer panel of XamlPad. This is because WPF controls have a ControlTemplate that contains the visual tree of that control. When you explicitly reference a control, you implicitly reference its visual hierarchy.
Profiling Visual Performance
WPF provides a suite of performance profiling tools that allow you to analyze the run-time behavior of your application and determine the types of performance optimizations you can apply. The Visual Profiler tool provides a rich, graphical view of performance data by mapping directly to the application's visual tree. In this screenshot, the CPU Usage section of the Visual Profiler gives you a precise breakdown of an object's use of WPF services, such as rendering and layout.
Visual Profiler display output
Visual Rendering Behavior
WPF introduces several features that affect the rendering behavior of visual objects: retained mode graphics, vector graphics, and device independent graphics.
Retained Mode Graphics
One of the keys to understanding the role of the Visual object is to understand the difference between immediate mode and retained mode graphics systems. A standard Win32 application based on GDI or GDI+ uses an immediate mode graphics system. This means that the application is responsible for repainting the portion of the client area that is invalidated, due to an action such as a window being resized, or an object changing its visual appearance.
In contrast, WPF uses a retained mode system. This means application objects that have a visual appearance define a set of serialized drawing data. Once the drawing data is defined, the system is responsible thereafter for responding to all repaint requests for rendering the application objects. Even at run time, you can modify or create application objects, and still rely on the system for responding to paint requests. The power in a retained mode graphics system is that drawing information is always persisted in a serialized state by the application, but rendering responsibility left to the system. The following diagram shows how the application relies on WPF for responding to paint requests.
Intelligent Redrawing
One of the biggest benefits in using retained mode graphics is that WPF can efficiently optimize what needs to be redrawn in the application. Even if you have a complex scene with varying levels of opacity, you generally do not need to write special-purpose code to optimize redrawing. Compare this with Win32 programming in which you can spend a great deal of effort in optimizing your application by minimizing the amount of redrawing in the update region. See Redrawing in the Update Region for an example of the type of complexity involved in optimizing redrawing in Win32 applications.
Vector Graphics
WPF uses vector graphics as its rendering data format. Vector graphics—which include Scalable Vector Graphics (SVG), Windows metafiles (.wmf), and TrueType fonts—store rendering data and transmit it as a list of instructions that describe how to recreate an image using graphics primitives. For example, TrueType fonts are outline fonts that describe a set of lines, curves, and commands, rather than an array of pixels. One of the key benefits of vector graphics is the ability to scale to any size and resolution.
Unlike vector graphics, bitmap graphics store rendering data as a pixel-by-pixel representation of an image, pre-rendered for a specific resolution. One of the key differences between bitmap and vector graphic formats is fidelity to the original source image. For example, when the size of a source image is modified, bitmap graphics systems stretch the image, whereas vector graphics systems scale the image, preserving the image fidelity.
The following illustration shows a source image that has been resized by 300%. Notice the distortions that appear when the source image is stretched as a bitmap graphics image rather than scaled as a vector graphics image.
The following markup shows two Path elements defined. The second element uses a ScaleTransform to resize the drawing instructions of the first element by 300%. Notice that the drawing instructions in the Path elements remain unchanged.
<Path
Data="M10,100 C 60,0 100,200 150,100 z"
Fill="{StaticResource linearGradientBackground}"
Stroke="Black"
StrokeThickness="2" />
<Path
Data="M10,100 C 60,0 100,200 150,100 z"
Fill="{StaticResource linearGradientBackground}"
Stroke="Black"
StrokeThickness="2" >
<Path.RenderTransform>
<ScaleTransform ScaleX="3.0" ScaleY="3.0" />
</Path.RenderTransform>
</Path>
About Resolution and Device-Independent Graphics
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.
The other system setting, DPI, describes the size of a screen inch in pixels. Most Windows systems have a DPI of 96, which means a screen inch is 96 pixels. Increasing the DPI setting makes the screen inch larger; decreasing the DPI makes the screen inch smaller. This means that a screen inch isn't the same size as a real-world inch; on most systems, it's probably not. As you increase the DPI, DPI-aware graphics and text become larger because you've increased the size of the screen inch. Increasing the DPI can make text easier to read, especially at high resolutions.
Not all applications are DPI-aware: some use hardware pixels as the primary unit of measurement; changing the system DPI has no effect on these applications. Many other applications use DPI-aware units to describe font sizes, but use pixels to describe everything else. Making the DPI too small or too large can cause layout problems for these applications, because the applications' text scales with the system's DPI setting, but the applications' UI does not. This problem has been eliminated for applications developed using WPF.
WPF supports automatic scaling by using the device independent pixel as its primary unit of measurement, instead of hardware pixels; graphics and text scale properly without any extra work from the application developer. The following illustration shows an example of how WPF text and graphics are appear at different DPI settings.
Graphics and text at different DPI settings
VisualTreeHelper Class
The VisualTreeHelper class is a static helper class that provides low-level functionality for programming at the visual object level, which is useful in very specific scenarios, such as developing high-performance custom controls. In most case, the higher-level WPF framework objects, such as Canvas and TextBlock, offer greater flexibility and ease of use.
Hit Testing
The VisualTreeHelper class provides methods for hit testing on visual objects when the default hit test support does not meet your needs. You can use the HitTest methods in the VisualTreeHelper class to determine whether a geometry or point coordinate value is within the boundary of a given object, such as a control or graphic element. For example, you could use hit testing to determine whether a mouse click within the bounding rectangle of an object falls within the geometry of a circle You can also choose to override the default implementation of hit testing to perform your own custom hit test calculations.
For more information on hit testing, see Hit Testing in the Visual Layer.
Enumerating the Visual Tree
The VisualTreeHelper class provides functionality for enumerating the members of a visual tree. To retrieve a parent, call the GetParent method. To retrieve a child, or direct descendant, of a visual object, call the GetChild method. This method returns a child Visual of the parent at the specified index.
The following example shows how to enumerate all the descendants of a visual object, which is a technique you might want to use if you were interested in serializing all the rendering information of a visual object hierarchy.
// Enumerate all the descendants of the visual object.
static public void EnumVisual(Visual myVisual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
// Do processing of the child visual object.
// Enumerate children of the child visual object.
EnumVisual(childVisual);
}
}
' Enumerate all the descendants of the visual object.
Public Shared Sub EnumVisual(ByVal myVisual As Visual)
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(myVisual) - 1
' Retrieve child visual at specified index value.
Dim childVisual As Visual = CType(VisualTreeHelper.GetChild(myVisual, i), Visual)
' Do processing of the child visual object.
' Enumerate children of the child visual object.
EnumVisual(childVisual)
Next i
End Sub
In most cases, the logical tree is a more useful representation of the elements in a WPF application. Although you do not modify the logical tree directly, this view of the application is useful for understanding property inheritance and event routing. Unlike the visual tree, the logical tree can represent non-visual data objects, such as ListItem. For more information on the logical tree, see Trees in WPF.
The VisualTreeHelper class provides methods for returning the bounding rectangle of visual objects. You can return the bounding rectangle of a visual object by calling GetContentBounds. You can return the bounding rectangle of all the descendants of a visual object, including the visual object itself, by calling GetDescendantBounds. The following code shows how you would calculate the bounding rectangle of a visual object and all its descendants.
// Return the bounding rectangle of the parent visual object and all of its descendants.
Rect rectBounds = VisualTreeHelper.GetDescendantBounds(parentVisual);
' Return the bounding rectangle of the parent visual object and all of its descendants.
Dim rectBounds As Rect = VisualTreeHelper.GetDescendantBounds(parentVisual)
See also
.NET Desktop feedback