Redigera

Dela via


WPF Architecture

This topic provides a guided tour of the Windows Presentation Foundation (WPF) class hierarchy. It covers most of the major subsystems of WPF, and describes how they interact. It also details some of the choices made by the architects of WPF.

System.Object

The primary WPF programming model is exposed through managed code. Early in the design phase of WPF there were a number of debates about where the line should be drawn between the managed components of the system and the unmanaged ones. The CLR provides a number of features that make development more productive and robust (including memory management, error handling, common type system, etc.) but they come at a cost.

The major components of WPF are illustrated in the figure below. The red sections of the diagram (PresentationFramework, PresentationCore, and milcore) are the major code portions of WPF. Of these, only one is an unmanaged component – milcore. Milcore is written in unmanaged code in order to enable tight integration with DirectX. All display in WPF is done through the DirectX engine, allowing for efficient hardware and software rendering. WPF also required fine control over memory and execution. The composition engine in milcore is extremely performance sensitive, and required giving up many advantages of the CLR to gain performance.

The position of WPF within the .NET Framework.

Communication between the managed and unmanaged portions of WPF is discussed later in this topic. The remainder of the managed programming model is described below.

System.Threading.DispatcherObject

Most objects in WPF derive from DispatcherObject, which provides the basic constructs for dealing with concurrency and threading. WPF is based on a messaging system implemented by the dispatcher. This works much like the familiar Win32 message pump; in fact, the WPF dispatcher uses User32 messages for performing cross thread calls.

There are really two core concepts to understand when discussing concurrency in WPF – the dispatcher and thread affinity.

During the design phase of WPF, the goal was to move to a single thread of execution, but a non-thread "affinitized" model. Thread affinity happens when a component uses the identity of the executing thread to store some type of state. The most common form of this is to use the thread local store (TLS) to store state. Thread affinity requires that each logical thread of execution be owned by only one physical thread in the operating system, which can become memory intensive. In the end, WPF’s threading model was kept in sync with the existing User32 threading model of single threaded execution with thread affinity. The primary reason for this was interoperability – systems like OLE 2.0, the clipboard, and Internet Explorer all require single thread affinity (STA) execution.

Given that you have objects with STA threading, you need a way to communicate between threads, and validate that you are on the correct thread. Herein lies the role of the dispatcher. The dispatcher is a basic message dispatching system, with multiple prioritized queues. Examples of messages include raw input notifications (mouse moved), framework functions (layout), or user commands (execute this method). By deriving from DispatcherObject, you create a CLR object that has STA behavior, and will be given a pointer to a dispatcher at creation time.

System.Windows.DependencyObject

One of the primary architectural philosophies used in building WPF was a preference for properties over methods or events. Properties are declarative and allow you to more easily specify intent instead of action. This also supported a model driven, or data driven, system for displaying user interface content. This philosophy had the intended effect of creating more properties that you could bind to, in order to better control the behavior of an application.

In order to have more of the system driven by properties, a richer property system than what the CLR provides was needed. A simple example of this richness is change notifications. In order to enable two way binding, you need both sides of the bind to support change notification. In order to have behavior tied to property values, you need to be notified when the property value changes. The Microsoft .NET Framework has an interface, INotifyPropertyChange, which allows an object to publish change notifications, however it is optional.

WPF provides a richer property system, derived from the DependencyObject type. The property system is truly a "dependency" property system in that it tracks dependencies between property expressions and automatically revalidates property values when dependencies change. For example, if you have a property that inherits (like FontSize), the system is automatically updated if the property changes on a parent of an element that inherits the value.

The foundation of the WPF property system is the concept of a property expression. In this first release of WPF, the property expression system is closed, and the expressions are all provided as part of the framework. Expressions are why the property system doesn’t have data binding, styling, or inheritance hard coded, but rather provided by later layers within the framework.

The property system also provides for sparse storage of property values. Because objects can have dozens (if not hundreds) of properties, and most of the values are in their default state (inherited, set by styles, etc.), not every instance of an object needs to have the full weight of every property defined on it.

The final new feature of the property system is the notion of attached properties. WPF elements are built on the principle of composition and component reuse. It is often the case that some containing element (like a Grid layout element) needs additional data on child elements to control its behavior (like the Row/Column information). Instead of associating all of these properties with every element, any object is allowed to provide property definitions for any other object. This is similar to the "expando" features of JavaScript.

System.Windows.Media.Visual

With a system defined, the next step is getting pixels drawn to the screen. The Visual class provides for building a tree of visual objects, each optionally containing drawing instructions and metadata about how to render those instructions (clipping, transformation, etc.). Visual is designed to be extremely lightweight and flexible, so most of the features have no public API exposure and rely heavily on protected callback functions.

Visual is really the entry point to the WPF composition system. Visual is the point of connection between these two subsystems, the managed API and the unmanaged milcore.

WPF displays data by traversing the unmanaged data structures managed by the milcore. These structures, called composition nodes, represent a hierarchical display tree with rendering instructions at each node. This tree, illustrated on the right hand side of the figure below, is only accessible through a messaging protocol.

When programming WPF, you create Visual elements, and derived types, which internally communicate to the composition tree through this messaging protocol. Each Visual in WPF may create one, none, or several composition nodes.

The Windows Presentation Foundation Visual Tree.

There is a very important architectural detail to notice here – the entire tree of visuals and drawing instructions is cached. In graphics terms, WPF uses a retained rendering system. This enables the system to repaint at high refresh rates without the composition system blocking on callbacks to user code. This helps prevent the appearance of an unresponsive application.

Another important detail that isn’t really noticeable in the diagram is how the system actually performs composition.

In User32 and GDI, the system works on an immediate mode clipping system. When a component needs to be rendered, the system establishes a clipping bounds outside of which the component isn’t allowed to touch the pixels, and then the component is asked to paint pixels in that box. This system works very well in memory constrained systems because when something changes you only have to touch the affected component – no two components ever contribute to the color of a single pixel.

WPF uses a "painter's algorithm" painting model. This means that instead of clipping each component, each component is asked to render from the back to the front of the display. This allows each component to paint over the previous component's display. The advantage of this model is that you can have complex, partially transparent shapes. With today’s modern graphics hardware, this model is relatively fast (which wasn’t the case when User32/ GDI were created).

As mentioned previously, a core philosophy of WPF is to move to a more declarative, "property centric" model of programming. In the visual system, this shows up in a couple of interesting places.

First, if you think about the retained mode graphic system, this is really moving away from an imperative DrawLine/DrawLine type model, to a data oriented model – new Line()/new Line(). This move to data driven rendering allows complex operations on the drawing instructions to be expressed using properties. The types deriving from Drawing are effectively the object model for rendering.

Second, if you evaluate the animation system, you'll see that it is almost completely declarative. Instead of requiring a developer to compute the next location, or next color, you can express animations as a set of properties on an animation object. These animations can then express the intent of the developer or designer (move this button from here to there in 5 seconds), and the system can determine the most efficient way to accomplish that.

System.Windows.UIElement

UIElement defines core subsystems including Layout, Input, and Events.

Layout is a core concept in WPF. In many systems there is either a fixed set of layout models (HTML supports three models for layout; flow, absolute, and tables) or no model for layout (User32 really only supports absolute positioning). WPF started with the assumption that developers and designers wanted a flexible, extensible layout model, which could be driven by property values rather than imperative logic. At the UIElement level, the basic contract for layout is introduced – a two phase model with Measure and Arrange passes.

Measure allows a component to determine how much size it would like to take. This is a separate phase from Arrange because there are many situations where a parent element will ask a child to measure several times to determine its optimal position and size. The fact that parent elements ask child elements to measure demonstrates another key philosophy of WPF – size to content. All controls in WPF support the ability to size to the natural size of their content. This makes localization much easier, and allows for dynamic layout of elements as things resize. The Arrange phase allows a parent to position and determine the final size of each child.

A lot of time is often spent talking about the output side of WPF – Visual and related objects. However there is a tremendous amount of innovation on the input side as well. Probably the most fundamental change in the input model for WPF is the consistent model by which input events are routed through the system.

Input originates as a signal on a kernel mode device driver and gets routed to the correct process and thread through an intricate process involving the Windows kernel and User32. Once the User32 message corresponding to the input is routed to WPF, it is converted into a WPF raw input message and sent to the dispatcher. WPF allows for raw input events to be converted to multiple actual events, enabling features like "MouseEnter" to be implemented at a low level of the system with guaranteed delivery.

Each input event is converted to at least two events – a "preview" event and the actual event. All events in WPF have a notion of routing through the element tree. Events are said to "bubble" if they traverse from a target up the tree to the root, and are said to "tunnel" if they start at the root and traverse down to a target. Input preview events tunnel, enabling any element in the tree an opportunity to filter or take action on the event. The regular (non-preview) events then bubble from the target up to the root.

This split between the tunnel and bubble phase makes implementation of features like keyboard accelerators work in a consistent fashion in a composite world. In User32 you would implement keyboard accelerators by having a single global table containing all the accelerators you wanted to support (Ctrl+N mapping to "New"). In the dispatcher for your application you would call TranslateAccelerator which would sniff the input messages in User32 and determine if any matched a registered accelerator. In WPF this wouldn’t work because the system is fully "composable" – any element can handle and use any keyboard accelerator. Having this two phase model for input allows components to implement their own "TranslateAccelerator".

To take this one step further, UIElement also introduces the notion of CommandBindings. The WPF command system allows developers to define functionality in terms of a command end point – something that implements ICommand. Command bindings enable an element to define a mapping between an input gesture (Ctrl+N) and a command (New). Both the input gestures and command definitions are extensible, and can be wired together at usage time. This makes it trivial, for example, to allow an end user to customize the key bindings that they want to use within an application.

To this point in the topic, "core" features of WPF – features implemented in the PresentationCore assembly, have been the focus. When building WPF, a clean separation between foundational pieces (like the contract for layout with Measure and Arrange) and framework pieces (like the implementation of a specific layout like Grid) was the desired outcome. The goal was to provide an extensibility point low in the stack that would allow external developers to create their own frameworks if needed.

System.Windows.FrameworkElement

FrameworkElement can be looked at in two different ways. It introduces a set of policies and customizations on the subsystems introduced in lower layers of WPF. It also introduces a set of new subsystems.

The primary policy introduced by FrameworkElement is around application layout. FrameworkElement builds on the basic layout contract introduced by UIElement and adds the notion of a layout "slot" that makes it easier for layout authors to have a consistent set of property driven layout semantics. Properties like HorizontalAlignment, VerticalAlignment, MinWidth, and Margin (to name a few) give all components derived from FrameworkElement consistent behavior inside of layout containers.

FrameworkElement also provides easier API exposure to many features found in the core layers of WPF. For example, FrameworkElement provides direct access to animation through the BeginStoryboard method. A Storyboard provides a way to script multiple animations against a set of properties.

The two most critical things that FrameworkElement introduces are data binding and styles.

The data binding subsystem in WPF should be relatively familiar to anyone that has used Windows Forms or ASP.NET for creating an application user interface (UI). In each of these systems, there is a simple way to express that you want one or more properties from a given element to be bound to a piece of data. WPF has full support for property binding, transformation, and list binding.

One of the most interesting features of data binding in WPF is the introduction of data templates. Data templates allow you to declaratively specify how a piece of data should be visualized. Instead of creating a custom user interface that can be bound to data, you can instead turn the problem around and let the data determine the display that will be created.

Styling is really a lightweight form of data binding. Using styling you can bind a set of properties from a shared definition to one or more instances of an element. Styles get applied to an element either by explicit reference (by setting the Style property) or implicitly by associating a style with the CLR type of the element.

System.Windows.Controls.Control

Control’s most significant feature is templating. If you think about WPF’s composition system as a retained mode rendering system, templating allows a control to describe its rendering in a parameterized, declarative manner. A ControlTemplate is really nothing more than a script to create a set of child elements, with bindings to properties offered by the control.

Control provides a set of stock properties, Foreground, Background, Padding, to name a few, which template authors can then use to customize the display of a control. The implementation of a control provides a data model and interaction model. The interaction model defines a set of commands (like Close for a window) and bindings to input gestures (like clicking the red X in the upper corner of the window). The data model provides a set of properties to either customize the interaction model or customize the display (determined by the template).

This split between the data model (properties), interaction model (commands and events), and display model (templates) enables complete customization of a control’s look and behavior.

A common aspect of the data model of controls is the content model. If you look at a control like Button, you will see that it has a property named "Content" of type Object. In Windows Forms and ASP.NET, this property would typically be a string – however that limits the type of content you can put in a button. Content for a button can either be a simple string, a complex data object, or an entire element tree. In the case of a data object, the data template is used to construct a display.

Summary

WPF is designed to allow you to create dynamic, data driven presentation systems. Every part of the system is designed to create objects through property sets that drive behavior. Data binding is a fundamental part of the system, and is integrated at every layer.

Traditional applications create a display and then bind to some data. In WPF, everything about the control, every aspect of the display, is generated by some type of data binding. The text found inside a button is displayed by creating a composed control inside of the button and binding its display to the button’s content property.

When you begin developing WPF based applications, it should feel very familiar. You can set properties, use objects, and data bind in much the same way that you can using Windows Forms or ASP.NET. With a deeper investigation into the architecture of WPF, you'll find that the possibility exists for creating much richer applications that fundamentally treat data as the core driver of the application.

See also