Users interact with a Windows Presentation Foundation (WPF) standalone application through a Window. The primary purpose of a Window is to host visual content and enable users to interact with that content. Standalone WPF applications use the Window class to provide their own windows.
That is MSDN's description of Window. Although that Chinese translation isn't quite natural English, we can still see Window's two major functions: first, host visual content; second, enable users to interact with that visual content.
Before formally studying Window's features, let's first look at what a Window actually is.
What Is a Window?
Window is at the heart of the Windows operating system. On the surface, Windows is composed of many, many windows. So what exactly is a Window?
In the usual sense, the windows we talk about are the outermost ones — the main windows with Close and Minimize buttons. In Windows programming, CreateWindow is used to create a window; dwStyle specifies the style, for example setting LBS_OWNERDRAWFIXED creates a ListBox, BS_PUSHBUTTON creates a Button, and so on. The return value of CreateWindow is the window handle. In that sense, in the Win32 world everything is a window — only the presentation differs. So what does a WPF Window correspond to?
Window in WPF
A Window in WPF inherits from ContentControl and hosts a single Content internally. Of course, with ItemsControl or Panel, Content can expand into many objects. These objects are all WPF objects — the visual content to be hosted. So how is user interaction with that visual content completed?
Whether drawing with GDI or DirectX, from the operating system's point of view a Window is a valid region that holds a handle. All operations on that region are sent through the handle to the message-handling function associated with the Window. That is, externally a WPF Window is still a traditional Win32 window; internally it converts messages into Routed Events, Commands, and so on for handling. To go deeper into this layer of processing and message conversion, we need to open up the WPF Window.
Digging into the WPF Window
As a bridge between the outside world and visual content, Window serves both an inward-facing and an outward-facing role.
Inward first: Window may contain Button, ListBox, and other controls, forming an object tree. The tree may have many child nodes, but there is only one root. This object tree is the core of WPF; Routed Events, Routed Commands, and similar mechanisms all attach to it. Leaving aside the specific object tree, what we care about is its "many-below-one" shape. If you imagine this object tree of visual content as a person, the root is the head; to move the arms and legs you only need to shout instructions at the head. In other words, for a WPF Window, the main inward concern is finding the head of the object tree (RootVisual) and then passing operations down through it.
Outward, the operating system cares about the registered Window style and Rect. For example, when a mouse button is pressed, the button message is sent to the system message queue. The system walks the Rects of all registered windows to determine which window the press occurred in, and at the appropriate time moves the button message from the system queue into the message queue of the thread that created the window, for the window to process. For a WPF Window, synchronizing this Rect is important: the Window's UI is WPF, but inside there is a hidden Win32 window created with CreateWindow. When the user sets win.Width = 60, the internal window's Rect must be synchronized; conversely, when WM_SIZE is received, RootVisual must be invoked to run WPF's Measure and Arrange passes.
A rough sketch of Window's message-handling process:
- The system sends the message to the hidden Win32 window. In the Dispatcher, GetMessage retrieves it and it is dispatched to the corresponding window procedure WndProc.
- WndProc should be a large switch-case handling different window messages. By message category, WPF provides different Managers to manage them. These Managers do not handle window messages directly, and not all messages go through WndProc and then to a Manager. For example, with WM_KEYDOWN: after the Dispatcher obtains the message via GetMessage, it calls ComponentDispatcher's RaiseThreadMessage method (for ComponentDispatcher, see Nick's article); finally KeyboardDevice raises the Keyboard.PreviewKeyDownEvent routed event.
- Still using WM_KEYDOWN: InputManager finds the region where the input occurred — the Window — and calls the Window's RaiseEvent method to raise the Keyboard.PreviewKeyDownEvent routed event.
- The routed event begins propagating along the object tree. The direction is out-and-back: from PreviewKeyDown to KeyDown. During this propagation, related routed events are also raised; for example, if focus is currently on a Button, when the event reaches the Button the Button's ClickEvent may also be raised, and so on.
Some of these Managers, such as ContentLayoutManager, are themselves internal and used only inside Measure and Arrange; here we are only saying that messages, after being classified, are ultimately managed by these Managers. The interesting part of this process is Input. Let's briefly discuss it.
Input
Routed events are the core of how WPF handles input. Briefly, there is an out-and-back process from PreviewKeyDown to KeyDown. PreviewKeyDown goes from parent to child; KeyDown goes from child to parent. This article is not about that processing pipeline; the focus is how a simple WM_KEYDOWN message is converted into the two routed events PreviewKeyDown and KeyDown.
As the diagram shows, InputManager is responsible for handling input. One input may come from different devices — Mouse, Keyboard, and so on. InputManager needs to answer two questions: first, what routed event this input will become; second, which UIElement the input acts on. The first conversion is done by an InputDevice — specifically MouseDevice, KeyboardDevice, and so on. It generates the corresponding routed event from the window message and reports this information to InputManager. InputManager then uses that information to find the target UIElement and raises the routed event.
Having covered Input, let's focus on Presentation. The name is Windows Presentation Foundation, so display is clearly a central concern.
Presentation
Earlier we introduced the visual content that needs to be displayed; in WPF it is organized as an object tree (more precisely, the Visual Tree). How is it actually drawn? What happens between the object tree and real rendering?
The figure is WPF's architecture diagram. Two important pieces are PresentationCore and MilCore. In PresentationCore the Visual class is defined; this is the heart of WPF display. Everything that can be displayed inherits from Visual directly or indirectly. The Visual Tree here means the tree composed of Visuals. MilCore (MIL — Media Integration Layer) is unmanaged code responsible for communication between WPF and DirectX. It mainly has two parts: first, the Composition Engine; second, the Render Engine. The former is responsible for creating the Composition Tree; the latter converts the Composition Tree into triangles DirectX understands and notifies DirectX to render.
A brief outline of the render flow:
- A Visual is added to the Visual Tree.
- The Visual Tree and the Composition Engine communicate through Message Transport, which includes Transport and Channel. Transport defines the transfer details; Channel operates on Transport and establishes a two-way communication pipe. When the Visual Tree is modified, the modified Visual data is sent to the Composition Engine through the Channel.
- After the Composition Engine receives the Visual data, it creates the corresponding Composition Node and adds it to the Composition Tree.
- The Composition Engine notifies the Render Engine to start drawing. Nodes in the Composition Tree are Rectangle, Ellipse, and so on — DirectX cannot understand these directly. The Render Engine converts them into DirectX-recognizable triangles; this process is called Tessellate.
- The Render Engine notifies DirectX to begin rendering. DirectX, through the driver (WDDM or XPDM), notifies the GPU to start drawing pixels to the screen.
In the first article we introduced WPF's threading model. WPF threads are split in two: a UI thread and a Render thread. The UI thread is managed code; it manages the Visual Tree and handles input and events. The Render thread is unmanaged code in the MIL; it only draws, transforming Visual data transferred from the UI thread and adding it to the Composition Tree for rendering. In this process the Render thread is passive: it waits for the UI thread to transfer data and send commands, and it also reports the results of operations (render finished, errors, and so on) back to the UI thread through the Channel.
That brings us to Visual data — how a Visual becomes a Composition Node. In the Avalon world, UCE (Unified Composition Engine) is responsible for this conversion layer. Of course, UCE cannot understand WPF objects. That lack of understanding means that if you hand it a WPF Line, it does not know how to turn it into the corresponding Composition Node; the WPF object must describe itself and tell UCE which Composition type it corresponds to. UCE provides the IResource interface, which defines a series of methods that can be passed to UCE through the Channel. WPF's Visual implements this interface; Visual subclasses override AddOnChannel / AddRefOnChannel-style methods and declare their corresponding Composition type. For example, LineGeometry declares that its type is DUCE.Resource.Type_LINEGEOMETRY. With this information, UCE can convert the transferred Visual data into the corresponding Composition Node.
Speaking of UCE: every WPF process has its own UCE. In Avalon (Windows Vista / Windows 7), the DWM (Desktop Window Manager) that paints the desktop also has its UCE (also called DUCE). Transparency requires desktop composition, and DWM also uses a Composition Tree to manage windows. Two diagrams describe UCE's processing:
Ultimately, DWM obtains the final transparent desktop effect after composition.
Of course, you do not need to dig into every detail of this process, and WPF programming rarely requires thinking from the UCE perspective. This is mainly to help clarify your mental model and understand WPF better. Having gone through these low-level details, let's bring the discussion back to Window and see how Window integrates all of this.
Inside Window
Earlier we mentioned that Window has a hidden Win32 window inside it used to receive messages. In WPF, HwndSource wraps this hidden window. So what happens between the Visual Tree and Window?
Looking at the Visual Tree, like a marionette, control the head (the root) and you can manipulate the whole thing. WPF provides CompositionTarget and PresentationSource to complete this internal handling. As for the detailed flow — that will have to wait for the next part. ^_^




Top comments (0)