Pro Wpf 4.5 in C#: Windows Presentation Foundation in .Net 4.5 [4 ed.] 1430243651, 9781430243656

Microsoft's Windows Presentation Foundation (WPF) provides you with a development framework for building high-quali

428 86 24MB

English Pages 1078 [1095] Year 2012

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Cover
Contents at a Glance
Contents
About the Author
About the Technical Reviewer
Acknowledgments
Introduction
About This Book
Chapter Overview
What You Need to Use This Book
Code Samples and URLs
Feedback
Part I: Fundamentals
Chapter 1: Introducing WPF
The Evolution of Windows Graphics
DirectX: The New Graphics Engine
Hardware Acceleration and WPF
WPF: A Higher-Level API
Resolution Independence
WPF Units
System DPI
Bitmap and Vector Graphics
The Architecture of WPF
The Class Hierarchy
WPF 4.5
The WPF Toolkit
Visual Studio 2012
The Last Word
Chapter 2: XAML
Understanding XAML
Graphical User Interfaces Before WPF
The Variants of XAML
XAML Compilation
XAML Basics
XAML Namespaces
The Code-Behind Class
Properties and Events in XAML
Simple Properties and Type Converters
Complex Properties
Markup Extensions
Attached Properties
Nesting Elements
Special Characters and Whitespace
Events
The Full Eight-Ball Example
Using Types from Other Namespaces
Loading and Compiling XAML
Code-Only
Code and Uncompiled XAML
Code and Compiled XAML
XAML Only
The Last Word
Chapter 3: Layout
Understanding Layout in WPF
The WPF Layout Philosophy
The Layout Process
The Layout Containers
Simple Layout with the StackPanel
Layout Properties
Alignment
Margin
Minimum, Maximum, and Explicit Sizes
The Border
The WrapPanel and DockPanel
The WrapPanel
The DockPanel
Nesting Layout Containers
The Grid
Fine-Tuning Rows and Columns
Layout Rounding
Spanning Rows and Columns
Splitting Windows
Shared Size Groups
The UniformGrid
Coordinate-Based Layout with the Canvas
Z-Order
The InkCanvas
Layout Examples
A Column of Settings
Dynamic Content
A Modular User Interface
The Last Word
Chapter 4: Dependency Properties
Understanding Dependency Properties
Defining a Dependency Property
Registering a Dependency Property
Adding a Property Wrapper
How WPF Uses Dependency Properties
Shared Dependency Properties
Attached Dependency Properties
Property Validation
The Validation Callback
The Coercion Callback
The Last Word
Chapter 5:Routed Events
Understanding Routed Events
Defining, Registering, and Wrapping a Routed Event
Sharing Routed Events
Raising a Routed Event
Handling a Routed Event
Event Routing
The RoutedEventArgs Class
Bubbling Events
Handling a Suppressed Event
Attached Events
Tunneling Events
WPF Events
Lifetime Events
Input Events
Keyboard Input
Handling a Key Press
Focus
Getting Key State
Mouse Input
Mouse Clicks
Capturing the Mouse
Drag-and-Drop
Multitouch Input
The Levels of Multitouch Support
Raw Touch
Manipulation
Inertia
The Last Word
Part II: Deeper Into WPF
Chapter 6: Controls
The Control Class
Background and Foreground Brushes
Fonts
Mouse Cursors
Content Controls
The Content Property
Aligning Content
The WPF Content Philosophy
Labels
Buttons
Tooltips
Specialized Containers
The ScrollViewer
The GroupBox
The TabItem
The Expander
Text Controls
Multiple Lines of Text
Text Selection
Spell Checking
The PasswordBox
List Controls
The ListBox
The ComboBox
Range-Based Controls
The Slider
The ProgressBar
Date Controls
The Last Word
Chapter 7: The Application
The Application Life Cycle
Creating an Application Object
Deriving a Custom Application Class
Application Shutdown
Application Events
Application Tasks
Showing a Splash Screen
Handling Command-Line Arguments
Accessing the Current Application
Interacting Between Windows
Single-Instance Applications
Assembly Resources
Adding Resources
Retrieving Resources
Resource-Aware Classes
Pack URIs
Resources in Other Assemblies
Content Files
Localization
Building Localizable User Interfaces
Preparing an Application for Localization
Managing the Translation Process
The Last Word
Chapter 8: Element Binding
Binding Elements Together
The Binding Expression
Binding Errors
Binding Modes
Creating Bindings with Code
Retrieving Bindings in Code
Multiple Bindings
Binding Updates
Binding Delays
Binding to Objects That Aren’t Elements
Source
RelativeSource
DataContext
The Last Word
Chapter 9: Commands
Understanding Commands
The WPF Command Model
The ICommand Interface
The RoutedCommand Class
The RoutedUICommand Class
The Command Library
Executing Commands
Command Sources
Command Bindings
Using Multiple Command Sources
Fine-Tuning Command Text
Invoking a Command Directly
Disabling Commands
Controls with Built-in Commands
Advanced Commands
Custom Commands
Using the Same Command in Different Places
Using a Command Parameter
Tracking and Reversing Commands
The Last Word
Chapter 10: Resources
Resource Basics
The Resources Collection
The Hierarchy of Resources
Static and Dynamic Resources
Nonshared Resources
Accessing Resources in Code
Application Resources
System Resources
Resource Dictionaries
Creating a Resource Dictionary
Using a Resource Dictionary
Sharing Resources Between Assemblies
The Last Word
Chapter 11: Styles and Behaviors
Style Basics
Creating a Style Object
Setting Properties
Attaching Event Handlers
The Many Layers of Styles
Automatically Applying Styles by Type
Triggers
A Simple Trigger
An Event Trigger
Behaviors
Getting Support for Behaviors
Understanding the Behavior Model
Creating a Behavior
Using a Behavior
Design-Time Behavior Support in Blend
The Last Word
Part III: Drawing and Animation
Chapter 12: Shapes, Brushes, and Transforms
Understanding Shapes
The Shape Classes
Rectangle and Ellipse
Sizing and Placing Shapes
Scaling Shapes with a Viewbox
Line
Polyline
Polygon
Line Caps and Line Joins
Dashes
Pixel Snapping
Using Brushes
The SolidColorBrush
The LinearGradientBrush
The RadialGradientBrush
The ImageBrush
A Tiled ImageBrush
The VisualBrush
The BitmapCacheBrush
Using Transforms
Transforming Shapes
Transforming Elements
Transparency
Making an Element Partially Transparent
Using Opacity Masks
The Last Word
Chapter 13: Geometries and Drawings
Paths and Geometries
Line, Rectangle, and Ellipse Geometries
Combining Shapes with GeometryGroup
Fusing Geometries with CombinedGeometry
Drawing Curves and Lines with PathGeometry
Using the Geometry Mini-Language
Clipping with Geometry
Drawings
Displaying a Drawing
Exporting Clip Art
The Last Word
Chapter 14: Effects and Visuals
Visuals
Drawing Visuals
Wrapping Visuals in an Element
Hit Testing
Complex Hit Testing
Effects
BlurEffect
DropShadowEffect
ShaderEffect
The WriteableBitmap Class
Generating a Bitmap
Writing to a WriteableBitmap
Using More-Efficient Pixel Writing
The Last Word
Chapter 15: Animation Basics
Understanding WPF Animation
Timer-Based Animation
Property-Based Animation
Basic Animation
The Animation Classes
Animations in Code
Simultaneous Animations
Animation Lifetime
The Timeline Class
Storyboards
The Storyboard
Event Triggers
Overlapping Animations
Synchronized Animations
Controlling Playback
Monitoring Progress
Animation Easing
Using an Easing Function
Easing In and Easing Out
Easing Function Classes
Creating a Custom Easing Function
Animation Performance
Desired Frame Rate
Bitmap Caching
The Last Word
Chapter 16: Advanced Animation
Animation Types Revisited
Animating Transforms
Animating Brushes
Animating Pixel Shaders
Key-Frame Animation
Discrete Key-Frame Animations
Easing Key Frames
Spline Key-Frame Animations
Path-Based Animation
Frame-Based Animation
Storyboards in Code
Creating the Main Window
Creating the Bomb User Control
Dropping the Bombs
Intercepting a Bomb
Counting Bombs and Cleaning Up
The Last Word
Part IV: Templates and Custom elements
Chapter 17: Control Templates
Understanding Logical Trees and Visual Trees
Understanding Templates
The Chrome Classes
Dissecting Controls
Creating Control Templates
A Simple Button
Template Bindings
Triggers That Change Properties
Triggers That Use Animation
Organizing Template Resources
Refactoring the Button Control Template
Applying Templates with Styles
Applying Templates Automatically
Working with User-Selected Skins
Building More Complex Templates
Nested Templates
Modifying the Scrollbar
Exploring the Control Template Examples
Visual States
The Last Word
Chapter 18: Custom Elements
Understanding Custom Elements in WPF
Building a Basic User Control
Defining Dependency Properties
Defining Routed Events
Adding Markup
Using the Control
Supporting Commands
Taking a Closer Look at User Controls
Creating a Lookless Control
Refactoring the Color Picker Code
Refactoring the Color Picker Markup
Streamlining the Control Template
Supporting Visual States
Starting the FlipPanel Class
Choosing Parts and States
The Default Control Template
Using the FlipPanel
Using a Different Control Template
Creating Custom Panels
The Two-Step Layout Process
The Canvas Clone
A Better Wrapping Panel
Custom-Drawn Elements
The OnRender() Method
Evaluating Custom Drawing
Creating a Custom-Drawn Element
Creating a Custom Decorator
The Last Word
Part V: Data
Chapter 19: Data Binding
Binding to a Database with Custom Objects
Building a Data Access Component
Building a Data Object
Displaying the Bound Object
Updating the Database
Change Notification
Binding to a Collection of Objects
Displaying and Editing Collection Items
Inserting and Removing Collection Items
Binding to the ADO.NET Objects
Binding to a LINQ Expression
Improving Performance in Long Lists
Virtualization
Item Container Recycling
Cache Length
Deferred Scrolling
Validation
Validation in the Data Object
Custom Validation Rules
Reacting to Validation Errors
Getting a List of Errors
Showing a DifferenError Indicator
Validating Multiple Values
Data Providers
The ObjectDataProvider
The XmlDataProvider
The Last Word
Chapter 20: Formatting Bound Data
Data Binding Redux
Data Conversion
Using the StringFormat Property
Introducing Value Converters
Formatting Strings with a Value Converter
Creating Objects with a Value Converter
Applying Conditional Formatting
Evaluating Multiple Properties
List Controls
List Styles
The ItemContainerStyle
A ListBox with Check Boxes or Radio Buttons
Alternating Item Style
Style Selectors
Data Templates
Separating and Reusing Templates
Using More Advanced Templates
Varying Templates
Template Selectors
Templates and Selection
Changing Item Layout
The ComboBox
The Last Word
Chapter 21: Data Views
The View Object
Retrieving a View Object
Navigating with a View
Creating a View Declaratively
Filtering, Sorting, and Grouping
Filtering Collections
Filtering the DataTable
Sorting
Grouping
Live Shaping
The Last Word
Chapter 22: Lists, Trees, and Grids
The ListView
Creating Columns with the GridView
Creating a Custom View
The TreeView
Creating a Data-Bound TreeView
Binding a DataSet to a TreeView
Just-in-Time Node Creation
The DataGrid
Resizing and Rearranging Columns
Defining Columns
Formatting and Styling Columns
Formatting Rows
Displaying Row Details
Freezing Columns
Selection
Sorting
Editing
The Last Word
Part VI: Windows, Pages, and Rich Controls
Chapter 23: Windows
The Window Class
Showing a Window
Positioning a Window
Saving and Restoring Window Location
Window Interaction
Window Ownership
The Dialog Model
Common Dialog Boxes
Nonrectangular Windows
A Simple Shaped Window
A Transparent Window with Shaped Content
Moving Shaped Windows
Resizing Shaped Windows
Putting It All Together: A Custom Control Template for Windows
Programming the Windows Taskbar
Using Jump Lists
Changing the Taskbar Icon and Preview
The Last Word
Chapter 24: Pages and Navigation
Page-Based Navigation
Page-Based Interfaces
Creating a Simple Page-Based Application with NavigationWindow
The Page Class
Hyperlinks
Hosting Pages in a Frame
Hosting Pages in Another Page
Hosting Pages in a Web Browser
The Page History
A Closer Look at URIs in WPF
Navigation History
Maintaining Custom Properties
The Navigation Service
Programmatic Navigation
Navigation Events
Managing the Journal
Adding Custom Items to the Journal
Using Page Functions
XAML Browser Applications
Creating an XBAP
Deploying an XBAP
Updating an XBAP
XBAP Security
Full-Trust XBAPs
Combination XBAP/Stand-Alone Applications
Coding for Different Security Levels
Embedding an XBAP in a Web Page
The WebBrowser Control
Navigating to a Page
Building a DOM Tree
Scripting a Web Page with .NET Code
The Last Word
Chapter 25: Menus, Toolbars, and Ribbons
Menus
The Menu Class
Menu Items
The ContextMenu Class
Menu Separators
Toolbars and Status Bars
The ToolBar
The StatusBar
Ribbons
Adding the Ribbon
The Application Menu
Tabs, Groups, and Buttons
Rich Tooltips
Keyboard Access with KeyTips
Ribbon Sizing
The Quick Access Toolbar
The Last Word
Chapter 26: Sound and Video
Playing WAV Audio
The SoundPlayer
The SoundPlayerAction
System Sounds
The MediaPlayer
The MediaElement
Playing Audio Programmatically
Handling Errors
Playing Audio with Triggers
Playing Multiple Sounds
Changing Volume, Balance, Speed, and Position
Synchronizing an Animation with Audio
Playing Video
Video Effects
Speech
Speech Synthesis
Speech Recognition
The Last Word
Chapter 27: 3-D Drawing
3-D Drawing Basics
The Viewport
3-D Objects
The Camera
Deeper into 3-D
Shading and Normals
More Complex Shapes
Model3DGroup Collections
Materials Revisited
Texture Mapping
Interactivity and Animations
Transforms
Rotations
A Flyover
The Trackball
Hit Testing
2-D Elements on 3-D Surfaces
The Last Word
Part VII: Documents and Printing
Chapter 28: Documents
Understanding Documents
Flow Documents
Understanding Flow Elements
Formatting Content Elements
Constructing a Simple Flow Document
Using Block Elements
Using Inline Elements
Interacting with Elements Programmatically
Text Justification
Read-Only Flow Document Containers
Zooming
Creating Pages and Columns
Loading Documents from a File
Printing
Editing a Flow Document
Loading a File
Saving a File
Formatting Selected Text
Getting Individual Words
Fixed Documents
Annotations
Understanding the Annotation Classes
Enabling the Annotation Service
Creating Annotations
Examining Annotations
Reacting to Annotation Changes
Storing Annotations in a Fixed Document
Customizing the Appearance of Sticky Notes
The Last Word
Chapter 29: Printing
Basic Printing
Printing an Element
Transforming Printed Output
Printing Elements Without Showing Them
Printing a Document
Manipulating the Pages in a Document Printout
Custom Printing
Printing with the Visual Layer Classes
Custom Printing with Multiple Pages
Print Settings and Management
Maintaining Print Settings
Printing Page Ranges
Managing a Print Queue
Printing Through XPS
Creating an XPS Document for a Print Preview
Writing to an In-Memory XPS Document
Printing Directly to the Printer via XPS
Asynchronous Printing
The Last Word
Part VIII: Additional Topics
Chapter 30: Interacting with Windows Forms
Assessing Interoperability
Mixing Windows and Forms
Adding Forms to a WPF Application
Adding WPF Windows to a Windows Forms Application
Showing Modal Windows and Forms
Showing Modeless Windows and Forms
Enabling Visual Styles for Windows Forms Controls
Creating Windows with Mixed Content
Understanding WPF and Windows Forms “Airspace”
Hosting Windows Forms Controls in WPF
Using WPF and Windows Forms User Controls
Hosting WPF Controls in Windows Forms
Access Keys, Mnemonics, and Focus
Property Mapping
The Last Word
Chapter 31: Multithreading
Understanding the Multithreading Model
The Dispatcher
The DispatcherObject
The BackgroundWorker
A Simple Asynchronous Operation
Creating the BackgroundWorker
Running the BackgroundWorker
Tracking Progress
Supporting Cancellation
The Last Word
Chapter 32: The Add-in Model
Choosing Between MAF and MEF
Understanding the Add-in Pipeline
How the Pipeline Works
The Add-in Folder Structure
Preparing a Solution That Uses the Add-in Model
Creating an Application That Uses Add-Ins
The Contract
The Add-in View
The Add-In
The Add-in Adapter
The Host View
The Host Adapter
The Host
More Add-Ins
Interacting with the Host
Visual Add-Ins
The Last Word
Chapter 33: ClickOnce Deployment
Understanding Application Deployment
The ClickOnce Installation Model
ClickOnce Limitations
Setting Up a Simple ClickOnce Publication
Setting the Publisher and Production
Starting the Publish Wizard
Understanding the Deployed File Structure
Installing a ClickOnce Application
Updating a ClickOnce Application
Additional ClickOnce Options
Publish Version
Updates
File Associations
Publish Options
The Last Word
Index
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X, Y
Z
Recommend Papers

Pro Wpf 4.5 in C#: Windows Presentation Foundation in .Net 4.5 [4 ed.]
 1430243651, 9781430243656

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

THE EXPERT’S VOICE® IN .NET

Pro WPF 4.5 in C#

•฀Understand฀the฀fundamentals฀of฀WPF฀programming,฀from฀XAML฀to฀controls฀and฀data฀flow฀ •฀Develop฀realistic฀application฀scenarios฀to฀see฀navigation,฀localization,฀ •฀Explore฀the฀advanced฀user฀interface฀controls฀that฀WPF฀provides •฀Learn฀to฀manage฀documents฀from฀within฀WPF:฀text฀layout,฀printing,฀and฀document฀ •฀Use฀graphics฀and฀multimedia฀to฀add฀punch฀to฀your฀applications

lives.฀A฀working฀knowledge฀of฀C#฀and฀the฀basic฀architecture฀of฀.NET฀is฀helpful฀to฀follow฀the฀

Microsoft’s Windows Presentation Foundation (WPF) provides you with a development framework for building high-quality user experiences for the Windows operating system. It blends together rich content from a wide range of sources and allows you unparalleled access to the processing power of your Windows computer. Pro WPF 4.5 in C# is a thorough, authoritative guide to how WPF really works. Packed with no-nonsense examples and practical advice, this book will teach you everything you need to know in order to use WPF in a professional setting. The book begins by building a firm foundation of elementary concepts, using your existing C# skills as a frame of reference, before moving on to discuss advanced concepts and demonstrate them in a hands-on way that emphasizes the time and effort savings that can be gained. •฀Understand฀the฀fundamentals฀of฀WPF฀programming,฀from฀XAML฀to฀controls฀and฀data฀flow฀ •฀Develop฀realistic฀application฀scenarios฀to฀see฀navigation,฀localization,฀and฀deployment฀ in action •฀Explore฀the฀advanced฀user฀interface฀controls฀that฀WPF฀provides •฀Learn฀to฀manage฀documents฀from฀within฀WPF:฀text฀layout,฀printing,฀and฀document฀packaging are all covered •฀Use฀graphics฀and฀multimedia฀to฀add฀punch฀to฀your฀applications This book is designed for developers encountering WPF for the first time in their professional lives.฀A฀working฀knowledge฀of฀C#฀and฀the฀basic฀architecture฀of฀.NET฀is฀helpful฀to฀follow฀the฀ examples easily, but all concepts are explained from the ground up.

www.allitebooks.com

For your convenience Apress has placed some of the front matter material after the index. Please use the Bookmarks and Contents at a Glance links to access them.

www.allitebooks.com

Contents at a Glance  About the Author ..............................................................................................................xxvi  About the Technical Reviewer.........................................................................................xxvii  Acknowledgments ..........................................................................................................xxviii  Introduction ......................................................................................................................xxix  Part I: Fundamentals .............................................................................................................1  Chapter 1: Introducing WPF ..................................................................................................3  Chapter 2: XAML ..................................................................................................................21  Chapter 3: Layout ................................................................................................................53  Chapter 4: Dependency Properties .....................................................................................93  Chapter 5: Routed Events ..................................................................................................105  Part II: Deeper Into WPF ....................................................................................................141  Chapter 6: Controls ............................................................................................................143  Chapter 7: The Application ................................................................................................195  Chapter 8: Element Binding ..............................................................................................227  Chapter 9: Commands .......................................................................................................243  Chapter 10: Resources ......................................................................................................269  Chapter 11: Styles and Behaviors .....................................................................................283  Part III: Drawing and Animation........................................................................................305  Chapter 12: Shapes, Brushes, and Transforms ................................................................307  Chapter 13: Geometries and Drawings .............................................................................347  Chapter 14: Effects and Visuals ........................................................................................369  Chapter 15: Animation Basics...........................................................................................391 iv

www.allitebooks.com

n CONTENTS AT A GLANCE

 Chapter 16: Advanced Animation .....................................................................................431  Part IV: Templates and Custom Elements .........................................................................463  Chapter 17: Control Templates..........................................................................................465  Chapter 18: Custom Elements ...........................................................................................505

 Part V: Data ........................................................................................................................555  Chapter 19: Data Binding ..................................................................................................557  Chapter 20: Formatting Bound Data .................................................................................601  Chapter 21: Data Views .....................................................................................................647  Chapter 22: Lists, Trees, and Grids ...................................................................................665  Part VI: Windows, Pages, and Rich Controls ....................................................................705  Chapter 23: Windows ........................................................................................................707  Chapter 24: Pages and Navigation ....................................................................................741  Chapter 25: Menus, Toolbars, and Ribbons ......................................................................787  Chapter 26: Sound and Video ............................................................................................813  Chapter 27: 3-D Drawing...................................................................................................837  Part VII: Documents and Printing......................................................................................881  Chapter 28: Documents .....................................................................................................883  Chapter 29: Printing ..........................................................................................................935  Part VIII: Additional Topics ................................................................................................965  Chapter 30: Interacting with Windows Forms ..................................................................967  Chapter 31: Multithreading ...............................................................................................983  Chapter 32: The Add-in Model ..........................................................................................997  Chapter 33: ClickOnce Deployment ................................................................................1021

 Index ................................................................................................................................1041

v

www.allitebooks.com

PART I

Fundamentals

1

www.allitebooks.com

ChAPTeR 1 nnn

Introducing WPF The Windows Presentation Foundation (WPF) is a modern graphical display system for Windows. It’s a radical change from the technologies that came before it, with innovative features such as built-in hardware acceleration and resolution independence, both of which you’ll explore in this chapter. WPF is the best toolkit to use if you want to build a rich desktop application that runs on Windows Vista, Windows 7, and Windows 8 in desktop mode (as well as the corresponding versions of Windows Server). In fact, it’s the only general-purpose toolkit that targets these versions of Windows. By comparison, Microsoft’s new Metro toolkit—although exciting—is limited to Windows 8 systems only. (WPF applications can even be made to run on ancient Windows XP computers, which are still found in many businesses. The only limitation is that you must configure Visual Studio to target the slightly older .NET 4.0 Framework, rather than .NET 4.5.) In this chapter, you’ll take your first look at the architecture of WPF. You’ll learn how it deals with varying screen resolutions, and you’ll get a high-level survey of its core assemblies and classes. You’ll also consider how WPF has evolved from its initial release to version 4.5.

The Evolution of Windows Graphics Before WPF, Windows developers spent nearly 15 years using essentially the same display technology. That’s because every traditional, pre-WPF Windows application relies on two well-worn parts of the Windows operating system to create its user interface: • •

User32: This provides the traditional Windows look and feel for elements such as windows, buttons, text boxes, and so on. GDI/GDI+: This provides drawing support for rendering shapes, text, and images at the cost of additional complexity (and often lackluster performance).

Over the years, both technologies have been refined, and the APIs that developers use to interact with them have changed dramatically. But whether you’re crafting an application with .NET and Windows Forms or even Visual Basic 6 or MFC-based C++ code, behind the scenes the same parts of the Windows operating system are at work. Different frameworks simply provide different wrappers for interacting with User32 and GDI/GDI+. They can provide improvements in efficiency, reduce complexity, and add prebaked features so you don’t have to code them yourself; but they can’t remove the fundamental limitations of a system component that was designed more than a decade ago.

3

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF

n Note The basic division of labor between User32 and GDI/GDI+ was introduced more than 15 years ago and was well established in Windows 3.0. Of course, User32 was simply User at that point, because software hadn’t yet entered the 32-bit world.

DirectX: The New Graphics Engine Microsoft created one way around the limitations of the User32 and GDI/GDI+ libraries: DirectX. DirectX began as a cobbled-together, error-prone toolkit for creating games on the Windows platform. Its design mandate was speed, and so Microsoft worked closely with video card vendors to give DirectX the hardware acceleration needed for complex textures, special effects such as partial transparency, and threedimensional graphics. Over the years since it was first introduced (shortly after Windows 95), DirectX has matured. It’s now an integral part of Windows, with support for all modern video cards. However, the programming API for DirectX still reflects its roots as a game developer’s toolkit. Because of its raw complexity, DirectX is almost never used in traditional types of Windows applications (such as business software). WPF changes all this. In WPF, the underlying graphics technology isn’t GDI/GDI+. Instead, it’s DirectX. In fact, WPF applications use DirectX no matter what type of user interface you create. That means that whether you’re designing complex three-dimensional graphics (DirectX’s forte) or just drawing buttons and plain text, all the drawing work travels through the DirectX pipeline. As a result, even the most mundane business applications can use rich effects such as transparency and anti-aliasing. You also benefit from hardware acceleration, which simply means DirectX hands off as much work as possible to the graphics processing unit (GPU), which is the dedicated processor on the video card.

n Note DirectX is more efficient because it understands higher-level ingredients such as textures and gradients that can be rendered directly by the video card. GDI/GDI+ doesn’t, so it needs to convert them to pixel-by-pixel instructions, which are rendered much more slowly by modern video cards. One component that’s still in the picture (to a limited extent) is User32. That’s because WPF still relies on User32 for certain services, such as handling and routing input and sorting out which application owns which portion of screen real estate. However, all the drawing is funneled through DirectX.

Hardware Acceleration and WPF Video cards differ in their support for specialized rendering features and optimizations. Fortunately, this isn’t a problem, for two reasons. First, most modern computers have video hardware that’s more than powerful enough for WPF features such as 3-D drawing and animation. This is true even of laptops and desktop computers with integrated graphics (graphics processors that are built in to the motherboard, rather than on a separate card). Second, WPF has a software fallback for everything it does. That means WPF is intelligent enough to use hardware optimizations where possible, but can perform the same work using software calculations if necessary. So if you run a WPF application on a computer with a legacy video card, the interface will still appear the way you designed it. Of course, the software alternative may be much slower, so you’ll find that computers with older video cards won’t run rich WPF applications very well, especially ones that incorporate complex animations or other intense graphical effects.

4

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF

WPF: A Higher-Level API If the only thing WPF offered was hardware acceleration through DirectX, it would be a compelling improvement but a limited one. But WPF includes a basket of high-level services designed for application programmers. The following are some of the most dramatic changes that WPF ushers into the Windows programming world: •











•฀



A web-like layout model: Rather than fix controls in place with specific coordinates, WPF emphasizes flexible flow layout that arranges controls based on their content. The result is a user interface that can adapt to show highly dynamic content or different languages. A rich drawing model: Rather than painting pixels, in WPF you deal with primitives— basic shapes, blocks of text, and other graphical ingredients. You also have new features, such as true transparent controls, the ability to stack multiple layers with different opacities, and native 3-D support. A rich text model: WPF gives Windows applications the ability to display rich, styled text anywhere in a user interface. You can even combine text with lists, floating figures, and other user interface elements. And if you need to display large amounts of text, you can use advanced document display features such as wrapping, columns, and justification to improve readability. Animation as a first-class programming concept: In WPF, there’s no need to use a timer to force a form to repaint itself. Instead, animation is an intrinsic part of the framework. You define animations with declarative tags, and WPF puts them into action automatically. Support for audio and video media: Previous user interface toolkits, such as Windows Forms, were surprisingly limited when dealing with multimedia. But WPF includes support for playing any audio or video file supported by Windows Media Player, and it allows you to play more than one media file at once. Even more impressively, it gives you the tools to integrate video content into the rest of your user interface, allowing you to pull off exotic tricks such as placing a video window on a spinning 3-D cube. Styles and templates: Styles allow you to standardize formatting and reuse it throughout your application. Templates allow you to change the way any element is rendered, even a core control such as the button. It has never been easier to build modern skinned interfaces. Commands: Most users realize that it doesn’t matter whether they trigger the Open command through a menu or through a toolbar; the end result is the same. Now that abstraction is available to your code, you can define an application command in one place and link it to multiple controls. Declarative user interface: Although you can construct a WPF window with code, Visual Studio takes a different approach. It serializes each window’s content to a set of XML tags in a XAML document. The advantage is that your user interface is completely separated from your code, and graphic designers can use professional tools to edit your XAML files and refine your application’s front end. (XAML is short for Extensible Application Markup Language, and it’s described in detail in Chapter 2.) 5

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF



Page-based applications: Using WPF, you can build a browser-like application that lets you move through a collection of pages, complete with forward and back navigation buttons. WPF handles the messy details such as the page history. You can even deploy your project as a browser-based application that runs right inside Internet Explorer.

Resolution Independence Traditional Windows applications are bound by certain assumptions about resolution. Developers usually assume a standard monitor resolution (such as 1366 × 768 pixels), design their windows with that in mind, and try to ensure reasonable resizing behavior for smaller and larger dimensions. The problem is that the user interface in traditional Windows applications isn’t scalable. As a result, if you use a high monitor resolution that crams in pixels more densely, your application windows become smaller and more difficult to read. This is particularly a problem with newer monitors that have high pixel densities and run at correspondingly high resolutions. For example, it’s common to find consumer monitors (particularly on laptops) that have pixel densities of 120 dpi or 144 dpi (dots per inch), rather than the more traditional 96 dpi. At their native resolution, these displays pack the pixels in much more tightly, creating eye-squintingly small controls and text. Ideally, applications would use higher pixel densities to show more detail. For example, a highresolution monitor could display similarly sized toolbar icons but use the extra pixels to render sharper graphics. That way, you could keep the same basic layout but offer increased clarity and detail. For a variety of reasons, this solution hasn’t been possible in the past. Although you can resize graphical content that’s drawn with GDI/GDI+, User32 (which generates the visuals for common controls) doesn’t support true scaling. WPF doesn’t suffer from this problem because it renders all user interface elements itself, from simple shapes to common controls such as buttons. As a result, if you create a button that’s 1 inch wide on your computer monitor, it can remain 1 inch wide on a high-resolution monitor—WPF will simply render it in greater detail and with more pixels. This is the big picture, but it glosses over a few details. Most importantly, you need to realize that WPF bases its scaling on the system DPI setting, not the DPI of your physical display device. This makes perfect sense—after all, if you’re displaying your application on a 100-inch projector, you’re probably standing several feet back and expecting to see a jumbo-size version of your windows. You don’t want WPF to suddenly scale down your application to “normal” size. Similarly, if you’re using a laptop with a highresolution display, you probably expect to have slightly smaller windows—it’s the price you pay to fit all your information onto a smaller screen. Furthermore, different users have different preferences. Some want richer detail, while others prefer to cram in more content. So, how does WPF determine how big an application window should be? The short answer is that WPF uses the system DPI setting when it calculates sizes. But to understand how this really works, it helps to take a closer look at the WPF measurement system.

WPF Units A WPF window and all the elements inside it are measured using device-independent units. A single deviceindependent unit is defined as 1/96 of an inch. To understand what this means in practice, you’ll need to consider an example. Imagine that you create a small button in WPF that’s 96 by 96 units in size. If you’re using the standard Windows DPI setting (96 dpi), each device-independent unit corresponds to one real, physical pixel. That’s because WPF uses this calculation:

6

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF

[Physical Unit Size] = [Device-Independent Unit Size] x [System DPI] = 1/96 inch x 96 dpi = 1 pixel Essentially, WPF assumes it takes 96 pixels to make an inch because Windows tells it that through the system DPI setting. However, the reality depends on your display device. For example, consider a 19-inch LCD monitor with a maximum resolution of 1600 by 1200 pixels. Using a dash of Pythagoras, you can calculate the pixel density for this monitor, as shown here:

= 100 dpi In this case, the pixel density works out to 100 dpi, which is slightly higher than what Windows assumes. As a result, on this monitor a 96-by-96-pixel button will be slightly smaller than 1 inch. On the other hand, consider a 15-inch LCD monitor with a resolution of 1024 by 768. Here, the pixel density drops to about 85 dpi, so the 96-by-96-pixel button appears slightly larger than 1 inch. In both these cases, if you reduce the screen size (say, by switching to 800 by 600 resolution), the button (and every other screen element) will appear proportionately larger. That’s because the system DPI setting remains at 96 dpi. In other words, Windows continues to assume it takes 96 pixels to make an inch, even though at a lower resolution it takes far fewer pixels.

n Tip As you no doubt know, LCD monitors are designed to work best at a specific resolution, which is called the native resolution. If you lower the resolution, the monitor must use interpolation to fill in the extra pixels, which can cause blurriness. To get the best display, it’s always best to use the native resolution. If you want larger windows, buttons, and text, consider modifying the system DPI setting instead (as described next).

System DPI So far, the WPF button example works exactly the same as any other user interface element in any other type of Windows application. The difference is the result if you change the system DPI setting. In the previous generation of Windows, this feature was sometimes called large fonts. That’s because the system DPI affects the system font size but often leaves other details unchanged.

n Note Many Windows applications don’t fully support higher DPI settings. At worst, increasing the system DPI can result in windows that have some content that’s scaled up and other content that isn’t, which can lead to obscured content and even unusable windows. This is where WPF is different. WPF respects the system DPI setting natively and effortlessly. For example, if you change the system DPI setting to 120 dpi (a common choice for users of large highresolution screens), WPF assumes that it needs 120 pixels to fill an inch of space. WPF uses the following calculation to figure out how it should translate its logical units to physical device pixels:

7

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF

[Physical Unit Size] = [Device-Independent Unit Size] x [System DPI] = 1/96 inch x 120 dpi = 1.25 pixels In other words, when you set the system DPI to 120 dpi, the WPF rendering engine assumes one device-independent unit equals 1.25 pixels. If you show a 96-by-96 button, the physical size will actually be 120 by 120 pixels (because 96 x 1.25 = 120). This is the result you expect—a button that’s 1 inch on a standard monitor remains 1 inch in size on a monitor with a higher pixel density. This automatic scaling wouldn’t help much if it applied only to buttons. But WPF uses deviceindependent units for everything it displays, including shapes, controls, text, and any other ingredient you put in a window. As a result, you can change the system DPI to whatever you want, and WPF adjusts the size of your application seamlessly.

n Note Depending on the system DPI, the calculated pixel size may be a fractional value. You might assume that WPF simply rounds off your measurements to the nearest pixel. However, by default, WPF does something different. If an edge of an element falls between pixels, it uses anti-aliasing to blend that edge into the adjacent pixels. This might seem like an odd choice, but it actually makes a fair bit of sense. Your controls won’t necessarily have straight, clearly defined edges if you use custom-drawn graphics to skin them; so some level of anti-aliasing is already necessary. The steps for adjusting the system DPI depend on the operating system. The following sections explain what to do, depending on your operating system.

Windows Vista

8

1.

Right-click your desktop and choose Personalize.

2.

In the list of links on the left, choose Adjust Font Size (DPI).

3.

Choose between 96 or 120 dpi. Or click Custom DPI to use a custom DPI setting. You can then specify a percentage value, as shown in Figure 1-1. (For example, 175% scales the standard 96 dpi to 168 dpi.) In addition, when using a custom DPI setting, you have an option named Use Windows XP Style DPI Scaling, which is described in the sidebar “DPI Scaling.”

CHAPTER 1 n INTRODUCING WPF

Figure 1-1. Changing the system DPI

Windows 7 and Windows 8 1.

Right-click your desktop and choose Personalize.

2.

In the list of links at the bottom-left of the window, choose Display.

3.

Choose between Smaller (the default option), Medium, or Larger. Although these options are described by scaling percentages (100%, 125%, or 150%), they actually correspond to the DPI values 96, 120, and 144. You’ll notice that the first two are the same standards found in Windows Vista and Windows XP, while the third one is larger still. Alternatively, you can click Set Custom Text Size to use a custom DPI percentage, as shown in Figure 1-1. (For example, 175% scales the standard 96 dpi to 168 dpi.) When using a custom DPI setting, you have an option named Use Windows XP Style DPI Scaling, which is described in the following sidebar.

9

CHAPTER 1 n INTRODUCING WPF

DPI SCAlIng Because older applications are notoriously lacking in their support for high DPI settings, Windows Vista introduced a technique called bitmap scaling. Later versions of Windows also support this feature. With bitmap scaling, when you run an application that doesn’t appear to support high DPI settings, Windows resizes it as though it were an image. The advantage of this approach is that the application still believes it’s running at the standard 96 dpi. Windows seamlessly translates input (such as mouse clicks) and routes them to the right place in the application’s “real” coordinate system. The scaling algorithm that Windows uses is a fairly good one—it respects pixel boundaries to avoid blurry edges and uses the video card hardware where possible to increase speed—but it inevitably leads to a fuzzier display. It also has a serious limitation in that Windows can’t recognize older applications that do support high DPI settings. That’s because applications need to include a manifest or call SetProcessDPIAware (in User32) to advertise their high DPI support. Although WPF applications handle this step correctly, applications created prior to Windows Vista won’t use either approach and will be stuck with bitmap scaling even when they support higher DPIs. There are two possible solutions. If you have a few specific applications that support high DPI settings but don’t indicate it, you can configure that detail manually. To do so, right-click the shortcut that starts the application (in the Start menu) and choose Properties. On the Compatibility tab, enable the option named Disable Display Scaling on High DPI Settings. If you have a lot of applications to configure, this gets tiring fast. The other possible solution is to disable bitmap scaling altogether. To do so, choose the Use Windows XP Style DPI Scaling option in the Custom DPI Setting dialog box shown in Figure 1-1. The only limitation of this approach is there may be some applications that won’t display properly (and possibly won’t be usable) at high DPI settings. By default, Use Windows XP Style DPI Scaling is checked for DPI sizes of 120 or less but unchecked for DPI sizes that are greater.

Bitmap and Vector Graphics When you work with ordinary controls, you can take WPF’s resolution independence for granted. WPF takes care of making sure that everything has the right size automatically. However, if you plan to incorporate images into your application, you can’t be quite as casual. For example, in traditional Windows applications, developers use tiny bitmaps for toolbar commands. In a WPF application, this approach is not ideal because the bitmap may display artifacts (becoming blurry) as it’s scaled up or down according to the system DPI. Instead, when designing a WPF user interface, even the smallest icon is generally implemented as a vector graphic. Vector graphics are defined as a set of shapes, and as such they can be easily scaled to any size.

n Note Of course, drawing a vector graphic takes more time than painting a basic bitmap, but WPF includes optimizations that are designed to lessen the overhead to ensure that drawing performance is always reasonable. It’s difficult to overestimate the importance of resolution independence. At first glance, it seems like a straightforward, elegant solution to a time-honored problem (which it is). However, in order to design interfaces that are fully scalable, developers need to embrace a new way of thinking.

10

CHAPTER 1 n INTRODUCING WPF

The Architecture of WPF WPF uses a multilayered architecture. At the top, your application interacts with a high-level set of services that are completely written in managed C# code. The actual work of translating .NET objects into Direct3D textures and triangles happens behind the scenes, using a lower-level unmanaged component called milcore.dll. milcore.dll is implemented in unmanaged code because it needs tight integration with Direct3D and because it’s extremely performance-sensitive. Figure 1-2 shows the layers at work in a WPF application.

Figure 1-2. The architecture of WPF Figure 1-2 includes these key components: •







PresentationFramework.dll: This holds the top-level WPF types, including those that represent windows, panels, and other types of controls. It also implements higherlevel programming abstractions such as styles. Most of the classes you’ll use directly come from this assembly. PresentationCore.dll: This holds base types, such as UIElement and Visual, from which all shapes and controls derive. If you don’t need the full window and control abstraction layer, you can drop down to this level and still take advantage of WPF’s rendering engine. WindowsBase.dll: This holds even more basic ingredients that have the potential to be reused outside of WPF, such as DispatcherObject and DependencyObject, which introduces the plumbing for dependency properties (a topic you’ll explore in detail in Chapter 4). milcore.dll: This is the core of the WPF rendering system and the foundation of the Media Integration Layer (MIL). Its composition engine translates visual elements into the triangle and textures that Direct3D expects. Although milcore.dll is considered part of WPF, it’s also an essential system component for Windows Vista and Windows 7. In fact, the Desktop Window Manager (DWM) uses milcore.dll to render the desktop.

11

CHAPTER 1 n INTRODUCING WPF

n Note milcore.dll is sometimes referred to as the engine for “managed graphics.” Much as the common language runtime (CLR) manages the lifetime of a .NET application, milcore.dll manages the display state. And just as the CLR saves you from worrying about releasing objects and reclaiming memory, milcore.dll saves you from thinking about invalidating and repainting a window. You simply create the objects with the content you want to show, and milcore.dll paints the appropriate portions of the window as it is dragged around, covered and uncovered, minimized and restored, and so on. • • •

WindowsCodecs.dll: This is a low-level API that provides imaging support (for example, processing, displaying, and scaling bitmaps and JPEGs). Direct3D: This is the low-level API through which all the graphics in a WPF application are rendered. User32: This is used to determine what program gets what real estate. As a result, it’s still involved in WPF, but it plays no part in rendering common controls.

The most important fact that you should realize is Direct3D renders all the drawing in WPF. It doesn’t matter whether you have a modest video card or a much more powerful one, whether you’re using basic controls or drawing more complex content, or whether you’re running your application on Windows XP, Windows Vista, or Windows 7. Even two-dimensional shapes and ordinary text are transformed into triangles and passed through the 3-D pipeline. There is no fallback to GDI+ or User32.

The Class Hierarchy Throughout this book, you’ll spend most of your time exploring the WPF namespaces and classes. But before you begin, it’s helpful to take a first look at the hierarchy of classes that leads to the basic set of WPF controls. Figure 1-3 shows a basic overview with some of the key branches of the class hierarchy. As you continue through this book, you’ll dig into these classes (and their relatives) in more detail.

12

CHAPTER 1 n INTRODUCING WPF

Figure 1-3. The fundamental classes of WPF The following sections describe the core classes in this diagram. Many of these classes lead to whole branches of elements (such as shapes, panels, and controls).

n Note The core WPF namespaces begin with System.Windows (for example, System.Windows, System.Windows. Controls, and System.Windows.Media). The sole exception is namespaces that begin with System.Windows.Forms, which are part of the Windows Forms toolkit.

System.Threading.DispatcherObject WPF applications use the familiar single-thread affinity (STA) model, which means the entire user interface is owned by a single thread. It’s not safe to interact with user interface elements from another thread. To facilitate this model, each WPF application is governed by a dispatcher that coordinates messages (which result from keyboard input, mouse movements, and framework processes such as layout). By deriving from DispatcherObject, every element in your user interface can verify whether code is running on the correct thread and access the dispatcher to marshal code to the user interface thread. You’ll learn more about the WPF threading model in Chapter 31.

13

CHAPTER 1 n INTRODUCING WPF

System.Windows.DependencyObject In WPF, the central way of interacting with onscreen elements is through properties. Early on in the design cycle, the WPF architects decided to create a more powerful property model that baked in features such as change notification, inherited default values, and more economical property storage. The ultimate result is the dependency property feature, which you’ll explore in Chapter 4. By deriving from DependencyObject, WPF classes get support for dependency properties.

System.Windows.Media.Visual Every element that appears in a WPF window is, at heart, a Visual. You can think of the Visual class as a single drawing object that encapsulates drawing instructions, additional details about how the drawing should be performed (such as clipping, opacity, and transformation settings), and basic functionality (such as hit testing). The Visual class also provides the link between the managed WPF libraries and the milcore.dll that renders your display. Any class that derives from Visual has the ability to be displayed on a window. If you prefer to create your user interface using a lightweight API that doesn’t have the higherlevel framework features of WPF, you can program directly with Visual objects, as described in Chapter 14.

System.Windows.UIElement UIElement adds support for WPF essentials such as layout, input, focus, and events (which the WPF team refers to by the acronym LIFE). For example, it’s here that the two-step measure and arrange layout process is defined, which you’ll learn about in Chapter 18. It’s also here that raw mouse clicks and key presses are transformed to more useful events such as MouseEnter. As with properties, WPF implements an enhanced event-passing system called routed events. You’ll learn how it works in Chapter 5. Finally, UIElement adds supports for commands (Chapter 9).

System.Windows.FrameworkElement FrameworkElement is the final stop in the core WPF inheritance tree. It implements some of the members that are merely defined by UIElement. For example, UIElement sets the foundation for the WPF layout system, but FrameworkElement includes the key properties (such as HorizontalAlignment and Margin) that support it. UIElement also adds support for data binding, animation, and styles, all of which are core features.

System.Windows.Shapes.Shape Basic shapes classes, such as Rectangle, Polygon, Ellipse, Line, and Path, derive from this class. These shapes can be used alongside more traditional Windows widgets such as buttons and text boxes. You’ll start building shapes in Chapter 12.

System.Windows.Controls.Control A control is an element that can interact with the user. It obviously includes classes such as TextBox, Button, and ListBox. The Control class adds additional properties for setting the font and the foreground and background colors. But the most interesting detail it provides is template support, which allows you to

14

CHAPTER 1 n INTRODUCING WPF

replace the standard appearance of a control with your own stylish drawing. You’ll learn about control templates in Chapter 17.

n Note In Windows Forms programming, every visual item in a form is referred to as a control. In WPF, this isn’t the case. Visual items are called elements, and only some elements are actually controls (those that can receive focus and interact with the user). To make this system even more confusing, many elements are defined in the System.Windows.Controls namespace, even though they don’t derive from System.Windows.Controls.Control and aren’t considered controls. One example is the Panel class.

System.Windows.Controls.ContentControl This is the base class for all controls that have a single piece of content. This includes everything from the humble Label to the Window. The most impressive part of this model (which is described in more detail in Chapter 6) is the fact that this single piece of content can be anything from an ordinary string to a layout panel with a combination of other shapes and controls.

System.Windows.Controls.ItemsControl This is the base class for all controls that show a collection of items, such as the ListBox and TreeView. List controls are remarkably flexible—for example, using the features that are built into the ItemsControl class, you can transform the lowly ListBox into a list of radio buttons, a list of check boxes, a tiled display of images, or a combination of completely different elements that you’ve chosen. In fact, in WPF, menus, toolbars, and status bars are actually specialized lists, and the classes that implement them all derive from ItemsControl. You’ll start using lists in Chapter 19 when you consider data binding. You’ll learn to enhance them in Chapter 20, and you’ll consider the most specialized list controls in Chapter 22.

System.Windows.Controls.Panel This is the base class for all layout containers—elements that can contain one or more children and arrange them according to specific layout rules. These containers are the foundation of the WPF layout system, and using them is the key to arranging your content in the most attractive, flexible way possible. Chapter 3 explores the WPF layout system in more detail.

WPF 4.5 WPF is a mature technology. It’s been part of several releases of .NET, with steady enhancements along the way: • •

WPF 3.0: The first version of WPF was released with two other new technologies: Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF). Together, these three technologies were called the .NET Framework 3.0. WPF 3.5: A year later, a new version of WPF was released as part of the .NET Framework 3.5. The new features in WPF are mostly minor refinements, including bug fixes and performance improvements. 15

CHAPTER 1 n INTRODUCING WPF

• • •

WPF 3.5 SP1: When the .NET Framework Service Pack 1 (SP1) was released, the designers of WPF had a chance to slip in a few new features, such as slick graphical effects (courtesy of pixel shaders) and the sophisticated DataGrid control. WPF 4: This release added a number of refinements, including better text rendering, more natural animation, and support for multitouch. WPF 4.5: The latest version of WPF has the fewest changes yet, which reflects its status as a mature technology. Along with the usual bug fixes and performance tweaks, WPF 4.5 adds a number of refinements to that data binding system, including improvements to data binding expressions, virtualization, support for the INotifyDataError interface, and data view synchronization. You’ll see these new features in Chapter 8, Chapter 19, and Chapter 22.

The WPF Toolkit Before a new control makes its way into the WPF libraries of the .NET platform, it often begins in a separate Microsoft download known as the WPF Toolkit. But the WPF Toolkit isn’t just a place to preview the future direction of WPF—it’s also a great source of practical components and controls that are made available outside the normal WPF release cycle. For example, WPF doesn’t include any sort of charting tools, but the WPF Toolkit includes a set of controls for creating bar, pie, bubble, scatter, and line graphs. This book occasionally references the WPF Toolkit to point out a useful piece of functionality that’s not available in the core .NET runtime. To download the WPF Toolkit, review its code, or read its documentation, surf to http://wpf.codeplex.com. There, you’ll also find links to other Microsoft-managed WPF projects, including WPF Futures (which provides more experimental WPF features) and WPF testing tools.

Visual Studio 2012 Although you can craft WPF user interfaces by hand or using the graphic-design-oriented tool Expression Blend, most developers will start in Visual Studio and spend most (or all) of their time there. This book assumes you’re using Visual Studio and occasionally explains how to use the Visual Studio interface to perform an important task, such as adding a resource, configuring project properties, or creating a control library assembly. However, you won’t spend much time exploring Visual Studio’s design-time frills. Instead, you’ll focus on the underlying markup and code you need to create professional applications.

n Note You probably already know how to create a WPF project in Visual Studio, but here’s a quick recap. First, select File  New  TRA Project. Then, pick the Visual C#  Windows group (in the tree on the left), and choose the WPF Application template (in the list on the right). You’ll learn about the more specialized WPF Browser Application template in Chapter 24. After you pick a directory, enter a project name, and click OK, you’ll end up with the basic skeleton of a WPF application.

16

CHAPTER 1 n INTRODUCING WPF

Multitargeting In the past, each version of Visual Studio was tightly coupled to a specific version of .NET. Visual Studio 2012 doesn’t have this restriction—it allows you to design an application that targets any version of .NET from 2.0 to 4.5. Although it’s obviously not possible to create a WPF application with .NET 2.0, all later versions have WPF support. You may choose to target an older version, such as .NET 3.5 or .NET 4 to get the broadest possible compatibility. For example, a .NET 3.5 application can run on the .NET 3.5, 4, and 4.5 runtimes. Or, you may choose to target .NET 4.5 to get access to newer features in WPF or in the .NET platform. However, if you need to support legacy Windows XP computers, you can’t target a version part of .NET 4, because this is the last .NET release that supports Windows XP. When you create a new project in Visual Studio, you can choose the version of the .NET Framework that you’re targeting from a drop-down list at the top of the New Project dialog box, just above the list of project templates (see Figure 1-4).

Figure 1-4. Choosing the target version of the .NET Framework You can also change the version you’re targeting at any point afterward by double-clicking the Properties node in the Solution Explorer and changing the selection in the Target Framework list. To provide accurate multitargeting, Visual Studio includes reference assemblies for each version of . NET. These assemblies include the metadata of every type but none of the code that’s required to implement it. That means Visual Studio can use the reference assembly to tailor its IntelliSense and error checking, ensuring that you aren’t able to use controls, classes, or members that aren’t available in the version of .NET that you’re targeting. It also uses this metadata to determine what controls should appear in the Toolbox, what members should appear in the Properties window and Object Browser, and so on, ensuring that the entire IDE is limited to the version you’ve chosen.

17

www.allitebooks.com

CHAPTER 1 n INTRODUCING WPF

The Visual Studio Designer Visual Studio includes a rich designer for creating WPF user interfaces. But just because Visual Studio 2012 allows you to drag and drop WPF windows into existence doesn’t mean you should start doing that right now—or at all. As you’ll learn in Chapter 3, WPF uses a flexible and nuanced layout model that allows you to use different strategies for sizing and positioning the elements in your user interface. To get the result you need, you’ll need to choose the right combination of layout containers, arrange them appropriately, and configure their properties. Visual Studio can help you out in this task, but it’s far easier if you learn the basics of XAML markup and WPF layout first. Then, you’ll be able to watch as Visual Studio’s visual designer generates your markup, and you can modify it by hand as needed. After you’ve mastered the syntax of XAML (Chapter 2) and you’ve learned about the family of WPF layout controls (Chapter 3), it’s up to you to choose how you want to create your windows. There are professional developers who use Visual Studio, those who use Expression Blend, those who write XAML by hand, and those who use a combination of both methods (for example, creating the basic layout structure by hand and then configuring it with the Visual Studio designer).

The Last Word In this chapter, you took your first look at WPF and the promise it holds. You considered the underlying architecture and briefly considered the core classes. Clearly, WPF introduces many significant changes. However, there are five key principles that immediately stand out because they are so different from previous Windows user interface toolkits such as Windows Forms. These principles are the following: • • •





18

Hardware acceleration: All WPF drawing is performed through DirectX, which allows it to take advantage of the latest in modern video cards. Resolution independence: WPF is flexible enough to scale up or down to suit your monitor and display preferences, depending on the system DPI setting. No fixed control appearance: In traditional Windows development, there’s a wide chasm between controls that can be tailored to suit your needs (which are known as owner-drawn controls) and those that are rendered by the operating system and essentially fixed in appearance. In WPF, everything from a basic Rectangle to a standard Button or more complex Toolbar is drawn using the same rendering engine and completely customizable. For this reason, WPF controls are often called lookless controls—they define the functionality of a control, but they don’t have a hardwired “look.” Declarative user interfaces: In the next chapter, you’ll consider XAML, the markup standard you use to define WPF user interfaces. XAML allows you to build a window without using code. Impressively, XAML doesn’t limit you to fixed, unchanging user interfaces. You can use tools such as data binding and triggers to automate basic user interface behavior (such as text boxes that update themselves when you page through a record source, or labels that glow when you hover overtop with the mouse), all without writing a single line of C#. Object-based drawing: Even if you plan to work at the lower-level visual layer (rather than the higher-level element layer), you won’t work in terms of painting and pixels. Instead, you’ll create shape objects and let WPF maintain the display in the most optimized manner possible.

CHAPTER 1 n INTRODUCING WPF

You’ll see these principles at work throughout this book. But before you go any further, it’s time to learn about a complementary standard. The next chapter introduces XAML, the markup language used to define WPF user interfaces.

19

ChAPTeR 2 nnn

XAML XAML (short for Extensible Application Markup Language and pronounced zammel) is a markup language used to instantiate .NET objects. Although XAML is a technology that can be applied to many problem domains, its primary role in life is to construct WPF user interfaces. In other words, XAML documents define the arrangement of panels, buttons, and controls that make up the windows in a WPF application. It’s unlikely that you’ll write XAML by hand. Instead, you’ll use a tool that generates the XAML you need. If you’re a graphic designer, that tool is likely to be a graphical design program such as Microsoft Expression Blend. If you’re a developer, you’ll probably start with Microsoft Visual Studio. Because both tools are equally at home with XAML, you can create a basic user interface with Visual Studio and then hand it off to a crack design team that can polish it up with custom graphics in Expression Blend. In fact, this ability to integrate the workflow between developers and designers is one of the key reasons that Microsoft created XAML. This chapter presents a detailed introduction to XAML. You’ll consider its purpose, its overall architecture, and its syntax. Once you understand the broad rules of XAML, you’ll know what is and isn’t possible in a WPF user interface—and how to make changes by hand when it’s necessary. More important, by exploring the tags in a WPF XAML document, you can learn a bit about the object model that underpins WPF user interfaces and get ready for the deeper exploration to come.

n What’s New WPF 4.5 adds nothing new to the XAML standard. In fact, even the minor refinements of XAML 2009 still aren’t fully implemented. They’re supported only in loose XAML files, not compiled XAML resources (which is what virtually every WPF application uses). In fact, XAML 2009 will probably never become a fully integrated part of WPF, because its improvements aren’t terribly important, and any change to the XAML compiler raises security and performance concerns. For that reason, XAML 2009 isn’t covered in this book.

Understanding XAML Developers realized long ago that the most efficient way to tackle complex, graphically rich applications is to separate the graphical portion from the underlying code. That way, artists can own the graphics, and developers can own the code. Both pieces can be designed and refined separately, without any versioning headaches.

21

CHAPTER 2 n XAML

Graphical User Interfaces Before WPF With traditional display technologies, there’s no easy way to separate the graphical content from the code. The key problem with a Windows Forms application is that every form you create is defined entirely in C# code. As you drop controls onto the design surface and configure them, Visual Studio quietly adjusts the code in the corresponding form class. Sadly, graphic designers don’t have any tools that can work with C# code. Instead, artists are forced to take their content and export it to a bitmap format. These bitmaps can then be used to skin windows, buttons, and other controls. This approach works well for straightforward interfaces that don’t change much over time, but it’s extremely limiting in other scenarios. Some of its problems include the following: • • • •

Each graphical element (background, button, and so on) needs to be exported as a separate bitmap. That limits the ability to combine bitmaps and use dynamic effects such as antialiasing, transparency, and shadows. A fair bit of user interface logic needs to be embedded in the code by the developer. This includes button sizes, positioning, mouseover effects, and animations. The graphic designer can’t control any of these details. There’s no intrinsic connection between the different graphical elements, so it’s easy to end up with an unmatched set of images. Tracking all these items adds complexity. Bitmaps can’t be resized without compromising their quality. For that reason, a bitmap-based user interface is resolution-dependent. That means it can’t accommodate large monitors and high-resolution , which is a major violation of the WPF design philosophy.

If you’ve ever been through the process of designing a Windows Forms application with custom graphics in a team setting, you’ve put up with a lot of frustration. Even if the interface is designed from scratch by a graphic designer, you’ll need to re-create it with C# code. Usually, the graphic designer will simply prepare a mock-up that you need to translate painstakingly into your application. WPF solves this problem with XAML. When designing a WPF application in Visual Studio, the window you’re designing isn’t translated into code. Instead, it’s serialized into a set of XAML tags. When you run the application, these tags are used to generate the objects that compose the user interface.

n Note It’s important to understand that WPF doesn’t require XAML. There’s no reason Visual Studio couldn’t use the Windows Forms approach and create code statements that construct your WPF windows. But if it did, your window would be locked into the Visual Studio environment and available to programmers only. In other words, WPF doesn’t require XAML. However, XAML opens up worlds of possibilities for collaboration, because other design tools understand the XAML format. For example, a savvy designer can use a tool such as Microsoft Expression Design to fine-tune the graphics in your WPF application or a tool such as Expression Blend to build sophisticated animations for it. After you’ve finished this chapter, you may want to read a Microsoft white paper at http://windowsclient.net/wpf/white-papers/ thenewiteration.aspx that reviews XAML and explores some of the ways developers and designers can collaborate on a WPF application.

22

CHAPTER 2 n XAML

n Tip XAML plays the same role for Windows applications as control tags do for ASP.NET web applications. The difference is that the ASP.NET tagging syntax is designed to look like HTML, so designers can craft web pages by using ordinary web design applications such as Microsoft Expression and Adobe Dreamweaver. As with WPF, the actual code for an ASP.NET web page is usually placed in a separate file to facilitate this design.

The Variants of XAML People use the term XAML in various ways. So far, I’ve used it to refer to the entire language of XAML which is an all-purpose XML-based syntax for representing a tree of .NET objects. (These objects could be buttons and text boxes in a window or custom classes you’ve defined. In fact, XAML could even be used on other platforms to represent non-.NET objects.) There are also several subsets of XAML: •

• •



WPF XAML encompasses the elements that describe WPF content, such as vector graphics, controls, and documents. Currently, it’s the most significant application of XAML, and it’s the subset you’ll explore in this book. XPS XAML is the part of WPF XAML that defines an XML representation for formatted electronic documents. It’s been published as the separate XML Paper Specification (XPS) standard. You’ll explore XPS in Chapter 28. Silverlight XAML is a subset of WPF XAML that’s intended for Microsoft Silverlight applications. Silverlight is a cross-platform browser plug-in that allows you to create rich web content with two-dimensional graphics, animation, and audio and video. Chapter 1 has more about Silverlight, or you can visit http://silverlight.net to learn about it in detail. WF XAML encompasses the elements that describe Windows Workflow Foundation (WF) content. You can learn more about WF at http://tinyurl.com/d9xr2nv.

XAML Compilation The creators of WPF knew that XAML needed to not only solve the problem of design collaboration—it also needed to be fast. And though XML-based formats such as XAML are flexible and easily portable to other tools and platforms, they aren’t always the most efficient option. XML was designed to be logical, readable, and straightforward, but not compact. WPF addresses this shortcoming with Binary Application Markup Language (BAML. BAML is really nothing more than a binary representation of XAML. When you compile a WPF application in Visual Studio, all your XAML files are converted into BAML, and that BAML is then embedded as a resource into the final DLL or EXE assembly. BAML is tokenized, which means lengthier bits of XAML are replaced with shorter tokens. Not only is BAML significantly smaller, but it’s also optimized in a way that makes it faster to parse at runtime. Most developers won’t worry about the conversion of XAML to BAML because the compiler performs it behind the scenes. However, it is possible to use XAML without compiling it first. This might make sense in scenarios that require some of the user interface to be supplied just in time (for example, pulled out of a database as a block of XAML tags). You’ll see how this works in the upcoming section “Loading and Compiling XAML.” 23

CHAPTER 2 n XAML

CReATIng XAMl WITh VISuAl STuDIo In this chapter, you’ll take a look at all the details of XAML markup. Of course, when you’re designing an application, you won’t write all your XAML by hand. Instead, you’ll use a tool such as Visual Studio that can drag and drop your user interface into existence. Based on that, you might wonder whether it’s worth spending so much time studying the syntax of XAML. The answer is a resounding yes. Understanding XAML is critical to WPF application design. It will help you learn key WPF concepts, such as attached properties (in this chapter), layout (Chapter 3), routed events (Chapter 4), the content model (Chapter 6), and so on. More important, a whole host of tasks are possible— or are far easier to accomplish—with handwritten XAML. They include the following: Most WPF developers use a combination of techniques, laying out some of their user interface with a design tool (Visual Studio or Expression Blend) and then fine-tuning it by editing the XAML markup by hand. However, you’ll probably find that it’s easiest to write all your XAML by hand until you learn about layout containers in Chapter 3. That’s because you need to use a layout container to properly arrange multiple controls in a window.

XAML Basics The XAML standard is quite straightforward once you understand a few ground rules: • •



Every element in a XAML document maps to an instance of a .NET class. The name of the element matches the name of the class exactly. For example, the element

Each attribute corresponds to a separate property of the Window class. All in all, this tells WPF to create a window with the caption Window1 and to make it 300 by 300 units large.

n Note As you learned in Chapter 1, WPF uses a relative measurement system that isn’t what most Windows developers expect. Rather than letting you set sizes using physical pixels, WPF uses device-independent units that can scale to fit different monitor resolutions and are defined as 1/96 of an inch. That means the 300-by-300-unit window in the previous example will be rendered as a 300-by-300-pixel window if your system DPI setting is the standard 96 dpi. However, on a system with a higher system DPI, more pixels will be used. Chapter 1 has the full story.

XAML Namespaces Clearly, it’s not enough to supply just a class name. The XAML parser also needs to know the .NET namespace where this class is located. For example, the Window class could exist in several places—it might refer to the System.Windows.Window class, or it could refer to a Window class in a third-party component or one you’ve defined in your application. To figure out which class you really want, the XAML parser examines the XML namespace that’s applied to the element. Here’s how it works. In the sample document shown earlier, two namespaces are defined: 2 3

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

n Note XML namespaces are declared by using attributes. These attributes can be placed inside any element start tag. However, convention dictates that all the namespaces you need to use in a document should be declared in the very first tag, as they are in this example. After a namespace is declared, it can be used anywhere in the document.

25

CHAPTER 2 n XAML

The xmlns attribute is a specialized attribute in the world of XML that’s reserved for declaring namespaces. This snippet of markup declares two namespaces that you’ll find in every WPF XAML document you create: •



http://schemas.microsoft.com/winfx/2006/xaml/presentation is the core WPF namespace. It encompasses all the WPF classes, including the controls you use to build user interfaces. In this example, this namespace is declared without a namespace prefix, so it becomes the default namespace for the entire document. In other words, every element is automatically placed in this namespace unless you specify otherwise. http://schemas.microsoft.com/winfx/2006/xaml is the XAML namespace. It includes various XAML utility features that allow you to influence how your document is interpreted. This namespace is mapped to the prefix x. That means you can apply it by placing the namespace prefix before the element name (as in ).

As you can see, the XML namespace name doesn’t match any particular .NET namespace. There are a couple of reasons the creators of XAML chose this design. By convention, XML namespaces are often uniform resource identifiers (URIs) as they are here. These URIs look like they point to a location on the Web, but they don’t. The URI format is used because it makes it unlikely that different organizations will inadvertently create different XML-based languages with the same namespace. Because the domain schemas.microsoft.com is owned by Microsoft, only Microsoft will use it in an XML namespace name. The other reason that there isn’t a one-to-one mapping between the XML namespaces used in XAML and .NET namespaces is because it would significantly complicate your XAML documents. The problem here is that WPF encompasses well over a dozen namespaces (all of which start with System.Windows). If each .NET namespace had a different XML namespace, you’d need to specify the right namespace for each and every control you use, which quickly gets messy. Instead, the creators of WPF chose to combine all of these .NET namespaces into a single XML namespace. This works because within the different .NET namespaces that are part of WPF, there aren’t any classes that have the same name. The namespace information allows the XAML parser to find the right class. For example, when it looks at the Window and Grid elements, it sees that they are placed in the default WPF namespace. It then searches the corresponding .NET namespaces until it finds System.Windows.Window and System. Windows.Controls.Grid.

The Code-Behind Class XAML allows you to construct a user interface, but in order to make a functioning application, you need a way to connect the event handlers that contain your application code. XAML makes this easy by using the Class attribute that’s shown here: 1

. If you try to use these values to set the content of an element, you’ll run into trouble because the XAML parser assumes you’re trying to do something else—such as create a nested element. For example, imagine you want to create a button that contains the text . The following markup won’t work:

The problem here is that it looks like you’re trying to create an element named Click with an attribute named Me. The solution is to replace the offending characters with entity references—specific codes that the XAML parser will interpret correctly. Table 2-1 lists the character entities you might choose to use. Note that the quotation mark character entity is required only when setting values by using an attribute, because the quotation mark indicates the beginning and ending of an attribute value. Table 2-1. XML Character Entities

Special Character

Character Entity

Less than ()

>

Ampersand (&)

&

Quotation mark (“) " Here’s the corrected markup that uses the appropriate character entities:



When the XAML parser reads this, it correctly understands that you want to add the text , and it passes a string with this content, complete with angle brackets, to the Button.Content property.

38

www.allitebooks.com

CHAPTER 2 n XAML

n Note This limitation is a XAML detail, and it won’t affect you if you want to set the Button.Content property in code. Of course, C# has its own special character (the backslash) that must be escaped in string literals for the same reason. Special characters aren’t the only stumbling block you’ll run into with XAML. Another issue is whitespace handling By default, XML collapses all whitespace, which means a long string of spaces, tabs, and hard returns is reduced to a single space. Furthermore, if you add whitespace before or after your element content, this space is ignored completely. You can see this in the eight-ball example. The text in the button and the two text boxes is separated from the XAML tags by using a hard return and tab to make the markup more readable. However, this extra space doesn’t appear in the user interface. Sometimes this isn’t what you want. For example, you may want to include a series of several spaces in your button text. In this case, you need to use the xml:space=”preserve” attribute on your element. The xml:space attribute is part of the XML standard, and it’s an all-or-nothing setting. After you switch it on, all the whitespace inside that element is retained. For example, consider this markup:

[There is a lot of space inside these quotation marks "

".]

In this example, the text in the text box will include the hard return and tab that appear before the actual text. It will also include the series of spaces inside the text and the hard return that follows the text. If you just want to keep the spaces inside, you’ll need to use this less-readable markup: [There is a lot of space inside these quotation marks "

".]

The trick here is to make sure no whitespace appears between the opening > and your content, or between your content and the closing secondsBetweenAdjustments)) { lastAdjustmentTime = DateTime.Now; secondsBetweenBombs -= secondsBetweenBombsReduction; secondsToFall -= secondsToFallReduction; // (Technically, you should check for 0 or negative values. // However, in practice these won't occur because the game will // always end first.) // Set the timer to drop the next bomb at the appropriate time. bombTimer.Interval = TimeSpan.FromSeconds(secondsBetweenBombs); // Update the status message. lblRate.Text = String.Format("A bomb is released every {0} seconds.", secondsBetweenBombs); lblSpeed.Text = String.Format("Each bomb takes {0} seconds to fall.", secondsToFall); } } With this code in place, there’s enough functionality to drop bombs at an ever-increasing rate. However, the game still lacks the code that responds to dropped and saved bombs.

Intercepting a Bomb The user saves a bomb by clicking it before it reaches the bottom of the Canvas. Because each bomb is a separate instance of the Bomb user control, intercepting mouse clicks is easy—all you need to do is handle the MouseLeftButtonDown event, which fires when any part of the bomb is clicked (but doesn’t fire if you click somewhere in the background, such as around the edges of the bomb circle). When a bomb is clicked, the first step is to get the appropriate bomb object and set its IsFalling property to indicate that it’s no longer falling. (The IsFalling property is used by the event handler that deals with completed animations.) private void bomb_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // Get the bomb. Bomb bomb = (Bomb)sender; bomb.IsFalling = false; // Record the bomb's current (animated) position. 458

CHAPTER 16 n ADVANCED ANIMATION

double currentTop = Canvas.GetTop(bomb); ... The next step is to find the storyboard that controls the animation for this bomb so it can be stopped. To find the storyboard, you need to look it up in the collection that this game uses for tracking. Currently, WPF doesn’t include any standardized way to find the animations that are acting on a given element. ... // Stop the bomb from falling. Storyboard storyboard = storyboards[bomb]; storyboard.Stop(); ... After a button is clicked, another set of animations moves the bomb off the screen, throwing it up and left or right (depending on which side is closest). Although you could create an entirely new storyboard to implement this effect, the BombDropper game clears the current storyboard that’s being used for the bomb and adds new animations to it. When this process is completed, the new storyboard is started: ... // Reuse the existing storyboard, but with new animations. // Send the bomb on a new trajectory by animating Canvas.Top // and Canvas.Left. storyboard.Children.Clear(); DoubleAnimation riseAnimation = new DoubleAnimation(); riseAnimation.From = currentTop; riseAnimation.To = 0; riseAnimation.Duration = TimeSpan.FromSeconds(2); Storyboard.SetTarget(riseAnimation, bomb); Storyboard.SetTargetProperty(riseAnimation, new PropertyPath("(Canvas.Top)")); storyboard.Children.Add(riseAnimation); DoubleAnimation slideAnimation = new DoubleAnimation(); double currentLeft = Canvas.GetLeft(bomb); // Throw the bomb off the closest side. if (currentLeft < canvasBackground.ActualWidth / 2) { slideAnimation.To = -100; } else { slideAnimation.To = canvasBackground.ActualWidth + 100; } slideAnimation.Duration = TimeSpan.FromSeconds(1); Storyboard.SetTarget(slideAnimation, bomb); Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(Canvas.Left)")); storyboard.Children.Add(slideAnimation); // Start the new animation. storyboard.Duration = slideAnimation.Duration; 459

CHAPTER 16 n ADVANCED ANIMATION

storyboard.Begin(); } Now the game has enough code to drop bombs and bounce them off the screen when the user saves them. However, to keep track of which bombs are saved and which ones are dropped, you need to react to the Storyboard.Completed event that fires at the end of an animation.

Counting Bombs and Cleaning Up As you’ve seen, the BombDropper uses storyboards in two ways: to animate a falling bomb and to animate a defused bomb. You could handle the completion of these storyboards with different event handlers, but to keep things simple, the BombDropper uses just one. It tells the difference between an exploded bomb and a rescued bomb by examining the Bomb.IsFalling property. // End the game when 5 bombs have fallen. private int maxDropped = 5; private void storyboard_Completed(object sender, EventArgs e) { ClockGroup clockGroup = (ClockGroup)sender; // Get the first animation in the storyboard, and use it to find the // bomb that's being animated. DoubleAnimation completedAnimation = (DoubleAnimation)clockGroup.Children[0].Timeline; Bomb completedBomb = (Bomb)Storyboard.GetTarget(completedAnimation); // Determine if a bomb fell or flew off the Canvas after being clicked. if (completedBomb.IsFalling) { droppedCount++; } else { savedCount++; } ... Either way, the code then updates the display test to indicate the number of bombs that have been dropped and saved: ... // Update the display. lblStatus.Text = String.Format("You have dropped {0} bombs and saved {1}.", droppedCount, savedCount); ... At this point, the code checks to see whether the maximum number of dropped bombs has been reached. If it has, the game ends, the timer is stopped, and all the bombs and storyboards are removed: ... // Check if it's game over. if (droppedCount >= maxDropped) 460

CHAPTER 16 n ADVANCED ANIMATION

{ bombTimer.Stop(); lblStatus.Text += "\r\n\r\nGame over."; // Find all the storyboards that are underway. foreach (KeyValuePair item in storyboards) { Storyboard storyboard = item.Value; Bomb bomb = item.Key; storyboard.Stop(); canvasBackground.Children.Remove(bomb); } // Empty the tracking collection. storyboards.Clear(); // Allow the user to start a new game. cmdStart.IsEnabled = true; } else { // Clean up just this bomb, and let the game continue. Storyboard storyboard = (Storyboard)clockGroup.Timeline; storyboard.Stop(); storyboards.Remove(completedBomb); canvasBackground.Children.Remove(completedBomb); } } This completes the code for BombDropper game. However, you can make plenty of refinements. Some examples include the following: Animate a bomb explosion effect: This effect can make the flames around the bomb twinkle or send small pieces of shrapnel flying across the Canvas. Animate the background: This change is easy, and it adds pizzazz. For example, you can create a linear gradient that shifts up, creating an impression of movement, or one that transitions between two colors. Add depth: It’s easier than you think. The basic technique is to give the bombs different sizes. Bombs that are bigger should have a higher ZIndex, ensuring that they overlap smaller bombs, and should be given a shorter animation time, ensuring that they fall faster. You can also make the bombs partially transparent, so as one falls, the others behind it are visible. Add sound effects: In Chapter 26, you’ll learn to use sound and other media in WPF. You can use well-timed sound effects to punctuate bomb explosions or rescued bombs.

461

CHAPTER 16 n ADVANCED ANIMATION

Use animation easing: If you want bombs to accelerate as they fall, bounce off the screen, or wiggle more naturally, you can add easing functions to the animations used here. And, as you’d expect, easing functions can be constructed in code just as easily as in XAML. Fine-tune the parameters: You can provide more dials to tweak behavior (for example, variables that set how the bomb times, trajectories, and frequencies that are altered as the game processes). You can also inject more randomness (for example, allowing saved bombs to bounce off the Canvas in slightly different ways).

The Last Word In this chapter, you learned the techniques needed to make practical animations and integrate them into your applications. The only missing ingredient is the eye candy—in other words, making sure the animated effects are as polished as your code. As you’ve seen over the past two chapters, the animation model in WPF is surprisingly full-featured. However, getting the result you want isn’t always easy. If you want to animate separate portions of your interface as part of a single animated “scene,” you’re often forced to write a fair bit of markup with interdependent details that aren’t always clear. In more-complex animations, you may be forced to hardcode details and fall back to code to perform calculations for the ending value of animation. And if you need fine-grained control over an animation, such as when modeling a physical particle system, you’ll need to control every step of the way by using frame-based animation. The future of WPF animation promises higher-level classes that are built on the basic plumbing you’ve learned about in this chapter. Ideally, you’ll be able to plug animations into your application simply by using prebuilt animation classes, wrapping your elements in specialized containers, and setting a few attached properties. The actual implementation that generates the effect you want—whether it’s a smooth dissolve between two images or a series of animated fly-ins that builds a window—will be provided for you.

462

PART IV

Templates and Custom elements

463

ChAPTeR 17 nnn

Control Templates In the past, Windows developers were forced to choose between convenience and flexibility. For maximum convenience, they could use prebuilt controls. These controls worked well enough, but they offered limited customization and almost always had a fixed visual appearance. Occasionally, some controls provided a less-than-intuitive “owner drawing” mode that allowed developers to paint a portion of the control by responding to a callback. But the basic controls—buttons, text boxes, check boxes, list boxes, and so on—were completely locked down. As a result, developers who wanted a bit more pizzazz were forced to build custom controls from scratch. This was a problem—not only was it slow and difficult to write the required drawing logic by hand, but custom control developers also needed to implement basic functionality from scratch (such as selection in a text box or key handling in a button). And even after the custom controls were perfected, inserting them into an existing application involved a fairly significant round of editing, which would usually necessitate changes in the code (and more rounds of testing). In short, custom controls were a necessary evil—they were the only way to get a modern, distinctive interface, but they were also a headache to integrate into an application and support. WPF solves the control customization problem with styles (which you considered in Chapter 11) and templates (which you’ll begin exploring in this chapter). These features work so well because of the dramatically different way that controls are implemented in WPF. In previous user interface technologies, such as Windows Forms, commonly used controls aren’t actually implemented in .NET code. Instead, the Windows Forms control classes wrap core ingredients from the Win32 API, which are untouchable. But as you’ve already learned, in WPF every control is composed in pure .NET code, with no Win32 API glue in the background. As a result, it’s possible for WPF to expose mechanisms (styles and templates) that allow you to reach into these elements and tweak them. In fact, tweak is the wrong word because, as you’ll see in this chapter, WPF controls allow the most radical redesigns you can imagine.

Understanding Logical Trees and Visual Trees Earlier in this book, you spent a great deal of time considering the content model of a window—in other words, how you can nest elements inside other elements to build a complete window. For example, consider the extremely simple two-button window shown in Figure 17-1. To create this window, you nest a StackPanel control inside a Window. In the StackPanel, you place two Button controls, and inside of each you can add some content of your choice (in this case, two strings). Here’s the markup:

465

CHAPTER 17 n CONTROL TEMPLATES

First Button Second Button

Figure 17-1. A window with three elements The assortment of elements that you’ve added is called the logical tree, and it’s shown in Figure 17-2. As a WPF programmer, you’ll spend most of your time building the logical tree and then backing it up with event-handling code. In fact, all of the features you’ve considered so far (such as property value inheritance, event routing, and styling) work through the logical tree.

Figure 17-2. The logical tree for SimpleWindow 466

CHAPTER 17 n CONTROL TEMPLATES

However, if you want to customize your elements, the logical tree isn’t much help. Obviously, you could replace an entire element with another element (for example, you could substitute a custom FancyButton class in place of the current Button), but this requires more work, and it could disrupt your application’s interface or its code. For that reason, WPF goes deeper with the visual tree. A visual tree is an expanded version of the logical tree. It breaks elements down into smaller pieces. In other words, instead of seeing a carefully encapsulated black box such as the Button control, you see the visual components of that button—the border that gives buttons their signature shaded background (represented by the ButtonChrome class), the container inside (a ContentPresenter), and the block that holds the button text (represented by the familiar TextBlock). Figure 17-3 shows the visual tree for Figure 17-1.

Figure 17-3. The visual tree for SimpleWindow All of these details are themselves elements—in other words, every individual detail in a control such as Button is represented by a class that derives from FrameworkElement.

n Note It’s important to realize that there is more than one possible way to expand a logical tree into a visual tree. Details such as the styles you’ve used and the properties you’ve set can affect the way a visual tree is composed. For instance, in the previous example, the button holds text content, and as a result, it automatically creates a nested TextBlock element. But as you know, the Button control is a content control, so it can hold any other element you want to use, as long as you nest it inside the button. 467

CHAPTER 17 n CONTROL TEMPLATES

So far, this doesn’t seem that remarkable. You’ve just seen that all WPF elements can be decomposed into smaller parts. But what’s the advantage for a WPF developer? The visual tree allows you to do two useful things: •



You can alter one of the elements in the visual tree by using styles. You can select the specific element you want to modify by using the Style.TargetType property. You can even use triggers to make changes automatically when control properties change. However, certain details are difficult or impossible to modify. You can create a new template for your control. In this case, your control template will be used to build the visual tree exactly the way you want it.

Interestingly enough, WPF provides two classes that let you browse through the logical and visual trees. These classes are System.Windows.LogicalTreeHelper and System.Windows.Media.VisualTreeHelper. You’ve already seen the LogicalTreeHelper in Chapter 2, where it allowed you to hook up event handlers in a WPF application with a dynamically loaded XAML document. The LogicalTreeHelper provides the relatively sparse set of methods listed in Table 17-1. Although these methods are occasionally useful, in most cases you’ll use the methods of a specific FrameworkElement instead. Table 17-1. LogicalTreeHelper Methods

Name

Description

FindLogicalNode()

Finds a specific element by name, starting at the element you specify and searching down the logical tree.

BringIntoView()

Scrolls an element into view (if it’s in a scrollable container and isn’t currently visible). The FrameworkElement.BringIntoView() method performs the same trick.

GetParent()

Gets the parent element of a specific element.

GetChildren()

Gets the child element of a specific element. As you learned in Chapter 2, different elements support different content models. For example, panels support multiple children, while content controls support only a single child. However, the GetChildren() method abstracts away this difference and works with any type of element.

The VisualTreeHelper provides a few similar methods—GetChildrenCount(), GetChild(), and GetParent()—along with a small set of methods that are designed for performing lower-level drawing. (For example, you’ll find methods for hit testing and bounds checking, which you considered in Chapter 14.) The VisualTreeHelper also doubles as an interesting way to study the visual tree in your application. Using the GetChild() method, you can drill down through the visual tree of any window and display it for your consideration. This is a great learning tool, and it requires nothing more than a dash of recursive code. Figure 17-4 shows one possible implementation. Here, a separate window displays an entire visual tree, starting at any supplied object. In this example, another window (named SimpleWindow) uses the VisualTreeDisplay window to show its visual tree.

468

CHAPTER 17 n CONTROL TEMPLATES

Figure 17-4. Programmatically examining the visual tree Here, a window named Window1 contains a Border, which in turn holds an AdornerDecorator. (The AdornerDecorator class adds support for drawing content in the adorner layer, which is a special invisible region that overlays your element content. WPF uses the adorner layer to draw details such as focus cues and drag-and-drop indicators.) Inside the AdornerDecorator is a ContentPresenter, which hosts the content of the window. That content includes StackPanel with two Button controls, each of which comprises a ButtonChrome (which draws the standard visual appearance of the button) and a ContentPresenter (which holds the button content). Finally, inside the ContentPresenter of each button is a TextBlock that wraps the text you see in the window.

n Note In this example, the code builds a visual tree in another window. If you place the TreeView in the same window as the one you’re examining, you’d inadvertently change the visual tree as you fill the TreeView with items. Here’s the complete code for the VisualTreeDisplay window: public partial class VisualTreeDisplay : System.Windows.Window { public VisualTreeDisplay() { InitializeComponent(); } 469

CHAPTER 17 n CONTROL TEMPLATES

public void ShowVisualTree(DependencyObject element) { // Clear the tree. treeElements.Items.Clear(); // Start processing elements, begin at the root. ProcessElement(element, null); } private void ProcessElement(DependencyObject element, TreeViewItem previousItem) { // Create a TreeViewItem for the current element. TreeViewItem item = new TreeViewItem(); item.Header = element.GetType().Name; item.IsExpanded = true; // Check whether this item should be added to the root of the tree //(if it's the first item), or nested under another item. if (previousItem == null) { treeElements.Items.Add(item); } else { previousItem.Items.Add(item); } // Check whether this element contains other elements. for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { // Process each contained element recursively. ProcessElement(VisualTreeHelper.GetChild(element, i), item); } } } Once you’ve added this tree to a project, you can use this code from any other window to display its visual tree: VisualTreeDisplay treeDisplay = new VisualTreeDisplay(); treeDisplay.ShowVisualTree(this); treeDisplay.Show();

n Tip You can delve into the visual tree of other applications by using the remarkable Snoop utility, which is available at http://snoopwpf.codeplex.com. Using Snoop, you can examine the visual tree of any currently running WPF application. You can also zoom in on any element, survey routed events as they’re being executed, and explore and even modify element properties.

470

CHAPTER 17 n CONTROL TEMPLATES

Understanding Templates This look at the visual tree raises a few interesting questions. For example, how is a control translated from the logical tree into the expanded representation of the visual tree? It turns out that every control has a built-in recipe that determines how it should be rendered (as a group of more fundamental elements). That recipe is called a control template, and it’s defined by using a block of XAML markup.

n Note Every WPF control is designed to be lookless, which means that its visuals (the “look”) can be completely redefined. What doesn’t change is the control’s behavior, which is hardwired into the control class (although it can often be fine-tuned using various properties). When you choose to use a control such as the Button, you choose it because you want button-like behavior (in other words, an element that presents content can be clicked to trigger an action and can be used as the default or cancel button on a window). However, you’re free to change the way a button looks and how it reacts when you mouse over it or press it, as well as any other aspect of its appearance and visual behavior. Here’s a simplified version of the template for the common Button class. It omits the XML namespace declarations, the attributes that set the properties of the nested elements, and the triggers that determine how the button behaves when it’s disabled, focused, or clicked:



...

Although we haven’t yet explored the ButtonChrome and ContentPresenter classes, you can easily recognize that the control template provides the expansion you saw in the visual tree. The ButtonChrome class defines the standard button visuals, while the ContentPresenter holds whatever content you’ve supplied. If you wanted to build a completely new button (as you’ll see later in this chapter), you simply need to create a new control template. In place of ButtonChrome, you’d use something else—perhaps your own custom element or a shape-drawing element like the ones described in Chapter 12.

n Note ButtonChrome derives from Decorator (much like the Border class). That means it’s designed to add a graphical embellishment around another element—in this case, around the content of a button. The triggers control how the button changes when it is focused, clicked, and disabled. There’s actually nothing particularly interesting in these triggers. Rather than perform the heavy lifting themselves, the focus and click triggers simply modify a property of the ButtonChrome class that provides the visuals for the button:

471

CHAPTER 17 n CONTROL TEMPLATES

True

True



True

True

The first trigger ensures that when the button receives focus, the RenderDefaulted property is set to true. The Second trigger ensures that when the button is clicked, the RenderPressed property is set to true. Either way, it’s up to the ButtonChrome class to adjust itself accordingly. The graphical changes that take place are too complex to be represented by a few property setter statements. Both of the Setter objects in this example use the TargetName property to act upon a specific piece of a control template. This technique is possible only when working with a control template. In other words, you can’t write a style trigger that uses the TargetName property to access the ButtonChrome object, because the name Chrome isn’t in scope in your style. This is just one of the ways that templates give you more power than styles alone. Triggers don’t always need to use the TargetName property. For example, the trigger for the IsEnabled property simply adjusts the foreground color of any text content in the button. This trigger does its work by setting the attached TextElement.Foreground property without the help of the ButtonChrome class:

#FFADADAD

False

You’ll see the same division of responsibilities when you build your own control templates. If you’re lucky enough to be able to do all your work directly with triggers, you may not need to create custom classes and add code. On the other hand, if you need to provide more-complex visual tailoring, you may need to derive a custom chrome class of your own. The ButtonChrome class itself provides no customization—it’s dedicated to rendering the standard theme-specific appearance of a button.

n Note All the XAML that you see in this section is extracted from the standard Button control template. A bit later, in the “Dissecting Controls” section, you’ll learn how to view a control’s default control template. 472

CHAPTER 17 n CONTROL TEMPLATES

TyPeS oF TeMPlATeS This chapter focuses on control templates, which allow you to define the elements that make up a control. However, there are actually three types of templates in the WPF world, all of which derive from the base FrameworkTemplate class. Along with control templates (represented by the ControlTemplate class), there are data templates (represented by DataTemplate and HierarchicalDataTemplate) and the more specialized panel template for an ItemsControl (ItemsPanelTemplate). Data templates are used to extract data from an object and display it in a content control or in the individual items of a list control. Data templates are ridiculously useful in data-binding scenarios, and they’re described in detail in Chapter 20. To a certain extent, data templates and control templates overlap. For example, both types of templates allow you to insert additional elements, apply formatting, and so on. However, data templates are used to add elements inside an existing control. The prebuilt aspects of that control aren’t changed. On the other hand, control templates are a much more drastic approach that allows you to completely rewrite the content model of a control. Finally, panel templates are used to control the layout of items in a list control (a control that derives from the ItemsControl class). For example, you can use them to create a list box that tiles its items from right to left and then down (rather than the standard top-to-bottom single-line display). Panel templates are described in Chapter 20. You can certainly combine template types in the same control. For example, if you want to create a slick list control that is bound to a specific type of data, lays its items out in a nonstandard way, and replaces the stock border with something more exciting, you’ll want to create your own data templates, panel template, and control template.

The Chrome Classes The ButtonChrome class is defined in the Microsoft.Windows.Themes namespace, which holds a relatively small set of similar classes that render basic Windows details. Along with ButtonChrome, these classes include BulletChrome (for check boxes and radio buttons), ScrollChrome (for scrollbars), ListBoxChrome, and SystemDropShadowChrome. This is the lowest level of the public control API. At a slightly higher level, you’ll find that the System.Windows.Controls.Primitives namespace includes a number of basic elements that you can use independently but are more commonly wrapped into more-useful controls. These include ScrollBar, ResizeGrip (for sizing a window), Thumb (the draggable button on a scrollbar), TickBar (the optional set of ticks on a slider), and so on. Essentially, System.Windows.Controls.Primitives provides bare-bones ingredients that can be used in a variety of controls and aren’t very useful on their own, while Microsoft.Windows.Themes contains the down-and-dirty drawing logic for rendering these details. There’s one more difference. The types in System.Windows.Controls.Primitives are, like most WPF types, defined in the PresentationFramework.dll assembly. However, those in the Microsoft.Windows. Themes are defined separately in three assemblies: PresentationFramework.Aero.dll, PresentationFramework.Luna.dll, and PresentationFramework.Royale.dll. Each assembly includes its own version of the ButtonChrome class (and other chrome classes), with slightly different rendering logic. The one that WPF uses depends on your operating system and theme settings.

n Note You’ll learn more about the internal workings of a chrome class in Chapter 18, and you’ll learn to build your own chrome class with custom rendering logic.

473

CHAPTER 17 n CONTROL TEMPLATES

Although control templates often draw on the chrome classes, they don’t always need to do so. For example, the ResizeGrip element (which is to create the grid of dots in the bottom-right corner of a resizable window) is simple enough that its template can use the drawing classes you learned about in Chapter 12 and Chapter 13, such as Path, DrawingBrush, and LinearGradientBrush. Here’s the (somewhat convoluted) markup that it uses:



















n Note It’s common to see the SnapsToDevicePixels setting in a prebuilt control template (and it’s useful in the one you create as well). As you learned in Chapter 12, SnapsToDevicePixels ensures that single-pixel lines aren’t placed “between” pixels because of WPF’s resolution independence, which creates a fuzzy 2-pixel line.

Dissecting Controls When you create a control template (as you’ll see in the next section), your template replaces the existing template completely. This gives you a high level of flexibility, but it also makes life a little more complex. In most cases, you’ll need to see the standard template that a control uses before you can create your own adapted version. In some cases, your control template might mirror the standard template with only a minor change. 474

CHAPTER 17 n CONTROL TEMPLATES

The WPF documentation doesn’t list the XAML for standard control templates. However, you can get the information you need programmatically. The basic idea is to grab a control’s template from its Template property (which is defined as part of the Control class) and then serialize it to XAML by using the XamlWriter class. Figure 17-5 shows an example with a program that lists all the WPF controls and lets you view each one’s control template.

Figure 17-5. Browsing WPF control templates The secret to building this application is a healthy dose of reflection, the .NET API for examining types. When the main window in this application is first loaded, it scans all the types in the core PresentationFramework.dll assembly (which is where the Control class is defined). It then adds these types to a collection, which it sorts by type name, and then binds that collection to a list. private void Window_Loaded(object sender, EventArgs e) { Type controlType = typeof(Control); List derivedTypes = new List(); // Search all the types in the assembly where the Control class is defined. Assembly assembly = Assembly.GetAssembly(typeof(Control)); foreach (Type type in assembly.GetTypes()) { // Only add a type of the list if it's a Control, a concrete class, // and public. if (type.IsSubclassOf(controlType) && !type.IsAbstract && type.IsPublic) 475

CHAPTER 17 n CONTROL TEMPLATES

{ derivedTypes.Add(type); } } // Sort the types. The custom TypeComparer class orders types // alphabetically by type name. derivedTypes.Sort(new TypeComparer()); // Show the list of types. lstTypes.ItemsSource = derivedTypes; } Whenever a control is selected from the list, the corresponding control template is shown in the text box on the right. This step takes a bit more work. The first challenge is that a control template is null until the control is displayed in a window. Using reflection, the code attempts to create an instance of the control and add it to the current window (albeit with a Visibility of Collapse so it can’t be seen). The second challenge is that you need to convert the live ControlTemplate object to the familiar XAML markup. The static XamlWriter.Save() method takes care of this task, although the code uses the XmlWriter and XmlWriterSettings objects to make sure the XAML is indented so that it’s easier to read. All of this code is wrapped in an exception-handling block, which catches the problems that result from controls that can’t be created or can’t be added to a Grid (such as another Window or a Page): private void lstTypes_SelectionChanged(object sender, SelectionChangedEventArgs e) { try { // Get the selected type. Type type = (Type)lstTypes.SelectedItem; // Instantiate the type. ConstructorInfo info = type.GetConstructor(System.Type.EmptyTypes); Control control = (Control)info.Invoke(null); // Add it to the grid (but keep it hidden). control.Visibility = Visibility.Collapsed; grid.Children.Add(control); // Get the template. ControlTemplate template = control.Template; // Get the XAML for the template. XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; StringBuilder sb = new StringBuilder(); XmlWriter writer = XmlWriter.Create(sb, settings); XamlWriter.Save(template, writer); // Display the template. txtTemplate.Text = sb.ToString();

476

CHAPTER 17 n CONTROL TEMPLATES

// Remove the control from the grid. grid.Children.Remove(control); } catch (Exception err) { txtTemplate.Text = ">"; } } It wouldn’t be much more difficult to extend this application so you can edit the template in the text box, convert it back to a ControlTemplate object (using the XamlReader), and then assign that to a control to see its effect. However, you’ll have an easier time testing and refining templates by putting them into action in a real window, as described in the next section.

n Tip If you’re using Expression Blend, you can also use a handy feature that lets you edit the template for any control that you’re working with. (Technically, this step grabs the default template, creates a copy of it for your control, and then lets you edit the copy.) To try this, right-click a control on the design surface and choose Edit Control Parts (Template) ä Edit a Copy. Your control template copy will be stored as a resource (see Chapter 10), so you’ll be prompted to choose a descriptive resource key, and you’ll need to choose between storing your resource in the current window or in the global application resources so you can use your control template throughout your application.

Creating Control Templates So far, you’ve learned a fair bit about the way templates work, but you haven’t built a template of your own. In the following sections, you’ll build a simple custom button and learn a few of the finer details about control templates in the process. As you’ve already seen, the basic Button control uses the ButtonChrome class to draw its distinctive background and border. One of the reasons that the Button class uses ButtonChrome instead of the WPF drawing primitives is that a standard button’s appearance depends on a few obvious characteristics (whether it’s disabled, focused, or in the process of being clicked) and other subtler factors (such as the current Windows theme). Implementing this sort of logic with triggers alone would be awkward. However, when you build your own custom controls, you’re probably not as worried about standardization and theme integration. (In fact, WPF doesn’t emphasize user interface standardization nearly as strongly as previous user interface technologies.) Instead, you’re more concerned with creating attractive, distinctive controls that blend in with the rest of your user interface. For that reason, you might not need to create classes such as ButtonChrome. Instead, you can use the elements you already know (along with the drawing elements you learned about in Chapter 12 and Chapter 13, and the animation skills you picked in Chapter 15 and Chapter 16) to design a self-sufficient control template with no code.

n Note For an alternate approach, check out Chapter 18, which explains how to build your own chrome with custom rendering logic and integrate it into a control template.

477

CHAPTER 17 n CONTROL TEMPLATES

A Simple Button To apply a custom control template, you simply set the Template property of your control. Although you can define an inline template (by nesting the control template tag inside the control tag), this approach rarely makes sense. That’s because you’ll almost always want to reuse your template to skin multiple instances of the same control. To accommodate this design, you need to define your control template as a resource and refer to it by using a StaticResource reference, as shown here:

A Simple Button with a Custom Template Not only does this approach make it easier to create a whole host of customized buttons, it also gives you the flexibility to modify your control template later without disrupting the rest of your application’s user interface. In this particular example, the ButtonTemplate resource is placed in the Resources collection of the containing window. However, in a real application, you’re much more likely to use application resources. The reasons (and a few design tips) are discussed a bit later in the “Organizing Template Resources” section. Here’s the basic outline for the control template:

...

You’ll notice that this control template sets the TargetType property to explicitly indicate it’s designed for buttons. As a matter of style, this is always a good convention to follow. In content controls, such as the button, it’s also a requirement, or the ContentPresenter won’t work. To create a template for a basic button, you need to draw your own border and background and then place the content inside the button. Two possible candidates for drawing the border are the Rectangle class and the Border class. The following example uses the Border class to combine a rounded orange outline with an eye-catching red background and white text:

...

This takes care of the background, but you still need a way to display the button content. You may remember from your earlier exploration that the Button class includes a ContentPresenter in its control template. The ContentPresenter is required for all content controls—it’s the “insert content here” marker that tells WPF where to stuff the content:



478

CHAPTER 17 n CONTROL TEMPLATES

This ContentPresenter sets the RecognizesAccessKey property to true. Although this isn’t required, it ensures that the button supports access keys—underlined letters that you can use to quickly trigger the button. In this case, if your button has text such as Click _Me, the user can trigger the button by pressing Alt+M. (Under standard Windows settings, the underscore is hidden, and the access key—in this case, M— appears underlined as soon as you press the Alt key.) If you don’t set RecognizesAccessKey to true, this detail will be ignored, and any underscores will be treated as ordinary underscores and displayed as part of the button content.

n Note If a control derives from ContentControl, its template will include a ContentPresenter that specifies where the content will be placed. If the control derives from ItemsControl, its template will include an ItemsPresenter that indicates where the panel that contains the list of items will be placed. In rare cases, the control may use a derived version of one of these classes—for example, the ScrollViewer’s control template uses a ScrollContentPresenter, which derives from ContentPresenter.

Template Bindings There’s still one minor issue with this example. Right now the tag you’ve added for your button specifies a Margin value of 10 and a Padding of 5. The StackPanel pays attention to the Margin property of the button, but the Padding property is ignored, leaving the contents of your button scrunched up against the sides. The problem here is that the Padding property doesn’t have any effect unless you specifically heed it in your template. In other words, it’s up to your template to retrieve the padding value and use it to insert some extra space around your content. Fortunately, WPF has a tool that’s designed exactly for this purpose: template bindings. By using a template binding, your template can pull out a value from the control to which you’re applying the template. In this example, you can use a template binding to retrieve the value of the Padding property and use it to create a margin around the ContentPresenter:



This achieves the desired effect of adding some space between the border and the content. Figure 17-6 shows your modest new button.

479

CHAPTER 17 n CONTROL TEMPLATES

Figure 17-6. A button with a customized control template Template bindings are similar to ordinary data bindings, but they’re lighter weight because they’re specifically designed for use in a control template. They support only one-way data binding (in other words, they can pass information from the control to the template but not the other way around), and they can’t be used to draw information from a property of a class that derives from Freezable. If you run into a situation where template bindings won’t work, you can use a full-fledged data binding instead. Chapter 18 includes a sample color picker that runs into this problem and uses a combination of template bindings and regular bindings.

n Note Template bindings support the WPF change-monitoring infrastructure that’s built into all dependency properties. That means that if you modify a property in a control, the template takes it into account automatically. This detail is particularly useful when you’re using animations that change a property value repeatedly in a short space of time. The only way you can anticipate what template bindings are needed is to check the default control template. If you look at the control template for the Button class, you’ll find that it uses a template binding in exactly the same way as this custom template—it takes the padding specified on the button and converts it to a margin around the ContentPresenter. You’ll also find that the standard button template includes a few more template bindings that aren’t used in the simple customized template, such as HorizontalAlignment, VerticalAlignment, and Background. That means if you set these properties on the button, they’ll have no effect on the simple custom template.

n Note Technically, the ContentPresenter works because it has a template binding that sets the ContentPresenter. Content property to the Button.Content property. However, this binding is implicit, so you don’t need to add it yourself.

480

CHAPTER 17 n CONTROL TEMPLATES

In many cases, leaving out template bindings isn’t a problem. In fact, you don’t need to bind a property if you don’t plan to use it or don’t want it to change your template. For example, it makes sense that the current simple button sets the Foreground property for text to white and ignores any value you’ve set for the Background property because the foreground and background are intrinsic parts of this button’s visual appearance. There’s another reason you might choose to avoid template bindings—your control may not be able to support them adequately. For example, if you’ve ever set the Background property of a button, you’ve probably noticed that this background isn’t handled consistently when the button is pressed (in fact, it disappears at this point and is replaced with the default visual for pressed buttons). The custom template shown in this example is similar. Although it doesn’t yet have any mouseover and mouse-pressed behavior, after you add these details you’ll want to take complete control over the colors and how they change in different states.

Triggers That Change Properties If you try the button that you created in the previous section, you’ll find it’s a major disappointment. Essentially, it’s nothing more than a rounded red rectangle—as you move the mouse over it or click it, there’s no visual feedback. The button simply lies there inert. This problem is easily fixed by adding triggers to your control template. You first considered triggers with styles in Chapter 11. As you know, you can use triggers to change one or more properties when another property changes. The bare minimums that you’ll want to respond to in your button are IsMouseOver and IsPressed. Here’s a revised version of the control template that changes the colors when these properties change:









There’s one other change that makes this template work. The Border element has been given a name, and that name is used to set the TargetName property of each Setter. This way, the Setter can update the Background and BorderBrush properties of the Border that’s specified in the template. Using names is the easiest way to make sure a single specific part of a template is updated. You could create an element-typed rule that affects all Border elements (because you know there is only a single border in the button template), but this approach is both clearer and more flexible if you change the template later. There’s one more required element in any button (and most other controls)—a focus indicator. There’s no way to change the existing border to add a focus effect, but you can easily add another element that shows it and simply show or hide this element based on the Button.IsKeyboardFocused property by using a trigger. Although you could create a focus effect in many ways, the following example simply adds a 481

CHAPTER 17 n CONTROL TEMPLATES

transparent Rectangle element with a dashed border. The Rectangle doesn’t have the ability to hold child content, so you need to make sure the Rectangle overlaps the rest of the content. The easiest way to do this is to wrap the Rectangle and the ContentPresenter in a one-cell Grid, with both elements in the same cell. Here’s the revised template with focus support:













Once again, the Setter finds the element it needs to change by using the TargetName property (which points to the FocusCue rectangle in this example).

n Note This technique of hiding or showing elements in response to a trigger is a useful building block in many templates. You can use it to replace the visuals of a control with something completely different when its state changes. (For example, a clicked button could change from a rectangle to an ellipse by hiding the former and showing the latter.) Figure 17-7 shows three buttons that use the revised template. The second button currently has focus (as represented by the dashed rectangle), while the mouse is hovering over the third button.

482

CHAPTER 17 n CONTROL TEMPLATES

Figure 17-7. Buttons with focus and mouseover support To really round out this button, you’ll add an additional trigger that changes the button background (and possibly the text foreground) when the IsEnabled property of the button becomes false:



To make sure that this rule takes precedence over any conflicting trigger settings, you should define it at the end of the list of triggers. That way, it doesn’t matter if the IsMouseOver property is also true; the IsEnabled property trigger takes precedence, and the button remains inactive.

TeMPlATeS VS. STyleS It might have occurred to you that there’s a similarity between templates and styles. Both allow you to change the appearance of an element, usually throughout your application. However, styles are far more limited in scope. They’re able to adjust properties of the control but not replace it with an entirely new visual tree that’s made up of different elements. Already, the simple button you’ve seen includes features that couldn’t be duplicated with styles alone. Although you could use styles to set the background of a button, you’d have more trouble adjusting the background when the button was pressed because the built-in template for the button already includes a trigger for that purpose. You also wouldn’t have an easy way to add the focus rectangle. Control templates also open the door to many more exotic types of buttons that are unthinkable with styles. For example, rather than using a rectangular border, you can create a button that’s shaped like an ellipse or uses a path to draw a more complex shape. All you need are the drawing classes from Chapter 12. The rest of your markup—even the triggers that switch the background from one state to another—require relatively few changes. 483

CHAPTER 17 n CONTROL TEMPLATES

Triggers That Use Animation As you learned in Chapter 11, triggers aren’t limited to setting properties. You can also use event triggers to run animations when specific properties change. At first glance, this may seem like a frill, but it’s actually a key ingredient in all but the simplest WPF controls. For example, consider the button you’ve studied so far. Currently, it switches instantaneously from one color to another when the mouse moves over the top. However, a more modern button might use a very brief animation to blend from one color to the other, which creates a subtle but refined effect. Similarly, the button might use an animation to change the opacity of the focus cue rectangle, fading it quickly into view when the button gains focus rather than showing it in one step. In other words, event triggers allow controls to change from one state to another more gradually and more gracefully, which gives them that extra bit of polish. Here’s a revamped button template that uses triggers to make the button color pulse (shift continuously between red and blue) when the mouse is over it. When the mouse moves away, the button background returns to its normal color by using a separate 1-second animation:

















484

CHAPTER 17 n CONTROL TEMPLATES

You can add the mouseover animation in two equivalent ways—by creating an event trigger that responds to the MouseEnter and MouseLeave events (as demonstrated here) or by creating a property trigger that adds enter and exit actions when the IsMouseOver property changes. This example uses two ColorAnimation objects to change the button. Here are some other tasks you might want to perform with an EventTrigger-driven animation: Show or hide an element: To do this, you need to change the Opacity property of an element in the control template. Change the shape or position: You can use a TranslateTransform to tweak the positioning of an element (for example, offsetting it slightly to give the impression that the button has been pressed). You can use a ScaleTransform or a RotateTransform to twiddle the element’s appearance slightly as the user moves the mouse over it. Change the lighting or coloration: To do this, you need an animation that acts on the brush that you use to paint the background. You can use a ColorAnimation to change colors in a SolidBrush, but more-advanced effects are possible by animating more-complex brushes. For example, you can change one of the colors in a LinearGradientBrush (which is what the default button control template does), or you can shift the center point of a RadialGradientBrush.

n Tip Some advanced lighting effects use multiple layers of transparent elements. In this case, your animation modifies the opacity of one layer to let other layers show through.

Organizing Template Resources When using control templates, you need to decide how broadly you want to share your templates and whether you want to apply them automatically or explicitly. The first question asks you to think about where you want to use your templates. For example, are they limited to a specific window? In most situations, control templates apply to multiple windows and possibly even the entire application. To avoid defining them more than once, you can define them in the Resources collection of the Application class. However, this raises another consideration. Often control templates are shared between applications. It’s quite possible that a single application might use templates that have been developed separately. However, an application can have only a single App.xaml file and a single Application.Resources collection. For that reason, it’s a better idea to define your resources in separate resource dictionaries. That gives you the flexibility to bring them into action in specific windows or in the entire application. It also allows you to combine styles because any application can hold multiple resource dictionaries. To add a resource dictionary in Visual Studio, right-click your project in the Solution Explorer window, choose Add ä New Item, and then select Resource Dictionary (WPF). You’ve already learned about resource dictionaries in Chapter 10. Using them is easy. You simply need to add a new XAML file to your application with content like this:

485

CHAPTER 17 n CONTROL TEMPLATES

...

Although you could combine all your templates into a single resource dictionary file, experienced developers prefer to create a separate resource dictionary for each control template. That’s because a control template can quickly become quite complex and can draw on a host of other related resources. Keeping these together in one place, but separate from other controls, is good organization. To use your resource dictionary, you simply add it to the Resources collection of a specific window or, more commonly, your application. You do this by using the MergedDictionaries collection. For example, if your button template is in a file named Button.xaml in a project subfolder named Resources, you could use this markup in your App.xaml file:







Refactoring the Button Control Template As you enhance and extend a control template, you may find that it wraps various details, including specialized shapes, geometries, and brushes. It’s a good idea to pull these details out of your control template and define them as separate resources. One reason you’ll take this step is to make it easier to reuse these brushes among a set of related controls. For example, you might decide that you want to create a customized Button, CheckBox, and RadioButton that use a similar set of colors. To make this easier, you could create a separate resource dictionary for your brushes (named Brushes.xaml) and merge that into the resource dictionary for each of your controls (such as Button.xaml, CheckBox.xaml, and RadioButton. xaml). To see this technique in action, consider the following markup. It presents the complete resource dictionary for a button, including the resources that the control template uses, the control template, and the style rule that applies the control template to every button in the application. This is the order that you always need to follow because a resource needs to be defined before it can be used. (If you defined one of the brushes after the template, you’d receive an error because the template wouldn’t be able to find the brush it requires.)

















487

CHAPTER 17 n CONTROL TEMPLATES

Figure 17-8 shows the button that this template defines. In this example, a gradient fill is used when the user moves the mouse over the button. However, the gradient is always centered in the middle of the button. If you want to create a more exotic effect, such as a gradient that follows the position of the mouse, you’ll need to use an animation or write code. Chapter 18 shows an example with a custom chrome class that implements this effect.

Figure 17-8. A gradient button

Applying Templates with Styles There’s one limitation in this design. The control template essentially hard-codes quite a few details, such as the color scheme. That means that if you want to use the same combination of elements in your button (Border, Grid, Rectangle, and ContentPresenter) and arrange them in the same way but you want to supply a different color scheme, you’ll be forced to create a new copy of the template that references different brush resources. This isn’t necessarily a problem (after all, the layout and formatting details may be so closely related that you don’t want to separate them anyway). However, it does limit your ability to reuse your control template. If your template uses a complex arrangement of elements that you know you’ll want to reuse with a variety of formatting details (usually colors and fonts), you can pull these details out of your template and put them into a style. To make this work, you’ll need to rework your template. Instead of using hard-coded colors, you need to pull the information out of control properties by using template bindings. The following example defines a streamlined template for the fancy button you saw earlier. The control template treats some details as fundamental, unchanging ingredients—namely, the focus box and the rounded 2-unit-thick border. The background and border brushes are configurable. The only trigger that remains is the one that shows the focus box:

488

CHAPTER 17 n CONTROL TEMPLATES









The associated style applies this control template, sets the border and background colors, and adds triggers that change the background depending on the state of the button:

Ideally, you’d be able to keep all the triggers in the control template because they represent control behavior and use the style simply to set basic properties. Unfortunately, that’s not possible here if you want to give the style the ability to set the color scheme.

n Note If you set triggers in both the control template and style, the style triggers win out.

489

CHAPTER 17 n CONTROL TEMPLATES

To use this new template, you need to set the Style property of a button rather than the Template property:

A Simple Button with a Custom Template You can now create new styles that use the same template but bind to different brushes to apply a new color scheme. There’s one significant limitation in this approach. You can’t use the Setter.TargetName property in this style because the style doesn’t contain the control template (it simply references it). As a result, your style and its triggers are somewhat limited. They can’t reach deep into the visual tree to change the aspect of a nested element. Instead, your style needs to set a property of the control, and the element in the control needs to bind the property by using a template binding.

ConTRol TeMPlATeS VS. CuSToM ConTRolS You can get around both of the problems discussed here—being forced to define control behavior in the style with triggers and not being able to target specific elements—by creating a custom control. For example, you could build a class that derives from Button and adds properties such as HighlightBackground, DisabledBackground, and PressedBackground. You could then bind to these properties in the control template and simply set them in the style with no triggers required. However, this approach has its own drawback. It forces you to use a different control in your user interface (such as CustomButton instead of just Button). This is more trouble when designing the application. Usually, you’ll switch from custom control templates to custom controls in one of two situations: If you decide to create a custom control, Chapter 18 has all the information you need.

Applying Templates Automatically In the current example, each button is responsible for hooking itself up to the appropriate template by using the Template or Style property. This makes sense if you’re using your control template to create a specific effect in a specific place in your application. It’s less convenient if you want to re-skin every button in your entire application with a custom look. In this situation, it’s more likely that you want all the buttons in your application to acquire your new template automatically. To make this a reality, you need to apply your control template with a style. The trick is to use a typed style that affects the appropriate element type automatically and sets the Template property. Here’s an example of the style you’d place in the resources collection of your resource dictionary to give your buttons a new look:

This works because the style doesn’t specify a key name, which means the element type (Button) is used instead. Remember, you can still opt out of this style by creating a button that explicitly sets its Style to a null value:

490 c

CHAPTER 17 n CONTROL TEMPLATES

n Tip This technique works even better if you’ve followed good design practices and defined your button in a separate resource dictionary. In this situation, the style doesn’t sprint into action until you add a ResourceDictionary tag that imports your resources into the entire application or a specific window, as described earlier. A resource dictionary that contains a combination of type-based styles is often called (informally) a theme. The possibilities of themes are remarkable. They allow you to take an existing WPF application and completely re-skin all its controls without changing the user interface markup at all. All you need to do is add the resource dictionaries to your project and merge them into the Application.Resources collection in the App.xaml file. If you hunt around the Web, you’ll find more than a few themes that you can use to revamp a WPF application. For example, you can download several sample themes as part of the WPF Futures release at http://tinyurl.com/ylojdry. To use a theme, add the .xaml file that contains the resource dictionary to your project. For example, WPF Futures includes a theme file named ExpressionDark.xaml. Then you need to make the styles active in your application. You could do this on a window-by-window basis, but it’s quicker to import them at the application level by adding markup like this:



Now the type-based styles in the resource dictionary will be in full force and will automatically change the appearance of every common control in every window of your application. If you’re an application developer in search of a hot new user interface but you don’t have the design skills to build it yourself, this trick makes it easy to plug in third-party pizzazz with almost no effort.

Working with User-Selected Skins In some applications, you might want to alter templates dynamically, usually in response to user preferences. This is easy enough to accomplish, but it’s not well-documented. The basic technique is to load a new resource dictionary at runtime and use it to replace the current resource dictionary. (It’s not necessary to replace all your resources, just those that are used for your skin.) The trick is retrieving the ResourceDictionary object, which is compiled and embedded as a resource in your application. The easiest approach is to use the ResourceManager class described in Chapter 10 to load up the resources you want. For example, imagine you’ve created two resources that define alternate versions of the same button control template. One is stored in a file named GradientButton.xaml, while the other is in a file named GradientButtonVariant.xaml. Both files are placed in the Resources subfolder in the current project for better organization. Now you can create a simple window that uses one of these resources, using a Resources collection like this:



491

CHAPTER 17 n CONTROL TEMPLATES



Now you can swap in a different resource dictionary by using code like this: ResourceDictionary newDictionary = new ResourceDictionary(); newDictionary.Source = new Uri( "Resources/GradientButtonVariant.xaml", UriKind.Relative); this.Resources.MergedDictionaries[0] = newDictionary; This code loads the resource dictionary named GradientButtonVariant and places it into the first slot in the MergedDictionaries collection. It doesn’t clear the MergedDictionaries collection (or any other window resources) because it’s possible that you might be linking to other resource dictionaries that you want to continue using. It doesn’t add a new entry to the MergedDictionaries collection because there could then be conflict between resources with the same name but in different collections. If you were changing the skin for an entire application, you’d use the same approach, but you’d use the resource dictionary of the application. You could update this resource dictionary by using code like this: Application.Current.Resources.MergedDictionaries[0] = newDictionary; You can also load a resource dictionary that’s defined in another assembly by using the pack URI syntax described in Chapter 7: ResourceDictionary newDictionary = new ResourceDictionary(); newDictionary.Source = new Uri( "ControlTemplateLibrary;component/GradientButtonVariant.xaml", UriKind.Relative); this.Resources.MergedDictionaries[0] = newDictionary; When you load a new resource dictionary, all the buttons are automatically updated to use the new template. You can also include basic styles as part of your skin if you don’t need to be quite as ambitious when modifying a control. This example assumes that the GradientButton.xaml and GradientButtonVariant.xaml resources use an element-typed style to change your buttons automatically. As you know, there’s another approach—you can opt in to a new template by manually setting the Template or Style property of your Button objects. If you take this approach, make sure you use a DynamicResource reference instead of a StaticResource. If you use a StaticResource, the button template won’t be updated when you switch skins.

n Note When using a DynamicResource reference, you’re making an assumption that the resource you need will appear somewhere in the resource hierarchy. If it doesn’t, the resource is simply ignored, and the buttons revert to their standard appearance without generating an error. There’s another way to load resource dictionaries programmatically. You can create a code-behind class for your resource dictionary in much the same way you create code-behind classes for windows. You can then instantiate that class directly rather than using the ResourceDictionary.Source property. This approach has the benefit of being strongly typed (there’s no chance of entering an invalid URI for the Source property), and it allows you to add properties, methods, and other functionality to your resource class. For example, you’ll use this ability to create a resource that has event-handling code for a custom window template in Chapter 23.

492

CHAPTER 17 n CONTROL TEMPLATES

Although it’s easy enough to create a code-behind class for your resource dictionary, Visual Studio doesn’t do it automatically. Instead, you need to add a code file with a partial class that derives from ResourceDictionary and calls InitializeComponent in the constructor: public partial class GradientButtonVariant : ResourceDictionary { public GradientButtonVariant() { InitializeComponent(); } } Here, the class name GradientButtonVariant is used, and the class is stored in a file named GradientButtonVariant.xaml.cs. The XAML file holding the resource is named GradientButtonVariant. xaml. It’s not necessary to make these names consistent, but it’s a good idea, and it’s in keeping with the convention Visual Studio uses when you create windows and pages. The next step is to link your class to the resource dictionary. You do that by adding the Class attribute to the root element of your resource dictionary, just as you do with a window and just as you can do with any XAML class. You then supply the fully qualified class name. In this example, the project is named ControlTemplates, which is the reason for the default namespace, so the finished tag looks like this:

You can now use this code to create your resource dictionary and apply it to a window: GradientButtonVariant newDictionary = new GradientButtonVariant(); this.Resources.MergedDictionaries[0] = newDictionary; If you want your GradientButtonVariant.xaml.cs file to appear nested under the GradientButtonVariant.xaml file in the Solution Explorer, you need to modify the .csproj project file in a text editor. Find the code-behind file in the section and change this:

to this:

Resources\GradientButtonVariant.xaml

Building More Complex Templates There is an implicit contract between a control’s template and the code that underpins it. If you’re replacing a control’s standard template with one of your own, you need to make sure your new template meets all the requirements of the control’s implementation code. In simple controls, this process is easy, because there are few (if any) real requirements on the template. In a complex control, the issue is subtler, because it’s impossible for the visuals and the implementation to be completely separated. In this situation, the control needs to make some assumptions about its visual display, no matter how well it has been designed. You’ve already seen two examples of the requirements a control can place on its control template, with placeholder elements (such as ContentPresenter and ItemsPresenter) and template bindings. In the following sections, you’ll see two more: elements with specific names (starting with PART_) and elements that are specially designed for use in a particular control’s template (such as Track in the ScrollBar control). To create a successful control template, you need to look carefully at the standard template for the control 493

CHAPTER 17 n CONTROL TEMPLATES

in question, make note of how these four techniques are used, and then duplicate them in your own templates.

n Note There’s another way to get comfortable with the interaction between controls and control templates. You can create your own custom control. In this case, you’ll have the reverse challenge—you’ll need to create code that uses a template in a standardized way and that can work equally well with templates supplied by other developers. You’ll tackle this challenge in Chapter 18 (which makes a great complement to the perspective you’ll get in this chapter).

Nested Templates The template for the button control can be decomposed into a few relatively simple pieces. However, many templates aren’t so simple. In some cases, a control template will contain a number of elements that every custom template will require as well. And in some cases, changing the appearance of a control involves creating more than one template. For example, imagine you’re planning to revamp the familiar ListBox control. The first step to create this example is to design a template for the ListBox and (optionally) add a style that applies the template automatically. Here are both ingredients rolled into one:

This style draws on two brushes for painting the border and the background. The actual template is a simplified version of the standard ListBox template, but it avoids the ListBoxChrome class in favor of a simpler Border. Inside the Border is the ScrollViewer that provides the list scrolling, and an ItemsPresenter that holds all the items of the list. This template is most notable for what it doesn’t let you do—namely, configure the appearance of individual items in the list. Without this ability, the selected item is always highlighted with the familiar blue background. To change this behavior, you need to add a control template for the ListBoxItem, which is a content control that wraps the content of each individual item in the list. As with the ListBox template, you can apply the ListBoxItem template by using an element-typed style. The following basic template wraps each item in an invisible border. Because the ListBoxItem is a content 494

CHAPTER 17 n CONTROL TEMPLATES

control, you use the ContentPresenter to place the item content inside. Along with these basics are triggers that react when an item is moused over or clicked:

Together, these two templates allow you to create a list box that uses animation to enlarge the item over which the mouse is currently positioned. Because each ListBoxItem can have its own animation, when you run your mouse up and down the list, you’ll see several items start to grow and then shrink back again, creating an intriguing “fish-eye” effect. (A more extravagant fish-eye effect would enlarge and warp the item over which you’re hovering, using animated transforms.)

495

CHAPTER 17 n CONTROL TEMPLATES

Although it’s not possible to capture this effect in a single image, Figure 17-9 shows a snapshot of this list after the mouse has moved rapidly over several items.

Figure 17-9. Using a custom template for the ListBoxItem You won’t reconsider the entire template ListBoxItem example here, because it’s built from many pieces that style the ListBox, the ListBoxItem, and the various constituents of the ListBox (such as the scrollbar). The important piece is the style that changes the ListBoxItem template. In this example, the ListBoxItem enlarges relatively slowly (over 1 second) and then decreases much more quickly (in 0.2 seconds). However, there is a 0.5-second delay before the shrinking animation begins. Note that the shrinking animation leaves out the From and To properties. That way, it always shrinks the text from its current size to its original size. If you move the mouse on and off a ListBoxItem, you’ll get the result you expect—it appears as though the item simply continues expanding while the mouse is on top and continues shrinking when the mouse is moved away.

n Tip As always, the best way to get used to these conventions is to play with the template browser shown earlier to look at the control templates for basic controls. You can then copy and edit the template to use it as a basis for your custom work.

Modifying the Scrollbar There’s one aspect of the list box that’s remained out of touch: the scrollbar on the right. It’s part of the ScrollViewer, which is part of the ListBox template. Even though this example redefines the ListBox template, it doesn’t alter the ScrollViewer of the ScrollBar. To customize this detail, you could create a new ScrollViewer template for use with the ListBox. You could then point the ScrollViewer template to your custom ScrollBar template. However, there’s an easier option. You can create an element-typed style that changes the template of all the ScrollBar controls it comes across. This avoids the extra work of creating the ScrollViewer template. Of course, you also need to think about how this design affects the rest of your application. If you create an element-typed style ScrollBar and add it to the Resources collection of a window, all the controls in that window will have the newly styled scrollbars whenever they use the ScrollBar control, which may 496

CHAPTER 17 n CONTROL TEMPLATES

be exactly what you want. On the other hand, if you want to change only the scrollbar in the ListBox, you must add the element-typed ScrollBar style to the resources collection of the ListBox itself. And finally, it you want to change the way scrollbars look in your entire application, you can add it to the resources collection in the App.xaml file. The ScrollBar control is surprisingly sophisticated. It’s actually built out of a collection of smaller pieces, as shown in Figure 17-10.

Figure 17-10. Dissecting the scrollbar The background of the scrollbar is represented by the Track class—it’s usually a shaded rectangle that’s stretched out over the length of the scrollbar. At the far ends of the scrollbar are buttons that allow you to move one increment up or down (or to the left or right). These are instances of the RepeatButton class, which derives from ButtonBase. The key difference between a RepeatButton and the ordinary Button class is that if you hold the mouse down on a RepeatButton, the Click event fires over and over again (which is handy for scrolling). In the middle of the scrollbar is a Thumb that represents the current position in the scrollable content. And, most interesting of all, the blank space on either side of the thumb is made up of two more RepeatButton objects, which are transparent. When you click either one of these, the scrollbar scrolls an entire page (a page is defined as the amount that fits in the visible window of the scrollable content). This gives you the familiar ability to jump quickly through scrollable content by clicking the bar on either side of the thumb. Here’s the template for a vertical scrollbar:



497

CHAPTER 17 n CONTROL TEMPLATES



















This template is fairly straightforward, once you understand the multipart structure of the scrollbar (as shown in Figure 17-10). There are a few key points to note:

498

CHAPTER 17 n CONTROL TEMPLATES

• •





The vertical scrollbar consists of a three-row grid. The top and bottom rows hold the buttons at either end (and appear as arrows). They’re fixed at 18 units. The middle section, which holds the track, takes the rest of the space. The RepeatButton elements at both ends use the same style. The only difference is the Content property that contains a Path that draws the arrow— because the top button has an up arrow, while the bottom button has a down arrow. For conciseness, these arrows are represented by using the path mini-language described in Chapter 13. The other details, such as the background fill and the circle that appears around the arrow, are defined in the control template, which is set out in the ScrollButtonLineStyle. Both buttons are linked to a command in the ScrollBar class (LineUpCommand and LineDownCommand). This is how they do their work. As long as you provide a button that’s linked to this command, it doesn’t matter what its name is, what it looks like, or what specific class it uses. (Commands are covered in detail in Chapter 9.) The Track has the name PART_Track. You must use this name in order for the ScrollBar class to hook up its code successfully. If you look at the default template for the ScrollBar class (which is similar, but lengthier), you’ll see it appears there as well.

n Note If you’re examining a control with reflection (or using a tool such as Reflector), you can look for the TemplatePart attributes attached to the class declaration. There should be one TemplatePart attribute for each named part. The TemplatePart attribute indicates the name of the expected element (through the Name property) and its class (through the Type property). In Chapter 18, you’ll see how to apply the TemplatePart attribute to your own custom control classes. •



The Track.ViewportSize property is set to 0. This is a specific implementation detail in this template. It ensures that the Thumb always has the same size. (Ordinarily, the thumb is sized proportionately based on the content so that if you’re scrolling through content that mostly fits in the window, the thumb becomes much larger.) The Track wraps two RepeatButton objects (whose style is defined separately) and the Thumb. Once again, these buttons are wired up to the appropriate functionality by using commands.

You’ll also notice that the template uses a key name that specifically identifies it as a vertical scrollbar. As you learned in Chapter 11, when you set a key name on a style, you ensure that it isn’t applied automatically, even if you’ve also set the TargetType property. The reason this example uses this approach is that the template is suitable only for scrollbars in the vertical orientation. Another, element-typed style uses a trigger to automatically apply the control template if the ScrollBar.Orientation property is set to Vertical:

Although you could easily build a horizontal scrollbar out of the same basic pieces, this example doesn’t take that step (and so retains the normally styled horizontal scrollbar). The final task is to fill in the styles that format the various RepeatButton objects and the Thumb. These styles are relatively modest, but they do change the standard look of the scrollbar. First, the Thumb is shaped like an ellipse:

Next, the arrows at either end are drawn inside nicely rounded circles. The circles are defined in the control template, while the arrows are provided from the content of the RepeatButton and inserted into the control template by using the ContentPresenter:

The RepeatButton objects that are displayed over the track aren’t changed. They simply use a transparent background so the track shows through:

Unlike the normal scrollbar, in this template no background is assigned to the Track, which leaves it transparent. That way, the gently shaded gradient of the list box shows through. Figure 17-11 shows the final list box.

Figure 17-11. A list box with a customized scrollbar

Exploring the Control Template Examples As you’ve seen, giving a new template to a common control can be a detailed task. That’s because all the requirements of a control template aren’t always obvious. For example, a typical ScrollBar requires a combination of two RepeatButton objects and a Track. Other control templates need elements with specific PART_ names. In the case of a custom window, you need to make sure the adorner layer is defined because some controls will require it. Although you can discover these details by exploring the default template for a control, these default templates are often complicated and include details that aren’t important and bindings that you probably

501

CHAPTER 17 n CONTROL TEMPLATES

won’t support anyway. Fortunately, there’s a better place to get started: the ControlTemplateExamples sample project (formerly known as SimpleStyles). The control template examples provide a collection of simple, streamlined templates for all WPF’s standard controls, which makes them a useful jumping-off point for any custom control designer. Unlike the default control templates, these use standard colors, perform all their work declaratively (with no chrome classes), and leave out optional parts such as template bindings for less commonly used properties. The goal of control template examples is to give developers a practical starting point that they can use to design their own graphically enhanced control templates. Figure 17-12 shows about half of the control template examples.

Figure 17-12. WPF controls with bare-bones styles The SimpleStyles examples are included with the .NET Framework SDK. The easiest way to get them is to download them directly from http://tinyurl.com/9jtk93x.

n Tip The SimpleStyles are one of the hidden gems of WPF. They provide templates that are easier to understand and enhance than the default control templates. If you need to enhance a common control with a custom look, this project should be your first stop. 502

CHAPTER 17 n CONTROL TEMPLATES

Visual States So far, you’ve learned the most direct (and most popular) way to craft a control template: using a mix of elements, binding expressions, and triggers. The elements create the overall visual structure of the control. The bindings pull information from the properties of the control class and apply it to the elements inside. And the triggers create the interactivity, allowing the control to change its appearance when its state changes. The advantage of this model is that it’s extremely powerful and extremely flexible. You can do just about anything you want. This isn’t immediately apparent in the button example, because the control template relies on built-in properties such as IsMouseOver and IsPressed. But even if these properties weren’t available, you could still craft a control template that changes itself in response to mouse movements and button clicks. The trick would be to use event triggers that apply animations. For example, you could add an event trigger that reacts to the Border.MouseOver by starting an animation that changes the border background color. This animation doesn’t even need to look like an animation—if you give it a duration of 0 seconds, it will apply itself immediately, just like the property triggers you’re using now. In fact, this exact technique is used in a number of professional template examples. Despite their capabilities, trigger-based templates have a downside: they require that the template designer has a detailed understanding of the way the control works. In the button example, the template designer needs to know about the existence of the IsMouseOver and IsPressed properties, for example, and how to use them. And these aren’t the only details—for example, most controls need to react visually to mouse movements, being disabled, getting focus, and many other state changes. When these states are applied in combination, it can be difficult to determine exactly how the control should look. The triggerbased model is also notoriously awkward with transitions. For example, imagine you want to create a button that pulses while the mouse is over it. To get a professional result, you may need two animations— one that changes the state of the button from normal to mouseover and one that applies the continuous pulsing effect immediately after that. Managing all these details with a trigger-based template can be a challenge. In WPF 4, Microsoft added a feature called visual states, which addresses this challenge. Using named parts (which you’ve already seen) and visual states, a control can provide a standardized visual contract. Rather than understanding the entire control, a template designer simply needs to understand the rules of the visual contract. As a result, it’s much easier to design a simple control template—especially when it’s for a control you’ve never worked with before. Much as controls can use the TemplatePart attribute to indicate specific named elements (or parts) that the control template should include, they can use the TemplateVisualState attribute to indicate the visual states they support. For example, an ordinary button would provide a set of visual states like this: [TemplateVisualState(Name="Normal", GroupName="CommonStates")] [TemplateVisualState(Name="MouseOver", GroupName="CommonStates")] [TemplateVisualState(Name="Pressed", GroupName="CommonStates")] [TemplateVisualState(Name="Disabled", GroupName="CommonStates")] [TemplateVisualState(Name="Unfocused", GroupName="FocusStates")] [TemplateVisualState(Name="Focused", GroupName="FocusStates")] public class Button : ButtonBase { ... } States are placed together in groups. Groups are mutually exclusive, which means a control has one state in each group. For example, the button shown here has two state groups: CommonStates and FocusStates. At any given time, the button has one of the states from the CommonStates group and one of the states from the FocusStates group. For example, if you tab to the button, its states will be Normal (from CommonStates) and Focused (from FocusStates). If you then move the mouse over the button, its states will be MouseOver (from 503

CHAPTER 17 n CONTROL TEMPLATES

CommonStates) and Focused (from FocusStates). Without state groups, you’d have trouble dealing with this situation. You’d either be forced to make some states dominate others (so a button in the MouseOver state would lose its focus indicator) or need to create many more states (such as FocusedNormal, UnfocusedNormal, FocusedMouseOver, UnfocusedMouseOver, and so on). At this point, you can already see the appeal of the visual states model. From the template, it’s immediately clear that a control template needs to address six state possibilities. You also know the name of each state, which is the only essential detail. You don’t need to know what properties the Button class provides or understand the inner workings of the control. Best of all, if you use Expression Blend, you’ll get enhanced design-time support when creating control templates for a control that supports visual states. Blend will show you the named parts and visual states the control supports (as defined with the TemplatePart and TemplateVisualState attributes), and you can then add the corresponding elements and storyboards. In the next chapter, you’ll see a custom control named the FlipPanel that puts the visual state model into practice.

The Last Word In this chapter, you learned how to use basic template-building techniques to re-skin core WPF controls like the button, without being forced to reimplement any core button functionality. These custom buttons support all the normal button behavior—you can tab from one to the next, you can click them to fire an event, you can use access keys, and so on. Best of all, you can reuse your button template throughout your application and still replace it with a whole new design at a moment’s notice. So, what more do you need to know before you can skin all the basic WPF controls? To get the snazzy look you probably want, you might need to spend more time studying the details of WPF drawing (Chapter 12 and Chapter 13) and animation (Chapter 15 and Chapter 16). It might surprise you to know that you can use the shapes and brushes you’ve already learned about to build sophisticated controls with glass-style blurs and soft glow effects. The secret is in combining multiple layers of shapes, each with a different gradient brush. The best way to get this sort of effect is to learn from the control template examples others have created. Here are two good examples to check out: •



504

There are plenty of handcrafted, shaded buttons with glass and soft glow effects on the Web. You can find an old (but still useful) tutorial that walks you through the process of creating a snazzy glass button in Expression Blend at http://tinyurl. com/3bk26g. An MSDN Magazine article about control templates provides examples of templates that incorporate simple drawings in innovative ways. For example, a CheckBox is replaced by an up-down lever, a slider is rendered with a three-dimensional tab, a ProgressBar is changed into a thermometer, and so on. Check it out at http://msdn. microsoft.com/magazine/cc163497.aspx.

ChAPTeR 18 nnn

Custom Elements In previous Windows development frameworks, custom controls played a central role. But in WPF, the emphasis has shifted. Custom controls are still a useful way to build custom widgets that you can share between applications, but they’re no longer a requirement when you want to enhance and customize core controls. (To understand how remarkable this change is, it helps to point out that this book’s predecessor, Pro .NET 2.0 Windows Forms and Custom Controls in C#, had nine complete chapters about custom controls and additional examples in other chapters. But in this book, you’ve made it to Chapter 18 without a single custom control sighting!) WPF de-emphasizes custom controls because of its support for styles, content controls, and templates. These features give every developer several ways to refine and extend standard controls without deriving a new control class. Here are your possibilities: Styles: You can use a style to painlessly reuse a combination of control properties. You can even apply effects by using triggers. Content controls: Any control that derives from ContentControl supports nested content. Using content controls, you can quickly create compound controls that aggregate other elements. (For example, you can transform a button into an image button or a list box into an image list.) Control templates: All WPF controls are lookless, which means they have hardwired functionality but their appearance is defined separately through the control template. Replace the default template with something new, and you can revamp basic controls such as buttons, check boxes, radio buttons, and even windows. Data templates: All ItemsControl-derived classes support data templates, which allow you to create a rich list representation of some type of data object. Using the right data template, you can display each item by using a combination of text, images, and even editable controls, all in a layout container of your choosing. If possible, you should pursue these avenues before you decide to create a custom control or another type of custom element. That’s because these solutions are simpler, easier to implement, and often easier to reuse. So, when should you create a custom element? Custom elements aren’t the best choice when you want to fine-tune the appearance of an element, but they do make sense when you want to change its underlying functionality. For example, there’s a reason that WPF has separate classes for the TextBox and 505

CHAPTER 18 n CUSTOM ELEMENTS

PasswordBox classes. They handle key presses differently, store their data internally in a different way, interact with other components such as the clipboard differently, and so on. Similarly, if you want to design a control that has its own distinct set of properties, methods, and events, you’ll need to build it yourself. In this chapter, you’ll learn how to create custom elements and how to make them into first-class WPF citizens. That means you’ll outfit them with dependency properties and routed events to get support for essential WPF services such as data binding, styles, and animation. You’ll also learn how to create a lookless control—a template-driven control that allows the control consumer to supply different visuals for greater flexibility.

Understanding Custom Elements in WPF Although you can code a custom element in any WPF project, you’ll usually want to place custom elements in a dedicated class library (DLL) assembly. That way, you can share your work with multiple WPF applications. To make sure you have the right assembly references and namespace imports, you should choose the Custom Control Library (WPF) project type when you create your application in Visual Studio. Inside your class library, you can create as many or as few controls as you like.

n Tip As with all class library development, it’s often a good practice to place both your class library and the application that uses your class library in the same Visual Studio solution. That way, you can easily modify and debug both pieces at once. The first step in creating a custom control is choosing the right base class to inherit from. Table 18-1 lists some commonly used classes for creating custom controls, and Figure 18-1 shows where they fit into the element hierarchy. Table 18-1. Base Classes for Creating a Custom Element

506

Name

Description

FrameworkElement

This is the lowest level you’ll typically use when creating a custom element. Usually, you’ll take this approach only if you want to draw your content from scratch by overriding OnRender()and using the System.Windows.Media. DrawingContext. It’s similar to the approach you saw in Chapter 14, where a user interface was constructed by using Visual objects. The FrameworkElement class provides the basic set of properties and events for elements that aren’t intended to interact with the user.

Control

This is the most common starting point when building a control from scratch. It’s the base class for all user-interactive widgets. The Control class adds properties for setting the background and foreground, as well as the font and alignment of content. The control class also places itself into the tab order (through the IsTabStop property) and introduces the notion of double-clicking (through the MouseDoubleClick and PreviewMouseDoubleClick events). But most important, the Control class defines the Template property that allows its appearance to be swapped out with a customized element tree for endless flexibility.

CHAPTER 18 n CUSTOM ELEMENTS

ContentControl

This is the base class for controls that can display a single piece of arbitrary content. That content can be an element or a custom object that’s used in conjunction with a template. (The content is set through the Content property, and an optional template can be provided in the ContentTemplate property.) Many controls wrap a specific, limited type of content (such as a string of text in a text box). Because these controls don’t support all elements, they shouldn’t be defined as content controls.

UserControl

This is a content control that can be configured using a design-time surface. Although a user control isn’t that different from an ordinary content control, it’s typically used when you want to quickly reuse an unchanging block of user interface in more than one window (rather than create a true stand-alone control that can be transported from one application to another).

ItemsControl or Selector

ItemsControl is the base class for controls that wrap a list of items but don’t support selection, while Selector is the more specialized base class for controls that do support selection. These classes aren’t often used to create custom controls, because the data-templating features of the ListBox, ListView, and TreeView provide a great deal of flexibility.

Panel

This is the base class for controls with layout logic. A layout control can hold multiple children and arranges them according to specific layout semantics. Often panels include attached properties that can be set on the children to configure how the children are arranged.

Decorator

This is the base class for elements that wrap another element and provide a graphical effect or specific feature. Two prominent examples are the Border, which draws a line around an element, and the Viewbox, which scales its content dynamically using a transform. Other decorators include the chrome classes used to give the familiar border and background to common controls such as the button.

A specific control class

If you want to introduce a refinement to an existing control, you can derive directly from that control. For example, you can create a TextBox with built-in validation logic. However, before you take this step, consider whether you could accomplish the same thing by using event-handling code or a separate component. Both approaches allow you to decouple your logic from the control and reuse it with other controls.

507

CHAPTER 18 n CUSTOM ELEMENTS

Figure 18-1. Element and control base classes

n Note Although you can create a custom element that isn’t a control, most custom elements you create in WPF will be controls—that is to say they’ll be able to receive focus, and they’ll interact with the user’s key presses and mouse actions. For that reason, the terms custom elements and custom controls are sometimes used interchangeably in WPF development. In this chapter, you’ll see a user control, a lookless color picker that derives directly from the Control class, a lookless FlipPanel that uses visual states, a custom layout panel, and a custom-drawn element that derives from FrameworkElement and overrides OnRender(). Many of the examples are quite lengthy. Although you’ll walk through almost all of the code in this chapter, you’ll probably want to follow up by downloading the samples and playing with the custom controls yourself.

Building a Basic User Control A good way to get started with custom controls is to take a crack at creating a straightforward user control. In this section, we’ll begin by creating a basic color picker. Later, you’ll see how to refactor this control into a more capable template-based control.

508

CHAPTER 18 n CUSTOM ELEMENTS

Creating a basic color picker is easy. However, creating a custom color picker is still a worthy exercise. Not only does it demonstrate a variety of important control-building concepts, but it also gives you a practical piece of functionality. You could create a custom dialog box for your color picker. But if you want to create a color picker that you can integrate into different windows, a custom control is a far better choice. The most straightforward type of custom control is a user control, which allows you to assemble a combination of elements in the same way as when you design a window or page. Because the color picker appears to be little more than a fairly straightforward grouping of existing controls with added functionality, a user control seems like a perfect choice. A typical color picker allows a user to select a color by clicking somewhere in a color gradient or specifying individual red, green, and blue components. Figure 18-2 shows the basic color picker you’ll create in this section (at the top of the window). It consists of three Slider controls for adjusting color components, along with a Rectangle that shows a preview of the selected color.

Figure 18-2. A color picker user control

n Note The user control approach has one significant flaw—it limits your ability to customize the appearance of your color picker to suit different windows, applications, and uses. Fortunately, it’s not much harder to step up to a more template-based control, as you’ll see a bit later.

Defining Dependency Properties The first step in creating the color picker is to add a user control to your custom control library project. When you do, Visual Studio creates a XAML markup file and a corresponding custom class to hold your initialization and event-handling code. This is the same experience as when you create a new window or page—the only difference is that the top-level container is the UserControl class. public partial class ColorPicker : System.Windows.Controls.UserControl { ... } 509

CHAPTER 18 n CUSTOM ELEMENTS

The easiest starting point is to design the public interface that the user control exposes to the outside world. In other words, it’s time to create the properties, methods, and events that the control consumer (the application that uses the control) will rely on to interact with the color picker. The most fundamental detail is the Color property—after all, the color picker is nothing more than a specialized tool for displaying and choosing a color value. To support WPF features such as data binding, styles, and animation, writeable control properties are almost always dependency properties. As you learned in Chapter 4, the first step to creating a dependency property is to define a static field for it, with the word Property added to the end of your property name: public static DependencyProperty ColorProperty; The Color property will allow the control consumer to set or retrieve the color value programmatically. However, the sliders in the color picker also allow the user to modify one aspect of the current color. To implement this design, you could use event handlers that respond when a slider value is changed and update the Color property accordingly. But it’s cleaner to wire the sliders up by using data binding. To make this possible, you need to define each of the color components as a separate dependency property: public static DependencyProperty RedProperty; public static DependencyProperty GreenProperty; public static DependencyProperty BlueProperty; Although the Color property will store a System.Windows.Media.Color object, the Red, Green, and Blue properties will store individual byte values that represent each color component. (You could also add a slider and a property for managing the alpha value, which allows you to create a partially transparent color, but this example doesn’t add this detail.) Defining the static fields for your properties is just the first step. You also need a static constructor in your user control that registers them, specifying the property name, the data type, and the control class that owns the property. As you learned in Chapter 4, this is the point where you can opt in to specific property features (such as value inheritance) by passing a FrameworkPropertyMetadata object with the right flags set. It’s also the point where you can attach callbacks for validation, value coercion, and property change notifications. In the color picker, you have just one consideration—you need to attach callbacks that respond when the various properties are changed. That’s because the Red, Green, and Blue properties are really a different representation of the Color property, and if one property changes, you need to make sure the others stay synchronized. Here’s the static constructor code that registers the four dependency properties of the color picker: static ColorPicker() { ColorProperty = DependencyProperty.Register( "Color", typeof(Color), typeof(ColorPicker), new FrameworkPropertyMetadata(Colors.Black, new PropertyChangedCallback(OnColorChanged))); RedProperty = DependencyProperty.Register( "Red", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata( new PropertyChangedCallback(OnColorRGBChanged))); GreenProperty = DependencyProperty.Register( "Green", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata( new PropertyChangedCallback(OnColorRGBChanged))); 510

CHAPTER 18 n CUSTOM ELEMENTS

BlueProperty = DependencyProperty.Register( "Blue", typeof(byte), typeof(ColorPicker), new FrameworkPropertyMetadata( new PropertyChangedCallback(OnColorRGBChanged))); } Now that you have your dependency properties defined, you can add standard property wrappers that make them easier to access and usable in XAML: public Color Color { get { return (Color)GetValue(ColorProperty); } set { SetValue(ColorProperty, value); } } public byte Red { get { return (byte)GetValue(RedProperty); } set { SetValue(RedProperty, value); } } public byte Green { get { return (byte)GetValue(GreenProperty); } set { SetValue(GreenProperty, value); } } public byte Blue { get { return (byte)GetValue(BlueProperty); } set { SetValue(BlueProperty, value); } } Remember, the property wrappers shouldn’t contain any logic, because properties may be set and retrieved directly by using the SetValue() and GetValue() methods of the base DependencyObject class. For example, the property synchronization logic in this example is implemented by using callbacks that fire when the property changes through the property wrapper or a direct SetValue() call. The property change callbacks are responsible for keeping the Color property consistent with the Red, Green, and Blue properties. Whenever the Red, Green, or Blue property is changed, the Color property is adjusted accordingly: private static void OnColorRGBChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ColorPicker colorPicker = (ColorPicker)sender; Color color = colorPicker.Color; if (e.Property == RedProperty) color.R = (byte)e.NewValue; else if (e.Property == GreenProperty) color.G = (byte)e.NewValue; 511

CHAPTER 18 n CUSTOM ELEMENTS

else if (e.Property == BlueProperty) color.B = (byte)e.NewValue; colorPicker.Color = color; } and when the Color property is set, the Red, Green, and Blue values are also updated: private static void OnColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { Color newColor = (Color)e.NewValue; Color oldColor = (Color)e.OldValue; ColorPicker colorPicker = (ColorPicker)sender; colorPicker.Red = newColor.R; colorPicker.Green = newColor.G; colorPicker.Blue = newColor.B; } Despite its appearances, this code won’t cause an infinite series of calls as each property tries to change the other. That’s because WPF doesn’t allow reentrancy in the property change callbacks. For example, if you change the Color property, the OnColorChanged() method will be triggered. The OnColorChanged() method will modify the Red, Green, and Blue properties, triggering the OnColorRGBChanged() callback three times (once for each property). However, the OnColorRBGChanged() will not trigger the OnColorChanged() method again.

n Tip It might occur to you to use the coercion callbacks discussed in Chapter 4 to deal with the color properties. However, this approach isn’t appropriate. Property coercion callbacks are designed for properties that are interrelated and may override or influence one another. They don’t make sense for properties that expose the same data in different ways. If you used property coercion in this example, it would be possible to set different values in the Red, Green, and Blue properties and have that color information override the Color property. The behavior you really want is to set the Red, Green, and Blue properties and use that color information to permanently change the value of the Color property.

Defining Routed Events You might also want to add routed events that can be used to notify the control consumer when something happens. In the color picker example, it’s useful to have an event that fires when the color is changed. Although you could define this event as an ordinary .NET event, using a routed event allows you to provide event bubbling and tunneling, so the event can be handled in a higher-level parent, such as the containing window. As with the dependency properties, the first step to defining a routed event is to create a static property for it, with the word Event added to the end of the event name: public static readonly RoutedEvent ColorChangedEvent;

512

CHAPTER 18 n CUSTOM ELEMENTS

You can then register the event in the static constructor. At this point, you specify the event name, the routing strategy, the signature, and the owning class: ColorChangedEvent = EventManager.RegisterRoutedEvent( "ColorChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(ColorPicker)); Rather than going to the work of creating a new delegate for your event signature, you can sometimes reuse existing delegates. The two useful delegates are RoutedEventHandler (for a routed event that doesn’t pass along any extra information) and RoutedPropertyChangedEventHandler (for a routed event that provides the old and new values after a property has been changed). The RoutedPropertyChangedEventHandler, which is used in the previous example, is a generic delegate that’s parameterized by type. As a result, you can use it with any property data type without sacrificing type safety. After you’ve defined and registered the event, you need to create a standard .NET event wrapper that exposes your event. This event wrapper can be used to attach (and remove) event listeners: public event RoutedPropertyChangedEventHandler ColorChanged { add { AddHandler(ColorChangedEvent, value); } remove { RemoveHandler(ColorChangedEvent, value); } } The final detail is the code that raises the event at the appropriate time. This code must call the RaiseEvent() method that’s inherited from the base DependencyObject class. In the color picker example, you simply need to add these lines of code to the end of the OnColorChanged() method: Color oldColor = (Color)e.OldValue; RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(oldColor, newColor); args.RoutedEvent = ColorPicker.ColorChangedEvent; colorPicker.RaiseEvent(args); Remember, the OnColorChanged() callback is triggered whenever the Color property is modified, either directly or by modifying the Red, Green, and Blue color components.

Adding Markup Now that your user control’s public interface is in place, all you need is the markup that creates the control’s appearance. In this case, a basic Grid is all that’s needed to bring together the three Slider controls and the Rectangle with the color preview. The trick is the data-binding expressions that tie these controls to the appropriate properties, with no event-handling code required. All in all, four data-binding expressions are at work in the color picker. The three sliders are bound to the Red, Green, and Blue properties and are allowed to range from 0 to 255 (the acceptable values for a byte). The Rectangle.Fill property is set using a SolidColorBrush, and the Color property of that brush is bound to the Color property of the user control. Here’s the complete markup:

513

CHAPTER 18 n CUSTOM ELEMENTS















The markup for a user control plays the same role as the control template for a lookless control. If you want to make some of the details in your markup configurable, you can use binding expressions that link them to control properties. For example, currently the Rectangle’s width is hard-coded at 50 units. However, you could replace this detail with a data-binding expression that pulls the value from a dependency property in your user control. That way, the control consumer could modify that property to choose a different width. Similarly, you could make the stroke color and thickness variable. However, if you want to make a control with real flexibility, you’re much better off to create a lookless control and define the markup in a template, as described later in this chapter. Occasionally, you might choose to use binding expressions to repurpose one of the core properties that’s already defined in your control. For example, the UserControl class uses its Padding property to add space between the outer edge and the inner content that you define. (This detail is implemented through the control template for the UserControl.) However, you could also use the Padding property to set the spacing around each slider, as shown here:

Similarly, you could grab the border settings for the Rectangle from the BorderThickness and BorderBrush properties of the UserControl. Once again, this is a shortcut that may make perfect sense for creating simple controls but can be improved by introducing additional properties (for example, SliderMargin, PreviewBorderBrush, and PreviewBorderThickness) or creating a full-fledged templatebased control. 514

CHAPTER 18 n CUSTOM ELEMENTS

nAMIng A uSeR ConTRol In the example shown here, the top-level UserControl is assigned a name (colorPicker). This allows you to write straightforward data-binding expressions that bind to properties in the custom user control class. However, this technique raises an obvious question. Namely, what happens when you create an instance of the user control in a window (or page) and assign a new name to it? Fortunately, this situation works without a hitch, because the user control performs its initialization before that of the containing window. First, the user control is initialized, and its data bindings are connected. Next, the window is initialized, and the name that’s set in the window markup is applied to the user control. The data-binding expressions and event handlers in the window can now use the window-defined name to access the user control, and everything works the way you’d expect. Although this sounds straightforward, you might notice a couple of quirks if you use code that examines the UserControl.Name property directly. For example, if you examine the Name property in an event handler in the user control, you’ll see the new name that was applied by the window. Similarly, if you don’t set a name in the window markup, the user control will retain the original name from the user control markup. You’ll then see this name if you examine the Name property in the window code. Neither of these quirks represents a problem, but a better approach would be to avoid naming the user control in the user control markup and use the Binding.RelativeSource property to search up the element tree until you find the UserControl parent. Here’s the lengthier syntax that does this:

You’ll see this approach later, when you build a template-based control in the section “Refactoring the Color Picker Markup.”

Using the Control Now that you’ve completed the control, using it is easy. To use the color picker in another window, you need to begin by mapping the assembly and .NET namespace to an XML namespace, as shown here:

Using the XML namespace you’ve defined and the user control class name, you can create your user control exactly as you create any other type of object in XAML markup. You can also set its properties and attach event handlers directly in the control tag, as shown here:

515

CHAPTER 18 n CUSTOM ELEMENTS

Because the Color property uses the Color data type and the Color data type is decorated with a TypeConverter attribute, WPF knows to use the ColorConverter to change the string color name into the corresponding Color object before setting the Color property. The code that handles the ColorChanged event is straightforward: private void colorPicker_ColorChanged(object sender, RoutedPropertyChangedEventArgs e) { lblColor.Text = "The new color is " + e.NewValue.ToString(); } This completes your custom control. However, there’s still one frill worth adding. In the next section, you’ll enhance the color picker with support for WPF’s command feature.

Supporting Commands Many controls have baked-in command support. You can add this to your controls in two ways: • •

Add command bindings that link your control to specific commands. That way, your control can respond to a command without the help of any external code. Create a new RoutedUICommand object for your command as a static field in your control. Then add a command binding for that command. This allows your control to automatically support commands that aren’t already defined in the basic set of command classes that you learned about in Chapter 9.

In the following example, you’ll use the first approach to add support for the ApplicationCommands. Undo command.

n Tip For more information about commands and how to create custom RoutedUICommand objects, refer to Chapter 9. To support an Undo feature in the color picker, you need to track the previous color in a member field: private Color? previousColor; It makes sense to make this field nullable, because when the control is first created, there shouldn’t be a previous color set. (You can also clear the previous color programmatically after an action that you want to make irreversible.) When the color is changed, you simply need to record the old value. You can take care of this task by adding this line to the end of the OnColorChanged() method: colorPicker.previousColor = (Color)e.OldValue; Now you have the infrastructure in place that you need to support the Undo command. All that’s left is to create the command binding that connects your control to the command and handle the CanExecute and Executed events. The best place to create command bindings is when the control is first created. For example, the following code uses the color picker’s constructor to add a command binding to the ApplicationCommands.Undo command:

516

CHAPTER 18 n CUSTOM ELEMENTS

public ColorPicker() { InitializeComponent(); SetUpCommands(); } private void SetUpCommands() { // Set up command bindings. CommandBinding binding = new CommandBinding(ApplicationCommands.Undo, UndoCommand_Executed, UndoCommand_CanExecute); this.CommandBindings.Add(binding); } To make your command functional, you need to handle the CanExecute event and allow the command as long as there is a previous value: private void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = previousColor.HasValue; } Finally, when the command is executed, you can swap in the new color. private void UndoCommand_Executed(object sender, ExecutedRoutedEventArgs e) { this.Color = (Color)previousColor; } You can trigger the Undo command in two ways. You can use the default Ctrl+Z key binding when an element in the user control has focus, or you can add a button to the client that triggers the command, like this one:

Undo

Either way, the current color is abandoned and the previous color is applied.

n Tip The current example stores just one level of undo information. However, it’s easy to create an undo stack that stores a series of values. You just need to store Color values in the appropriate type of collection. The Stack collection in the System.Collections.Generic namespaces is a good choice, because it implements a last-in first-out approach that makes it easy to grab the most recent Color object when performing an undo operation.

More Robust Commands The technique described earlier is a perfectly legitimate way to connect commands to controls, but it’s not the technique that’s used in WPF elements and professional controls. These elements use a more robust

517

CHAPTER 18 n CUSTOM ELEMENTS

approach and attach static command handlers by using the CommandManager. RegisterClassCommandBinding() method. The problem with the implementation shown in the previous example is that it uses the public CommandBindings collection. This makes it a bit fragile, because the client can modify the CommandBindings collection freely. This isn’t possible if you use the RegisterClassCommandBinding() method. This is the approach that WPF controls use. For example, if you look at the CommandBindings collection of a TextBox, you won’t find any of the bindings for hardwired commands such as Undo, Redo, Cut, Copy, and Paste, because these are registered as class bindings. The technique is fairly straightforward. Instead of creating the command binding in the instance constructor, you must create the command binding in the static constructor, using code like this: CommandManager.RegisterClassCommandBinding(typeof(ColorPicker), new CommandBinding(ApplicationCommands.Undo, UndoCommand_Executed, UndoCommand_CanExecute)); Although this code hasn’t changed much, there’s an important shift. Because the UndoCommand_ Executed() and UndoCommand_CanExecute() methods are referred to in the constructor, they must both be static methods. To retrieve instance data (such as the current color and the previous color information), you need to cast the event sender to a ColorPicker object and use it. Here’s the revised command-handling code: private static void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { ColorPicker colorPicker = (ColorPicker)sender; e.CanExecute = colorPicker.previousColor.HasValue; } private static void UndoCommand_Executed(object sender, ExecutedRoutedEventArgs e) { ColorPicker colorPicker = (ColorPicker)sender; Color currentColor = colorPicker.Color; colorPicker.Color = (Color)colorPicker.previousColor; } Incidentally, this technique isn’t limited to commands. If you want to hardwire event-handling logic into your control, you can use a class event handler with the EventManager.RegisterClassHandler() method. Class event handlers are always invoked before instance event handlers, allowing you to easily suppress events.

Taking a Closer Look at User Controls User controls provide a fairly painless but somewhat limited way to create a custom control. To understand why, it helps to take a closer look at how user controls work. Behind the scenes, the UserControl class works a lot like the ContentControl class from which it derives. In fact, it has just a few key differences: •

518

The UserControl class changes some default values. Namely, it sets IsTabStop and Focusable to false (so it doesn’t occupy a separate place in the tab order), and it sets HorizontalAlignment and VerticalAlignment to Stretch (rather than Left and Top) so it fills the available space.

CHAPTER 18 n CUSTOM ELEMENTS

• •

The UserControl class applies a new control template that consists of a Border element that wraps a ContentPresenter. The ContentPresenter holds the content you add by using markup. The UserControl class changes the source of routed events. When events bubble or tunnel from controls inside the user control to elements outside the user control, the source changes to point to the user control rather than the original element. This gives you a bit more encapsulation. (For example, if you handle the UIElement. MouseLeftButtonDown event in the layout container that holds the color picker, you’ll receive an event when you click the Rectangle inside. However, the source of this event won’t be the Rectangle element but the ColorPicker object that contains the Rectangle. If you create the same color picker as an ordinary content control, this isn’t the case—it’s up to you to intercept the event in your control, handle it, and reraise it.)

The most significant difference between user controls and other types of custom controls is the way that a user control is designed. Like all controls, user controls have a control template. However, you’ll rarely change this template—instead, you’ll supply the markup as part of your custom user control class, and this markup is processed using the InitializeComponent() method when the control is created. On the other hand, a lookless control has no markup—everything it needs is in the template. An ordinary ContentControl has the following stripped-down template:

This template does little more than fill in the supplied content and apply the optional content template. Properties such as Padding, Background, HorizontalAlignment, and VerticalAlignment won’t have any effect unless you explicitly bind to it. The UserControl has a similar template with a few more niceties. Most obviously, it adds a Border element and binds its properties to the BorderBrush, BorderThickness, Background, and Padding properties of the user control to make sure they have some meaning. Additionally, the ContentPresenter inside binds to the alignment properties.



519

CHAPTER 18 n CUSTOM ELEMENTS

Technically, you could change the template of a user control. In fact, you could move all your markup into the template, with only slight readjusting. But there’s really no reason to take this step—if you want a more flexible control that separates the visual look from the interface that’s defined by your control class, you’d be much better off creating a custom lookless control, as described in the next section.

Creating a Lookless Control The goal of user controls is to provide a design surface that supplements the control template, giving you a quicker way to define the control at the price of future flexibility. This causes a problem if you’re happy with the functionality of a user control, but you need to tailor its visual appearance. For example, imagine you want to use the same color picker but give it a different “skin” that blends better into an existing application window. You may be able to change some aspects of the user control through styles, but parts of it are locked away inside, hard-coded into the markup. For example, there’s no way to move the preview rectangle to the left side of the sliders. The solution is to create a lookless control—a control that derives from one of the control base classes but doesn’t have a design surface. Instead, this control places its markup into a default template that can be replaced at will without disturbing the control logic.

Refactoring the Color Picker Code Changing the color picker into a lookless control isn’t too difficult. The first step is easy—you simply need to change the class declaration, as shown here: public class ColorPicker : System.Windows.Controls.Control { ... } In this example, the ColorPicker class derives from Control. FrameworkElement isn’t suitable, because the color picker does allow user interaction and the other higher-level classes don’t accurately describe the color picker’s behavior. For example, the color picker doesn’t allow you to nest other content inside, so the ContentControl class isn’t appropriate. The code inside the ColorPicker class is the same as the code for the user control (aside from the fact that you must remove the call to InitializeComponent() in the constructor). You follow the same approach to define dependency properties and routed events. The only difference is that you need to tell WPF that you will be providing a new style for your control class. This style will provide the new control template. (If you don’t take this step, you’ll continue whatever template is defined in the base class.) To tell WPF that you’re providing a new style, you need to call the OverrideMetadata() method in the static constructor of your class. You call this method on the DefaultStyleKeyProperty, which is a dependency property that defines the default style for your control. The code you need is as follows: DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker), new FrameworkPropertyMetadata(typeof(ColorPicker))); You could supply a different type if you want to use the template of another control class, but you’ll almost always create a specific style for each one of your custom controls.

Refactoring the Color Picker Markup After you’ve added the call to OverrideMetadata, you simply need to plug in the right style. This style needs to be placed in a resource dictionary named generic.xaml, which must be placed in a Themes

520

CHAPTER 18 n CUSTOM ELEMENTS

subfolder in your project. That way, your style will be recognized as the default style for your control. Here’s how to add the generic.xaml file: 1.

Right-click the class library project in the Solution Explorer, and choose Add ä New Folder.

2.

Name the new folder Themes.

3.

Right-click the Themes folder, and choose Add ä New Item.

4.

In the Add New Item dialog box, pick the XML file template, enter the name generic.xaml, and click Add.

Figure 18-3 shows the generic.xaml file in the Themes folder.

Figure 18-3. A WPF application and class library

TheMe-SPeCIFIC STyleS AnD geneRIC.XAMl As you’ve seen, the ColorPicker gets its default control template from a file named generic.xaml, which is placed in a project folder named Themes. This slightly strange convention is actually part of a legacy WPF feature: Windows theme support. The original goal of Windows theme support was to let developers create customized versions of their controls to match different Windows themes. This goal made the most sense on old Windows XP computers, 521

CHAPTER 18 n CUSTOM ELEMENTS

which used themes to control the overall color scheme of Windows applications. When Windows Vista was released, it introduced the Aero theme, which effectively replaced the old theme choices. The versions of Windows that followed haven’t changed that state of affairs, and so the Windows theming feature in WPF (which was never much used) is now universally ignored. The bottom line this: WPF developers creating applications today always use a generic.xaml file to set their default control styles. The name of the generic.xaml file (and the Themes folder in which it’s placed) is a holdover from the past. Often, a custom control library has several controls. To keep their styles separate for easier editing, the generic.xaml file often uses resource dictionary merging. The following markup shows a generic.xaml file that pulls in the resources from the ColorPicker.xaml resource dictionary in the same Themes subfolder of a control library named CustomControls:



Your custom control style must use the TargetType attribute to attach itself to the color picker automatically. Here’s the basic structure of the markup that appears in the ColorPicker.xaml file:

You can use your style to set any properties in the control class (whether they’re inherited from the base class or new properties you’ve added). However, the most useful task that your style performs is to apply a new template that defines the default visual appearance of your control. It’s fairly easy to convert ordinary markup (such as that used by the color picker) into a control template. Keep these considerations in mind: •



522

When creating binding expressions that link to properties in the parent control class, you can’t use the ElementName property. Instead, you need to use the RelativeSource property to indicate that you want to bind to the parent control. If one-way data binding is all that you need, you can usually use the lightweight TemplateBinding markup extension instead of the full-fledged Binding. You can’t attach event handlers in the control template. Instead, you’ll need to give your elements recognizable names and attach event handlers to them programmatically in the control constructor.

CHAPTER 18 n CUSTOM ELEMENTS



Don’t name an element in a control template unless you want to attach an event handler or interact with it programmatically. When naming an element you want to use, give it a name in the form PART_ElementName.

With these considerations in mind, you can create the following template for the color picker. The most important changed details are highlighted in bold.

As you’ll notice, some binding expressions have been replaced with the TemplateBinding extension. Others still use the Binding extension but have the RelativeSource set to point to the template parent (the custom control). Although both TemplateBinding and Binding with a RelativeSource of TemplatedParent are for the same purpose—extracting data from the properties of your custom control—the lighter-weight TemplateBinding is always appropriate. It won’t work if you need two-way binding (as with the sliders) or when binding to the property of a class that derives from Freezable (such as the SolidColorBrush).

Streamlining the Control Template As it stands, the color picker control template fills in everything you need, and you can use it in the same way that you use the color picker user control. However, it’s still possible to simplify the template by removing some of the details. Currently, any control consumer that wants to supply a custom template will be forced to add a slew of data-binding expressions to ensure that the control continues to work. This isn’t difficult, but it is tedious. Another option is to configure all the binding expressions in the initialization code of the control itself. This way, the template doesn’t need to specify these details.

n Note This is the same technique you use when attaching event handlers to the elements that make up a custom control. You attach each event handler programmatically, rather than use event attributes in the template.

Adding Part Names For this system to work, your code needs to be able to find the elements it needs. WPF controls locate the elements they need by name. As a result, your element names become part of the public interface of your control and need suitably descriptive names. By convention, these names begin with the text PART_ followed by the element name. The element name uses initial caps, just like a property name. PART_ RedSlider is a good choice for a required element name, while PART_sldRed, PART_redSlider, and RedSlider are all poor choices. For example, here’s how you would prepare the three sliders for programmatic binding, by removing the binding expression from the Value property and adding a PART_ name:

Notice that the Margin property still uses a binding expression to add padding, but this is an optional detail that can easily be left out of a custom template (which may choose to hard-code the padding or use a different layout). To ensure maximum flexibility, the Rectangle isn’t given a name. Instead, the SolidColorBrush inside is given a name. That way, the color preview feature can be used with any shape or an arbitrary element, depending on the template.

524

CHAPTER 18 n CUSTOM ELEMENTS





Manipulating Template Parts You could connect your binding expressions when the control is initialized, but there’s a better approach. WPF has a dedicated OnApplyTemplate() method that you should override if you need to search for elements in the template and attach event handlers or add data-binding expressions. In that method, you can use the GetTemplateChild() method (which is inherited from FrameworkElement) to find the elements you need. If you don’t find an element that you want to work with, the recommended pattern is to do nothing. Optionally, you can add code that checks that the element, if present, is the correct type and raises an exception if it isn’t. (The thinking here is that a missing element represents a conscious opting out of a specific feature, whereas an incorrect element type represents a mistake.) Here’s how you can connect the data-binding expression for a single slider in the OnApplyTemplate() method: public override void OnApplyTemplate() { base.OnApplyTemplate(); RangeBase slider = GetTemplateChild("PART_RedSlider") as RangeBase; if (slider != null) { // Bind to the Red property in the control, using a two-way binding. Binding binding = new Binding("Red"); binding.Source = this; binding.Mode = BindingMode.TwoWay; slider.SetBinding(RangeBase.ValueProperty, binding); } ... } Notice that the code uses the System.Windows.Controls.Primitives.RangeBase class (from which Slider derives) instead of the Slider class. That’s because the RangeBase class provides the minimum required functionality—in this case, the Value property. By making the code as generic as possible, the control consumer gains more freedom. For example, it’s now possible to supply a custom template that uses a different RangeBase-derived control in place of the color sliders. The code for binding the other two sliders is virtually identical. The code for binding the SolidColorBrush is slightly different, because the SolidColorBrush does not include the SetBinding() method (which is defined in the FrameworkElement class). One easy workaround is to create a binding expression for the ColorPicker.Color property, which uses the one-way-to-source direction. That way, when the color picker’s color is changed, the brush is updated automatically.

525

CHAPTER 18 n CUSTOM ELEMENTS

SolidColorBrush brush = GetTemplateChild("PART_PreviewBrush") as SolidColorBrush; if (brush != null) { Binding binding = new Binding("Color"); binding.Source = brush; binding.Mode = BindingMode.OneWayToSource; this.SetBinding(ColorPicker.ColorProperty, binding); } To see the benefit of this change in design, you need to create a control that uses the color picker but supplies a new control template. Figure 18-4 shows one possibility.

Figure 18-4. A color picker custom control with two different templates

Documenting Template Parts There’s one last refinement that you should make to the previous example. Good design guidelines suggest that you add the TemplatePart attribute to your control declaration to document what part names you use in your template and what type of control you use for each part. Technically, this step isn’t required, but it’s a piece of documentation that can help others who are using your class (and it can also be inspected by design tools that let you build customized control templates, such as Expression Blend). Here are the TemplatePart attributes you should add to the ColorPicker control class: [TemplatePart(Name="PART_RedSlider", Type=typeof(RangeBase))] [TemplatePart(Name = "PART_BlueSlider", Type=typeof(RangeBase))] [TemplatePart(Name="PART_GreenSlider", Type=typeof(RangeBase))] public class ColorPicker : System.Windows.Controls.Control { ... } 526

CHAPTER 18 n CUSTOM ELEMENTS

FInDIng A ConTRol’S DeFAulT STyle Every control has a default style. You call DefaultStyleKeyProperty.OverrideMetadata() in the static constructor of your control class to indicate what default style your custom control should use. If you don’t, your control will simply use the default style that’s defined for the control that your class derives from. Contrary to what you might expect, the default theme style is not exposed through the Style property. All the controls in the WPF library return a null reference for their Style property. Instead, the Style property is reserved for an application style (the type you learned to build in Chapter 11). If you set an application style, it’s merged into the default theme style. If you set an application style that conflicts with the default style, the application style wins and overrides the property setter or trigger in the default style. However, the details you don’t override remain. This is the behavior you want. It allows you to create an application style that changes just a few properties (for example, the text font in a button), without removing the other essential details that are supplied in the default theme style (such as the control template). Incidentally, you can retrieve the default style programmatically. To do so, you can use the FindResource() method to search up the resource hierarchy for a style that has the right element-type key. For example, if you want to find the default style that’s applied to the Button class, you can use this code statement: Style style = Application.Current.FindResource(typeof(Button));

Supporting Visual States The ColorPicker control is a good example of control design. Because its behavior and its visual appearance are carefully separated, other designers can develop new templates that change its appearance dramatically. One of the reasons the ColorPicker is so simple is that it doesn’t have a concept of states. In other words, it doesn’t distinguish its visual appearance based on whether it has focus, whether the mouse is on top, whether it’s disabled, and so on. The FlipPanel control in the following example is a bit different. The basic idea behind the FlipPanel is that it provides two surfaces to host content, but only one is visible at a time. To see the other content, you “flip” between the sides. You can customize the flipping effect through the control template, but the default effect uses a simple fade that transitions between the front and back (see Figure 18-5). Depending on your application, you could use the FlipPanel to combine a data-entry form with some helpful documentation, to provide a simple or a more complex view on the same data, or to fuse together a question and an answer in a trivia game.

527

CHAPTER 18 n CUSTOM ELEMENTS

Figure 18-5. Flipping the FlipPanel You can perform the flipping programmatically (by setting a property named IsFlipped), or the user can flip the panel by using a convenient button (unless the control consumer removes it from the template). Clearly, the control template needs to specify two separate sections: the front and back content regions of the FlipPanel. However, there’s an additional detail—namely, the FlipPanel needs a way to switch between its two states: flipped and not flipped. You could do the job by adding triggers to your template. One trigger would hide the front panel and show the second panel when a button is clicked, while the other would reverse these changes. Both could use any animations you like. But by using visual states, you clearly indicate to the control consumer that these two states are a required part of the template. Rather than writing triggers for the right property or event, the control consumer simply needs to fill in the appropriate state animations—a task that gets even easier with Expression Blend.

Starting the FlipPanel Class Stripped down to its bare bones, the FlipPanel is surprisingly simple. It consists of two content regions that the user can fill with a single element (most likely, a layout container that contains an assortment of elements). Technically, that means the FlipPanel isn’t a true panel, because it doesn’t use layout logic to organize a group of child elements. However, this isn’t likely to pose a problem, because the structure of the FlipPanel is clear and intuitive. The FlipPanel also includes a flip button that lets the user switch between the two content regions. Although you can create a custom control by deriving from a control class such as ContentControl or Panel, the FlipPanel derives directly from the base Control class. If you don’t need the functionality of a specialized control class, this is the best starting point. You shouldn’t derive from the simpler FrameworkElement class unless you want to create an element without the standard control and template infrastructure: public class FlipPanel : Control {...} The first order of business is to create the properties for the FlipPanel. As with almost all the properties in a WPF element, you should use dependency properties. Here’s how FlipPanel defines the FrontContent property that holds the element that’s displayed on the front surface: public static readonly DependencyProperty FrontContentProperty = DependencyProperty.Register("FrontContent", typeof(object), typeof(FlipPanel), null); 528

CHAPTER 18 n CUSTOM ELEMENTS

Next, you need to add a traditional .NET property procedure that calls the base GetValue() and SetValue() methods to change the dependency property. Here’s the property procedure implementation for the FrontContent property: public object FrontContent { get { return base.GetValue(FrontContentProperty); } set { base.SetValue(FrontContentProperty, value); } } The BackContent property is virtually identical: public static readonly DependencyProperty BackContentProperty = DependencyProperty.Register("BackContent", typeof(object), typeof(FlipPanel), null); public object BackContent { get { return base.GetValue(BackContentProperty); } set { base.SetValue(BackContentProperty, value); } } You need to add just one more essential property: IsFlipped. This Boolean property keeps track of the current state of the FlipPanel (forward-facing or backward-facing) and lets the control consumer flip it programmatically: public static readonly DependencyProperty IsFlippedProperty = DependencyProperty.Register("IsFlipped", typeof(bool), typeof(FlipPanel), null); public bool IsFlipped { get { return (bool)base.GetValue(IsFlippedProperty); } set { base.SetValue(IsFlippedProperty, value); ChangeVisualState(true); } } 529

CHAPTER 18 n CUSTOM ELEMENTS

The IsFlipped property setter calls a custom method called ChangeVisualState(). This method makes sure the display is updated to match the current flip state (forward-facing or backward-facing). You’ll consider the code that takes care of this task a bit later. The FlipPanel doesn’t need many more properties, because it inherits virtually everything it needs from the Control class. One exception is the CornerRadius property. Although the Control class includes BorderBrush and BorderThickness properties, which you can use to draw a border around the FlipPanel, it lacks the CornerRadius property for rounding square edges into a gentler curve, as the Border element does. Implementing the same effect in the FlipPanel is easy, provided you add the CornerRadius dependency property and use it to configure a Border element in the FlipPanel’s default control template: public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(FlipPanel), null); public CornerRadius CornerRadius { get { return (CornerRadius)GetValue(CornerRadiusProperty); } set { SetValue(CornerRadiusProperty, value); } } You also need to add a style that applies the default template for the FlipPanel. You place this style in the generic.xaml resource dictionary, as you did when developing the ColorPicker. Here’s the basic skeleton you need:

There’s one last detail. To tell your control to pick up the default style from the generic.xaml file, you need to call the DefaultStyleKeyProperty.OverrideMetadata() method in the FlipPanel’s static constructor: DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipPanel), new FrameworkPropertyMetadata(typeof(FlipPanel)));

Choosing Parts and States Now that you have the basic structure in place, you’re ready to identify the parts and states that you’ll use in the control template. Clearly, the FlipPanel requires two states: Normal: This storyboard ensures that only the front content is visible. The back content is flipped, faded, or otherwise shuffled out of view. Flipped: This storyboard ensures that only the back content is visible. The front content is animated out of the way.

530

CHAPTER 18 n CUSTOM ELEMENTS

In addition, you need two parts: FlipButton: This is the button that, when clicked, changes the view from the front to the back (or vice versa). The FlipPanel provides this service by handling this button’s events. FlipButtonAlternate: This is an optional element that works in the same way as the FlipButton. Its inclusion allows the control consumer to use two approaches in a custom control template. One option is to use a single flip button outside the flippable content region. The other option is to place a separate flip button on both sides of the panel, in the flippable region.

n Note Keen eyes will notice a confusing design choice here. Unlike the custom ColorPicker, the named parts in the FlipPanel don’t use the PART_ naming syntax (as in PART_FlipButton). That’s because the PART_ naming system was introduced before the visual state model. With the visual state model, the conventions have changed to favor simpler names, although this is still an emerging standard, and it could change in the future. In the meantime, your custom controls should be fine as long as they use the TemplatePart attribute to point out all the named parts. You could also add parts for the front content and back content regions. However, the FlipPanel control doesn’t need to manipulate these regions directly, as long as the template includes an animation that hides or shows them at the appropriate time. (Another option is to define these parts so you can explicitly change their visibility in code. That way, the panel can still change between the front and back content region even if no animations are defined, by hiding one section and showing the other. For simplicity’s sake, the FlipPanel doesn’t go to these lengths.) To advertise the fact that the FlipPanel uses these parts and states, you should apply the TemplatePart attribute to your control class, as shown here: [TemplateVisualState(Name = "Normal", GroupName="ViewStates")] [TemplateVisualState(Name = "Flipped", GroupName = "ViewStates")] [TemplatePart(Name = "FlipButton", Type = typeof(ToggleButton))] [TemplatePart(Name = "FlipButtonAlternate", Type = typeof(ToggleButton))] public class FlipPanel : Control { ... } The FlipButton and FlipButtonAlternate parts are restricted—each one can be only a ToggleButton or an instance of a ToggleButton-derived class. (As you may remember from Chapter 6, the ToggleButton is a clickable button that can be in one of two states. In the case of the FlipPanel control, the ToggleButton states correspond to normal front-forward view or a flipped back-forward view.)

n Tip To ensure the best, most flexible template support, use the least-specialized element type that you can. For example, it’s better to use FrameworkElement than ContentControl, unless you need some property or behavior that ContentControl provides.

531

CHAPTER 18 n CUSTOM ELEMENTS

The Default Control Template Now, you can slot these pieces into the default control template. The root element is a two-row Grid that holds the content area (in the top row) and the flip button (in the bottom row). The content area is filled with two overlapping Border elements, representing the front and back content, but only one of the two is ever shown at a time. To fill in the front and back content regions, the FlipPanel uses the ContentPresenter. This technique is virtually the same as in the custom button example, except you need two ContentPresenter elements, one for each side of the FlipPanel. The FlipPanel also includes a separate Border element wrapping each ContentPresenter. This lets the control consumer outline the flippable content region by setting a few straightforward properties on the FlipPanel (BorderBrush, BorderThickness, Background, and CornerRadius), rather than being forced to add a border by hand. Here’s the basic skeleton for the default control template:









725

CHAPTER 23 n WINDOWS









The top-level element in this template is a Border object for the window frame. Inside that is a Grid with three rows. The contents of the Grid break down as follows: • •



726

The top row holds the title bar, which consists of an ordinary TextBlock that displays the window title and a close button. A template binding pulls the window title from the Window.Title property. The middle row holds a nested Border with the rest of the window content. The content is inserted using a ContentPresenter. The ContentPresenter is wrapped in the AdornerDecorator, which ensures that the adorner layer is placed over your element content. The third row holds another ContentPresenter. However, this content presenter doesn’t use the standard binding to get its content from the Window.Content

CHAPTER 23 n WINDOWS

property. Instead, it explicitly pulls its content from the Window.Tag property. Usually, this content is just ordinary text, but it could include any element content you want to use.

n Note The Tag property is used because the Window class doesn’t include any property that’s designed to hold footer text. Another option is to create a custom class that derives from Window and adds a Footer property. • •

Also in the third row is a resize grip. A trigger shows the resize grip when the Window.ResizeMode property is set to CanResizeWithGrip. Finally, two invisible rectangles run along the right and bottom edge of the Grid (and thus the window). They allow the user to click and drag to resize the window.

Two details that aren’t shown here are the relatively uninteresting style for the resize grip (which simply creates a small pattern of dots to use as the resize grip) and the close button (which draws a small X on a red square). This markup also doesn’t include the formatting details, such as the gradient brush that paints the background and the properties that create a nicely rounded border edge. To see the full markup, refer to the sample code provided for this chapter. The window template is applied using a simple style. This style also sets three key properties of the Window class that make it transparent. This allows you to create the window border and background using WPF elements.

At this point, you’re ready to use your custom window. For example, you could create a window like this that sets the style and fills in some basic content:

This is a test. OK

There’s just one problem. Currently, the window lacks most of the basic behavior windows require. For example, you can’t drag the window around the desktop, resize it, or use the close button. To perform these actions, you need code. There are two possible ways to add the code you need: you could expand your example into a custom Window-derived class, or you could create a code-behind class for your resource dictionary. The custom 727

CHAPTER 23 n WINDOWS

control approach provides better encapsulation and allows you to extend the public interface of your window (for example, adding useful methods and properties that you can use in your application). The code-behind approach is a relatively lightweight alternative that allows you to extend the capabilities of a control template while letting your application continue to use the base control classes. It’s the approach that you’ll see in this example. You’ve already learned how to create a code-behind class for your resource dictionary (see the “UserSelected Skins” section in Chapter 17). Once you’ve created the code file, it’s easy to add the event handling code you need. The only challenge is that your code runs in the resource dictionary object, not inside your window object. That means you can’t use the this keyword to access the current window. Fortunately, there’s an easy alternative: the FrameworkElement.TemplatedParent property. For example, to make the window draggable, you need to intercept a mouse event on the title bar and initiate dragging. Here’s the revised TextBlock that wires up an event handler when the user clicks with the mouse:

Now you can add the following event handler to the code-behind class for the resource dictionary: private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Window win = (Window) ((FrameworkElement)sender).TemplatedParent; win.DragMove(); } You can add the event handling code for the close button and resize rectangles in the same way. To see the finished resource dictionary markup and code, along with a template that you apply to any window, refer to the download examples for this chapter. Of course, there’s still a lot of polish needed before this window is attractive enough to suit a modern application. But it demonstrates the sequence of steps you need to follow to build a complex control template, with code, and it achieves a result that would have required custom control development in previous user interface frameworks.

Programming the Windows Taskbar Both Windows 7 and Windows 8 (in desktop mode) use a revamped taskbar that adds several enhanced features—features that aren’t available in Windows Vista. WPF does a good job of supporting these features without forcing developers to add unmanaged API calls or rely on separate assemblies. First, WPF provides basic support for jump lists (the lists that appear when you right-click a taskbar button). Second, WPF lets you to change the taskbar preview image and the taskbar icon that’s used for your application. In the following sections, you’ll see how to use these features.

n Note You can safely use the Windows taskbar features even in an application that targets Windows Vista. Any markup or code you use to interact with the enhanced Windows 7 and Windows 8 taskbar is harmlessly ignored on Windows Vista. (The same is true if you’re targeting .NET 4, and you build an application that can run on Windows XP computers. WPF simply ignores the taskbar features that don’t apply.)

728

CHAPTER 23 n WINDOWS

Using Jump Lists Jump lists are the handy mini-menus that pop up when you right-click a taskbar button. They appear for both currently running applications and applications that aren’t currently running but have their buttons pinned to the taskbar. Typically, a jump list provides a quick way to open a document that belongs to the appropriate application—for example, to open a recent document in Word or a frequently played song in Windows Media Player. However, some programs use them more creatively to perform applicationspecific tasks.

Recent Document Support The taskbar in Windows 7 and Windows 8 adds a jump list to every document-based application, provided that application is registered to handle a specific file type. For example, in Chapter 7, you learned how to build a single-instance application that was registered to handle .testDoc files (called SingleInstanceApplication). When you run this program and right-click its taskbar button, you’ll see a list of recently opened documents, as shown in Figure 23-8.

Figure 23-8. An automatically generated jump list If you click one of the recent documents in the jump list, Windows launches another instance of your application and passes the full document path to it as a command-line argument. Of course, you can code around this behavior if it isn’t what want. For example, consider the single-instance application from Chapter 7. If you open a document from its jump list, the new instance quietly passes the file path to the currently running application, and then shuts itself down. The end result is that the same application gets to handle all the documents, whether they’re opened from inside the application or from the jump list. As noted, to get the recent document support, your application must be registered to handle the corresponding file type. There are two easy ways to accomplish this. First, you can add the relevant details to the Windows registry using code, as detailed in Chapter 7. Second, you can do it by hand with Windows Explorer. Here’s a quick review of what you need to do: 1.

Right-click the appropriate file (for example, one with the extension .testDoc).

2.

Choose Open With  Choose Default Program to show the Open With dialog box.

3.

Click the Browse button, find your WPF application’s .exe file, and select it.

729

CHAPTER 23 n WINDOWS

4.

Optionally, clear the “Always use selected program to open this kind of file” option. Your application doesn’t need to be the default application to get jump list support.

5.

Click OK.

When registering a file type, you need to keep a few guidelines in mind: •





When creating a file-type registration, you give Windows the exact path to your executable. So do this after you place your application somewhere sensible, or you’ll need to reregister it every time you move your application file. Don’t worry about taking over common file types. As long as you don’t make your application into the default handler for that file type, you won’t change the way Windows works. For example, it’s perfectly acceptable to register your application to handle .txt files. That way, when the user opens a .txt file with your application, it appears in your application’s recent document list. Similarly, if the user chooses a document from your application’s jump list, Windows launches your application. However, if the user double-clicks a .txt file in Windows Explorer, Windows still launches the default application for .txt files (typically Notepad). To test jump lists in Visual Studio, you must switch off running the Visual Studio hosting process. If you’re running it, Windows will check the file-type registrations for the hosting process (say, YourApp.vshost.exe) instead of your application (YourApp.exe). To avoid this problem, run your compiled application directly from Windows Explorer, or choose Debug ฀ Start Without Debugging. Either way, you won’t get debugging support while you’re testing the jump list.

n Tip If you want to stop using the Visual Studio hosting process for a longer period of time, you can change your project configuration. Double-click the Properties node in the Solution Explorer. Then choose the Debug tab and clear the check box next to the “Enable the Visual Studio hosting process” setting. Not only does Windows give your applications the recent document list for free, but it also supports pinning, which allows users to attach their most important documents to the jump list and keep them there permanently. As with the jump list for any other application, the user can pin a document by clicking the tiny thumbnail icon. Windows will then move the chosen file to a separate list category, which is called Pinned. Similarly, the user can remove an item from the recent documents list by right-clicking it and choosing Remove.

Custom Jump Lists The jump list support you’ve seen so far is built in to Windows, and doesn’t require any WPF logic. However, WPF adds on to this support by allowing you to take control of the jump list and fill it with custom items. To do so, you simply add some markup that defines a section in your App. xaml file, as shown here:

730

CHAPTER 23 n WINDOWS





When you define a custom jump list in this way, Windows stops showing the recent document list. To get it back, you need to explicitly opt in with the JumpList.ShowRecentCategory property:

You can also add the ShowFrequentCategory property to show a list of the most frequently opened documents that your application is registered to handle. In addition, you can create your own jump list items and place them in a custom category of your choosing. To do so, you must add JumpPath or JumpTask objects to your JumpList. Here’s an example of the JumpPath, which represents a document:

When creating a JumpPath, you can supply two details. The CustomCategory property sets the heading that’s shown before the item in the jump list. (If you add several items with the same category name, they will be grouped together.) If you don’t supply a category, Windows uses the category name Tasks. The Path property is a file path that points to a document. Your path must use a fully qualified file name, the file must exist, and it must be a file type that your application is registered to handle. If you break any of these rules, the item won’t appear in the jump list. Clicking a JumpPath item is exactly the same as clicking one of the files in the recent documents section. When you do, Windows launches a new instance of the application and passes the document path as a command-line argument. The JumpTask object serves a slightly different purpose. While each JumpPath maps to a document, each JumpTask maps to an application. Here’s an example that creates a JumpTask for Windows Notepad:

...

Although a JumpPath requires just two details, a JumpTask uses several more properties. Table 23-2 lists them all.

731

CHAPTER 23 n WINDOWS

Table 23-2. Properties of the JumpTask Class

Name

Description

Title

The text that appears in the jump list.

Description

The tooltip text that appears when you hover over the item.

ApplicationPath

The executable file for the application. As with the document path in a JumpList, the ApplicationPath property requires a fully qualified path.

IconResourcePath

Points to the file that has the thumbnail icon that Windows will show next to that item in the jump list. Oddly enough, Windows won’t choose a default icon or pull it out of the application executable. If you want to see a valid icon, you must set the IconResourcePath.

IconResourceIndex

If the application or icon resource identified by IconResourcePath has multiple icons, you also need to use IconResourceIndex to pick the one you want.

WorkingDirectory

The working directory where the application will be started. Usually, this will be a folder that contains documents for the application.

ApplicationPath

A command-line parameter that you want to pass to the application, such as a file to open.

Creating Jump List Items in Code Although it’s easy to fill a jump list using markup in the App.xaml file, there’s a serious disadvantage to this approach. As you’ve seen, both the JumpPath and the JumpTask items require a fully qualified file path. However, this information often depends on the way the application is deployed, and so it shouldn’t be hard-coded. For that reason, it’s common to create or modify the application jump list programmatically. To configure the jump list in code, you use the JumpList, JumpPath, and JumpPath classes from the System.Windows.Shell namespace. The following example demonstrates this technique by creating a new JumpPath object. This item allows the user to open Notepad to view a readme.txt file that’s stored in the current application folder, no matter where it’s installed. private void Application_Startup(object sender, StartupEventArgs e) { // Retrieve the current jump list. JumpList jumpList = new JumpList(); JumpList.SetJumpList(Application.Current, jumpList); // Add a new JumpPath for a file in the application folder. string path = Path.GetDirectoryName( System.Reflection.Assembly.GetExecutingAssembly().Location); path = Path.Combine(path, "readme.txt"); if (File.Exists(path)) { JumpTask jumpTask = new JumpTask(); jumpTask.CustomCategory = "Documentation"; jumpTask.Title = "Read the readme.txt"; jumpTask.ApplicationPath = @"c:\windows\notepad.exe"; jumpTask.IconResourcePath = @"c:\windows\notepad.exe"; jumpTask.Arguments = path; jumpList.JumpItems.Add(jumpTask); 732

CHAPTER 23 n WINDOWS

} // Update the jump list. jumpList.Apply(); } Figure 23-9 shows a customized jump list that includes this newly added JumpTask.

Figure 23-9. A jump list with a custom JumpTask

Launching Application Tasks from the Jump List So far, all the examples you’ve seen have used the jump list to open documents or launch applications. You haven’t seen an example that uses it to trigger a task inside a running application. This isn’t an oversight in the WPF jump list classes—it’s just the way jump lists are designed. To work around it, you need to use a variation of the single-instance technique you used in Chapter 7. Here’s the basic strategy: •

When the Application.Startup event fires, create a JumpTask that points to your application. Instead of using a file name, set the Arguments property to a special code that your application recognizes. For example, you could set it to @#StartOrder if you wanted this task to pass a “start order” instruction to your application.

•฀

Use the single-instance code from Chapter 7. When a second instance starts, pass the command-line parameter to the first instance and shut down the new application.

•฀

When the first instance receives the command-line parameter (in the OnStartupNextInstance() method), perform the appropriate task.



Don’t forget to remove the tasks from the jump list when the Application.Exit event fires, unless the tasks’ commands will work equally well when your application is launched for the first time.

To see a basic implementation of this technique, refer to the JumpListApplicationTask project with the sample code for this chapter.

733

CHAPTER 23 n WINDOWS

Changing the Taskbar Icon and Preview The taskbar in Windows 7 and Windows 8 adds several more refinements, including an optional progress display and thumbnail preview windows. Happily, WPF makes it easy to work with all these features. To access any of these features, you use TaskbarItemInfo class, which is found in the same System. Windows.Shell namespace as the jump list classes you considered earlier. Every window can have one associated TaskbarItemInfo object, and you can create it in XAML by adding this markup to your window:

...



This step, on its own, doesn’t change anything in your window or application. But now you’re ready to use the features that are demonstrated in the following sections.

Thumbnail Clipping Much as Windows gives every application automatic jump list support, it also provides a thumbnail preview window that appears when the user hovers over the taskbar button. Ordinarily, the thumbnail preview window shows a scaled-down version of the client region of the window (everything but the window border). However, in some cases, it might show just a portion of the window. The advantage in this case is that it focuses attention on the relevant part of the window. In a particularly large window, it may make content legible that would otherwise be too small to read. You can tap into this feature in WPF using the TaskbarItemInfo.ThumbnailClipMargin property. This specifies a Thickness object that sets the margin space between the content you want to show in the thumbnail and the edges of the window. The example shown in Figure 23-10 demonstrates how this works. Every time the user clicks a button in this application, the clipping region is shifted to include just the clicked button. Figure 23-10 shows the preview after clicking the second button.

734

CHAPTER 23 n WINDOWS

Figure 23-10. A window that clips its thumbnail preview

n Note You can’t change the thumbnail preview to show a graphic of your choosing. Your only option is to direct it to show a portion of the full window. To create this effect, the code must take several details into account: the coordinates of the button, its size, and the size of the content region of the window (which, helpfully, is the size of the top-level Grid named LayoutRoot, which sits just inside the window and contains all its markup). Once you have these numbers, a few simple calculations are all you need to constrain the preview to the correct region: private void cmdShrinkPreview_Click(object sender, RoutedEventArgs e) { // Find the position of the clicked button, in window coordinates. Button cmd = (Button)sender; Point locationFromWindow = cmd.TranslatePoint(new Point(0, 0), this); // Determine the width that should be added to every side. double left = locationFromWindow.X; double top = locationFromWindow.Y; double right = LayoutRoot.ActualWidth - cmd.ActualWidth - left; double bottom = LayoutRoot.ActualHeight - cmd.ActualHeight - top; 735

CHAPTER 23 n WINDOWS

// Apply the clipping. taskBarItem.ThumbnailClipMargin = new Thickness(left, top, right, bottom); }

Thumbnail Buttons Some applications use the preview window for an entirely different purpose. They place buttons into a small toolbar area under the preview. Windows Media Player is one example. If you hover over its taskbar icon, you’ll get a preview that includes play, pause, forward, and back buttons, which give you a convenient way to control playback without switching to the application itself. WPF supports thumbnail buttons—in fact, it makes them easy. You simply need to add one or more ThumbButtonInfo objects to the TaskbarItemInfo.ThumbButtonInfos collection. Each ThumbButtonInfo needs an image, which you supply with the ImageSource property. You can also use a Description that adds tooltip text. You can then hook up the button to a method in your application by handling its Click event. Here’s an example that adds Media Player play and pause buttons:



Figure 23-11 shows these buttons under the preview window.

Figure 23-11. Adding buttons to the thumbnail preview

736

CHAPTER 23 n WINDOWS

n Note Remember that the taskbar buttons are not shown in Windows Vista. For that reason, they should duplicate the functionality that’s already in your window, rather than provide new features. As your application performs different tasks and enters different states, some taskbar buttons may not be appropriate. Fortunately, you can manage them using a small set of useful properties, which are listed in Table 23-3. Table 23-3. Properties of the ThumbButtonInfo Class

Name

Description

ImageSource

Sets the image you want to show for the button, which must be embedded in your application as a resource. Ideally, you’ll use a . png file that has a transparent background.

Description

Sets the tooltip text that appears when the user hovers over the button.

Command, CommandParameter, and CommandTarget

Designate a command that the button should trigger. You can use these properties instead of the Click event.

Visibility

Allows you to hide or show a button.

IsEnabled

Allows you to disable a button, so it’s visible but can’t be clicked.

IsInteractive

Allows you to disable a button without dimming its appearance. This is useful if you want the button to act as a sort of status indicator.

IsBackgroundVisible

Allows you to disable the mouseover feedback for the button. If true (the default), Windows highlights the button and displays a border around it when the mouse moves overtop. If false, it doesn’t.

DismissWhenClicked

Allows you to create a single-use button. As soon as it is clicked, Windows removes it from the taskbar. (For more control, you can use code to add or remove buttons on the fly, although it’s usually just easier to show and hide them with the Visibility property.)

Progress Notification If you’ve ever copied a large file in Windows Explorer, you’ve seen how it uses progress notification to shade the background of the taskbar button in green. As the copy progresses, the green background fills the button area from left to right, like a progress bar, until the operation completes. What you might not realize is that this feature isn’t specific to Windows Explorer. Instead, it’s built into the taskbar and available to all your WPF applications. All you need to do is use two properties of the TaskbarItemInfo class: ProgressValue and ProgressState. ProgressState starts out at None, which means no progress indicator is shown. However, if you set it to TaskbarItemProgressState.Normal, you get the green-colored progress background that Windows Explorer uses. The ProgressValue property determines its size, from 0 (none) to 1 (full, or complete). For example, setting ProgressValue to 0.5 fills half of the taskbar button’s background with a green fill. The TaskbarItemProgressState enumeration provides a few possibilities other than just None and Normal. You can use Pause to show a yellow background instead of green, Error to show a red background, and Indeterminate to show a continuous, pulsing progress background that ignores the ProgressValue 737

CHAPTER 23 n WINDOWS

property. This latter option is suitable when you don’t know how long the current task will take to complete (for example, when calling a web service).

Overlay Icons The final taskbar feature is the taskbar overlay—the ability to add a small image overtop of the taskbar icon. For example, the Messenger chat application uses different overlays to signal different statuses. To use an overlay, you simply need a very tiny .png or .ico file with a transparent background. You aren’t forced to use a specific pixel size, but you’ll obviously want an image that’s a fair bit smaller than the taskbar button picture. Assuming you’ve added this image to your project, you can show it by simply setting the TaskBarItemInfo.Overlay property. Most commonly, you’ll set it using an image resource that’s already defined in your markup, as shown here: taskBarItem.Overlay = (ImageSource)this.FindResource("WorkingImage"); Alternatively, you can use the familiar pack URI syntax to point to an embedded file, as shown here: taskBarItem.Overlay = new BitmapImage( new Uri("pack://application:,,,/working.png")); Set the Overlay property to a null reference to remove the overlay altogether. Figure 23-12 shows the pause.png image being used as an overlay over the generic WPF application icon. This indicates that the application’s work is currently paused.

Figure 23-12. Showing an icon overlay

The Last Word In this chapter, you explored the WPF window model. You began with the basics: positioning and sizing a window, creating owned windows, and using the common dialog boxes. Then you considered more sophisticated effects, like irregularly shaped windows, and custom window templates. In the last part of this chapter, you considered the impressive support that WPF includes for the taskbar in Windows 7 and Windows 8 (in desktop mode). You saw how your WPF application can get basic 738

CHAPTER 23 n WINDOWS

jump list support and add custom items. You also learned how you can focus the thumbnail preview window on a section of your window and give it convenient command buttons. Finally, you learned to use progress notification and overlays with your taskbar icon.

739

ChAPTeR 24 nnn

Pages and Navigation Most traditional Windows applications are arranged around a window that contains toolbars and menus. The toolbars and menus drive the application—as the user clicks them, actions happen, and other windows appear. In document-based applications, several equally important “main” windows may be open at once, but the overall model is the same. The users spend most of their time in one place, and jump to separate windows when necessary. Windows applications are so common that it’s sometimes hard to imagine different ways to design an application. However, the Web uses a dramatically different page-based navigation model, and desktop developers have realized that it’s a surprisingly good choice for designing certain types of applications. In a bid to give desktop developers the ability to build weblike desktop applications, WPF includes its own page-based navigation system. As you’ll see in this chapter, it’s a remarkably flexible model. Currently, the page-based model is most commonly used for simple, lightweight applications (or small feature subsets in a more complex window-based application). However, page-based applications are a good choice if you want to streamline application deployment. That’s because WPF allows you to create a page-based application that runs directly inside Internet Explorer or Firefox. This means that users can run your application without an explicit installation step—they simply point their browsers to the correct location. You’ll learn about this model, called XBAP, in this chapter. Finally, this chapter wraps up with a look at WPF’s WebBrowser control, which lets you host HTML web pages in a WPF window. As you’ll see, the WebBrowser not only shows web pages, but also allows you to programmatically explore their structure and content (using the HTML DOM). It even allows your application to interact with JavaScript code.

Page-Based Navigation The average web application looks quite a bit different from traditional rich client software. The users of a website spend their time navigating from one page to another. Unless they’re unlucky enough to face popup advertising, there’s never more than one page visible at a time. When completing a task (such as placing an order or performing a complicated search), the user traverses these pages in a linear sequence from start to finish. HTML doesn’t support the sophisticated windowing capabilities of desktop operating systems, so the best web developers rely on good design and clear, straightforward interfaces. As web design has become increasingly more sophisticated, Windows developers have also begun to see the advantages of this approach. Most important, the web model is simple and streamlined. For that reason, novice users often find websites easier to use than Windows applications, even though Windows applications are obviously much more capable. 741

CHAPTER 24 n PAGES AND NAVIGATION

In recent years, developers have begun mimicking some of the conventions of the Web in desktop applications. Financial software such as Microsoft Money is a prime example of a weblike interface that leads users through set tasks. However, creating these applications is often more complicated than designing a traditional window-based application, because developers need to re-create basic browser features such as navigation.

n Note In some cases, developers have built weblike applications by using the Internet Explorer browser engine. This is the approach that Microsoft Money takes, but it’s one that would be more difficult for non-Microsoft developers. Although Microsoft provides hooks into Internet Explorer, such as the WebBrowser control, building a complete application around these features is far from easy. It also risks sacrificing the best capabilities of ordinary Windows applications. In WPF, there’s no longer any reason to compromise, because WPF includes a built-in page model that incorporates navigation. Best of all, this model can be used to create a variety of page-based applications, applications that use some page-based features (for example, in a wizard or help system), or applications that are hosted directly in the browser.

Page-Based Interfaces To create a page-based application in WPF, you need to stop using the Window class as your top-level container for user interfaces. Instead, it’s time to switch to the System.Windows.Controls.Page class. The model for creating pages in WPF is much the same as the model for creating windows. Although you could create page objects with just code, you’ll usually create a XAML file and a code-behind file for each page. When you compile that application, the compiler creates a derived page class that combines your code with a bit of automatically generated glue (such as the fields that refer to each named element on your page). This is the same process that you learned about when you considered compilation with a window-based application in Chapter 2.

n Note You can add a page to any WPF project. Just choose Project ä Add Page in Visual Studio. Although pages are the top-level user interface ingredient when you’re designing your application, they aren’t the top-level container when you run your application. Instead, your pages are hosted in another container. This is the secret to WPF’s flexibility with page-based applications, because you can use one of several containers: • • • •

The NavigationWindow, which is a slightly tweaked version of the Window class A Frame that’s inside another window A Frame that’s inside another page A Frame that’s hosted directly in Internet Explorer or Firefox

You’ll consider all of these hosts in this chapter.

742

CHAPTER 24 n PAGES AND NAVIGATION

Creating a Simple Page-Based Application with NavigationWindow To try an extremely simple page-based application, create a page like this:

This is a simple page.

OK Close

Now modify the App.xaml file so that the startup page is your page file:

When you run this application, WPF is intelligent enough to realize that you’re pointing it to a page rather than a window. It automatically creates a new NavigationWindow object to serve as a container and shows your page inside of it (Figure 24-1). It also reads the page’s WindowTitle property and uses that for the window caption.

Figure 24-1. A page in a NavigationWindow

743

CHAPTER 24 n PAGES AND NAVIGATION

n Note One difference between a page and a window is that you don’t typically set the size of a page, because the page size is determined by the host. If you do set the Width and Height properties of the page, the page is made exactly that size, but some content is clipped if the host window is smaller, or it’s centered inside the available space if the host window is larger. The NavigationWindow looks more or less like an ordinary window, aside from the back and forward navigation buttons that appear in the bar at the top. As you might expect, the NavigationWindow class derives from Window, and it adds a small set of navigation-related properties. You can get a reference to the containing NavigationWindow object by using code like this: // Get a reference to the window that contains the current page. NavigationWindow win = (NavigationWindow)Window.GetWindow(this); This code won’t work in the page constructor, because the page hasn’t been placed inside its container yet—instead, wait at least until the Page.Loaded event fires.

n Tip It’s best to avoid the NavigationWindow approach if at all possible, and use properties of the Page class (and the navigation service described later in this chapter). Otherwise, your page will be tightly coupled to the NavigationWindow, and you won’t be able to reuse it in different hosts. If you want to create a code-only application, you’d need to create both the navigation window and the page to get the effect shown in Figure 24-1. Here’s the code that would do it: NavigationWindow win = new NavigationWindow() win.Content = new Page1(); win.Show();

The Page Class Like the Window class, the Page class allows a single nested element. However, the Page class isn’t a content control; it derives directly from FrameworkElement. The Page class is also simpler and more streamlined than the Window class. It adds a small set of properties that allow you to customize its appearance, interact with the container in a limited way, and use navigation. Table 24-1 lists these properties. Table 24-1. Properties of the Page Class

744

Name

Description

Background

Takes a brush that allows you to set the background fill.

Content

Takes the single element that’s shown in the page. Usually, this is a layout container, such as a Grid or a StackPanel.

Foreground, FontFamily, and FontSize

Determine the default appearance of text inside the page. The values of these properties are inherited by the elements inside the page. For example, if you set the foreground fill and font size, by default, the content inside the page gets these details.

CHAPTER 24 n PAGES AND NAVIGATION

WindowWidth, WindowHeight, and WindowTitle

Determine the appearance of the window that wraps your page. These properties allow you to take control of the host by setting its width, height, and caption. However, they have an effect only if your page is being hosted in a window (rather than a frame).

NavigationService

Returns a reference to a NavigationService object, which you can use to programmatically send the user to another page.

KeepAlive

Determines whether the page object should be kept alive after the user navigates to another page. You’ll take a closer look at this property later in this chapter (in the “Navigation History” section), when you consider how WPF restores the pages in your navigation history.

ShowsNavigationUI

Determines whether the host for this page shows its navigation controls (the forward and back buttons). By default, it’s true.

Title

Sets the name that’s used for the page in the navigation history. The host does not use the title to set the caption in the title bar; instead, the WindowTitle property serves that purpose.

It’s also important to notice what’s not there—namely, there’s no equivalent of the Hide() and Show() methods of the Window class. If you want to show a different page, you’ll need to use navigation.

Hyperlinks The easiest way to allow the user to move from one page to another is by using hyperlinks. In WPF, hyperlinks aren’t separate elements. Instead, they’re inline flow elements, which must be placed inside another element that supports them. (The reason for this design is that hyperlinks and text are often intermixed. You’ll learn more about flow content and text layout in Chapter 28.) For example, here’s a combination of text and links in a TextBlock element, which is the most practical container for hyperlinks:

This is a simple page. Click here to go to Page2.

When rendered, hyperlinks appear as the familiar blue, underlined text (see Figure 24-2).

745

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-2. Linking to another page You can handle clicks on a link in two ways. You can respond to the Click event and use code to perform some task, or direct the user to another page. However, there’s an easier approach. The Hyperlink class also includes a NavigateUri property, which you set to point to any other page in your application. Then, when users click this hyperlink, they travel to the destination page automatically.

n Note The NavigateUri property works only if you place the hyperlink in a page. If you want to use a hyperlink in a window-based application to let users perform a task, launch a web page, or open a new window, you need to handle the RequestNavigate event and write the code yourself. Hyperlinks aren’t the only way to move from one page to another. The NavigationWindow includes prominent forward and back buttons (unless you set the Page.ShowsNavigationUI property to false to hide them). Clicking these buttons moves you through the navigation sequence one page at a time. And similar to a browser, you can click the drop-down arrow at the edge of the forward button to examine the complete sequence and jump forward or backward several pages at a time (Figure 24-3).

Figure 24-3. The history of visited pages

746

CHAPTER 24 n PAGES AND NAVIGATION

You’ll learn more about how the page history works—and its limitations—later in the “Navigation History” section.

n Note If you navigate to a new page, and that page doesn’t set the WindowTitle property, the window keeps the title it had on the previous page. If you don’t set the WindowTitle on any page, the window caption is left blank.

Navigating to Websites Interestingly, you can also create a hyperlink that points to web content. When the user clicks the link, the target web page loads in the page area:

Visit the website www.prosetech.com.

However, if you use this technique, make sure you attach an event handler to the Application. DispatcherUnhandledException or Application.NavigationFailed event. The attempt to navigate to a website could fail if the computer isn’t online, the site isn’t available, or the web content can’t be reached. In this case, the network stack returns an error such as “404: File Not Found,” which becomes a WebException. To handle this exception gracefully and prevent your application from shutting down unexpectedly, you need to neutralize it with an event handler such as this: private void App_NavigationFailed(object sender, NavigationFailedEventArgs e) { if (e.Exception is System.Net.WebException) { MessageBox.Show("Website " + e.Uri.ToString() + " cannot be reached."); // Neutralize the error so the application continues running. e.Handled = true; } } NavigationFailed is just one of several navigation events that are defined in the Application class. You’ll get the full list later in this chapter, in Table 24-2.

n Note After you lead users to a web page, they’ll be able to click its links to travel to other web pages, leaving your content far behind. In fact, they’ll return to your WPF page only if they use the navigation history to go back or if you’re showing the page in a custom window (as discussed in the next section) and that window includes a control that navigates back to your content. You can’t do a number of things when displaying pages from external websites. You can’t prevent the user from navigating to specific pages or sites. Also, you can’t interact with the web page by using the HTML Document Object Model (DOM). That means you can’t scan a page looking for links or dynamically change a page. All of these tasks are possible using the WebBrowser control, which is described at the end of this chapter. 747

CHAPTER 24 n PAGES AND NAVIGATION

Fragment Navigation The last trick that you can use with the hyperlink is fragment navigation. By adding the number sign (#) at the end of the NavigateUri, followed by an element name, you can jump straight to a specific control on a page. However, this works only if the target page is scrollable. (The target page is scrollable if it uses the ScrollViewer control or if it’s hosted in a web browser.) Here’s an example:

Review the full text.

When the user clicks this link, the application moves to the page named Page2, and scrolls down the page to the element named myTextBox. The page is scrolled down until myTextBox appears at the top of the page (or as close as possible, depending on the size of the page content and the containing window). However, the target element doesn’t receive focus.

Hosting Pages in a Frame The NavigationWindow is a convenient container, but it’s not your only option. You can also place pages directly inside other windows or even inside other pages. This makes for an extremely flexible system, because you can reuse the same page in different ways, depending on the type of application you need to create. To embed a page inside a window, you simply need to use the Frame class. The Frame class is a content control that can hold any element, but it makes particular sense when used as a container for a page. It includes a property named Source, which points to a XAML page that you want to display. Here’s an ordinary window that wraps some content in a StackPanel and places a Frame in a separate column:





This is ordinary window content. Close



Figure 24-4 shows the result. A border around the frame shows the page content. There’s no reason you need to stop at one frame. You can easily create a window that wraps multiple frames, and you can point them to different pages. 748

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-4. A window with a page embedded in a frame As you can see in Figure 24-4, this example doesn’t include the familiar navigation buttons. This is because the Frame.NavigationUIVisibility property is (by default) set to Automatic. As a result, the navigation controls appear only after there’s something in the forward and back list. To try this, navigate to a new page. You’ll see the buttons appear inside the frame, as shown in Figure 24-5.

Figure 24-5. A frame with navigation buttons You can change the NavigationUIVisibility property to Hidden if you never want to show the navigation buttons, or change it to Visible if you want them to appear right from the start. Having the navigation buttons inside the frame is a great design if your frame contains content that’s separate from the main flow of the application. (For example, maybe you’re using it to display contextsensitive help or the content for a walk-through tutorial.) But in other cases, you may prefer to show the buttons at the top of the window. To do this, you need to change your top-level container from Window to NavigationWindow. That way, your window will include the navigation buttons. The frame inside the

749

CHAPTER 24 n PAGES AND NAVIGATION

window will automatically wire itself up to these buttons, so the user gets a similar experience to what’s shown in Figure 24-3, except now the window also holds the extra content.

n Tip You can add as many Frame objects as you need to a window. For example, you could easily create a window that allows the user to browse through an application task, help documentation, and an external website, using three separate frames.

Hosting Pages in Another Page Frames give you the ability to create more-complex arrangements of windows. As you learned in the previous section, you can use several frames in a single window. You can also place a frame inside another page to create a nested page. In fact, the process is exactly the same—you simply add a Frame object inside your page markup. Nested pages present a more complex navigation situation. For example, imagine you visit a page and then click a link in an embedded frame. What happens when you click the back button? Essentially, all the pages in a frame are flattened into one list. So the first time you click the back button, you move to the previous page in the embedded frame. The next time you click the back button, you move to the previously visited parent page. Figure 24-6 shows the sequence you follow. Notice that the back navigation button is enabled in the second step.

Figure 24-6. Navigation with an embedded page Most of the time, this navigation model is fairly intuitive, because you’ll have one item in the back list for each page you visit. However, in some cases the embedded frame plays a less important role. For example, maybe it shows different views of the same data or allows you to step through multiple pages of help content. In these cases, stepping through all the pages in the embedded frame may seem awkward or time-consuming. Instead, you may want to use the navigation controls to control the navigation of the parent frame only, so that when you click the back button, you move to the previous parent page right 750

CHAPTER 24 n PAGES AND NAVIGATION

away. To do this, you need to set the JournalOwnership property of the embedded frame to OwnsJournal. This tells the frame to maintain its own distinct page history. By default, the embedded frame will now acquire navigation buttons that allow you to move back and forth through its content (see Figure 24-7). If this isn’t what you want, you can use the JournalOwnership property in conjunction with the NavigationUIVisibility property to hide the navigation controls altogether, as shown here:

Now the embedded frame is treated as though it’s just a piece of dynamic content inside your page. From the user’s point of view, the embedded frame doesn’t support navigation.

Figure 24-7. An embedded page that owns its journal and supports navigation

Hosting Pages in a Web Browser The final way that you can use page-based navigation applications is in Internet Explorer or Firefox. However, in order to use this approach, you need to create a XAML browser application (which is known as an XBAP). In Visual Studio, the XBAP is a separate project template, and you must select it (rather than the standard WPF Windows application) when creating a project in order to use browser hosting. You’ll examine the XBAP model later in this chapter.

geTTIng The RIghT SIze WInDoW There are really two types of page-based applications: •฀ Stand-alone Windows applications that use pages for part or all of their user interfaces. You’ll use this approach if you need to integrate a wizard into your application or you want a simple task-oriented application. This way, you can use WPF’s navigation and journal features to simplify your coding. 751

CHAPTER 24 n PAGES AND NAVIGATION

•฀

Browser applications (XBAPs) that are hosted by Internet Explorer or Firefox, and usually run with limited permissions. You’ll use this approach if you want a lightweight, web-based deployment model.

If your application falls into the first category, you probably won’t want to set the Application.StartupUri property to point to a page. Instead, you’ll create the NavigationWindow manually, and then load your first page inside it (as shown earlier), or you’ll embed your pages in a custom window by using the Frame control. Both of these approaches give you the flexibility to set the size of the application window, which is important for making sure your application looks respectable when it first starts up. On the other hand, if you’re creating an XBAP, you have no control over the size of the containing web browser window, and you must set the StartupUri property to point to a page.

The Page History Now that you’ve learned about pages and the different ways to host them, you’re ready to delve deeper into the navigation model that WPF uses. In this section, you’ll learn how WPF hyperlinks work and how pages are restored when you navigate back to them.

A Closer Look at URIs in WPF You might wonder how properties such as Application.StartupUri, Frame.Source, and Hyperlink. NavigateUri actually work. In an application that’s made up of loose XAML files and run in the browser, it’s fairly straightforward: when you click a hyperlink, the browser treats the page reference as a relative URI and looks for the XAML page in the current folder. But in a compiled application, the pages are no longer available as separate resources; instead, they’re compiled to Binary Application Markup Language (BAML) and embedded into the assembly. So, how can they be referenced using a URI? This system works because of the way that WPF addresses application resources. When you click a hyperlink in a compiled XAML application, the URI is still treated as a relative path. However, it’s relative to the base URI for the application. That’s because a hyperlink that points to Page1.xaml is actually expanded to the pack URI shown here: pack://application:,,,/Page1.xaml Chapter 7 describes the pack URI syntax in detail. But the most important detail is the final portion, which includes the resource name. At this point, you might be wondering why it’s important to understand how hyperlink URIs work if the process is so seamless. The chief reason is that you might choose to create an application that navigates to XAML pages stored in another assembly. In fact, there are good reasons for this design. Because pages can be used in different containers, you might want to reuse the same set of pages in an XBAP and an ordinary Windows application. That way, you can deploy two versions of your application: a browser-based version and a desktop version. To avoid duplicating your code, you should place all the pages you plan to reuse in a separate class library assembly (DLL), which can then be referenced by both your application projects. This necessitates a change in your URIs. If you have a page in one assembly that points to a page in another, you need to use the following syntax: pack://application:,,,/PageLibrary;component/Page1.xaml Here, the component is named PageLibrary and the path ,,,/PageLibrary;component/Page1.xaml points to a page named Page1.xaml that’s compiled and embedded inside.

752

CHAPTER 24 n PAGES AND NAVIGATION

Of course, you probably won’t use the absolute path. Instead, it makes more sense to use the following slightly shorter relative path in your URIs: /PageLibrary;component/Page1.xaml

n Tip Use the project template called Custom Control Library (WPF) when you create the SharedLibrary assembly to get the correct assembly references, namespace imports, and application settings.

Navigation History The WPF page history works just like the history in a browser. Every time you navigate to a new page, the previous page is added to the back list. If you click the back button, the page is added to the forward list. If you back out from one page and then navigate to a new page, the forward list is cleared. The behavior of the back and forward lists is fairly straightforward, but the plumbing that supports them is more complex. For example, imagine you visit a page with two text boxes, type something in, and move ahead. If you head back to this page, you’ll find that WPF restores the state of your text boxes— meaning whatever content you placed in them is still there.

n Note There’s an important difference between returning to a page through the navigation history and clicking a link that takes you to the same page. For example, if you click links that take you from Page1 to Page2 to Page1, WPF creates three separate page objects. The second time you see Page1, WPF creates it as a separate instance, with its own state. However, if you click the back button twice to return to the first Page1 instance, you’ll see that your original Page1 state remains. You might assume that WPF maintains the state of previously visited pages by keeping the page object in memory. The problem with that approach is that the memory overhead may not be trivial in a complex application with many pages. For that reason, WPF can’t assume that maintaining the page object is a safe strategy. Instead, when you navigate away from a page, WPF stores the state of all your controls and then destroys the page. When you return to a page, WPF re-creates the page (from the original XAML) and then restores the state of your controls. This strategy has lower overhead because the memory required to save just a few details of control state is far less than the memory required to store the page and its entire visual tree of objects. This system raises an interesting question: how does WPF decide which details to store? WPF examines the complete element tree of your page, and it looks at the dependency properties of all your elements. Properties that should be stored have a tiny bit of extra metadata—a journal flag that indicates they should be kept in the navigation log known as the journal. (The journal flag is set by using the FrameworkPropertyMetadata object when registering the dependency property, as described in Chapter 4.) If you take a closer look at the navigation system, you’ll find that many properties don’t have the journal flag. For example, if you set the Content property of a content control or the Text property of a TextBlock element by using code, neither of these details will be retained when you return to the page. The same is true if you set the Foreground or Background properties dynamically. However, if you set the Text property of a TextBox, the IsSelected property of a CheckBox, or the SelectedIndex property of a ListBox, all these details will remain. 753

CHAPTER 24 n PAGES AND NAVIGATION

So what can you do if this isn’t the behavior you want? What if you set many properties dynamically, and you want your pages to retain all of their information? You have several options. The most powerful is to use the Page.KeepAlive property, which is false by default. When set to true, WPF doesn’t use the serialization mechanism described previously. Instead, it keeps all your page objects alive. Thus, when you navigate back to a page, it’s exactly the way you left it. Of course, this option has the drawback of increased memory overhead, so you should enable it only on the few pages that really need it.

n Tip When you use the KeepAlive property to keep a page alive, it won’t fire the Initialized event the next time you navigate to it. (Pages that aren’t kept alive but are “rehydrated” by using WPFs journaling system will fire the Initialized event each time the user visits them.) If this behavior isn’t what you want, you should instead handle the Unloaded and Loaded events of the Page, which always fire. Another solution is to choose a different design that passes information around. For example, you can create page functions (described later in this chapter) that return information. Using page functions, along with extra initialization logic, you can design your own system for retrieving the important information from a page and restoring it when needed. There’s one more wrinkle with the WPF navigation history. As you’ll discover later in this chapter, you can write code that dynamically creates a page object and then navigates to it. In this situation, the ordinary mechanism of maintaining the page state won’t work. WPF doesn’t have a reference to the XAML document for the page, so it doesn’t know how to reconstruct the page. (And if the page is created dynamically, there may not even be a corresponding XAML document.) In this situation, WPF always keeps the page object alive in memory, no matter what the KeepAlive property says.

Maintaining Custom Properties Ordinarily, any fields in your page class lose their values when the page is destroyed. If you want to add custom properties to your page class and make sure they retain their values, you can set the journal flag accordingly. However, you can’t take this step with an ordinary property or a field. Instead, you need to create a dependency property in your page class. You’ve already taken a look at dependency properties in Chapter 4. To create a dependency property, you need to follow two steps. First, you need to create the dependency property definition. Second, you need an ordinary property procedure that sets or gets the value of the dependency property. To define the dependency property, you need to create a static field like this: private static DependencyProperty MyPageDataProperty; By convention, the field that defines your dependency property has the name of your ordinary property, plus the word Property at the end.

n Note This example uses a private dependency property. That’s because the only code that needs to access this property is in the page class where it’s defined. To complete your definition, you need a static constructor that registers your dependency property definition. This is the place where you set the services that you want to use with your dependency property (such as support for data binding, animation, and journaling).

754

CHAPTER 24 n PAGES AND NAVIGATION

static PageWithPersistentData() { FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.Journal = true; MyPageDataProperty = DependencyProperty.Register( "MyPageDataProperty", typeof(string), typeof(PageWithPersistentData), metadata, null); } Now you can create the ordinary property that wraps this dependency property. However, when you write the getter and setter, you’ll use the GetValue() and SetValue() methods that are defined in the base DependencyObject class: private string MyPageData { set { SetValue(MyPageDataProperty, value); } get { return (string)GetValue(MyPageDataProperty); } } Add all these details to a single page (in this example, one named PageWithPersistentData), and the MyPageData property value will be automatically serialized when users navigate away and restored when they return.

The Navigation Service So far, the navigation you’ve seen relies heavily on hyperlinks. When this approach works, it’s simple and elegant. However, in some cases, you’ll want to take more control of the navigation process. For example, hyperlinks work well if you’re using pages to model a fixed, linear series of steps that the user traverses from start to finish (such as a wizard). However, if you want the user to complete small sequences of steps and return to a common page, or if you want to configure the sequence of steps based on other details (such as the user’s previous actions), you need something more.

Programmatic Navigation You can set the Hyperlink.NavigateUri and Frame.Source properties dynamically. However, the most flexible and powerful approach is to use the WPF navigation service. You can access the navigation service through the container that hosts the page (such as Frame or NavigationWindow), but this approach limits your pages so they can be used only in that type of container. The best approach is to access the navigation service through the static NavigationService.GetNavigationService() method. You pass a reference to your page to the GetNavigationService() method, and it returns a live NavigationService object that lets you perform programmatic navigation: NavigationService nav; nav = NavigationService.GetNavigationService(this); This code works no matter which container you’re using to host your pages.

n Note The NavigationService isn’t available in a page constructor or when the Page.Initialized event fires. Use the Page.Loaded event instead. 755

CHAPTER 24 n PAGES AND NAVIGATION

The NavigationService class gives you a number of methods you can use to trigger navigation. The most commonly used is the Navigate() method, which allows you to navigate to a page based on its URI: nav.Navigate(new System.Uri("Page1.xaml", UriKind.RelativeOrAbsolute)); or by creating the appropriate page object: Page1 nextPage = new Page1(); nav.Navigate(nextPage); If possible, you’ll want to navigate by URI, because that allows WPF’s journaling system to preserve the page data without needing to keep the tree of page objects alive in memory. When you pass a page object to the Navigate() method, the entire object is always retained in memory. However, you may decide to create the page object manually if you need to pass information into the page. You can pass in information by using a custom page class constructor (which is the most common approach), or you can call another custom method in the page class after you’ve created it. If you add a new constructor to the page, make sure your constructor calls InitializeComponent() to process your markup and create the control objects.

n Note If you decide you need to use programmatic navigation, it’s up to you whether you use button controls, hyperlinks, or something else. Typically, you’ll use conditional code in your event handler to indicate the page to which you navigate. WPF navigation is asynchronous. As a result, you can cancel the navigation request before it’s complete by calling the NavigationService.StopLoading() method. You can also use the Refresh() method to reload a page. Finally, the NavigationService also provides GoBack() and GoForward() methods, which allow you to move through the back and forward lists. This is useful if you’re creating your own navigation controls. Both of these methods raise an InvalidOperationException if you try to navigate to a page that doesn’t exist (for example, you attempt to go back when you’re on the first page). To avoid these errors, check the Boolean CanGoBack and CanGoForward properties before using the matching methods.

Navigation Events The NavigationService class also provides a useful set of events that you can use to react to navigation. The most common reason you’ll react to navigation is to perform some sort of task when navigation is complete. For example, if your page is hosted inside a frame in a normal window, you might update status bar text in the window when navigation is complete. Because navigation is asynchronous, the Navigate() method returns before the target page has appeared. In some cases, the time difference could be significant, such as when you’re navigating to a loose XAML page on a website (or a XAML page in another assembly that triggers a web download) or when the page includes time-consuming code in its Initialized or Loaded event handler. The WPF navigation process unfolds like this:

756

1.

The page is located.

2.

The page information is retrieved. (If the page is on a remote site, it’s downloaded at this point.)

3.

Any related resources that the page needs (such as images) are also located and downloaded.

CHAPTER 24 n PAGES AND NAVIGATION

4.

The page is parsed, and the tree of objects is generated. At this point, the page fires its Initialized event (unless it’s being restored from the journal) and its Loaded event.

5.

The page is rendered.

6.

If the URI includes a fragment, WPF navigates to that element.

Table 24-2 lists the events that are raised by the NavigationService class during the process. These navigation events are also provided by the Application class and by the navigation containers (NavigationWindow and Frame). If you have more than one navigation container, this gives you the flexibility to handle the navigation in different containers separately. However, there’s no built-in way to handle the navigation events for a single page. After you attach an event handler to the navigation service in a navigation container, it continues to fire events as you move from page to page (or until you remove the event handler). Generally, this means that the easiest way to handle navigation is at the application level. Navigation events can’t be suppressed by using the RoutedEventArgs.Handled property. That’s because navigation events are ordinary .NET events, not routed events.

n Tip You can pass data from the Navigate() method to the navigation events. Just look for one of the Navigate() method overloads that take an extra object parameter. This object is made available in the Navigated, NavigationStopped, and LoadCompleted events through the NavigationEventArgs.ExtraData property. For example, you could use this property to keep track of the time a navigation request was made.

Table 24-2. Events of the NavigationService Class

Name

Description

Navigating

Navigation is just about to start. You can cancel this event to prevent the navigation from taking place.

Navigated

Navigation has started, but the target page has not yet been retrieved.

NavigationProgress

Navigation is underway, and a chunk of page data has been downloaded. This event is raised periodically to provide information about the progress of navigation. It provides the amount of information that has been downloaded (NavigationProgressEventArgs.BytesRead) and the total amount of information that’s required (NavigationProgressEventArgs.MaxBytes). This event fires every time 1 KB of data is retrieved.

LoadCompleted

The page has been parsed. However, the Initialized and Loaded events have not yet been fired.

FragmentNavigation

The page is about to be scrolled to the target element. This event fires only if you use a URI with fragment information.

NavigationStopped

Navigation was canceled with the StopLoading() method.

NavigationFailed

Navigation has failed because the target page could not be located or downloaded. You can use this event to neutralize the exception before it bubbles up to become an unhandled application exception. Just set NavigationFailedEventArgs.Handled to true.

757

CHAPTER 24 n PAGES AND NAVIGATION

Managing the Journal Using the techniques you’ve learned so far, you’ll be able to build a linear navigation-based application. You can make the navigation process adaptable (for example, using conditional logic so that users are directed to different steps along the way), but you’re still limited to the basic start-to-finish approach. Figure 24-8 shows this navigation topology, which is common when building simple task-based wizards. The dashed lines indicate the steps we’re interested in—when the user exits a group of pages that represent a logical task.

Figure 24-8. Linear navigation If you try to implement this design by using WPF navigation, you’ll find that there’s a missing detail. Namely, when the user is finished with the navigation process (either because the user canceled the operation during one of the steps or because the user completed the task at hand), you need to wipe out the back history. If your application revolves around a main window that isn’t navigation-based, this isn’t a problem. When the user launches the page-based task, your application simply creates a new NavigationWindow to take the user through it. When the task ends, you can destroy that window. However, if your entire application is navigation-based, this isn’t as easy. You need a way to drop the history list when the task is canceled or complete, so the user can’t step back to one of the intermediary steps. Unfortunately, WPF doesn’t allow you to have much control over the navigation stack. It just gives you two methods in the NavigationService class: AddBackEntry() and RemoveBackEntry(). RemoveBackEntry() is the one you need in this example. It takes the most recent item from the back list and deletes it. RemoveBackEntry() also returns a JournalEntry object that describes that item. It tells you the URI (through the Source property) and the name that it uses in the navigation history (through the Name property). Remember that the name is set based on the Page.Title property. If you want to clear several entries after a task is complete, you’ll need to call RemoveBackEntry() multiple times. You can use two approaches. If you’ve decided to remove the entire back list, you can use the CanGoBack property to determine when you’ve reached the end: while (nav.CanGoBack) { nav.RemoveBackEntry(); }

758

CHAPTER 24 n PAGES AND NAVIGATION

Alternatively, you can continue removing items until you remove the task starting point. For example, if a page launches a task starting with a page named ConfigureAppWizard.xaml, you could use this code when the task is complete: string pageName; while (pageName != "ConfigureAppWizard.xaml") { JournalEntry entry = nav.RemoveBackEntry(); pageName = System.IO.Path.GetFileName(entry.Source.ToString()); } This code takes the full URI that’s stored in the JournalEntry.Source property and trims it down to just the page name by using the static GetFileName() method of the Path class (which works equally well with URIs). Using the Title property would make for more convenient coding, but it isn’t as robust. Because the page title is displayed in the navigation history and is visible to the user, it’s a piece of information you would need to translate into other languages when localizing your application. This would break code that expects a hard-coded page title. And even if you don’t plan to localize your application, it’s not difficult to imagine a scenario where the page title is changed to be clearer or more descriptive. Incidentally, it is possible to examine all the items in the back and forward lists by using the BackStack and ForwardStack properties of the navigation container (such as NavigationWindow or Frame). However, it’s not possible to get this information generically through the NavigationService class. In any case, these properties expose simple read-only collections of JournalEntry objects. They don’t allow you to modify the lists, and they’re rarely needed.

Adding Custom Items to the Journal Along with the RemoveBackEntry() method, the NavigationService also gives you an AddBackEntry() method. The purpose of this method is to allow you to save “virtual” entries in the back list. For example, imagine you have a single page that allows the user to perform a fairly sophisticated configuration task. If you want the user to be able to step back to a previous state of that window, you can save it by using the AddBackEntry() method. Even though it’s only a single page, it may have several corresponding entries in the list. Contrary to what you might expect, when you call AddBackEntry(), you don’t pass in a JournalEntry object. (In fact, the JournalEntry class has a protected constructor and so it can’t be instantiated by your code.) Instead, you need to create a custom class that derives from the abstract System.Windows. Navigation.CustomContentState class and stores all the information you need. For example, consider the application shown in Figure 24-9, which allows you to move items from one list to another.

759

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-9. A dynamic list Now imagine that you want to save the state of this window every time an item is moved from one list to the other. The first thing you need is a class that derives from CustomContentState and keeps track of this information you need. In this case, you simply need to record the contents of both lists. Because this class will be stored in the journal (so your page can be “rehydrated” when needed), it needs to be serializable: [Serializable()] public class ListSelectionJournalEntry : CustomContentState { private List sourceItems; private List targetItems; public List SourceItems { get { return sourceItems; } } public List TargetItems { get { return targetItems; } } ... This gets you off to a good start, but there’s still a fair bit more to do. For example, you probably don’t want the page to appear with the same title in the navigation history multiple times. Instead, you’ll probably want to use a more descriptive name. To make this possible, you need to override the JournalEntryName property. In this example, there’s no obvious, concise way to describe the state of both lists. So it makes sense to let the page choose the name when it saves the entry in the journal. This way, the page can add a descriptive name based on the most recent action (such as Added Blue or Removed Yellow). To create this design, you simply need to make the JournalEntryName depend on a variable, which can be set in the constructor: 760

CHAPTER 24 n PAGES AND NAVIGATION

... private string _journalName; public override string JournalEntryName { get { return _journalName; } } ... The WPF navigation system calls your JournalEntryName property to get the name it should show in the list. The next step is to override the Replay() method. WPF calls this method when the user navigates to an entry in the back or forward list so that you can apply the previously saved state. There are two approaches you can take in the Replay() method. You can retrieve a reference to the current page by using the NavigationService.Content property. You can then cast that into the appropriate page class and call whatever method is required to implement your change. The other approach, which is used here, is to rely on a callback: ... private ReplayListChange replayListChange; public override void Replay(NavigationService navigationService, NavigationMode mode) { this.replayListChange(this); } ... The ReplayListChange delegate isn’t shown here, but it’s quite simple. It represents a method with one parameter: the ListSelectionJournalEntry object. The page can then retrieve the list information from the SourceItems and TargetItems properties and restore the page. With this in place, the last step is to create a constructor that accepts all the information you need: the two lists of items, the title to use in the journal, and the delegate that should be triggered when the state needs to be reapplied to the page: ... public ListSelectionJournalEntry( List sourceItems, List targetItems, string journalName, ReplayListChange replayListChange) { this.sourceItems = sourceItems; this.targetItems = targetItems; this.journalName = journalName; this.replayListChange = replayListChange; } } To hook up this functionality into the page, you need to take three steps: 1.

Call AddBackReference() at the appropriate time to store an extra entry in the navigation history.

2.

Handle the ListSelectionJournalEntry callback to restore your window when the user navigates through the history.

761

CHAPTER 24 n PAGES AND NAVIGATION

3.

Implement the IProvideCustomContentState interface and its single GetContentState() method in your page class. When the user navigates to another page through the history, the GetContentState() method is called by the navigation service. This allows you to return an instance of your custom class that will be stored as the state of the current page.

n Note The IProvideCustomContentState interface is an easily overlooked but essential detail. When the user navigates using the forward or back list, two things need to happen. Your page needs to add the current view to the journal (using IProvideCustomContentState), and then it needs to restore the selected view (using the ListSelectionJournalEntry callback). First, whenever the Add button is clicked, you need to create a new ListSelectionJournalEntry object and call AddBackReference()journal:AddBackReference() method” so the previous state is stored in the history. This process is factored out into a separate method so that you can use it in several places in the page (for example, when either the Add button or the Remove button is clicked): private void cmdAdd_Click(object sender, RoutedEventArgs e) { if (lstSource.SelectedIndex != -1) { // Determine the best name to use in the navigation history. NavigationService nav = NavigationService.GetNavigationService(this); string itemText = lstSource.SelectedItem.ToString(); string journalName = "Added " + itemText; // Update the journal (using the method shown below.) nav.AddBackEntry(GetJournalEntry(journalName)); // Now perform the change. lstTarget.Items.Add(itemText); lstSource.Items.Remove(itemText); } } private ListSelectionJournalEntry GetJournalEntry(string journalName) { // Get the state of both lists (using a helper method). List source = GetListState(lstSource); List target = GetListState(lstTarget); // Create the custom state object with this information. // Point the callback to the Replay method in this class. return new ListSelectionJournalEntry( source, target, journalName, Replay); } You can use a similar process when the Remove button is clicked. The next step is to handle the callback in the Replay() method and update the lists, as shown here: 762

CHAPTER 24 n PAGES AND NAVIGATION

private void Replay(ListSelectionJournalEntry state) { lstSource.Items.Clear(); foreach (string item in state.SourceItems) { lstSource.Items.Add(item); } lstTarget.Items.Clear(); foreach (string item in state.TargetItems) { lstTarget.Items.Add(item); } } And the final step is to implement IProvideCustomContentState in the page: public partial class PageWithMultipleJournalEntries : Page, IProvideCustomContentState IProvideCustomContentState defines a single method named GetContentState(). In GetContentState(), you need to store the state for the page in the same way you do when the Add or Remove button is clicked. The only difference is that you don’t add it by using the AddBackReference() method. Instead, you provide it to WPF through a return value: public CustomContentState GetContentState() { // We haven't stored the most recent action, // so just use the page name for a title. return GetJournalEntry("PageWithMultipleJournalEntries"); } Remember that the WPF navigation service calls GetContentState() when the user travels to another page by using the back or forward buttons. WPF takes the CustomContentState object you return and stores that in the journal for the current page. There’s a potential quirk here. If the user performs several actions and then travels back through the navigation history reversing them, the “undone” actions in the history will have the hard-coded page name (PageWithMultipleJournalEntries), rather than the more descriptive original name (such as Added Orange). To improve the way this is handled, you can store the journal name for the page by using a member variable in your page class. The downloadable code for this example takes that extra step. This completes the example. Now when you run the application and begin manipulating the lists, you’ll see several entries appear in the history (Figure 24-10).

763

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-10. Custom entries in the journal

Using Page Functions So far, you’ve learned how to pass information to a page (by instantiating the page programmatically, configuring it, and then passing it to the NavigationService.Navigate() method), but you haven’t seen how to return information from a page. The easiest (and least structured) approach is to store information in some sort of static application variable so that it’s accessible to any other class in your program. However, that design isn’t the best if you just need a way to transmit simple bits of information from one page to another, and you don’t want to keep this information in memory for a long time. If you clutter your application with global variables, you’ll have a difficult time figuring out the dependencies (which variables are used by which pages), and it will become much more difficult to reuse your pages and maintain your application. The other approach that WPF provides is the PageFunction class. A PageFunction is a derived version of the Page class that adds the ability to return a result. In a way, a PageFunction is analogous to a dialog box, while a page is analogous to a window. To create a PageFunction in Visual Studio, right-click your project in the Solution Explorer, and choose Add ä New Item. Next, select the WPF category, choose the Page Function (WPF) template, enter a file name, and click Add. The markup for a PageFunction is nearly identical to the markup you use for a Page. The difference is the root element, which is instead of . Technically, the PageFunction is a generic class. It accepts a single type parameter, which indicates the data type that’s used for the PageFunction’s return value. By default, every new page function is parameterized by string (which means it returns a single string as its return value). However, you can easily modify that detail by changing the TypeArguments attribute in the element. In the following example, the PageFunction returns an instance of a custom class named Product. In order to support this design, the element maps the appropriate namespace (NavigationAplication) to a suitable XML prefix (local), which is then used when setting the TypeArguments attribute.

Incidentally, as long as you set the TypeArguments attribute in your markup, you don’t need to specify the same information in your class declaration. Instead, the XAML parser will generate the correct class automatically. That means this code is enough to declare the page function shown earlier: public partial class SelectProductPageFunction { ... } Although this more explicit code works just as well: public partial class SelectProductPageFunction: PageFunction { ... } Visual Studio uses this more explicit syntax when you create a PageFunction. By default, all new PageFunction classes that Visual Studio creates derive from PageFunction. The PageFunction needs to handle all its navigation programmatically. When you click a button or a link that finishes the task, your code must call the PageFunction.OnReturn() method. At this point, you supply the object you want to return, which must be an instance of the class you specified in the declaration. Or you can supply a null value, which indicates that the task was not completed. Here’s an example with two event handlers: private void lnkOK_Click(object sender, RoutedEventArgs e) { // Return the selection information. OnReturn(new ReturnEventArgs(lstProducts.SelectedValue)); } private void lnkCancel_Click(object sender, RoutedEventArgs e) { // Indicate that nothing was selected. OnReturn(null); } Using the PageFunction is just as easy. The calling page needs to instantiate the PageFunction programmatically because it needs to hook up an event handler to the PageFunction.Returned event. (This extra step is required because the NavigationService.Navigate() method is asynchronous and returns immediately.) SelectProductPageFunction pageFunction = new SelectProductPageFunction(); pageFunction.Return += new ReturnEventHandler( SelectProductPageFunction_Returned); this.NavigationService.Navigate(pageFunction); When the user finishes using the PageFunction and clicks a link that calls OnReturn(), the PageFunction.Returned event fires. The returned object is available through the ReturnEventArgs.Result property: private void SelectProductPageFunction_Returned(object sender, ReturnEventArgs e) 765

CHAPTER 24 n PAGES AND NAVIGATION

{ Product product = (Product)e.Result; if (e != null) lblStatus.Text = "You chose: " + product.Name; } Usually, the OnReturn() method marks the end of a task, and you don’t want the user to be able to navigate back to the PageFunction. You could use the NavigationService.RemoveBackEntry() method to implement this, but there’s an easier approach. Every PageFunction also provides a property named RemoveFromJournal. If you set this to true, the page is automatically removed from the history when it calls OnReturn(). By adding the PageFunction to your application, you now have the ability to use a different sort of navigation topology. You can designate one page as a central hub and allow users to perform various tasks through page functions, as shown in Figure 24-11. Often, a PageFunction will call another page function. In this case, the recommended way to handle the navigation process after it’s complete is to use a chained series of OnReturn() calls. In other words, if PageFunction1 calls PageFunction2, which then calls PageFunction3, when PageFunction3 calls OnReturn(), it triggers the Returned event handler in PageFunction2, which then calls OnReturn(), which then fires the Returned event in PageFunction1, which finally calls OnReturn() to end the whole process. Depending on what you’re trying to accomplish, it may be necessary to pass your return object up through the whole sequence until it reaches a root page.

Figure 24-11. Linear navigation 766

CHAPTER 24 n PAGES AND NAVIGATION

XAML Browser Applications XBAPs are page-based applications that run inside the browser. XBAPs are full-blown WPF applications, with a few key differences: •

They run inside the browser window. They can take the entire display area for the web page, or you can place them somewhere inside an ordinary HTML document by using the tag (as you’ll see shortly).

n Note The technical reality is that any type of WPF application, including an XBAP, runs as a separate process managed by the Common Language Runtime (CLR). An XBAP appears to run “inside” the browser simply because it displays all its content in the browser window. This is different from the model used by plug-ins and Silverlight applications, which are loaded inside the browser process.

•฀



They usually have limited permissions. Although it’s possible to configure an XBAP so that it requests full trust permissions, the goal is to use XBAP as a lighter-weight deployment model that allows users to run WPF applications without allowing potentially risky code to execute. The permissions given to an XBAP are the same as the permissions given to a .NET application that’s run from the Web or local intranet, and the mechanism that enforces these restrictions (code access security) is the same. That means that by default an XBAP cannot write files, interact with other computer resources (such as the Registry), connect to databases, or pop up full-fledged windows. They aren’t installed. When you run an XBAP, the application is downloaded and cached in the browser. However, it doesn’t remain installed on the computer. This gives you the instant-update model of the Web. In other words, every time a user returns to use an application, the newest version is downloaded if it doesn’t exist in the cache.

The advantage of XBAPs is that they offer a prompt-free experience. If .NET is installed, a client can surf to an XBAP in the browser and start using it just like a Java applet, a Flash movie, or a JavaScriptenhanced web page. There’s no installation prompt or security warning. The obvious trade-off is that you need to abide by a stringently limited security model. If your application needs greater capabilities (for example, it needs to read or write arbitrary files, interact with a database, use the Windows Registry, and so on), you’re far better off creating a stand-alone Windows application. You can then offer a streamlined (but not completely seamless) deployment experience for your application by using ClickOnce deployment, which is described in Chapter 33. Currently, two browsers are able to launch XBAP applications: Internet Explorer and Firefox. Chrome doesn’t support XBAP applications (although you can google your way to some unsupported hacks that some developers have used to make them work on a specific machine). As with any .NET application, the client computer also needs the version of .NET that you targeted (when you compiled your application) in order to run it.

767

CHAPTER 24 n PAGES AND NAVIGATION

Creating an XBAP Any page-based application can become an XBAP, although Visual Studio forces you to create a new project with the WPF Browser Application template in order to create one. The difference is four key elements in the .csproj project file, as shown here: True False .xbap Internet These tags tell WPF to host the application in the browser (HostInBrowser), to cache it along with other temporary Internet files rather than install it permanently (Install), to use the extension .xbap (ApplicationExtension), and to request the permissions for only the Internet zone (TargetZone). The fourth part is optional. As you’ll see shortly, it’s technically possible to create an XBAP that has greater permissions. However, XBAPs almost always run with the limited permissions available in the Internet zone, which is the key challenge to programming one successfully.

n Tip The .csproj file also includes other XBAP-related tags that ensure the right debugging experience. The easiest way to change an application from an XBAP into a page-based application with a stand-alone window (or vice versa) is to create a new project of the desired type, and then import all the pages from the old project. After you’ve created your XBAP, you can design your pages and code them in exactly the same way as if you were using the NavigationWindow. For example, you set the StartupUri in the App.xaml file to one of your pages. When you compile your application, an .xbap file is generated. You can then request that .xbap file in Internet Explorer or Firefox, and (provided the .NET Framework is installed) the application runs in limited trust mode automatically. Figure 24-12 shows an XBAP in Internet Explorer.

Figure 24-12. An XBAP in the browser

768

CHAPTER 24 n PAGES AND NAVIGATION

The XBAP application runs just the same as an ordinary WPF application, provided you don’t attempt to perform any restricted actions (such as showing a stand-alone window). If you’re running your application in Internet Explorer, the browser buttons take the place of the buttons on the NavigationWindow, and they show the back and forward page lists. On previous versions of Internet Explorer and in Firefox, you get a new set of navigation buttons at the top of your page, which isn’t quite as nice.

Deploying an XBAP Although you could create a setup program for an XBAP (and you can run an XBAP from the local hard drive), there’s rarely a reason to take this step. Instead, you can simply copy your compiled application to a network share or a virtual directory.

n Note You can get a similar effect by using loose XAML files. If your application consists entirely of XAML pages with no code-behind files, you don’t need to compile it at all. Instead, just place the appropriate .xaml files on your web server and let users browse to them directly. Of course, loose XAML files obviously can’t do as much as their compiled counterparts, but they’re suitable if you simply need to display a document, a graphic, or an animation, or if you wire up all the functionality you need through declarative binding expressions. Unfortunately, deploying an XBAP isn’t as simple as just copying the .xbap file. You need to copy the following three files to the same folder: •

•฀



ApplicationName.exe: This file has the compiled IL code, just as it does in any .NET application. ApplicationName.exe.manifest: This file is an XML document that indicates requirements of your application (for example, the version of the .NET assemblies you used to compile your code). If your application uses other DLLs, you can make these available in the same virtual directory as your application, and they’ll be downloaded automatically. ApplicationName.xbap: The .xbap file is another XML document. It represents the entry point to your application. In other words, this is the file that the user needs to request in the browser to install your XBAP. The markup in the .xbap file points to the application file and includes a digital signature that uses the key you’ve chosen for your project.

After you’ve transferred these files to the appropriate location, you can run the application by requesting the .xbap file in Internet Explorer or Firefox. It makes no difference whether the files are on the local hard drive or a remote web server—you can request them in the same way.

n Tip It’s tempting, but don’t run the .exe file. If you do, nothing will happen. Instead, double-click the .xbap file in Windows Explorer (or type its path in the address box in your web browser). Either way, all three files must be present, and the browser must be able to recognize the .xbap file extension. The browser will show a progress page as it begins downloading the .xbap file (Figure 24-13). This downloading process is essentially an installation process that copies the .xbap application to the local 769

CHAPTER 24 n PAGES AND NAVIGATION

Internet cache. When the user returns to the same remote location on subsequent visits, the cached version will be used. (The only exception occurs if there’s a newer version of the XBAP on the server, as described in the next section.)

Figure 24-13. Running an .xbap application for the first time When you create a new XBAP application, Visual Studio also includes an automatically generated certificate file with a name such as ApplicationName_TemporaryKey.pfx. This certificate contains a public/ private key pair that’s used to add a signature to your .xbap file. If you publish an update to your application, you’ll need to sign it with the same key to ensure that the digital signature remains consistent. Rather than using the temporary key, you may want to create a key of your own (which you can then share between projects and protect with a password). To do so, double-click the Properties node under your project in the Solution Explorer and use the options in the Signing tab.

Updating an XBAP When you debug an XBAP application, Visual Studio always rebuilds your XBAP and loads the latest version in the browser. You don’t need to take any extra steps. This isn’t the case if you request an XBAP directly in your browser. When running XBAPs in this fashion, there’s a potential problem. If you rebuild the application, deploy it to the same location, and then rerequest it in the browser, you won’t necessarily get the updated version. Instead, you’ll continue running the older cached copy of the application. This is true even if you close and reopen the browser window, click the browser’s Refresh button, and increment the assembly version of your XBAP. You can manually clear the ClickOnce cache, but this obviously isn’t a convenient solution. Instead, you need to update the publication information that’s stored in your .xbap file so that the browser recognizes that your newly deployed XBAP represents a new version of your application. Updating the assembly version isn’t enough to trigger an update; instead, you need to update the publish version.

n Note The extra step of updating the publication information is required because the download-and-cache functionality of an .xbap is built by using the plumbing from ClickOnce, the deployment technology that you’ll learn 770

CHAPTER 24 n PAGES AND NAVIGATION

about in Chapter 33. ClickOnce uses the publication version to determine when an update should be applied. This allows you to build an application multiple times for testing (each time with a different assembly version number) but increment the publish version only when you want to deploy a new release. The easiest way to rebuild your application and apply a new publication version is to choose Build ä Publish [ProjectName] from the Visual Studio menu (and then click Finish). You don’t need to use the publication files (which are placed in the Publish folder under your project directory). That’s because the newly generated .xbap file in the Debug or Release folder will indicate the new publish version. All you need to do is deploy this .xbap file (along with the .exe and .manifest files) to the appropriate location. The next time you request the .xbap file, the browser will download the new application files and cache them. You can see the current publish version by double-clicking the Properties item in the Solution Explorer, choosing the Publish tab, and looking at the settings in the Publish Version section at the bottom of the tab. Make sure you keep the Automatically Increment Revision with Each Publish setting switched on so that the publish version is incremented when you publish your application, which clearly marks it as a new release.

XBAP Security The most challenging aspect to creating an XBAP is staying within the confines of the limited security model. Ordinarily, an XBAP runs with the permissions of the Internet zone. This is true even if you run your XBAP from the local hard drive. The .NET Framework uses code access security (a core feature that it has had since version 1.0) to limit what your XBAP is allowed to do. In general, the limitations are designed to correspond with what comparable Java or JavaScript code could do in an HTML page. For example, you’ll be allowed to render graphics, perform animations, use controls, show documents, and play sounds. You can’t access computer resources such as files, the Windows Registry, databases, and so on. One simple way to find out whether an action is allowed is to write some test code and try it. The WPF documentation also has full details. Table 24-3 provides a quick list of significant supported and disallowed features. Table 24-3. Key WPF Features and the Internet Zone

Allowed

Not Allowed

All core controls, including the RichTextBox

Windows Forms controls (through interop)

Pages, the MessageBox, and the OpenFileDialog

Stand-alone windows and other dialog boxes (such as the SaveFileDialog)

Isolated storage

Access to the file system and access to the Registry

2-D and 3-D drawing, audio and video, flow and XPS documents, and animation

Bitmap effects and pixel shaders (presumably because they rely on unmanaged code)

“Simulated” drag-and-drop (code that responds to mouse-move events)

Windows drag-and-drop

ASP.NET (.asmx) web services and Windows Communication Foundation (WCF) services

Most advanced WCF features (non-HTTP transport, server-initiated connections, and WS-* protocols) and communicating with any server other than the one where the XBAP is hosted

771

CHAPTER 24 n PAGES AND NAVIGATION

So what’s the effect if you attempt to use a feature that’s not allowed in the Internet zone? Ordinarily, your application fails as soon as it runs the problematic code with a SecurityException. Figure 24-14 shows the result of running an ordinary XBAP that attempts to perform a disallowed action and not handling the resulting SecurityException.

Figure 24-14. An unhandled exception in an XBAP

Full-Trust XBAPs It’s possible to create an XBAP that runs with full trust, although this technique isn’t recommended. To do so, double-click the Properties node in the Solution Explorer, choose the Security tab, and select This Is a Full Trust Application. However, users won’t be able to run your application from a web server or virtual directory anymore. Instead, you’ll need to take one of the following steps to ensure that your application is allowed to execute in full trust: •

•฀ •

772

Run the application from the local hard drive. (You can launch the .xbap file like an executable file by double-clicking it or using a shortcut.) You may want to use a setup program to automate the installation process. Add the certificate you’re using to sign the assembly (by default, it’s a .pfx file) to the Trusted Publishers store on the target computer. You can do this by using the certmgr.exe tool. Assign full trust to the website URL or network computer where the .xbap file is deployed. To do this, you need to use the Microsoft .NET 2.0 Framework Configuration tool (which you can find in the Administrative Tools section of the Control Panel, accessed from the Start menu).

CHAPTER 24 n PAGES AND NAVIGATION

The first option is the most straightforward. However, all of these steps require an awkward configuration or deployment step that must be performed on everyone else’s computer. As a result, they aren’t ideal approaches.

n Note If your application requires full trust, you should consider building a stand-alone WPF application and deploying it by using ClickOnce (as described in Chapter 33). The real goal of the XBAP model is to create a WPF equivalent to the traditional HTML-and-JavaScript website (or Flash applet).

Combination XBAP/Stand-Alone Applications So far, you’ve considered how to deal with XBAPs that may run under different levels of trust. However, there’s another possibility. You might take the same application and deploy it as both an XBAP and a standalone application that uses the NavigationWindow (as described in the beginning of this chapter). In this situation, you don’t necessarily need to test your permissions. It may be enough to write conditional logic that tests the static BrowserInteropHelper.IsBrowserHosted property and assumes that a browser-hosted application is automatically running with Internet zone permissions. The IsBrowserHosted property is true if your application is running inside the browser. Unfortunately, changing between a stand-alone application and an XBAP is not an easy feat, because Visual Studio doesn’t provide direct support. However, other developers have created tools to simplify the process. One example is the flexible Visual Studio project template found at http://scorbs. com/2006/06/04/vs-template-flexible-application. It allows you to create a single project file and choose between an XBAP and a stand-alone application by using the build configuration list. In addition, it provides a compilation constant you can use to conditionally compile code in either scenario, as well as an application property you can use to create binding expressions that conditionally show or hide certain elements based on the build configuration. Another option is to place your pages in a reusable class library assembly. Then you can create two top-level projects: one that creates a NavigationWindow and loads the first page inside and another that launches the page directly as an XBAP. This makes it easier to maintain your solution, but will probably still need some conditional code that tests the IsBrowserHosted property and checks specific CodeAccessPermission objects.

Coding for Different Security Levels In some situations, you might choose to create an application that can function in different security contexts. For example, you may create an XBAP that can run locally (with full trust) or be launched from a website. In this case, it’s key to write flexible code that can avoid an unexpected SecurityException. Every separate permission in the code-access security model is represented by a class that derives from CodeAccessPermission. You can use this class to check whether your code is running with the required permission. The trick is to call the CodeAccessPermission.Demand() method, which requests a permission. This demand fails (throwing a SecurityException) if the permission isn’t granted to your application. Here’s a simple function that allows you to check for a given permission:

773

CHAPTER 24 n PAGES AND NAVIGATION

private bool CheckPermission(CodeAccessPermission requestedPermission) { try { // Try to get this permission. requestedPermission.Demand(); return true; } catch { return false; } } You can use this function to write code like this, which checks to see whether the calling code has permission to write to a file before attempting the operation: // Create a permission that represents writing to a file. FileIOPermission permission = new FileIOPermission( FileIOPermissionAccess.Write, @"c:\highscores.txt"); // Check for this permission. if (CheckPermission(permission)) { // (It's safe to write to the file.) } else { // (It's not allowed. Do nothing or show a message.) } The obvious disadvantage with this code is that it relies on exception handling to control normal program flow, which is discouraged (both because it leads to unclear code and because it adds overhead). An alternative would be to simply attempt to perform the operation (such as writing to a file) and then catch any resulting SecurityException. However, this approach makes it more likely that you’ll run into a problem halfway through a task, when recovery or cleanup may be more difficult.

Using Isolated Storage In many cases, you may be able to fall back on less-powerful functionality if a given permission isn’t available. For example, although code running in the Internet zone isn’t allowed to write to arbitrary locations on the hard drive, it is able to use isolated storage. Isolated storage provides a virtual file system that lets you write data to a small, user-specific and application-specific slot of space. The actual location on the hard drive is obfuscated (so there’s no way to know exactly where the data will be written beforehand), and the total space available is typically 1 MB. A typical location is a path in the form c:\ Users\[UserName]\AppData\Local\IsolatedStorage\[GuidIdentifier]. Data in one user’s isolated store is restricted from all other nonadministrative users.

774

CHAPTER 24 n PAGES AND NAVIGATION

n Note Isolated storage is the .NET equivalent of persistent cookies in an ordinary web page. It allows small bits of information to be stored in a dedicated location that has specific controls in place to prevent malicious attacks (such as code that attempts to fill the hard drive or replace a system file). Isolated storage is covered in detail in the .NET reference. However, it’s quite easy to use because it exposes the same stream-based model as ordinary file access. You simply use the types in the System.IO. IsolatedStorage namespace. Typically, you’ll begin by calling the IsolatedStorageFile. GetUserStoreForApplication() method to get a reference to the isolated store for the current user and application. (Each application gets a separate store.) You can then create a virtual file in that location by using the IsolatedStorageFileStream. Here’s an example: // Create a permission that represents writing to a file. string filePath = System.IO.Path.Combine(appPath, "highscores.txt"); FileIOPermission permission = new FileIOPermission( FileIOPermissionAccess.Write, filePath); // Check for this permission. if (CheckPermission(permission)) { // Write to local hard drive. try { using (FileStream fs = File.Create(filePath)) { WriteHighScores(fs); } } catch { ... } } else { // Write to isolated storage. try { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream( "highscores.txt", FileMode.Create, store)) { WriteHighScores(fs); } } catch { ... } } You can also use methods such as IsolatedStorageFile.GetFileNames() and IsolatedStorageFile. GetDirectoryNames() to enumerate the contents of the isolated store for the current user and application. Remember that if you’ve made the decision to create an ordinary XBAP that will be deployed on the Web, you already know that you won’t have FileIOPermission for the local hard drive (or anywhere else). If 775

CHAPTER 24 n PAGES AND NAVIGATION

this is the type of application you’re designing, there’s no reason to use the conditional code shown here. Instead, your code can jump straight to the isolated storage classes.

n Tip To determine the amount of available isolated storage space, check IsolatedStorageFile.AvailableFreeSpace. You should use code that checks this detail and refrains from writing data if the available space is insufficient. To increase the amount of data you can pack into isolated storage, you may want to wrap your file-writing operations with the DeflateStream or GZipStream. Both types are defined in the System.IO.Compression namespace and use compression to reduce the number of bytes required to store data.

Simulating Dialog Boxes with the Pop-up Control Another limited feature in XBAPs is the ability to open a secondary window. In many cases, you’ll use navigation and multiple pages instead of separate windows, and you won’t miss this functionality. However, sometimes it’s convenient to pop open a window to show some sort of a message or collect input. In a stand-alone Windows application, you’d use a modal dialog box for this task. In an XBAP, there’s another possibility—you can use the Popup control that was introduced in Chapter 6. The basic technique is easy. First, you define the Popup in your markup, making sure to set its StaysOpen property to true so it will remain open until you close it. (There’s no point in using the PopupAnimation or AllowsTransparency property, because neither will have any effect in a web page.) Include suitable buttons, such as OK and Cancel, and set the Placement property to Center so the pop-up will appear in the middle of the browser window. Here’s a simple example:







Please enter your name.

OK Cancel



At the appropriate time (for example, when a button is clicked), disable the rest of your user interface and show the Popup. To disable your user interface, you can set the IsEnabled property of some top-level container, such as a StackPanel or a Grid, to false. (You can also set the Background property of the page to 776

CHAPTER 24 n PAGES AND NAVIGATION

gray, which will draw the user’s attention to the Popup.) To show the Popup, simply set its IsVisible property to true. Here’s an event handler that shows the previously defined Popup: private void cmdStart_Click(object sender, RoutedEventArgs e) { DisableMainPage(); } private void DisableMainPage() { mainPage.IsEnabled = false; this.Background = Brushes.LightGray; dialogPopUp.IsOpen = true; } When the user clicks the OK or Cancel button, close the Popup by setting its IsVisible property to false, and reenable the rest of the user interface: private void dialog_cmdOK_Click(object sender, RoutedEventArgs e) { // Copy name from the Popup into the main page. lblName.Content = "You entered: " + txtName.Text; EnableMainPage(); } private void dialog_cmdCancel_Click(object sender, RoutedEventArgs e) { EnableMainPage(); } private void EnableMainPage() { mainPage.IsEnabled = true; this.Background = null; dialogPopUp.IsOpen = false; } Figure 24-15 shows the Popup in action.

777

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-15. Simulating a dialog box with the Popup Using the Popup control to create this work-around has one significant limitation. To ensure that the Popup control can’t be used to spoof legitimate system dialog boxes, the Popup window is constrained to the size of the browser window. If you have a large Popup window and a small browser window, this could chop off some of your content. One solution, which is demonstrated with the sample code for this chapter, is to wrap the full content of the Popup control in a ScrollViewer with the VerticalScrollBarVisibility property set to Auto. There’s one other, even stranger option for showing a dialog box in a WPF page. You can use the Windows Forms library from .NET 2.0. You can safely create and show an instance of the System.Windows. Forms.Form class (or any custom form that derives from Form), because it doesn’t require unmanaged code permission. In fact, you can even show the form modelessly, so the page remains responsive. The only drawback is that a security balloon automatically appears superimposed over the form and remains until the user clicks the warning message (as shown in Figure 24-16). You’re also limited in what you can show in the form. Windows Forms controls are acceptable, but WPF content isn’t allowed. For an example of this technique, refer to the sample code for this chapter.

778

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-16. Using a .NET 2.0 form for a dialog box

Embedding an XBAP in a Web Page Usually, an XBAP is loaded directly in the browser so it takes up all the available space. However, you can have one other option: you can show an XBAP inside a portion of an HTML page, along with other HTML content. All you need to do is create an HTML page that uses the tag to point to your .xbap file, as shown here:

An HTML Page That Contains an XBAP

Regular HTML Content

More HTML Content

Using an is a relatively uncommon technique, but it does allow you to pull off a few new tricks, such as displaying more than one XBAP in the same browser window or making it easy to add your application to a site that’s powered by a content-management system such as WordPress.

The WebBrowser Control As you’ve seen in this chapter, WPF blurs the boundaries between traditional desktop applications and the Web. Using pages, you can create WPF applications with web-style navigation. Using XBAPs, you can run WPF inside a browser window, like a web page. And using the Frame control, you can perform the reverse trick and put an HTML web page into a WPF window. However, when you use the Frame to show HTML content, you give up all control over that content. You have no way to inspect it or to follow along as the user navigates to a new page by clicking a link. You certainly have no way to call JavaScript methods in an HTML web page or let them call your WPF code. This is where the WebBrowser control comes into the picture.

779

CHAPTER 24 n PAGES AND NAVIGATION

n Tip The Frame is a good choice if you need a container that can switch seamlessly between WPF and HTML content. The WebBrowser is a better choice if you need to examine the object model of a page, limit or monitor page navigation, or create a path through which JavaScript and WPF code can interact. Both the WebBrowser and the Frame (when it’s displaying HTML content) show a standard Internet Explorer window. This window has all the features and frills of Internet Explorer, including JavaScript, Dynamic HTML, ActiveX controls, and plug-ins. However, the window doesn’t include additional details such as a toolbar, address bar, or status bar (although you can add all of these ingredients to your form by using other controls). The WebBrowser isn’t written from scratch in managed code. Like the Frame (when it’s displaying HTML content), it wraps the shdocvw.dll COM component, which is a part of Internet Explorer and is included with Windows. As a side effect, the WebBrowser and the Frame have a few graphical limitations that other WPF controls don’t share. For example, you can’t place other elements on top of the HTML content that’s displayed in these controls, and you can’t use a transform to skew or rotate it.

n Note As a feature, WPF’s ability to show HTML (either through the Frame or the WebBrowser) isn’t nearly as useful as the page model or XBAPs. However, you might choose to use it in specialized situations where you have already developed HTML content that you don’t want to replace. For example, you might use the WebBrowser to show HTML documentation inside an application, or to allow a user to jump between the functionality in your application and that in a third-party website.

Navigating to a Page After you’ve placed the WebBrowser control on a window, you need to point it to a document. The easiest approach is to set the Source property with a URI. Set this to a remote URL (for example, http://mysite. com/mypage.html) or a fully qualified file path (such as file:///c:\mydocument.text). The URI can point to any file type that Internet Explorer can open, although you’ll almost always use the WebBrowser to show HTML pages.

n Note You can also direct the WebBrowser to a directory. For example, set the Url property to file:///c:\. In this case, the WebBrowser window becomes the familiar Explorer-style file browser, allowing the user to open, copy, paste, and delete files. However, the WebBrowser doesn’t provide events or properties that allow you to restrict this ability (or even monitor it), so tread carefully! In addition to the Source property, you can navigate to a URL by using any of the navigation methods described in Table 24-4.

780

CHAPTER 24 n PAGES AND NAVIGATION

Table 24-4. Navigation Methods for the WebBrowser

Method

Description

Navigate()

Navigates to the new URL you specify. If you use the overloaded method, you can choose to load this document into a specific frame, post back data, and send additional HTML headers.

NavigateToString()

Loads the content from the string you supply, which should contain the full HTML content of a web page. This provides some interesting options, including the ability to retrieve HTML text from a resource in your application and display it.

NavigateToStream()

Loads the content from a stream that contains an HTML document. This allows you to open a file and feed it straight into the WebBrowser for rendering, without needing to hold the whole HTML content in memory at once.

GoBack() and GoForward()

Move to the previous or next document in the navigation history. To avoid errors, you should check the CanGoBack and CanGoForward properties before using these methods, because attempting to move to a document that does not exist (for example, trying to move back while on the first document in the history) will cause an exception.

Refresh()

Reloads the current document.

All WebBrowser navigation is asynchronous. That means your code continues executing while the page is downloading. The WebBrowser also adds a small set of events, including the following: •

•฀ •

Navigating fires when you set a new URL, or the user clicks a link. You can inspect the URL, and cancel navigation by setting e.Cancel to true. Navigated fires after Navigating, just before the web browser begins downloading the page. LoadCompleted fires when the page is completely loaded. This is your chance to process the page.

Building a DOM Tree Using the WebBrowser, you can create C# code that browses through the tree of HTML elements on a page. You can even modify, remove, or insert elements as you go, using a programming model that’s similar to the HTML DOM used in web browser scripting languages such as JavaScript. In the following sections, you’ll see both techniques. Before you can use the DOM with the WebBrowser, you need to add a reference to the Microsoft HTML Object Library (mshtml.tlb). This is a COM library, so Visual Studio needs to generate a managed wrapper. To do so, choose Project ä Add Reference, pick the COM tab, select the Microsoft HTML Object Library, and click OK. The starting point for exploring the content in a web page is the WebBrowser.Document property. This property provides an HTMLDocument object that represents a single web page as a hierarchical collection of IHTMLElement objects. You’ll find a distinct IHTMLElement object for each tag in your web page, including paragraphs (

), hyperlinks (), images (), and all the other familiar ingredients of HTML markup.

781

CHAPTER 24 n PAGES AND NAVIGATION

The WebBrowser.Document property is read-only. That means that although you can modify the linked HtmlDocument, you can’t create a new HtmlDocument object on the fly. Instead, you need to set the Source property or call the Navigate() method to load a new page. After the WebBrowser. LoadCompleted event fires, you can access the Document property.

n Tip Building the HTMLDocument takes a short but distinctly noticeable amount of time (depending on the size and complexity of the web page). The WebBrowser won’t actually build the HTMLDocument for the page until you try to access the Document property for the first time. Each IHTMLElement object has a few key properties: •

tagName is the actual tag, without the angle brackets. For example, an anchor tag takes the form …, and has the tag name A.

•฀

id contains the value of the id attribute, if specified. Often, elements are identified with unique id attributes if you need to manipulate them in an automated tool or server-side code.

•฀

children provides a collection of IHTMLElement objects, one for each contained tag.

•฀

innerHTML shows the full content of the tag, including any nested tags and their content.

•฀

innerText shows the full content of the tag and the content of any nested tags. However, it strips out all the HTML tags.



outerHTML and outerText play the same role as innerHTML and innerText, except they include the current tag (rather than just its contents).

To get a better understanding of innerText, innerHTML, and outerHTML, consider the following tag:

Here is some interesting text.

The innerText for this tag is as follows: Here is some interesting text. This is the innerHTML: Here is some interesting text. Finally, the outerHTML is the full tag:

Here is some interesting text.

In addition, you can retrieve the attribute value for an element by name, by using the IHTMLElement. getAttribute() method. To navigate the document model for an HTML page, you simply move through the children collections of each IHTMLElement. The following code performs this task in response to a button click, and builds a tree that shows the structure of elements and the content on the page (see Figure 24-17).

782

CHAPTER 24 n PAGES AND NAVIGATION

private void cmdBuildTree_Click(object sender, System.EventArgs e) { // Analyzing a page takes a nontrivial amount of time. // Use the hourglass cursor to warn the user. this.Cursor = Cursors.Wait; // Get the DOM object from the WebBrowser control. HTMLDocument dom = (HTMLDocument)webBrowser.Document; // Process all the HTML elements on the page, and display them // in the TreeView named treeDOM. ProcessElement(dom.documentElement, treeDOM.Items); this.Cursor = null; } private void ProcessElement(IHTMLElement parentElement, ItemCollection nodes) { // Scan through the collection of elements. foreach (IHTMLElement element in parentElement.children) { // Create a new node that shows the tag name. TreeViewItem node = new TreeViewItem(); node.Header = ""; nodes.Add(node); if ((element.children.length == 0) && (element.innerText != null)) { // If this element doesn't contain any other elements, add // any leftover text content as a new node. node.Items.Add(element.innerText); } else { // If this element contains other elements, process them recursively. ProcessElement(element, node.Items); } } }

783

CHAPTER 24 n PAGES AND NAVIGATION

Figure 24-17. A tree model of a web page If you want to find a specific element without digging through all the layers of the web page, you have a couple of simpler options. You can use the HTMLDocument.all collection, which allows you to retrieve any element on the page by using its id attribute. If you need to retrieve an element that doesn’t have an id attribute, you can use the HTMLDocument method getElementsByTagName().

Scripting a Web Page with .NET Code The last trick you’ll see with the WebBrowser is something even more intriguing: the ability to react to webpage events in your Windows code. The WebBrowser makes this technique remarkably simple. The first step is to create a class that will receive the messages from the JavaScript code. To make it scriptable, you must add the ComVisible attribute (from the System.Runtime.InteropServices namespace) to the class declaration: [ComVisible(true)] public class HtmlBridge { public void WebClick(string source) { MessageBox.Show("Received: " + source); } } Next, you need to register an instance of this class with the WebBrowser. You do this by setting the WebBrowser.ObjectForScripting property: 784

CHAPTER 24 n PAGES AND NAVIGATION

public MainWindow() { InitializeComponent(); webBrowser.Navigate("file:///" + System.IO.Path.Combine( Path.GetDirectoryName(Application.ResourceAssembly.Location), "sample.htm")); webBrowser.ObjectForScripting = new HtmlBridge(); } Now the sample.html web page will be able to call any public method in the HtmlBridge class, including HtmlBridge.WebClick(). In the web page, you use JavaScript code to trigger the event. Here, the trick is the window.external object, which represents the linked .NET object. Using this object, you specify a method that you want to trigger; for example, use window.external.HelloWorld() if you want to call a public method named HelloWorld in the .NET object.

n Caution If you use JavaScript to trigger an event from your web page, make sure that your class doesn’t include any other public methods that aren’t related to web access. A nefarious user could theoretically find the HTML source, and modify it to call a different method than the one you intend. Ideally, the scriptable class should contain only web-related methods to ensure security. To build the JavaScript command into your web page, you first need to decide to which web-page event you want to react. Most HTML elements support a small number of events, and some of the most useful include the following: •

onFocus occurs when a control receives focus.

•฀

onBlur occurs when focus leaves a control.

•฀

onClick occurs when the user clicks a control.

•฀

onChange occurs when the user changes the value of certain controls.



onMouseOver occurs when the user moves the mouse pointer over a control.

To write a JavaScript command that responds to one of these events, you simply add an attribute with that name to the element tag. For example, if you have an image tag that looks like this:

you can add an onClick attribute that triggers the HelloWorld() method in your linked .NET class whenever the user clicks the image:

Figure 24-18 shows an application that puts it all together. In this example, a WebBrowser control shows a local HTML file that contains four buttons, each of which is a graphical image. But when the user clicks a button, the image uses the onClick attribute to trigger the HtmlBridge.WebClick() method:

10,10,0 0,10,10 10,0,10 10,10,0 10,10,10 10,10,10"

Most of the mesh points aren’t mapped at all. In fact, the only points that are mapped are these four, which define the face of the cube that’s oriented toward the camera: (0,0,0) (0,0,10) (0,10,0) (0,10,10) Because this is actually a flat surface, mapping is relatively easy. You can choose a set of TextureCoordinates for this face by removing the dimension that has a value of 0 in all four points. (In this example, that’s the X coordinate because the visible face is on the left side of the cube.) Here’s the TextureCoordinates that fill this requirement:

862

CHAPTER 27 n 3-D DRAWING

(0,0) (0,10) (10,0) (10,10) The TextureCoordinates collection uses relative coordinates. To keep things simple, you may want to use 1 to indicate the maximum value. In this example, that transformation is easy: (0,0) (0,1) (1,0) (1,1) This set of TextureCoordinates essentially tells WPF to take the point (0, 0) at the bottom left of the rectangle that represents the brush content, and map that to the corresponding point (0, 0, 0) in 3-D space. Similarly, take the bottom-right corner (0, 1) and map that to (0, 0, 10), make the top-left corner (1, 0) map to (0, 10, 0), and make the top-right corner (1, 1) map to (0, 10, 10). Here’s the cube mesh that uses this texture mapping. All the other coordinates in the Positions collection are mapped to (0, 0), so that the texture is not applied to these areas: (dt.Rows.Count - 1)) break; point.X = col1_X; text = GetFormattedText(dt.Rows[i]["ModelNumber"].ToString()); dc.DrawText(text, point); // Add second column. text = GetFormattedText(dt.Rows[i]["ModelName"].ToString()); point.X = col2_X; dc.DrawText(text, point); point.Y += text.Height; } } return new DocumentPage(visual, pageSize, new Rect(pageSize), new Rect(pageSize)); }

955

CHAPTER 29 n PRINTING

Now that the StoreDateSetDocumentPaginator is complete, you can use it whenever you want to print the contents of the DataTable with the product list, as shown here: PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) { StoreDataSetPaginator paginator = new StoreDataSetPaginator(ds.Tables[0], new Typeface("Calibri"), 24, 96*0.75, new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight)); printDialog.PrintDocument(paginator, "Custom-Printed Pages"); } The StoreDataSetPaginator has a certain amount of flexibility built in—for example, it can work with different fonts, margins, and paper sizes—but it can’t deal with data that has a different schema. Clearly, there’s still room in the WPF library for a handy class that could accept data, column and row definitions, headers and footers, and so on, and then print a properly paginated table. WPF doesn’t have anything like this currently, but you can expect third-party vendors to provide components that fill the gaps.

Print Settings and Management So far, you’ve focused all your attention on two methods of the PrintDialog class: PrintVisual() and PrintDocument(). This is all you need to use to get a decent printout, but you have more to do if you want to manage printer settings and jobs. Once again, the PrintDialog class is your starting point.

Maintaining Print Settings In the previous examples, you saw how the PrintDialog class allows you to choose a printer and its settings. However, if you’ve used these examples to make more than one printout, you may have noticed a slight anomaly. Each time you return to the Print dialog box, it reverts to the default print settings. You need to pick the printer you want and adjust it all over again. Life doesn’t need to be this difficult. You have the ability to store this information and reuse it. One good approach is to store the PrintDialog as a member variable in your window. That way, you don’t need to create the PrintDialog before each new print operation—you just keep using the existing object. This works because the PrintDialog encapsulates the printer selection and printer settings through two properties: PrintQueue and PrintTicket. The PrintQueue property refers to a System.Printing.PrintQueue object, which represents the print queue for the selected printer. And as you’ll discover in the next section, the PrintQueue also encapsulates a good deal of features for managing your printer and its jobs. The PrintTicket property refers to a System.Printing.PrintTicket object, which defines the settings for a print job. It includes details such as print resolution and duplexing. If you want, you’re free to tweak the settings of a PrintTicket programmatically. The PrintTicket class even has a GetXmlStream() method and a SaveTo() method, both of which let you serialize the ticket to a stream, and a constructor that lets you recreate a PrintTicket object based on the stream. This is an interesting option if you want to persist specific print settings between application sessions. (For example, you could use this ability to create a “print profile” feature.) As long as these PrintQueue and PrintTicket properties remain consistent, the selected printer and its properties will remain the same each time you show the Print dialog box. So even if you need to create the PrintDialog box multiple times, you can simply set these properties to keep the user’s selections.

956

CHAPTER 29 n PRINTING

Printing Page Ranges You haven’t yet considered one of the features in the PrintDialog class. You can allow the user to choose to print only a subset of a larger printout using the Pages text box in the Page Range box. The Pages text box lets the user specify a group of pages by entering the starting and ending page (for example, 4–6) or pick a specific page (for example, 4). It doesn’t allow multiple page ranges (such as 1–3,5). The Pages text box is disabled by default. To switch it on, you simply need to set the PrintDialog. UserPageRangeEnabled property to true before you call ShowDialog(). The Selection and Current Page options remain disabled, because they aren’t supported by the PrintDialog class. You can also set the MaxPage and MinPage properties to constrain the pages that the user can pick. After you’ve shown the Print dialog box, you can determine whether the user entered a page range by checking the PageRangeSelection property. If it provides a value of UserPages, there’s a page range present. The PageRange property provides a PageRange property that indicates the starting page (PageRange. PageFrom) and ending page (PageRange.PageTo). It’s up to your printing code to take these values into account and print only the requested pages.

Managing a Print Queue Typically, a client application has a limited amount of interaction with the print queue. After a job is dispatched, you may want to display its status or (rarely) provide the option to pause, resume, or cancel the job. The WPF print classes go far beyond this level and allow you to build tools that can manage local or remote print queues. The classes in the System.Printing namespace provide the support for managing print queues. You can use a few key classes to do most of the work, and they’re outlined in Table 29-2. Table 29-2. Key Classes for Print Management

Name

Description

PrintServer and LocalPrintServer

Represents a computer that provides printers or another device that does. (This “other device” might include a printer with built-in networking or a dedicated piece of network hardware that acts as a print server.) Using the PrintServer class, you can get a collection of PrintQueue objects for that computer. You can also use the LocalPrintServer class, which derives from PrintServer and always represents the current computer. It adds a DefaultPrintQueue property that you can use to get (or set) the default printer and a static GetDefaultPrintQueue() method that you can use without creating a LocalPrintServer instance.

PrintQueue

Represents a configured printer on a print server. The PrintQueue class allows you to get information about that printer’s status and manage the print queue. You can also get a collection of PrintQueueJobInfo objects for that printer.

PrintSystemJobInfo

Represents a job that’s been submitted to a print queue. You can get information about its status and modify its state or delete it.

Using these basic ingredients, you can create a program that launches a printout without any user intervention. PrintDialog dialog = new PrintDialog(); // Pick the default printer. dialog.PrintQueue = LocalPrintServer.GetDefaultPrintQueue();

957

CHAPTER 29 n PRINTING

// Print something. dialog.PrintDocument(someContent, "Automatic Printout"); You can also create and apply a PrintTicket object to the PrintDialog to configure other print-related settings. More interestingly, you can delve deeper in the PrintServer, PrintQueue, and PrintSystemJobInfo classes to study what’s taking place. Figure 29-8 shows a simple program that allows you to browse the print queues on the current computer and see the outstanding jobs for each one. This program also allows you to perform some basic printer management tasks, such as suspending a printer (or a print job), resuming the printer (or print job), and canceling one job or all the jobs in a queue. By considering how this application works, you can learn the basics of the WPF print management model.

Figure 29-8. Browsing printer queues and jobs This example uses a single PrintServer object, which is created as a member field in the window class: private PrintServer printServer = new PrintServer(); When you create a PrintServer object without passing any arguments to the constructor, the PrintServer represents the current computer. Alternatively, you could pass the UNC path that points to a print server on the network, like this: private PrintServer printServer = new PrintServer(@"\\Warehouse\PrintServer");

958

CHAPTER 29 n PRINTING

Using the PrintServer object, the code grabs a list of print queues that represent the printers that are configured on the current computer. This step is easy—all you need to do is call the PrintServer. GetPrintQueues() method when the window is first loaded: private void Window_Loaded(object sender, EventArgs e) { lstQueues.DisplayMemberPath = "FullName"; lstQueues.SelectedValuePath = "FullName"; lstQueues.ItemsSource = printServer.GetPrintQueues(); } The only piece of information this code snippet uses is the PrintQueue.FullName property. However, the PrintQueue class is stuffed with properties you can examine. You can get the default print settings (using properties such as DefaultPriority, DefaultPrintTicket, and so on), you can get the status and general information (using properties such as QueueStatus and NumberOfJobs), and you can isolate specific problems using Boolean IsXxx and HasXxx properties (such as IsManualFeedRequired, IsWarmingUp, IsPaperJammed, IsOutOfPaper, HasPaperProblem, and NeedUserIntervention). The current example reacts when a printer is selected in the list by displaying the status for that printer and then fetching all the jobs in the queue. The PrintQueue.GetPrintJobInfoCollection() performs this task. private void lstQueues_SelectionChanged(object sender, SelectionChangedEventArgs e) { try { PrintQueue queue = printServer.GetPrintQueue(lstQueues.SelectedValue.ToString()); lblQueueStatus.Text = "Queue Status: " + queue.QueueStatus.ToString(); lstJobs.DisplayMemberPath = "JobName"; lstJobs.SelectedValuePath = "JobIdentifier"; lstJobs.ItemsSource = queue.GetPrintJobInfoCollection(); } catch (Exception err) { MessageBox.Show(err.Message, "Error on " + lstQueues.SelectedValue.ToString()); } } Each job is represented as a PrintSystemJobInfo object. When a job is selected in the list, this code shows its status: private void lstJobs_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (lstJobs.SelectedValue == null) { lblJobStatus.Text = ""; } else { PrintQueue queue = printServer.GetPrintQueue(lstQueues.SelectedValue.ToString()); 959

CHAPTER 29 n PRINTING

PrintSystemJobInfo job = queue.GetJob((int)lstJobs.SelectedValue); lblJobStatus.Text = "Job Status: " + job.JobStatus.ToString(); } } The only remaining detail is the event handlers that manipulate the queue or job when you click one of the buttons in the window. This code is extremely straightforward. All you need to do is get a reference to the appropriate queue or job and then call the corresponding method. For example, here’s how to pause a PrintQueue: PrintQueue queue = printServer.GetPrintQueue(lstQueues.SelectedValue.ToString()); queue.Pause(); And here’s how to pause a print job: PrintQueue queue = printServer.GetPrintQueue(lstQueues.SelectedValue.ToString()); PrintSystemJobInfo job = queue.GetJob((int)lstJobs.SelectedValue); job.Pause();

n Note It’s possible to pause (and resume) an entire printer or a single job. You can do both tasks using the Printers icon in the Control Panel. Right-click a printer to pause or resume a queue, or double-click a printer to see its jobs, which you can manipulate individually. Obviously, you’ll need to add error handling when you perform this sort of task, because it won’t necessarily succeed. For example, Windows security might stop you from attempting to cancel someone else’s print job or an error might occur if you try to print to a networked printer after you’ve lost your connection to the network. WPF includes quite a bit of print-related functionality. If you’re interested in using this specialized functionality (perhaps because you’re building some sort of tool or creating a long-running background task), check out the classes in the System.Printing namespace in the MSDN help.

Printing Through XPS As you learned in Chapter 28, WPF supports two complementary types of documents. Flow documents handle flexible content that flows to fit any page size you specify. XPS documents store print-ready content that’s based on a fixed-page size. The content is frozen in place and preserved in its precise, original form. As you’d expect, printing an XpsDocument is easy. The XpsDocument class exposes a DocumentPaginator, just like the FlowDocument. However, the DocumentPaginator of an XpsDocument has little to do, because the content is already laid out in fixed, unchanging pages. Here’s the code you might use to load an XPS file into memory, show it in a DocumentViewer, and then send it to the printer: // Display the document. XpsDocument doc = new XpsDocument("filename.xps", FileAccess.ReadWrite); docViewer.Document = doc.GetFixedDocumentSequence(); doc.Close(); // Print the document. if (printDialog.ShowDialog() == true) 960

CHAPTER 29 n PRINTING

{ printDialog.PrintDocument(docViewer.Document.DocumentPaginator, "A Fixed Document"); } Obviously, you don’t need to show a fixed document in a DocumentViewer before you print it. This code includes that step because it’s the most common option. In many scenarios, you’ll load up the XpsDocument for review and print it after the user clicks a button. As with the viewers for FlowDocument objects, the DocumentViewer also handles the ApplicationCommands.Print command, which means you can send an XPS document from the DocumentViewer to the printer with no code required.

Creating an XPS Document for a Print Preview WPF also includes all the support you need to programmatically create XPS documents. Creating an XPS document is conceptually similar to printing some content—once you’ve built your XPS document, you’ve chosen a fixed page size and frozen your layout. So why bother taking this extra step? There are two good reasons: • •

Print preview. You can use your generated XPS document as a print preview by displaying it in a DocumentViewer. The user can then choose whether to go ahead with the printout. Asynchronous printing. The XpsDocumentWriter class includes both a Write() method for synchronous printing and a WriteAsync() method that lets you send content to the printer asynchronously. For a long, complex print operation, the asynchronous option is preferred. It allows you to create a more responsive application.

The basic technique for creating an XPS document is create an XpsDocumentWriter object using the static XpsDocument.CreateXpsDocumentWriter() method. Here’s an example: XpsDocument xpsDocument = new XpsDocument("filename.xps", FileAccess.ReadWrite); XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument); The XpsDocumentWriter is a stripped-down class—its functionality revolves around the Write() and WriteAsync() methods that write content to your XPS document. Both of these methods are overloaded multiple times, allowing you to write different types of content, including another XPS document, a page that you’ve extracted from an XPS document, a visual (which allows you to write any element), and a DocumentPaginator. The last two options are the most interesting, because they duplicate the options you have with printing. For example, if you’ve created a DocumentPaginator to enable custom printing (as described earlier in this chapter), you can also use it to write an XPS document. Here’s an example that opens an existing flow document and then writes it to a temporary XPS document using the XpsDocumentWriter.Write() method. The newly created XPS document is then displayed in a DocumentViewer, which acts as a print preview. using (FileStream fs = File.Open("FlowDocument1.xaml", FileMode.Open)) { FlowDocument flowDocument = (FlowDocument)XamlReader.Load(fs); writer.Write(((IDocumentPaginatorSource)flowDocument).DocumentPaginator); // Display the new XPS document in a viewer. docViewer.Document = xpsDocument.GetFixedDocumentSequence(); 961

CHAPTER 29 n PRINTING

xpsDocument.Close(); } You can get a visual or paginator in a WPF application in an endless variety of ways. Because the XpsDocumentWriter supports these classes, it allows you to write any WPF content to an XPS document.

Writing to an In-Memory XPS Document The XpsDocument class assumes that you want to write your XPS content to a file. This is a bit awkward for situations like the one shown previously, where the XPS document is a temporary stepping stone that’s used to create a preview. Similar problems occur if you want to serialize XPS content to some other storage location, like a field in a database record. It’s possible to get around this limitation, and write XPS content directly to a MemoryStream. However, it takes a bit more work, as you first need to create a package for your XPS content. Here’s the code that does the trick: // Get ready to store the content in memory. MemoryStream ms = new MemoryStream(); // Create a package usign the static Package.Open() method. Package package = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite); // Every package needs a URI. Use the pack:// syntax. // The actual file name is unimportant. Uri documentUri = new Uri("pack://InMemoryDocument.xps"); // Add the package. PackageStore.AddPackage(documentUri, package); // Create the XPS document based on this package. At the same time, choose // the level of compression you want for the in-memory content. XpsDocument xpsDocument = new XpsDocument(package, CompressionOption.Fast, DocumentUri.AbsoluteUri); When you’re finished using the XPS document, you can close the stream to recover the memory.

n Note Don’t use the in-memory approach if you might have a larger XPS document (for example, if you’re generating an XPS document based on content in a database, and you don’t know how many records there will be). Instead, use a method like Path.GetTempFileName() to get a suitable temporary path for a file-based XPS document.

Printing Directly to the Printer via XPS As you’ve learned in this chapter, the printing support in WPF is built on the XPS print path. If you use the PrintDialog class, you might not see any sign of this low-level reality. If you use the XpsDocumentWriter, it’s impossible to miss. So far, you’ve been funneling all your printing through the PrintDialog class. This isn’t necessary—in fact, the PrintDialog delegates the real work to the XpsDocumentWriter. The trick is to create an 962

CHAPTER 29 n PRINTING

XpsDocumentWriter that wraps a PrintQueue rather than a FileStream. The actual code for writing the printed output is identical—you simply rely on the Write() and WriteAsync() methods. Here’s a snippet of code that shows the Print dialog box, gets the selected printer, and uses it to create an XpsDocumentWriter that submits the print job: string filePath = Path.Combine(appPath, "FlowDocument1.xaml"); if (printDialog.ShowDialog() == true) { PrintQueue queue = printDialog.PrintQueue; XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(queue); using (FileStream fs = File.Open(filePath, FileMode.Open)) { FlowDocument flowDocument = (FlowDocument)XamlReader.Load(fs); writer.Write(((IDocumentPaginatorSource)flowDocument).DocumentPaginator); } } Interestingly, this example still uses the PrintDialog class. However, it simply uses it to display the standard Print dialog box and allow the user to choose a printer. The actual printing is performed through the XpsDocumentWriter.

Asynchronous Printing The XpsDocumentWriter makes asynchronous printing easy. In fact, you can convert the previous example to use asynchronous printing by simply replacing the call to the Write() method with a call to WriteAsync().

n Note In Windows, all print jobs are printed asynchronously. However, the process of submitting the print job takes place synchronously if you use Write() and asynchronously if you use WriteAsync(). In many cases, the time taken to submit a print job won’t be significant, and you won’t need this feature. Another consideration is that if you want to build (and paginate) the content you want to print asynchronously, this is often the most time-consuming stage of printing, and if you want this ability, you’ll need to write the code that runs your printing logic on a background thread. You can use the techniques described in Chapter 31 (such as the BackgroundWorker) to make this process relatively easy. The signature of the WriteAsync() method matches the signature of the Write() method—in other words, WriteAsync() accepts a paginator, visual, or one of a few other types of objects. Additionally, the WriteAsync() method includes overloads that accept an optional second parameter with state information. This state information can be any object you want to use to identify the print job. This object is provided through the WritingCompletedEventArgs object when the WritingCompleted event fires. This allows you to fire off multiple print jobs at once, handle the WritingCompleted event for each one with the same event handler, and determine which one has been submitted each time the event fires. When an asynchronous print job is underway, you can cancel it by calling the CancelAsync() method. The XpsDocumentWriter also includes a small set of events that allow you to react as a print job is submitted, including WritingProgressChanged, WritingCompleted, and WritingCancelled. Keep in mind 963

CHAPTER 29 n PRINTING

that the WritingCompleted event fires when the print job has been written to the print queue, but this doesn’t mean the printer has printed it yet.

The Last Word In this chapter, you learned about WPF’s printing model. First you considered the easiest entry point: the all-in-one PrintDialog class that allows users to configure print settings and allows your application to send a document or visual to the printer. After considering a variety of ways to extend the PrintDialog and use it with onscreen and dynamically generated content, you looked at the lower-level XPS printing model. You then learned about the XpsDocumentWriter, which supports the PrintDialog and can be used independently. The XpsDocumentWriter gives you an easy way to create a print preview (because WPF doesn’t include any print preview control), and it allows you to submit your print job asynchronously.

964

PART VIII

Additional Topics

965

ChAPTeR 30 nnn

Interacting with Windows Forms In an ideal world, once developers master a new technology such as WPF, they’d leave the previous framework behind. Everything would be written using the latest, most capable toolkit, and no one would ever worry about legacy code. Of course, this ideal world is nothing like the real world, and most WPF developers will need to interact with the Windows Forms platform at some point for two reasons: to leverage existing code investments and to compensate for missing features in WPF. In this chapter, you’ll look at strategies for integrating Windows Forms and WPF content. You’ll consider how to use both types of windows in a single application, and you’ll explore the more impressive trick of mixing content from both platforms in a single window. But before you delve into WPF and Windows Forms interoperability, it’s worth taking a step back and assessing the reasons you should (and shouldn’t) use WPF interoperability.

n What’s New Early betas of WPF 4.5 included a mechanism that solved the airspace issue (the inability to overlap content that’s created by WPF with content that’s created by Windows Forms). However, this feature was dropped from the final release, leaving WPF’s interoperability support unchanged.

Assessing Interoperability There’s no tool to transform Windows Forms interfaces into similar WPF interfaces (and even if there were, such a tool would be only a starting point of a long and involved migration process). Of course, there’s no need to transplant a Windows Forms application into the WPF environment—most of the time, you’re better off keeping old applications as is and using WPF for new projects. However, life isn’t always that simple. You might decide that you want to add a WPF feature (such as an eye-catching 3-D animation) to an existing Windows Forms application. Or you might decide that you want to migrate an existing Windows Forms application to WPF piece by piece, as you release updated versions. Either way, the interoperability support in WPF can help you make the transition gradually and without sacrificing the work that’s invested in your legacy code. Before you toss WPF elements and Windows Forms controls together, it’s important to assess your overall goals. In many situations, developers are faced with deciding between incrementally enhancing a Windows Forms application (and gradually moving it into the WPF world) or replacing it with a newly rewritten WPF masterpiece. Obviously, the first approach is faster and easier to test, debug, and release.

967

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

However, in a suitably complex application that needs a major WPF injection, there may come a point where it’s simpler to start over in WPF and import the legacy bits that you need.

n Note As always, when moving from one user interface platform to another, you should be forced to migrate only the user interface. Other details, such as data access code, validation rules, file access, and so on, should be abstracted away in separate classes (and possibly even separate assemblies), which you can plug into a WPF frontend just as easily as a Windows Forms application. Of course, this level of componentization isn’t always possible, and sometimes other details (such as data-binding considerations and validation strategies) can lead you to shape your classes a certain way and inadvertently limit their reusability.

Mixing Windows and Forms The cleanest way to integrate WPF and Windows Forms content is to place each in a separate window. That way, your application consists of well-encapsulated window classes, each of which deals with just a single technology. Any interoperability details are handled in the glue code—the logic that creates and shows your windows.

Adding Forms to a WPF Application The easiest approach to mixing windows and forms is to add one or more forms (from the Windows Forms toolkit) to an otherwise ordinary WPF application. Visual Studio makes this easy—just right-click the project name in the Solution Explorer and choose Add  New Item. Then select the Windows Forms category on the left side and choose the Windows Form template. Finally, give your form a file name and click Add. The first time you add a form, Visual Studio adds references to all the required Windows Forms assemblies, including System.Windows.Forms.dll and System.Drawing.dll. You can design a form in a WPF project in the same way that you design it in a Windows Forms project. When you open a form, Visual Studio loads the normal Windows Forms designer and fills the Toolbox with Windows Forms controls. When you open the XAML file for a WPF window, you get the familiar WPF design surface instead.

n Note For better separation between WPF and Windows Forms content, you might choose to place the “foreign” content in a separate class library assembly. For example, a Windows Forms application might use the WPF windows defined in a separate assembly. This approach makes especially good sense if you plan to reuse some of these windows in both Windows Forms and WPF applications.

Adding WPF Windows to a Windows Forms Application The reverse trick is a bit more awkward. Visual Studio doesn’t directly allow you to create a new WPF window in a Windows Forms application. (In other words, you won’t see it as one of the available templates when you right-click your project and choose Add ฀ New Item.) However, you can add the existing .cs and .xaml files that define a WPF window from another WPF project. To do so, right-click your project in the 968

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Solution Explorer, choose Add  Existing Item, and find both these files. You’ll also need to add references to the core WPF assemblies (PresentationCore.dll, PresentationFramework.dll, and WindowsBase.dll).

n Tip There’s a shortcut to adding the WPF references you need. You can add a WPF user control (which Visual Studio does support), which causes Visual Studio to add these references automatically. You can then delete the user control from your project. To add a WPF user control, right-click the project, choose Add  New Item, pick the WPF category, and select the User Control (WPF) template. After you add a WPF window to a Windows Forms application, that window is treated correctly. When you open it, you’ll be able to use the WPF designer to modify it. When you build the project, the XAML will be compiled, and the automatically generated code will be merged with your code-behind class, just as it is in a full-fledged WPF application. Creating a project that uses forms and windows isn’t too difficult. However, there are a few extra considerations when you show these forms and windows at runtime. If you need to show a window or form modally (as you would with a dialog box), the task is straightforward, and your code is essentially unchanged. But if you want to show a window modelessly, you need a bit of extra code to ensure proper keyboard support, as you’ll see in the following sections.

Showing Modal Windows and Forms Showing a modal form from a WPF application is effortless. You use exactly the same code you’d use in a Windows Forms project. For example, if you have a form class named Form1, you’d use code like this to show it modally: Form1 frm = new Form1(); if (frm.ShowDialog() == System.Windows.Forms.DialogResult.OK) { MessageBox.Show("You clicked OK in a Windows Forms form."); } You’ll notice that the Form.ShowDialog() method works in a slightly different way than WPF’s Window. ShowDialog() method. While Window.ShowDialog() returns true, false, or null, Form.ShowDialog() returns a value from the DialogResult enumeration. The reverse trick—showing a WPF window from a form—is just as easy. Once again, you simply interact with the public interface of your Window class, and WPF takes care of the rest: Window1 win = new Window1(); if (win.ShowDialog() == true) { MessageBox.Show("You clicked OK in a WPF window."); }

Showing Modeless Windows and Forms It’s not quite as straightforward if you want to show windows or forms modelessly. The challenge is that keyboard input is received by the root application and needs to be delivered to the appropriate window. In order for this to work between WPF and Windows Forms content, you need a way to forward these messages along to the right window or form. 969

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

If you want to show a WPF window modelessly from inside a Windows Forms application, you must use the static ElementHost.EnableModelessKeyboardInterop() method. You’ll also need a reference to the WindowsFormsIntegration.dll assembly, which defines the ElementHost class in the System.Windows. Forms.Integration namespace. (You’ll learn more about the ElementHost class later in this chapter.) You call the EnableModelessKeyboardInterop() method after you create the window but before you show it. When you call it, you pass in a reference to the new WPF window, as shown here: Window1 win = new Window1(); ElementHost.EnableModelessKeyboardInterop(win); win.Show(); When you call EnableModelessKeyboardInterop(), the ElementHost adds a message filter to the Windows Forms application. This message filter intercepts keyboard messages when your WPF window is active and forwards them to your window. Without this detail, your WPF controls won’t receive any keyboard input. If you need to show a modeless Windows Forms application inside a WPF application, you use the similar WindowsFormsHost.EnableWindowsFormsInterop() method. However, you don’t need to pass in a reference to the form you plan to show. Instead, you simply need to call this method once before you show any form. (One good choice is to call this method at application startup.) WindowsFormsHost.EnableWindowsFormsInterop(); Now you can show your form modelessly without a hitch: Form1 frm = new Form1(); frm.Show(); Without the call to EnableWindowsFormsInterop(), your form will still appear, but it won’t recognize all keyboard input. For example, you won’t be able to use the Tab key to move from one control to the next. You can extend this process to multiple levels. For example, you could create a WPF window that shows a form (modally or modelessly), and that form could then show a WPF window. Although you won’t need to do this very often, it’s more powerful than the element-based interoperability support you’ll learn about later. This support allows you to integrate different types of content in the same window but doesn’t allow you to nest more than one layer deep (for example, creating a WPF window that contains a Windows Forms control that, in turn, hosts a WPF control).

Enabling Visual Styles for Windows Forms Controls When you show a form in a WPF application, that form uses the shockingly old-fashioned (pre–Windows XP) styles for buttons and other common controls. That’s because support for the newer styles must be explicitly enabled by calling the Application.EnableVisualStyles() method. Ordinarily, Visual Studio adds this line of code to the Main() method of every new Windows Forms application. However, when you create a WPF application, this detail isn’t included. To resolve this issue, just call the EnableVisualStyles() method once before showing any Windows Forms content. A good place to do this is at the start of the application, as shown here: public partial class App : System.Windows.Application { protected override void OnStartup(StartupEventArgs e) { // Raises the Startup event. base.OnStartup(e);

970

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

System.Windows.Forms.Application.EnableVisualStyles(); } } Notice that the EnableVisualStyles() method is defined in the System.Windows.Forms.Application class, not the System.Windows.Application class that forms the core of your WPF application.

Creating Windows with Mixed Content In some cases, the clean window-by-window separation isn’t suitable. For example, you might want to place WPF content in an existing form alongside Windows Forms content. Although this model is conceptually messier, WPF handles it quite gracefully. In fact, including Windows Forms content in a WPF application (or vice versa) is more straightforward than adding ActiveX content to a Windows Forms application. In the latter scenario, Visual Studio must generate a wrapper class that sits between the ActiveX control and your code, which manages the transition from managed to unmanaged code. This wrapper is component-specific, which means each ActiveX control you use requires a separate customized wrapper. And because of the quirks of COM, the interface exposed by the wrapper might not match the interface of the underlying component exactly. When integrating Windows Forms and WPF content, you don’t need a wrapper class. Instead, you use one of a small set of containers, depending on the scenario. These containers work with any class, so there’s no code-generation step. This simpler model is possible because even though Windows Forms and WPF are dramatically different technologies, they are both firmly grounded in the world of managed code. The most significant advantage of this design is that you can interact with Windows Forms controls and WPF elements in your code directly. The interoperability layer comes into effect only when this content is rendered in the window. This part takes place automatically without requiring any developer intervention. You also don’t need to worry about keyboard handling in modeless windows because the interoperability classes you’ll use (ElementHost and WindowsFormsHost) handle that automatically.

Understanding WPF and Windows Forms “Airspace” To integrate WPF and Windows Forms content in the same window, you need to be able to segregate a portion of your window for “foreign” content. For example, it’s completely reasonable to throw a 3-D graphic into a Windows Forms application because you can place that 3-D graphic in a distinct region of a window (or even make it take up the entire window). However, it’s not easy or worthwhile to reskin all the buttons in your Windows Forms application by making them WPF elements, because you’ll need to create a separate WPF region for each button. Along with the considerations of complexity, some things just aren’t possible with WPF interoperability. For example, you can’t combine WPF and Windows Forms content by overlapping it. That means you can’t have a WPF animation send an element flying over a region that’s rendered with Windows Forms. Similarly, you can’t overlap partially transparent Windows Forms content over a WPF region to blend them together. Both of these violate what’s known as the airspace rule, which dictates that WPF and Windows Forms must always have their own distinct window regions, which they manage exclusively. Figure 30-1 shows what’s allowed and what isn’t.

971

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Figure 30-1. The airspace rule Technically, the airspace rule results from the fact that in a window containing WPF content and Windows Forms content, both regions have a separate window handle, or hwnd. Each hwnd is managed, rendered, and refreshed separately. Window handles are managed by the Windows operating system. In classic Windows applications, every control is a separate window, which means each control has ownership of a distinct piece of screen real estate. Obviously, this type of “window” isn’t the same as the top-level windows that float around your screen—it’s simply a self-contained region (rectangular or otherwise). In WPF, the model is dramatically different—there’s a single, top-level hwnd, and the WPF engine does the compositing for the entire window, which allows more-pleasing rendering (for example, effects such as dynamic anti-aliasing) and far greater flexibility (for example, visuals that render content outside their bounds).

n Note A few WPF elements use separate window handles. These include menus, tooltips, and the drop-down portion of a combo box, all of which need the ability to extend beyond the bounds of the window. The implementation of the airspace rule is fairly straightforward. If you place Windows Forms content on top of WPF content, you’ll find that the Windows Forms content is always on top, no matter where it’s declared in the markup or what layout container you use. That’s because the WPF content is a single window, and the container with Windows Forms content is implemented as a separate window that’s displayed on top of a portion of the WPF window. If you place WPF content in a Windows Forms form, the result is a bit different. Every control in Windows Forms is a distinct window and therefore has its own hwnd. So, WPF content can be layered 972

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

anywhere with relation to other Windows Forms controls in the same window, depending on its z-index. (The z-index is determined by the order in which you add controls to the parent’s Controls collection, so that controls added later appear on top of those added before.) However, the WPF content still has its own completely distinct region. That means you can’t use transparency or any other technique to partially overwrite (or combine your element with) Windows Forms content. Instead, the WPF content exists in its own self-contained region.

Hosting Windows Forms Controls in WPF To show a Windows Forms control in a WPF window, you use the WindowsFormsHost class in the System. Windows.Forms.Integration namespace. The WindowsFormsHost is a WPF element (it derives from FrameworkElement) that has the ability to hold exactly one Windows Forms control, which is provided in the Child property. It’s easy enough to create and use WindowsFormsHost programmatically. However, in most cases, it’s easiest to create it declaratively in your XAML markup. The only disadvantage is that Visual Studio doesn’t include much designer support for the WindowsFormsHost control. Although you can drag and drop it onto a window, you need to fill in its content (and map the required namespace) by hand. The first step is to map the System.Windows.Forms namespace so you can refer to the Windows Forms control you want to use:

Now you can create the WindowsFormsHost and the control inside just as you would any other WPF element. Here’s an example that uses the MaskedTextBox from Windows Forms:



n Note The WindowsFormsHost can hold any Windows Forms control (that is, any class that derives from System. Windows.Forms.Control). It can’t hold Windows Forms components that aren’t controls, such as the HelpProvider or the NotifyIcon. Figure 30-2 shows a MaskedTextBox in a WPF window.

973

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Figure 30-2. A masked text box for a phone number You can set most of the properties of your MaskedTextBox directly in your markup. That’s because Windows Forms uses the same TypeConverter infrastructure (discussed in Chapter 2) to change strings into property values of a specific type. This isn’t always convenient—for example, the string representation of a type may be awkward to enter by hand—but it usually allows you to configure your Windows Forms controls without resorting to code. For example, here’s a MaskedTextBox equipped with a mask that shapes user input into a seven-digit phone number with an optional area code:

You can also use ordinary XAML markup extensions to fill in null values, use static properties, create type objects, or use objects that you’ve defined in the Resources collection of the window. Here’s an example that uses the type extension to set the MaskedTextBox.ValidatingType property. This specifies that the MaskedTextBox should change the supplied input (a phone number string) into an Int32 when the Text property is read or the focus changes:

One markup extension that won’t work is a data-binding expression because it requires a dependency property. (Windows Forms controls are constructed out of normal .NET properties.) If you want to bind a property of a Windows Forms control to the property of a WPF element, there’s an easy workaround—just set the dependency property on the WPF element and adjust the BindingDirection as required. (Chapter 8 has the full details.) Finally, it’s important to note that you can hook events up to your Windows Forms control by using the familiar XAML syntax. Here’s an example that attaches an event handler for the MaskInputRejected event, which occurs when a keystroke is discarded because it doesn’t suit the mask:

974

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Obviously, these aren’t routed events, so you can’t define them at higher levels in the element hierarchy. When the event fires, your event handler responds by showing an error message in another element. In this case, it’s a WPF label that’s located elsewhere on the window: private void maskedTextBox_MaskInputRejected(object sender, System.Windows.Forms.MaskInputRejectedEventArgs e) { lblErrorText.Content = "Error: " + e.RejectionHint.ToString(); }

n Tip Don’t import the Windows Forms namespaces (such as System.Windows.Forms) in a code file that already uses WPF namespaces (such as System.Windows.Controls). The Windows Forms classes and the WPF classes share many names. Basic ingredients (such as Brush, Pen, Font, Color, Size, and Point) and common controls (such as Button, TextBox, and so on) are found in both libraries. To prevent naming clashes, it’s best to import just one set of namespaces in your window (WPF namespaces for a WPF window, Windows Forms namespaces for a form) and use fully qualified names or a namespace alias to access the others. This example illustrates the nicest feature about WPF and Windows Forms interoperability: it doesn’t affect your code. Whether you’re manipulating a Windows Forms control or a WPF element, you use the familiar class interface for that object. The interoperability layer is simply the magic that lets both ingredients coexist in the window. It doesn’t require any extra code.

n Note To have Windows Forms controls use more up-to-date control styles introduced with Windows XP, you must call EnableVisualStyles() when your application starts, as described in the “Visual Styles for Windows Forms Controls” section earlier in this chapter. Windows Forms content is rendered by Windows Forms, not WPF. Therefore, display-related properties of the WindowsFormsHost container (properties such as Transform, Clip, and Opacity) have no effect on the content inside. This means that even if you set a rotational transform, set a narrow clipping region, and make your content 50 percent transparent, you’ll see no change. Similarly, Windows Forms uses a different coordinate system that sizes controls by using physical pixels. As a result, if you increase the system DPI setting of your computer, you’ll find that the WPF content resizes cleanly to be more detailed, but the Windows Forms content does not.

Using WPF and Windows Forms User Controls One of the most significant limitations of the WindowsFormsHost element is that it can hold only a single Windows Forms control. To compensate, you could use a Windows Forms container control. Unfortunately, Windows Forms container controls don’t support XAML content models, so you’ll need to fill in the contents of the container control programmatically. A much better approach is to create a Windows Forms user control. This user control can be defined in a separate assembly that you reference, or you can add it directly to your WPF project (using the familiar Add  New Item command). This gives you the best of both worlds—you have full design support to build your user control and an easy way to integrate it into your WPF window. 975

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

In fact, using a user control gives you an extra layer of abstraction similar to using separate windows. That’s because the containing WPF window won’t be able to access the individual controls in your user control. Instead, it will interact with the higher-level properties you’ve added to your user control, which can then modify the controls inside. This makes your code better encapsulated and simpler because it limits the points of interaction between the WPF window and your Windows Forms content. It also makes it easier to migrate to a WPF-only solution in the future, simply by creating a WPF user control that has the same properties and swapping that in place of the WindowsFormsHost. (And once again, you can further improve the design and flexibility of your application by moving the user control into a separate class library assembly.)

n Note Technically, your WPF window can access the controls in a user control by accessing the Controls collection of the user control. However, to use this back door, you need to write error-prone lookup code that searches for specific controls by using a string name. That’s always a bad idea. As long as you’re creating a user control, it’s a good idea to make it behave as much like WPF content as possible so it’s easier to integrate into your WPF window layout. For example, you may want to consider using the FlowLayoutPanel and TableLayoutPanel container controls so that the content inside your user controls flows to fit its dimensions. Simply add the appropriate control and set its Dock property to DockStyle.Fill. Then place the controls you want to use inside. For more information about using the Windows Forms layout controls (which are subtly different from the WPF layout panels), refer to my book Pro .NET 2.0 Windows Forms and Custom Controls in C# (Apress, 2005).

ACTIVeX InTeRoPeRABIlITy WPF has no direct support for ActiveX interoperability. However, Windows Forms has extensive support in the form of runtime callable wrappers (RCWs), dynamically generated interop classes that allow a managed Windows Forms application to host an Active component. Although there are .NET-to-COM quirks that can derail some controls, this approach works reasonably well for most scenarios, and it works seamlessly if the person who creates the component also provides a primary interop assembly, which is a handcrafted, finetuned RCW that’s guaranteed to dodge interop issues. So, how does this help if you need to design a WPF application that uses an ActiveX control? In this case, you need to layer two levels of interoperability. First you place the ActiveX control in a Windows Forms user control or form. You then place that user control in your WPF window or show the form from your WPF application.

Hosting WPF Controls in Windows Forms The reverse approach—hosting WPF content in a form built with Windows Forms—is just as easy. In this situation, you don’t need the WindowsFormsHost class. Instead, you use the System.Windows.Forms. Integration.ElementHost class, which is part of the WindowsFormsIntegration.dll assembly. The ElementHost has the ability to wrap any WPF element. However, the ElementHost is a genuine Windows Forms control, which means you can place it in a form alongside other Windows Forms content. In some respects, the ElementHost is more straightforward than the WindowsFormsHost, because every control in Windows Forms is displayed as a separate hwnd. Thus, it’s not terribly difficult for one of these windows to be rendered with WPF instead of User32/GDI+.

976

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Visual Studio provides some design-time support for the ElementHost control, but only if you place your WPF content in a WPF user control. Here’s what to do: 1.

Right-click the project name in the Solution Explorer, and choose Add  New Item. Pick the User Control (WPF) template, supply a name for your custom component class, and click Add.

n Note This example assumes you’re placing the WPF user control directly in your Windows Forms project. If you have a complex user control, you must choose to use a more structured approach and place it in a separate class library assembly.

2.

3.

Add the WPF controls you need to your new WPF user control. Visual Studio gives you the usual level of design-time support for this step, so you can drag WPF controls from the Toolbox, configure them with the Properties window, and so on. When you’re finished, rebuild your project (choose Build  Build Solution). You can’t use your WPF user control in a form until you’ve compiled it.

4.

Open to the Windows Forms form where you want to add your WPF user control (or create a new form by right-clicking the project in the Solution Explorer and choosing Add  Windows Form).

5.

To place the WPF user control in a form, you need the help of the ElementHost control. The ElementHost control appears on the WPF Interoperability tab of the Toolbox. Drag it onto your form, and size it accordingly.

Tip For better separation, it’s a good idea to add the ElementHost to a specific container rather than directly to the form. This makes it easier to separate your WPF content from the rest of the window. Typically, you’ll use the Panel, FlowLayoutPanel, or TableLayoutPanel.

6.

To choose the content for the ElementHost, you use the smart tag. If the smart tag isn’t visible, you can show it by selecting the ElementHost and clicking the arrow in the top-right corner. In the smart tag you’ll find a drop-down list named Select Hosted Content. Using this list, you can pick the WPF user control you want to use, as shown in Figure 30-3.

977

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

Figure 30-3. Selecting WPF content for an ElementHost 7.

Although the WPF user control will appear in your form, you can’t edit its content there. To jump to the corresponding XAML file in a hurry, click the Edit Hosted Content link in the ElementHost smart tag.

Technically, the ElementHost can hold any type of WPF element. However, the ElementHost smart tag expects you to choose a user control that’s in your project (or a referenced assembly). If you want to use a different type of control, you’ll need to write code that adds it to the ElementHost programmatically.

Access Keys, Mnemonics, and Focus The WPF and Windows Forms interoperability works because the two types of content can be rigorously separated. Each region handles its own rendering and refreshing and interacts with the mouse independently. However, this segregation isn’t always appropriate. For example, it runs into potential problems with keyboard handling, which sometimes needs to be global across an entire form. Here are some examples: • • 978

When you tab from the last control in one region, you expect focus to move to the first control in the next region. When you use a shortcut key to trigger a control (such as a button), you expect that button to respond no matter what region of the window it’s located in.

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

• •

When you use a label mnemonic, you expect the focus to move to the linked control. Similarly, if you suppress a keystroke by using a preview event, you don’t expect the corresponding key event to occur in either region, no matter what control currently has focus.

The good news is that all these expected behaviors work without any customization needed. For example, consider the WPF window shown in Figure 30-4. It includes two WPF buttons (top and bottom) and a Windows Forms button (in the middle). Here’s the markup:

Figure 30-4. Three buttons with shortcut keys



Use Alt+_A

Use Alt+_C

n Note The syntax for identifying accelerator keys is slightly different in WPF (which uses an underscore) than in Windows Forms. Windows Forms uses the & character, which must be escaped as & in XML because it’s a special character. 979

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

When this window first appears, the text in all buttons is normal. When the user presses and holds the Alt key, all three shortcuts are underlined. The user can then trigger any one of the three buttons by pressing the A, B, or C key (while holding down Alt). The same magic works with mnemonics, which allows labels to forward the focus to a nearby control (typically a text box). You can also tab through the three buttons in this window as though they were all WPF-defined controls, moving from top to bottom. Finally, the same example continues to work if you host a combination of Windows Forms and WPF content in a Windows Forms form. Keyboard support isn’t always this pretty, and there are a few focus-related quirks that you may run into. Here’s a list of issues to watch out for: •

Although WPF supports a keystroke forwarding system to make sure every element and control gets a chance to handle keyboard input, the keyboard-handling models of WPF and Windows Forms still differ. For that reason, you won’t receive keyboard events from the WindowsFormsHost when the focus is in the Windows Forms content inside. Similarly, if the user moves from one control to another inside a WindowsFormsHost, you won’t receive the GotFocus and LostFocus events from the WindowsFormsHost.

n Note Incidentally, the same is true for WPF mouse events. For example, the MouseMove event won’t fire for the WindowsFormsHost while you move the mouse inside its bounds. •



Windows Forms validation won’t fire when you move the focus from a control inside the WindowsFormsHost to an element outside the WindowsFormsHost. Instead, it will fire only when you move from one control to another inside the WindowsFormsHost. (When you remember that the WPF content and the Windows Forms content are essentially separated windows, this makes perfect sense, because it’s the same behavior you experience if you switch between different applications.) If the window is minimized while the focus is somewhere inside a WindowsFormsHost, the focus may not be restored when the window is restored.

Property Mapping One of the most awkward details in interoperability between WPF and Windows Forms is the way they use similar but different properties. For example, WPF controls have a Background property that allows you to supply a brush that paints the background. Windows Forms controls use a simpler BackColor property that fills the background with a color based on an ARGB value. Obviously, there’s a disconnect between these two properties, even though they’re often used to set the same aspect of a control’s appearance. Most of the time, this isn’t a problem. As a developer, you’ll simply be forced to switch between both APIs, depending on the object you’re working with. However, WPF adds a little bit of extra support through a feature called property translators. Property translators won’t allow you to write WPF-style markup and have it work with Windows Forms controls. In fact, property translators are quite modest. They simply convert a few basic properties of the WindowsFormsHost (or ElementHost) from one system to another so that they can be applied on the child control. For example, if you set the WindowsFormsHost.IsEnabled property, the Enabled property of the control inside is modified accordingly. This isn’t a necessary feature (you could do much the same thing by

980

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

modifying the Enabled property of the child directly, instead of the IsEnabled property of the container), but it can often make your code a bit clearer. To make this work, the WindowsFormsHost and ElementHost classes both have a PropertyMap collection, which is responsible for associating a property name with a delegate that identifies a method that performs the conversion. By using a method, the property map system is able to handle sticky conversions such as BackColor to Background, and vice versa. By default, each is filled with a default set of associations. (You’re free to create your own or replace the existing ones, but this degree of low-level fiddling seldom makes sense.) Table 30-1 lists the standard property map conversions that are provided by the WindowsFormHost and ElementHost classes. Table 30-1. Property Maps

WPF Property

Windows Forms Property

Comments

Foreground

ForeColor

Converts any ColorBrush into the corresponding Color object. In the case of a GradientBrush, the color of the GradientStop with the lowest offset value is used instead. For any other type of brush, the ForeColor is not changed, and the default is used.

Background

BackColor or BackgroundImage

Converts any SolidColorBrush to the corresponding Color object. Transparency is not supported. If a more exotic brush is used, the WindowsFormsHost creates a bitmap and assigns it to the BackgroundImage property instead.

Cursor

Cursor

FlowDirection

RightToLeft

FontFamily, FontSize, FontStretch, Font FontStyle, FontWeight IsEnabled

Enabled

Padding

Padding

Visibility

Visible

Converts a value from the Visibility enumeration into a Boolean value. If Visibility is Hidden, the Visible property is set to true so that the content size can be used for layout calculations but the WindowsFormsHost does not draw the content. If Visibility is Collapsed, the Visible property is not changed (so it remains with its currently set or default value), and the WindowsFormsHost does not draw the content.

981

CHAPTER 30 n INTERACTING WITH WINDOWS FORMS

n Note Property maps work dynamically. For example, if the WindowsFormsHost.FontFamily property is changed, a new Font object is constructed and applied to the Font property of the child control.

WIn32 InTeRoPeRABIlITy WPF certainly doesn’t limit its interoperability to Windows Forms applications—if you want to work with the Win32 API or place WPF content in a C++ MFC application, you can do that too. You can host Win32 in WPF by using the System.Windows.Interop.HwndHost class, which works analogously to the WindowsFormsHost class. The same limitations that apply to WindowsFormsHost apply to HwndSource (for example, the airspace rule, focus quirks, and so on). In fact, WindowsFormsHost derives from HwndHost. The HwndHost is your gateway to the traditional world of C++ and MFC applications. However, it also allows you to integrate managed DirectX content. Currently, WPF does not include any DirectX interoperability features, and you can’t use the DirectX libraries to render content in a WPF window. However, you can use DirectX to build a separate window and then host that inside a WPF window by using the HwndHost. Although DirectX is far beyond the scope of this book (and an order of magnitude more complex than WPF programming), you can learn more at http://msdn.microsoft.com/directx. The complement of HwndHost is the HwndSource class. While HwndHost allows you to place any hwnd in a WPF window, HwndSource wraps any WPF visual or element in an hwnd so it can be inserted in a Win32based application, such as an MFC application. The only limitation is that your application needs a way to access the WPF libraries, which are managed .NET code. This isn’t a trivial task. If you’re using a C++ application, the simplest approach is to use the Managed Extensions for C++. You can then create your WPF content, create an HwndSource to wrap it, set the HwndHost.RootVisual property to the top-level element, and then place the HwndSource into your window.

The Last Word In this chapter you considered the interoperability support that allows WPF applications to show Windows Forms content (and vice versa). Then you examined the WindowsFormsHost element, which lets you embed a Windows Forms control in a WPF window, and the ElementHost, which lets you embed a WPF element in a form. Both of these classes provide a simple, effective way to manage the transition from Windows Forms to WPF.

982

ChAPTeR 31 nnn

Multithreading As you’ve discovered over the previous 30 chapters, WPF revolutionizes almost all the conventions of Windows programming. It introduces a new approach to everything from defining the content in a window to rendering 3-D graphics. WPF even introduces a few new concepts that aren’t obviously UIfocused, such as dependency properties and routed events. Of course, a great number of coding tasks fall outside the scope of user interface programming and haven’t changed in the WPF world. For example, WPF applications use the same classes as other .NET applications when contacting databases, manipulating files, and performing diagnostics. There are also a few features that fall somewhere between traditional .NET programming and WPF. These features aren’t strictly limited to WPF applications, but they do have specific WPF considerations. One example is the add-in model, which allows your WPF application to dynamically load and use separately compiled components with useful bits of functionality. (It’s described in the next chapter.) And in this chapter, you’ll look at multithreading, which allows your WPF application to perform background work while keeping a responsive user interface.

n Note Both multithreading and the add-in model are advanced topics that could provide an entire book worth of material; therefore, you won’t get an exhaustive examination of either feature in this book. However, you will get the basic outline you need to use them with WPF, and you’ll establish a solid foundation for future exploration.

Understanding the Multithreading Model Multithreading is the art of executing more than one piece of code at once. The goal of multithreading is usually to create a more responsive interface—one that doesn’t freeze up while it’s in the midst of other work—although you can also use multithreading to take better advantage of dual-core CPUs when executing a processor-intensive algorithm or to perform other work during a high-latency operation (for example, to perform some calculations while waiting for a response from a web service). Early in the design of WPF, the creators considered a new threading model. This model—called thread rental—allowed user interface objects to be accessed on any thread. To reduce the cost of locking, groups of related objects could be grouped under a single lock (called a context). Unfortunately, this design introduced additional complexity for single-threaded applications (which needed to be context-aware) and made it more difficult to interoperate with legacy code (such as the Win32 API). Ultimately, the plan was abandoned. 983

CHAPTER 31 n MULTITHREADING

The result is that WPF supports a single-threaded apartment model that’s very much like the one used in Windows Forms applications. It has a few core rules: •





WPF elements have thread affinity. The thread that creates them owns them, and other threads can’t interact with them directly. (An element is a WPF object that’s displayed in a window.) WPF objects that have thread affinity derive from DispatcherObject at some point in their class hierarchy. DispatcherObject includes a small set of members that allow you to verify whether code is executing on the right thread to use a specific object— and if not, to switch it over. In practice, one thread runs your entire application and owns all WPF objects. Although you could use separate threads to show separate windows, this design is rare.

In the following sections, you’ll explore the DispatcherObject class and learn the simplest way to perform an asynchronous operation in a WPF application.

The Dispatcher A dispatcher manages the work that takes place in a WPF application. The dispatcher owns the application thread and manages a queue of work items. As your application runs, the dispatcher accepts new work requests and executes one at a time. Technically, a dispatcher is created the first time you instantiate a class that derives from DispatcherObject on a new thread. If you create separate threads and use them to show separate windows, you’ll wind up with more than one dispatcher. However, most applications keep things simple and stick to one user interface thread and one dispatcher. They then use multithreading to manage data operations and other background tasks.

n Note The dispatcher is an instance of the System.Windows.Threading.Dispatcher class. All the dispatcherrelated objects are also found in the small System.Windows.Threading namespace, which is new to WPF. (The core threading classes that have existed since .NET 1.0 are found in System.Threading.) You can retrieve the dispatcher for the current thread by using the static Dispatcher. CurrentDispatcher property. Using this Dispatcher object, you can attach event handlers that respond to unhandled exceptions or respond when the dispatcher shuts down. You can also get a reference to the System.Threading.Thread that the dispatcher controls, shut down the dispatcher, or marshal code to the correct thread (a technique you’ll see in the next section).

The DispatcherObject Most of the time, you won’t interact with a dispatcher directly. However, you’ll spend plenty of time using instances of DispatcherObject, because every visual WPF object derives from this class. A DispatcherObject is simply an object that’s linked to a dispatcher—in other words, an object that’s bound to the dispatcher’s thread. The DispatcherObject introduces just three members, listed in Table 31-1.

984

CHAPTER 31 n MULTITHREADING

Table 31-1. Members of the DispatcherObject Class

Name

Description

Dispatcher

Returns the dispatcher that’s managing this object

CheckAccess()

Returns true if the code is on the right thread to use the object; returns false otherwise

VerifyAccess()

Does nothing if the code is on the right thread to use the object; throws an InvalidOperationException otherwise

WPF objects call VerifyAccess() frequently to protect themselves. They don’t call VerifyAccess() in response to every operation (because that would impose too great a performance overhead), but they do call it often enough that you’re unlikely to use an object from the wrong thread for very long. For example, the following code responds to a button click by creating a new System.Threading. Thread object. It then uses that thread to launch a small bit of code that changes a text box in the current window. private void cmdBreakRules_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(UpdateTextWrong); thread.Start(); } private void UpdateTextWrong() { // Simulate some work taking place with a five-second delay. Thread.Sleep(TimeSpan.FromSeconds(5)); txt.Text = "Here is some new text."; } This code is destined to fail. The UpdateTextWrong() method will be executed on a new thread, and that thread isn’t allowed to access WPF objects. In this case, the TextBox object catches the violation by calling VerifyAccess(), and an InvalidOperationException is thrown. To correct this code, you need to get a reference to the dispatcher that owns the TextBox object (which is the same dispatcher that owns the window and all the other WPF objects in the application). Once you have access to that dispatcher, you can call Dispatcher.BeginInvoke() to marshal some code to the dispatcher thread. Essentially, BeginInvoke() schedules your code as a task for the dispatcher. The dispatcher then executes that code. Here’s the corrected code: private void cmdFollowRules_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(UpdateTextRight); thread.Start(); } private void UpdateTextRight() { // Simulate some work taking place with a five-second delay. Thread.Sleep(TimeSpan.FromSeconds(5));

985

CHAPTER 31 n MULTITHREADING

// Get the dispatcher from the current window, and use it to invoke // the update code. this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) delegate() { txt.Text = "Here is some new text."; } ); } The Dispatcher.BeginInvoke() method takes two parameters. The first indicates the priority of the task. In most cases, you’ll use DispatcherPriority.Normal, but you can also use a lower priority if you have a task that doesn’t need to be completed immediately and that should be kept on hold until the dispatcher has nothing else to do. For example, this might make sense if you need to display a status message about a long-running operation somewhere in your user interface. You can use DispatcherPriority.ApplicationIdle to wait until the application has finished all other work or the even more laid-back DispatcherPriority. SystemIdle to wait until the entire system is at rest and the CPU is idle. You can also use an above-normal priority to get the dispatcher’s attention right away. However, it’s recommended that you leave higher priorities to input messages (such as key presses). These need to be handled nearly instantaneously, or the application will feel sluggish. On the other hand, adding a few milliseconds of extra time to a background operation won’t be noticeable, so a priority of DispatcherPriority.Normal makes more sense in this situation. The second BeginInvoke() parameter is a delegate that points to the method with the code you want to execute. This could be a method somewhere else in your code, or you can use an anonymous method to define your code inline (as in this example). The inline approach works well for simple operations, like this single-line update. However, if you need to use a more complex process to update the user interface, it’s a good idea to factor this code into a separate method.

n Note The BeginInvoke() method also has a return value, which isn’t used in the earlier example. BeginInvoke() returns a DispatcherOperation object, which allows you to follow the status of your marshaling operation and determine when your code has actually been executed. However, the DispatcherOperation is rarely useful, because the code you pass to BeginInvoke() should take very little time. Remember, if you’re performing a time-consuming background operation, you need to perform this operation on a separate thread and then marshal its result to the dispatcher thread (at which point you’ll update the user interface or change a shared object). It makes no sense to perform your time-consuming code in the method that you pass to BeginInvoke(). For example, this slightly rearranged code still works but is impractical: private void UpdateTextRight() { // Get the dispatcher from the current window. this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) delegate() { // Simulate some work taking place. Thread.Sleep(TimeSpan.FromSeconds(5)); txt.Text = "Here is some new text."; } ); 986

CHAPTER 31 n MULTITHREADING

} The problem here is that all the work takes place on the dispatcher thread. That means this code ties up the dispatcher in the same way a non-multithreaded application would.

n Note The dispatcher also provides an Invoke() method. Like BeginInvoke(), Invoke() marshals the code you specify to the dispatcher thread. But unlike BeginInvoke(), Invoke() stalls your thread until the dispatcher executes your code. You might use Invoke() if you need to pause an asynchronous operation until the user has supplied some sort of feedback. For example, you could call Invoke() to run a snippet of code that shows an OK/Cancel dialog box. After the user clicks a button and your marshaled code completes, the Invoke() method will return, and you can act upon the user’s response.

The BackgroundWorker You can perform asynchronous operations in many ways. You’ve already seen one no-frills approach— creating a new System.Threading.Thread object by hand, supplying your asynchronous code, and launching it with the Thread.Start() method. This approach is powerful, because the Thread object doesn’t hold anything back. You can create dozens of threads at will, set their priorities, control their status (for example, pausing, resuming, and aborting them), and so on. However, this approach is also a bit dangerous. If you access shared data, you need to use locking to prevent subtle errors. If you create threads frequently or in large numbers, you’ll generate additional, unnecessary overhead. The techniques to write good multithreading code—and the .NET classes you’ll use—aren’t WPFspecific. If you’ve written multithreaded code in a Windows Forms application, you can use the same techniques in the WPF world. In the remainder of this chapter, you’ll consider one of the simplest and safest approaches: the System.ComponentModel.BackgroundWorker component. The BackgroundWorker was introduced in .NET 2.0 to simplify threading considerations in Windows Forms applications. However, the BackgroundWorker is equally at home in WPF. The BackgroundWorker component gives you a nearly foolproof way to run a time-consuming task on a separate thread. It uses the dispatcher behind the scenes and abstracts away the marshaling issues with an event-based model. As you’ll see, the BackgroundWorker also supports two frills: progress events and cancel messages. In both cases, the threading details are hidden, making for easy coding.

n Note The BackgroundWorker is perfect if you have a single asynchronous task that runs in the background from start to finish (with optional support for progress reporting and cancellation). If you have something else in mind—for example, an asynchronous task that runs throughout the entire life of your application or an asynchronous task that communicates with your application while it does its work, you’ll need to design a customized solution using .NET’s threading support.

A Simple Asynchronous Operation To try the BackgroundWorker, it helps to consider a sample application. The basic ingredient for any test is a time-consuming process. The following example uses a common algorithm for finding prime numbers 987

CHAPTER 31 n MULTITHREADING

in a given range, called the sieve of Eratosthenes, which was invented by Eratosthenes himself in about 240 BC. With this algorithm, you begin by making a list of all the integers in a range of numbers. You then strike out the multiples of all primes less than or equal to the square root of the maximum number. The numbers that are left are the primes. In this example, I won’t go into the theory that proves the sieve of Eratosthenes works or show the fairly trivial code that performs it. (Similarly, don’t worry about optimizing it or comparing it against other techniques.) However, you will see how to perform the sieve of Eratosthenes algorithm asynchronously. The full code is available with the online examples for this chapter. It takes this form: public class Worker { public static int[] FindPrimes(int fromNumber, int toNumber) { // Find the primes between fromNumber and toNumber, // and return them as an array of integers. } } The FindPrimes() method takes two parameters that delimit a range of numbers. The code then returns an integer array with all the prime numbers that occur in that range. Figure 31-1 shows the example we’re building. This window allows the user to choose the range of numbers to search. When the user clicks Find Primes, the search begins, but it takes place in the background. When the search is finished, the list of prime numbers appears in the list box.

Figure 31-1. A completed prime number search

988

CHAPTER 31 n MULTITHREADING

Creating the BackgroundWorker To use the BackgroundWorker, you begin by creating an instance. Here, you have two options: • •

You can create the BackgroundWorker in your code and attach all the event handlers programmatically. You can declare the BackgroundWorker in your XAML. The advantage of this approach is that you can hook up your event handlers by using attributes. Because the BackgroundWorker isn’t a visible WPF element, you can’t place it just anywhere. Instead, you need to declare it as a resource for your window.

Both approaches are equivalent. The downloadable sample uses the second approach. The first step is to make the System.ComponentModel namespace accessible in your XAML document through a namespace import. To do this, you need to map the namespace to an XML prefix:

Now you can create an instance of the BackgroundWorker in the Window.Resources collection. When doing this, you need to supply a key name so the object can be retrieved later. In this example, the key name is backgroundWorker:

The advantage of declaring the BackgroundWorker in the Window.Resources section is that you can set its properties and attach its event handlers by using attributes. For example, here’s the BackgroundWorker tag you’ll end up with at the end of this example, which enables support for progress notification and cancellation and attaches event handlers to the DoWork, ProgressChanged, and RunWorkerCompleted events:

To get access to this resource in your code, you need to pull it out of the Resources collection. In this example, the window performs this step in its constructor so that all your event-handling code can access it more easily: public partial class BackgroundWorkerTest : Window { private BackgroundWorker backgroundWorker; public BackgroundWorkerTest() { InitializeComponent(); backgroundWorker = 989

CHAPTER 31 n MULTITHREADING

((BackgroundWorker)this.FindResource("backgroundWorker")); } ... }

Running the BackgroundWorker The first step to using the BackgroundWorker with the prime-number search example is to create a custom class that allows you to transmit the input parameters to the BackgroundWorker. When you call BackgroundWorker.RunWorkerAsync(), you can supply any object, which will be delivered to the DoWork event. However, you can supply only a single object, so you need to wrap the to and from numbers into one class, as shown here: public class FindPrimesInput { public int From { get; set; } public int To { get; set; } public FindPrimesInput(int from, int to) { From = from; To = to; } } To start the BackgroundWorker on its way, you need to call the BackgroundWorker.RunWorkerAsync() method and pass in the FindPrimesInput object. Here’s the code that does this when the user clicks the Find Primes button: private void cmdFind_Click(object sender, RoutedEventArgs e) { // Disable this button and clear previous results. cmdFind.IsEnabled = false; cmdCancel.IsEnabled = true; lstPrimes.Items.Clear(); // Get the search range. int from, to; if (!Int32.TryParse(txtFrom.Text, out from)) { MessageBox.Show("Invalid From value."); return; } if (!Int32.TryParse(txtTo.Text, out to)) { MessageBox.Show("Invalid To value."); return; 990

CHAPTER 31 n MULTITHREADING

} // Start the search for primes on another thread. FindPrimesInput input = new FindPrimesInput(from, to); backgroundWorker.RunWorkerAsync(input); } When the BackgroundWorker begins executing, it grabs a free thread from the CLR thread pool and then fires the DoWork event from this thread. You handle the DoWork event and begin your timeconsuming task. However, you need to be careful not to access shared data (such as fields in your window class) or user interface objects. Once the work is complete, the BackgroundWorker fires the RunWorkerCompleted event to notify your application. This event fires on the dispatcher thread, which allows you to access shared data and your user interface, without incurring any problems. After the BackgroundWorker acquires the thread, it fires the DoWork event. You can handle this event to call the Worker.FindPrimes() method. The DoWork event provides a DoWorkEventArgs object, which is the key ingredient for retrieving and returning information. You retrieve the input object through the DoWorkEventArgs.Argument property and return the result by setting the DoWorkEventArgs.Result property. private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { // Get the input values. FindPrimesInput input = (FindPrimesInput)e.Argument; // Start the search for primes and wait. // This is the time-consuming part, but it won't freeze the // user interface because it takes place on another thread. int[] primes = Worker.FindPrimes(input.From, input.To); // Return the result. e.Result = primes; } After the method completes, the BackgroundWorker fires the RunWorkerCompletedEventArgs on the dispatcher thread. At this point, you can retrieve the result from the RunWorkerCompletedEventArgs. Result property. You can then update the interface and access window-level variables without worry. private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { // An error was thrown by the DoWork event handler. MessageBox.Show(e.Error.Message, "An Error Occurred"); } else { int[] primes = (int[])e.Result; foreach (int prime in primes) { lstPrimes.Items.Add(prime); } 991

CHAPTER 31 n MULTITHREADING

} cmdFind.IsEnabled = true; cmdCancel.IsEnabled = false; progressBar.Value = 0; } Notice that you don’t need any locking code, and you don’t need to use the Dispatcher.BeginInvoke() method. The BackgroundWorker takes care of these issues for you. Behind the scenes, the BackgroundWorker uses a few multithreading classes that were introduced in . NET 2.0, including AsyncOperationManager, AsyncOperation, and SynchronizationContext. Essentially, the BackgroundWorker uses AsyncOperationManager to manage the background task. The AsyncOperationManager has some built-in intelligence—namely, it’s able to get the synchronization context for the current thread. In a Windows Forms application, the AsyncOperationManager gets a WindowsFormsSynchronizationContext object, whereas a WPF application gets a DispatcherSynchronizationContext object. Conceptually, these classes do the same job, but their internal plumbing is different.

Tracking Progress The BackgroundWorker also provides built-in support for tracking progress, which is useful for keeping the client informed about how much work has been completed in a long-running task. To add support for progress, you need to first set the BackgroundWorker.WorkerReportsProgress property to true. Actually, providing and displaying the progress information is a two-step affair. First, the DoWork event-handling code needs to call the BackgroundWorker.ReportProgress() method and provide an estimated percent complete (from 0 percent to 100 percent). You can do this as little or as often as you like. Every time you call ReportProgress(), the BackgroundWorker fires the ProgressChanged event. You can react to this event to read the new progress percentage and update the user interface. Because the ProgressChanged event fires from the user interface thread, there’s no need to use Dispatcher. BeginInvoke(). The FindPrimes() method reports progress in 1 percent increments, using code like this: int iteration = list.Length / 100; for (int i = 0; i < list.Length; i++) { ... // // // // if

Report progress only if there is a change of 1%. Also, don't bother performing the calculation if there isn't a BackgroundWorker or if it doesn't support progress notifications. ((i % iteration == 0) && (backgroundWorker != null) && backgroundWorker.WorkerReportsProgress)

{ backgroundWorker.ReportProgress(i / iteration); } } After you’ve set the BackgroundWorker.WorkerReportsProgress property, you can respond to these progress notifications by handling the ProgressChanged event. In this example, a progress bar is updated accordingly: 992

CHAPTER 31 n MULTITHREADING

private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { progressBar.Value = e.ProgressPercentage; } Figure 31-2 shows the progress meter while the task is in progress.

Figure 31-2. Tracking progress for an asynchronous task

Supporting Cancellation It’s just as easy to add support for canceling a long-running task with the BackgroundWorker. The first step is to set the BackgroundWorker.WorkerSupportsCancellation property to true. To request a cancellation, your code needs to call the BackgroundWorker.CancelAsync() method. In this example, the cancellation is requested when a Cancel button is clicked: private void cmdCancel_Click(object sender, RoutedEventArgs e) { backgroundWorker.CancelAsync(); }

993

CHAPTER 31 n MULTITHREADING

Nothing happens automatically when you call CancelAsync(). Instead, the code that’s performing the task needs to explicitly check for the cancel request, perform any required cleanup, and return. Here’s the code in the FindPrimes() method that checks for cancellation requests just before it reports progress: for (int i = 0; i < list.Length; i++) { ... if ((i % iteration) && (backgroundWorker != null)) { if (backgroundWorker.CancellationPending) { // Return without doing any more work. return; } if (backgroundWorker.WorkerReportsProgress) { backgroundWorker.ReportProgress(i / iteration); } } } The code in your DoWork event handler also needs to explicitly set the DoWorkEventArgs.Cancel property to true to complete the cancellation. You can then return from that method without attempting to build up the string of primes. private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { FindPrimesInput input = (FindPrimesInput)e.Argument; int[] primes = Worker.FindPrimes(input.From, input.To, backgroundWorker); if (backgroundWorker.CancellationPending) { e.Cancel = true; return; } // Return the result. e.Result = primes; } Even when you cancel an operation, the RunWorkerCompleted event still fires. At this point, you can check whether the task was canceled and handle it accordingly. private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { MessageBox.Show("Search cancelled."); } else if (e.Error != null) 994

CHAPTER 31 n MULTITHREADING

{ // An error was thrown by the DoWork event handler. MessageBox.Show(e.Error.Message, "An Error Occurred"); } else { int[] primes = (int[])e.Result; foreach (int prime in primes) { lstPrimes.Items.Add(prime); } } cmdFind.IsEnabled = true; cmdCancel.IsEnabled = false; progressBar.Value = 0; } Now the BackgroundWorker component allows you to start a search and end it prematurely.

The Last Word To design a safe and stable multithreading application, you need to understand WPF’s threading rules. In this chapter, you explored these rules and learned how to safely update controls from other threads. You also saw how to build in progress notification, provide cancellation support, and make multithreading easy with BackgroundWorker.

995

ChAPTeR 32 nnn

The Add-in Model Add-ins (also known as plug-ins) are separately compiled components that your application can find, load, and use dynamically. Often an application is designed to use add-ins so that it can be enhanced in the future without needing to be modified, recompiled, and retested. Add-ins also give you the flexibility to customize separate instances of an application for a particular market or client. But the most common reason to use the add-in model is to allow third-party developers to extend the functionality of your application. For example, add-ins in Adobe Photoshop provide a wide range of picture-processing effects. Add-ins in Firefox provide enhanced web-surfing features and entirely new functionality. In both cases, the add-ins are created by third-party developers. Since .NET 1.0, developers have had all the technology they need to create their own add-in system. The two basic ingredients are interfaces (which allow you to define the contracts through which the application interacts with the add-in and the add-in interacts with the application) and reflection (which allows your application to dynamically discover and load add-in types from a separate assembly). However, building an add-in system from scratch requires a fair bit of work. You need to devise a way to locate add-ins, and you need to ensure that they’re managed correctly (in other words, that they execute in a restricted security context and can be unloaded when necessary). Fortunately, .NET has a prebuilt add-in model that saves you the trouble. It uses interfaces and reflection, like the add-in model that you’d probably write yourself. However, it handles the low-level plumbing for tedious tasks such as discovery and hosting. In this chapter, you’ll learn how to use the addin model in a WPF application.

Choosing Between MAF and MEF Before you can get started building an extensible application with add-ins, you need to deal with an unexpected headache. Namely, .NET doesn’t have just one add-in framework; it has two. .NET 3.5 introduced an add-in model called the Managed Add-in Framework (MAF). But to make matters even more interesting (and a whole lot more confusing), .NET 4 added a new model called the Managed Extensibility Framework (MEF). Developers, who had to create their own add-in system not long ago, suddenly have two completely separate technologies that share the same ground. So, what’s the difference? MAF is the more elaborate framework of the two. It allows you to decouple your add-ins from your application so they depend on nothing more than the interface you define. This gives you welcome flexibility if you want to handle versioning scenarios—for example, if you need to change the interface but continue to support old add-ins for backward compatibility. MAF also allows your application to load addins into a separate application domain so that they can crash harmlessly, without affecting the main 997

CHAPTER 32 n THE ADD-IN MODEL

application. All of these features mean that MAF works well if you have one development team working on an application and another one (or several) working on its add-ins. MAF is also particularly well suited for supporting third-party add-ins. But MAF’s features come at a cost. MAF is a complex framework, and setting up the add-in pipeline is tedious, even for a simple application. This is where MEF comes in. It’s a lighter-weight option that aims to make extensibility as easy as copying related assemblies into the same folder. But MEF also has a different underlying philosophy than MAF. Whereas MAF is a strict, interface-driven add-in model, MEF is a freewheelin’ system that allows an application to be built out of a collection of parts. Each part can export functionality, and any part can import the functionality of any other part. This system gives developers far more flexibility, and it works particularly well for designing composable applications (modular programs that are developed by a single development team but need to be assembled in different ways, with differently implemented features, for separate releases). The obvious danger is that MEF is too loose, and a poorly designed application can quickly become a tangle of interrelated parts. If you think MAF is the add-in system for you, keep reading—it’s the technology that’s discussed in this chapter. If you want to check out MEF, you can learn more at Microsoft’s MEF community site at http://tinyurl.com/37s2jdx. And if your real interest is not in add-ins but in composable applications, you’ll want to check out Microsoft’s Composite Application Library (CAL), which is also known by its old code name, Prism. Although MEF is a general-purpose solution for building any sort of modular .NET application, CAL is tailored for WPF. It includes UI-oriented features, such as the ability to let different modules communicate with events and show content in separate display regions. CAL also has support for creating “hybrid” applications that can be compiled for the WPF platform or the browser-based Silverlight platform. You can find the documentations and downloads for CAL at http://tinyurl.com/5ljve8.

n Note From this point on, when the text refers to the add-in model, it means the MAF add-in model.

Understanding the Add-in Pipeline The key advantage of the add-in model is that you don’t need to write the underlying plumbing for tasks such as discovery. The key disadvantage is the add-in model’s sheer complexity. The designers of .NET have taken great care to make the add-in model flexible enough to handle a wide range of versioning and hosting scenarios. The end result is that you must create at least seven (!) separate components to implement the add-in model in an application, even if you don’t need to use its most sophisticated features. The heart of the add-in model is the add-in pipeline, which is a chain of components that allow the hosting application to interact with an add-in (see Figure 32-1). At one end of the pipeline is the hosting application. At the other end is the add-in. In between are the five components that govern the interaction.

998

CHAPTER 32 n THE ADD-IN MODEL

Figure 32-1. Communicating through the add-in pipeline At first glance, this model seems a bit excessive. A simpler scenario would put a single layer (the contract) between the application and the add-in. However, the additional layers (the views and adapters) allow the add-in model to be much more flexible in certain situations (as described in the sidebar “MoreAdvanced Adapters”).

How the Pipeline Works The contract is the cornerstone of the add-in pipeline. It includes one or more interfaces that define how the host application can interact with its add-ins and how the add-ins can interact with the host application. The contract assembly can also include custom serializable types that you plan to use to transmit data between the host application and the add-in. The add-in pipeline is designed with extensibility and flexibility in mind. It’s for this reason that the host application and the add-in don’t directly use the contract. Instead, they use their own respective versions of the contract, called views. The host application uses the host view, while the add-in uses the add-in view. Typically, the view includes abstract classes that closely match the interfaces in the contract. Although they’re usually quite similar, the contracts and views are completely independent. It’s up to the adapters to link these two pieces together. The adapters perform this linkage by providing classes that simultaneously inherit from the view classes and implement the contract interfaces. Figure 32-2 shows this design.

Figure 32-2.Class relationships in the pipeline

999

CHAPTER 32 n THE ADD-IN MODEL

Essentially, the adapters bridge the gap between the views and the contract interface. They map calls on a view to calls on the contract interface. They also map calls on the contract interface to the corresponding method on the view. This complicates the design somewhat but adds an all-important extra layer of flexibility. To understand how the adapters work, consider what happens when an application uses an add-in. First, the host application calls one of the methods in the host view. But remember, the host view is an abstract class. Behind the scenes, the application is actually calling a method on the host adapter through the host view. (This is possible because the host adapter class derives from the host view class.) The host adapter then calls the corresponding method in the contract interface, which is implemented by the addin adapter. Finally, the add-in adapter calls a method in the add-in view. This method is implemented by the add-in, which performs the actual work.

MoRe-ADVAnCeD ADAPTeRS If you don’t have any specialized versioning or hosting needs, the adapters are fairly straightforward. They simply pass the work along through the pipeline. However, the adapters are also an important extensibility point for more-sophisticated scenarios. One example is versioning. Obviously, you can independently update an application or its add-ins without changing the way they interact, as long as you continue to use the same interfaces in the contract. However, in some cases you might need to change the interfaces to expose new features. This causes a bit of a problem, because the old interfaces must still be supported for backward compatibility with old add-ins. After a few revisions, you’ll end up with a complex mess of similar yet different interfaces, and the application will need to recognize and support them all. With the add-in model, you can take a different approach to backward compatibility. Instead of providing multiple interfaces, you can provide a single interface in your contract and use adapters to create different views. For example, a version 1 add-in can work with a version 2 application (which exposes a version 2 contract) as long as you have an add-in adapter that spans the gap. Similarly, if you develop an add-in that uses the version 2 contract, you can use it with the original version 1 application (and version 1 contract) by using a different add-in adapter. It’s possible to work similar magic if you have specialized hosting needs. For example, you can use adapters to load add-ins with different isolation levels or even share them between applications. The hosting application and the add-in don’t need to be aware of these details, because the adapters handle all the details. Even if you don’t need to create custom adapters to implement specialized versioning and hosting strategies, you still need to include these components. However, all your add-ins can use the same view and adapter components. In other words, after you’ve gone to the trouble of setting up the complete pipeline for one add-in, you can add more add-ins without much work, as illustrated in Figure 32-3.

1000

CHAPTER 32 n THE ADD-IN MODEL

Figure 32-3. Multiple add-ins that use the same pipeline In the following sections, you’ll learn how to implement the add-in pipeline for a WPF application.

The Add-in Folder Structure To use the add-in pipeline, you must follow a strict directory structure. This directory structure is separate from the application. In other words, it’s perfectly acceptable to have your application residing at one location and all the add-ins and pipeline components residing at another location. However, the add-in components must be arranged in specifically named subdirectories with respect to one another. For example, if your add-in system uses the root directory c:\MyApp, you need the following subdirectories: c:\MyApp\AddInSideAdapters c:\MyApp\AddInViews c:\MyApp\Contracts c:\MyApp\HostSideAdapters c:\MyApp\AddIns Finally, the AddIns directory (shown last in this list) must have a separate subdirectory for each add-in your application is using, such as c:\MyApp\AddIns\MyFirstAddIn, c:\MyApp\AddIns\MySecondAddIn, and so on. In this example, it’s assumed that the application executable is deployed in the c:\MyApp subdirectory. In other words, the same directory does double duty as the application folder and as the addin root. This is a common deployment choice, but it’s certainly not a requirement.

n Note If you’ve been paying close attention to the pipeline diagrams, you may have noticed that there’s a subdirectory for each component except the host-side views. That’s because the host views are used directly by the host application, so they’re deployed alongside the application executable. (In this example, that means they are in c:\MyApp.) The add-in views aren’t deployed in the same way, because it’s likely that several add-ins will use the same add-in view. Thanks to the dedicated AddInViews folder, you need to deploy (and update) just one copy of each add-in view assembly.

1001

CHAPTER 32 n THE ADD-IN MODEL

Preparing a Solution That Uses the Add-in Model The add-in folder structure is mandatory. If you leave out one of the subdirectories listed in the previous section, you’ll encounter a runtime exception when you search for add-ins. Currently, Visual Studio doesn’t have a template for creating applications that use add-ins. Thus, it’s up to you to create these folders and set up your Visual Studio project to use them. Here’s the easiest approach to follow: 1.

Create a top-level directory that will hold all the projects you’re about to create. For example, you might name this directory c:\AddInTest.

2.

Create a new WPF project for the host application in this directory. It doesn’t matter what you name the project, but you must place it in the top-level directory you created in step 1 (for example, c:\AddInTest\HostApplication).

3.

Add a new class library project for each pipeline component, and place them all in the same solution. At a bare minimum, you’ll need to create a project for one add-in (for example, c:\AddInTest\MyAddIn), one add-in view (c:\AddInTest\ MyAddInView), one add-in-side adapter (c:\AddInTest\MyAddInAdapter), one host view (c:\AddInTest\HostView), and one host-side adapter (c:\AddInTest\ HostAdapter). Figure 32-4 shows an example from the downloadable code for this chapter, which you’ll consider in the following sections. It includes an application (named HostApplication) and two add-ins (named FadeImageAddIn and NegativeImageAddIn).

Figure 32-4. A solution that uses the add-in pipeline

n Note Technically, it doesn’t matter what project names and directory names you use when you create the pipeline components. The required folder structure, which you learned about in the previous section, will be created when you build the application (provided you configure your project settings properly, as described in the following two steps). However, to simplify the configuration process, it’s strongly recommended that you create all the project directories in the top-level directory you established in step 1. 1002

CHAPTER 32 n THE ADD-IN MODEL

4.

Now you need to create a build directory inside the top-level directory. This is where your application and all the pipeline components will be placed after they’re compiled. It’s common to name this directory Output (as in c:\ AddInTest\Output).

5.

As you design the various pipeline components, you’ll modify the build path of each one so that the component is placed in the right subdirectory. For example, your add-in adapter should be compiled to a directory such as c:\AddInTest\ Output\AddInSideAdapters. To modify the build path, double-click the Properties node in the Solution Explorer. Then click the Build tab. In the Output section (at the bottom), you’ll find a text box named Output Path. You need to use a relative output path that travels one level up the directory tree and then uses the Output directory. For example, the output path for an add-in adapter would be ..\Output\AddInSideAdapters. As you build each component in the following sections, you’ll learn which build path to use. Figure 32-5 shows a preview of the final result, based on the solution shown in Figure 32-4.

Figure 32-5. The folder structure for a solution that uses the add-in pipeline There’s one more consideration when developing with the add-in model in Visual Studio: references. Some pipeline components need to reference other pipeline components. However, you don’t want the referenced assembly to be copied with the assembly that contains the reference. Instead, you rely on the add-in model’s directory system. To prevent a referenced assembly from being copied, you need to select the assembly in the Solution Explorer (it appears under the References node). Then set Copy Local to False in the Properties window. As you build each component in the following sections, you’ll learn which references to add.

n Tip Correctly configuring an add-in project can take a bit of work. To start off on the right foot, you can use the add-in example that’s discussed in this chapter, which is available with the downloadable code for this book.

1003

CHAPTER 32 n THE ADD-IN MODEL

Creating an Application That Uses Add-Ins In the following sections, you’ll create an application that uses the add-in model to support different ways of processing a picture (Figure 32-6). When the application starts, it lists all the add-ins that are currently present. The user can then select one of the add-ins from the list and use it to modify the currently displayed picture.

Figure 32-6. An application that uses add-ins to manipulate a picture

The Contract The starting point for defining the add-in pipeline for your application is to create a contract assembly. The contract assembly defines two things: • •

The interfaces that determine how the host will interact with the add-in and how the add-in will interact with the host. Custom types that you use to exchange information between the host and add-in. These types must be serializable.

The example shown in Figure 32-6 uses an exceedingly simple contract. Plug-ins provide a method named ProcessImageBytes() that accepts a byte array with image data, modifies it, and returns the modified byte array. Here’s the contract that defines this method: 1004

CHAPTER 32 n THE ADD-IN MODEL

[AddInContract] public interface IImageProcessorContract : IContract { byte[] ProcessImageBytes(byte[] pixels); } When creating a contract, you must derive from the IContract interface, and you must decorate the class with the AddInContract attribute. Both the interface and the attribute are found in the System.AddIn. Contract namespace. To have access to them in your contract assembly, you must add a reference to the System.AddIn.Contract.dll assembly. Because the image-processing example doesn’t use custom types to transmit data (just ordinary byte arrays), no types are defined in the contract assembly. Byte arrays can be transmitted between the host application and add-in because arrays and bytes are serializable. The only additional step you need is to configure the build directory. The contract assembly must be placed in the Contracts subdirectory of the add-in root, which means you can use an output path of ..\ Output\Contracts in the current example.

n Note In this example, the interfaces are kept as simple as possible to avoid clouding the code with extra details. In a more realistic image-processing scenario, you might include a method that returns a list of configurable parameters that affect how the add-in processes the image. Each add-in would have its own parameters. For example, a filter that darkens a picture might include an Intensity setting, a filter that skews a picture might have an Angle setting, and so on. The host application could then supply these parameters when calling the ProcessImageBytes() method.

The Add-in View The add-in view provides an abstract class that mirrors the contract assembly and is used on the add-in side. Creating this class is easy: [AddInBase] public abstract class ImageProcessorAddInView { public abstract byte[] ProcessImageBytes(byte[] pixels); } Notice that the add-in view class must be decorated with the AddInBase attribute. This attribute is found in the System.AddIn.Pipeline namespace. The add-in view assembly requires a reference to the System.AddIn.dll assembly in order to access it. The add-in view assembly must be placed in the AddInViews subdirectory of the add-in root, which means you can use an output path of ..\Output\AddInViews in the current example.

The Add-In The add-in view is an abstract class that doesn’t provide any functionality. To create a usable add-in, you need a concrete class that derives from the abstract view class. This class can then add the code that actually does the work (in this case, processing the image).

1005

CHAPTER 32 n THE ADD-IN MODEL

The following add-in inverts the color values to create an effect that’s similar to a photo negative. Here’s the complete code: [AddIn("Negative Image Processor", Version = "1.0.0.0", Publisher = "Imaginomics", Description = "Inverts colors to look like a photo negative")] public class NegativeImageProcessor : AddInView.ImageProcessorAddInView { public override byte[] ProcessImageBytes(byte[] pixels) { for (int i = 0; i < pixels.Length - 2; i++) { // Assuming 24-bit, color, each pixel has three bytes of data. pixels[i] = (byte)(255 - pixels[i]); pixels[i + 1] = (byte)(255 - pixels[i + 1]); pixels[i + 2] = (byte)(255 - pixels[i + 2]); } return pixels; } }

n Note In this example, the byte array is passed into the ProcessImageBytes() method through a parameter, modified directly, and then passed back to the calling code as the return value. However, when you call ProcessImageBytes() from a different application domain, this behavior isn’t as simple as it seems. The add-in infrastructure actually makes a copy of the original byte array and passes that copy to the add-in’s application domain. After the byte array is modified and has been returned from the method, the add-in infrastructure copies it back into the host’s application domain. If ProcessImageBytes() didn’t return the modified byte array in this way, the host would never see the changed picture data. To create an add-in, you simply need to derive a class from the abstract view class and decorate it with the AddIn attribute. Additionally, you can use the properties of the AddIn attribute to supply an add-in name, version, publisher, and description, as done here. This information is made available to the host during add-in discovery. The add-in assembly requires two references: one to the System.AddIn.dll assembly and one to the add-in view project. However, you must set the Copy Local property of the add-in view reference to False (as described earlier in the section “Preparing a Solution That Uses the Add-in Model”). That’s because the add-in view isn’t deployed with the add-in—instead, it’s placed in the designated AddInViews subdirectory. The add-in must be placed in its own subdirectory in the AddIns subdirectory of the add-in root. In the current example, you would use an output path such as ..\Output\AddIns\NegativeImageAddIn.

The Add-in Adapter The current example has all the add-in functionality that you need, but there’s still a gap between the addin and the contract. Although the add-in view is modeled after the contract, it doesn’t implement the contract interface that’s used for communication between the application and the add-in.

1006

CHAPTER 32 n THE ADD-IN MODEL

The missing ingredient is the add-in adapter. It implements the contract interface. When a method is called in the contract interface, it calls the corresponding method in the add-in view. Here’s the code for the most straightforward add-in adapter that you can create: [AddInAdapter] public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract { private AddInView.ImageProcessorAddInView view; public ImageProcessorViewToContractAdapter( AddInView.ImageProcessorAddInView view) { this.view = view; } public byte[] ProcessImageBytes(byte[] pixels) { return view.ProcessImageBytes(pixels); } } All add-in adapters must derive from ContractBase (from the System.AddIn.Pipeline namespace). ContractBase derives from MarshalByRefObject, which allows the adapter to be called over an application domain boundary. All add-in adapters must also be decorated with the AddInAdapter attribute (from the System.AddIn.Pipeline namespace). Furthermore, the add-in adapter must include a constructor that receives an instance of the appropriate view as an argument. When the add-in infrastructure creates the add-in adapter, it automatically uses this constructor and passes in the add-in itself. (Remember, the addin derives from the abstract add-in view class expected by the constructor.) Your code simply needs to store this view for later use. The add-in adapter requires three references: one to System.AddIn.dll, one to System.AddIn.Contract. dll, and one to the contract project. You must set the Copy Local property of the contract reference to False (as described earlier in the section “Preparing a Solution That Uses the Add-in Model”). The add-in adapter assembly must be placed in the AddInSideAdapters subdirectory of the add-in root, which means you can use an output path of ..\Output\AddInSideAdapters in the current example.

The Host View The next step is to build the host side of the add-in pipeline. The host interacts with the host view. Like the add-in view, the host view is an abstract class that closely mirrors the contract interface. The only difference is that it doesn’t require any attributes. public abstract class ImageProcessorHostView { public abstract byte[] ProcessImageBytes(byte[] pixels); } The host view assembly must be deployed along with the host application. You can adjust the output path manually (for example, so the host view assembly is placed in the ..\Output folder in the current example). Or, when you add the host view reference to the host application, you can leave the Copy Local property set to True. This way, the host view will be copied automatically to the same output directory as the host application. 1007

CHAPTER 32 n THE ADD-IN MODEL

The Host Adapter The host-side adapter derives from the host view. It receives an object that implements the contract, which it can then use when its methods are called. This is the same forwarding process that the add-in adapter uses, but in reverse. In this example, when the host application calls the ProcessImageBytes() method of the host view, it’s actually calling ProcessImageBytes() in the host adapter. The host adapter calls ProcessImageBytes() on the contract interface (which is then forwarded across the application boundary and transformed into a method call on the add-in adapter). Here’s the complete code for the host adapter: [HostAdapter] public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView { private Contract.IImageProcessorContract contract; private ContractHandle contractHandle; public ImageProcessorContractToViewHostAdapter( Contract.IImageProcessorContract contract) { this.contract = contract; contractHandle = new ContractHandle(contract); } public override byte[] ProcessImageBytes(byte[] pixels) { return contract.ProcessImageBytes(pixels); } } You’ll notice that the host adapter uses two member fields. It stores a reference to the current contract object, and it stores a reference to a System.AddIns.Pipeline.ContractHandle object. The ContractHandle object manages the lifetime of the add-in. If the host adapter doesn’t create a ContractHandle object (and keep a reference to it), the add-in will be released immediately after the constructor code ends. When the host application attempts to use the add-in, it will receive an AppDomainUnloadedException. The host adapter project needs references to System.Add.dll and System.AddIn.Contract.dll. It also needs references to the contract assembly and the host view assembly (both of which must have Copy Local set to False). The output path is the HostSideAdapters subdirectory in the add-in root (in this example, it’s ..\Output\HostSideAdapters).

The Host Now that the infrastructure is in place, the final step is to create the application that uses the add-in model. Although any type of executable .NET application could be a host, this example uses a WPF application. The host needs just one reference that points to the host view project. The host view is the entry point to the add-in pipeline. In fact, now that you’ve done the heavy lifting in implementing the pipeline, the host doesn’t need to worry about how it’s managed. It simply needs to find the available add-ins, activate the ones it wants to use, and then call the methods that are exposed by the host view.

1008

CHAPTER 32 n THE ADD-IN MODEL

The first step—finding the available add-ins—is called discovery. It works through the static methods of the System.AddIn.Hosting.AddInStore class. To load add-ins, you simply supply the add-in root path and call AddInStore.Update(), as shown here: // In this example, the path where the application is running // is also the add-in root. string path = Environment.CurrentDirectory; AddInStore.Update(path); After calling Update(), the add-in system will create two files with cached information. A file named PipelineSegments.store will be placed in the add-in root. This file includes information about the different views and adapters. A file named AddIns.store will be placed in the AddIns subdirectory, with information about all the available add-ins. If new views, adapters, or add-ins are added, you can update these files by calling AddInStore.Update() again. (This method returns quite quickly if there are no new add-ins or pipeline components.) If there is reason to expect that there is a problem with existing add-in files, you can call AddInStore.Rebuild() instead, which always rebuilds the add-in files from scratch. After you’ve created the cache files, you can search for the specific add-ins. You can use the FindAddIn() method to find a single specific add-in, or you can use the FindAddIns() method to find all the add-ins that match a specified host view. The FindAddIns() method returns a collection of tokens, each of which is an instance of the System.AddIn.Hosting.AddInToken class. IList tokens = AddInStore.FindAddIns( typeof(HostView.ImageProcessorHostView), path); lstAddIns.ItemsSource = tokens; You can get information about the add-in through a few key properties (Name, Description, Publisher, and Version). In the image-processing application (shown in Figure 32-6), the token list is bound to a ListBox control, and some basic information is shown about each add-in by using the following data template:







You can create an instance of the add-in by calling the AddInToken.Activate method. In the current application, the user clicks the Go button to activate an add-in. The information is then pulled out of the current image (which is shown in the window) and passed to the ProcessImageBytes() method of the host view. Here’s how it works: private void cmdProcessImage_Click(object sender, RoutedEventArgs e) { // Copy the image information from the image to a byte array. BitmapSource source = (BitmapSource)img.Source; int stride = source.PixelWidth * source.Format.BitsPerPixel/8; stride = stride + (stride % 4) * 4; 1009

CHAPTER 32 n THE ADD-IN MODEL

int arraySize = stride * source.PixelHeight * source.Format.BitsPerPixel / 8; byte[] originalPixels = new byte[arraySize]; source.CopyPixels(originalPixels, stride, 0); // Get the selected add-in token. AddInToken token = (AddInToken)lstAddIns.SelectedItem; // Get the host view. HostView.ImageProcessorHostView addin = token.Activate( AddInSecurityLevel.Internet); // Use the add-in. byte[] changedPixels = addin.ProcessImageBytes(originalPixels); // Create a new BitmapSource with the changed image data, and display it. BitmapSource newSource = BitmapSource.Create(source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY, source.Format, source.Palette, changedPixels, stride); img.Source = newSource; } When you call the AddInToken.Activate method, quite a few steps unfold behind the scenes:

1010

1.

A new application domain is created for the add-in. Alternatively, you can load the add-in into the application domain of the host application or into a completely separate process. However, the default is to place it in a distinct application domain in the current process, which usually gives the best compromise between stability and performance. You can also choose the level of permissions that are given to the new application domain. (In this example, they’re limited to the Internet set of permissions, which is a heavily restricted permission set that’s applied to code that’s executed from the Web.)

2.

The add-in assembly is loaded into the new application domain. The add-in is then instantiated through reflection, using its no-argument constructor. As you’ve already seen, the add-in derives from an abstract class in the add-in view assembly. As a result, loading the add-in also loads the add-in view assembly into the new application domain.

3.

The add-in adapter is instantiated in the new application domain. The add-in is passed to the add-in adapter as a constructor argument. (The add-in is typed as the add-in view.)

4.

The add-in adapter is made available to the host’s application domain (through a remoting proxy). However, it’s typed as the contract that it implements.

5.

In the host application domain, the host adapter is instantiated. The add-in adapter is passed to the host adapter through its constructor.

6.

The host adapter is returned to the host application (typed as the host view). The application can now call the methods of the host view to interact with the add-in through the add-in pipeline.

CHAPTER 32 n THE ADD-IN MODEL

There are other overloads for the Activate method that allow you to supply a custom permission set (to fine-tune security), a specific application domain (which is useful if you want to run several add-ins in the same application domain), and an outside process (which allows you to host the add-in in a completely separate EXE application for even greater isolation). All of these examples are illustrated in the Visual Studio help. This code completes the example. The host application can now discover its add-ins, activate them, and interact with them through the host view.

ADD-In lIFeTIMe You don’t need to manage the lifetime of your add-ins by hand. Instead, the add-in system will automatically release an add-in and shut down its application domain. In the previous example, the add-in is released when the variable that points to the host view goes out of scope. If you want to keep the same add-in active for a longer time, you could assign it to a member variable in the window class. In some situations, you might want more control over the add-in lifetime. The add-in model gives the host application the ability to shut down an add-in automatically by using the AddInController class (from the System.AddIn.Hosting namespace), which tracks all the currently active add-ins. The AddInControls provides a static method named GetAddInController(), which accepts a host view and returns an AddInController for that add-in. You can then use the AddInController.Shutdown() method to end it, as shown here: AddInController controller = AddInController.GetAddInController(addin); controller.Shutdown();

At this point, the adapters will be disposed, the add-in will be released, and the add-in’s application domain will be shut down if it doesn’t contain any other add-ins.

More Add-Ins Using the same add-in view, it’s possible to create an unlimited number of distinct add-ins. In this example there are two, which process images in different ways. The second add-in uses a crude algorithm to darken the picture by removing part of the color from random pixels: [AddIn("Fade Image Processor", Version = "1.0.0.0", Publisher = "SupraImage", Description = "Darkens the picture")] public class FadeImageProcessor : AddInView.ImageProcessorAddInView { public override byte[] ProcessImageBytes(byte[] pixels) { Random rand = new Random(); int offset = rand.Next(0, 10); for (int i = 0; i < pixels.Length - 1 - offset; i++) { if ((i + offset) % 5 == 0) { pixels[i] = 0; } } return pixels; } 1011

CHAPTER 32 n THE ADD-IN MODEL

} In the current example, this add-in builds to the output path ..\Output\AddIns\FadeImageAddIn. There’s no need to create additional views or adapters. After you deploy this add-in (and then call the Rebuild() or Update() method of the AddInStore class), your host application will find both add-ins.

Interacting with the Host In the current example, the host is in complete control of the add-in. However, the relationship is often reversed. A common example is an add-in that drives an area of application functionality. This is particularly common with visual add-ins (the subject of the next section), such as custom toolbars. Often this process of allowing the add-in to call the host is called automation. From a conceptual standpoint, automation is quite straightforward. The add-in simply needs a reference to an object in the host’s application domain, which it can manipulate through a separate interface. However, the add-in system’s emphasis on versioning flexibility makes the implementation of this technique a bit more complicated. A single host interface is not enough, because it tightly binds the host and the add-in together. Instead, you’ll need to implement a pipeline with views and adapters. To see this challenge, consider the slightly updated version of the image-processing application, which is shown in Figure 32-7. It features a progress bar at the bottom of the window that’s updated as the add-in processes the image data.

Figure 32-7. An add-in that reports progress

n Tip The rest of this section explores the changes you need to make to the image processor to support host automation. To see how these pieces fit together and examine the full code, download the code samples for this chapter. 1012

CHAPTER 32 n THE ADD-IN MODEL

For this application to work, the add-in needs a way to pass progress information to the host while it works. The first step in implementing this solution is to create the interface that defines how the add-in can interact with the host. This interface should be placed in the contract assembly (or in a separate assembly in the Contracts folder). Here’s the interface that describes how the add-in should be allowed to report progress, by calling a method named ReportProgress() in the host application: public interface IHostObjectContract : IContract { void ReportProgress(int progressPercent); } As with the add-in interface, the host interface must inherit from IContract. Unlike the add-in interface, the host interface does not use the AddInContract attribute, because it isn’t implemented by an add-in. The next step is to create the add-in view and host view. As when designing an add-in, you simply need an abstract class that closely corresponds to the interface you’re using. To use the IHostObjectContract interface shown earlier, you simply need to add the following class definition to both the add-in view and host view projects. public abstract class HostObject { public abstract void ReportProgress(int progressPercent); } Notice that the class definition does not use the AddInBase attribute in either project. The actual implementation of the ReportProgress() method is in the host application. It needs a class that derives from the HostObject class (in the host view assembly). Here’s a slightly simplified example that uses the percentage to update a ProgressBar control: public class AutomationHost : HostView.HostObject { private ProgressBar progressBar; public Host(ProgressBar progressBar) { this.progressBar = progressBar; } public override void ReportProgress(int progressPercent) { progressBar.Value = progressPercent; } } You now have a mechanism that the add-in can use to send progress information to the host application. However, there’s one problem—the add-in doesn’t have any way to get a reference to the HostObject. This problem doesn’t occur when the host application is using an add-in, because it has a discovery feature that it can use to search for add-ins. There’s no comparable service for add-ins to locate their host. The solution is for the host application to pass the HostObject reference to the add-in. Typically, this step will be performed when the add-in is first activated. By convention, the method that the host application uses to pass this reference is often called Initialize(). 1013

CHAPTER 32 n THE ADD-IN MODEL

Here’s the updated contract for image processor add-ins: [AddInContract] public interface IImageProcessorContract : IContract { byte[] ProcessImageBytes(byte[] pixels); void Initialize(IHostObjectContract hostObj); } When Initialize() is called, the add-in will simply store the reference for later use. The add-in can then call the ReportProgress() method whenever that is appropriate, as shown here: [AddIn] public class NegativeImageProcessor : AddInView.ImageProcessorAddInView { private AddInView.HostObject host; public override void Initialize(AddInView.HostObject hostObj) { host = hostObj; } public override byte[] ProcessImageBytes(byte[] pixels) { int iteration = pixels.Length / 100; for (int i = 0; i < pixels.Length - 2; i++) { pixels[i] = (byte)(255 - pixels[i]); pixels[i + 1] = (byte)(255 - pixels[i + 1]); pixels[i + 2] = (byte)(255 - pixels[i + 2]); if (i % iteration == 0) host.ReportProgress(i / iteration); } return pixels; } } So far, the code hasn’t posed any real challenges. However, the last piece—the adapters—is a bit more complicated. Now that you’ve added the Initialize() method to the add-in contract, you need to also add it to the host view and add-in view. However, the signature of the method can’t match the contract interface. That’s because the Initialize() method in the interface expects an IHostObjectContract as an argument. The views, which are not linked to the contract in any way, have no knowledge of the IHostObjectContract. Instead, they use the abstract HostObject class that was described earlier: public abstract class ImageProcessorHostView { public abstract byte[] ProcessImageBytes(byte[] pixels); public abstract void Initialize(HostObject host); }

1014

CHAPTER 32 n THE ADD-IN MODEL

The adapters are the tricky part. They need to bridge the gap between the abstract HostObject view classes and the IHostObjectContract interface. For example, consider the ImageProcessorContractToViewHostAdapter on the host side. It derives from the abstract ImageProcessorHostView class, and as a result it implements the version of Initialize() that receives a HostObject instance. This Initialize() method needs to convert this view to the contract and then call the IHostObjectContract.Initialize() method. The trick is to create an adapter that performs this transformation (much like the adapter that performs the same transformation with the add-in view and add-in interface). The following code shows the new HostObjectViewToContractHostAdapter that does the work and the Initialize() method that uses it to make the jump from the view class to the contract interface: public class HostObjectViewToContractHostAdapter : ContractBase, Contract.IHostObjectContract { private HostView.HostObject view; public HostObjectViewToContractHostAdapter(HostView.HostObject view) { this.view = view; } public void ReportProgress(int progressPercent) { view.ReportProgress(progressPercent); } } [HostAdapter] public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView { private Contract.IImageProcessorContract contract; private ContractHandle contractHandle; ... public override void Initialize(HostView.HostObject host) { HostObjectViewToContractHostAdapter hostAdapter = new HostObjectViewToContractHostAdapter(host); contract.Initialize(hostAdapter); } } A similar transformation takes place in the add-in adapter, but in reverse. Here, the ImageProcessorViewToContractAdapter implements the IImageProcessorContract interface. It needs to take the IHostObjectContract object that it receives in its version of the Initialize() method and then convert the contract to a view. Next, it can pass the call along by calling the Initialize() method in the view. Here’s the code:

1015

CHAPTER 32 n THE ADD-IN MODEL

[AddInAdapter] public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract { private AddInView.ImageProcessorAddInView view; ... public void Initialize(Contract.IHostObjectContract hostObj) { view.Initialize(new HostObjectContractToViewAddInAdapter(hostObj)); } } public class HostObjectContractToViewAddInAdapter : AddInView.HostObject { private Contract.IHostObjectContract contract; private ContractHandle handle; public HostObjectContractToViewAddInAdapter( Contract.IHostObjectContract contract) { this.contract = contract; this.handle = new ContractHandle(contract); } public override void ReportProgress(int progressPercent) { contract.ReportProgress(progressPercent); } } Now, when the host calls Initialize() on an add-in, it can flow through the host adapter (ImageProcessorContractToViewHostAdapter) and the add-in adapter (ImageProcessorViewToContractAdapter), before being called on the add-in. When the add-in calls the ReportProgress() method, it flows through similar steps, but in reverse. First it flows through the add-in adapter (HostObjectContractToViewAddInAdapter), and then it passes to the host adapter (HostObjectViewToContractHostAdapter). This walk-through completes the example—sort of. The problem is that the host application calls the ProcessImageBytes() method on the main user interface thread. As a result, the user interface is effectively locked up. Although the calls to ReportProgress() are handled and the progress bar is updated, the window isn’t refreshed until the operation is complete. A far better approach is to perform the time-consuming call to ProcessImageBytes() on a background thread, either by creating a Thread object by hand or by using the BackgroundWorker. Then, when the user interface needs to be updated (when ReportProgress() is called and when the final image is returned), you must use the Dispatcher.BeginInvoke() method to marshal the call back to the user interface thread. All of these techniques were demonstrated earlier in this chapter. To see the threading code in action in this example, refer to the downloadable code for this chapter.

1016

CHAPTER 32 n THE ADD-IN MODEL

Visual Add-Ins Considering that the WPF is a display technology, you’ve probably started to wonder whether there’s a way to have an add-in generate a user interface. This isn’t a small challenge. The problem is that the user interface elements in WPF aren’t serializable. Thus, they can’t be passed between the host application and the add-in. Fortunately, the designers of the add-in system created a sophisticated workaround. The solution is to allow WPF applications to display user interface content that’s hosted in separate application domains. In other words, your host application can display controls that are actually running in the application domain of an add-in. If you interact with these controls (clicking them, typing in them, and so on), the events are fired in the add-in’s application domain. If you need to pass information from the add-in to the application, or vice versa, you use the contract interfaces, as you’ve explored in the previous sections. Figure 32-8 shows this technique in action in a modified version of the image-processing application. When an add-in is selected, the host application asks the add-in to provide a control with suitable content. That control is then displayed at the bottom of the window.

Figure 32-8. A visual add-in In this example, the negative image add-in has been selected. It provides a user control that wraps an Image control (with a preview of the effect) and a Slider control. As the slider is adjusted, the intensity of the effect is changed, and the preview is updated. (The update process is sluggish, because of the poorly 1017

CHAPTER 32 n THE ADD-IN MODEL

optimized image-processing code. Much better algorithms could be used, possibly incorporating unsafe code blocks for maximum performance.) Although the plumbing that makes this work is fairly sophisticated, it’s surprisingly easy to use. The key ingredient is the INativeHandleContract interface from the System.AddIn.Contract namespace. It allows a window handle to be passed between an add-in and the host application. Here’s the revised IImageProcessorContract from the contract assembly. It replaces the ProcessImageBytes() method with a GetVisual() method that accepts similar image data but returns a chunk of user interface: [AddInContract] public interface IImageProcessorContract : IContract { INativeHandleContract GetVisual(Stream imageStream); } You don’t use the INativeHandlerContract in the view classes, because it isn’t directly usable in your WPF applications. Instead, you use the type you expect to see—a FrameworkElement. Here’s the host view: public abstract class ImageProcessorHostView { public abstract FrameworkElement GetVisual(Stream imageStream); } And here’s the nearly identical add-in view: [AddInBase] public abstract class ImageProcessorAddInView { public abstract FrameworkElement GetVisual(Stream imageStream); } This example is surprisingly similar to the automation challenge in the previous section. Once again, you have a different type being passed in the contract from the one that’s used in the views. And once again, you need to use the adapters to perform the contract-to-view and view-to-contract conversion. However, this time the work is done for you by a specialized class called FrameworkElementAdapters. FrameworkElementAdapters is found in the System.AddIn.Pipeline namespace, but it’s actually part of WPF, and it’s part of the System.Windows.Presentation.dll assembly. The FrameworkElementAdapters class provides two static methods that perform the conversion work: ContractToViewAdapter() and ViewToContractAdapter(). Here’s how the FrameworkElementAdapters.ContractToViewAdapter() method bridges the gap in the host adapter: [HostAdapter] public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView { private Contract.IImageProcessorContract contract; private ContractHandle contractHandle; ... public override FrameworkElement GetVisual(Stream imageStream) { return FrameworkElementAdapters.ContractToViewAdapter( contract.GetVisual(imageStream)); 1018

CHAPTER 32 n THE ADD-IN MODEL

} } And here’s how the FrameworkElementAdapters.ViewToContractAdapter() method bridges the gap in the add-in adapter: [AddInAdapter] public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract { private AddInView.ImageProcessorAddInView view; ... public INativeHandleContract GetVisual(Stream imageStream) { return FrameworkElementAdapters.ViewToContractAdapter( view.GetVisual(imageStream)); } } Now the final detail is to implement the GetVisual() method in the add-in. In the negative image processor, a new user control named ImagePreview is created. The image data is passed to the ImagePreview control, which sets up the preview image and handles slider clicks. (The user control code is beside the point for this example, but you can see the full details by downloading the samples for this chapter.) [AddIn] public class NegativeImageProcessor : AddInView.ImageProcessorAddInView { public override FrameworkElement GetVisual(System.IO.Stream imageStream) { return new ImagePreview(imageStream); } } Now that you’ve seen how to return a user interface object from an add-in, there’s no limit to what type of content you can generate. The basic infrastructure—the INativeHandleContract interface and the FrameworkElementAdapters class—remains the same.

The Last Word In this chapter, you dove into the deeply layered add-in model. You learned how its pipeline works, why it works the way it does, and how to create basic add-ins that support host automation and provide visual content. There’s quite a bit more you can learn about the add-in model. If you plan to make add-ins a key part of a professional application, you may want to take a closer look at specialized versioning and hosting scenarios and deployment, best practices for dealing with unhandled add-in exceptions, and how to allow more-complex interactions between the host and add-in and between separate add-ins. To get the details, you can visit two old (and now discontinued) Microsoft blogs that still provide technical information about the add-in model. They are http://blogs.msdn.com/clraddins (with posts by the Microsoft developers who created the add-in system) and http://blogs.msdn.com/zifengh (with posts by Jason He, who writes about his experience adapting Paint.NET to use the add-in model). 1019

ChAPTeR 33 nnn

ClickOnce Deployment Sooner or later, you’ll want to unleash your WPF applications on the world. Although you can use dozens of ways to transfer an application from your development computer to an end user’s desktop, most WPF applications use one of the following deployment strategies: Run in the browser: If you create a page-based WPF application, you can run it right in the browser. You don’t need to install anything. However, your application needs to be able to function with a very limited set of privileges. (For example, you won’t be allowed to access arbitrary files, use the Windows Registry, pop up new windows, and so on.) You learned about this approach in Chapter 24. Deploy via the browser: WPF applications integrate closely with the ClickOnce setup feature, which allows users to launch a setup program from a browser page. Best of all, applications that are installed through ClickOnce can be configured to check for updates automatically. On the negative side, you have little ability to customize your setup and no way to perform system configuration tasks (such as modifying the Windows Registry, creating a database, and so on). Deploy via a traditional setup program: This approach still lives on in the WPF world. If you choose this option, it’s up to you whether you want to create a full-fledged Windows Installer setup or a more streamlined (but more limited) ClickOnce setup. After you’ve built your setup, you can choose to distribute it by placing it on a CD, in an e-mail attachment, on a network share, and so on. In this chapter, you’ll focus on the second approach: deploying your application with the ClickOnce deployment model.

Understanding Application Deployment Although it’s technically possible to move a .NET application from one computer to another just by copying the folder that contains it, professional applications often require a few more frills. For example, you might need to add multiple shortcuts to the Start menu, add registry settings, and set up additional resources (such as a custom event log or a database). To get these features, you need to create a custom setup program. You have many options for creating a setup program. You can use a retail product such as Flexera Software’s InstallShield, or you can create a Windows Installer setup by using the Setup Project template in 1021

CHAPTER 33 n CLICKONCE DEPLOYMENT

Visual Studio. Traditional setup programs give you a familiar setup wizard, with plenty of features for transferring files and performing a variety of configuration actions. Your other choice is to use the ClickOnce deployment system that’s closely integrated in WPF. ClickOnce has plenty of limitations (most of them by design), but it offers two important advantages: • •

Support for installing from a browser page (which can be hosted on an internal network or placed on the Web) Support for automatically downloading and installing updates

These two features might not be enough to entice developers to give up the features of a full-fledged setup program. But if you need a simple, lightweight deployment that works over the Web and supports automatic updates, ClickOnce is perfect.

ClICkonCe AnD PARTIAl TRuST Ordinary WPF applications require full trust because your application needs unmanaged code permission to create a WPF window. This means that installing a stand-alone WPF application using ClickOnce presents the same security roadblock as installing any type of application from the Web—namely, the web browser will present a security warning. If the user goes ahead, the installed application will have the ability to do anything that the current user can do. WPF does have a way to combine partial-trust programming and a ClickOnce-based installation experience. The trick is to use the XBAP model described in Chapter 24. In this situation, your application runs in the browser; therefore, it doesn’t need to create any windows and doesn’t need unmanaged code permission. Even better, because the application is accessed through a URL (and then cached locally), the user always runs the latest, most up-to-date version. Behind the scenes, the automatic XBAP download uses the same ClickOnce technology that you’ll use in this chapter. XBAPs aren’t covered in this chapter. For more information about XBAPs and partial-trust programming, refer to Chapter 24. ClickOnce is designed with simple, straightforward applications in mind. It’s particularly suitable for line-of-business applications and internal company software. Typically, these applications perform their work with the data and services on middle-tier server computers. As a result, they don’t need privileged access to the local computer. These applications are also deployed in enterprise environments that may include thousands of workstations. In these environments, the cost of application deployment and updating isn’t trivial, especially if it needs to be handled by an administrator. As a result, it’s more important to provide a simple, streamlined setup process than to pack in features. ClickOnce may also make sense for consumer applications that are deployed over the Web, particularly if these applications are updated frequently and don’t have extensive installation requirements. However, the limitations of ClickOnce (such as the lack of flexibility for customizing the setup wizard) don’t make it practical for sophisticated consumer applications that have detailed setup requirements or need to guide the user through a set of proprietary configuration steps. In these cases, you’ll need to create a custom setup application.

1022

CHAPTER 33 n CLICKONCE DEPLOYMENT

n Note For ClickOnce to install a WPF application, the computer must already have the .NET Framework runtime. When you first launch a ClickOnce setup, a bootstrapper runs that verifies this requirement. If the .NET Framework runtime isn’t installed, the bootstrapper shows a message box that explains the issue and prompts the user to install .NET from Microsoft’s website.

The ClickOnce Installation Model Although ClickOnce supports several types of deployment, the overall model is designed to make web deployment practical and easy. Here’s how it works: You use Visual Studio to publish your ClickOnce application to a web server. Then the user surfs to an automatically generated web page (named publish. htm) that provides a link to install the application. When the user clicks that link, the application is downloaded, installed, and added to the Start menu. Figure 33-1 shows this process.

Figure 33-1. Installing a ClickOnce application Although ClickOnce is ideal for web deployment, the same basic model lends itself to other scenarios, including the following: • • •

Deploying your application from a network file share Deploying your application from a CD or DVD Deploying your application to a web server or network file share and then sending a link to the setup program via e-mail

The installation web page isn’t created when deploying to a network share, a CD, or a DVD. Instead, in these cases users must install the application by running the setup.exe program directly. The most interesting part of a ClickOnce deployment is the way it supports updating. Essentially, you (the developer) have control over several update settings. For example, you can configure the application to check for updates automatically or periodically at certain intervals. When users launch your application, they actually run a shim that checks for newer versions and offers to download them. You can even configure your application to use a weblike online-only mode. In this situation, the application must be launched from the ClickOnce web page. The application is still cached locally for optimum performance, but users won’t be able to run the application unless they’re able to connect to the site where the application was published. This ensures that users always run the latest, most up-to-date version of your application.

1023

CHAPTER 33 n CLICKONCE DEPLOYMENT

ClickOnce Limitations ClickOnce deployment doesn’t allow for much configuration. Many aspects of its behavior are completely fixed, either to guarantee a consistent user experience or to encourage enterprise-friendly security policies. The limitations of ClickOnce include the following: • • •

• • • •

ClickOnce applications are installed for a single user. You cannot install an application for all users on a workstation. ClickOnce applications are always installed in a system-managed user-specific folder. You cannot change or influence the folder where the application is installed. If ClickOnce applications are installed in the Start menu, you get just two shortcuts: one that launches the application and one that launches a help web page in the browser. You can’t change this, and you can’t add a ClickOnce application to the Startup group, the Favorites menu, and so on. You can’t change the user interface of the setup wizard. That means you can’t add new dialog boxes, change the wording of existing ones, and so on. You can’t change the installation page that ClickOnce applications generate. However, you can edit the HTML by hand after it’s generated. A ClickOnce setup can’t install shared components in the global assembly cache (GAC). A ClickOnce setup can’t perform custom actions (such as creating a database or configuring Registry settings).

You can work around some of these issues. For example, you could configure your application to add Registry settings the first time it’s launched on a new computer. However, if you have complex setup requirements, you’re much better off creating a full-fledged custom setup program. You can use a thirdparty tool such as InstallShield, or you can create a setup project in Visual Studio. Finally, it’s worth noting that .NET makes it possible to build a custom installer that uses ClickOnce deployment technology. This gives you the option of designing an advanced setup application without sacrificing the automatic updating feature that ClickOnce provides. However, there are also some drawbacks. This approach not only forces you to write (and debug) a fair bit of code, it also requires that you use the legacy classes from the Windows Forms toolkit to build your setup user interface. Mixing custom setup applications with ClickOnce is beyond the scope of this book, but if you’re interested, you can get started with the example at http://tinyurl.com/9qx9ckp.

Setting Up a Simple ClickOnce Publication Before you get started with ClickOnce publishing, you need to set some basic information about your project. First, double-click the Properties node in the Solution Explorer, and then click the Publish tab. You’ll see the settings shown in Figure 33-2.

1024

CHAPTER 33 n CLICKONCE DEPLOYMENT

Figure 33-2. ClickOnce project settings You’ll learn your way around the settings in this window later in this chapter. But first you need to supply some basic publication details.

Setting the Publisher and Production Before you can install your application, it needs a basic identity, including a publisher name and a product name that can be used in the setup prompts and the Start menu shortcuts. To supply this information, click the Options button to show the Publish Options dialog box. It displays a slew of additional settings, separated into several groups. Although Description is selected (in the list on the left), you’ll see text boxes that allow you to supply three key details: the publisher name, suite name, and product name (see Figure 33-3).

1025

CHAPTER 33 n CLICKONCE DEPLOYMENT

Figure 33-3. Supplying some basic information about your project These details are important because they’re used to create the Start menu hierarchy. If you supply the optional suite name, ClickOnce creates a shortcut for your application in the form [Publisher Name]  [Suite Name]  [Product Name]. If you don’t supply the suite name, ClickOnce creates the shortcut [Publisher Name]  [Product Name]. From the example shown in Figure 33-3, the shortcut will be generated as Acme Software  ClickOnceTest (see Figure 33-4).

Figure 33-4. The ClickOnce shortcuts (based on the information in Figure 33-3) 1026

CHAPTER 33 n CLICKONCE DEPLOYMENT

If you specify a support URL, ClickOnce will create an additional shortcut named [Product Name] Online Support. When the user clicks this shortcut, it launches the default web browser and sends it to the page you’ve specified. The error URL specifies a website link that will be shown (in a dialog box) if an error occurs while attempting to install the application. You’ll learn about the other groups of settings at the end of this chapter. For now, click OK after you’ve filled in the publisher name, product name, and any other details you choose to supply.

Starting the Publish Wizard The easiest way to configure your ClickOnce settings is to click the Publish Wizard button at the bottom of the property page shown in Figure 33-2. This launches a wizard that walks you through a few short steps to gather the essential information. The wizard doesn’t give you access to all the ClickOnce features you’ll learn about in this chapter, but it’s a quick way to get started. The first choice you’re faced with in the Publish Wizard is choosing the location where you want to publish the application (see Figure 33-5).

Figure 33-5. Choosing a publish location There’s nothing particularly important about the location where you first publish your application, because this isn’t necessarily the same location you’ll use to host the setup files later. In other words, you could publish to a local directory and then transfer the files to a web server. The only caveat is that you need to know the ultimate destination of your files when you run the Publish Wizard, because you need to supply this information. Without it, the automatic update feature won’t work. 1027

CHAPTER 33 n CLICKONCE DEPLOYMENT

Of course, you could choose to publish the application directly to its final destination, but it’s not necessary. In fact, building the installation locally is often the easiest option. To get a better sense of how this works, start by choosing a local file path location (such as c:\Temp\ClickOnceApp). Then click Next. You’re now faced with the real question—where users will go to install this application (see Figure 33-6).

Figure 33-6. Choosing the installation type This bit is important, because it influences your update strategy. The choices you make are stored in a manifest file that’s deployed with your application.

n Note There is one case in which you won’t see the dialog box in Figure 33-6. If you enter a virtual directory to a web server for the publish location (in other words, a URL starting with http://), the wizard assumes this is the final installation location. In Figure 33-6, you have essentially three choices. You can create an installation for a network file share, a web server, or CD or DVD media. The following sections explain each approach.

Publishing for a Network File Share In this case, all the users in your network will access the installation by browsing to a specific UNC path and running a file named setup.exe at that location. A UNC path is a network path in the form \\ ComputerName\ShareName. You can’t use a networked drive, because networked drives depend on system 1028

CHAPTER 33 n CLICKONCE DEPLOYMENT

settings (so different users might have their drives mapped differently). To provide automatic updates, the ClickOnce infrastructure needs to know exactly where it can find the installation files, because this is also the location where you’ll deploy updates.

Publishing for a Web Server You can create an installation for a web server on a local intranet or the Internet. Visual Studio will generate an HTML file named publish.htm that simplifies the process. Users request this page in a browser and click a link to download and install the application. You have several options for transferring your files to a web server. If you want to take a two-step approach (publish the files locally and then transfer them to the right location), you simply need to copy the files from the local directory to your web server by using the appropriate mechanism (such as FTP). Make sure you preserve the directory structure. If you want to publish your files straight to the web server without any advance testing, you have two choices. If you are using IIS and the current account you’re running has the necessary permissions to create a new virtual directory on the web server (or upload files to an existing one), you can publish files straight to your web server. Just supply the virtual directory path in the first step of the wizard. For example, you could use the publish location http://ComputerName/VirtualDirectoryName (in the case of an intranet) or http://DomainName/VirtualDirectoryName (for a server on the Internet). You can also publish straight to a web server by using FTP. This is often required in Internet (rather than intranet) scenarios. In this case, Visual Studio will contact your web server and transfer the ClickOnce files over FTP. You’ll be prompted for user and password information when you connect.

n Note FTP is used to transfer files—it’s not used for the actual installation process. Instead, the idea is that the files you upload become visible on a web server, and users install the application from the publish.htm file on that web server. As a result, when you use an FTP path in the first step of the wizard (Figure 33-5), you’ll still need to supply the corresponding web URL in the second step (Figure 33-6). This is important, because the ClickOnce publication needs to return to this location to perform its automatic update checks.

PuBlIShIng To The loCAl WeB SeRVeR If you’re publishing your application to a virtual directory on the local computer, you’ll need to ensure that Internet Information Services (IIS) is installed by using the Programs and Features entry in the Control Panel, which allows you to turn Windows features on or off. When you choose to install IIS, make sure you include the .NET Extensibility option and the IIS 6 Management Compatibility option (which allows Visual Studio to interact with IIS). Additionally, you need to run Visual Studio as an administrator before you can publish to a virtual directory. The easiest way to do this is to right-click the Microsoft Visual Studio shortcut in the Start menu and choose Run as Administrator. You can also configure your computer to always run Visual Studio as an administrator, which is a trade-off between convenience and security that needs to be weighed carefully. To put this in place, right-click the Visual Studio shortcut, choose Properties, and then head to the Compatibility tab, where you’ll find an option named Run This Program as an Administrator.

1029

CHAPTER 33 n CLICKONCE DEPLOYMENT

Publishing for a CD or DVD If you choose to publish to setup media such as a CD or DVD, you still need to decide whether you plan to support the automatic update feature. Some organizations will use CD-based deployment exclusively, while others will use it to supplement their existing web-based or networked-based deployment. You choose which option applies for use in the third step of the wizard (see Figure 33-7).

Figure 33-7. Support for automatic updates Here, you have three options: • • •

1030

You can supply a URL or UNC path that the application will check for updates. This assumes that you plan to publish the application to that location. You can omit this information and bypass the automatic update feature altogether. You can omit this information but tell the ClickOnce application to use the installation location as the update location. For example, if you use this strategy and someone installs the application from \\CompanyServer-B\MyClickOnceApp, the application will automatically check this location (and only this location) for updates each time it runs. This looser approach gives you more flexibility, but it also risks causing problems (most commonly, not being able to find updated versions if users install it from the wrong path). You can’t choose this behavior through the Publish Wizard. If you want it, you need to set the Exclude Deployment Provider URL setting, as described in the “Publish Options” section later in this chapter.

CHAPTER 33 n CLICKONCE DEPLOYMENT

n Note The Publish Wizard doesn’t give you an option for how often to check for updates. By default, ClickOnce applications check for an update whenever they’re launched. If a new version is found, .NET prompts the user to install it before launching the application. You’ll learn how to change these settings in the “Updates” section later in this chapter.

Choosing Online or Offline If you’re creating a deployment for a web server or network share, you’ll get one additional option, as shown in Figure 33-8.

Figure 33-8. Support for offline use The default choice is to create an online/offline application that runs whether or not the user can connect to the published location. In this case, a shortcut for the application is added to the Start menu. If you choose to create an online-only application, the user needs to return to the published location to run the application. (To help make this clear, the publish.htm web page will show a button labeled Run instead of Install.) This ensures that an old version of the application can’t be used after you roll out an update. This part of the deployment model is analogous to a web application. When you create an online-only application, the application will still be downloaded (into a locally cached location) the first time it’s launched. Thus, while startup times may be longer (because of the initial 1031

CHAPTER 33 n CLICKONCE DEPLOYMENT

download), the application will still run as quickly as any other installed Windows application. However, the application can’t be launched when the user isn’t connected to the network or Internet, which makes it unsuitable for mobile users (such as laptop users who don’t always have an Internet connection available). If you choose to create an application that supports offline use, the setup program will add a Start menu shortcut. The user can launch the application from this shortcut, regardless of whether the computer is online or offline. If the computer is online, the application will check for new versions in the location where the application was published. If an update exists, the application will prompt the user to install it. You’ll learn how to configure this policy later.

n Note If you choose to publish for a CD installation, you don’t have the option of creating an online-only application. This is the final choice in the Publish Wizard. Click Next to see the final summary, and click Finish to generate the deployment files and copy them to the location you chose in step 1.

n Tip From this point on, you can quickly republish your application by clicking the Publish Now button or by choosing Build  Publish [ApplicationName] from the menu.

Understanding the Deployed File Structure ClickOnce uses a fairly straightforward directory structure. It creates a setup.exe file in the location you chose and a subdirectory for the application. For example, if you deployed an application named ClickOnceTest to the location c:\ClickOnceTest, you’ll end up with files like these: c:\ClickOnceTest\setup.exe c:\ClickOnceTest\publish.htm c:\ClickOnceTest\ClickOnceTest.application c:\ClickOnceTest\ClickOnceTest_1_0_0_0.application c:\ClickOnceTest\ClickOnceTest_1_0_0_0\ClickOnceTest.exe.deploy c:\ClickOnceTest\ClickOnceTest_1_0_0_0\ClickOnceTest.exe.manifest The publish.htm file is present only if you’re deploying to a web server. The .manifest and .application files store information about required files, update settings, and other details. (You can get a low-level look at these files and their XML file in the MSDN Help.) The .manifest and .application files are digitally signed at the time of publication, so these files can’t be modified by hand. If you do make a change, ClickOnce will notice the discrepancy and refuse to install the application. As you publish newer versions of your application, ClickOnce adds new subdirectories for each new version. For example, if you change the publish version of your application to 1.0.0.1, you’ll get a new directory like this: c:\ClickOnceTest\ClickOnceTest_1_0_0_1\ClickOnceTest.exe.deploy c:\ClickOnceTest\ClickOnceTest_1_0_0_1\ClickOnceTest.exe.manifest 1032

CHAPTER 33 n CLICKONCE DEPLOYMENT

When you run the setup.exe program, it handles the process of installing any prerequisites (such as the .NET Framework) and then installs the most recent version of your application.

Installing a ClickOnce Application To see ClickOnce in action with a web deployment, follow these steps: 1.

Make sure you have the optional IIS web server component installed (as described in the “Publishing to the Local Web Server” sidebar earlier in this chapter).

2.

Using Visual Studio, create a basic Windows application, and compile it.

3.

Launch the Publish Wizard (by clicking the Publish Wizard button or choosing Build  Publish), and select http://localhost/ClickOnceTest for the publish location. The localhost portion of the URL points to the current computer. As long as IIS is installed and you are running with sufficient privileges, Visual Studio will be able to create this virtual directory.

4.

Choose to create an online and offline application, and then click Finish to end the wizard. The files will be deployed to a folder named ClickOnceTest in the IIS web server root (by default, the directory c:\Inetpub\wwwroot).

5.

Run the setup.exe program directly, or load up the publish.htm page in Internet Explorer (shown in Figure 33-9) and click Install. You’ll receive a security message asking whether you want to trust the application (similar to when you download an ActiveX control in a web browser).

Figure 33-9. The publish.htm installation page 1033

CHAPTER 33 n CLICKONCE DEPLOYMENT

lAunChIng A ClICkonCe SeTuP FRoM A DIFFeRenT BRoWSeR Installing a ClickOnce application from Internet Explorer is easy. You simply visit the publish.htm page and click the Install button. This starts the setup process immediately. You can also install ClickOnce applications from other browsers, but you need at least one extra step. For example, if you click the Install button in Google Chrome or Mozilla Firefox, you’ll actually start downloading the setup program. After the download is complete, you can choose to launch the file to start the setup process (although the browser will give you an extra warning to explain that executable files can harm your computer). If you want to make other browsers as adept as Internet Explorer at handling ClickOnce applications, you can install a plug-in that does the job. Chrome offers one at http://tinyurl.com/492nyw9, and there’s one for Firefox at http://tinyurl.com/7cxq4vw. These plug-ins allow you to launch ClickOnce installations with a single-click, just as the name promises. 6.

If you choose to continue, the application will be downloaded, and you’ll be asked to verify that you want to install it.

7.

After the application is installed, you can run it from the Start menu shortcut or uninstall it by using the Add/Remove Programs dialog box.

The shortcut for ClickOnce applications isn’t the standard shortcut to which you’re probably accustomed. Instead, it’s an application reference—a text file with information about the application name and the location of the deployment files. The actual program files for your application are stored in a location that’s difficult to find and impossible to control. The location follows this pattern: c:\Documents and Settings\[UserName]\Local Settings\Apps\2.0\[...]\[...]\[...] The final three portions of this path are opaque, automatically generated strings such as C6VLXKCE.828. Clearly, you aren’t expected to access this directory directly.

Updating a ClickOnce Application To see how a ClickOnce application can update itself automatically, follow these steps with the installation from the previous example:

1034

1.

Make a minor but noticeable change in the application (for example, adding a button).

2.

Recompile the application, and republish it to the same location.

3.

Run the application from the Start menu. The application will detect the new version and ask you whether you’d like to install it (see Figure 33-10).

4.

After you accept the update, the new version of the application will install and start.

CHAPTER 33 n CLICKONCE DEPLOYMENT

Figure 33-10. Detecting a newer version of a ClickOnce application In the following sections, you’ll learn how to customize some additional ClickOnce options.

n Note The ClickOnce engine, dfsvc.exe, handles updates and downloads.

Additional ClickOnce Options The Publish Wizard is a quick way to create a ClickOnce deployment, but it doesn’t allow you to adjust all the possible options. For that, you need to take a closer look at the Publish tab in the application properties window shown earlier. Many of the settings here duplicate details you’ve already seen in the wizard. For example, the first two text boxes allow you to set the publishing location (the place where the ClickOnce files will be placed, as set in step 1 of the wizard) and the installation location (the place from which the user will run the setup, as set in step 2 of the wizard). The Install Mode setting allows you to choose whether the application should be installed on the local computer or run in an online-only mode, as described earlier. However, there are also some settings you haven’t already seen, which are discussed in the following sections.

Publish Version The Publish Version section sets the version of your application that’s stored in the ClickOnce manifest file. This isn’t the same as the assembly version, which you can set on the Application tab, although you might set both to match. The key difference is that the publish version is the criteria used to determine whether a new update is available. If a user launches version 1.5.0.0 of an application, and version 1.5.0.1 is available, the ClickOnce infrastructure will show the update dialog box shown in Figure 33-10. By default, the Automatically Increment Revision with Each Publish check box is set, in which case the final part of the publish version (the revision number) is incremented by 1 after each publication, so 1.0.0.0 becomes 1.0.0.1, then 1.0.0.2, and so on. If you want to publish the same version of your application to multiple locations by using Visual Studio, you should switch off this option. However, keep in mind that the automatic update feature springs into action only if it finds a higher version number. The date stamp on the deployed files has no effect (and isn’t reliable).

1035

CHAPTER 33 n CLICKONCE DEPLOYMENT

It may seem horribly inelegant to track separate assembly and publication version numbers. However, sometimes it makes sense. For example, while testing an application, you may want to keep the assembly version number fixed without preventing testers from getting the latest version. In this case, you can use the same assembly version number but keep the autoincrementing publish version number. When you’re ready to release an official update, you can set the assembly version and the publish version to match. Also, a published application might contain multiple assemblies with different version numbers. In this case, it wouldn’t be realistic to use the assembly version number—instead, the ClickOnce infrastructure needs to consider a single version number to determine whether an update is warranted.

Updates Click the Updates button to show the Application Updates dialog box (Figure 33-11), where you can choose your update strategy.

Figure 33-11. Setting update options

n Note The Updates button isn’t available if you’re creating an online-only application. An online-only application always runs from its published location on a website or network share.

1036

CHAPTER 33 n CLICKONCE DEPLOYMENT

You first choose whether the application performs update checking. If it does, you can choose when updates are performed. You have two options: Before the application starts: If you use this model, the ClickOnce infrastructure checks for an application update (on the website or network share) every time the user runs the application. If an update is detected, it’s installed, and then the application is launched. This option is a good choice if you want to make sure the user gets an update as soon as it’s available. After the application starts: If you use this model, the ClickOnce infrastructure checks for a new update after the application is launched. If an updated version is detected, this version is installed the next time the user starts the application. This is the recommended option for most applications, because it improves load times. If you choose to perform checks after the application starts, the check is performed in the background. You can choose to perform it every time the application is run (the default option) or in lessfrequent intervals. For example, you can limit checks to once per number of hours, days, or weeks. You can also specify a minimum required version. You can use this to make updates mandatory. For example, if you set the publish version to 1.5.0.1 and the minimum version to 1.5.0.0 and then publish your application, any user who has a version older than 1.5.0.0 will be forced to update before being allowed to run the application. (By default there is no minimum version, and all updates are optional.)

n Note Even if you specify a minimum version and require the application to check for updates before starting, a user could end up running an old version of your application. This happens if the user is offline, in which case the update check will fail without an error. The only way around this limitation is to create an online-only application.

File Associations ClickOnce allows you to set up to eight file associations. These are file types that will be linked to your application so that double-clicking a file of this type in Windows Explorer automatically launches your application. To create a file association, begin by clicking the Options button in the Publish tab. This shows the Publish Options dialog box. Then click File Associations in the list on the left. This shows a grid where you can enter the information for a file association, as shown in Figure 33-12.

1037

CHAPTER 33 n CLICKONCE DEPLOYMENT

Figure 33-12. Creating a file association Every file association requires four pieces of information: the file extension, text description, ProgID, and icon file. The ProgID is a text code that uniquely identifies your file type. By convention, it should be based on the application name and version, as in MyApplication.testDoc.1.0, although the format doesn’t really matter as long as it’s unique. The icon points to a file in your project. In order for this file to be included in your ClickOnce setup, you must select it in the Solution Explorer and set the Build Action to Content.

n Note One detail you don’t need to specify for your file association is the name or path of your program. That’s because ClickOnce already has this information. There’s one potential stumbling block when using file associations with ClickOnce. Contrary to what you might expect, when a user double-clicks a registered file, it isn’t passed to your application as a command-line argument. Instead, you must retrieve it from the current AppDomain, as shown here: string commandLineFile = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData[0]; Another tricky point is that the file location is passed in URI format, as in file:///c:\MyApp\MyFile. testDoc. That means you need code like this to get the true file path and clean up escaped spaces (which are translated in the %20 character in a URI): Uri fileUri = new Uri(commandLineFile); string filePath = Uri.UnescapeDataString(fileUri.AbsolutePath); You can now check for the existence of the file and attempt to open it, as you normally would.

1038

CHAPTER 33 n CLICKONCE DEPLOYMENT

Publish Options As you’ve already seen, you can click the Options button to see the Publish Options dialog box with even more options. You use the list on the left to pick the group of settings you want to tweak. You’ve already looked through the settings in the Description and File Association groups. Table 33-1 describes the settings in the Deployment group, and Table 33-2 describes the settings in the Manifest group. Table 33-1. ClickOnce Deployment Settings

Setting

Description

Deployment web page

Sets the name of the installation page in web deployments (which is publish.htm by default).

Automatically generate deployment web page after every publish

If set (the default), the web page is re-created during every publish operation.

Open deployment web page after publish

If set (the default), Visual Studio launches the installation page in your web browser after a successful publication so you can test it.

Use “.deploy” file extension

If set (the default), the installation web page always has the file extension .deploy. You shouldn’t change this detail, because the .deploy file extension is registered on the IIS web server and locked down to prevent malicious users from snooping through it.

For CD installations, automatically start Setup when CD is inserted

If set, Visual Studio generates an autorun.inf file to tell CD or DVD players to launch the setup program immediately when the CD is inserted into the drive.

Verify files uploaded to a web server

If set, the publish process downloads each file after publishing it to verify that it can be downloaded. If a file cannot be downloaded, you’ll receive a notification that explains the problem.

Table 33-2. ClickOnce Manifest Settings

Setting

Description

Block application from being activated via a URL

If set, the user will be able to launch the application from the Start menu only after it’s installed, not from the web browser.

Allow URL parameters to be passed to application

If set, this allows the application to receive URL information from the browser that launches it, such as query string arguments. You can retrieve the URI through the ApplicationDeployment class in the System.Deployment.Application namespace. Just use the ApplicationDeployment.CurrentDeployment. ActivationUri property.

1039

CHAPTER 33 n CLICKONCE DEPLOYMENT

Setting

Description

Use application manifest for trust information

If set, you can re-sign the application manifest after you publish the application. Usually, you’ll do this so you can use a certificate with your company name. This information will then appear in the trust message the user sees when installing the application.

Exclude deployment provider URL

If set, the application will automatically check its installation location for updates. You can use this option if you don’t know the exact deployment location but you still want to use ClickOnce automatic updating.

Create desktop shortcut

If set, the setup will create a desktop icon in addition to the Start menu icon.

The Last Word This chapter gave a quick tour of the ClickOnce deployment model, which was introduced in .NET 2.0 and remains a good choice for deploying stand-alone WPF applications. As with XBAPs, ClickOnce entails certain compromises—for example, you need to accept that there are certain client configuration details you can’t control. But now that most computers have web browsers that support ClickOnce, it’s become a truly practical way to deploy applications that have modest setup requirements.

1040

nnn

Index nA AccelerationRatio property, 404–405 AcceptsReturn, 179 Activated event, 120 ActiveX control, 976 ActiveX interoperability, 976 ActualHeight property, 63 ActualWidth property, 63, 398, 399 AddBackEntry() method, 758, 759 AddBackReference() method, 761, 763 AddDirtyRect() method, 390 AddedItems property, 186 AddFixedDocument() method, 920 AddHandler() method, 106, 108, 114 Adding Resources, 213–214 Add-ins, 997 AddInToken.Activate method, 1009, 1010 advantages and disadvantages, 998 class definition, view and host, 1013 discovery, 1009 FindAddIn() method, 1009 host interaction automation, 1012 ReportProgress() method, 1012 information details, 1009 Initialize() method, 1013–1016 interfaces, 997 lifetime, 1011 MAF and MEF, 997–998 pipeline, 998 adapters, 999, 1006–1007 AddInStore.Rebuild() method, 1009 AddInStore.Update() method, 1009

class relationships, 999 communication, 999 contract, 999, 1004–1005 directory structure, 1001 host adapter, 1008 host view, 1007, 1008 MustInherit abstract class, 1005–1006 solution preparation, 1002–1003 views, 999 to process images, 1011 reflection, 997 view, 1005 visual add-ins FrameworkElementAdapters, 1018 GetVisual() method, 1019 IImageProcessorContract, 1017 user interface element, 1017 AddLogicalChild() method, 372 AddVisualChild() method, 372 AddVisual() method, 373 ADO.NET DataView, 656 ADO.NET objects, 572–574 AdornedElementPlaceholder, 590–591 AdornerDecorator, 469 Adorner layer, 589 Airspace rule, 971–973 Aligning Content, 156 AllowDrop property, 131 AllowsTransparency property, 168, 776 all-purpose Undo() method, 265 AlternationCount property, 614 AlternationIndex property, 620–621 AmbientLight, 846 AnchorChanged event, 931 1041

n INDEX

Anchor property, 930 Anchors collection, 930 Anchors property, 928 Animation, 431 bitmap caching, 428–430 brushes ColorAnimation, 438 ellipse, 438 Gradient Obsession, 439 hypnotic effects, 439 PointAnimation, 438 RadialGradientBrush, 437 VisualBrush, 439–440 challenges, 431 classes cluttered namespace, 395 ColorAnimation, 393 complete list, 394–395 data type, 393 DoubleAnimation, 393, 395 interpolation animation class, 394 key-frame animation, 394 linear interpolation, 394 path-based animation, 394 System.Windows.Media.Animation namespace, 394 TypeNameAnimation, 393 TypeNameAnimationUsingPath, 394 code-based animated button, 397 BeginAnimation() method, 396 Duration property, 401 By property, 400 From property, 398–399 To property, 399–400 standard frame rate, 396 storyboard, 396 discrete key-frames, 443–444 dynamic user interfaces, 391 easing function classes, 420–422 custom, 423–425 EasingFunctionBase class, 418 EasingFunction property, 418, 419 Easing In, 419–420 Easing Out, 419–420 ElasticEase class, 418 growStoryboard animation, 418 linear interpolation, 418 Oscillations property, 418 Springiness, 418 1042

easing key frames, 444 frame-based Canvas.SetTop() method, 450 CompositionTarget.Rendering event, 449 EllipseInfo object, 449 falling circles, 449 rendering event handler, 448 techniques, 448 TimeTracker class, 451 frame rate, 425–427 guidelines, 431 key-frame animation, 442–443 lifetime, 401–402 multiple transforms, 435–437 path-based, 446–448 pixel shaders, 440–442 property-based, 392–393 reference types, 393 simultaneous, 401 spline key frame, 444–445 storyboards (see Storyboards) storyboards in code (see Bomb-dropping game) Timeline class AccelerationRatio property, 404–405 AnimationTimeline, 403 DecelerationRatio property, 404–405 MediaTimeline, 403 properties, 404 RepeatBehavior property, 405–406 TimelineGroup, 403 timer-based, 391–392 transforms, 432 definition, 432 LayoutTransform, 432, 434 RenderTransform, 432 RotateTransform, 432 ScaleTransform, 432 vs. traditional media files, 391 AnimationTimeline class, 403 AnnotationHelper class, 923, 924, 930 AnnotationHelper.GetAnchorInfo() method, 930 AnnotationResource objects, 930 Annotations classes, 922–923 creation, 924–928 customizing sticky notes, 932û933 DocumentViewer, 922 enabling annotation service, 923û924 examination AnchorChanged event, 931

n INDEX

AnnotationHelper.GetAnchorInfo() method, 930 AnnotationStore.GetAnnotations() method, 930 AuthorChanged event, 931 CargoChanged event, 931 Cargos property, 928–929 ContentLocator object, 928 GetAnchorInfo() method, 930 GetAnnotations() method, 928 IAnchorInfo, 930 properties, 928 Section object, 929–930 StoreContentChanged event, 931 TextAnchor objects, 930 FlowDocumentPageViewer, 922 FlowDocumentReader, 922 FlowDocumentScrollViewer, 922 highlighting, 921 sticky notes, 921 storing, fixed document, 931–932 AnnotationService class, 922, 924 AnnotationService.Enable() method, 923, 924 AnnotationService object, 923 AnnotationStore class, 922 AnnotationStore.GetAnnotations() method, 930 AnnotationStream object, 923, 931 AnnotationType property, 928 AnnotionService.GetService() method, 924 Anti-aliasing, 324 AppendAudio() method, 832 AppendBreak() method, 832 AppendText() method, 832 AppendTextWithHint() method, 832 AppendTextWithPronounciation() method, 832 Application class, 747 ApplicationCommands.Print command, 920, 927 ApplicationCommands.Undo command, 516–517 Application.DispatcherUnhandledException event, 747 Application.EnableVisualStyles() method, 970 ApplicationExtension, 768 Application.MainWindow property, 196 ApplicationName.exe, 769 ApplicationName.exe.manifest, 769 ApplicationName.xbap, 769 Application.NavigationFailed event, 747 Application resources, 275–276 Application.Run() method, 196, 199 ApplicationsCommands.Open command, 791 Application.Shutdown() method, 198

Application.ShutdownMode, 198 Application.Startup event fires, 207 Application.StartupUri property, 203, 752 Application style, 527 Application.Windows collection, 204 ApplyPropertyValue() method, 917 Arcs, 356–358 Arrange() method, 543 ArrangeOverride() method, 543–544, 547 Assembly resources, 269 AssemblyName.g.resources, 214 binary resources, 212 content files, 217–218 embedded resource, 213 ImageLibrary, 217 .NET applications, 212 .NET Reflector, 214 pack URI syntax, 216 resource-aware classes, 215–216 retrieving, 214–215 strong-named assembly, 217 Asset Library, 303 asynchronous data bindings, 597–598 Asynchronous navigation, 756 AsyncOperation class, 992 AsyncOperationManager class, 992 Attached events, 114 Audio, speech MediaPlayer class, 816–818 playing WAV audio SoundPlayerAction class, 815 SoundPlayer class, 813–815 system sounds, 816 speech recognition. (see Speech recognition) synthesis (see Speech synthesis) and video (see MediaElement class) AuthorChanged event, 931 Authors property, 928 Automatic columns, 910 AutoReverse property, 402, 404, 405 AutoWordSelection, 180 AuxiliaryPaneContentTemplate, 802 Axis lines, 849

nB BackColor property, 980 BackContent property, 529 BackEase class, 420 Background property, 529, 744, 776, 887, 899 1043

n INDEX

BackgroundWorker asynchronous operation, 987–988 cancellation BackgroundWorker.CancelAsync() method, 993 BackgroundWorker. WorkerSupportsCancellation property, 993 CancelAsync() method, 994 DoWorkEventArgs.Cancel property, 994 FindPrimes() method, 994 RunWorkerCompleted event, 994–995 cancel messages, 987 creation, 989 progress events, 997 running BackgroundWorker.RunWorkerAsync() method, 990 Dispatcher.BeginInvoke() method, 992 DispatcherSynchronizationContext object, 992 DoWork event, 991 FindPrimesInput object, 990–991 multithreading classes, 992 prime-number search, 990 RunWorkerCompleted event, 991 RunWorkerCompletedEventArgs.Result property, 991 WindowsFormsSynchronizationContext object, 992 Worker.FindPrimes() method, 991 System.ComponentModel.BackgroundWorker component, 987 System.Threading.Thread object, 987 Thread.Start() method, 987 tracking progress, 992–993 BackgroundWorker.CancelAsync() method, 993 BackgroundWorker.ReportProgress() method, 992 BackgroundWorker.WorkerReportsProgress property, 992 BackgroundWorker.WorkerSupportsCancellation property, 993 BackStack property, 759 BandIndex property, 796 Bare-bones flow document, 890 BasedOn attribute, 291 BasedOn property, 287, 291 BeginAnimation() method, 396, 401, 402 BeginChange() method, 181 BeginInit() method, 119 BeginStoryboard action, 297, 407, 820, 825 BeginStoryboard.HandoffBehavior property, 409 1044

BeginStyle() method, 832 BeginTime property, 404, 411 Behaviors, 283 assemblies, 298 code-free application design, 299 creation Behavior class, 300 Canvas coordinates, 300 DragInCanvasBehavior code, 300–301 element reposition, 302 event handlers, 301 FrameworkElement, 300 mouse button release, 302 OnAttached() method, 300 OnDetaching() method, 300 UIElement, 300 watermark text, 300 CustomBehaviorsLibrary, 302 custom controls, 298 design-time behavior, 303 design-time feature, 298 draggable elements, 303 DragInCanvasBehavior class, 302 Ellipse elements, 302 Expression Blend, 298 feature version, 299 guidelines, 299 Interaction.Behaviors attached property, 302 Microsoft.Expression.Interactions.dll assembly, 299 Rectangle element, 302 System.Windows.Interactivity.dll assembly, 299, 302 triggers, 299 user-interface functionality, 298 Visual Studio application, 298 BetweenShowDelay window, 167 Bevel line join, 321 Bézier curves, 358–359 Binary Application Markup Language (BAML), 752 BindingDirection, 974 Binding.ElementName property, 238 Binding elements Binding.ElementName property, 238 code creation, 232–233 code retrieving, 233 delay property, 238 element-to-element binding, 227 errors, 229 expression, 228–229 linked controls, 228

n INDEX

modes default, 231–232 OneWayToSource, 231 slider thumb, 229 System.Windows.Data.BindingMode enumeration, 230 multiple binding, 234–236 object DataContext, 241 RelativeSource, 239–240 source, 239 updates, 237–238 Binding errors, 229 Binding expression, 228–229 BindingExpression.UpdateSource() method, 238 BindingGroup property, 592 Binding.IsAsync property, 598 BindingListCollectionView, 647, 648, 657, 658, 663 Binding.NotifyOnValidationError property, 587 BindingOperations.GetBinding() method, 233 Binding.RelativeSource property, 515, 619 Binding.StringFormat property, 603, 614 Binding.XPath property, 600 BitmapCacheBrush.BitmapCache property, 336 BitmapCacheBrush class, 325, 336–337 BitmapEffect class, 380–381 BitmapImage.BeginInit() method, 608 BitmapImage object, 216, 605 Blender, 856 Block elements, 886 BlockUIContainer, 895–896 List, 891–892 Paragraph and Run, 890 Section, 894–895 Table, 892–894 Block.NextBlock property, 903 BlockUIContainer class, 916 BlockUIContainer element, 895, 899 BlurEffect, 381, 540 Bold element, 896 Bomb-dropping game animations drops the bomb, 452 falling vs. rescued bomb, 460 DispatcherTimer, 455–458 IsFalling property, 458–460 main window Border element, 453 game statistics, 453 refinements, 461 rotates the bomb, 452

save and update, 453, 460 user control, 454–455 Boolean property, 529 Boolean SuppressExceptions property, 610 BorderBrush property, 64, 146, 887, 894, 899 BorderThickness property, 64, 146, 887, 894, 899 Bottom property, 82 BounceEase class, 420 Bound object, 562–564 Box value, 891 Brushes BitmapCacheBrush, 336–337 change notification, 325 classes, 325 Freezable, 325 ImageBrush, 331–332, 342 LinearGradientBrush, 326–328 partial transparency, 325 RadialGradientBrush, 329–330 SolidColorBrush, 325–326, 342 TileBrush, 342 tiled ImageBrush, 332–335 VisualBrush, 335–336 Building Localizable User Interfaces, 218–219 Built-in element, 133 ButtonBase, 797 ButtonChrome class, 469, 550 ButtonChrome decorator, 65, 146 ButtonChrome.RenderMouseOver property, 550 Button.FontFamily, 289 ButtonState event, 129 Button.Style property, 409, 793 By property, 400

nC Cache length, 578–579 CacheLengthUnit property, 578 CacheMode property, 429 Calendar class, 192 CalendarClosed event, 193 CalendarOpened event, 193 call RenderOpen() method, 371 CanAddToQuickAccessToolbarDirectly property, 810 CanContentScroll, 172 CanExecute event, 516, 517 CanExecute() method, 245, 246 CanExecuteSave() method, 261 CanGoBack property, 756, 758, 781 CanGoForward property, 756, 781 1045

n INDEX

CanUndo property, 181 Canvas, 313 animatable content, 398 Bottom property, 82 ClipToBounds property, 83 code example with four buttons, 83 Height property, 83 InkCanvas, 84 annotating content with user-drawn strokes, 84 EditingMode property, enumeration values, 85 Stroke objects, 84 Strokes collection, 84 Left property, 82 lightweight features of, 83 nesting inside a user interface, 83 placing elements using exact coordinates, 82 Right property, 82 Top property, 82 Width property, 83 ZIndex property, 84 Canvas.ClipToBounds property,316, 426 CargoChanged event, 931 Cargos property, 928 CategoryName property, 632, 633 Category.Products property, 571 CellSpacing property, 894 Center property, 329 CenterX property, 339 CenterY property, 339 ChangeVisualState() method, 530, 537–538 CheckAccess() method, 985 CheckBox elements, 160–161, 617, 618, 620, 753 CheckBox.IsChecked property, 113 CheckedListBox class, 185 children tag, 782 Chrome classes, 549 CircleEase class, 421 Circle value, 891 Class bindings, 258 ClearHighlightsForSelection() method, 924 ClickCount event, 129 Click event, 114 ClickOnce cache, 770, 771 ClickOnce deployment model, 1021 additional options file associations, 1037 publish options, 1039 Publish Version, 1035 update option, 1036 1046

advantages, 1022 installation, 1023, 1033 limitations, 1022, 1024 partial-trust programming, 1022 publication CD or DVD, 1030 directory structure, 1032 installation type, 1028 network file share, 1028 online/offline application, 1031 project settings, 1024 publisher and production settings, 1025 Publish Wizard, 1027 web server, 1029 shortcuts, 1027 strategies, 1021 updates, 1034 Clip art, 367–368 Clipboard group, 803 ClipGeometry class, 364 Clipping property, 828 ClipToBounds property, 83 Clock.CurrentProgress property, 417 Close() method, 817 Closing event, 120 CodeAccessPermission.Demand() method, 773 CodeAccessPermission objects, 773 Code access security, 771 Code-based approach, 653 Coercion Callback, 102–104 Collection items displaying and editing data template supply, 570 DisplayMemberPath property, 570 Grid.DataContent property, 570 IsLocked property, 571 ListBox.SelectedItem property, 571 list of products, 568 master-details display, 571 SelectionChanged event, 570 StoreDB.GetProducts() method, 568–569 ToString() method, 569, 570 unhelpful bound list, 570 UpdateProducts() method, 571 inserting and removing, 572 CollectionViewSource class, 651–653 CollectionViewSource.View property, 652 ColorAnimation class, 393, 395 Color.FromArgb() method, 145 ColorPicker class, 520 ColorPicker.Color property, 525

n INDEX

ColorPicker control class, 526 Color property, 509–512 ColumnDefinition element, 70 ColumnDefinition.MaxWidth property, 884 ColumnGap property, 910 Column property, 71 ColumnRuleBrush property, 910 ColumnRuleWidth property, 910 ColumnSpan property, 894 ColumnWidth property, 910 ComboBox, 566, 642–645, 651 ComboBox class, 576 ComboBox control, 643, 645 ComboBox.IsTextSearchEnabled property, 643 ComboBoxItem object, 187, 614, 643 Command-Line Arguments, 202–204 CommandManager.InvalidateRequerySuggested() method, 260 CommandParameter property, 790, 926 Command property, 790 Commands, 243 ApplicationCommands class, 248–249 application task, 245 binding ExecutedRoutedEventArgs object, 251 links, 244 markup, 251 New command, code, 250 NewCommand_Executed event handler, 251 UIElement class, 250 built-in commands, 257–258 CanExecuteSave() method, 261 CommandBindings collection, 261 ComponentCommands, 248 custom commands, 259–260 custom controls, 254 Cut, Copy, and Paste commands, 261 disabling, 255–256 EditingCommands, 248 Execute() method, 254 features, 243 fine-tuning command text, 253–254 ICommand interface, 245–246 isDirty flag, 261 limits, 256–257 mapping events, 244 MediaCommands, 248 menu item, 252–253 NavigationCommands, 247 nested element, 254 parameter, 262–263

prebuilt commands, 246 RoutedCommand class, 246–247 RoutedUICommand class, 247 scope, 260 sources, 245, 249 StaticResource extension, 262 targets, 245 TextBox.Tag, 261 tool, 244 tracking and reversing all-purpose Undo() method, 265 ApplicationCommands.Undo command, 263 ApplicationUndo command, 267 application-wide Undo action, 265–266 CanExecute handler, 267 CommandHistoryItem class code, 265 CommandHistoryItem object, 264 CommandHistoryItem, Undo() method, 267 CommandManager class, 263, 266 EditingCommands.Backspace command, 264 MonitorCommands, 266 PreviewExecuted event handler, 263–264 two-files-at-once text editor, 261 undoable commands, 244 without binding, 250 CommandTarget property, 790, 925 Common language runtime (CLR), 12 Compare() method, 658 ComponentResourceKey, 279–282 Composite Application Library (CAL), 998 ComVisible attribute, 784 ConfigureAppWizard.xaml page, 759 ContainerFromElement() method, 187 ContainerStyle property, 659 ContainerStyleSelector property, 659 ContainerUIElement3D class, 875 ContainerVisual, 370 ContentControl class, 152, 507, 520, 531 Content controls, 505, 626 aligning content, 156 buttons ButtonBase, 159 CheckBox, 159, 160–161 IsCancel, 159 IsDefault, 159–160 RadioButton, 159, 161–162 ToggleButton and RepeatButton, 160 buttons types, 153 HeaderedContentControl class, 153 1047

n INDEX

Content controls (cont.) hierarchy, 153 Label and ToolTip, 153 labels, 158 property, 154–155 ScrollViewer (see ScrollViewer) single nested element, 152 tooltips fancy, 163 nested element combination, 162 Popup, 167–169 StackPanel, 163 ToolTip properties, 163–166 ToolTipService Properties, 166–167 user-interactive controls, 163 WPF content philosophy, 157–158 ContentElement class, 396, 885 Content elements block elements (see Block elements) events handling, 885 Focusable property, 885 formatting, 887–888 Hyperlink, 885, 895 inline elements (see Inline elements) Paragraph elements, 886 System.Windows.Documents namespace, 886 vs. noncontent elements, 885 ContentPresenter element, 619 ContentRendered event, 120 ContentTemplate property, 155, 626 ContextMenu class, 791 ContextMenu property, 791 ContextMenuService.ShowOnDisabled attached property, 791 Control class, 506 MouseDoubleClick event, 129 PreviewMouseDoubleClick event, 129 Control.FontFamily point, 99 ControllableStoryboardAction class, 411 Controls background and foreground brushes setting colors, code, 144–145 setting colors, XAML, 145–146 content (see Content controls:) date, 190–193 fonts embedding, 149–150 FontFamily, 146 FontStretch, 147 FontStyle, 147 FontWeight, 147 1048

inheritance, 148 substitution, 149 text decorations and typography, 148 text formatting mode, 150–151 list (see List controls) mouse cursors, 151–152 range-based controls (see Range-based controls) System.Windows.Control class, 144 text (see Text controls) user-interactive element, 144 Control templates, 505 automatic application, 490–491 bare-bones styles, 502 browsing, 475 built-in recipe, 471 BulletChrome class, 473 ButtonChrome class, 471–473 Button class, 471 Button.xaml, 486–488 creation basic button, 478 bindings, 479–481 border, 478 Button class, 478 button control, 477 ContentPresenter, 478 EventTrigger-driven animation, 484–485 inline template, 478 properties changing, triggers, 481–483 standardization and theme integration, 477 StaticResource reference, 478 Template property, 478 data templates, 473 DrawingBrush, 474 gradient button, 488 LinearGradientBrush, 474 ListBoxChrome class, 473 live ControlTemplate object conversion, 476– 477 logical tree LogicalTreeHelper methods, 468 SimpleWindow, 466 System.Windows.LogicalTreeHelper, 468 MergedDictionaries collection, 486 nested templates, 494–496 panel template, 473 Path, 474 PresentationFramework.Aero.dll assembly, 473 PresentationFramework.dll assembly, 475 PresentationFramework.Luna.dll assembly, 473

n INDEX

PresentationFramework.Royale.dll assembly, 473 reflection, 475, 476 RenderDefaulted property, 472 ResizeGrip element, 474 resource dictionary, 485, 486 scrollbar background, 497 ContentPresenter, 500–501 dissecting, 497 element-typed style, 496, 499–500 horizontal scrollbar, 500 ListBox, 497, 501 multipart structure, 498 PART_Track, 499 RepeatButton class, 497 RepeatButton elements, 499 ScrollViewer, 496 three-row grid, 499 thumb, 497, 500 Track.ViewportSize property, 499 transparent background, 501 vertical scrollbar, 497–498 ScrollChrome class, 473 SimpleStyles, 502 SnapsToDevicePixels setting, 474 StackPanel control, 465 styles, 483, 488–490 style trigger, 472 SystemDropShadowChrome class, 473 TargetName property, 472 TextElement.Foreground property, 472 two-button window, 465, 466 user-selected skins, 491–493 visual states model, 503–504 visual tree, 467 GetChild() method, 468 GetChildrenCount() method, 468 GetParent() method, 468 SimpleWindow, 467 Style.TargetType property, 468 System.Windows.Media.VisualTreeHelper, 468 VisualTreeDisplay window, 468–470 vs. custom controls, 490 XAML markup, 471 XamlWriter class, 475 XamlWriter.Save() method, 476 XmlWriter and XmlWriterSettings objects, 476– 477 ConvertBack() method, 606, 612

Convert() method, 606, 612 ConvertToString() method, 124 CornerRadius, 64 CornerRadius pr, 530 CreateHighlightCommand, 926 CreateHighlightsForSelection() method, 924 CreateInkStickyNoteCommand, 925, 926 CreateInkStickyNoteForSelection() method, 924 CreateInstanceCore() method, 423 CreateTextStickyNoteForSelection() method, 924 CreationTime property, 928 CubicEase class, 421 CubicEase function, 423 CurrentGlobalSpeedInvalidated event, 416 CurrentStateInvalidated event, 416 CurrentTimeInvalidated event, 416 CurrentUICulture property, 218 CustomContentState class, 760 CustomContentState object, 763 Custom Control Library, 753 Custom decorator, 553–554 CustomDrawnDecorator, 553–554 CustomDrawnElement class, 553 Custom-drawn elements composite elements, 548 composition, 548 creation BackgroundColor dependency property, 552 CustomDrawnElement, 551–552 DrawingContext.DrawRectangle() method, 552 FrameworkElement, 551 FrameworkPropertyMetadata.AffectRender flag, 552 GetForegroundBrush() method, 552–553 InvalidateVisual() method, 552 RadialGradientBrush, 551, 552 custom decorator creation, 553–554 evaluation, 550 OnRender() Method, 548–549 Custom elements base classes, 506–508 Custom Control Library project, 506 custom-drawn elements (see Custom-drawn elements) custom panels (see Custom panels) DLL assembly, 506 lookless control (see Lookless control) user control ColorChanged event, 516 ColorConverter, 516 1049

n INDEX

Custom elements, user control (cont.) Color data type, 516 color picker, 509 ColorPicker object, 518, 519 CommandBindings collection, 518 command-handling code, 518 CommandManager. RegisterClassCommandBinding() method, 518 command support, 516–517 ContentPresenter, 519 dependency properties, 509–512 EventManager.RegisterClassHandler() method, 518 Focusable, 518 HorizontalAlignment, 518 InitializeComponent() method, 519 IsTabStop, 518 markup addition, 513–515 RegisterClassCommandBinding() method, 518 routed events, 512–513 template-based control, 508, 509 TypeConverter attribute, 516 UndoCommand_CanExecute() method, 518 UndoCommand_Executed() method, 518 UserControl class, 519 VerticalAlignment, 518 XAML markup, 515 XML namespace, 515 visual states (see FlipPanel) Visual Studio solution, 506 CustomFilter property, 657 Custom panels Canvas clone, 544–545 docking toolbars, 541 tear-off toolbars, 541 two-step layout process ArrangeOverride() method, 543–544 MeasureOverride() method, 541–543 WrapPanel ArrangeOverride() method, 547 FrameworkPropertyMetadata object, 545 LineBreakBefore property, 545–547 WrapBreakPanel, 545, 547 CustomPopupPlacementCallback, 164 CustomResources class, 281 Custom scrolling, 172–173 custom SetContent() method, 206 CustomSort property, 658 1050

nD Dashed lines, 322–323 Data access component design, 558 DLL component, 558 Store database, 558–560 Data binding. See also Binding elements collection of objects ADO.NET objects, 572–574 collection items (see Collection items) IEnumerable interface, 567 ItemsControl class, 566, 567 LINQ Expression, 574–576 database binding with custom objects bound object display, 562–564 change notification, 565–566 data access component (see Data access component) database updation, 564–565 data object, 561–562 null values, 564 data providers (see Data providers) data templates, 629 long lists, performance improvement cache length, 578–579 deferred scrolling, 579 item container recycling, 578 Virtualization, 576–578 Redux, 601–603 two-way, 557 validation (see Validation, data binding) DataContext property, 563, 602 Data conversion conditional formatting, 610–611 human-readable strings, 603 multiple properties evaluation, 612–613 string formatting (see String formatting) value converters (see Value converters) WPF tools, 603 DataFormats class, 914 DataFormats.Rtf format, 914 DataFormats.Text format, 914 DataFormats.XamlPackage format, 914 DataFormat.Xaml format, 914 Data forms, 575–576 DataGrid, 566, 578, 665, 686 automatic column generation, 686 column-based model, 686 columns Binding property, 690

n INDEX

DataGridCheckBoxColumn, 691 DataGridComboBoxColumn, 691 DataGridHyperlinkColumn, 691 DataGridTemplateColumn, 693 definition, 689 format and style, 694 frozen columns, 699 Header property, 690 Width property, 690 wrapped text, 695 display properties, 687, 688 editing, 700 resizing and rearranging columns, 688–689 rows highlighting rows, 697 LoadingRow event, 696 row details, 698 value converter, 697 selection, 700 sorting, 700 DataGridView, 797 Data object, 561–562 Data providers DataContext, 595 definition, 595 ObjectDataProvider (see ObjectDataProvider) ProductListSource class, 595 resource section, 595 System.Windows.Data.DataSourceProvider class, 595 XmlDataProvider, 595, 598–600 DataRow object, 573–574 DataRowView object, 631 Data Sources window, 576 DataTable class, 575, 648, 656–657 DataTable data binding, 572–574 DataTable.DefaultView property, 573, 657 DataTable.Rows collection, 573, 574 DataTemplate.DataType property, 628 Data templates, 505 advanced templates, 629–631 Border object, 627 content controls, 626 ContentTemplate, 626 DisplayMemberPath property, 625 Grid objects, 627 ItemsSource proprty, 626 ItemTemplate property, 626 list box example, 626 list controls, 626 ListView, 627

selection Binding.RelativeSource property, 637 changing item layout, 641 expanding selected item, 638, 639 Foreground and Background properties, 636 highlighting selected item, 637, 638 ItemContainerStyle, 639 ItemContainerStyle property, 636 ListBoxItem.Background property, 637 RelativeSource property, 639 SnapsToDevicePixels property, 638 StackPanel, tigger, 640 unreadable text, highlighted item, 636 Visibility property, 639 selectors, 633–636 separating and reusing, 628–629 varying templates, 632–633 XAML markup, 626 DataTemplateSelector, 633–636 DataTrigger class, 294 Data triggers, 611, 636, 640 DataView class, 573 DataView.RowFilter, 657 Data views filtering check boxes, 656 DataTable, 656–657 dedicated filtering class, 655 FilterItem() method, 656 FilterProduct() method, 653 Filter property, 653 inline filtering method, 654 minimum price, 655–656 MinMaxFilter, 655 Refresh() method, 656 removing Filter property, 656 StringFilter, 655 TextChanged event, 656 grouping CategoryID property, 659 CategoryName property, 659 CollectionView.GroupDescriptions collection, 659 GroupItem objects, 659 GroupStyle.HeaderTemplate, 660 GroupStyle objects, 660 GroupStyle properties, 659 ItemsControl.GroupStyle property, 659 ListBox.GroupStyle property, 660 product list, 660 PropertyGroupDescription object, 660 1051

n INDEX

Data views, grouping (cont.) in ranges, 661–663 SortDescription, 661 and virtualization, 663 live shaping, 663–664 sorting, 657–658 view object (see View object) DataView.Sort property, 658 Data virtualization, 577–578 Date controls, 190–193 DatePicker class, 192 DateValidationError event, 193 Deactivated event, 120 DecelerationRatio property, 427 Decimal value, 891 Declarative user interfaces, 18 Decorator class, 507 DecreaseZoom() method, 908 DefaultProductStyle, 622 DefaultStyleKeyProperty, 520 DefaultStyleKeyProperty.OverrideMetadata() method, 527 Default transition, 535 Deferred scrolling, 579 DefiningGeometry property, 309 DeflateStream, 776 DeleteInkStickyNotesForSelection() method, 924 Delete() method, 574 DeleteStickyNotesCommand, 926 DeleteTextStickyNotesForSelection() method, 924 DeleteVisual() method, 373 DependencyObject class, 511, 513, 565, 755 Dependency properties, 754, 755 attached property, 99–100 definition, 94 .NET property system, 93 property validation CoerceValueCallback method, 101 Coercion Callback, 102–104 ropertyChangedCallback, 101 SetValue() method, 100 ValidateValueCallback, 101 validation callback, 101–102 property wrapper, 96–97 registering FrameworkPropertyMetadata object, 95 Register() method, 95 reusing, 99 TextBlock.FontFamily, 99 uses, 97–98 WPF-specific creation, 94 1052

DependencyProperty.Register() method, 95 DependencyProperty.UnsetValue, 917 DependencyProperty.ValidateValueCallback, 97 DesiredSize property, 62, 542, 543 Device-independent units, 324 Device property, 121 DialogResult enumeration, 969 DiffuseMaterial, 859 Direct events, 127 DirectionalLight, 844, 845, 846 Discrete key-frame animation, 443–444 Disc value, 891 Dispatcher.BeginInvoke() method, 985, 986 Dispatcher.CurrentDispatcher property, 984 DispatcherObject class, 984 BeginInvoke() method, 986, 987 Dispatcher.BeginInvoke() method, 985, 986 DispatcherPriority.ApplicationIdle, 986 DispatcherPriority.Normal, 986 DispatcherPriority.SystemIdle, 986 InvalidOperationException, 985 Invoke() method, 987 members, 984 System.Threading.Thread object, 985 time-consuming code, 986 UpdateTextWrong() method, 985 VerifyAccess() method, 985 DispatcherOperation object, 986 DispatcherPriority.ApplicationIdle, 986 DispatcherPriority.SystemIdle, 986 DisplayDateChanged event, 193 DisplayDateStart, 192 DisplayMemberPath property, 567, 573, 602, 614, 644 Dispose() method, 833 Dock property, 67, 976 Document Object Model (DOM), 747 Document property, 782 Documents fixed documents, 883 flow documents (see Flow documents) DocumentViewer, 884 docViewer, 902 DoDragDrop() method, 131 DoubleAnimation class, 297, 393, 395 Double.NaN, 398 Double.PositiveInfinity, 542 DoWorkEventArgs.Argument property, 991 DoWorkEventArgs.Cancel property, 994 DoWorkEventArgs.Result property, 991 Drag-and-drop operations, 130

n INDEX

DragDrop class, 131 DragEnter event, 131 DragIncrement property, 77 DrawEllipse() method, 370 DrawImage() method, 370 DrawingBrush class, 325, 830 DrawingCanvas class, 373 DrawingContext class, 548 DrawingContext.Close() method, 370 DrawingContext.DrawGeometry() method, 549 DrawingContext.DrawImage() method, 549 DrawingContext methods, 370–371 DrawingImage class, 331 DrawingImage property, 803 Drawings classes, 364 clip art, 367–368 displaying Canvas, 364 classes, 364 Drawing brush and image approach, 366, 367 Path element, 365, 366 GeometryDrawing, 363 triangle, 363 DrawingVisual object, 550 DrawingVisual.RenderOpen() method, 370, 550 Drawing visuals, 370–372 DrawLine() method, 370 DrawRectangle() method, 370 DrawRoundedRectangle() method, 370 DrawSelectionSquare() method, 379 DrawSquare() method, 374, 376, 379 DrawText() method, 370 DropShadowEffect, 382–383 Duration.Automatic value, 401 Duration.Forever value, 401 Duration property, 401, 404 Dynamic property-resolution system, 393 DynamicResource references, 279 Dynamic resources, 271 ComponentResourceKey, 280 system resources, 276, 277 vs. static resource, 272–274

ne EaseInCore() method, 423–425 EaseIn mode, 419 EaseInOut property, 419–420 EaseIn property, 420

EaseOut property, 419–420 EasingFunctionBase class, 418, 419 EasingFunction property, 418, 419 Easing key frames animation, 444 EasingMode property, 420 EditingMode property, 85 ElasticEase class, 418, 420 ElasticEase function, 419 ElasticEase.Oscillations property, 419 Electric Rain ZAM 3D, 856 ElementHost class, 970, 981 ElementHost control, 976–978 ElementHost.EnableModelessKeyboardInterop() method, 970 ElementName property, 522 Ellipse class, 308, 310 Ellipse element, 533 Ellipse geometry, 349 EmbeddedFont.xaml, 150 Embedded frame, 750 Embedded Resource, 213 Embedding font, 149–150 EmissiveMaterial, 859, 860 Enabled property, 981 EnableModelessKeyboardInterop() method, 970 EnableVisualStyles() method, 970, 971, 975 EnableWindowsFormsInterop() method, 970 EndChange() method, 181 EndInit() method, 119 EndLineCap property, 320 EndPoint property, 327 EndStyle() method, 832 EndVoice() method, 832 Entity data model, 575 ErrorContent property, 586, 588 EventArgs classes, 121 EventArgs object, 402 Event handlers mapping, 243 EventSetter objects, 289 EventTrigger class, 294 Event triggers, 290, 296–298, 406 attachment with style, 408–409 BeginStoryboard, 407 Click event, 407 data-binding expression, 408 definition, 407 DoubleAnimation object, 408 IValueConverter, 408 System.Windows.TriggerAction, 408 Window.Loaded event, 407 EventTrigger.SourceName property, 413, 821 1053

n INDEX

ExceptionRoutedEventArgs.ErrorException property, 819 ExceptionValidationRule, 581–582, 587, 588 Executed event, 516 Execute() method, 245, 246, 254 ExpandDirection property, 176 Explorer-style file browser, 780 ExponentialEase class, 421 Export plug-ins, 856 Expression Blend, 298, 299 Expression Blend SDK, 299 Expression Blend 3 SDK, 298

nF FastSpeedProperty, 598 FieldOfView property, 850 Figure elements, 896, 901, 909 FileMode.Read, 932 FileMode.ReadWrite mode, 932 FileRegistrationHelper class, 211 FileRegistrationHelper registers, 211 FillBehavior property, 402, 411 Fill brush, 318 Fill property, 309, 310 FillRule property, 319 FilterProduct() method, 653 FindPrimes() method, 988, 992, 994 FindResource() method, 527 FirstDayOfWeek, 192 Fixed documents, 883 annotations (see Annotations) DocumentViewer, 884 XPS files, 920, 921 Fixed frame rate, 392 Fixed-sized tiles, 332 Fixed-size floater, 900 FlipButton, 531 FlipButtonAlternate, 531 FlipPanel BackContent property, 529 Control class, 528 CornerRadius property, 530 default control template basic skeleton, 532 ChangeVisualState() method, 537–538 ContentPresenter elements, 532 flip button, 533 GetTemplateChild() method, 537 OnApplyTemplate() method, 536–537 state animations, 533–535 1054

state transitions, 535–536 ToggleButton, 537 VisualStateManager.GoToState() method, 537 DefaultStyleKeyProperty.OverrideMetadata() method, 530 different control template, 539–540 flipping, 527, 528 FrameworkElement class, 528 FrontContent property, 529 GetValue() and SetValue() methods, 529 IsFlipped property, 529 parts and states selection, 530–531 structure, 528 usage, 538–539 Flipping tiles, 335 Floater element, 896–900, 909 FlowDocument.Blocks.FirstBlock property, 902 FlowDocument.Blocks.LastBlock property, 902 FlowDocument.Blocks property, 902 FlowDocument.ContentEnd property, 914 FlowDocument.ContentStart property, 914 FlowDocument element, 888 FlowDocument.IsHyphenationEnabled property, 906 FlowDocument.IsOptimalParagraphEnabled property, 906 FlowDocumentMaxPageWidth property, 910 FlowDocument objects, 179 FlowDocumentPageViewer, 884, 907–911 FlowDocument properties, 910 FlowDocumentReader, 884, 901, 907–908, 911, 922 Flow documents annotations (see Annotations) ColumnDefinition.MaxWidth property, 884 construction, 888–890 content elements (see Content elements) editing (see RichTextBox control) flow elements, 885–887 Grid container, 884 long lines, flow content, 884 programmatic interaction dynamic modification, 904 free-flowing structure, 902 Generate button code, 905 navigation properties, 902–903 single paragraph and bolded text coding, 902 Span elements, 904–905 Span.Tag property, 903 Tag property, 903

n INDEX

read-only containers FlowDocumentPageViewer, 907 FlowDocumentReader, 907 FlowDocumentScrollViewer, 907 loading from file, 911–912 pages and columns creation, 908–911 printing, 912 TextBlock, 907 RichTextBox, 884 support rich layout, 884 text justification, 905–906 wasted space, flow content, 884, 885 zooming, 907–908 FlowDocumentScrollViewer, 884, 888, 890, 901, 902, 907, 908, 920, 922 FlowDocumentScrollViewer.IsSelectionEnabled property, 890 FlowLayoutPanel, 976 Focus control and noncontrol classes, 125 definition of, 125 focusable property, 125 moving from one element to another, 125 Focusable property, 125, 885 FontFamily attribute, 284 FontFamily.BaseUri property, 150 FontFamilyConverter, 284 FontFamily property, 744, 887 FontSize property, 744, 887 FontStretch property, 887 FontStyle property, 887 FontWeight property, 147, 887 FontWeights.Bold property, 917 FontWeights.Normal property, 917 FooterPaneContent template, 802 ForceCursor property, 152 Foreground property, 291, 744, 887 FormattedText.MaxTextWidth property, 370 Formatting bound data ComboBox, 642–645 data binding, 601–603 data conversion (see Data conversion) data templates (see Data templates) list controls, 613–615 list styles (see List styles) FormHasErrors() method, 589 Form.ShowDialog() method, 969 ForSelection, 924 ForwardStack property, 759 Fragment navigation, 748 FragmentNavigation event, 757

Frame, 780 Frame-based animation, 448–451 Frame class, 748 Frame.NavigationUIVisibility property, 749 Frame objects, 750 Frame.Source property, 752, 755 FrameworkContentElement class, 885 FrameworkElement class, 151, 162, 181, 341, 506, 531, 885 FrameworkElement.Cursor property, 152 FrameworkElement.FindResource() method, 275 FrameworkElement.Triggers collection, 408 FrameworkPropertyMetadata.Inherits flag, 148 FrameworkPropertyMetadata object, 95, 510, 545 Freezable class, 273 From property, 398–399 FrontContent property, 529

ng Geometries classes, 347 combined geometry, 351–354 curves and lines arcs, 356–358 Bézier curves, 358–359 clipping, 361–362 mini-language, 359–361 pathfigure class, 354 pathsegment class, 355 straight lines, 355 ellipse, 349 line, 348 object, 348 rectangle, 348 shapes, 349–351 Geometry3D class, 839 GeometryModel3D class, 839 Geometry object, 309, 338 GeometryTransform property, 309 GetAnnotations() method, 928 GetContentState() method, 762, 763 GetDefaultView()method, 648 getElementsByTagName() method, 784 GetFileName() method, 759 GetFixedDocument-Sequence() method, 920 GetForegroundBrush() method, 552–553 GetNavigationService() method, 755 GetPosition() method, 127 GetProduct() method, 561, 573, 596 GetProperty() method, 544 1055

n INDEX

GetTemplateChild() method, 525, 537 GetTouchPoint() method, 134 GetValue() method, 511 GetVisualChild() method, 372 GetVisual() method, 375 GetVisuals() method, 378 Global Assembly Cache (GAC), 220 Glue code, 968 GoBack() method, 756, 781 GoForward() method, 756, 781 GotFocus event, 980 Gradient Obsession, 439 GradientStop, 327, 328 GrammarBuilder, 834 GrammarBuilder.Append() method, 834 Grid ColumnDefinition and RowDefinition elements, 70 Column property, 71 ColumnSpan property, 75 creating a Grid-based layout, 70 IsSharedSizeScope property, 81 Layout Rounding, 74 placing individual elements into cells, 71 row and column absolute sizes, 72 automatic sizes, 72 ColumnDefinition object, 73 proportional sizes, 72 RowDefinition object, Height property, 73 using nested Grid containers, 74 Row property, 71 RowSpan property, 75 SharedSizeGroup property, 81 shared size groups, 79 Shared size property, 81 ShowGridLines property, 70 splitter bars, 76 Background property, 78 DragIncrement property, 77 guidelines, 76 ResizeBehavior property, 77 ResizeDirection property, 77 resizing rows or columns, 76 ShowsPreview property, 77 UniformGrid code example with four buttons, 82 description of, 82 in Visual Studio, 72 Grid.DataContent property, 570 Grid wrapping, 563 1056

GroupDescriptions property, 652 GroupName property, 161 GroupSizeReductionOrder property, 807 GroupStyle.HeaderTemplate,660 GroupStyle property, 615 GroupStyleSelector property, 615 growStoryboard animation, 418 GZipStream, 776

nh Handles statement, 113 HandoffBehavior.Compose, 410 HandoffBehavior enumeration, 409 HasContent property, 155 HasDropShadow property, 164 HeaderedContentControl, 169 HeaderTemplate property,659 HeaderTemplateSelector property,659 HeightExplicitly property, 59 Height property, 62, 901 “Hidden” annotation, 925 Hide() method, 745 hierarchy, 271–272 Higher-level tasks, 243 HighlightTemplate, 633 Hit testing ContainerUIElement3D, 875 in 3-D scene, 871 ModelUIElement3D, 873–874 in the Viewport, 872–873 HitTest() method, 377 HitTestResultBehavior,378 HitTestResultCallback() method, 377 HitTestResult object, 378 HorizontalAlignment property, 58, 59, 156, 311, 393, 544, 899 HorizontalAnchor property, 901 HorizontalOffset property, 901 HorizontalScrollBarVisibility property, 179 Horizontal scrolling, 171 HostInBrowser, 768 HtmlBridge cla, 785 HtmlBridge.WebClick() method, 785 HTMLDocument object, 781, 782 HtmlDocument object, 782 hwnd, 972, 976 HwndHost, 982 HwndSource class, 982 HwndSource property, 120 Hyperlink element, 896

n INDEX

Hyperlink.NavigateUri property, 752, 755 Hyperlinks Click event, 746 content element, 885, 895 fragment navigation, 748 inline flow elements, 745 NavigateUri property, 746 navigating to websites, 747 Page.ShowsNavigationUI property, 746 RequestNavigate event, 746 TextBlock element, 745 visited pages history, 746 WindowTitle property, 747

nI IAnchorInfo, 930 IAnimatable interface, 396 IBindingList, 647 IBindingList interface, 573 ICollectionView interface, 658 ICollectionViewLiveShaping property, 663 ICollectionView reference, 648 ICollectionViewSource.Refresh() method, 663 IComparer object, 658 IDataErrorInfo interface, 579 Id property, 928 id tag, 782 IEnumerable, 647 IEnumerable(Of T), 575 IHTMLElement.getAttribute() method, 782 IHTMLElement object, 781–783 ImageBrush class, 325, 331–332, 343 Stretch property, 334 Viewport property, 333 ViewportUnits property, 333 Image class, 549 ImageDirectory property, 609 ImagePathConverter class, 608–609, 610, 629 ImageSource property, 281, 331, 609, 800 Image.Stretch property, 900 IMultiValueConverter interface, 612, 633 IncreaseZoom() method, 908 indowsFormsApplicationBase, 207 Inertia, 139, 140 InitializeComponent() method, 120, 520, 597, 756 Initialized event, 119 Initialized/Loaded event handler, 756 InitialShowDelay property, 166, 167 Inline elements, 886 Bold, 896

Figure, 896, 901 Floater, 896–900 Hyperlink, 896 InlineUIContainer, 896 Italic, 896 LineBreak, 896 Run, 896 Span, 896 Underline, 896 whitespace, 896–897 InlineUIContainer class, 919 InlineUIContainer element, 896, 899 InnerException property, 120, 588 innerHTML tag, 782 innerText tag, 782 INotifyCollectionChanged interface, 572 INotifyDataErrorInfo interface, 579, 582 INotifyDataError interface BindingValidationFailed event, 585 GetErrors() and HasErrors() methods, 584 IDataErrorInfo interface, 582 INotifyDataErrorInfo interface, 582 Product.ModelNumber property, 583–584 SetErrors() and ClearErrors() methods, 583 ValidatesOnDataErrors property, 585 ValidatesOnNotifyDataErrors property, 585 INotifyPropertyChanged interface, 566, 632 InputDevice class, 121 InputEventArgs class device property, 121 Timestamp property, 121 Input events, 118, 121 Interaction.Behaviors attached property, 302 InteractiveVisual3D, 877 Internet Explorer browser engine, 742 IntersectionDetail property, 378 InvalidateVisual() method, 550, 552 InvalidOperationException property, 756, 985 IProvideCustomContentState interface, 762, 763 IsAdditive property, 400 IsAsync property, 597 IsBrowserHosted property, 773 IsChecked property, 161, 791 IsColumnWidthFlexible property, 910 IScrollInfo, 172 IsCumulative property, 405 IsDefault, 160 IsDocumentEnabled property, 919 IsDropDownOpen property, 192, 643 IsEditable property, 643, 644 IsEnabled property, 125, 164, 291 1057

n INDEX

IsExpanded property, 176 IsFlipped property, 529 IsHighlighted property, 791 IsInitialized property, 119 IsInitialLoadEnabled property, 597 IsLiveFiltering property, 663, 664 IsLiveGrouping property, 663 IsLiveSorting property, 663 IsLoaded property, 119 IsLocked property, 571, 795 IsMainMenu property, 788 IsMouseDirectlyOver property, 128 IsMouseOver property, 128 IsolatedStorageFile.AvailableFreeSpace, 776 IsolatedStorageFile.GetDirectoryNames() method, 775 IsolatedStorageFile.GetFileNames() method, 775 IsolatedStorageFile.GetUserStoreForApplication() method, 775 IsOpen property, 164, 166 IsPressed property, 791 IsReadOnly property, 180, 643, 644 IsRepeat property, 123 IsSelected property, 639, 753 IsSharedSizeScope property, 81 IsSingleInstance property, 208 IsSnapToTickEnabled property, 189, 235 IsSubmenuOpen property, 791 IsTabStop property, 125 IsThreeState, 161 IsTodayHighlighted, 192 ISupportInitialize interface, 119 IsValid property, 586 IsVisible property, 291, 777 Italic element, 896 Item-based scrolling, 579 ItemContainerStyle property, 614–617 ItemContainerStyleSelector property, 614 ItemsControl class, 507, 576, 602, 613–615, 622, 626, 642, 643 ItemsControl.IsSynchronizedWithCurrentItem, 651 ItemsPanel property, 614, 788 ItemsPanelTemplate property, 577, 641, 642 ItemsPresenter, 577 ItemsSource DisplayMemberPath property, 788 ItemsSource property, 567, 602, 614, 626, 629, 651 ItemStringFormat property, 614 ItemTemplate property, 567, 614, 626, 788 ItemTemplateSelector property, 614, 788 IValueConverter class, 606, 612, 633 IValueConverter interface, 612 1058

nj JournalEntryName property, 760–761 JournalEntry.Source property, 759 journal flag, 753 JournalOwnership property, 751

nk KeepAlive property, 745, 754 KeepTogether property, 911 KeepWithNext property, 911 Key attribute, 270 Keyboard class, 126 KeyboardDevice class, 126 KeyboardDevice methods, 126 KeyboardDevice property, 126 Keyboard events, 118, 121 Keyboard-handling models, 980 KeyConverter, using, 124 KeyDown event, 116, 122, 123 KeyEventArgs object, 123, 126 Key-frame animation, 442–443 Key property, 123 KeyStates property, 126 KeyTip property, 806 KeyUp event, 122

nl Language integrated query (LINQ) Expression, 574–576 Large fonts, 331 LargeImageSource property, 803 LastChildFill, 67 LastModificationTime property, 928 Layout abstract, 55, 56 Canvas, 82 ColumnDefinition element, 88 ColumnSpan property, 88 DockPanel, 66 Dock, 67 LastChildFill, 67 setting the order of docking controls, 67 dynamic content, 88, 90 Grid, 70 modular user interfaces, 90 nesting layout containers, 68, 70 principles, 53, 54 scrolling, 54 ScrollViewer, 54

n INDEX

StackPanel (see StackPanel) stages of, 54 Visibility property, 91 WrapPanel (see WrapPanel) Layout arranging window components in a tabular structure, 87 LayoutTransform property, 341, 828 Left property, 82 Lifetime events, 118 LinearGradientBrush class, 146, 325–328, 345, 415 Linear interpolation, 418 Linear navigation, journal management, 758 LineBreakBefore property, 545–547 LineBreak element, 896 Line class Canvas, 316 dashes, 322–323 flow content model, 316 line caps, 320 line joins, 320 nonzero Margin, 316 Stroke property, 316 LineCount property, 179 LineDown() method, 171 LineHeight property, 888 Line-justification algorithms, 905, 906 LineLeft() method, 171 LineRight() method, 171 LineStackingStrategy property, 888 LineUp() method, 171 ListBoxChrome decorator, 65 ListBox class, 184–187, 566, 615, 625–627, 753 ListBox control, 615, 641, 652 ListBox.DisplayMemberPath property, 619 ListBox.GroupStyle property, 660 ListBoxItem class, 410, 614, 621, 622, 625 ListBox.ItemContainerStyle property, 615, 620 ListBoxItem.ContentTemplate, 620, 626 ListBoxItem.IsSelected property, 616, 619, 639, 641 ListBoxItem objects, 576–578, 577, 615 ListBox.Items.Add() method, 629 ListBox.ItemsSource property, 576 ListBoxItem.Template property, 619 ListBox.SelectedItem property, 571 ListBox.SelectionMode property, 620 ListBox.SnapsToDevicePixels property, 638 ListBox.Template property, 619 ListCollectionView, 647, 648, 657, 658, 663 List controls, 613–615, 626 ComboBox, 187–188 ItemsControl class, 183

ListBox, 184 List element, 891–892 ListItem collection, 903 ListSelectionJournalEntry callback, 761 ListSelectionJournalEntry object, 761, 762 List styles ItemContainerStyle property, 615–617 item style alternation, 620–622 ListBox CheckBox elements, 617, 618 ListBoxItem, 618 RadioButton elements, 617–619 style selectors background color, 624 Binding.SourceUpdated, 625 bold formatting, 624 Category property, 624 DataTable.RowChanged, 625 ItemContainerStyleSelector property, 625 Resources collection, 622 rudimentary selector, 622 SelectStyle() method, 622–624 SingleCriteriaHighlightStyleSelector, 624 ListView, 566, 665 advantages, 666 Custom View DataTemplate, 671 DefaultStyleKey property, 670 GridView class, 675 ImageDetailView, 676 ItemContainer.DefaultKeyStyle property, 670 passing information, 677–678 ResourceKey, 670 TileView objects, 676 View class, 672–673 view objects, 675 View styles, 673–675 GridView, 666 cell templates, 668–670 column creation, 667 column headers, 670 resizing columns, 668 ListBox class, 665 ViewBase class, 665 LiveFilteringProperties, 664 LiveGroupingProperties, 664 Live shaping, 663–664 LiveSortingProperties, 664 LoadAsync() method, 814 LoadCompleted event, 757, 781 1059

n INDEX

LoadedBehavior property, 818, 819, 820, 824 Loaded event, 119 LoadFile() method, 203 Load() method, 814 Localization BAML resources, 218 building user interfaces, 218–219 CurrentUICulture property, 218 default culture, 219 fine-grained, 219 GAC, 220 language, 219 probing, 218 satellite assembly, 220 translation process extracting localizable content, 221–224 markup elements preparation, 220–221 satellite assembly building, 224–226 user-interface, 218 .vbproj .csproj file, 219 Lock() method, 390 Logical scrolling, 172 LookDirection property, 868 Lookless control, 505, 506 color picker code, 520 color picker markup ColorPicker.xaml file, 522 generic.xaml file, 520–521 OverrideMetadata, 520 TemplateBinding markup extensio, 522–523 theme-specific styles, 521–522 control template adding part names, 524–525 documenting template parts, 526–527 event handlers, 524 manipulating template parts, 525–526 Losing a mouse capture, 130 LostFocus event, 120, 980 LostMouseCapture event, 130 LowerLatin value, 891 LowerRoman value, 891

nM Main() method, 196 MainWindow, 799 Managed Add-in Framework (MAF), 997–998 Managed Extensibility Framework (MEF), 997–998 Manipulation, 133, 136, 139 Margin property, 58, 887 MarkerOffset property, 891 1060

MarkerStyle property, 891 Markup-based approach, 653 MaskedTextBox, 973–974 MaskInputRejected event, 974 MatrixCamera class, 846 MatrixTransform class, 338 MaxHeight property, 58 MaxLines property, 179 MaxWidth property, 58 Measure() met, 542, 543 MeasureOverride() method, 541–542, 544 MediaClock class, 820 MediaCommands class, 819 MediaElement class, 549, 815 animation synchronization, 825–827 error handling, 819 playing audio with triggers, 820–822 playing multiple sounds, 822–823 playing video, 827 Position control, 825 programmatic audio play, 818–819 SpeedRatio control, 824 video effects (see Video effects) Clipping property, 828 content control, 827 copying content, 828 intermediary rendering surface, 830 LayoutTransform property, 828 Opacity property, 828 reflection effect, 828–829 RenderTransformOrigin property, 828 RenderTransform property, 828 resolutions and frame rates, 830 VideoDrawing class, 830 Volume and Balance control, 823–824 MediaElement.Clock information, 825 MediaElement.Source property, 820 MediaElement.SpeedRatio property, 824 MediaEnded event, 825 MediaFailed event, 817, 819 MediaOpened event, 817 MediaPlayer class, 816–818, 823, 830 MediaPlayer.Close() method, 816 MediaState enumeration, 818 MediaTimeline class, 403, 820, 823 MediaTimeline.Source property, 820 MediumSpeedProperty, 598 MemoryStream, 923 Menu class, 614 MenuItem class, 789–791 MenuItem.Click event, 790

n INDEX

MenuItem.Icon property, 791 MenuItem.InputGestureText property, 791 MenuItem.IsChecked property, 791 MenuItem objects, 789, 791 Menus ContextMenu class, 791 items, 789–791 Menu class, 788 Separators, 792–793 MergedDictionaries property, 278–279 MessageBeep Win32 API, 816 Message property, 588 Microsoft.Expression.Interactions.dll assembly, 299, 303 Microsoft HTML Object Library, 781 Microsoft Money, 742 MinHeight property, 58 MinLines property, 179 MinOrphanLines property, 911 MinWidth property, 58 MinWindowLines property, 911 Miter line join, 321 Mixed menus, 787 Model3DGroup class, 857 ModelUIElement3D class, 871, 873–874 MonitorCommands, 266 MouseButton event, 129 MouseButtonEventArgs object, 113, 129 Mouse.Capture() method, 129 Mouse class, 129 Mouse cursors, 151–152 MouseDoubleClick event, 129 MouseDown event, 872, 919 MouseEnter event, 127, 289, 290, 296 MouseEventArgs object, 107, 127 Mouse events, 118 AllowDrop property, 131 ButtonState event, 129 capturing the mouse by calling Mouse.Capture() method, 129 ClickCount event, 129 creating drag-and-drop source, 131 direct events, definition of, 127 drag-and-drop operations, 130 DragDrop class, 131 DragEnter event, 131 dragging-and-dropping into other applications, 132 IsMouseDirectlyOver property, 128 IsMouseOver property, 128 LostMouseCapture event, 130

MouseButton event, 129 MouseButtonEventArgs object, 129 mouse class, 129 mouse click events, 128 MouseDoubleClick event, 129 MouseEnter event, 127 MouseLeave event, 127 MouseMove event, 127 PreviewMouseDoubleClick event, 129 PreviewMouseMove event, 127 MouseLeave event, 127, 289, 290, 297 MouseLeftButton, 374 MouseLeftButtonDown event, 128 MouseLeftButtonUp event, 129 MouseMove event, 127 Mouse.OverrideCursor property, 152 MouseRightButtonDown event, 128 MouseRightButtonUp event, 129 MouseUp() method, 108 MultiBinding, 612 MultiBinding.StringFormat property, 612 MultiDataTrigger class, 294 Multiple views, 653 Multithreading BackgroundWorker (see BackgroundWorker) context, 983 definition, 983 dispatcher, 984 DispatcherObject class, 984 BeginInvoke() method, 986, 987 Dispatcher.BeginInvoke() method, 985, 986 DispatcherPriority.ApplicationIdle, 986 DispatcherPriority.Normal, 986 DispatcherPriority.SystemIdle, 986 InvalidOperationException, 985 Invoke() method, 987 members, 984 System.Threading.Thread object, 985 time-consuming code, 986 UpdateTextWrong() method, 985 VerifyAccess() method, 985 dual-core CPUs, 983 element, 984 single-threaded apartment model, 984 thread affinity, 984 thread rental, 983 Multitouch input, 132, 140 MultiTrigger class, 294, 296 MyPageData property, 755 myTextBox element, 748 1061

n INDEX

nn

NotifyPropertyChanged property, 663

Navigated event, 757, 781 Navigate() method, 756, 781, 782 NavigateToStream() method, 781 NavigateToString() method, 781 NavigateUri property, 746, 896 Navigating event, 757, 781 NavigationFailed event, 747, 757 NavigationFailedEventArgs.Handled, 757 NavigationProgress event, 757 NavigationService class events, 756–757 journal AddBackEntry() method, 759 AddBackReference() method, 763 CustomContentState class, 760 CustomContentState object, 763 custom entries, 764, 766 dynamic list, 760 GetContentState() method, 762, 763 IProvideCustomContentState interface, 762, 763 JournalEntryName property, 760–761 JournalEntry object, 759 ListSelectionJournalEntry callback, 761 ListSelectionJournalEntry object, 762 management, 758–759 NavigationService.Content property, 761 PageWithMultipleJournalEntries, 763 ReplayListChange delegate, 761 Replay() method, 761–763 System.Windows.Navigation. CustomContentState class, 759 “virtual” entries, 759 Page Functions (see PageFunction class) programmatic navigation, 755–756 NavigationService.Content property, 761 NavigationService.GetNavigationService() method, 755 NavigationService.Navigate() method, 764, 765 NavigationService property, 745 NavigationService.StopLoading() method, 756 NavigationStopped event, 757 NavigationUIVisibility property, 749, 751 NavigationWindow, 742–744, 752, 769 Nested page, 750 .NET Framework, 771 non-MenuItem objects, 790 Nonshared resources, 274 Nonzero fill rule, 319

no

1062

ObjectAnimationUsingKeyFrames, 394 Object-based drawing, 18 ObjectDataProvider, 595 asynchronous support, 597 binding, 596 error handling, 596–597 features, 596 GetProducts() method, 596 Object resource, 269 access, code, 275 adaptability, 269 application, 275–276 collection, 270–271 dynamic resource, 272–274 efficiency, 269 maintainability, 269 nonshared, 274 static resource, 272–274 system, 276–277 ObservableCollection class, 572, 575, 602 Office ribbon, 787 OnApplyTemplate() method, 525, 536–537 onBlur event, 785 onChange event, 785 onClick event, 785 OnColorChanged() method, 512, 513 OnColorRGBChanged() method, 512 OneWayToSource binding, 580 onFocus event, 785 OnMainWindowClose approach, 198 onMouseOver event, 785 OnPropertyChanged() method, 566 OnRender() method, 154, 371, 548–550 OnReturn() method, 765, 766 OnStartup() method, 208 OpacityMask gradient, 828 OpacityMask property, 344 Opacity masks, 414 Opacity property, 325, 342, 344, 826, 828 Open() method, 816, 817 Optimal paragraph layout, 905 Ordinary property, 754 Orientation property, 794 orizontalContentAlignment property, 156 OrthographicCamera class, 846 Oscillations property, 418 outerHTML tag, 782

n INDEX

outerText tag, 782 Overflow menu, 794–795 OverflowMode.Always, 795 OverflowMode.AsNeeded, 795 OverflowMode.Never, 795 OverflowMode property, 795 OverrideMetadata() method, 520

nP PackageStore.GetPackage() method, 931 Padding property, 69, 514, 887, 899 Page-based interfaces. See Page-based navigation Page-based navigation application with NavigationWindow, 743–744 custom property maintenance, 754–755 events, 756–757 hosting pages in another page, 750–751 in Frame, 748–750 in Web Browser, 751–752 hyperlinks Click event, 746 fragment navigation, 748 inline flow elements, 745 NavigateUri property, 746 navigating to websites, 747 Page.ShowsNavigationUI property, 746 RequestNavigate event, 746 TextBlock element, 745 visited pages history, 746 WindowTitle property, 747 Internet Explorer browser engine, 742 journal AddBackEntry() method, 759 AddBackReference() method, 763 CustomContentState class, 760 CustomContentState object, 763 custom entries, 764, 766 dynamic list, 760 GetContentState() method, 762, 763 IProvideCustomContentState interface, 762, 763 JournalEntryName property, 760–761 JournalEntry object, 759 ListSelectionJournalEntry callback, 761 ListSelectionJournalEntry object, 762 management, 758–759 NavigationService.Content property, 761 PageWithMultipleJournalEntries, 763 ReplayListChange delegate, 761

Replay() method, 761–763 System.Windows.Navigation. CustomContentState class, 759 “virtual” entries, 759 navigation events, 756 navigation history, 753–754 Navigation Service (see NavigationService class) Page class, 744–745 Page Functions (see PageFunction class) programmatic navigation, 755–756 System.Windows.Controls.Page class, 742 URIs, WPF, 752–753 WebBrowser control (see WebBrowser control) weblike applications, 742 weblike interface, 742 websites vs. Windows applications, 741 XAML file, 742 XBAPs (see XAML browser applications (XBAPs)) Page class, 744–745 PageDown() method, 171 PageFunction class creation, 764 OnReturn() method, 765, 766 Product class, 764 programmatic navigation, 765 RemoveFromJournal property, 766 Returned event, 765 TypeArguments attribute, 764, 765 element, 764 Page.Initialized event, 755 Page.KeepAlive property, 754 PageLeft() method, 171 PageLibrary component, 752 Page.Loaded event, 744, 755 PageRight() method, 171 Page.ShowsNavigationUI property, 746 PageUp() method, 171 Panel class, 507, 549 Paragraph element, 890 Paragraph properties, 911 Parse() method, 606 PasswordBox, 179 PasswordBox.PasswordChar property, 100 Path-based animation, 446–448 PathGeometry object, 394 Path shape, 338 Pause() method, 819 PauseStoryboard action, 411 PerspectiveCamera class, 846 Pixel-based scrolling, 579 Pixel snapping, 324 1063

n INDEX

PlacementMode, 164 Placement property, 776 PlacementRectangle property, 164 PlacementTarget property, 164 PlayLooping() method, 814 Play() method, 814, 816, 817, 819, 822, 823 PlaySync() class, 815 PlaySync() method, 814 Plug-ins. See Add-ins PointLight and SpotLight, 846 Polygon class, 308, 318–320 Polyline class, 308 dynamic graphics, 318 Expression Design, 318 line caps, 320 line joins, 320 PointCollection object, 317 Points property, 317 with several segments, 317 Pop() method, 370 PopupAnimation property, 167, 776 Popup.Child property, 167 Popup.StaysOpen property, 167 Position property, 825 PositivePriceRule, 587 PowerEase class, 421 Prebuilt commands, 246 PreviewKeyDown event, 116, 122, 124 PreviewKeyUp event, 122 PreviewMouseDoubleClick event, 129 PreviewMouseLeftButtonDown event, 128 PreviewMouseLeftButtonUp event, 129 PreviewMouseMove event, 127 PreviewMouseRightButtonDown event, 128 PreviewMouseRightButtonUp event, 129 PreviewTextInput event, 122, 123, 124 PreviewTouchDown event, 133 PreviewTouchMove event, 133 PreviewTouchUp event, 133 PriceConverter class, 607 Primary interop assembly, 976 PrintDocument() method, 244 Printing, 935 an element, 936 in-memory TextBlock object, 941 limitations, 937 PrintDialog object, 936 PrintVisual() method, 937 annotations, 944 document ColumnWidth and ColumnGap, 943 1064

DocumentPaginator property, 943 FlowDocumentPageViewer, 943 FlowDocumentReader, 943 limitations, 944 PrintDocument() method, 942 Document paginator, 945 GetPage() method, 946 HeaderedFlowDocumentPaginator class, 945–946, 947 PageCount, 946 PageSize, 946 with multiple pages data split, 951 DocumentPaginator, 951 DrawText() and DrawLine() methods, 955 GetFormattedText() method, 953 GetPage() method, 951, 954 PaginateData() method, 952 StoreDataSetPaginator, 952, 956 page ranges, 957 PrintDialog class, 938 PrintDocument() method, 936 PrintVisual() method, 936 print queues, 957–960 print settings, 956 Transform object, 938 Canvas, 939 Measure() and Arrange() methods, 939 PrintableAreaWidth and PrintableAreaHeight properties, 939–940 visual-layer classes ContainerVisual, 948 custom printout, 948, 950 DrawingContext methods, 948–949 DrawingVisual, 948 XPS documents asynchronous printing, 961, 963–964 DocumentPaginator, 960 MemoryStream, writing to, 962 PrintDialog class, 962 print preview, 961 Write() and WriteAsync() methods, 961 Print() method, 912 ProductByPriceFilter object, 655, 656 Product.CategoryName property, 660 Product class, 764 Product.Description property, 564 productMatchesTracked collection, 575 Product.ModelName property, 626 Product.ModelNumber property, 598, 602 Product objects, 628

n INDEX

Product.UnitCost property, 606, 664 Programmatic navigation, 755–756 ProgressBar content, 416 ProgressBar control, 190 ProgressBar.Value, 417 ProgressChanged event, 992 PromptBuilder.AppendSsml() method, 833 PromptBuilder.BeginStyle() method, 832 PromptBuilder class, 831–833 PromptBuilder.ToXml() method, 833 PromptEmphasis, 832 PromptRate value, 832 PromptStyle object, 832 PromptVolume value, 832 Property-based animation, 392–393 PropertyChanged event, 566, 625 PropertyChanged property, 565 PropertyGroupDescription.Name property, 660 PropertyMap collection, 981 PropertyNameChanged property, 565 Property-resolution system, 399 Property translators, 980 Proportional coordinate system, 329 Proportionally sized tiles, 332 proportional scaling, 315 PushClip() method, 371 PushEffect() method, 371 PushOpacityMask() method, 371 PushOpacity() method, 371 PushTransform() method, 371 PushXxx() methods, 371

nQ QuadraticEase class, 421 QuarticEase class, 421 Quick Access Toolbar (QAT), 809–810 QuickAccessToolBarImageSource property, 810 QuinticEase class, 421

nR RadialGradientBrush class, 325, 329–330, 552 RadioButton elements, 617–620 RadioButton.Focusable property, 619 RadioButton.IsChecked property, 619 RadioButton objects, 373 RadiusX property, 310, 329 RadiusY property, 310, 329 RaiseEvent() method, 107, 513 RangeBase class, 525 Range-based controls

ProgressBar, 190 properties, 188 slider, 188–190 RangeBase-derived control, 525 Raw touch support, 133, 136 record browser, 649, 650 Rectangle class, 308, 310 Rectangle.Fill property, 513 Rectangle geometry, 348 Refresh() method, 781 RegisterEvent() method, 110 RegisterPixelShaderSamplerProperty() method, 384 RegisterRoutedEvent() method, 106 RelativeSource property, 522 RelativeTransform property, 828 RemoveBackEntry() method, 758 RemoveFromJournal property, 766 RemoveHandler() method, 106 RemoveRequested event, 416 RemoveSignature() method, 920 RemoveStoryboard action, 411 RenderedGeometry property, 309 RenderTransformOrigin property, 340, 341, 828 RenderTransform property, 339, 341, 828 RepeatBehavior property, 405–406 RepeatButton, 160 Repeat interval, 405 Replay() method, 761–763 RequestNavigate event, 746 ResizeBehavior property, 77 ResizeDirection property, 77 ResourceDictionary class, 270 ResourceKey objects, 277 ResourceKey property, 272 Resources assembly resource, 269 dictionary ComponentResourceKey, 279–282 creation, 277 CustomResources, 280 generic.xaml, 279 ImageSource property, 281 library assembly, 281 MergedDictionaries property, 278–279 merging resource collection, 278–279 ResourceDictionary object, 278, 279 ResourceLibrary, 280 ReusableDictionary.xaml, 279 object resource (see Object resource) Resources property, 270, 287 ResumeStoryboard action, 411, 822 1065

n INDEX

Retrieving resources, 214–215 ReturnEventArgs.Result property, 765 Reversible animation, 402 RGB and scRGB, 145 RibbonApplicationMenu class, 800 RibbonApplicationMenuItem class, 800 RibbonApplicationMenuItem objects, 800, 804 Ribbon.AuxiliaryPaneContent, 802 RibbonButton objects, 802–804, 804, 807 RibbonCheckBox class, 804 RibbonComboBox class, 804 RibbonControlsLibrary.dll assembly, 798 Ribbon.FooterPaneContent, 802 RibbonGroup objects, 802–804 RibbonGroupSizeDefinitionCollection, 808 RibbonGroupSizeDefinition objects, 807 RibbonMenuButton class, 804 Ribbon.QuickAccessToolBar property,809 RibbonRadioButton class, 804 Ribbon.Resources, 808 Ribbons adding, 798–780 application menu, 800–802 keyboard access with KeyTips, 806 Microsoft, 798 QAT,809–810 rich tooltips, 804–805 sizing, 807–809 Tabs, Groups, and Buttons, 802–804 Windows applications, 798 RibbonSeparator class, 804 RibbonSplitButton class, 804 RibbonTab objects, 802–804 RibbonTest, 799 RibbonTextBox class, 804 RibbonToggleButton class, 804 RibbonWindow class, 798–800 RibbonWrapPanel, 807 RichTextBox control, 179 file loading data format, 914–915 Load() method, 914 System.Windows.Documents.TextRange class, 886 TextRange, 913–914 XamlReader.Load() method, 913 FlowDocument object, 913 individual words, 919 MouseUp event, 919 PreviewMouseDown event, 919 saving file, 915–916 1066

text formatting, 916–918 UIElement objects, 919 RichTextBox.IsDocumentEnabled property,919 RichTextBox.Selection property,917 Right property, 82 RotateTransform class, 337, 339 RotateTransform3D, 866–867, 878 Round line join, 321 RoutedCommand class, 246–247 RoutedEventArgs class, 107, 110 RoutedEventArgs.Handled property, 113, 757 RoutedEventHandler property, 513 Routed events AddHandler() method, 106, 108, 114 attached events, 114 bubbled image click, 111 click event, 114 definition of, 105 delegate type, 108 MouseEventArgs object, 107 name property, 115 naming event handler methods, 107 RaiseEvent() method, 107 RegisterEvent() method, 110 RegisterRoutedEvent() method, 106 registration, 105 RemoveHandler() helper method, 109 RemoveHandler() methods, 106 RemoveHandler statement, 108 sharing, 106 signatures, 107 suppressed events, 114 tunneling events (see Tunneling events) types of, 109 wiring up, attached event, 115 wrapping of, .NET events, 106 RoutedPropertyChangedEventHandler property,513 RoutedUICommand object, 516 RowDefinition element, 70 Row property, 71 RowSpan property, 894 Run element, 890, 895 Runtime callable wrappers (RCWs), 976 RunWorkerCompleted event, 994 RunWorkerCompletedEventArgs.Result property, 991

nS SayAs enumeration, 832

n INDEX

ScaleTransform, 540 ScaleTransform class, 337 ScrollToEnd() method, 171 ScrollToHome() method, 171 ScrollToHorizontalOffset() method, 171 ScrollToLeftEnd() method, 171 ScrollToRightEnd() method, 171 ScrollToVerticalOffset() method, 171 ScrollToXxx() methods, 171 ScrollViewer, 577, 778 custom scrolling, 172–173 definition, 170 expander, 175–178 GroupBox, 173 HorizontalScrollBarVisibility property, 170 horizontal scrolling, 170 programmatic scrolling, 171–172 scrollable window, 170 scrollbar, 170 TabItem, 173–175 ScrollViewer.HorizontalScrollBarVisibility property, 641 Section.Background property, 894 Section element, 894–895 Section.Style property, 895 SecureString.Dispose() method, 183 SecurityException, 772 SeekStoryboard action, 411 SelectedIndex property, 615, 753 SelectedItem.Products property, 571 SelectedItem property, 571, 615 SelectedText property, 180 SelectedValue propertry, 615 SelectionBoxItemTemplate property, 645 SelectionChanged event, 180 SelectionChangedEventArgs object, 186 SelectionMode propertry, 615 Selector class, 507, 615, 642 SelectStyle() method, 622 Separator objects, 789 SetBinding() method, 525 SetProperty() method, 544 SetSpeedRatio() method, 414 SetStoryboardSpeedRatio action, 411, 824 SetStoryboardSpeedRatio.SpeedRatio action, 414 Setters property, 286 Setter.TargetName property, 287 SetValue() method, 100, 511 SetZIndex() method, 84 ShaderEffect class, 383–384 Shape classes, 549

Shapes angling, 338 animation, 338 classes ellipse class, 308, 310 LayoutTransform property, 341 line class (see Line class) polygon class, 308, 318–320 polyline class (see Polyline class) rectangle class, 308, 310 elements, 307–308 FrameworkElement, 307 pixel snapping, 324 primitives, 307 properties, 309 repeating, 338 scaling with Viewbox autosized container, 316 Canvas.ClipToBounds property, 315 Canvas with resizability, 314 DPI setting, 315 non-Viewbox size, 315 StretchDirection property, 315 vector graphics, 314 Viewbox.Stretch property, 315 Width and Height properties, 315 window resizing, 315 sizing and placing, 311–313 transforming, 339–340 Shared attribute, 274 SharedLibrary assembly, 753 SharedSizeGroup property, 627 ShowDuration property, 166 ShowGridLines property, 70 Show() method, 745 ShowOnDisabled property, 167 ShowsNavigationUI property, 745 ShowsPreview property, 77 ShutdownMode property, 198 Sieve of Eratosthenes, 988 SignDigitally() method, 920 Silverlight, 299 SineEase class, 421 SingleCriteriaHighlightStyleSelector, 624–625 SingleCriteriaHighlightTemplateSelector, 634 Single-instance application Application.Startup event fires, 207 building document-based applications, 207 file-type registration, 211–212 IsSingleInstance property, 208 Microsoft Word, 207 1067

n INDEX

Single-instance application (cont.) OnStartup() method, 208 OnStartupNextInstance() method, 208 ShowDocument() window, 207 SingleInstanceApplicationWrapper class, 209 WCF, 207 WindowsFormsApplicationBase, 207, 208 WpfApp class, 208, 210 SingleInstanceApplicationWrapper class, 209 SingleInstanceApplicationWrapper. OnStartupNextInstance() method, 211 Single-thread affinity (STA), 13 Single-threaded apartment model, 984 SizeToContent property, 64, 178 SkewTransform class, 339 SkipStoryboardToFill action, 411 Slider controls, 513 Slider.Value property, 414 SlowSpeedProperty, 598 SmallImageSource property, 802, 803 Snapshot-and-replace behavior, 409 SnapshotAndReplace value, 409 SnapsToDevicePixels property, 309, 324 SolidColorBrush class, 325–326, 342, 513, 525 SolidColorBrush objects, 145 SolidColorBrush property, 927 Solution Explorer, 968, 977 SomethingClicked() method, 112, 113 SortByModelNameLength class, 658 SortByTextLength class, 658 SortDescription objects, 657, 658 SortDescriptions collection, 658 SortDescriptions property, 652 SoundLocation property, 814 SoundPlayerAction class, 815 SoundPlayer class, 813–815 SoundPlayer.Stream property, 814 SourceInitialized event, 120 SourceItems property, 761 Source property, 782, 823 Span element, 890, 896 Span.Tag property, 903 SpashScreen.Show() method, 202 SpecularMaterial, 859, 860 SpeechDetected event, 833 SpeechHypothesized event, 833 Speech recognition COM object, 833 Dispose() method, 833 GrammarBuilder, 834 SpeechDetected event, 833 1068

SpeechHypothesized event, 833 SpeechRecognitionRejected event, 833 SpeechRecognizer class, 833 SRGS grammar, 834 SubsetMatchingMode enumeration, 834 Windows accessibility feature, 833 Speech Recognition Grammar Specification (SRGS), 834 SpeechRecognitionRejected event, 833 SpeechRecognized event, 833 SpeechRecognizedEventArgs.Result property, 833 SpeechRecognizer class, 833 Speech synthesis dynamic text, 831 Narrator, 831 PromptBuilder class, 831–833 SpeechSynthesizer class, 831 SSML standard, 832 System.Speech.Synthesis namespace, 831 Speech Synthesis Markup Language (SSML) standard, 832 SpeechSynthesizer class, 831 SpeedRatio property, 284, 404, 411, 414 SpellCheck.CustomDictionaries collection, 183 SpellCheck.CustomDictionaries property, 182 SpellCheck.IsEnabled property, 182 SpellingReform property, 181 Splash Screen, 202 SplashScreen.Close() method, 202 Spline key frames animation, 444–448 SpreadMethod property, 328 Square value, 891 SrgsDocument, 834 Stack collection, 517 StackPanel, 311, 341, 659 arranging elements, 58 Border class, 64 button sizing, 62, 63 button stack example, 56 changing alignment defaults, 59 FrameworkElement class, 59 Height property, 62 HorizontalAlignment property, 59 in horizontal orientation, 58 layout properties, 58 setting control margins, 60 SizeToContent property, 64 Thickness structure, 60 VerticalAlignment property, 59 in Visual Studio, 57 Width property, 62

n INDEX

window, 64 StackPanel.Visibility property, 641 StartLineCap property, 320 StartPoint property, 327 StartupEventArgs.Args property, 203 StartupUri property, 197 StartVoice() method, 832 StaticResource reference, 628 Static resources, 271 StatusBar class, 793, 796–797 StatusBarItem object, 797 StaysOpen property, 164, 776 StickyNoteControl class, 932–933 StickyNoteControl.IsActive property, 932 Stitching. See Z-fighting Stop() method, 819 StopStoryboard action, 411 StoreContentChanged event, 931 StoreDB.GetProduct() method, 559, 568–569 Storyboard.CurrentTimeInvalidated event, 825, 826 Storyboards, 396, 406 declarative animation, 406 definition, 406 DoubleAnimation, 406 event trigger (see Event triggers) monitoring progress, 415–417 overlapping animations, 409–410 playback control BeginStoryboard action, 411, 412, 414 BeginStoryboardName property, 414 controllable animation, 412 ControllableStoryboardAction class, 411 EventTrigger.SourceName property, 413 media player actions, 413 Name property, 414 PauseStoryboard action, 411 SetSpeedRatio() method, 414 stopping vs. completing animation, 412 Storyboard object, 414 Triggers collection, 413 wipe effect, 414 synchronized animations, 410–411 TargetName property, 406 TargetProperty, 406 Timeline, 411 Storyboard.SlipBehavior property, 825 Storyboard.TargetName property, 407 Storyboard.TargetProperty property, 407 Straight lines, 355 StreamResourceInfo object, 215 StreamSource property, 608

StretchDirection property, 315, 827 Stretch property, 309, 312, 331, 334, 827 StringAnimationUsingKeyFrames, 394 StringFormat property, 612 String formatting, 603 StringFormat property Binding.StringFormat property, 603 ItemStringFormat property, 605 .NET format strings, 603 numeric data, 604 OrderDate property, 604 StringFormat value, 604 times and dates, 604 UnitCost property, 603 Visual Studio, 604 XAML parser, 604 with value converters Binding.StringFormat property, 605 creation, 606 DataBinding namespace, 607 Decimal.ToString() method, 606 Parse() method, 606 PriceConverter class, 607 Product.UnitCost property, 606 Resources collection, 607 StaticResource reference, 607 System.Globalization.NumberStyles value, 606 ToString() method, 606 TryParse() method, 606 XML namespace prefix, 607 StrokeDashArray property, 309, 322 StrokeDashCap property, 309, 323, 324 StrokeDashOffset property, 309, 323 StrokeEndLineCap property, 309 StrokeLineJoin property, 309 StrokeMiterLimit property, 309, 321 Stroke objects, 84 Stroke property, 309, 310 Strokes collection, 84 StrokeStartLineCap property, 309 StrokeThickness property, 309 Style inheritance, 292 Style property, 527, 887 Styles, 283, 505 advantages, 286 attaching event handlers, 289–291 automatic application, types, 292–293 BigFontButtonStyle, 285, 286 data types, 284 definition, 283 1069

n INDEX

Styles (cont.) dependency property, 283 FindResource() method, 285 font properties, 283 FontSettings object, 285 FontWeight.Bold, 284 layers, 291 list (see List styles) Padding and Margin properties, 286 programmatical, 285 property value inheritance feature, 284 Setter object, 285 setting properties attribute with nested element, 288 Button class, 289 Control class, 289 dependency property, 288, 289 Label class, 289 Setter objects, 288 TargetType property, 289 TextBlock class, 289 static resources, 284 Style class properties, 286 Style object creation, 287 Style property, 285 System.Windows.Style object, 285 triggers (see Triggers) triggers and templates, 283 vs. CSS, 283 window resources, 284 Windows Forms, 970–971 Style selectors, 611 Style.TargetType property, 616, 932 Style triggers, 617 Stylus events, 118 SubsetMatchingMode enumeration, 834 SynchronizationContext class, 992 SystemBrushes class, 325 System.Collections.Generic namespaces, 517 SystemColors class, 276 System.ComponentModel.BackgroundWorker component, 987 System.ComponentModel.INotifyPropertyChanged interface, 565 System.ComponentModel namespace, 989 System.ComponentModel. PropertyGroupDescription objects, 659 System.ComponentModel.SortDescription objects, 657 System DPI font size, 7 1070

higher pixel density, 8 scaling, 10 Windows 7 and 8, 9 Windows Vista, 8 System.Drawing.dll assembly, 968 System.Drawing namespace, 276 SystemFontFamilies collection, 149 SystemFonts class, 276 System.Globalization.CultureInfo class, 224 System.IO.Compression namespace, 776 System.IO.IsolatedStorage namespace, 775 System.IO.Packaging namespace, 931 System.Linq.Enumerable helper class, 575 System.Linq namespace, 575 System.Media.SystemSounds class, 816 SystemParameters class, 276 System resources, 276–277 System.Runtime.InteropServices namespace, 784 System.Security.Principal.WindowsIdentity class, 926 System.Security.SecureString object, 183 SystemSounds class, 816 System.Speech.Recognition namespace, 833 System.Speech.Recognition.SrgsGrammar namespace, 834 System.Speech.Synthesis namespace, 831 System.Threading.DispatcherObject, 13 System.Threading.Thread, 984 System.TimeSpan, 401 System.Windows.Annotations namespace, 922, 925 System.Windows.Annotations.Storage namespace, 922 System.Windows.Application class, 971 System.Windows.Clipboard class, 132 System.Windows.Control class, 144 System.Windows.Controls.ContentControl, 15 System.Windows.Controls.Control, 14 System.Windows.Controls.Decorator class, 65 System.Windows.Controls.ItemsControl, 15 System.Windows.Controls namespace, 797, 932 System.Windows.Controls.Page class, 742 System.Windows.Controls.Panel, 15, 641 System.Windows.Controls.Primitives namespace, 797 System.Windows.Controls.Primitives.RangeBase class, 525 System.Windows.Data.Binding class, 229 System.Windows.Data.CollectionViewSource class, 648 System.Windows.DependencyObject, 14 System.Windows.Documents.TextRange class, 913

n INDEX

System.Windows.Forms.Application class, 971 System.Windows.Forms.dll assembly, 968 System.Windows.Forms.Form class, 778 System.Windows.Forms.Integration.ElementHost class, 976–978 System.Windows.Forms.Integration namespace, 970, 973 System.Windows.Forms namespace, 973 System.Windows.FrameworkElement, 14 System.Windows.Freezable class, 146 System.Windows.Input.Cursor object, 151 System.Windows.Input.MediaCommands class, 819 System.Windows.Interactivity.dll assembly, 299, 303 System.Windows.Interop.HwndHost class, 982 System.Windows.Markup namespace, 911 System.Windows.Media.Animation namespace, 393–395, 417 System.Windows.Media.Brush, 325 System.Windows.Media.Color object, 510 System.Windows.Media namespace, 816 System.Windows.Media.Transform class, 337 System.Windows.Media.Visual, 14 System.Windows.Navigation.CustomContentState class, 759 System.Windows.Shapes.Shape class, 14, 308 System.Windows.SystemColors, 144 System.Windows.Threading.Dispatcher class, 984 System.Windows.UIElement, 14

nT TabControl, 173 TableCell element, 892–893 TableColumn objects, 894 Table element, 892–894 TableLayoutPanel, 976 TableRow element, 892 TableRowGroup element, 892 Table.Rows property, 894 TabStripPlacement property, 174 tagName tag, 782 Tag property, 823 TargetInvocationException, 588 TargetInvocationException.Message property, 588 TargetItems property, 761 TargetName property, 406 TargetNullValue property, 564 TargetProperty, 406 TargetType property, 287, 293 TargetZone, 768 Taskbar programming

jump lists, 729 in code, 732–733 custom jump list, 730–732 JumpListApplicationTask, 733 JumpTask, 733 launch applications, 733 properties, 731 recent document support, 729–730 overlay icons, 738 progress notification, 737 thumbnail buttons, 736–737 thumbnail preview, 734 TemplatePart attributes, 526, 531 Template selectors, 611, 632–636 TestApplication.App., 197 TextAlignment property, 887, 905 TextAnchor object, 930 TextBlock class, 549, 907 TextBlock element, 293, 416, 753 TextBlock.FontFamily property, 289 TextBlock.FontSize property, 235 TextBlock.Foreground property, 328 TextBlock.Inlines collection, 907 TextBoxBase, 179 TextBox control, 122, 507, 753 TextBox objects, 588 TextBox.Tag property, 904 TextChanged event, 122, 656 TextCompositionEventArgs object, 123 Text controls multiple lines, 179–180 PasswordBox, 179, 183 RichTextBox control, 179 spell check, 181–183 TextBox control, 179 text selection, 180–181 TextDecorations property, 148, 888 TextInput event, 122, 123 Text justification, 905–906 TextMarkerStyle enumeration, 891 TextOptions.TextFormattingMode, 151 TextPointer objects, 914 Text property, 753 TextRange class, 913–914, 917 TextRange.Load() method, 914, 916 TextRange.Save() method, 915 TextRange.Text property, 918 TextSearch.TextPath property, 644 TextSelection.GetPropertyValue() method, 917 TextSelection object, 917 TextTrimming property, 907 1071

n INDEX

Texture mapping ImageBrush mapping bitmap paint, 861 Positions collection, 863 TextureCoordinates collection, 862, 863 textured cube, 862 video and the VisualBrush, 864–865 TextWrapping property, 158, 907 ThicknessAnimation class, 393 Thread affinity, 984 Thread rental, 983 3-D drawing advantages, 838 camera axis lines, 849 complete 3-D scene, 849 FieldOfView property, 850 final scene, 849 LookDirection property, 868 MatrixCamera class, 846 NearPlaneDistance and FarPlaneDistance properties, 850 OrthographicCamera class, 846 PerspectiveCamera class, 846 positioning and angling, 848 Position property, 847 z-fighting, 850 complex shapes, 856 DiffuseMaterial, 859, 860 EmissiveMaterial, 859 four ingredients:, 837 interactivity and animations fly over, 868 hit testing (see Hit testing) rotations, 866–867 trackball, 870–871 transforms, 865–866 MaterialGroup, 859 Model3DGroup collections, 857, 858 shading and normals blending illumination, 854 cube with lighting artifacts, 852 front face of cube, 853 normal calculation, 854, 855 Positions collection, 854 smoothening blending, 855 visible faces of cube, 853 SpecularMaterial, 859 texture mapping (see Texture mapping) 3-D cube creation, 851 3-D objects (see 3-D objects) 1072

3-D performance, 869–870 2-D elements on 3-D surfaces, 875–878 Viewport3D class, 838 3-D objects definition, 840 GeometryModel3D class material classes, 843 properties, 843 light sources adding to viewport, 845, 846 AmbientLight, 846 light classes, 844 lighting calculation, 844 white DirectionalLight, 844 MeshGeometry3D coordinate system, 841 Normals, 841 positions, 841 TextureCoordinates, 841 triangle definition, 840 TriangleIndices, 841 vs. 2-D drawing classes, 839 Three-dimensional surface, 828 TickBar element, 550 TickPlacement, 189 TileBrush, 336, 342 Tiled ImageBrush class, 332–335 TileMode enumeration, 334 TimeLine class, 404 Timeline class AccelerationRatio property, 404–405 AnimationTimeline, 403 DecelerationRatio property, 404–405 MediaTimeline, 403 properties, 404 RepeatBehavior property, 405–406 TimelineGroup, 403 Timeline.DesiredFrameRate attached property, 426 TimelineGroup class, 403 Timer-based animation, 391–392 TimeSpan object, 401 Timestamp property, 121 ToArray() method, 575 ToggleBold command, 916 ToggleButton, 160, 531, 533, 537 ToggleItalic command, 916 ToggleUnderline command, 916 ToList() method, 575 Toolbars Button, 793 CheckBox, 793, 794

n INDEX

ComboBox, 793 definition, 793 different controls, 794 HeaderedItemsControl, 794 LayoutTransform, 794 Orientation property, 794 overflow menu, 794–795 RadioButton, 793 Separator, 793 ToggleButton, 793 ToolBarTray, 795–796 ToolBarTray, 795–796 ToolTipClosing, 166 ToolTipDescription property, 805 ToolTipFooterDescription property, 805 ToolTipFooterImageSource property, 805 ToolTipFooterTitle property, 805 ToolTipImageSource property, 805 ToolTipOpening, 166 ToolTip property, 804–805, 887 ToolTipService class, 166–167 ToolTipTitle property, 805 Top property, 82 To property, 399–400 ToString() method, 124, 154, 174 TouchDown event, 133 TouchEnter event, 134 TouchLeave event, 134 TouchMove event, 133 TouchUp event, 133 Transform3D class, 839 TransformGroup class, 338 Transform object, 309 Transforms, 865–866 classes, 337 definition, 337 elements, 341 Freezable, 338 shapes, 338 TranslateTransform class, 337 Transparency opacity masks, 344–346 semitransparent element, 342 semitransparent layers, 342–343 XAML contents, 343 TravelProductStyle, 622 TreeView, 571, 577, 665 controls, 686 data-binding categories and products, 679 DataSet, 682–683

hierarchical data, 679 HierarchicalDataTemplate, 681 ItemTemplate, 682 templates, 681 flexibility, 678 HeaderedItemsControl class, 678 just-in-time node creation directory-browsing application, 683 Expanded event, 685 FileSystemWatcher, 686 placeholder, 685 System.IO.DriveInfo class, 684 WPF’s implementation, 678 TreeView class, 614 TriggerAction, 815 Trigger class, 294 Trigger.EnterActions property, 297, 409 Trigger.ExitActions property, 297 Triggers Button.IsPressed property, 295 Conditions collection, 296 control template, 295 event trigger, 296–298 formatting, 296 FrameworkElement.Triggers collection, 294 keyboard focus button, 294 multiple triggers, 295 pre-trigger value, 295 Style.Triggers collection, 294 System.Windows.TriggerBase, 294 TriggerBase classes, 294 Trigger.Setters collection, 294 Triggers property, 286 TryFindResource() method, 275 TryParse() method, 606 Tunneling events firing sequence, 116 naming, 116 Tweak, 465 Two-step layout process ArrangeOverride() method, 543–544 MeasureOverride() method, 541–543 TwoWay binding, 580 txtMinPrice text box, 657 TypeArguments attribute, 764 TypeNameAnimation, 393 TypeNameAnimationBase class, 394 TypeNameAnimation classes, 394 TypeNameAnimationUsingKeyFrames class, 394 TypeNameAnimationUsingPath class, 394 Typography property, 148, 888 1073

n INDEX

nu UIElement.CacheMode property, 337 UIElement class, 341, 885 UIElement3D class, 871 UIElement.OnRender() method, 154 Underline element, 896 Undo command, 517 Uniform property, 827 UniformToFill property, 827 UnitCost property, 612 UnitsInStock property, 612 UnloadedBehavior property, 819, 820 Unlock() method, 390 UnmanagedMemoryStream object, 215 UpdateSourceTrigger.Explicit mode, 237 UpdateSourceTrigger property, 235 UpDirection property, 868 UpperLatin value, 891 UpperRoman value, 891 use PushOpacity() method, 371 User Account Control (UAC), 211–212 UserControl class, 507 UserControl.Name property, 515

nV Validate() method, 593 ValidateValueCallback, 99 Validation, data binding catching invalid values, 579–580 custom rules Binding.ValidationRules collection, 586 Decimal.Parse() method, 586 ErrorContent property, 586 ExceptionValidationRule, 587 IsValid property, 586 minimum and maximum decimal values, 585–586 NumberStyles enumeration, 586 pattern-based text data, 587 PositivePriceRule, 586, 587 System.Text.RegularExpressions.Regex class, 587 UpdateSourceTrigger property, 586 Validate() method, 585 ValidationResult object, 586 data object disadvantage, 581 ExceptionValidationRule, 580, 581

1074

INotifyDataError Interface (see INotifyDataError interface) Product.UnitPrice property, 580 StoreDB and Product classes, 581 UnitsInStock, 581 different error indicator AdornedElementPlaceholder, 590 adorner layer, 589 error templates, 589–591 TextBox control, 591 validation error message, 592 Validation.Errors property, 591 Validation.HasError, 591 error reaction, 587–588 list of errors, 588–589 multiple values BindingGroup.CommitEdit() method, 594, 595 BindingGroup.GetValue() method, 593 BindingGroup.Items collection, 593 BindingGroup.Name property, 593 binding groups, 592 DataContext set, 592 DataGrid control, 595 event handling code, 594 GetValue() method, 594 Grid.DataContext property, 592 item-level validation, 592, 594 NoBlankProductRule, 592, 593 Validate() method, 593 Validation.ErrorTemplate, 594 OneWayToSource binding, 580 TwoWay binding, 580 ValidationError.ErrorContent property, 588 ValidationErrorEventArgs.Error property, 587 ValidationError object, 587 Validation.ErrorTemplate property, 581 Validation.HasError property, 581 ValueChanged event, 826 ValueConversion attribute, 606 Value converter class, 605 Value converters, 603 background color change, 605 Binding.StringFormat property, 605 data formatting, 605 data templates, 632 Format and Parse binding events, 605 object creation Binding.DoNothing value, 610 BitmapImage class, 609 BitmapImage object, 608, 610

n INDEX

byte array, 608 ConvertBack() method, 610 Convert() method, 610 displaying bound images, 609 ImageDirectory, 608 ImageDirectory property, 609 ImagePathConverter, 608–609 ImagePathConverter class, 609 ImageSource object, 610 Image.Source property, 609 OpenFileDialog class, 610 ProductImage field, 608 StreamSource property, 608 System.Windows.Forms.Binding class, 608 System.Windows.Media.Imaging. BitmapImage object, 608 string formatting Binding.StringFormat property, 605 creation, 606 DataBinding namespace, 607 Decimal.ToString() method, 606 Parse() method, 606 PriceConverter class, 607 Product.UnitCost property, 606 Resources collection, 607 StaticResource reference, 607 System.Globalization.NumberStyles value, 606 ToString() method, 606 TryParse() method, 606 XML namespace prefix, 607 WPF object creation, 605 ValueInStockConverter, 612 vbproj .csproj file, 768 VerifyAccess() method, 985 VerticalAlignment property, 58, 156, 544 VerticalAnchor property, 901 VerticalContentAlignment property, 156 VerticalOffset property, 901 VerticalScrollBarVisibility property, 179, 778 Video effects Clipping property, 828 content control, 827 copying content, 828 intermediary rendering surface, 830 LayoutTransform property, 828 Opacity property, 828 reflection effect, 828–829 RenderTransform property, 828 resolutions and frame rates, 830

VideoDrawing class, 830 playing, 827 VideoDrawing class, 830 Viewbox property, 332 Viewbox.Stretch property, 315 View object CollectionView, 647 creation, 651–653 navigation brute-force approach, 651 ComboBox, 651 CurrentChanged event, 650 CurrentItem, 648 CurrentPosition, 648 data binding and triggers, 650 DataContext, 649 ICollectionView interface, 649 ItemsControl. IsSynchronizedWithCurrentItem, 651 ItemsSource property, 651 lookup list, 651 ModelName property, 651 previous and next buttons code, 650 record browser with drop-down list, 650 reference storage, 649 ObservableCollection class, 648 retrieval, 648 Viewport3D class, 838 Viewport property, 333, 872–873 ViewportUnits property, 333 Virtualization, 663 VirtualizingStackPanel, 577–579, 642 VirtualizingStackPanel. IsVirtualizingWhenGrouping property, 663 VirtualizingStackPanel.ScrollUnit property, 579 Virtual trackball, 870–871 Visibility property, 91, 125 VisualBrush class, 325, 335–336, 345, 346, 828, 830, 864–865, 876 VisualChildrenCount property, 372 Visual3D class, 396, 839 Visual layer, 308 Visuals complex compound shapes, 369 complex hit testing, 377–380 draw, 370–371 hit testing, 375–377 layer model, 370 wrapping, 372–375 VisualStateGroups element, 534 VisualStateGroup.Transitions collection, 535 1075

n INDEX

VisualStateManager element, 534, 536 VisualStateManager.VisualStateGroups element, 533 Visual states model, 503–504 Visual Studio data source, 575 VisualTransition element, 536 VisualTransition.GeneratedDuration property,536 VisualTreeHelper class, 377 VisualTreeHelper.HitTest() method,375, 377, 871 VRoutedEventArgs class, 107

nW WAV audio SoundPlayerAction class, 815 SoundPlayer class, 813–815 system sounds, 816 WebBrowser control, 747 DOM tree, 781–785 HTML web page, 779 page navigation, 780–781 scripting with .NET code, 784–786 vs. Frame, 780 web-style navigation, 779 WebBrowser.Document property,782 WebBrowser element, 341 WebBrowser.LoadCompleted event, 782 WebBrowser.NavigateToString() method,786 WebBrowser.ObjectForScripting property,784 WebClick() method, 785, 786 Width property, 59, 62, 901 Window class, 707 Close() and Hide() method, 710 properties, 707–708 ShowDialog() method, 709 Show() method, 709 WindowHeight property, 745 Window.Owner property,205 Window.Resources collection, 821, 989 Windows, 707 built-in dialog boxes, 716–717 custom control template, 725 AdornerDecorator element, 725 basic structure, 725 Border object, Grids, 726 code-behind approach, 728 problems, 727 window border and background, 727 dialog model, 715–716 interaction DoUpdate() method, 713 1076

MainWindow and Windows properties, 713 one-to-many, 714 ownership, 715 positioning, 710–711 saving and restoring location, 711–713 shaped window background image, 718 basic techniques, 717 border, 719 Grid, 720 nontransparent content, 718 transparent background, 721–722 with transparent regions, 718 Window.DragMove() method,723 Window.ResizeMode property,723–725 taskbar programming (see Taskbar programming) Windows Communication Foundation (WCF), 207 Windows Forms, 392, 793 adding Forms to WPF application, 968 adding WPF Windows to Windows Forms application, 968–969 enabling visual styles, 970–971 glue code, 968 interaction reasons, 967 interoperability access, 967–968 with mixed content access keys, mnemonics, and focus, 978–980 ActiveX content, 971 airspace, 971–973 component-specific wrapper, 971 interoperability layer, 971 property mapping, 980–982 System.Windows.Forms.Integration. ElementHost class, 976–978 WindowsFormsHost control, 976–978 and WPF user controls, 975–976 modal, 969 modeless, 969–970 WindowsFormsApplicationBase, 207, 208 WindowsFormsHost class, 980, 981, 982 WindowsFormsHost control, 976–978 WindowsFormsHost element, 341, 975 WindowsFormsHost. EnableWindowsFormsInterop() method, 970 Windows Forms layout controls, 976 Windows Forms toolkit, 968 Window.ShowDialog() method, 969 Windows Media Player, 816 Windows Presentation Foundation (WPF)

n INDEX

animations, 5 architecture class hierarchy, 12–15 Direct3D, 12 layers, 11 milcore dll, 11 PresentationCore.dll, 11 User32, 12 WindowsBase.dll, 11 WindowsCodecs.dll, 12 architecture PresentationFramework.dll, 11 audio and video media, 5 commands, 5 declarative user interface, 5 page-based applications, 6 resolution independence bitmap and vector graphics, 10 consumer monitors, 6 device-independent units, 6, 7 DPI setting, 6 higher pixel densities, 6 monitor resolution, 6 system DPI (see System DPI) rich drawing model, 5 rich text model, 5 styles and templates, 5 web-like layout model, 5 windows graphics DirectX, 4 GDI/GDI+, 3 hardware acceleration and WPF, 4 User32, 3 WPF 4.5, 15 toolkit, 16 visual studio 2012, 16, 17 WindowTitle property, 745, 747 Window.Unloaded event handler, 817 WindowWidth property, 745 Win32 interoperability, 982 WPF BeginInit() method, 119 controls (see Controls) ConvertToString() method, 124 custom elements (see Custom elements) dependency properties (see Dependency properties) direct events, definition of, 127 drag-and-drop operations, 130 EndInit() method, 119 focus, 125 focusable property, 125

FrameworkElement class and the ISupportInitialize interface, 119 handling a key press, 122 hiding or disabling a control, 125 HwndSource property, 120 InitializeComponent() method, 120 initialized event, 119 InnerException property, 120 InputDevice class, 121 input events, 118, 120 IsEnabled property, 125 IsInitialized property, 119 IsLoaded property, 119 IsRepeat property, 123 IsTabStop property, 125 Keyboard class, 126 KeyboardDevice class, 126 KeyboardDevice methods, table of, 126 KeyboardDevice property, 126 keyboard events, 118, 121 KeyDown event, 123 KeyEventArgs object, 123, 126 Key property, 123 KeyStates property, 126 lifetime events, 118 loaded event, 119 LostFocus event, 120 modifier keys, checking the status of, 126 mouse click events, 128 mouse coordinates, 127 MouseEventArgs object, 127 mouse events, 118 MouseMove event, 127 multithreading (see Multithreading) PasswordBox class, 506 PreviewKeyDown event, 124 PreviewMouseMove event, 127 PreviewTextInput event, 123, 124 rendering process for events, 119 stylus events, 118 TabIndex property, setting, 125 TextBox class, 505 TextBox control and the TextInput event, 122 TextCompositionEventArgs object, 123 TextInput event, 123 ToString() method, 124 using the KeyConverter, 124 virtual key state, 126 visibility property, 125 writing validation logic in a PreviewKeyDown event handler, 124 1077

n INDEX

WPF (cont.) XamlParseException object, 120 WPF 4.5, 787 WPF application accessing current application, 204 assembly resources (see Assembly resources) handling command-line arguments, 202–204 life cycle App.g.cs file, 197 App.xaml.cs file, 198 object creation, 195–196 shutdown, 198–199 StartupUri property, 197 TestApplication.App., 197 Visual Studio derives, 197 XAML document, 198 XBAPs, 195 localization (see Localization) single-instance (see Single-instance application) Splash Screen, 202 System.Windows.Application class, 195 windows interation, 205–207 WPF’s 2-D drawing model, 368 WPF visuals. See Visuals WrapBreakPanel, 545, 547 WrapDirection property, 901 WrapPanel, 641, 659 ArrangeOverride() method, 547 code example for a series of buttons, 66 controlling small-scale details controlling, 66 default settings, 65 FrameworkPropertyMetadata object, 545 LineBreakBefore property, 545–547 WrapBreakPanel, 545, 547 Wrapping visuals, 372–375 WrapWithOverflow, 179 WriteableBitmap class Bgra32 format, 385 Bgr32 format, 385 black-and-white and grayscale formats, 386 colorData array, 386 DPI resolution, 385 graphical content, 385 Image element, 385 indexed formats, 386 modifiable array pixels, 385 Pbgra32, 386 PixelFormats, 385 pixel writing, 387–390 WritePixels() method, 386 1078

WYSIWYG interface, 889

n X, y XAML browser applications (XBAPs), 195, 751 ClickOnce deployment, 767 creation, 768–769 deployment, 769–770 embedding, web page, 779 Firefox, 767 full-trust, 772 Internet Explorer, 767 limited permissions, 767 not installed on computer, 767 prompt-free experience, 767 run inside browser window, 767 security CodeAccessPermission.Demand() method, 773 code access security, 771 dialog boxes with pop-up control, 776–779 FileIOPermission, 774 isolated storage, 774–776 .NET Framework, 771 SecurityException, 772 unhandled exception, 772 WPF Features and Internet Zone, 771 and stand-alone applications combination, 773 updation, 770–771 XAML document, 989 XAML format, 915 XamlParseException object, 120, 278 XamlReader class, 911 XamlReader.Load() method, 913, 916 XamlWriter class, 912 .xbap extension, 768 XmlDataProvider, 595, 598–600 XML Paper Specification (XPS), 216 XmlStreamStore class, 922 XPath expressions, 599 XPS document, 920, 921 XpsDocument class, 920

nz ZAM 3D modeling, 866 Z-fighting, 850 z-index, 973 ZIndex property, 84 Zoom property, 907

Pro WPF 4.5 in C#

nnn Matthew MacDonald

Pro WPF 4.5 in C# Copyright © 2012 by Matthew MacDonald This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. Exempted from this legal reservation are brief excerpts in connection with reviews or scholarly analysis or material supplied specifically for the purpose of being entered and executed on a computer system, for exclusive use by the purchaser of the work. Duplication of this publication or parts thereof is permitted only under the provisions of the Copyright Law of the Publisher’s location, in its current version, and permission for use must always be obtained from Springer. Permissions for use may be obtained through RightsLink at the Copyright Clearance Center. Violations are liable to prosecution under the respective Copyright Law. ISBN-13 (pbk): 978-1-4302-4365-6 ISBN-13 (electronic): 978-1-4302-4366-3 Trademarked names, logos, and images may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, logo, or image, we use the names, logos, and images only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. The use in this publication of trade names, trademarks, service marks, and similar terms, even if they are not identified as such, is not to be taken as an expression of opinion as to whether or not they are subject to proprietary rights. While the advice and information in this book are believed to be true and accurate at the date of publication, neither the authors nor the editors nor the publisher can accept any legal responsibility for any errors or omissions that may be made. The publisher makes no warranty, express or implied, with respect to the material contained herein. President and Publisher: Paul Manning Lead Editor: Ewan Buckingham Technical Reviewer: Fabio Claudio Ferracchiati Editorial Board: Steve Anglin, Ewan Buckingham, Gary Cornell, Louise Corrigan, Morgan Ertel, Jonathan Gennick, Jonathan Hassell, Robert Hutchinson, Michelle Lowman, James Markham, Matthew Moodie, Jeff Olson, Jeffrey Pepper, Douglas Pundick, Ben Renow-Clarke, Dominic Shakeshaft, Gwenan Spearing, Matt Wade, Tom Welsh Coordinating Editor: Mark Powers Copy Editors: Sharon Wilkey and Linda Seifert Compositor: Bytheway Publishing Services Indexer: SPi Global Artist: SPi Global Cover Designer: Anna Ishchenko Distributed to the book trade worldwide by Springer Science+Business Media New York, 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail [email protected], or visit www. springeronline.com. For information on translations, please e-mail [email protected], or visit www.apress.com. Apress and friends of ED books may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also available for most titles. For more information, reference our Special Bulk Sales–eBook Licensing web page at www.apress.com/bulk-sales. Any source code or other supplementary materials referenced by the author in this text is available to readers at www. apress.com/9781430243656. For detailed information about how to locate your book’s source code, go to www.apress.com/ source-code.

ii

For my wonderful family, Faria, Maya, and Brenna

iii

Contents  About the Author ..............................................................................................................xxvi  About the Technical Reviewer.........................................................................................xxvii  Acknowledgments ..........................................................................................................xxviii  Introduction ......................................................................................................................xxix  Part I: Fundamentals .............................................................................................................1  Chapter 1: Introducing WPF ..................................................................................................3 The Evolution of Windows Graphics .......................................................................................3 DirectX: The New Graphics Engine .................................................................................................................4 Hardware Acceleration and WPF ....................................................................................................................4

WPF: A Higher-Level API .........................................................................................................5 Resolution Independence .......................................................................................................6 WPF Units .......................................................................................................................................................6 System DPI .....................................................................................................................................................7 Bitmap and Vector Graphics .........................................................................................................................10

The Architecture of WPF .......................................................................................................11 The Class Hierarchy ......................................................................................................................................12

WPF 4.5 ................................................................................................................................15 The WPF Toolkit ............................................................................................................................................16 Visual Studio 2012 ........................................................................................................................................16

The Last Word .......................................................................................................................18  Chapter 2: XAML ..................................................................................................................21 Understanding XAML ...........................................................................................................21 Graphical User Interfaces Before WPF ..........................................................................................................22 vi

n CONTENTS

The Variants of XAML ....................................................................................................................................23 XAML Compilation ........................................................................................................................................23

XAML Basics .........................................................................................................................24 XAML Namespaces .......................................................................................................................................25 The Code-Behind Class.................................................................................................................................26

Properties and Events in XAML ............................................................................................29 Simple Properties and Type Converters ........................................................................................................30 Complex Properties ......................................................................................................................................31 Markup Extensions .......................................................................................................................................33 Attached Properties ......................................................................................................................................34 Nesting Elements..........................................................................................................................................35 Special Characters and Whitespace .............................................................................................................38 Events ...........................................................................................................................................................39 The Full Eight-Ball Example ..........................................................................................................................40

Using Types from Other Namespaces ...................................................................................41 Loading and Compiling XAML ...............................................................................................43 Code-Only .....................................................................................................................................................44 Code and Uncompiled XAML.........................................................................................................................46 Code and Compiled XAML.............................................................................................................................48 XAML Only ....................................................................................................................................................50

The Last Word .......................................................................................................................51  Chapter 3: Layout ................................................................................................................53 Understanding Layout in WPF...............................................................................................53 The WPF Layout Philosophy ..........................................................................................................................53 The Layout Process ......................................................................................................................................54 The Layout Containers ..................................................................................................................................55

Simple Layout with the StackPanel ......................................................................................56 Layout Properties..........................................................................................................................................58 Alignment .....................................................................................................................................................59 Margin ..........................................................................................................................................................60 Minimum, Maximum, and Explicit Sizes .......................................................................................................62 The Border ....................................................................................................................................................64

The WrapPanel and DockPanel .............................................................................................65 The WrapPanel..............................................................................................................................................65 The DockPanel ..............................................................................................................................................66 Nesting Layout Containers............................................................................................................................68 vii

The Grid ................................................................................................................................70 Fine-Tuning Rows and Columns ...................................................................................................................72 Layout Rounding ...........................................................................................................................................74 Spanning Rows and Columns .......................................................................................................................75 Splitting Windows .........................................................................................................................................76 Shared Size Groups ......................................................................................................................................79 The UniformGrid............................................................................................................................................82

Coordinate-Based Layout with the Canvas...........................................................................82 Z-Order .........................................................................................................................................................84 The InkCanvas ..............................................................................................................................................84

Layout Examples ..................................................................................................................86 A Column of Settings ....................................................................................................................................87 Dynamic Content ..........................................................................................................................................88 A Modular User Interface ..............................................................................................................................90

The Last Word .......................................................................................................................92  Chapter 4: Dependency Properties .....................................................................................93 Understanding Dependency Properties ................................................................................93 Defining a Dependency Property ..................................................................................................................94 Registering a Dependency Property .............................................................................................................94 Adding a Property Wrapper...........................................................................................................................96 How WPF Uses Dependency Properties ........................................................................................................97 Shared Dependency Properties ....................................................................................................................99 Attached Dependency Properties .................................................................................................................99

Property Validation .............................................................................................................100 The Validation Callback...............................................................................................................................101 The Coercion Callback ................................................................................................................................102

The Last Word .....................................................................................................................104  Chapter 5: Routed Events ..................................................................................................105 Understanding Routed Events ............................................................................................105 Defining, Registering, and Wrapping a Routed Event .................................................................................105 Sharing Routed Events ...............................................................................................................................106 Raising a Routed Event ...............................................................................................................................107 Handling a Routed Event ............................................................................................................................107

Event Routing .....................................................................................................................109 The RoutedEventArgs Class ........................................................................................................................110 Bubbling Events ..........................................................................................................................................111 viii

n CONTENTS

Handling a Suppressed Event .....................................................................................................................114 Attached Events ..........................................................................................................................................114 Tunneling Events ........................................................................................................................................116

WPF Events.........................................................................................................................118 Lifetime Events ...........................................................................................................................................118 Input Events ................................................................................................................................................120

Keyboard Input ...................................................................................................................121 Handling a Key Press ..................................................................................................................................122 Focus ..........................................................................................................................................................125 Getting Key State ........................................................................................................................................126

Mouse Input........................................................................................................................127 Mouse Clicks ..............................................................................................................................................128 Capturing the Mouse ..................................................................................................................................129 Drag-and-Drop............................................................................................................................................130

Multitouch Input .................................................................................................................132 The Levels of Multitouch Support ...............................................................................................................133 Raw Touch ..................................................................................................................................................133 Manipulation ...............................................................................................................................................136 Inertia .........................................................................................................................................................139

The Last Word .....................................................................................................................140  Part II: Deeper Into WPF ....................................................................................................141  Chapter 6: Controls ............................................................................................................143 The Control Class................................................................................................................144 Background and Foreground Brushes ........................................................................................................144 Fonts ...........................................................................................................................................................146 Mouse Cursors............................................................................................................................................151

Content Controls .................................................................................................................152 The Content Property ..................................................................................................................................154 Aligning Content .........................................................................................................................................156 The WPF Content Philosophy ......................................................................................................................157 Labels .........................................................................................................................................................158 Buttons .......................................................................................................................................................159 Tooltips .......................................................................................................................................................162

Specialized Containers .......................................................................................................169 The ScrollViewer.........................................................................................................................................170 The GroupBox .............................................................................................................................................173 ix

n CONTENTS

The TabItem ................................................................................................................................................173 The Expander ..............................................................................................................................................175

Text Controls .......................................................................................................................179 Multiple Lines of Text ..................................................................................................................................179 Text Selection .............................................................................................................................................180 Spell Checking ............................................................................................................................................181 The PasswordBox .......................................................................................................................................183

List Controls .......................................................................................................................183 The ListBox .................................................................................................................................................184 The ComboBox............................................................................................................................................187

Range-Based Controls ........................................................................................................188 The Slider ...................................................................................................................................................188 The ProgressBar .........................................................................................................................................190

Date Controls ......................................................................................................................190 The Last Word .....................................................................................................................193  Chapter 7: The Application ................................................................................................195 The Application Life Cycle ..................................................................................................195 Creating an Application Object ...................................................................................................................195 Deriving a Custom Application Class ..........................................................................................................197 Application Shutdown.................................................................................................................................198 Application Events ......................................................................................................................................199

Application Tasks ................................................................................................................201 Showing a Splash Screen ...........................................................................................................................202 Handling Command-Line Arguments ..........................................................................................................202 Accessing the Current Application..............................................................................................................204 Interacting Between Windows ....................................................................................................................205 Single-Instance Applications ......................................................................................................................207

Assembly Resources ..........................................................................................................212 Adding Resources .......................................................................................................................................213 Retrieving Resources..................................................................................................................................214 Resource-Aware Classes ............................................................................................................................215 Pack URIs....................................................................................................................................................216 Resources in Other Assemblies ..................................................................................................................216 Content Files ...............................................................................................................................................217

Localization ........................................................................................................................218 Building Localizable User Interfaces ..........................................................................................................218 x

n CONTENTS

Preparing an Application for Localization ...................................................................................................219 Managing the Translation Process..............................................................................................................220

The Last Word .....................................................................................................................226  Chapter 8: Element Binding ..............................................................................................227 Binding Elements Together .................................................................................................227 The Binding Expression ..............................................................................................................................228 Binding Errors .............................................................................................................................................229 Binding Modes ............................................................................................................................................229 Creating Bindings with Code ......................................................................................................................232 Retrieving Bindings in Code........................................................................................................................233 Multiple Bindings ........................................................................................................................................234 Binding Updates .........................................................................................................................................237 Binding Delays ............................................................................................................................................238

Binding to Objects That Aren’t Elements ............................................................................238 Source ........................................................................................................................................................239 RelativeSource............................................................................................................................................239 DataContext ................................................................................................................................................241

The Last Word .....................................................................................................................242  Chapter 9: Commands .......................................................................................................243 Understanding Commands .................................................................................................243 The WPF Command Model..................................................................................................245 The ICommand Interface ............................................................................................................................245 The RoutedCommand Class........................................................................................................................246 The RoutedUICommand Class ....................................................................................................................247 The Command Library ................................................................................................................................247

Executing Commands .........................................................................................................249 Command Sources .....................................................................................................................................249 Command Bindings ....................................................................................................................................249 Using Multiple Command Sources..............................................................................................................252 Fine-Tuning Command Text ........................................................................................................................253 Invoking a Command Directly .....................................................................................................................254 Disabling Commands ..................................................................................................................................255 Controls with Built-in Commands ...............................................................................................................257

Advanced Commands .........................................................................................................259 Custom Commands ....................................................................................................................................259 Using the Same Command in Different Places ...........................................................................................260 xi

Using a Command Parameter .....................................................................................................................262 Tracking and Reversing Commands ...........................................................................................................263

The Last Word .....................................................................................................................268  Chapter 10: Resources ......................................................................................................269 Resource Basics .................................................................................................................269 The Resources Collection ...........................................................................................................................270 The Hierarchy of Resources........................................................................................................................271 Static and Dynamic Resources ...................................................................................................................272 Nonshared Resources.................................................................................................................................274 Accessing Resources in Code.....................................................................................................................275 Application Resources ................................................................................................................................275 System Resources ......................................................................................................................................276

Resource Dictionaries ........................................................................................................277 Creating a Resource Dictionary ..................................................................................................................277 Using a Resource Dictionary.......................................................................................................................278 Sharing Resources Between Assemblies ...................................................................................................279

The Last Word .....................................................................................................................282  Chapter 11: Styles and Behaviors .....................................................................................283 Style Basics ........................................................................................................................283 Creating a Style Object ...............................................................................................................................287 Setting Properties .......................................................................................................................................288 Attaching Event Handlers ...........................................................................................................................289 The Many Layers of Styles..........................................................................................................................291 Automatically Applying Styles by Type........................................................................................................292

Triggers ..............................................................................................................................294 A Simple Trigger .........................................................................................................................................294 An Event Trigger..........................................................................................................................................296

Behaviors............................................................................................................................298 Getting Support for Behaviors ....................................................................................................................298 Understanding the Behavior Model ............................................................................................................299 Creating a Behavior ....................................................................................................................................300 Using a Behavior.........................................................................................................................................302 Design-Time Behavior Support in Blend .....................................................................................................303

The Last Word .....................................................................................................................304  Part III: Drawing and Animation........................................................................................305  Chapter 12: Shapes, Brushes, and Transforms ................................................................307 xii

n CONTENTS

Understanding Shapes .......................................................................................................307 The Shape Classes .....................................................................................................................................308 Rectangle and Ellipse .................................................................................................................................310 Sizing and Placing Shapes .........................................................................................................................311 Scaling Shapes with a Viewbox ..................................................................................................................313 Line .............................................................................................................................................................316 Polyline .......................................................................................................................................................317 Polygon .......................................................................................................................................................318 Line Caps and Line Joins ............................................................................................................................320 Dashes ........................................................................................................................................................322 Pixel Snapping ............................................................................................................................................324

Using Brushes ....................................................................................................................325 The SolidColorBrush ...................................................................................................................................326 The LinearGradientBrush ............................................................................................................................326 The RadialGradientBrush ............................................................................................................................329 The ImageBrush .........................................................................................................................................331 A Tiled ImageBrush .....................................................................................................................................332 The VisualBrush ..........................................................................................................................................335 The BitmapCacheBrush ..............................................................................................................................336

Using Transforms ................................................................................................................337 Transforming Shapes..................................................................................................................................339 Transforming Elements...............................................................................................................................341

Transparency ......................................................................................................................342 Making an Element Partially Transparent ...................................................................................................342 Using Opacity Masks ..................................................................................................................................344

The Last Word .....................................................................................................................346  Chapter 13: Geometries and Drawings .............................................................................347 Paths and Geometries ........................................................................................................347 Line, Rectangle, and Ellipse Geometries .....................................................................................................348 Combining Shapes with GeometryGroup ....................................................................................................349 Fusing Geometries with CombinedGeometry .............................................................................................351 Drawing Curves and Lines with PathGeometry ..........................................................................................354 Using the Geometry Mini-Language ...........................................................................................................359 Clipping with Geometry ..............................................................................................................................361

Drawings ............................................................................................................................363 Displaying a Drawing ..................................................................................................................................364 xiii

Exporting Clip Art ........................................................................................................................................367

The Last Word .....................................................................................................................368  Chapter 14: Effects and Visuals ........................................................................................369 Visuals ................................................................................................................................369 Drawing Visuals ..........................................................................................................................................370 Wrapping Visuals in an Element .................................................................................................................372 Hit Testing ...................................................................................................................................................375 Complex Hit Testing ....................................................................................................................................377

Effects ................................................................................................................................380 BlurEffect ....................................................................................................................................................381 DropShadowEffect ......................................................................................................................................382 ShaderEffect ...............................................................................................................................................383

The WriteableBitmap Class.................................................................................................385 Generating a Bitmap ...................................................................................................................................385 Writing to a WriteableBitmap ......................................................................................................................386 Using More-Efficient Pixel Writing ..............................................................................................................387

The Last Word .....................................................................................................................390  Chapter 15: Animation Basics...........................................................................................391 Understanding WPF Animation ...........................................................................................391 Timer-Based Animation ..............................................................................................................................391 Property-Based Animation ..........................................................................................................................392

Basic Animation ..................................................................................................................393 The Animation Classes ...............................................................................................................................393 Animations in Code .....................................................................................................................................396 Simultaneous Animations ...........................................................................................................................401 Animation Lifetime .....................................................................................................................................401 The Timeline Class ......................................................................................................................................403

Storyboards ........................................................................................................................406 The Storyboard ...........................................................................................................................................406 Event Triggers .............................................................................................................................................407 Overlapping Animations ..............................................................................................................................409 Synchronized Animations ...........................................................................................................................410 Controlling Playback ...................................................................................................................................411 Monitoring Progress ...................................................................................................................................415

Animation Easing................................................................................................................417 Using an Easing Function ...........................................................................................................................418 xiv

n CONTENTS

Easing In and Easing Out ............................................................................................................................419 Easing Function Classes .............................................................................................................................420 Creating a Custom Easing Function............................................................................................................423

Animation Performance ......................................................................................................425 Desired Frame Rate ....................................................................................................................................425 Bitmap Caching ..........................................................................................................................................428

The Last Word .....................................................................................................................430  Chapter 16: Advanced Animation .....................................................................................431 Animation Types Revisited..................................................................................................431 Animating Transforms.................................................................................................................................432 Animating Brushes .....................................................................................................................................437 Animating Pixel Shaders.............................................................................................................................440

Key-Frame Animation .........................................................................................................442 Discrete Key-Frame Animations .................................................................................................................443 Easing Key Frames .....................................................................................................................................444 Spline Key-Frame Animations ....................................................................................................................444

Path-Based Animation ........................................................................................................446 Frame-Based Animation .....................................................................................................448 Storyboards in Code ...........................................................................................................452 Creating the Main Window..........................................................................................................................453 Creating the Bomb User Control .................................................................................................................454 Dropping the Bombs ...................................................................................................................................455 Intercepting a Bomb ...................................................................................................................................458 Counting Bombs and Cleaning Up ..............................................................................................................460

The Last Word .....................................................................................................................462  Part IV: Templates and Custom Elements .........................................................................463  Chapter 17: Control Templates..........................................................................................465 Understanding Logical Trees and Visual Trees....................................................................465 Understanding Templates ...................................................................................................471 The Chrome Classes ...................................................................................................................................473 Dissecting Controls .....................................................................................................................................474

Creating Control Templates ................................................................................................477 A Simple Button ..........................................................................................................................................478 Template Bindings ......................................................................................................................................479 Triggers That Change Properties ................................................................................................................481 xv

Triggers That Use Animation .......................................................................................................................484

Organizing Template Resources .........................................................................................485 Refactoring the Button Control Template ....................................................................................................486 Applying Templates with Styles ..................................................................................................................488 Applying Templates Automatically ..............................................................................................................490 Working with User-Selected Skins .............................................................................................................491

Building More Complex Templates .....................................................................................493 Nested Templates .......................................................................................................................................494 Modifying the Scrollbar ..............................................................................................................................496 Exploring the Control Template Examples ..................................................................................................501

Visual States .......................................................................................................................503 The Last Word .....................................................................................................................504  Chapter 18: Custom Elements ...........................................................................................505 Understanding Custom Elements in WPF ...........................................................................506 Building a Basic User Control .............................................................................................508 Defining Dependency Properties ................................................................................................................509 Defining Routed Events ..............................................................................................................................512 Adding Markup ...........................................................................................................................................513 Using the Control ........................................................................................................................................515 Supporting Commands ...............................................................................................................................516 Taking a Closer Look at User Controls ........................................................................................................518

Creating a Lookless Control................................................................................................520 Refactoring the Color Picker Code ..............................................................................................................520 Refactoring the Color Picker Markup..........................................................................................................520 Streamlining the Control Template .............................................................................................................524

Supporting Visual States ....................................................................................................527 Starting the FlipPanel Class........................................................................................................................528 Choosing Parts and States .........................................................................................................................530 The Default Control Template .....................................................................................................................532 Using the FlipPanel .....................................................................................................................................538 Using a Different Control Template .............................................................................................................539

Creating Custom Panels .....................................................................................................540 The Two-Step Layout Process ....................................................................................................................541 The Canvas Clone .......................................................................................................................................544 A Better Wrapping Panel .............................................................................................................................545

Custom-Drawn Elements....................................................................................................548 xvi

n CONTENTS

The OnRender() Method ..............................................................................................................................548 Evaluating Custom Drawing .......................................................................................................................550 Creating a Custom-Drawn Element ............................................................................................................551 Creating a Custom Decorator .....................................................................................................................553

The Last Word .....................................................................................................................554  Part V: Data ........................................................................................................................555  Chapter 19: Data Binding ..................................................................................................557 Binding to a Database with Custom Objects ......................................................................557 Building a Data Access Component ............................................................................................................558 Building a Data Object ................................................................................................................................561 Displaying the Bound Object.......................................................................................................................562 Updating the Database ...............................................................................................................................564 Change Notification ....................................................................................................................................565

Binding to a Collection of Objects ......................................................................................566 Displaying and Editing Collection Items .....................................................................................................567 Inserting and Removing Collection Items ...................................................................................................572 Binding to the ADO.NET Objects .................................................................................................................572 Binding to a LINQ Expression......................................................................................................................574

Improving Performance in Long Lists .................................................................................576 Virtualization ...............................................................................................................................................576 Item Container Recycling ............................................................................................................................578 Cache Length ..............................................................................................................................................578 Deferred Scrolling.......................................................................................................................................579

Validation ............................................................................................................................579 Validation in the Data Object ......................................................................................................................580 Custom Validation Rules .............................................................................................................................585 Reacting to Validation Errors ......................................................................................................................587 Getting a List of Errors ................................................................................................................................588 Showing a DifferenError Indicator ..............................................................................................................589 Validating Multiple Values...........................................................................................................................592

Data Providers ....................................................................................................................595 The ObjectDataProvider ..............................................................................................................................596 The XmlDataProvider ..................................................................................................................................598

The Last Word .....................................................................................................................600  Chapter 20: Formatting Bound Data .................................................................................601 Data Binding Redux ............................................................................................................601 xvii

Data Conversion .................................................................................................................603 Using the StringFormat Property ................................................................................................................603 Introducing Value Converters......................................................................................................................605 Formatting Strings with a Value Converter .................................................................................................605 Creating Objects with a Value Converter ....................................................................................................608 Applying Conditional Formatting ................................................................................................................610 Evaluating Multiple Properties ....................................................................................................................612

List Controls .......................................................................................................................613 List Styles ...........................................................................................................................615 The ItemContainerStyle ..............................................................................................................................615 A ListBox with Check Boxes or Radio Buttons ............................................................................................617 Alternating Item Style .................................................................................................................................620 Style Selectors............................................................................................................................................622

Data Templates ...................................................................................................................625 Separating and Reusing Templates ............................................................................................................628 Using More Advanced Templates ................................................................................................................629 Varying Templates ......................................................................................................................................632 Template Selectors .....................................................................................................................................633 Templates and Selection ............................................................................................................................636 Changing Item Layout .................................................................................................................................641

The ComboBox ...................................................................................................................642 The Last Word .....................................................................................................................645  Chapter 21: Data Views .....................................................................................................647 The View Object ..................................................................................................................647 Retrieving a View Object .............................................................................................................................648 Navigating with a View ...............................................................................................................................648 Creating a View Declaratively .....................................................................................................................651

Filtering, Sorting, and Grouping ..........................................................................................653 Filtering Collections ....................................................................................................................................653 Filtering the DataTable................................................................................................................................656 Sorting ........................................................................................................................................................657 Grouping .....................................................................................................................................................658 Live Shaping ...............................................................................................................................................663

The Last Word .....................................................................................................................664  Chapter 22: Lists, Trees, and Grids ...................................................................................665 The ListView .......................................................................................................................665 xviii

n CONTENTS

Creating Columns with the GridView ..........................................................................................................666 Creating a Custom View..............................................................................................................................670

The TreeView ......................................................................................................................678 Creating a Data-Bound TreeView ................................................................................................................679 Binding a DataSet to a TreeView.................................................................................................................682 Just-in-Time Node Creation........................................................................................................................683

The DataGrid .......................................................................................................................686 Resizing and Rearranging Columns............................................................................................................688 Defining Columns .......................................................................................................................................689 Formatting and Styling Columns ................................................................................................................694 Formatting Rows ........................................................................................................................................696 Displaying Row Details ...............................................................................................................................698 Freezing Columns .......................................................................................................................................699 Selection .....................................................................................................................................................700 Sorting ........................................................................................................................................................700 Editing.........................................................................................................................................................700

The Last Word .....................................................................................................................703  Part VI: Windows, Pages, and Rich Controls ....................................................................705  Chapter 23: Windows ........................................................................................................707 The Window Class ..............................................................................................................707 Showing a Window .....................................................................................................................................709 Positioning a Window .................................................................................................................................710 Saving and Restoring Window Location .....................................................................................................711

Window Interaction ............................................................................................................713 Window Ownership.....................................................................................................................................715 The Dialog Model ........................................................................................................................................715 Common Dialog Boxes ................................................................................................................................716

Nonrectangular Windows ...................................................................................................717 A Simple Shaped Window ...........................................................................................................................717 A Transparent Window with Shaped Content..............................................................................................721 Moving Shaped Windows ...........................................................................................................................723 Resizing Shaped Windows..........................................................................................................................723 Putting It All Together: A Custom Control Template for Windows ................................................................725

Programming the Windows Taskbar ...................................................................................728 Using Jump Lists ........................................................................................................................................729 Changing the Taskbar Icon and Preview.....................................................................................................734 xix

n CONTENTS

The Last Word .....................................................................................................................738  Chapter 24: Pages and Navigation ....................................................................................741 Page-Based Navigation ......................................................................................................741 Page-Based Interfaces .......................................................................................................742 Creating a Simple Page-Based Application with NavigationWindow..........................................................743 The Page Class ...........................................................................................................................................744 Hyperlinks...................................................................................................................................................745 Hosting Pages in a Frame ...........................................................................................................................748 Hosting Pages in Another Page ..................................................................................................................750 Hosting Pages in a Web Browser ................................................................................................................751

The Page History ................................................................................................................752 A Closer Look at URIs in WPF......................................................................................................................752 Navigation History ......................................................................................................................................753 Maintaining Custom Properties ..................................................................................................................754

The Navigation Service.......................................................................................................755 Programmatic Navigation ...........................................................................................................................755 Navigation Events .......................................................................................................................................756 Managing the Journal.................................................................................................................................758 Adding Custom Items to the Journal ..........................................................................................................759 Using Page Functions .................................................................................................................................764

XAML Browser Applications................................................................................................767 Creating an XBAP........................................................................................................................................768 Deploying an XBAP .....................................................................................................................................769 Updating an XBAP .......................................................................................................................................770 XBAP Security .............................................................................................................................................771 Full-Trust XBAPs .........................................................................................................................................772 Combination XBAP/Stand-Alone Applications.............................................................................................773 Coding for Different Security Levels ...........................................................................................................773 Embedding an XBAP in a Web Page ...........................................................................................................779

The WebBrowser Control ....................................................................................................779 Navigating to a Page...................................................................................................................................780 Building a DOM Tree ...................................................................................................................................781 Scripting a Web Page with .NET Code ........................................................................................................784

The Last Word .....................................................................................................................786  Chapter 25: Menus, Toolbars, and Ribbons ......................................................................787 Menus.................................................................................................................................787 xx

n CONTENTS

The Menu Class ..........................................................................................................................................788 Menu Items .................................................................................................................................................789 The ContextMenu Class ..............................................................................................................................791 Menu Separators ........................................................................................................................................792

Toolbars and Status Bars....................................................................................................793 The ToolBar .................................................................................................................................................793 The StatusBar .............................................................................................................................................796

Ribbons ..............................................................................................................................797 Adding the Ribbon ......................................................................................................................................798 The Application Menu .................................................................................................................................800 Tabs, Groups, and Buttons ..........................................................................................................................802 Rich Tooltips ...............................................................................................................................................804 Keyboard Access with KeyTips ...................................................................................................................806 Ribbon Sizing ..............................................................................................................................................807 The Quick Access Toolbar ...........................................................................................................................809

The Last Word .....................................................................................................................811  Chapter 26: Sound and Video ............................................................................................813 Playing WAV Audio ..............................................................................................................813 The SoundPlayer.........................................................................................................................................813 The SoundPlayerAction...............................................................................................................................815 System Sounds ...........................................................................................................................................816

The MediaPlayer .................................................................................................................816 The MediaElement ..............................................................................................................818 Playing Audio Programmatically .................................................................................................................818 Handling Errors ...........................................................................................................................................819 Playing Audio with Triggers ........................................................................................................................820 Playing Multiple Sounds .............................................................................................................................822 Changing Volume, Balance, Speed, and Position ........................................................................................823 Synchronizing an Animation with Audio .....................................................................................................825 Playing Video ..............................................................................................................................................827 Video Effects ...............................................................................................................................................827

Speech................................................................................................................................831 Speech Synthesis .......................................................................................................................................831 Speech Recognition ....................................................................................................................................833

The Last Word .....................................................................................................................835

xxi

 Chapter 27: 3-D Drawing...................................................................................................837 3-D Drawing Basics ............................................................................................................837 The Viewport ...............................................................................................................................................838 3-D Objects .................................................................................................................................................838 The Camera ................................................................................................................................................846

Deeper into 3-D ..................................................................................................................850 Shading and Normals .................................................................................................................................852 More Complex Shapes ................................................................................................................................856 Model3DGroup Collections .........................................................................................................................857 Materials Revisited .....................................................................................................................................858 Texture Mapping .........................................................................................................................................861

Interactivity and Animations ...............................................................................................865 Transforms..................................................................................................................................................865 Rotations ....................................................................................................................................................866 A Flyover .....................................................................................................................................................868 The Trackball ..............................................................................................................................................870 Hit Testing ...................................................................................................................................................871 2-D Elements on 3-D Surfaces ...................................................................................................................875

The Last Word .....................................................................................................................878  Part VII: Documents and Printing......................................................................................881  Chapter 28: Documents .....................................................................................................883 Understanding Documents .................................................................................................883 Flow Documents .................................................................................................................884 Understanding Flow Elements ....................................................................................................................885 Formatting Content Elements .....................................................................................................................887 Constructing a Simple Flow Document ......................................................................................................888 Using Block Elements .................................................................................................................................890 Using Inline Elements .................................................................................................................................895 Interacting with Elements Programmatically .............................................................................................902 Text Justification ........................................................................................................................................905

Read-Only Flow Document Containers ...............................................................................906 Zooming ......................................................................................................................................................907 Creating Pages and Columns......................................................................................................................908 Loading Documents from a File ..................................................................................................................911 Printing .......................................................................................................................................................912

Editing a Flow Document....................................................................................................912 xxii

n CONTENTS

Loading a File .............................................................................................................................................913 Saving a File ...............................................................................................................................................915 Formatting Selected Text ............................................................................................................................916 Getting Individual Words .............................................................................................................................919

Fixed Documents ................................................................................................................920 Annotations ........................................................................................................................921 Understanding the Annotation Classes .......................................................................................................922 Enabling the Annotation Service.................................................................................................................923 Creating Annotations ..................................................................................................................................924 Examining Annotations ...............................................................................................................................928 Reacting to Annotation Changes.................................................................................................................931 Storing Annotations in a Fixed Document...................................................................................................931 Customizing the Appearance of Sticky Notes .............................................................................................932

The Last Word .....................................................................................................................933  Chapter 29: Printing ..........................................................................................................935 Basic Printing .....................................................................................................................935 Printing an Element ....................................................................................................................................936 Transforming Printed Output ......................................................................................................................938 Printing Elements Without Showing Them..................................................................................................941 Printing a Document ...................................................................................................................................942 Manipulating the Pages in a Document Printout ........................................................................................945

Custom Printing ..................................................................................................................948 Printing with the Visual Layer Classes........................................................................................................948 Custom Printing with Multiple Pages .........................................................................................................951

Print Settings and Management .........................................................................................956 Maintaining Print Settings ..........................................................................................................................956 Printing Page Ranges .................................................................................................................................957 Managing a Print Queue .............................................................................................................................957

Printing Through XPS..........................................................................................................960 Creating an XPS Document for a Print Preview ..........................................................................................961 Writing to an In-Memory XPS Document ....................................................................................................962 Printing Directly to the Printer via XPS .......................................................................................................962 Asynchronous Printing................................................................................................................................963

The Last Word .....................................................................................................................964  Part VIII: Additional Topics ................................................................................................965 xxiii

 Chapter 30: Interacting with Windows Forms ..................................................................967 Assessing Interoperability ..................................................................................................967 Mixing Windows and Forms ...............................................................................................968 Adding Forms to a WPF Application ............................................................................................................968 Adding WPF Windows to a Windows Forms Application .............................................................................968 Showing Modal Windows and Forms .........................................................................................................969 Showing Modeless Windows and Forms ....................................................................................................969 Enabling Visual Styles for Windows Forms Controls...................................................................................970

Creating Windows with Mixed Content...............................................................................971 Understanding WPF and Windows Forms “Airspace” .................................................................................971 Hosting Windows Forms Controls in WPF ...................................................................................................973 Using WPF and Windows Forms User Controls ...........................................................................................975 Hosting WPF Controls in Windows Forms ...................................................................................................976 Access Keys, Mnemonics, and Focus .........................................................................................................978 Property Mapping .......................................................................................................................................980

The Last Word .....................................................................................................................982  Chapter 31: Multithreading ...............................................................................................983 Understanding the Multithreading Model ...........................................................................983 The Dispatcher ...........................................................................................................................................984 The DispatcherObject .................................................................................................................................984

The BackgroundWorker ......................................................................................................987 A Simple Asynchronous Operation..............................................................................................................987 Creating the BackgroundWorker ................................................................................................................989 Running the BackgroundWorker.................................................................................................................990 Tracking Progress .......................................................................................................................................992 Supporting Cancellation .............................................................................................................................993

The Last Word .....................................................................................................................995  Chapter 32: The Add-in Model ..........................................................................................997 Choosing Between MAF and MEF.......................................................................................997 Understanding the Add-in Pipeline .....................................................................................998 How the Pipeline Works ..............................................................................................................................999 The Add-in Folder Structure .....................................................................................................................1001 Preparing a Solution That Uses the Add-in Model ....................................................................................1002

Creating an Application That Uses Add-Ins.......................................................................1004 The Contract .............................................................................................................................................1004 xxiv

n CONTENTS

The Add-in View ........................................................................................................................................1005 The Add-In ................................................................................................................................................1005 The Add-in Adapter ...................................................................................................................................1006 The Host View ...........................................................................................................................................1007 The Host Adapter ......................................................................................................................................1008 The Host ...................................................................................................................................................1008 More Add-Ins ............................................................................................................................................1011

Interacting with the Host ..................................................................................................1012 Visual Add-Ins...................................................................................................................1017 The Last Word ...................................................................................................................1019  Chapter 33: ClickOnce Deployment ................................................................................1021 Understanding Application Deployment ...........................................................................1021 The ClickOnce Installation Model .............................................................................................................1023 ClickOnce Limitations ...............................................................................................................................1024

Setting Up a Simple ClickOnce Publication ......................................................................1024 Setting the Publisher and Production .......................................................................................................1025 Starting the Publish Wizard ......................................................................................................................1027 Understanding the Deployed File Structure ..............................................................................................1032 Installing a ClickOnce Application.............................................................................................................1033 Updating a ClickOnce Application .............................................................................................................1034

Additional ClickOnce Options ...........................................................................................1035 Publish Version .........................................................................................................................................1035 Updates.....................................................................................................................................................1036 File Associations .......................................................................................................................................1037 Publish Options .........................................................................................................................................1039

The Last Word ...................................................................................................................1040  Index ................................................................................................................................1041

xxv

About the Author  Matthew MacDonald is an author, educator, and three-time Microsoft

MVP. He’s the author of more than a dozen books about .NET programming, including Pro Silverlight 5 in C# (Apress, 2012) and Beginning ASP.NET 4.5 in C# (Apress, 2012). He’s also the author of Your Brain: The Missing Manual (O’Reilly Media, 2008), a popular look at getting the most from your squishy gray matter. Matthew lives in Toronto with his wife and two daughters.

xxvi

About the Technical Reviewer  Fabio Claudio Ferracchiati, a prolific writer on cutting-edge technologies, has contributed to more than a dozen books on .NET, C#, Visual Basic, and ASP.NET. He is a .NET Microsoft Certified Solution Developer and lives in Milan, Italy. You can read his blog at Ferracchiati.com.

xxvii

Acknowledgments No author can complete a book without a small army of helpful individuals. I’m deeply indebted to the whole Apress team, including Mark Powers, who shepherded this edition through production; Sharon Wilkey and Linda Seifert, who speedily performed the copy edit; and many other individuals who worked behind the scenes indexing pages, drawing figures, and proofreading the final copy. Fabio Claudio Ferracchiati deserves my sincere thanks for his insightful and timely tech review comments. I’m also thankful for the legions of die-hard bloggers on the various WPF teams, who never fail to shed light on the deepest recesses of WPF. I encourage anyone who wants to learn more about the future of WPF to track them down. Finally, I’d never write any book without the support of my wife and these special individuals: Nora, Razia, Paul, and Hamid. Thanks everyone! —Matthew MacDonald

xxviii

Introduction When .NET first appeared, it introduced a small avalanche of new technologies. There was a whole new way to write web applications (ASP.NET), a whole new way to connect to databases (ADO.NET), new typesafe languages (C# and VB .NET), and a managed runtime (the CLR). Not least among these new technologies was Windows Forms, a library of classes for building Windows applications. Although Windows Forms is a full-featured toolkit, it’s hardwired to old, essential bits of Windows plumbing. Most significantly, Windows Forms relies on the Windows API to create the visual appearance of standard user interface elements such as buttons, text boxes, check boxes, and so on. As a result, these ingredients are essentially uncustomizable. For example, if you want to create a stylish glow button you need to create a custom control and paint every aspect of the button (in all its different states) using a lower-level drawing model. Even worse, ordinary windows are carved up into distinct regions, with each control getting its own piece of real estate. As a result, there’s no good way for the painting in one control (for example, the glow effect behind a button) to spread into the area owned by another control. And don’t even think about introducing animated effects such as spinning text, shimmering buttons, shrinking windows, or live previews because you’ll have to paint every detail by hand. The Windows Presentation Foundation (WPF) changed all this by introducing a model with entirely different plumbing. Although WPF includes the standard controls you’re familiar with, it draws every text, border, and background fill itself. As a result, WPF can provide much more powerful features that let you alter the way any piece of screen content is rendered. Using these features, you can restyle common controls such as buttons, often without writing any code. Similarly, you can use transformation objects to rotate, stretch, scale, and skew anything in your user interface, and you can even use WPF’s baked-in animation system to do it right before the user’s eyes. And because the WPF engine renders the content for a window as part of a single operation, it can handle unlimited layers of overlapping controls, even if these controls are irregularly shaped and partially transparent. Underlying WPF is a powerful infrastructure based on DirectX, the hardware-accelerated graphics API that’s commonly used in cutting-edge computer games. This means that you can use rich graphical effects without incurring the performance overhead that you’d suffer with Windows Forms. In fact, you even get advanced features such as support for video files and 3-D content. Using these features (and a good design tool), it’s possible to create eye-popping user interfaces and visual effects that would have been all but impossible with Windows Forms. It’s also important to note that you can use WPF to build an ordinary Windows application with standard controls and a straightforward visual appearance. In fact, it’s just as easy to use common controls in WPF as it is in the older Windows Forms model. Even better, WPF enhances features that appeal directly to business developers, including a vastly improved data binding model, a set of classes for printing content and managing print queues, and a document feature for displaying large amounts of formatted text. You’ll even get a model for building page-based applications that run seamlessly in Internet Explorer xxix

n INTRODUCTION

and can be launched from a website, all without the usual security warnings and irritating installation prompts. Overall, WPF combines the best of the old world of Windows development with new innovations for building modern, graphically rich user interfaces.

About This Book This book is an in-depth exploration of WPF for professional developers who know the .NET platform, the C# language, and the Visual Studio development environment. Experience with previous versions of WPF is not required, although new features are highlighted with a “What’s New” box at the beginning of each chapter for more seasoned WPF developers. This book provides a complete description of every major WPF feature, from XAML (the markup language used to define WPF user interfaces) to 3-D drawing and animation. Along the way, you’ll occasionally work with code that involves other features of the .NET Framework, such as the ADO.NET classes you use to query a database. These features aren’t discussed here. Instead, if you want more information about .NET features that aren’t specific to WPF, you can refer to one of the many dedicated . NET titles from Apress.

Chapter Overview This book includes 33 chapters. If you’re just starting out with WPF, you’ll find it’s easiest to read them in order, as later chapters often draw on the techniques demonstrated in earlier chapters. The following list gives you a quick preview of each chapter: Chapter 1: Introducing WPF describes the architecture of WPF, its DirectX plumbing, and the new device-independent measurement system that resizes user interfaces automatically. Chapter 2: XAML describes the XAML standard that you use to define user interfaces. You’ll learn why it was created and how it works, and you’ll create a basic WPF window using different coding approaches. Chapter 3: Layout delves into the layout panels that allow you to organize elements in a WPF window. You’ll consider different layout strategies, and you’ll build some common types of windows. Chapter 4: Dependency Properties describes how WPF uses dependency properties to provide support for key features such as data binding and animation. Chapter 5: Routed Events describes how WPF uses event routing to send events bubbling or tunneling through the elements in your user interface. It also describes the basic set of mouse, keyboard, and multitouch events that all WPF elements support. Chapter 6: Controls considers the controls every Windows developer is familiar with, such as buttons, text boxes, and labels—and their WPF twists. Chapter 7: The Application introduces the WPF application model. You’ll see how to create singleinstance and document-based WPF applications. Chapter 8: Element Binding introduces WPF data binding. You’ll see how to bind any type of object to your user interface. Chapter 9: Commands introduces the WPF command model, which allows you to wire multiple controls to the same logical action. Chapter 10: Resources describes how resources let you embed binary files in your assembly and reuse important objects throughout your user interface. xxx

n INTRODUCTION

Chapter 11: Styles and Behaviors explains the WPF style system, which lets you apply a set of common property values to an entire group of controls. Chapter 12: Shapes, Brushes, and Transforms introduces the 2-D drawing model in WPF. You’ll learn to create shapes, alter elements with transforms, and paint exotic effects with gradients, tiles, and images. Chapter 13: Geometries and Drawings delves deeper into 2-D drawing. You’ll learn to create complex paths that incorporate arcs and curves and how to use complex graphics efficiently. Chapter 14: Effects and Visuals describes lower-level graphics programming. You’ll apply Photoshopstyle effects with pixel shaders, build a bitmap by hand, and use WPF’s visual layer for optimized drawing. Chapter 15: Animation Basics explores WPF’s animation framework, which lets you integrate dynamic effects into your application using straightforward, declarative markup. Chapter 16: Advanced Animations explore more sophisticated animation techniques like keyframe animation, path-based animation, and frame-based animation. You’ll also consider a detailed example that shows how to create and manage dynamic animations with code. Chapter 17: Control Templates shows you how you can give any WPF control a dramatic new look (and new behavior) by plugging in a customized template. You’ll also see how templates allow you to build a skinnable application. Chapter 18: Custom Elements explores how you can extend the existing WPF controls and create your own. You’ll see several examples, including a template-based color picker, a flippable panel, a custom layout container, and a decorator that performs custom drawing. Chapter 19: Data Binding shows you how to fetch information from a database, insert it into a custom data objects, and bind these objects to WPF controls. You’ll also learn how to improve the performance of huge data-bound lists with virtualization, and catch editing mistakes with validation. Chapter 20: Formatting Bound Data shows some of the tricks for turning raw data into rich data displays that incorporate pictures, controls, and selection effects. Chapter 21: Data Views explores how you use the view in a data-bound window to navigate through a list of data items, and to apply filtering, sorting, and grouping. Chapter 22: Lists, Grids, and Trees gives you a tour of WPF’s rich data controls, including the ListView, TreeView, and DataGrid. Chapter 23: Windows examines how windows work in WPF. You’ll also learn how to create irregularly shaped windows and use Vista glass effects. You’ll also make the most of Windows 7 features by customizing taskbar jump lists, thumbnails, and icon overlays. Chapter 24: Pages and Navigation describes how you can build pages in WPF and keep track of navigation history. You’ll also see how to build a browser-hosted WPF application that can be launched from a website. Chapter 25: Menus, Toolbars, and Ribbons considers command-oriented controls such as menus and toolbars. You’ll also get a taste of more modern user interface with the freely downloadable Ribbon control. Chapter 26: Sound and Video describes WPF’s media support. You’ll see how to control playback for sound and video, and how to throw in synchronized animations and live effects. Chapter 27: 3-D Drawing explores the support for drawing 3-D shapes in WPF. You’ll learn how to create, transform, and animate 3-D objects. You’ll even see how to place interactive 2-D controls on xxxi

n INTRODUCTION

3-D surfaces. Chapter 28: Documents introduces WPF’s rich document support. You’ll learn to use flow documents to present large amounts of text in the most readable way possible, and you’ll use fixed documents to show print-ready pages. You’ll even use the RichTextBox to provide document editing. Chapter 29: Printing demonstrates WPF’s printing model, which lets you draw text and shapes in a print document. You’ll also learn how to manage page settings and print queues. Chapter 30: Interacting with Windows Forms examines how you can combine WPF and Windows Forms content in the same application—and even in the same window. Chapter 31: Multithreading describes how to create responsive WPF applications that perform timeconsuming work in the background. Chapter 32: The Add-In Model shows you how to create an extensible application that can dynamically discover and load separate components. Chapter 33: ClickOnce Deployment shows how you can deploy WPF applications using the ClickOnce setup model.

What You Need to Use This Book In order to run a WPF 4.5 application, your computer must have Windows 7, Windows 8, or Windows Vista with Service Pack 2. You also need the .NET Framework 4.5. In order to create a WPF 4.5 application (and open the sample projects included with this book), you need Visual Studio 2012, which includes the .NET Framework 4.5. There’s one other option. Instead of using any version of Visual Studio, you can use Expression Blend—a graphically oriented design tool—to build and test WPF applications. Overall, Expression Blend is intended for graphic designers who spend their time creating serious eye candy, while Visual Studio is ideal for code-heavy application programmers. This book assumes you’re using Visual Studio. If you do plan to use Expression Blend, make sure you are using a version that explicitly supports WPF (the version that’s bundled with some versions of Visual Studio is for Metro development only, and doesn’t support WPF). At the time of this writing, the version of Expression Blend that supports WPF is available as a preview called Blend + Sketchflow Preview for Visual Studio 2012, and it’s available at http://tinyurl.com/ cgar5lz.

Code Samples and URLs It’s a good idea to check the Apress website or http://www.prosetech.com to download the most recent upto-date code samples. You’ll need to do this to test most of the more sophisticated code examples described in this book because the less significant details are usually left out. This book focuses on the most important sections so that you don’t need to wade through needless extra pages to understand a concept. To download the source code, surf to http://www.prosetech.com and look for the page for this book. You’ll also find a list of links that are mentioned in this book, so you can find important tools and examples without needless typing.

xxxii

n INTRODUCTION

Feedback This book has the ambitious goal of being the best tutorial and reference for programming WPF. Toward that end, your comments and suggestions are extremely helpful. You can send complaints, adulation, and everything in between directly to [email protected]. I can’t solve your .NET problems or critique your code, but I will benefit from information about what this book did right and wrong (or what it may have done in an utterly confusing way).

xxxiii